From 6612c1cd2b58574002c46bf02992a9dbcfe7d16a Mon Sep 17 00:00:00 2001 From: lekss361 Date: Sun, 17 May 2026 15:26:59 +0000 Subject: [PATCH] =?UTF-8?q?feat(22begh):=20schema=20extension=20=E2=80=94?= =?UTF-8?q?=20flats+6=20/=20objects+30=20/=20developers+4=20/=20metro=5Fto?= =?UTF-8?q?p3=20(#306)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/scrapers/domrf_kn.py | 224 +++++++++++++++++++- data/sql/113_22begh_kn_schema_extension.sql | 161 ++++++++++++++ 2 files changed, 377 insertions(+), 8 deletions(-) create mode 100644 data/sql/113_22begh_kn_schema_extension.sql diff --git a/backend/app/services/scrapers/domrf_kn.py b/backend/app/services/scrapers/domrf_kn.py index ddc03d88..eab5e9a5 100644 --- a/backend/app/services/scrapers/domrf_kn.py +++ b/backend/app/services/scrapers/domrf_kn.py @@ -186,15 +186,25 @@ def _extract_list(payload: dict[str, Any]) -> list[dict[str, Any]]: def _norm_object(row: dict[str, Any], region_cd: int | None = None) -> dict[str, Any]: """Map naш.дом.рф /kn/object row → domrf_kn_objects column dict. - Real API field names: + Real API field names (confirmed via payload audit 2026-05-17, obj=65136): objId, hobjId, developer{devId, shortName, fullName, groupName, companyGroup, devInn}, rpdRegionCd, objAddr, shortAddr, objCommercNm, objFloorMin, objFloorMax, objElemLivingCnt, objSquareLiving, objReady100PercDt, objClass, latitude, longitude, objProblemFlg, problemFlag, siteStatus, objGreenHouseFlg, objGuarantyEscrowFlg, - objStatus, aiDescription, freeFlatsInfo{priceMin, numberFlats}. + objStatus, aiDescription, freeFlatsInfo{priceMin, numberFlats}, + residentialBuildings (section_count), rpdNum (project_declaration_num), + objPublDt (project_published_at), metro{name, time, line, color, colors, isWalk, id}. Note: DOM.РФ /kn/object list endpoint never populates `objClass` in API responses (field absent from payload). Class is extracted from `aiDescription` text as fallback. + + 22e fields NOT in kn-API list payload (remain NULL until catalog scraper 22d): + first_floor_type, elevators_passenger/cargo_count, parking_total_slots, + guest_parking_inside/outside_count, ceiling_height_m, finishing_variants_count, + has_free_planning, playground_kids/sport_count, has_bike_paths, trash_areas_count, + has_ramp, has_low_platforms, has_wheelchair_lift, flat_area_min/max, + price_max_rub, price_per_m2_min/max, parking_provision_pct, avg_flat_area_m2, + domrf_score_location/transport/infrastructure. """ dev = row.get("developer") if isinstance(row.get("developer"), dict) else {} company_group = _g(dev, "companyGroup") if dev else None @@ -210,6 +220,34 @@ def _norm_object(row: dict[str, Any], region_cd: int | None = None) -> dict[str, obj_id, ) + # Metro: kn-API returns a single metro object (nearest station). + # Stored as metro_top3 JSON array (single element) for future multi-station extension. + metro_raw = row.get("metro") + metro_nearest_name: str | None = None + metro_nearest_walk_minutes: int | None = None + metro_top3: list[dict[str, Any]] | None = None + if isinstance(metro_raw, dict) and metro_raw.get("name"): + metro_nearest_name = metro_raw.get("name") + raw_time = metro_raw.get("time") + if raw_time is not None: + try: + metro_nearest_walk_minutes = round(float(raw_time)) + except (TypeError, ValueError): + pass + metro_top3 = [ + { + "name": metro_raw.get("name"), + "time": raw_time, + "line": metro_raw.get("line"), + "color": metro_raw.get("color"), + "isWalk": metro_raw.get("isWalk"), + } + ] + + # freeFlatsInfo.priceMin — price of cheapest available flat in the object + free_info = row.get("freeFlatsInfo") if isinstance(row.get("freeFlatsInfo"), dict) else {} + price_min_rub = _g(free_info, "priceMin") if free_info else None + return { "obj_id": _g(row, "objId", "obj_id", "id"), "hobj_id": _g(row, "hobjId", "hobj_id"), @@ -235,6 +273,43 @@ def _norm_object(row: dict[str, Any], region_cd: int | None = None) -> dict[str, "latitude": _g(row, "latitude"), "longitude": _g(row, "longitude"), "obj_status": _g(row, "objStatus"), + # 22e: fields mappable from kn-API list payload + "section_count": _g(row, "residentialBuildings"), + "project_declaration_num": _g(row, "rpdNum"), + "project_published_at": _to_date(_g(row, "objPublDt")), + "price_min_rub": price_min_rub, + "dev_group_name": _g(dev, "groupName"), + # 22e + 22h: metro (nearest station from kn-API, single object) + "metro_nearest_name": metro_nearest_name, + "metro_nearest_walk_minutes": metro_nearest_walk_minutes, + "metro_top3": json.dumps(metro_top3, ensure_ascii=False) if metro_top3 else None, + # 22e: fields NOT in kn-API list payload — filled by catalog scraper (22d) + "first_floor_type": None, + "elevators_passenger_count": None, + "elevators_cargo_count": None, + "parking_total_slots": None, + "guest_parking_inside_count": None, + "guest_parking_outside_count": None, + "ceiling_height_m": None, + "finishing_variants_count": None, + "has_free_planning": None, + "avg_flat_area_m2": None, + "playground_kids_count": None, + "playground_sport_count": None, + "has_bike_paths": None, + "trash_areas_count": None, + "has_ramp": None, + "has_low_platforms": None, + "has_wheelchair_lift": None, + "flat_area_min": None, + "flat_area_max": None, + "price_max_rub": None, + "price_per_m2_min": None, + "price_per_m2_max": None, + "parking_provision_pct": None, + "domrf_score_location": None, + "domrf_score_transport": None, + "domrf_score_infrastructure": None, } @@ -308,6 +383,13 @@ def _norm_flat(row: dict[str, Any], region_cd: int | None) -> dict[str, Any]: "city": _g(row, "city"), "region_cd": region_cd, "obj_name": _g(row, "objName") or _g(row.get("objInfo") or {}, "objCommercNm"), + # 22b: новые поля квартиры + "section_no": _g(row, "_entrance"), # entranceNumber injected by _flatten_table + "finishing_type": None, # заполнит catalog scraper 22d + "ceiling_height_m": None, # заполнит catalog scraper 22d + "key_handover_dt": None, # заполнит catalog scraper 22d + "catalog_updated_at": None, # заполнит catalog scraper 22d + "catalog_url_hash": None, # заполнит catalog scraper 22d } @@ -321,14 +403,38 @@ UPSERT_OBJECT_SQL = text( floor_min, floor_max, flat_count, square_living, ready_dt, problem_flag, site_status, green_house, escrow, obj_class, wall_type, energy_eff, - latitude, longitude, obj_status, snapshot_date + latitude, longitude, obj_status, + section_count, project_declaration_num, project_published_at, + price_min_rub, dev_group_name, + metro_nearest_name, metro_nearest_walk_minutes, metro_top3, + first_floor_type, elevators_passenger_count, elevators_cargo_count, + parking_total_slots, guest_parking_inside_count, guest_parking_outside_count, + ceiling_height_m, finishing_variants_count, has_free_planning, avg_flat_area_m2, + playground_kids_count, playground_sport_count, has_bike_paths, trash_areas_count, + has_ramp, has_low_platforms, has_wheelchair_lift, + flat_area_min, flat_area_max, price_max_rub, price_per_m2_min, price_per_m2_max, + parking_provision_pct, domrf_score_location, domrf_score_transport, + domrf_score_infrastructure, + snapshot_date ) VALUES ( :obj_id, :hobj_id, :comm_name, :addr, :short_addr, :region_cd, :dev_id, :dev_name, :dev_inn, :floor_min, :floor_max, :flat_count, :square_living, :ready_dt, :problem_flag, :site_status, :green_house, :escrow, :obj_class, :wall_type, :energy_eff, - :latitude, :longitude, :obj_status, :snapshot_date + :latitude, :longitude, :obj_status, + :section_count, :project_declaration_num, :project_published_at, + :price_min_rub, :dev_group_name, + :metro_nearest_name, :metro_nearest_walk_minutes, CAST(:metro_top3 AS jsonb), + :first_floor_type, :elevators_passenger_count, :elevators_cargo_count, + :parking_total_slots, :guest_parking_inside_count, :guest_parking_outside_count, + :ceiling_height_m, :finishing_variants_count, :has_free_planning, :avg_flat_area_m2, + :playground_kids_count, :playground_sport_count, :has_bike_paths, :trash_areas_count, + :has_ramp, :has_low_platforms, :has_wheelchair_lift, + :flat_area_min, :flat_area_max, :price_max_rub, :price_per_m2_min, :price_per_m2_max, + :parking_provision_pct, :domrf_score_location, :domrf_score_transport, + :domrf_score_infrastructure, + :snapshot_date ) ON CONFLICT (obj_id, snapshot_date) DO UPDATE SET comm_name = EXCLUDED.comm_name, @@ -345,7 +451,91 @@ UPSERT_OBJECT_SQL = text( energy_eff = COALESCE(EXCLUDED.energy_eff, domrf_kn_objects.energy_eff), latitude = EXCLUDED.latitude, longitude = EXCLUDED.longitude, - obj_status = EXCLUDED.obj_status + obj_status = EXCLUDED.obj_status, + section_count = COALESCE(EXCLUDED.section_count, domrf_kn_objects.section_count), + project_declaration_num = COALESCE( + EXCLUDED.project_declaration_num, domrf_kn_objects.project_declaration_num + ), + project_published_at = COALESCE( + EXCLUDED.project_published_at, domrf_kn_objects.project_published_at + ), + price_min_rub = EXCLUDED.price_min_rub, + dev_group_name = COALESCE(EXCLUDED.dev_group_name, domrf_kn_objects.dev_group_name), + metro_nearest_name = COALESCE( + EXCLUDED.metro_nearest_name, domrf_kn_objects.metro_nearest_name + ), + metro_nearest_walk_minutes = COALESCE( + EXCLUDED.metro_nearest_walk_minutes, domrf_kn_objects.metro_nearest_walk_minutes + ), + metro_top3 = COALESCE(EXCLUDED.metro_top3, domrf_kn_objects.metro_top3), + first_floor_type = COALESCE( + EXCLUDED.first_floor_type, domrf_kn_objects.first_floor_type + ), + elevators_passenger_count = COALESCE( + EXCLUDED.elevators_passenger_count, domrf_kn_objects.elevators_passenger_count + ), + elevators_cargo_count = COALESCE( + EXCLUDED.elevators_cargo_count, domrf_kn_objects.elevators_cargo_count + ), + parking_total_slots = COALESCE( + EXCLUDED.parking_total_slots, domrf_kn_objects.parking_total_slots + ), + guest_parking_inside_count = COALESCE( + EXCLUDED.guest_parking_inside_count, domrf_kn_objects.guest_parking_inside_count + ), + guest_parking_outside_count = COALESCE( + EXCLUDED.guest_parking_outside_count, domrf_kn_objects.guest_parking_outside_count + ), + ceiling_height_m = COALESCE( + EXCLUDED.ceiling_height_m, domrf_kn_objects.ceiling_height_m + ), + finishing_variants_count = COALESCE( + EXCLUDED.finishing_variants_count, domrf_kn_objects.finishing_variants_count + ), + has_free_planning = COALESCE( + EXCLUDED.has_free_planning, domrf_kn_objects.has_free_planning + ), + avg_flat_area_m2 = COALESCE( + EXCLUDED.avg_flat_area_m2, domrf_kn_objects.avg_flat_area_m2 + ), + playground_kids_count = COALESCE( + EXCLUDED.playground_kids_count, domrf_kn_objects.playground_kids_count + ), + playground_sport_count = COALESCE( + EXCLUDED.playground_sport_count, domrf_kn_objects.playground_sport_count + ), + has_bike_paths = COALESCE(EXCLUDED.has_bike_paths, domrf_kn_objects.has_bike_paths), + trash_areas_count = COALESCE( + EXCLUDED.trash_areas_count, domrf_kn_objects.trash_areas_count + ), + has_ramp = COALESCE(EXCLUDED.has_ramp, domrf_kn_objects.has_ramp), + has_low_platforms = COALESCE( + EXCLUDED.has_low_platforms, domrf_kn_objects.has_low_platforms + ), + has_wheelchair_lift = COALESCE( + EXCLUDED.has_wheelchair_lift, domrf_kn_objects.has_wheelchair_lift + ), + flat_area_min = COALESCE(EXCLUDED.flat_area_min, domrf_kn_objects.flat_area_min), + flat_area_max = COALESCE(EXCLUDED.flat_area_max, domrf_kn_objects.flat_area_max), + price_max_rub = COALESCE(EXCLUDED.price_max_rub, domrf_kn_objects.price_max_rub), + price_per_m2_min = COALESCE( + EXCLUDED.price_per_m2_min, domrf_kn_objects.price_per_m2_min + ), + price_per_m2_max = COALESCE( + EXCLUDED.price_per_m2_max, domrf_kn_objects.price_per_m2_max + ), + parking_provision_pct = COALESCE( + EXCLUDED.parking_provision_pct, domrf_kn_objects.parking_provision_pct + ), + domrf_score_location = COALESCE( + EXCLUDED.domrf_score_location, domrf_kn_objects.domrf_score_location + ), + domrf_score_transport = COALESCE( + EXCLUDED.domrf_score_transport, domrf_kn_objects.domrf_score_transport + ), + domrf_score_infrastructure = COALESCE( + EXCLUDED.domrf_score_infrastructure, domrf_kn_objects.domrf_score_infrastructure + ) """ ) @@ -354,11 +544,17 @@ UPSERT_FLAT_SQL = text( INSERT INTO domrf_kn_flats ( id, ods_id, flat_type, flat_number, is_studio, total_area, living_area, rooms, status, price_rub, price_per_m2, floor, num_floors, obj_id, - city, region_cd, obj_name, snapshot_date + city, region_cd, obj_name, + section_no, finishing_type, ceiling_height_m, + key_handover_dt, catalog_updated_at, catalog_url_hash, + snapshot_date ) VALUES ( :id, :ods_id, :flat_type, :flat_number, :is_studio, :total_area, :living_area, :rooms, :status, :price_rub, :price_per_m2, :floor, :num_floors, :obj_id, - :city, :region_cd, :obj_name, :snapshot_date + :city, :region_cd, :obj_name, + :section_no, :finishing_type, :ceiling_height_m, + :key_handover_dt, :catalog_updated_at, :catalog_url_hash, + :snapshot_date ) ON CONFLICT (id, snapshot_date) DO UPDATE SET status = EXCLUDED.status, @@ -373,7 +569,19 @@ UPSERT_FLAT_SQL = text( total_area = COALESCE(EXCLUDED.total_area, domrf_kn_flats.total_area), rooms = COALESCE(EXCLUDED.rooms, domrf_kn_flats.rooms), floor = COALESCE(EXCLUDED.floor, domrf_kn_flats.floor), - num_floors = COALESCE(EXCLUDED.num_floors, domrf_kn_flats.num_floors) + num_floors = COALESCE(EXCLUDED.num_floors, domrf_kn_flats.num_floors), + section_no = COALESCE(EXCLUDED.section_no, domrf_kn_flats.section_no), + finishing_type = COALESCE(EXCLUDED.finishing_type, domrf_kn_flats.finishing_type), + ceiling_height_m = COALESCE( + EXCLUDED.ceiling_height_m, domrf_kn_flats.ceiling_height_m + ), + key_handover_dt = COALESCE(EXCLUDED.key_handover_dt, domrf_kn_flats.key_handover_dt), + catalog_updated_at = COALESCE( + EXCLUDED.catalog_updated_at, domrf_kn_flats.catalog_updated_at + ), + catalog_url_hash = COALESCE( + EXCLUDED.catalog_url_hash, domrf_kn_flats.catalog_url_hash + ) """ ) diff --git a/data/sql/113_22begh_kn_schema_extension.sql b/data/sql/113_22begh_kn_schema_extension.sql new file mode 100644 index 00000000..c6313c8f --- /dev/null +++ b/data/sql/113_22begh_kn_schema_extension.sql @@ -0,0 +1,161 @@ +-- Migration 112: schema extension for issues #297 sub-tasks 22b + 22e + 22g + 22h +-- Adds 6 flat-level cols, 30 object-level cols (incl. metro_top3), 4 developer metrics. +-- +-- Payload audit (obj=65136, 2026-05-17): +-- kn_api payload keys: objId, hobjId, objCommercNm, objAddr, shortAddr, rpdRegionCd, +-- developer{devId,devInn,orgForm,fullName,groupName,shortName,companyGroup}, +-- rpdNum, objPublDt, objStatus, siteStatus, objFloorMax, objFloorMin, objPriceAVG, +-- problemFlag, rpdRegionCd, objSquareLiving, objElemLivingCnt, objGreenHouseFlg, +-- objGuarantyEscrowFlg, objReady100PercDt, hobjRenderPhotoUrl, aiDescription, +-- freeFlatsInfo{priceMin, numberFlats}, residentialBuildings, +-- metro{id, line, name, time, color, colors, isWalk}, latitude, longitude, pdId, buildType. +-- +-- flat-level (portal/table): flatId, odsId, elemId, type, number, isStudio, +-- totalArea, livingArea, rooms, status, price, pricePerSquareMeter, numberFloors, +-- _objId(injected), _floor(injected), _entrance(injected as entranceNumber). +-- +-- Fields that map NOW (from existing kn-API payload): +-- 22b: section_no (from _entrance/entranceNumber injected by _flatten_table) +-- 22e: section_count (residentialBuildings), project_declaration_num (rpdNum), +-- project_published_at (objPublDt), metro_nearest_name + metro_nearest_walk_minutes +-- (metro.name + metro.time), metro_top3 (metro wrapped as single-element array), +-- dev_group_name (developer.groupName), price_min_rub (freeFlatsInfo.priceMin) +-- +-- Fields remaining NULL until catalog scraper 22d: +-- finishing_type, ceiling_height_m (flat), key_handover_dt, catalog_updated_at, +-- catalog_url_hash, first_floor_type, elevators_passenger_count, elevators_cargo_count, +-- parking_total_slots, guest_parking_inside_count, guest_parking_outside_count, +-- ceiling_height_m (obj), finishing_variants_count, has_free_planning, +-- playground_kids_count, playground_sport_count, has_bike_paths, trash_areas_count, +-- has_ramp, has_low_platforms, has_wheelchair_lift, flat_area_min, flat_area_max, +-- price_max_rub, price_per_m2_min, price_per_m2_max, parking_provision_pct, +-- avg_flat_area_m2, domrf_score_location, domrf_score_transport, +-- domrf_score_infrastructure, houses_building, houses_building_delayed, +-- houses_delivered, houses_delivered_delayed. + +BEGIN; + +-- ───────────────────────────────────────────────────────────────────────────── +-- 22b — domrf_kn_flats +6 columns +-- ───────────────────────────────────────────────────────────────────────────── +COMMENT ON TABLE domrf_kn_flats IS 'Per-flat catalogue from DOM.RF kn-API portal/table endpoint'; + +ALTER TABLE domrf_kn_flats + ADD COLUMN IF NOT EXISTS section_no int, + ADD COLUMN IF NOT EXISTS finishing_type text, + ADD COLUMN IF NOT EXISTS ceiling_height_m numeric(3,2), + ADD COLUMN IF NOT EXISTS key_handover_dt date, + ADD COLUMN IF NOT EXISTS catalog_updated_at date, + ADD COLUMN IF NOT EXISTS catalog_url_hash text; + +COMMENT ON COLUMN domrf_kn_flats.section_no IS '22b: подъезд из entrance.entranceNumber (portal/table)'; +COMMENT ON COLUMN domrf_kn_flats.finishing_type IS '22b/22d: тип отделки — заполнит catalog scraper (22d)'; +COMMENT ON COLUMN domrf_kn_flats.ceiling_height_m IS '22b/22d: высота потолков — заполнит catalog scraper (22d)'; +COMMENT ON COLUMN domrf_kn_flats.key_handover_dt IS '22b/22d: дата выдачи ключей — заполнит catalog scraper (22d)'; +COMMENT ON COLUMN domrf_kn_flats.catalog_updated_at IS '22b/22d: дата обновления инфо на catalog-странице (22d)'; +COMMENT ON COLUMN domrf_kn_flats.catalog_url_hash IS '22b/22d: hash-часть URL квартиры на каталоге dom.rf (22d)'; + +-- ───────────────────────────────────────────────────────────────────────────── +-- 22e + 22h — domrf_kn_objects +30 columns (incl. metro_top3) +-- ───────────────────────────────────────────────────────────────────────────── +COMMENT ON TABLE domrf_kn_objects IS 'Per-object (ЖК) catalogue from DOM.RF kn-API list endpoint'; + +-- Building specs (8) +ALTER TABLE domrf_kn_objects + ADD COLUMN IF NOT EXISTS first_floor_type text, + ADD COLUMN IF NOT EXISTS section_count int, + ADD COLUMN IF NOT EXISTS elevators_passenger_count int, + ADD COLUMN IF NOT EXISTS elevators_cargo_count int, + ADD COLUMN IF NOT EXISTS parking_total_slots int, + ADD COLUMN IF NOT EXISTS guest_parking_inside_count int, + ADD COLUMN IF NOT EXISTS guest_parking_outside_count int, + ADD COLUMN IF NOT EXISTS ceiling_height_m numeric(3,2); + +-- Apartment summary (3) +ALTER TABLE domrf_kn_objects + ADD COLUMN IF NOT EXISTS finishing_variants_count int, + ADD COLUMN IF NOT EXISTS has_free_planning bool, + ADD COLUMN IF NOT EXISTS avg_flat_area_m2 numeric(5,1); + +-- Yard infrastructure (4) +ALTER TABLE domrf_kn_objects + ADD COLUMN IF NOT EXISTS playground_kids_count int, + ADD COLUMN IF NOT EXISTS playground_sport_count int, + ADD COLUMN IF NOT EXISTS has_bike_paths bool, + ADD COLUMN IF NOT EXISTS trash_areas_count int; + +-- Accessibility / OVZ (3) +ALTER TABLE domrf_kn_objects + ADD COLUMN IF NOT EXISTS has_ramp bool, + ADD COLUMN IF NOT EXISTS has_low_platforms bool, + ADD COLUMN IF NOT EXISTS has_wheelchair_lift bool; + +-- Catalog / UI (9) +ALTER TABLE domrf_kn_objects + ADD COLUMN IF NOT EXISTS flat_area_min numeric(8,2), + ADD COLUMN IF NOT EXISTS flat_area_max numeric(8,2), + ADD COLUMN IF NOT EXISTS price_min_rub bigint, + ADD COLUMN IF NOT EXISTS price_max_rub bigint, + ADD COLUMN IF NOT EXISTS price_per_m2_min numeric(12,2), + ADD COLUMN IF NOT EXISTS price_per_m2_max numeric(12,2), + ADD COLUMN IF NOT EXISTS parking_provision_pct numeric(5,1), + ADD COLUMN IF NOT EXISTS project_published_at date, + ADD COLUMN IF NOT EXISTS project_declaration_num text; + +-- Metro & scores (5) — 22h merged into 22e +ALTER TABLE domrf_kn_objects + ADD COLUMN IF NOT EXISTS metro_nearest_name text, + ADD COLUMN IF NOT EXISTS metro_nearest_walk_minutes int, + ADD COLUMN IF NOT EXISTS metro_top3 jsonb, + ADD COLUMN IF NOT EXISTS domrf_score_location int, + ADD COLUMN IF NOT EXISTS domrf_score_transport int, + ADD COLUMN IF NOT EXISTS domrf_score_infrastructure int; + +-- Developer split (1) +ALTER TABLE domrf_kn_objects + ADD COLUMN IF NOT EXISTS dev_group_name text; + +COMMENT ON COLUMN domrf_kn_objects.section_count + IS '22e: подъезды (residentialBuildings из kn-API)'; +COMMENT ON COLUMN domrf_kn_objects.project_published_at + IS '22e: дата публикации проекта (objPublDt из kn-API)'; +COMMENT ON COLUMN domrf_kn_objects.project_declaration_num + IS '22e: номер проектной декларации (rpdNum из kn-API)'; +COMMENT ON COLUMN domrf_kn_objects.price_min_rub + IS '22e: минимальная цена (freeFlatsInfo.priceMin из kn-API)'; +COMMENT ON COLUMN domrf_kn_objects.metro_nearest_name + IS '22h: ближайшая станция метро (metro.name из kn-API)'; +COMMENT ON COLUMN domrf_kn_objects.metro_nearest_walk_minutes + IS '22h: время пешком до метро, мин (metro.time из kn-API, rounded)'; +COMMENT ON COLUMN domrf_kn_objects.metro_top3 + IS '22h: топ-3 ближайших станций [{name,time,line,color,isWalk}], сейчас 1 элемент'; +COMMENT ON COLUMN domrf_kn_objects.dev_group_name + IS '22e: группа компаний (developer.groupName из kn-API)'; +COMMENT ON COLUMN domrf_kn_objects.domrf_score_location + IS '22e: оценка расположения от DOM.RF (catalog 22d)'; +COMMENT ON COLUMN domrf_kn_objects.domrf_score_transport + IS '22e: оценка транспорта от DOM.RF (catalog 22d)'; +COMMENT ON COLUMN domrf_kn_objects.domrf_score_infrastructure + IS '22e: оценка инфраструктуры от DOM.RF (catalog 22d)'; + +-- ───────────────────────────────────────────────────────────────────────────── +-- 22g — domrf_developers +4 metrics +-- ───────────────────────────────────────────────────────────────────────────── +COMMENT ON TABLE domrf_developers IS 'Застройщики DOM.RF (companyGroup-level)'; + +ALTER TABLE domrf_developers + ADD COLUMN IF NOT EXISTS houses_building int, + ADD COLUMN IF NOT EXISTS houses_building_delayed int, + ADD COLUMN IF NOT EXISTS houses_delivered int, + ADD COLUMN IF NOT EXISTS houses_delivered_delayed int; + +COMMENT ON COLUMN domrf_developers.houses_building + IS '22g: домов строится (из карточки застройщика, catalog 22d)'; +COMMENT ON COLUMN domrf_developers.houses_building_delayed + IS '22g: строится с задержкой'; +COMMENT ON COLUMN domrf_developers.houses_delivered + IS '22g: сдано домов'; +COMMENT ON COLUMN domrf_developers.houses_delivered_delayed + IS '22g: сдано с задержкой'; + +COMMIT;