Files
breakpilot-compliance/backend-compliance/compliance/api/module_routes.py
Benjamin Admin f7a0b11e41 fix: Vorbereitung-Module auf 100% — Feld-Fixes, Backend-Persistenz, Endpoints
- ScopeExportTab: 11 Feldnamen-Mismatches gegen ScopeDecision Interface korrigiert
  (level→determinedLevel, riskScore→risk_score, hardTriggers→triggeredHardTriggers,
  depthDescription→depth, effortEstimate→estimatedEffort, isMandatory→required,
  triggeredByHardTrigger→triggeredBy, effortDays→estimatedEffort)
- Company Profile: GET vom Backend beim Mount, snake_case→camelCase, SDK State Fallback
- Modules: Aktivierung/Deaktivierung ans Backend schreiben (activate/deactivate Endpoints)
- Obligations: Explizites Fehler-Banner statt stiller Fallback bei Backend-Fehler
- Source Policy: BlockedContentDB Model + GET /api/v1/admin/blocked-content Endpoint
- Import: Offline-Modus Label fuer Backend-Fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 12:02:40 +01:00

287 lines
10 KiB
Python

"""
FastAPI routes for Service Module Registry.
Endpoints:
- /modules: Module listing and management
- /modules/overview: Module compliance overview
- /modules/{module_id}: Module details
- /modules/seed: Seed modules from data
- /modules/{module_id}/regulations: Add regulation mapping
"""
import logging
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from classroom_engine.database import get_db
from ..db import RegulationRepository
from .schemas import (
ServiceModuleResponse, ServiceModuleListResponse, ServiceModuleDetailResponse,
ModuleRegulationMappingCreate, ModuleRegulationMappingResponse,
ModuleSeedRequest, ModuleSeedResponse, ModuleComplianceOverview,
)
logger = logging.getLogger(__name__)
router = APIRouter(tags=["compliance-modules"])
# ============================================================================
# Service Module Registry
# ============================================================================
@router.get("/modules", response_model=ServiceModuleListResponse)
async def list_modules(
service_type: Optional[str] = None,
criticality: Optional[str] = None,
processes_pii: Optional[bool] = None,
ai_components: Optional[bool] = None,
db: Session = Depends(get_db),
):
"""List all service modules with optional filters."""
from ..db.repository import ServiceModuleRepository
repo = ServiceModuleRepository(db)
modules = repo.get_all(
service_type=service_type,
criticality=criticality,
processes_pii=processes_pii,
ai_components=ai_components,
)
# Count regulations and risks for each module
results = []
for m in modules:
reg_count = len(m.regulation_mappings) if m.regulation_mappings else 0
risk_count = len(m.module_risks) if m.module_risks else 0
results.append(ServiceModuleResponse(
id=m.id,
name=m.name,
display_name=m.display_name,
description=m.description,
service_type=m.service_type.value if m.service_type else None,
port=m.port,
technology_stack=m.technology_stack or [],
repository_path=m.repository_path,
docker_image=m.docker_image,
data_categories=m.data_categories or [],
processes_pii=m.processes_pii,
processes_health_data=m.processes_health_data,
ai_components=m.ai_components,
criticality=m.criticality,
owner_team=m.owner_team,
owner_contact=m.owner_contact,
is_active=m.is_active,
compliance_score=m.compliance_score,
last_compliance_check=m.last_compliance_check,
created_at=m.created_at,
updated_at=m.updated_at,
regulation_count=reg_count,
risk_count=risk_count,
))
return ServiceModuleListResponse(modules=results, total=len(results))
@router.get("/modules/overview", response_model=ModuleComplianceOverview)
async def get_modules_overview(db: Session = Depends(get_db)):
"""Get overview statistics for all modules."""
from ..db.repository import ServiceModuleRepository
repo = ServiceModuleRepository(db)
overview = repo.get_overview()
return ModuleComplianceOverview(**overview)
@router.get("/modules/{module_id}", response_model=ServiceModuleDetailResponse)
async def get_module(module_id: str, db: Session = Depends(get_db)):
"""Get a specific module with its regulations and risks."""
from ..db.repository import ServiceModuleRepository
repo = ServiceModuleRepository(db)
module = repo.get_with_regulations(module_id)
if not module:
# Try by name
module = repo.get_by_name(module_id)
if module:
module = repo.get_with_regulations(module.id)
if not module:
raise HTTPException(status_code=404, detail=f"Module {module_id} not found")
# Build regulation list
regulations = []
for mapping in (module.regulation_mappings or []):
reg = mapping.regulation
if reg:
regulations.append({
"code": reg.code,
"name": reg.name,
"relevance_level": mapping.relevance_level.value if mapping.relevance_level else "medium",
"notes": mapping.notes,
})
# Build risk list
risks = []
for mr in (module.module_risks or []):
risk = mr.risk
if risk:
risks.append({
"risk_id": risk.risk_id,
"title": risk.title,
"inherent_risk": risk.inherent_risk.value if risk.inherent_risk else None,
"module_risk_level": mr.module_risk_level.value if mr.module_risk_level else None,
})
return ServiceModuleDetailResponse(
id=module.id,
name=module.name,
display_name=module.display_name,
description=module.description,
service_type=module.service_type.value if module.service_type else None,
port=module.port,
technology_stack=module.technology_stack or [],
repository_path=module.repository_path,
docker_image=module.docker_image,
data_categories=module.data_categories or [],
processes_pii=module.processes_pii,
processes_health_data=module.processes_health_data,
ai_components=module.ai_components,
criticality=module.criticality,
owner_team=module.owner_team,
owner_contact=module.owner_contact,
is_active=module.is_active,
compliance_score=module.compliance_score,
last_compliance_check=module.last_compliance_check,
created_at=module.created_at,
updated_at=module.updated_at,
regulation_count=len(regulations),
risk_count=len(risks),
regulations=regulations,
risks=risks,
)
@router.post("/modules/seed", response_model=ModuleSeedResponse)
async def seed_modules(
request: ModuleSeedRequest,
db: Session = Depends(get_db),
):
"""Seed service modules from predefined data."""
from classroom_engine.database import engine
from ..db.models import ServiceModuleDB, ModuleRegulationMappingDB, ModuleRiskDB
from ..db.repository import ServiceModuleRepository
from ..data.service_modules import BREAKPILOT_SERVICES
try:
# Ensure tables exist
ServiceModuleDB.__table__.create(engine, checkfirst=True)
ModuleRegulationMappingDB.__table__.create(engine, checkfirst=True)
ModuleRiskDB.__table__.create(engine, checkfirst=True)
repo = ServiceModuleRepository(db)
result = repo.seed_from_data(BREAKPILOT_SERVICES, force=request.force)
return ModuleSeedResponse(
success=True,
message=f"Seeded {result['modules_created']} modules with {result['mappings_created']} regulation mappings",
modules_created=result["modules_created"],
mappings_created=result["mappings_created"],
)
except Exception as e:
logger.error(f"Module seeding failed: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/modules/{module_id}/activate")
async def activate_module(module_id: str, db: Session = Depends(get_db)):
"""Activate a service module."""
from ..db.repository import ServiceModuleRepository
repo = ServiceModuleRepository(db)
module = repo.get_by_id(module_id)
if not module:
module = repo.get_by_name(module_id)
if not module:
raise HTTPException(status_code=404, detail=f"Module {module_id} not found")
module.is_active = True
db.commit()
return {"status": "activated", "id": module.id, "name": module.name}
@router.post("/modules/{module_id}/deactivate")
async def deactivate_module(module_id: str, db: Session = Depends(get_db)):
"""Deactivate a service module."""
from ..db.repository import ServiceModuleRepository
repo = ServiceModuleRepository(db)
module = repo.get_by_id(module_id)
if not module:
module = repo.get_by_name(module_id)
if not module:
raise HTTPException(status_code=404, detail=f"Module {module_id} not found")
module.is_active = False
db.commit()
return {"status": "deactivated", "id": module.id, "name": module.name}
@router.post("/modules/{module_id}/regulations", response_model=ModuleRegulationMappingResponse)
async def add_module_regulation(
module_id: str,
mapping: ModuleRegulationMappingCreate,
db: Session = Depends(get_db),
):
"""Add a regulation mapping to a module."""
from ..db.repository import ServiceModuleRepository
repo = ServiceModuleRepository(db)
module = repo.get_by_id(module_id)
if not module:
module = repo.get_by_name(module_id)
if not module:
raise HTTPException(status_code=404, detail=f"Module {module_id} not found")
# Verify regulation exists
reg_repo = RegulationRepository(db)
regulation = reg_repo.get_by_id(mapping.regulation_id)
if not regulation:
regulation = reg_repo.get_by_code(mapping.regulation_id)
if not regulation:
raise HTTPException(status_code=404, detail=f"Regulation {mapping.regulation_id} not found")
try:
new_mapping = repo.add_regulation_mapping(
module_id=module.id,
regulation_id=regulation.id,
relevance_level=mapping.relevance_level,
notes=mapping.notes,
applicable_articles=mapping.applicable_articles,
)
return ModuleRegulationMappingResponse(
id=new_mapping.id,
module_id=new_mapping.module_id,
regulation_id=new_mapping.regulation_id,
relevance_level=new_mapping.relevance_level.value if new_mapping.relevance_level else "medium",
notes=new_mapping.notes,
applicable_articles=new_mapping.applicable_articles,
module_name=module.name,
regulation_code=regulation.code,
regulation_name=regulation.name,
created_at=new_mapping.created_at,
)
except Exception as e:
logger.error(f"Failed to add regulation mapping: {e}")
raise HTTPException(status_code=500, detail=str(e))