-- 028_matching_tables.sql -- Purpose: Cross-source matching link tables for deduplication and enrichment. -- Implements 3-tier house + apartment matching per Cross_Source_Matching_Strategy. -- house_sources — one canonical house ↔ many external (source, ext_id) pairs -- listing_sources — one canonical listing ↔ many external (source, ext_id) pairs -- house_address_aliases — manual overrides for corpus/address format mismatches -- Dependencies: -- - houses table (009_houses.sql) -- - listings table (002_core_tables.sql) -- Deploy order: Apply after 027. -- -- Sources: Cross_Source_Matching_Strategy sec 2 BEGIN; -- address_fingerprint column on houses (needed for Tier-1 matching algorithm) ALTER TABLE houses ADD COLUMN IF NOT EXISTS address_fingerprint text; CREATE INDEX IF NOT EXISTS houses_addr_fp_idx ON houses (address_fingerprint) WHERE address_fingerprint IS NOT NULL; -- canonical / merged_into fields on listings (needed for dedup workflow) ALTER TABLE listings ADD COLUMN IF NOT EXISTS canonical boolean DEFAULT true, ADD COLUMN IF NOT EXISTS merged_into bigint REFERENCES listings(id) ON DELETE SET NULL; CREATE INDEX IF NOT EXISTS listings_merged_into_idx ON listings (merged_into) WHERE merged_into IS NOT NULL; -- ----------------------------------------------------------------------- -- house_sources: link canonical house to external source records -- ----------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS house_sources ( id bigserial PRIMARY KEY, house_id bigint NOT NULL REFERENCES houses(id) ON DELETE CASCADE, ext_source text NOT NULL, -- 'avito' / 'cian' / 'yandex' / 'n1' ext_id text NOT NULL, -- source-specific house/newbuilding ID confidence real NOT NULL, -- 0.0–1.0 matched_method text NOT NULL, -- 'cadastr_exact'/'address_geo_year'/'geo_only'/'new'/'manual' matched_at timestamptz NOT NULL DEFAULT NOW(), UNIQUE (ext_source, ext_id) ); CREATE INDEX IF NOT EXISTS house_sources_house_idx ON house_sources (house_id); -- Low-confidence matches for review queue CREATE INDEX IF NOT EXISTS house_sources_low_conf_idx ON house_sources (confidence) WHERE confidence < 0.85; -- ----------------------------------------------------------------------- -- listing_sources: link canonical listing to external source records -- ----------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS listing_sources ( id bigserial PRIMARY KEY, listing_id bigint NOT NULL REFERENCES listings(id) ON DELETE CASCADE, ext_source text NOT NULL, -- 'avito' / 'cian' ext_id text NOT NULL, -- Avito source_id / Cian cianId (as text) confidence real NOT NULL, -- 0.0–1.0 matched_method text NOT NULL, -- 'cadastr_exact'/'minhash'/'composite'/'new'/'manual' matched_at timestamptz NOT NULL DEFAULT NOW(), source_url text, -- URL объявления на источнике last_scraped_at timestamptz, -- последний раз видели на источнике UNIQUE (ext_source, ext_id) ); CREATE INDEX IF NOT EXISTS listing_sources_listing_idx ON listing_sources (listing_id); -- Low-confidence listing matches for review CREATE INDEX IF NOT EXISTS listing_sources_low_conf_idx ON listing_sources (confidence) WHERE confidence < 0.85; -- Recency index for stale-data detection CREATE INDEX IF NOT EXISTS listing_sources_scraped_idx ON listing_sources (ext_source, last_scraped_at DESC); -- ----------------------------------------------------------------------- -- house_address_aliases: manual mapping for corpus/address format mismatches -- e.g. 'ул. Постовского 17А' ↔ 'ул. Постовского 17 корп.1' -- ----------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS house_address_aliases ( id bigserial PRIMARY KEY, house_id bigint NOT NULL REFERENCES houses(id) ON DELETE CASCADE, normalized_address text NOT NULL, -- normalized form (output of normalize_address()) fingerprint text, -- SHA1 of normalized_address source text, -- which scraper/dataset provided this alias UNIQUE (normalized_address) ); CREATE INDEX IF NOT EXISTS haa_house_idx ON house_address_aliases (house_id); CREATE INDEX IF NOT EXISTS haa_fingerprint_idx ON house_address_aliases (fingerprint) WHERE fingerprint IS NOT NULL; COMMIT;