"use client"; /** * 6.3 Уровень уверенности — level pill + EXPLICIT drivers list from * confidence.factors (DoD: «явный список drivers»). * * The factors map is mostly { note, level, value } entries, but the prod * envelope also carries a stray boolean `advisory_capped` — narrowed out via a * runtime type guard so we never render a non-factor. */ import { Badge } from "@/components/ui/Badge"; import type { ConfidenceFactor, ReportConfidence } from "@/types/forecast"; import { CONFIDENCE_RU, confidenceVariant } from "./forecast-helpers"; interface Props { confidence: ReportConfidence; } // Human-readable labels for known factor keys; unknown keys fall back to the key. const FACTOR_RU: Record = { deal_count: "Объём сделок", analog_count: "Аналоги (ЖК)", domrf_coverage: "Покрытие ДОМ.РФ", history_months: "Глубина истории", component: "Компонент", }; function isConfidenceFactor(v: unknown): v is ConfidenceFactor { if (typeof v !== "object" || v === null) return false; const o = v as Record; return ( typeof o.note === "string" && (o.level === "high" || o.level === "medium" || o.level === "low") ); } function factorLabel(key: string): string { if (FACTOR_RU[key]) return FACTOR_RU[key]; // component_2..N → «Компонент N» (note уже несёт «вкладывающий сервис: …») const m = key.match(/^component_(\d+)$/); if (m) return `Компонент ${m[1]}`; return key; } function formatFactorValue(value: number | null): string | null { if (value == null) return null; // Fractions in 0..1 read as percent (e.g. coverage); integers/large stay raw. // Upper bound is inclusive so full coverage (1.0) renders as «100%», not «1». if (value > 0 && value <= 1) return `${Math.round(value * 100)}%`; return Math.round(value).toLocaleString("ru"); } export function ForecastConfidenceBlock({ confidence }: Props) { const level = confidence.level; const drivers = Object.entries(confidence.factors) .filter((entry): entry is [string, ConfidenceFactor] => isConfidenceFactor(entry[1]), ) // Sort high → medium → low so the strongest evidence reads first. .sort((a, b) => LEVEL_RANK[a[1].level] - LEVEL_RANK[b[1].level]); return (
{/* Level pill + rationale */}
Уровень уверенности {level ? ( {CONFIDENCE_RU[level]} ) : ( не определена )}
{confidence.rationale && (

{confidence.rationale}

)}
{/* Explicit drivers list */} {drivers.length > 0 ? (
Факторы достоверности ({drivers.length})
{drivers.map(([key, factor]) => { const valueStr = formatFactorValue(factor.value); return (
{CONFIDENCE_RU[factor.level]}
{factorLabel(key)} {valueStr != null && ( · {valueStr} )}
{factor.note}
); })}
) : (

Факторы достоверности недоступны.

)}
); } const LEVEL_RANK: Record = { high: 0, medium: 1, low: 2, };