mega-plant/HANDOFF-PROMPT-2.md

245 lines
10 KiB
Markdown
Raw Normal View History

# 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**