[Fable 5][P2] _AVG_PRICE_SQL без snapshot-фильтра: цена конкурента — смесь исторических снапшотов, flats_sold кратно завышен #1210

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

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

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

Файл

backend/app/services/site_finder/competitors.py:443

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

        AVG(f.price_per_m2) FILTER (WHERE f.price_per_m2 IS NOT NULL)
            AS avg_price_per_m2,
        COUNT(*) FILTER (
            WHERE LOWER(COALESCE(f.status, '')) LIKE '%прод%'
               OR LOWER(COALESCE(f.status, '')) = 'sold'
        ) AS flats_sold
    FROM domrf_kn_flats f
    WHERE f.obj_id = ANY(:obj_ids)
    GROUP BY f.obj_id

Why bug

domrf_kn_flats — append-per-snapshot (UPSERT по (id, snapshot_date) в domrf_kn.py), поэтому best_layouts фильтрует snapshot_date = MAX(...). Здесь фильтра нет: avg_price_per_m2 (UI-поле Competitor + вход _price_similarity) усредняется по всем снапшотам → устаревшая цена на растущем рынке; flats_sold считает квартиру по разу на снапшот → гард raw_sold <= flats_total либо гасит stage_at_horizon в нейтраль, либо пропускает ×N-ошибочный sold_pct.

Suggested fix

Добавить в WHERE фильтр последнего снапшота: f.snapshot_date = (SELECT MAX(snapshot_date) FROM domrf_kn_flats) — зеркально best_layouts._SUPPLY_BATCH_SQL.

Verify notes

  • [real/P2] domrf_kn_flats версионируется (UNIQUE(id,snapshot_date), миграция 50: «несколько срезов одной квартиры»). _AVG_PRICE_SQL без snapshot-фильтра: AVG смешивает цены всех дат (stale в Competitor.avg_price_per_m2 и _price_similarity), COUNT '%прод%' множит sold ×N снапшотов; гард raw_sold<=flats_total (стр. 593) либо пропускает завышенный sold_pct, либо гасит stage_at_horizon в 0.5. Соседи фильтруют latest (best_layouts:186/429, _COMPETITORS_SQL DISTINCT ON) — здесь нет.
  • [real/P2] Таблица append-per-snapshot: UNIQUE(id,snapshot_date) (миграция 50), UPSERT ON CONFLICT(id,snapshot_date), weekly cron "15 4 * * mon", retention нет. Соседние читатели (best_layouts.py:186/429, analytics_queries.py:981) фильтруют MAX(snapshot_date) — _AVG_PRICE_SQL нет: avg_price_per_m2 (UI + _price_similarity) смешивает все недельные снапшоты, flats_sold множится на число снапшотов; guard raw_sold<=flats_total лишь гасит stage в нейтраль или пропускает завышенный sold_pct.

Provenance

  • Finders: site-finder-A
  • Confidence: 0.65
  • Originally batched at: main@d796027
## Из армейского ревью Claude Fable 5 (2026-06-11) Confirmed by 2/2 адверсариальных верификатора, severity **P2** (correctness), confidence 0.65. ## Файл [`backend/app/services/site_finder/competitors.py:443`](backend/app/services/site_finder/competitors.py:443) ## Evidence (цитата кода) ``` AVG(f.price_per_m2) FILTER (WHERE f.price_per_m2 IS NOT NULL) AS avg_price_per_m2, COUNT(*) FILTER ( WHERE LOWER(COALESCE(f.status, '')) LIKE '%прод%' OR LOWER(COALESCE(f.status, '')) = 'sold' ) AS flats_sold FROM domrf_kn_flats f WHERE f.obj_id = ANY(:obj_ids) GROUP BY f.obj_id ``` ## Why bug domrf_kn_flats — append-per-snapshot (UPSERT по (id, snapshot_date) в domrf_kn.py), поэтому best_layouts фильтрует snapshot_date = MAX(...). Здесь фильтра нет: avg_price_per_m2 (UI-поле Competitor + вход _price_similarity) усредняется по всем снапшотам → устаревшая цена на растущем рынке; flats_sold считает квартиру по разу на снапшот → гард raw_sold <= flats_total либо гасит stage_at_horizon в нейтраль, либо пропускает ×N-ошибочный sold_pct. ## Suggested fix Добавить в WHERE фильтр последнего снапшота: f.snapshot_date = (SELECT MAX(snapshot_date) FROM domrf_kn_flats) — зеркально best_layouts._SUPPLY_BATCH_SQL. ## Verify notes - [real/P2] domrf_kn_flats версионируется (UNIQUE(id,snapshot_date), миграция 50: «несколько срезов одной квартиры»). _AVG_PRICE_SQL без snapshot-фильтра: AVG смешивает цены всех дат (stale в Competitor.avg_price_per_m2 и _price_similarity), COUNT '%прод%' множит sold ×N снапшотов; гард raw_sold<=flats_total (стр. 593) либо пропускает завышенный sold_pct, либо гасит stage_at_horizon в 0.5. Соседи фильтруют latest (best_layouts:186/429, _COMPETITORS_SQL DISTINCT ON) — здесь нет. - [real/P2] Таблица append-per-snapshot: UNIQUE(id,snapshot_date) (миграция 50), UPSERT ON CONFLICT(id,snapshot_date), weekly cron "15 4 * * mon", retention нет. Соседние читатели (best_layouts.py:186/429, analytics_queries.py:981) фильтруют MAX(snapshot_date) — _AVG_PRICE_SQL нет: avg_price_per_m2 (UI + _price_similarity) смешивает все недельные снапшоты, flats_sold множится на число снапшотов; guard raw_sold<=flats_total лишь гасит stage в нейтраль или пропускает завышенный sold_pct. ## Provenance - Finders: `site-finder-A` - Confidence: 0.65 - Originally batched at: main@d796027
bot-backend added the
Fable 5 ревью
label 2026-06-13 04:49:43 +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#1210
No description provided.