# CESCO mixer mounting-position calculator — teaching brief for Grok

Paste this entire file into a Grok chat and say:

> This is the full brief for the Cesco Australia transit-mixer mounting-position calculator. Treat it as source of truth. Rebuild or continue the app from this. Do not invent new physics. Empirical numbers may be wrong — the user will upload a corrected yellow-cell xlsx (`CESCO_empirical_data.xlsx`) to replace tables only, not math.

Owner: Meth Visula (Cesco Australia). Daily shop/engineering tool. Position rounded to nearest 5 mm.

---

## 1. What the tool is

A **single-page web calculator** that replaces two (then three) Excel workbooks used to decide **where to mount a transit concrete mixer on a truck chassis**.

Output that the shop actually uses:

- **Shop position (mm)** — distance from the **centre of the rear axle group** to the **rear of the mixer** (end of frame / mixer rear). Rounded to nearest **5 mm**.
- **Theoretical position (mm)** — same distance before rounding.
- Corrected chassis tare (front / rear / total).
- Max legal payload and volume.
- Estimated axle loads from 0.00 to 10.00 m³ in 0.1 steps.

Trucks may be 6x4, 8x4, 10x4 etc. **Load-sharing axle groups are treated as one front point load and one rear point load** at the centre of each group. Record the real axle config (optional). Calculation always uses a single wheelbase `WB`.

---

## 2. Source workbooks (history)

1. `FINAL TARE CAL & TRUCK WT with Ad blue.xls` — tare correction (empty/partial fuel & AdBlue → full; driver to 100 kg).
2. `AxelLoad2016.xls` — mini vs maxi mixer sheets. One sheet per job. Empirical COG of mixer **full of concrete at nominal capacity**. Then payload is reduced to legal, COG is shifted, mounting position is computed.
3. **Authoritative:** `Copy of faddf77a292b1817ec4428712e5e6458405eeac04ac97b827d68fa33bb1.xlsm` — “forget previous logic, do the app based on this excel.” Spec sheet + truck spec + option penalties + COG table + state legal limits.

The live engine is a **faithful port of that third workbook**, not a first-principles rewrite.

---

## 3. Sign convention (do not flip)

All longitudinal distances in mm.

| Symbol | Meaning |
|---|---|
| `WB` | Wheelbase, steer-axle CL to rear-axle-group CL |
| Fuel / AdBlue `from FA` | Distance of tank CL **from front axle**, aft |
| Mixer **COG at nominal** | Distance of mixer+nominal-concrete COG **from the rear of the mixer**, forward |
| **Position** (theo / shop) | Distance from **rear-axle-group CL** to **mixer rear**. This is what is set on the shop floor |
| COG from rear (legal split) | Where the mixer+payload resultant must sit, measured forward from rear axle CL, so legal front/rear are just filled |

If you reverse any of these, shop position will be wrong by roughly a wheelbase.

---

## 4. Math (locked — do not “improve”)

Units: tonnes (t), millimetres (mm), litres (L), kg. Density t/m³.

### 4.1 Driver (workbook A31)

```
driverAddT = (100 − driverKg) / 1000
```

`driverKg = 0` means **no driver was on the weighbridge** → add **100 kg to the front**.

### 4.2 Diesel — fill remaining **to FULL** @ 0.8 kg/L (A32 / A39)

```
remainL = fuelCapL × (1 − fuelLevel)          # fuelLevel is 0–1
fuelMassT = remainL × 0.8 / 1000
fuelFrontT = remainL × 0.8 × (WB − fuelLocMm) / (WB × 1000)
fuelRearT  = fuelMassT − fuelFrontT
```

### 4.3 AdBlue — **as written in the workbook, not “corrected”**

Front share uses **0.8 kg/L** (A33). Remainder uses **1.0 kg/L** (A40).

```
remainL = adCapL × (1 − adLevel)
adFrontT = remainL × 0.8 × (WB − adLocMm) / (WB × 1000)
adMassT  = remainL × 1.0 / 1000
adRearT  = adMassT − adFrontT
```

Yes, front and rear densities differ. Port it literally.

### 4.4 Final tare (A34 / A41)

```
finalFrontT = initFrontT + driverAddT + fuelFrontT + adFrontT
finalRearT  = initRearT  + fuelRearT  + adRearT
finalTotalT = finalFrontT + finalRearT
```

### 4.5 Legal limits

From state + mass-management table (below). User may override front/rear. `GVM = legalFront + legalRear`.

### 4.6 Mixer lookup

```
size_label  → SIZE_MAP → nominalM3, tableStartM3
nominalM3   → SIZE_ROWS[nominalM3]
framed      = mixerType ∈ {Standard Loadmaster, Weight$aver}
cogNominalMm  = framed ? framedCog : framelessCog
basicWeightT  = framed ? framedWt  : framelessWt
barrelPenT    = SIZE_ROWS.barrelPenT
gearboxPenT   = SIZE_ROWS.gearboxPenT
```

**Current quirk:** dropdown `7.6m3` maps to **nominal 7.5**. User may change this in the empirical xlsx.

Auto position is allowed only if `cogNominalMm > 0` AND `basicWeightT > 0`. Otherwise **Invalid**.

### 4.7 Option penalties (workbook operational-weight block)

```
R = 1 on the “standard” side of the pair, 0 on the other
S = (R == 0) ? 1 : 0
appliedT = (S > R) ? X : 0     # i.e. only fires when R = 0
```

| Line | R = 1 (no apply) | R = 0 (apply X) | X |
|---|---|---|---|
| Barrel shell | `standard` | `4mm` | from size table `barrelPenT` |
| Gearbox | any gearbox **except ZF4300** | **ZF4300** (inverted) | from size table `gearboxPenT` |
| Wheel guards | Steel Running Boards | Standard Plastic (or anything else) | **−0.04 t** |
| Trunnion rollers | Standard Steel Greaseable | Nylon | **−0.03 t** |
| Chutes | Standard Steel Frame Poly Lined | Steel | **−0.025 t** |
| Runners | Steel | fixed nylon (or anything else) | **−0.06 t** |
| Load cell | Standard No scales | Load cells | **+0.053 t** |
| Water tank | n/a | always | `litres / 1000` |

```
operationalWeightT = basicWeightT + Σ appliedT
```

### 4.8 Payload, COG shift, position (L70 / L73 / L84)

```
maxPayKg = max(0, 1000 × (GVM − operationalWeightT − finalTotalT))
maxVolM3 = maxPayKg / densityTm3 / 1000

availFrontT = legalFrontT − finalFrontT
availRearT  = legalRearT  − finalRearT
cogFromRearMm = WB × availFrontT / (availFrontT + availRearT)

cogShiftMm = cogShiftMmPerM3 × (nominalM3 − maxVolM3)
             # default cogShiftMmPerM3 = 55
             # positive when carrying less than nominal → COG moves forward relative to mixer rear

posTheoMm = cogNominalMm + cogShiftMm − cogFromRearMm
posShopMm = round(posTheoMm / 5) × 5     # nearest 5 mm, including .5 → away from 0 per JS Math.round

cogAfterRoundMm = cogNominalMm + cogShiftMm − posShopMm
overhangLimitMm = 0.6 × WB
overhangLegal   = posShopMm < overhangLimitMm
```

Mixer share at max payload:

```
mixerPlusPayT    = operationalWeightT + maxPayKg/1000
mixerFrontAtMaxT = mixerPlusPayT × cogAfterRoundMm / WB
mixerRearAtMaxT  = mixerPlusPayT − mixerFrontAtMaxT
totalFrontAtMax  = finalFrontT + mixerFrontAtMaxT
totalRearAtMax   = finalRearT  + mixerRearAtMaxT
```

### 4.9 Axle-load table

For `V = 0.00, 0.10, … 10.00`, plus an extra row at `maxVolM3` if it is not on a 0.1 step:

```
payloadKg = V × densityTm3 × 1000
M         = payloadKg/1000 + operationalWeightT
cogC      = cogNominalMm + cogShiftMmPerM3 × (nominalM3 − V)
arm       = cogC − posShopMm
frontT    = finalFrontT + M × arm / WB
rearT     = finalRearT  + M × (WB − arm) / WB
OVER if front > legalFront+0.005 or rear > legalRear+0.005 or total > GVM+0.005
MAX row = the legal volume row
```

Do not emit this table until `positionValid` is true.

---

## 5. Empirical tables currently in the app

**Some of these are wrong.** The user has `CESCO_empirical_data.xlsx` (yellow = editable). When they upload a corrected copy, **replace tables only, keep §4 math.**

### 5.1 SIZE_MAP (dropdown → lookup key)

| size_label | nominal_m3 | table_start_m3 | auto ok? |
|---|---:|---:|---|
| 2.4m3 | 2.4 | 1.1 | NO (no COG/weight row) |
| 3.0m3 | 3.0 | 1.5 | NO |
| 3.5m3 | 3.5 | 1.5 | NO |
| 4m3 | 4.0 | 2.0 | NO |
| 6m3 | 6.0 | 4.0 | NO (COG 2120 but weight 0) |
| 6.5m3 | 6.5 | 4.0 | YES |
| 7m3 | 7.0 | 5.5 | NO (COG 2428 but weight 0) |
| **7.6m3** | **7.5** | 5.5 | YES framed+frameless |
| 8m3 | 8.0 | 5.5 | framed only |
| 9m3 | 9.0 | 5.5 | YES |
| 10m3 | 10.0 | 5.5 | NO |
| 11.5m3 Semi-agi | 11.5 | 5.5 | NO |

### 5.2 SIZE_ROWS (by nominal_m3)

| nominal | framedCog mm | framelessCog mm | framedWt t | framelessWt t | barrel X t | ZF4300 X t |
|---:|---:|---:|---:|---:|---:|---:|
| 2.4 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3.0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3.5 | 0 | 0 | 0 | 0 | 0 | 0 |
| 4.0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 6.0 | 2120 | 0 | 0 | 0 | 0 | 0 |
| 6.5 | 2300 | 2232 | 2.80 | 2.40 | 0.160 | 0.100 |
| 7.0 | 2428 | 0 | 0 | 0 | 0 | 0 |
| 7.5 | 2480 | 2450 | 3.13 | 2.75 | 0.125 | 0.045 |
| 8.0 | 2750 | 0 | 3.42 | 0 | 0.200 | 0.045 |
| 9.0 | 2998 | 2954 | 3.54 | 3.23 | 0.210 | 0.045 |
| 10.0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 11.5 | 0 | 0 | 0 | 0 | 0 | 0 |

`0` = no workbook data → Invalid for that frame.

### 5.3 Water litres

| label | L |
|---|---:|
| Standard 200 Litre | 200 |
| 180 Litre | 180 |
| 300 Litre | 300 |
| 200ltr with 150l restrictor | 200 |
| 200ltr and 300ltr | 500 |
| 2 x 200 ltr | 400 |
| other / N/A / blank | 0 |

### 5.4 Legal limits (t) — Truck specification P19:V23

| state | MM yes F | MM yes R | MM no F | MM no R |
|---|---:|---:|---:|---:|
| QLD | 12.0 | 17.0 | 11.5 | 16.5 |
| NSW | 11.5 | 17.0 | 11.0 | 16.5 |
| VIC | 11.5 | 17.0 | 11.0 | 16.5 |
| SA | 11.5 | 17.0 | 11.0 | 16.5 |
| WA | 11.0 | 17.0 | 11.0 | 16.5 |
| NT | 11.0 | 17.0 | 11.0 | 16.5 |
| TAS | 11.0 | 17.0 | 11.0 | 16.5 |

### 5.5 Constants

| key | value |
|---|---|
| driver target | 100 kg |
| diesel | 0.8 kg/L, fill **to full** |
| AdBlue front | 0.8 kg/L |
| AdBlue remainder | 1.0 kg/L |
| shop round | 5 mm |
| overhang | 0.6 × WB |
| default density | **2.35 t/m³** |
| default COG shift | **55 mm per m³** |
| axle table | 0.00–10.00 step 0.1 |

### 5.6 Frame from mixer type

- Framed: `Standard Loadmaster`, `Weight$aver`
- Frameless: `Frameless`

---

## 6. Golden regression — spec example

Must still produce **shop 1245 mm** after any table/UI change (until the user changes empirical data).

```
serial 2023040, Mack ANTHEM 2024 8x4, QLD, mass management YES
WB 4450
init F/R 6.24 / 2.98 t
driver 0 kg
diesel 350 L @ 0.25, 2700 mm from FA
AdBlue 70 L @ 0.50, 1880 mm from FA
mixer Standard Loadmaster 7.6m3 (→ nominal 7.5)
ZF4300, Standard Plastic, Standard Steel Greaseable,
Standard Steel Frame Poly Lined, fixed nylon, 4mm barrel,
Standard No scales, Standard 200 Litre
density 2.35, COG shift 55
legal 12 / 17 t
```

Expected (4 dp shown; shop is integer):

```
driverAddT     = 0.1000 t
fuel remain    = 262.5 L
fuelMassT      = 0.2100 t
fuelFrontT     = 0.0826 t
fuelRearT      = 0.1274 t
ad remain      = 35 L
adFrontT       = 0.0162 t
adMassT        = 0.0350 t
adRearT        = 0.0188 t
finalFrontT    = 6.4388 t
finalRearT     = 3.1262 t
finalTotalT    = 9.5650 t
cogNominal     = 2480 mm
basicWeight    = 3.13 t
penalties      = +0.125 +0.045 −0.04 +0 −0.06 +0 +0.200 = +0.270 t
operationalWt  = 3.400 t
GVM            = 29 t
maxPay         = 16.035 t = 16035 kg
maxVol         = 6.8234 m³
avail F/R      = 5.5612 / 13.8738 t
cogFromRear    ≈ 1273.1 mm
cogShift       ≈ 37.21 mm
posTheo        ≈ 1244 mm
posShop        = 1245 mm
```

If you change empirical tables, this golden example will move. Keep a copy of “before” vs “after”.

---

## 7. Product / UI rules the user already locked in

Single page, this order:

1. **Truck & chassis tare** → live corrected F/R/total at the bottom of the section, fed into section 2.
2. **Mixer, accessories, legal limits** (legal front/rear live **here**, not in truck section).
3. **Mounting position** (theo + shop) then shop ticket.
4. **Estimated axle loading** full 0–10 m³ table.

Other locked behaviour:

- Do **not** show quote number, salesman, company spec in the UI (fields exist on the type for export/jobs only).
- Do **not** show the Excel drawings/previews.
- Required fields (`*`): wheelbase, initial F/R tare, driver in tare, diesel level/tank/from FA, AdBlue level/tank/from FA, mixer type/size/nominal (nominal is derived), all accessories, density, COG shift, state, mass management, legal F/R.
- Optional labelled “optional”: serial, customer, stage, truck make/model/year, axle config, chassis dims.
- Required controls are **bright teal** (`#2dd4bf`) with dark text. Optional/output stay dark.
- Form **starts blank** (all 0 / Select…) **except** density 2.35 and COG shift 55.
- Shop and theoretical read **`Invalid`** until every required field has a real value **and** the size/frame is in the COG table.
- **Do not restore a previous localStorage draft on page load** — that overwrote the form after the 2nd/3rd keystroke. Autosave may exist; load draft must not stomp a blank start. Draft key currently `cesco.mixer.draft.v3`.
- **Reset all** → `blankInput()` (density 2.35, shift 55). **Spec example** loads the golden job.
- Enter in an input/select jumps to the next editable field and selects it.
- Each of the four sections has a collapsed **Show the work** `<details>` panel with live substitution.
- Toolbar: Jobs, Reset all, Spec example, Tables (empirical xlsx), Print, Export JSON, Save.
- Jobs: localStorage always; cloud if signed in (TanStack Start / better-auth).
- Print: shop ticket + sections; hide chrome (`.no-print`).

---

## 8. App stack (current workspace)

- TanStack Start + React + Vite, port **8080**, host `0.0.0.0`
- Tailwind v4, IBM Plex Sans / Mono, dark industrial theme
- Pure calc: `src/lib/engine.ts`
- Tables/dropdowns: `src/lib/catalog.ts`
- Types: `src/lib/types.ts`
- UI: `src/components/calculator.tsx`, `field.tsx`, `work.tsx`, `shop-ticket.tsx`
- Empirical xlsx served from `public/CESCO_empirical_data.xlsx`
- This brief served from `public/CESCO_Grok_teaching_brief.md`

Do not gold-plate. Do not add auth-gated calc. Calc must work signed out.

---

## 9. How to ingest a corrected empirical xlsx

User uploads `CESCO_empirical_data.xlsx` with yellow cells edited.

Sheets and what to write into `catalog.ts` / engine constants:

| Sheet | Writes to |
|---|---|
| SIZE_MAP | `SIZE_MAP` |
| SIZE_COG_WEIGHT | `SIZE_ROWS` |
| OPTION_PENALTIES | `optionPenalties()` X values and R=1/R=0 strings |
| WATER_TANKS | `waterLitres()` |
| MIXER_TYPES | `isFramed()` |
| LEGAL_LIMITS | `LEGAL_LIMITS` |
| CONSTANTS | densities, driver target, shop round, defaults |
| DROPDOWNS | option lists |

Do **not** change formula structure unless CONSTANTS / a user message explicitly says the method changed.

Grey key columns must stay so a parser can round-trip.

---

## 10. What not to do

- Do not derive COG from first principles or “fix” AdBlue 0.8/1.0 split.
- Do not treat mini/maxi as separate engines; one engine, size table does the work.
- Do not hide the axle table or paginate it.
- Do not default the form to the Mack spec example.
- Do not auto-load last job into a fresh session.
- Do not show position numbers while required inputs are 0.

---

## 11. Prompt to give the next Grok (copy)

```
You are continuing Cesco Australia’s mixer mounting-position calculator.
Read CESCO_Grok_teaching_brief.md as the spec.
Math in §4 is locked. Empirical tables in §5 are current but some values are wrong;
when I upload CESCO_empirical_data.xlsx, replace tables only.
UI rules in §7 are locked.
Golden spec example in §6 must stay 1245 mm shop until tables change.
Rebuild or edit the existing TanStack app; do not start a parallel calculator.
```

---

## 12. Source snapshots (current)

### catalog.ts — SIZE_MAP / SIZE_ROWS / legal / water / defaults

See workspace file `src/lib/catalog.ts` and `src/lib/engine.ts`. They are the implementation of §4–§5. If this brief and those files disagree, **the TypeScript is what the running app does**; update the brief after a table import.

End of brief.
