Merge pull request 'feat(tradein): кадастр зданий как источник house_metadata (#393)' (#406) from feat/tradein-cadastre-enrichment into main
This commit is contained in:
commit
c86f2e5a72
3 changed files with 155 additions and 0 deletions
|
|
@ -29,6 +29,7 @@ logger = logging.getLogger(__name__)
|
|||
OVERPASS_URL = "https://overpass-api.de/api/interpreter"
|
||||
_CACHE_RADIUS_M = 40 # дом в пределах 40 м от точки — считаем тем же
|
||||
_OVERPASS_RADIUS_M = 25 # ищем здание в 25 м от геокодированной точки
|
||||
_CAD_RADIUS_M = 60 # радиус поиска здания в cad_buildings (#393)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
|
|
@ -137,6 +138,39 @@ def _cache_put(db: Session, meta: HouseMetadata, raw: dict) -> None:
|
|||
db.commit()
|
||||
|
||||
|
||||
# ── Источник #393: кадастр зданий (cad_buildings, импорт из gendesign-БД) ────
|
||||
def _cad_buildings_get(db: Session, lat: float, lon: float) -> HouseMetadata | None:
|
||||
"""Ближайшее здание из cad_buildings (данные Росреестра) по координатам.
|
||||
|
||||
Приоритетный источник house_metadata — год / этажность полнее, чем OSM
|
||||
(~34к домов ЕКБ с годом постройки). Таблицу наполняет import-cadastre.sh.
|
||||
"""
|
||||
row = db.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT year_built, floors
|
||||
FROM cad_buildings
|
||||
WHERE geom IS NOT NULL
|
||||
AND (year_built IS NOT NULL OR floors IS NOT NULL)
|
||||
AND ST_DWithin(geom::geography, ST_MakePoint(:lon, :lat)::geography, :radius)
|
||||
ORDER BY ST_Distance(geom::geography, ST_MakePoint(:lon, :lat)::geography)
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{"lat": lat, "lon": lon, "radius": _CAD_RADIUS_M},
|
||||
).fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
return HouseMetadata(
|
||||
lat=lat, lon=lon,
|
||||
year_built=row.year_built,
|
||||
total_floors=row.floors,
|
||||
house_type=None, # cad_buildings.purpose не маппится в panel/brick
|
||||
total_units=None,
|
||||
source="cadastre",
|
||||
)
|
||||
|
||||
|
||||
# ── Provider: OSM Overpass ───────────────────────────────────────────────────
|
||||
async def _overpass_lookup(lat: float, lon: float) -> tuple[HouseMetadata, dict] | None:
|
||||
"""Запрос здания у точки через Overpass API. (meta, raw_payload) или None."""
|
||||
|
|
@ -181,6 +215,15 @@ async def get_house_metadata(lat: float, lon: float, db: Session) -> HouseMetada
|
|||
оценка обязана работать даже если картография недоступна.
|
||||
"""
|
||||
try:
|
||||
# #393: кадастр зданий (Росреестр, cad_buildings) — приоритетный источник.
|
||||
cad = _cad_buildings_get(db, lat, lon)
|
||||
if cad is not None:
|
||||
logger.info(
|
||||
"house_metadata: cad_buildings (%.5f, %.5f) → year=%s floors=%s",
|
||||
lat, lon, cad.year_built, cad.total_floors,
|
||||
)
|
||||
return cad
|
||||
|
||||
cached = _cache_get(db, lat, lon)
|
||||
if cached is not None:
|
||||
logger.info("house_metadata cache hit: (%.5f, %.5f)", lat, lon)
|
||||
|
|
|
|||
34
tradein-mvp/backend/data/sql/006_cad_buildings.sql
Normal file
34
tradein-mvp/backend/data/sql/006_cad_buildings.sql
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
-- Trade-In MVP — кадастр зданий ЕКБ (#393).
|
||||
-- Срез cad_buildings из gendesign-БД (данные Росреестра по зданиям),
|
||||
-- импортируется ETL-ом deploy/import-cadastre.sh.
|
||||
-- Основной источник обогащения house_metadata (год / этажность) —
|
||||
-- данные точнее и полнее, чем OSM Overpass.
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cad_buildings (
|
||||
cad_num text PRIMARY KEY, -- кадастровый номер здания, 66:41:...
|
||||
address text,
|
||||
floors int,
|
||||
year_built int,
|
||||
area_m2 numeric(12, 2), -- площадь здания (не квартиры)
|
||||
purpose text, -- Жилое / Нежилое / Многоквартирный дом
|
||||
lat double precision,
|
||||
lon double precision,
|
||||
geom geometry(Point, 4326),
|
||||
imported_at timestamptz NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS cad_buildings_geom_idx ON cad_buildings USING GIST (geom);
|
||||
|
||||
-- Авто-update geom из (lat, lon) — переиспользуем функцию из 002_core_tables.sql.
|
||||
DROP TRIGGER IF EXISTS cad_buildings_set_geom_trg ON cad_buildings;
|
||||
CREATE TRIGGER cad_buildings_set_geom_trg
|
||||
BEFORE INSERT OR UPDATE OF lat, lon ON cad_buildings
|
||||
FOR EACH ROW EXECUTE FUNCTION listings_set_geom();
|
||||
|
||||
COMMENT ON TABLE cad_buildings IS
|
||||
'#393 — кадастр зданий ЕКБ (срез cad_buildings из gendesign-БД). '
|
||||
'Приоритетный источник обогащения house_metadata: год постройки / этажность.';
|
||||
|
||||
COMMIT;
|
||||
78
tradein-mvp/deploy/import-cadastre.sh
Executable file
78
tradein-mvp/deploy/import-cadastre.sh
Executable file
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/env bash
|
||||
# Импорт кадастра зданий ЕКБ из gendesign-БД в tradein cad_buildings (#393).
|
||||
#
|
||||
# Источник: gendesign-postgres-1 / cad_buildings (~37k зданий, 36k по ЕКБ).
|
||||
# Берём ЕКБ (cad_num LIKE '66:41:%') с годом постройки или этажностью.
|
||||
# Координаты — центроид geom. Используется как источник обогащения
|
||||
# house_metadata (год / этажность) — точнее OSM.
|
||||
#
|
||||
# Запуск на прод-хосте: ./import-cadastre.sh
|
||||
# Повторяемо: upsert по cad_num (PRIMARY KEY), новый запуск обновит данные.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SRC_PG="${SRC_PG:-gendesign-postgres-1}"
|
||||
DST_PG="${DST_PG:-tradein-postgres}"
|
||||
SRC_DB="${SRC_DB:-gendesign}"
|
||||
SRC_USER="${SRC_USER:-gendesign}"
|
||||
|
||||
echo "[$(date -u +%H:%M:%S)] import-cadastre: здания ЕКБ из cad_buildings"
|
||||
|
||||
# 1. Staging-таблица в tradein.
|
||||
docker exec "$DST_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -c "
|
||||
CREATE TABLE IF NOT EXISTS cad_buildings_staging (
|
||||
cad_num text PRIMARY KEY, address text, floors int, year_built int,
|
||||
area_m2 numeric, purpose text, lat double precision, lon double precision
|
||||
);
|
||||
TRUNCATE cad_buildings_staging;
|
||||
"
|
||||
|
||||
# 2. Трансформируем на gendesign-стороне, пайпим CSV в staging.
|
||||
# Центроид geom → lat/lon (ST_Transform на 4326 на случай иного SRID).
|
||||
docker exec "$SRC_PG" psql -U "$SRC_USER" -d "$SRC_DB" -v ON_ERROR_STOP=on -c "
|
||||
COPY (
|
||||
SELECT
|
||||
cad_num,
|
||||
NULLIF(trim(readable_address), '') AS address,
|
||||
NULLIF(floors, 0) AS floors,
|
||||
CASE WHEN year_built BETWEEN 1800 AND 2100
|
||||
THEN year_built END AS year_built,
|
||||
round(COALESCE(build_record_area, area), 2) AS area_m2,
|
||||
purpose,
|
||||
ST_Y(ST_Centroid(ST_Transform(geom, 4326))) AS lat,
|
||||
ST_X(ST_Centroid(ST_Transform(geom, 4326))) AS lon
|
||||
FROM cad_buildings
|
||||
WHERE cad_num LIKE '66:41:%'
|
||||
AND cad_num IS NOT NULL
|
||||
AND (year_built IS NOT NULL OR floors IS NOT NULL)
|
||||
) TO STDOUT WITH CSV
|
||||
" | docker exec -i "$DST_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -c "
|
||||
COPY cad_buildings_staging FROM STDIN WITH CSV
|
||||
"
|
||||
|
||||
STAGED=$(docker exec "$DST_PG" psql -U tradein -d tradein -tAc \
|
||||
"SELECT count(*) FROM cad_buildings_staging")
|
||||
echo "[$(date -u +%H:%M:%S)] staged: $STAGED зданий"
|
||||
|
||||
# 3. Upsert в cad_buildings (trigger проставит geom из lat/lon).
|
||||
docker exec "$DST_PG" psql -U tradein -d tradein -v ON_ERROR_STOP=on -c "
|
||||
INSERT INTO cad_buildings
|
||||
(cad_num, address, floors, year_built, area_m2, purpose, lat, lon)
|
||||
SELECT cad_num, address, floors, year_built, area_m2, purpose, lat, lon
|
||||
FROM cad_buildings_staging
|
||||
ON CONFLICT (cad_num) DO UPDATE SET
|
||||
address = EXCLUDED.address,
|
||||
floors = EXCLUDED.floors,
|
||||
year_built = EXCLUDED.year_built,
|
||||
area_m2 = EXCLUDED.area_m2,
|
||||
purpose = EXCLUDED.purpose,
|
||||
lat = EXCLUDED.lat,
|
||||
lon = EXCLUDED.lon,
|
||||
imported_at = NOW();
|
||||
|
||||
DROP TABLE cad_buildings_staging;
|
||||
"
|
||||
|
||||
TOTAL=$(docker exec "$DST_PG" psql -U tradein -d tradein -tAc \
|
||||
"SELECT count(*) FROM cad_buildings")
|
||||
echo "[$(date -u +%H:%M:%S)] cad_buildings всего: $TOTAL"
|
||||
Loading…
Add table
Reference in a new issue