fix(p3-batch): восстановление 13 Fable 5 P3-fixes из lost branches #1301

Merged
bot-backend merged 13 commits from recover/fable5-p3-batch-2026-06-13 into main 2026-06-13 10:06:06 +00:00
Collaborator

Summary

Восстановление 13 P3-fixes Fable 5, которые были утеряны при batch-push 2026-06-13: head SHA 13 веток оказались resetnuty к main-коммитам (#1220/#1230/#1233) — Forgejo показывал «уже в target», merge-button недоступен.

Работа cherry-pick'нута из локального reflog (8 коммитов) + 2 stash + 3 ручных восстановления тривиальных fix'ов. Все 13 issues адресованы.

Backend (11 fixes)

  • #1232 poi_loader: timedelta(days=730) вместо replace(year=-2) (leap-year safe)
  • #1226 objective_sync_config: log seed failure (bare except → logger.exception)
  • #1228 izyatie_ocr: «6б:» → «66:» для кад-номеров МСК-66
  • #1234 parcel_snapshot.html: Unicode «—» вместо — в Jinja or-fallback
  • #1231 noise_loader: per-row SAVEPOINT
  • #1221 analytics: _district_market_saturation latest-snapshot filter
  • #1229 best_layouts: normalize supply room_bucket vocabulary
  • #1222 forecasting: propagate confounded flag → §15
  • #1116 gknspecial: per-prefix db.commit() для durability
  • #1095 ird_overlay_lookup: расширить except до DataError
  • #1159 okn: expand registry coverage + detail-card name/address

Frontend (2 fixes)

  • #1237 UserMenu: отобразить роль analyst, расширить Role union
  • #1240 PilotRequestModal: mirror Pydantic schema validation

Test plan

  • ruff clean на всех затронутых .py
  • CI green
  • manual smoke в проде после deploy

Closes #1232
Closes #1237
Closes #1231
Closes #1226
Closes #1228
Closes #1234
Closes #1221
Closes #1229
Closes #1240
Closes #1222
Closes #1116
Closes #1095
Closes #1159

Поскольку этот PR заменяет 13 broken PR (#1268, #1269, #1276, #1278, #1279, #1280, #1291, #1292, #1293, #1295, #1297, #1298, #1300), они будут закрыты после merge.

## Summary Восстановление 13 P3-fixes Fable 5, которые были утеряны при batch-push 2026-06-13: head SHA 13 веток оказались resetnuty к main-коммитам (#1220/#1230/#1233) — Forgejo показывал «уже в target», merge-button недоступен. Работа cherry-pick'нута из локального reflog (8 коммитов) + 2 stash + 3 ручных восстановления тривиальных fix'ов. Все 13 issues адресованы. ### Backend (11 fixes) - `#1232` poi_loader: timedelta(days=730) вместо replace(year=-2) (leap-year safe) - `#1226` objective_sync_config: log seed failure (bare except → logger.exception) - `#1228` izyatie_ocr: «6б:» → «66:» для кад-номеров МСК-66 - `#1234` parcel_snapshot.html: Unicode «—» вместо `—` в Jinja or-fallback - `#1231` noise_loader: per-row SAVEPOINT - `#1221` analytics: _district_market_saturation latest-snapshot filter - `#1229` best_layouts: normalize supply room_bucket vocabulary - `#1222` forecasting: propagate confounded flag → §15 - `#1116` gknspecial: per-prefix db.commit() для durability - `#1095` ird_overlay_lookup: расширить except до DataError - `#1159` okn: expand registry coverage + detail-card name/address ### Frontend (2 fixes) - `#1237` UserMenu: отобразить роль analyst, расширить Role union - `#1240` PilotRequestModal: mirror Pydantic schema validation ## Test plan - [x] ruff clean на всех затронутых .py - [ ] CI green - [ ] manual smoke в проде после deploy Closes #1232 Closes #1237 Closes #1231 Closes #1226 Closes #1228 Closes #1234 Closes #1221 Closes #1229 Closes #1240 Closes #1222 Closes #1116 Closes #1095 Closes #1159 > Поскольку этот PR заменяет 13 broken PR (#1268, #1269, #1276, #1278, #1279, #1280, #1291, #1292, #1293, #1295, #1297, #1298, #1300), они будут закрыты после merge.
bot-backend added 13 commits 2026-06-13 10:05:27 +00:00
Loop UPSERT в sync_noise_sources_to_db без begin_nested: один замкнутый
3-точечный natural=water way [A,B,A] даёт POLYGON((A,B,A)), PostGIS
отвергает (< 4 точек в ring) → outer tx rollback + raise → весь weekly
noise/water/utility sync падает, тот же way отравляет каждый прогон.

- Оборачиваем каждый UPSERT в `with db.begin_nested():` + per-row
  try/except → logger.warning + skipped++ (канон pzz_loader.py:111).
- В _way_to_polygon_wkt проверяем итоговое кольцо ≥ 4 точек (fail-safe).
- Outer except: добавлен logger.exception для видимости.

Closes #1231
domrf_kn_objects/sales_agg хранят ~3 snapshot'а на obj_id (weekly UPSERT,
retention=none). JOIN без latest-фильтра считал каждый ЖК N раз:
sold_median тянулся старыми perc → sat_factor занижен, гейт n>=5
проходил уже при 2 ЖК (n=6), sat_n завышен ~×N в scope. Сданные ЖК
просачивались через старые снапшоты со статусом 'Строящиеся'.

Patch: AND a.snapshot_date = (SELECT MAX(snapshot_date) FROM ...) для
обеих таблиц (зеркало #1210/#1212). COUNT(*) → COUNT(DISTINCT a.obj_id)
defensive. 29 recommend_mix + 8 saturation тестов зелёные.

Closes #1221
best_layouts._SUPPLY_BATCH_SQL эмитил {studio,euro-1,euro-2,1,2,3,4+},
а _INLINE_VELOCITY_SQL читает {студия,1,2,3,4+} из
objective_corpus_room_month (prod check: 'euro-*' rows отсутствуют).

Эффект: rooms=2 + area<50 уходили в euro-1/euro-2 supply-стороной →
выпадали из знаменателя bucket '2' → sold_pct_of_supply двушек
завышен, is_oversold ложно True. (rb='euro-*') dead lookups в supply_map.

Patch: убраны euro-* WHEN в supply CASE. SF-08 euro-биннинг отложен
до момента когда velocity-сторона начнёт его отдавать. +2 regression
теста (bucket match, string guard). 35 best_layouts тестов зелёные.

Closes #1229
Backend PilotRequestInput требует name min_length=2 и др., но modal
проверял только !form.name.trim(). 1-char name ('Я') проходил, backend
возвращал 422, apiFetch клал raw FastAPI JSON в Error.message, modal
рендерил его буквально для public lead form.

Patch: PILOT_LIMITS + PILOT_EMAIL_RE константы (синхр с backend),
per-field onBlur+live валидация с RU-сообщениями, minLength/maxLength
+ aria-invalid/aria-describedby, submit disabled при ошибках,
formatSubmitError → 4xx/5xx → human text. 26 vitest кейсов.

Closes #1240
DemandSupplyForecast.as_dict() не эмитил 'confounded'/'is_confounded_window',
report_assembler._confounded() всегда возвращал False и §15 confounded_window
factor в compute_report_confidence был мёртв: 48-мес окна, пересекающие
2024-07-01 шок никогда не тянули report confidence к 'low' и шок не назывался
в rationale.

Patch: добавлено confounded: bool в DemandSupplyForecast (от §9.5 macro_coef
OR §9.6 rate_sensitivity), exposed в as_dict(). _confounded() уже использовал
.get() defensively — блокер был в producer'е.

+3 теста: contract на real DemandSupplyForecast.as_dict(), end-to-end
assemble_report → confounded_window factor surfaces at level=low, weakest-link
тянет overall к 'low'. 61 report_assembler + 1034 forecasting тестов зелёные.

Closes #1222
harvest_gknspecial_zones делал единственный commit в конце всех 8 prefix-
циклов: WorkerLostError (OOM / рестарт хоста) в середине прогона →
~20k записанных строк откатывались, прогресс ноль.

Mirror ird_harvest.py:216 (per-quarter commit): db.commit() после
финального logger.info внутри for-prefix loop. SAVEPOINT-per-row уже
изолирует битые фичи; per-prefix commit добавляет durability между
префиксами. WFS-fail на префиксе → continue → commit не делается;
предыдущие префиксы уже durable.

+2 теста (durability, WFS-fail skip). 29 gknspecial тестов зелёные.

Closes #1116
parcel_ird_overlaps ловил только (OperationalError, ProgrammingError).
При D9b-wiring в analyze malformed WKT в ST_GeomFromText давал PostGIS
ERROR → SQLAlchemy DataError, который пробивал try/except → analyze
падал вместо graceful-degrade.

- ird_overlay_lookup: + DataError в imports и в except tuple.
- quarter_dump_lookup: симметричное расширение в _get_engineering_*.
- test_ird_overlay_lookup: parametrized test_graceful_when_db_fails
  покрывает 3 класса (Operational/Programming + DataError для WKT).

Closes #1095
okn_objects_sync собирал 428 ОКН из ~798 ЕГРОКН и сохранял raw_props={id}
без названия/адреса — analyze показывал 'ОКН рядом' без указания КАКОГО.

Coverage: расширил _ADDRESS_PASSES с 2 до 11 подстрок (Екатеринбург,
г. Екатеринбург, 7 районов ЕКБ, 2 формы Свердловской обл.). _PASSES =
декартово (address × category). first-wins дедуп сохраняет высшую категорию.

Detail: fetch_okn_detail(source_id) — GET Show/Show?id=, BeautifulSoup
парсит HTML (dl/dt/dd и tr/td), извлекает name, address, protection_category,
object_kind, dating. _enrich_with_detail сливает в raw_props с graceful
failover (cap=1500, exception per-object не валит прогон).

Lookup: parcel_okn_objects возвращает name/address из raw_props->>'name'.
Existing ird_analyze тесты не сломаны.

49 okn-related тестов passed, ruff clean.

Closes #1159
fix(snapshot-pdf): Unicode '—' вместо HTML-entity '&mdash;' в Jinja or-fallback (#1234)
All checks were successful
CI / changes (pull_request) Successful in 6s
Deploy / changes (push) Successful in 9s
CI / frontend-tests (pull_request) Successful in 55s
Deploy / build-backend (push) Successful in 2m20s
Deploy / build-frontend (push) Successful in 3m25s
Deploy / build-worker (push) Successful in 4m16s
Deploy / deploy (push) Successful in 1m22s
CI / backend-tests (pull_request) Successful in 6m43s
3df163f741
bot-backend merged commit 3df163f741 into main 2026-06-13 10:06:06 +00:00
bot-backend deleted branch recover/fable5-p3-batch-2026-06-13 2026-06-13 10:06:07 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1301
No description provided.