feat(scrapers): search_by_quarter + QuarterDump (#94 pt.2/4) #109
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#109
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/nspd-quarter-dump"
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
Sprint 1.1 item #1 из 4-частного плана #94 part 2 (foundation для NSPD harvest pipeline).
Замыкает: G1 #28 ПЗЗ, G3 #30 ЗОУИТ, P2 #46 neighbors, E1 #51 parcels backfill, #96 ЕГРН помещения — все они смогут читать
QuarterDumpвместо собственных NSPD-вызовов.Что внутри
backend/app/services/scrapers/nspd_client.py(+225 lines)QuarterDump— frozen dataclass (slots=True) snapshot структура:quarter: NSPDFeature | None — сам polygon кварталаparcels,buildings,territorial_zones,red_lines,engineering_structureszouit: dict[str, list[NSPDFeature]]— 5 ЗОУИТ groups (okn/engineering/natural/protected/other)risks: dict[str, list[NSPDFeature]]— 11 risk groups (flooding/landslide/erosion/etc)bbox_3857,layers_fetched(tuple для immutability),fetched_at_utc.total_featuresproperty для smoke checksNSPDClient.search_by_quarter(quarter_cad, include_zouit=True, include_risks=False)— orchestrator:search_by_cad(thematic_id=2)для quarter geometry_geojson_bbox_3857get_features_in_bboxinclude_zouit: 5 ЗОУИТ layersinclude_risks: 11 risk layersTotal cost: 6 / 11 / 22 HTTP в зависимости от флагов (~3.6с / ~6.6с / ~13с при
rate_ms=600)._geojson_bbox_3857(geometry)— module-level recursive coord walker. Поддерживает Polygon/MultiPolygon/LineString/Point, returns None для empty/garbage geometry.Class constants
QUARTER_CORE_LAYERS,QUARTER_ZOUIT_LAYERS,QUARTER_RISK_LAYERSмаппят short_name → key вLAYERScatalog (из PR #98).backend/tests/test_nspd_client.py(+334 lines)+12 unit tests (31 total in file), no network:
_geojson_bbox_3857: 6 tests (polygon / multipolygon / linestring / point / empty / no_type)QuarterDump: 2 tests (total_featuressum verification, frozen immutability via AttributeError)search_by_quarter: 4 mock tests (core_only/with_zouit/empty_quarter/layers_fetched_with_risks) черезmonkeypatch.setattrнаfetch_geoportal+_http_get_jsonEmpty-quarter semantics
Когда NSPD не нашёл cad-номер квартала:
quarter=None,bbox_3857=None_fetch_layer)zouit/risksdicts populated с пустыми lists для каждого short_name (контракт структуры стабилен — frontend знает какие keys ожидать)layers_fetched=('search',)— никаких bulk-fetches не было сделаноPre-push review (code-reviewer agent)
Verdict: MINOR, 0 blocking. Применено 3 из 5 рекомендаций перед push:
import datetimeперенесён на module-level (был inline)layers_fetchedтип →tuple[str, ...](immutable contents в frozen dataclass; bbox_3857 уже был tuple — теперь consistency)RUF012ClassVar annotation на class-level constants; inlineLAYERS as _LAYERSimport в тестовом helper'еBonus: ruff UP038
isinstance(node, (int, float))→isinstance(node, int | float)(PEP 604 union syntax).Acceptance (per #94 plan)
search_by_quarter()метод реализованQuarterDumpтип готов как контракт для downstream consumerssearch_by_cad+get_features_in_bbox(no new HTTP layer)nspd_quarter_dumpstable → отдельный PR (Sprint 1.1 item #2)harvest_quartertask + beat → отдельный PR (Sprint 1.1 item #3)analyze_parcelintegration через QuarterDump → отдельный PR (Sprint 1.1 item #4)Test plan
ruff check + format— cleanpytest tests/test_nspd_client.py -v— 31/31 passed in 0.05sPart of #94. Sequential rule — следующий PR создаётся ТОЛЬКО после merge этого.
✅ Auto-review passed (LGTM) — pt.2/4 NSPD scraper rewrite, чисто.
Architecture & contracts:
NspdLiteWafErrorpropagается, не возвращаются partial results — соответствует vault research про WAF.bbox is Noneguard в_fetch_layerкорректно предотвращает HTTP calls когда quarter не найден (verified test).get_features_in_bbox(foundation PR).frozen=True, slots=TrueнаQuarterDump— правильный выбор для immutable snapshot.Geometry handling:
_geojson_bbox_3857обрабатывает Point/LineString/Polygon/MultiPolygon через recursive_walk— covered all 4 types + edge cases.thematic_id=2correct (quarters per NSPD_API_Map_May12.md).Tests:
Conventions:
requests, noprint(), no hardcoded creds, no injection. Full type annotations.Minor nits (non-blocker, для follow-up):
layers_fetchedсемантика = "layer was requested" (не "non-empty"). Рассмотреть rename →layers_requestedв pt.3/4.fetched_at_utc: strвместоdatetime— pragmatic для frozen+JSON-serializable, но property возвращающий datetime был бы чище.QUARTER_*_LAYERSиспользует# noqa: RUF012— рассмотретьClassVar[dict[str, str]]в cleanup pass.test_nspd_client.py:447URL parsingparts[6]хрупкое — добавить комментарий о assumed URL structure.Merging.