Balloon quantities — BOM QTY check#
The effective quantity a balloon designates is not “one balloon = one
part”: it is multiplicator × repetition × effective_leader_count (each
None → 1), and the drawing total for a tag is the sum over its
balloons. drawing_tools exposes all three attributes on every
Balloon. This demo verifies that the drawing’s effective quantity per tag
agrees with the BOM QTY column — BOM quantities drive procurement and
kitting, so drift starves or inflates the assembly line.
Input drawing#
Sheet 1 of support_assembly.json.
How the check works#
Accumulate true_qty per tag over all balloons, then compare against the
BOM QTY column.
"""Demo: BOM vs Balloon Quantity verification.
Computes each tag's effective drawing quantity
(``multiplicator * repetition * effective_leader_count`` summed over balloons)
and compares it against the BOM ``QTY`` column, then writes (into the current
working directory):
- ``balloon_quantities_report.md`` — Markdown verification report
- ``bom_vs_balloon_qty.html`` — annotated drawing (green=OK, red=MISMATCH)
"""
from pathlib import Path
from drawing_tools.sheet.featured_sheet import FeaturedSheet
from ..shared import read_bom_qty_by_tag, write_report
from ..viz_helpers import load_sheet, render_html
from . import build_visualization
from .build_visualization import BOM_XLSX, DRAWING_JSON, OUT_HTML
#: relative → written to the current working directory.
REPORT_MD = Path("balloon_quantities_report.md")
def compare(sheet: FeaturedSheet, expected_by_tag: dict[str, int]) -> list[tuple[str, int, int | None, str]]:
"""Return sorted (tag, bom_qty, drawing_qty, status) for every BOM tag.
A BOM tag with no balloon on the sheet gets status ``MISSING`` (drawing qty
``None``) — conceptually distinct from a quantity ``MISMATCH``.
"""
drawing_qty = build_visualization.drawing_qty_by_tag(sheet)
rows = []
for tag, expected in expected_by_tag.items():
actual = drawing_qty.get(tag)
if actual is None:
status = "MISSING"
elif actual == expected:
status = "OK"
else:
status = "MISMATCH"
rows.append((tag, expected, actual, status))
rows.sort(key=lambda row: row[0])
return rows
def build_report_md(rows: list[tuple[str, int, int | None, str]]) -> str:
"""Render the Markdown metrics + per-tag table for the quantity report."""
total = len(rows)
ok = sum(row[3] == "OK" for row in rows)
mismatch = sum(row[3] == "MISMATCH" for row in rows)
missing = sum(row[3] == "MISSING" for row in rows)
rate = 100 * ok / total if total else 0.0
metrics = (
"| Metric | Value |\n|--------|-------|\n"
f"| Total checked | {total} |\n| OK | {ok} |\n| MISMATCH | {mismatch} |\n| MISSING | {missing} |\n"
f"| **Pass rate** | **{rate:.1f}%** |"
)
header = "| | TAG | BOM QTY | Drawing QTY | Status |\n|---|-----|---------|-------------|--------|"
body = "\n".join(
f"| {'🟢' if status == 'OK' else '🔴'} | {tag} | {expected} | {actual if actual is not None else '—'} | {status} |"
for tag, expected, actual, status in rows
)
return "\n\n".join([metrics, header + "\n" + body])
def main() -> None:
"""Run the quantity check, write the report and the visualization."""
sheet = load_sheet(DRAWING_JSON, sheet_index=1)
expected_by_tag = read_bom_qty_by_tag(BOM_XLSX)
rows = compare(sheet, expected_by_tag)
for tag, expected, actual, status in rows:
print(f"TAG {tag}: BOM={expected} drawing={actual if actual is not None else '—'} -> {status}")
ok = sum(row[3] == "OK" for row in rows)
mismatch = sum(row[3] == "MISMATCH" for row in rows)
missing = sum(row[3] == "MISSING" for row in rows)
rate = 100 * ok / len(rows) if rows else 0.0
print(f"\n{len(rows)} checked | {ok} OK | {mismatch} MISMATCH | {missing} MISSING | pass rate {rate:.1f}%")
write_report(REPORT_MD, "Quantity Verification Report", [build_report_md(rows)])
render_html(sheet, build_visualization.build_extras(sheet, expected_by_tag), OUT_HTML)
if __name__ == "__main__":
main()
Result#
Metric |
Value |
|---|---|
Total checked |
9 |
OK |
7 |
MISMATCH |
2 |
Pass rate |
77.8% |
Mismatches: 102 (BOM 3, drawing 2) and 401 (BOM 1, drawing 2) — both
exercise repetition=2 with a single leader. Tags 104/400/402 exercise
effective_leader_count=2 × repetition=2 → 4 (OK). The visualization
boxes each balloon green/red and labels the BOM-expected quantity under each
mismatch.