gendesign/memory/_append_per_flat.py
2026-04-26 15:30:02 +03:00

71 lines
8.3 KiB
Python
Raw Permalink 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.

"""Append v3 — per-flat endpoint discovery for ДОМ.РФ.
Visible script. Manually delete when done."""
import json
import pathlib
target = pathlib.Path(__file__).parent / "memory-gendesign.jsonl"
addendum = {
"type": "entity",
"name": "DataSources_DomRF_PerFlat_Apr26",
"entityType": "research",
"observations": [
"Дата: 26.04.2026 вечер. ОТКРЫТИЕ: ДОМ.РФ выдает per-квартира данные через GET /сервисы/api/kn/flats. До этого думали что только per-ЖК доступен",
"Endpoint: GET https://наш.дом.рф/сервисы/api/kn/flats?regions=66&offset=0&limit=N&flatStatus=free,booked",
"Auth: Authorization: Basic MTpqd2U= (тот же что для всех /сервисы/api/kn/*)",
"Параметры: regions (через запятую), offset, limit, flatStatus, + любые из 30+ фильтров (priceFrom/To, areaFrom/To, roomCount, type, energyEff, wall, hasParking, и т.д.)",
"По Свердл. обл. возвращает 24 441 квартиру в продаже/брони. Через offset/limit можно качать пачками (например limit=500, ~50 запросов на регион)",
"Структура per-flat (17 полей):",
"id (int) — глобальный ID квартиры в ЕИСЖС",
"odsId (string) — формат objId/sectionNum/flatNum (например '64057/2/55') — связь с реестром проектных деклараций",
"elemId (UUID) — токен для URL детальной страницы квартиры",
"type (string) — Квартира-студия / 1-комнатная / 2-комнатная / 3-комнатная / 4+комнатная / Апартаменты",
"number (string) — номер квартиры по застройщику",
"isStudio (bool)",
"totalArea (float) — общая площадь м² (это та что продаётся)",
"livingArea (float) — ЖИЛАЯ площадь м² (без коридоров/санузлов) — для финмодели важно отдельно",
"rooms (int) — количество комнат (для студии = 1)",
"status (enum) — free / booked / sold",
"price (int) — полная цена в ₽",
"pricePerSquareMeter (int) — цена м² в ₽",
"priceChangeDate (string DD-MM-YYYY HH:MM) — дата последнего изменения цены ⭐ КЛЮЧЕВОЕ ПОЛЕ для velocity",
"socialMortgage (bool) — доступна соц-ипотека",
"numberFloors (int) — этажность дома",
"floor (int) — этаж этой квартиры",
"image {attachmentId, attachmentIdSmall, hash, size, source, type} — type='layout' = планировка квартиры. URL для скачивания: /api/ext/file/{attachmentId}?type=png",
"objInfo {hobjId, objTransferPlanDt, objReady100PercDt, objCommercNm, objAddr} — связь с ЖК (плановая дата передачи + дата готовности + название + адрес)",
"🔥 priceChangeDate — это золотая жила. Снимая снапшот раз в день, мы видим:",
"1) Какие квартиры подешевели/подорожали (delta price)",
"2) Какие квартиры перешли в booked/sold (status changes)",
"3) Velocity по ЖК: кол-во квартир сменивших status за период",
"4) Эффективность ценовой стратегии: ЖК с активными priceChangeDate (последние 7 дней) vs ЖК где цены не двигались месяцами",
"Это ровно то что нужно для Plan A 'плитка 3 — за какое время продастся': вывожу absorption rate (квартир/мес) для ЖК-аналогов в радиусе 5 км → проектирую на наш концепт",
"🛠 ETL-стратегия для backend:",
"1) Daily full-refresh: для каждого региона из таргетов (66=Свердл, 50=МО, 77=Москва) → loop offset/limit с шагом 500 → upsert в таблицу dom_rf_flats",
"2) Daily delta tracking: SQL diff dom_rf_flats vs dom_rf_flats_yesterday → вычислить (a) кто перешёл в sold, (b) кому изменилась цена, (c) кто новый. Записать в dom_rf_flat_events",
"3) Materialized views: median(pricePerSquareMeter) по (region, type, year_quarter), absorption_rate по hobjId (sold quarters / 30 days)",
"📦 SQL schema для миграции (Stage 2a):",
"CREATE TABLE dom_rf_flats (id BIGINT PRIMARY KEY, ods_id VARCHAR(50), elem_id VARCHAR(64), obj_id INT, hobj_id INT, type VARCHAR(50), number VARCHAR(20), is_studio BOOL, rooms SMALLINT, total_area NUMERIC(8,2), living_area NUMERIC(8,2), price NUMERIC(12,2), price_per_sqm NUMERIC(10,2), price_change_dt TIMESTAMPTZ, status VARCHAR(20), social_mortgage BOOL, floor SMALLINT, num_floors SMALLINT, layout_attachment_id VARCHAR(80), obj_commerc_nm TEXT, obj_addr TEXT, obj_transfer_plan_dt TIMESTAMPTZ, obj_ready_dt TIMESTAMPTZ, last_synced_at TIMESTAMPTZ, raw JSONB, region_cd SMALLINT)",
"Индексы: (region_cd), (hobj_id), (status), (type), (price_per_sqm), (price_change_dt). Для радиус-запросов нужно JOIN с dom_rf_objects по hobj_id чтобы получить geom",
"CREATE TABLE dom_rf_flat_events (flat_id BIGINT, event_dt TIMESTAMPTZ, event_type VARCHAR(20), old_value JSONB, new_value JSONB, PRIMARY KEY(flat_id, event_dt)) -- тип event: 'status_change', 'price_change', 'new_listing', 'delisting'",
"💎 Что ещё можно подсосать через этот endpoint:",
"Скачивать ПЛАНИРОВКИ всех квартир (image.attachmentId, type='layout') — это набор ~24K image для Свердл. Можем использовать для AI-генерации (тренировка модели на схемах квартир) или показывать пользователю аналоги",
"objInfo.objTransferPlanDt vs objReady100PercDt — это плановая дата передачи квартир (когда фактически передают по ДДУ) vs дата готовности дома (когда строительство закончено). Лаг между ними обычно 3-6 мес и важен для absorption analysis",
"🔬 Качество данных: проверил на 2 квартирах из ЛО ('LIBERTY DOM'), цены 2.6М и 2.7М ₽, площадь 22 и 23 м² — реалистично. priceChangeDate '30-11-2025' = 5 мес. назад → значит эти квартиры в LIBERTY DOM плохо продаются (цена не двигалась)",
"Юридический статус (повтор): данные публикуются в ЕИСЖС в соответствии с 214-ФЗ как открытая информация по строящимся объектам. Использование в коммерческом продукте легально, для реестра ПО декларируем 'интеграция с ЕИСЖС ДОМ.РФ'",
],
}
relations = [
{"type": "relation", "from": "DataSources_DomRF_PerFlat_Apr26", "to": "DataSources_DomRF_Catalog_Apr26", "relationType": "extends"},
{"type": "relation", "from": "DataSources_DomRF_PerFlat_Apr26", "to": "PlanA_AnalyticsFirst_Apr25", "relationType": "fully_unblocks"},
{"type": "relation", "from": "DataSources_DomRF_PerFlat_Apr26", "to": "SalesTracking_Strategy_Apr25", "relationType": "supersedes"},
]
with target.open("a", encoding="utf-8") as f:
f.write(json.dumps(addendum, ensure_ascii=False) + "\n")
for r in relations:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f"Appended 1 entity + {len(relations)} relations")
print(f"Final line count: {sum(1 for _ in target.open('r', encoding='utf-8'))}")