"""Phase 13: Executive Dashboard — PostureSnapshot model.""" import uuid from datetime import datetime from sqlalchemy import ( Boolean, Column, Date, DateTime, Float, ForeignKey, Index, Integer, UniqueConstraint, ) from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.orm import relationship from app.database import Base class PostureSnapshot(Base): """ Daily point-in-time capture of the organisation's security posture. Aggregates data from all phases (coverage, risk, ownership, knowledge, attack-paths) into a single row that can be trended over time. """ __tablename__ = "posture_snapshots" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) snapshot_date = Column(Date, nullable=False) # one per calendar day # ── Coverage ────────────────────────────────────────────────────────────── total_techniques = Column(Integer, nullable=False, default=0) validated_count = Column(Integer, nullable=False, default=0) partial_count = Column(Integer, nullable=False, default=0) not_covered_count = Column(Integer, nullable=False, default=0) coverage_pct = Column(Float, nullable=False, default=0.0) # 0–100 # ── Risk ───────────────────────────────────────────────────────────────── avg_risk_score = Column(Float, nullable=False, default=0.0) critical_count = Column(Integer, nullable=False, default=0) high_count = Column(Integer, nullable=False, default=0) medium_count = Column(Integer, nullable=False, default=0) low_count = Column(Integer, nullable=False, default=0) # ── Operations ──────────────────────────────────────────────────────────── open_queue_items = Column(Integer, nullable=False, default=0) orphan_techniques = Column(Integer, nullable=False, default=0) # ── Knowledge ───────────────────────────────────────────────────────────── playbook_count = Column(Integer, nullable=False, default=0) lesson_count = Column(Integer, nullable=False, default=0) # ── MTTD (from attack-path executions completed in last 30 d) ──────────── mttd_avg_seconds = Column(Float, nullable=True) # None if no data executions_30d = Column(Integer, nullable=False, default=0) detection_rate_30d = Column(Float, nullable=True) # avg across executions # ── Meta ───────────────────────────────────────────────────────────────── created_by = Column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True ) created_at = Column(DateTime, default=datetime.utcnow) extra = Column(JSONB, nullable=True) # full breakdown / by-tactic creator = relationship("User", foreign_keys=[created_by]) __table_args__ = ( UniqueConstraint("snapshot_date", name="uq_posture_snapshot_date"), Index("ix_posture_snapshots_date", "snapshot_date"), )