114 lines
2.9 KiB
Python
114 lines
2.9 KiB
Python
"""Phase 13: Executive Dashboard — Pydantic schemas."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime
|
|
from typing import Any, Dict, List, Optional
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class PostureSnapshotOut(BaseModel):
|
|
id: UUID
|
|
snapshot_date: date
|
|
|
|
# Coverage
|
|
total_techniques: int
|
|
validated_count: int
|
|
partial_count: int
|
|
not_covered_count: int
|
|
coverage_pct: float
|
|
|
|
# Risk
|
|
avg_risk_score: float
|
|
critical_count: int
|
|
high_count: int
|
|
medium_count: int
|
|
low_count: int
|
|
|
|
# Operations
|
|
open_queue_items: int
|
|
orphan_techniques: int
|
|
|
|
# Knowledge
|
|
playbook_count: int
|
|
lesson_count: int
|
|
|
|
# MTTD
|
|
mttd_avg_seconds: Optional[float] = None
|
|
executions_30d: int
|
|
detection_rate_30d: Optional[float] = None
|
|
|
|
# Meta
|
|
created_by: Optional[UUID] = None
|
|
created_at: Optional[datetime] = None
|
|
extra: Optional[Dict[str, Any]] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class ExecutiveSummary(BaseModel):
|
|
"""Full executive view — current posture + trends."""
|
|
snapshot: PostureSnapshotOut
|
|
coverage_trend: List[Dict[str, Any]] = Field(
|
|
default_factory=list,
|
|
description="Last 30-day coverage_pct series [{date, value}]",
|
|
)
|
|
risk_trend: List[Dict[str, Any]] = Field(
|
|
default_factory=list,
|
|
description="Last 30-day avg_risk_score series [{date, value}]",
|
|
)
|
|
top_risks: List[Dict[str, Any]] = Field(
|
|
default_factory=list,
|
|
description="Top 5 highest-risk techniques",
|
|
)
|
|
coverage_by_tactic: List[Dict[str, Any]] = Field(
|
|
default_factory=list,
|
|
description="Per-tactic validated/partial/not_covered counts",
|
|
)
|
|
recent_activity: List[Dict[str, Any]] = Field(
|
|
default_factory=list,
|
|
description="Most-recent events (tests, paths, queue changes)",
|
|
)
|
|
|
|
|
|
class KpiBlock(BaseModel):
|
|
"""Compact KPI block for a dashboard header."""
|
|
coverage_pct: float
|
|
avg_risk_score: float
|
|
critical_count: int
|
|
open_queue_items: int
|
|
orphan_techniques: int
|
|
mttd_avg_seconds: Optional[float] = None
|
|
detection_rate_30d: Optional[float] = None
|
|
playbook_count: int
|
|
lesson_count: int
|
|
snapshot_date: date
|
|
snapshot_id: Optional[UUID] = None
|
|
|
|
|
|
class CoverageByTactic(BaseModel):
|
|
tactic: str
|
|
total: int
|
|
validated: int
|
|
partial: int
|
|
not_covered: int
|
|
coverage_pct: float
|
|
|
|
|
|
class PostureHistoryEntry(BaseModel):
|
|
snapshot_date: date
|
|
coverage_pct: float
|
|
avg_risk_score: float
|
|
critical_count: int
|
|
open_queue_items: int
|
|
|
|
|
|
class ActivityEntry(BaseModel):
|
|
ts: datetime
|
|
category: str # "test" | "attack_path" | "queue" | "osint"
|
|
title: str
|
|
detail: Optional[str] = None
|