The smallest thing that prices a job properly.
Eight features, one calculation engine, mobile first. Every number in the examples below is computed live by the same engine.ts that runs the app.
MVP specification
| # | Feature | V1 scope | Explicitly NOT in V1 |
|---|---|---|---|
| 1 | User account | Email magic link plus Google sign-in. One user per business. | Teams, roles, SSO |
| 2 | Business profile | Name, ABN, phone, email, suburb, GST toggle, default rates (employee, owner, subbie), supplies, travel/km, target margin, T&Cs, monthly overhead list. | Logo on PDF (Pro, V1.1) |
| 3 | Quote calculator | 6-step wizard: job type, property, labour, other costs, overhead, target. Live price bar on every step. Rule-based time suggestion. | Photo/AI room recognition, Maps travel |
| 4 | Profit / margin | Margin %, markup %, or profit $ targets; before or after overhead basis; 3 overhead allocation methods; red/amber/green with configurable thresholds; rounding options. | Actual vs quoted hours |
| 5 | GST | 10%, on/off from the profile. Ex-GST and GST shown separately; customer price includes GST. | Tax invoices |
| 6 | Customer message | 4 tones; editable; leak-check for internal terms; copy plus SMS, WhatsApp, email deep links; optional AI polish. | Sending from our servers, PDF |
| 7 | Save quote | Draft, Sent, Accepted, Declined, Completed. Duplicate and edit. | Customer e-acceptance |
| 8 | Quote history | Filterable list; basic monthly KPIs on Home. | Full report suite (Pro) |
Platform: responsive web app built on Hono + Cloudflare Pages. The engine is a pure TypeScript module shared by the client bundle and the server routes, so a quote is always reproducible. Storage: Cloudflare D1 (SQLite) keeps the business profile, overhead lines and saved quotes. Calculations run entirely on the device, so the app keeps working offline.
User journey
First run (target: first quote in under 4 minutes)
On-site quote (core loop)
Follow-up and habit
Wireframes
$322.15 inc GST
BE $250 · 30% · $13/h
Mobile UI design
Principles
- One-thumb use: primary action pinned bottom-right; tap targets 44px or larger (steppers are 40×40 inside 52px rows).
- Steppers over keyboards: most inputs are counts, so no keyboard pops up on site.
- The live price is always visible (bottom bar).
- Colour means one thing only: red/amber/green is reserved for margin status.
- Numbers use tabular mono so columns line up.
System
- Type: Bricolage Grotesque (display) · Geist (UI) · Geist Mono (numbers).
- Palette: warm paper #F5F3EE, ink #15171C, brand blue oklch(.52 .18 258); status red/amber/green in oklch.
- Radius 12–22px; hairline borders instead of shadows (clearer in bright sunlight).
Database / data structure
-- Cloudflare D1 (SQLite). All money stored in cents or REAL, AUD. All prices stored ex-GST + gst separately. businesses (id, name, abn, phone, email, suburb, state, gst_registered, employee_rate, owner_rate, sub_rate, supplies_per_job, travel_per_km, target_margin, billable_hours_pm, jobs_pm, terms, updated_at) overhead_items (id, business_id, label, monthly_amount, sort) settings (id PK, red_below_pct, green_at_pct, round_to, overhead_method, basis) quotes (id, customer, suburb, status, job_type, created_at, inputs TEXT, -- JSON snapshot: property, labour, costs, overhead, target engine_version TEXT, -- e.g. "1.0.0" — results reproducible forever chosen_price_inc REAL, message_tone, message_text, parent_quote_id TEXT) -- duplicates
Storing the input snapshot plus engine version means a quote always shows the numbers it was sent with, even after defaults or the engine change. The client also caches state in localStorage so the calculator keeps working offline and syncs when reconnected.
Calculation engine specification
Definitions (strict)
| Term | Formula |
|---|---|
| Labour hours | cleaners on job × hours on site |
| 1. Direct labour | employees × h × employee rate + owner × h × owner rate |
| Subcontractor cost | subbies × h × sub rate + fixed subcontracted items |
| 2. Other direct costs | supplies + travel + parking + equipment + laundry + waste + consumables + other |
| Direct job cost (Cd) | direct labour + subcontractor cost + other direct |
| 3. Overhead (O) | per hour: (monthly OH ÷ billable labour h) × labour h · per job: monthly OH ÷ jobs per month (or override) · % of revenue: p × P |
| Revenue ex GST (P) | see pricing below |
| 4. GST | round₂(P × 10%) if registered, else 0 |
| 6. Customer price | P + GST |
| Gross profit / margin | P − Cd · (P − Cd) ÷ P |
| 5. Profit after overhead | P − Cd − O · margin: ÷ P · markup: ÷ (Cd + O) |
| Break-even (inc GST) | (Cd + O_fixed) ÷ (1 − p) × 1.1 |
| Per labour hour | P ÷ labour h · profit ÷ labour h |
Solving for the price
Let C = Cd + O_fixed when the target is after overhead (the default), or C = Cd when it's on direct costs only (classic gross margin). Let p be the %-of-revenue overhead (0 if another method is used).
Gross margin M : P = C ÷ (1 − M − p) // NOT C × (1 + M)
Markup K : P = C × (1 + K) ÷ (1 − p(1 + K))
Profit $T : P = (C + T) ÷ (1 − p)
Guard : if denominator ≤ 0 → error "target + overhead % must be < 100%"
Rounding : P rounded to cents first, then GST = round₂(P × 0.10)Rounding options
For step s (default $5): offer floor, ceil and ceil + s of the inc-GST price. Each is re-evaluated: P' = price ÷ 1.1, then margin, profit and status are recalculated. The recommended option is the lowest round-up, never the cheapest. Options below the calculated price are marked as trimming margin.
Status
Red if profit after overhead is below 0 or the margin (on the chosen basis) is under the red threshold (default 15%). Amber if it's under the green threshold (default 30%). Otherwise green. Both thresholds are configurable.
Time suggestion (not a price input)
Transparent rule-of-thumb: minutes per room type × job-type multiplier (bond 2.1×, deep 1.6×, builders 2.6×…) × condition (0.85–1.75) × frequency (regular only) × storeys × pets + access. Commercial and builders use floor-area rates. The user must accept it; it never changes hours on its own.
Example calculations engine self-test: pass
Computed live below by the same engine.ts that runs the app.
A · The brief’s test case: 30% gross margin on direct cost, before overhead
✓ PASS: expected $292.86 + $29.29 = $322.15
| Labour hours | 2 × 2.5 h = 5 h |
| 1 · Direct labour | $190.00 |
| Subcontractors | $0.00 |
| 2 · Other direct costs | $15.00 |
| Direct job cost | $205.00 |
| 3 · Allocated overhead | $0.00 |
| Revenue ex GST | $292.86 |
| 4 · GST | $29.29 |
| 6 · Customer price | $322.15 |
| Gross profit · margin | $87.86 · 30.0% |
| 5 · Profit after OH · margin · markup | $87.86 · 30.0% · 42.9% |
| Break-even (inc GST) | $225.50 |
| Revenue / profit per labour h | $58.57 / $17.57 |
| Status | GREEN |
| Rounding options | $320 → 29.5% · $325 → 30.6% ★ · $330 → 31.7% |
B · Same job, target after overhead (per labour hour, $1,450/mo ÷ 320 h)
| Labour hours | 2 × 2.5 h = 5 h |
| 1 · Direct labour | $190.00 |
| Subcontractors | $0.00 |
| 2 · Other direct costs | $15.00 |
| Direct job cost | $205.00 |
| 3 · Allocated overhead | $22.66 |
| Revenue ex GST | $325.22 |
| 4 · GST | $32.52 |
| 6 · Customer price | $357.74 |
| Gross profit · margin | $120.22 · 37.0% |
| 5 · Profit after OH · margin · markup | $97.56 · 30.0% · 42.9% |
| Break-even (inc GST) | $250.43 |
| Revenue / profit per labour h | $65.04 / $19.51 |
| Status | AMBER |
| Rounding options | $355 → 29.5% · $360 → 30.4% ★ · $365 → 31.4% |
C · Owner-operator doing the job solo. Owner time valued at $45/h
| Labour hours | 1 × 3 h = 3 h |
| 1 · Direct labour | $135.00 (owner $135.00) |
| Subcontractors | $0.00 |
| 2 · Other direct costs | $20.00 |
| Direct job cost | $155.00 |
| 3 · Allocated overhead | $13.59 |
| Revenue ex GST | $240.85 |
| 4 · GST | $24.09 |
| 6 · Customer price | $264.94 |
| Gross profit · margin | $85.85 · 35.6% |
| 5 · Profit after OH · margin · markup | $72.26 · 30.0% · 42.9% |
| Break-even (inc GST) | $185.45 |
| Revenue / profit per labour h | $80.28 / $24.09 |
| Status | GREEN |
| Rounding options | $260 → 28.7% · $265 → 30.0% ★ · $270 → 31.3% |
D · The markup trap: 30% markup vs 30% margin
| Labour hours | 2 × 2.5 h = 5 h |
| 1 · Direct labour | $190.00 |
| Subcontractors | $0.00 |
| 2 · Other direct costs | $15.00 |
| Direct job cost | $205.00 |
| 3 · Allocated overhead | $0.00 |
| Revenue ex GST | $266.50 |
| 4 · GST | $26.65 |
| 6 · Customer price | $293.15 |
| Gross profit · margin | $61.50 · 23.1% |
| 5 · Profit after OH · margin · markup | $61.50 · 23.1% · 30.0% |
| Break-even (inc GST) | $225.50 |
| Revenue / profit per labour h | $53.30 / $12.30 |
| Status | AMBER |
| Rounding options | $290 → 22.2% · $295 → 23.6% ★ · $300 → 24.8% |
E · Bond clean with % of revenue overhead (12%) and $80 profit target
| Labour hours | 2 × 5 h = 10 h |
| 1 · Direct labour | $380.00 |
| Subcontractors | $150.00 |
| 2 · Other direct costs | $35.00 |
| Direct job cost | $565.00 |
| 3 · Allocated overhead | $87.95 |
| Revenue ex GST | $732.95 |
| 4 · GST | $73.30 |
| 6 · Customer price | $806.25 |
| Gross profit · margin | $167.95 · 22.9% |
| 5 · Profit after OH · margin · markup | $80.00 · 10.9% · 12.3% |
| Break-even (inc GST) | $706.26 |
| Revenue / profit per labour h | $73.30 / $8.00 |
| Status | RED |
| Rounding options | $805 → 10.8% · $810 → 11.3% ★ · $815 → 11.7% |
F · Not GST registered
| Labour hours | 1 × 3 h = 3 h |
| 1 · Direct labour | $114.00 |
| Subcontractors | $0.00 |
| 2 · Other direct costs | $10.00 |
| Direct job cost | $124.00 |
| 3 · Allocated overhead | $16.11 |
| Revenue ex GST | $200.16 |
| 4 · GST | n/a |
| 6 · Customer price | $200.16 |
| Gross profit · margin | $76.16 · 38.0% |
| 5 · Profit after OH · margin · markup | $60.05 · 30.0% · 42.9% |
| Break-even (inc GST) | $140.11 |
| Revenue / profit per labour h | $66.72 / $20.02 |
| Status | GREEN |
| Rounding options | $200 → 29.9% · $205 → 31.7% ★ · $210 → 33.3% |
AI assistant rules
- Explanation first, AI second. The “Why this price?” paragraph is generated deterministically from engine output, so it can never be wrong. The AI only answers follow-up questions.
- The AI prompt contains only the calculated figures (no customer name), and is told to use only those, never invent market or competitor prices or wage rates, and never give tax, legal or accounting advice.
- “Polish message” may rephrase only. The result is rejected if the price string disappears; the leak-check still runs.
- Every AI output is labelled and user-editable. Nothing is sent automatically. The API key never reaches the client — all model calls go through the server route.
Market price comparison
Stored with a confidence level, source and retrieval date, at Australia, state or city level. Displayed as a bar with a “You” marker. It never changes the calculated price. Current data status:
| Job type | Region | Range | Confidence | Source |
|---|---|---|---|---|
| Bond / end-of-lease per clean | Sydney (NSW) | 1br $369, 2br $419, 3br $517, 4br $677 | verified | Fantastic Cleaners Sydney published “from” prices, Sept 2026 |
| Bond / end-of-lease per clean | Melbourne (VIC) | 1br $305, 2br $345, 3br $440, 4br $585 | partial | hipages article quoting Fantastic Cleaners Melbourne (2023 prices — likely higher now) |
| Bond / end-of-lease per clean | Australia (indicative) | 1br $280–420, 2br $340–520, 3br $420–680, 4br $560–900 | draft | Working range for validation — to be confirmed with 30+ local price lists |
| Residential regular per cleaner-hour | Australia (indicative) | $45–75 | draft | Working range for validation — confirm via operator interviews & published hourly rates |
| Deep / spring clean per clean (3-bed) | Australia (indicative) | $350–650 | draft | Working range for validation |
| Airbnb / short-stay turnover per turnover (1–2 bed, ex linen) | Australia (indicative) | $90–180 | draft | Working range for validation |
| Commercial office per cleaner-hour | Australia (indicative) | $40–70 | draft | Working range for validation — contract pricing varies widely |
| Post-construction / builders clean per m² (indicative) | Australia (indicative) | $5–12 | draft | Working range for validation |
Before launch: collect 30+ published price lists per capital city (bond, regular hourly, Airbnb turnover, commercial hourly, builders per m²) and record each source.
Job types and multipliers
| Job type | ID | Time multiplier |
|---|---|---|
| Residential regular | regular | 1.0× |
| One-off clean | oneoff | 1.3× |
| Deep / spring clean | deep | 1.6× |
| Bond / end-of-lease | bond | 2.1× |
| Airbnb / short stay | airbnb | 0.9× |
| Commercial office | commercial | 1.0× |
| NDIS cleaning | ndis | 1.1× |
| Post-construction / new-build | construction | 2.6× |
| Custom | custom | 1.0× |