feat(site-finder): NSPD frontend integration в LandTab (#202) #212

Merged
lekss361 merged 1 commit from feat/202-nspd-ui-landtab into main 2026-05-16 11:55:32 +00:00
Owner

Summary

Issue #202 — frontend integration NSPD данных. Backend поставил поля в /analyze через PR #109/#110/#111/#116, но LandTab показывал hardcoded "API Росреестра закрыт, см. PKK" → false impression. Этот PR заменяет на live data.

Backend response (verified live)

  • nspd_zoning: {zone_code, zone_name, source, raw_props} (поля permitted_use/max_floors из issue spec — отсутствуют, использован реальный shape)
  • nspd_zouit_overlaps: [{group_key, layer, subcategory, name, raw_props}] (severity derived из group_key)
  • nspd_engineering_nearby: [{name, type, distance_m, raw_props}]
  • nspd_dump: {available, stale, fetched_at_utc, harvest_triggered}

Frontend additions (~800 LOC, 8 files)

File LOC Что
types/nspd.ts 71 NspdZoning/NspdZouit/NspdEngineering/NspdDumpMeta types
types/site-finder.ts +5 ParcelAnalysis extended с nspd_* optional fields
hooks/useConnectionPoints.ts 23 TanStack useQuery для /api/v1/parcels/{cad}/connection-points
components/site-finder/NspdZoningBlock.tsx 209 zone + permitted_use + PKK deep-link fallback при null
components/site-finder/NspdZouitOverlapsBlock.tsx 125 severity colors (block=red/warn=yellow/low=blue) per group_key
components/site-finder/NspdEngineeringNearbyBlock.tsx 213 combine analyze.nspd_engineering_nearby + /connection-points endpoint
components/site-finder/NspdFreshnessBadge.tsx 106 available/stale/loading badge
components/site-finder/LandTab.tsx +48/-41 удалён hardcoded "API закрыт" блок (lines 35-77), заменён 4 компонентами

Async harvest UX

  • При harvest_triggered=true && available=false — skeleton + текст «Загружаем НСПД, 15-30с»
  • Auto-polling (Phase 2) — отдельный PR (если harvest не завершился за reasonable time)

Test plan

  • tsc --noEmit + npm run lint + npm run build — все clean
  • Hardcoded "API закрыт" блок удалён из LandTab.tsx (verified grep)
  • PKK deep-link сохранён внутри NspdZoningBlock как fallback при null
  • qa-tester post-deploy:
    • chrome-devtools /site-finder?cad=66:41:0603016:194 → LandTab → 4 NSPD blocks visible
    • NspdZoningBlock показывает реальный zone_code/zone_name (не "API закрыт")
    • NspdZouitOverlapsBlock — severity badges colored correctly
    • NspdEngineeringNearbyBlock — list of structures + distances
    • NspdFreshnessBadge — green/yellow per available/stale
    • Network: /connection-points fetched on page load
    • 0 console errors

Closes part of #202. Phase 2 (auto-polling) — follow-up.

Related: PR #109/#110/#111/#116 (backend NSPD harvest), PR #190 (connection-points endpoint).

## Summary **Issue #202** — frontend integration NSPD данных. Backend поставил поля в `/analyze` через PR #109/#110/#111/#116, но LandTab показывал hardcoded "API Росреестра закрыт, см. PKK" → false impression. Этот PR заменяет на live data. ### Backend response (verified live) - `nspd_zoning: {zone_code, zone_name, source, raw_props}` (поля `permitted_use`/`max_floors` из issue spec — отсутствуют, использован реальный shape) - `nspd_zouit_overlaps: [{group_key, layer, subcategory, name, raw_props}]` (severity derived из `group_key`) - `nspd_engineering_nearby: [{name, type, distance_m, raw_props}]` - `nspd_dump: {available, stale, fetched_at_utc, harvest_triggered}` ### Frontend additions (~800 LOC, 8 files) | File | LOC | Что | |---|---|---| | `types/nspd.ts` | 71 | NspdZoning/NspdZouit/NspdEngineering/NspdDumpMeta types | | `types/site-finder.ts` | +5 | `ParcelAnalysis` extended с nspd_* optional fields | | `hooks/useConnectionPoints.ts` | 23 | TanStack `useQuery` для `/api/v1/parcels/{cad}/connection-points` | | `components/site-finder/NspdZoningBlock.tsx` | 209 | zone + permitted_use + PKK deep-link fallback при null | | `components/site-finder/NspdZouitOverlapsBlock.tsx` | 125 | severity colors (block=red/warn=yellow/low=blue) per group_key | | `components/site-finder/NspdEngineeringNearbyBlock.tsx` | 213 | combine analyze.nspd_engineering_nearby + /connection-points endpoint | | `components/site-finder/NspdFreshnessBadge.tsx` | 106 | available/stale/loading badge | | `components/site-finder/LandTab.tsx` | +48/-41 | удалён hardcoded "API закрыт" блок (lines 35-77), заменён 4 компонентами | ### Async harvest UX - При `harvest_triggered=true && available=false` — skeleton + текст «Загружаем НСПД, 15-30с» - **Auto-polling (Phase 2)** — отдельный PR (если harvest не завершился за reasonable time) ## Test plan - [x] `tsc --noEmit` + `npm run lint` + `npm run build` — все clean - [x] Hardcoded "API закрыт" блок удалён из LandTab.tsx (verified grep) - [x] PKK deep-link сохранён внутри NspdZoningBlock как fallback при null - [ ] **qa-tester post-deploy:** - chrome-devtools `/site-finder?cad=66:41:0603016:194` → LandTab → 4 NSPD blocks visible - NspdZoningBlock показывает реальный `zone_code/zone_name` (не "API закрыт") - NspdZouitOverlapsBlock — severity badges colored correctly - NspdEngineeringNearbyBlock — list of structures + distances - NspdFreshnessBadge — green/yellow per available/stale - Network: `/connection-points` fetched on page load - 0 console errors Closes part of #202. Phase 2 (auto-polling) — follow-up. Related: PR #109/#110/#111/#116 (backend NSPD harvest), PR #190 (connection-points endpoint).
lekss361 added 1 commit 2026-05-16 11:50:09 +00:00
Replace hardcoded 'API Росреестра закрыт' с live NSPD данными.

8 files / +800 LOC: types/nspd.ts, useConnectionPoints.ts, NspdZoningBlock,
NspdZouitOverlapsBlock, NspdEngineeringNearbyBlock, NspdFreshnessBadge.

LandTab.tsx — удалён старый dead-end fallback, заменён 4 компонентами с
PKK deep-link как fallback при NSPD null.

Phase 2 (harvest auto-polling) — отдельный PR.

tsc + lint + build clean.
Author
Owner

Code Review verdict — PR #212

Summary

  • Status: APPROVE
  • Scope: frontend-only (components/site-finder/**, hooks/**, types/**) — ALLOWED for auto-merge per .claude/rules/git-pr.md
  • Files reviewed: 8 (+806 / -41)
  • SHA: e5de841
  • Mergeable: true

Critical issues

None.

Minor observations (non-blocking)

  • NspdEngineeringNearbyBlock.tsx:46-52 — dedupe key uses Math.round(distanceM), so two items at 12.4 m and 12.6 m collide and one is dropped. Acceptable for informational table (sub-meter precision irrelevant for UX), but worth a follow-up if a real duplicate-detection bug surfaces.
  • useConnectionPoints.ts — error state not surfaced in NspdEngineeringNearbyBlock (only isPending is read). 404/500 from /connection-points would silently degrade to "no analyze items" instead of showing a hint. Low priority — backend currently returns 200 with empty arrays when dump absent (dump_available=false), so error path is rare.
  • Tailwind vs inline styles — frontend rule (.claude/rules/frontend.md) says "Tailwind 4 утилитарные классы; inline styles только для динамических значений". This PR uses inline styles throughout, but is consistent with existing site-finder/ blocks (NeighborsBlock, GeologyBlock, GeometrySuitabilityBlock all do the same). Not a regression — refactor would be a separate sweep.

Positive observations

  • Security: raw_props rendered via React children (String(v ?? "—")) — auto-escaped, no dangerouslySetInnerHTML anywhere → no XSS surface even if backend returns HTML-looking values. PKK URLs use encodeURIComponent(cadNum). Schemes are hardcoded https:// (allowlisted) — safeUrl() not needed.
  • Type fidelity: ConnectionPointsResponse / EngineeringStructure / ConnectionPointsSummary TS shapes mirror backend/app/schemas/parcel.py exactly (verified: nearest_structure_distance_m: float | None, dump_fetched_at: str | None, intersects_parcel: bool). No any, uses Record<string, unknown> for opaque dicts.
  • Backward compat: ParcelAnalysis.nspd_* все optional → старый бэк (без полей) не ломает UI. hasContent check корректно расширен.
  • Loading/empty/stale state machine в NspdFreshnessBadge чисто покрывает все 5 branches (no dump / harvesting / no available / stale / fresh).
  • Async harvest UX: NspdZoningBlock показывает skeleton + «Загружаем НСПД, 15–30 с» при harvest_triggered && !available && !data — соответствует issue spec.
  • PKK fallback правильно сохранён внутри NspdZoningBlock при data == null (не удалён вместе с hardcoded блоком).
  • TanStack Query с staleTime: 5 min, enabled: !!cadNum && enabled — корректно. Default gcTime (5 min) подходит для этого use case. cadNum! non-null assertion безопасен из-за enabled guard.
  • Severity mapping в NspdZouitOverlapsBlock: okn/protected → red (block), engineering → yellow (warn), natural → blue (low), other → gray — разумный default.
  • LandTab integration: hardcoded «API Росреестра закрыт» полностью удалён, заменён 4 компонентами без impact на другие tab'ы.

Auto-polling (Phase 2)

Known follow-up — отдельный PR. Текущая реализация показывает skeleton без авто-refetch; user должен перезагрузить страницу через ~30s. Acceptable для MVP.

  • Merge можно делать.
  • qa-tester post-deploy: проверить 4 блока на 66:41:0603016:194 + check Network tab на /connection-points fetch.
<!-- gendesign-review-bot: sha=e5de841 verdict=approve --> ## Code Review verdict — PR #212 ### Summary - **Status:** APPROVE - **Scope:** frontend-only (`components/site-finder/**`, `hooks/**`, `types/**`) — ALLOWED for auto-merge per `.claude/rules/git-pr.md` - **Files reviewed:** 8 (+806 / -41) - **SHA:** `e5de841` - **Mergeable:** true ### Critical issues None. ### Minor observations (non-blocking) - **`NspdEngineeringNearbyBlock.tsx:46-52`** — dedupe key uses `Math.round(distanceM)`, so two items at 12.4 m and 12.6 m collide and one is dropped. Acceptable for informational table (sub-meter precision irrelevant for UX), but worth a follow-up if a real duplicate-detection bug surfaces. - **`useConnectionPoints.ts`** — error state not surfaced in `NspdEngineeringNearbyBlock` (only `isPending` is read). 404/500 from `/connection-points` would silently degrade to "no analyze items" instead of showing a hint. Low priority — backend currently returns 200 with empty arrays when dump absent (`dump_available=false`), so error path is rare. - **Tailwind vs inline styles** — frontend rule (`.claude/rules/frontend.md`) says "Tailwind 4 утилитарные классы; inline styles только для динамических значений". This PR uses inline styles throughout, but is consistent with existing `site-finder/` blocks (NeighborsBlock, GeologyBlock, GeometrySuitabilityBlock all do the same). Not a regression — refactor would be a separate sweep. ### Positive observations - **Security:** `raw_props` rendered via React children (`String(v ?? "—")`) — auto-escaped, no `dangerouslySetInnerHTML` anywhere → no XSS surface even if backend returns HTML-looking values. PKK URLs use `encodeURIComponent(cadNum)`. Schemes are hardcoded `https://` (allowlisted) — `safeUrl()` not needed. - **Type fidelity:** `ConnectionPointsResponse` / `EngineeringStructure` / `ConnectionPointsSummary` TS shapes mirror `backend/app/schemas/parcel.py` exactly (verified: `nearest_structure_distance_m: float | None`, `dump_fetched_at: str | None`, `intersects_parcel: bool`). No `any`, uses `Record<string, unknown>` for opaque dicts. - **Backward compat:** `ParcelAnalysis.nspd_*` все optional → старый бэк (без полей) не ломает UI. `hasContent` check корректно расширен. - **Loading/empty/stale state machine** в `NspdFreshnessBadge` чисто покрывает все 5 branches (no dump / harvesting / no available / stale / fresh). - **Async harvest UX:** `NspdZoningBlock` показывает skeleton + «Загружаем НСПД, 15–30 с» при `harvest_triggered && !available && !data` — соответствует issue spec. - **PKK fallback** правильно сохранён внутри `NspdZoningBlock` при `data == null` (не удалён вместе с hardcoded блоком). - **TanStack Query** с `staleTime: 5 min`, `enabled: !!cadNum && enabled` — корректно. Default `gcTime` (5 min) подходит для этого use case. `cadNum!` non-null assertion безопасен из-за `enabled` guard. - **Severity mapping** в `NspdZouitOverlapsBlock`: okn/protected → red (block), engineering → yellow (warn), natural → blue (low), other → gray — разумный default. - **LandTab integration**: hardcoded «API Росреестра закрыт» полностью удалён, заменён 4 компонентами без impact на другие tab'ы. ### Auto-polling (Phase 2) Known follow-up — отдельный PR. Текущая реализация показывает skeleton без авто-refetch; user должен перезагрузить страницу через ~30s. Acceptable для MVP. ### Recommended next steps - Merge можно делать. - qa-tester post-deploy: проверить 4 блока на `66:41:0603016:194` + check Network tab на `/connection-points` fetch.
lekss361 merged commit e450546178 into main 2026-05-16 11:55:32 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#212
No description provided.