# 03 — The Pricing Engine (form answer → dollar quote)

[← Back to README](README.md) · Previous: [02-lead-form.md](02-lead-form.md) · Next: [04-the-worker.md](04-the-worker.md)

---

## The concept

The pricing engine is a small **lookup table** plus a little arithmetic. It takes the four
answers from the lead form, square-footage bracket, service type, bathroom count, and
frequency, and turns them into a dollar quote **instantly, with no human involved.** No
spreadsheet, no "let me get back to you." The lead gets a number in seconds.

It is deliberately simple. There is no AI guessing prices, no fuzzy logic on the money. A
bracket plus a service tier points at one row in a table; bathrooms and frequency adjust
it; the result is the quote. Simple means predictable, and predictable means you can trust
it to run unattended.

This file documents NeatNow's actual Calgary numbers as the worked example. The structure is
what you copy; the numbers are what you swap.

---

## The three service types

| Code name | What it is |
|---|---|
| **Standard** | A regular maintenance clean. The base price. Can be recurring (and earn a frequency discount) or one-time. |
| **First-Time Clean (FTC)** | A deeper one-off clean to get a home to baseline before standard cleans begin. Priced as Standard base **plus** an FTC extra. Not recurring, no frequency discount. |
| **Move-In/Out (MIO)** | A deep clean of an empty home for a move. Priced as Standard base **plus** an MIO extra. No frequency discount. |

The key design idea: **Standard base is the foundation for every tier.** FTC and MIO are
not separate price tables; they are the Standard base **plus an extra** for that bracket.

---

## The square-footage price table

This is the heart of the engine, taken exactly from `calculateFbPrice` in the worker. Read
it as: pick the bracket row, take the Standard base; for FTC or MIO, **add** the extra in
that row to the Standard base.

| Sqft bracket | Standard base | + FTC extra | + MIO extra | FTC total | MIO total |
|---|---|---|---|---|---|
| 1700-1999 | $225 | +$175 | +$275 | $400 | $500 |
| 2000-2249 | $235 | +$175 | +$300 | $410 | $535 |
| 2250-2499 | $252 | +$175 | +$300 | $427 | $552 |
| 2500-2749 | $270 | +$225 | +$325 | $495 | $595 |
| 2750-2999 | $294 | +$225 | +$325 | $519 | $619 |
| 3000+ | **custom quote** | — | — | — | — |

(The "FTC total" / "MIO total" columns are just Standard base + the extra, shown for
convenience; the engine stores the base and the extra separately and adds them.)

> 🔧 **Customize:** Every number above is NeatNow's Calgary pricing. Replace the whole
> table with your own brackets and prices. **Keep the structure**: a Standard base per
> bracket, and an FTC/MIO *extra* added on top, not a separate table. And remember the
> lockstep rule from [02-lead-form.md](02-lead-form.md), your form's sqft options must
> match these bracket labels exactly.

---

## How each answer maps

### Service type → tier

The engine reads the service answer and picks the tier by keyword (`fbServiceTypeFromLabel`):

| If the answer contains… | Tier |
|---|---|
| `move` | Move-In/Out |
| `first` / `deep` | First-Time Clean |
| anything else (e.g. `one time`, `bi-weekly`) | Standard |

> ⚠️ **First-Time Clean, in practice:** The mapper *does* route **"first"** or **"deep"** →
> First-Time Clean out of the box. But NeatNow's **live form does not offer** a first-time
> option (its choices are one-time, bi-weekly, and move-in/out), so no lead's answer contains
> **"first"** or **"deep"**, and in practice today's live form quotes Standard or Move-In/Out
> only. To quote FTC from a Facebook lead, add a form option whose label contains **"first"**
> or **"deep"**, that is the only change needed; the mapper already handles it (no code change).

### Square footage → bracket

The form options map one-to-one to the table brackets. If a size comes in unparseable or
below 1700, it floors into `1700-1999`. `3000+` (or any number ≥ 3000) becomes a **custom
quote** (see below).

### Bathrooms → add-on

**+$25 per bathroom beyond the first.** So 1 bath adds $0, 2 baths add $25, "3+" adds $50.
The engine reads the first number in the answer.

> 🔧 **Customize:** Set your own per-bathroom add-on.

### Frequency → discount (Standard only)

If the service is **Standard** and recurring, a frequency discount comes off the subtotal:

| Frequency | Discount |
|---|---|
| Weekly | 20% |
| Biweekly | 15% |
| Monthly | 10% |
| One-time | 0% |

> **The discount applies to Standard cleans only.** FTC and MIO never get a frequency
> discount, they are one-off jobs. The discount is rounded to the nearest dollar.

> 🔧 **Customize:** Set your own recurring discounts, or none.

---

## The order of operations

For any non-custom quote, the engine computes:

```
base       = Standard base for the bracket
             (+ FTC extra,  if First-Time Clean)
             (+ MIO extra,  if Move-In/Out)

bathroomAddon = (bathrooms - 1) x $25     (never below 0)

subtotal   = base + bathroomAddon

discount   = round(subtotal x frequencyRate)   ← Standard recurring only, else 0

quote      = subtotal - discount
```

That `quote` (the worker calls it `discountedSubtotal`) is the number texted to the lead.

---

## GST handling — read this carefully to avoid double-taxing

Here is exactly how it works, so you do not tax the customer twice.

> **The Facebook / square-footage quote is shown PRE-TAX.** The engine appends the literal
> string `" (+ GST)"` to the number. **GST (5%) is NOT added into the FB quote total.**

The SMS-formatting function is tiny and does exactly this (`formatFbPriceForSms`):

```
if custom quote:  return "a custom quote ... I'll follow up shortly with details"
otherwise:        return "$<quote> (+ GST)"
```

So a $212 quote is texted as the string **`$212 (+ GST)`**. The 5% is communicated, not
calculated, on this path. Do **not** also bake 5% into the number, or the lead is quoted
tax twice. This path is intentionally pre-tax-with-a-note: it texts the number followed by
the `(+ GST)` label rather than folding tax into the total.

> 🔧 **Customize:** Use your own tax. If your region's sales tax differs, change the `" (+
> GST)"` label, and keep the same rule: show the pre-tax number plus a tax note on this
> path, do not fold the tax into the displayed figure.

---

## Custom quotes (no auto-price)

The engine deliberately refuses to put a number on some leads:

- **Square footage `3000+`** (or any size ≥ 3000), or
- **An unparseable / missing size answer.**

In these cases there is no auto-price. The lead instead gets a message saying it is a
custom quote and that you will follow up shortly. This is a feature, not a gap: large or
unknown homes vary too much to honestly auto-quote, so the engine hands them to you.

---

## Worked example 1 — Standard, biweekly, 2 bath, 1700-1999

Lead picks: "Bi-weekly cleaning" · 2 bathrooms · 1700-1999 sqft.

| Step | Value |
|---|---|
| Service tier (contains no "move") | Standard |
| Frequency (contains "bi-week") | Biweekly → 15% |
| Bracket | 1700-1999 → base **$225** |
| Bathroom add-on | (2 − 1) × $25 = **$25** |
| Subtotal | 225 + 25 = **$250** |
| Discount | round(250 × 0.15) = **$38** |
| **Quote** | 250 − 38 = **$212** |

SMS price fragment the lead sees:

```
$212 (+ GST)
```

Assembled into the full quote text, an illustrative version reads like:

```
Hi Sara, thanks for reaching out to NeatNow. For a biweekly Standard Clean
of an 1700-1999 sqft home with 2 bathrooms, your price is $212 (+ GST) per
visit. Want me to lock in your first date?
```

---

## Worked example 2 — Move-Out, 2 bath, 1700-1999

Lead picks: "Move in / move out cleaning" · 2 bathrooms · 1700-1999 sqft.

| Step | Value |
|---|---|
| Service tier (contains "move") | Move-In/Out |
| Bracket | 1700-1999 → Standard base **$225** |
| MIO extra (1700-1999 row) | +**$275** |
| Base price | 225 + 275 = **$500** |
| Bathroom add-on | (2 − 1) × $25 = **$25** |
| Subtotal | 500 + 25 = **$525** |
| Discount (MIO never discounts) | **$0** |
| **Quote** | **$525** |

SMS price fragment:

```
$525 (+ GST)
```

Full quote text (illustrative):

```
Hi Sara, thanks for reaching out to NeatNow. For a Move In/Out Clean of an
1700-1999 sqft home with 2 bathrooms, your price is $525 (+ GST). Want me to
get you booked in?
```

---

## Worked example 3 — Custom quote (3000+)

Lead picks any service · 3000+ sqft. The engine returns a custom quote, no number (illustrative message):

```
Hi Sara, thanks for reaching out to NeatNow. For a home that size, I'll put
together a custom quote and follow up shortly with the details.
```

---

## Recap

- One lookup table + a few additions = an instant, unattended quote.
- Standard base is the spine; FTC and MIO are extras on top of it.
- Bathrooms add a flat per-extra amount; recurring Standard cleans earn a frequency discount.
- The FB quote is **pre-tax**, shown with a `" (+ GST)"` note. Never double-tax.
- `3000+` and unparseable sizes route to a human (custom quote).
- The price table and the form options are one contract: change one, change the other.

Next, wire this into the engine that receives the lead and sends the text:
**[04-the-worker.md](04-the-worker.md)**.
