[Fable 5][P3] §25.3 _price_overlap: нулевая ширина ценовой вилки нашего проекта внутри вилки кандидата даёт 0.0 вместо 1.0/None #1224

Closed
opened 2026-06-13 04:49:46 +00:00 by bot-backend · 0 comments
Collaborator

Из армейского ревью Claude Fable 5 (2026-06-11)

Confirmed by 2/2 адверсариальных верификатора, severity P3 (correctness), confidence 0.7.

Файл

backend/app/services/forecasting/special_indices.py:460

Evidence (цитата кода)

c_width = c_hi - c_lo
    o_width = o_hi - o_lo
    widths = [w for w in (c_width, o_width) if math.isfinite(w) and w > 0]
    if not widths:
        return None
    return _clamp01(overlap / min(widths))

Why bug

own_planned_project / domrf допускают price_min == price_max (CHECK миграции 148 требует лишь >=0). Точка 150k внутри вилки кандидата [120k,160k): overlap = 0, нулевая ширина выкидывается из min(widths) → 0/40000 = 0.0 — ось говорит «цены не пересекаются», хотя по документированной семантике («полное накрытие более узкого диапазона = 1.0») это предельный случай 1.0. Проверено численно: [148k,152k] → 1.0, [150k,150k] → 0.0 (разрыв). Ось 0.0 входит в среднее пары и занижает каннибализацию вместо None-not-0.

Suggested fix

Обработать вырожденную вилку до фильтра widths: ширина == 0 и точка внутри другой вилки → 1.0; вне → 0.0; иначе текущая логика.

Verify notes

  • [real/P3] Трасса: own=[150k,150k] внутри comfort [120k,160k): lo=hi=150k, overlap=0.0; o_width=0 отброшен фильтром w>0 → 0/40000=0.0. При [148k,152k] → 1.0 — разрыв. Докстринг обещает «полное накрытие более узкого диапазона = 1.0» и None-not-0, а ось фабрикует 0. Вход достижим: Pydantic (own_project.py:75) режет только min>max, CHECK миграции 148 — только >=0; min==max проходит (и из domrf min может равняться max). 0.0 входит в среднее осей пары → каннибализация занижена. Advisory, 1 ось из 4 → P3.
  • [real/P3] Численно подтверждено: own [150k,150k] внутри [120k,160k) → overlap=0, o_width=0 выкинут → 0/40000=0.0; [148k,152k] → 1.0 (разрыв). Защиты нет: Pydantic (own_project.py:75) отвергает лишь min>max, CHECK 148 — лишь >=0; равенство проходит из manual и domrf. 0.0 входит в среднее осей (стр.683), занижая max-сигнал. Противоречит докстрингу («накрытие узкого = 1.0») и None-not-0. Edge-case advisory-индекса → P3.

Provenance

  • Finders: forecasting
  • Confidence: 0.7
  • Originally batched at: main@d796027
## Из армейского ревью Claude Fable 5 (2026-06-11) Confirmed by 2/2 адверсариальных верификатора, severity **P3** (correctness), confidence 0.7. ## Файл [`backend/app/services/forecasting/special_indices.py:460`](backend/app/services/forecasting/special_indices.py:460) ## Evidence (цитата кода) ``` c_width = c_hi - c_lo o_width = o_hi - o_lo widths = [w for w in (c_width, o_width) if math.isfinite(w) and w > 0] if not widths: return None return _clamp01(overlap / min(widths)) ``` ## Why bug own_planned_project / domrf допускают price_min == price_max (CHECK миграции 148 требует лишь >=0). Точка 150k внутри вилки кандидата [120k,160k): overlap = 0, нулевая ширина выкидывается из min(widths) → 0/40000 = 0.0 — ось говорит «цены не пересекаются», хотя по документированной семантике («полное накрытие более узкого диапазона = 1.0») это предельный случай 1.0. Проверено численно: [148k,152k] → 1.0, [150k,150k] → 0.0 (разрыв). Ось 0.0 входит в среднее пары и занижает каннибализацию вместо None-not-0. ## Suggested fix Обработать вырожденную вилку до фильтра widths: ширина == 0 и точка внутри другой вилки → 1.0; вне → 0.0; иначе текущая логика. ## Verify notes - [real/P3] Трасса: own=[150k,150k] внутри comfort [120k,160k): lo=hi=150k, overlap=0.0; o_width=0 отброшен фильтром w>0 → 0/40000=0.0. При [148k,152k] → 1.0 — разрыв. Докстринг обещает «полное накрытие более узкого диапазона = 1.0» и None-not-0, а ось фабрикует 0. Вход достижим: Pydantic (own_project.py:75) режет только min>max, CHECK миграции 148 — только >=0; min==max проходит (и из domrf min может равняться max). 0.0 входит в среднее осей пары → каннибализация занижена. Advisory, 1 ось из 4 → P3. - [real/P3] Численно подтверждено: own [150k,150k] внутри [120k,160k) → overlap=0, o_width=0 выкинут → 0/40000=0.0; [148k,152k] → 1.0 (разрыв). Защиты нет: Pydantic (own_project.py:75) отвергает лишь min>max, CHECK 148 — лишь >=0; равенство проходит из manual и domrf. 0.0 входит в среднее осей (стр.683), занижая max-сигнал. Противоречит докстрингу («накрытие узкого = 1.0») и None-not-0. Edge-case advisory-индекса → P3. ## Provenance - Finders: `forecasting` - Confidence: 0.7 - Originally batched at: main@d796027
bot-backend added the
Fable 5 ревью
label 2026-06-13 04:49:46 +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#1224
No description provided.