diff --git a/adk-rust/crates/detmir-portal/src/static/app.js b/adk-rust/crates/detmir-portal/src/static/app.js index 956b2f5..cde0e64 100644 --- a/adk-rust/crates/detmir-portal/src/static/app.js +++ b/adk-rust/crates/detmir-portal/src/static/app.js @@ -319,7 +319,6 @@ function renderExecutiveDashboard(report) { const highRisk = Array.isArray(dashboard.high_risk_departments) ? dashboard.high_risk_departments : []; const candidates = Array.isArray(dashboard.critical_candidates) ? dashboard.critical_candidates : []; const summary = dashboard.summary || {}; - const narrativeStatus = summary.risk_narrative_status || executiveDashboardStatus(dashboard); return `
@@ -327,11 +326,7 @@ function renderExecutiveDashboard(report) {

Сводка руководителя

Что происходит в организации прямо сейчас: доверие к KPI, покрытие, риски, дела и готовность расследований.

- ${ui(narrativeStatus)} - -
-
Главная причина риска${ui(summary.main_risk_cause || "связанный риск не выражен")}${ui(narrativeStatus)}
-
Подтверждающие слоиTrust KPI · Agent Coverage · Business Risk · Risk Heatmap · Security Correlation · Incident Candidates · Cases
+ ${ui(dashboard.forensics_readiness || "UNKNOWN")}
Trust KPI${ui(optionalPercent(dashboard.trust_kpi_score))}
@@ -350,6 +345,27 @@ function renderExecutiveDashboard(report) { `; } +function renderRiskNarrative(report) { + const dashboard = report?.executive_dashboard; + const summary = dashboard?.summary || {}; + const narrativeStatus = summary.risk_narrative_status || executiveDashboardStatus(dashboard || {}); + return ` +
+
+
+

Связанная картина риска

+

Главный управленческий вывод: что происходит, почему это риск и какие слои это подтверждают.

+
+ ${ui(narrativeStatus)} +
+
+
Главная причина риска${ui(summary.main_risk_cause || "связанный риск не выражен")}${ui(narrativeStatus)}
+
Подтверждающие слоиTrust KPI · Agent Coverage · Business Risk · Risk Heatmap · Security Correlation · Incident Candidates · Cases
+
+
+ `; +} + function optionalPercent(value) { const number = Number(value); return Number.isFinite(number) ? `${Math.round(number)}%` : "нет данных"; @@ -873,6 +889,7 @@ function renderOperator(data, report) { ${escapeHtml(report?.headline || "Оперативный срез")}
${renderPeriodBanner(report)} + ${renderRiskNarrative(report)} ${renderExecutiveDashboard(report)} ${renderExecutiveMetrics(report, incidents)} ${renderAgentQuality(report?.agent_quality, report?.agent_quality_explain)} @@ -2130,6 +2147,7 @@ function renderReports(data) { ${escapeHtml(data.severity)} ${renderPeriodBanner(data)} + ${renderRiskNarrative(data)} ${renderExecutiveDashboard(data)} ${renderReportTypeCards()}
diff --git a/docs/BUSINESS_RISK_RU.md b/docs/BUSINESS_RISK_RU.md index d3513d1..c991376 100644 --- a/docs/BUSINESS_RISK_RU.md +++ b/docs/BUSINESS_RISK_RU.md @@ -351,6 +351,13 @@ POST /api/cases/{case_id}/status управленческий вывод: что происходит, почему это риск и какие слои это подтверждают. +Такой же порядок используется в UI `/portal` и `/reports`: сначала отдельная +карточка `Связанная картина риска`, затем `Сводка руководителя`, далее +доверие к KPI/качество данных, Business Risk/Heatmap/Correlation, кандидаты, +дела и технические детали. В `/api/reports` narrative остается в существующих +обратно совместимых полях: первый `executive_points[]` и +`executive_dashboard.summary.main_risk_cause`. + Оперативный отчет содержит разделы: ```text diff --git a/scripts/detmir-portal-tabs-smoke.mjs b/scripts/detmir-portal-tabs-smoke.mjs index cc1ec92..2342e9c 100644 --- a/scripts/detmir-portal-tabs-smoke.mjs +++ b/scripts/detmir-portal-tabs-smoke.mjs @@ -124,6 +124,7 @@ async function main() { ); const readyBodyText = await page.locator("#content").innerText({ timeout }); const requiredExecutive = [ + "Связанная картина риска", "Сотрудников в работе", "Средний индекс активности", "Главная причина риска", @@ -148,6 +149,27 @@ async function main() { ok: requiredExecutive.every((marker) => containsText(readyBodyText, marker)), required: requiredExecutive, }); + const riskNarrativeIndex = readyBodyText.indexOf("Связанная картина риска"); + const executiveIndex = readyBodyText.indexOf("Сводка руководителя"); + const trustIndex = readyBodyText.indexOf("Достоверность данных агента"); + const businessRiskIndex = readyBodyText.indexOf("Риски подразделений"); + const candidatesIndex = readyBodyText.indexOf("Кандидаты в инциденты"); + checks.push({ + name: "management_block_order", + ok: + riskNarrativeIndex >= 0 && + executiveIndex > riskNarrativeIndex && + trustIndex > executiveIndex && + businessRiskIndex > trustIndex && + candidatesIndex > businessRiskIndex, + order: [ + "Связанная картина риска", + "Сводка руководителя", + "Достоверность данных агента", + "Риски подразделений", + "Кандидаты в инциденты", + ], + }); } if (item.tab === "settings") { const requiredSettings = [