fix(ptica): лоты без площади — своя корзина, а не «<25 м²» (#2464) #2983
No reviewers
Labels
No labels
Fable 5 ревью
GG-форсайт
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
feedback/max
generative
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
ИРД
вторичка
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#2983
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/2464-area-bin-null"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Дефект
_SUPPLY_ONLY_LOTS_SQLраскладывал лоты без площади в ту же корзину, что и настоящие студии:Сколько это на проде
Замер 20.08.2026 — последний снапшот на физлот,
premise_kind='квартира', не проданные:Корзина «<25» = 18 570 лотов, из которых 62 % — неизвестное. Блок «По предложению (без темпа продаж)» показывал долю мелких лотов почти втрое завышенной.
У апартаментов та же картина, но мельче: 31 без площади из 1 235 (2.5 %).
Зеркала у этого правила не было
Комментарий над запросом говорит «area_bin — зеркало layout_signature.area_bin». Проверил обе стороны:
layout_signature.area_bin(area_m2: float)— принимает float, NULL-ветки не имеет вовсе;94_mv_layout_velocity.sql— по площади вообще не группирует.То есть
NULL → '<25'не переносило чужое правило, а придумывало своё.Правка
Отдельная корзина «н/д».
Исключать такие лоты нельзя — они реально в продаже, и без них предложение занизилось бы на 6.4 %. Медиана площади у корзины «н/д» выйдет
NULL(PERCENTILE_CONTигнорирует NULL) — то есть честно «неизвестно», а не выдуманное число.Схема не меняется:
area_binостаётсяstr, OpenAPI и типы фронта прежние. В PDF-экспортёре значение попадает в ячейку через_html.escape— новая метка рисуется штатно.Как проверено
Тест герметичный и прогоняет настоящий SQL: временная таблица
objective_lotsзатеняет боевую в пределах сессии (temp-схема идёт раньшеpublicвsearch_path), поэтому запрос берётся из модуля дословно, а прод-данные не читаются и не меняются.origin/mainкорзины выходят{'<25': 2, '25-40': 1, '40-60': 1}— конкретное неверное значение (два лота вместо одного), ноль TypeError/ImportError/KeyError.pytest backend/tests/sql/— 38 passed (через туннель к проду).skip_allowlist.txtс причиной, по образцу соседних DB-тестов.Что изменится в отчёте после мержа
В блоке «По предложению» у комплексов с неполными данными появится строка «н/д» с прочерком в столбце «Ср. площадь». Доля «<25» упадёт до настоящей. Отчитаюсь числами после деплоя.
Часть эпика #2464.
`_SUPPLY_ONLY_LOTS_SQL` раскладывал `area_pd IS NULL` в ту же корзину `'<25'`, что и настоящие студии. Замер прода 20.08.2026 (последний снапшот на физлот, premise_kind='квартира', не проданные): в продаже 181 353 без area_pd 11 557 (6.4 %) реально < 25 м² 7 013 Корзина «<25» состояла из неизвестного на 62 % и завышала долю мелких лотов в блоке «По предложению (без темпа продаж)». Зеркала у такого отображения не было: `layout_signature.area_bin` принимает float и NULL-ветки не имеет вовсе, а velocity-MV по площади не группирует — то есть `NULL → '<25'` было выдумкой, а не переносом чужого правила. Исключать такие лоты нельзя: они реально в продаже, и без них предложение занизилось бы на 6.4 %. Поэтому отдельная корзина «н/д». Медиана площади у неё выйдет NULL (PERCENTILE_CONT игнорирует NULL) — честно. Схема не меняется: area_bin остаётся str, OpenAPI прежний. Тест герметичный и прогоняет НАСТОЯЩИЙ SQL: временная таблица objective_lots затеняет боевую в пределах сессии, запрос берётся из модуля дословно, прод-данные не читаются. Двусторонне: против origin/main корзины распределяются как {'<25': 2, '25-40': 1, '40-60': 1} — конкретное неверное значение, ни одного TypeError/ImportError. Контроли (сумма лотов сохраняется, обычные корзины не меняются) зелёные с обеих сторон. pytest backend/tests/sql/ — 38 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>Проверено на проде
Код в живом контейнере (не по статусу джобы):
Эффект на живых данных — распределение корзин по квартирам в продаже:
Корзина «<25»: было 18 570 → стало 7 013. Доля мелких лотов в структуре остатков падает с 10.3 % до 3.9 % — то есть больше чем вдвое была не мелкими лотами, а неизвестной площадью.
Ни один лот не потерян: 11 557 переехали в «н/д», сумма по корзинам прежняя. Медиана площади у «н/д» — NULL, как и задумано:
PERCENTILE_CONTигнорирует NULL, и в отчёте там будет прочерк, а не выдуманное число.