feat(understand): room frames, the nearest panel and possible packages — what the field test asked for
The takeoffs were run again on a real P&ID pair (kept outside the repository)
and compared with the hand-made workbooks. The user chose how three gaps
should close:
- Rooms drawn as frames. A room label that no wall face holds takes the
smallest closed, straight-sided polyline around it, provided it holds no
other room number and is at least ten label heights a side (label+frame,
0.8). A P&ID draws its rooms this way, so P&ID lengths are now cut at the
frames rather than sent to the nearest label. An unnumbered mention inside
a frame ('hot room') does not deny it.
- The nearest panel, flagged. cable_takeoff(panel_rule="nearest") is the
default: a load that no callout places takes the nearest panel on the
layout, among names with a panel prefix (is_panel_name: CP, JB, DB,
MCC ...) or names a callout gives. The row is flagged panel_inferred, and
an inferred panel never makes a package. panel_rule="stated" keeps the old
behaviour. The takeoff_roundtrip gate and the cable correctness check read
stated panels only: a guess is no evidence.
- Possible packages and detail copies. pipe_takeoff reports detail_copies:
compact groups of tags drawn twice (repeated), and compact groups of one
equipment's own parts, such as M150B, M150C and M150D of M150 (family). A
vendor package or an equipment detail shows up this way. Nothing is
removed until exclude_regions names a region. The runs left out go to the
control rows as excluded_region and to excluded_m. The workbook's method
sheet names the regions.
Each change has a synthetic test that failed first. Suite: 4986 passed, 1
xfailed. The inventory and the document numbers match (README: 4987
collected).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
db5b4d3ef8
commit
554daff8d0
@@ -192,6 +192,34 @@ def test_without_a_layout_lengths_stay_empty_with_an_open_item(pair):
|
||||
assert all(row["panel"] is None for row in result["rows"])
|
||||
|
||||
|
||||
def test_a_load_no_callout_places_takes_the_nearest_panel_flagged(pair):
|
||||
# M82's 'Wiring to CP1' callout is gone. By default the nearest panel on the
|
||||
# layout is taken - CP-1 at hypot(13000, 2000) = 13.2 m, not CP-M88 at 23.1 m
|
||||
# - and the row says so; with panel_rule='stated' the panel stays open.
|
||||
pid, _layout = pair
|
||||
layout = snap([r for r in layout_records() if r.handle != "wM82"], 4, "layout.dxf")
|
||||
result = cable_rows(pid, layout)
|
||||
m82 = by_tag(result)["M82"]
|
||||
assert (m82["panel"], m82["panel_source"]) == ("CP-1", "nearest")
|
||||
assert m82["length_m"] * 1000.0 == pytest.approx(15000.0, abs=1e-6) # Manhattan
|
||||
assert m82["cable_m"] == 18
|
||||
assert {
|
||||
"tag": "M82",
|
||||
"item": "panel_inferred",
|
||||
"detail": "no wiring callout names a panel; CP-1 is the nearest panel on the layout, "
|
||||
"13.2 m away",
|
||||
} in result["open_items"]
|
||||
assert ("M82", "missing_panel") not in {(i["tag"], i["item"]) for i in result["open_items"]}
|
||||
assert result["method"]["panel_rule"] == "nearest"
|
||||
stated = cable_rows(pid, layout, panel_rule="stated")
|
||||
assert by_tag(stated)["M82"]["panel"] is None
|
||||
assert ("M82", "missing_panel") in {(i["tag"], i["item"]) for i in stated["open_items"]}
|
||||
with pytest.raises(
|
||||
ValueError, match="^panel_rule: 'closest' unknown; choose from nearest, stated"
|
||||
):
|
||||
cable_rows(pid, layout, panel_rule="closest")
|
||||
|
||||
|
||||
def test_a_tag_missing_from_the_layout_is_an_open_item(pair):
|
||||
pid, _layout = pair
|
||||
layout = snap(layout_records(skip=("M87",)), 4, "layout.dxf")
|
||||
|
||||
Reference in New Issue
Block a user