WiseAI Realtor — Contact Record Expected Output Spec
⛔ STATUS: DRAFT — NOT APPROVED. CLAUDE.md Rule #17 (HARD GATE) is NOT satisfied.
Stage-1 agent research, pre-populated from the realtor design docs + the
record.htmlmock. Do not build the customer-facing screen until the founder approves and confirms the open items in a Stage-2 interview.
Sourced from (read-only, 2026-06-29):
design-direction.md(Lead/Contact detail = "Two-tier: drawer (triage) + full record page; AI summary on top → unified timeline → right-rail next-best-action; NO tab-sprawl"; AI summary panel component),data-model.md(re_contactsrelationship memory + consent rollup,re_interactionstimeline,re_consent_eventsledger, identity/merge §3.2),backend-completeness-audit.md(P0 #2 identity/dedup, P0 #3 consent), andrealtor-mockups/record.html. Format matched toacceptance/ai-front-desk.md.
0. Scope — what this spec covers, and what it does NOT
This spec covers the full contact record page — the deep tier of the
two-tier model. The triage tier (the right drawer that opens from the Leads
list) is specified in realtor-leads-mvp.md; both render the same contact, at
different depths. "Open record" from the drawer/list lands here.
Does NOT cover: the Leads list/drawer (realtor-leads-mvp.md); the
conversation thread itself (realtor-inbox-mvp.md — the record links to
transcripts but the live thread + take-over lives in the inbox); the Active
Business Hub / Deals board (a sibling deals spec — this record shows the linked
deal card read-only-ish, not the deals workspace); lead capture behaviour
(ai-front-desk.md).
Build posture: the contact record is NEW — it is the CRM-spine read view
that closes the audit's "unified contact timeline = PARTIAL" and "no long-lived
contact" gaps. It reuses existing transcript/recording assets and the
local_business_leads capture buffer via the Phase-A forward-fill
(data-model.md §2.2). Until re_* ships, the record falls back to the latest
local_business_leads row for that person (no merged history).
1. The mock screen this spec governs + what it reads
Governs: realtor-mockups/record.html — a single full page (explicitly
"two-tier: this is the full page", "no tab-sprawl") in a 3-column layout:
- Left col — identity card (avatar, name, "First-time buyer", score + language chips, phone/email/location) + a Relationship memory card (🎂 birthday, 🏡 move-in anniversary, 👨👩👧 family, 💰 mortgage, 🔗 referral source, interest tags) + a consent line ("Consent: CASL ✓ · PIPEDA ✓ · calls in Punjabi").
- Center col — "Aria's summary" AI panel on top (teal accent, "updated Nh ago", recap + a ⚡ next-best-action button), then a unified Activity timeline (showing booked, AI voice call with an inline EN/ਪੰ transcript excerpt + "Full transcript →", QR scan, first-seen-website) with an All / Calls / Messages / Notes filter and an Add a note… composer.
- Right col — Next best actions checklist, a Tasks card (due chips), a Linked listings card, and a Deal card (Stage / Est. value / Est. commission).
Reads (target-state CRM-spine APIs — WS1a; verify exact route names; do not fabricate):
| Region | Underlying read |
|---|---|
| Identity + relationship memory | re_contacts (names, primary_email/phone, preferred_language, birthday, home_anniversary, family, mortgage, referral_source_*, tags, interests_notes) |
| Consent line | re_contacts consent rollup (*_consent_status, do_not_contact) backed by re_consent_events |
| Aria's summary | re_contacts.ai_summary |
| Activity timeline | re_interactions WHERE contact_id = … ORDER BY occurred_at DESC (+ transcript/recording links) |
| Next best actions / Tasks | re_tasks (open, due) + AI-suggested actions |
| Linked listings | local_business_listings referenced in interactions/saved searches |
| Deal card | re_deals (stage, expected value; commission gated by role) |
| Merge affordance | re_contacts.merged_into_contact_id + identity resolver (data-model.md §3.2) |
Demo tenant + record: Terry & Sheri Real Estate (…0c01); the mock's
"Harjit Singh" is demo content. NEVER a real customer
(feedback_never_modify_customer_data).
2. The AI-Bridge / honesty anchor
- Aria's summary is a recap, not advice. The AI panel summarizes the relationship and proposes a next action for the agent ("Confirm Sunday 2pm & send 2 comparable listings"). It must NOT state a home value as fact, give legal/mortgage/tax advice, or use steering language about a neighbourhood/group (audit P0 #5 RE guardrails).
- AI work is attributed honestly in the timeline ("by Aria (AI voice, Punjabi)") — never dressed up as a human touch.
- Consent is always visible and load-bearing. The record surfaces consent + do-not-contact status prominently; every send/call affordance on the record respects it.
3. Role-based visibility (the commission gate)
Per data-model.md §3.1 (agent ownership) and §5.2 (commission leakage is the
highest-sensitivity risk):
| Bucket | Roles (rbac.ts) | Record visibility |
|---|---|---|
| Brokerage / team management | brokerage_owner, broker_admin (brokerage), team_admin (team) | Full record incl. the Deal card's commission/GCI — brokerage_owner edits, all three read at their scope |
| Agent — owns the deal | agent who owns the contact's deal | Full record incl. their OWN-deal commission (commissions:amount:read:own) — read, not edit |
| Agent — not the owner | another agent / isa | Per routing: typically no access to a peer's owned contact; if shared, a peer's commission is NOT shown (no read:own match) |
| ISA / support / external | isa, transaction_coordinator, marketing_assistant, external_partner | Scoped/read-only; NO commission capability — commission row absent |
The mock shows "Est. commission $15,997" on the Deal card — visible to a viewer with the commission capability for that deal's scope (
rbac.ts):brokerage_owner/broker_admin/team_adminread at brokerage/team scope andbrokerage_owneredits; the owningagentreads their OWN deal's commission (commissions:amount:read:own);isa/transaction_coordinator/marketing_assistant/external_partnerhave no commission capability (row absent). A peer's commission MUST NEVER show to an agent (noread:ownmatch) — enforced at the API + query layer (a single un-gated select leaks a colleague's income) and every commission read is logged (data-model.md§5.2; the Team & Permissions mock notes "every commission read logged"). Add a contract test liketier-config.contract.test.ts. NOTE: commission/GCI is deferred in the MVP (A2) — this capability gating applies if/when it ships (consistent withrealtor-deals-mvp.md§3).
4. Expected outputs — per region
4.1 — "Aria's summary" AI panel — ON TOP (no tab-sprawl)
Should see:
- The AI summary card at the top of the record's center column (teal left-accent, "Aria's summary" label, "updated Nh ago"), a 2–4 sentence relationship recap, and a ⚡ next-best-action button (e.g. "Confirm Sunday 2pm & send 2 comparable listings"). Collapsible.
- The whole record is one page — identity/memory, summary, timeline, tasks, deal — with no tab strip splitting the record (anti-pattern #1; the mock's own footer states "no tab-sprawl"). The only tabs allowed are the timeline filter (All / Calls / Messages / Notes), which filters one timeline, not the record.
Should NOT see:
- The summary buried below the fold or behind a tab.
- A long-scroll, tab-sprawled IXACT-style record (anti-pattern #1).
- An AI summary that asserts a value/legal/mortgage position as fact.
Success: opening the record gives a 10-second orientation from the top summary + a clear single next action, on one page.
4.2 — Unified activity timeline (from re_interactions)
Should see:
- One reverse-chronological timeline spanning every channel (QR scan → AI voice call → showing booked → website-first-seen → notes), each row with an icon/color, title, relative time, actor attribution, and inline detail where useful — including a transcript excerpt for an AI call (the mock shows an ਪੰ→EN excerpt) with a "Full transcript →" link, and call duration.
- A timeline filter (All / Calls / Messages / Notes) and an Add a note…
composer that appends a
noteinteraction (actor=human). - The timeline is the source for speed-to-lead (first outbound − first inbound) surfaced where relevant.
Should NOT see:
- Separate disconnected tabs for calls vs chats vs forms (the whole point is a unified timeline — audit "unified contact timeline").
- A transcript shown as ground truth without the recording link / actor.
Reads: re_interactions WHERE contact_id = …; transcripts/recordings via
recording_url/transcript.
Success: the agent sees the entire relationship history — across QR, voice, chat, web, notes — in one stream, newest first, with transcripts one click away.
4.3 — Relationship memory (the IXACT/FUB table-stakes gap)
Should see:
- A Relationship memory card: 🎂 birthday, 🏡 move-in/home anniversary, 👨👩👧 family (spouse/kids), 💰 mortgage (lender, broker, renewal — a future reach-out trigger), 🔗 referral source (links to the referring contact), and freeform interest tags (first-time, 3-bed, Woodstock N, good schools).
- These feed keep-in-touch triggers (the dashboard Follow-Up Coach reads them).
Should NOT see:
- Relationship-memory fields that don't persist or aren't editable.
Reads: re_contacts (birthday, home_anniversary, family jsonb, mortgage
jsonb, referral_source_contact_id/text, tags, interests_notes).
Success: the agent can record and recall the human details that make keep-in-touch real — and a referral source links to the actual referring contact.
4.4 — Next-best-action right rail + Tasks + linked listings + deal
Should see:
- A Next best actions checklist (AI-suggested + agent-checkable: confirm showing, send comparables, add to a "first-time buyer" drip).
- A Tasks card (open tasks with due chips — "Today", "Sun") from
re_tasks. - A Linked listings card (properties tied to the contact via interactions/ saved searches).
- A Deal card (Stage, Est. value, Est. commission — role-gated §3) linking to the deal in the Active Business Hub.
Should NOT see:
- A peer's commission on the deal card for an
agentwho doesn't own that deal (§3 — onlyread:ownfor the owning agent; API-enforced). - A "next best action" that would contact a suppressed contact.
Success: the right rail tells the agent exactly what to do next and ties the contact to its tasks, listings, and transaction.
4.5 — Consent + do-not-contact visibility
Should see:
- A clear consent line/badge on the record: per-channel status (CASL email/
SMS/voice — implied/express/withdrawn/expired), PIPEDA relationship, language
preference for calls (e.g. "CASL ✓ · PIPEDA ✓ · calls in Punjabi"), backed by
the
re_consent_eventsledger (a "view consent history" affordance is acceptable). - When
do_not_contact = trueor a channel's consent is withdrawn/expired, the record shows a prominent suppression state and the corresponding Call/Text/Email actions are disabled with the reason.
Should NOT see:
- A send/call action enabled against a suppressed channel.
- Consent shown as a static checkbox with no provenance (it must reflect the
ledger; the rollup is a cache, never the source of truth —
data-model.md§5.2).
Success: the agent can see, at a glance, exactly which channels they may use for this person and why — and the UI refuses the ones they may not.
4.6 — Merge / dedup affordance (audit P0 #2)
Should see:
- When the system detects a likely duplicate (same normalized phone/email — the
resolver,
data-model.md§3.2), the record surfaces a "possible duplicate — review/merge" affordance. Merge is human-confirmed for fuzzy matches, reversible (the losing row setsmerged_into_contact_id, never hard-deleted), and unions emails/phones/relationship-memory + repoints interactions/deals/ tasks/consent to the survivor. - After merge, the timeline shows the combined history (the "one person who scanned a QR + called + submitted a form" becomes one record).
Should NOT see:
- An auto-merge of two people who share a household phone (over-eager match) — a
hard unique constraint is explicitly not used; dedup is a service, not a
constraint (
data-model.md§3.2/§5.2). - A merge that hard-deletes a row or loses consent provenance.
Success: three disconnected captures of the same person can be merged into one record with a unified timeline, reversibly, with consent preserved.
5. Empty / loading / error states
| State | Expected output |
|---|---|
| New contact, thin data | Record renders with what exists; empty cards show a helpful prompt ("No relationship details yet — add a birthday/family note") not a blank box. |
| No AI summary yet | The summary card shows "Aria will summarize once there's activity" — not an empty teal box. |
| Loading | Skeletons for summary/timeline/cards, not a spinner (anti-pattern #7). |
| Timeline/record read error | Inline "Couldn't load activity — retry"; identity/memory still render. |
| Suppressed contact | Prominent suppression banner; send/call disabled (§4.5). |
| Commission row absent (RBAC) | The Est. commission row is absent (not a teaser/locked control that hints at the value) for a viewer without the commission capability for that deal's scope — a peer agent, isa, transaction_coordinator, marketing_assistant, or external_partner. The owning agent sees their own (read:own). |
6. Accessibility (AODA → WCAG 2.1 AA)
- Heading structure: the record is one page with a clear heading hierarchy
(identity → summary → activity → rail); the timeline is a list/
<ol>with meaningful row text; the timeline filter is a tablist controlling one region (the only tabs allowed). - Contrast: ≥4.5:1 text; gold (e.g. the Deal card tint) is accent only with AA-contrast text over it; teal summary accent + links meet AA.
- Keyboard: add-note composer, next-best-action checkboxes, merge dialog, "Full transcript", Call/Text/Email all keyboard-operable with visible focus; any merge/transcript dialog traps + restores focus.
- Consent/suppression conveyed by text + icon, not color alone; disabled actions expose an accessible reason (not just greyed out).
- Motion: summary/skeleton animation respects
prefers-reduced-motion.
7. Acceptance checklist (QA runs on the deployed URL)
Behavioural verification on wiseaiagency.com (real host) against the demo
tenant (…0c01); never "build passes". Sample at ≥2 timepoints.
// Layout / no tab-sprawl
test.fixme('record is ONE page: Aria summary on top → unified timeline → right rail; no record-level tab strip', () => {});
test.fixme('Aria summary recaps the relationship and proposes an agent next action (no value/legal advice asserted)', () => {});
// Unified timeline
test.fixme('timeline spans QR/voice/chat/web/notes in one reverse-chron stream; AI rows attributed to Aria', () => {});
test.fixme('an AI call row links to the full transcript + recording; Add-a-note appends a note interaction', () => {});
// Relationship memory
test.fixme('relationship memory (birthday/family/mortgage/referral/tags) persists and is editable; referral links the referrer', () => {});
// Consent + suppression
test.fixme('consent line reflects the re_consent_events ledger; do_not_contact disables Call/Text/Email with a reason', () => {});
// Merge / dedup
test.fixme('a likely duplicate surfaces a review/merge affordance; merge is human-confirmed, reversible, unions history', () => {});
test.fixme('no auto-merge of a shared-household phone; merged row keeps merged_into_contact_id (never hard-deleted)', () => {});
// RBAC commission gate
test.fixme('a capability-holder sees Est. commission (brokerage_owner edits; broker_admin/team_admin/owning-agent read at scope); a viewer WITHOUT it (peer agent, isa, support, external) does NOT — row absent, API-enforced, read logged', () => {});
// States + a11y
test.fixme('empty/loading/error states render per §5; never blank cards, never a full-page spinner', () => {});
test.fixme('record is keyboard-navigable; timeline is a semantic list; dialogs trap+restore focus; reduced-motion honored', () => {});
8. Guardrails for agents building against this spec
- Rule #17 not satisfied — do not build until founder approval.
- One record, AI summary on top, unified timeline — NO tab-sprawl (anti-pattern #1) is the defining constraint; do not "fix" it into tabs.
- Commission is the highest-sensitivity field — gate by capability
(
rbac.ts) at the API/query layer, log every read, contract-test it (data-model.md§5.2). Anagentmust never see a colleague's commission; anagentMAY see their own deal's commission (commissions:amount:read:own); onlybrokerage_owneredits. - Consent rollup is a cache — the
re_consent_eventsledger is the source of truth; never write the rollup directly from feature code (data-model.md§5.2). - Merge is reversible + human-confirmed for fuzzy matches; never hard-delete.
- Migrate-before-use —
re_*are proposals; verify columns viainformation_schema(Rule #18); founder-gated DDL. - Verify on the real host; behaviour not DOM presence; evidence-or-nothing.
- If code diverges, update the spec first (founder approval), then the code.
End of spec. STATUS: DRAFT — NOT APPROVED. Open items for Stage-2: (1) whether
RESOLVED by rbac.ts — an agent reads their OWN deal commission
(commissions:amount:read:own), only brokerage_owner edits (formerly open);
(2) the merge-confirmation UX (auto-suggest threshold vs always-manual);
(3) how much of re_consent_events history to surface on the record vs a
drill-in.