Skip to content

Drawing Viewer — Layer Classification & Coverage

StatusShipped
LinearPIN-365 ↗ PIN-146 ↗
ModuleProgress Drawings ↗

Every DWG that lands in Pinley is drawn by a different engineering firm, and each firm names its layers its own way (H-DUCT, SM-SA-LP, ACME-M-SUPPLY, |m-piping|…). Before the Drawing Viewer can colour, filter, or quantify anything by trade, every layer has to be mapped to a discipline. That mapping is the job of the classification rules engine. The two admin tabs are the two sides of that engine: Layer Classification is where you configure the mapping, and Classification Coverage is where you monitor how well it is working — and where you spot the firms whose drawings need new rules.

Concepts (read this first)

A few terms recur throughout both tabs:

Term What it means
Discipline The trade a layer belongs to. Canonical codes: M (mechanical / HVAC), P (plumbing, incl. fire sprinkler), E (electrical), S (structural), A (architectural), FP (fire protection), MISC (unclassified).
Segment Optional sub-type for HVAC air: SUPPLY, RETURN, EXHAUST, OUTSIDE, TRANSFER. Only meaningful on M layers.
Rule One row mapping a layer-name regex → a discipline (+ optional segment). Carries a scope, a sort order, and an enabled flag.
Scope Where a rule applies: Global (everywhere) → Provider / firm (all projects tied to one drawing-file provider) → Project (one project only).
MISC / FALLBACK When no rule matches a layer, it falls back to MISC. A high share of MISC layers = low coverage.
Firm In the admin copy, "firm" = a DrawingFileProvider. There is always an (unaffiliated) bucket for drawings whose project has no provider.

How a winner is chosen for any layer name (same Python classifier.py the parse worker uses):

  1. Most specific scope wins — Project override beats Firm/Provider beats Global.
  2. Within a scope, lowest sort_order wins (1 = first, 100 = default baseline).
  3. Patterns match by re.search — the pattern can match a substring anywhere in the layer name.
  4. If nothing matches → MISC (FALLBACK).

Rules are append-only: editing a rule never mutates the row — it writes a new version and marks the old one superseded (the old row stays on disk for audit).


Prerequisites

  • Role: logged in as Admin (the two pages live under the sidebar ADMIN & FINANCE section; gated for Admin / Finance / Executive).
  • Data: at least a handful of drawings ingested to READY status. The Coverage dashboard shows "No drawings ingested yet" until drawings reach READY. The Rules admin works with the seeded ruleset even before any drawing exists.
  • A provider with drawings if you want the per-firm coverage rows to be non-empty (otherwise everything lands in the (unaffiliated) bucket).
  • Permissions cached? If you logged in months ago, force a relogin to refresh accountpermissions in localStorage before testing.

1. Open the Layer Classification admin

Sidebar → ADMIN & FINANCELayer Classification (route /admin/layer-classification).

The page has three columns:

  • Left — Providers (firms). Lists every drawing-file provider. Selecting one filters the rules table to that firm's scope. Selecting Global shows only rules with no provider/project scope.
  • Centre — Rules table. All rules in the selected scope, with the pattern, discipline, segment, sort order, and enabled state. Toggle Include disabled to see deactivated rows; Pending drafts shows the misclassification-feedback queue (see §5).
  • Right — Preview classification. Paste any layer name → Run → see which rule wins in the current scope.

Layer Classification admin — providers panel, rules table, live preview

2. Preview how a layer classifies

In the right-hand Preview classification panel, type a layer name (e.g. sm-salpduct, M-HVAC-DUCT-1, or CITY-Duct-Supply) and press Run. The result grid shows the winning Discipline, Segment, Source (RULE / FALLBACK / MANUAL_OVERRIDE), and the Matched rule id + version.

This uses the same classifier.py engine as the real parse worker, so the preview is faithful — it is the fastest way to check whether a layer is covered before touching any drawing.

3. Add a new rule

When a new firm's layer naming shows up unclassified, add a rule. In the Rules table click Add rule (the new rule is created in the currently-selected scope — pick the firm on the left first, or leave Global for an everywhere rule). Fill the modal:

  • Pattern (Python regex) — e.g. (?i)(?:^|[-_|\s])ducts?(?:[-_]|$|\s). Use (?i) for case-insensitive.
  • Discipline — the target trade.
  • Segment (optional) — only for HVAC air layers.
  • Sort order1 wins first, 100 is the default baseline. Lower a rule's sort order to make it win over a broader rule in the same scope.

New classification rule modal

Discipline vocabulary caveat

The standalone New classification rule modal currently offers the legacy long-form discipline names (HVAC / PLUMBING / ELEC / STRUCT / ARCH / MISC) instead of the canonical codes (M / P / E / S / A / FP / MISC) used everywhere else. Rules authored with the long-form names will not roll up into the canonical discipline buckets on the Coverage dashboard or the viewer's colour map. The reliable path that always uses canonical codes is promoting an override from the Coverage dashboard (§6). This is known pre-existing tech debt — tracked as a follow-on (see Known issues).

After saving, the new rule is live for future parses immediately, but existing drawings keep their old classification until you re-run the engine (§7).

4. Open the Classification Coverage dashboard

Sidebar → ADMIN & FINANCEClassification Coverage (route /admin/layer-coverage), or the Coverage dashboard → link in the Rules-admin header.

The dashboard answers "how well is classification working, per firm?":

  • Global summary at the top — total drawings, total layers, and overall MISC %.
  • One row per firm, showing the active rule count, drawing count, MISC %, override %, and last-upload date.
  • Expand a firm to see the discipline breakdown (a stacked bar across M / P / E / S / A / FP / MISC with a legend) and the Top overridden layer names table.

The two health metrics and their colour thresholds:

Metric Meaning Green Amber Red
MISC % Share of layers that matched no rule (the coverage gap) < 15 % 15–30 % ≥ 30 %
Override % Share of layers a user manually corrected (rules lagging reality) < 3 % 3–10 % ≥ 10 %

Classification Coverage dashboard — per-firm rollups, discipline breakdown, top overridden

5. The misclassification feedback queue

There are three ways a layer's classification gets corrected, and they all feed back into the rules library:

  1. A PM/admin flags a layer as misclassified directly in the viewer's LayerPanel → it lands in the Rules-admin Pending drafts queue as a disabled draft rule the admin reviews and promotes.
  2. A user overrides a layer's discipline in the viewer → it surfaces in the Coverage dashboard's Top overridden table (§6).
  3. An admin adds a rule from scratch (§3).

6. Promote a frequent correction into a rule

This is the core loop. In an expanded firm row, the Top overridden layer names table is effectively a rule-authoring backlog — the layer names users keep correcting, with the most common FROM → TO direction. Click → Rule on a row to open Promote override to rule:

  • The layer name and the FROM → TO correction are shown at the top.
  • Pattern — pick Exact (this layer only), Same prefix + keyword (recommended), Keyword only (broad), or Custom.
  • Discipline / Segment / Sort order — pre-filled from the correction (canonical codes here).
  • ScopeGlobal or the specific Provider.
  • Inline preview — test the candidate pattern against a layer name before saving.
  • Reclassify after save — leave checked to immediately re-run the engine over the affected scope.

Click Create rule. The correction is now a permanent rule, and (if the checkbox was on) the affected drawings reclassify in the background.

Promote override to rule modal

7. Reclassify drawings after a rules change

Rule changes do not retroactively touch existing drawings — you must re-run the engine. Both admin pages expose:

  • Reclassify all drawings — re-runs the classifier across every drawing.
  • Reclassify this firm's drawings — scoped to the selected/expanded provider.

Reclassification only re-applies layer classification — it does not regenerate scenes (the rendered geometry is untouched). You get a toast with the enqueued count and a job id; the dashboard metrics refresh shortly after.

8. How an admin keeps coverage high (the short version)

The whole workflow is a watch → act → re-run loop:

  1. Watch the Coverage dashboard. A firm with a high (amber/red) MISC % is usually a new engineer whose layer naming the rules don't catch yet. A high override % means users are repeatedly hand-correcting the same layers.
  2. Act on whichever signal fired:
    • High MISC % → layers aren't matching any rule → add patterns (§3), ideally scoped to that firm.
    • High override % / a populated Top overridden table → the corrections are already telling you the right answer → one-click → Rule to codify them (§6).
    • Anything in the Pending drafts queue → review and promote (§5).
  3. Re-run the classifier for that firm (§7). MISC % and override % drop, and coverage recovers.

Verification Checklist

  • Both pages load under ADMIN & FINANCE for an Admin user; a non-Admin without GetUsers does not see them.
  • Preview classification returns a discipline + matched rule for a known layer name (e.g. sm-salpductM / SUPPLY) and MISC / FALLBACK for a nonsense name.
  • Add rule creates a row visible in the current scope; the preview reflects the new rule.
  • Coverage dashboard shows per-firm rows with MISC % / override % chips colour-coded by threshold.
  • Expanding a firm renders the discipline stacked bar + legend and the Top overridden table.
  • → Rule opens the promote modal pre-filled with the FROM → TO correction; Create rule adds the rule and (with the checkbox) enqueues reclassification.
  • After Reclassify this firm's drawings, the firm's MISC % decreases on refresh.

Known Issues / Current Gaps

  • ⚠ The standalone New classification rule modal (RuleFormModal) offers legacy long-form discipline names (HVAC/PLUMBING/ELEC/STRUCT/ARCH) instead of the canonical M/P/E/S/A/FP/MISC taxonomy. Rules created this way don't roll up into the canonical buckets. Author canonical rules via the Coverage → Rule promote flow until this is unified — pre-existing tech debt surfaced in PIN-365.
  • ⚠ The Coverage discipline legend includes FP, but the seeded ruleset folds fire sprinkler into P, so the FP bucket is effectively always empty today.
  • 🕐 Coverage drilldown is firm-level (expand a row); there is no per-drawing coverage route yet.

Updating this walkthrough

Run the full flow yourself end-to-end. If it passes, bump last_verified: in the frontmatter to today's date. If any step fails, fix the doc or the code in the same PR — drift is the enemy.