fix(understand): what the field test found — P&ID lengths need no equipment, caret notation, numbered rooms

Run on a real P&ID pair (kept outside the repository), the pipe takeoff wrote
an empty table. Three causes, each fixed with a test that failed first:

- Measured on the P&ID, a run still needed equipment tags at both ends,
  which only routing on the layout needs. Few pipes on a field P&ID end on
  a tagged outline. On the P&ID each piece now goes to the room it lies in:
  cut at the room outlines when the P&ID has room faces, else to the
  nearest room label. Diameters are kept piece by piece. On the layout a
  takeoff that could not route every run now says how many, why, the drawn
  length left to the control rows, and that length_source='pid' with
  force=True measures on the P&ID instead.
- A TEXT stores its line breaks in caret notation ('6^JROOM'), which
  plain() left as it was, so a room label's number read as '6^J...'.
  plain() now decodes ^J, ^M, ^I and '^ ', leaving MTEXT stacks
  ('\S1^2;') alone.
- Labels that only mention a room were read as rooms: a control cabinet
  naming the room it serves, or a note pointing into a room. An unnumbered
  label that names equipment is no room. Without room faces, when the
  drawing numbers its rooms, only numbered labels take pieces.

On the field pair the takeoff now fills its table, and every room key is a
real room number. The comparison with the hand-made workbook stays with
the user's files. README collected tests: 4981. The suite passes (4980, 1
xfailed).

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:
Umut CAN
2026-09-24 15:57:18 +03:00
co-authored by Claude Opus 5.5
parent 307ccc799f
commit ac32700ba8
7 changed files with 213 additions and 44 deletions
+5 -1
View File
@@ -35,7 +35,7 @@ from engineering.understand.clusters import find_clusters
from engineering.understand.labels import parse_tag, wiring_target
from engineering.understand.snapshot import EntityRecord, Snapshot, length_of
from engineering.understand.units import infer_units, robust_extents, wall_segments
from engineering.understand.vocab import classify_layer, room_label
from engineering.understand.vocab import classify_layer, equipment_kind, room_label
__all__ = [
"describe",
@@ -186,6 +186,10 @@ def find_rooms(records: Sequence[EntityRecord], *, tol: float, membership: dict)
if rec.type in _TEXT_TYPES and rec.text:
parsed = room_label(rec.text)
at = _anchor(rec)
# an unnumbered label that names equipment ('control cabinet - filling
# room') labels the equipment; a numbered 'pump room 3' is a room
if parsed and parsed["number"] is None and equipment_kind(rec.text):
continue
if parsed and at is not None:
labels.append((rec, parsed, at))
segments, bulged = wall_segments(records)