refactor(backend/api): split schemas.py into per-domain modules (1899 -> 39 LOC shim)
Phase 1 Step 3 of PHASE1_RUNBOOK.md. compliance/api/schemas.py is
decomposed into 16 per-domain Pydantic schema modules under
compliance/schemas/:
common.py ( 79) — 6 API enums + PaginationMeta
regulation.py ( 52)
requirement.py ( 80)
control.py (119) — Control + Mapping
evidence.py ( 66)
risk.py ( 79)
ai_system.py ( 63)
dashboard.py (195) — Dashboard, Export, Executive Dashboard
service_module.py (121)
bsi.py ( 58) — BSI + PDF extraction
audit_session.py (172)
report.py ( 53)
isms_governance.py (343) — Scope, Context, Policy, Objective, SoA
isms_audit.py (431) — Finding, CAPA, Review, Internal Audit, Readiness, Trail, ISO27001
vvt.py (168)
tom.py ( 71)
compliance/api/schemas.py becomes a 39-line re-export shim so existing
imports (from compliance.api.schemas import RegulationResponse) keep
working unchanged. New code should import from the domain module
directly (from compliance.schemas.regulation import RegulationResponse).
Deferred-from-sweep: all 28 class Config blocks in the original file
were converted to model_config = ConfigDict(...) during the split.
schemas.py-sourced PydanticDeprecatedSince20 warnings are now gone.
Cross-domain references handled via targeted imports (e.g. dashboard.py
imports EvidenceResponse from evidence, RiskResponse from risk). common
API enums + PaginationMeta are imported by every domain module.
Verified:
- 173/173 pytest compliance/tests/ tests/contracts/ pass
- OpenAPI 360 paths / 484 operations unchanged (contract test green)
- All new files under the 500-line hard cap (largest: isms_audit.py
at 431, isms_governance.py at 343, dashboard.py at 195)
- No file in compliance/schemas/ or compliance/api/schemas.py
exceeds the hard cap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
172
backend-compliance/compliance/schemas/audit_session.py
Normal file
172
backend-compliance/compliance/schemas/audit_session.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""
|
||||
Audit Session Pydantic schemas — extracted from compliance/api/schemas.py.
|
||||
|
||||
Phase 1 Step 3: the monolithic ``compliance.api.schemas`` module is being
|
||||
split per domain under ``compliance.schemas``. This module is re-exported
|
||||
from ``compliance.api.schemas`` for backwards compatibility.
|
||||
"""
|
||||
|
||||
from datetime import datetime, date
|
||||
from typing import Optional, List, Any, Dict
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from compliance.schemas.common import (
|
||||
PaginationMeta, RegulationType, ControlType, ControlDomain,
|
||||
ControlStatus, RiskLevel, EvidenceStatus,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Audit Session & Sign-off Schemas (Phase 3 - Sprint 3)
|
||||
# ============================================================================
|
||||
|
||||
class AuditResult(str):
|
||||
"""Audit result values for sign-off."""
|
||||
COMPLIANT = "compliant"
|
||||
COMPLIANT_WITH_NOTES = "compliant_notes"
|
||||
NON_COMPLIANT = "non_compliant"
|
||||
NOT_APPLICABLE = "not_applicable"
|
||||
PENDING = "pending"
|
||||
|
||||
|
||||
class AuditSessionStatus(str):
|
||||
"""Audit session status values."""
|
||||
DRAFT = "draft"
|
||||
IN_PROGRESS = "in_progress"
|
||||
COMPLETED = "completed"
|
||||
ARCHIVED = "archived"
|
||||
|
||||
|
||||
class CreateAuditSessionRequest(BaseModel):
|
||||
"""Request to create a new audit session."""
|
||||
name: str = Field(..., min_length=1, max_length=200)
|
||||
description: Optional[str] = None
|
||||
auditor_name: str = Field(..., min_length=1, max_length=100)
|
||||
auditor_email: Optional[str] = None
|
||||
auditor_organization: Optional[str] = None
|
||||
regulation_codes: Optional[List[str]] = None # Filter by regulations
|
||||
|
||||
|
||||
class UpdateAuditSessionRequest(BaseModel):
|
||||
"""Request to update an audit session."""
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=200)
|
||||
description: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
|
||||
|
||||
class AuditSessionSummary(BaseModel):
|
||||
"""Summary of an audit session for list views."""
|
||||
id: str
|
||||
name: str
|
||||
auditor_name: str
|
||||
status: str
|
||||
total_items: int
|
||||
completed_items: int
|
||||
completion_percentage: float
|
||||
created_at: datetime
|
||||
started_at: Optional[datetime] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class AuditSessionResponse(AuditSessionSummary):
|
||||
"""Full response for an audit session."""
|
||||
description: Optional[str] = None
|
||||
auditor_email: Optional[str] = None
|
||||
auditor_organization: Optional[str] = None
|
||||
regulation_ids: Optional[List[str]] = None
|
||||
compliant_count: int = 0
|
||||
non_compliant_count: int = 0
|
||||
updated_at: datetime
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class AuditSessionListResponse(BaseModel):
|
||||
"""List response for audit sessions."""
|
||||
sessions: List[AuditSessionSummary]
|
||||
total: int
|
||||
|
||||
|
||||
class AuditSessionDetailResponse(AuditSessionResponse):
|
||||
"""Detailed response including statistics breakdown."""
|
||||
statistics: Optional["AuditStatistics"] = None
|
||||
|
||||
|
||||
class SignOffRequest(BaseModel):
|
||||
"""Request to sign off a single requirement."""
|
||||
result: str = Field(..., description="Audit result: compliant, compliant_notes, non_compliant, not_applicable, pending")
|
||||
notes: Optional[str] = None
|
||||
sign: bool = Field(False, description="Whether to create digital signature")
|
||||
|
||||
|
||||
class SignOffResponse(BaseModel):
|
||||
"""Response for a sign-off operation."""
|
||||
id: str
|
||||
session_id: str
|
||||
requirement_id: str
|
||||
result: str
|
||||
notes: Optional[str] = None
|
||||
is_signed: bool
|
||||
signature_hash: Optional[str] = None
|
||||
signed_at: Optional[datetime] = None
|
||||
signed_by: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class AuditChecklistItem(BaseModel):
|
||||
"""A single item in the audit checklist."""
|
||||
requirement_id: str
|
||||
regulation_code: str
|
||||
article: str
|
||||
paragraph: Optional[str] = None
|
||||
title: str
|
||||
description: Optional[str] = None
|
||||
|
||||
# Current audit state
|
||||
current_result: str = "pending" # AuditResult
|
||||
notes: Optional[str] = None
|
||||
is_signed: bool = False
|
||||
signed_at: Optional[datetime] = None
|
||||
signed_by: Optional[str] = None
|
||||
|
||||
# Context info
|
||||
evidence_count: int = 0
|
||||
controls_mapped: int = 0
|
||||
implementation_status: Optional[str] = None
|
||||
|
||||
# Priority
|
||||
priority: int = 2
|
||||
|
||||
|
||||
class AuditStatistics(BaseModel):
|
||||
"""Statistics for an audit session."""
|
||||
total: int
|
||||
compliant: int
|
||||
compliant_with_notes: int
|
||||
non_compliant: int
|
||||
not_applicable: int
|
||||
pending: int
|
||||
completion_percentage: float
|
||||
|
||||
|
||||
class AuditChecklistResponse(BaseModel):
|
||||
"""Response for audit checklist endpoint."""
|
||||
session: AuditSessionSummary
|
||||
items: List[AuditChecklistItem]
|
||||
pagination: PaginationMeta
|
||||
statistics: AuditStatistics
|
||||
|
||||
|
||||
class AuditChecklistFilterRequest(BaseModel):
|
||||
"""Filter options for audit checklist."""
|
||||
regulation_code: Optional[str] = None
|
||||
result_filter: Optional[str] = None # "pending", "compliant", "non_compliant", etc.
|
||||
search: Optional[str] = None
|
||||
signed_only: bool = False
|
||||
|
||||
Reference in New Issue
Block a user