ADR-0072 — Walk-in (on-site) prospects are tracked, not booked
- Status: Accepted — prospect-only tracking
- Date: 2026-06-30
- Deciders: Fede
- Trello: Clara AI — Product (walk-in tracking)
- Related: ADR-0071 (PM-English — same incident), ADR-0027 (entity classes)
Context
Clara talks to prospects over voice, SMS, and email, and her leasing flow is built around scheduling a future tour (schedule_tour → check_availability → propose/confirm a slot). That framing fails the hottest lead there is: a prospect who is physically on-site right now.
In the 2026-06-29 on-site incident, a Spanish-speaking prospect drove to a community, stood outside, and could not find the leasing office. Clara gave him the street address he was already standing on, pointed him at a website map, handed him a phone number that looped back to Clara, and offered to book a tour for the next day. A human walked out and found him ~37 minutes later.
There are two distinct gaps:
- Behavior — Clara does not recognize "I'm here now / I can't find the office" and respond with wayfinding + a get-them-in-now path. (Partly addressed for voice already — see Consequences.)
- Data — a walk-in creates no record. A PM cannot see it happened, measure how often it happens, know whether the prospect ever toured, or follow up. Conversion tracking and lead-source ROI are impossible without a record.
Research (industry + this codebase) settles the modeling question:
- The industry does not treat walk-in as a fourth tour format. The format axis stays in-person / self-guided / virtual. Walk-in is a lead-source / arrival distinction — RealPage's guest card literally uses
Contact Type = Internet | Phone | Visit. - This repo already has the slot:
PROSPECT_SOURCE.WALK_IN = 'walk_in'is wired end-to-end (search facet, pipeline filter, detail-page Source row) but is never set by any in-product action — only AppFolio rental-application sync stamps it. - Overloading
Tour.modewithwalk_inis actively harmful: it silently falls into the in-person branch of everymode === 'virtual'check, forks tour reporting, and arms a nonsensical "see you tomorrow" 24h reminder that — because a walk-in's start time is now/past — fires immediately. - Self-guided tours with one-time unit access codes are a related-but-different pattern that we are explicitly NOT adopting here. Ours is purely walk-in vs. schedule-a-time. The only "code" involved is office wayfinding (e.g. a door buzz code), which lives in property knowledge — not a generated smart-lock code, and never hardcoded in source.
Decision
Treat an on-site / walk-in prospect as a first-class, tracked arrival event, modeled as a lead-source distinction — not a new tour-format type, and not a self-guided tour.
Behavior. When a prospect signals they are physically on-site or can't find the office, Clara:
- reads office wayfinding from property knowledge (
{{property_policies}}for voice;knowledge.sectionsfor the text agent) — the buzz code, which entrance, which door — instead of the bare street address or a website map; - does not book a future tour for them (
schedule_touris the wrong action for someone standing at the door); - captures the lead with
source = walk_inso it lands in the pipeline; - escalates to a live human if it cannot resolve — voice:
transfer_to_number {{pm_phone_number}}; text:notify_leasing_team.
Data model — prospect-only (decided). No new export interface, and no Tour row is created for a walk-in:
ProspectInquiry.source— reuse the already-wiredPROSPECT_SOURCE.WALK_IN. An on-site walk-in is stampedwalk_inat creation via awalk_inflag Clara sets onsave_prospect. No UI changes needed (pipeline filter, search facet, detail Source row already render it).Tour.mode('in_person' | 'virtual') is left untouched —walk_inis never a tour-format value (atsc-enforced guard locks this). Because a walk-in mints noTourrow, the 24h-reminder misfire risk (a past-dated tour firingreminderFireAtMsimmediately) does not arise at all.- Follow-up is anchored on the visit time (
createdAt). Pipeline visibility — the walk-in shows up filterable bysource = walk_in— ships now; an automatedcreatedAt-anchored nudge is a later enhancement.
This ADR adds no new entity and no new field — it activates an existing enum value on ProspectInquiry (canonical, spine-traced via personId). The ADR-0027 new-entity classification table is therefore omitted.
What is the same, and what is different (walk-in vs. scheduled tour)
| Scheduled tour | Walk-in (on-site) | |
|---|---|---|
| Prospect captured on the spine (Person + ProspectInquiry) | ✅ | ✅ |
| Appears in the prospect pipeline + conversion reporting | ✅ | ✅ (filterable by source = walk_in) |
| Clara captures name/contact, answers questions, can escalate | ✅ | ✅ |
| Fair-Housing-consistent intake / disposition | ✅ | ✅ |
| Arrival | booked a future date/time | physically here, now, no appointment |
schedule_tour / a future calendar slot |
✅ created | ❌ not created |
| Outlook / Google Calendar event | ✅ synced | ❌ none (it already happened / is happening) |
| 24h pre-tour reminder SMS | ✅ armed | ❌ never armed (would fire immediately) |
| Lead source | inbound channel (phone/email/website…) | walk_in |
| Clara's first move | check availability → propose a slot → confirm | read office wayfinding → get them in now → escalate to a human if stuck |
| Follow-up anchor | the future tour time | the visit time (createdAt) |
| Post-visit "apply now" link | after the tour completes | may fire promptly |
Tour record |
created (mode: in_person|virtual) |
none — tracked on the prospect (source = walk_in), no Tour row |
| Self-guided / one-time unit access code | ❌ | ❌ (only office wayfinding, from property knowledge) |
Consequences
- A PM gets a tracked walk-in in the pipeline (
source = walk_in) they can attribute, measure (walk-in volume + conversion), and follow up on — the core gap the incident exposed. - No new tour-format type → no reporting fork and no silent fallthrough in the many
mode === 'virtual'branches. - Shipped in this change: (a) the text agent (
clara-unified) now injects property-knowledge wayfinding (propertyPolicies, built fromPropertyKnowledge.sections) and handles an on-site/walk-in prospect over SMS/email; (b)save_prospecttakes awalk_inflag → the server stampssource = walk_in(text channel, end-to-end); (c) the voice on-site escalation is now mandatory — Clara musttransfer_to_number {{pm_phone_number}}and must NOTend_callwhile a prospect is still stuck (closes the 2026-06-30 EN harness gap), and is instructed to capture the walk-in. - No new tour-format type → no reporting fork, no silent fallthrough in
mode === 'virtual'branches, no reminder-timer misfire. - Deferred (follow-up): (a) the voice agent passing
walk_inend-to-end needs the param added to the catalog (src/lib/tools/leasing.ts) plus a manualsync-voice-tool-schemas.ts --apply(NOT automatic on merge — kept out of this PR to avoid desyncing the live ElevenLabs snapshot and ~6 projection unit tests); (b) an automatedcreatedAt-anchored walk-in follow-up nudge; (c) a dedicated "walk-ins to follow up" view (today they appear in the prospect pipeline filtered bysource = walk_in). - Out of scope: self-guided tours, smart-lock / one-time access-code integrations. Office wayfinding (buzz code, which door) stays property knowledge in the data layer and is never hardcoded in source; committed test scenarios use a fake buzz code.
Alternatives considered
- New
Tour.mode = 'walk_in'. Rejected. Conflates arrival with format, forks reporting, silently falls through everymode === 'virtual'else-branch, and arms a reminder that fires immediately for a past-dated tour. - Self-guided tours with one-time unit access codes. Out of scope. A genuinely different pattern (unattended smart-lock touring with per-prospect codes). Ours is walk-in vs. schedule-a-time; tangentially related, deliberately not adopted now.
- Track purely on the prospect — no
Tourrow for a walk-in. ✅ CHOSEN. Stampsource = walk_in, drive follow-up off the inquiry'screatedAt, and never mint aTour. ATour.originfield would only be worth adding if we later want walk-in tours in tour-level metrics; deferred until that need is real.