WillTools 2026-09-09 -- clean snapshot of the development repo for review
ci / test (push) Successful in 6m14s
ci / test (push) Successful in 6m14s
History-free snapshot of the private development repository at 77a82eb. Code, docs and the public catalogue only: no plant or job knowledge layers, no client, job or producer names (tools/scrub.py clean against the widened term list; a second word-level grep clean). The intro deck is left out because its slide images cannot be checked for job drawings.
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Rows 5.13, 5.14 and 5.15 once the hatch gather exists.
|
||||
|
||||
WHAT CHANGED ON 2026-08-31. These three rows shipped with their arithmetic
|
||||
built and their inputs deliberately un-gathered: `ticketqc.read_ticket` set
|
||||
no `solid_zone_pattern`, `recess_patterns`, `recess_regions` or
|
||||
`legend_patterns`, so all three stood down naming the gather. That was the
|
||||
right call while nobody had run the reads. The reads have now been taken on
|
||||
the live model, id by id, and `ticketqc._read_insulation_hatch` is the path
|
||||
they exercised -- so the rows run.
|
||||
|
||||
NO PLANT SPELLING IN THIS FILE. The real filled-region type names carry a
|
||||
plant initialism and live in the plant layer; the placeholders below have
|
||||
the same SHAPE and none of the identity, which is all the mechanism needs
|
||||
(CLAUDE.md ss3).
|
||||
"""
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import conftest # noqa: F401
|
||||
|
||||
# THE INERT REVIT STUB, AND IT IS PUT BACK. `ticketqc` imports
|
||||
# `Autodesk.Revit.DB` at MODULE scope, so `conftest`'s package trick cannot
|
||||
# reach it -- `test_ticketchecks_host.py` explains the whole shape and this
|
||||
# is the same borrowing. It is removed again immediately, because
|
||||
# `sys.modules` is process-wide and `test_reinforceoffline.py` asserts that
|
||||
# no `Autodesk*` module is loaded.
|
||||
_ADDED = []
|
||||
if 'Autodesk.Revit.DB' not in sys.modules:
|
||||
import types
|
||||
_autodesk = types.ModuleType('Autodesk')
|
||||
_revit = types.ModuleType('Autodesk.Revit')
|
||||
_db = types.ModuleType('Autodesk.Revit.DB')
|
||||
for _name in ('FilteredElementCollector', 'AssemblyInstance', 'View',
|
||||
'ViewSheet', 'ViewType', 'BuiltInCategory', 'Element',
|
||||
'StorageType', 'ElementId', 'XYZ', 'Transform',
|
||||
'Options', 'Solid', 'BoundingBoxXYZ'):
|
||||
setattr(_db, _name, type(str(_name), (object,), {}))
|
||||
_autodesk.Revit = _revit
|
||||
_revit.DB = _db
|
||||
sys.modules['Autodesk'] = _autodesk
|
||||
sys.modules['Autodesk.Revit'] = _revit
|
||||
sys.modules['Autodesk.Revit.DB'] = _db
|
||||
_ADDED = ['Autodesk.Revit.DB', 'Autodesk.Revit', 'Autodesk']
|
||||
try:
|
||||
from vibe import ticketpage as TP # noqa: E402
|
||||
from vibe import ticketchecks as TC # noqa: E402
|
||||
finally:
|
||||
for _name in _ADDED:
|
||||
sys.modules.pop(_name, None)
|
||||
|
||||
RECESS_A = 'RECESS 2IN NF'
|
||||
RECESS_B = 'RECESS 3IN NF'
|
||||
|
||||
|
||||
def _know(**over):
|
||||
"""A plant armed the way the real one now is: a solid-zone family, a
|
||||
legend it reads its reference off, two recess types, and a plant-side
|
||||
copy of the legend for the piece whose legend could not be read."""
|
||||
know = {
|
||||
'drawings': {'insulation_layout': 'Elevation Top 1'},
|
||||
'insulation': {
|
||||
'solid_zone': {'family': 'SOLID_ZONE_RECTANGULAR',
|
||||
'family_verified': True,
|
||||
'view_filter': 'SOLID ZONE'},
|
||||
'legend': {
|
||||
'view_name': 'TICKET NOTE - INS. LEGEND',
|
||||
'name_tokens': ['LEGEND'],
|
||||
'swatch_types': {'SOLID ZONE SWATCH': 'solid_zone',
|
||||
RECESS_A: 'recess_2in_nf',
|
||||
RECESS_B: 'recess_3in_nf'},
|
||||
'expected_patterns': {'solid_zone': 'ANSI35',
|
||||
'recess_2in_nf': 'TRIANG',
|
||||
'recess_3in_nf': 'TUCKPOINT'},
|
||||
},
|
||||
'recess': {'region_types': [RECESS_A, RECESS_B],
|
||||
'modelled_path_verified': True,
|
||||
'modelled_path_unverified_because': None},
|
||||
},
|
||||
}
|
||||
know.update(over)
|
||||
return know
|
||||
|
||||
|
||||
def _ticket(**over):
|
||||
t = {'mark': 'PNL101', 'sheets': [object()],
|
||||
'solid_zones': [{'id': 1, 'member': True, 'lifter_id': 9}],
|
||||
'lifters': [{'family': 'F'}],
|
||||
'legend_patterns': {'solid_zone': 'ANSI35',
|
||||
'recess_2in_nf': 'TRIANG',
|
||||
'recess_3in_nf': 'TUCKPOINT'},
|
||||
'recess_regions': {},
|
||||
'recess_patterns': {RECESS_A: 'TRIANG', RECESS_B: 'TUCKPOINT'},
|
||||
'solid_zone_pattern': 'TRIANG',
|
||||
'insulation_hatch_notes': []}
|
||||
t.update(over)
|
||||
return t
|
||||
|
||||
|
||||
# ----------------------------------------------------------------- 5.13
|
||||
|
||||
def test_513_runs_and_fails_on_the_measured_job():
|
||||
"""THE MEASURED ANSWER. The solid zones draw the pattern the view
|
||||
template's `SOLID ZONE` filter sets, and the legend's own swatch for a
|
||||
solid zone is a different pattern element entirely. The row FAILS, and
|
||||
both patterns and both provenances are in the sentence -- "the hatch is
|
||||
wrong" is not actionable, "it draws X and your own legend shows Y" is.
|
||||
"""
|
||||
t = _ticket()
|
||||
found = TC.check_solid_zone_hatch(t, {}, _know(), None)
|
||||
assert len(found) == 1
|
||||
assert found[0].severity == 'FAIL'
|
||||
assert found[0].code == 'SOLID-ZONE-HATCH'
|
||||
assert 'TRIANG' in found[0].message and 'ANSI35' in found[0].message
|
||||
assert 'legend' in found[0].message
|
||||
assert t['_solid_zone_hatch_silence'] is None
|
||||
|
||||
|
||||
def test_513_passes_when_the_filter_sets_the_legends_own_pattern():
|
||||
found = TC.check_solid_zone_hatch(
|
||||
_ticket(solid_zone_pattern='ANSI35'), {}, _know(), None)
|
||||
assert found[0].severity == 'OK'
|
||||
|
||||
|
||||
def test_513_stands_down_carrying_the_gathers_own_sentences():
|
||||
"""A row that stood down saying only "it was not gathered" would throw
|
||||
away the reason the gather recorded, and the reader would have to
|
||||
reproduce the read by hand to find out why."""
|
||||
t = _ticket(solid_zone_pattern=None,
|
||||
insulation_hatch_notes=[
|
||||
'the insulation layout view is not placed on this ticket'])
|
||||
found = TC.check_solid_zone_hatch(t, {}, _know(), None)
|
||||
assert found == []
|
||||
said = t['_solid_zone_hatch_silence']
|
||||
assert 'what the gather recorded' in said
|
||||
assert 'not placed on this ticket' in said
|
||||
|
||||
|
||||
# ----------------------------------------------------------------- 5.14
|
||||
|
||||
def test_514_stands_down_and_refuses_to_imply_the_pieces_were_judged():
|
||||
"""ZERO PLACED, JOB-WIDE. The only recess regions that exist in this
|
||||
model are legend swatches, and the gather excludes legend views on
|
||||
purpose. So a zero here is a real zero -- and it is still not a pass:
|
||||
this reader has never seen a job that DOES draw a recess, so it cannot
|
||||
tell a piece that needs none from a piece whose recesses were
|
||||
forgotten, and the sentence says exactly that."""
|
||||
t = _ticket()
|
||||
found = TC.check_recess_regions(t, {}, _know(), None)
|
||||
assert found == []
|
||||
said = t['_recess_silence']
|
||||
assert 'no recess region is drawn on any ticket view' in said
|
||||
assert 'legend views are excluded' in said
|
||||
assert 'cannot tell a piece that needs no recess' in said
|
||||
|
||||
|
||||
def test_514_no_longer_hedges_about_a_modelled_path_that_was_settled():
|
||||
"""The one candidate for a modelled recess was measured and ruled out,
|
||||
so `modelled_path_verified` is true and the caveat goes. A tool that
|
||||
kept hedging about a closed question is noise on a red row."""
|
||||
t = _ticket()
|
||||
TC.check_recess_regions(t, {}, _know(), None)
|
||||
assert 'modelled-recess path was NOT consulted' not in t['_recess_silence']
|
||||
know = _know()
|
||||
know['insulation']['recess']['modelled_path_verified'] = False
|
||||
know['insulation']['recess']['modelled_path_unverified_because'] = 'asked'
|
||||
t2 = _ticket()
|
||||
TC.check_recess_regions(t2, {}, know, None)
|
||||
assert 'NOT consulted' in t2['_recess_silence']
|
||||
|
||||
|
||||
def test_514_becomes_a_real_comparison_the_day_a_design_side_arrives():
|
||||
"""No new plumbing: the row already compares when something states what
|
||||
this piece should have."""
|
||||
t = _ticket(recess_regions={RECESS_A: 2})
|
||||
found = TC.check_recess_regions(t, {'recesses': {RECESS_A: 2}},
|
||||
_know(), None)
|
||||
assert found[0].severity == 'OK'
|
||||
found = TC.check_recess_regions(
|
||||
_ticket(recess_regions={}), {'recesses': {RECESS_A: 2}},
|
||||
_know(), None)
|
||||
assert found[0].severity == 'FAIL'
|
||||
|
||||
|
||||
# ----------------------------------------------------------------- 5.15
|
||||
|
||||
def test_515_runs_clean_for_both_recess_types():
|
||||
"""The type-level patterns match their legend swatches. One finding for
|
||||
BOTH types, never one per type -- `checklist_states` keeps the last
|
||||
finding per code and a second RECESS-HATCH would silently replace the
|
||||
first."""
|
||||
t = _ticket()
|
||||
found = TC.check_recess_hatch(t, {}, _know(), None)
|
||||
assert len(found) == 1
|
||||
assert found[0].severity == 'OK'
|
||||
assert RECESS_A in found[0].message and RECESS_B in found[0].message
|
||||
# The size question is named and explicitly NOT decided here.
|
||||
assert 'not this row' in found[0].message
|
||||
|
||||
|
||||
def test_515_fails_on_the_type_that_drifted_and_still_reports_the_other():
|
||||
t = _ticket(recess_patterns={RECESS_A: 'STARS', RECESS_B: 'TUCKPOINT'})
|
||||
found = TC.check_recess_hatch(t, {}, _know(), None)
|
||||
assert found[0].severity == 'FAIL'
|
||||
assert 'STARS' in found[0].message
|
||||
assert 'TUCKPOINT' in found[0].message
|
||||
|
||||
|
||||
def test_515_stands_down_when_a_filter_could_override_the_type():
|
||||
"""THE CONDITION UNDER WHICH A TYPE-LEVEL READ IS AUTHORITATIVE, and it
|
||||
is checked on every run rather than recorded once. A filled region is a
|
||||
Detail Item; a filter selecting that category beats the type, and then
|
||||
the type says what the region WOULD have drawn with. Measured
|
||||
2026-08-31: no filter on this template carries the category -- which is
|
||||
a fact about today's template, not a property of Revit."""
|
||||
t = _ticket(recess_patterns=None, insulation_hatch_notes=[
|
||||
'filter(s) INSULATION select the Detail Items category on the '
|
||||
'insulation layout view, so a filled region\'s TYPE no longer says '
|
||||
'what it drew with -- the recess patterns were not read'])
|
||||
found = TC.check_recess_hatch(t, {}, _know(), None)
|
||||
assert found == []
|
||||
said = t['_recess_hatch_silence']
|
||||
assert 'Detail Items category' in said
|
||||
assert 'what the gather recorded' in said
|
||||
|
||||
|
||||
def test_the_sheets_own_legend_beats_the_plant_map_and_the_finding_says_so():
|
||||
""""the legend says ANSI35" and "a YAML file written on 2026-08-31 says
|
||||
ANSI35" are different claims. The legend is the self-updating one."""
|
||||
from_sheet = TC.check_solid_zone_hatch(_ticket(), {}, _know(), None)[0]
|
||||
assert "ticket's own legend" in from_sheet.message
|
||||
from_plant = TC.check_solid_zone_hatch(
|
||||
_ticket(legend_patterns={}), {}, _know(), None)[0]
|
||||
assert 'plant layer' in from_plant.message
|
||||
|
||||
|
||||
def test_a_gather_that_recorded_nothing_leaves_the_sentence_alone():
|
||||
"""`_hatch_gather_note` must add, never replace: the row keeps its own
|
||||
words when the gather has nothing to add."""
|
||||
assert TC._hatch_gather_note({}, 'because X') == 'because X'
|
||||
assert TC._hatch_gather_note({'insulation_hatch_notes': []},
|
||||
'because X') == 'because X'
|
||||
Reference in New Issue
Block a user