Files
breakpilot-compliance/backend-compliance/compliance/schemas/incident.py
Sharang Parnerkar cc1c61947d refactor(backend/api): extract Incident services (Step 4 — file 11 of 18)
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>
2026-04-09 08:35:57 +02:00

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",
]