Files
PacketStation/server/packetstation/domain/checklists.py
T

127 lines
5.5 KiB
Python
Raw Normal View History

"""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