gendesign/backend/app/services/site_finder
bot-backend 7d5ca247ca fix(ptica): ключ gisogd_permits — id документа на портале, а не (группа, номер) (#2986)
`UNIQUE (doc_group, doc_num)` вводился, чтобы склеивать ОДИН документ,
пришедший из двух схем портала. Замер 20.08.2026 показал, что задача,
ради которой ключ введён, почти отсутствует, а побочный эффект огромен:
docNum у ГИСОГД НЕ уникален — разрешение и изменения к нему носят один
номер.

    группа   документов   различных key   различных docNum   схлопывается
    DocRS         6098          6096            4305             1793
    DocRV         5419          5415            4969              450
    DocIZ          548           547             393              155

    общих docNum между схемами (DocRS): 2   ← ради этого ключ и вводился
    общих key    между схемами (DocRS): 2   ← те же два

На проде 9182 строки против 12 065 документов на портале — нет 23.9 %
реестра. Пример 66-06-06-2026: портал отдаёт два документа (key …719586 —
само разрешение, key …752293 — изменения к нему), а UPSERT с
предпочтением позднего date_reg оставлял только изменение. Так вытеснено
598 из 4320 строк РНС (13.8 %) — в §6 на месте разрешения показывается
изменение к нему, без признака подмены.

Ключ стал `UNIQUE (source_key)`: разделяет разрешение и изменения (разные
key) и по-прежнему склеивает настоящие межсхемные дубли (у них key
ОБЩИЙ — ровно 7 записей по всем группам). Дедуп перед сменой не нужен:
source_key на проде уже уникален (9182 из 9182, NOT NULL).

Заодно группа DocIZ добавлена в GROUP_CODE — её не было вовсе, 548
документов не грузились. CHECK расширен значением 'IZ'.

§6 сужена до РНС/РВЭ ЯВНО: агрегат обещает total_count = rs_count +
rv_count, а строки 'IZ' попадали бы в total и ни в один счётчик.
Показывать ли изменения отдельной строкой — вопрос продуктовый (#2986);
до его решения сужение стоит в запросе, а не держится на том, что таких
строк «пока нет».

Проверки:
- два гейта на лоадер (GROUP_CODE и цель ON CONFLICT) — БЕЗ базы,
  двусторонние: на origin/main дают конкретные неверные значения
  ({'DocRS','DocRV'} и старый ON CONFLICT в тексте запроса);
- гейт на §6 и контроль инварианта total = rs + rv на данных — красные
  на origin/main;
- герметичная репетиция миграции на временной копии: со старым ключом
  разрешение и изменение схлопываются в одну строку (и остаётся именно
  изменение — как на проде), после миграции живут раздельно; межсхемный
  дубль по-прежнему склеивается; CHECK принимает 'IZ' и отвергает мусор.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 22:41:27 +05:00
..
__init__.py init 2026-04-25 13:45:19 +03:00
best_layouts.py fix(ptica): лоты без площади — своя корзина, а не «<25 м²» (#2464) 2026-08-20 21:41:47 +05:00
cadastre_fetch.py fix(week-review): автофиксы код-ревью — 169 issue (label «week ревью 1») 2026-06-15 20:21:11 +05:00
competitors.py fix(site_finder): SAVEPOINT-isolate DB errors across cluster A (#2464) (#2465) 2026-07-07 12:50:11 +00:00
connection_capacity_lookup.py fix(ptica): два запроса connection-capacity перестают травить общую сессию (#2464) (#2964) 2026-08-20 09:53:34 +00:00
custom_pois.py fix(site_finder): SAVEPOINT supply/market + convert 2 bare rollbacks (#2464 Wave 2) (#2469) 2026-07-08 06:46:09 +00:00
developer_attribution.py fix(site_finder): SAVEPOINT-isolate DB errors across cluster A (#2464) (#2465) 2026-07-07 12:50:11 +00:00
district_resolver.py fix(forecasting): SAVEPOINT-isolate §22 db.execute failures + close RELEASE-trap (#2464) (#2466) 2026-07-08 05:41:35 +00:00
eesk_reserve_loader.py fix(ptica): загрузка ЦП больше не пишется в категориальную колонку 2026-08-14 10:58:35 +05:00
eias_heat_loader.py fix(ptica): загрузчик теплоснабжения фиксирует по организациям, а не одной транзакцией (#2464) (#2973) 2026-08-20 11:51:57 +00:00
ekgas_outlet_loader.py feat(ekgas): геокод точек выхода через DaData (#2119 B2, PR-3/4) (#2249) 2026-07-03 03:19:18 +00:00
filters.py init 2026-04-25 13:45:19 +03:00
functional_zone_lookup.py fix(ptica): шесть lookup'ов /analyze глотали ошибку БД без SAVEPOINT (#2464) (#2951) 2026-08-20 07:38:23 +00:00
future_supply.py fix(site_finder): SAVEPOINT supply/market + convert 2 bare rollbacks (#2464 Wave 2) (#2469) 2026-07-08 06:46:09 +00:00
gate_verdict.py fix(gate): area-gate ЗОУИТ utility-easement blocker (sub-17) — снять over-block 2026-06-24 14:45:59 +05:00
gazprom_grs_loader.py fix(site-finder): три честности-фикса — B1 ГРС «выход №N» + околонуль + Росреестр-квартал (#2257) 2026-07-03 06:27:27 +00:00
granddoc_lookup.py fix(ptica): шесть lookup'ов /analyze глотали ошибку БД без SAVEPOINT (#2464) (#2951) 2026-08-20 07:38:23 +00:00
ird_analyze.py fix(site_finder): SAVEPOINT-isolate gate-lookup db.execute swallows (#2464 Wave 2) (#2468) 2026-07-08 06:46:03 +00:00
ird_overlay_lookup.py fix(ptica): шесть lookup'ов /analyze глотали ошибку БД без SAVEPOINT (#2464) (#2951) 2026-08-20 07:38:23 +00:00
krt_lookup.py fix(site_finder): SAVEPOINT-isolate gate-lookup db.execute swallows (#2464 Wave 2) (#2468) 2026-07-08 06:46:03 +00:00
layout_signature.py fix(sf-08): add euro-1/euro-2 room_bucket for small-format rooms=2 flats 2026-05-17 14:38:48 +03:00
layout_velocity_refresh.py feat(analytics): mv_layout_velocity MV + refresh helper (#113 PR B) (#194) 2026-05-16 08:26:59 +00:00
locations.py fix(site_finder): SAVEPOINT-isolate gate-lookup db.execute swallows (#2464 Wave 2) (#2468) 2026-07-08 06:46:03 +00:00
macro.py feat(macro): ЕМИСС среднедушевые доходы (id=57039) -> macro_indicator (#946 part2) 2026-06-07 18:44:38 +05:00
market_metrics.py fix(site_finder): SAVEPOINT supply/market + convert 2 bare rollbacks (#2464 Wave 2) (#2469) 2026-07-08 06:46:09 +00:00
network_obremenenie.py feat(site-finder): сетевые обременения из ЗОУИТ (#1070, Refs #103) (#1328) 2026-06-14 14:49:32 +00:00
network_zone_humanize.py feat(site-finder): позитив-разрез сетевых ЗОУИТ «сеть рядом с участком» (Фаза C2/C3) (#2281) 2026-07-03 08:18:24 +00:00
noise_loader.py fix(week-review): автофиксы код-ревью — 169 issue (label «week ревью 1») 2026-06-15 20:21:11 +05:00
okn_lookup.py fix(site_finder): SAVEPOINT-isolate gate-lookup db.execute swallows (#2464 Wave 2) (#2468) 2026-07-08 06:46:03 +00:00
ors_client.py fix(ptica): нечисловое значение от OSRM/ORS больше не даёт 500 вместо деградации (#2464) (#2944) 2026-08-19 17:20:28 +00:00
osm_tp_geocoder.py feat(site-finder): OSM-геокод городских ТП/РП ЕКБ (#2119 B2, финальный хвост) (#2252) 2026-07-03 05:22:02 +00:00
osrm_client_local.py fix(ptica): нечисловое значение от OSRM/ORS больше не даёт 500 вместо деградации (#2464) (#2944) 2026-08-19 17:20:28 +00:00
own_portfolio.py fix(site_finder): SAVEPOINT supply/market + convert 2 bare rollbacks (#2464 Wave 2) (#2469) 2026-07-08 06:46:09 +00:00
parcel_financial.py feat(site-finder): оптимизатор программ — «Как участок сходится» при отрицательном вердикте (#2181) (#2222) 2026-07-02 20:57:12 +00:00
parser.py init 2026-04-25 13:45:19 +03:00
pat_lookup.py fix(site_finder): SAVEPOINT-isolate DB errors across cluster A (#2464) (#2465) 2026-07-07 12:50:11 +00:00
permits_nearby.py fix(ptica): ключ gisogd_permits — id документа на портале, а не (группа, номер) (#2986) 2026-08-20 22:41:27 +05:00
planning_lookup.py fix(ptica): шесть lookup'ов /analyze глотали ошибку БД без SAVEPOINT (#2464) (#2951) 2026-08-20 07:38:23 +00:00
poi_loader.py fix(tradein/location): заменить сломанный коэффициент локации на калиброванный индекс (#2531) 2026-07-26 21:48:15 +00:00
poi_score.py fix(site-finder): normalize POI weighted score to 0..100 on backend (#1486) 2026-06-17 20:26:35 +03:00
ppt_tep_lookup.py fix(site_finder): SAVEPOINT-isolate gate-lookup db.execute swallows (#2464 Wave 2) (#2468) 2026-07-08 06:46:03 +00:00
premises_lookup.py fix(site_finder): SAVEPOINT-isolate gate-lookup db.execute swallows (#2464 Wave 2) (#2468) 2026-07-08 06:46:03 +00:00
program_optimizer.py feat(site-finder): оптимизатор программ — «Как участок сходится» при отрицательном вердикте (#2181) (#2222) 2026-07-02 20:57:12 +00:00
pzz_loader.py fix(pzz_loader): replace bare db.rollback() with SAVEPOINT per-row (#124) 2026-05-14 23:29:23 +03:00
quarter_dump_lookup.py fix(ptica): пустой слой риск-зон перестаёт означать «рисков нет» (#2934) (#2940) 2026-08-19 16:34:26 +00:00
quarter_price_index_refresh.py fix(week-review): backend-аудит v2 — 82 фиксов (#1660) 2026-06-17 17:13:38 +00:00
quarter_price_refresh.py feat(analytics): mv_quarter_price_per_m2 MV + refresh helper (#33 D1 PR A) (#209) 2026-05-16 11:12:01 +00:00
reservation_lookup.py fix(ptica): шесть lookup'ов /analyze глотали ошибку БД без SAVEPOINT (#2464) (#2951) 2026-08-20 07:38:23 +00:00
riasurt_lookup.py fix(site_finder): SAVEPOINT-isolate gate-lookup db.execute swallows (#2464 Wave 2) (#2468) 2026-07-08 06:46:03 +00:00
rosseti_reserve_loader.py fix(site-finder): Россети-раскрытие — URL-энкоженные href, ссылки на xlsx не находились (#2121) 2026-07-02 09:36:14 +00:00
rosseti_wfs_loader.py feat(site-finder): резервы мощности для ТП — электро+вода на карте §3 (#2119 Фаза A) (#2120) 2026-07-02 08:36:51 +00:00
sales_tracker_mv_refresh.py chore(sql): renumber sales-tracker MV migration 161→164 (avoid collision) 2026-06-17 22:36:39 +03:00
saturation.py fix(site_finder): SAVEPOINT-isolate DB errors across cluster A (#2464) (#2465) 2026-07-07 12:50:11 +00:00
scorer.py init 2026-04-25 13:45:19 +03:00
supply_layers.py fix(site_finder): SAVEPOINT-isolate DB errors across cluster A (#2464) (#2465) 2026-07-07 12:50:11 +00:00
utility_infrastructure_loader.py fix(site-finder): true-counts вместо молча-усечённых len(list) в connection-points/utility (#2445-A1/A2) (#2450) 2026-07-05 18:54:49 +00:00
velocity.py fix(ptica): фильтр класса в velocity ссылался на алиас, которого нет в CTE (#2464-G) (#2865) 2026-08-13 17:46:27 +00:00
vodokanal_reserve_loader.py fix(vodokanal): column-aligned парсинг DOCX — реальные резервы вместо № п/п (#2119/#2126) (#2238) 2026-07-02 22:03:15 +00:00
weight_profiles.py fix(ptica): у пользователя не может быть двух дефолтных профилей весов (#2464) 2026-08-20 16:58:48 +05:00
zone_regulation.py fix(ptica): шесть lookup'ов /analyze глотали ошибку БД без SAVEPOINT (#2464) (#2951) 2026-08-20 07:38:23 +00:00