← back to Commercialrealestate

scripts/db/condo-schema.sql

90 lines

-- condo-schema.sql — the condo + warrantability layer for the CRE explorer (local Postgres "cre").
-- Pairs the existing broker/firm/listing graph with LA condo listings and an FHA/VA-approval-based
-- warrantability PROXY (HUD FHA-approved condo list = primary signal; heuristic text flags = secondary).
--
-- HONEST LABELING (hard rule): warrantable_status here is a PROXY derived from public FHA/VA approval
-- + listing-text heuristics. It is NOT a lender-verified Fannie/Freddie condo-questionnaire result.
-- "fha_approved" == financing-eligible on the FHA list, used as a warrantability proxy only.

-- The authoritative FHA-approved-condo reference table (loaded from data/fha-approved-condos.json).
-- This is the lookup the classifier matches a condo's project name / address / zip against.
CREATE TABLE IF NOT EXISTS fha_condo (
  condo_id          text,                 -- HUD condo id (e.g. S009020)
  submission        text,                 -- submission/phase (e.g. 001)
  project_name      text NOT NULL,
  address           text,
  city              text,
  zip               text,
  county            text,
  composition       text,                 -- "ALL 21 UNITS IN PROJECT" etc.
  fha_concentration text,                 -- % FHA-insured loans in the project
  approval_method   text,                 -- HRAP | DELRAP
  hud_status        text,                 -- Approved | Expired | Rejected | Withdrawn
  status_date       text,
  expiration_date   text,
  warrant_signal    text,                 -- fha_approved | fha_expired | fha_other
  fetched_at        timestamptz DEFAULT now(),
  PRIMARY KEY (condo_id, submission, project_name)
);
CREATE INDEX IF NOT EXISTS idx_fha_zip       ON fha_condo(zip);
CREATE INDEX IF NOT EXISTS idx_fha_city      ON fha_condo(lower(city));
CREATE INDEX IF NOT EXISTS idx_fha_project   ON fha_condo(lower(project_name));
CREATE INDEX IF NOT EXISTS idx_fha_signal    ON fha_condo(warrant_signal);

-- Warrantability status enum (PROXY values — see header note).
DO $$ BEGIN
  CREATE TYPE warrant_status AS ENUM ('fha_approved', 'fha_expired', 'not_listed', 'heuristic_flag');
EXCEPTION WHEN duplicate_object THEN NULL; END $$;

-- The condo listing table — actual LA condos-for-sale, linked to the broker graph + classified.
CREATE TABLE IF NOT EXISTS condo (
  id                 text PRIMARY KEY,     -- listing id (crexi/zillow/redfin id, or a synthetic hash)
  address            text,
  city               text,
  zip                text,
  price              bigint,
  hoa                integer,             -- monthly HOA dues, USD
  beds               numeric,
  baths              numeric,
  sqft               integer,
  year_built         integer,
  project_name       text,                -- the HOA / condo-project name when known
  listing_text       text,                -- description blob the heuristic flags are mined from
  warrantable_status warrant_status,      -- result of classify-warrantability.js (PROXY)
  warrant_source     text,                -- "HUD FHA list (S009020 001, exp 08/21/2026)" | "heuristic: cash-only" | ...
  warrant_signals    jsonb,               -- full {status, signals[]} for transparency on the card
  firm_id            integer REFERENCES firm(id),   -- listing brokerage (reuse the broker graph)
  firm_name          text,                -- denormalized convenience
  broker_name        text,                -- lead listing broker when known
  source             text,
  created_at         timestamptz DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_condo_zip     ON condo(zip);
CREATE INDEX IF NOT EXISTS idx_condo_status  ON condo(warrantable_status);
CREATE INDEX IF NOT EXISTS idx_condo_firm    ON condo(firm_id);

-- Convenience view: condo with its listing broker + firm + status, ready for /api/condos.
CREATE OR REPLACE VIEW condo_card AS
  SELECT c.id, c.address, c.city, c.zip, c.price, c.hoa, c.beds, c.baths, c.sqft,
         c.year_built, c.project_name, c.warrantable_status, c.warrant_source, c.warrant_signals,
         c.broker_name, COALESCE(c.firm_name, f.name) AS firm_name, c.source, c.created_at
    FROM condo c
    LEFT JOIN firm f ON f.id = c.firm_id;

-- The match function: given a condo's project name + city + zip, find the best FHA-list match and
-- return its warrant_signal. SQL-side mirror of classify-warrantability.js's FHA-match step so the
-- DB can self-classify on insert if desired. Returns NULL when no list match (caller -> not_listed).
CREATE OR REPLACE FUNCTION fha_match(p_project text, p_address text, p_city text, p_zip text)
RETURNS TABLE (warrant_signal text, source text) AS $$
  -- 1) exact-ish project-name match within the same zip (strongest)
  SELECT f.warrant_signal,
         'HUD FHA list (' || COALESCE(f.condo_id,'?') || ' ' || COALESCE(f.submission,'') ||
           ', ' || f.hud_status || COALESCE(', exp ' || f.expiration_date, '') || ')'
    FROM fha_condo f
   WHERE p_project IS NOT NULL AND length(p_project) > 3
     AND lower(f.project_name) = lower(p_project)
     AND (p_zip IS NULL OR f.zip = p_zip)
   ORDER BY CASE f.warrant_signal WHEN 'fha_approved' THEN 0 ELSE 1 END
   LIMIT 1;
$$ LANGUAGE sql STABLE;