""" Compliance repositories — extracted from compliance/db/repository.py. Phase 1 Step 5: the monolithic repository module is decomposed per aggregate. Every repository class is re-exported from ``compliance.db.repository`` for backwards compatibility. """ import uuid from datetime import datetime, date, timezone from typing import List, Optional, Dict, Any, Tuple from sqlalchemy.orm import Session as DBSession, selectinload, joinedload from sqlalchemy import func, and_, or_ from compliance.db.models import ( RegulationDB, RequirementDB, ControlDB, ControlMappingDB, EvidenceDB, RiskDB, AuditExportDB, AuditSessionDB, AuditSignOffDB, AuditResultEnum, AuditSessionStatusEnum, RegulationTypeEnum, ControlDomainEnum, ControlStatusEnum, RiskLevelEnum, EvidenceStatusEnum, ExportStatusEnum, ServiceModuleDB, ModuleRegulationMappingDB, ) class ServiceModuleRepository: """Repository for service modules (Sprint 3).""" def __init__(self, db: DBSession): self.db = db def create( self, name: str, display_name: str, service_type: str, description: Optional[str] = None, port: Optional[int] = None, technology_stack: Optional[List[str]] = None, repository_path: Optional[str] = None, docker_image: Optional[str] = None, data_categories: Optional[List[str]] = None, processes_pii: bool = False, processes_health_data: bool = False, ai_components: bool = False, criticality: str = "medium", owner_team: Optional[str] = None, owner_contact: Optional[str] = None, ) -> "ServiceModuleDB": """Create a service module.""" from .models import ServiceModuleDB, ServiceTypeEnum module = ServiceModuleDB( id=str(uuid.uuid4()), name=name, display_name=display_name, description=description, service_type=ServiceTypeEnum(service_type), port=port, technology_stack=technology_stack or [], repository_path=repository_path, docker_image=docker_image, data_categories=data_categories or [], processes_pii=processes_pii, processes_health_data=processes_health_data, ai_components=ai_components, criticality=criticality, owner_team=owner_team, owner_contact=owner_contact, ) self.db.add(module) self.db.commit() self.db.refresh(module) return module def get_by_id(self, module_id: str) -> Optional["ServiceModuleDB"]: """Get module by ID.""" from .models import ServiceModuleDB return self.db.query(ServiceModuleDB).filter(ServiceModuleDB.id == module_id).first() def get_by_name(self, name: str) -> Optional["ServiceModuleDB"]: """Get module by name.""" from .models import ServiceModuleDB return self.db.query(ServiceModuleDB).filter(ServiceModuleDB.name == name).first() def get_all( self, service_type: Optional[str] = None, criticality: Optional[str] = None, processes_pii: Optional[bool] = None, ai_components: Optional[bool] = None, ) -> List["ServiceModuleDB"]: """Get all modules with filters.""" from .models import ServiceModuleDB, ServiceTypeEnum query = self.db.query(ServiceModuleDB).filter(ServiceModuleDB.is_active) if service_type: query = query.filter(ServiceModuleDB.service_type == ServiceTypeEnum(service_type)) if criticality: query = query.filter(ServiceModuleDB.criticality == criticality) if processes_pii is not None: query = query.filter(ServiceModuleDB.processes_pii == processes_pii) if ai_components is not None: query = query.filter(ServiceModuleDB.ai_components == ai_components) return query.order_by(ServiceModuleDB.name).all() def get_with_regulations(self, module_id: str) -> Optional["ServiceModuleDB"]: """Get module with regulation mappings loaded.""" from .models import ServiceModuleDB, ModuleRegulationMappingDB from sqlalchemy.orm import selectinload return ( self.db.query(ServiceModuleDB) .options( selectinload(ServiceModuleDB.regulation_mappings) .selectinload(ModuleRegulationMappingDB.regulation) ) .filter(ServiceModuleDB.id == module_id) .first() ) def add_regulation_mapping( self, module_id: str, regulation_id: str, relevance_level: str = "medium", notes: Optional[str] = None, applicable_articles: Optional[List[str]] = None, ) -> "ModuleRegulationMappingDB": """Add a regulation mapping to a module.""" from .models import ModuleRegulationMappingDB, RelevanceLevelEnum mapping = ModuleRegulationMappingDB( id=str(uuid.uuid4()), module_id=module_id, regulation_id=regulation_id, relevance_level=RelevanceLevelEnum(relevance_level), notes=notes, applicable_articles=applicable_articles, ) self.db.add(mapping) self.db.commit() self.db.refresh(mapping) return mapping def get_overview(self) -> Dict[str, Any]: """Get overview statistics for all modules.""" from .models import ModuleRegulationMappingDB modules = self.get_all() total = len(modules) by_type = {} by_criticality = {} pii_count = 0 ai_count = 0 for m in modules: type_key = m.service_type.value if m.service_type else "unknown" by_type[type_key] = by_type.get(type_key, 0) + 1 by_criticality[m.criticality] = by_criticality.get(m.criticality, 0) + 1 if m.processes_pii: pii_count += 1 if m.ai_components: ai_count += 1 # Get regulation coverage regulation_coverage = {} mappings = self.db.query(ModuleRegulationMappingDB).all() for mapping in mappings: reg = mapping.regulation if reg: code = reg.code regulation_coverage[code] = regulation_coverage.get(code, 0) + 1 # Calculate average compliance score scores = [m.compliance_score for m in modules if m.compliance_score is not None] avg_score = sum(scores) / len(scores) if scores else None return { "total_modules": total, "modules_by_type": by_type, "modules_by_criticality": by_criticality, "modules_processing_pii": pii_count, "modules_with_ai": ai_count, "average_compliance_score": round(avg_score, 1) if avg_score else None, "regulations_coverage": regulation_coverage, } def seed_from_data(self, services_data: List[Dict[str, Any]], force: bool = False) -> Dict[str, int]: """Seed modules from service_modules.py data.""" modules_created = 0 mappings_created = 0 for svc in services_data: # Check if module exists existing = self.get_by_name(svc["name"]) if existing and not force: continue if existing and force: # Delete existing module (cascades to mappings) self.db.delete(existing) self.db.commit() # Create module module = self.create( name=svc["name"], display_name=svc["display_name"], description=svc.get("description"), service_type=svc["service_type"], port=svc.get("port"), technology_stack=svc.get("technology_stack"), repository_path=svc.get("repository_path"), docker_image=svc.get("docker_image"), data_categories=svc.get("data_categories"), processes_pii=svc.get("processes_pii", False), processes_health_data=svc.get("processes_health_data", False), ai_components=svc.get("ai_components", False), criticality=svc.get("criticality", "medium"), owner_team=svc.get("owner_team"), ) modules_created += 1 # Create regulation mappings for reg_data in svc.get("regulations", []): # Find regulation by code reg = self.db.query(RegulationDB).filter( RegulationDB.code == reg_data["code"] ).first() if reg: self.add_regulation_mapping( module_id=module.id, regulation_id=reg.id, relevance_level=reg_data.get("relevance", "medium"), notes=reg_data.get("notes"), ) mappings_created += 1 return { "modules_created": modules_created, "mappings_created": mappings_created, }