Два disclosure-фикса + 3 consistency follow-up (по code-review).
invest_score = взвешенная POI-сумма (distance-decay + center-bonus, max-ref
40), НЕ калиброванный инвест-рейтинг (scorer.py — пустой стаб). Был мислейбл
«Инвест-балл». Формулу/SCORE_MAX_REFERENCE не трогаем — только честная подпись.
- backend parcels.py: score_explanation → «POI-скор (0–40, оценочный)...
Не инвест-рейтинг...»
- KpiCard: новый optional caption prop (backward-compat).
- Section1ParcelInfo: KPI «Инвест-балл» → «POI-скор · 0–40» + caption «оценочный».
- legacy site-finder: scoreLabel → «POI-скор · {label} · оценочный».
- CompareTable: «Инвест-балл» → «POI-скор · 0–40» (тот же score, был мислейбл).
- landing app/page.tsx: «Score 0–100» → «POI-скор 0–40» (реальная шкала).
- Section1 scoreColor: пороги 0–100 → 0–40 калиброванные (_score_label:
<5 плохо · 5–15 средне · 15–25 хорошо · ≥25 отлично) — раньше хороший
~30/40 участок рисовался red рядом с честным «0–40» лейблом.
MOI (months_of_inventory) математически КОРРЕКТЕН — по определению не зависит
от горизонта проекции (MOI = сток / месячный темп). Не баг. Но дублировался
колонкой на 4 горизонта в ForecastHorizonsBlock (116.6/116.6/117.6/117.6 —
выглядело сломанным). Убрал колонку; MOI остаётся 1 раз в Section6 key_numbers.
deficit_index (варьируется) сохранён. Подчищены unused balanced/DEFICIT_BALANCE_EPS.
138 frontend vitest passed, ruff/tsc/lint clean.
Refs #1871
79 lines
1.6 KiB
TypeScript
79 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
type KpiColor = "neutral" | "amber" | "green" | "red" | "blue";
|
|
|
|
const BG: Record<KpiColor, string> = {
|
|
neutral: "#f9fafb",
|
|
amber: "#fef3c7",
|
|
green: "#dcfce7",
|
|
red: "#fee2e2",
|
|
blue: "#dbeafe",
|
|
};
|
|
|
|
const FG: Record<KpiColor, string> = {
|
|
neutral: "#374151",
|
|
amber: "#b45309",
|
|
green: "#15803d",
|
|
red: "#b91c1c",
|
|
blue: "#1d4ed8",
|
|
};
|
|
|
|
interface Props {
|
|
value: string;
|
|
label: string;
|
|
color?: KpiColor;
|
|
/**
|
|
* Опциональная подпись под значением (caption-токен 12/16, --fg-tertiary).
|
|
* Видимая (не hover) — для честных квалификаторов вроде «оценочный».
|
|
*/
|
|
caption?: string;
|
|
}
|
|
|
|
export function KpiCard({ value, label, color = "neutral", caption }: Props) {
|
|
return (
|
|
<div
|
|
style={{
|
|
padding: "12px 16px",
|
|
background: BG[color],
|
|
borderRadius: 10,
|
|
minWidth: 0,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
fontSize: 20,
|
|
fontWeight: 700,
|
|
color: FG[color],
|
|
lineHeight: 1.2,
|
|
overflow: "hidden",
|
|
textOverflow: "ellipsis",
|
|
whiteSpace: "nowrap",
|
|
}}
|
|
>
|
|
{value}
|
|
</div>
|
|
{caption ? (
|
|
<div
|
|
style={{
|
|
fontSize: 12,
|
|
lineHeight: "16px",
|
|
color: "var(--fg-tertiary)",
|
|
marginTop: 2,
|
|
}}
|
|
>
|
|
{caption}
|
|
</div>
|
|
) : null}
|
|
<div
|
|
style={{
|
|
fontSize: 11,
|
|
color: "#6b7280",
|
|
marginTop: 4,
|
|
lineHeight: 1.4,
|
|
}}
|
|
>
|
|
{label}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|