Commit Graph
4 Commits
Author SHA1 Message Date
Umut CANandClaude Opus 5.5 e640def4ff feat(understand): orthogonal lengths and pipe takeoff rows
route.py: Manhattan distance, the rectilinear minimum spanning tree (Prim
over |dx| + |dy|, exact on the points given) and the x-then-y route.

pipe_rows turns the P&ID's runs into rows by room x service x diameter.
length_source auto measures on the P&ID only when the scale check calls it
to scale; otherwise each run is the RMST of its tags' layout positions plus
vertical_allowance per connection, with the drawn length kept beside it as
schematic_m. length_source=pid on a schematic P&ID is refused with the
statistics unless force=True. Status A / B / C; a C run goes to control
with its schematic length, counted and flagged. Routed lengths are shared
among the layout rooms their route crosses (track F's face finder); the
allowance is its own column. Lengths are metres from the unit the geometry
implies: a layout declaring inches over millimetres is read in mm and warned
about, never multiplied by 25.4.

The takeoff carries narrow stand-ins for the unit inference, the plan-copy
clusters and the scale check (group U's modules are on another branch); a
scale result passed in is used first. Tags written twice in the chosen
cluster are ambiguous, never picked.

Beyond the plan: the scale stand-in calls a non-positive median ratio
insufficient (as group U's scale_check does) instead of to_scale with a
factor of 0; an INSUNITS code the reader does not convert is named in the
unit warning instead of being called "no unit"; wall layers are classified
once per layer, not once per record.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-24 12:14:17 +03:00
Umut CANandClaude Opus 5.5 7bfba02ef0 fix(task-5): fittings bridge every port pair, reducers found however the pipe meets them, unread-layer lines keep their labels
- A three- or four-way fitting joined its ports as a star around the port
  seen first, so the diameter carried into an unlabelled branch depended on
  record order. Every port pair is now bridged; two sizes equally near stay
  unassigned whatever the order (six-order parametrised test).
- A reducer was seen only through free ends in its box. It is now also found
  where pieces meet at a vertex inside its box (a line broken at the
  reducer) and where a straight segment is drawn through the middle of its
  box (cut at the foot of the box centre). A direct label holds for its
  entity only on its side of every reducer.
- A diameter or direction label nearer to an open line on a layer that is
  not read belongs to that line: it is dropped and listed in
  stats.labels_skipped instead of landing on the nearest read pipe.

Headless engine only: no new ActiveX call, so no live probe applies.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-24 12:08:47 +03:00
Umut CANandClaude Opus 5.5 1426c53fd3 feat(understand): the line-network engine - runs, services, diameters, equipment
build_network reads the segments of a foreign P&ID's piping and electrical
layers into runs: ends merged within tol, a branch ending on a header split
into a tee, a crossing without an end left unconnected, and a line drawn
twice - or a line over a polyline - cut into identical pieces that are kept
once, the doubled length reported as overlap_length. An INSERT holding two to
four free ends is an inline fitting joined through; a reducer word in its
block name (reducer, perekhod, reduksiyon, verloop, reduzier) stops diameter
continuity.

Each diameter label goes to the nearest piece and holds for its drawn entity
(direct); unlabelled pieces take the nearest direct diameter along the run
(continuity) or stay unassigned - two sizes equally near are never guessed.
Labels stay as drawn. A supply or return word near a run overrides its
layer's service for the whole run. Run ends attach to the tag whose labelled
geometry holds them; a shape holding two tags (a room, a skid) is not
equipment. Tests on hand-built drawings and on the synthetic plant pair.

Beyond the plan: a CIP / glycol layer naming no direction (classify_layer
files it `unknown` on purpose) now takes its direction from the word instead
of staying unknown; is_reducer matches on vocab.fold and knows the German
word equipment_kind already had; the fitting, equipment and attachment
lookups go through grid indexes - measured 149 s -> 2.8 s on a 59,200-record
synthetic drawing, results identical to the plan engine on it and on the
plant pair. No ActiveX call is added (pure module).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-24 11:53:03 +03:00
Umut CANandClaude Opus 5.5 9ab0ae1773 feat(understand): the snapshot, the vocabulary and the label parsers; MAX_DXF_BYTES 512 MB; the office extra
Track H's foundation, landed on main before its three groups branch.

- engineering/understand/snapshot.py: one bulk read of a drawing into frozen
  WCS records (OCS translated through backends/ocs.py, text decoded, an
  INSERT boxed by its block geometry without its attribute texts). A DXF is
  read with ezdxf after the size check; the headless engine's document is
  read under its own lock; the live engine exports the active document with
  Document.Export(<tmp>/snap, "DXF", <set of all>) - never renamed, deleted
  afterwards - and a DWG is opened read-only, exported and closed. Measured on
  AutoCAD 2026: ".dxf" is refused with Invalid argument, the file lands as
  snap.DXF, model space, paper space and unused block definitions all reach
  it, and the selection set does not narrow a DXF export.
- engineering/understand/labels.py: %%c, \U+2205, the U+2300 sign, o-stroke
  and MTEXT format runs all read as one diameter spelling; sizes are kept as
  drawn and never equated; Cyrillic look-alike capitals fold into Latin tags;
  panels, wiring callouts and electrical data (kW, phases, volts, N, VFD) in
  EN / TR / RU / NL / DE. A power that is not written stays None.
- engineering/understand/vocab.py: layer classification with a confidence and
  the deciding keyword, supply / return words, room labels and block kinds in
  five languages. A CIP layer without a direction word is not guessed.
- MAX_DXF_BYTES defaults to 512 MB (a real layout was 188 MB); the refusal
  names the size and the variable. New `office` extra (openpyxl), also in
  `full`; uv.lock regenerated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FKFStjEFzpGyLq5bMpWxTd
2026-09-24 11:38:59 +03:00