[SF-v2 #955] 955-A3: Refactor exporters на SiteFinderReport + unified pdf.py #989

Closed
opened 2026-06-02 19:24:48 +00:00 by lekss361 · 2 comments
Owner

Часть EPIC #955 (Site Finder v2 / GG-форсайт). Декомпозиция критпути — ground-truth (прод-БД + код) + web-research, 2026-06-02.

Scope: 2 разрозненных PDF-эндпоинта (snapshot.pdf parcels.py L2906, best-layouts/pdf L2870) с разным входом; pdf.py/excel.py/dxf.py — пустые 4-строчные stub. Свести на один report-объект.

Файлы: exporters/layout_tz_pdf.py, snapshot_pdf.py; implement exporters/pdf.py как unified entry

DoD:

  • все PDF-пути рендерят из одного report-объекта; snapshot.pdf больше не re-query'ит БД

Зависит от: 955-A2
Effort: L

**Часть EPIC #955 (Site Finder v2 / GG-форсайт).** Декомпозиция критпути — ground-truth (прод-БД + код) + web-research, 2026-06-02. **Scope:** 2 разрозненных PDF-эндпоинта (snapshot.pdf parcels.py L2906, best-layouts/pdf L2870) с разным входом; pdf.py/excel.py/dxf.py — пустые 4-строчные stub. Свести на один report-объект. **Файлы:** exporters/layout_tz_pdf.py, snapshot_pdf.py; implement exporters/pdf.py как unified entry **DoD:** - все PDF-пути рендерят из одного report-объекта; snapshot.pdf больше не re-query'ит БД **Зависит от:** 955-A2 **Effort:** L
lekss361 added this to the Site Finder v2 — Foresight (GG-форсайт) milestone 2026-06-02 19:24:53 +00:00
lekss361 added the
GG-форсайт
scope/backend
site-finder
labels 2026-06-02 19:24:53 +00:00
lekss361 reopened this issue 2026-06-03 17:59:33 +00:00
Author
Owner

🟠 Reopen — MEDIUM: PDF-экспортёр молча теряет колонки «Спрос»/«Предложение»/«Overall» на реальных данных. Валидация #1023.

Баг (контракт ключей). backend/app/services/exporters/report_pdf.py:

  • _build_future_market читает f.get("demand") / f.get("supply"), но реальный продьюсер DemandSupplyForecast.as_dict() (demand_supply_forecast.py:156) эмитит projected_demand_units / projected_supply_units — ключей demand/supply нет.
  • _build_scenarios читает data.get("overall"), но ScenarioForecast.as_dict() (scenarios.py:107) выдаёт scenario/rate_path/forecasts/advisory — ключа overall нет.

→ На реальном выводе ассемблера (#1021) колонки «Спрос», «Предложение» и «Overall» всегда . Через .get() — без исключения, молча.

Корень: экспортёр писался/тестировался на выдуманных fixture-формах ({"demand":410,"supply":380}, by_scenario:{…,"overall":…}), которых пайплайн не производит; ассемблер (#1021) и его тесты используют верные формы — т.е. правильная сторона контракта именно у ассемблера.

Fix: заменить ключи на projected_demand_units/projected_supply_units; для сценариев вывести overall из forecasts (напр. deficit_index приоритетного горизонта) или убрать колонку; добавить fixtures, зеркалящие реальный as_dict(). Тот же дефект в Excel — см. #991.

Остальной рендер корректен, html.escape на динамике, WeasyPrint вызывается верно.

🟠 **Reopen — MEDIUM: PDF-экспортёр молча теряет колонки «Спрос»/«Предложение»/«Overall» на реальных данных.** Валидация #1023. **Баг (контракт ключей).** `backend/app/services/exporters/report_pdf.py`: - `_build_future_market` читает `f.get("demand")` / `f.get("supply")`, но реальный продьюсер `DemandSupplyForecast.as_dict()` (`demand_supply_forecast.py:156`) эмитит `projected_demand_units` / `projected_supply_units` — ключей `demand`/`supply` нет. - `_build_scenarios` читает `data.get("overall")`, но `ScenarioForecast.as_dict()` (`scenarios.py:107`) выдаёт `scenario`/`rate_path`/`forecasts`/`advisory` — ключа `overall` нет. → На реальном выводе ассемблера (#1021) колонки «Спрос», «Предложение» и «Overall» **всегда `—`**. Через `.get()` — без исключения, молча. **Корень:** экспортёр писался/тестировался на выдуманных fixture-формах (`{"demand":410,"supply":380}`, `by_scenario:{…,"overall":…}`), которых пайплайн не производит; ассемблер (#1021) и его тесты используют верные формы — т.е. правильная сторона контракта именно у ассемблера. **Fix:** заменить ключи на `projected_demand_units`/`projected_supply_units`; для сценариев вывести overall из `forecasts` (напр. deficit_index приоритетного горизонта) или убрать колонку; добавить fixtures, зеркалящие реальный `as_dict()`. _Тот же дефект в Excel — см. #991._ _Остальной рендер корректен, `html.escape` на динамике, WeasyPrint вызывается верно._
Collaborator

FIXED (PR #1037). PDF exporter now reads projected_demand_units/projected_supply_units (not demand/supply); scoring.overall was NOT broken (only scenarios). ROOT: test fixtures were stale (hand-typed wrong keys = false-green) -> rebuilt from real SiteFinderReport.as_dict() + contract-key regression guard (reintroducing old keys FAILS the test). 28 tests. Closing on test-verify; reopen if re-review disagrees.

FIXED (PR #1037). PDF exporter now reads projected_demand_units/projected_supply_units (not demand/supply); scoring.overall was NOT broken (only scenarios). ROOT: test fixtures were stale (hand-typed wrong keys = false-green) -> rebuilt from real SiteFinderReport.as_dict() + contract-key regression guard (reintroducing old keys FAILS the test). 28 tests. Closing on test-verify; reopen if re-review disagrees.
Sign in to join this conversation.
No project
No assignees
2 participants
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#989
No description provided.