Skip to main content

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.html mock. 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_contacts relationship memory + consent rollup, re_interactions timeline, re_consent_events ledger, identity/merge §3.2), backend-completeness-audit.md (P0 #2 identity/dedup, P0 #3 consent), and realtor-mockups/record.html. Format matched to acceptance/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 colNext 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):

RegionUnderlying read
Identity + relationship memoryre_contacts (names, primary_email/phone, preferred_language, birthday, home_anniversary, family, mortgage, referral_source_*, tags, interests_notes)
Consent linere_contacts consent rollup (*_consent_status, do_not_contact) backed by re_consent_events
Aria's summaryre_contacts.ai_summary
Activity timelinere_interactions WHERE contact_id = … ORDER BY occurred_at DESC (+ transcript/recording links)
Next best actions / Tasksre_tasks (open, due) + AI-suggested actions
Linked listingslocal_business_listings referenced in interactions/saved searches
Deal cardre_deals (stage, expected value; commission gated by role)
Merge affordancere_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):

BucketRoles (rbac.ts)Record visibility
Brokerage / team managementbrokerage_owner, broker_admin (brokerage), team_admin (team)Full record incl. the Deal card's commission/GCIbrokerage_owner edits, all three read at their scope
Agent — owns the dealagent who owns the contact's dealFull record incl. their OWN-deal commission (commissions:amount:read:own) — read, not edit
Agent — not the owneranother agent / isaPer 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 / externalisa, transaction_coordinator, marketing_assistant, external_partnerScoped/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_admin read at brokerage/team scope and brokerage_owner edits; the owning agent reads their OWN deal's commission (commissions:amount:read:own); isa/transaction_coordinator/marketing_assistant/ external_partner have no commission capability (row absent). A peer's commission MUST NEVER show to an agent (no read:own match) — 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 like tier-config.contract.test.ts. NOTE: commission/GCI is deferred in the MVP (A2) — this capability gating applies if/when it ships (consistent with realtor-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 note interaction (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 agent who doesn't own that deal (§3 — only read:own for 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.


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_events ledger (a "view consent history" affordance is acceptable).
  • When do_not_contact = true or 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 sets merged_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

StateExpected output
New contact, thin dataRecord 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 yetThe summary card shows "Aria will summarize once there's activity" — not an empty teal box.
LoadingSkeletons for summary/timeline/cards, not a spinner (anti-pattern #7).
Timeline/record read errorInline "Couldn't load activity — retry"; identity/memory still render.
Suppressed contactProminent 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). An agent must never see a colleague's commission; an agent MAY see their own deal's commission (commissions:amount:read:own); only brokerage_owner edits.
  • Consent rollup is a cache — the re_consent_events ledger 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-usere_* are proposals; verify columns via information_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.