feat(market): contract views market.v_* over producer tables (#2130)
Some checks failed
Deploy Trade-In / build-browser (push) Blocked by required conditions
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / build-frontend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / changes (push) Has been cancelled

This commit is contained in:
lekss361 2026-07-02 14:02:59 +00:00
parent 5f661bd0b7
commit e2bb5061f9

View file

@ -0,0 +1,219 @@
-- 154_market_contract_views.sql
-- Public data contract for external consumers of the trade-in house dataset (#2130).
--
-- Context:
-- `gendesign_reader` (101_gendesign_reader_role.sql) is currently granted SELECT
-- directly on 5 raw producer tables: houses, house_sources, houses_price_dynamics,
-- house_reliability_checks, house_reviews (102 grants listings/offer_price_history
-- separately). Consumers (gendesign-backend ETL #976,
-- backend/app/services/etl/newbuilding_crossload.py) read these raw tables directly,
-- which means every scraper-driven ALTER TABLE (new column, renamed column, dropped
-- bookkeeping field) is a silent breaking change for an external product.
--
-- This migration introduces `market.v_*` views as a stable, explicit-column contract
-- layer over those 5 raw tables. Consumers should migrate to reading the views
-- instead of the raw tables. The column list below IS the promise of stability —
-- adding a column later is backward compatible, renaming/removing one is not.
--
-- Regranting `gendesign_reader` to the views + revoking direct raw-table access is a
-- SEPARATE follow-up (#2138) — intentionally NOT done here. This migration only adds
-- the views; existing raw-table grants (101_gendesign_reader_role.sql) are untouched.
--
-- Column selection:
-- Every column actually read by the current consumer
-- (backend/app/services/etl/newbuilding_crossload.py `_SOURCE_SQL`, gendesign repo) is
-- included: houses.{source, ext_house_id, url, slug, address, full_address, lat, lon,
-- house_class, developer_name, developer_key, year_built, total_floors, total_units,
-- rating, reviews_count, houses_by_turn, cian_internal_house_id, raw_payload,
-- last_scraped_at} + house_sources.{house_id, ext_source, ext_id} (LEFT JOIN for
-- yandex_jk_id).
--
-- houses_price_dynamics / house_reliability_checks / house_reviews have no current
-- gendesign-side consumer, but are granted to gendesign_reader (101) as future ETL
-- sources — views expose their full content-bearing column set so future consumers
-- get the same stability guarantee from day one.
--
-- Deliberately EXCLUDED from all views (producer-internal, high-churn, not part of
-- the public contract):
-- - raw_payload / raw_characteristics / map_pins / rating_distribution — raw
-- scrape-source blobs kept for internal retrofit, not a stable shape.
-- (houses.raw_payload IS however read by the ETL today — see v_houses below,
-- kept for that reason; house_sources/house_reviews raw_payload are NOT read
-- by any current consumer and are excluded.)
-- - *_validated_at / *_status / *_qc_* / *_error_reason / last_imv_attempt_at —
-- internal scrape-state bookkeeping (dadata QC codes, IMV backfill status),
-- churns independently of the actual house data.
-- - complex_id — unused FK placeholder (no complexes table yet, per 040 comment).
--
-- Dependencies:
-- - 105_market_schema_yandex_enrichment.sql (CREATE SCHEMA market)
-- - 009_houses.sql + all subsequent houses ALTERs (010/020/029/031/040/064/070/071/105)
-- - 028_matching_tables.sql + 029_extend_matching_valuation_dynamics.sql (house_sources)
-- - 024_houses_price_dynamics.sql + 029 dims (houses_price_dynamics)
-- - 025_house_reliability_checks.sql (house_reliability_checks)
-- - 014_house_reviews.sql + 043_house_reviews_extend.sql (house_reviews)
--
-- Idempotent: CREATE SCHEMA IF NOT EXISTS + CREATE OR REPLACE VIEW — safe to re-run.
-- AUTO-APPLIED on prod via deploy-tradein.yml/_schema_migrations.
-- Does NOT touch grants — gendesign_reader keeps reading raw tables until #2138.
BEGIN;
CREATE SCHEMA IF NOT EXISTS market;
-- ── market.v_houses ──────────────────────────────────────────────────────────
CREATE OR REPLACE VIEW market.v_houses AS
SELECT
id,
source,
ext_house_id,
url,
slug,
address,
full_address,
short_address,
lat,
lon,
geom,
year_built,
house_type,
house_class,
material_walls,
material_floors,
series_name,
total_floors,
total_units,
entrances,
flat_count,
is_emergency,
passenger_elevators,
cargo_elevators,
has_concierge,
closed_yard,
has_playground,
hot_water,
heat_supply_type,
gas_supply_type,
overlap_type,
parking_type,
infrastructure_summary,
infrastructure_walk_distance,
developer_name,
developer_key,
management_company_id,
rating,
reviews_count,
rating_score,
rating_string,
transport_accessibility_rate,
has_panorama,
advantages,
banks,
builders,
houses_by_turn,
corpus_count,
commission_year,
commission_month,
total_area_ha,
cadastral_number,
house_fias_id,
yandex_jk_id,
yandex_jk_slug,
cian_internal_house_id,
cian_zhk_url,
raw_payload,
first_seen_at,
last_scraped_at
FROM public.houses;
COMMENT ON VIEW market.v_houses IS
'Stable public contract over public.houses (#2130). Explicit column list is the '
'stability promise — do not SELECT * against the base table from external '
'consumers. raw_payload is included because it is read today by gendesign ETL '
'#976 (newbuilding_crossload.py); scraper-internal QC/status/validated_at '
'bookkeeping columns are intentionally excluded.';
-- ── market.v_house_sources ───────────────────────────────────────────────────
CREATE OR REPLACE VIEW market.v_house_sources AS
SELECT
id,
house_id,
ext_source,
ext_id,
confidence,
matched_method,
matched_at,
ext_url,
last_seen_at
FROM public.house_sources;
COMMENT ON VIEW market.v_house_sources IS
'Stable public contract over public.house_sources (#2130). Cross-source house '
'match links (house_id -> ext_source/ext_id). raw_payload (internal source '
'snapshot for diffing) is intentionally excluded — not read by any current '
'consumer.';
-- ── market.v_houses_price_dynamics ───────────────────────────────────────────
CREATE OR REPLACE VIEW market.v_houses_price_dynamics AS
SELECT
id,
house_id,
month_date,
price_per_sqm,
source,
room_count,
prices_type,
period,
recorded_at
FROM public.houses_price_dynamics;
COMMENT ON VIEW market.v_houses_price_dynamics IS
'Stable public contract over public.houses_price_dynamics (#2130). Monthly '
'price-per-sqm time series per house, dimensioned by room_count/prices_type/'
'period. No current gendesign consumer — view exists for forward-compat with '
'the gendesign_reader grant in 101_gendesign_reader_role.sql.';
-- ── market.v_house_reliability_checks ────────────────────────────────────────
CREATE OR REPLACE VIEW market.v_house_reliability_checks AS
SELECT
id,
house_id,
check_status,
check_name,
details,
source,
recorded_at
FROM public.house_reliability_checks;
COMMENT ON VIEW market.v_house_reliability_checks IS
'Stable public contract over public.house_reliability_checks (#2130). '
'наш.дом.рф reliability check results scraped via Cian newbuilding pages. '
'No current gendesign consumer — view exists for forward-compat with the '
'gendesign_reader grant in 101_gendesign_reader_role.sql.';
-- ── market.v_house_reviews ────────────────────────────────────────────────────
CREATE OR REPLACE VIEW market.v_house_reviews AS
SELECT
id,
house_id,
source,
ext_review_id,
author_name,
review_title,
score,
model_experience,
rated_date,
text_main,
text_pros,
text_cons,
likes,
scraped_at
FROM public.house_reviews;
COMMENT ON VIEW market.v_house_reviews IS
'Stable public contract over public.house_reviews (#2130). Resident reviews '
'from Avito/Cian/Yandex Houses Catalog. raw_payload excluded (internal retrofit '
'blob, no current consumer). No current gendesign consumer — view exists for '
'forward-compat with the gendesign_reader grant in 101_gendesign_reader_role.sql.';
COMMIT;