Skip to main content

Knowledge > Products > WiseAI Realtor > Do Not Reinvent

WiseAI Realtor — Do Not Reinvent

Read this before touching real-estate code. A full real-estate Pro Website already shipped to churchwiseai-web main (PR #1017 + ~16 follow-on PRs, merged 2026-06-25/26). The job is to extend the vertical, not rebuild it. Every path below was verified present on 2026-06-27.

Module → existing code

ModuleExisting code (relative to churchwiseai-web/)Status
Pro Website templatesrc/components/templates/RealEstateSiteTemplate.tsx, RealEstateTemplate.tsx, RealEstateVoiceCall.tsx; rendered via src/app/s/[slug]/page.tsxReuse as-is
Vertical registrysrc/lib/verticals/registry.ts (+ church.ts, funeral.ts, vet.ts). NOTE: no real_estate.ts profile yet — real estate currently renders through the template/site-config path, not a full VerticalProfile.Extend if a full profile is needed
Real-estate domain libsrc/lib/real-estate/: site-config.ts (per-agent registry; Team Beckett = first instance), site-data.ts, types.ts (canonical REListing shape), guides.ts (14 guides), jsonld.ts, fair-housing.ts (steering-phrase linter), designations.ts (ABR/SRES/SRS/MVA), chat-knowledge.ts, listing-kit.tsReuse as-is
Server-only RE modulessrc/lib/real-estate/server/: site-content.ts (merges DB overrides over code defaults), listings.ts, listing-kit.tsReuse as-is
DDF / RESO (REALTOR.ca)src/lib/real-estate/ddf/: client.ts (RESO Web API, OAuth2 client-credentials; cleanEnv() strips BOM/CRLF from DDF_* — REQUIRED, the Vercel secrets carry it), map.ts (RESO Property → REListing), sync.ts (manual-safe diff: insert/update/delete ONLY source='ddf' rows; never touches manual), reso-types.ts, fixtures.ts; triggers POST /api/real-estate/ddf-sync (owner button) + /api/cron/ddf-sync (every 30 min since 2026-08-20; the per-row change comparison in row-changed.ts is what made that affordable — a quiet run writes nothing)LIVE (PR #1064, 2026-06-27). Do NOT rebuild. Gated on DDF_ENABLED + per-tenant metadata.ddf.authorized. Beckett c01 = 11 listings synced from ListOfficeKey 296475 — CORRECTION (2026-07-01, verified live): this is the WHOLE Sutton Group Preferred Realty office's active inventory (11 diverse listings spanning $199,900–$3,800,000, incl. the office's own building), NOT just Beckett's personal listings — the office-scope filter is already "the whole area" access; there is no code lever to go wider (a different office/franchise would need its own DLA). The feed does NOT expose per-listing agent identity (ListAgentFullName returns null on every synced row, confirmed live) — a listing_agent_id column + capture path exists (migrations/20260630_re_listing_agent_name.sql added the discarded-field capture; PR #1095 added the actual manual-tag UI on the Listings screen, since the feed data itself can't drive it) for the team to manually mark whose listing is whose. Manual scroll additions coexist + survive sync. Caveat: $expand=Media is invalid on the live feed (Media is inline).
Public site sub-pagessrc/app/s/[slug]/: about, blog, featured, guides, listing (detail + maps), partners, reviews, scan (QR yard-sign → lead), tools (mortgage/LTT/CMHC/property-tax/rent-vs-buy/house-hack), privacy, terms, [page]Reuse as-is
RE API routessrc/app/api/real-estate/: home-value, listings (+ listings/[id]), listing-kit, places-nearby (Google Places, field-masked + cached), site-content (draft/publish editor API), contact, newsletterReuse as-is
Leads store + dashboardRE web/form/voice/chat leads write to local_business_leads (source = voice|chatbot|form) via createLead (src/lib/local-business/server/mutations.ts); read in the local-business dashboard (/business/[token]). ⚠️ CORRECTION (QA 2026-06-27, verified in code): RE does NOT use the church voice_prayer_requests/callback/visitor_contacts tables (those are church-only); lead_events/campaign_members belong to the separate outbound prospecting funnel, not the inbound RE lead store.Reuse local_business_leads, but it is per-interaction (no long-lived contact yet) — see data-model.md
AI chat agentsrc/app/api/chatbot/stream/route.ts (production; Haiku 4.5, SSE). Multilingual already handled: respond-in-caller's-language at route.ts:396 and :1556. Per-tenant product_knowledge injected at runtime.Reuse; fork tools/prompt for RE
AI voice agentvoice-agent-livekit/session.py (resolve_route, load_product_knowledge); vertical code already exists at voice-agent-livekit/verticals/real_estate/Reuse; extend RE prompts/tools
Content editor (WYSIWYG)src/app/admin/[token]/components/WebsiteEditor/ (draft/publish, target=website_draft, draft_* columns). Editor API: src/app/api/real-estate/site-content/route.tsReuse as-is for hero/team/specialties/testimonials/communities/partners/faqs/brand. Blog is NO LONGER edited here (2026-07-01) — see Content Studio below. The old admin RealEstateWebsiteEditor.tsx Blog tab is retired to a redirect notice; do not resurrect BlogForm/RichTextEditor/RTButton (deleted, they wrote to metadata.website.blog[], which the public site stopped reading).
Content Studiosrc/app/realtor/app/studio/ + src/components/realtor/studio/ContentStudioBody.tsx + src/lib/real-estate/server/content-items.ts (CRUD over re_content_items, 7 confirmed types: blog/newsletter/social_post/buyer_guide/seller_guide/neighbourhood_page/market_update) + src/lib/real-estate/server/content-generator.ts (AI generate/rewrite, mirrors listing-kit.ts's Anthropic-direct pattern) + routes under /api/real-estate/content-items/*. LIVE (2026-07-01). Blog storage was PROMOTED from local_businesses.metadata.website.blog[] into re_content_items (the table already existed from the 2026-06-27 phase-6 migration; only a data backfill was needed — migrations/20260701_re_content_items_blog_backfill.sql). /s/[slug]/blog now reads re_content_items via getContentItemsForPublicSite() in site-content.ts. Translate (re_content_translations) and any newsletter SEND pipeline are explicitly NOT wired — schema-only / editor-only, per content-studio-mvp.md §0 guardrails. Do not rebuild; extend.Reuse as-is
Integrations hubsrc/app/realtor/app/integrations/page.tsx + src/lib/real-estate/server/ddf-status.ts (getDdfConnectionStatus() — the ONE DDF-status read, also used by settings/feeds/page.tsx). LIVE (2026-07-01), deliberately thin — an honest status hub, NOT a CRM/Zapier/Calendar connector platform. No OAuth, no API keys, no webhooks exist. Before adding a real connector here, confirm the founder wants that scope — this screen was built specifically to avoid a repeat of the fake-Connect-button bug class (PR #1091).Reuse as-is; extend only with a real, working connection
Listing Studiosrc/components/admin/local-business/ListingStudio.tsx (AI MLS copy ×3 lengths + fair-housing scrub + social/email)Reuse as-is
Acceptance specs (already approved)knowledge/acceptance/: real-estate-blog-mvp.md, real-estate-content-editor-mvp.md, reviews-engine-mvp.md, cwa-pro-website.md, pro-website-multipage.md, ai-front-desk.md, realtor-content-studio-mvp.md (2026-07-01), realtor-integrations-mvp.md (2026-07-01)Read before building; specs exist

Listings data flow & REALTOR.ca (get the direction right)

REALTOR.ca is fed ONLY by the local MLS® boards — never by a website or our platform. DDF is outbound (boards → DDF → us). There is NO path to push a listing TO REALTOR.ca from our system. Consequences for the build:

  • Featured / "Scroll" Listings (agent's own listings in a homepage carousel, before the general search) is standard across the field — build it as a featured + display-order flag; the carousel renders featured first.
  • An agent's own MLS listing is entered on their board's MLS System → appears on REALTOR.ca → flows back to our site via DDF; we just pin/feature it. We do not create it.
  • An agent-created (manual) listing in our Listings manager is for pre-market / coming-soon / exclusive properties — it lives on the agent's own website ONLY, never REALTOR.ca. To reach REALTOR.ca the agent must list it on their board.
  • Compliance: manual listings carry no MLS® number; "coming soon" must follow board exclusive-listing rules; MLS®/REALTOR.ca trademarks only on real MLS listings. The unified REListing shape already distinguishes source (Manual vs DDF) — see src/lib/real-estate/{types.ts, ddf/*, server/listings.ts}.
  • Edit vs feature/order — confirmed against the Becketts' SellingToolz backend (2026-06-27): MLS/feed listings are read-only (you can't edit the board's authoritative record); manual listings are fully editable. SellingToolz also locks dragging of feed listings ("Feed listings are unable to be edited or dragged") — which pushes agents to manually re-create their own listings to control the carousel = the duplication trap. We beat this: keep a per-tenant display overlay (featured flag + sort_order, keyed by listing id/MLS#) so agents can feature AND drag/reorder feed listings too, without editing MLS data and without duplicating. Net rule for our Listings/Featured manager: edit = manual only; feature + reorder = all listings (feed + manual). (Note: count varies by status — Becketts showed 11 in their board account vs 9 in the SellingToolz scroll; reconcile active-vs-total when we map.)
  • Pro Website system: knowledge/products/pro-website/overview.md, template-system.md, sections.md, vanity-urls.md, admin-features.md, chatbot-integration.md
  • WiseAI Agency positioning: knowledge/products/wiseaiagency/overview.md
  • Voice agent: knowledge/products/voice-agent/overview.md
  • New vertical playbook: knowledge/runbooks/business-ops/new-vertical-launch.md

Genuinely greenfield (safe to build)

  • Multilingual content generation — blogs/guides/neighbourhood pages authored per-language (chat/voice already respond multilingually, but per-language content generation is not built). Phase 2 voice (Punjabi/Urdu) is design-drafted only; no script ships without native review.
  • US IDX adapter — only CREA DDF (Canada) exists. US MLS would need a separate adapter shaped like ddf/client.ts.
  • Brokerage / team RBAC hierarchy — current RBAC is token + role; brokerage-scoped multi-agent teams are not modelled.
  • First-owner bootstrap for a brand-new realtor account — no self-serve path exists. A zero-member account cannot invite itself (the invite flow at src/app/realtor/accept-invite needs an EXISTING owner to mint the invite). Confirmed live 2026-07-01: Team Beckett's real account (00000000-0000-4000-a000-000000000c01) had ZERO realtor_memberships rows despite the backend having shipped since 06-29 — only the TeamMoelker + TeamTest fixture accounts were ever seeded. Today's workaround: attach an already-existing auth.users identity (any founder/admin email that's already signed up somewhere in the system) as brokerage_owner/is_primary_owner via a direct SQL insert into realtor_users + realtor_memberships. There is also no account-switcher UIRailAccount in RealtorRail.tsx is a single object, not a list, so one person cannot cleanly hold memberships on two different businesses today (which one shows is whatever active.find(is_primary_owner) ?? active[0] happens to return first).

Active worktrees / branches (don't collide)

  • C:\dev\cwa-beckett (primary), C:\dev\cwa-ws6 (Punjabi/Urdu voice test), cwa-voice-picker. Treat as possibly-active — confirm before reusing.
  • Always branch off the repo's deploy branch; never reuse another agent's branch or worktree (CLAUDE.md Rule #10).

Help & training system (added 2026-07-02)

Feature 2 of the realtor help system extended the Aria concierge from setup-only into a day-to-day OPERATIONAL product-help assistant + added a lightweight in-app help layer. Do NOT rebuild these:

ModuleExisting code (relative to churchwiseai-web/)Status
Operational Ariasrc/lib/real-estate/onboarding/assistant/prompt.ts (resolveScopeMode / isOperationalRoute / buildOperationsCorpusBlock; setup mode is byte-intact, operational mode widens ONLY the scope rule) + context-server.ts (loadRealtorOperationsKnowledge). The RE AI Bridge frame + guardrail block + honest-metrics/guide-don't-touch/out-of-lane contract are UNCHANGED.Extend the corpus, not the scaffolding
Operations corpusproduct_knowledge category='realtor_operations' (migrations 20260702_product_knowledge_allow_realtor_operations.sql + 20260702_realtor_operations_product_knowledge.sql). Loaded ONLY by /api/realtor/onboarding/assistant; LOW priority so it never leaks into the church chatbot. Update via the content-ops runbook + validate_product_knowledge().Add rows here for new features
See-point on working screensscreen-registry.ts — header targets for inbox/leads/pipeline/contacts/analytics/studio (listings/reviews/qr already present); each id ↔ a data-aria-target on the real header (anti-rot lint).Register new working screens here
In-app help layersrc/components/realtor/shared/ScreenHelpHint.tsx (one-line per-screen hint + Ask Aria) on the working-screen headers, and the top-bar Help "?" in shell/RealtorTopBar.tsx. Both open the single Aria panel via aria-panel-store.Reuse ScreenHelpHint; don't add a second panel
Operator guideknowledge/products/wiseai-realtor/operator-guide.md — plain-language per-screen how-to for training/demos.Keep in sync when a screen changes

Spec: knowledge/acceptance/realtor-help-system-mvp.md.