Three-part design (12a/12b/12c) for adding a flat drag-drop recipe editor alongside the existing tree editor, with a reusable step library, Steelhead-style Move Parts/Rack/Stop-Timer dialogs, and recipe-order + chronological CoC PDF reports. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
974 lines
54 KiB
Markdown
974 lines
54 KiB
Markdown
# Steelhead "Move Parts" Screen Inventory — Simple Recipe Editor
|
||
|
||
Working notes captured during brainstorming. Each screenshot the user
|
||
provides is logged here so we can fold the field requirements into the
|
||
final design. Do NOT lose these notes — they drive the data shape on
|
||
`fp.step.template.transition.input` and the eventual transition dialog
|
||
on the tablet.
|
||
|
||
## Common dialog header (all screens)
|
||
|
||
- **Title bar**: "Move Parts"
|
||
- **Cancel** + red **MOVE (n)** button at footer (n = part count being moved)
|
||
|
||
## Screenshot 1 — node→node move (no station)
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Part Count | Integer (with stepper) | 1 | "Available: 1" hint shown beneath the input |
|
||
| Part Number | Read-only link | TEST225451 | resolves back to part record |
|
||
| From Node | Read-only link | Contract Review | current node operator is leaving |
|
||
| Transfer Type | Selection | Step | (other values likely: Step / Hold / Scrap / Return — TBD as more screens come in) |
|
||
| To Node | Read-only link | Ready for Incoming Inspection | destination node |
|
||
| To Location | Selection + camera icon | Global | location picker; camera icon = take photo evidence inline |
|
||
| Number of Customer WOs | Char | (blank) | optional; only present on this screen |
|
||
| Billed Labor | section with timer icon + "Reset All Edits" button | — | per-operator timer breakdown follows |
|
||
| **Per-operator labor row** | composite | Kris Pathinather, Timer Duration: 56s (100.0% billed), WO #4521 PN: TEST225451 Qty: 1, hrs/min/sec edit fields | reconcilable timer vs. billed split; the small icon top-right of the row appears to be "edit notes" |
|
||
|
||
## Screenshot 2 — node→node move with station picker (between two real shop steps)
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Part Count | Integer (stepper) | 49 | Available: 49 |
|
||
| Part Number | Read-only link | TEST225451 | |
|
||
| From Node | Read-only link | Ready for Incoming Inspection | |
|
||
| Transfer Type | Selection | Step | |
|
||
| To Node | Read-only link | Incoming Inspection | |
|
||
| **To Station** | Selection | Incoming Inspection | NEW field on this variant — appears when destination node has multiple stations to choose from. Defaults to a sensible value but is editable. |
|
||
| To Location | Selection + camera | Global | |
|
||
| Billed Labor | section | — | (not expanded on this screen — implies optional / collapsed by default when timer hasn't accrued) |
|
||
|
||
## Screenshot 3 — second node→node move with From Station
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Part Count | Integer (stepper) | 49 | Available: 49 |
|
||
| Part Number | Read-only link | TEST225451 | |
|
||
| From Node | Read-only link | Incoming Inspection | |
|
||
| **From Station** | Read-only link | Incoming Inspection | NEW — appears when the source node had a station selected previously |
|
||
| Transfer Type | Selection | Step | |
|
||
| To Node | Read-only link | Adhesion Test Coupon | |
|
||
| To Location | Selection + camera | Global | |
|
||
| Billed Labor | section with "Reset All Edits" button | — | |
|
||
| Per-operator labor row | composite | Kris Pathinather, Timer Duration: 4s (100.0% billed), WO #4521 PN: TEST225451 Qty: 49, hrs/min/sec edit fields | same shape as screenshot 1 |
|
||
|
||
## Patterns emerging across screenshots so far
|
||
|
||
1. **Variable field set per transition.** Some moves show "Number of Customer WOs", some don't. Some show "To Station" / "From Station", some don't. The recipe author needs to be able to declare which fields appear on which transitions — that's what `fp.step.template.transition.input` is for.
|
||
|
||
2. **Read-only context fields are always present** (Part Count, Part Number, From Node, To Node, From/To Station when applicable, To Location, Transfer Type). These are *system-derived* — the recipe author doesn't author them, they come from the runtime context. Our model only needs to capture the *author-defined* prompts (extra compliance fields).
|
||
|
||
3. **Camera icon next to To Location** = inline photo capture, attached to the transition log. Implies the runtime needs a `photo` input type that can either upload a file or trigger device camera (mobile / tablet).
|
||
|
||
4. **Billed Labor is a separate concern** — it's a labor reconciliation widget, not a recipe-defined input. Operator can edit hrs/min/sec to reconcile timer vs. actual time billed. Per-operator row with Timer Duration + "100.0% billed" indicator. "Reset All Edits" button reverts all manual reconciliations to timer values. This is its own sub-system; goes outside `fp.step.template` (it's a runtime feature, not authored on a recipe).
|
||
|
||
5. **Transfer Type values seen so far**: `Step`. More variants expected (Hold, Scrap, Return, Rework). Each variant likely has its own required field subset.
|
||
|
||
## Screenshot 4 — Adhesion Test Coupon → Ready for racking (no station, no labor block)
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Part Count | Integer (stepper) | 49 | Available: 49 |
|
||
| Part Number | Read-only link | TEST225451 | |
|
||
| From Node | Read-only link | Adhesion Test Coupon | |
|
||
| Transfer Type | Selection | Step | |
|
||
| To Node | Read-only link | Ready for racking | |
|
||
| To Location | Selection + camera | Global | |
|
||
| Billed Labor | section icon only | — | not expanded — implies no timer accrued on this leg |
|
||
|
||
Pattern: short transitions between QA-style intermediate nodes don't
|
||
expand the labor reconciliation panel. Labor block expands only when a
|
||
non-zero timer has accrued OR when an operator has a row to reconcile.
|
||
|
||
## Screenshot 5 — Ready for racking → Racking (no station, no labor)
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Part Count | Integer (stepper) | 49 | Available: 49 |
|
||
| Part Number | Read-only link | TEST225451 | |
|
||
| From Node | Read-only link | Ready for racking | |
|
||
| Transfer Type | Selection | Step | |
|
||
| To Node | Read-only link | Racking | |
|
||
| To Location | Selection + camera | Global | |
|
||
| Billed Labor | section icon only | — | collapsed |
|
||
|
||
Pattern: same as screenshot 4. "Ready for X" nodes are gating-only
|
||
(parts wait there) — moving past one accrues no measurable labor.
|
||
|
||
## Screenshot 6 — Racking → Ready For Plating + amber **rack-required warning**
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Part Count | Integer (stepper) | 49 | Available: 49 |
|
||
| Part Number | Read-only link | TEST225451 | |
|
||
| From Node | Read-only link | Racking | |
|
||
| Transfer Type | Selection | Step | |
|
||
| To Node | Read-only link | Ready For Plating | |
|
||
| **To Station** | Selection | SP Line | destination has multiple plating lines; SP Line is the auto-pick |
|
||
| To Location | Selection + camera | Global | |
|
||
| **WARNING BLOCK** (amber, with ⚠ icon) | banner | "Parts are currently at a RACKING node and are not racked." | Soft block — informs operator the parts haven't been associated with a physical rack yet |
|
||
| Billed Labor | section icon only | — | collapsed |
|
||
| Footer | THREE buttons | Cancel · **RACK PARTS** (red secondary) · **MOVE (49)** (red primary) | RACK PARTS opens a separate Rack Parts dialog (screenshots 7–8) |
|
||
|
||
**Critical pattern**: a node's **type** (e.g. Racking) can require
|
||
auxiliary records (rack assignment) BEFORE the move is allowed. The UI
|
||
warns but doesn't hard-block — operator can still hit MOVE if they
|
||
choose, presumably escalating an audit flag. RACK PARTS is the
|
||
"resolve the warning" path.
|
||
|
||
This implies the recipe author needs a way to declare:
|
||
> "This step's destination requires a rack assignment before move." —
|
||
> a node-type-level rule, not a transition-input rule.
|
||
|
||
→ NEW field on `fp.step.template` and `fusion.plating.process.node`:
|
||
`requires_rack_assignment` Boolean. When True and the operator hasn't
|
||
linked a rack to this batch of parts yet, the tablet shows the amber
|
||
warning + RACK PARTS button.
|
||
|
||
## Screenshot 7 — Rack Parts dialog (overlay on top of Move Parts)
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Title | "Rack Parts" with QR-scanner icon top-right | — | scanner icon = scan rack QR to auto-fill To Rack |
|
||
| To Rack | Search-and-select dropdown | (blank → "Search Racks…") | M2O picker against a rack registry |
|
||
| Per-line row | composite | TEST225451 on WO 4521 (49) — Unit: **Count** dropdown — Amount: **49** | shows the part being racked + qty in the chosen unit |
|
||
| Unit | Selection | Count | (other values likely: Count / Pieces / Lbs / Kg / Sheets — TBD) |
|
||
| Amount | Number | 49 | usually = Part Count from Move Parts but editable |
|
||
| Billed Labor | section icon only | — | collapsed; same widget as Move Parts |
|
||
| Footer | THREE buttons | Cancel · **SAVE** (disabled until To Rack set) · **SAVE + PRINT** (disabled until To Rack set) | SAVE + PRINT prints rack travel ticket / barcode |
|
||
|
||
## Screenshot 8 — Rack Parts dropdown expanded
|
||
|
||
| Element | Notes |
|
||
|---|---|
|
||
| Searchable dropdown | Free-text "Search Racks…" filters list |
|
||
| Shows list of named racks: Rack 3, Rack 4, Rack 5, Rack 6, Rack 7, Rack 9, Rack 11 ... | Gaps in numbering (no Rack 8, no Rack 10) imply **active filter** — dropdown only shows racks currently empty / available. Numbers persist; full list is sparser than the index range. |
|
||
| Highlighted on hover | Rack 3 shown highlighted, indicating standard combobox UX |
|
||
|
||
**Implication for our model**: a `fp.rack` registry already has the
|
||
shape we need (`name`, `active`, `state` for empty/in-use). Need a
|
||
M2O on the racking transition log: `rack_id`. Selection is filtered
|
||
to `state='empty'` by default, with an override to show all.
|
||
|
||
The QR scanner icon implies a scan-to-fill flow on the tablet — same
|
||
mechanism we already use elsewhere (`/fp/shopfloor/scan` endpoint
|
||
resolves a scanned QR to a tank/job/etc.). For racks, we'd extend the
|
||
scan endpoint with a `fp-rack:<id>` token resolver.
|
||
|
||
## Patterns updated after screens 4–8
|
||
|
||
6. **Three transition-time prompt families now visible**:
|
||
- **Author-defined compliance prompts** (per-step on
|
||
`fp.step.template.transition.input`) — variable per step.
|
||
- **Always-on context fields** (Part Count, From/To Node, etc.) —
|
||
system-derived, not authored.
|
||
- **Step-type-driven side dialogs** — declared by Boolean flags on
|
||
the step (e.g. `requires_rack_assignment`) — open a separate
|
||
mini-dialog (Rack Parts) before the main move can complete.
|
||
Other likely flags: `requires_bake_window`, `requires_qc_check`,
|
||
`requires_signature`. Each maps to an existing or new sub-dialog.
|
||
|
||
7. **Move button label reflects qty**: `MOVE (49)` vs `MOVE (1)`.
|
||
Operator confidence cue — they see exactly how many parts they're
|
||
committing to move before tapping.
|
||
|
||
8. **Soft-block vs hard-block UI language**:
|
||
- **Amber warning + clickable resolution button** = soft block
|
||
(operator may proceed with audit log).
|
||
- **Red error + disabled MOVE** = hard block (operator cannot
|
||
proceed). Not yet seen in any screenshot, but pattern is implied
|
||
by the amber design.
|
||
|
||
## Screenshot 9 — Compact part-row card (paused timer)
|
||
|
||
| Element | Notes |
|
||
|---|---|
|
||
| Checkbox (left) | bulk-select for cross-row actions |
|
||
| Red **paused** icon (∥∥) | visual cue: timer is currently paused on this row |
|
||
| Inline summary | "1 TEST225451 \| Rack 3 \| Racking" — Qty + part link + rack link + node link, all clickable navigations |
|
||
| Sub-line | "Kris Pathinather: 27s" — operator name + accrued timer duration on this row |
|
||
|
||
Pattern: this is a Plant Overview / Tablet row — a **per-batch
|
||
position card** showing a single chunk of parts at a single node, with
|
||
the operator who currently owns its labor timer + its rack assignment
|
||
inline. Clicking any blue link drills into that record.
|
||
|
||
## Screenshot 10 — Stop User Labor Timer dialog
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Title | "Stop User Labor Timer" | — | Distinct dialog from Move Parts; fires when operator pauses without moving |
|
||
| Billed Labor | section | Kris Pathinather, Timer Duration: 32s (100.0% billed) | with **RESET ALL EDITS** button |
|
||
| Per-row | composite | "WO #4521 PN: TEST225451 Qty: 1" + Product Select + hrs/min/sec inputs | NEW: **Product** dropdown — operator can split timer time onto multiple products. Defaults to current PN. |
|
||
| Footer | THREE buttons | Cancel · **SAVE** · **SAVE & START NEW TIMER** | Distinguished from Move Parts which is Cancel/MOVE — labor reconciliation is its own action |
|
||
|
||
**Critical insight**: labor reconciliation is a **standalone flow**,
|
||
not embedded in Move Parts. Operator can stop the timer without moving
|
||
parts. This implies our model needs:
|
||
- A persistent **labor timer record** (`fp.labor.timer`?) per
|
||
(operator × WO × part × node), independent of the move log.
|
||
- A **labor reconciliation** action that closes a timer with a billed
|
||
hrs/min/sec breakdown, optionally splitting across multiple products.
|
||
|
||
## Screenshot 11 — Move Rack: tyut (full-rack move dialog)
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Title | "Move Rack: tyut" | — | rack name (here "tyut") in the title — ties move to a specific rack record |
|
||
| **Rack Labels** | M2M with `+` button | (empty) | tag rack with shop labels (priority colours, customer codes, etc.) |
|
||
| **Parts** section | static list of contained parts | "49 TEST225451 Parts on WO 4521" + "1 TEST225451 Parts on WO 4521" | shows ALL parts on the rack at once — moving a rack moves everything on it as one unit |
|
||
| Type | Selection | Step | same as Move Parts |
|
||
| **To Node** | Selection (read-only here, "Soak Clean (SP-1)") | Soak Clean (SP-1) | greyed-out — auto-derived from current step's recipe path |
|
||
| **To Station** | Selection | Soak Clean (SP-1) | editable; defaults to first compatible station |
|
||
| Billed Labor | section + RESET ALL EDITS | — | per-batch row breakdown: each contained part qty gets its own hrs/min/sec inputs |
|
||
| Per-row | composite (×2 in this rack) | "WO #4521 PN: TEST225451 Qty: 49" → 0/0/6 + "WO #4521 PN: TEST225451 Qty: 1" → 0/0/0 | individual labor split per chunk on the rack |
|
||
| Footer | TWO buttons | Cancel · **SAVE** | no separate MOVE button — SAVE commits the move |
|
||
|
||
**Critical pattern**: Move Rack is a **rack-as-unit move**, distinct
|
||
from Move Parts (parts-as-unit). When parts are racked, they MUST move
|
||
as a rack — Steelhead enforces this by not surfacing the per-part Move
|
||
button (see screenshot 12: those buttons are disabled/greyed when
|
||
racked). The recipe author doesn't author move-rack rules; they're
|
||
runtime-enforced based on whether the parts are currently on a rack.
|
||
|
||
## Screenshot 12 — Plant overview: Racks vs Parts panes (rack collapsed UI)
|
||
|
||
| Section | Notes |
|
||
|---|---|
|
||
| **Racks** header | with red "UNRACK MULTIPLE" button — bulk unrack action |
|
||
| Rack row | red ∥∥ pause icon · "50 Parts" · rack name "tyut" (Rack 3) · current node breadcrumb "Soak Clean (SP-1) / Soak Clean (SP-1)" · "Kris Pathinather: 14s" labor stamp · **MOVE RACK** primary button on the right |
|
||
| **Parts** header | with `+ ADD NEW PARTS` button + filters (Part Number ▾, Part Account ▾) + search box "Search by PN or group" |
|
||
| Parts row(s) | qty + PN + rack + node breadcrumb + operator/timer + per-row buttons: **MOVE PARTS** (greyed out / disabled because racked) + QR icon + ribbon icon + ⋮ kebab + ⌄ expand |
|
||
|
||
**Critical UI rule emerging**: when parts are racked, **the per-part
|
||
Move Parts button greys out**. The only way to move racked parts is
|
||
via **MOVE RACK**. This is enforced by the disabled button state, not
|
||
by error message. Operator UX is: "You can't accidentally move just
|
||
some of these parts — they're racked together, move the rack."
|
||
|
||
This implies a runtime guard on `fp.job.step` (or wherever the move
|
||
controller lives): if `rack_id` is set on the part-batch, reject
|
||
move-parts calls and require move-rack.
|
||
|
||
## Screenshot 13 — Move Rack: tyut (different destination, station picker shows non-default station)
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Title | "Move Rack: tyut" | — | same dialog shape as screenshot 11 |
|
||
| Rack Labels | M2M + button | (empty) | |
|
||
| Parts list | static | "49 TEST225451 Parts on WO 4521" + "1 TEST225451 Parts on WO 4521" | same contents as 11 |
|
||
| Type | Selection | Step | |
|
||
| To Node | Selection (read-only) | Rinse (SP-2) | rack has advanced one step from screenshot 11 (Soak Clean → Rinse) |
|
||
| **To Station** | Selection | "Cold Water Rinse …" (truncated) | NEW: shows that one node can have **multiple stations** with descriptive names, not just SP-numbered codes. The SP-2 prefix is the tank code; "Cold Water Rinse" is the station's display name. |
|
||
| Billed Labor | section + RESET ALL EDITS | — | per-batch labor split as before; total Timer Duration: 10s |
|
||
| Footer | TWO buttons | Cancel · **SAVE** | |
|
||
|
||
**New insight**: stations have both a **code** (SP-2) AND a **friendly
|
||
name** (Cold Water Rinse). Our model already has `name` + `code` on
|
||
`fusion.plating.tank` — confirming our existing design matches
|
||
Steelhead's naming model 1:1.
|
||
|
||
## Patterns updated after screens 9–13
|
||
|
||
9. **Labor timer is a first-class persistent entity**, not a UI
|
||
ephemeral. It has a stop-without-move flow (screen 10), it can be
|
||
reset, it splits across products. Probably its own model
|
||
`fp.labor.timer` with states: `running / paused / stopped /
|
||
reconciled`.
|
||
|
||
10. **Rack-vs-parts move duality**: parts-not-racked → Move Parts
|
||
dialog; parts-racked → Move Rack dialog (MOVE PARTS greys out).
|
||
Move Rack moves all rack contents at once with per-chunk labor
|
||
breakdown. Need `rack_id` on the move-controller decision.
|
||
|
||
11. **Rack Labels** as a tagging surface — M2M against a tag-like
|
||
registry. Not yet authored on `fp.step.template` (it's a runtime
|
||
rack metadata feature). Goes on `fp.rack` directly when we build
|
||
that registry.
|
||
|
||
12. **Plant Overview shape**: top section "Racks" with rack-level
|
||
primary action (MOVE RACK) + bulk action (UNRACK MULTIPLE);
|
||
bottom section "Parts" with per-part actions (greyed when
|
||
racked) + filters + search. This is the layout the simple-mode
|
||
customer probably also wants on their plant overview — but
|
||
that's a separate sub-project from the recipe editor.
|
||
|
||
13. **"To Node" vs "To Station" hierarchy is consistent**: To Node is
|
||
read-only (auto-derived from recipe sequence), To Station is
|
||
editable (operator picks among compatible stations on that
|
||
node). For our simple recipe editor, this means each step's
|
||
`tank_ids` M2M is the **authoritative compatible-station list**
|
||
that the runtime uses to populate the To Station dropdown. The
|
||
recipe author's job is to declare the ALLOWED set; the operator
|
||
picks among them at run time.
|
||
|
||
## Screenshot 14 — Move Rack: tyut → Ready For DeRack (no station picker)
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Title | "Move Rack: tyut" | — | rack name in title |
|
||
| Rack Labels | M2M + button | (empty) | |
|
||
| Parts list | static | "49 TEST225451 Parts on WO 4521" + "1 TEST225451 Parts on WO 4521" | full rack contents |
|
||
| Type | Selection | Step | |
|
||
| **To Node** | Selection (read-only, greyed) | "Ready For DeRac…" (truncated; full = "Ready For DeRack") | gating-only node — no station to choose, no plating action |
|
||
| **No To Station field** | — | — | confirms: when destination is a single-station / gating node, the To Station row does not render |
|
||
| Billed Labor | section + RESET ALL EDITS | — | per-batch breakdown |
|
||
| Per-row labor | composite | "WO #4521 PN: TEST225451 Qty: 49" → 0/0/4 + "WO #4521 PN: TEST225451 Qty: 1" → 0/0/0 | |
|
||
| Footer | TWO buttons | Cancel · **SAVE** | |
|
||
|
||
**Pattern**: gating nodes (anything starting with "Ready For…") are
|
||
single-station and skip the To Station selector. Move Rack and Move
|
||
Parts both honour this. Our model should treat any node with exactly
|
||
one tank in `tank_ids` (or none — implying "any global location") as a
|
||
node that hides the station picker.
|
||
|
||
## Screenshot 15 — Move Parts with **soft-block: missing spec measurements** (MOVE button DISABLED)
|
||
|
||
| Field | Type | Example | Notes |
|
||
|---|---|---|---|
|
||
| Part Count | Integer (stepper) | 49 | Available: 49 |
|
||
| Part Number | Read-only link | TEST225451 | |
|
||
| From Node | Read-only link | Bake | |
|
||
| **From Station** | Read-only link | Bake | |
|
||
| Transfer Type | Selection | Step | |
|
||
| To Node | Read-only link | Adhesion Testing | |
|
||
| To Location | Selection + camera | Global | |
|
||
| **WARNING BLOCK** (amber, ⚠ icon) | banner | "Additional Spec Measurements are required for this part." | distinct from the rack-required warning |
|
||
| Billed Labor | section + RESET ALL EDITS | — | Timer Duration: 7s |
|
||
| Per-row labor | composite | "WO #4521 PN: TEST225451 Qty: 49" → 0/0/7 | |
|
||
| Footer | TWO buttons | Cancel · **MOVE (49)** (DISABLED / greyed) | **HARD BLOCK** — operator can't proceed until spec measurements are recorded |
|
||
|
||
**Critical pattern — first hard block we've seen**:
|
||
- Steelhead shows the same amber colour for both soft-block (rack
|
||
warning) and hard-block (missing spec). The DIFFERENCE is the
|
||
primary button state: soft-block leaves MOVE enabled (audit and
|
||
proceed); hard-block greys MOVE out (must resolve first).
|
||
- Steelhead does NOT explain HOW to record the missing measurements
|
||
in this dialog — the operator is left to figure out where to enter
|
||
them. This is what the user means by "Steelhead is limited."
|
||
|
||
**Where we improve over Steelhead**:
|
||
- The amber banner should have a **clickable resolution button** like
|
||
the rack warning's "RACK PARTS" button — e.g. **"RECORD MEASUREMENTS"**
|
||
that opens the spec-measurement input dialog inline.
|
||
- After recording, the banner clears + MOVE re-enables. No screen
|
||
hunting.
|
||
- Distinguish soft vs hard visually: amber for soft (proceed-with-
|
||
audit), red for hard (must-resolve).
|
||
|
||
## Patterns updated after screens 14–15
|
||
|
||
14. **Gating nodes (Ready For …)** are single-station / no-station
|
||
nodes — UI hides the To Station selector for them. Our recipe
|
||
editor's step-template form should let the author mark a step as
|
||
"gating" (no tanks/stations needed) and the runtime auto-hides
|
||
the station picker.
|
||
|
||
15. **Soft-block vs hard-block protocol**:
|
||
- **Soft-block**: amber banner + resolution button + MOVE stays
|
||
enabled (rack-required warning, screen 6).
|
||
- **Hard-block**: amber banner + MOVE disabled until resolved
|
||
(missing spec measurements, screen 15).
|
||
- **Steelhead's gap**: hard-block doesn't tell operator where to
|
||
go to resolve. **Our improvement**: every blocker (hard or
|
||
soft) gets an inline resolution button.
|
||
|
||
16. **Spec measurements vs operation measurements vs transition
|
||
inputs**: three distinct concepts now visible:
|
||
- **Operation measurements** (`input_template_ids` on
|
||
`fp.step.template`) — recorded *during* a step, e.g. "Actual #
|
||
of parts" mid-bake.
|
||
- **Transition inputs** (`transition_input_ids`) — recorded
|
||
*when leaving* a step, e.g. "Customer WO #" or photo evidence.
|
||
- **Spec measurements** (NEW from screen 15) — part-level
|
||
specs that are required *for the part itself* across multiple
|
||
steps, e.g. "thickness reading" required after Adhesion
|
||
Testing. These don't belong on the step template — they belong
|
||
on the **part** record (or on a part × step rule). The amber
|
||
block fires when the part hasn't satisfied a spec rule.
|
||
|
||
→ Implies a separate authoring surface: per part (or per
|
||
customer × part), a list of "required spec measurements" with
|
||
their trigger node. Out of scope for this sub-project but worth
|
||
flagging in the design as a hand-off point.
|
||
|
||
17. **MOVE button label DISABLED state**: greyed background, greyed
|
||
text, still shows count "MOVE (49)". Steelhead does NOT show a
|
||
tooltip explaining why — another improvement opportunity for us.
|
||
|
||
## Screenshots 16, 17, 18 — Paper Job Traveller (3-page WO #023633-1, Amphenol Canada, ENP-Aluminum)
|
||
|
||
These are the **manual paper job travellers** the client fills in by
|
||
pen as the parts move. They're the gold-standard spec of what data
|
||
flows through a real plating job today. Every column on these sheets
|
||
is a candidate for digital capture in our recipe → step-template →
|
||
runtime stack.
|
||
|
||
### Header (repeats on every page)
|
||
|
||
| Column | Example | Where it lives in our model |
|
||
|---|---|---|
|
||
| WO # | 023633-1 | `fp.job.name` (already exists) |
|
||
| Barcode | (1D barcode of WO #) | `fp.job.qr_code` (already auto-generated; switch to 1D Code 128 for traveller print) |
|
||
| Date In | 29-11-2024 | `fp.job.date_received` (already exists, comes from `fp.receiving`) |
|
||
| Due Date | 11-12-2024 | `fp.job.date_due` (already exists, from SO commitment date) |
|
||
| Type | ENP-ALUMINUM | `fp.job.coating_config_id.name` (already exists) |
|
||
| Order No. | 023633 | `fp.job.sale_order_id.name` (already exists) |
|
||
| P.O. No. | 731830 | `fp.job.sale_order_id.client_order_ref` (already exists) |
|
||
| Customer | AMPHENOL CANADA + address + phone | `fp.job.partner_id.*` (already exists) |
|
||
| WO Generated By | RIYA | `fp.job.create_uid.name` (already exists) |
|
||
|
||
→ **No new model fields needed for the header.** Existing job fields
|
||
cover everything. The traveller-print report just needs to pull them.
|
||
|
||
### Item Information block (page 1, top)
|
||
|
||
| Column | Example | Where it lives |
|
||
|---|---|---|
|
||
| Item informations / Part # | VS-E0443220025 | `fp.part.catalog.part_number` (already exists) |
|
||
| Rev. | 1F | `fp.part.catalog.revision` (already exists) |
|
||
| Mat. (material) | 6061-T6511 | NEW field on `fp.part.catalog`: `base_material` Char (e.g. "6061-T6511 aluminum"). Currently we have implicit material via coating config — making it explicit per-part is small. |
|
||
| Catg. (category) | ENP-ALUMINUM | derived from `fp.part.catalog.coating_config_id.name` |
|
||
| S/N (serial #) | (blank for batch) | already exists as `x_fc_serial_number` (Sub 5) |
|
||
| Item-Name / Process Description | "SHELL RECEPTACLE / 01 - ELECTROLESS NICKEL PLATING PER E499-303-00-002 OF AMPHENOL SPEC # E499-303-00-XXX REV : 1F" | `fp.part.catalog.name` + `fp.part.catalog.customer_facing_description` (already exist; just need to merge in the report) |
|
||
| Qty Rec. | 5850 → 5839, 5835 (multiple counted lines) | NEW: traveller needs to show **received qty** AND **per-stage running counts**. Stages: Received → Inspected → Racked → Plated → Final-counted. Capture per-stage. |
|
||
| VIS INSP. | 0 (visual inspection rejects/holds during incoming) | NEW: integer column on `fp.job` for "qty rejected at incoming inspection" |
|
||
| Rework | (blank) | NEW: integer column for "qty sent to rework" |
|
||
| Special Requirements | "MID PHOS / PANEL THICKNESS: 0.0224"-0.0228" (PLATING THICKNESS: 0.0005"-0.0007") / BAKE @ 250 DEG F FOR 1 HOUR / *****RUN EACH LOAD WITH 3 TEST PANELS AND VERIFY THE PLATING THICKNESS USING XRF PRIOR TO REMOVING PARTS FROM THE TANK..." | NEW: `fp.job.special_requirements` Text (free-form). Today this lives in customer specs but isn't pulled onto the traveller. |
|
||
| Stamp + Date | initials "LY" + "06.12.24" | runtime sign-off — captured per-section by the inspector |
|
||
|
||
### Process-Sheet header (page 1, middle)
|
||
|
||
| Column | Example | Where it lives |
|
||
|---|---|---|
|
||
| Process name 1 | "ENIP (A) BAKE (GENERIC)" | `fp.job.process_node_id.name` (root recipe) |
|
||
| Process name 2 | "ENIP (A) BAKE" | sub-process node name (already exists in tree) |
|
||
| Catg. | ELECTROLESS NICKEL | from coating config |
|
||
| Special Req. | (blank in this WO) | `fp.job.special_requirements` |
|
||
| Spec / Info block | (blank, reserved for sticker / inspector notes) | print-only blank |
|
||
|
||
### Step rows (the heart of the traveller)
|
||
|
||
Each step row carries:
|
||
|
||
| Column | Type / format | Notes |
|
||
|---|---|---|
|
||
| **Step #** | Integer (1, 2, 3, ... 25) | already on our model as `sequence` (auto-numbered in the OWL editor) |
|
||
| **Tank** | Char code (A-1, A-2, A-13, blank) | comes from `tank_ids` M2M chosen at runtime → `fp.job.step.tank_id` |
|
||
| **Operation** | Operation name (e.g. "Soak clean", "Etch", "E-Nickel Plate", "Inspection") | `fp.step.template.name` |
|
||
| **Operation actual-data column** (multi-row, hand-written) | Free-form actuals filled in pen — varies per step. Examples: "Actual Qty: 5839", "Actual Time: 04 min", "Actual Temperature: 170 ºF", "Actual time: 01 min / Actual temperature: 190 ºF / Plating thickness: 0.0005"", "PASS: ✓ FAIL: ___", "Actual thickness: 0.00057" | THIS IS THE OPERATION-MEASUREMENT INPUT LIST. Each value the operator pencils in is a `fp.step.template.input` row. |
|
||
| **Instruction** | Free-form instructions tied to a Work Instruction reference | "Immerse parts in alkaline soak cleaner for 4-6 minutes @ 150-170 ºF as per WI 10.07." / "Verify plating thickness is as per customer requirements and as per WI 10.09" / "Verify the job traveler was filled out completely and the information is correct." | maps to `fp.step.template.description` (Html) |
|
||
| **Unit** | Char (each / Minutes / Seconds / mils / min / ºF / blank) | NEW: `fp.step.template.unit` Char or Selection. Currently NOT in our model. Operators need to know what unit the actual-time / actual-thickness etc. should be in. |
|
||
| **Material** | mostly N/A; some steps have "MID PHOS" written | this column is for **chemistry callout** when the step has a material-specific bath. Maps to `fp.step.template.process_type_id` (already exists) — the process type's name prints here. |
|
||
| **Voltage** | mostly N/A | electrolytic steps only. NEW: optional `voltage_target` Float on step template. |
|
||
| **Viscosity** | mostly N/A | bath-quality callout. NEW: optional `viscosity_target` Float. |
|
||
| **Time (min)** | range, e.g. "4 - 6" / "55 - 65" / "25 - 35" | the **target range** the operator must hit. NEW: `time_min_target` + `time_max_target` Float on step template. |
|
||
| **Temp.** | (mostly empty in target column; pencilled in actual) | the **target range**. NEW: `temp_min_target` + `temp_max_target` + `temp_unit` Selection (F/C). |
|
||
| **Stamp** | initials column | runtime per-step sign-off. Captured as `fp.job.step.signoff_user_id` + signoff datetime. |
|
||
| **Date** | dd.mm.yy format | per-step completion date (auto-stamped on `Mark Done`). Already exists as `fp.job.step.date_finished`. |
|
||
|
||
### Concrete examples from the WO that show the data shape
|
||
|
||
| Step # | Tank | Operation | Targets (printed) | Actuals (handwritten) |
|
||
|---|---|---|---|---|
|
||
| 1 | — | Part verification and quantity check | unit=each | "Actual Qty: 5839" |
|
||
| 2 | — | Issue Panels | — | (no actual; just stamp/date) |
|
||
| 3 | — | Rack | — | "Actual Qty: 5839" |
|
||
| 4 | A-1 | Soak clean | 4-6 min @ 150-170 °F | "Actual time: 04 min, Actual Temperature: 170 °F" |
|
||
| 5 | A-2 | Rinse | — | (stamp only) |
|
||
| 6 | — | Water Break Test | — | "PASS: ✓" |
|
||
| 7 | A-3 | Etch | 55-65 sec | "Actual Time: 60 sec." |
|
||
| 9 | A-5 | Desmut | 55-65 sec | "Actual Time: 60 sec." |
|
||
| 11 | A-7 | Zincate | 25-35 sec | "Actual Time: 25 sec." |
|
||
| 13 | A-5 | Strip Zincate | 15-25 sec | "Actual Time: 25 sec." |
|
||
| 17 | A-13 | E-Nickel Plate | 185-190 °F | "Actual time: 01 min, Actual temperature: 190 °F, Plating thickness: 0.0005"" |
|
||
| 22 | — | Baking | (Time In / Time Out) °F | "Time In: 10:00, Time Out: 11:00, Actual temperature: 250 °F" |
|
||
| 23 | — | Inspection | mils | "Actual thickness: 0.00057, PASS: ✓" |
|
||
| 24 | — | Final Inspection | — | "Verify the job traveler was filled out completely…" |
|
||
| 25 | — | Shipping | — | "Actual Qty: 5839" |
|
||
|
||
## What the traveller tells us about our data model gaps
|
||
|
||
### Step template — NEW fields needed
|
||
|
||
| Field | Type | Reason |
|
||
|---|---|---|
|
||
| `unit` | Char or Selection | Print "each / Minutes / Seconds / mils / ºF / N/A" so operator knows units |
|
||
| `time_min_target` | Float | Lower bound of operation time |
|
||
| `time_max_target` | Float | Upper bound; runtime warns if outside |
|
||
| `time_unit` | Selection (`sec / min / hr`) | how to interpret time fields |
|
||
| `temp_min_target` | Float | Lower bound of operation temperature |
|
||
| `temp_max_target` | Float | Upper bound |
|
||
| `temp_unit` | Selection (`F / C`) | how to interpret temp fields |
|
||
| `voltage_target` | Float (optional) | electrolytic steps |
|
||
| `viscosity_target` | Float (optional) | bath-quality |
|
||
| `material_callout` | Char (optional) | "MID PHOS" — short string printed in the Material column. Defaults to `process_type_id.name` if not set. |
|
||
|
||
### Step template input list (`fp.step.template.input`) — likely defaults
|
||
|
||
For the traveller to render a useful actual-data column, each step
|
||
template should pre-populate its input list with the right entries:
|
||
|
||
- **Cleaning / etch / desmut / zincate / acid steps**: `Actual Time
|
||
(sec or min)` + (optional) `Actual Temperature (°F)`.
|
||
- **Plating step**: `Actual time (min)` + `Actual temperature (°F)` +
|
||
`Plating thickness (")`.
|
||
- **Bake step**: `Time In (HH:MM)` + `Time Out (HH:MM)` + `Actual
|
||
temperature (°F)`.
|
||
- **Receiving / racking / shipping**: `Actual Qty`.
|
||
- **Inspection (visual)**: `PASS / FAIL` selection.
|
||
- **Inspection (thickness measurement)**: `Actual thickness
|
||
(mils)` + `PASS / FAIL`.
|
||
- **Water break test**: `PASS / FAIL` selection.
|
||
- **Rinse / dry**: no input — sign-off only.
|
||
|
||
These defaults come "out of the box" when the customer drops a
|
||
library step into a recipe — no need for them to author the input
|
||
list every time. They can override / add later.
|
||
|
||
### Job-level NEW fields
|
||
|
||
| Field | Type | Reason |
|
||
|---|---|---|
|
||
| `qty_received` | Integer | "Qty Rec." column on traveller header |
|
||
| `qty_visual_inspection_rejects` | Integer | "VIS INSP." column |
|
||
| `qty_rework` | Integer | "Rework" column |
|
||
| `special_requirements` | Text | "Special Requirements" block (long free text from customer spec) |
|
||
| `qty_at_stage` (computed One2many?) | per-step running count | NEW — would let "Qty Rec.: 5850 → 5839 → 5835" auto-render on the traveller. Computed from `fp.job.step.qty_done` chain. |
|
||
|
||
### Part catalog NEW fields
|
||
|
||
| Field | Type | Reason |
|
||
|---|---|---|
|
||
| `base_material` | Char | "6061-T6511 aluminum" — currently implicit |
|
||
|
||
### Traveller report (new QWeb template)
|
||
|
||
`fusion_plating_reports/report/report_fp_job_traveller_v2.xml` — landscape A4 multi-page:
|
||
|
||
1. **Page 1**: header + Item Information block + Process-Sheet header + first 6–8 steps.
|
||
2. **Pages 2..N**: continuation rows for the remaining steps.
|
||
3. **Last page**: Footer / final inspection / shipping rows + room for stamps / dates.
|
||
|
||
The current `report_fp_job_traveller.xml` (in `fusion_plating_jobs`,
|
||
shipped in S5/S18) is portrait and minimal. We rebuild it to match
|
||
this paper format.
|
||
|
||
### What we'll auto-capture instead of pencilling in
|
||
|
||
| Pencilled today | Auto-captured by us |
|
||
|---|---|
|
||
| Actual Qty | comes from `fp.job.qty_done` chain (already exists) |
|
||
| Actual Time | timer on `fp.job.step` (already exists, S1/S2 stuff) |
|
||
| Actual Temperature | IoT sensor reading (already exists in `fusion_iot/fusion_plating_iot/`) — ties to step's tank, snapshot saved at sign-off |
|
||
| Plating thickness | Fischerscope auto-extract (already exists in `fusion_plating_certificates/fp.thickness.reading`, S19) |
|
||
| Time In / Time Out (bake) | bake-window record (already exists, S6/S15) |
|
||
| PASS/FAIL | QC checklist (already exists in `fusion_plating_quality`, S18/S19) |
|
||
| Stamp (initials) | per-step `signoff_user_id` (already exists) |
|
||
| Date | per-step `date_finished` (already exists) |
|
||
|
||
Most of the runtime capture machinery already exists. **The gap is
|
||
making the recipe editor expose target ranges + units + per-step
|
||
input list defaults** so the traveller can render the targets next to
|
||
the actuals.
|
||
|
||
## Patterns updated after screens 16–18
|
||
|
||
18. **Paper traveller is the spec for what's authored on a step
|
||
template.** Every column on the paper sheet maps to either:
|
||
- an authored field on `fp.step.template` (target range, unit,
|
||
material callout, voltage, viscosity), OR
|
||
- a runtime-captured field on `fp.job.step` (actual time, actual
|
||
temp, sign-off, date), OR
|
||
- a runtime-captured input value (`fp.job.step.input.value`)
|
||
tied to a `fp.step.template.input` definition.
|
||
|
||
19. **Step "Actual" data is multi-field per step**, not just one
|
||
"actual" value. E.g. an etch step has Actual Time only; a
|
||
plating step has Actual Time + Actual Temp + Plating Thickness;
|
||
a bake has Time In + Time Out + Actual Temp. The input list per
|
||
step template must be flexible enough to model this — which it
|
||
already is via `input_template_ids`. We just need sane defaults
|
||
so the customer doesn't have to author them from scratch.
|
||
|
||
20. **Target ranges (e.g. 4-6 minutes, 150-170 °F)** are first-class
|
||
authored data. They drive: traveller print, runtime overrun
|
||
warnings (S7), out-of-spec alerts on IoT readings (existing),
|
||
and the operator UX cue ("you're at 7 min, target is 4-6 — log
|
||
a deviation?"). Currently we have `estimated_duration` (single
|
||
value) on `process.node` — needs to extend to min/max per step.
|
||
|
||
21. **Customer-spec callout** ("MID PHOS", "BAKE @ 250 DEG F FOR 1
|
||
HOUR", "RUN EACH LOAD WITH 3 TEST PANELS…") is per-job, not
|
||
per-step. It's the **job-header special-requirements** field
|
||
(free text, copied from customer spec library). Already covered
|
||
by `fusion.plating.customer.spec` model — needs to be pulled
|
||
onto the traveller print.
|
||
|
||
22. **Multi-pass through same tank** (e.g. step 11 = A-7 Zincate,
|
||
step 13 = A-5 Strip Zincate, step 15 = A-7 Zincate AGAIN). Our
|
||
recipe model handles this fine — each step is its own node
|
||
instance. Worth confirming the simple editor renders this
|
||
visually (e.g. "Zincate" appears twice in the ordered list,
|
||
each with its own SP-7 station tag). The screenshots from the
|
||
customer's other system in the original brainstorm already
|
||
showed this pattern (Primary Rinse appeared twice in the
|
||
Selected list).
|
||
|
||
## Screenshots 19–22 — Steelhead's auto-generated CoC traveller PDF (7-page report)
|
||
|
||
These pages are the **digital output** Steelhead produces after a job
|
||
completes — a multi-page PDF traveller showing the full chain of
|
||
custody, every step transition, every captured measurement, the
|
||
operator who recorded each value, and the timestamp. This is what
|
||
gets sent to the customer with the parts. Functionally equivalent to
|
||
our own `fp.certificate` flow + the new traveller report we plan to
|
||
build.
|
||
|
||
**Critical context**: this is the OUTPUT that Steelhead generates from
|
||
its captured data. Everything on these pages is something we MUST be
|
||
able to capture + render to match feature parity. Job:
|
||
- Customer: (printed at top, e.g. cert-style header)
|
||
- Part: 2144A6201-5 OUTER CYLINDER ASSY
|
||
- Description: "ELECTROLESS NICKEL PLATING & BAKE PER LGPS 1104G & IAW
|
||
TECHNIQUE#: QA-016-36 REV.1, PLATING THICKNESS: 0.0019",
|
||
HYDROGEN EMBRITTLEMENT RELIEF BAKING @ 375ºF FOR 23 HOURS"
|
||
- Quantity: 1
|
||
- WO#: 620, PO#: 980806214, Date: 2025-09-23
|
||
- Specification(s): "Electroless Nickel Plating"
|
||
- Footer: "Cert Created At: 2025-09-23", page #/total, **Nadcap Accredited** logo, **ENTECH** logo
|
||
|
||
### Page 1 — Header + first 3 step transitions (no captured data yet)
|
||
|
||
Steps shown:
|
||
1. **Ready for Incoming Inspection** — Moved By: Riya Bhatt, Time: Sep 18, 2025 07:22:31 AM
|
||
2. **Ready For Plating** — Moved By: Kris Pathinather, Time: Sep 18, 2025 07:25:00 AM
|
||
3. **Soak Clean (S-3)** — Moved By: Kris Pathinather, Time: Sep 18, 2025 07:31:26 AM
|
||
|
||
**Pattern**: each step heading shows `Step Name (station code)` —
|
||
"Soak Clean (S-3)" combines the step's name with the chosen station.
|
||
Below the heading, "Part Number" and "Moved By: <user> Time:
|
||
<datetime>" are always rendered. Then if the step has captured
|
||
measurements, a sub-table follows.
|
||
|
||
### Page 2 — Step transitions with captured measurement tables
|
||
|
||
Each step that captured operator inputs renders a 4-column table:
|
||
**Name | Description | Value | Recorded By**
|
||
|
||
Sample tables:
|
||
|
||
**Soak Clean** sub-table (between page 1 step heading and page 2 next
|
||
step heading):
|
||
| Name | Description | Value | Recorded By |
|
||
|---|---|---|---|
|
||
| Soak Clean Time (5-10 min.) | (blank) | 00:05:22 | Kris Pathinather |
|
||
| Soak Clean Temp 165-195 (ºF) | (blank) | 170 | Kris Pathinather |
|
||
|
||
**ElectroClean (S-3)** sub-table:
|
||
| Name | Description | Value | Recorded By |
|
||
|---|---|---|---|
|
||
| ElectroClean Time 30-90 Seconds | (blank) | 00:00:55 | Kris Pathinather |
|
||
| ElectroClean Amperage (A) | (blank) | 280 | Kris Pathinather |
|
||
| Surface Area (FT2) | (blank) | 7 | Kris Pathinather |
|
||
| ElectroClean (SP-1) Temperature (ºF) | (blank) | 170 | Kris Pathinather |
|
||
|
||
**Water Break Free Test** sub-table (test-style measurement):
|
||
| Name | Description | Value | Recorded By |
|
||
|---|---|---|---|
|
||
| Water Break Free Test | "Perform water break test on parts as per WI 10.08. Observe parts for 30 – 60 seconds after removing from the soap rinse and observe if the production parts exhibit a water break free surface. If test fails, repeat from step Soak Clean." | PASS | Kris Pathinather |
|
||
|
||
### Page 3 — Mid-job step transitions (HCl Activation, Rinses, Plating, Porosity)
|
||
|
||
Sample tables:
|
||
|
||
**Acid Dip** sub-table (HCl Activation step — the input description
|
||
includes the WI reference):
|
||
| Name | Description | Value | Recorded By |
|
||
|---|---|---|---|
|
||
| Acid Dip Time | "Immerse parts in HCl tank for 20-40 seconds as per WI 10.07." | 00:00:30 | Kris Pathinather |
|
||
|
||
**Electroless Nickel Plating (S-10)** sub-table — KEY step, multiple
|
||
measurements:
|
||
| Name | Description | Value | Recorded By |
|
||
|---|---|---|---|
|
||
| E-Nickel Plate Temp (187-193ºF) | (blank) | 190 | Kris Pathinather |
|
||
| Final Panel Thickness | (blank) | 0.0248 | Kris Pathinather |
|
||
| Actual Thickness (") | "Immerse parts with test panels in E-Nickel tank as per WI 10.07." | 0.0018 | Kris Pathinather |
|
||
|
||
**Porosity Test** — minimal heading-only entry with one inline
|
||
"Results: PASS" text under the step name (instead of a table).
|
||
|
||
### Pages 4–5 — More transitions (DeRacking, Ready for De-Masking, De-Masking, Ready for bake, Bake)
|
||
|
||
Heading-only step transitions (no captured data table):
|
||
- **DeRacking** — Moved By: Ryan Persaud, Time: Sep 18, 2025 02:16:45 PM
|
||
- **Ready for De-Masking** — Moved By: Ryan Persaud, Time: Sep 18, 2025 02:20 PM
|
||
- **De-Masking** — Moved By: Ryan Persaud, Time: Sep 18, 2025 02:21:05 PM
|
||
- **Ready for bake** — Moved By: Ryan Persaud, Time: Sep 18, 2025 02:44:52 PM
|
||
- **Bake** — Moved By: Ryan Persaud, Time: Sep 18, 2025 2:45:27 PM
|
||
|
||
**Pattern**: gating / no-input steps still render a heading + "Moved
|
||
By + Time" but no measurement table. They contribute to the
|
||
chain-of-custody trail without measurements.
|
||
|
||
### Page 6/7 — Bake captured data + Adhesion Test + EN Final Inspection (final pass/fail)
|
||
|
||
**Bake** sub-table:
|
||
| Name | Description | Value | Recorded By |
|
||
|---|---|---|---|
|
||
| Hydrogen Embrittlement Time | (blank) | 23:48:19 | Ryan Persaud |
|
||
| Hydrogen Embrittlement Relief Temp (ºF) | (blank) | 375 | Ryan Persaud |
|
||
|
||
**Post Plate Inspection** (heading-only, gating)
|
||
- Moved By: Brett Kinzett, Time: Sep 22, 2025 08:22:01 AM
|
||
|
||
**Final EN inspection** sub-table — pass/fail summary:
|
||
| Name | Description | Value | Recorded By |
|
||
|---|---|---|---|
|
||
| Adhesion Test | (blank) | PASS | Brett Kinzett |
|
||
| EN Final Inspection | (blank) | PASS | Brett Kinzett |
|
||
|
||
## What this PDF tells us (key insights)
|
||
|
||
1. **Steelhead's CoC traveller is a chronological audit log**, NOT a
|
||
recipe traveller. It walks the actual transition history (not the
|
||
authored recipe order) and prints captured data per step. Useful
|
||
for AS9100 / Nadcap audit because it shows EXACTLY what happened,
|
||
in time order, by whom.
|
||
|
||
2. **Every captured input has 4 attributes**:
|
||
- **Name** (e.g. "Soak Clean Time (5-10 min.)") — the input
|
||
definition's display name. The target range is **embedded in
|
||
the name** ("(5-10 min.)") rather than a separate column. This
|
||
is a Steelhead UX choice — we should do better and put min/max
|
||
in their own columns so the auditor can see authored vs actual
|
||
side-by-side.
|
||
- **Description** (long-form WI reference) — pulled from the
|
||
step's `description` (Html) field. Sometimes blank. Usually
|
||
populated for inspection-type steps (where the operator needs
|
||
the procedure text), not for routine measurements.
|
||
- **Value** (the recorded value) — type varies: time HH:MM:SS,
|
||
number (with implicit unit), PASS/FAIL, etc.
|
||
- **Recorded By** — operator's full name.
|
||
|
||
3. **Time values are always in HH:MM:SS** even for sub-minute
|
||
readings ("00:00:55"). Steelhead uses one consistent time
|
||
format. We should do the same on the report — it's noise for
|
||
the operator entering the data ("how do I type 55 seconds?")
|
||
but clean on the report.
|
||
|
||
4. **Step heading has the station baked in** — "Soak Clean (S-3)",
|
||
"Electroless Nickel Plating (S-10)". This means our report needs
|
||
to render the **station code** chosen at runtime, not just the
|
||
step name. Already in our model (`fp.job.step.tank_id.code`).
|
||
|
||
5. **Sub-tables only appear when there's captured data**. Heading-
|
||
only steps (Ready For X, gating nodes, racking moves) just show
|
||
the chain-of-custody line. Our report should follow the same
|
||
pattern — clean PDF with no empty tables.
|
||
|
||
6. **Same input name with different runtime captures** — "Rinse
|
||
(S-11 / S-13)" is a single transition heading covering BOTH a
|
||
rinse pass through tank S-11 AND tank S-13 (two physical rinse
|
||
tanks operators dipped through in sequence). Steelhead collapses
|
||
them into one heading. Our model has each as a separate step;
|
||
we'd render two headings unless we add a "merge consecutive
|
||
identical steps" report option. **Decision later** — for now,
|
||
keep them separate.
|
||
|
||
7. **Multi-day jobs are normal**. The bake spans 23h 48min, post-
|
||
plate inspection happens days later (Sep 19 / Sep 22). The
|
||
traveller knows because each transition has its own timestamp.
|
||
Our chain-of-custody log already captures this.
|
||
|
||
8. **Two operators on the same job** — Riya Bhatt does receiving,
|
||
Kris Pathinather does the wet-line, Ryan Persaud handles
|
||
masking/bake, Brett Kinzett does final inspection. The "Moved By"
|
||
field changes per step, reflecting hand-offs. Already supported
|
||
by our model.
|
||
|
||
9. **Footer branding** — "Nadcap Accredited (Administered by PRI)"
|
||
logo + ENTECH company logo. Per-page. Page numbering "n/total".
|
||
Plus "Cert Created At: <date>". Our existing `fp.certificate`
|
||
PDF flow can render this footer; we just need to add the Nadcap
|
||
logo asset + company logo configurable.
|
||
|
||
## What this means for our recipe editor + traveller report
|
||
|
||
### Step template input list — render targets in their OWN columns
|
||
|
||
Steelhead embeds "(5-10 min.)" in the input name. We should split
|
||
this into two authored fields:
|
||
|
||
| Field | Type | Notes |
|
||
|---|---|---|
|
||
| `target_min` | Float | Lower bound (e.g. 5) |
|
||
| `target_max` | Float | Upper bound (e.g. 10) |
|
||
| `target_unit` | Char | "min" / "ºF" / "A" / "FT2" / "in" / "%" / "(blank for pass-fail)" |
|
||
| `display_label` | Char (compute) | "Soak Clean Time" or "Soak Clean Temp" — clean name without the embedded range |
|
||
|
||
Report renders 5 columns per measurement table:
|
||
**Name** | **Description** | **Target** | **Actual** | **Recorded By**
|
||
|
||
The Target column shows "5-10 min" (or "165-195 ºF" or "30-90 sec"
|
||
auto-formatted from the three new fields).
|
||
|
||
### Job traveller report = chronological audit, NOT step-order
|
||
|
||
Build the report to walk `fp.job.step` records ordered by
|
||
`date_started` (or `date_finished`), not by `sequence`. This matches
|
||
Steelhead's chain-of-custody ordering and is what auditors expect.
|
||
|
||
### Heading format for each step transition
|
||
|
||
`<Step Name> (<Tank code>)` — already supported by our model. No
|
||
gap.
|
||
|
||
### Time format
|
||
|
||
Use HH:MM:SS for all duration values on the report, even sub-minute.
|
||
On entry (tablet input), accept "55 sec" or "00:00:55" — convert to
|
||
canonical HH:MM:SS for storage.
|
||
|
||
### Pass/Fail value rendering
|
||
|
||
For boolean inputs, render "PASS" / "FAIL" in caps. Already
|
||
straightforward.
|
||
|
||
### Step description field carries the WI reference
|
||
|
||
Already supported by our `fp.step.template.description` Html field.
|
||
The report just needs to render it stripped of HTML tags inline
|
||
(plain-text in the table cell).
|
||
|
||
### Each operator's name comes from `res.users.name`
|
||
|
||
Our existing pattern. No gap.
|
||
|
||
## Patterns updated after screens 19–22
|
||
|
||
23. **The CoC PDF is a chronological audit, not a recipe-order
|
||
print.** Different report from the operator-facing traveller
|
||
(which prints in recipe order so the operator knows what's
|
||
next). We need both:
|
||
- **Operator traveller** (recipe-order, paper-style, A4
|
||
landscape, blank actual columns) — what Riya prints when
|
||
Carlos starts a job.
|
||
- **Customer CoC** (chronological, captured-data, formal,
|
||
portrait, branded) — what gets attached to the cert and sent
|
||
to the customer.
|
||
Today we ship the CoC via `fp.certificate` (S18/S19). Need to
|
||
extend it to walk the chain-of-custody and render measurement
|
||
sub-tables.
|
||
|
||
24. **Target ranges should be authored as min/max + unit, not
|
||
embedded in the name string.** Steelhead embeds them which is
|
||
a UX bug — auditors can't filter by "all jobs where Soak Clean
|
||
Temp was out of range" because the range only exists as text.
|
||
By splitting into structured fields, we get free queryability
|
||
AND can colour-code the value cell on the report
|
||
(green if in range, red if not).
|
||
|
||
25. **Step description vs input description**: Steelhead uses the
|
||
"Description" column on the input table to show the WI
|
||
reference. That's the **step-level** description repeated on
|
||
the input row. Our model already has `description` on the step
|
||
template — we just render it once at the input table header
|
||
instead of per-row. Saves report space.
|
||
|
||
26. **Inputs can be multi-valued per step** — ElectroClean has 4
|
||
inputs, E-Nickel Plate has 3, Soak Clean has 2. Our
|
||
`input_template_ids` One2many already handles this. Confirmed
|
||
by real data.
|
||
|
||
27. **"PASS" entries can be standalone (no table)** — Porosity Test
|
||
just prints "Results: PASS" inline, no input table. This is a
|
||
Steelhead simplification. We should normalize: every step with
|
||
a captured input renders a table, even if it's a single
|
||
pass/fail row. Cleaner audit trail.
|
||
|
||
28. **Move-By trail is the operator chain-of-custody. CRITICAL for
|
||
aerospace / Nadcap.** Our existing `fp.job.step.signoff_user_id`
|
||
+ `date_finished` already provides this. The report just walks
|
||
them in time order.
|
||
|
||
## Screenshot 23 — Page 7/7 of CoC: Final Inspection / Packaging + Ready For Shipping
|
||
|
||
Step transitions on this page:
|
||
- **Ready For Final Inspection / Packaging** — Moved By: Brett Kinzett, Sep 22, 2025 08:42:22 AM (heading-only, gating)
|
||
- **Final Inspection / Packaging** — Moved By: Brett Kinzett, Sep 23, 2025 11:39:47 AM (heading + measurement table)
|
||
- **Ready For Shipping** — Moved By: Brett Kinzett, Sep 24, 2025 01:45:49 PM (heading-only, gating)
|
||
- (unnamed transitions) — Sep 24, 2025 02:03:29 PM and 02:06:16 PM
|
||
|
||
**Final Inspection / Packaging** sub-table:
|
||
| Name | Description | Value | Recorded By |
|
||
|---|---|---|---|
|
||
| Outgoing Part Count | (blank) | PASS | Brett Kinzett |
|
||
| Thickness Test Pass / Fail | (blank) | PASS | Brett Kinzett |
|
||
| Qty Accepted | (blank) | 1 | Brett Kinzett |
|
||
| Qty Rejected | (blank) | 0 | Brett Kinzett |
|
||
| Actual Coating Thickness - Final | (blank) | 0.0018 | Brett Kinzett |
|
||
|
||
Notes:
|
||
- "Outgoing Part Count" with value "PASS" is odd — looks like Steelhead conflated a count check with a pass/fail input. Our model should keep them separate (`Qty Accepted` integer + `Outgoing Part Count Verified` boolean).
|
||
- "Qty Accepted" + "Qty Rejected" are the **final disposition split** at packaging time. Non-negotiable for AS9100 shipment audit.
|
||
- "Actual Coating Thickness - Final" duplicates the mid-job thickness reading captured at E-Nickel Plate. Steelhead re-takes it at final inspection as a sanity-check before sign-off.
|
||
- The two unnamed transition rows at the bottom are likely "Shipped" / "Closed" gating moves whose headings got cut off the screenshot.
|
||
|
||
## Screenshot 24 — Cert sign-off footer (last block on the CoC)
|
||
|
||
Two side-by-side panels:
|
||
|
||
**Left panel — "Certified By:"**
|
||
- Embedded signature image (handwritten "K Pathinathn" — Kris Pathinather signed)
|
||
- Printed name below: "Name: Kris Pathinather"
|
||
|
||
**Right panel — "Certification Statement: Ref. WO# 620"** (top half) + **"Other Comments:"** (bottom half), both blank-but-bordered for handwritten or template-rendered cert language.
|
||
|
||
This is the **certificate of conformance attestation block** — a
|
||
named individual takes legal responsibility for the cert. Required
|
||
for Nadcap, AS9100, CGP. Already supported by our existing
|
||
`fp.certificate.signoff_user_id` + signature image (S18). Just need
|
||
to surface the signed image + the printed name + the cert statement
|
||
in our PDF footer.
|
||
|
||
The "Certification Statement" body in our existing CoC is already
|
||
authored on the certificate template — Steelhead leaves it blank in
|
||
this screenshot suggesting the full statement was on a different
|
||
page. We DO render it (boilerplate "We certify that the parts
|
||
conform to specification…" language).
|
||
|
||
## Patterns updated after screens 23–24
|
||
|
||
29. **Final inspection captures the disposition split** — Qty
|
||
Accepted / Qty Rejected as integer fields at the last QC step.
|
||
These should appear as `input_template_ids` on a "Final
|
||
Inspection" library step (sane defaults). Plus a redundant
|
||
"final coating thickness" reading. We model these as standard
|
||
operation-measurement inputs.
|
||
|
||
30. **Certificate sign-off block is a 2-column footer**:
|
||
left = signature image + name, right = cert statement + other
|
||
comments. Already supported by `fp.certificate` (S18). Just
|
||
confirm our PDF template arranges them this way.
|
||
|
||
31. **CoC renders gating "Shipped / Closed" transitions** as
|
||
chain-of-custody entries with no measurement tables — same
|
||
treatment as "Ready For X" gating nodes.
|
||
|
||
## Screenshot inventory closed (24 screenshots logged total)
|
||
|
||
All Steelhead screens captured. The data model + UI implications
|
||
have been folded into the Pending-Questions section below. We can
|
||
now resume the brainstorm with the full picture in hand.
|
||
|
||
## Pending questions to ask user once screenshots are done
|
||
|
||
- What other Transfer Type values exist? (Hold / Scrap / Rework / Return / Splits?)
|
||
- Does Billed Labor show up on every screen or only certain ones?
|
||
- Are From Station / To Station always editable, or read-only when single-station nodes?
|
||
- What does the camera icon save against — the part record, the transition log, or the job?
|
||
- Is "Number of Customer WOs" a free-text count or a multi-select of existing customer POs?
|
||
- Is there a way to **split** a move (e.g. send 40 to next step, hold 9)? Steelhead's Part Count selector hints at it.
|