8.2 KiB
ГАР/ФИАС loader знаменателя «квартир на дом» (runbook)
Наполняет таблицу gar_house_flats числом помещений-квартир под каждым домом из
официального дампа ГАР/ФИАС и матчит к houses по нормализованному адресу, проставляя
houses.gar_flat_count. Это «знаменатель» для страницы «доля квартир дома в продаже»
(active_vtorichka_listings / total_apartments_in_building, view v_building_sale_share,
мигр. 143).
Код:
tradein-mvp/backend/app/services/gar_flats_loader.py— стриминговый парсер + upsert + матчер.tradein-mvp/backend/app/tasks/gar_flats_load.py— CLI (python -m app.tasks.gar_flats_load).
1. Получить ГАР-дамп региона 66 (ops-шаг, вручную)
Лоадер потребляет уже распакованные XML локально — многогигабайтную загрузку/распаковку делаем отдельно (это не часть лоадера).
-
Источник: ГАР (Государственный адресный реестр), портал ФНС — https://fias.nalog.ru/ (раздел «Скачать»). Берём «ГАР. Версия со всеми объектами» — формат XML,
gar_xml.zip.- Полный РФ-архив
gar_xml.zip— десятки ГБ. Нам нужен только регион 66 (Свердловская обл.). - Внутри архива объекты разложены по папкам с кодом региона: распаковываем только папку
66/. Пример (извлечь одну папку без распаковки всего архива):unzip gar_xml.zip '66/*' -d /data/gar # → /data/gar/66/AS_ADDR_OBJ_*.XML, AS_HOUSES_*.XML, AS_APARTMENTS_*.XML, # AS_MUN_HIERARCHY_*.XML, ... - Размер распакованной папки
66/: ориентир — несколько ГБ (крупнейшие файлы —AS_APARTMENTS_*иAS_MUN_HIERARCHY_*, по сотни МБ — единицы ГБ). Поэтому парсер стримит (lxml.etree.iterparse+ очистка элементов), а не грузит дерево целиком.
Альтернатива: ГАР обновляется примерно еженедельно дельтами; нам достаточно полного среза. Знаменатель «всего квартир в доме» почти статичен → ре-прогон раз в квартал ок.
- Полный РФ-архив
Нужные файлы в папке региона (остальные лоадер игнорирует):
AS_ADDR_OBJ_*.XML— адресные объекты (регион/город/улица).AS_HOUSES_*.XML— дома.AS_APARTMENTS_*.XML— помещения/квартиры.AS_MUN_HIERARCHY_*.XML— иерархия (приоритетна; при отсутствии используетсяAS_ADM_HIERARCHY_*).
2. Запуск лоадера
Где: в окружении с доступом к БД tradein (контейнер tradein-backend или локальный venv с
DATABASE_URL на tradein). Каталог --dir — путь к распакованной папке региона.
# в контейнере tradein-backend (DATABASE_URL уже выставлен)
docker exec -it tradein-backend \
python -m app.tasks.gar_flats_load --dir /data/gar/66 --region 66 --version 2026-06-01
Локально:
cd tradein-mvp/backend
DATABASE_URL=postgresql+psycopg://<user>:<pw>@localhost:<port>/tradein \
uv run python -m app.tasks.gar_flats_load --dir /data/gar/66 --region 66 --version 2026-06-01
Аргументы:
--dir(обязательный) — каталог с распакованными ГАР XML региона.--region(умолч.66) — код региона; пишется вgar_house_flats.region_code.--version(умолч. сегодня,YYYY-MM-DD) — метка версии дампа (gar_house_flats.gar_version).
Что делает (две фазы, обе коммитятся):
- load — стримит XML → считает помещения на дом (по иерархии) → собирает адрес →
UPSERT в
gar_house_flats(ON CONFLICT (house_guid), идемпотентно).norm_addressсчитает SQL-fntradein_normalize_short_addrповерх «улица, номер». - match —
UPDATE houses SET gar_flat_count = …по нормализованному адресу (gar_match_method='norm_address'), идемпотентно (IS DISTINCT FROMgate).
В логах — сводка: houses, apartments (учтено под домами), upserted, houses_matched.
3. Проверка результата
-- сколько домов получили ГАР-знаменатель
SELECT count(*) FILTER (WHERE gar_flat_count IS NOT NULL) AS matched,
count(*) AS total
FROM houses;
-- топ домов по доле квартир в продаже (эффективный знаменатель из ГАР)
SELECT short_address, active_secondary, flat_count_effective, sale_share_pct, gar_match_method
FROM v_building_sale_share
WHERE gar_flat_count IS NOT NULL
ORDER BY sale_share_pct DESC NULLS LAST
LIMIT 20;
4. Семантика и ограничения
- flat_count = число активных (
ISACTUAL=1 AND ISACTIVE=1) помещений ГАР, чейPARENTOBJIDв иерархии ==OBJECTIDдома. Тип помещения (APARTTYPE) НЕ фильтруется — это «всего помещений в доме» (квартиры + нежилые), сознательное приближение знаменателя. - Адрес/норма.
norm_address=tradein_normalize_short_addr(«<улица>, <номер>»)— тот же нормализатор, что и дляhousesпри матче. Строка строится из «улица + номер» (не из полной цепочки) для устойчивости: для ЕКБ эквивалентноfn(full_address)(fn срезает регион/город), но не оставляет префикс для не-«г.» населённых пунктов.full_addressхранится отдельно как человекочитаемая provenance-цепочка (регион→город→улица→номер). - Tie-break. Если несколько ГАР-строк дают один
norm_address, матчер берёт строку с максимальнымflat_count(при равенстве — лексикографически меньшийhouse_guid), черезDISTINCT ON (norm_address) ORDER BY norm_address, flat_count DESC, house_guid. - Дома с 0 квартир грузятся (
flat_count=0), но матчер их игнорирует (flat_count > 0). - Покрытие матча — точное равенство нормализованного адреса. Расхождения формата типа
улицы (
улvsул.vsулица) и корпусов (5 к1vs5/1) не совпадут точно. Это ожидаемо: подgar_house_flats.norm_addressуже создан GIN-trgm индекс (мигр. 143) для будущего нечёткого (fuzzy) прохода — он вне scope этого лоадера.
5. Идемпотентность / ре-прогон
gar_house_flats—ON CONFLICT (house_guid) DO UPDATE→ повторный прогон обновляет, не дублит.- Матчер — plain
UPDATEсIS DISTINCT FROM→ повторный прогон ничего не трогает. - Ре-прогон с новым
--versionбезопасен; рекомендуемая частота — раз в квартал.