diff --git a/fusion_plating/docs/superpowers/specs/2026-04-29-step-library-audit-design.md b/fusion_plating/docs/superpowers/specs/2026-04-29-step-library-audit-design.md new file mode 100644 index 00000000..6b0c20b8 --- /dev/null +++ b/fusion_plating/docs/superpowers/specs/2026-04-29-step-library-audit-design.md @@ -0,0 +1,287 @@ +# Step Library Expansion + Per-Recipe Configurability + Audit Coverage + +**Date:** 2026-04-29 +**Modules:** `fusion_plating` (core), `fusion_plating_jobs`, `fusion_plating_reports` +**Status:** Approved by user, ready for implementation plan + +## Problem + +The plating-shop step library (`fp.step.template`) covers ~60% of a real plating workflow but is missing several common steps (Receiving, Electroclean, Strike, Salt Spray, Adhesion Test, Hardness Test, Packaging, Tank Replenishment). Existing kinds have light default measurement seeding — many industry-standard fields (Bath ID, current density, photo evidence, multi-point thickness) aren't authored by default, so audit-required values often get skipped. + +The recipe author also can't customise step measurements at the recipe level — they can only edit the library template, which affects every recipe that step appears in. There's no per-recipe override for prompts, target ranges, or instructions. + +The office team writes per-step instructions that operators read at runtime; this channel exists but is hidden in a tab and isn't surfaced in the simple recipe editor for inline editing. + +## Goals + +1. Cover the full plating workflow with appropriate Step Kinds and audit-grade default measurements. +2. Add specialised input types (`photo`, `multi_point_thickness`, `bath_chemistry_panel`, `ph`) that match real shop instrumentation. +3. Make every prompt **per-recipe configurable** — recipe authors can disable, rename, retarget, reorder, or add custom prompts on each recipe step without touching the library. +4. Make office→operator instructions visible and editable directly in the simple recipe editor, with library default + per-recipe override. +5. Wire all of the above through to runtime (`fp.job.step` input wizard, tablet QC checklist, CoC chronological report) so recorded values are captured for audit. +6. Battle-test end-to-end after deploy. + +## Non-Goals + +- IoT/sensor auto-fill of measurements (read pH probe / bath chemistry panel directly from instruments) — separate project. +- Customer-portal display of recorded measurements — separate. +- Step Kinds beyond the 8 added here (deburring, anodize seal, abrasive blasting) — same pattern, easy to add later. +- Replacing the existing tablet OWL QC checklist — extend only. +- Per-customer or per-part override of step measurements — separate concern (parts already carry their own recipe variant). + +## Architecture Map + +``` +fp.step.template (library — office authors here) + ├── description (Html, "Default Operator Instructions") + ├── default_kind (Selection, drives DEFAULT_INPUTS_BY_KIND) + ├── input_template_ids → fp.step.template.input + └── transition_input_ids → fp.step.template.transition.input + ↓ [snapshot copy on drag into recipe] +fusion.plating.process.node (recipe step — recipe authors override here) + ├── description (Html, per-recipe override; empty = fall back to library) + ├── collect_measurements (Boolean, master switch — NEW) + └── input_ids → fusion.plating.process.node.input + ├── collect (Boolean, per-prompt opt-out — NEW) + ├── template_input_id (m2o — NEW, traceability for "reset to defaults") + └── (existing fields: name, input_type, target_min/max, target_unit, required, hint, sequence, kind) + ↓ [MO/job creation copies recipe nodes] +fp.job.step (work order step — operator records here) + ↓ [Mark Done → input wizard fires, FILTERED to collect=True only] +fp.job.step.input.wizard.line + ↓ [operator types values → commit] +fp.job.step.move (transfer_type='step', json input data) + ↓ [QWeb render] +report_coc_chronological.xml (audit document — only collect=True inputs) +``` + +## New Step Kinds + +Added to `fp.step.template.default_kind` Selection and `DEFAULT_INPUTS_BY_KIND`: + +| Kind code | Label | Default Operation Measurements | +|---|---|---| +| `receiving` | Receiving / Incoming Inspection | Qty Received, Qty Rejected, Customer PO# Verified (boolean), Packing Slip #, Condition Notes, Damage Photo (photo, optional), Inspector Initials (signature) | +| `electroclean` | Electroclean | Time, Temperature, Amperage (A), Voltage (V), Current Density (ASF), Polarity (selection: anodic/cathodic/periodic), Bath ID | +| `strike` | Strike (Wood's Nickel / Activation) | Time, Temperature, Amperage (A), Voltage (V), Current Density (ASF), Bath ID | +| `salt_spray` | Salt Spray / Corrosion Test | Test Duration (hr), Result (pass_fail), Red Rust %, White Corrosion %, Lab Report (photo) | +| `adhesion_test` | Adhesion Test | Test Method (selection: bend/tape/burnish/file), Result (pass_fail), Photo of Coupon (photo) | +| `hardness_test` | Hardness Test (HV / HK / HRC) | Test Load (gf), Reading 1/2/3 (multi_point_thickness), Average (number), Equipment ID, Last Calibration Date | +| `packaging` | Packaging / Pre-Ship | Packaging Type (selection: VCI bag/bubble wrap/separator paper/custom crate), Qty Per Package, Package Count, Cert Package Included (boolean), Customer-Supplied Packaging (boolean) | +| `replenishment` | Tank Replenishment | Bath ID, Chemistry Added (text), pH Before (ph), pH After (ph), Concentration Before, Concentration After, Operator Initials (signature) | + +## Beefed-Up Defaults on Existing Kinds + +Idempotently appended via `action_seed_default_inputs` (skips if name already present): + +| Existing Kind | New seeded inputs | +|---|---| +| `racking` | Rack ID, Masking Applied (boolean), Photo of Racked Load (photo) | +| `derack` | Mask Removal Method (selection), Residue Check (pass_fail) | +| `mask` | Mask Material (selection: Microshield/latex tape/vinyl plugs/wax), Photo (photo) | +| `demask` | Residue Check (pass_fail), Surface Condition (selection: clean/marks/needs rework) | +| `cleaning` | Bath ID, Ultrasonic On (boolean), Titration Done (boolean) | +| `etch` | Acid Concentration (% or g/L), Bath ID, HE Risk Flag (boolean) | +| `rinse` | Rinse Type (selection: cascade/spray/DI/city), Conductivity µS/cm, Time | +| `plate` | Bath ID, pH (ph), Bath Concentration (g/L), Current Density ASF (electroplate), Multi-Point Thickness (multi_point_thickness) | +| `bake` | Oven ID, Chart Recorder File (photo) | +| `inspect` | Defect Type (selection), Thickness Sample, Photo, Inspector Signature | +| `final_inspect` | Defect Categorization (selection), Dimensional Verification (pass_fail), Surface Finish Ra, Inspector Signature | +| `wbf_test` | Retest Count, Photo on FAIL (photo) | +| `dry` | Dry Method (selection), Time, Temperature | +| `ship` | Carrier (selection), Tracking #, BoL #, Photo of Sealed Shipment | + +## New Input Types + +Added to `fp.step.template.input.input_type`, `fusion.plating.process.node.input.input_type` (the recipe-step input model), and `fp.job.step.input.wizard.line.input_type` (runtime wizard): + +| Type code | Label | Renders as | +|---|---|---| +| `photo` | Photo | Binary widget with `image` rendering — capture or upload one image | +| `multi_point_thickness` | Multi-Point Thickness (avg) | Five number inputs (R1–R5) + auto-computed average; min/max bounds applied to average | +| `bath_chemistry_panel` | Bath Chemistry Panel | Bundle: pH + concentration + temperature + bath ID — saves clicks for plate steps | +| `ph` | pH | Number input clamped to 0–14, two-decimal precision | + +`signature` already exists; will be auto-seeded on more kinds via the new defaults. + +## Per-Recipe Configurability — Model Changes + +| Field | Lives on | Type | Default | Purpose | +|---|---|---|---|---| +| `collect` | `fusion.plating.process.node.input_ids` row | Boolean | `True` | Per-prompt opt-out. Wizard / report filter to `collect=True` only | +| `template_input_id` | same | Many2one to `fp.step.template.input` | (snapshot link) | Traceability — "this prompt was seeded from library template X". Powers "reset to library defaults" | +| `collect_measurements` | `fusion.plating.process.node` | Boolean | `True` | Master switch. When `False`, runtime skips the input wizard entirely for this step | + +Existing fields on `fusion.plating.process.node.input_ids` (`name`, `input_type`, `target_min`, `target_max`, `target_unit`, `required`, `hint`, `sequence`, `selection_options`) are already editable — recipe author can rename / retarget / reorder freely without touching the library. + +## Per-Recipe Configurability — Recipe Author Capabilities + +Each recipe step exposes: + +1. **Disable a prompt** — toggle `collect` off (preserves history; not deleted) +2. **Override target range** — edit `target_min` / `target_max` per recipe +3. **Rename a prompt** — edit `name` +4. **Reorder prompts** — drag handle on `sequence` +5. **Mark required per recipe** — flip `required` independently of library +6. **Add custom prompt** — new row with `template_input_id = False` +7. **Disable entire step's data collection** — master `collect_measurements = False` on the recipe node +8. **Reset to library defaults** — re-sync `input_ids` + `description` from linked library template, preserve custom rows where `template_input_id = False` + +## Operator Instructions (Office → Floor) + +`fp.step.template.description` (Html) — relabel UI to **"Default Operator Instructions"** with tooltip *"Standing instructions the office gives operators for this step. Snapshot-copied onto every recipe that uses this step. Recipe authors can override per recipe."* + +`fusion.plating.process.node.description` (existing, already snapshot-copied) is the per-recipe override. Empty recipe-node `description` falls back to library at runtime so blank means "use library default", not "show nothing". + +Runtime visibility — three places: +1. **Tablet shop-floor card** — instructions panel above input prompts (expandable) +2. **Backend Mark-Done wizard** — same panel at top of input form +3. **Printed traveller / WO sheet** — instructions section under each step heading + +## Simple Recipe Editor — UI Changes + +Each step card gains two expansions: + +``` +┌─ Plate ENP ───────────────────────────────────────┐ +│ Plate ENP @ Tank #3 · 30 min · 180-200°F │ +│ │ +│ ▸ 📋 Instructions (using library default) │ +│ ▸ ⚙ Measurements (5/5 collected) │ +└────────────────────────────────────────────────────┘ +``` + +### Instructions expansion + +- Source toggle: **"Library default"** vs **"Custom for this recipe"** +- Rich-text editor showing current text (library default if recipe override empty) +- **[Use library default]** / **[Save override]** buttons +- Badge on step card: **"📋 default"** vs **"📋 custom"** + +### Measurements expansion + +- Master toggle: **"Collect measurements at this step"** +- Editable list of prompts with: drag handle, collect checkbox, name, input type, target range, unit, required toggle +- **[+ Add custom prompt]** to append a recipe-only prompt +- **[Reset to library defaults]** button +- Badge on step card: **"5/5 collected"** (green), **"3/5 collected"** (amber), **"No measurements"** (grey) + +## "Add Common Audit Fields" One-Click Action + +New button on `fp.step.template` form: `action_add_common_audit_fields`. Idempotently appends: + +- Operator Initials (signature, required) +- Bath ID (text) +- Photo on Failure (photo, optional, hint="upload only if failure observed") +- Equipment ID (text) + +Skip rows whose name already exists. Logs to chatter. + +## Runtime Wiring + +| Component | Change | +|---|---| +| `fp.job.step.input.wizard` `default_get` | Filter `recipe_node.input_ids` to `kind == 'step_input' AND collect == True`. If `recipe_node.collect_measurements == False`, return empty `line_ids`. | +| `fp.job.step.input.wizard.line.input_type` | Mirror the 4 new input types so the wizard form can render per-type widgets | +| `fp.job.step.input.wizard_views.xml` | Conditional widgets per input type (photo → image binary; multi_point_thickness → 5-cell row + avg; bath_chemistry_panel → 4-cell group; ph → number with [0,14]) | +| `fusion_plating_shopfloor` tablet QC checklist OWL | Same per-type rendering on tablet | +| `fp.job.step.move` | No schema change — JSON `input_data` field already accepts arrays/dicts; just confirm encoding round-trips for the new types | +| Snapshot-copy logic (library → recipe node) | Verify `template_input_id` link is set; verify all fields including new types copy verbatim | +| `_fp_recipe_to_job` (MO/job creation) | Verify `collect`, `collect_measurements`, `template_input_id` carry through to `fp.job.step.recipe_node_id` chain | + +## CoC / Audit Report Rendering + +`fusion_plating_reports/views/report_coc_chronological.xml`: + +1. Filter rendered inputs to `collect == True` only. +2. Render branches for new input types: + - `photo` → thumbnail `` from `ir.attachment`, fallback to `[photo: filename]` if attachment missing + - `multi_point_thickness` → `R1: x, R2: y, R3: z, R4: a, R5: b → avg M` + - `bath_chemistry_panel` → 4 labelled rows (pH, conc, temp, bath ID) + - `ph` → `pH X.XX` +3. Skip prompts where the recipe author opted out (`collect=False`) — don't render them as "(not collected)" since auditors only care about what WAS measured. + +## Migration + +Single migration script: `fusion_plating/migrations/19.0.18.7.0/post-migrate.py` + +1. Default `collect=True` on all existing `fusion.plating.process.node.input_ids` rows. +2. Default `collect_measurements=True` on all existing `fusion.plating.process.node` rows. +3. For each existing `fp.step.template`, re-run `action_seed_default_inputs` (idempotent — adds new defaults without clobbering manual edits). +4. Backfill `template_input_id` on recipe-node inputs by name-matching against the linked library template's inputs (best-effort; rows that don't match stay as `False` and become "custom prompts" for purposes of reset-to-defaults). + +Seed data file: `fusion_plating/data/fp_step_template_data.xml` (`noupdate="1"`) — one example template per new Step Kind so users see populated entries on upgrade. + +## Edge Cases + +1. **Recipe node with empty `description`** — runtime renders the linked library template's `description` (fall-through). If both empty, instructions section is hidden in the operator wizard. +2. **Recipe author opts out of all prompts but leaves `collect_measurements=True`** — wizard fires with zero rows; UX gives "no inputs to record, mark done?" confirmation. Same as today's behaviour for kinds with no defaults (rinse, dry, gating). +3. **Photo input on operation measurement (new) vs transition input (existing)** — both kinds always store the image as an `ir.attachment` linked to the `fp.job.step.move` row (never as inline base64 in the `input_data` JSON, to keep the payload small and indexable). Distinguish operation vs transition by `kind` field on the prompt row. CoC report iterates both kinds and renders thumbnails. +4. **Multi-point thickness validation** — at least one reading required when `required=True`; average computed over **non-empty cells only** (empty = "didn't measure that point"); when the computed average falls outside `[target_min, target_max]` the wizard shows a non-blocking warning banner before commit (operator can override and proceed, but the out-of-band condition is recorded on the move for QC review). +5. **Bath chemistry panel — partial fill** — auditor wants to know which fields were captured; all four fields stored in the JSON payload as separate keys. Empty values render as "—" in the CoC. +6. **Reset to library defaults** — preserves rows where `template_input_id = False` (recipe-author-added custom prompts). Re-enables `collect=True` on rows where `template_input_id` is set. Logs to chatter with diff summary. +7. **Library template renamed** — recipe nodes already snapshotted; `template_input_id` link survives the rename. "Reset to defaults" pulls the renamed prompt back. +8. **Library prompt deleted** — recipe nodes' `template_input_id` becomes a dangling reference; reset-to-defaults treats those as "deleted upstream, leave as recipe custom" (don't drop them — recipe author may still want them). +9. **Existing recipes upgrading** — the migration sets `collect=True` everywhere, so behaviour is unchanged for existing data. Only newly-disabled prompts (post-upgrade) suppress at runtime. + +## Battle Test Plan + +Build `fusion_plating/scripts/bt_step_library_audit.py` exercising every change. Same `bt_s*.py` pattern as `fusion_plating_quality/scripts/`. Runs in odoo-shell against the live entech DB after deploy. Reports PASS / FAIL / SKIP per assertion, summary counts at the end. + +| # | Assertion | +|---|---| +| 1 | Every new Step Kind has at least 1 seed template loaded | +| 2 | Every Step Kind (new + existing) yields the expected default inputs after `action_seed_default_inputs` runs on a fresh template | +| 3 | Library → recipe drag snapshot-copies every input field including the 4 new types | +| 4 | `template_input_id` link is set on snapshot-copied inputs | +| 5 | Recipe → job step inheritance preserves `collect`, `collect_measurements`, custom prompts | +| 6 | Operator wizard at runtime filters to `collect=True` only | +| 7 | Master `collect_measurements=False` skips the wizard entirely | +| 8 | Adding a custom prompt to a recipe node (no `template_input_id`) survives reset-to-defaults | +| 9 | Reset-to-defaults re-enables opted-out library prompts and pulls in newly-added library prompts | +| 10 | Each new input type stores and round-trips through `fp.job.step.move.input_data` (JSON) | +| 11 | Multi-point thickness average computed correctly (incl. edge case of 1 reading) | +| 12 | Photo attachment lands in `ir.attachment` and links to the move row | +| 13 | CoC chronological report renders each new input type without errors | +| 14 | CoC excludes `collect=False` rows | +| 15 | Operator instructions render at runtime — recipe override beats library default; empty override falls back to library | +| 16 | "Add Common Audit Fields" one-click is idempotent (run twice, no duplicates) | +| 17 | `action_seed_default_inputs` is idempotent after manual edits (user-edited rows survive re-seed) | +| 18 | `description` on recipe node clears correctly via "Use library default" toggle | + +## Files Touched + +| File | Change | +|---|---| +| `fusion_plating/models/fp_step_template.py` | Extend `default_kind` Selection, extend `DEFAULT_INPUTS_BY_KIND`, add `action_add_common_audit_fields` | +| `fusion_plating/models/fp_step_template_input.py` | Add 4 input types to selection | +| `fusion_plating/models/fp_process_node.py` | Add `collect_measurements` on node; add `collect` + `template_input_id` on inline input model; mirror new input types | +| `fusion_plating/models/fp_process_node_inherit.py` | If applicable for filtering helpers | +| `fusion_plating/views/fp_step_template_views.xml` | Add "Add Common Audit Fields" button; relabel `description` to "Default Operator Instructions" | +| `fusion_plating/data/fp_step_template_data.xml` | NEW — seed templates for the 8 new kinds | +| `fusion_plating/migrations/19.0.18.7.0/post-migrate.py` | NEW — backfill `collect`, `collect_measurements`, `template_input_id`; re-run `action_seed_default_inputs` | +| `fusion_plating/static/src/js/simple_recipe_editor.js` | Render Instructions + Measurements expansions; collect badge; reset-to-defaults action | +| `fusion_plating/static/src/xml/simple_recipe_editor.xml` | Templates for new affordances | +| `fusion_plating/static/src/scss/simple_recipe_editor.scss` | Styles for expansions and badges | +| `fusion_plating/controllers/simple_recipe_controller.py` | New endpoints: `/fp/simple_recipe/step/toggle_collect`, `/fp/simple_recipe/step/edit_input`, `/fp/simple_recipe/step/edit_instructions`, `/fp/simple_recipe/step/reset_to_library` | +| `fusion_plating_jobs/wizards/fp_job_step_input_wizard.py` | Filter to `collect=True` only; mirror new input types | +| `fusion_plating_jobs/wizards/fp_job_step_input_wizard_views.xml` | Conditional widgets per input type | +| `fusion_plating_shopfloor/static/src/...` (tablet QC checklist OWL) | Per-type rendering on tablet | +| `fusion_plating_reports/views/report_coc_chronological.xml` | Render branches for new types; filter to `collect=True` | +| `fusion_plating/scripts/bt_step_library_audit.py` | NEW — battle-test script | +| `fusion_plating/__manifest__.py` | Bump version to `19.0.18.7.0` | +| `fusion_plating_jobs/__manifest__.py` | Bump version | +| `fusion_plating_reports/__manifest__.py` | Bump version | + +## Effort Estimate + +6–8 working days. Roughly 60% backend (models, migrations, wizard filtering, CoC, battle test) and 40% frontend (OWL editor expansions, per-input-type widgets in wizard + tablet). + +## Out of Scope (Defer) + +- IoT/sensor auto-fill of measurements +- Customer portal display of recorded values +- Step Kinds beyond the 8 listed (deburring, anodize seal, abrasive blasting) +- Replacing the existing tablet QC checklist OWL component +- Per-customer or per-part override of step measurements +- Notification/email when audit-required values are missing post-completion