Files
LynkedUpPro_CRM/docs/superpowers/specs/2026-05-29-lead-verification-design.md
T

7.8 KiB
Raw Blame History

Lead Verification — Design Spec

Status: Approved direction (adapted port from gitea/bugfix/project-refinement). Feeds an implementation plan.

Purpose

Add the pre-pipeline lead-verification stage the CRM is missing. Inbound leads (door-knock, storm canvass, web form, call center, referral) land in a verification queue where a verification/call-center team confirms they're real — identity, insurance, reachability — before they enter the sales pipeline. This is a B2B-first internal tool (the roofing company's verification desk), not a customer-facing surface.

It exists on the old lineage (gitea/bugfix/project-refinement, commits 567b29c "adds lead verification module" + 8a4d577) as a self-contained module. Our revamp branch has diverged heavily (Plans 17), so this is an adapted port — bring the UI + flow, re-author the data against our canonical model, wire it to our existing leads pipeline.

Why it fits (no duplication)

verifyLead(id) in the source module auto-converts a verified record into a New Lead pushed onto the leads array. That leads array is exactly the one we enriched (Plan 3) and made clickable with a detail page (Plan 6). So the chain becomes:

Lead Verification queue → (verify) → New Lead in `leads` → Kanban pipeline → Project (PRJ-2026-###)

It sits upstream of everything we've built and reuses our pipeline as its output. Nothing we built is duplicated or replaced.

The flow & states

Two status axes (both preserved from the source):

  • Coarse status (drives summary tiles + primary badge): Pending → Assigned → In Progress → Verified | Unverified.
  • Granular verificationStatus (the work detail): Unassigned, Pending Outreach, Awaiting Confirmation, Verifying Identity, Reviewing Insurance, Emergency — Awaiting Verifier, Failed Contact, Verified.

Transitions:

  • A new record is Pending / Unassigned.
  • Assign to a verifier → Assigned / Pending Outreach.
  • Verifier works it → In Progress with a granular verificationStatus (Verifying Identity / Reviewing Insurance / etc.).
  • VerifyVerified (record leaves the active queue) and a New Lead is pushed to leads (linked back via verificationId).
  • Unverify (e.g. Failed Contact) → Unverified with a reason note.
  • Notes can be added at any point (audit trail).

Data model (into src/data/mockStore.jsx)

  • MOCK_LEAD_VERIFICATIONS — ~15 records, re-keyed to our world. Per record (shape from source, adapted): { id, leadId, customerName, phone, email, address, source, status, verificationStatus, assigneeId, assigneeName, assignedAt, verifiedAt, urgency, verificationNotes, history:[{ at, by, action }] }.
    • Assignees/verifiers = our canonical admins as the verification desk: a1 Wade Hollis, a2 Darlene Brooks, a3 Roy Schaefer (field agents may also appear). No fake names.
    • Sources = our real lead sources: Door Knock, Storm Canvass, Web Form, Call Center, Referral.
    • Customers = realistic Plano homeowners (varied; not the existing pipeline customers — these are new inbound).
    • A spread across all states so the demo shows every status (a couple Verified, a couple Unverified/Failed Contact, several in-progress/assigned/pending, one "Emergency — Awaiting Verifier").
  • leadVerifications state + the 6 mutators on the context value:
    • assignLeadVerification(id, assigneeId, assigneeName) → Assigned / Pending Outreach.
    • reassignLeadVerification(id, assigneeId, assigneeName).
    • setLeadVerificationStatus(id, newStatus, note?).
    • verifyLead(id) → marks Verified + appends history + pushes a New Lead onto leads using OUR enriched lead schema (firstName/lastName, address/city/state, phones:[{number,type,isPrimary}], emails, leadSource, status:'New', createdByName:'Lead Verification', verificationId).
    • unverifyLead(id, note).
    • addLeadVerificationNote(id, note).

UI (ported components, adapted imports)

  • src/components/ui/Select.jsx — the dark-mode-aware select (reusable; also lets us standardise dropdowns). New shared component.
  • src/components/LeadVerification/statusConfig.js, SummaryCards.jsx, LeadTableRow.jsx, LeadCard.jsx, LeadStatusBadge.jsx, LeadActionsMenu.jsx, AssignLeadModal.jsx, LeadDetailsModal.jsx.
  • src/pages/LeadVerification/LeadVerificationPage.jsx — summary tiles (counts per status) + search + filters (status / source / assignee) + a responsive table (desktop) / cards (mobile) + the assign / reassign / verify / unverify / view modals. Uses usePermissions (we already have the hook) for action gating.

Routing & nav

  • Add a single unified route /lead-verificationLeadVerificationPage, gated allowedRoles={['OWNER','ADMIN','FIELD_AGENT']} (interim coarse guard; Plan 8 ABAC replaces it). (Per our Plan-2 unified-route direction — not the source's three role-prefixed routes.)
  • Add a nav entry ("Lead Verification") for the relevant roles in the sidebar/layout.

Explicitly NOT ported

  • The source's access-control-matrix / RBAC changes (commit 7b485a4) — our Plan 8 ABAC supersedes them. Gate with the existing coarse ProtectedRoute for now.
  • Their old-lineage mockStore wholesale — only the verification slice is ported, re-authored against our canonical roster.

Grounding requirements (must be real, coherent data — not placeholders)

The source module's MOCK_LEAD_VERIFICATIONS is not copied verbatim — it is re-authored to our standards:

  • Real people, no fakes. Verifiers/assignees are the canonical admins (Wade Hollis / Darlene Brooks / Roy Schaefer); customer names are realistic Plano/DFW homeowners (no "John Smith", "Alice Customer", movie characters, or sequential placeholders). Phones use varied real DFW area codes (972/469/214/945) formatted en-US, never (972) 555-0NNN sequences.
  • Real places & sources. Addresses are real Plano street/zip combos; sources are the company's actual channels (Door Knock, Storm Canvass, Web Form, Call Center, Referral). Storm-sourced records should reference the same storm-season framing used elsewhere.
  • Computed, not hardcoded, summaries. The summary tiles (counts per status) and any totals are derived from the leadVerifications array at render — never hardcoded numbers. (Same compute-don't-hardcode rule as the dashboards.)
  • Coherent dates. assignedAtverifiedAt ≤ DEMO_TODAY (2026-05-29); history timestamps strictly increasing; created dates realistic for an inbound queue.
  • Verify produces a real, coherent lead. The New Lead pushed onto leads matches our enriched schema so it renders correctly in the Leads list AND opens in the lead detail page (Plan 6) with no empty/broken fields.
  • Internally consistent. A record's status and verificationStatus agree (e.g. Verified coarse ↔ Verified granular; In Progress ↔ a working granular state); an Assigned/In Progress record has a real assignee; Unassigned has none.

Acceptance

  • Build clean (0 duplicate-key warnings).
  • The page lists 15 verification records with correct per-status summary tiles; filters (status/source/assignee) work; assignees are canonical admins (no fake names).
  • Verify a record → it leaves the active queue as Verified AND a matching New Lead appears in the Leads list (/…/leads) with createdByName: 'Lead Verification'; the new lead opens in the lead detail page (Plan 6).
  • Assign / reassign / set-status / unverify / add-note all persist in session and reflect in the table + badges.
  • /lead-verification reachable from nav for OWNER/ADMIN/FIELD_AGENT.

Reuse / dependencies

  • usePermissions hook — already present.
  • Our enriched leads schema (Plan 3/6) — the verify output target.
  • components/ui/Select — newly ported; reusable across the app.