52d94303bc
ci / test (push) Has been cancelled
Public demo snapshot of an in-house pyRevit extension for precast concrete shop-ticket QC (37 checks / 46 checklist rows) and a standalone panel handling solver (PCI Chapter 8). - No client, job, plant or product data: knowledge layers ship as structural templates only. - Ticket QC's model-read escalation targets a LAN model server endpoint (configurable), not a cloud provider. - Built from the private development repo via tools/make_dist.py.
119 lines
4.9 KiB
Python
119 lines
4.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""The `mc` pack: one producer's design-summary habits.
|
|
|
|
Loaded only when the plant layer declares it (`extract.rule_packs: [mc]`) --
|
|
see `vibe/extractrules/__init__.py` for why that is declared and not inferred
|
|
from `plant.id`.
|
|
|
|
WHERE THESE RULES COME FROM
|
|
---------------------------
|
|
Every rule below was written against text observed on a real sheet, and each
|
|
one says which. They are not a model of how this producer draws; they are a
|
|
record of lines that actually landed in `unresolved` and what they turned out
|
|
to be. A rule with no observation behind it does not belong here -- this file
|
|
is evidence, and a guess mixed into evidence is indistinguishable from it
|
|
afterwards.
|
|
|
|
Observed on `WPA1.0` (sample_job, 8" horizontal wall, read 2026-09-01):
|
|
all seven notes the generic reader could not place, listed in the order they
|
|
sit on the sheet.
|
|
|
|
WHAT IS DELIBERATELY NOT HERE
|
|
------------------------------
|
|
`STRIPPING & BLOCKING DETAIL < 25'` is NOT treated as the same thing as the
|
|
bare `STRIPPING & BLOCKING DETAIL` beside it. The bare one is the title of a
|
|
detail drawn on the sheet and carries no data. The other one carries a
|
|
CONDITION -- which detail applies below twenty-five feet -- and a condition is
|
|
the part that could change what gets built. So the bare title is furniture and
|
|
the conditional one is a note, and the rule that separates them tests for
|
|
anything trailing the title rather than matching both with one loose pattern.
|
|
That asymmetry is the module's standing rule (`__init__`: when unsure, note).
|
|
|
|
Nothing here encodes a NUMBER. `*USE MC STANDARD REINFORCEMENT ...` names a
|
|
standard this office keeps; what that standard says is plant knowledge and
|
|
belongs in the plant knowledge file, looked up by name, never frozen into a
|
|
regex in this file. The rule's whole job is to carry the sentence to a field a
|
|
person reads.
|
|
|
|
IronPython 2.7: no f-strings, no pathlib, no typing, no dataclasses.
|
|
"""
|
|
import re
|
|
|
|
from vibe.extractrules import Placement, NOTE, FURNITURE
|
|
|
|
NAME = 'mc'
|
|
|
|
|
|
def _rule(rule_id, pattern, kind, reason):
|
|
return (rule_id, re.compile(pattern, re.IGNORECASE), kind, reason)
|
|
|
|
|
|
# Order matters and is not alphabetical: a more specific rule must come before
|
|
# the looser one it would otherwise be swallowed by. The conditional detail
|
|
# title is tested before the bare one for exactly that reason.
|
|
RULES = (
|
|
# ---------------------------------------------------------- instructions
|
|
_rule('mc/standard-reinforcement',
|
|
r"^\*?\s*USE\s+MC\s+STANDARD\s+REINFORCEMENT\b",
|
|
NOTE,
|
|
'a standing instruction naming this plant\'s standard reinforcement '
|
|
'for cut-down panels and openings -- prose, not a number; what the '
|
|
'standard says is plant knowledge, looked up by name'),
|
|
|
|
_rule('mc/conditional-bar',
|
|
r"^#\s*\d+\s+BAR\b.*\bONLY\s+NEEDED\s+WHEN\b",
|
|
NOTE,
|
|
'a conditional reinforcing instruction -- the bar is required only '
|
|
'when the condition in the sentence holds, so the condition is the '
|
|
'value and it stays as prose'),
|
|
|
|
_rule('mc/pick-instruction',
|
|
r"^USE\s+FOR\b.*\bPICK\b",
|
|
NOTE,
|
|
'a handling instruction saying which pick this detail is for -- '
|
|
'prose, and it qualifies a detail rather than stating a number'),
|
|
|
|
_rule('mc/detail-title-conditional',
|
|
r"^STRIPPING\s*&\s*BLOCKING\s+DETAIL\b\s*\S+",
|
|
NOTE,
|
|
'a standard-detail title carrying a CONDITION (which detail applies, '
|
|
'and when) -- the title alone would be furniture, the condition is '
|
|
'not'),
|
|
|
|
# ------------------------------------------------------------- furniture
|
|
_rule('mc/detail-title',
|
|
r"^STRIPPING\s*&\s*BLOCKING\s+DETAIL\s*$",
|
|
FURNITURE,
|
|
'the bare title of a standard detail drawn on the sheet -- names '
|
|
'the detail, states nothing about this design'),
|
|
|
|
_rule('mc/legend-title',
|
|
r"^STRAND\s+PATTERN\s*$",
|
|
FURNITURE,
|
|
'the title above the strand-pattern legend -- the legend\'s DATA is '
|
|
'the dimension chain under it, which is read as geometry, not as '
|
|
'this text'),
|
|
|
|
_rule('mc/legend-label',
|
|
r"^STRAND\s*$",
|
|
FURNITURE,
|
|
'a bare column label in the strand legend -- a heading over values '
|
|
'read elsewhere, with no value of its own'),
|
|
)
|
|
|
|
|
|
def classify(text):
|
|
"""A Placement for `text`, or None when this pack does not recognise it.
|
|
|
|
`text` arrives already whitespace-normalised by the caller (one space
|
|
between words, no leading or trailing run), so every pattern here can
|
|
assume single spaces and anchor at both ends where it means to.
|
|
"""
|
|
probe = (text or '').strip()
|
|
if not probe:
|
|
return None
|
|
for rule_id, pattern, kind, reason in RULES:
|
|
if pattern.search(probe):
|
|
return Placement(kind, rule_id, reason)
|
|
return None
|