Files
breakpilot-compliance/backend-compliance/compliance/schemas/isms_audit.py
Sharang Parnerkar d9dcfb97ef 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>
2026-04-07 18:06:27 +02:00

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