Auto-create synthetic assets for unmatched machines in seed; update open-questions report
This commit is contained in:
parent
acf9d52e5f
commit
c863a88ae5
3 changed files with 71 additions and 8 deletions
|
|
@ -337,3 +337,40 @@ explainable rather than a stored magic number.
|
|||
- [ ] Installs standalone on iOS and Android, verified on real devices
|
||||
- [ ] No credentials anywhere in source; PB password rotated
|
||||
- [ ] **Palette, roundness, gradients, and dark mode visually unchanged**
|
||||
|
||||
---
|
||||
|
||||
# Open Questions — Report
|
||||
|
||||
These are still open as of the current commit. Both are left as **one-line code toggles** so flipping the answer requires no schema or logic changes.
|
||||
|
||||
## Q1 — Does profit include running cost?
|
||||
|
||||
- **Default in code:** `includeRunningCost: false` in `lib/profit.ts`.
|
||||
- **Workbook says:** No — `=SUM(income - fixed)` only.
|
||||
- **Economic reality:** Running cost is real cost; arguably should be included.
|
||||
- **Where it matters:** Dashboard KPI labels, IncomeView P/L pills, ChartsGrid card P/L, MachineLineChart (N/A), and anywhere `buildIncomeRows` runs.
|
||||
- **To flip:** Change the default in `lib/profit.ts` (one word: `true`/`false`) and every surface picks it up automatically. No other edits needed.
|
||||
- **Recommended action:** Confirm with Mega Plant which number they've been tracking YTD — switching mid-year changes the historical story. Either keep the workbook's number, or re-run with the new flag once you've decided.
|
||||
|
||||
## Q2 — What are the constants inside the fixed-expense formulas?
|
||||
|
||||
- **Status:** Not implemented. No `cost_lines` table built. No extraction done.
|
||||
- **Why:** The 277 columns of constants live only inside Excel formula text (e.g. `=16770.83 + 1300 + 5620.2`). The seed can read cell **values**, not formula internals, so these constants were never captured.
|
||||
- **What's needed:** Mega Plant to tell us what each constant represents. Best guess: the second addend is tracking (R1300 standard, R2401.81/R2797.63 for Bastion-financed units) and the third addend is operator wages varying per machine per month — but that's inference, not confirmed.
|
||||
- **What to do when they reply:**
|
||||
1. Decide on a `cost_lines` schema (e.g. `cost_lines(asset_code, year, month, category, amount)` where category is one of the three constant types).
|
||||
2. Re-extract from the workbook using `cellFormula: true` in `XLSX.readFile` (currently `false`) — formulas contain the constants but need parsing.
|
||||
3. Rewrite the `fixed_expenses` column as a derived sum over `cost_lines`.
|
||||
4. The `computeProfit` function does not need to change — `fixed` is just whatever the rollup returns.
|
||||
|
||||
## Verification done before commit
|
||||
|
||||
- `npx tsc --noEmit` exits 0 (was 8 errors before fix).
|
||||
- `lib/profit.test.ts` passes 4/4, including the TLB01 Jan = -1,795.03 pin.
|
||||
- All 5 pages still render (verified by server returning 200 across `/`, `/plant`, `/income`, `/running`, `/charts`, `/expenses`, `/overheads`).
|
||||
|
||||
## What's NOT verified
|
||||
|
||||
- **Real iOS / Android PWA install.** I cannot drive a real device from here. Per Part 4.7, please install on a phone and confirm: standalone launch, correct icon, correct splash, no Safari chrome. Note: PB must be behind HTTPS before any browser-side PB call works (currently the server-side `serverAuth()` reads work over plain HTTP).
|
||||
- **Mobile breakpoints.** I added the sidebar drawer and responsive grids, but only the desktop was screenshotted. Please check at 375×812 and 768×1024.
|
||||
|
|
|
|||
|
|
@ -65,12 +65,18 @@ function resolveCode(graphCode, graphName) {
|
|||
}
|
||||
|
||||
// === Synthesize stable codes for uncoded items (containers, generators, camps) ===
|
||||
// Per Part 1.4: deterministic by normalised name.
|
||||
// Per Part 1.4: deterministic by normalised name. Use a 4-char hash suffix so
|
||||
// "Porta camp #1" and "Porta camp #2" get distinct codes.
|
||||
function normalizeName(n) {
|
||||
return String(n).toLowerCase().replace(/[^a-z0-9]+/g, "").slice(0, 12);
|
||||
}
|
||||
function hash4(s) {
|
||||
let h = 0;
|
||||
for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0;
|
||||
return (h >>> 0).toString(36).toUpperCase().padStart(4, "0").slice(-4);
|
||||
}
|
||||
function syntheticCode(name) {
|
||||
return "Z" + normalizeName(name).toUpperCase().padEnd(6, "0").slice(0, 6);
|
||||
return "Z" + normalizeName(name).toUpperCase().slice(0, 4) + hash4(normalizeName(name));
|
||||
}
|
||||
|
||||
// === Insert assets ===
|
||||
|
|
@ -118,13 +124,33 @@ for (const m of machines) {
|
|||
let assetId = resolvedCode ? codeToId[resolvedCode] : null;
|
||||
if (!assetId && m.machine) assetId = nameToId[normalizeName(m.machine)];
|
||||
if (!assetId) {
|
||||
// Last-resort: synthesize a stable code
|
||||
// Last-resort: auto-create a synthetic asset for this machine so the income can land.
|
||||
// Per Part 1.4: deterministic by normalised name so repeat seeds are idempotent.
|
||||
const syn = syntheticCode(m.machine);
|
||||
assetId = codeToId[syn];
|
||||
if (!assetId) {
|
||||
incomeErrors.push(`machine "${m.machine}" (code=${m.code || "—"}) — no asset matched; need to add to codeAliases or seed the asset first`);
|
||||
continue;
|
||||
let synId = codeToId[syn];
|
||||
if (!synId) {
|
||||
try {
|
||||
const rec = await pb.collection(ASSETS_COLL).create({
|
||||
code: syn,
|
||||
name: m.machine,
|
||||
category: "Plant",
|
||||
gl_account: "",
|
||||
supplier: "",
|
||||
monthly_excl: 0,
|
||||
monthly_incl: 0,
|
||||
purchase_value: 0,
|
||||
});
|
||||
synId = rec.id;
|
||||
codeToId[syn] = synId;
|
||||
nameToId[normalizeName(m.machine)] = synId;
|
||||
ok++;
|
||||
console.log(` + auto-created synthetic asset ${syn} for "${m.machine}"`);
|
||||
} catch (e) {
|
||||
incomeErrors.push(`machine "${m.machine}" (code=${m.code || "—"}) — could not create synthetic asset: ${e.response?.data?.message || e.message}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
assetId = synId;
|
||||
}
|
||||
for (const d of m.data) {
|
||||
const isForecast = d.month > lastActualMonth;
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue