fix(ptica): «Риск: Низкий» больше не выглядит вердиктом (#2934) (#2939)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m28s
Deploy / deploy (push) Successful in 1m13s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 8s

This commit is contained in:
bot-backend 2026-08-19 15:53:21 +00:00
parent a2fbe4b400
commit 1afa34c5a6
4 changed files with 110 additions and 7 deletions

View file

@ -80,11 +80,14 @@ export function InvestScoreBlock({
</div>
<div className={styles.statLine}>
<span className={styles.k}>Риск</span>
<span
className={`${styles.v} ${toneClass(inv.riskTone)}`}
title={inv.risk.caption}
>
<span className={`${styles.v} ${toneClass(inv.riskTone)}`}>
{inv.risk.value}
{/* #2934: «предв.» было только в атрибуте title на экране его не
видно, а на печати и подавно. Пометка о том, что оценка
предварительная, должна читаться там же, где значение. */}
{inv.risk.caption && (
<span className={styles.fieldCaption}> {inv.risk.caption}</span>
)}
</span>
</div>

View file

@ -0,0 +1,50 @@
/**
* #2934: пометка «предв.» у строки «Риск» должна читаться на экране.
*
* Значение берётся из adaptRiskGauge = `100 - buildability`, то есть обратной
* застраиваемости. К природным опасностям (затопление, оползни, карст) оно
* отношения не имеет, а источники природного риска пусты: cad_risk_zones 0 строк,
* слои risk_* НСПД 0 объектов на 669 дампов (замер 19.08.2026).
*
* До правки «предв.» лежало ТОЛЬКО в атрибуте title на экране невидимо, при
* печати тем более, а значение при этом красилось зелёным.
*/
import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";
import { InvestScoreBlock } from "../InvestScoreBlock";
import analyzeFixture from "@/lib/mocks/parcel-analyze.json";
import type { ParcelAnalysis } from "@/types/site-finder";
/** Участок с gate_verdict — иначе застраиваемость null и строка «Риск» пуста. */
function analysisWithGate(): ParcelAnalysis {
return {
...(analyzeFixture as unknown as ParcelAnalysis),
gate_verdict: {
can_build_mkd: true,
verdict_label: "Можно",
blockers: [],
warnings: [],
checks_performed: [],
source: "nspd_dump",
},
} as unknown as ParcelAnalysis;
}
describe("InvestScoreBlock — строка «Риск» (#2934)", () => {
it("показывает пометку «предв.» видимым текстом", () => {
render(
<InvestScoreBlock
analysis={analysisWithGate()}
onOpenDrawer={() => {}}
/>,
);
// Предусловие: строка «Риск» вообще отрисована со значением.
expect(screen.getByText("Риск")).toBeInTheDocument();
expect(screen.getByText("Низкий")).toBeInTheDocument();
// На origin/main «предв.» живёт только в атрибуте title — getByText не найдёт.
expect(screen.getByText("предв.")).toBeInTheDocument();
});
});

View file

@ -17,6 +17,8 @@ import { describe, expect, it } from "vitest";
import {
adaptBuySignalGauge,
adaptEconomyCard,
adaptInvestScore,
adaptRiskGauge,
adaptFinanceDrawer,
adaptInvestmentClearance,
adaptLegalDrawer,
@ -352,3 +354,44 @@ describe("ptica-adapt gate (#1892 ЗОУИТ area-gate)", () => {
expect(v.tone).toBe("good");
});
});
describe("ptica-adapt risk placeholder (#2934)", () => {
/** Участок, у которого застраиваемость СЧИТАЕТСЯ: без gate_verdict она null,
* и тон был бы "none" просто из-за отсутствия данных проверка прошла бы
* вхолостую и на неисправленном коде тоже. */
function analysisWithGate(): ParcelAnalysis {
return {
...analysis,
gate_verdict: {
can_build_mkd: true,
verdict_label: "Можно",
blockers: [],
warnings: [],
checks_performed: [],
source: "nspd_dump",
},
} as unknown as ParcelAnalysis;
}
it("«Риск» не окрашивается как вердикт, пока значение не настоящее", () => {
const a = analysisWithGate();
const gauge = adaptRiskGauge(a);
// Предусловие: значение реально посчитано, иначе тон был бы "none" по другой
// причине и проверка ничего бы не доказывала.
expect(gauge.value).not.toBeNull();
expect(gauge.tone).toBe("good"); // именно тот случай, что красился зелёным
expect(gauge.isReal).toBe(false);
expect(adaptInvestScore(a).riskTone).toBe("none");
});
it("подпись «предв.» доезжает до поля, а не только до тултипа", () => {
const inv = adaptInvestScore(analysisWithGate());
expect(inv.risk.caption).toBe("предв.");
expect(inv.risk.isReal).toBe(false);
});
it("контроль: «Потенциал» приглушён как и раньше", () => {
expect(adaptInvestScore(analysis).potentialTone).toBe("none");
});
});

View file

@ -353,9 +353,16 @@ export function adaptInvestScore(
risk.value != null
? { value: risk.label, isReal: false, caption: "предв." }
: placeholder("предв.");
// Hero «Риск» value color (prototype .v.warn): green when low, yellow otherwise.
const riskTone: PticaValueTone =
risk.value == null ? "none" : risk.tone === "good" ? "good" : "warn";
// #2934: плейсхолдер не красим как вердикт. `adaptRiskGauge` считает риск как
// `100 - buildability`, то есть обратную застраиваемость — к природным опасностям
// (затопление, оползни, карст) он отношения не имеет, и сам помечен
// `isReal: false`. Зелёное «Низкий» читалось как «рисков нет», хотя источники
// природного риска пусты: cad_risk_zones 0 строк, слои risk_* НСПД 0 объектов на
// 669 дампов (замер 19.08).
//
// Приглушаем так же, как уже приглушён `potentialTone` — соглашение в этом же
// файле: пока значение не настоящее, цвета-вердикта у него нет.
const riskTone: PticaValueTone = "none";
const overall =
report?.scoring?.overall ?? report?.exec_summary.key_numbers.overall_score;