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.
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
# -*- 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
|
||||
Reference in New Issue
Block a user