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-webmain(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
| Module | Existing code (relative to churchwiseai-web/) | Status |
|---|---|---|
| Pro Website template | src/components/templates/RealEstateSiteTemplate.tsx, RealEstateTemplate.tsx, RealEstateVoiceCall.tsx; rendered via src/app/s/[slug]/page.tsx | Reuse as-is |
| Vertical registry | src/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 lib | src/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.ts | Reuse as-is |
| Server-only RE modules | src/lib/real-estate/server/: site-content.ts (merges DB overrides over code defaults), listings.ts, listing-kit.ts | Reuse 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-pages | src/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 routes | src/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, newsletter | Reuse as-is |
| Leads store + dashboard | RE 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 agent | src/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 agent | voice-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.ts | Reuse 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 Studio | src/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 hub | src/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 Studio | src/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
REListingshape already distinguishes source (Manual vs DDF) — seesrc/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 (
featuredflag +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.)
Shared-platform docs (link, do not duplicate)
- 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-inviteneeds an EXISTING owner to mint the invite). Confirmed live 2026-07-01: Team Beckett's real account (00000000-0000-4000-a000-000000000c01) had ZEROrealtor_membershipsrows despite the backend having shipped since 06-29 — only the TeamMoelker + TeamTest fixture accounts were ever seeded. Today's workaround: attach an already-existingauth.usersidentity (any founder/admin email that's already signed up somewhere in the system) asbrokerage_owner/is_primary_ownervia a direct SQL insert intorealtor_users+realtor_memberships. There is also no account-switcher UI —RailAccountinRealtorRail.tsxis a single object, not a list, so one person cannot cleanly hold memberships on two different businesses today (which one shows is whateveractive.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:
| Module | Existing code (relative to churchwiseai-web/) | Status |
|---|---|---|
| Operational Aria | src/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 corpus | product_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 screens | screen-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 layer | src/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 guide | knowledge/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.