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>
432 lines
13 KiB
Python
432 lines
13 KiB
Python
"""
|
|
ISMS Audit Execution (Findings, CAPA, Reviews, Internal Audit, Readiness) 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,
|
|
)
|
|
|
|
|
|
class AuditFindingBase(BaseModel):
|
|
"""Base schema for Audit Finding."""
|
|
finding_type: str # "major", "minor", "ofi", "positive"
|
|
iso_chapter: Optional[str] = None
|
|
annex_a_control: Optional[str] = None
|
|
title: str
|
|
description: str
|
|
objective_evidence: str
|
|
impact_description: Optional[str] = None
|
|
affected_processes: Optional[List[str]] = None
|
|
affected_assets: Optional[List[str]] = None
|
|
owner: Optional[str] = None
|
|
due_date: Optional[date] = None
|
|
|
|
|
|
class AuditFindingCreate(AuditFindingBase):
|
|
"""Schema for creating Audit Finding."""
|
|
audit_session_id: Optional[str] = None
|
|
internal_audit_id: Optional[str] = None
|
|
auditor: str
|
|
|
|
|
|
class AuditFindingUpdate(BaseModel):
|
|
"""Schema for updating Audit Finding."""
|
|
title: Optional[str] = None
|
|
description: Optional[str] = None
|
|
root_cause: Optional[str] = None
|
|
root_cause_method: Optional[str] = None
|
|
owner: Optional[str] = None
|
|
due_date: Optional[date] = None
|
|
status: Optional[str] = None
|
|
|
|
|
|
class AuditFindingResponse(AuditFindingBase):
|
|
"""Response schema for Audit Finding."""
|
|
id: str
|
|
finding_id: str
|
|
audit_session_id: Optional[str] = None
|
|
internal_audit_id: Optional[str] = None
|
|
root_cause: Optional[str] = None
|
|
root_cause_method: Optional[str] = None
|
|
status: str
|
|
auditor: Optional[str] = None
|
|
identified_date: date
|
|
closed_date: Optional[date] = None
|
|
verification_method: Optional[str] = None
|
|
verified_by: Optional[str] = None
|
|
verified_at: Optional[datetime] = None
|
|
closure_notes: Optional[str] = None
|
|
closed_by: Optional[str] = None
|
|
is_blocking: bool
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class AuditFindingListResponse(BaseModel):
|
|
"""List response for Audit Findings."""
|
|
findings: List[AuditFindingResponse]
|
|
total: int
|
|
major_count: int
|
|
minor_count: int
|
|
ofi_count: int
|
|
open_count: int
|
|
|
|
|
|
class AuditFindingCloseRequest(BaseModel):
|
|
"""Request to close an Audit Finding."""
|
|
closure_notes: str
|
|
closed_by: str
|
|
verification_method: str
|
|
verification_evidence: str
|
|
|
|
|
|
# --- Corrective Actions (CAPA) ---
|
|
|
|
class CorrectiveActionBase(BaseModel):
|
|
"""Base schema for Corrective Action."""
|
|
capa_type: str # "corrective", "preventive", "both"
|
|
title: str
|
|
description: str
|
|
expected_outcome: Optional[str] = None
|
|
assigned_to: str
|
|
planned_completion: date
|
|
effectiveness_criteria: Optional[str] = None
|
|
estimated_effort_hours: Optional[int] = None
|
|
resources_required: Optional[str] = None
|
|
|
|
|
|
class CorrectiveActionCreate(CorrectiveActionBase):
|
|
"""Schema for creating Corrective Action."""
|
|
finding_id: str
|
|
planned_start: Optional[date] = None
|
|
|
|
|
|
class CorrectiveActionUpdate(BaseModel):
|
|
"""Schema for updating Corrective Action."""
|
|
title: Optional[str] = None
|
|
description: Optional[str] = None
|
|
assigned_to: Optional[str] = None
|
|
planned_completion: Optional[date] = None
|
|
status: Optional[str] = None
|
|
progress_percentage: Optional[int] = None
|
|
implementation_evidence: Optional[str] = None
|
|
|
|
|
|
class CorrectiveActionResponse(CorrectiveActionBase):
|
|
"""Response schema for Corrective Action."""
|
|
id: str
|
|
capa_id: str
|
|
finding_id: str
|
|
planned_start: Optional[date] = None
|
|
actual_completion: Optional[date] = None
|
|
status: str
|
|
progress_percentage: int
|
|
approved_by: Optional[str] = None
|
|
actual_effort_hours: Optional[int] = None
|
|
implementation_evidence: Optional[str] = None
|
|
evidence_ids: Optional[List[str]] = None
|
|
effectiveness_verified: bool
|
|
effectiveness_verification_date: Optional[date] = None
|
|
effectiveness_notes: Optional[str] = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class CorrectiveActionListResponse(BaseModel):
|
|
"""List response for Corrective Actions."""
|
|
actions: List[CorrectiveActionResponse]
|
|
total: int
|
|
|
|
|
|
class CAPAVerifyRequest(BaseModel):
|
|
"""Request to verify CAPA effectiveness."""
|
|
verified_by: str
|
|
effectiveness_notes: str
|
|
is_effective: bool
|
|
|
|
|
|
# --- Management Review (ISO 27001 9.3) ---
|
|
|
|
class ReviewAttendee(BaseModel):
|
|
"""Single attendee in management review."""
|
|
name: str
|
|
role: str
|
|
|
|
|
|
class ReviewActionItem(BaseModel):
|
|
"""Single action item from management review."""
|
|
action: str
|
|
owner: str
|
|
due_date: date
|
|
|
|
|
|
class ManagementReviewBase(BaseModel):
|
|
"""Base schema for Management Review."""
|
|
title: str
|
|
review_date: date
|
|
review_period_start: Optional[date] = None
|
|
review_period_end: Optional[date] = None
|
|
chairperson: str
|
|
attendees: Optional[List[ReviewAttendee]] = None
|
|
|
|
|
|
class ManagementReviewCreate(ManagementReviewBase):
|
|
"""Schema for creating Management Review."""
|
|
pass
|
|
|
|
|
|
class ManagementReviewUpdate(BaseModel):
|
|
"""Schema for updating Management Review."""
|
|
# Inputs (9.3)
|
|
input_previous_actions: Optional[str] = None
|
|
input_isms_changes: Optional[str] = None
|
|
input_security_performance: Optional[str] = None
|
|
input_interested_party_feedback: Optional[str] = None
|
|
input_risk_assessment_results: Optional[str] = None
|
|
input_improvement_opportunities: Optional[str] = None
|
|
input_policy_effectiveness: Optional[str] = None
|
|
input_objective_achievement: Optional[str] = None
|
|
input_resource_adequacy: Optional[str] = None
|
|
# Outputs (9.3)
|
|
output_improvement_decisions: Optional[str] = None
|
|
output_isms_changes: Optional[str] = None
|
|
output_resource_needs: Optional[str] = None
|
|
# Action items
|
|
action_items: Optional[List[ReviewActionItem]] = None
|
|
# Assessment
|
|
isms_effectiveness_rating: Optional[str] = None
|
|
key_decisions: Optional[str] = None
|
|
status: Optional[str] = None
|
|
|
|
|
|
class ManagementReviewResponse(ManagementReviewBase):
|
|
"""Response schema for Management Review."""
|
|
id: str
|
|
review_id: str
|
|
input_previous_actions: Optional[str] = None
|
|
input_isms_changes: Optional[str] = None
|
|
input_security_performance: Optional[str] = None
|
|
input_interested_party_feedback: Optional[str] = None
|
|
input_risk_assessment_results: Optional[str] = None
|
|
input_improvement_opportunities: Optional[str] = None
|
|
input_policy_effectiveness: Optional[str] = None
|
|
input_objective_achievement: Optional[str] = None
|
|
input_resource_adequacy: Optional[str] = None
|
|
output_improvement_decisions: Optional[str] = None
|
|
output_isms_changes: Optional[str] = None
|
|
output_resource_needs: Optional[str] = None
|
|
action_items: Optional[List[ReviewActionItem]] = None
|
|
isms_effectiveness_rating: Optional[str] = None
|
|
key_decisions: Optional[str] = None
|
|
status: str
|
|
approved_by: Optional[str] = None
|
|
approved_at: Optional[datetime] = None
|
|
minutes_document_path: Optional[str] = None
|
|
next_review_date: Optional[date] = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class ManagementReviewListResponse(BaseModel):
|
|
"""List response for Management Reviews."""
|
|
reviews: List[ManagementReviewResponse]
|
|
total: int
|
|
|
|
|
|
class ManagementReviewApproveRequest(BaseModel):
|
|
"""Request to approve Management Review."""
|
|
approved_by: str
|
|
next_review_date: date
|
|
minutes_document_path: Optional[str] = None
|
|
|
|
|
|
# --- Internal Audit (ISO 27001 9.2) ---
|
|
|
|
class InternalAuditBase(BaseModel):
|
|
"""Base schema for Internal Audit."""
|
|
title: str
|
|
audit_type: str # "scheduled", "surveillance", "special"
|
|
scope_description: str
|
|
iso_chapters_covered: Optional[List[str]] = None
|
|
annex_a_controls_covered: Optional[List[str]] = None
|
|
processes_covered: Optional[List[str]] = None
|
|
departments_covered: Optional[List[str]] = None
|
|
criteria: Optional[str] = None
|
|
planned_date: date
|
|
lead_auditor: str
|
|
audit_team: Optional[List[str]] = None
|
|
|
|
|
|
class InternalAuditCreate(InternalAuditBase):
|
|
"""Schema for creating Internal Audit."""
|
|
pass
|
|
|
|
|
|
class InternalAuditUpdate(BaseModel):
|
|
"""Schema for updating Internal Audit."""
|
|
title: Optional[str] = None
|
|
scope_description: Optional[str] = None
|
|
actual_start_date: Optional[date] = None
|
|
actual_end_date: Optional[date] = None
|
|
auditee_representatives: Optional[List[str]] = None
|
|
status: Optional[str] = None
|
|
audit_conclusion: Optional[str] = None
|
|
overall_assessment: Optional[str] = None
|
|
|
|
|
|
class InternalAuditResponse(InternalAuditBase):
|
|
"""Response schema for Internal Audit."""
|
|
id: str
|
|
audit_id: str
|
|
actual_start_date: Optional[date] = None
|
|
actual_end_date: Optional[date] = None
|
|
auditee_representatives: Optional[List[str]] = None
|
|
status: str
|
|
total_findings: int
|
|
major_findings: int
|
|
minor_findings: int
|
|
ofi_count: int
|
|
positive_observations: int
|
|
audit_conclusion: Optional[str] = None
|
|
overall_assessment: Optional[str] = None
|
|
report_date: Optional[date] = None
|
|
report_document_path: Optional[str] = None
|
|
report_approved_by: Optional[str] = None
|
|
report_approved_at: Optional[datetime] = None
|
|
follow_up_audit_required: bool
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class InternalAuditListResponse(BaseModel):
|
|
"""List response for Internal Audits."""
|
|
audits: List[InternalAuditResponse]
|
|
total: int
|
|
|
|
|
|
class InternalAuditCompleteRequest(BaseModel):
|
|
"""Request to complete Internal Audit."""
|
|
audit_conclusion: str
|
|
overall_assessment: str # "conforming", "minor_nc", "major_nc"
|
|
follow_up_audit_required: bool
|
|
|
|
|
|
# --- ISMS Readiness Check ---
|
|
|
|
class PotentialFinding(BaseModel):
|
|
"""Potential finding from readiness check."""
|
|
check: str
|
|
status: str # "pass", "fail", "warning"
|
|
recommendation: str
|
|
iso_reference: Optional[str] = None
|
|
|
|
|
|
class ISMSReadinessCheckResponse(BaseModel):
|
|
"""Response for ISMS Readiness Check."""
|
|
id: str
|
|
check_date: datetime
|
|
triggered_by: Optional[str] = None
|
|
overall_status: str # "ready", "at_risk", "not_ready"
|
|
certification_possible: bool
|
|
# Chapter statuses
|
|
chapter_4_status: Optional[str] = None
|
|
chapter_5_status: Optional[str] = None
|
|
chapter_6_status: Optional[str] = None
|
|
chapter_7_status: Optional[str] = None
|
|
chapter_8_status: Optional[str] = None
|
|
chapter_9_status: Optional[str] = None
|
|
chapter_10_status: Optional[str] = None
|
|
# Findings
|
|
potential_majors: List[PotentialFinding]
|
|
potential_minors: List[PotentialFinding]
|
|
improvement_opportunities: List[PotentialFinding]
|
|
# Scores
|
|
readiness_score: float
|
|
documentation_score: Optional[float] = None
|
|
implementation_score: Optional[float] = None
|
|
evidence_score: Optional[float] = None
|
|
# Priority actions
|
|
priority_actions: List[str]
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class ISMSReadinessCheckRequest(BaseModel):
|
|
"""Request to run ISMS Readiness Check."""
|
|
triggered_by: str = "manual"
|
|
|
|
|
|
# --- Audit Trail ---
|
|
|
|
class AuditTrailEntry(BaseModel):
|
|
"""Single audit trail entry."""
|
|
id: str
|
|
entity_type: str
|
|
entity_id: str
|
|
entity_name: Optional[str] = None
|
|
action: str
|
|
field_changed: Optional[str] = None
|
|
old_value: Optional[str] = None
|
|
new_value: Optional[str] = None
|
|
change_summary: Optional[str] = None
|
|
performed_by: str
|
|
performed_at: datetime
|
|
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class AuditTrailResponse(BaseModel):
|
|
"""Response for Audit Trail query."""
|
|
entries: List[AuditTrailEntry]
|
|
total: int
|
|
pagination: PaginationMeta
|
|
|
|
|
|
# --- ISO 27001 Chapter Status Overview ---
|
|
|
|
class ISO27001ChapterStatus(BaseModel):
|
|
"""Status of a single ISO 27001 chapter."""
|
|
chapter: str
|
|
title: str
|
|
status: str # "compliant", "partial", "non_compliant", "not_started"
|
|
completion_percentage: float
|
|
open_findings: int
|
|
key_documents: List[str]
|
|
last_reviewed: Optional[datetime] = None
|
|
|
|
|
|
class ISO27001OverviewResponse(BaseModel):
|
|
"""Complete ISO 27001 status overview."""
|
|
overall_status: str # "ready", "at_risk", "not_ready", "not_started"
|
|
certification_readiness: float # 0-100
|
|
chapters: List[ISO27001ChapterStatus]
|
|
scope_approved: bool
|
|
soa_approved: bool
|
|
last_management_review: Optional[datetime] = None
|
|
last_internal_audit: Optional[datetime] = None
|
|
open_major_findings: int
|
|
open_minor_findings: int
|
|
policies_count: int
|
|
policies_approved: int
|
|
objectives_count: int
|
|
objectives_achieved: int
|
|
|