Files
Odoo-Modules/fusion_plating/docs/superpowers/specs/2026-04-29-step-library-audit-design.md
gsinghpal 9401afb21d spec(step-library): full plating workflow coverage + per-recipe configurability + audit
Adds 8 new Step Kinds (Receiving, Electroclean, Strike, Salt Spray,
Adhesion Test, Hardness Test, Packaging, Replenishment) with industry-
standard default measurements. Adds 4 new input types (photo,
multi_point_thickness, bath_chemistry_panel, ph). Beefs up existing
kinds (cleaning, etch, plate, bake, ship, etc.) with bath ID, photos,
multi-point thickness, signatures.

Per-recipe configurability: each recipe step can disable, rename,
retarget, reorder prompts; add custom prompts; toggle entire-step
data collection. Library is the smart default; recipe is final say.

Office-to-operator instructions: relabel as Default Operator
Instructions in the library; per-recipe override surfaced in the
simple recipe editor; falls back to library default at runtime when
recipe override is empty.

Battle test plan covers 18 assertions end-to-end on entech.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 21:48:12 -04:00

288 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 (R1R5) + 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 014, 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 `<img>` 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
68 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