"""Compliance domain entities with coverage calculation logic. Pure domain logic — no framework imports. """ # Enable future language features for compatibility from __future__ import annotations # Import enum import enum # Import uuid import uuid # Import dataclass, field from dataclasses from dataclasses import dataclass, field # Define class ControlCoverageStatus class ControlCoverageStatus(str, enum.Enum): """Computed coverage level for a single compliance control.""" # Assign covered = "covered" covered = "covered" # Assign partially_covered = "partially_covered" partially_covered = "partially_covered" # Assign not_covered = "not_covered" not_covered = "not_covered" # Apply the @dataclass decorator @dataclass # Define class ComplianceControlEntity class ComplianceControlEntity: """Pure domain representation of a single compliance framework control. Derives its coverage status from the technique statuses associated with it via the ``technique_statuses`` list. """ # control_id: str control_id: str # title: str title: str # Assign id = None id: uuid.UUID | None = None # Assign description = None description: str | None = None # Assign category = None category: str | None = None # Assign technique_statuses = field(default_factory=list) technique_statuses: list[str] = field(default_factory=list) # Apply the @property decorator @property # Define function coverage_status def coverage_status(self) -> ControlCoverageStatus: """Compute the coverage status for this control based on linked technique statuses. Returns: ControlCoverageStatus: ``covered`` when all techniques are covered, ``partially_covered`` when at least one is covered, and ``not_covered`` when none are covered or the control has no techniques. """ # Check: not self.technique_statuses if not self.technique_statuses: # Return ControlCoverageStatus.not_covered return ControlCoverageStatus.not_covered # Assign covered_statuses = {"validated", "partial"} covered_statuses = {"validated", "partial"} # Assign covered = [s for s in self.technique_statuses if s in covered_statuses] covered = [s for s in self.technique_statuses if s in covered_statuses] # Check: len(covered) == len(self.technique_statuses) if len(covered) == len(self.technique_statuses): # Return ControlCoverageStatus.covered return ControlCoverageStatus.covered # Alternative: len(covered) > 0 elif len(covered) > 0: # Return ControlCoverageStatus.partially_covered return ControlCoverageStatus.partially_covered # Return ControlCoverageStatus.not_covered return ControlCoverageStatus.not_covered # Apply the @dataclass decorator @dataclass # Define class ComplianceFrameworkEntity class ComplianceFrameworkEntity: """Pure domain representation of a compliance framework (e.g. NIST 800-53, PCI-DSS). Aggregates a collection of controls and provides aggregate coverage statistics. """ # name: str name: str # Assign id = None id: uuid.UUID | None = None # Assign version = None version: str | None = None # Assign description = None description: str | None = None # Assign is_active = True is_active: bool = True # Assign controls = field(default_factory=list) controls: list[ComplianceControlEntity] = field(default_factory=list) # Apply the @property decorator @property # Define function total_controls def total_controls(self) -> int: """Return the total number of controls in this framework. Returns: int: Count of all controls regardless of coverage status. """ # Return len(self.controls) return len(self.controls) # Apply the @property decorator @property # Define function covered_controls def covered_controls(self) -> int: """Return the number of fully covered controls in this framework. Returns: int: Count of controls with ``ControlCoverageStatus.covered`` status. """ # Return sum( return sum( # Literal argument value 1 for c in self.controls if c.coverage_status == ControlCoverageStatus.covered ) # Apply the @property decorator @property # Define function coverage_pct def coverage_pct(self) -> float: """Return the percentage of controls that are fully covered. Returns: float: A value from 0.0 to 100.0, rounded to one decimal place. Returns 0.0 when the framework has no controls. """ # Check: self.total_controls == 0 if self.total_controls == 0: # Return 0.0 return 0.0 # Return round(self.covered_controls / self.total_controls * 100, 1) return round(self.covered_controls / self.total_controls * 100, 1) # Define function get_gap_controls def get_gap_controls(self) -> list[ComplianceControlEntity]: """Return controls that are not fully covered. Returns: list[ComplianceControlEntity]: Controls with ``partially_covered`` or ``not_covered`` status. """ # Return [ return [ c for c in self.controls if c.coverage_status != ControlCoverageStatus.covered ]