report_pdf.py был единственным экспортёром (vs report_docx/report_md) без
выделенной секции уверенности с разбивкой факторов, а его старый тонкий
dump в «Сводке» не обрабатывал dict-образные factor-payload'ы защищённо,
как это делают sibling-экспортёры → мог криво рендерить/падать на dict-факторе.
Портирован _build_confidence (4-колоночная таблица Фактор/Значение/Уровень/
Комментарий, isinstance(dict) ветка 1:1 из docx/md), зарегистрирован последним
в _SECTION_BUILDERS, footer перенесён в него (рендерится ровно раз в конце —
проверено на пустом confidence). Убрано дублирование из «Сводки».
Refs #2445
deficit_index pins to -1.0 for every ЕКБ segment (12mo demand flow vs multi-year
supply stock → log-ratio clamps) → zero discriminating power, though the oversupply
is partly real. Add MOI (gross competing supply / demand_per_mo), the real-estate
absorption standard, as an additive non-saturating companion that DISCRIMINATES
(Уралмаш 42mo … Чермет 109mo) where deficit cannot. deficit_index math kept exactly
as-is (honest absolute: -1 = genuinely oversupplied); docstrings clarify -1 is common
and MOI is the discriminating companion (no recalibration). _gross_supply extract-method
(single source of truth; _project_supply behavior byte-identical, code-review-verified).
Surface MOI in §22 future_market (passthrough) + exec_summary key_numbers/verdict.
Guards: no demand → None, no supply → 0. Prod: MOI varies 42→109mo, deficit stays -1.
Discrimination test pins MOI separating two segments both at deficit -1. Refs #952.
REOPENED. PDF + Excel exporters read non-existent dict keys, so demand/supply/
scenario columns silently rendered "—". Tests passed only because the fixtures
were stale (hand-typed the same wrong keys → fixture agreed with buggy exporter).
- future_market: demand/supply → projected_demand_units/projected_supply_units
- scenarios: drop non-existent per-scenario "overall"; show primary-horizon
deficit_index from ScenarioForecast.forecasts (scoring.overall was NOT broken)
- Excel #991: add missing future_supply (index + breakdown) + confidence.factors
sections; add future_supply to PDF for parity
- tests: rebuild forecast/scenario fixtures from real DemandSupplyForecast /
ScenarioForecast as_dict(); contract-key regression guards fail on key-drift
(verified: reintroducing old keys fails the new tests). 28 passed.
Refs #989#991