mega-plant/HANDOFF-PROMPT-2.md
Mega Plant b70fc8c5c7 Round 2 (Parts B-D): asset register restructure, type field, sequential codes, flat sortable table, cost_lines, removed synthetic code, fail loudly
- B.1: removed running_cost from PB monthly_income schema
- B.2: 40 of 40 machines now have income (was 32/40)
- B.3: removed syntheticCode() + auto-create branch; deleted 2 ghost Z* assets; seed fails loudly on unmatched
- C.1: collapsed 13 cost-component rows into 5 machines + new cost_lines collection; Finance/Insurance/Tracking categories removed from assets
- C.2: type field on assets derived from code prefix
- C.3: sequential codes SC01-07, OC01-02, PC01-02; fixed SITE CONTANER spelling
- C.4: flat sortable plant register table with type filter pills
- C.5: wired filter input on plant register
- D: verified term 2 (insurance) reconciles for all 5 financed machines
2026-09-12 10:24:26 +02:00

244 lines
10 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.

# Mega Plant — Round 2
Round 1 (`HANDOFF-PROMPT.md`) was mostly landed well. This round covers three items
that didn't land, plus the asset-register restructure, plus a partial answer to
Open Question 2 that came out of the data.
## Ground rules (unchanged)
**DO NOT restyle anything.** Palette, roundness, gradients, spacing, dark mode all
stay exactly as they are. They're approved.
**Do not invent data to make a step pass.** See B.3 — this happened in round 1.
**Verify and report.** Run `npm run typecheck` after each part and say what it output.
---
# Part A — Already done for you, do not redo
The endless-recompile loop is **fixed**. Root cause: `next-pwa` writes `sw.js` and
`workbox-*.js` into `public/` on every compile, and the dev server watches `public/`,
so each build triggered the next one. The dev server was sitting at 67.9% CPU.
Changes already committed to your working tree:
- `next.config.ts` — added `disable: process.env.NODE_ENV === "development"`
- `.gitignore` — added `public/sw.js`, `public/workbox-*.js`, `public/swe-worker-*.js`
- `git rm --cached` on those three (they had been committed as build artifacts)
- Deleted the stale generated files from `public/`
Verified after the fix: dev server idle at **0.0% CPU**, `/` and `/plant` both 200,
`/sw.js` 404 in dev (correct — it only generates for production builds),
`/manifest.json` 200.
**Leave this alone.** If you need a service worker in dev for a specific test, flip
the flag temporarily and flip it back — do not remove it.
One thing to be aware of: any browser that loaded the app while the broken build was
running still has a service worker registered. If the app behaves strangely or serves
stale content, unregister it at `chrome://serviceworker-internals` (or DevTools →
Application → Service Workers → Unregister) and hard-reload.
---
# Part B — Round 1 items that didn't land
## B.1 `running_cost` is still on `monthly_income`
Round 1 §1.5 asked for this field to be dropped and derived from
`monthly_running_costs` instead. The seed has a comment claiming it was done, but the
live PocketBase schema still has it:
```
monthly_income fields: asset, fixed_expenses, income, is_forecast, month, running_cost, year
```
So the same number is still stored in two collections and will drift the moment
either is edited.
- [ ] Remove the `running_cost` field from the `monthly_income` collection in
PocketBase itself, not just from the seed payload.
- [ ] Derive running cost on read by joining `monthly_running_costs` on
`(asset, year, month)`.
- [ ] Update `buildIncomeRows` in `lib/rows.ts` to take running costs as a separate
argument rather than reading `r.running_cost`.
- [ ] Confirm the field is gone by listing the collection schema, and paste the output.
## B.2 32 of 40 machines have income, not 40
Better than the 19 you started with, but still short. 8 machine blocks from the
workbook are still not landing.
- [ ] Report exactly which 8 are missing and why each one fails to match.
- [ ] Fix the matching for those 8.
- [ ] Do not close this item by creating assets for them — see B.3.
## B.3 The seed invents assets instead of failing
Round 1 §1.4 asked for an explicit alias map and a loud failure on unmatched rows.
Instead `scripts/seed.mjs` gained a `syntheticCode()` fallback that auto-creates
asset rows. Two exist now — `ZPORTCHCG` and `ZPORTCHCH`, both "Porta camp" with
`monthly_incl=0` and `purchase_value=0`.
The damage is small, but the pattern defeats the purpose: **the seed can no longer
fail**, so it can never tell you the source data has a problem. It swapped a silent
drop for a silent ghost row.
- [ ] Delete `syntheticCode()` and the auto-create branch.
- [ ] Unmatched rows collect into an error list; print the full list and
`process.exit(1)`.
- [ ] Delete the two `Z*` assets from PocketBase.
- [ ] Handle the Porta camps by giving them real asset numbers (see C.3).
---
# Part C — The asset register
This is the main work this round.
## C.1 Five machines are stored as 13 separate "assets"
The `assets` collection has categories `Finance Costs` (5), `Insurance` (5), and
`Tracking` (3). These are **not assets**. They are cost components of five machines,
split across three rows each:
```
TLB06 Finance 29036.08 | Insurance 2401.81 | Tracking 694.51
TLB07 Finance 29819.00 | Insurance 2797.63 | Tracking 605.39
MG02 Finance 19088.95 | Insurance 2414.09 | Tracking —
RV04 Finance 28641.00 | Insurance 1459.25 | Tracking —
Toyota Bakkie LD13HZ GP (no code)
Finance 8990.91 | Insurance 2131.99 | Tracking 249.45
```
So the Plant Register currently shows TLB06 three times and never once as a machine.
TLB07 three times. MG02 and RV04 twice each. The Toyota Bakkie three times.
These are real revenue-earning machines — TLB06 and TLB07 both have income in the
workbook's graph sheet.
- [ ] Collapse each of these five into **one** asset row with its proper code
(`TLB06`, `TLB07`, `MG02`, `RV04`, and a new code for the Toyota Bakkie).
- [ ] Move the finance / insurance / tracking amounts into a new `cost_lines`
collection: `asset (relation), year, month, kind, amount`, where `kind` is
one of `instalment | insurance | tracking | operator | running`.
- [ ] Delete the `Finance Costs`, `Insurance`, and `Tracking` categories from
`assets` entirely.
- [ ] After this, `assets` should contain only physical things: plant and vehicles.
## C.2 Group by machine type, not category
After C.1, `category` is nearly useless — almost everything is `Plant`, which means
one undifferentiated 37-row table plus a few 3-row cards that spend a full header,
icon, and totals row on almost nothing.
Group by **machine type**, derived from the code prefix:
| Prefix | Type | Members |
| --- | --- | --- |
| `TLB` | Backhoe loaders (TLB) | TLB01–TLB07 |
| `SS` | Skid steers | SS01, SS02 |
| `RV` | Rollers | RV01, RV03, RV04 |
| `MG` | Graders | MG01, MG02 |
| `TP6` | Tipper trucks | TP601, TP602 |
| `WB` | Water bowsers | WB01, WB02 |
| `EXC` | Excavators | EXC01 |
| `CT` | Crane / cherry picker | CT01 |
| `DT` | Diesel truck | DT01 |
| `WT` | Water trailer | WT01 |
| — | Site infrastructure | containers, generators, camps |
- [ ] Add a `type` field on `assets`, populated from the code prefix at seed time.
Store it — do not re-derive it in the UI on every render.
- [ ] Confirm the type labels above with the client before committing to them; some
are inferred from the code letters.
## C.3 Uncoded duplicates need real asset numbers
Seven rows all named `SITE CONTANER` with no code. Three `Toyota Bakkie - LD13HZ GP`.
Two `Office Container`. Two `Porta camp`. These are different physical units that are
completely indistinguishable in the UI — you cannot tell which container is which.
- [ ] Ask the client for real asset numbers for every uncoded item.
- [ ] Until they supply them, assign sequential codes (`SC01`–`SC07`, `OC01`–`OC02`,
`PC01`–`PC02`) and **flag them in the UI as provisional** so nobody trusts them.
- [ ] Fix the spelling: `SITE CONTANER` → `SITE CONTAINER`.
## C.4 Flatten the register to one table
With ~41 real assets, the job on this page is scanning, sorting, and comparing
machines against each other. Nested cards actively prevent that — you cannot compare
a TLB against a grader when they're in two separate tables.
- [ ] Replace the per-category card sections with **one** table.
- [ ] Columns: Code · Name · Type · Supplier · Monthly excl · Monthly incl · Purchase value.
- [ ] Sortable by clicking any column header. Default sort: Type, then Code.
- [ ] Sticky header row.
- [ ] One totals row at the bottom.
- [ ] A `Type` filter as segmented pills above the table (All / TLB / SS / RV / …).
Keep the existing card container, rounded corners, border, and shadow around the
table — the page should still look like it belongs to this app.
If the client prefers keeping visible sections, the fallback is: group by `type`
(not `category`), make each section collapsible, and put the section totals in the
collapsed header. Ask before choosing.
## C.5 The `Filter…` input on the register is fake
`app/plant/page.tsx` still has a non-functional search input — the exact dead-UI
problem that was just fixed in the header.
- [ ] Wire it to filter the table by code, name, type, or supplier.
- [ ] Or delete it and rely on the header's `GlobalSearch`. Do not leave it dead.
---
# Part D — Open Question 2 is now partly answered
Round 1 flagged that every fixed-expense cell is a hand-typed sum of three unlabelled
constants. The `assets` data answers the second term.
```
Workbook TLB06 I9: = 29036.28 + 2401.81 + 5620.2
assets TLB06 Finance Insurance
29036.08 2401.81 ← exact match
Workbook TLB07 I10: = 29819.00 + 2797.63 + 5620.2
assets TLB07 Finance Insurance
29819.00 2797.63 ← exact match
```
So:
- **Term 1** = monthly instalment / finance cost ✅ confirmed
- **Term 2** = **insurance** ✅ confirmed (the flat `1300` on owned machines is their
insurance; the Bastion-financed units carry their own per-machine premium)
- **Term 3** = still unknown. Varies per machine *and* per month — `5620.20`,
`6437.20`, `7315.95`, `10653.73`. Most likely operator wages, possibly with tracking
folded in. **Still needs client confirmation.**
- [ ] Once C.1 is done, verify the reconstruction holds for all five financed machines
across all 12 months, and report any that don't add up.
- [ ] Ask the client what term 3 is. That is the last piece needed before fixed
expenses can be computed from inputs rather than copied as a stored number.
---
# Acceptance criteria
- [ ] `npm run typecheck` exits clean
- [ ] `npm run build` succeeds
- [ ] Dev server stays idle when no files change (no recompile loop)
- [ ] `running_cost` no longer exists on `monthly_income`; schema output pasted as proof
- [ ] 40 of 40 machines have income, with zero auto-created assets
- [ ] Seed exits non-zero and lists every failure when any row cannot be matched
- [ ] `assets` contains only physical plant and vehicles — no `Finance Costs`,
`Insurance`, or `Tracking` categories
- [ ] TLB06, TLB07, MG02, RV04, and the Toyota Bakkie each appear exactly **once**
- [ ] Every asset has a code; provisional codes are visibly marked as such
- [ ] Plant Register is one sortable table with a working filter
- [ ] Term 2 of the fixed-expense formula reconciles to insurance for all five
financed machines
- [ ] **Palette, roundness, gradients, and dark mode visually unchanged**