gendesign/tradein-mvp/backend/scripts/audit_address_sample.sql
Light1YT 02ad9a1f58 feat(tradein): Phase 1 of #582 — address mismatch audit infrastructure
Stratified 200-house Yandex reverse-geocode audit comparing our DB's
(address, lat/lon) pair against what Yandex.Карты returns for those
coordinates. Лежит фундамент данных для Phases 2-5 (canonical = Yandex
per user decision).

What ships:
- Migration 066: address_mismatch_audit table + FDW foreign table
  gendesign_ekb_districts_geom для stratified sampling.
- Sampling SQL: 8 EKB districts × 25 houses = 200.
- Driver scripts/audit_address_mismatch.py с двумя режимами:
  API (если YANDEX_GEOCODER_API_KEY есть) и Playwright (CAPTCHA-aware,
  персистентный контекст). Resumable по --batch.
- Report SQL: p50/p75/p95 distance, top-20 outliers, per-district breakdown.
- 19 unit-тестов: normalize, distance bind, API parser, resume idempotency,
  captcha → status=blocked.

Devops note: после деплоя миграции — выдать tradein_fdw_reader SELECT на
gendesign.public.ekb_districts_geom перед первым запуском audit.

Refs #582 (Phase 1 of 5). Phase 3 backfill заблокирован до Yandex API
key (#402).
2026-05-25 12:50:30 +05:00

53 lines
1.6 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- audit_address_sample.sql
-- Stratified sample of EKB houses for the address-mismatch audit (issue #582).
--
-- Strategy:
-- 1. Spatial-join houses → gendesign_ekb_districts_geom via ST_Contains
-- (only houses with lat/lon falling inside one of the 8 admin polygons).
-- 2. Partition by district, ROW_NUMBER() with a deterministic ORDER BY id
-- so repeated runs return the same sample (deterministic = matches the
-- "resumable per (house_id, audit_batch)" guarantee in 066 migration).
-- 3. Keep top :limit_per_district per district → ~200 rows total for 8
-- districts × 25.
--
-- Bind param:
-- :limit_per_district — default 25, expressed as :limit_per_district
-- (psql variables resolve in :name form for the report; for Python use
-- SQLAlchemy text() with bindparams).
--
-- Columns returned:
-- id, address, lat, lon, district
--
-- NB: uses CAST(:x AS int) per project sql.md rule (psycopg v3 ignores ::type
-- after bind params).
WITH houses_in_districts AS (
SELECT
h.id,
h.address,
h.lat,
h.lon,
d.district_name AS district,
h.geom
FROM houses h
JOIN gendesign_ekb_districts_geom d
ON ST_Contains(d.geom, h.geom)
WHERE h.lat IS NOT NULL
AND h.lon IS NOT NULL
AND h.address IS NOT NULL
AND length(trim(h.address)) > 0
),
ranked AS (
SELECT
id,
address,
lat,
lon,
district,
ROW_NUMBER() OVER (PARTITION BY district ORDER BY id) AS rn
FROM houses_in_districts
)
SELECT id, address, lat, lon, district
FROM ranked
WHERE rn <= CAST(:limit_per_district AS int)
ORDER BY district, id;