Commit graph

223 commits

Author SHA1 Message Date
6a2d873c70 fix(rosseti): считать координату ключа в SQL тем же double, что в питоне
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI / changes (pull_request) Successful in 14s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m24s
CI / backend-tests (pull_request) Successful in 17m41s
Ревью PR #3329. round(ST_X(geom)::numeric * 100000) округляет по кратчайшему
десятичному представлению float8, а питон — по двоичному double: расхождение на
0.19% реальных координат (761 из 400000), напр. 64.423605 → питон 6442360
(6442360.499999999), numeric-путь 6442361. Каждое расхождение = вечный дубль ЦП,
который сам не зарастёт — миграция применяется один раз (_schema_migrations).
Теперь в SQL sign/floor/abs над float8 без каста в numeric: IEEE754 бит в бит
как math.floor в питоне.

test_coord_e5 брал 60.123455, где двоичное и десятичное округление совпадают —
защита, которая не защищает. Добавлено расходящееся значение 64.423605.

RAISE WARNING при rows_after > 700 заменён на RAISE EXCEPTION: warning не
останавливает прогон, файл помечался бы applied навсегда вместе с дублями.

Refs #3322
2026-09-02 14:51:47 +05:00
b35f444d50 fix(rosseti): стабильный external_id ЦП вместо сессионного fid GeoServer
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m22s
CI / backend-tests (pull_request) Successful in 17m46s
feature['id'] в WFS-ответе — сессионный fid, новый на каждый GetFeature.
ON CONFLICT (source, external_id) не срабатывал ни разу, каждый weekly-прогон
дописывал полный комплект ~488 фич: 4880 строк на 481 ЦП. Задуманный sha1-фолбэк
по атрибутам был мёртв — fid присутствует всегда.

Ключ теперь считается ТОЛЬКО по стабильным атрибутам (нормализованное имя, класс
напряжения, координаты в 1e-5 градуса), fid игнорируется. Координата квантуется
в целое, а не форматируется как float: ключ обязан совпадать байт-в-байт с
бэкфиллом в SQL. sha256 вместо sha1 — встроен в PG16, pgcrypto не нужен.

Починка разбора старые строки не убирает (ключи не совпадут, ON CONFLICT ничего
не перезапишет) → data/sql/99c_power_supply_centers_dedup.sql: пересчёт ключа
существующих строк + схлопывание копий (победитель — свежайший snapshot,
NULLS LAST явно), с печатью чисел до/после и идемпотентностью.

Refs #3322
2026-09-02 14:43:58 +05:00
84505b1e0b chore(site-finder): удалить мёртвый код — 14 неиспользуемых символов (#3235)
Some checks failed
Deploy / changes (push) Successful in 8s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 2m30s
Deploy / build-worker (push) Successful in 3m39s
Deploy / deploy (push) Successful in 1m18s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Failing after 12s
2026-08-29 15:39:10 +00:00
cdf493f345 chore(format): нормализация под ruff 0.15.20 — 161 файл, только формат (#2864) (#3022)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy Trade-In / changes (push) Successful in 13s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy / build-backend (push) Successful in 2m23s
Deploy Trade-In / test (push) Successful in 3m56s
Deploy / build-worker (push) Successful in 4m16s
Deploy Trade-In / build-backend (push) Successful in 1m19s
Deploy / deploy (push) Successful in 1m49s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 12s
Deploy Trade-In / deploy (push) Successful in 2m25s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-08-21 12:01:52 +00:00
ccf84b4adc fix(best-layouts): avg_area_m2 = NULL вместо 0 при пустом окне сделок (#2867) (#3018)
Some checks failed
Deploy / perimeter-smoke (push) Blocked by required conditions
Deploy / deploy-status (push) Blocked by required conditions
Deploy / changes (push) Successful in 11s
Deploy / build-backend (push) Successful in 2m48s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-worker (push) Successful in 5m23s
Deploy / build-frontend (push) Successful in 4m18s
Deploy / deploy (push) Has been cancelled
2026-08-21 10:27:02 +00:00
5cd3502493 fix(ptica): три места, где код делал не то, что говорил (#2464)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 2m10s
Deploy / build-worker (push) Successful in 3m22s
Deploy / deploy (push) Successful in 1m33s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 9s
Часть эпика #2464.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 19:18:00 +00:00
b12f506953 docs(ptica): две докстроки обещали то, чего в коде нет (#2464)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 1m55s
Deploy / build-worker (push) Successful in 3m47s
Deploy / deploy (push) Successful in 1m22s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 9s
Часть эпика #2464.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 19:08:32 +00:00
f227768a53 fix(ptica): три места, где код делал не то, что говорил (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m17s
CI / backend-tests (pull_request) Successful in 17m20s
1. `load_water_reserves_from_docx` собирал `result` с ключом `period`,
   печатал ПОЛНЫЙ словарь в лог, а возвращал
   `{k: v for … if isinstance(v, int)}` — период это строка или None,
   поэтому выбрасывался всегда. По логам казалось, что период отдаётся;
   вызывающий не получал его ни разу.

   Фильтр стоял ради аннотации `dict[str, int]` и ничего не защищал:
   соседняя ветка `load_water_reserves` кладёт в тот же словарь
   `{"error": str(...)}`, а единственный потребитель — задача
   `sync_water_reserves` — результат логирует и возвращает как есть.
   Период полезен: без него «загружено 42 записи» не отличить от
   прошлогодних. Аннотация исправлена, иначе следующий проход mypy вернул
   бы фильтр обратно — на это поставлен отдельный тест.

2. `get_sqlite_info` — TOCTOU: `p.exists()`, затем незащищённый
   `p.stat()`. try/except покрывал только `sqlite3.connect` ниже, поэтому
   OSError из stat улетал наружу и превращал диагностическую функцию в
   источник отказа. Файл между проверками реально исчезает — его
   переписывает выгрузка Объектива. Теперь отдаём то, что успели узнать,
   с ключом `stat_error`.

3. `place_program` — предупреждение «участок мал» печатало КАТАЛОЖНЫЕ
   `house.footprint_*`, хотя ставили по `fp_w`/`fp_d`. При
   переопределённом в программе габарите сообщение называло размер,
   которым никто не пытался ставить, и уводило от причины.

Двусторонне: против origin/main четыре теста красные с конкретными
значениями («период выброшен из ответа: {'records': 1, 'inserted': 1,
'updated': 0}», «аннотация всё ещё требует только int: dict[str, int]»).

Контроли зелёные с обеих сторон: отсутствующий файл по-прежнему даёт
exists=False без ошибки; `fp_w`/`fp_d` — действительно те размеры,
которыми ставят (иначе первый тест сверял бы имена, а не смысл).

pytest backend/tests/services/ — 3207 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 23:59:02 +05:00
c979cf886a fix(ptica): подпись сетевого обременения называет все виды, а не первый (#2464)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 2m49s
Deploy / build-worker (push) Successful in 6m39s
Deploy / deploy (push) Successful in 1m53s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 13s
Часть эпика #2464.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 18:53:32 +00:00
c3c8674b3c docs(ptica): две докстроки обещали то, чего в коде нет (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m4s
CI / backend-tests (pull_request) Successful in 17m21s
1. `QuarterDump` (nspd_client): «Default = только core, чтобы не сжигать
   rate-limit на 17 запросов». Фактический дефолт `search_by_quarter` —
   `include_zouit=True`, то есть 5 ЗОУИТ-слоёв входят в дефолтный вызов.
   Числа 17 тоже нет: territorial_zones/red_lines/engineering и все ЗОУИТ
   идут через grid-walk при grid_n=7, по 49 запросов КАЖДЫЙ — дефолтный
   дамп это сотни запросов. Экономит rate-limit только include_risks=False.
   Докстрока самого метода 640 строками ниже говорит верно («Default
   True») — правильный образец лежал рядом с дефектом.

2. `find_active_on_demand_job` (cadastre_fetch): «Если в БД есть FAILED
   on-demand за последние 60 секунд — тоже None». В SQL нет ни слова
   'failed', ни какого-либо временного фильтра. Обещание вдвойне вредно:
   подразумевало, что неуспешная джоба СТАРШЕ минуты вернётся как
   активная (не вернётся), и отправляло отлаживающего искать окно,
   которого нет.

Гейты сверяют утверждение докстроки с кодом, а не читаемость текста:
обещание «только core» требует `include_zouit=False` в сигнатуре;
обещание минутного окна требует временного фильтра в теле.

Двусторонне: против origin/main три гейта красные с конкретными
сообщениями. Контроли зелёные с обеих сторон — характеризующий фиксирует
фактические три статуса в SQL, а test_docstrings_state_the_actual_behaviour
ловит «починку» через вычёркивание неудобной фразы.

Два подводных камня, на которые наступил и оставил защиту:
- гейт ищет обещание по тексту, поэтому старые формулировки в докстроках
  ПЕРЕСКАЗАНЫ, а не процитированы — иначе он не отличает цитату от
  утверждения (оговорено прямо в тексте докстроки);
- тело функции нельзя брать как последний кусок разбиения по тройным
  кавычкам: SQL сам в них обёрнут, и проверка шла бы по огрызку после
  запроса. Из-за этого один гейт проходил по случайности. Вынесен
  хелпер `_body`.

pytest backend/tests/services/ — 3199 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 23:43:40 +05:00
b1dc5d5507 fix(ptica): подпись сетевого обременения называет все виды, а не первый (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m0s
CI / backend-tests (pull_request) Successful in 17m6s
`cad_utility_label` брался от ПЕРВОГО overlap'а с распознанным
`network_kind`, а покрытие агрегировалось по ВСЕМУ bucket'у. Участок под
двумя видами охранных зон получал в отчёте конкретную причину, дающую
малую часть площади:

    «Сетевое обременение (теплоснабжение) покрывает 74% участка»
     ← теплоснабжение даёт 10%, остальные 64% — инженерные коммуникации

Это не редкость. Прод 20.08.2026: 316 пересечений охранных зон РАЗНЫХ
видов, 155 зон вовлечено; самая частая пара — «тепловых сетей» ×
«инженерных коммуникаций» (296 из 316).

Теперь копятся ВСЕ различённые виды и называются через запятую;
множественное число, когда их больше одного. Покрытие — их объединение,
и подпись это отражает.

Заодно снята зависимость от порядка: подпись бралась от первого overlap'а,
а он определяется `ORDER BY reg_numb_border, id`, который к покрытию
отношения не имеет. Виды сортируются.

Проверка попутно опровергла ДОВОД пункта эпика. Пункт говорит о смешении
сетевых зон с keyword-совпадениями без network_kind. На проде таких ноль:
из 3493 строк cad_zouit — 103 СЗЗ-предупреждения, 1936 с распознанной
сетью, 1454 generic warning, и НИ ОДНОЙ «только по ключевому слову»
(classify_network_zone уже покрывает все встречающиеся шаблоны). Вывод
пункта — «конкретная причина приписывается чужой площади» — верен, но по
другой причине: смешиваются РАЗНЫЕ ВИДЫ СЕТЕЙ, а не сети с keyword'ами.

Двусторонне: против origin/main три теста красные, головной — с полной
строкой, которую увидел бы пользователь. Контроли зелёные с обеих сторон:
один вид сохраняет прежнюю формулировку в единственном числе, две зоны
одного вида не дают дубль в подписи, area-gate не тронут (тонкая полоса
остаётся warning).

pytest test_gate_verdict + services/site_finder — 727 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 23:30:09 +05:00
06b0063c67 fix(ptica): невозможные параметры регламента не превращаются в деньги (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m56s
CI / backend-tests (pull_request) Successful in 17m8s
`synthesize_teap_from_buildability` проверяла параметры только на `> 0`.
Процент застройки 150 давал пятно БОЛЬШЕ участка (10 000 м² → 15 000 м²),
а дальше — жилую площадь, число квартир и выручку: физически невозможные
числа, поданные как обычные цифры финмодели. КСИТ 500 давал GFA
5 000 000 м² на гектаре.

Параметры приходят из ПЗЗ-регламента (`zone_regulation_cache`) — внешние
разобранные данные, то есть граница доверия.

Невозможное значение ОТБРАСЫВАЕТСЯ, а не роняет расчёт: если рядом есть
КСИТ, GFA считается по нему и остаётся верной. Лучше отсутствие
параметра, чем неверный. Если вменяемых не осталось — None, и caller
штатно показывает отсутствие финоценки с caveat, а не ноль.

Границы взяты с запасом к реальным данным прода 20.08.2026 (33 строки
zone_regulation_cache: pct 0..100, far 1..4, floors 0..5): pct ≤ 100,
far ≤ 30, этажей ≤ 100 — сито против порчи разбора, а не норматив.

ВТОРОЙ дефект, найденный этими же тестами и существовавший до правки:
ветка «нет ни процента, ни этажности → пятно = GFA» неявно предполагает
один этаж, и при КСИТ > 1 давала пятно больше участка (10 000 м² с far=2
→ 20 000 м²). Добавлен физический инвариант «пятно ≤ участок» — не
эвристика, а геометрия, и стоит он ОДИН раз после всех ветвей, чтобы
держаться и для будущих способов оценки пятна. GFA при этом не меняется.

Двусторонне: против origin/main четыре теста красные с конкретными
невозможными значениями («пятно 15000.0 больше участка 10000.0»,
«GFA=5000000.0»). Восемь контролей зелёные с обеих сторон — среди них
пять сочетаний (pct, far, floors), взятых ДОСЛОВНО с прода, и граница
100 % застройки, которая законна и на проде есть.

pytest test_parcel_financial + services/generative + новый файл — 185 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 23:14:38 +05:00
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
1b2a26bb9a fix(ptica): лоты без площади — своя корзина, а не «<25 м²» (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m27s
CI / backend-tests (pull_request) Successful in 17m13s
`_SUPPLY_ONLY_LOTS_SQL` раскладывал `area_pd IS NULL` в ту же корзину
`'<25'`, что и настоящие студии. Замер прода 20.08.2026 (последний
снапшот на физлот, premise_kind='квартира', не проданные):

    в продаже            181 353
    без area_pd           11 557   (6.4 %)
    реально < 25 м²        7 013

Корзина «<25» состояла из неизвестного на 62 % и завышала долю мелких
лотов в блоке «По предложению (без темпа продаж)».

Зеркала у такого отображения не было: `layout_signature.area_bin`
принимает float и NULL-ветки не имеет вовсе, а velocity-MV по площади
не группирует — то есть `NULL → '<25'` было выдумкой, а не переносом
чужого правила.

Исключать такие лоты нельзя: они реально в продаже, и без них
предложение занизилось бы на 6.4 %. Поэтому отдельная корзина «н/д».
Медиана площади у неё выйдет NULL (PERCENTILE_CONT игнорирует NULL) —
честно. Схема не меняется: area_bin остаётся str, OpenAPI прежний.

Тест герметичный и прогоняет НАСТОЯЩИЙ SQL: временная таблица
objective_lots затеняет боевую в пределах сессии, запрос берётся из
модуля дословно, прод-данные не читаются.

Двусторонне: против origin/main корзины распределяются как
{'<25': 2, '25-40': 1, '40-60': 1} — конкретное неверное значение, ни
одного TypeError/ImportError. Контроли (сумма лотов сохраняется,
обычные корзины не меняются) зелёные с обеих сторон.

pytest backend/tests/sql/ — 38 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 21:41:47 +05:00
afa648b21f fix(ptica): у пользователя не может быть двух дефолтных профилей весов (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m26s
CI / backend-tests (pull_request) Successful in 17m14s
create_profile/update_profile делают «снять is_default у всех → поставить новому»
двумя отдельными операторами. Между ними инвариант нарушен, и при одновременных
запросах у пользователя может оказаться ДВА профиля с is_default=TRUE. А читающий
_SELECT_DEFAULT брал LIMIT 1 БЕЗ ORDER BY — выбор молча перескакивал между ними от
запроса к запросу.

Два рубежа, а не один:

  миграция 190 — частичный уникальный индекс (user_id) WHERE is_default: два
                 дефолта становятся невозможными на уровне БД;
  ORDER BY id  — детерминированный выбор, если индекс когда-нибудь снимут.
                 Соседние запросы этого файла тай-брейк по id уже имеют.

Индекс не мешает штатной переустановке дефолта: порядок операторов в коде уже
правильный (сначала снять у всех, потом поставить), поэтому в момент проверки
дефолтов ноль. Это отдельно проверено тестом.

Безопасность миграции: на проде нарушений нет — у admin один дефолт, у __system__
ноль, ни одного пользователя с двумя. Таблица в 4 строки, индексируется мгновенно.
lock_timeout проставлен по #2752.

Тест проверяет ПОВЕДЕНИЕ на живом Postgres: вторая установка дефолта отвергается
базой. Плюс фальсификация — без индекса два дефолта вставляются молча; без неё
зелёный тест неотличим от «оно и так не вставлялось». Плюс два контроля:
переустановка дефолта работает, разные пользователи сохраняют свои.

Тест про ORDER BY вынесен в tests/services/site_finder, а НЕ внесён в
skip_allowlist: живой БД он не требует, и пропускаться вместе с DB-тестами ему
незачем. Против origin/main он краснеет, показывая запрос без тай-брейка.

Прогоны: без БД — 648 passed rc=0; с БД — 5 passed rc=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 16:58:48 +05:00
9b18c23a5a fix(ptica): загрузчик теплоснабжения фиксирует по организациям, а не одной транзакцией (#2464) (#2973)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 2m26s
Deploy / build-worker (push) Successful in 3m29s
Deploy / deploy (push) Successful in 1m29s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 8s
2026-08-20 11:51:57 +00:00
7abc0ed431 fix(ptica): два запроса connection-capacity перестают травить общую сессию (#2464) (#2964)
Some checks failed
Deploy / deploy (push) Blocked by required conditions
Deploy / perimeter-smoke (push) Blocked by required conditions
Deploy / deploy-status (push) Blocked by required conditions
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Has been cancelled
Deploy / build-worker (push) Has been cancelled
2026-08-20 09:53:34 +00:00
c93d6cbde1 fix(ptica): шесть lookup'ов /analyze глотали ошибку БД без SAVEPOINT (#2464) (#2951)
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 2m15s
Deploy / build-worker (push) Successful in 3m14s
Deploy / deploy (push) Successful in 1m28s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 10s
2026-08-20 07:38:23 +00:00
56868f2bde fix(ptica): нечисловое значение от OSRM/ORS больше не даёт 500 вместо деградации (#2464) (#2944)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 2m16s
Deploy / build-worker (push) Successful in 6m44s
Deploy / deploy (push) Successful in 1m31s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 9s
2026-08-19 17:20:28 +00:00
5a7d558a5c fix(ptica): пустой слой риск-зон перестаёт означать «рисков нет» (#2934) (#2940)
All checks were successful
Deploy / changes (push) Successful in 11s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 3m10s
Deploy / build-frontend (push) Successful in 4m33s
Deploy / build-worker (push) Successful in 4m42s
Deploy / deploy (push) Successful in 1m50s
Deploy / deploy-status (push) Successful in 2s
Deploy / perimeter-smoke (push) Successful in 13s
2026-08-19 16:34:26 +00:00
fe019f26ee fix(ptica): загрузка ЦП больше не пишется в категориальную колонку
All checks were successful
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m19s
CI / backend-tests (pull_request) Successful in 16m23s
Лоадер ЕЭСК писал степень загрузки из колонки E как
`load_index = COALESCE(load_index, CAST(:load_pct AS text))`.
load_index — категориальная: 'open'|'limited'|'closed'|NULL
(data/sql/180_connection_capacity.sql:35), её заполняет
rosseti_wfs_loader._map_load_index.

Число строкой в этой колонке ломает обе стороны: фронтовый classifyLoadIndex
отбрасывает всё вне перечисления в null («неизвестно»), а
power_summary.by_load_index — словарь по значению, то есть получил бы бакет
с именем вида "41.0" рядом с open/limited/closed.

Сегодня не стреляло только потому, что load_index заполнен у всех строк
(open 2741 / limited 346 / closed 329, NULL 0 — замер верификации 13.08,
подтверждён вторым прогоном скептика), и COALESCE не проваливался. Первая же
строка с пустым индексом положила бы туда число.

Колонку E больше не читаем: места под процент в power_supply_centers нет —
load_index категориальный, current_load_mva в мегавольт-амперах.

_pct_share_to_percent оставлен с тестами, но в докстроке теперь прямо
написано, что продакшен-вызывающих у него НЕТ и при каких условиях он снова
понадобится — чтобы «код есть, эффекта нет» не выглядел работающим.

Старый тест фиксировал ровно отменяемое поведение (`first["load_pct"] == 41.0`)
— заменён на проверку, что ни SQL, ни параметры загрузку не несут. Проверять
пришлось исполняемый текст, а не прозу: слово load_index осталось в поясняющем
комментарии, и наивная проверка на подстроку падала на своём же объяснении.

Тесты двусторонние: против лоадера из main падает ровно новый.

Хунк форматирования — не мой: pre-commit ruff v0.7.4 против 0.15.12 (#2864).

Refs #2464
2026-08-14 10:58:35 +05:00
c40261cb16 fix(ptica): фильтр класса в velocity ссылался на алиас, которого нет в CTE (#2464-G) (#2865)
All checks were successful
Deploy / changes (push) Successful in 12s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m35s
Deploy / build-worker (push) Successful in 3m45s
Deploy / deploy (push) Successful in 1m58s
2026-08-13 17:46:27 +00:00
53bb769ea5 fix(ptica): нет сделок за окно — цена не 0 ₽/м², а «нет данных» (#2464-B) (#2868)
All checks were successful
Deploy / changes (push) Successful in 11s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m48s
Deploy / build-worker (push) Successful in 3m5s
Deploy / deploy (push) Successful in 1m20s
2026-08-13 12:26:51 +00:00
1307d55da6 fix(site-finder): метка источника весов выводится из результата резолва, а не из входа (#2811) (#2817)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m52s
Deploy / build-worker (push) Successful in 3m0s
Deploy / deploy (push) Successful in 1m30s
2026-08-10 10:34:39 +00:00
580be61914 fix(tradein/location): заменить сломанный коэффициент локации на калиброванный индекс (#2531)
All checks were successful
Deploy / build-backend (push) Successful in 6m30s
Deploy / build-worker (push) Successful in 6m44s
Deploy / changes (push) Successful in 11s
Deploy Trade-In / changes (push) Successful in 15s
Deploy Trade-In / build-backend (push) Successful in 1m19s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy (push) Successful in 2m26s
Deploy Trade-In / deploy (push) Successful in 3m10s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 3m6s
Deploy Trade-In / test (push) Successful in 5m5s
2026-07-26 21:48:15 +00:00
f987e819b2 fix(honesty): true competitors/neighbors totals + permits truncation disclosure (#2464 cluster B) (#2471)
All checks were successful
Deploy / changes (push) Successful in 10s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m6s
Deploy / build-worker (push) Successful in 3m13s
Deploy / deploy (push) Successful in 1m38s
2026-07-08 08:31:02 +00:00
d3f3370bed fix(site_finder): SAVEPOINT supply/market + convert 2 bare rollbacks (#2464 Wave 2) (#2469)
All checks were successful
Deploy / changes (push) Successful in 10s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m30s
Deploy / build-worker (push) Successful in 3m45s
Deploy / deploy (push) Successful in 1m54s
2026-07-08 06:46:09 +00:00
01360e9c3d fix(site_finder): SAVEPOINT-isolate gate-lookup db.execute swallows (#2464 Wave 2) (#2468)
Some checks failed
Deploy / build-backend (push) Blocked by required conditions
Deploy / build-worker (push) Blocked by required conditions
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
Deploy / changes (push) Has been cancelled
2026-07-08 06:46:03 +00:00
22f3c44dc2 fix(site_finder): SAVEPOINT-isolate quarter-dump lookup db.execute (#2464 Wave 2) (#2467)
All checks were successful
Deploy / changes (push) Successful in 11s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m42s
Deploy / build-worker (push) Successful in 3m53s
Deploy / deploy (push) Successful in 1m54s
2026-07-08 06:30:32 +00:00
82fdabccba fix(forecasting): SAVEPOINT-isolate §22 db.execute failures + close RELEASE-trap (#2464) (#2466)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m35s
Deploy / build-worker (push) Successful in 3m1s
Deploy / deploy (push) Successful in 1m34s
2026-07-08 05:41:35 +00:00
39dd63333e fix(site_finder): SAVEPOINT-isolate DB errors across cluster A (#2464) (#2465)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 3m29s
Deploy / build-worker (push) Successful in 4m48s
Deploy / deploy (push) Successful in 1m43s
2026-07-07 12:50:11 +00:00
82e0b8b0a3 fix(site-finder): true-counts вместо молча-усечённых len(list) в connection-points/utility (#2445-A1/A2) (#2450)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 2m9s
Deploy / build-frontend (push) Successful in 3m27s
Deploy / build-worker (push) Successful in 4m19s
Deploy / deploy (push) Successful in 1m55s
2026-07-05 18:54:49 +00:00
b40995780b fix(site-finder): раскрыть proxy-fraction в EKB-median velocity-бенчмарке (#2445-A4) (#2448)
Some checks failed
Deploy / changes (push) Has been cancelled
Deploy / build-backend (push) Has been cancelled
Deploy / build-worker (push) Has been cancelled
Deploy / build-frontend (push) Has been cancelled
Deploy / deploy (push) Has been cancelled
2026-07-05 18:54:42 +00:00
c8c96a87ee fix(site-finder): competitors avg-price по per-object снапшоту, не глобальному (#2445-A5) (#2447)
Some checks failed
Deploy / build-backend (push) Blocked by required conditions
Deploy / build-worker (push) Blocked by required conditions
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
Deploy / changes (push) Has been cancelled
2026-07-05 18:54:36 +00:00
9b6aba42dc fix(site-finder): L2 hidden-supply не обнуляет неизвестный flat_count (#2445-A3) (#2446)
Some checks failed
Deploy / deploy (push) Blocked by required conditions
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Has been cancelled
Deploy / build-worker (push) Has been cancelled
2026-07-05 18:54:14 +00:00
b3a59b7cf1 feat(gisogd66): permits_nearby — точный радиус-запрос РНС/РВЭ (#2367 PR-2, backend-half) (#2401)
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-backend (push) Successful in 2m25s
Deploy / build-worker (push) Successful in 3m38s
Deploy / build-frontend (push) Successful in 4m18s
Deploy / deploy (push) Successful in 1m51s
2026-07-04 09:59:49 +00:00
9feadc8458 fix(market-metrics): offer-trend таймаут — warning вместо exception (ложные GlitchTip-события) (#2329)
Some checks failed
Deploy / deploy (push) Blocked by required conditions
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Has been cancelled
Deploy / build-worker (push) Has been cancelled
2026-07-03 22:26:19 +00:00
85435fbf1b fix(site-finder): SET LOCAL литералом — bind-параметр ломал nearby_network_zones (#2283)
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m57s
Deploy / build-worker (push) Successful in 3m3s
Deploy / deploy (push) Successful in 1m47s
2026-07-03 08:44:38 +00:00
7c12812f81 feat(site-finder): позитив-разрез сетевых ЗОУИТ «сеть рядом с участком» (Фаза C2/C3) (#2281)
Some checks failed
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 2m49s
Deploy / build-worker (push) Successful in 4m31s
Deploy / build-frontend (push) Successful in 5m19s
Deploy / deploy (push) Has been cancelled
2026-07-03 08:18:24 +00:00
99ee09e230 feat(site-finder): теплотрассы из OSM в слой инженерной инфраструктуры (Фаза C1) (#2280)
All checks were successful
Deploy / changes (push) Successful in 21s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m53s
Deploy / build-worker (push) Successful in 3m11s
Deploy / deploy (push) Successful in 1m34s
2026-07-03 08:05:44 +00:00
f891f8f00f feat(eias-heat): +6 тепло-организаций ЕКБ — ненулевые резервы ×10 (#2119) (#2270)
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m14s
Deploy / build-worker (push) Successful in 3m19s
Deploy / deploy (push) Successful in 1m43s
2026-07-03 07:02:03 +00:00
21608eced1 fix(site-finder): три честности-фикса — B1 ГРС «выход №N» + околонуль + Росреестр-квартал (#2257)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy / build-backend (push) Successful in 3m35s
Deploy / build-worker (push) Successful in 5m31s
Deploy / build-frontend (push) Successful in 7m23s
Deploy / deploy (push) Successful in 2m17s
2026-07-03 06:27:27 +00:00
6f5bdffbe4 feat(site-finder): OSM-геокод городских ТП/РП ЕКБ (#2119 B2, финальный хвост) (#2252)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m51s
Deploy / build-worker (push) Successful in 3m0s
Deploy / deploy (push) Successful in 1m36s
2026-07-03 05:22:02 +00:00
15757685c9 feat(site-finder): Celery + §3 точки выхода газосети (#2119 B2, PR-4/4 — финал формы-6) (#2251)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Successful in 2m17s
Deploy / build-worker (push) Successful in 3m19s
Deploy / build-frontend (push) Successful in 3m54s
Deploy / deploy (push) Successful in 1m34s
2026-07-03 04:26:41 +00:00
cc9516ab3d feat(ekgas): геокод точек выхода через DaData (#2119 B2, PR-3/4) (#2249)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 2m6s
Deploy / build-worker (push) Successful in 3m8s
Deploy / deploy (push) Successful in 1m34s
2026-07-03 03:19:18 +00:00
c438c9a9e8 fix(ekgas): label-детект сетки формы 6 — живой файл сдвинут (#2119 B2) (#2248)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m54s
Deploy / build-worker (push) Successful in 3m9s
Deploy / deploy (push) Successful in 1m27s
2026-07-03 02:51:03 +00:00
af70d50f0d feat(site-finder): лоадер точек выхода ГРС «Екатеринбурггаз» — форма 6 (#2119 B2, PR-2/4) (#2247)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m46s
Deploy / build-worker (push) Successful in 2m51s
Deploy / deploy (push) Successful in 1m28s
2026-07-03 02:25:12 +00:00
1bba3db7eb fix(best-layouts): lots-проекты в пуле core-матчинга — supply-only реально наполняется (#2177) (#2241)
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Has been skipped
Deploy / build-backend (push) Successful in 1m40s
Deploy / build-worker (push) Successful in 2m42s
Deploy / deploy (push) Successful in 1m24s
2026-07-03 00:14:19 +00:00
9cfefc0ca4 feat(site-finder): supply-only fallback в §4.2 — предложение без темпа продаж (#2177 шаг 3) (#2240)
Some checks failed
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 1m48s
Deploy / build-worker (push) Failing after 1m54s
Deploy / build-frontend (push) Successful in 3m31s
Deploy / deploy (push) Has been skipped
2026-07-02 23:18:42 +00:00
fc55c2c0b7 feat(best-layouts): атрибуция velocity по ядру комплекса (#2177 шаг 2b) (#2239)
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Successful in 1m54s
Deploy / build-worker (push) Successful in 2m59s
Deploy / build-frontend (push) Successful in 3m34s
Deploy / deploy (push) Successful in 1m32s
2026-07-02 22:41:25 +00:00