127 lines
5.5 KiB
Python
127 lines
5.5 KiB
Python
"""The department checklists and the Q.C. checks, ported from the old checklistFor(), qcChecksFor() and qcMissing().
|
|||
|
|
|
||
|
|
Weld departments get the welding list, the others the plain list. The drawing, rev and WPS are filled into the text.
|
||
|
|
The keys are the old keys, so a checklist stored as {key: {done, at, by, stamp}} reads the same.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from collections.abc import Iterable, Mapping
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
NO_REV = "-"
|
||
|
|
NDE_NONE = ("", "none", "none required")
|
||
|
|
NDE_OK = ("acceptable", "accept", "accepted")
|
||
|
|
REJECTS = ("reject", "rejected")
|
||
|
|
|
||
|
|
|
||
|
|
def _item(group: str, key: str, text: str) -> dict[str, str]:
|
||
|
|
return {"group": group, "key": key, "text": text}
|
||
|
|
|
||
|
|
|
||
|
|
def checklist_for(weld: bool, drawing: str | None, rev: str | None, wps: str | None) -> list[dict[str, str]]:
|
||
|
|
"""The stage checklist for a department, as a flat list of {group, key, text} in display order."""
|
||
|
|
dwg = f"Drawing {drawing or ''} Rev {rev or NO_REV} matches the packet".replace(" ", " ")
|
||
|
|
if not weld:
|
||
|
|
g1, g2 = "Before starting", "When done"
|
||
|
|
return [
|
||
|
|
_item(g1, "dwg", dwg),
|
||
|
|
_item(g1, "prev", "Previous department's work complete and released"),
|
||
|
|
_item(g1, "mat", "Material and parts on hand match the packet"),
|
||
|
|
_item(g2, "work", "Work completed per drawing and procedure"),
|
||
|
|
_item(g2, "insp", "Self-inspection done; no damage to finished surfaces"),
|
||
|
|
_item(g2, "qty", "Completed quantity matches each work order"),
|
||
|
|
]
|
||
|
|
g1, g2 = "Before welding", "After welding"
|
||
|
|
return [
|
||
|
|
_item(g1, "dwg", dwg),
|
||
|
|
_item(g1, "heat", "Heat numbers on material match the Materials tab"),
|
||
|
|
_item(g1, "wps", f"WPS reviewed: {wps or 'per drawing'}"),
|
||
|
|
_item(g1, "fit", "Fit-up and dimensions checked against the drawing"),
|
||
|
|
_item(g1, "pre", "Preheat and interpass temperature per WPS"),
|
||
|
|
_item(g2, "vt", "Visual inspection of all welds acceptable"),
|
||
|
|
_item(g2, "stamp", "Welds stamped with welder ID"),
|
||
|
|
_item(g2, "clean", "Slag and spatter removed"),
|
||
|
|
_item(g2, "qty", "Completed quantity matches each work order"),
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
def qc_items_for(
|
||
|
|
drawing: str | None, rev: str | None, wps: str | None, welder_stamp: str | None
|
||
|
|
) -> list[dict[str, str]]:
|
||
|
|
"""The Q.C. inspection items, as a flat list of {group, key, text}."""
|
||
|
|
g1, g2 = "Data book package", "Physical spool"
|
||
|
|
stamp = f"[{welder_stamp}] " if welder_stamp else ""
|
||
|
|
return [
|
||
|
|
_item(g1, "db_heats", "Heat number recorded for every BOM item"),
|
||
|
|
_item(g1, "db_mtr", "MTRs on file and match the heat numbers"),
|
||
|
|
_item(g1, "db_weldmap", "Weld map complete: every weld identified"),
|
||
|
|
_item(g1, "db_wps", f"Welds made to {wps or 'the WPS'}; welder {stamp}qualified"),
|
||
|
|
_item(g1, "db_ck", "Stall checklist and weld record complete"),
|
||
|
|
_item(g2, "ph_dim", f"Dimensions verified against {drawing or 'the drawing'} Rev {rev or NO_REV}"),
|
||
|
|
_item(g2, "ph_heat", "Heat numbers on material match the heat map"),
|
||
|
|
_item(g2, "ph_stamp", "Welds stamped with welder ID"),
|
||
|
|
_item(g2, "ph_clean", "Clean: no slag, spatter or arc strikes"),
|
||
|
|
_item(g2, "ph_qty", "Quantity matches the work orders"),
|
||
|
|
]
|
||
|
|
|
||
|
|
|
||
|
|
def checklist_keys(weld: bool) -> list[str]:
|
||
|
|
return [i["key"] for i in checklist_for(weld, None, None, None)]
|
||
|
|
|
||
|
|
|
||
|
|
def qc_keys() -> list[str]:
|
||
|
|
return [i["key"] for i in qc_items_for(None, None, None, None)]
|
||
|
|
|
||
|
|
|
||
|
|
def is_done(value: Any) -> bool:
|
||
|
|
"""A checklist or Q.C. check entry: `true`, or `{"done": true, ...}`."""
|
||
|
|
if value is True:
|
||
|
|
return True
|
||
|
|
return isinstance(value, Mapping) and value.get("done") is True
|
||
|
|
|
||
|
|
|
||
|
|
def checklist_complete(checklist: Mapping[str, Any] | None, weld: bool) -> bool:
|
||
|
|
"""Every key of the department checklist is done. An empty list counts as complete."""
|
||
|
|
ck = checklist or {}
|
||
|
|
return all(is_done(ck.get(k)) for k in checklist_keys(weld))
|
||
|
|
|
||
|
|
|
||
|
|
def _plural(n: int, word: str) -> str:
|
||
|
|
return f"{n} {word}{'s' if n > 1 else ''}"
|
||
|
|
|
||
|
|
|
||
|
|
def qc_missing(
|
||
|
|
packet: Mapping[str, Any], qc: Mapping[str, Any] | None, welds: Iterable[Mapping[str, Any]]
|
||
|
|
) -> list[str]:
|
||
|
|
"""What Q.C. still has to record before an accept. The old qcMissing().
|
||
|
|
|
||
|
|
`packet` is unused today (the item list does not depend on it for the keys) but kept for the old signature.
|
||
|
|
`qc` is the open inspection row (checks, weld_exam, nde_method, nde_result, inspector) or None.
|
||
|
|
`welds` are the live weld rows (weld_no).
|
||
|
|
"""
|
||
|
|
del packet
|
||
|
|
q = qc or {}
|
||
|
|
checks = q.get("checks") or {}
|
||
|
|
missing: list[str] = []
|
||
|
|
open_items = sum(1 for k in qc_keys() if not is_done(checks.get(k)))
|
||
|
|
if open_items:
|
||
|
|
missing.append(_plural(open_items, "inspection item"))
|
||
|
|
exam = q.get("weld_exam") or {}
|
||
|
|
results = []
|
||
|
|
for w in welds:
|
||
|
|
entry = exam.get(str(w.get("weld_no")))
|
||
|
|
result = entry.get("result") if isinstance(entry, Mapping) else None
|
||
|
|
results.append(str(result).lower() if result else "")
|
||
|
|
no_result = sum(1 for r in results if not r)
|
||
|
|
if no_result:
|
||
|
|
missing.append(_plural(no_result, "weld") + " without a visual result")
|
||
|
|
if any(r in REJECTS for r in results):
|
||
|
|
missing.append("a rejected weld (reject the packet instead)")
|
||
|
|
method = str(q.get("nde_method") or "").strip().lower()
|
||
|
|
if method not in NDE_NONE and str(q.get("nde_result") or "").strip().lower() not in NDE_OK:
|
||
|
|
missing.append("NDE result")
|
||
|
|
if not str(q.get("inspector") or "").strip():
|
||
|
|
missing.append("inspector name")
|
||
|
|
return missing
|