Why Ask Clara told Sean that 4 of Camellia's top 5 problems are renewals
Investigated 2026-08-03 · status: root cause confirmed, fix proposed
Verdict: the answer was wrong, and it wasn't the chat model's fault.
The nightly "portfolio signals" snapshot that Ask Clara reads flags nine Camellia
tenants as renewal risks, each partly because of "30+ days since last
conversation." That silence claim is false for at least four of the nine —
they had renewal calls and texts with Clara on August 1–2, the
day before Sean asked. The snapshot can't see those conversations because it
joins on the wrong ID.
What Sean saw
Asking Ask Clara "top 5 problems for Camellia" returns one tour-conversion
finding and four renewals findings. Reproduced on prod 2026-08-03 with the same
snapshot (reportDate 2026-08-03): the Camellia-scoped snapshot holds
10 findings — 9 renewals, 1 leasing. Ask Clara faithfully
summarized what it was given.
Root cause 1 — a broken identity join fabricates the "silence" factor
The renewal-risk signal (src/lib/domain/insights/signals/renewal-risk.ts)
counts a tenant as risky when 2+ factors converge, one of which is
silence: no conversation in the last 30 days. It looks up conversations
by person-spine ID (pers_… UUIDs). But all 149
Camellia tenant records carry no spine personId — their leases
still use legacy numeric IDs (e.g. …4620062). The lookup can never
match, so every single Camellia lease inside the renewal horizon
is automatically "silent", regardless of what Clara has actually been doing.
Proof from prod, 2026-08-03 — tenants the snapshot calls "30+ days silent"
with their real last conversations (all topic renewal):
| Tenant (legacy ID) | Snapshot claim | Actual last conversation |
| …4620052 | 30+ days silent | Aug 2, voice + SMS |
| …4620005 | 30+ days silent | Aug 2, voice + SMS |
| …4620009 | 30+ days silent | Aug 1, SMS |
| …4620051 | 30+ days silent | Aug 1, SMS |
Conversations DO carry the legacy tenantId — the data
to join correctly exists; the signal just doesn't use it. (Checked the 100 most
recent Camellia conversations; the remaining five flagged tenants had no
conversation in that window, so their silence flag may be genuine.)
The same broken join is why the findings read "tenant 1773625953462…" instead
of a name — the name lookup goes through the same missing spine ID.
In plain terms
The nightly report decides "we haven't talked to this tenant in a month" by
looking them up in the conversation log under a new ID system. Camellia's
tenants are still filed under the old ID system, so the lookup finds nothing
and everyone looks ignored — even the tenants Clara called and texted about
their renewal yesterday.
Root cause 2 — renewals structurally flood the top-5
Even with a correct join, the ranking favors renewals: the renewal-risk
signal emits one finding per lease at the highest
priority of any signal (90), while tour conversion, vacancy,
lead-funnel etc. each emit one aggregated finding at priority 45–80. Ten
per-lease renewal rows vs one row per other problem means "top 5" will almost
always read as a renewals list. The second factor feeding it — an outstanding
balance — fires at a floor of just $50.
In plain terms
Renewals get one line on the list for every tenant; everything else gets one
line total. Ask five things off the top of that list and you'll mostly hear
about renewals, whatever is actually going on at the property.
What was right in the answer
The #1 finding — 8 of 8 tours in 14 days produced zero
applications — checks out against the live tour data and deserves the
attention. And some balances are real (one tenant owes $4,135); the delinquency
half of the renewal story is worth a look even though the "no outreach" half
is wrong for at least four tenants.
Proposed fix
- Join fallback (bug fix): in the renewal-risk signal, resolve
last-contact by spine
personId or legacy
tenantId — conversations already carry both. Same fallback for the
tenant-name lookup.
- Ranking (design tweak): aggregate per-lease renewal findings
into one cluster finding per urgency band (28d / 58d / 89d) so the top-5 stays
mixed, and/or raise the $50 balance floor.
Item 1 is a contained code fix; item 2 changes what the ranked
list means and is worth a quick founder call before shipping.
Related: today's Ask Clara voice demo failure was a
separate bug — the site-wide Permissions-Policy header shipped
microphone=(), blocking the mic on every page with no browser
prompt. Fixed in PR #5352 (microphone=(self)).