# PropertyLab — New Project CRM: Lead Lifecycle Engine Specification

**Version:** 1.0 (locked)
**Date:** 3 July 2026
**Owner:** Wai Kit (Founder/CEO) · Engineering: Lee Jie (CTO)
**Purpose:** Single source of truth for the lead lifecycle engine powering the New Project AI Sales Automation system (Meta Ads → AI WhatsApp → Caller → Closer → SPA → Convert/Cancel → Recycle). This document supersedes the original workflow slide deck wherever they conflict.

---

## 1. Core concept

The **Lead Pool Engine is the hub of the entire system, not a pipeline step.** Every lead enters through it, and every lead that is not fully converted returns to it. Nothing dead-ends except a signed SPA (and even converted leads recycle into a nurture category).

Most CRMs treat "won" and "cancelled" as terminal states. This system deliberately does not: a cancelled buyer has already demonstrated purchase intent and trust, and a converted buyer is the referral engine. Both are recycled into special high-trust categories with customized AI messaging.

### Full lifecycle (canonical flow)

```
Lead Generation (Meta Ads: FB/IG lead form)
        │
        ▼
┌─────────────────────────────────────────────────────┐
│  LEAD POOL ENGINE — 4 categories                     │
│                                                      │
│  [Fresh lead]      → Bucket A only (count = 0)       │
│  [Recycle lead]    → Buckets B / C / D (count ≥ 1)   │
│  [Cancelled lead]  → booked w/ trust, then cancelled │
│  [Converted lead]  → won deals, referral + rebuy     │
└─────────────────────────────────────────────────────┘
        │
        ▼
AI WhatsApp Bot (contacts + qualifies; category-aware template)
        │
        ├── Tier 1 lead (appointment made) ────────────────► CLOSER POOL
        ├── Tier 2 lead (replied, no appointment) ──► CALLER POOL
        └── Tier 3 lead (no response after follow-up seq) ─► CALLER POOL
                                                        │
CALLER POOL (works Tier 2/3 leads, bucket-based allocation)
        ├── Appointment made ──────────────────────────► CLOSER POOL
        └── No appointment ──► RECYCLE (orange lane: count++, → B/C/D)

CLOSER POOL (booked appointments only, tier-scored)
        ├── Closed / booked ──► SPA SIGNED?
        │        ├── Signed ─────────► CONVERT / WON ──► recycle to [Converted lead] (green lane)
        │        └── Not signed ──► LEADER REVIEW
        │                 ├── Case saved ──► CONVERT / WON ──► [Converted lead]
        │                 └── Not saved ──► recycle to [Cancelled lead] (pink lane)
        └── Not closed ──► RECYCLE (orange lane: count++, → B/C/D)
```

### Mermaid version (renderable)

```mermaid
flowchart TD
    ADS[Lead generation via Meta Ads] --> FRESH
    subgraph POOL[Lead Pool Engine — 4 categories]
        FRESH[Fresh lead — Bucket A, count 0]
        RECYCLE[Recycle lead — Buckets B/C/D]
        CANCELLED[Cancelled lead — AI win-back]
        CONVERTED[Converted lead — referral/rebuy nurture]
    end
    POOL --> AI[AI WhatsApp Bot — contacts + qualifies]
    AI -->|Appointment made| T1[Tier 1 lead]
    AI -->|Replied, no appt| T2[Tier 2 lead]
    AI -->|No response| T3[Tier 3 lead]
    T1 --> CLOSER[Closer Pool]
    T2 --> CALLER[Caller Pool]
    T3 --> CALLER
    CALLER -->|Appointment made| CLOSER
    CALLER -->|No appointment| RECYCLE
    CLOSER -->|Not closed| RECYCLE
    CLOSER -->|Closed / booked| SPA{SPA signed?}
    SPA -->|Signed| WON[Convert / Won]
    SPA -->|Not signed| LEADER[Leader review]
    LEADER -->|Case saved| WON
    LEADER -->|Not saved| CANCELLED
    WON --> CONVERTED
```

---

## 2. Glossary

| Term | Definition |
|---|---|
| **Lead Pool Engine** | Central hub. Classifies every lead into one of 4 categories and (for Fresh/Recycle) one of 4 buckets. Feeds the AI WhatsApp Bot. |
| **Category** | One of `fresh`, `recycle`, `cancelled`, `converted`. Determines the AI message strategy. |
| **Bucket** | A/B/C/D — derived from `distribution_count`. Applies only to `fresh` and `recycle` categories. Governs which caller tier may receive the lead. |
| **Distribution count** | Number of times a lead has been distributed to a human agent. Incremented on every recycle. |
| **Lead tier (1/2/3)** | Outcome of AI qualification: 1 = appointment made, 2 = replied but no appointment, 3 = no response. **Do not confuse with caller tier or closer tier.** |
| **Caller tier (1/2/3)** | Skill tier of a human caller agent. Governs bucket allocation. Promoted/demoted weekly. |
| **Closer tier (1/2/3)** | Skill tier of a human closer, scored by closing rate. |
| **SPA** | Sale and Purchase Agreement. Signing = true conversion. |
| **Leader review** | Rescue stage for closed-but-unsigned deals, performed by a team leader. |
| **Special remark** | Free-text + structured field on Cancelled/Converted leads capturing context (e.g. cancellation reason) that the AI must use in messaging. |

**Naming rule for code:** there are THREE independent tier systems that share the word "tier". Always namespace: `lead_tier`, `caller_tier`, `closer_tier`. Never a bare `tier` column.

---

## 3. Lead categories (Pool Engine)

| Category | Entry condition | Buckets | AI message strategy |
|---|---|---|---|
| `fresh` | New lead from Meta Ads lead form. `distribution_count = 0`. | **A only** | Qualification script: intro, confirm interest, qualifying questions (requirement, budget, preferred date/time, FAQs) |
| `recycle` | Returned via orange lane: caller made no appointment, OR closer did not close. `distribution_count ≥ 1`. | **B / C / D** | Re-engagement variant of qualification script; references prior contact where appropriate |
| `cancelled` | Closed deal where SPA was not signed AND leader review could not save the case. | none (special segment) | **Custom win-back**: acknowledge history, address the cancellation reason from `special_remark`, rebuild trust. NEVER the generic script. |
| `converted` | SPA signed (directly or after leader rescue). | none (special segment) | **Nurture**: new project launches, referral incentives, rebuy opportunities. Highest-trust tone. |

**Invariants:**
0. **`fresh` originates ONLY from lead generation (Meta Ads lead form intake).** It is set once, at lead creation, and never again. No state transition, recycle path, admin action, or import job may set an existing lead back to `fresh`. Fresh is a birth state, not a status.
1. Fresh leads ALWAYS enter Bucket A. The recycle lane can NEVER place a lead in Bucket A.
2. `category` is derivable: `fresh` iff `distribution_count == 0` and lead has never reached SPA stage; `recycle` iff `count ≥ 1` and never reached SPA stage; `cancelled` / `converted` set explicitly at SPA/leader-review resolution.
3. Cancelled and Converted leads bypass bucket logic entirely — they are worked by dedicated AI sequences, not the standard caller allocation.

### Buckets (fresh + recycle categories only)

| Bucket | distribution_count | Meaning |
|---|---|---|
| A | 0 | Fresh, never distributed |
| B | 1–2 | Distributed to 1–2 agents before |
| C | 3–4 | Distributed to 3–4 agents before |
| D | 5–6 | Distributed to 5–6 agents before |

**Open decision (must resolve before build):** behavior at `distribution_count > 6`. Recommended: terminal `archived` status (a true cold store, excluded from allocation) with periodic AI-only reactivation attempts. Do not let leads loop forever.

### Core data per lead (from source deck, extended)

`lead_id | source_campaign | category | status | lead_tier | appointment_result | previous_assigned_agents[] | distribution_count | last_contact_date | next_eligible_bucket | spa_status | special_remark | flow_version_id`

---

## 4. AI WhatsApp Bot

**Position in flow:** the Pool Engine's next step is always the AI bot. The AI works leads from ALL categories, with a category-aware prompt router.

### Behavior (fresh/recycle categories)

1. Send intro message: confirm interest, ask simple qualifying questions.
2. Decision: **Client responds?**
   - **Yes** → qualify (requirement, budget, preferred date/time, FAQs) → decision: **Appointment made?**
     - **Yes → `lead_tier = 1`**: create appointment, notify salesperson, send confirmation, add reminders. Route → **Closer Pool**.
     - **No → `lead_tier = 2`** (replied, no appointment): capture objection, send info, schedule follow-up, mark pending/not interested. Route → **Caller Pool**.
   - **No response → `lead_tier = 3`**: run follow-up sequence — **5 min reminder → 1 day reminder → 3 day reminder → mark cold**. Route → **Caller Pool**.

### Category-aware prompt router

```
if category == fresh:      template = QUALIFY_FRESH
if category == recycle:    template = QUALIFY_REENGAGE
if category == cancelled:  template = WIN_BACK  (must reference special_remark cancellation reason)
if category == converted:  template = NURTURE   (referral incentive / new launch / rebuy)
```

Cancelled and Converted sequences have their own cadence settings, separately configurable per flow (see §9 Flow Builder).

---

## 5. Caller Pool

Human callers work `lead_tier` 2 and 3 leads. Allocation is pull-based: agent clicks **Request Leads** in the app.

### Allocation rules by caller tier

| Caller tier | Profile | Allocation per round (~8–10 leads) |
|---|---|---|
| Tier 1 | Experienced / independent appointment setter | 8–10 fresh (Bucket A). If fresh insufficient: 4–5 A + 4–5 B. Priority: newest / least-distributed. |
| Tier 2 | Average caller | 4–5 Bucket B + 4–5 Bucket C. Goal: prove consistency before earning fresh leads. |
| Tier 3 | New / non-experienced | 4–5 Bucket C + 4–5 Bucket D. Goal: train on recycled leads first. |

Agents may request unlimited rounds daily, subject to available inventory.

### Request Leads transaction (atomic)

1. Read agent `caller_tier`.
2. Check available buckets per allocation table.
3. Allocate 8–10 leads.
4. **Lock** those leads to the agent (no double-distribution).
5. Increment `distribution_count` on each allocated lead.
6. Create follow-up tasks.

### Caller outcomes

- **Appointment made** → lead routes to Closer Pool.
- **Appointment not made** → lead recycles (orange lane): `distribution_count++`, re-bucket, return to Pool Engine `recycle` category. It will be re-worked by the AI bot and/or redistributed per bucket rules.

### Weekly caller tier promotion / reset (from source deck p.8)

- Weekly window: **Monday 00:00 → Sunday 23:59**. Weekly counters reset Monday; evaluation runs Sunday 23:59.
- Score = appointments made **from eligible non-fresh leads** within the window.
- **Tier 3 → Tier 2:** ≥ 2 appointments from non-fresh leads in the same week.
- **Tier 2 → Tier 1:** ≥ 4 appointments from non-fresh leads in the same week.
- **Tier 1 retention:** must maintain 4 appointments weekly after leads distributed; otherwise drop to Tier 2.
- Keep weekly counters separate from lifetime performance. Tier movement is automatic; **admin can override** any agent's tier.

---

## 6. Closer Pool

Closers receive **booked appointments only** (from AI Tier 1 leads and from caller-made appointments).

### Closer tier scoring (per 10 served customers)

| Closer tier | Closing rate | Per 10 served | Treatment |
|---|---|---|---|
| Tier 1 (Matured) | 70%+ | 7–8 closed | Handles high-value / priority showroom appointments |
| Tier 2 (Average) | ~50% | 5 closed | Normal appointments; needs coaching |
| Tier 3 (Low) | <50% | 0–4 closed | Needs training + AI feedback; review objection handling closely |

**Formula:** `closing_rate = closed / served_customers` (e.g. 7 bookings from 10 served = 70%).

### Showroom closing workflow (recording pipeline)

Appointment confirmed → closer assigned (system records closer, customer, appointment ID) → **recorder pin on** (full presentation + Q&A) → presentation flow → objection handling → closing attempt → outcome logged.

Recording must link: `appointment_id, customer_id, closer_id, datetime, final_outcome`. **Consent/privacy notice required per company practice (PDPA — Malaysia).**

### AI coaching pipeline

Recording upload → transcribe + segment by presentation stage → AI analysis (opening quality, needs discovery, value explanation, objection handling, closing attempt) → closer feedback (strengths, weak points, suggested script, objection playbook).

Analysis lenses: closing flow score · objection type (price, trust, timing, comparison, spouse/family, loan/finance) · response quality · close attempt clarity.

Dashboard metrics: served customers | closed bookings | closing rate | closer_tier | recording reviewed | objection types | close attempt score | coaching action.

**Note:** the weekly promotion logic in §5 is defined for CALLERS (appointment-based). A parallel weekly evaluation for CLOSERS (closing-rate-based) is implied but not fully specified in the source deck — implement closer tier recalculation on a rolling basis per the scoring matrix above, with admin override.

---

## 7. Post-close stage: SPA, Leader Review, Convert / Cancel

This stage is NEW relative to the original deck and is authoritative.

1. Closer outcome **Closed / booked** → decision: **SPA signed?**
2. **Signed** → status `converted` → **Convert / Won**.
3. **Not signed** → **Leader Review** (team leader rescue attempt):
   - **Case saved** → Convert / Won → `converted`.
   - **Not saved** → lead recycles into **`cancelled` category** (pink lane). At this moment the leader MUST record the **cancellation reason** into `special_remark` — this is the point where someone actually knows why the deal died, and the AI win-back sequence depends on it.
4. **Convert / Won is not terminal**: the lead recycles into the **`converted` category** (green lane) for referral + rebuy nurture.

### Why cancelled/converted are high-value segments

- **Cancelled** = bought with trust, then pulled out. Purchase intent proven. Requires customized AI win-back messaging that acknowledges history and addresses the recorded cancellation reason — never the generic re-contact script.
- **Converted** = highest trust. Can refer and rebuy. Custom AI nurture: new launches, referral incentives.

Both categories carry `special_remark` and route through the category-aware prompt router (§4).

---

## 8. Data model (suggested schema)

```sql
-- leads
lead_id            uuid PK
source_campaign_id text
name, phone        text
category           enum('fresh','recycle','cancelled','converted','archived')
bucket             enum('A','B','C','D') NULL   -- derived; NULL for cancelled/converted/archived
distribution_count int default 0
caller_attempts    int default 0                 -- see note below
closer_attempts    int default 0
lead_tier          smallint NULL                 -- 1/2/3, set by AI qualification
status             text                          -- pending / cold / booked / served / etc.
appointment_result text NULL
spa_status         enum('na','pending','signed','not_signed') default 'na'
special_remark     text NULL                     -- REQUIRED when category=cancelled
last_contact_at    timestamptz
flow_version_id    uuid                          -- flow builder version lead is running on
created_at, updated_at

-- lead_distributions (audit of every hand-off)
id, lead_id, agent_id, agent_role enum('caller','closer'), bucket_at_time,
distributed_at, outcome enum('appointment','no_appointment','closed','not_closed'), outcome_at

-- agents
agent_id, name, role enum('caller','closer','leader','admin')
caller_tier smallint NULL, closer_tier smallint NULL
weekly_appointments_nonfresh int   -- resets Monday 00:00
lifetime stats fields…

-- appointments
appointment_id, lead_id, closer_id, scheduled_at, showroom, outcome, recording_id NULL

-- recordings
recording_id, appointment_id, customer_id, closer_id, recorded_at, file_uri,
consent_captured bool, transcript_uri, ai_analysis jsonb, final_outcome
```

**Design note — split the counters.** `distribution_count` drives bucketing, but a lead that reached a closer and didn't close is qualitatively different from one no caller ever booked. Track `caller_attempts` and `closer_attempts` separately so re-bucketing rules can diverge later without a migration.

### State machine (lead.category × stage)

```
fresh ──AI──► lead_tier assigned
recycle ──AI──► lead_tier assigned
lead_tier=1 ──► closer_stage
lead_tier=2|3 ──► caller_stage
caller_stage: appointment ──► closer_stage
caller_stage: no_appointment ──► category=recycle, count++, bucket=f(count)
closer_stage: not_closed ──► category=recycle, count++, bucket=f(count)
closer_stage: closed ──► spa_stage
spa_stage: signed ──► category=converted
spa_stage: not_signed ──► leader_review
leader_review: saved ──► category=converted
leader_review: not_saved ──► category=cancelled  (special_remark REQUIRED)
count > 6 ──► category=archived   (recommended; confirm with product)
```

---

## 9. AI Agent Flow Builder (product surface)

A new left-nav tab **AI Agent** in the New Project CRM: a drag-and-drop sequence flow builder where the lifecycle above ships as the default template.

- **Node types (8 primitives):** Trigger, AI message, Condition, Delay, Assign pool, Human takeover, Recycle, Webhook.
- **Default template** = the canonical flow in §1, shipped as a locked template that users **clone** rather than edit.
- **Draft → Publish lifecycle.** Editing never mutates a live flow.
- **Flow versioning:** in-flight leads finish on the `flow_version_id` they started on. Republishing creates a new version.
- **Condition nodes** operate on CRM fields with a picker (no free text): `appointment_status`, `spa_status`, `category`, `lead_tier`, `distribution_count`, operators =, ≠, >, <.
- **Recycle node config:** target category (`recycle`/`cancelled`/`converted`), whether to increment count, and which AI template applies on re-entry.
- **AI message node config:** template selector (Category-aware auto / Fresh qualify / Win-back Cancelled / Nurture Converted), follow-up cadence (default 5 min → 1 day → 3 day), no-response fallback (tag Tier 3 → Caller pool / recycle / human takeover).

---

## 10. Acceptance criteria (engine)

1. A Meta lead form submission creates a lead with `category=fresh`, `bucket=A`, `distribution_count=0`, and enqueues it for the AI bot within 5 minutes.
2. A recycled lead can never have `bucket=A`, and no code path other than lead-generation intake can set `category=fresh` (assert: any UPDATE setting category to `fresh` on an existing lead is rejected at the service layer).
3. Request Leads is atomic: no lead is ever locked to two agents; `distribution_count` increments exactly once per distribution.
4. Tier-1 caller requesting leads when Bucket A has ≥10 available receives only A; when A has <8, receives the A+B fallback mix.
5. AI outcome `lead_tier=1` creates an appointment record, notifies the assigned salesperson, and the lead appears in the Closer Pool without entering caller allocation.
6. Closer outcome `not_closed` returns the lead to the pool as `recycle` with count incremented.
7. `spa_status=not_signed` + leader `not_saved` sets `category=cancelled` and REJECTS the transition if `special_remark` is empty.
8. Both `cancelled` and `converted` leads receive their dedicated AI templates, never the generic qualify script (assert via template-id logging).
9. Weekly caller-tier evaluation runs Sunday 23:59 Asia/Kuala_Lumpur; counters reset Monday 00:00; all movements logged with admin-override capability.
10. Every showroom recording row links appointment, customer, closer, datetime, and outcome, and stores a consent flag (PDPA).

## 11. Open decisions (confirm with Wai Kit before implementing)

1. **Bucket D exhaustion:** what happens after distribution_count exceeds 6? (Recommended: `archived` + periodic AI-only reactivation.)
2. **Closer weekly evaluation:** exact window/thresholds for closer_tier movement (deck defines the scoring matrix but only the caller weekly logic).
3. **Re-entry behavior for `cancelled`/`converted`:** AI-only sequences, or can these leads ever be re-allocated to human callers? Current assumption: AI-only with human takeover node available in the flow builder.
4. **Fresh-lead starvation:** if Tier-1 caller headcount outgrows fresh-lead volume (~1,000 leads/month baseline), the A+B fallback becomes the norm — confirm acceptable or add an inventory guard.
