Files
Aegis/backend/app/domain/entities/compliance.py

72 lines
2.0 KiB
Python

"""Compliance domain entities with coverage calculation logic.
Pure domain logic — no framework imports.
"""
from __future__ import annotations
import enum
import uuid
from dataclasses import dataclass, field
class ControlCoverageStatus(str, enum.Enum):
covered = "covered"
partially_covered = "partially_covered"
not_covered = "not_covered"
@dataclass
class ComplianceControlEntity:
control_id: str
title: str
id: uuid.UUID | None = None
description: str | None = None
category: str | None = None
technique_statuses: list[str] = field(default_factory=list)
@property
def coverage_status(self) -> ControlCoverageStatus:
if not self.technique_statuses:
return ControlCoverageStatus.not_covered
covered_statuses = {"validated", "partial"}
covered = [s for s in self.technique_statuses if s in covered_statuses]
if len(covered) == len(self.technique_statuses):
return ControlCoverageStatus.covered
elif len(covered) > 0:
return ControlCoverageStatus.partially_covered
return ControlCoverageStatus.not_covered
@dataclass
class ComplianceFrameworkEntity:
name: str
id: uuid.UUID | None = None
version: str | None = None
description: str | None = None
is_active: bool = True
controls: list[ComplianceControlEntity] = field(default_factory=list)
@property
def total_controls(self) -> int:
return len(self.controls)
@property
def covered_controls(self) -> int:
return sum(
1 for c in self.controls
if c.coverage_status == ControlCoverageStatus.covered
)
@property
def coverage_pct(self) -> float:
if self.total_controls == 0:
return 0.0
return round(self.covered_controls / self.total_controls * 100, 1)
def get_gap_controls(self) -> list[ComplianceControlEntity]:
return [
c for c in self.controls
if c.coverage_status != ControlCoverageStatus.covered
]