"use client"; /** * PticaScenarios — root of the ПТИЦА dark «Сценарии» tab (§22 Прогноз). * * Reads the REAL async forecast via useParcelForecastQuery(cad). The forecast is * enqueued fire-and-forget by /analyze on page mount — this hook only polls. While * the envelope is `pending` (or the first request is loading) we render a CALM * skeleton («Прогноз считается…»), NOT an error: the poll resolves once the report * is assembled. Once `report` is present we compose the sub-blocks: * 3 scenario cards · горизонты (chart + table) · сравнение · уверенность + * рекомендованный продукт · прозрачность скоринга. * * The horizon (6/12/18 мес) selector lives in this tab's header; its state is * lifted to PticaPageContent so the hero/other tabs can share it. */ import styles from "@/app/site-finder/analysis/[cad]/ptica/ptica.module.css"; import { useParcelForecastQuery } from "@/lib/site-finder-api"; import type { ForecastReport, ForecastSegment } from "@/types/forecast"; import { ScenarioCards } from "./ScenarioCards"; import { HorizonForecast } from "./HorizonForecast"; import { ScenarioCompareTable } from "./ScenarioCompareTable"; import { ConfidencePanel } from "./ConfidencePanel"; import { RecommendedProduct } from "./RecommendedProduct"; import { ScoringTransparency } from "./ScoringTransparency"; import { FutureSupply } from "./FutureSupply"; interface Props { cad: string; horizon: number; onHorizonChange: (h: number) => void; } const HORIZONS = [6, 12, 18]; function segmentLabel(segment: ForecastSegment | undefined): string | null { if (!segment) return null; const parts = [segment.obj_class, segment.room_bucket].filter( (p): p is string => !!p, ); return parts.length > 0 ? parts.join(" · ") : null; } export function PticaScenarios({ cad, horizon, onHorizonChange }: Props) { const { data, isLoading, error } = useParcelForecastQuery(cad); const header = (
Сценарный прогноз §22 формируется в фоне после запуска анализа. Обычно занимает 1–2 минуты — обновится автоматически.