feat(portal): correlate workforce and security risk
This commit is contained in:
@@ -347,6 +347,23 @@ struct RiskHeatmapItem {
|
|||||||
heat_level: String,
|
heat_level: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize)]
|
||||||
|
struct SecurityCorrelationItem {
|
||||||
|
department: String,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
trust_kpi_score: Option<u8>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
activity_score: Option<u8>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
business_risk_level: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
critical_candidates: Option<usize>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
open_cases: Option<usize>,
|
||||||
|
correlation_score: u8,
|
||||||
|
correlation_reason: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
struct RiskIncidentCandidate {
|
struct RiskIncidentCandidate {
|
||||||
id: String,
|
id: String,
|
||||||
@@ -992,6 +1009,7 @@ struct ReportMarkdownContext<'a> {
|
|||||||
business_risk_history: &'a [BusinessRiskHistoryItem],
|
business_risk_history: &'a [BusinessRiskHistoryItem],
|
||||||
business_risk_history_summary: &'a BusinessRiskHistorySummary,
|
business_risk_history_summary: &'a BusinessRiskHistorySummary,
|
||||||
risk_heatmap: &'a [RiskHeatmapItem],
|
risk_heatmap: &'a [RiskHeatmapItem],
|
||||||
|
security_correlation: &'a [SecurityCorrelationItem],
|
||||||
risk_incident_candidates: &'a [RiskIncidentCandidate],
|
risk_incident_candidates: &'a [RiskIncidentCandidate],
|
||||||
incident_review_audit_summary: &'a IncidentReviewAuditSummary,
|
incident_review_audit_summary: &'a IncidentReviewAuditSummary,
|
||||||
}
|
}
|
||||||
@@ -2299,6 +2317,7 @@ fn build_reports(
|
|||||||
&risk_incident_candidates,
|
&risk_incident_candidates,
|
||||||
inputs.cases,
|
inputs.cases,
|
||||||
);
|
);
|
||||||
|
let security_correlation = build_security_correlation(&risk_heatmap);
|
||||||
let executive_dashboard = build_executive_dashboard(
|
let executive_dashboard = build_executive_dashboard(
|
||||||
snapshot,
|
snapshot,
|
||||||
&agent_quality_explain,
|
&agent_quality_explain,
|
||||||
@@ -2413,6 +2432,16 @@ fn build_reports(
|
|||||||
item.open_cases.unwrap_or(0)
|
item.open_cases.unwrap_or(0)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
for item in security_correlation
|
||||||
|
.iter()
|
||||||
|
.filter(|item| item.correlation_score > 0)
|
||||||
|
.take(3)
|
||||||
|
{
|
||||||
|
executive_points.push(format!(
|
||||||
|
"Корреляция Workforce/Security {}: {}/100 — {}",
|
||||||
|
item.department, item.correlation_score, item.correlation_reason
|
||||||
|
));
|
||||||
|
}
|
||||||
executive_points.push(format!(
|
executive_points.push(format!(
|
||||||
"Главный риск: {}",
|
"Главный риск: {}",
|
||||||
executive_dashboard.summary.main_risk
|
executive_dashboard.summary.main_risk
|
||||||
@@ -2447,6 +2476,7 @@ fn build_reports(
|
|||||||
business_risk_history: &business_risk_history,
|
business_risk_history: &business_risk_history,
|
||||||
business_risk_history_summary: &business_risk_history_summary,
|
business_risk_history_summary: &business_risk_history_summary,
|
||||||
risk_heatmap: &risk_heatmap,
|
risk_heatmap: &risk_heatmap,
|
||||||
|
security_correlation: &security_correlation,
|
||||||
risk_incident_candidates: &risk_incident_candidates,
|
risk_incident_candidates: &risk_incident_candidates,
|
||||||
incident_review_audit_summary: &incident_review_audit_summary,
|
incident_review_audit_summary: &incident_review_audit_summary,
|
||||||
},
|
},
|
||||||
@@ -2541,6 +2571,7 @@ fn build_reports(
|
|||||||
"business_risk_history": business_risk_history,
|
"business_risk_history": business_risk_history,
|
||||||
"business_risk_history_summary": business_risk_history_summary,
|
"business_risk_history_summary": business_risk_history_summary,
|
||||||
"risk_heatmap": risk_heatmap,
|
"risk_heatmap": risk_heatmap,
|
||||||
|
"security_correlation": security_correlation,
|
||||||
"risk_incident_candidates": risk_incident_candidates,
|
"risk_incident_candidates": risk_incident_candidates,
|
||||||
"incident_review_audit_summary": incident_review_audit_summary,
|
"incident_review_audit_summary": incident_review_audit_summary,
|
||||||
"workforce_policy": workforce_policy_explain,
|
"workforce_policy": workforce_policy_explain,
|
||||||
@@ -3191,6 +3222,92 @@ fn heatmap_rank(level: &str) -> u8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_security_correlation(heatmap: &[RiskHeatmapItem]) -> Vec<SecurityCorrelationItem> {
|
||||||
|
let mut items = heatmap
|
||||||
|
.iter()
|
||||||
|
.map(security_correlation_item)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
items.sort_by(|left, right| {
|
||||||
|
right
|
||||||
|
.correlation_score
|
||||||
|
.cmp(&left.correlation_score)
|
||||||
|
.then_with(|| {
|
||||||
|
heatmap_rank(right.business_risk_level.as_deref().unwrap_or("UNKNOWN")).cmp(
|
||||||
|
&heatmap_rank(left.business_risk_level.as_deref().unwrap_or("UNKNOWN")),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.then_with(|| left.department.cmp(&right.department))
|
||||||
|
});
|
||||||
|
items
|
||||||
|
}
|
||||||
|
|
||||||
|
fn security_correlation_item(item: &RiskHeatmapItem) -> SecurityCorrelationItem {
|
||||||
|
let mut score = 0u64;
|
||||||
|
let mut reasons = Vec::new();
|
||||||
|
let trust = item.trust_kpi_score;
|
||||||
|
let activity = item.activity_score;
|
||||||
|
let business_level = item.business_risk_level.as_deref().unwrap_or("UNKNOWN");
|
||||||
|
let critical_candidates = item.critical_candidates.unwrap_or(0);
|
||||||
|
let open_cases = item.open_cases.unwrap_or(0);
|
||||||
|
|
||||||
|
match business_level {
|
||||||
|
"CRITICAL" => score += 40,
|
||||||
|
"HIGH" => score += 30,
|
||||||
|
"MEDIUM" => score += 15,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if matches!(business_level, "HIGH" | "CRITICAL") && trust.is_some_and(|value| value < 75) {
|
||||||
|
reasons.push("низкий Trust KPI + высокий риск".to_string());
|
||||||
|
}
|
||||||
|
if let Some(value) = trust {
|
||||||
|
if value < 50 {
|
||||||
|
score += 25;
|
||||||
|
} else if value < 75 {
|
||||||
|
score += 15;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(value) = activity {
|
||||||
|
if value < 35 {
|
||||||
|
score += 20;
|
||||||
|
} else if value < 60 {
|
||||||
|
score += 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if activity.is_some_and(|value| value < 60) && critical_candidates > 0 {
|
||||||
|
reasons.push("снижение активности + рост кандидатов".to_string());
|
||||||
|
}
|
||||||
|
if let Some(value) = item.agent_coverage_pct {
|
||||||
|
if value < 75 {
|
||||||
|
score += 25;
|
||||||
|
reasons.push("большое количество отсутствующих агентов".to_string());
|
||||||
|
} else if value < 90 {
|
||||||
|
score += 10;
|
||||||
|
}
|
||||||
|
} else if business_level != "UNKNOWN" {
|
||||||
|
reasons.push("покрытие агентов не подтверждено".to_string());
|
||||||
|
}
|
||||||
|
if critical_candidates > 0 {
|
||||||
|
score += (critical_candidates as u64).saturating_mul(20).min(35);
|
||||||
|
}
|
||||||
|
if open_cases > 0 {
|
||||||
|
score += (open_cases as u64).saturating_mul(15).min(30);
|
||||||
|
reasons.push("рост открытых расследований".to_string());
|
||||||
|
}
|
||||||
|
if reasons.is_empty() {
|
||||||
|
reasons.push("прямая связка Workforce и Security не выражена".to_string());
|
||||||
|
}
|
||||||
|
SecurityCorrelationItem {
|
||||||
|
department: item.department.clone(),
|
||||||
|
trust_kpi_score: item.trust_kpi_score,
|
||||||
|
activity_score: item.activity_score,
|
||||||
|
business_risk_level: item.business_risk_level.clone(),
|
||||||
|
critical_candidates: item.critical_candidates,
|
||||||
|
open_cases: item.open_cases,
|
||||||
|
correlation_score: score.min(100) as u8,
|
||||||
|
correlation_reason: reasons.join("; "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn build_executive_dashboard(
|
fn build_executive_dashboard(
|
||||||
snapshot: &Snapshot,
|
snapshot: &Snapshot,
|
||||||
agent_quality_explain: &AgentQualityExplain,
|
agent_quality_explain: &AgentQualityExplain,
|
||||||
@@ -5360,6 +5477,7 @@ fn render_report_markdown(
|
|||||||
context.business_risk_history_summary,
|
context.business_risk_history_summary,
|
||||||
);
|
);
|
||||||
append_risk_heatmap_markdown(&mut text, context.risk_heatmap);
|
append_risk_heatmap_markdown(&mut text, context.risk_heatmap);
|
||||||
|
append_security_correlation_markdown(&mut text, context.security_correlation);
|
||||||
append_risk_incident_candidates_markdown(&mut text, context.risk_incident_candidates);
|
append_risk_incident_candidates_markdown(&mut text, context.risk_incident_candidates);
|
||||||
append_incident_review_markdown(&mut text, context.risk_incident_candidates);
|
append_incident_review_markdown(&mut text, context.risk_incident_candidates);
|
||||||
append_incident_review_audit_markdown(
|
append_incident_review_audit_markdown(
|
||||||
@@ -5672,6 +5790,28 @@ fn append_risk_heatmap_markdown(text: &mut String, items: &[RiskHeatmapItem]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn append_security_correlation_markdown(text: &mut String, items: &[SecurityCorrelationItem]) {
|
||||||
|
text.push_str("\n## Корреляция Workforce ↔ Security\n\n");
|
||||||
|
if items.is_empty() {
|
||||||
|
text.push_str("- Корреляция пока не рассчитана: недостаточно данных по подразделениям.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
text.push_str("- Примечание: это аналитическая связка признаков, она не создает инциденты автоматически.\n");
|
||||||
|
for item in items.iter().take(10) {
|
||||||
|
text.push_str(&format!(
|
||||||
|
"- {}: score={}/100, Trust={}, activity={}, business_risk={}, critical_candidates={}, open_cases={}\n",
|
||||||
|
item.department,
|
||||||
|
item.correlation_score,
|
||||||
|
optional_score_text(item.trust_kpi_score),
|
||||||
|
optional_score_text(item.activity_score),
|
||||||
|
item.business_risk_level.as_deref().unwrap_or("UNKNOWN"),
|
||||||
|
item.critical_candidates.unwrap_or(0),
|
||||||
|
item.open_cases.unwrap_or(0)
|
||||||
|
));
|
||||||
|
text.push_str(&format!(" - причина: {}\n", item.correlation_reason));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn append_risk_incident_candidates_markdown(
|
fn append_risk_incident_candidates_markdown(
|
||||||
text: &mut String,
|
text: &mut String,
|
||||||
candidates: &[RiskIncidentCandidate],
|
candidates: &[RiskIncidentCandidate],
|
||||||
@@ -9393,6 +9533,18 @@ mod tests {
|
|||||||
assert_eq!(report["risk_heatmap"][0]["heat_level"], "HIGH");
|
assert_eq!(report["risk_heatmap"][0]["heat_level"], "HIGH");
|
||||||
assert_eq!(report["risk_heatmap"][0]["trust_kpi_score"], 50);
|
assert_eq!(report["risk_heatmap"][0]["trust_kpi_score"], 50);
|
||||||
assert_eq!(report["risk_heatmap"][0]["activity_score"], 50);
|
assert_eq!(report["risk_heatmap"][0]["activity_score"], 50);
|
||||||
|
assert!(report["security_correlation"].is_array());
|
||||||
|
assert_eq!(
|
||||||
|
report["security_correlation"][0]["department"],
|
||||||
|
"Бухгалтерия"
|
||||||
|
);
|
||||||
|
assert_eq!(report["security_correlation"][0]["correlation_score"], 40);
|
||||||
|
assert!(
|
||||||
|
report["security_correlation"][0]["correlation_reason"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap()
|
||||||
|
.contains("покрытие агентов")
|
||||||
|
);
|
||||||
assert_eq!(report["business_risk_history"].as_array().unwrap().len(), 3);
|
assert_eq!(report["business_risk_history"].as_array().unwrap().len(), 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
report["business_risk_history"][0]["department"],
|
report["business_risk_history"][0]["department"],
|
||||||
@@ -9558,6 +9710,16 @@ mod tests {
|
|||||||
.iter()
|
.iter()
|
||||||
.any(|item| item.as_str().unwrap().contains("Проблемная зона"))
|
.any(|item| item.as_str().unwrap().contains("Проблемная зона"))
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
report["executive_points"]
|
||||||
|
.as_array()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.any(|item| item
|
||||||
|
.as_str()
|
||||||
|
.unwrap()
|
||||||
|
.contains("Корреляция Workforce/Security"))
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
report["executive_points"]
|
report["executive_points"]
|
||||||
.as_array()
|
.as_array()
|
||||||
@@ -9577,6 +9739,12 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.contains("## Карта рисков подразделений")
|
.contains("## Карта рисков подразделений")
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
report["markdown"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap()
|
||||||
|
.contains("## Корреляция Workforce ↔ Security")
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
report["markdown"]
|
report["markdown"]
|
||||||
.as_str()
|
.as_str()
|
||||||
|
|||||||
@@ -877,6 +877,7 @@ function renderOperator(data, report) {
|
|||||||
${renderAgentQualityNodes(report?.agent_quality_nodes, report?.agent_quality_nodes_summary)}
|
${renderAgentQualityNodes(report?.agent_quality_nodes, report?.agent_quality_nodes_summary)}
|
||||||
${renderAgentCoverageSla(report?.agent_coverage_sla)}
|
${renderAgentCoverageSla(report?.agent_coverage_sla)}
|
||||||
${renderRiskHeatmap(report?.risk_heatmap)}
|
${renderRiskHeatmap(report?.risk_heatmap)}
|
||||||
|
${renderSecurityCorrelation(report?.security_correlation)}
|
||||||
${renderBusinessRisk(report?.business_risk)}
|
${renderBusinessRisk(report?.business_risk)}
|
||||||
${renderBusinessRiskTimeline(report?.business_risk_history, report?.business_risk_history_summary)}
|
${renderBusinessRiskTimeline(report?.business_risk_history, report?.business_risk_history_summary)}
|
||||||
${renderRiskIncidentCandidates(report?.risk_incident_candidates)}
|
${renderRiskIncidentCandidates(report?.risk_incident_candidates)}
|
||||||
@@ -1827,6 +1828,61 @@ function riskPercentText(value) {
|
|||||||
return Number.isFinite(number) ? `${Math.round(number)}%` : "UNKNOWN";
|
return Number.isFinite(number) ? `${Math.round(number)}%` : "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderSecurityCorrelation(items) {
|
||||||
|
const rows = Array.isArray(items) ? items.slice(0, 10) : [];
|
||||||
|
const score = Number(rows[0]?.correlation_score || 0);
|
||||||
|
const status = score >= 80 ? "CRITICAL" : score >= 60 ? "HIGH" : score >= 35 ? "MEDIUM" : rows.length ? "LOW" : "UNKNOWN";
|
||||||
|
return `
|
||||||
|
<section class="card security-correlation-card">
|
||||||
|
<div class="section-head">
|
||||||
|
<div>
|
||||||
|
<h3>Корреляция Workforce и Security</h3>
|
||||||
|
<p class="muted">Связь между падением активности, доверием к KPI, кандидатами в инциденты и открытыми делами. Инциденты автоматически не создаются.</p>
|
||||||
|
</div>
|
||||||
|
<span class="badge ${statusClass(status)}">${ui(status)}</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-scroll">
|
||||||
|
<table class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Подразделение</th>
|
||||||
|
<th>Trust KPI</th>
|
||||||
|
<th>Активность</th>
|
||||||
|
<th>Бизнес-риск</th>
|
||||||
|
<th>Security</th>
|
||||||
|
<th>Корреляция</th>
|
||||||
|
<th>Причина</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${rows.length ? rows.map(item => `
|
||||||
|
<tr>
|
||||||
|
<td><strong>${ui(item.department || "Без подразделения")}</strong></td>
|
||||||
|
<td>${ui(riskPercentText(item.trust_kpi_score))}</td>
|
||||||
|
<td>${ui(riskPercentText(item.activity_score))}</td>
|
||||||
|
<td><span class="badge ${statusClass(item.business_risk_level)}">${ui(item.business_risk_level || "UNKNOWN")}</span></td>
|
||||||
|
<td>кандидаты ${ui(item.critical_candidates ?? 0)} · дела ${ui(item.open_cases ?? 0)}</td>
|
||||||
|
<td><strong>${ui(Number(item.correlation_score || 0))}/100</strong></td>
|
||||||
|
<td>${ui(item.correlation_reason || "связь не выражена")}</td>
|
||||||
|
</tr>
|
||||||
|
`).join("") : `
|
||||||
|
<tr>
|
||||||
|
<td>Нет данных</td>
|
||||||
|
<td>UNKNOWN</td>
|
||||||
|
<td>UNKNOWN</td>
|
||||||
|
<td><span class="badge status-unknown">UNKNOWN</span></td>
|
||||||
|
<td>кандидаты 0 · дела 0</td>
|
||||||
|
<td>0/100</td>
|
||||||
|
<td>недостаточно данных по подразделениям</td>
|
||||||
|
</tr>
|
||||||
|
`}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
function businessRiskReasons(item) {
|
function businessRiskReasons(item) {
|
||||||
const reasons = Array.isArray(item?.reasons) && item.reasons.length
|
const reasons = Array.isArray(item?.reasons) && item.reasons.length
|
||||||
? item.reasons.join("; ")
|
? item.reasons.join("; ")
|
||||||
@@ -2077,6 +2133,7 @@ function renderReports(data) {
|
|||||||
${renderAgentQualityNodes(data.agent_quality_nodes, data.agent_quality_nodes_summary)}
|
${renderAgentQualityNodes(data.agent_quality_nodes, data.agent_quality_nodes_summary)}
|
||||||
${renderAgentCoverageSla(data.agent_coverage_sla)}
|
${renderAgentCoverageSla(data.agent_coverage_sla)}
|
||||||
${renderRiskHeatmap(data.risk_heatmap)}
|
${renderRiskHeatmap(data.risk_heatmap)}
|
||||||
|
${renderSecurityCorrelation(data.security_correlation)}
|
||||||
${renderBusinessRisk(data.business_risk)}
|
${renderBusinessRisk(data.business_risk)}
|
||||||
${renderBusinessRiskTimeline(data.business_risk_history, data.business_risk_history_summary)}
|
${renderBusinessRiskTimeline(data.business_risk_history, data.business_risk_history_summary)}
|
||||||
${renderRiskIncidentCandidates(data.risk_incident_candidates)}
|
${renderRiskIncidentCandidates(data.risk_incident_candidates)}
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ Business Risk не является автоматическим обвинен
|
|||||||
рискам, кандидатам, делам и готовности расследований;
|
рискам, кандидатам, делам и готовности расследований;
|
||||||
- `risk_heatmap` - карта рисков подразделений по Trust KPI, активности,
|
- `risk_heatmap` - карта рисков подразделений по Trust KPI, активности,
|
||||||
покрытию агентов, открытым делам и кандидатам в инциденты;
|
покрытию агентов, открытым делам и кандидатам в инциденты;
|
||||||
|
- `security_correlation` - аналитическая связка Workforce и Security:
|
||||||
|
активность, Trust KPI, Business Risk, кандидаты и открытые дела;
|
||||||
- `business_risk_history` - timeline риска по подразделениям;
|
- `business_risk_history` - timeline риска по подразделениям;
|
||||||
- `business_risk_history_summary` - сводка динамики.
|
- `business_risk_history_summary` - сводка динамики.
|
||||||
- `risk_incident_candidates` - read-only кандидаты для ручной проверки.
|
- `risk_incident_candidates` - read-only кандидаты для ручной проверки.
|
||||||
@@ -74,6 +76,19 @@ Business Risk не является автоматическим обвинен
|
|||||||
- `heat_level` - итоговая зона карты: `LOW`, `MEDIUM`, `HIGH`,
|
- `heat_level` - итоговая зона карты: `LOW`, `MEDIUM`, `HIGH`,
|
||||||
`CRITICAL` или `UNKNOWN`.
|
`CRITICAL` или `UNKNOWN`.
|
||||||
|
|
||||||
|
`security_correlation`:
|
||||||
|
|
||||||
|
- `department` - подразделение;
|
||||||
|
- `trust_kpi_score` - доверие к KPI активности, если доступно;
|
||||||
|
- `activity_score` - индекс активности подразделения, если доступен;
|
||||||
|
- `business_risk_level` - уровень Business Risk;
|
||||||
|
- `critical_candidates` - число кандидатов `HIGH`/`CRITICAL`;
|
||||||
|
- `open_cases` - открытые дела по подразделению;
|
||||||
|
- `correlation_score` - сила связки Workforce и Security от 0 до 100;
|
||||||
|
- `correlation_reason` - человеко-понятное объяснение связи, например
|
||||||
|
`низкий Trust KPI + высокий риск` или
|
||||||
|
`снижение активности + рост кандидатов`.
|
||||||
|
|
||||||
Элемент `business_risk_history`:
|
Элемент `business_risk_history`:
|
||||||
|
|
||||||
- `date` - дата daily point;
|
- `date` - дата daily point;
|
||||||
@@ -317,6 +332,7 @@ POST /api/cases/{case_id}/status
|
|||||||
```text
|
```text
|
||||||
## Сводка руководителя
|
## Сводка руководителя
|
||||||
## Карта рисков подразделений
|
## Карта рисков подразделений
|
||||||
|
## Корреляция Workforce ↔ Security
|
||||||
## Риски подразделений
|
## Риски подразделений
|
||||||
## Динамика бизнес-рисков
|
## Динамика бизнес-рисков
|
||||||
## Кандидаты в инциденты
|
## Кандидаты в инциденты
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ async function main() {
|
|||||||
"Качество данных по рабочим местам",
|
"Качество данных по рабочим местам",
|
||||||
"Покрытие агентов",
|
"Покрытие агентов",
|
||||||
"Карта рисков подразделений",
|
"Карта рисков подразделений",
|
||||||
|
"Корреляция Workforce и Security",
|
||||||
"Риски подразделений",
|
"Риски подразделений",
|
||||||
"Динамика рисков",
|
"Динамика рисков",
|
||||||
"Кандидаты в инциденты",
|
"Кандидаты в инциденты",
|
||||||
|
|||||||
Reference in New Issue
Block a user