Two shared-line callers named a home community; neither got a card.
Findings — read-only investigation; 30-day sweep pending · Western Slope
On this page
Both callers named a community, never a full address. The matcher found several homes that fit and, by design, picked none — but the card-writer only reads the ONE home already picked, never the candidate list, so nothing was filed. Silently: the email alert for this was removed. The matcher also skips AppFolio's own records, checking only current listings. Nothing here is switched off.
| Caller | Said | Resolver returned | Card? |
|---|---|---|---|
| Jason Peshko | "Ember Estates" | 3 candidates, no primary | No |
| Ricky Kale | "Amber/Ember Lane," no number | 2 candidates (resolver, not spoken), no primary | No |
Full transcripts, ids and code trace: Details below.
appfolio-399, "Ember Estates HZ," with 27 unit rows (361–374 Ember Ln, 2860A–2868B Florida St; verified live, 27 UNIT# rows under PROP#appfolio-399). Seven of those same homes ALSO exist as separate single-family properties, appfolio-405..411; 363 and 364 Ember Ln exist ONLY as units under 399, with no separate single-family property row at all. The matcher never reads either AppFolio property record — it only reads the scraped public-listings feed, which knows about neither the community property nor its units.homesOfInterest/candidates) as a fifth option, so a caller who named a community correctly and got several real candidates back is treated exactly like a caller who named nothing at all. Confirmed by code.no-pms-property. PR #11361 (2026-09-23, titled around reducing noise) removed that email. Today a call that resolves to several homes or none produces only a log line ("uncarded lead recorded silently… by product decision") — no outbox row, nothing queued, nothing that retries or pages anyone. This is a contributing cause, not the root cause: even if the matching gap above didn't exist, this pattern of calls would now fail with zero visibility. It also conflicts with Fede's 2026-09-24 ruling that a refusal to act is an engineering alert, never a silent one.Full 12-row table and the log-coverage caveat: Details below.
Window: 2026-08-26 to 2026-09-25, all 62 inbound Western Slope voice calls. Runtime logs only start 2026-09-23T18:08Z, so 58 of these 62 calls have no log coverage at all — "no log found" below means no log exists to check, not that nothing happened.
| Date (MDT) | Caller | What they said | What got stamped | Card? |
|---|---|---|---|---|
| 09-18 08:13 AM | Unknown caller | "Pomona Park Townhomes" | Nothing — despite a clean single-property match | No |
| 09-18 10:32 AM | Stephanie | "Pomona Park townhomes" | Resolved to appfolio-392 | Yes |
| 09-18 01:05 PM | Karen G. | "Ember Estates" → asked for 367 Ember Lane | Resolved on her own inquiry (appfolio-408), but zero writes came from her call; her card later came from a separate Zillow inquiry that absorbed hers | No (from her call) |
| 09-21 01:24 PM | Officer Barrett (not a prospect) | An address, not a leasing ask | No inquiry created — non-leasing call | n/a |
| 09-21 01:48 PM | Jason P. | "Book a showing for 367 Ember Lane" — his only exact-address ask; his two earlier 09-21 calls named nothing (asked for a live agent, transfers rang out) | Pre-fix code stamped the raw shell "western-slope-front-door-367 Ember Lane" on inquiry 8198eb19; never re-resolved by the 09-22 fixes | No |
| 09-22 08:07 AM | Kaitlin N. | Nothing — 6-second call, dropped after "Leasing." | Nothing stamped, correctly | n/a |
| 09-22 12:04 PM | Unknown caller | "Ember Estates" (also said other, unrelated names) | 2 candidates, correctly no primary | No |
| 09-22 12:29 PM | Dylan L. | "566 Placer Street" (key pickup) | Resolved to an existing card | Existing card, notes only |
| 09-23 09:40 AM | Brody R. (existing tenant) | Billing dispute, mentioned "Pomona Park" | No inquiry created — non-leasing call | n/a |
| 09-24 10:43 AM | Chloe | Market-research question, mentioned "Pomona Park" | No inquiry created — non-leasing call | n/a |
| 09-24 05:28 PM | Jason Peshko | "Ember Estates," no number this time | Hit the several-candidates branch (3 candidates, no primary); the candidates don't include 367 Ember Lane, the address he'd named on 09-21 and which still sits unresolved as a raw shell on this same inquiry | No |
| 09-24 05:48 PM | Ricky Kale | "Amber/Ember Lane," no number spoken | 2 candidates, correctly no primary | No |
"Imagine we're scaling to 10,000 units. And there's nuance on the names, Ember Estates or Ember Lane, that should all match the same." — Fede. Today: exact addresses always resolve; a community or bare street correctly returns several candidates rather than guessing; but the reference data is incomplete, a garbled name resolves to nothing, and the same street spelled two ways ("Ember Lane" vs "Ember Ln") in the source data can silently split a community's own homes into found and missed.
| Check | What it does today |
|---|---|
| Reference data compared | Only currently-offered listings (18 of 70 onboarded WS properties) — not the full portfolio, not AppFolio's own property/unit tables. |
| Exact address | Resolves cleanly to one property (e.g. "367 Ember Lane" → exactly one home). |
| Community name ("Ember Estates") | Deterministic, code-only match against marketing text. Correctly returns several candidates, never guesses one. Capped at 3 candidates (slice(0,3)). |
| Street only ("Ember Lane") | Same deterministic path, narrows to the homes on that street, still several — never guesses one. |
| Zero vs several matches | Both cases stop short of picking a primary home. A "several" case and a "zero" case both leave the lead with no primary home, so the card-writer treats them identically. |
| Speech garble ("Ember States"/"Amber Lane") | No fuzzy or sound-alike tolerance anywhere in the deterministic matcher — a garble that doesn't survive basic text normalization matches nothing. (Kale's case didn't even reach this: he said "Amber Lane" but the model transcription step already normalized it to "Ember Lane" before the matcher ran, so the matcher itself wasn't tested by this case.) |
| Spelling of the same street, two ways | The exact-address tier normalizes street suffixes (ln→lane), but the looser containment tier used for a street-only mention does not. Probed live: "Ember Lane" found 367 and 372 (listed as "Ember Lane") but missed 363 and 364 (listed as "Ember Ln") — same street, inconsistent suffix spelling in the source data, no normalization on that path. (inferred: the exact code line for the unnormalized containment check.) |
| Model vs deterministic | Only the first step — pulling out the words the caller used — is model-guessed. Everything after that (matching those words to a real home) is plain, deterministic code, with comments suggesting the rules don't change at higher unit counts. (inferred: not actually tested at 10,000-unit scale.) |
No fix is proposed on this page. The design for how Clara should handle a community/street with several matching homes is being written up on the leasing engine designs page.
Two callers converge on one extractor, in src/lib/integrations/voice/postcall-prospect-capture.ts:
maybeCreateProspectFromTranscript (postcall-prospect-capture.ts:177-325), called from src/app/api/voice/call-ended/route.ts:2384 when no existing lead was found.stampNamedHomeForExistingProspect (postcall-prospect-capture.ts:670-722), called from route.ts:2393 — added by #10805 so a caller who already has a lead also gets extraction run.extractProspectFromTranscript (postcall-prospect-capture.ts:121-175) against the caller+Clara transcript. Model: FAST_MODEL, forced tool call extract_prospect whose schema carries the homeText field (added by #10755) — "the community, street, or full address the CALLER named," preferring the more specific reference.homeText is resolved via resolveOrgHomeForLine (org-home-booking.ts:369-392) — the same seam schedule_tour uses (#9532).
propertyId first (matchOrgHomeByPropertyId, org-home-booking.ts:158-162), then address/alias tiers (resolveListingTarget/pickOrgHome, org-home-booking.ts:232-264), then (added 2026-09-23) homesQuotedInCall, which requires the whole spoken address to appear in the transcript (org-home-booking.ts:184-220).homeKey lowercases, strips punctuation, maps street-suffix abbreviations (appfolio-public-listings.ts:110-125).slice(0,3)): pickOrgHome returns { home: null, candidates, candidateHomes } (org-home-booking.ts:256-264). The capture module never guesses — it writes one homesOfInterest entry per candidate (postcall-prospect-capture.ts:510-524) and, only if aiNotes is still blank, a note listing the candidates (:536-546). No primary field is stamped in this branch. This is the exact branch #10755's own test suite asserts should produce no card — not a gap the fix wave caught.Inside stampNamedHomeFromTranscript (postcall-prospect-capture.ts:428-584):
targetUnitId/targetHomeAddress/targetListingId) — fill-if-blank-or-unresolved only, via deriveInquiryHomePatchIfUnresolved (inquiry-home-patch.ts:85-93) → written with updateInquiryFieldsForProspect (:489-493). Never overwrites a booking that already resolves to an onboarded property.homesOfInterest — append-always via mergeHomesOfInterest (homes-of-interest.ts:78-118), one entry per resolved home / candidate / unresolved raw text; capped at 10, oldest dropped.propertyId is never re-pointed off the org front door (western-slope-front-door). By design, only targetHomeAddress/targetListingId/targetUnitId on the inquiry carry the resolved home (org-home-pms-property.ts:6-12, postcall-guest-card-outbox.ts:48-56).Guest-card outbox writer requirement (postcall-guest-card-outbox.ts): candidate order is tour → inquiry.propertyId → conversation.propertyId → last-resort targetHomeAddress match (:301-356). resolveIntentPropertyId (:203-210) keeps the first candidate with a real PMS writer. If none of the candidates has one — e.g. every candidate is the front-door shell — it returns { enqueued: false, reason: 'no-pms-property', candidates } and logs a warning, deliberately never enqueuing, never retrying (:358-367, design note at :60-66):
logWarn('GuestCardOutbox',
`no intent appended for inquiry ${prospect.id}: none of [${candidates.join(', ')}] is a ` +
'property with a PMS connection — a front-door line is not a building, and there is ' +
'no home to file the card against. Never enqueued, so the drain never retries it.');
return { enqueued: false, reason: 'no-pms-property', candidates };
So the AppFolio card write depends entirely on targetHomeAddress (or a resolved tour/inquiry propertyId) resolving to an onboarded PMS property — the several-candidates list is never consulted by the writer.
Alerting history on this exact skip: PR #10903 (2026-09-22) added a team email fired on no-pms-property. PR #11361 (2026-09-23, framed around cutting alert noise) removed that email. Today the skip produces only a log line ("uncarded lead recorded silently… by product decision") — no outbox row, no retry, no page. This conflicts with Fede's 2026-09-24 ruling that a refusal to act is an engineering alert, never silent.
src/lib/maintenance/eval/robot-call-scenarios.ts — three robot-bench-* scenarios added by #10757 against the Fairhaven synthetic many-homes test bench (+17208071724, property fairhaven-homes), explicitly marked "not executed by the PR that added this scenario."evals/fast-tier/postcall-prospect-capture/ — prompt/model regression harness; extract-prod.py pulls the most-recent real conversations globally, no org or date-range filter (not a 30-day sweep).scripts/harvest-conversations.ts — the actual 30-day-style sweep tool (--days, --property-id, --channel voice), feeding evals/replay/run-replay.ts. This is the mechanism available for the pending sweep, not lane-specific to this bug.| PR | Merged | What changed | Proven on |
|---|---|---|---|
| #9105 | 2026-09-18 | Guest cards from calls: write-intent outbox + AppFolio writer, dark, org switch off. | Willows-style dark ship; design doc |
| #9155 | 2026-09-17 | Write one AppFolio guest card when a call ends (property-level setting, default off). | Not WS-shaped; generic property setting |
| #9490 | 2026-09-18 | Guest-card intent files against the discussed home, never the front-door row. | Real WS prod data: "Pomona Park Townhomes, 624 Eisenhauer Street," intent parked 3/3 attempts against western-slope-front-door |
| #9579 | 2026-09-19 | A booked company home carries its real onboarded property, not the front-door shell. | Real WS call ("Karen Green," 367 Ember Lane) |
| #9745 | 2026-09-19 | One writer per call — outbox owns the org. | Willows (appfolio-45) |
| #10755 | 2026-09-22 14:51 | Extract the home the caller named and stamp it on the lead so shared-line calls get a card. Cites the WS Ember Estates incident directly. | WS-shaped incident cited by name; fix built generically |
| #10757 | 2026-09-22 15:31 | Shared-line replay scenarios for the WS guest-card misses. | Fairhaven synthetic test bench, not real WS addresses |
| #10783 | 2026-09-22 16:46 | A stale bare home address no longer blocks the guest card; loose bare-street matcher added. | Fairhaven bench (synthetic) |
| #10796 | 2026-09-22 17:27 | A lead keeps the list of homes discussed (homesOfInterest), append-and-dedupe, capped at 10. | Unit-test only; Ember Estates is the worked example in the design comment, not replayed live |
| #10805 | 2026-09-22 19:03 | A repeat caller who names a home gets it stamped too, not only a first-time caller. | Real Fairhaven bench call; Ember Estates incident by reference |
| #10881 | 2026-09-22 20:27 | A raw home label no longer blocks stamping the home a repeat caller names. | Reproduced live on two real Fairhaven bench calls |
| #10942 | 2026-09-23 00:20 | A home named on a repeat call is always added to the lead's list, even if a primary is already resolved. | Real bench call, mutation control (red without the fix, green with it) |
| #10903 | 2026-09-22 | Adds a team email alert whenever a call ends with the no-pms-property skip. | — |
| #11361 | 2026-09-23 | Removes that alert (framed around cutting alert noise); the skip now only produces a log line, nothing that pages or retries. | — |
No PR in this set replays a corpus of real WS AppFolio guest-card data end-to-end. The closest is #9490/#9579 (real WS production conversations, single calls, not a corpus).
western-slope-issue-list-2026-09-21.html)appfolio-399, 27 units); seven of its homes additionally exist as separate single-family properties. See the roster audit above for the verified figures.homesOfInterest is the data half of option A, the "separate card entry" half is not built.| Test file | Data shape exercised |
|---|---|
src/__tests__/postcall-prospect-capture.test.ts | 39 hits for "Ember," 11 for "Fairhaven," 19 for "Quillwood," 7 for "appfolio-45" — mix of the literal WS incident name and synthetic fixtures |
src/lib/domain/leasing/__tests__/inquiry-home-patch.test.ts | Pure-function unit cases, generic |
src/lib/domain/leasing/__tests__/homes-of-interest.test.ts | mergeHomesOfInterest unit tests: dedupe, cap-at-10, mutation control |
src/lib/domain/leasing/__tests__/org-home-pms-property.test.ts | Loose bare-street matcher unit tests |
src/__tests__/guest-card-outbox-from-call.test.ts | 50 hits "appfolio-45" (Willows), some Ember |
src/__tests__/api-routes-voice.test.ts | 50 "appfolio-45", 24 "Fairhaven," 26 "Quillwood," 17 "Willows," only 1 "Ember" — the bulk of the last three PRs' regression coverage is Fairhaven/Willows synthetic fixtures, not WS-shaped multi-single-family-under-one-community data |
Fairhaven is confirmed synthetic, not real WS inventory: scripts/voice-harness/scenarios/knowledge-exception-fairhaven.yaml header states "TEST COMPANY: Fairhaven Residential (TEST)… None of the 7 addresses below is a real Fairhaven listing," used so robocalls can hit a real many-homes line without touching WS production data.
| Field | Source | Compared how | File:line |
|---|---|---|---|
id | always '' | never compared — no Unit row backs a home | org-home-booking.ts:55-57 |
number (full street address) | OrgListing.address | exact / substring / whole-token containment | org-home-booking.ts:58-59; tiers in listing-resolution.ts:340-378 |
key (listing externalId) | OrgListing.externalId | not text-compared; stamped on match | org-home-booking.ts:60-70 |
aliasText | listing.title + listing.description | name/marketing tier only, last resort, requires a 2+ word Title-Case proper-noun phrase | org-home-booking.ts:87-97; matcher listing-resolution.ts:363-378, :302-309 |
propertyId | roster row's internal property id, when PMS-joined | exact-equality first | org-home-booking.ts:109-119, :158-162 |
An AppFolio property NAME field does not exist as a discrete roster field — confirmed live: PROP#appfolio-405..411 (name) all equal the street address itself (e.g. "374 Ember Lane"), no communityName/marketing field anywhere on those single-family property rows. "Ember Estates" only lives in listing prose (aliasText). A street-only mention ("Ember Lane") is compared, but only via containment against number, never aliasText.
Correction to an earlier read of this data: "Ember Estates" IS a real AppFolio property, appfolio-399 ("Ember Estates HZ"), carrying 27 unit rows (361–374 Ember Ln, 2860A–2868B Florida St — verified live, 27 UNIT# rows under PROP#appfolio-399). Seven of those homes also exist as separate single-family properties, appfolio-405..411; 363 and 364 Ember Ln exist only as units under 399, with no separate property row. The matcher never queries either the AppFolio property table or the unit table — the roster it actually reads is a separately-scraped public-listings feed that knows about neither.
Roster is that listings feed (getOrgOfferableHomes → listOrgListings/offeredOrgListings), i.e. only currently-offered homes — not AppFolio's own property/unit records. Confirmed live: WS org (org_c42d9150…) has 70 onboarded property rows but only 18 OrgListing rows total, of which only 4 are offered:true on the Ember cluster (364/367/372/363 Ember Ln); 374 Ember Lane is delisted; single-family properties appfolio-409 (362 Ember Lane) and appfolio-410 (361 Ember Lane) have no listing row at all and would never appear in the roster.
homeKey lowercases, strips punctuation, maps street-suffix abbreviations (appfolio-public-listings.ts:110-125).containsTokens, listing-resolution.ts:386-394); short tokens only count behind a unit designator. This containment tier does not run street-suffix normalization the way the exact-address tier does: probed live, "Ember Lane" matched 367/372 Ember Lane but missed 363/364 Ember Ln, purely because of how the address is spelled in the listings feed. (inferred: exact code line for the unnormalized path.)slice(0,3)) — a community with more than 3 live listings drops the rest silently.homeText extraction is model-guessed (FAST_MODEL via extractProspectFromTranscript, postcall-prospect-capture.ts:121-165). Everything downstream is deterministic, pure code — no LLM in the matcher itself.pickOrgHome returns { home: null, candidates, candidateHomes } (org-home-booking.ts:256-264); capture writes one homesOfInterest entry per candidate, never a primary.{home:null, candidates:[]}; capture writes ONE homesOfInterest entry with the raw homeText, no propertyId. Thread tag only fires when a propertyId resolved, so zero/several never get tagged — the two failure modes are indistinguishable downstream.Real rows: appfolio-405..411 = 374/2863(406)/372/367/362/361 Ember or Florida. Org org_c42d9150… has 70 onboarded properties total but only 18 listings, 4 offered on the Ember cluster. Offline probe (resolver-probe.ts, run via pickOrgHome imported live, no DB writes):
| Query | Result |
|---|---|
| "Ember Estates" | 3 candidates: 364, 367, 372 Ember Ln/Lane (name-tier match on aliasText) |
| "Ember Lane" | 2 candidates: 367, 372 Ember Lane (address-containment on suffix-normalized "ember lane") |
| "Ember States" (garble) | 0 candidates — no fuzzy tolerance, resolves to nothing |
| "367 Ember Lane" | ONE HOME: 367 Ember Lane, propertyId=appfolio-408 (exact address match) |
Note on the "10,000 units" framing: production WS scale today is 70 onboarded properties / 18 listings. The code comments (listing-resolution.ts header) treat the matching rules as scale-invariant (exact-first, then narrow containment, then a proper-noun-gated name tier), so nothing in the algorithm itself is expected to change behavior at higher unit counts — but this was not tested at 10,000-unit scale (inferred).
pers_81064e43-e6cb-4bf5-ab4b-8a12262f120d — the same person as the 2026-09-21 Ember Estates calls the six 22 September PRs were built for; he called back on 24 September and failed the same way, on the same inquiry.8198eb19-c020-45fb-8b08-102be80eb147, conversation (09-24 call) conv_voice_73d01668-9683-4295-8f16-7dbc5011ec14, org org_c42d9150-b9ad-46f8-8f5f-b0fe56bacf38 (Western Slope), bound propertyId = western-slope-front-door."western-slope-front-door-367 Ember Lane" on this same inquiry, and it was never re-resolved after the 09-22 fixes shipped. His two earlier 09-21 calls named nothing — he asked for a live agent and the transfers rang out. On his 09-24 call he said only "Ember Estates," no number, and hit the several-candidates branch fresh.homesOfInterest (from the 09-24 call): 2863 Florida St, 363 Ember Ln, 364 Ember Ln (all Grand Junction, CO 81501) — none of these is 367 Ember Lane, the address still sitting unresolved as a raw shell on this inquiry.aiNotes (09-24 call): "Jason Peshko is interested in three-bedroom, two-bath homes at Ember Estates and requested a showing for 367 Ember Lane… transfer attempts were unsuccessful." — this note is pulling in his 09-21 ask; he did not repeat the address on this call.pers_6caf599e-3bb7-4348-a16a-ed115c961b13, inquiry ba42fd03-f391-46b5-9d97-bd4cc9c8b422, conversation conv_voice_ae06db35-4eff-468c-bcdb-f40681d5a709, same org/propertyId.homesOfInterest came from the post-call summary step's own resolver candidates — produced by the matcher when it ran "Ember Lane" against the roster — not from anything Kale said himself.aiNotes: "Ricky Kale called asking about homes for lease on Ember Lane… $2,750 to $2,825 a month with a grand opening special…"targetUnitId is blank (""); Peshko's is the malformed string "western-slope-front-door-367 Ember Lane" — not a real Unit.id shape.phone-index GSI query GSI1PK=PROPERTY#western-slope-front-door, GSI1SK begins_with INQUIRY# (16 front-door inquiries total, matched by name off the person profile rows).logWarn('GuestCardOutbox', 'no intent appended for inquiry <id>: none of [<candidates>] is a property with a PMS connection — a front-door line is not a building, and there is no home to file the card against. Never enqueued, so the drain never retries it.') → { enqueued: false, reason: 'no-pms-property', candidates }. Confirmed no write-intent row exists for either inquiry (queried phone-index GSI GSI1PK=PMS_WRITE_INTENT_INQUIRY#<inquiryId>, 0 items each).CONFIG / GUEST_CARD_WRITE_FROM_CALLS#org_c42d9150-b9ad-46f8-8f5f-b0fe56bacf38 → enabled: true, armed by Fede 2026-09-18. Not gated off — the skip happens purely on the "no PMS-mapped candidate" path./propflow/vercel/runtime and /aws/lambda/propflow-agent-runtime-prod by both inquiry and conversation ids, window 20:00–02:00 UTC 9/24–9/25 — 0 matching events in either group (the warning line likely lands in a different sink; could not verify it reached CloudWatch specifically).