Лучшие планировки: в «покрытие P% (Y из N комплексов)» N считается одинаково во всех ветках #3570
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#3570
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/ptica-best-layouts"
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?
Пункт эпика #2464
best_layouts.py:1113. Эпик этим PR не закрывается: в его списке остаются открытые пункты, напримерcompetitors.py:553, который ждёт открытого #2962.Что было
get_best_layoutsотдаётdata_quality.objects_total_in_radiusиз трёх веток, и считали они это поле по-разному (сверено на origin/main@34642e1d)::1141)groups_total_pre_filter, до exclude/filter:1231)len(complex_groups), после фильтра:1464)groups_total_after_filter, после фильтра20.08 пункт остановили с пометкой «нужно решение, какой смысл у поля верен». Позже к нему дописали «ОТКЛОНЁН» с доводом «поле описывает радиус, до фильтра», но этот довод противоречит двум веткам из трёх. Дефект остался в коде.
Почему выбран смысл «после фильтра»
Решение принято по тому, как поле используют, а не по его имени. Потребителей у поля два (
git grepпо репозиторию), и оба выводят его как N в строке про покрытие:frontend/src/components/site-finder/BestLayoutsBlock.tsx:133: «покрытие P% (Y из N комплексов)»;backend/app/services/exporters/layout_tz_pdf.py:201: «Покрытие: Y из N комплексов с данными velocity (P%)».В штатной ветке P = Y / (комплексы после фильтра). Если в N подставить число до фильтра, строка начнёт противоречить сама себе. Вторая фальсификация ниже это показывает: при таком N пользователь увидел бы «покрытие 100% (1 из 2 комплексов)». Сырое число obj_id в радиусе до фильтра по-прежнему отдаётся в
raw_objects_total, так что эта информация не теряется.Поэтому изменена только ранняя ветка «фильтр никого не оставил»: теперь там
len(complex_groups). Штатная ветка и ветка «нет velocity» не менялись.Если владелец выберет смысл «до фильтра», придётся менять штатную ветку и добавлять отдельное поле для знаменателя покрытия. Сейчас это дёшево: пользователи этой разницы пока не видят (см. ниже).
Что сделано
best_layouts.py: убранаgroups_total_pre_filter, ранний пустой ответ отдаётlen(complex_groups).schemas/parcel.py: комментарий к полю теперь говорит «после exclude/filter (знаменатель coverage)». Это Python-комментарий, OpenAPI не меняется.test_objects_total_is_coverage_denominator_in_every_branchпараметризован по трём веткам (all-filtered / no-velocity / normal). В каждой ветке он проверяет значения:objects_total_in_radius,objects_with_velocity_data,raw_objects_total == 2иvelocity_coverage_pct= Y/N.test_exclude_competitor_obj_ids,test_exclude_competitor_obj_ids_filter). Теперь они ждутobjects_total_in_radius == 0иraw_objects_total == 1.Тесты
backend:pytest tests/services/site_finder/test_best_layouts.py tests/api/v1/test_parcel_best_layouts.pyдал 96 passed, rc=0.backend(TESTING=1 pytest tests/) после rebase на99f8525b: 5061 passed, 87 skipped, 4 failed, rc=1. Все 4 падения вtests/ops/test_2203_backup_trailer_grep_dashdash.py: на macOS BSDmktempне знает--suffix=.sql.gz. К этой правке они отношения не имеют.ruff check .— All checks passed (rc=0);ruff format --checkпо 4 изменённым файлам — already formatted (rc=0).tradein-mvp/. Файлы этого PR иbackend/они не трогают.Фальсификация
1. Вернул исходник
best_layouts.pyиз origin/main, тесты остались новыми. Результат: rc=1,3 failed, 93 passed:2. Перевёл штатную ветку на число до фильтра (вариант «до фильтра» без правки знаменателя). Результат: rc=1,
1 failed, 95 passed:Это и есть та самая противоречивая строка «100% (1 из 2)». После обеих проверок исходник восстановлен из копии,
diff -qрасхождений не нашёл.Приёмка на проде (после деплоя; на 17.09.2026 не проверено)
Пользователь пока не видит эффекта. Фронт не передаёт фильтр:
BestLayoutsBlockподключён вMarketTab.tsx:271иSection3SettingsAndCompetitors.tsx:295безselectedCompetitorObjIds. По замеру 20.08 из 4094 разборов за 120 суток ни один не передавалexclude/filter_competitor_obj_ids. Поэтому приёмка идёт по коду в контейнере и по живому запросу:ssh poincare docker exec gendesign-backend-1 grep -c groups_total_pre_filter /app/app/services/site_finder/best_layouts.pyдолжен вернуть0(живой контейнер 17.09 вернул2: объявление и использование на:1141).POST /api/v1/parcels/{cad}/best-layoutsс{"filter_competitor_obj_ids": [-1]}на любом участке, где есть конкуренты:data_quality.objects_total_in_radius == 0,raw_objects_total > 0. До деплоя вobjects_total_in_radiusприходило число комплексов в радиусе.objects_total_in_radiusсовпадает со значением до деплоя.Refs #2464
🤖 Generated with Claude Code