"""Phase 12: Risk Intelligence model — per-technique risk scoring.""" import uuid from datetime import datetime from sqlalchemy import ( Boolean, Column, DateTime, Float, ForeignKey, Index, Integer, String, UniqueConstraint, ) from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.orm import relationship from app.database import Base class TechniqueRiskProfile(Base): """ Aggregated risk profile for one technique. Combines four weighted factors: • detection_gap (35 %) — 0=fully covered → 1=no coverage • threat_actor_rel (30 %) — normalised actor count • osint_signals (20 %) — normalised recent OSINT items (30 d) • test_failure_rate (15 %) — proportion of tests where blue didn't detect risk_score = weighted sum × 100 → 0–100 risk_level: critical ≥75 | high ≥50 | medium ≥25 | low ≥10 | info """ __tablename__ = "technique_risk_profiles" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) technique_id = Column( UUID(as_uuid=True), ForeignKey("techniques.id", ondelete="CASCADE"), nullable=False, ) # ── Computed scores ─────────────────────────────────────────────────────── risk_score = Column(Float, nullable=False, default=0.0) # 0–100 likelihood = Column(Float, nullable=False, default=0.0) # 0–100 impact = Column(Float, nullable=False, default=0.0) # 0–100 risk_level = Column(String(16), nullable=False, default="info") # ── Raw factor values ───────────────────────────────────────────────────── detection_gap = Column(Float, nullable=False, default=1.0) # 0–1 threat_actor_count = Column(Integer, nullable=False, default=0) osint_signal_count = Column(Integer, nullable=False, default=0) # last 30 d test_fail_count = Column(Integer, nullable=False, default=0) test_total_count = Column(Integer, nullable=False, default=0) test_failure_rate = Column(Float, nullable=False, default=0.0) # 0–1 confidence_level = Column(Float, nullable=False, default=0.0) # DLC 0–1 # ── Rich detail ────────────────────────────────────────────────────────── scoring_breakdown = Column(JSONB, nullable=True) # per-factor contributions recommendations = Column(JSONB, nullable=True) # list[str] # ── Meta ───────────────────────────────────────────────────────────────── computed_at = Column(DateTime, default=datetime.utcnow) is_stale = Column(Boolean, default=True) technique = relationship("Technique", foreign_keys=[technique_id]) __table_args__ = ( UniqueConstraint("technique_id", name="uq_risk_profile_technique"), Index("ix_risk_profiles_risk_score", "risk_score"), Index("ix_risk_profiles_risk_level", "risk_level"), Index("ix_risk_profiles_stale", "is_stale"), )