"""Coverage snapshot models — periodic snapshots of coverage state. CoverageSnapshot stores aggregate metrics at a point in time. SnapshotTechniqueState stores per-technique state (normalized, one row per technique per snapshot) to avoid bloated JSONB fields. """ # Import uuid import uuid # Import from sqlalchemy from sqlalchemy import ( Column, DateTime, Float, ForeignKey, Index, Integer, String, func, ) # Import JSONB, UUID from sqlalchemy.dialects.postgresql from sqlalchemy.dialects.postgresql import JSONB, UUID # Import relationship from sqlalchemy.orm from sqlalchemy.orm import relationship # Import Base from app.database from app.database import Base # Define class CoverageSnapshot class CoverageSnapshot(Base): """A point-in-time snapshot of the organisation's overall coverage.""" # Assign __tablename__ = "coverage_snapshots" __tablename__ = "coverage_snapshots" # Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) # Assign name = Column(String, nullable=True) # e.g. "Pre-remediación Q1" name = Column(String, nullable=True) # e.g. "Pre-remediación Q1" # Assign organization_score = Column(Float, nullable=False) organization_score = Column(Float, nullable=False) # Assign total_techniques = Column(Integer, nullable=False) total_techniques = Column(Integer, nullable=False) # Assign validated_count = Column(Integer, nullable=False) validated_count = Column(Integer, nullable=False) # Assign partial_count = Column(Integer, nullable=False) partial_count = Column(Integer, nullable=False) # Assign not_covered_count = Column(Integer, nullable=False) not_covered_count = Column(Integer, nullable=False) # Assign in_progress_count = Column(Integer, nullable=False) in_progress_count = Column(Integer, nullable=False) # Assign not_evaluated_count = Column(Integer, nullable=False) not_evaluated_count = Column(Integer, nullable=False) # Assign coverage_percentage = Column(Float, nullable=False, default=0.0) coverage_percentage = Column(Float, nullable=False, default=0.0) # Assign by_tactic = Column(JSONB, nullable=False, default=dict) by_tactic = Column(JSONB, nullable=False, default=dict) # Assign by_status = Column(JSONB, nullable=False, default=dict) by_status = Column(JSONB, nullable=False, default=dict) # Assign stale_count = Column(Integer, nullable=False, default=0) stale_count = Column(Integer, nullable=False, default=0) # Assign never_tested_count = Column(Integer, nullable=False, default=0) never_tested_count = Column(Integer, nullable=False, default=0) # Assign created_by = Column( created_by = Column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), # Keyword argument: nullable nullable=True, ) # Assign created_at = Column(DateTime(timezone=True), server_default=func.now()) created_at = Column(DateTime(timezone=True), server_default=func.now()) # Relationships creator = relationship("User", foreign_keys=[created_by]) # Assign technique_states = relationship( technique_states = relationship( # Literal argument value "SnapshotTechniqueState", # Keyword argument: back_populates back_populates="snapshot", # Keyword argument: cascade cascade="all, delete-orphan", ) # Define class SnapshotTechniqueState class SnapshotTechniqueState(Base): """Per-technique state within a snapshot (normalised storage).""" # Assign __tablename__ = "snapshot_technique_states" __tablename__ = "snapshot_technique_states" # Assign id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) # Assign snapshot_id = Column( snapshot_id = Column( UUID(as_uuid=True), ForeignKey("coverage_snapshots.id", ondelete="CASCADE"), # Keyword argument: nullable nullable=False, ) # Assign technique_id = Column( technique_id = Column( UUID(as_uuid=True), ForeignKey("techniques.id", ondelete="CASCADE"), # Keyword argument: nullable nullable=False, ) # Assign mitre_id = Column(String, nullable=False) # denormalised for fast queries mitre_id = Column(String, nullable=False) # denormalised for fast queries # Assign status = Column(String, nullable=False) status = Column(String, nullable=False) # Assign score = Column(Float, nullable=True) score = Column(Float, nullable=True) # Relationships snapshot = relationship("CoverageSnapshot", back_populates="technique_states") # Assign technique = relationship("Technique") technique = relationship("Technique") # Assign __table_args__ = ( __table_args__ = ( Index("ix_snapshot_technique_states_snapshot", "snapshot_id"), Index("ix_snapshot_technique_states_technique", "technique_id"), )