feat(portal): prioritize risk narrative

This commit is contained in:
igor04091968
2026-06-04 17:31:50 +03:00
parent f60cca7f59
commit da6e80a90c
4 changed files with 239 additions and 113 deletions
+191 -91
View File
@@ -517,11 +517,13 @@ struct ExecutiveCandidateSummary {
#[derive(Clone, Debug, Serialize)]
struct ExecutiveDashboardSummary {
#[serde(skip_serializing_if = "Option::is_none")]
risk_narrative_status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
main_risk_cause: Option<String>,
main_risk: String,
main_improvement: String,
main_data_gap: String,
#[serde(skip_serializing_if = "Option::is_none")]
main_risk_cause: Option<String>,
}
#[derive(Clone, Debug, Serialize)]
@@ -2378,7 +2380,24 @@ fn build_reports(
} else {
"Контур DetMir требует технической проверки"
};
let mut executive_points = vec![
let mut executive_points = Vec::new();
executive_points.push(format!(
"Главный управленческий вывод: {}",
executive_dashboard
.summary
.main_risk_cause
.as_deref()
.unwrap_or("связанный риск не выражен")
));
executive_points.push(format!(
"Статус связанной картины риска: {}",
executive_dashboard
.summary
.risk_narrative_status
.as_deref()
.unwrap_or("NORMAL")
));
executive_points.extend([
format!("Сбор данных: {}. {}", collection.status, collection.text),
format!(
"Достоверность данных агента: {}. {}",
@@ -2401,7 +2420,7 @@ fn build_reports(
"Открытые вопросы: {}, в работе: {}",
metrics.open_incidents, metrics.acknowledged_incidents
),
];
]);
if agent_quality_history_summary.ok_days < 5 {
executive_points.push("KPI требует валидации: нестабильный сбор данных агента".to_string());
}
@@ -2421,46 +2440,11 @@ fn build_reports(
),
_ => {}
}
for item in business_risk.iter().take(3) {
if item.risk_level != "LOW" {
let reason = item
.reasons
.first()
.cloned()
.unwrap_or_else(|| "требуется управленческая проверка".to_string());
executive_points.push(format!(
"Подразделение {}: {} — причина: {}",
item.department, item.risk_level, reason
));
}
}
for department in stable_high_risk_departments(&business_risk_history, 3) {
executive_points.push(format!(
"Подразделение {department} сохраняет высокий риск несколько дней подряд."
));
}
for candidate in risk_incident_candidates.iter().take(3) {
executive_points.push(format!(
"Кандидат в инцидент {}: {} — {}",
candidate.id,
candidate.risk_level.as_deref().unwrap_or("UNKNOWN"),
candidate.reason.as_deref().unwrap_or("требуется проверка")
));
}
for item in risk_heatmap
.iter()
.filter(|item| !matches!(item.heat_level.as_str(), "LOW" | "UNKNOWN"))
.take(3)
{
executive_points.push(format!(
"Связанная картина риска: {}",
linked_risk_statement(item)
));
}
executive_points.push(format!(
"Главный риск: {}",
executive_dashboard.summary.main_risk
));
executive_points.push(format!(
"Главное улучшение: {}",
executive_dashboard.summary.main_improvement
@@ -3060,6 +3044,7 @@ fn build_risk_heatmap(
critical_candidates,
);
let links = risk_narrative_links(
&heat_level,
trust_kpi_score,
activity_score,
agent_coverage_pct,
@@ -3068,6 +3053,7 @@ fn build_risk_heatmap(
critical_candidates,
);
let summary = risk_heatmap_summary(
&heat_level,
trust_kpi_score,
activity_score,
agent_coverage_pct,
@@ -3256,6 +3242,7 @@ fn heatmap_rank(level: &str) -> u8 {
}
fn risk_narrative_links(
heat_level: &str,
trust_kpi_score: Option<u8>,
activity_score: Option<u8>,
agent_coverage_pct: Option<u8>,
@@ -3278,6 +3265,16 @@ fn risk_narrative_links(
label: "Business Risk".to_string(),
summary: format!("уровень {}", business_risk_level.unwrap_or("UNKNOWN")),
},
RiskNarrativeLink {
target: "risk_heatmap".to_string(),
label: "Карта риска".to_string(),
summary: format!("итоговый статус карты: {heat_level}"),
},
RiskNarrativeLink {
target: "security_correlation".to_string(),
label: "Корреляция".to_string(),
summary: "связь Workforce и Security по подразделению".to_string(),
},
RiskNarrativeLink {
target: "incident_candidates".to_string(),
label: "Кандидаты".to_string(),
@@ -3297,6 +3294,7 @@ fn risk_narrative_links(
}
fn risk_heatmap_summary(
heat_level: &str,
trust_kpi_score: Option<u8>,
activity_score: Option<u8>,
agent_coverage_pct: Option<u8>,
@@ -3305,10 +3303,11 @@ fn risk_heatmap_summary(
critical_candidates: usize,
) -> String {
format!(
"Trust {} → Coverage {} → Business Risk {} → Candidates {} → Cases {} → {}",
"Trust {} → Coverage {} → Business Risk {} → Risk Heatmap {} → Security Correlation → Candidates {} → Cases {} → {}",
optional_score_text(trust_kpi_score),
optional_score_text(agent_coverage_pct),
business_risk_level.unwrap_or("UNKNOWN"),
heat_level,
critical_candidates,
open_cases,
risk_narrative_conclusion(
@@ -3359,21 +3358,6 @@ fn risk_narrative_conclusion(
}
}
fn linked_risk_statement(item: &RiskHeatmapItem) -> String {
format!(
"В подразделении {} {}.",
item.department,
risk_narrative_conclusion(
item.trust_kpi_score,
item.activity_score,
item.agent_coverage_pct,
item.business_risk_level.as_deref(),
item.open_cases.unwrap_or(0),
item.critical_candidates.unwrap_or(0),
)
)
}
fn build_security_correlation(heatmap: &[RiskHeatmapItem]) -> Vec<SecurityCorrelationItem> {
let mut items = heatmap
.iter()
@@ -3526,6 +3510,16 @@ fn build_executive_dashboard(
let resolved_cases_30d = resolved_cases_30d(inputs.cases);
let forensics_readiness = forensics_readiness(snapshot, inputs.candidates, inputs.evidence);
let summary = ExecutiveDashboardSummary {
risk_narrative_status: Some(risk_narrative_status(
inputs.risk_heatmap,
inputs.security_correlation,
&forensics_readiness,
)),
main_risk_cause: executive_main_risk_cause(
inputs.risk_heatmap,
inputs.security_correlation,
&forensics_readiness,
),
main_risk: executive_main_risk(
&high_risk_departments,
&critical_candidates,
@@ -3537,11 +3531,6 @@ fn build_executive_dashboard(
&critical_candidates,
),
main_data_gap: executive_main_data_gap(snapshot, inputs.agent_quality_explain),
main_risk_cause: executive_main_risk_cause(
inputs.risk_heatmap,
inputs.security_correlation,
&forensics_readiness,
),
};
ExecutiveDashboard {
trust_kpi_score,
@@ -3673,6 +3662,35 @@ fn executive_main_data_gap(
"критичных пробелов в данных не выявлено".to_string()
}
fn risk_narrative_status(
risk_heatmap: &[RiskHeatmapItem],
security_correlation: &[SecurityCorrelationItem],
forensics_readiness: &str,
) -> String {
let worst_heat_rank = risk_heatmap
.iter()
.map(|item| heatmap_rank(item.heat_level.as_str()))
.max()
.unwrap_or_else(|| heatmap_rank("UNKNOWN"));
let max_correlation = security_correlation
.iter()
.map(|item| item.correlation_score)
.max()
.unwrap_or(0);
if worst_heat_rank >= heatmap_rank("CRITICAL") || max_correlation >= 85 {
"CRITICAL".to_string()
} else if worst_heat_rank >= heatmap_rank("HIGH") || max_correlation >= 60 {
"HIGH_RISK".to_string()
} else if worst_heat_rank >= heatmap_rank("MEDIUM")
|| max_correlation > 0
|| !matches!(forensics_readiness, "READY" | "OBSERVE")
{
"ATTENTION".to_string()
} else {
"NORMAL".to_string()
}
}
fn executive_main_risk_cause(
risk_heatmap: &[RiskHeatmapItem],
security_correlation: &[SecurityCorrelationItem],
@@ -3684,13 +3702,7 @@ fn executive_main_risk_cause(
let correlation = security_correlation
.iter()
.find(|item| item.department == top.department);
let mut statement = linked_risk_statement(top);
if let Some(item) = correlation.filter(|item| item.correlation_score > 0) {
statement.push_str(&format!(
" Корреляция Workforce/Security: {}/100, {}.",
item.correlation_score, item.correlation_reason
));
}
let mut statement = risk_narrative_statement(top, correlation);
if !matches!(forensics_readiness, "READY") {
statement.push_str(&format!(
" Готовность Forensics: {forensics_readiness}, доказательная база требует проверки."
@@ -3699,6 +3711,38 @@ fn executive_main_risk_cause(
Some(statement)
}
fn risk_narrative_statement(
item: &RiskHeatmapItem,
correlation: Option<&SecurityCorrelationItem>,
) -> String {
let correlation_score = correlation
.map(|value| format!("{}/100", value.correlation_score))
.unwrap_or_else(|| "0/100".to_string());
let correlation_reason = correlation
.map(|value| format!(" Причина корреляции: {}.", value.correlation_reason))
.unwrap_or_default();
format!(
"В подразделении {} связаны слои: Trust KPI {}, Agent Coverage {}, Business Risk {}, Risk Heatmap {}, Security Correlation {}, Incident Candidates {}, Cases {}. {}.{}",
item.department,
optional_score_text(item.trust_kpi_score),
optional_score_text(item.agent_coverage_pct),
item.business_risk_level.as_deref().unwrap_or("UNKNOWN"),
item.heat_level,
correlation_score,
item.critical_candidates.unwrap_or(0),
item.open_cases.unwrap_or(0),
risk_narrative_conclusion(
item.trust_kpi_score,
item.activity_score,
item.agent_coverage_pct,
item.business_risk_level.as_deref(),
item.open_cases.unwrap_or(0),
item.critical_candidates.unwrap_or(0),
),
correlation_reason
)
}
fn build_investigation_pack(
snapshot: &Snapshot,
candidate_id: &str,
@@ -5652,6 +5696,11 @@ fn render_report_markdown(
text.push_str(&format!("- {item}\n"));
}
append_executive_dashboard_markdown(&mut text, context.executive_dashboard);
append_linked_risk_narrative_markdown(
&mut text,
context.risk_heatmap,
context.security_correlation,
);
append_agent_quality_markdown(&mut text, &snapshot.agent_quality);
append_agent_quality_history_markdown(
&mut text,
@@ -5672,11 +5721,6 @@ fn render_report_markdown(
);
append_risk_heatmap_markdown(&mut text, context.risk_heatmap);
append_security_correlation_markdown(&mut text, context.security_correlation);
append_linked_risk_narrative_markdown(
&mut text,
context.risk_heatmap,
context.security_correlation,
);
append_risk_incident_candidates_markdown(&mut text, context.risk_incident_candidates);
append_incident_review_markdown(&mut text, context.risk_incident_candidates);
append_incident_review_audit_markdown(
@@ -5692,6 +5736,23 @@ fn render_report_markdown(
fn append_executive_dashboard_markdown(text: &mut String, dashboard: &ExecutiveDashboard) {
text.push_str("\n## Сводка руководителя\n\n");
text.push_str(&format!(
"- Статус связанной картины риска: {}\n",
dashboard
.summary
.risk_narrative_status
.as_deref()
.unwrap_or("NORMAL")
));
text.push_str(&format!(
"- Главная причина риска: {}\n",
dashboard
.summary
.main_risk_cause
.as_deref()
.unwrap_or("связанный риск не выражен")
));
text.push_str("- Подтверждающие слои: Trust KPI, Agent Coverage, Business Risk, Risk Heatmap, Security Correlation, Incident Candidates, Cases\n");
text.push_str(&format!(
"- Trust KPI: {}\n",
dashboard
@@ -5737,14 +5798,6 @@ fn append_executive_dashboard_markdown(text: &mut String, dashboard: &ExecutiveD
.as_deref()
.unwrap_or("UNKNOWN")
));
text.push_str(&format!(
"- Главная причина риска: {}\n",
dashboard
.summary
.main_risk_cause
.as_deref()
.unwrap_or("связанный риск не выражен")
));
text.push_str(&format!(
"- Главный риск: {}\n",
dashboard.summary.main_risk
@@ -6043,14 +6096,15 @@ fn append_linked_risk_narrative_markdown(
.map(|value| format!("{}/100", value.correlation_score))
.unwrap_or_else(|| "0/100".to_string());
text.push_str(&format!(
"- {} → Trust {} → Coverage {} → Business Risk {} → Candidates {} → Cases {} → Correlation {} → {}\n",
"- {} → Trust KPI {} → Agent Coverage {} → Business Risk {} → Risk Heatmap {} → Security Correlation {} → Incident Candidates {} → Cases {} → {}\n",
item.department,
optional_score_text(item.trust_kpi_score),
optional_score_text(item.agent_coverage_pct),
item.business_risk_level.as_deref().unwrap_or("UNKNOWN"),
item.heat_level,
correlation_text,
item.critical_candidates.unwrap_or(0),
item.open_cases.unwrap_or(0),
correlation_text,
item.summary.as_deref().unwrap_or("вывод не сформирован")
));
if let Some(value) = correlation.and_then(|value| value.explanation.as_deref()) {
@@ -9755,6 +9809,27 @@ mod tests {
.unwrap()
.contains("В подразделении")
);
assert_eq!(
report["executive_dashboard"]["summary"]["risk_narrative_status"],
"HIGH_RISK"
);
let main_risk_cause = report["executive_dashboard"]["summary"]["main_risk_cause"]
.as_str()
.unwrap();
for layer in [
"Trust KPI",
"Agent Coverage",
"Risk Heatmap",
"Business Risk",
"Security Correlation",
"Incident Candidates",
"Cases",
] {
assert!(
main_risk_cause.contains(layer),
"main_risk_cause must mention {layer}"
);
}
assert_eq!(report["business_risk"].as_array().unwrap().len(), 1);
assert_eq!(report["business_risk"][0]["department"], "Бухгалтерия");
assert_eq!(report["business_risk"][0]["risk_level"], "MEDIUM");
@@ -9787,11 +9862,31 @@ mod tests {
assert_eq!(report["risk_heatmap"][0]["trust_kpi_score"], 50);
assert_eq!(report["risk_heatmap"][0]["activity_score"], 50);
assert!(report["risk_heatmap"][0]["links"].is_array());
let heatmap_link_targets = report["risk_heatmap"][0]["links"]
.as_array()
.unwrap()
.iter()
.filter_map(|item| item["target"].as_str())
.collect::<Vec<_>>();
for target in [
"risk_heatmap",
"trust_kpi",
"agent_coverage",
"business_risk",
"security_correlation",
"incident_candidates",
"cases",
] {
assert!(
heatmap_link_targets.contains(&target),
"risk heatmap links must include {target}"
);
}
assert!(
report["risk_heatmap"][0]["summary"]
.as_str()
.unwrap()
.contains("Trust")
.contains("Security Correlation")
);
assert!(report["security_correlation"].is_array());
assert_eq!(
@@ -9967,21 +10062,21 @@ mod tests {
.as_array()
.unwrap()
.iter()
.any(|item| item.as_str().unwrap().contains("причина:"))
.next()
.unwrap()
.as_str()
.unwrap()
.contains("Главный управленческий вывод")
);
assert!(
report["executive_points"]
.as_array()
.unwrap()
.iter()
.any(|item| item.as_str().unwrap().contains("Связанная картина риска"))
);
assert!(
report["executive_points"]
.as_array()
.any(|item| item
.as_str()
.unwrap()
.iter()
.any(|item| item.as_str().unwrap().contains("Кандидат в инцидент"))
.contains("Статус связанной картины риска"))
);
assert!(
report["markdown"]
@@ -10007,6 +10102,11 @@ mod tests {
.unwrap()
.contains("## Связанная картина риска")
);
let markdown = report["markdown"].as_str().unwrap();
assert!(
markdown.find("## Связанная картина риска").unwrap()
< markdown.find("## Достоверность данных").unwrap()
);
assert!(
report["markdown"]
.as_str()
@@ -23,9 +23,9 @@ async function postJson(path, payload) {
function statusClass(status) {
const s = String(status || "UNKNOWN").toLowerCase();
if (s === "ok" || s === "true" || s === "low" || s === "false_positive" || s === "resolved") return "status-ok";
if (s === "warn" || s === "warning" || s === "fallback" || s === "stale" || s === "medium" || s === "in_review" || s === "postponed" || s === "open" || s === "in_progress") return "status-warn";
if (s === "degraded" || s === "high" || s === "confirmed" || s === "rejected" || s === "archived") return "status-degraded";
if (s === "ok" || s === "true" || s === "normal" || s === "low" || s === "false_positive" || s === "resolved") return "status-ok";
if (s === "warn" || s === "warning" || s === "attention" || s === "fallback" || s === "stale" || s === "medium" || s === "in_review" || s === "postponed" || s === "open" || s === "in_progress") return "status-warn";
if (s === "degraded" || s === "high" || s === "high_risk" || s === "confirmed" || s === "rejected" || s === "archived") return "status-degraded";
if (s === "fail" || s === "false" || s === "error" || s === "critical" || s === "missing") return "status-fail";
return "status-unknown";
}
@@ -319,6 +319,7 @@ 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 `
<section class="card executive-dashboard-card">
<div class="section-head">
@@ -326,7 +327,11 @@ function renderExecutiveDashboard(report) {
<h3>Сводка руководителя</h3>
<p class="muted">Что происходит в организации прямо сейчас: доверие к KPI, покрытие, риски, дела и готовность расследований.</p>
</div>
<span class="badge ${statusClass(executiveDashboardStatus(dashboard))}">${ui(dashboard.forensics_readiness || "UNKNOWN")}</span>
<span class="badge ${statusClass(narrativeStatus)}">${ui(narrativeStatus)}</span>
</div>
<div class="list compact-list executive-summary-list">
<div class="row compact-row"><strong>Главная причина риска</strong><span class="muted">${ui(summary.main_risk_cause || "связанный риск не выражен")}</span><span class="badge ${statusClass(narrativeStatus)}">${ui(narrativeStatus)}</span></div>
<div class="row compact-row"><strong>Подтверждающие слои</strong><span class="muted">Trust KPI · Agent Coverage · Business Risk · Risk Heatmap · Security Correlation · Incident Candidates · Cases</span><span></span></div>
</div>
<div class="quality-grid">
<div><span class="muted">Trust KPI</span><strong>${ui(optionalPercent(dashboard.trust_kpi_score))}</strong></div>
@@ -337,13 +342,10 @@ function renderExecutiveDashboard(report) {
<div><span class="muted">Закрыто за 30 дней</span><strong>${ui(dashboard.resolved_cases_30d ?? 0)}</strong></div>
</div>
<div class="list compact-list executive-summary-list">
<div class="row compact-row"><strong>Главная причина риска</strong><span class="muted">${ui(summary.main_risk_cause || "связанный риск не выражен")}</span><span></span></div>
<div class="row compact-row"><strong>Главный риск</strong><span class="muted">${ui(summary.main_risk || "нет данных")}</span><span></span></div>
<div class="row compact-row"><strong>Главное улучшение</strong><span class="muted">${ui(summary.main_improvement || "нет данных")}</span><span></span></div>
<div class="row compact-row"><strong>Пробел в данных</strong><span class="muted">${ui(summary.main_data_gap || "нет данных")}</span><span></span></div>
</div>
${highRisk.length ? `<p class="muted small">Риск-подразделения: ${ui(highRisk.slice(0, 5).map(item => `${item.department} (${item.risk_level})`).join("; "))}</p>` : ""}
${candidates.length ? `<p class="muted small">Кандидаты для проверки: ${ui(candidates.slice(0, 5).map(item => `${item.id} (${item.risk_level})`).join("; "))}</p>` : ""}
</section>
`;
}
@@ -1838,7 +1840,9 @@ function renderRiskLayerLinks(links) {
function riskLayerTarget(target) {
const value = String(target || "");
if (value === "risk_heatmap") return { tab: "operator", selector: ".risk-heatmap-card" };
if (value === "business_risk") return { tab: "operator", selector: "#business-risk-section" };
if (value === "security_correlation") return { tab: "operator", selector: ".security-correlation-card" };
if (value === "incident_candidates") return { tab: "operator", selector: "#risk-candidates-section" };
if (value === "cases") return { tab: "incidents", selector: "#cases-section" };
if (value === "agent_coverage") return { tab: "operator", selector: "#agent-coverage-section" };
+35 -14
View File
@@ -1,8 +1,13 @@
# Business Risk
`Business Risk` - управленческий слой AWatch-rus / DetMir, который показывает
не просто активность сотрудников, а зоны организационного риска по
подразделениям.
`Business Risk` - расчетный слой AWatch-rus / DetMir, который показывает не
просто активность сотрудников, а зоны организационного риска по подразделениям.
Над ним расположен основной управленческий слой `Risk Narrative`: единая
связанная картина риска для руководителя. Он не создает новых сущностей и не
добавляет workflow, а связывает уже рассчитанные слои в один вывод:
`Trust KPI → Agent Coverage → Business Risk → Risk Heatmap → Security
Correlation → Incident Candidates → Cases`.
## Назначение
@@ -14,8 +19,9 @@
- где есть падение тренда;
- где проблемные рабочие места портят доверие к отчету.
Business Risk не является автоматическим обвинением сотрудников или
подразделений. Это read-only приоритизация управленческой проверки.
Business Risk и Risk Narrative не являются автоматическим обвинением
сотрудников или подразделений. Это read-only приоритизация управленческой
проверки.
## API
@@ -62,9 +68,11 @@ Business Risk не является автоматическим обвинен
- `summary.main_risk` - главный риск текущего среза;
- `summary.main_improvement` - главное подтвержденное улучшение;
- `summary.main_data_gap` - главный пробел в данных.
- `summary.risk_narrative_status` - optional единый статус связанной картины
риска: `NORMAL`, `ATTENTION`, `HIGH_RISK` или `CRITICAL`;
- `summary.main_risk_cause` - optional связанная причина риска:
Trust KPI, coverage, Business Risk, candidates, cases и готовность
Forensics.
Trust KPI, Agent Coverage, Risk Heatmap, Business Risk, Security
Correlation, Incident Candidates, Cases и готовность Forensics.
`risk_heatmap`:
@@ -79,9 +87,11 @@ Business Risk не является автоматическим обвинен
- `heat_level` - итоговая зона карты: `LOW`, `MEDIUM`, `HIGH`,
`CRITICAL` или `UNKNOWN`.
- `links` - optional read-only переходы к уже существующим слоям портала:
Trust KPI, Business Risk, кандидаты, дела и покрытие агентов.
Trust KPI, Agent Coverage, Business Risk, Risk Heatmap, Security
Correlation, Incident Candidates и Cases.
- `summary` - optional связанная строка
`Trust → Coverage → Business Risk → Candidates → Cases → вывод`.
`Trust → Coverage → Business Risk → Risk Heatmap → Security Correlation →
Candidates → Cases → вывод`.
`security_correlation`:
@@ -336,13 +346,19 @@ POST /api/cases/{case_id}/status
## Markdown
Оперативный отчет содержит раздел:
Оперативный отчет содержит раздел `Связанная картина риска` сразу после
`Сводка руководителя`, до технических деталей. Это главный управленческий
вывод: что происходит, почему это риск и какие слои это подтверждают.
Оперативный отчет содержит разделы:
```text
## Сводка руководителя
## Связанная картина риска
## Достоверность данных
## SLA покрытия агентов
## Карта рисков подразделений
## Корреляция Workforce ↔ Security
## Связанная картина риска
## Риски подразделений
## Динамика бизнес-рисков
## Кандидаты в инциденты
@@ -354,13 +370,18 @@ POST /api/cases/{case_id}/status
## Executive Summary
Executive summary показывает до 3 наиболее рискованных подразделений, если
их уровень риска выше `LOW`.
Executive summary сначала показывает связанный управленческий вывод по Risk
Narrative, а затем только поддерживающие технические пункты. Отдельные строки
по Business Risk и кандидатам не дублируют главный вывод, если эти причины уже
вошли в narrative.
Формат:
```text
Подразделение Бухгалтерия: HIGH — причина: низкий Trust KPI
Главный управленческий вывод: В подразделении Бухгалтерия связаны слои:
Trust KPI 50%, Agent Coverage 80%, Risk Heatmap HIGH, Business Risk MEDIUM,
Security Correlation 40/100, Incident Candidates 1, Cases 0. риск связан из-за
слабого Trust KPI, падения активности, слабого покрытия агентов.
```
Если подразделение 3+ дня подряд находится в `HIGH` или `CRITICAL`,
+1
View File
@@ -127,6 +127,7 @@ async function main() {
"Сотрудников в работе",
"Средний индекс активности",
"Главная причина риска",
"Подтверждающие слои",
"Достоверность данных агента",
"Стабильность агента за 7 дней",
"Качество данных по рабочим местам",