feat(sf-fe-a10): Section 4 «Оценка участка» #358

Merged
lekss361 merged 1 commit from feat/sf-fe-a10-section4-estimate into main 2026-05-18 03:57:25 +00:00
Owner

Summary

SF Frontend Wave 4 §A10: Section 4 «Оценка участка» на analysis page.

Files (2)

NEW frontend/src/components/site-finder/analysis/Section4Estimate.tsx:

  • id="section-4" + scrollMarginTop: 72
  • Canonical useParcelAnalyzeQuery(cad) — TanStack Query кэш shared с другими секциями (no extra request)
  • Loading skeleton + error state
  • <HeadlineBar>"Оценка: {score} · {LABEL}" + subtitle с gate_verdict + score_explanation
  • <GateVerdictBanner> — conditional gate_verdict
  • 2-col: <ScoreBreakdownPanel> + <ScoreBreakdownStackedBar> (collapse 1-col если no score_breakdown_detailed)
  • Geology / Hydrology / GeotechRisk blocks — repeat(auto-fit, minmax(220px, 1fr)), conditional каждый
  • <NspdZouitOverlapsBlock> — conditional nspd_zouit_overlaps
  • <SuccessRecommendationBlock> — conditional success_recommendation

Modified frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx:

  • Section 4 placeholder → <Section4Estimate cad={cad}/>
  • Sections 1/2/3/5 wiring PRESERVED (verified git diff)

Real data

  • Все blocks conditional на presence соответствующего field в B5 response
  • Graceful empty-state без mock fallback
  • Canonical hook (lib/site-finder-api)

Constraints

  • TS strict
  • Canonical useParcelAnalyzeQuery
  • Real backend data
  • CSS tokens (globals.css)
  • pre-commit prettier + lint pass + tsc --noEmit 0 errors
  • Sections 1/2/3/5 PRESERVED

Verify post-deploy

  • /site-finder/analysis/66:41:0204016:10 — Section 4 рендерится с HeadlineBar + Score breakdown + (optional) geology/hydrology/zouit/recommendation
  • Sections 1/2/3 продолжают рендериться

Part of plan vault code/patterns/SiteFinder_Frontend_Migration_Plan_May18.md §A10.

## Summary SF Frontend Wave 4 §A10: Section 4 «Оценка участка» на analysis page. ## Files (2) **NEW** `frontend/src/components/site-finder/analysis/Section4Estimate.tsx`: - `id="section-4"` + `scrollMarginTop: 72` - Canonical `useParcelAnalyzeQuery(cad)` — TanStack Query кэш shared с другими секциями (no extra request) - Loading skeleton + error state - `<HeadlineBar>` — `"Оценка: {score} · {LABEL}"` + subtitle с gate_verdict + score_explanation - `<GateVerdictBanner>` — conditional `gate_verdict` - 2-col: `<ScoreBreakdownPanel>` + `<ScoreBreakdownStackedBar>` (collapse 1-col если no `score_breakdown_detailed`) - Geology / Hydrology / GeotechRisk blocks — `repeat(auto-fit, minmax(220px, 1fr))`, conditional каждый - `<NspdZouitOverlapsBlock>` — conditional `nspd_zouit_overlaps` - `<SuccessRecommendationBlock>` — conditional `success_recommendation` **Modified** `frontend/src/app/site-finder/analysis/[cad]/AnalysisPageContent.tsx`: - Section 4 placeholder → `<Section4Estimate cad={cad}/>` - Sections 1/2/3/5 wiring **PRESERVED** (verified `git diff`) ## Real data - Все blocks conditional на presence соответствующего field в B5 response - Graceful empty-state без mock fallback - Canonical hook (lib/site-finder-api) ## Constraints - ✅ TS strict - ✅ Canonical `useParcelAnalyzeQuery` - ✅ Real backend data - ✅ CSS tokens (globals.css) - ✅ pre-commit prettier + lint pass + `tsc --noEmit` 0 errors - ✅ Sections 1/2/3/5 PRESERVED ## Verify post-deploy - `/site-finder/analysis/66:41:0204016:10` — Section 4 рендерится с HeadlineBar + Score breakdown + (optional) geology/hydrology/zouit/recommendation - Sections 1/2/3 продолжают рендериться Part of plan vault `code/patterns/SiteFinder_Frontend_Migration_Plan_May18.md` §A10.
lekss361 added 1 commit 2026-05-18 01:24:22 +00:00
Add Section4Estimate component wiring real backend data from
useParcelAnalyzeQuery (canonical hook). Renders conditionally:
HeadlineBar with score + label, GateVerdictBanner, 2-col
ScoreBreakdownPanel + ScoreBreakdownStackedBar, Geology/Hydrology/
GeotechRisk grid, NspdZouitOverlapsBlock, SuccessRecommendationBlock.
Replace placeholder in AnalysisPageContent.tsx; sections 1-3 and 5 preserved.
Author
Owner

Deep Code Review — verdict: APPROVE

Files reviewed: 2 (P2: 1 NEW, 1 MOD) — +260/-8

Sub-component imports (all resolve, prop contracts match)

Import File exists Named export Props contract
HeadlineBar from @/components/ui/HeadlineBar yes yes {title, subtitle, rightSlot?} matches
GateVerdictBanner from @/components/site-finder/GateVerdictBanner yes yes {verdict: GateVerdict | null | undefined} matches
ScoreBreakdownPanel yes yes {topPositives, topNegatives, byGroup, detailed} exact match
ScoreBreakdownStackedBar yes yes {breakdown: FactorContribution[]} matches (passing detailed)
GeologyBlock yes yes {geology: ParcelAnalysisGeology} matches
HydrologyBlock yes yes {hydrology: Hydrology} matches
GeotechRiskBlock yes yes {risk: GeotechRisk} matches
NspdZouitOverlapsBlock yes yes {overlaps: NspdZouitOverlap[]} matches (default [] if null)
SuccessRecommendationBlock yes yes {recommendation: ParcelSuccessRecommendation | null | undefined} matches

All ParcelAnalysis field accesses (score, score_label, score_explanation, score_top_3_positives, score_top_3_negatives, score_by_group, score_breakdown_detailed, gate_verdict, confidence_label, geology, hydrology, geotech_risk, nspd_zouit_overlaps, success_recommendation) verified in frontend/src/types/site-finder.ts:330-397.

Sections 1/2/3/5 PRESERVED

AnalysisPageContent.tsx diff shows ONLY the Section 4 placeholder swap (lines 130-137 → <Section4Estimate cad={cad} />). Section 1/2/3 component calls, Section 5 placeholder, breadcrumb, sidebar nav, loading/error branches — all untouched. No regression risk on existing sections.

Conditional render hygiene (clean)

  • analysis.gate_verdict && <GateVerdictBanner.../> — guarded
  • hasBreakdown && <grid> with gridTemplateColumns: hasStackedBar ? "1fr 1fr" : "1fr" — proper 2→1-col collapse
  • Risk grid wrapped in (geology \|\| hydrology \|\| geotech_risk) && <grid> with repeat(auto-fit, minmax(220px, 1fr)) — responsive, conditional per-block inside
  • hasZouit checks both !== undefined && !== null
  • success_recommendation !== undefined guard (and inner component handles null || ranking.length === 0)
  • Empty state: HeadlineBar always renders (score + label fallback to just "Оценка: {score}" if no label) — no blank section
  • score.toFixed(1) — if score == null this would throw, but ParcelAnalysis.score is non-nullable in types so OK

Conventions (frontend.md + ui-ux.md)

  • "use client" directive present (TanStack Query hook used)
  • TS strict — one as unknown as ParcelAnalysis cast (matches the established pattern in AnalysisPageContent.tsx:87, same justification: ParcelAnalyzeResponse is a subset of ParcelAnalysis)
  • Canonical useParcelAnalyzeQuery(cad) — cache shared with parent, no extra HTTP
  • CSS tokens used (--bg-headline, --bg-card, --bg-card-alt, --border-card, --border-soft, --danger, --danger-soft, --fg-secondary) — no raw hex
  • No dangerouslySetInnerHTML, no XSS surface (only plain text rendering of score_explanation / verdict_label)
  • No emoji, no console.log, no any
  • id="section-4" + scrollMarginTop: 72 matches Section 5 placeholder pattern for sidebar scrollspy

Minor nits (non-blocking)

  • L41 buildHeadlineTitle: score.toFixed(1) assumes score: number (which it is per type). If backend ever returns null, this throws — but type-safe today.
  • L77 inline styles for Section4Skeleton + error state could use shared CSS but this matches existing pattern in AnalysisPageContent.tsx.

Verdict

APPROVE. Sub-component imports all resolve with matching prop contracts, sections 1/2/3/5 wiring preserved, conditional renders clean (each optional block properly guarded with &&), conventions OK.

Merging via deep-code-reviewer.

## Deep Code Review — verdict: APPROVE **Files reviewed**: 2 (P2: 1 NEW, 1 MOD) — +260/-8 ### Sub-component imports (all resolve, prop contracts match) | Import | File exists | Named export | Props contract | |---|---|---|---| | `HeadlineBar` from `@/components/ui/HeadlineBar` | yes | yes | `{title, subtitle, rightSlot?}` matches | | `GateVerdictBanner` from `@/components/site-finder/GateVerdictBanner` | yes | yes | `{verdict: GateVerdict \| null \| undefined}` matches | | `ScoreBreakdownPanel` | yes | yes | `{topPositives, topNegatives, byGroup, detailed}` exact match | | `ScoreBreakdownStackedBar` | yes | yes | `{breakdown: FactorContribution[]}` matches (passing `detailed`) | | `GeologyBlock` | yes | yes | `{geology: ParcelAnalysisGeology}` matches | | `HydrologyBlock` | yes | yes | `{hydrology: Hydrology}` matches | | `GeotechRiskBlock` | yes | yes | `{risk: GeotechRisk}` matches | | `NspdZouitOverlapsBlock` | yes | yes | `{overlaps: NspdZouitOverlap[]}` matches (default `[]` if null) | | `SuccessRecommendationBlock` | yes | yes | `{recommendation: ParcelSuccessRecommendation \| null \| undefined}` matches | All `ParcelAnalysis` field accesses (`score`, `score_label`, `score_explanation`, `score_top_3_positives`, `score_top_3_negatives`, `score_by_group`, `score_breakdown_detailed`, `gate_verdict`, `confidence_label`, `geology`, `hydrology`, `geotech_risk`, `nspd_zouit_overlaps`, `success_recommendation`) verified in `frontend/src/types/site-finder.ts:330-397`. ### Sections 1/2/3/5 PRESERVED `AnalysisPageContent.tsx` diff shows ONLY the Section 4 placeholder swap (lines 130-137 → `<Section4Estimate cad={cad} />`). Section 1/2/3 component calls, Section 5 placeholder, breadcrumb, sidebar nav, loading/error branches — all untouched. No regression risk on existing sections. ### Conditional render hygiene (clean) - `analysis.gate_verdict && <GateVerdictBanner.../>` — guarded - `hasBreakdown && <grid>` with `gridTemplateColumns: hasStackedBar ? "1fr 1fr" : "1fr"` — proper 2→1-col collapse - Risk grid wrapped in `(geology \|\| hydrology \|\| geotech_risk) && <grid>` with `repeat(auto-fit, minmax(220px, 1fr))` — responsive, conditional per-block inside - `hasZouit` checks both `!== undefined && !== null` - `success_recommendation !== undefined` guard (and inner component handles `null || ranking.length === 0`) - Empty state: HeadlineBar always renders (score + label fallback to just `"Оценка: {score}"` if no label) — no blank section - `score.toFixed(1)` — if `score == null` this would throw, but `ParcelAnalysis.score` is non-nullable in types so OK ### Conventions (frontend.md + ui-ux.md) - `"use client"` directive present (TanStack Query hook used) - TS strict — one `as unknown as ParcelAnalysis` cast (matches the established pattern in `AnalysisPageContent.tsx:87`, same justification: `ParcelAnalyzeResponse` is a subset of `ParcelAnalysis`) - Canonical `useParcelAnalyzeQuery(cad)` — cache shared with parent, no extra HTTP - CSS tokens used (`--bg-headline`, `--bg-card`, `--bg-card-alt`, `--border-card`, `--border-soft`, `--danger`, `--danger-soft`, `--fg-secondary`) — no raw hex - No `dangerouslySetInnerHTML`, no XSS surface (only plain text rendering of `score_explanation` / `verdict_label`) - No emoji, no `console.log`, no `any` - `id="section-4"` + `scrollMarginTop: 72` matches Section 5 placeholder pattern for sidebar scrollspy ### Minor nits (non-blocking) - L41 `buildHeadlineTitle`: `score.toFixed(1)` assumes `score: number` (which it is per type). If backend ever returns null, this throws — but type-safe today. - L77 inline styles for Section4Skeleton + error state could use shared CSS but this matches existing pattern in `AnalysisPageContent.tsx`. ### Verdict APPROVE. Sub-component imports all resolve with matching prop contracts, sections 1/2/3/5 wiring preserved, conditional renders clean (each optional block properly guarded with `&&`), conventions OK. Merging via deep-code-reviewer.
Author
Owner

Polling tick: bot review APPROVE при 01:28, но PR ещё open через 15+ мин. External merge не propagated.

Main session НЕ мержит сама (memory feedback_no_self_review). Если bot stuck — нужно либо retry merge от external window, либо user manual merge через UI.

Дальнейшие блоки A11 + A12 ждут unblock этого PR (sequential).

Polling tick: bot review APPROVE при 01:28, но PR ещё open через 15+ мин. External merge не propagated. Main session НЕ мержит сама (memory `feedback_no_self_review`). Если bot stuck — нужно либо retry merge от external window, либо user manual merge через UI. Дальнейшие блоки A11 + A12 ждут unblock этого PR (sequential).
lekss361 merged commit 91026dcc6f into main 2026-05-18 03:57:25 +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#358
No description provided.