fix(parcels): _parse_floors handle int (post-migration #169) #176
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
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#176
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/parse-floors-int"
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?
Summary
cad_buildings.floorsтеперь INT (был TEXT), but_parse_floors()ожидал str →'int' object has no attribute 'strip'Error from prod logs
Fix
Type union
str | int | None. Ifint→ return directly. Else strip + parse как раньше.Test plan
POST /api/v1/parcels/66:41:0204016:10/analyzereturns 200neighbors_summaryрендерится в UICloses 500 regression from #169.
Review
CI ещё pending; сам hotfix
_parse_floorsкорректный и тонкий — но в этом же PR прилетают smoke-тестыtests/smoke/test_prod_smoke.py, которые сломают CI policy. Блокер до того как смерджу.✅ Что хорошо
_parse_floorsfix корректен. Подтвердил поdata/sql/92_cad_bulk_layers.sql—cad_buildings.floorsтеперьINT. Оба caller'а (parcels.py:715и:789) читаютr.get("floors")из row dict, никакихboolне попадёт. Сохранилась эквивалентность для0/""/None: пустая строка → None через явный check,0→ isinstance branch →0 > 0 == False→ None.floors— нет упоминаний вapp/schemas/parcel.pyиfrontendобращается кneighbors_summaryкак opaque object.🔴 Блокер: prod_smoke marker не зарегистрирован → CI любого PR будет стрелять в prod
.github/workflows/ci.yml:60—uv run pytest -q(без-mфильтра).backend/pyproject.toml [tool.pytest.ini_options].markers— толькоslow,prod_smokeотсутствует.pytestmark = pytest.mark.prod_smokeНЕ skip'нет тесты, потому что у marker'а нет deselect-механизма без-m 'not prod_smoke'в addopts. Pytest выкинетPytestUnknownMarkWarning, но соберёт и выполнит все тесты.Эффект: каждый push/PR от любого fork'а будет в CI делать:
POST https://gendsgn.ru/api/v1/parcels/66:41:0204016:10/analyze(60s timeout × 3 теста)GET https://gendsgn.ru/health,/site-finder,/admin/scrape/*То есть hammer prod из всех CI-раннеров + утечка prod URL в логи forked PRs.
В docstring файла прямо написано "skip by default in CI, run only on-demand" — но реализация это не обеспечивает.
Требуемый фикс (в этом же PR)
В
backend/pyproject.toml [tool.pytest.ini_options]:Тогда дефолтные
pytest -qбудут исключать smoke по умолчанию, а post-deploy workflow сможет явно запуститьpytest -m prod_smokeoverride.Минор (некритичный — можно follow-up)
test_parcels_analyze_returns_200:91-93логически верный (фейлится только приdata_available=False AND note startswith "neighbors query failed"), но фразаis not False or (... is False)нечитаема — лучшеassert not (data_available is False and note.startswith(...)).Жду нового SHA с
addoptsфильтром.Re-review (SHA
4ace87a)CI fail в backend job — именно тот сценарий что я и предупреждал в прошлом ревью:
CI runner GitHub Actions сделал POST на
https://gendsgn.ru/api/v1/parcels/.../analyze(потому что fix ещё не задеплоен!) → prod вернул 500 → тесты упали → CI red. И prod URL утёк в публичные CI логи.🔴 Что НЕ исправлено
backend/pyproject.tomlвсё тот же (проверил branchfix/parse-floors-int):То что я просил в предыдущем комменте:
Без этого smoke-тесты будут стрелять в prod из CI каждого push'а + PR'а. Это обязательный фикс в этом же PR, без него мерджить нельзя.
🟡 Scope creep — NSPD headers
В этом же SHA прилетели изменения в
backend/app/scrapers/nspd_bulk_client.py(rewriteDEFAULT_HEADERSпод legacynspd_lite.pyheaders — Chrome 144, lowercase keys, cache-control). Это не из PR title "fix(parcels): _parse_floors". Это самостоятельный fix WAF blocks на pilot v2, заслуживает отдельного PR или хотя бы upgraded title + body.Так как изменение
headers— это connection-config diff и риск низкий (legacy precedent уже работает), могу пропустить если будет:Что нужно для approve+merge
prod_smokemarker +addopts = "-m 'not prod_smoke'"вbackend/pyproject.toml [tool.pytest.ini_options].Жду нового SHA.
Review (SHA
d83fb9c)Все блокеры из прошлых ревью закрыты + cross-check (agent):
✅ Закрыто
backend/pyproject.toml—addopts = ["-m", "not prod_smoke"]+ markerprod_smokeзарегистрирован. List-formaddoptsвалиден, pytest парсит какpytest -m "not prod_smoke"._parse_floors: type-unionstr | int | None, isinstance fast-path для INT, сохранён range-parser для backwards-compat. Оба caller'а (parcels.py:715,:789) читают row dict.✅ NSPD headers — низкий риск
Изменение в
nspd_bulk_client.pyDEFAULT_HEADERS (Chrome 148→144, lowercase keys, +cache-control/pragma/origin) — outbound-only, exact matchnspd_lite.py. Проверилbackend/tests/scrapers/— нет assertions на header keys/values, mock'и stuck на httpx response side. Inline-комментарий "blocked 50/50 в pilot run v2" justifies change. Принимаю как WAF hotfix-продолжение, в идеале PR body стоило бы обновить — оставлю как минор.✅ Contract drift отсутствует
backend/app/schemas/не содержит поляfloors; frontend относится кneighbors_summaryкак opaque. NSPD headers — outbound only.approve merge