compliance/api/incident_routes.py (916 LOC) -> 280 LOC thin routes +
two services + 95-line schemas file.
Two-service split for DSGVO Art. 33/34 Datenpannen-Management:
incident_service.py (460 LOC):
- CRUD (create, list, get, update, delete)
- Stats, status update, timeline append, close
- Module-level helpers: _calculate_risk_level, _is_notification_required,
_calculate_72h_deadline, _incident_to_response, _measure_to_response,
_parse_jsonb, _append_timeline, DEFAULT_TENANT_ID
incident_workflow_service.py (329 LOC):
- Risk assessment (likelihood x impact -> risk_level)
- Art. 33 authority notification (with 72h deadline tracking)
- Art. 34 data subject notification
- Corrective measures CRUD
Both services use raw SQL via sqlalchemy.text() — no ORM models for
incident_incidents / incident_measures tables. Migrated from the Go
ai-compliance-sdk; Python backend is Source of Truth.
Legacy test compat: tests/test_incident_routes.py imports
_calculate_risk_level, _is_notification_required, _calculate_72h_deadline,
_incident_to_response, _measure_to_response, _parse_jsonb,
DEFAULT_TENANT_ID directly from compliance.api.incident_routes — all
re-exported via __all__.
Verified:
- 223/223 pytest pass (173 core + 50 incident)
- OpenAPI 360/484 unchanged
- mypy compliance/ -> Success on 141 source files
- incident_routes.py 916 -> 280 LOC
- Hard-cap violations: 8 -> 7
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
96 lines
2.3 KiB
Python
96 lines
2.3 KiB
Python
"""
|
|
Incident / Datenpannen schemas (DSGVO Art. 33/34).
|
|
|
|
Phase 1 Step 4: extracted from ``compliance.api.incident_routes``.
|
|
"""
|
|
|
|
from typing import List, Optional
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
class IncidentCreate(BaseModel):
|
|
title: str
|
|
description: Optional[str] = None
|
|
category: Optional[str] = "data_breach"
|
|
severity: Optional[str] = "medium"
|
|
detected_at: Optional[str] = None
|
|
affected_data_categories: Optional[List[str]] = None
|
|
affected_data_subject_count: Optional[int] = 0
|
|
affected_systems: Optional[List[str]] = None
|
|
|
|
|
|
class IncidentUpdate(BaseModel):
|
|
title: Optional[str] = None
|
|
description: Optional[str] = None
|
|
category: Optional[str] = None
|
|
status: Optional[str] = None
|
|
severity: Optional[str] = None
|
|
affected_data_categories: Optional[List[str]] = None
|
|
affected_data_subject_count: Optional[int] = None
|
|
affected_systems: Optional[List[str]] = None
|
|
|
|
|
|
class StatusUpdate(BaseModel):
|
|
status: str
|
|
|
|
|
|
class RiskAssessmentRequest(BaseModel):
|
|
likelihood: int
|
|
impact: int
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class AuthorityNotificationRequest(BaseModel):
|
|
authority_name: str
|
|
reference_number: Optional[str] = None
|
|
contact_person: Optional[str] = None
|
|
notes: Optional[str] = None
|
|
|
|
|
|
class DataSubjectNotificationRequest(BaseModel):
|
|
notification_text: str
|
|
channel: str = "email"
|
|
affected_count: Optional[int] = 0
|
|
|
|
|
|
class MeasureCreate(BaseModel):
|
|
title: str
|
|
description: Optional[str] = None
|
|
measure_type: str = "corrective"
|
|
responsible: Optional[str] = None
|
|
due_date: Optional[str] = None
|
|
|
|
|
|
class MeasureUpdate(BaseModel):
|
|
title: Optional[str] = None
|
|
description: Optional[str] = None
|
|
measure_type: Optional[str] = None
|
|
status: Optional[str] = None
|
|
responsible: Optional[str] = None
|
|
due_date: Optional[str] = None
|
|
|
|
|
|
class TimelineEntryRequest(BaseModel):
|
|
action: str
|
|
details: Optional[str] = None
|
|
|
|
|
|
class CloseIncidentRequest(BaseModel):
|
|
root_cause: str
|
|
lessons_learned: Optional[str] = None
|
|
|
|
|
|
__all__ = [
|
|
"IncidentCreate",
|
|
"IncidentUpdate",
|
|
"StatusUpdate",
|
|
"RiskAssessmentRequest",
|
|
"AuthorityNotificationRequest",
|
|
"DataSubjectNotificationRequest",
|
|
"MeasureCreate",
|
|
"MeasureUpdate",
|
|
"TimelineEntryRequest",
|
|
"CloseIncidentRequest",
|
|
]
|