# -*- coding: utf-8 -*- """The reinforcing index's Revit-free half: eligibility, features, staleness. No Revit, no model. Everything here is the part of the index that has to be right before a single piece of geometry is read -- who may be a donor, what a feature record says, what gets rebuilt when something moves, and the distinction between a piece that has no openings and a piece nobody could measure. The numbers in the feature tests are the measured WP157/WP168 rectangles from the worked example: the two panels are different lengths and their two big openings sit at the identical ABSOLUTE feet-from-start, which is exactly the match a proportional record would have thrown away. """ # pylint: disable=import-error,invalid-name,broad-except import json import os from vibe import reinforcecore as core # --------------------------------------------------------------- eligibility def test_reinforced_and_not_on_hold_is_the_whole_rule(): assert core.is_eligible(1, 0) is True assert core.is_eligible(1, None) is True def test_on_hold_disqualifies_even_a_reinforced_piece(): """The measured case: the worked example's second donor is on hold.""" assert core.is_eligible(1, 1) is False def test_unreinforced_is_never_a_donor(): assert core.is_eligible(0, 0) is False assert core.is_eligible(None, None) is False assert core.is_eligible(None, 0) is False # ------------------------------------------------------------------ features def test_feature_records_absolute_position_not_a_fraction(): """Same rectangle, two panels of different lengths -> identical fields.""" a = core.feature('WP157', 0, 6.5, 10.583, 0.354, 4.479, True, 0.0, 27.625, 11.5, 8.0) b = core.feature('WP168', 0, 6.5, 10.583, 0.354, 4.479, True, 0.0, 24.917, 11.5, 8.0) for key in ('x0', 'x1', 'y0', 'y1', 'w', 'h', 'cx', 'cy'): assert a[key] == b[key], key assert a['piece_length_ft'] != b['piece_length_ft'] def test_feature_is_flat_and_two_dimensional(): f = core.feature('WP157', 0, 24.25, 27.625, 6.25, 11.5, True, 0.0, 27.625, 11.5, 8.0) assert f['w'] == 3.375 and f['h'] == 5.25 assert f['cx'] == 20.938 or round(f['cx'], 3) == round((24.25 + 27.625) / 2, 3) assert f['cy'] == 8.875 for value in f.values(): assert not isinstance(value, (dict, list)), 'features must stay flat' def test_feature_carries_no_category(): """A blockout and a notch produce the same shape of record, on purpose.""" blockout = core.feature('WP157', 0, 24.25, 27.625, 6.25, 11.5, True, 0.0, 27.625, 11.5, 8.0) notch = core.feature('WP157', 1, 0.0, 0.875, 1.125, 3.0, True, 0.0, 27.625, 11.5, 8.0) assert blockout['kind'] == notch['kind'] == core.KIND_OPENING def test_unknown_penetration_is_none_not_false(): f = core.feature('WP157', 0, 0.0, 0.667, 11.292, 11.5, None, None, 27.625, 11.5, 8.0) assert f['through'] is None assert f['concrete_in'] is None def test_blind_recess_keeps_its_concrete_depth(): f = core.feature('WP157', 0, 0.0, 0.667, 11.292, 11.5, False, 3.5, 27.625, 11.5, 8.0) assert f['through'] is False assert f['concrete_in'] == 3.5 def test_features_are_reindexed_in_a_stable_order(): late = core.feature('WP157', 0, 24.25, 27.625, 6.25, 11.5, True, 0.0, 27.625, 11.5, 8.0) early = core.feature('WP157', 1, 0.0, 0.875, 1.125, 3.0, True, 0.0, 27.625, 11.5, 8.0) rec = core.piece_record('WP157', 1, 'sig', 1, 0, features=[late, early]) assert [f['x0'] for f in rec['features']] == [0.0, 24.25] assert [f['index'] for f in rec['features']] == [0, 1] # ------------------------------------------------- could-not-judge vs nothing def test_no_openings_and_could_not_measure_are_different_answers(): measured = core.piece_record('WP393', 1, 'sig', 1, 0, features=[]) unread = core.piece_record('WP999', 2, 'sig', 1, 0, error='no concrete solid') assert measured['features'] == [] assert measured['error'] is None assert unread['features'] is None assert unread['error'] def test_unjudged_pieces_are_held_back_from_the_donor_list_and_reported(): cache = core.new_cache('m') cache['pieces'] = { 'A': core.piece_record('A', 1, 's', 1, 0, features=[]), 'B': core.piece_record('B', 2, 's', 1, 0, error='no panel frame'), } names = [r['name'] for r in core.donors(cache)] assert names == ['A'] assert [r['name'] for r in core.donors(cache, include_unjudged=True)] \ == ['A', 'B'] plan = {'build': [], 'fresh': ['A', 'B']} assert core.coverage(cache, plan)['unjudged'] == ['B'] # ----------------------------------------------------------------- staleness def test_signature_moves_when_a_member_is_added_or_removed(): base = core.signature(1, 0, [10, 11, 12]) assert core.signature(1, 0, [10, 11, 12, 13]) != base assert core.signature(1, 0, [10, 11]) != base def test_signature_ignores_member_order(): assert core.signature(1, 0, [12, 10, 11]) == core.signature(1, 0, [10, 11, 12]) def test_signature_moves_when_either_flag_moves(): base = core.signature(1, 0, [10, 11]) assert core.signature(0, 0, [10, 11]) != base assert core.signature(1, 1, [10, 11]) != base def test_signature_cannot_see_a_geometry_edit_in_place(): """The honest limit, asserted so nobody assumes otherwise. Moving or resizing an opening changes no member id and no flag, so the signature is unchanged and the cached record stays. `rebuild_all` is the only answer to this, and it is cheap. """ before = core.signature(1, 0, [10, 11, 12]) after = core.signature(1, 0, [10, 11, 12]) assert before == after # ---------------------------------------------------------------- the plan def _live(**kw): return kw def test_plan_builds_new_and_stale_reuses_fresh(): cache = core.new_cache('m') cache['pieces'] = { 'A': core.piece_record('A', 1, 'sigA', 1, 0, features=[]), 'B': core.piece_record('B', 2, 'sigB', 1, 0, features=[]), } live = _live(A=('sigA', 1, 0), # unchanged B=('sigB2', 1, 0), # moved C=('sigC', 1, 0)) # new plan = core.plan(cache, live) assert plan['fresh'] == ['A'] assert plan['build'] == ['B', 'C'] assert plan['drop'] == [] def test_plan_drops_a_piece_that_has_gone_on_hold(): cache = core.new_cache('m') cache['pieces'] = {'A': core.piece_record('A', 1, 'sigA', 1, 0, features=[])} plan = core.plan(cache, _live(A=('sigA2', 1, 1))) assert plan['build'] == [] and plan['fresh'] == [] assert plan['drop'] == ['A'] def test_plan_picks_up_a_piece_taken_off_hold(): cache = core.new_cache('m') plan = core.plan(cache, _live(A=('sigA', 1, 0))) assert plan['build'] == ['A'] def test_plan_drops_a_piece_that_left_the_model(): cache = core.new_cache('m') cache['pieces'] = {'A': core.piece_record('A', 1, 'sigA', 1, 0, features=[])} assert core.plan(cache, {})['drop'] == ['A'] def test_a_schema_bump_rebuilds_everything(): cache = core.new_cache('m') cache['schema'] = core.SCHEMA - 1 cache['pieces'] = {'A': core.piece_record('A', 1, 'sigA', 1, 0, features=[])} plan = core.plan(cache, _live(A=('sigA', 1, 0))) assert plan['build'] == ['A'] # --------------------------------------------------- partial builds are ok def test_a_partial_build_is_kept_and_says_what_is_missing(): cache = core.new_cache('m') live = _live(A=('sA', 1, 0), B=('sB', 1, 0), C=('sC', 1, 0)) plan = core.plan(cache, live) # only A got built before the budget ran out cache = core.apply_plan(cache, plan, [core.piece_record('A', 1, 'sA', 1, 0, features=[])], built_at='now') cov = core.coverage(cache, plan) assert cov['eligible'] == 3 assert cov['indexed'] == 1 assert cov['missing'] == ['B', 'C'] assert cov['complete'] is False def test_a_finished_build_reports_complete(): cache = core.new_cache('m') live = _live(A=('sA', 1, 0)) plan = core.plan(cache, live) cache = core.apply_plan(cache, plan, [core.piece_record('A', 1, 'sA', 1, 0, features=[])]) assert core.coverage(cache, plan)['complete'] is True # ------------------------------------------------------------------- io def test_round_trip_through_disk(tmp_path): path = str(tmp_path / 'reinforce' / 'index.json') cache = core.new_cache('model') f = core.feature('A', 0, 6.5, 10.583, 0.354, 4.479, True, 0.0, 27.625, 11.5, 8.0) cache['pieces'] = {'A': core.piece_record('A', 1, 'sA', 1, 0, features=[f])} core.save(path, cache) assert os.path.isfile(path) back = core.load(path) assert back['pieces']['A']['features'][0]['x0'] == 6.5 assert back['doc_key'] == 'model' def test_a_damaged_cache_costs_a_rebuild_and_says_so(tmp_path): path = str(tmp_path / 'index.json') with open(path, 'wb') as handle: handle.write(b'{not json') cache = core.load(path) assert cache['pieces'] == {} assert cache['_load_error'] def test_an_old_schema_is_not_read_as_data(tmp_path): path = str(tmp_path / 'index.json') with open(path, 'wb') as handle: handle.write(json.dumps({'schema': 0, 'pieces': {'A': {}}}).encode('utf-8')) cache = core.load(path) assert cache['pieces'] == {} assert cache['_load_error'] def test_a_missing_cache_is_an_empty_one_not_an_error(): cache = core.load(None) assert cache['schema'] == core.SCHEMA assert cache['pieces'] == {} assert '_load_error' not in cache def test_all_features_is_flat_across_donors(): cache = core.new_cache('m') fa = core.feature('A', 0, 1.0, 2.0, 1.0, 2.0, True, 0.0, 20.0, 10.0, 8.0) fb = core.feature('B', 0, 3.0, 4.0, 1.0, 2.0, True, 0.0, 20.0, 10.0, 8.0) cache['pieces'] = { 'A': core.piece_record('A', 1, 's', 1, 0, features=[fa]), 'B': core.piece_record('B', 2, 's', 1, 0, features=[fb]), 'C': core.piece_record('C', 3, 's', 1, 0, error='unreadable'), } feats = core.all_features(cache) assert len(feats) == 2 assert sorted(f['piece'] for f in feats) == ['A', 'B'] def test_disk_form_is_packed_but_the_caller_sees_the_full_feature(tmp_path): """Derived fields are recomputed on load, not stored 690 times over.""" path = str(tmp_path / 'index.json') cache = core.new_cache('m') f = core.feature('A', 0, 6.5, 10.583, 0.354, 4.479, True, 0.0, 27.625, 11.5, 8.0) cache['pieces'] = {'A': core.piece_record( 'A', 1, 'sA', 1, 0, length_ft=27.625, width_ft=11.5, thickness_in=8.0, features=[f])} core.save(path, cache) with open(path, 'rb') as handle: raw = handle.read().decode('utf-8') assert 'piece_length_ft' not in raw # stored once per piece, not per feature assert '"cx"' not in raw # derived, not stored back = core.load(path) assert back['pieces']['A']['features'][0] == f def test_a_measured_empty_piece_survives_the_round_trip(tmp_path): path = str(tmp_path / 'index.json') cache = core.new_cache('m') cache['pieces'] = { 'A': core.piece_record('A', 1, 'sA', 1, 0, features=[]), 'B': core.piece_record('B', 2, 'sB', 1, 0, error='no concrete solid'), } core.save(path, cache) back = core.load(path) assert back['pieces']['A']['features'] == [] assert back['pieces']['B']['features'] is None # ---------------------------------------------------------- MASTER, the one # provenance signal the donor pool has. `TICKET_IS_REINFORCED` says somebody # drew bars; MASTER says somebody reviewed them. It is typed BY HAND into the # assembly's identity comment, so the rule that reads it has to be tolerant # about case and about surrounding text -- and has to be predictable enough # that a reader can say in advance what it will and will not accept. def test_master_accepts_the_bare_token_in_any_case(): for text in ('MASTER', 'master', 'Master', 'mAsTeR'): assert core.is_master_text(text) is True def test_master_accepts_the_token_with_text_around_it(): # Every one of these is a shape that really occurs in the field, with the # job-specific words replaced. assert core.is_master_text('MASTER;reverse strands') is True assert core.is_master_text('MASTER, bend bars to fit inside stirrups') assert core.is_master_text('reverse strands; master') is True assert core.is_master_text(' MASTER ') is True assert core.is_master_text('MASTER-PANEL') is True def test_master_rejects_an_empty_or_absent_field(): assert core.is_master_text(None) is False assert core.is_master_text('') is False assert core.is_master_text('deflect strand 1"') is False def test_master_rejects_the_token_inside_another_word(): """A whole-word rule, stated so it can be argued with rather than guessed. `MASTERED` and `REMASTER` are not this plant's marker and are not treated as one. The cost is that a plural or a typo is missed -- which is visible, because the raw string is kept on the record as `master_note`. """ assert core.is_master_text('MASTERED') is False assert core.is_master_text('REMASTER') is False assert core.is_master_text('MASTERS') is False def test_master_token_is_plant_knowledge_not_a_literal(): assert core.is_master_text('GEPRUEFT', 'GEPRUEFT') is True assert core.is_master_text('MASTER', 'GEPRUEFT') is False # An empty token switches the signal off rather than matching everything. assert core.is_master_text('MASTER', '') is False def test_a_piece_record_carries_the_boolean_and_the_raw_string(): rec = core.piece_record('PNL101', 1, 's', 1, 0, features=[], master=True, master_note='MASTER;reverse strands') assert rec['master'] is True assert rec['master_note'] == 'MASTER;reverse strands' def test_a_piece_record_defaults_to_not_a_master(): rec = core.piece_record('PNL102', 1, 's', 1, 0, features=[]) assert rec['master'] is False assert rec['master_note'] is None def test_the_signature_moves_when_a_piece_is_typed_up_as_master(): """Without this the signal would never take effect. Typing MASTER into the identity comment changes nothing else about the piece -- not its members, not its flags -- so a stamp that ignored it would leave the cached record standing and the new marking would do nothing at all until something unrelated moved. """ before = core.signature(1, 0, [10, 11], master=False) after = core.signature(1, 0, [10, 11], master=True) assert before != after def test_the_signature_moves_when_elevation_group_is_recorded(): """TIER 1 (`elevation_group`) is cheap -- a single parameter read, the same class of cost `master` already pays -- so it IS folded into the stamp, unlike tier 2 (`elevation_band`, see below). Without this, MC mapping `Tagged_Sheet` for the first time, or correcting a value on one panel, would do nothing until something unrelated about the piece moved. """ before = core.signature(1, 0, [10, 11], elevation_group=None) after = core.signature(1, 0, [10, 11], elevation_group='E3.16') assert before != after def test_the_signature_treats_case_and_whitespace_as_the_same_group(): """`elevation_group_key`'s normalisation, at the seam that matters: two spellings of the same sheet must not force a rebuild of a piece that has not actually changed.""" assert (core.signature(1, 0, [10, 11], elevation_group='E3.16') == core.signature(1, 0, [10, 11], elevation_group=' e3.16 ')) def test_signature_is_five_pipe_joined_fields_of_flags_and_id_stamp(): """The general shape, pinned so a future change to it is deliberate: reinforced | on_hold | master | elevation_group | id-count | checksum -- six segments now that tier 1 rejoined it.""" reinforced, on_hold, ids = 1, 0, [10, 11] six_field = '{0}|{1}|{2}|{3}|{4}|{5}'.format( 1 if reinforced == 1 else 0, 1 if on_hold == 1 else 0, 0, '', len(ids), sum(ids) % 1000000007) assert core.signature(reinforced, on_hold, ids) == six_field # --------------------------------------------------------- elevation_band # # Owner's ruling, 2026-09-02: elevation is a purely geometric fact -- same # axis AND overlapping coordinate band, never a parameter. See # `reinforcecore.elevation_band`'s module note for the full reasoning; these # tests pin the definition itself, off plain numbers, no Revit involved. def _basis(axis): """A clean unit basis vector along one global axis, for fixtures.""" return {'X': (1.0, 0.0, 0.0), 'Y': (0.0, 1.0, 0.0), 'Z': (0.0, 0.0, 1.0)}[axis] def test_elevation_band_picks_the_smallest_dimension_as_thickness(): """20 ft long, 10 ft wide, 0.667 ft (8 in) thick -- clean and orthogonal. Local axis 2 (span 0.667) is thinnest; its basis (the THIRD argument) points world +X, so the band lands on the X axis and its range is read off the box at that axis. """ band = core.elevation_band( [20.0, 10.0, 0.667], _basis('Y'), _basis('Z'), _basis('X'), [100.0, 0.0, 0.0], [100.667, 20.0, 10.0]) assert band == {'axis': 'X', 'min': 100.0, 'max': 100.667} def test_elevation_band_normalises_a_reversed_lo_hi_pair(): band = core.elevation_band( [20.0, 10.0, 0.667], _basis('Y'), _basis('Z'), _basis('X'), [100.667, 20.0, 10.0], [100.0, 0.0, 0.0]) assert band == {'axis': 'X', 'min': 100.0, 'max': 100.667} def test_elevation_band_is_none_when_near_square(): """Smallest and second-smallest spans are 8.0 and 8.5 -- ratio 1.0625, under the 1.25 near-square ratio -- so which one is genuinely "the thickness" cannot be told, and the band declines rather than guesses. """ band = core.elevation_band( [20.0, 8.5, 8.0], _basis('X'), _basis('Y'), _basis('Z'), [0.0, 0.0, 0.0], [20.0, 8.5, 8.0], near_square_ratio=1.25) assert band is None def test_elevation_band_is_none_when_not_cleanly_axis_aligned(): """The thinnest axis's world direction is a 45-degree blend of X and Y -- a skewed wall -- so no global axis is a clean match and the band declines rather than forcing the nearest one.""" diag = (0.70710678, 0.70710678, 0.0) band = core.elevation_band( [20.0, 10.0, 0.667], _basis('X'), _basis('Y'), diag, [0.0, 0.0, 0.0], [10.0, 10.0, 10.0]) assert band is None def test_elevation_band_declines_gracefully_on_bad_input(): """Never raises: bad input is could-not-judge, the same as ambiguous or off-axis geometry is.""" assert core.elevation_band(None, (1, 0, 0), (0, 1, 0), (0, 0, 1), [0, 0, 0], [1, 1, 1]) is None assert core.elevation_band([1.0, 2.0], (1, 0, 0), (0, 1, 0), (0, 0, 1), [0, 0, 0], [1, 1, 1]) is None assert core.elevation_band([1.0, 2.0, 0.5], (1, 0, 0), (0, 1, 0), (0, 0, 1), None, [1, 1, 1]) is None # ----------------------------------------------------------- same_elevation def test_same_elevation_true_for_overlapping_bands_on_the_same_axis(): a = {'axis': 'X', 'min': 100.0, 'max': 100.667} b = {'axis': 'X', 'min': 100.1, 'max': 100.767} assert core.same_elevation(a, b) is True def test_same_elevation_false_for_non_overlapping_bands_on_the_same_axis(): a = {'axis': 'X', 'min': 100.0, 'max': 100.667} b = {'axis': 'X', 'min': 120.0, 'max': 120.667} assert core.same_elevation(a, b) is False def test_same_elevation_false_for_a_different_axis_even_when_close(): """The owner's explicit ruling: perpendicular walls never match, however numerically close the coordinates happen to be.""" a = {'axis': 'X', 'min': 100.0, 'max': 100.667} b = {'axis': 'Y', 'min': 100.0, 'max': 100.667} assert core.same_elevation(a, b) is False def test_same_elevation_false_when_bands_only_abut(): """Pinned: touching at exactly one point (no interior overlap) is NOT the same elevation -- the signature of a stepped facade, not a shared wall plane. See `same_elevation`'s own docstring for the reasoning.""" a = {'axis': 'X', 'min': 100.0, 'max': 100.667} b = {'axis': 'X', 'min': 100.667, 'max': 101.334} assert core.same_elevation(a, b) is False def test_same_elevation_false_when_either_band_is_unknown(): """UNKNOWN IS NEVER A MATCH -- two could-not-judge bands never agree with each other, and a known band is never matched against nothing.""" a = {'axis': 'X', 'min': 100.0, 'max': 100.667} assert core.same_elevation(a, None) is False assert core.same_elevation(None, a) is False assert core.same_elevation(None, None) is False def test_same_elevation_never_raises_on_a_malformed_band(): assert core.same_elevation({'axis': 'X'}, {'axis': 'X', 'min': 1, 'max': 2}) is False assert core.same_elevation('not a dict', {'axis': 'X', 'min': 1, 'max': 2}) is False # ----------------------------------------------------- elevation_group_key # # TIER 1. Owner's ruling, 2026-09-02 (the SAME day as the geometric ruling): # MC's model already carries a grouping parameter (`Tagged_Sheet`, mapped # through the role `panel.elevation_group`) -- two panels with the same # non-empty value are the same elevation with no geometry involved. "对MC而 # 言,所有的墙应该都有一个参数叫Tagged_Sheet。如果俩panel这个参数一样,那么 # 他们就是同一个elevation,不用跑哪个几何引擎。" def test_elevation_group_key_normalises_case_and_whitespace(): assert (core.elevation_group_key('E3.16') == core.elevation_group_key(' e3.16 ')) def test_elevation_group_key_is_none_for_none_and_for_blank(): """Unmapped role and never-typed are the same could-not-judge answer.""" assert core.elevation_group_key(None) is None assert core.elevation_group_key('') is None assert core.elevation_group_key(' ') is None def test_elevation_group_key_never_raises_on_a_non_string(): assert core.elevation_group_key(42) is None assert core.elevation_group_key([1, 2]) is None def test_two_different_groups_are_different_keys(): assert core.elevation_group_key('E3.16') != core.elevation_group_key( 'E3.22') # --------------------------------------------------------- elevation_match # # The two-tier comparator itself: tier 1 (`elevation_group`) tried first, # tier 2 (`elevation_band`) as the fallback. See the module note above # `elevation_band` for the full ordering rule. _BAND_X = {'axis': 'X', 'min': 100.0, 'max': 100.667} _BAND_X_OVERLAP = {'axis': 'X', 'min': 100.1, 'max': 100.767} _BAND_Y = {'axis': 'Y', 'min': 100.0, 'max': 100.667} def test_elevation_match_by_group_needs_no_geometry_at_all(): """Same non-empty group, WILDLY different (even absent) bands -- the group still decides it, exactly the MC shortcut.""" target = {'elevation_group': 'E3.16', 'elevation_band': _BAND_X} donor = {'elevation_group': ' e3.16 ', 'elevation_band': None} assert core.elevation_match(target, donor) == core.ELEVATION_BY_GROUP def test_elevation_match_falls_back_to_band_when_group_does_not_answer(): target = {'elevation_group': None, 'elevation_band': _BAND_X} donor = {'elevation_group': None, 'elevation_band': _BAND_X_OVERLAP} assert core.elevation_match(target, donor) == core.ELEVATION_BY_BAND def test_elevation_match_falls_back_to_band_when_groups_differ(): """A group mismatch does not veto the geometric answer -- it simply fails to answer tier 1, and tier 2 gets to try.""" target = {'elevation_group': 'E3.16', 'elevation_band': _BAND_X} donor = {'elevation_group': 'E3.22', 'elevation_band': _BAND_X_OVERLAP} assert core.elevation_match(target, donor) == core.ELEVATION_BY_BAND def test_elevation_match_is_none_when_neither_tier_answers(): target = {'elevation_group': None, 'elevation_band': _BAND_X} donor = {'elevation_group': None, 'elevation_band': _BAND_Y} assert core.elevation_match(target, donor) is None def test_elevation_match_never_raises_on_missing_keys(): """A pre-migration record with neither field at all -- could-not-judge, not an exception.""" assert core.elevation_match({}, {}) is None assert core.elevation_match(None, None) is None assert core.elevation_match({'elevation_group': 'E3.16'}, {}) is None # ------------------------------------------------------- piece_record: band def test_a_piece_record_carries_its_elevation_band(): band = {'axis': 'X', 'min': 100.0, 'max': 100.667} rec = core.piece_record('PNL425', 1, 's', 1, 0, features=[], elevation_band=band) assert rec['elevation_band'] == band def test_a_piece_record_defaults_to_no_elevation_band(): """None, not a missing key -- a record built with no argument still reads the same could-not-judge value one built for an ambiguous or off-axis panel would.""" rec = core.piece_record('PNL426', 1, 's', 1, 0, features=[]) assert rec['elevation_band'] is None def test_elevation_band_survives_a_save_and_load_round_trip(tmp_path): path = str(tmp_path / 'index.json') band = {'axis': 'X', 'min': 100.0, 'max': 100.667} cache = core.new_cache('doc') cache['pieces'] = { 'A': core.piece_record('A', 1, 's', 1, 0, features=[], elevation_band=band), } core.save(path, cache) back = core.load(path) assert back['pieces']['A']['elevation_band'] == band # ------------------------------------------------------ piece_record: group def test_a_piece_record_carries_its_elevation_group(): rec = core.piece_record('PNL425', 1, 's', 1, 0, features=[], elevation_group='E3.16') assert rec['elevation_group'] == 'E3.16' def test_a_piece_record_defaults_to_no_elevation_group(): rec = core.piece_record('PNL426', 1, 's', 1, 0, features=[]) assert rec['elevation_group'] is None def test_elevation_group_survives_a_save_and_load_round_trip(tmp_path): path = str(tmp_path / 'index.json') cache = core.new_cache('doc') cache['pieces'] = { 'A': core.piece_record('A', 1, 's', 1, 0, features=[], elevation_group='E3.16'), } core.save(path, cache) back = core.load(path) assert back['pieces']['A']['elevation_group'] == 'E3.16' def test_masters_lists_only_the_reviewed_donors(): cache = core.new_cache('doc') cache['pieces'] = { 'A': core.piece_record('A', 1, 's', 1, 0, features=[], master=True), 'B': core.piece_record('B', 2, 's', 1, 0, features=[]), 'C': core.piece_record('C', 3, 's', 1, 0, error='unreadable', master=True), } assert core.masters(cache) == ['A'] assert core.masters(cache, include_unjudged=True) == ['A', 'C'] def test_master_survives_a_save_and_load_round_trip(tmp_path): path = str(tmp_path / 'index.json') cache = core.new_cache('doc') cache['pieces'] = { 'A': core.piece_record('A', 1, 's', 1, 0, features=[], master=True, master_note='MASTER'), } core.save(path, cache) back = core.load(path) assert back['pieces']['A']['master'] is True assert back['pieces']['A']['master_note'] == 'MASTER' def test_an_index_built_before_master_existed_is_not_read_as_no_masters( tmp_path): """The whole answer to "what happens to yesterday's cache". A file written under the previous schema is thrown away WHOLE. It is never partially adopted, so not one piece from it can be served as a record that was checked for MASTER and found not to be one. It costs a rebuild, which is six seconds, and the reason is reported. """ path = str(tmp_path / 'index.json') old = {'schema': core.SCHEMA - 1, 'doc_key': 'doc', 'built_at': 'y', 'pieces': {'A': {'name': 'A', 'sig': 's', 'features': []}}} handle = open(path, 'wb') handle.write(json.dumps(old).encode('utf-8')) handle.close() cache = core.load(path) assert cache['pieces'] == {} assert cache['_load_error'] # and nothing from it can be mistaken for a judged non-master assert core.masters(cache) == [] def test_master_unknown_is_not_master_false(): """The role unmapped is a third answer and the record keeps it. `master=None` means the plant has not said which parameter carries the marker, so nothing was read. It must not collapse into False, which is the verdict of a field that WAS read. """ rec = core.piece_record('PNL103', 1, 's', 1, 0, features=[], master=None) assert rec['master'] is None cache = core.new_cache('doc') cache['pieces'] = {'PNL103': rec} assert core.masters(cache) == [] assert core.master_signal_read(cache) is False def test_the_signal_reads_as_run_as_soon_as_one_piece_was_judged(): cache = core.new_cache('doc') cache['pieces'] = { 'A': core.piece_record('A', 1, 's', 1, 0, features=[], master=False), } assert core.master_signal_read(cache) is True assert core.masters(cache) == [] # ------------------------------------------------------ the design number # # `design_number` decides who may be a template at all (owner ruling, # 2026-09-01: a donor must be the same design as its target). Before that rule # may be trusted, this has to answer whether the index records the field -- # and there are THREE answers, not two. def test_a_recorded_design_number_reads_as_recorded(): assert core.design_status([ core.piece_record('A', 1, 's', 1, 0, design_number='PNL1.0'), core.piece_record('B', 2, 's', 1, 0), ]) == core.DESIGN_RECORDED def test_records_that_all_carry_an_empty_design_number_read_as_unread(): """Measured on the real index, 2026-09-01: 773 pieces, every one of them `design_number: null`, because the build ran with no knowledge layer. The field was there and nobody had read it -- which is 'nobody looked', not 'every piece matches'.""" assert core.design_status([ core.piece_record('A', 1, 's', 1, 0), core.piece_record('B', 2, 's', 1, 0), ]) == core.DESIGN_UNREAD def test_records_written_before_the_field_read_as_not_recorded(): old = core.piece_record('A', 1, 's', 1, 0) del old['design_number'] assert core.design_status([old]) == core.DESIGN_NOT_RECORDED def test_nothing_at_all_is_not_recorded_never_recorded(): """An empty sequence is 'nothing was looked at', which is the could-not-judge answer and not a clean bill.""" assert core.design_status([]) == core.DESIGN_NOT_RECORDED assert core.design_status(None) == core.DESIGN_NOT_RECORDED def test_an_index_that_never_stamped_a_pattern_says_none_not_a_pattern(): """None means 'this file does not say', and the caller then falls back the way an unmapped nomenclature always has. It is not 'no pattern'.""" assert core.design_re_in(core.new_cache('doc')) is None assert core.design_re_in({}) is None assert core.design_re_in({'nomenclature': 'nonsense'}) is None assert core.design_re_in({'nomenclature': {'design_number_re': ''}}) is None def test_the_stamped_pattern_survives_a_save_and_load_round_trip(tmp_path): """The offline reader has no knowledge layer, so the pattern the button reduced designs with has to be IN THE FILE or the two disagree.""" path = str(tmp_path / 'index.json') cache = core.new_cache('doc') cache['nomenclature'] = {'design_number_re': r'^([A-Za-z]+)'} core.save(path, cache) assert core.design_re_in(core.load(path)) == r'^([A-Za-z]+)' def test_the_three_design_answers_are_three_different_strings(): assert len({core.DESIGN_RECORDED, core.DESIGN_UNREAD, core.DESIGN_NOT_RECORDED}) == 3 # ------------------------------------------------- targets, and who may donate # # ELIGIBILITY FILTERS DONORS. IT MUST NOT DECIDE WHO CAN BE A TARGET. # # Up to schema 4 it decided both, and the consequence was invisible until # somebody asked to run with Revit closed: the piece an engineer wants # suggestions FOR is the one not yet reinforced, so it was the one piece the # index never held. `plan(include=...)` measures it too. Nothing about the # donor rule moves, and these tests are mostly about that: the pool must be # exactly the same size afterwards. def test_a_piece_that_may_not_donate_is_still_measured_as_a_target(): cache = core.new_cache('m') live = _live(A=('sA', 1, 0), # a donor T=('sT', 0, 0)) # not reinforced -- an ordinary target plan = core.plan(cache, live, include=lambda name: name == 'T') assert plan['build'] == ['A', 'T'] assert plan['donors'] == ['A'] assert plan['targets'] == ['T'] def test_without_a_predicate_the_index_is_donors_only_as_before(): cache = core.new_cache('m') plan = core.plan(cache, _live(A=('sA', 1, 0), T=('sT', 0, 0))) assert plan['build'] == ['A'] assert plan['targets'] == [] def test_widening_what_is_measured_never_widens_the_donor_pool(): """The whole safety of the change, as arithmetic.""" cache = core.new_cache('m') cache['pieces'] = { 'A': core.piece_record('A', 1, 's', 1, 0, features=[]), 'T': core.piece_record('T', 2, 's', 0, 0, features=[]), 'H': core.piece_record('H', 3, 's', 1, 1, features=[]), } assert [r['name'] for r in core.donors(cache)] == ['A'] assert [r['name'] for r in core.targets(cache)] == ['H', 'T'] def test_a_target_typed_master_is_not_counted_as_a_reviewed_donor(): """A MASTER that may not donate is not a template this answer could use.""" cache = core.new_cache('m') cache['pieces'] = { 'A': core.piece_record('A', 1, 's', 1, 0, features=[], master=True), 'T': core.piece_record('T', 2, 's', 0, 0, features=[], master=True), } assert core.masters(cache) == ['A'] def test_coverage_counts_donors_and_targets_apart(): cache = core.new_cache('m') live = _live(A=('sA', 1, 0), B=('sB', 1, 0), T=('sT', 0, 0)) plan = core.plan(cache, live, include=lambda name: True) cache = core.apply_plan(cache, plan, [ core.piece_record('A', 1, 'sA', 1, 0, features=[]), core.piece_record('T', 3, 'sT', 0, 0, features=[]), ]) cov = core.coverage(cache, plan) assert cov['eligible'] == 2 # A and B -- never T assert cov['indexed'] == 1 assert cov['missing'] == ['B'] assert cov['targets'] == 1 assert cov['complete'] is False def test_a_plan_without_the_donor_split_still_reads_as_it_did(): """Back-compat: a hand-built plan from before the split.""" cache = core.new_cache('m') cache['pieces'] = {'A': core.piece_record('A', 1, 's', 1, 0, features=[])} cov = core.coverage(cache, {'build': [], 'fresh': ['A']}) assert cov['eligible'] == 1 and cov['complete'] is True def test_a_predicate_that_raises_measures_the_piece_rather_than_dropping_it(): """Could-not-tell may only ever resolve towards measuring, never towards a silent exclusion -- `vibe.scope`'s standing rule, applied here.""" def boom(_name): raise ValueError('no idea') plan = core.plan(core.new_cache('m'), _live(T=('sT', 0, 0)), include=boom) assert plan['build'] == ['T'] # ------------------------------------------- what must never reach the file def test_a_load_error_is_never_written_into_the_index(): """Measured on the real index, 2026-08-30: a healthy schema-4 file carried `_load_error: schema 3, expected 4` for good, because `load()` stamps the key on the empty cache it returns, `refresh` fills that same dict and saves it. Every later press then reported a load failure that had not happened and forced a rebuild it did not need.""" import os import tempfile folder = tempfile.mkdtemp() path = os.path.join(folder, 'index.json') cache = core.new_cache('m') cache['_load_error'] = 'schema 4, expected 5' cache['_anything_else'] = 'also transient' cache['pieces'] = {'A': core.piece_record('A', 1, 's', 1, 0, features=[])} core.save(path, cache) raw = open(path, 'rb').read().decode('utf-8') assert '_load_error' not in raw assert '_anything_else' not in raw assert core.load(path).get('_load_error') is None def test_the_declared_scope_survives_a_round_trip(): """The offline reader has no knowledge layer, so the scope has to travel in the file itself.""" import os import tempfile path = os.path.join(tempfile.mkdtemp(), 'index.json') cache = core.new_cache('m') cache['scope'] = {'prefixes': ['PNL'], 'note': 'cladding only', 'declared_by': 'somebody'} core.save(path, cache) assert core.load(path)['scope']['prefixes'] == ['PNL'] def test_a_stray_load_error_in_a_healthy_file_is_not_a_load_error(): """The leg that turned a cosmetic defect into a full re-scan per press. Files written before `_pack` learned to strip transient keys carry the message from the schema bump that CAUSED the rebuild, copied into the rebuilt file. A reader that believes it rebuilds the index on every press for ever. This file parsed and its schema matches; what some earlier load thought is not a fact about it. """ import json import os import tempfile path = os.path.join(tempfile.mkdtemp(), 'index.json') healthy = {'schema': core.SCHEMA, 'doc_key': 'm', 'built_at': 'x', 'scope': None, 'pieces': {}, '_load_error': 'schema 4, expected 5'} handle = open(path, 'wb') try: handle.write(json.dumps(healthy).encode('utf-8')) finally: handle.close() got = core.load(path) assert got.get('_load_error') is None assert got['built_at'] == 'x' # and the file was otherwise adopted def test_a_real_schema_mismatch_still_discards_the_whole_file(tmp_path): """The third leg: a genuine mismatch must still be reported once, and nothing may be adopted from it.""" import json path = str(tmp_path / 'index.json') old = {'schema': core.SCHEMA - 1, 'doc_key': 'm', 'built_at': 'x', 'pieces': {'A': {'name': 'A'}}} handle = open(path, 'wb') try: handle.write(json.dumps(old).encode('utf-8')) finally: handle.close() got = core.load(path) assert got['_load_error'] assert got['pieces'] == {} assert got['built_at'] is None def test_the_plan_a_two_argument_caller_gets_matches_the_one_refresh_builds(): """The button computes its own plan with two arguments, to size a progress bar and to decide whether a refresh is needed at all. If that plan were donors-only while `refresh`'s was not, every target in the file would land in its `drop` list on every press -- and a caller that fed that plan to `apply_plan` would evict them all.""" cache = core.new_cache('m') cache['scope'] = {'prefixes': ['PNL'], 'note': None, 'declared_by': None} cache['pieces'] = { 'PNL1': core.piece_record('PNL1', 1, 's1', 1, 0, features=[]), 'PNL2': core.piece_record('PNL2', 2, 's2', 0, 0, features=[]), } live = _live(PNL1=('s1', 1, 0), PNL2=('s2', 0, 0)) plan = core.plan(cache, live) # two arguments, as the button assert plan['drop'] == [] assert plan['build'] == [] assert plan['targets'] == ['PNL2'] def test_an_index_with_no_scope_stamp_keeps_the_donors_only_behaviour(): """A file that never recorded a scope has no targets in it to protect.""" cache = core.new_cache('m') # scope is None plan = core.plan(cache, _live(A=('sA', 1, 0), T=('sT', 0, 0))) assert plan['build'] == ['A'] # ================================================================== # THE FEATURE REGISTRY (schema 6). The feature-id is an INDEX/ACCELERATOR # only: `covers()` stays the sole ranker, the id never ranks and never becomes # a tolerance bin. Three owner-locked decisions (2026-09-02): the grain is the # index's EXISTING rounding (no new tolerance), the edge condition is the # binary concrete-edge vs block-out-edge, and the id is a positive integer. # These fixtures are neutral -- generic piece marks, no client or plant names. # ================================================================== # A strip (ligament) feature, in the shape `reinforcebind._record` produces: # both axes, plus the derived `strip_ft` (width across) and `span_ft` (span # along), `between`, and the outline flag. The registry reads exactly these. def _strip(kind, x0, x1, y0, y1, between, outline_approximate=False, edge=None): x0, x1, y0, y1 = float(x0), float(x1), float(y0), float(y1) strip = (x1 - x0) if kind == 'strip_x' else (y1 - y0) span = (y1 - y0) if kind == 'strip_x' else (x1 - x0) return {'kind': kind, 'x0': x0, 'x1': x1, 'y0': y0, 'y1': y1, 'w': round(x1 - x0, 3), 'h': round(y1 - y0, 3), 'strip_ft': round(strip, 3), 'span_ft': round(span, 3), 'between': between, 'edge': edge, 'outline_approximate': outline_approximate} # --------------------------------------------- descriptor: determinism + grain def test_a_descriptor_is_deterministic_hashable_and_json_serialisable(): f = _strip('strip_x', 0.0, 3.0, 1.0, 2.0, 'void-void') d1 = core.canonical_descriptor(f) d2 = core.canonical_descriptor(dict(f)) assert d1 == d2 assert hash(d1) == hash(d2) # hashable assert {d1: 'ok'}[d2] == 'ok' # usable as a dict key assert json.loads(json.dumps(d1)) == list( # JSON-serialisable [d1[0], list(d1[1]), d1[2], d1[3]]) def test_the_grain_is_the_existing_rounding_and_no_new_tolerance(): """3.00 vs 3.02 -> DIFFERENT (covers() ranks them); 3.000 vs 3.0004 -> SAME (the index's existing 3-dp / EXACT_TOL grain already collapsed them). Driven by `canonical_descriptor`'s own `_f`, not a fresh bin -- the values are passed raw here to prove the descriptor does the graining.""" def strip(width): return {'kind': 'strip_x', 'strip_ft': width, 'span_ft': 1.0, 'between': 'void-void'} assert (core.canonical_descriptor(strip(3.00)) != core.canonical_descriptor(strip(3.02))) assert (core.canonical_descriptor(strip(3.000)) == core.canonical_descriptor(strip(3.0004))) # ------------------------------------------------- descriptor: the binary edge def test_the_specific_panel_edge_is_dropped_from_the_descriptor(): """A VOID_EDGE strip against x0 and one against x1 -- same kind and dims -- share ONE descriptor: only concrete-vs-blockout is encoded, never the x0/x1/y0/y1 sub-label.""" at_x0 = _strip('strip_x', 0.0, 2.0, 1.0, 3.0, 'void-edge', edge='x0') at_x1 = _strip('strip_x', 5.0, 7.0, 1.0, 3.0, 'void-edge', edge='x1') assert (core.canonical_descriptor(at_x0) == core.canonical_descriptor(at_x1)) def test_concrete_edge_and_block_out_edge_are_distinguished(): """Same kind and dims, differing only in `between`: a VOID_EDGE (touches a concrete panel edge) and a VOID_VOID (between two block-outs) land in DIFFERENT descriptors.""" edge = _strip('strip_x', 0.0, 2.0, 1.0, 3.0, 'void-edge') between = _strip('strip_x', 5.0, 7.0, 1.0, 3.0, 'void-void') assert (core.canonical_descriptor(edge) != core.canonical_descriptor(between)) assert core.canonical_descriptor(edge)[2] == 'concrete_edge' assert core.canonical_descriptor(between)[2] == 'block_out_edge' def test_an_opening_is_block_out_edge_and_uses_its_own_dims(): op = core.feature('P1', 0, 1.0, 4.0, 2.0, 5.0, True, 0.0, 20.0, 10.0, 8.0) d = core.canonical_descriptor(op) assert d[0] == core.KIND_OPENING assert d[1] == (3.0, 3.0) # (w, h) assert d[2] == 'block_out_edge' assert d[3] is False # ------------------------------------------------------- descriptor: approx def test_an_approximate_edge_feature_never_shares_a_descriptor_with_a_measured(): measured = _strip('strip_x', 0.0, 2.0, 1.0, 3.0, 'void-edge') approx = _strip('strip_x', 0.0, 2.0, 1.0, 3.0, 'void-edge', outline_approximate=True) d_m = core.canonical_descriptor(measured) d_a = core.canonical_descriptor(approx) assert d_m != d_a assert d_m[3] is False and d_a[3] is True reg = core.Registry() assert reg.get_or_add(d_m) != reg.get_or_add(d_a) # -> different ids def test_outline_approximate_only_splits_edge_touching_features(): """A VOID_VOID strip measures exactly even on an approximate-outline piece, exactly as `covers()` treats it -- so the flag must NOT split it off. Only edge-touching features (VOID_EDGE, opening) carry the approximation.""" plain = _strip('strip_x', 0.0, 2.0, 1.0, 3.0, 'void-void') approx = _strip('strip_x', 0.0, 2.0, 1.0, 3.0, 'void-void', outline_approximate=True) assert (core.canonical_descriptor(plain) == core.canonical_descriptor(approx)) # ---------------------------------------------------- the ids themselves def test_ids_are_positive_sequential_and_deduped_across_panels(): reg = core.Registry() a = _strip('strip_x', 0.0, 3.0, 0.0, 2.0, 'void-void') b = _strip('strip_y', 0.0, 2.0, 0.0, 3.0, 'void-void') id_a = reg.get_or_add(core.canonical_descriptor(a)) id_b = reg.get_or_add(core.canonical_descriptor(b)) assert id_a == 1 and id_b == 2 # positive, sequential from 1 # DEDUP: an identical descriptor on another panel (different absolute # position, same kind/dims/edge) shares the id. elsewhere = _strip('strip_x', 10.0, 13.0, 4.0, 6.0, 'void-void') assert reg.get_or_add(core.canonical_descriptor(elsewhere)) == id_a def test_ids_are_stable_across_a_rebuild_and_never_renumbered(): a = {'kind': 'strip_x', 'strip_ft': 3.0, 'span_ft': 2.0, 'between': 'void-void'} b = {'kind': 'opening', 'w': 3.0, 'h': 3.0} c = {'kind': 'strip_y', 'strip_ft': 1.0, 'span_ft': 4.0, 'between': 'void-edge'} cache = core.new_cache('m') reg1 = core.register_features(cache, [{'name': 'P1', 'features': [a, b]}]) id_a = reg1.lookup(core.canonical_descriptor(a)) id_b = reg1.lookup(core.canonical_descriptor(b)) # A rebuild re-registers off the SAME persisted registry (cache['registry'] # was updated in place), and a NEW descriptor is appended, not renumbered. reg2 = core.register_features(cache, [{'name': 'P1', 'features': [a, b, c]}]) assert reg2.lookup(core.canonical_descriptor(a)) == id_a assert reg2.lookup(core.canonical_descriptor(b)) == id_b assert reg2.lookup(core.canonical_descriptor(c)) == 3 def test_register_features_skips_a_never_measured_record(): cache = core.new_cache('m') reg = core.register_features(cache, [ {'name': 'P1', 'features': None}, # could-not-judge {'name': 'P2', 'features': [ {'kind': 'opening', 'w': 2.0, 'h': 2.0}]}, ]) assert reg.serialize()['next_id'] == 2 # one descriptor, from P2 # --------------------------------------------------- lookup is read-only def test_lookup_is_read_only_and_never_grows_the_vocabulary(): """A target-style lookup of an ABSENT descriptor returns None and does not add it or move next_id -- the target must never mutate the shared vocabulary.""" reg = core.Registry() reg.get_or_add(core.canonical_descriptor( _strip('strip_x', 0.0, 3.0, 0.0, 2.0, 'void-void'))) before = reg.serialize() absent = core.canonical_descriptor( _strip('strip_y', 0.0, 2.0, 0.0, 9.0, 'void-edge')) assert reg.lookup(absent) is None assert reg.serialize() == before # unchanged, next_id and all assert reg.serialize()['next_id'] == 2 # --------------------------------------------------- feature_ids_of projection def test_feature_ids_of_is_a_correct_derived_projection(): a = {'kind': 'strip_x', 'strip_ft': 3.0, 'span_ft': 2.0, 'between': 'void-void'} b = {'kind': 'opening', 'w': 3.0, 'h': 3.0} cache = core.new_cache('m') reg = core.register_features(cache, [{'name': 'P1', 'features': [a, b]}]) record = {'name': 'P1', 'features': [a, b]} ids = core.feature_ids_of(record, reg) assert ids == [reg.lookup(core.canonical_descriptor(a)), reg.lookup(core.canonical_descriptor(b))] assert ids == [1, 2] # DERIVED, not stored: nothing wrote a feature-id array onto the record. assert 'feature_ids' not in record def test_feature_ids_of_projects_an_unregistered_feature_to_none(): """A target feature no donor ever produced is an accelerator MISS (None), which `covers()` then resolves the ordinary way -- never a fresh id, so the read-only projection cannot grow the vocabulary.""" reg = core.Registry() # empty vocabulary novel = {'kind': 'opening', 'w': 5.0, 'h': 5.0} assert core.feature_ids_of({'features': [novel]}, reg) == [None] assert reg.serialize()['next_id'] == 1 # not grown def test_feature_ids_of_propagates_could_not_judge_as_none(): reg = core.Registry() assert core.feature_ids_of({'features': None}, reg) is None assert core.feature_ids_of({'features': []}, reg) == [] # ----------------------------------------------------- the newly-marked diff def test_newly_reinforced_names_only_pieces_that_became_donors(): old = core.new_cache('m') old['pieces'] = { 'A': core.piece_record('A', 1, 's', 1, 0, features=[]), # a donor 'H': core.piece_record('H', 2, 's', 1, 1, features=[]), # on hold 'T': core.piece_record('T', 3, 's', 0, 0, features=[]), # unreinforced } live = { 'A': ('s', 1, 0), # still a donor -- not "newly" 'H': ('s2', 1, 0), # taken off hold -> NEW 'T': ('s2', 1, 0), # newly reinforced -> NEW 'N': ('s', 1, 0), # brand new eligible piece -> NEW 'X': ('s', 0, 0), # still ineligible -> not } assert core.newly_reinforced(old, live) == {'H', 'T', 'N'} def test_newly_reinforced_against_an_empty_old_cache_is_all_current_donors(): live = {'A': ('s', 1, 0), 'B': ('s', 0, 0), 'C': ('s', 1, 1)} assert core.newly_reinforced(core.new_cache('m'), live) == {'A'} assert core.newly_reinforced(None, live) == {'A'} # ------------------------------------------ the snapshot update statistics _OPEN = {'kind': 'opening', 'w': 2.0, 'h': 2.0} _STRIP = {'kind': 'strip_x', 'strip_ft': 3.0, 'span_ft': 2.0, 'between': 'void-void'} def _donor_rec(name, features, reinforced=1, on_hold=0): # A plain donor record for the diff -- bare feature dicts (no x0/x1), so # NOT built through `piece_record` (which sorts on absolute position); the # registry helpers key off `canonical_descriptor`, which needs no position. return {'name': name, 'reinforced': reinforced, 'on_hold': on_hold, 'features': features} def test_snapshot_stats_splits_new_vs_known_ids_for_newly_marked_panels(): """The owner's ask: this press marked P2; it has 2 features; one shape was already in the vocabulary, one is new this press.""" old = core.new_cache('m') old['pieces'] = {'P1': _donor_rec('P1', [_OPEN])} # already a donor core.register_features(old, [old['pieces']['P1']]) # _OPEN -> id 1 new = core.new_cache('m') new['registry'] = old['registry'] # ids persist a rebuild p1 = _donor_rec('P1', [_OPEN]) p2 = _donor_rec('P2', [_OPEN, _STRIP]) # newly marked new['pieces'] = {'P1': p1, 'P2': p2} core.register_features(new, [p1, p2]) # _STRIP gets a fresh id live = {'P1': ('s', 1, 0), 'P2': ('s', 1, 0)} stats = core.snapshot_stats(old, new, live) a_id = core.Registry.deserialize(old['registry']).lookup( core.canonical_descriptor(_OPEN)) c_id = core.Registry.deserialize(new['registry']).lookup( core.canonical_descriptor(_STRIP)) assert stats['first_snapshot'] is False assert stats['newly'] == ['P2'] # P1 was already a donor panel = stats['panels'][0] assert panel['name'] == 'P2' assert panel['feature_count'] == 2 assert panel['known_ids'] == [a_id] # _OPEN, seen before assert panel['new_ids'] == [c_id] # _STRIP, new this press assert stats['known_ids'] == [a_id] assert stats['new_ids'] == [c_id] def test_snapshot_stats_first_snapshot_is_flagged_and_everything_is_new(): """An absent/empty old cache is the first snapshot: every reinforced piece is new to the pool, and its feature-IDs are all new -- never a silent empty delta.""" new = core.new_cache('m') p1 = _donor_rec('P1', [_OPEN, _STRIP]) new['pieces'] = {'P1': p1} core.register_features(new, [p1]) live = {'P1': ('s', 1, 0)} for old in (core.new_cache('m'), None): stats = core.snapshot_stats(old, new, live) assert stats['first_snapshot'] is True assert stats['newly'] == ['P1'] assert stats['panels'][0]['known_ids'] == [] # nothing was known assert len(stats['panels'][0]['new_ids']) == 2 assert len(stats['new_ids']) == 2 def test_snapshot_stats_could_not_measure_panel_propagates_none(): """A newly-marked piece whose geometry could not be read carries features=None; the stat propagates could-not-judge rather than reporting zero features.""" new = core.new_cache('m') new['pieces'] = {'P9': _donor_rec('P9', None)} # could not judge live = {'P9': ('s', 1, 0)} stats = core.snapshot_stats(None, new, live) panel = stats['panels'][0] assert panel['name'] == 'P9' assert panel['feature_count'] is None assert panel['feature_ids'] is None def test_snapshot_stats_no_new_donors_is_an_empty_diff_not_a_crash(): """Nothing newly marked -> empty `newly`, empty pools. This is "nothing changed", which the button distinguishes from "nothing is reinforced".""" old = core.new_cache('m') old['pieces'] = {'P1': _donor_rec('P1', [_OPEN])} core.register_features(old, [old['pieces']['P1']]) new = core.new_cache('m') new['registry'] = old['registry'] new['pieces'] = {'P1': _donor_rec('P1', [_OPEN])} core.register_features(new, [new['pieces']['P1']]) live = {'P1': ('s', 1, 0)} stats = core.snapshot_stats(old, new, live) assert stats['newly'] == [] assert stats['panels'] == [] assert stats['new_ids'] == [] and stats['known_ids'] == [] # ---------------------------------------------- schema 6: seed + round-trip def test_new_cache_seeds_an_empty_registry(): cache = core.new_cache('m') reg = core.Registry.deserialize(cache['registry']) assert reg.serialize()['next_id'] == 1 assert core.feature_ids_of({'features': []}, reg) == [] def test_the_registry_survives_a_save_and_load_round_trip(tmp_path): path = str(tmp_path / 'index.json') a = {'kind': 'strip_x', 'strip_ft': 3.0, 'span_ft': 2.0, 'between': 'void-void'} b = {'kind': 'opening', 'w': 3.0, 'h': 3.0} cache = core.new_cache('m') core.register_features(cache, [{'name': 'P1', 'features': [a, b]}]) core.save(path, cache) back = core.load(path) reg = core.Registry.deserialize(back['registry']) assert reg.lookup(core.canonical_descriptor(a)) == 1 assert reg.lookup(core.canonical_descriptor(b)) == 2 assert reg.serialize()['next_id'] == 3 # ids preserved verbatim def test_a_pre_registry_schema_cache_is_discarded_to_a_loud_empty(tmp_path): """An older-schema file is thrown away WHOLE and comes back a loud EMPTY: the stale vocabulary is never silently adopted, and an empty registry is a could-not-judge / NO_DONORS condition, not a success.""" path = str(tmp_path / 'index.json') old = {'schema': core.SCHEMA - 1, 'doc_key': 'm', 'built_at': 'y', 'registry': {'next_id': 9, 'by_id': {'1': '[]'}, 'by_key': {'x': 1}}, 'pieces': {'A': {'name': 'A', 'features': []}}} with open(path, 'wb') as handle: handle.write(json.dumps(old).encode('utf-8')) back = core.load(path) assert back['_load_error'] # loud assert back['pieces'] == {} # empty # the stale next_id=9 vocabulary is NOT adopted assert core.Registry.deserialize(back['registry']).serialize()['next_id'] == 1 def test_a_current_schema_cache_missing_a_registry_defaults_to_empty(tmp_path): """A hand-built same-schema cache with no registry key at all still loads with a valid empty vocabulary rather than leaving a caller to guess.""" path = str(tmp_path / 'index.json') healthy = {'schema': core.SCHEMA, 'doc_key': 'm', 'built_at': 'x', 'scope': None, 'pieces': {}} # no 'registry' key with open(path, 'wb') as handle: handle.write(json.dumps(healthy).encode('utf-8')) back = core.load(path) assert core.Registry.deserialize( back['registry']).serialize()['next_id'] == 1