Safety class — drawing vs BOM#
Safety class classifies a part for downstream QA: Structural (ST,
traceability + NDT) or Non Structural (NS, lighter QA). It appears in
two places that must agree: the drawing’s title block (a cell like
SAFETY CLASS ST ACC TO EP04-06) and the BOM metadata (a Safety
Class column). This demo extracts both, normalizes abbreviations
(ST ↔ Structural), and flags a mismatch — a wrong safety class routes
the part through the wrong inspection workflow.
Input drawing#
Sheet 1 of support_assembly.json — note the title block (cartouche).
How the check works#
Detect the title block with SheetTableDetector, pull the safety-class
token from the SAFETY CLASS … cell, read the BOM Safety Class value,
normalize both, and compare.
"""Demo: Safety Class — Drawing title block vs BOM metadata.
Extracts the safety-class token from the title block (`SheetTableDetector`),
reads the `Safety Class` value from the BOM metadata, normalizes both and
compares, then writes (into the current working directory):
- ``safety_class_report.md`` — Markdown verification report
- ``safety_class_check.html`` — annotated drawing (frame + arrow)
"""
from pathlib import Path
from drawing_tools.sheet.featured_sheet import FeaturedSheet
from ..shared import read_bom_safety_class, write_report
from ..viz_helpers import load_sheet, render_html
from .build_visualization import DATA_DIR, DRAWING_JSON, OUT_HTML, build_extras, locate_safety_class
BOM_XLSX = DATA_DIR / "bom_support_assembly_v8.xlsx"
#: relative → written to the current working directory.
REPORT_MD = Path("safety_class_report.md")
def normalize(value: str) -> str:
"""Normalize a safety-class value to ``STRUCTURAL`` / ``NON STRUCTURAL``."""
normalized = value.strip().upper().replace("-", " ").replace("_", " ")
if normalized in {"ST", "STRUCTURAL"}:
return "STRUCTURAL"
if normalized in {"NS", "NON STRUCTURAL"}:
return "NON STRUCTURAL"
return normalized
def drawing_safety_class(sheet: FeaturedSheet) -> str:
"""Return the safety-class token (``ST``/``NS``) from the drawing's title block.
Delegates to :func:`build_visualization.locate_safety_class` — the single
detection + matching strategy shared with the visualization — so report and
drawing can never disagree on which token was read.
"""
token, _text_piece, _title_block = locate_safety_class(sheet)
return token
def build_report_md(drawing_raw: str, bom_raw: str, status: str) -> str:
"""Render the Markdown source-values + check-result tables."""
sources = (
"### Source values\n\n"
"| Source | Raw value | Normalized |\n|--------|-----------|------------|\n"
f"| Drawing title block | {drawing_raw} | {normalize(drawing_raw)} |\n"
f"| BOM metadata | {bom_raw} | {normalize(bom_raw)} |"
)
check = (
"### Check result\n\n"
"| Check | Status |\n|-------|--------|\n"
f"| safety_class | {'🟢 OK' if status == 'OK' else '🔴 MISMATCH'} |"
)
ok = 1 if status == "OK" else 0
summary = f"**Summary.** 1 check · {ok} OK · {1 - ok} MISMATCH · pass rate {100 * ok:.0f}%."
return "\n\n".join([sources, check, summary])
def main() -> None:
"""Run the safety-class check, write the report and the visualization."""
sheet = load_sheet(DRAWING_JSON, sheet_index=1)
drawing_raw = drawing_safety_class(sheet)
bom_raw = read_bom_safety_class(BOM_XLSX)
status = "OK" if normalize(drawing_raw) == normalize(bom_raw) else "MISMATCH"
print(f"Drawing safety class : {drawing_raw} ({normalize(drawing_raw)})")
print(f"BOM safety class : {bom_raw} ({normalize(bom_raw)})")
print(f"Status : {status}")
write_report(REPORT_MD, "Safety Class Verification Report", [build_report_md(drawing_raw, bom_raw, status)])
render_html(sheet, build_extras(sheet, bom_value=bom_raw, status=status), OUT_HTML)
if __name__ == "__main__":
main()
Result#
Source |
Raw value |
Normalized |
|---|---|---|
Drawing title block |
ST |
STRUCTURAL |
BOM metadata |
Non Structural |
NON STRUCTURAL |
1 check · 0 OK · 1 MISMATCH · pass rate 0%. The drawing says ST
(Structural) but the BOM says Non Structural. The visualization frames the
ST token red and points an arrow from the explanatory labels to the cell.