[Fable 5][P2] §25 compute_special_indices: итоговый confidence считается по ВСЕМ 6 индексам (включая unavailable→'low'), а не «MIN по доступным» — карточка систематически … #1592

Closed
opened 2026-06-16 17:13:56 +00:00 by bot-backend · 0 comments
Collaborator

Severity: medium · Категория: ложь в отчёте / неверный агрегат (confidence) · Файл: backend/app/services/forecasting/special_indices.py:1692 (агрегация); ср. контракт 228/237 и docstring 1582; источник 'low' — 804; механизм 272-281

Файл: gendesign/backend/app/services/forecasting/special_indices.py:1692.

Суть: confidence = _min_confidence([idx.confidence for idx in indices.values()]) передаёт confidence ВСЕХ шести индексов, включая деградировавшие в _unavailable (value=None). _unavailable() жёстко проставляет confidence='low' (стр. 804). _min_confidence (стр. 272-281) исключает из MIN только элементы, РАВНЫЕ None (if v is not None), но строка 'low' не None — поэтому недоступные индексы участвуют в MIN и тянут его вниз до 'low'.

Расхождение с контрактом: dataclass SpecialIndices (стр. 228, 237) и docstring compute_special_indices (стр. 1582) обещают «confidence = MIN по ДОСТУПНЫМ индексам», а docstring _min_confidence (стр. 275) — «None игнорируем (компонент без сигнала)». То есть компоненты без сигнала (value=None) должны исключаться из агрегата, но вместо None в список кладётся 'low'. Сам модуль на стр. 1694 определяет доступность как idx.value is not None (n_available), и этот же критерий не применён к строке 1692.

Импакт: деградация хотя бы одного индекса — типовой путь, а не край. Без cad_num сразу два индекса (cannibalization, competitor_strength) уходят в _unavailable→'low'; нет проданных лотов сегмента → artificial_demand 'low'. В итоге карточка, где 4-5 индексов реально посчитаны с 'medium', публикует общий confidence='low'. Искажение консервативное (только занижение), модуль ADVISORY (не основание для инвест-решения), поэтому P2/medium, а не выше.

Фикс: исключать индексы без сигнала из агрегации —
confidence = _min_confidence([idx.confidence if idx.value is not None else None for idx in indices.values()])
Тогда _unavailable-индексы (value=None) подадут None и будут проигнорированы _min_confidence, как и обещают docstring'и. Граничный случай (все индексы недоступны → пустой/весь-None список) уже корректно даёт 'low' (стр. 279-280).

Почему баг: Деградация хотя бы одного индекса — ОБЫЧНЫЙ путь, а не край: без cad_num сразу два индекса (cannibalization и competitor_strength) уходят в _unavailable→'low'; нет проданных лотов сегмента → artificial_demand 'low'; нет района/тонкие данные → ещё. В результате карточка, где 4-5 индексов реально посчитаны с confidence='medium', публикует общий confidence='low'. Это прямое расхождение с заявленным контрактом «MIN по доступным» и систематическое занижение/искажение уверенности в advisory-отчёте (ложь в отчёте — но в консервативную сторону). Корректная реализация должна передавать idx.confidence if idx.value is not None else None, чтобы недоступные индексы (без сигнала) исключались из MIN, как и обещают docstring'и.

Как чинить: В строке 1692 исключить недоступные индексы из агрегата, согласовав с контрактом: confidence = _min_confidence([idx.confidence if idx.value is not None else None for idx in indices.values()]) (или фильтровать idx for idx in indices.values() if idx.value is not None). Тогда _min_confidence по-настоящему берёт MIN по ДОСТУПНЫМ, а полностью пустая карточка корректно даёт 'low' (ветка «весь-None → low»).


deep-audit backend v2 (после PR #1543) · verify+harden, confidence 0.9 · unit B22

**Severity:** medium · **Категория:** ложь в отчёте / неверный агрегат (confidence) · **Файл:** `backend/app/services/forecasting/special_indices.py:1692 (агрегация); ср. контракт 228/237 и docstring 1582; источник 'low' — 804; механизм 272-281` Файл: gendesign/backend/app/services/forecasting/special_indices.py:1692. Суть: `confidence = _min_confidence([idx.confidence for idx in indices.values()])` передаёт confidence ВСЕХ шести индексов, включая деградировавшие в _unavailable (value=None). _unavailable() жёстко проставляет confidence='low' (стр. 804). _min_confidence (стр. 272-281) исключает из MIN только элементы, РАВНЫЕ None (`if v is not None`), но строка 'low' не None — поэтому недоступные индексы участвуют в MIN и тянут его вниз до 'low'. Расхождение с контрактом: dataclass SpecialIndices (стр. 228, 237) и docstring compute_special_indices (стр. 1582) обещают «confidence = MIN по ДОСТУПНЫМ индексам», а docstring _min_confidence (стр. 275) — «None игнорируем (компонент без сигнала)». То есть компоненты без сигнала (value=None) должны исключаться из агрегата, но вместо None в список кладётся 'low'. Сам модуль на стр. 1694 определяет доступность как `idx.value is not None` (n_available), и этот же критерий не применён к строке 1692. Импакт: деградация хотя бы одного индекса — типовой путь, а не край. Без cad_num сразу два индекса (cannibalization, competitor_strength) уходят в _unavailable→'low'; нет проданных лотов сегмента → artificial_demand 'low'. В итоге карточка, где 4-5 индексов реально посчитаны с 'medium', публикует общий confidence='low'. Искажение консервативное (только занижение), модуль ADVISORY (не основание для инвест-решения), поэтому P2/medium, а не выше. Фикс: исключать индексы без сигнала из агрегации — `confidence = _min_confidence([idx.confidence if idx.value is not None else None for idx in indices.values()])` Тогда _unavailable-индексы (value=None) подадут None и будут проигнорированы _min_confidence, как и обещают docstring'и. Граничный случай (все индексы недоступны → пустой/весь-None список) уже корректно даёт 'low' (стр. 279-280). **Почему баг:** Деградация хотя бы одного индекса — ОБЫЧНЫЙ путь, а не край: без cad_num сразу два индекса (cannibalization и competitor_strength) уходят в _unavailable→'low'; нет проданных лотов сегмента → artificial_demand 'low'; нет района/тонкие данные → ещё. В результате карточка, где 4-5 индексов реально посчитаны с confidence='medium', публикует общий confidence='low'. Это прямое расхождение с заявленным контрактом «MIN по доступным» и систематическое занижение/искажение уверенности в advisory-отчёте (ложь в отчёте — но в консервативную сторону). Корректная реализация должна передавать `idx.confidence if idx.value is not None else None`, чтобы недоступные индексы (без сигнала) исключались из MIN, как и обещают docstring'и. **Как чинить:** В строке 1692 исключить недоступные индексы из агрегата, согласовав с контрактом: `confidence = _min_confidence([idx.confidence if idx.value is not None else None for idx in indices.values()])` (или фильтровать `idx for idx in indices.values() if idx.value is not None`). Тогда _min_confidence по-настоящему берёт MIN по ДОСТУПНЫМ, а полностью пустая карточка корректно даёт 'low' (ветка «весь-None → low»). --- <sub>deep-audit backend v2 (после PR #1543) · verify+harden, confidence 0.9 · unit B22</sub>
bot-backend added the
week ревью 1
label 2026-06-16 17:13:56 +00:00
Sign in to join this conversation.
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#1592
No description provided.