Tours need a phone
Zillow leads book tours with no phone, no-show, and nobody can call them. Clara asks for a number, then books.
Decided D1–D5 (Fede, Oct 19) — building, local branches2026-10-18, updated Oct 19 night (virtual tours section) · Western Slope · local branches, nothing live · details in the folds below
Virtual tours: what broke on Oct 19 and the gaps
Getting the notes from the Zoom call itself: Virtual tour notes, from the Zoom call to the guest card (Oct 30, proposed).
A Western Slope Zillow lead, Jaleigh, asked for a virtual tour. She was told "virtual, here's the Zoom link." Everyone on our side was told in person. Not a new break: the booking has worked this way since July.
Oct 19, Mountain time
Why: the booking only reads "virtual" from the email that sets the time. The earlier ask is ignored.
| Where | Last night | After the fix |
|---|---|---|
| Message to Jaleigh | virtual tour confirmed + Zoom link | same |
| Team email | "In person" | virtual + link |
| Kat's booking text | no Zoom link | virtual + link |
| Kat's calendar | street address | Zoom link |
| AppFolio showing | no tour type | virtual |
| Reminder | none (text only, she has no phone) | carries the Zoom link too |
Gaps we still have
- No phone before booking. Zillow sends none and nothing asks yet. The ask-then-book chain is half in: first piece live, second armed, more open.
- Relay email is not on her PropFlow contact. On purpose, since an August fix for split duplicates. The AppFolio card has it.
- Reminders are text only. A lead with no phone gets none.
- The virtual ask never reaches the AppFolio card as a note.
- Wrong label. Her team email was tagged "resident inquiry" (a lookup fixed since July). Fixed and merged: prospects now tag Leasing.
- Zoom waiting room unknown. The link opens with no sign-in and no registration; we can't see whether a waiting room is on.
- Calendar body has no video link. Kat's calendar event body has no video link (only the title and location say virtual); the staff booking text has the link but not the word "virtual".
What is being done
| Item | Status |
|---|---|
| Carry the earlier virtual ask into the booking: tour type, team email, Kat's text, calendar, AppFolio showing all say virtual with the link; link on the reminder; replay of this exact lead with a planted-defect control | building · fix PR: #21353 (merged, live in production, Willows end-to-end run PASSED 12:30 AM MT Oct 20) |
| Ask for a phone before booking | part live, rest in review |
| Prospects tagged Leasing, not resident | fix open |
| Recovery: Clara emailed Jaleigh and Spencer (another Zillow lead, in person Tue 9 AM, no phone) at 9:57 PM asking for a number | sent; replies watched; numbers go to PropFlow and AppFolio on arrival |
Unrelated check: Kat's note on Elizabeth, a different prospect, did land on her AppFolio card.
Evidence (for engineers)
| Claim | Evidence |
|---|---|
| Lead and timing | inquiry 78ecf4f1 (person pers_9b79fa4b), source zillow, created 2026-10-20T00:28:52Z, stage tour_confirmed, notes "Virtual tour. / Tour requested: Wednesday at 11:00 AM", AppFolio card 3028 |
| Saved in person | tour a50c6039 mode in_person, via email_state_machine; team email "— In person"; staff text to Kat without virtual |
| Jaleigh told virtual | Clara reply 00:51:36Z, link from org settings.videoTourLink |
| Mode read per email only, since July | process-tour.ts:1003,1472; apply-tour-intent.ts:1052,1096; since #3166 |
| AppFolio showing, no type | outbox: create_card (no email/phone), assign, record_communication, schedule_showing → showing 599; no append_note for virtual |
| Contact empty, relay kept off | CONTACT primaryEmail "" primaryPhone ""; relay only as session claim claim_992ed6f4; by design mintable-email.ts:50-55, portal-relay.ts:32-54 |
| Card 3028 has the relay | written by the drain (#18418); PMSREC row checked |
| Phone chain | #21250 merged + deployed (no effect on this lead); #21254..#21308 open |
| "Resident inquiry" label | pm_escalation_email msg_a1e93f08 (maintenance.pm-forward); lease→resident_inquiry in handle-forward-to-property-manager.ts:130-145 (#3981); fix #21342 merged |
| Kat's Elizabeth note landed | staff conv c9f809af, tour 3923bf1d; append_note sent 2026-10-19T23:45:56Z to card 3023 |
| Zoom link settings | us06web.zoom.us/j/83036599249; passwordRequiredForParticipants true, embedPasscode true, authenticatedUsersCanJoin false, isRegister false; waiting room not visible |
| Recovery emails | Jaleigh: conv 32408500, sent_graph 2026-10-20T03:57:12Z from leasing@westernslopepm.com, threaded · Spencer: conv 30520fc7, sent_graph 03:57:21Z, threaded |
| Fix PR | fix PR: #21353 (merged, live, Willows run passed 12:30 AM MT Oct 20) |
| Jaleigh repair (07:35 AM MT Oct 20) | tour a50c6039 mode in_person → virtual (no history entry, no version bump); Kat's calendar event re-rendered "Virtual tour: Jaleigh · 2841 Morningside Court · 249", location "Virtual Tour (video call)"; correction text to Kat with the Zoom link, SID SM7969780023a374a741f19b7db178d1e8 (staff conv c9f809af); card 3028 note "Virtual tour (video call) — Wed Oct 21, 11:00 AM MT" queued as append_note intent gc:append_note:…:mode_virtual:a50c6039 (first attempt ran on the laptop without the robot token and failed; redriven, written by the production robot 2026-10-20T13:45:47Z, state sent); showing 599 untouched; T-24h/T-1h reminders render virtual at send time |
| Willows live run (rung 4): tour saved virtual when the ask came in an earlier email; renter confirmation, team email subject, staff text, calendar event and both reminders carry the video link | tour 5ed948d8-7556-41a5-9f0a-bde0cf65749e (mode virtual, Wed Oct 21 11:00); renter confirmation 06:30:46Z; staff text SM90ce3e45466273bcbf03c35ebc4b0d91; calendar event title 'Virtual tour: Rowan · The Willows · 204', location 'Virtual Tour (video call)'; PMS showing not proven at Willows (unit 204 has no AppFolio unit id, test-data gap) |
Kat, Oct 16: "I just had another no show and if I could've called them I could have found out if they were still planning on coming."
| Tours since go-live | No phone at booking | Zillow relay only | Recorded no-shows | Reminders sent |
|---|---|---|---|---|
| 33 | 7 | 6 | 2, both no-phone leads | 0 |
Today
Ask, then book
What Clara says, the replay, the decisions
Clara: "Friday, October 23 at 3:30 PM works. What's the best number to reach you? I'll book it once I have it."as built, passes the booking-promise guard · name and phone both missing: "Can I get your full name and the best number to reach you?" · Spanish: "El viernes, 23 de octubre a las 3:30 PM funciona. ¿A qué número le puedo contactar? Lo reservo en cuanto lo tenga."
If the slot is gone when the number arrives, Clara offers the next times. No number: no tour exists, nothing to expire, nothing blocked. A phone already on file: booked as today, no ask. Staff bookings: no ask.
| Replay, 33 real tours | Asked for a number | Staff booking, no ask | Booked as today |
|---|---|---|---|
| Ask-then-book, real records | 6 (5 people) | 1 | 26 |
| Ask removed (control) | 0 | 0 | 33 |
| Did any of the 5 ever send a number later? | No. Every prospect message in their threads was read (31 messages, all email): none contains a phone number, before or after the tour. The ask would have been the only way to get one. | ||
Decisions (Fede, Oct 19, his words)
Harness: 13 cases on the lead-outcome replay, each with a planted-defect control
Extended the existing lead-outcome replay family (no new harness), synthetic records through the real reply and booking path. Red on main (12 of 19 assertions fail), green on the merged branches (19 of 19), and each of 8 planted defects turns the file red. Registered as mutation controls; atlas row added.
| # | Case | Main | Branch | Planted defect that reds it |
|---|---|---|---|---|
| 1 | No phone, time picked: one ask, nothing written | red | green | phone treated as on file |
| 2 | Number arrives: booked, card row queued, reminders armed | red | green | consent skipped; outbox dropped |
| 3 | Slot gone when the number arrives: next times, nothing booked | red | green | calendar not re-read |
| 4 | No number ever: no tour, no record | red | green | phone treated as on file |
| 5 | Phone on file: booked as today, no ask | green | green | phone treated as missing |
| 6 | Staff booking: no ask | green | green | staff context dropped |
| 7 | Number typed in the reply vs only in the signature | red | green | consent skipped / consent on a found number |
| 8 | A different number volunteered (known gap: not saved yet) | red | green | phone treated as missing |
| 9 | Spanish thread: Spanish ask | red | green | phone treated as on file |
| 10 | Duplicate person with a known number merges; card row targets the survivor | red | green | merge skipped; outbox dropped |
| 11 | Zillow relay-only lead end to end | red | green | outbox dropped; consent skipped |
| 12 | Two prospects, same slot: the second gets the next times | red | green | calendar not re-read |
| 13 | Team-offered time accepted with no phone, then the number: that tour confirms | red | green | phone treated as on file |
Defect the harness found, now fixed: a number typed in an email reply and saved through one prospect tool got no reminder-text consent (only the booking call's phone field granted it), so such a prospect would have been booked without the 24h and 1h texts. Fixed on the branch; that case is red before the fix and green after, and a number pulled from a parsed lead email still gets no consent. 19 of 19 assertions green on the merged branch. Known gap kept: a second, different number is not saved (D5 follow-up). Case 12 proves the calendar is re-read, not the race for the slot itself.
The seven real tours Clara would have asked
| Tour | Home | Prospect | Contact on file | Booked via | Outcome | With the change |
|---|---|---|---|---|---|---|
| Oct 7, 12:00 PM | 2841 Morningside Ct | Sara D. | Zillow relay only (a duplicate record of hers, from an Apartments.com card, had her phone; merged Oct 9, after the tour) | unrecorded | asked; her number would have merged the two records at once | |
| Oct 7, 1:00 PM | 2841 Morningside Ct | Bowmuxlow M. | Zillow relay only | no-show | asked | |
| Oct 9, 5:30 PM | 2841 Morningside Ct | Bowmuxlow M. | Zillow relay only (rebooked after the no-show) | unrecorded | asked | |
| Oct 14, 12:00 PM | 2980 Emerald Cir | Natalie H. | Zillow relay only; two guest cards (3016, 3017), neither linked | unrecorded | asked once for both homes | |
| Oct 14, 1:00 PM | 2841 Morningside Ct | Natalie H. | same | unrecorded | same visit day | |
| Oct 16, 3:30 PM | 2841 Morningside Ct | Alicia J. | Zillow relay only; card 3015 never linked, a second person record minted Oct 16 | no-show | asked | |
| Oct 17, 10:00 AM | Northridge Apts | Tiffani H. | Gmail, no phone (Rent. lead); card 3012 has no phone | staff text | unrecorded | staff booking: bypass, team already had the thread |
Replay result (local build, Oct 18 evening). A script rebuilt each person's contact records as they stood when the tour was created and ran the real gate decision from the local branch over all 33 tours.
| Run | Held | Staff bypass | Booked | Result |
|---|---|---|---|---|
| Gate on, real records | 6 | 1 (Tiffani H., staff text) | 26 | every no-phone tour held or bypassed; 0 disagreements with an independent read of the same records |
| Control: gate forced off | 0 | 0 | 33 | the check turns red when the rule is removed |
| Control: a fake phone planted on Alicia J. before her booking | 5 | 1 | 27 | she flips to booked |
What Clara would have said, from the real leasing prompt and the real booking tools fed the real prospect message (the replay harness cannot set its clock back to a past slot, so this is the reply path, not the full loop). All three asked for a phone; the control run fed the line production actually sent said "You're all set, tour confirmed" all three times.
Two gaps the replay builder found by reading the gate, both sent back to the gate builder to fix on the branch: (1) a held tour could still be confirmed with no phone if the prospect's next message asks to move the time, because the reschedule path never checks contact (main has the same hole for the name rule); (2) a prospect id passed by the model counts as a staff booking and skips the rule. Neither fired in the 33 real tours.
Saving the phone to the guest card, and the duplicate check (D3: no gate)
Today
- A phone reaches the AppFolio card only when Clara creates the card. Learned later, it stays on our side.
- Phones flow from AppFolio into PropFlow every 15 minutes, never back.
- A phone typed into an email reply is never stored as a phone at all; the tools have no phone field.
- Same phone in one company is already one person (automatic merge), but it needs the phone first.
After
- New neutral write: "update contact" → outbox → PMS adapter → the robot's existing fill-empty contact route (robot #490, Oct 9). Fills an empty phone or email; refuses to overwrite.
- The outbox row carries no phone; the current value is read from the person at send time.
- Loop guard: a phone that arrived from the AppFolio sync is never pushed back.
- Duplicate check: the given number goes through the identity path, so a match to a person we already know merges on the spot (Sara D.'s case). A matching guest card at the same home links instead of creating a new one; across homes is a known gap on the hub (section 22).
Local build, Oct 18 evening (branch fede/guest-card-contact-intent, 8 small commits). A new "update contact" write kind and its queueing function (one row per card per field; the row carries the field name, never the number). A set-contact method on the shared guest-card writer, its AppFolio version, and a robot client call to the fill-empty route. The drain reads the person's current phone or email at send time and ends in one of four states: filled, no change, already set, no value. The trigger fires only when a field was newly filled and the number came from the prospect or a staff edit, never from the AppFolio sync (that is the loop guard, with a planted-defect control that turns red when the sync's provenance is added to the push list). It rides on the existing per-building "guest card after a call" setting, no new switch.
| Check | Result |
|---|---|
| Drain, writer, trigger and queueing unit tests | 23 of 23 pass, exit 0 (unit-covered, not "tested") |
| Mutation control (sync provenance pushed back) | green as shipped, red when planted |
| No AppFolio name outside the adapter layer | self-review gate passes |
| Size | 1,091 lines over the whole branch; splits into a chain of 4 PRs of about 330 lines when Fede says go |
| Integration with the phone rule | first cut hooked the wrong save path; the rule saves through the identity path that merges duplicates, so a second trigger is being added there |
| Real card | not yet: next rung is a count of rows it would queue per customer, then one live fill on a Willows bench card |
Ownership: the write intent and the outbox are in the PMS layer this session owns (lane 8); the after-reply adapter and side-effects belong to lane 10 and are not touched. No AppFolio name outside the adapter files. The robot checkout on this machine is behind; the route exists on its main.
Local proof (ask-then-book shape, Oct 19)
Both branches are rebased onto current main. Unit tests are listed as unit-covered, never as "tested"; the only validation that counts is the end-to-end run in production after merge, at Willows first.
| Check | Phone ask (fede/tour-phone-gate, 10 commits) | Guest-card fill (fede/guest-card-contact-intent, 10 commits) |
|---|---|---|
| Replay, red before / green after | Zillow relay replay: red on main (books confirmed with no phone), green on the branch, 6 of 6: ask once, nothing written, a typed number books it with reminder consent, a signature number books it without, one person. Team-offered time accepted with no phone: red without the fix, green with it. | 29 of 29 across drain, writer, trigger, queueing, identity-path trigger; no settings row → still queued; survivor after a merge. |
| Planted defect (mutation control) | ask removed → red | sync provenance pushed back → red |
| The two together | integration checkout rebuilt from current main plus both branches; the test "a relay-only prospect gives a phone → one guest-card fill row; a known number → the surviving record; sync provenance → no row" passes 3 of 3. | |
| Real-record replay | 33 Western Slope tours: 6 asked, 1 staff no-ask, 26 booked as today; ask removed → 0; none of the 5 ever sent a number later | count of rows it would queue per customer: with the harness |
| Harness | 13 cases on the lead-outcome replay family, red on main, green on the branches, 8 planted defects red (fold above); one D4 consent gap found, fix in progress | |
| Self-review gate | everything passes except size (1,655 and 1,345 lines); each ships as a chain of one-concern PRs of about 300 lines. 0 AppFolio names outside the adapter layer. | |
| Willows live run | after the chain merges: a real relay-only email to the Willows inbox, the ask, a number, the booking, the card fill, the reminder texts, with record ids | |
Known follow-ups: a volunteered second number (D5) is not built, reminders go to one number per person; a time the team must confirm (no calendar connected) is still recorded as a request with no phone; the harness's fake robot does not know the new fill route.
Finding for later, no decision asked: every note Clara wrote to guest cards since Sep 18
Read-only inspection, Oct 18, of every note Clara queued for a guest card since the outbox began on Sep 18 (596 note-bearing writes across every customer; counts are rule-based over all of them, not a sample). Western Slope is the only live customer with notes: Situs and Camellia have none, and Camellia's 83 cards in the Oct 9 export carry no notes at all.
Gap count, the 20 Western Slope prospects who wrote back since Oct 1. "Happened" is read from the real threads; "got a note" is any note on the card for that milestone.
| Milestone | Happened | Got a note | What the card shows instead |
|---|---|---|---|
| Tour booked | 14 | 4 | 11 appear as a scheduled showing, no note line |
| Rescheduled | 4 | 0 | the old showing row |
| Cancelled | 1 | 0 | nothing |
| Deferred or not a fit | 4 | 0 | nothing (one case reached the card only through Kat's after-tour answer) |
| Application sent | 9 | 8 | the same sentence every time |
| Call | 3 | 2 | a call summary |
| Went quiet (3+ days) | 16 | 0 | nothing; the card still reads as "Clara reached out once" |
| Total | 51 | 14 | 9 of the 20 prospects have only the first email on their card |
The deferrals that got no note: Angele T. ("I will reach out"), Ethan S. (busy "for a couple weeks"), Natalee M. ("let me get back to you on timing"), and Erika L. (a home "out of my first range"). One prospect, Tessa L., wrote 20 times and called once, and her card has nothing from Clara at all. Every email logged on a card is a first email; Clara's later emails and texts are never logged.
Clara's note today (real, scrubbed)
"9/18 – Riley C. contacted The Willows to inquire about the availability and earliest move-in date for a one-bedroom unit. The agent, Clara, was unable to confirm current availability from the records as the office was closed. Clara informed Riley that the team would be notified to follow up with the requested details. Riley confirmed the contact number and expressed continued interest in moving forward once the information is provided." (70 words, first contact)
"Email · Subject: Marisol is requesting information about 1601 Blake Street … Hello, is this one still available? Could I tour it?" (the inbound email pasted in)
How the team writes (Kat, real, scrubbed)
"Have a showing scheduled 3/10/24"
"Samantha didn't show up. I texted her and she responded and said she would let me know if she wanted to reschedule."
"I spoke with him today, his max budget is $2,500. I told him about the 1 month free. He will be back in town next week and reach out if he wants to move forward."
"No longer interested." · closes with AppFolio's own reason, e.g. "Reason: Found a Different Location"
The best Clara notes on any card today are Kat's answers to the after-tour check-in, relayed by Clara ("Post-tour check-in from Kat: good fit, looking for end of December move-in"). That is the shape the design below copies. Full report, per-note sheet and 15 scrubbed samples are kept with the session; the Fairhaven test property shows 127 failed notes (no time zone on the property), a test-data defect, not a customer one.
References and queries
- Kat Barker's email, Oct 16 5:06 PM and Fede's reply (Slack #western-slope, ts 1792188405 / 1792189615). Jay Taylor's Oct 9 email (ts 1791594212). Oct 14 weekly with Western Slope, raw transcript (#transcripts thread 1792015510).
- Leasing hub: leasing-engine-designs, sections 6 (prospect notes field audit), 10 (what we write to AppFolio), 22 (guest-card updates write-intent audit), 25 (guest-card notes, team style). Name rule: PR #19305. Robot fill-empty contact route: appfolio-browser-agent #490.
- Testing: testing playbook; harness atlas. Branches (local, no PR): fede/tour-phone-gate, fede/guest-card-contact-intent.
- Numbers: read-only DynamoDB queries over propflow-prod on Oct 18. Tours: by property partition since Sep 17; person claims by type and created time; mirror guest cards since Sep 17 by lead source; reminder marker rows. Notes: write-intent outbox by status (776 sent, 724 failed, 1 pending, 0 writing); Western Slope conversations since Oct 1 (264: 178 voice, 61 email, 15 video, 10 text) with the 86 non-voice threads read in full; staff notes from the Oct 9 AppFolio history pull (3,274 Western Slope cards) and the 2022 to 2025 robot card dump (1,707 cards). Per-tour sheet, per-note sheet and scripts kept with the session.