Project Tracker

Every task maps to a deliverable on the portal (Foundation, Models 1–8, Platform). Ordered by priority: M8 close-out first, then production, then the data layer that unlocks Models 1–7.

46
Shipped
1
Awaiting Liam's sign-off
20
V2 & downstream
What we agreed
A shared view of the end-to-end engagement: what's shipped, what's blocked on input from WBV, and what's next on Toby's plate. Both sides use it to stay aligned without needing a status call.
How we've delivered it
Every task mapped to a deliverable on the portal and ordered by priority (P0 M8 close-out → P5 handover). Shipped items collapsed at the bottom. Refreshed as the project moves.

Priority 0 · Model 8 Rota Tool

Close-out the rota tool

One task left: Liam signs the Rules spec. Everything else moved to Iterative V2 (bolt-on work after go-live).

Get Liam's signature on the Rota Rules spec
Why: Rules page has a sign-off box ready. Once signed, the spec is locked — future changes go through a versioned update. Unblocks confident go-live.
What: Collect signature PNG. Embed in rota-rules.html, pre-fill name/role/date, bump to "Version 1.0 — Signed", add Signed badge near the top.
Ask Liam~15min

Post sign-off · Iterative improvements

Bolt-on V2 work

None of these block sign-off. They're iterative improvements that make a working tool better. Listed here so the plan is visible and Liam signs knowing what's coming.

All Iterative V2 items shipped. New asks land here as they come up.


Priority 1 · Model 8 Rota Tool

Put the rota tool in production

Make the tool run itself — nightly automated refresh so the rota reflects the latest holidays, bookings and rota-sheet edits without anyone pressing a button.

Nightly cron on the Mac Mini
Why: Rota only refreshes today when Toby runs the script. Nightly run keeps the output fresh against holidays, bookings and rota-sheet edits.
What: Schedule python3 deliverables/d2_rota_analysis.py nightly on the Mac Mini; auto-push regenerated HTML to the maji repo. Open the page in the morning and it reflects today's data.
Toby~1h
Push optimised rota back to Google Sheets
Status: descoped (Liam, 21 Apr WhatsApp). He’s confirmed the CSV download from the rota page is fine — no need for direct Sheets write-back. Keeps the implementation simpler and removes a service-account write-scope risk.
Descoped

Ongoing · Data quality

Data Questions & Gaps

Specific dealer / event data questions to put to Liam short-term, and the standing data requirements to flow into Toby Austin's web-app API contract long-term. Not blocking work, but unresolved items here degrade model accuracy.

Short term: flag gaps to Liam as they come up. He patches the source Sheet; the nightly sync picks it up.
Long term: every standing field we need from a dealer/event goes into the Austin API contract so his web-app becomes the single source of truth when it ships.
Adam Chapman was previously recorded as “Adam Harland”
Status: resolved — same person, name change. Alias persisted in the dealer_aliases table in Postgres; Models 2/7 joins on historical sales will resolve correctly.
Resolved
Gary Trainor was previously recorded as “Gary Clitheroe”
Status: resolved — same person, name change (confirmed by Liam 21 Apr). Initials stayed GC. Alias in dealer_aliases.
Resolved
Andrew Wood was previously recorded as “Adrian Williamson”
Status: resolved — same person, full name change (confirmed by Liam 21 Apr WhatsApp). Initials stayed AW. Alias persisted in dealer_aliases.
Going forward: Liam confirmed no current or past dealers share initials with a different person, and any future clashes will use disambiguated initials at source (e.g. AWi vs AWo). So we don’t need a tenure/date-range model for historical attribution — initials alone remain a stable identifier per person.
Resolved
Patrick Regan (PR): non-starter
Status: resolved (Liam, WhatsApp 21 Apr). Offered a place, never actually started. Sync has him hard-coded as non-live until Liam removes him from the Dealers tab or adds the “live” flag.
Resolved
Ingest the “live” flag from the Dealers tab
Why: Tamara and Patrick are on the Dealers tab but haven’t started. Liam’s adding a live / not live flag column to the tab (this evening, after his Jersey ferry). Once it’s in, the sync should read it and respect it — only dealers with live = true get pulled into active = true in Postgres, so the optimiser never proposes a non-starter.
Action: when Liam signals the flag is in the Sheet, update parse_dealers to read the new column, then delete the temporary KNOWN_NON_STARTERS hard-code in sync_sheets_to_postgres.py. Run make sync && make validate to confirm active counts match Liam’s live roster.
Toby~20min once flag lands
Monthly target formula: ROUNDUP(4.2 × weekly)
FYI: per Liam, the Dealers-tab per-Month block is usually computed as ROUNDUP(4.2 × per-Week). Matches ~90% of current dealers; Adam Chapman and a few others have custom overrides in the Sheet.
Implication: the Sheet value is authoritative for the optimiser (we trust explicit overrides). Formula is useful as a sanity check for new dealers before Liam fills in the per-Month cell, and as a fallback if the per-Month block ever drifts or has broken formulae (e.g. Tamara’s 17 was a formula pointing at the TBC row below, not her own weekly).
Captured
Former dealers in the alias lookup — what to do with them?
The Dealers-tab alias lookup also lists initials that don’t match any current active dealer: AHB (Adam Brazil), AS (Anastasia Stanford), DC (Darcie), DV (Darria Valkova), GB (Gary Brennan), JT (Jordan Tull), LV (Liam Vasey himself). These appear to be historical dealers who’ve left.
Action: they show up with 7 “unknown initials” warnings on every sync run. Consider a former_dealers table to track them so historical sales by these dealers can still be attributed. Not urgent — only matters for dealer performance analysis across 2+ years of data.
Toby
Tamara Harvey (TH): non-starter
Status: resolved (Liam, WhatsApp 21 Apr). She keeps delaying her start date — not live. Sync hard-codes her as active=false for now so the optimiser doesn’t pick her.
Resolved
Adam Chapman: monthly target
Status: resolved. The per-Month block in the Dealers tab (row 78 onwards) has his target as 19. Sync now reads this block and enriches dealers automatically. 39/40 active dealers now have a monthly target.
Resolved
Venue postcode WR14 3G (Malvern, 27 Feb 2026) — truncated
One event venue postcode in the Sheet was WR14 3G — only 2 chars after the space instead of the expected 3. No WR14 3G* postcode exists at all in postcodes.io (live or terminated), so the original was likely a transcription error from a different unit within the same sector.
Fix applied: substituted WR14 3AA (central Malvern) as a proxy. All WR14 3XX units sit within ~0.5 miles of each other, so drive-time error is <1 min for any dealer. Only 1 event affected (id 161, Malvern, 2026-02-27).
Low-priority follow-up: Liam — if you remember the exact Malvern venue, feel free to correct it in the Sheet. Otherwise no action needed.
Resolved (proxy)
Standing data we need per dealer — requirements for Austin’s API
Capture every field we rely on so Austin’s web-app becomes the long-term source. Current list (full in the contract doc):
  • name, initials, alias list (for name-change history like Chapman/Harland)
  • home postcode (drives all travel calculations)
  • active / locked flags
  • monthly_target, max_per_week, roadtrip_cap_per_month, traveller_preference
  • joined_date, left_date (soft delete when they leave)
  • comments (for constraints like “Does not work Tuesdays”)
Action: share the contract doc with Austin before he builds the dealer table.
Toby

Priority 2 · Enabler

Data infrastructure

Move off manual sheet reads into a proper data layer. Required before Models 1–7 can be built and validated properly.

Postgres on Railway
Production store for events, dealers, assignments, and external enrichment. Schema mirrors the current Google Sheets model with referential integrity added.
Toby
Automated daily sync from Google Sheets
Pull all 59 tabs into Postgres nightly with change tracking so corrections and late edits propagate through to the models.
Toby
Validation & reconciliation rules
Schema checks, cross-sheet reconciliation (e.g. sales dealer tags vs rota assignments), anomaly flags. Surfaces data quality issues before they pollute models.
Toby
Historical backfill
2,312 events + commodity-normalised revenue + all derived metrics loaded and locked. One-off.
Toby
External data enrichment (ongoing)
ONS Census + IMD already loaded for Foundation. Extend to per-postcode house prices, Mosaic segments, population age profiles. Annual refresh.
Toby

Priority 3 · Models

Build Models 1–7

Each model is one block: design → build → validate against held-out data → confidence bounds → ship. Validation happens inside each block, not as a separate phase.

Model 1: Event Repeat Timing Engine
When to return to each area. Predicts optimal revisit windows from historical repeat performance.
Toby~1 wk
Model 2: Dealer Performance Ranking
Fair per-dealer scoring that controls for area quality. Resolves any remaining dealer-identity ambiguity in passing.
Toby~1 wk
Model 3: Area Intelligence & Postcode Scoring
Continuous version of the Foundation analysis. Rolls scoring + target lists forward as new event data lands.
Toby~1 wk
Model 4: Dealer Capacity Planning
What-if scenarios: holidays, new hires, volume shifts. Impact on travel, fairness and fill rates before a decision. (This absorbed the old "scenario planner" from the rota tool.)
Toby~1 wk
Model 5: Pattern Analysis
Day-of-week, weather, seasonality, event-type cross-cuts. Separates real signal from noise.
Toby~1 wk
Model 6: Event Attendance Predictor
Footfall forecast per event. Feeds the "3rd seat at twin events" rule in M8.
Toby~1 wk
Model 7: Estimate vs Actual Tracker
Per-dealer calibration of estimates against realised SKU outcomes. Replaces the blanket 30% reduction with honest per-dealer math.
Toby~1 wk

Priority 4 · Section C

Platform & AI interface

The front-end layer the ops team uses day-to-day. Fixed pages for recurring questions, a conversational interface for ad-hoc ones.

Self-Serve AI Query Tool
Claude-powered conversational UI. Ask questions about event/dealer data in plain English.
Toby
Fixed Analysis Pages
Event calendar, dealer leaderboard, area explorer + heatmap, capacity dashboard. Live views backed by Postgres.
Toby
Data upload & Jotform webhooks
Event registration forms pipe straight into the database. Replaces manual collation.
Toby

Priority 5 · End of engagement

Testing & handover

Move from Toby-as-operator to WBV running the platform themselves.

UAT with the ops team
Run the live platform against a period of recent operational decisions; flag any gap between what the tools recommend and what the ops team would have done manually.
Toby + WBV
Training sessions
One session per deliverable area. Recorded so new ops staff can self-onboard.
Toby + WBV
Documentation
Per-deliverable pages covering what it does, how to read it, the underlying assumptions, and how to change them.
Toby
Go-live & handover
Full platform live on majiai.co. Ops team owns day-to-day use; Toby on support retainer.
Toby + WBV

V2 · Optional

Nice-to-have, not blocking

Would improve the product but nothing upstream waits on them. Picked up opportunistically.

OSRM proper road routing
Haversine + 1.3 road factor is already accurate enough for ranking. OSRM would swap in exact UK road drive times. Self-hosted on the Mac Mini, no API costs.
TobyV2
Constraint editing in the UI
Sliders for weekly cap divisor, roadtrip threshold, roadtrip cost, etc. Lets Liam experiment without needing Toby. Requires a backend endpoint on Railway.
TobyV2
Dealer pair preferences / conflicts
Why: Only relevant if Liam confirms pairs matter operationally (training pairs, personality fit, must-pair / never-pair).
Need: Confirmation from Liam whether this is a real constraint. If yes, list of pairs with preference.
Ask Liam

Done

Recently shipped

What's already live. Expand each group to see the detail.

Foundation — What Makes a Great Area 2 items
"What Makes a Great Area" analysis + postcode scoring model (V2)
Shipped
New area target lists: 2,793 villages, 1,305 towns, 213 city suburbs
Shipped
M8 Rota Tool — follow-up fixes (14–21 Apr 2026) 10 items
Resolved "have we over-constrained the optimiser?" — 7 bugs fixed, 23/23 weeks now CP-SAT optimal, 383h saved (up from 237h)
Shipped
County-specific roadtrip thresholds superseded by the Pareto sweep approach
Shipped
Roadtrip Pareto sweep complete — finding: the optimiser uses ~0 roadtrips at every cap (0, 1, 2, 3, 4, 5, 6). Avg drive stays ~39h per dealer, avg nights ~0.04, regardless of the cap. Conclusion: at current fleet size and geography the solver naturally avoids roadtrips. The 25h penalty is redundant insurance. Chart lives on the Analysis tab.
Shipped
Relaxed the roadtrip penalty from 25h to 10h — total saved unchanged at 392h, confirming the Pareto finding.
Shipped
Over-constraint surfacing diagnostic — on CP-SAT infeasible, the optimiser now relaxes each hard constraint (H1..H7) in turn to identify which is blocking, logs the culprit to the CLI, and attaches the diagnosis to the weekly result. Greedy fallback still runs; happy-path behaviour unchanged.
Shipped
Drive-hour gap investigation binned — the Pareto finding (optimiser uses ~0 roadtrips at any cap) makes the question self-answering. The 7h-per-dealer driving gap is within-week coordination the optimiser gets right. No follow-up interview needed unless Liam specifically wants to review individual historical roadtrips.
Shipped
Chain Intelligence V2 shipped — CP-SAT model now has overnight-stay and chain-continuation decision variables. Objective charges prev-venue->current-venue drive cost on chain days (was home->current every day). Anti-home-base rule: chains only count if both days are >2h from the dealer's home. Reporting layer made symmetric, so the "saved" number reflects real driving, not home-to-venue assumptions. The previous 392h headline was an over-statement (charged Liam as if he drove home every chain night); the honest number is 344h saved across the historical period. Proposed rotas now flag overnight stays with a purple "stay" badge next to the dealer's initials, plus a weekly summary of which dealers need accommodation.
Shipped
May rota ingestion from Liam's one-off Excel — 208 events, 202 holidays, per-dealer May weekly cap overrides (holidays baked in). Pipeline now reads from data/external/May Rota information for Toby.xlsx with fallback to Downloads. Supersedes the quarterly file for May weeks; Liam's 30 Apr baseline assignments preserved for W18 before/after comparison.
Shipped
Dealer postcode reconciliation — four corrections made (Anna Jankowiak from invalid B31 0DB to B3 1DB, Chris Kodic, James Moulds, Georgia Nield added). Post-geocode fallback safety net added: if a dealer's primary postcode fails to geocode, the May Excel alternate is used automatically. AJ now has real drive times instead of 0h; solver no longer treats her as free travel.
Shipped
CSV export restructured to match Liam's Sheet — new header Road show #, Area, Date, Dealer #1, Dealer #2, Dealer #3. Dealer cells paste cleanly into his existing rows keyed by Road show #; no risk of overwriting other columns. Road show # populated from the May Excel for W18-W22 and from the Sheet's own column for historical weeks.
Shipped
"What changed" summary strip on the optimiser tab
Shipped
Twin event staffing rule confirmed: target 3 dealers (one sets up venue B while the other two finish venue A). Drops to 2 only when no other dealer has capacity. Implemented with a high soft penalty rather than a hard rule so the solver still finds a solution when capacity forces a downgrade.
Shipped
Overnight-stays data ask dropped — fair bit of work for marginal gain; chain model calibrates from the existing rota clustering instead
Shipped
Jersey handling confirmed: out of the optimiser. Liam picks the Jersey team manually; those dealers marked unavailable for normal events that week. Political decision, not a travel-cost one. Implementation task added.
Shipped
Traveller preference ask dropped: the roadtrip cap column (0/1/2) already encodes the preference — cap 0 = reluctant, cap 2 = willing. No separate happy/reluctant tag needed. R3 rule updated to make this explicit.
Shipped
Rule Feedback tab removed from rota-tool: rules live in the dedicated rota-rules.html sign-off spec. One canonical source, not three.
Shipped
Jersey handling implemented per rule D6. Jersey events filtered out of the optimiser; dealers assigned to Jersey in the source rota auto-marked unavailable for mainland events that week (same mechanism as holidays). 3 historical + 3 future Jersey events excluded cleanly.
Shipped
Roadtrip budget Pareto sweep (caps 0–6, Liam's original ask) shipped on the Analysis tab with manual-rota baselines. Data finding recorded as V2 follow-up: optimiser gets 39h / 0 nights at every cap vs manual 46h / 1.46 nights — suggests Liam's manual rota may be committing dealers sequentially and forcing roadtrips later, but could also be operational judgement. Two-step follow-up plan captured in V2.
Shipped
M8 Rota Tool — main shipping batch (13 Apr 2026) 22 items
Fixed silent matrix gaps: Neath Port Talbot, Gwynedd, Merthyr Tydfil missing from Liam's drivetime tab. Added Haversine fallback (dealer postcode → county centroid) so all (dealer, county) combos have a sensible cost.
Shipped
Section content (titles + tables) aligned within a 1240px column
Shipped
Restructured rota deliverable: portal card lands ON the tool; explainer demoted to an "About" tab; page renamed to rota-tool.html
Shipped
Fixed blank-tab bug (duplicate const FEEDBACK_KEY was throwing SyntaxError, halting all JS)
Shipped
Weekly cap formula updated to divide by 47 working weeks (was 12 × 4)
Shipped
Roadtrip threshold raised to 3.5 hours (was 2.5h)
Shipped
Twin events: both halves get same dealers, 0.75 weight against quota
Shipped
Future weeks limited to 6 weeks ahead
Shipped
Reserve dealers (Andrew Wood, James Moulds, Oliver Reay) only used when needed
Shipped
Substitute dealers (Edd Thomas, Lee Rushworth) preferred for high-volume days
Shipped
Gary Trainor: existing assignments locked, optimiser works around them
Shipped
Ella Vasey Friday avoidance (soft, can override in emergency)
Shipped
Ella Vasey Sheffield-on-Thursday preference (soft bonus)
Shipped
Postcode-precision drive times (Haversine + 1.3 road factor, postcodes.io geocoding, cached locally)
Shipped
41 dealer postcodes geocoded
Shipped
Average drive per dealer per week shown as primary metric (not total)
Shipped
Unfilled seats highlighted in red on future-week tables
Shipped
Per-dealer hours summary chips above future-week table
Shipped
CSV download format matches the rota sheet column order
Shipped
Single tabbed page (Tool / Analysis / Rules) replacing three separate pages
Shipped
Multi-day roadtrip chain detection (3-day chain counts as 1 trip against the cap)
Shipped
Rules feedback page with comment boxes per rule and download-as-text export
Shipped
Proof of Concept (early Apr 2026) 4 items
Data audit & ingestion (59 sheets, 2,312 events)
Shipped
Commodity price normalisation
Shipped
5 prototype models built (directional)
Shipped
Analysis dashboard & roadmap delivered
Shipped