[medium] object_flats_quartirography: бакет 'Нежилые' дробится на дубли, когда у нежилого юнита rooms не NULL #1358

Closed
opened 2026-06-14 18:09:05 +00:00 by bot-backend · 1 comment
Collaborator

Описание

Эндпойнт нарушает контракт «Группирует по rooms: 1/2/3/Нежилые» — возвращает две строки с одинаковым room_label='Нежилые', но разными count/area/price и разной позицией сортировки. room_label схлопывает в 'Нежилые' несколько условий (rooms IS NULL OR flat_type LIKE '%нежил%' OR '%nonliv%'), но sort_key = COALESCE(f.rooms, -1), и GROUP BY идёт по (room_label, sort_key). Нежилой юнит с flat_type матчащим '%нежил%' но с ненулевым rooms (напр. коммерция с rooms=2) получает room_label='Нежилые' и sort_key=2, тогда как настоящие rooms IS NULL нежилые — sort_key=-1. Они попадают в ДВЕ разные группы.

Корень

sort_key берётся из сырого rooms, а не из логического room_label. Scraper (domrf_kn.py:429,434) хранит сырые API-значения flat_type и rooms без нормализации rooms=NULL для нежилых, поэтому строка может иметь и '%нежил%' и rooms=2 одновременно.

Evidence

analytics_queries.py:986-1012:

CASE WHEN f.rooms IS NULL OR LOWER(f.flat_type) LIKE '%нежил%' OR LOWER(f.flat_type) LIKE '%nonliv%'
     THEN 'Нежилые' ... END AS room_label,
COALESCE(f.rooms, -1) AS sort_key
...
GROUP BY room_label, sort_key ORDER BY sort_key

Return на строках ~1020-1031 передаёт строки как есть, без клиентского слияния.

Suggested fix

Вывести sort_key из room_label, а не из сырого rooms. Обернуть тем же CASE:

CASE WHEN <нежилые-cond> THEN -1 WHEN f.rooms=0 THEN 0 ... ELSE f.rooms END AS sort_key

Либо GROUP BY room_label only и sort_key = MIN(...) по группе — гарантирует одну строку на логический бакет.

Обоснование severity (medium)

Выход вводит в заблуждение и нарушает задокументированный контракт, но затрагивает только объекты с нежилыми юнитами при ненулевом rooms — подмножество, не все объекты. Confidence 0.92.


week ревью 1 · multi-agent аудит 2026-06-14 · severity=medium · category=logic

Затронутые файлы:

  • /Users/anton/Птица/gendesign/backend/app/services/analytics_queries.py
## Описание Эндпойнт нарушает контракт «Группирует по rooms: 1/2/3/Нежилые» — возвращает две строки с одинаковым `room_label='Нежилые'`, но разными count/area/price и разной позицией сортировки. `room_label` схлопывает в 'Нежилые' несколько условий (`rooms IS NULL OR flat_type LIKE '%нежил%' OR '%nonliv%'`), но `sort_key = COALESCE(f.rooms, -1)`, и GROUP BY идёт по `(room_label, sort_key)`. Нежилой юнит с `flat_type` матчащим '%нежил%' но с ненулевым `rooms` (напр. коммерция с rooms=2) получает `room_label='Нежилые'` и `sort_key=2`, тогда как настоящие `rooms IS NULL` нежилые — `sort_key=-1`. Они попадают в ДВЕ разные группы. ## Корень `sort_key` берётся из сырого `rooms`, а не из логического `room_label`. Scraper (domrf_kn.py:429,434) хранит сырые API-значения flat_type и rooms без нормализации rooms=NULL для нежилых, поэтому строка может иметь и '%нежил%' и rooms=2 одновременно. ## Evidence `analytics_queries.py:986-1012`: ```sql CASE WHEN f.rooms IS NULL OR LOWER(f.flat_type) LIKE '%нежил%' OR LOWER(f.flat_type) LIKE '%nonliv%' THEN 'Нежилые' ... END AS room_label, COALESCE(f.rooms, -1) AS sort_key ... GROUP BY room_label, sort_key ORDER BY sort_key ``` Return на строках ~1020-1031 передаёт строки как есть, без клиентского слияния. ## Suggested fix Вывести `sort_key` из `room_label`, а не из сырого rooms. Обернуть тем же CASE: ```sql CASE WHEN <нежилые-cond> THEN -1 WHEN f.rooms=0 THEN 0 ... ELSE f.rooms END AS sort_key ``` Либо `GROUP BY room_label` only и `sort_key = MIN(...)` по группе — гарантирует одну строку на логический бакет. ## Обоснование severity (medium) Выход вводит в заблуждение и нарушает задокументированный контракт, но затрагивает только объекты с нежилыми юнитами при ненулевом rooms — подмножество, не все объекты. Confidence 0.92. --- _week ревью 1 · multi-agent аудит 2026-06-14 · severity=**medium** · category=`logic`_ Затронутые файлы: - `/Users/anton/Птица/gendesign/backend/app/services/analytics_queries.py`
bot-backend added the
week ревью 1
label 2026-06-14 18:09:05 +00:00
bot-backend added the
scope/backend
label 2026-06-15 11:01:52 +00:00
Owner

🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)

Текущее состояние кода: backend/app/services/analytics_queries.py:987-1012 (функция object_flats_quartirography).

Корень: room_label (988-995) схлопывает в 'Нежилые' по условию f.rooms IS NULL OR LOWER(f.flat_type) LIKE '%нежил%' OR '%nonliv%', но sort_key = COALESCE(f.rooms, -1) (996) берётся из сырого rooms, а GROUP BY room_label, sort_key (1011) группирует по обоим. Нежилой юнит с flat_type LIKE '%нежил%' но rooms = 2 получает room_label='Нежилые' и sort_key=2, тогда как настоящие rooms IS NULL нежилые — sort_key=-1 → две разные группы с одинаковой подписью 'Нежилые'. Возврат (1020-1031) отдаёт строки как есть, без клиентского слияния. Нарушает задокументированный контракт (docstring :974 «Группирует по rooms: 1/2/3/Нежилые»).

Что править:

  • Вывести sort_key из того же CASE, что и room_label, а не из сырого rooms:
    CASE WHEN <нежилые-cond> THEN -1
         WHEN f.rooms = 0 THEN 0
         ELSE f.rooms END AS sort_key
    
  • Либо GROUP BY room_label only + sort_key = MIN(COALESCE(f.rooms, -1)) по группе — гарантирует одну строку на логический бакет.

Что посмотреть:

  • Код: analytics_queries.py:987-1031. Учесть порядок веток CASE (нежилые проверяется ДО f.rooms = 0 → 'Студия', :990) — новый sort_key-CASE должен зеркалить тот же порядок. Опц. долгосрочно: нормализовать rooms=NULL для нежилых в скрейпере domrf_kn.py (упоминается в теле). Тест: объект с нежилым юнитом rooms=2 + нежилым rooms IS NULL → ровно одна строка 'Нежилые'.
  • Vault: релевантных заметок не найдено.
**🔍 week ревью 1 — верификация (ПОДТВЕРЖДЕНО)** **Текущее состояние кода:** `backend/app/services/analytics_queries.py:987-1012` (функция `object_flats_quartirography`). **Корень:** `room_label` (988-995) схлопывает в `'Нежилые'` по условию `f.rooms IS NULL OR LOWER(f.flat_type) LIKE '%нежил%' OR '%nonliv%'`, но `sort_key = COALESCE(f.rooms, -1)` (996) берётся из **сырого** `rooms`, а `GROUP BY room_label, sort_key` (1011) группирует по обоим. Нежилой юнит с `flat_type LIKE '%нежил%'` но `rooms = 2` получает `room_label='Нежилые'` и `sort_key=2`, тогда как настоящие `rooms IS NULL` нежилые — `sort_key=-1` → две разные группы с одинаковой подписью 'Нежилые'. Возврат (1020-1031) отдаёт строки как есть, без клиентского слияния. Нарушает задокументированный контракт (docstring `:974` «Группирует по rooms: 1/2/3/Нежилые»). **Что править:** - Вывести `sort_key` из того же CASE, что и `room_label`, а не из сырого `rooms`: ```sql CASE WHEN <нежилые-cond> THEN -1 WHEN f.rooms = 0 THEN 0 ELSE f.rooms END AS sort_key ``` - Либо `GROUP BY room_label` only + `sort_key = MIN(COALESCE(f.rooms, -1))` по группе — гарантирует одну строку на логический бакет. **Что посмотреть:** - Код: `analytics_queries.py:987-1031`. Учесть порядок веток CASE (нежилые проверяется ДО `f.rooms = 0` → 'Студия', `:990`) — новый sort_key-CASE должен зеркалить тот же порядок. Опц. долгосрочно: нормализовать `rooms=NULL` для нежилых в скрейпере `domrf_kn.py` (упоминается в теле). Тест: объект с нежилым юнитом `rooms=2` + нежилым `rooms IS NULL` → ровно одна строка 'Нежилые'. - Vault: релевантных заметок не найдено.
Sign in to join this conversation.
No milestone
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#1358
No description provided.