System Architecture & Documentation
Reference guide for the Lead/Deal analysis experience and enrichment pipeline.
Table of Contents
1) PropertyInsightsCard
File: components/leads/PropertyInsightsCard.jsx
What it displays
- Always renders grouped sections from top-level columns in
enhanced_deal_analysis: - OWNERSHIP: owner_name, ownership_months (converted to years), is_absentee, is_distressed
- TAX & LIENS: tax_assessed_value, tax_delinquent, lien_count
- SALE HISTORY: last_sale_date, last_sale_price, previous_listing_price, previous_listing_dom
- VALUATION: estimated_market_value, price_per_acre, lot_size_acres, asking_price
- If
propertyreach_raw(JSONB) exists, it expands with additional sections using exact camelCase fields from the raw object: - PROPERTY DETAILS: apn, landUse, bedrooms, bathrooms, rooms, stories, squareFeet, groundFloorSqft, lotAcres, lotSquareFeet, yearBuilt, pool, deck, hoa, hoaType, vacant, airConditioning, garage/garageUnits, appliances (array)
- LOCATION: fullAddress, city, state, zip, county, fips, latitude, longitude (shown as GPS), floodZone
- OWNER DETAILS: contacts[] → name, age, gender, education, occupation, language, phones, emails, mailingAddress
- FINANCIAL: assessedImprovement, marketImprovement, linkedEstimate, pricePerSqFt, estimatedEquity (shown as percent)
- SALE & LISTING: lastSaleDate, lastSalePrice, loanRecording, priorPurchaseMethod, listingDate/status/type/price
- LINKED PROPERTIES: linkedProperties count, linkedVacant count
- Formatting: currency values show as $ with commas, dates are human-readable, booleans display as Yes/No badges, GPS as decimal coordinates, arrays comma-separated.
Data source & fetching
- Reads from Supabase view/table
enhanced_deal_analysisviasupabaseProxyusing:viewName = enhanced_deal_analysis?lead_id=eq.${leadId}&order=analyzed_at.desc&limit=1(schema: public). - Primary fields come from individual columns; optional
propertyreach_rawJSONB is parsed when present for expanded view. - Hidden entirely if no record is returned for the lead.
Placement
- Rendered on Lead Detail immediately after Quick Actions.
2) EnhancedLeviAnalysis
File: components/leads/EnhancedLeviAnalysis.jsx
- Sections shown: Property & Valuation, Risk Assessment, Pricing Strategy, Scorecard, Due Diligence, Analyst Notes.
- Change: The previous “PROPERTYREACH ENRICHMENT DATA” accordion (and its empty-state message) was removed. All enrichment now lives in PropertyInsightsCard.
3) Lead Detail Page Layout Order
File: pages/LeadDetail.jsx
- Property Images
- Quick Actions
- Property Insights Card (this new card)
- Quick Links
- Deal Score (DealSnapshotStrip)
- Quick Stats
- LEVI Analysis
- Lead Information
4) Deal Detail Page Overview
File: pages/DealDetails.jsx (overview)
- Consolidated dashboard for a single deal with tabs for Acquisition, Disposition, Financials, Due Diligence, and AI-driven Intelligence.
- Shows property imagery/maps, enhanced analysis (LEVI), risk & valuation metrics, comparable sales, exit strategies, and collaborative artifacts (contacts, documents, timeline, notes).
- Pulls enriched data and KPIs from Supabase mirrors and Base44 entities, surfacing current stage and calculated KPIs.
5) n8n Workflow Integration
- The GPT Agent Land Deal Processor (n8n) writes the full PropertyReach API payload to
enhanced_deal_analysis.propertyreach_raw(JSONB) via Supabase. - Older analyses may have
propertyreach_raw = NULL; on new runs it is populated. When present, PropertyInsightsCard automatically expands to display detailed sections backed by raw fields.
6) Branding & Data Source Disclosure
- End-user UI intentionally hides third-party branding. The card header reads “Property Insights” with no source attributions.
- References to the underlying data provider are internal-only within documentation and code comments.
7) SMS Campaign System (Acquisition Control)
Page: pages/AcquisitionControl.jsx
Architecture Overview
The Campaign System is a 4-tab interface for managing bulk SMS outreach. It uses two new entities (SMSCampaign, LeadList) plus the existing Lead entity. Sending is handled client-side via direct n8n webhook calls.
Entities
- SMSCampaign (
entities/SMSCampaign.json): Stores campaign metadata (name, status, schedule, send counts). Status lifecycle:draft → scheduled → sending → completed/paused. RLS restricted to admin/staff/owner/programmer roles. - LeadList (
entities/LeadList.json): Represents an imported CSV file. Links to a campaign viacampaign_id. Thebatch_idfield matches thebase44_idon Lead records for filtering. Status lifecycle:uploaded → mapped → ready → sending → sent. - Lead (existing): CSV-imported leads are tagged with
source='csv_import'andbase44_id=<batch_id>. The batch_id is generated from the list label + timestamp.
Components
components/acquisition/UploadListsTab.jsx— Multi-file CSV upload with column auto-mapping, label editing, and batch import. Creates LeadList records on import.components/acquisition/CampaignsTab.jsx— Campaign CRUD, list assignment modal, schedule modal, Send Now with real-time progress, Pause/Resume.components/acquisition/ListsTab.jsx— List table with assign-to-campaign, direct send, and delete actions.components/acquisition/QuickSendTab.jsx— Legacy ad-hoc send using Supabase-backedcountEligibleLeadsandgetEligibleLeadsbackend functions.components/acquisition/SMSProgressBar.jsx— Reusable progress bar showing sent/success/failed counts.
SMS Delivery Flow
- Leads are fetched from Base44 Lead entity filtered by
base44_id = batch_idandstatus = 'new'. - For each lead, a randomized message is generated using the lead's city/county.
- Message is POSTed to
https://n8n.srv1251391.hstgr.cloud/webhook/send-sms-manualwith{phone, message, leadId}. - On success, the Lead record is updated:
status → 'contacted',last_contact_at → now(). - Campaign/List entity records are updated with final send counts and status.
Access Control
The page checks user.role against ['admin', 'staff', 'owner', 'programmer']. Unauthorized users see a "Access Denied" screen. Both entities have matching RLS rules.
Quick Send (Legacy)
The Quick Send tab uses two backend functions (functions/countEligibleLeads.js and functions/getEligibleLeads.js) that query the Supabase leads table directly, cross-referencing the conversations table to exclude already-contacted phones. This path is separate from the campaign system and exists for ad-hoc sends.