# 02 — The Lead Form (the questions that drive the quote)

[← Back to README](README.md) · Previous: [01-foundations.md](01-foundations.md) · Next: [03-pricing-engine.md](03-pricing-engine.md)

---

This file is about **what questions to ask** on your Facebook lead form and **why**.
The step-by-step click path to actually *build* the form inside Ads Manager lives in
[05-facebook-ad.md](05-facebook-ad.md). Read this one first so you know what you are
building before you build it.

---

## What a Meta lead form is

A Meta **Instant Form** (also called a "Lead Form") is a mini form that opens *inside*
Facebook or Instagram when someone taps your ad. It does not send them to your website.
The form lives inside **Ads Manager**, attached to the ad, and Facebook stores every
submission. When a lead submits, Facebook fires a "new lead" notification (a webhook)
that your engine listens for. That is the trigger for the whole pipeline.

Because the form opens in-app and Facebook **pre-fills** the person's name, phone, and
email from their profile, completion rates are high. Your job is to keep it that way: ask
only what you must.

---

## The form's one job

> **Collect exactly the answers the pricing engine needs to compute a quote, and nothing
> that adds friction.**

Every extra question costs you completed leads. So the rule is simple: a question earns
its place **only if the price changes based on the answer**. For a cleaning company, the
price is driven by four things:

1. **What service** they want (standard, deeper one-time, move-in/out)
2. **How big** the home is (square footage)
3. **How many bathrooms** (each extra one adds labour)
4. **How soon / how often** (timing + recurring frequency, which can earn a discount)

Plus the identity fields needed to text them the quote (name, phone) and to save a backup
(email). That is the whole form. Resist adding anything else.

---

## How the engine reads the answers (and why exact wording is forgiving)

The engine does **not** require your question labels to match word-for-word. It matches
each field by **fuzzy keyword**: it lowercases the label, strips out spaces and
punctuation, and checks whether a known keyword appears *anywhere inside it*. So a
question titled "How many bathrooms do you have?" and one titled "Bathrooms" both match on
the keyword `bathroom`.

Here is exactly what the parser looks for (from `parseLeadFields` in the worker):

| What the engine wants | Keywords it searches the label for | Source of the answer |
|---|---|---|
| First name | `first_name`, `firstname` | Facebook pre-fill |
| Last name | `last_name`, `lastname` | Facebook pre-fill |
| Full name (fallback) | `full_name`, `name` | Pre-fill, only if first/last are absent |
| Email | `email` | Facebook pre-fill |
| Phone | `phone_number`, `phone` | Facebook pre-fill |
| Street address (optional) | `street_address`, `address` | Custom question, if you ask it |
| **Service type** | `service`, `type_of_service`, `what_type_of_service` | Your custom question |
| **Timing / how soon** | `how_soon`, `timing`, `when` | Your custom question |
| **Square footage** | `square_footage`, `sqft`, `size`, `approximate_square` | Your custom question |
| **Bathrooms** | `bathroom`, `how_many_bathrooms` | Your custom question |

The forgiveness is in the **question label**, not the **answer**. For square footage, the
engine reads the **first number** in the chosen answer and **floors it into the nearest pricing
bracket** (anything 3000+ or unparseable becomes a custom quote). So your sqft options do not
have to be worded a specific way, but they must **line up with your pricing brackets**, or a
filler lands in a bracket you did not intend (see the critical callout below).

> 🔧 **Customize:** As long as your service / sqft / bathrooms question wording contains
> one of the keywords above, the parser will find it. If you want to use wording with none
> of those keywords, you (or your AI assistant) must add your keyword to the matching list
> in the worker. Easiest path: keep a keyword in the label.

---

## The four custom questions

### Q1 — Service type

Ask what kind of clean they want. The engine maps the chosen label to a service tier by
looking for a keyword **inside the answer** (see [03-pricing-engine.md](03-pricing-engine.md)
for the full mapping logic):

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

> ⚠️ **Note on First-Time Clean:** The mapper routes an answer containing **"first"** or
> **"deep"** → First-Time Clean out of the box (and **"move"** → Move In/Out; everything else →
> Standard). But NeatNow's **live form does not offer** a first-time option, its three choices
> are one-time, bi-weekly, and move-in/out, so in practice no lead selects it and the live form
> quotes only Standard and Move In/Out. To quote First-Time from a Facebook lead, just add a
> form option whose label contains **"first"** or **"deep"**, no code change needed; the mapper
> already routes it. See [03-pricing-engine.md](03-pricing-engine.md).

NeatNow's real Q1 options (this is the live form):

```
What type of cleaning do you need?
  - One time cleaning
  - Bi-weekly cleaning
  - Move in / move out cleaning
```

Notice this one question does double duty: it captures **service type** *and* **frequency**.
"Bi-weekly cleaning" both keeps the service at Standard and flags a recurring (biweekly)
clean, which can earn a frequency discount.

> 🔧 **Customize:** Offer the service tiers you actually sell. If you sell a true deep /
> first-time clean as its own line, add it here AND wire its keyword into the mapper.

---

### Q2 — Timing / how soon

```
How soon do you need cleaning?
  - ASAP
  - Within 2 weeks
  - Within 30 days
```

This does not change the price. It tells you (in the owner-alert text) how hot the lead is,
so you know who to call first. Keep it to one tap.

---

### Q3 — Square footage (the price driver)

This is the price-driving question, and its answer options are the ones that must line up with
your pricing brackets. The options are a frozen set of brackets that map one-to-one to the pricing
table:

```
Approximate square footage of your home?
  - 1700-1999
  - 2000-2249
  - 2250-2499
  - 2500-2749
  - 2750-2999
  - 3000+
```

A few deliberate choices here:

- **Below 1700 is intentionally not offered.** The engine floors anything smaller into the
  `1700-1999` bracket, and NeatNow's economics do not target tiny units through this
  funnel. Not offering the option avoids attracting leads outside the model.
- **`3000+` is a custom-quote bucket.** There is too much price spread above 3000 sqft to
  auto-quote honestly, so the engine deliberately does not put a number on it (it sends a
  "custom quote, I'll follow up" text instead). More in
  [03-pricing-engine.md](03-pricing-engine.md).

> 🔧 **Customize:** These brackets and their boundaries are NeatNow's. Pick brackets that
> match how *your* pricing tiers up, then make the form options and the pricing table
> identical. Whatever your top bracket is, decide if it auto-quotes or routes to a custom
> quote.

---

### Q4 — Bathrooms

```
How many bathrooms do you have?
  - 1
  - 2
  - 3+
```

The engine reads the first number in the answer. Each bathroom **beyond the first** adds a
flat add-on to the quote (NeatNow: +$25 each). "3+" is read as 3.

---

## Identity & CTA

- **Pre-fill fields:** add First name, Last name, Phone number, Email. Facebook auto-fills
  these from the user's profile; the lead just taps to confirm. This is where the
  high-completion magic comes from, so always use the pre-fill fields rather than typing-it-in
  custom questions.
- **Street address:** optional. NeatNow *dropped* it from the live form to cut friction
  (geo is already handled by the ad's location targeting, and the address is collected
  later when you follow up). The engine will read it if present and ignore it if not.
- **CTA button:** set the form's call-to-action to **`GET_QUOTE`**. It sets the right
  expectation: tap, get a price.

---

## ⚠️ The form and the pricing engine are two halves of one contract

This is the one thing not to get wrong in this file.

> **The form's answer options and the pricing engine MUST stay in lockstep.**
> If you add, rename, or re-bracket a square-footage option on the form, you MUST make the
> identical change in the pricing logic. If they drift apart, leads get a **wrong or blank
> quote**, automatically, with no human in the loop to catch it.

Concretely: the form option `2250-2499` only produces a correct quote because the pricing
table has a matching `2250-2499` row. Add a `1500-1699` option to the form without adding
it to the table, and every lead who picks it falls into the fallback bracket (or a custom
quote) and gets a price you did not intend. Think of the form options as a list of keys,
and the pricing table as the lock. Change one, change the other, in the same sitting.

---

## Recommended question list (copy this into the form builder)

```
CTA button: Get Quote (GET_QUOTE)

Custom questions (multiple choice):

1. What type of cleaning do you need?
   - One time cleaning
   - Bi-weekly cleaning
   - Move in / move out cleaning

2. How soon do you need cleaning?
   - ASAP
   - Within 2 weeks
   - Within 30 days

3. Approximate square footage of your home?
   - 1700-1999
   - 2000-2249
   - 2250-2499
   - 2500-2749
   - 2750-2999
   - 3000+

4. How many bathrooms do you have?
   - 1
   - 2
   - 3+

Pre-fill fields (Facebook auto-fills):
   - First name
   - Last name
   - Phone number
   - Email
```

> 🔧 **Customize:** Swap the service tiers, sqft brackets, and bathroom options for yours.
> Keep the structure (one price-driving question each for service, size, bathrooms; one
> timing question; pre-fill identity fields), and keep a matching keyword in each label.

---

Next, see how these four answers become a dollar figure:
**[03-pricing-engine.md](03-pricing-engine.md)**.
