diff --git a/AGENTS.md b/AGENTS.md index 3b7a703..a0dfda0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -819,7 +819,15 @@ session with **no other memory of this project**. So: calculator (2026-10-05):** the first in-app tool lives at `/diy/deck-cost` (`components/DeckCostCalculator.tsx`) — a self-contained (localStorage) model of labour, equipment amortisation, operations, materials, process time, packaging - and margin → selling price; the `deckCost` card links to it. **Currency + and margin → selling price; the `deckCost` card links to it. **Naming is + deliberate (2026-10-05):** the labour multiplier is **"Labour burden (×)"** (not + "overhead" — rent/bills are the separate **Operation expenses** section; the + hint warns against counting them twice), the derived `wage × burden` figure is + the **"Loaded rate"**, and the multiplier is **"Markup (×)"** with the implied + **gross margin %** shown (it is *not* a margin). The three `# boards` columns + are context-specific: **Lifetime boards** (equipment), **Boards / order** + (materials), **Boards / run** (processes). CSV keys stay machine-stable + (`overhead_factor`, `profit_margin`, …) for round-tripping. **Currency (2026-10-05):** a **searchable currency selector** (Popover + search) covering **sats + BTC** (both Bitcoin units) and every **fiat** Coinbase reports (crypto is filtered out via `FIAT_CODES`), and switching **converts every money field** diff --git a/src/components/DeckCostCalculator.tsx b/src/components/DeckCostCalculator.tsx index db70d1e..5ddbdf0 100644 --- a/src/components/DeckCostCalculator.tsx +++ b/src/components/DeckCostCalculator.tsx @@ -479,6 +479,9 @@ export function DeckCostCalculator() { }; }, [s]); + /** Markup (×) expressed as a gross margin, so the name matches the maths. */ + const marginPct = s.profitMargin > 0 ? Math.round((1 - 1 / s.profitMargin) * 100) : 0; + const patch = (p: Partial) => setS((prev) => ({ ...prev, ...p })); const savePreset = () => { @@ -554,8 +557,8 @@ export function DeckCostCalculator() { section('Labor'); kv('Hourly rate', fmt(s.hourlyRate)); - kv('Overhead factor', `${s.overheadFactor}\u00d7`); - kv('Labor cost', `${fmt(s.hourlyRate * s.overheadFactor)}/hr`); + kv('Labour burden', `${s.overheadFactor}\u00d7`); + kv('Loaded rate', `${fmt(s.hourlyRate * s.overheadFactor)}/hr`); section('Equipment'); s.equipment.forEach((e) => { @@ -589,8 +592,8 @@ export function DeckCostCalculator() { // ── Result (kept together) ── ensure(180); section('Result'); - kv('Profit margin', `${s.profitMargin}\u00d7`); - kv('Scrap', `${s.scrapPct}%`); + kv('Markup', `${s.profitMargin}\u00d7 (\u2248 ${marginPct}% gross margin)`); + kv('Scrap / waste', `${s.scrapPct}%`); kv('Cost / board (no packaging)', fmt(r.costNoPack)); kv('Cost / board (with packaging)', fmt(r.costWithPack)); y += 6; @@ -867,8 +870,8 @@ export function DeckCostCalculator() { '', '## Labor', `- Hourly rate: ${fmt(s.hourlyRate)}`, - `- Overhead factor: ${s.overheadFactor}×`, - `- Labor cost: ${fmt(s.hourlyRate * s.overheadFactor)}/hr`, + `- Labour burden: ${s.overheadFactor}×`, + `- Loaded rate: ${fmt(s.hourlyRate * s.overheadFactor)}/hr`, '', '## Equipment', ...s.equipment.map((e) => `- ${e.name}: ${fmt(e.boards > 0 ? (e.cost + e.setupHrs * r.laborPerHr) / e.boards : 0)} (${fmt(e.cost)} · ${e.boards.toLocaleString()} boards)`), @@ -894,7 +897,7 @@ export function DeckCostCalculator() { `- **Packaging per board: ${fmt(r.packaging)}**`, '', '## Result', - `- Profit margin: ${s.profitMargin}× · Scrap: ${s.scrapPct}%`, + `- Markup: ${s.profitMargin}× (≈ ${marginPct}% gross margin) · Scrap: ${s.scrapPct}%`, `- Cost/board (no packaging): ${fmt(r.costNoPack)}`, `- Cost/board (with packaging): ${fmt(r.costWithPack)}`, `- **Selling price (no packaging): ${fmt(r.priceNoPack)}**`, @@ -1043,16 +1046,17 @@ export function DeckCostCalculator() { {numInput(s.hourlyRate, (n) => patch({ hourlyRate: n }))}
- Labor cost + Loaded rate
{fmt(s.hourlyRate * s.overheadFactor)}/hr
+

Loaded rate = wage × burden (employer taxes and insurance on labour). Rent and bills go under Operation expenses — don’t count them in both.

{/* Equipment */} @@ -1060,7 +1064,7 @@ export function DeckCostCalculator() {

Equipment

- + {s.equipment.map((e) => ( @@ -1098,7 +1102,7 @@ export function DeckCostCalculator() {

Materials

EquipmentCost ({unit})Setup (h)# boardsPer board
EquipmentCost ({unit})Setup (h)Lifetime boardsPer board
- + {s.materials.map((m) => { const per = m.boards > 0 ? (m.orderCost + m.shipping + (m.orderMin + m.unloadMin) * r.laborPerMin) / m.boards : 0; @@ -1130,7 +1134,7 @@ export function DeckCostCalculator() {

Process costs

MaterialOrder ({unit})Ship ({unit})# boardsOrder (m)Unload (m)Per board
MaterialOrder ({unit})Ship ({unit})Boards / orderOrder (m)Unload (m)Per board
- + {s.processes.map((p) => { const per = p.boards > 0 ? (p.minutes * r.laborPerMin) / p.boards : 0; @@ -1187,11 +1191,14 @@ export function DeckCostCalculator() {

Margins

- - + +
Cost / board (no packaging)
{fmt(r.costNoPack)}
Cost / board (with packaging)
{fmt(r.costWithPack)}
+

+ Markup {s.profitMargin}× ≈ {marginPct}% gross margin · waste {s.scrapPct}% is spread over the good boards. +

ProcessTime (m)# boardsPer board
ProcessTime (m)Boards / runPer board