fix(cadastre): NSPD response parse — properties.category + top-level meta (#168) #177
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#177
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/nspd-response-parse"
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
Bugs
1.
NSPDBulkFeature.category_idищетcategoryId— реальное полеcategoryNSPD response:
properties.category(без Id suffix). Initial HAR parse misinterpreted.Fix: try both for backwards compat.
2.
metaлежит на top-level, не вdataReal shape:
{"data": {"features": [...]}, "meta": [...]}. Code искал вdata.meta.Fix:
payload.get("meta") or data_block.get("meta") or [].Live verification on prod
Без этих фиксов upsert_features routes по None category_id → no-op для всех 50/50 quarters в pilot v3.
Test plan
Review
Cross-check (agent) выявил реальный test-coverage gap. CI зелёный + diff минимальный, но существующие фикстуры тестируют только legacy fallback, не новый primary path. Это критично — именно потому что не было unit-теста, парс-баг прошёл аж до prod и положил все 50/50 quarters в pilot v3.
🔴 Блокер: новые ветки кода не покрыты тестами
backend/tests/scrapers/test_nspd_bulk_client.py:51,71иbackend/tests/services/test_cadastre_bulk.py:45,79,115,240,254,461,472— все фикстуры всё ещё используют"categoryId": Nвproperties. После фикса CI ходит по fallback branch (properties.get("categoryId")), а primary branch (properties.get("category")) не выполняется ни в одном тесте.То же самое для meta — фикстура в
test_nspd_bulk_client.py:75-83всё ещё держитmetaвнутриdata.meta(legacy shape), top-level meta path не тестируется.Author заявляет live verification на quarter
66:41:0303161→ 101 features, но recorded payload в repo не положен. Это уже третий "live-only" парс-фикс подряд (#175 verify=False, #176 headers + smoke, #177 parse) — паттерн пора прервать unit-тестом.Минимально нужно:
properties.categoryId→properties.category(или добавить новый кейс)metaв одной фикстуре на top-level🟡
int(val)без try/except — crash на non-numeric stringbackend/app/schemas/nspd_bulk.py:75— если NSPD когда-нибудь вернётcategory: "ЗУ"(а они известны type-inconsistency'ю),int(val)рейзит ValueError посреди loop'а вbulk_harvest.upsert_features:295и убивает harvest остальных features в quarter. Используй уже существующий_safe_intизbulk_harvest.pyили try/except → None.🟡 Truthiness trap (низкий риск, но real)
properties.get("category") or properties.get("categoryId")— если NSPD вернётcategory: 0(наблюдаемые ID 36368/36369/37159 — все truthy, поэтому в practice safe), fallback сработает неожиданно. Правильнее:То же для meta
payload.get("meta") or data_block.get("meta")— но дляmeta=[]поведение совпадает (оба → пустой meta_counts), оставлю как минор.✅ Что хорошо
bulk_harvest.py:296-318маппитcat == CAT_PARCEL (36368)—category_idвсё ещё возвращает тот же int, источник просто сменился. Подтверждает диагноз "all 50/50 routed to None".meta_countsвсё ещё читаетcategoryId(с Id) — соответствует author'скому live payload[{"totalCount":170,"categoryId":36368}]. Только location переехала, parser unchanged.admin/scrape/cadastre/page.tsx) парсит admin job-state, не NSPD feature shape.Что нужно
test_nspd_bulk_client.pyилиtest_cadastre_bulk.pyчтобы веткаproperties.category+ top-levelmetaреально выполнилась в CI._safe_int(val)вместо гологоint(val)вcategory_idproperty.is Nonecheck.Жду новый SHA.
Re-review (SHA
4f48684)Все блокеры из прошлого комента закрыты + cross-check (agent):
✅ Test coverage gap — closed
test_nspd_bulk_client.py:146-158—test_nspd_bulk_feature_parse_basicтеперь валидирует feature с"category": 36368и assert'итfeat.category_id == 36368. Primary path реально выполняется.test_cadastre_bulk.py— все 3 фикстуры (_make_parcel_feature,_make_building_feature,_make_zouit_feature) и inline-cases (37, 79, 115, 240, 254, 461, 472) теперь используютproperties.category.upsert_featuresтестируется по primary path.sample_quarter_response—metaпереехал на top-level (выпрыгнул изdataблока).test_search_by_quarter_parses_featuresvalidatesmeta_counts→ top-level meta path exercised.✅ Truthiness trap + int crash — closed
category=0—is not Noneкорректно возвращает 0 (а не падает к fallback).category="ЗУ"— ValueError caught → None (не убивает upsert_features loop).categoryId=Nfallback работает когдаcategoryотсутствует.✅ CI зелёный
backend 1m16s + frontend 1m35s — оба новых code path реально выполняются + не сломали legacy fallback.
Минор (не блокер)
Нет отдельных юнит-тестов на
int("ЗУ")иcategory=0paranoia ветки — fixture edits покрыли rename, но defensive code (try/except) explicitly не тестируется. Логика прямолинейная, low risk, оставлю как минор. После pilot v4 ничто не мешает добавить параметризованный тест на error paths.approve merge