Files
govoplan-risk-compliance/src/govoplan_risk_compliance/backend/schemas.py
T

380 lines
9.4 KiB
Python

from __future__ import annotations
from datetime import date, datetime
from typing import Any, Literal
from pydantic import BaseModel, Field, model_validator
class ConnectorSnapshotResponse(BaseModel):
ref: str
provider_id: str
publisher: str
jurisdiction: str
list_type: str
source_id: str
source_version: str
publication_at: datetime | None
effective_at: datetime | None
acquired_at: datetime
byte_count: int
sha256: str
parser_version: str
raw_evidence_ref: str
connector_run_id: str
licence_notes: str | None = None
trust_notes: str | None = None
class ConnectorSnapshotListResponse(BaseModel):
available: bool
snapshots: list[ConnectorSnapshotResponse]
class ListSnapshotImportRequest(BaseModel):
connector_snapshot_ref: str = Field(
min_length=1,
max_length=300,
)
class ListSnapshotResponse(BaseModel):
id: str
visibility: str
provider_id: str
publisher: str
jurisdiction: str
list_type: str
source_id: str
source_version: str
publication_at: datetime | None
effective_at: datetime | None
acquired_at: datetime
sha256: str
connector_run_id: str
raw_evidence_ref: str
source_parser_version: str
normalization_version: str
signature_evidence: dict[str, Any]
provenance: dict[str, Any]
entry_count: int
status: str
imported_by: str | None
imported_at: datetime
class ListSnapshotImportResponse(BaseModel):
snapshot: ListSnapshotResponse
created: bool
class ListSnapshotListResponse(BaseModel):
snapshots: list[ListSnapshotResponse]
class ScreeningIdentifierInput(BaseModel):
type: str = Field(default="document", max_length=100)
value: str = Field(min_length=1, max_length=1000)
class ScreeningAddressInput(BaseModel):
street: str | None = Field(default=None, max_length=1000)
city: str | None = Field(default=None, max_length=500)
region: str | None = Field(default=None, max_length=500)
postal_code: str | None = Field(default=None, max_length=100)
country: str | None = Field(default=None, max_length=255)
class ScreeningSubjectInput(BaseModel):
subject_type: Literal["person", "entity"]
subject_ref: str | None = Field(default=None, max_length=500)
primary_name: str | None = Field(default=None, max_length=1000)
aliases: list[str] = Field(default_factory=list, max_length=100)
identifiers: list[ScreeningIdentifierInput] = Field(
default_factory=list,
max_length=100,
)
dates: list[str] = Field(default_factory=list, max_length=100)
addresses: list[ScreeningAddressInput] = Field(
default_factory=list,
max_length=100,
)
@model_validator(mode="after")
def validate_lookup_fields(self) -> "ScreeningSubjectInput":
if not str(self.primary_name or "").strip() and not self.identifiers:
raise ValueError("A name or identifier is required.")
return self
class ScreeningPolicyInput(BaseModel):
fuzzy_threshold: float = Field(default=0.88, ge=0.8, le=1)
max_snapshot_age_days: int = Field(default=7, ge=1, le=365)
max_candidates: int = Field(default=100, ge=1, le=100)
class ScreeningRunCreateRequest(BaseModel):
list_snapshot_id: str = Field(min_length=1, max_length=36)
idempotency_key: str = Field(min_length=1, max_length=255)
subject: ScreeningSubjectInput
policy: ScreeningPolicyInput = Field(
default_factory=ScreeningPolicyInput
)
class ScreeningFreshnessCheckRequest(BaseModel):
current_subject: ScreeningSubjectInput | None = None
expected_list_snapshot_id: str | None = Field(
default=None,
max_length=36,
)
policy: ScreeningPolicyInput | None = None
failure_policy: Literal[
"block",
"review",
"degraded",
] = "block"
class SubjectSnapshotResponse(BaseModel):
id: str
subject_ref: str | None
subject_type: str
primary_name: str | None
normalized_name: str | None
aliases: list[str]
identifiers: list[dict[str, str]]
dates: list[str]
addresses: list[dict[str, str]]
fingerprint: str
submitted_by: str | None
class SanctionsAliasResponse(BaseModel):
name: str
normalized_name: str
quality: str | None
alias_type: str
class SanctionsIdentifierResponse(BaseModel):
identifier_type: str
value: str
issuing_country: str | None
note: str | None
class SanctionsDateResponse(BaseModel):
date_type: str
value: str
precision: str
class SanctionsAddressResponse(BaseModel):
street: str | None
city: str | None
region: str | None
postal_code: str | None
country: str | None
note: str | None
class SanctionsEntryResponse(BaseModel):
id: str
source_entry_id: str
subject_type: str
primary_name: str
normalized_name: str
original_script_name: str | None
reference_number: str | None
listed_on: date | None
programmes: list[str]
measures: list[str]
raw_evidence_locator: str
details: dict[str, Any]
aliases: list[SanctionsAliasResponse]
identifiers: list[SanctionsIdentifierResponse]
dates: list[SanctionsDateResponse]
addresses: list[SanctionsAddressResponse]
class CandidateResponse(BaseModel):
id: str
score: int
match_kind: str
evidence: list[dict[str, Any]]
review_status: str
current_disposition_id: str | None
entry: SanctionsEntryResponse
class ScreeningRunSummaryResponse(BaseModel):
id: str
evidence_ref: str
subject_name: str | None
subject_type: str
list_snapshot_id: str
list_source_version: str
matcher_version: str
normalization_version: str
policy_version: str
status: str
outcome: str
candidate_count: int
started_at: datetime
completed_at: datetime | None
created_at: datetime
class ScreeningRunResponse(BaseModel):
id: str
evidence_ref: str
idempotency_key: str
request_hash: str
matcher_version: str
normalization_version: str
policy_version: str
policy_snapshot: dict[str, Any]
status: str
outcome: str
candidate_count: int
started_at: datetime
completed_at: datetime | None
created_by: str | None
created_at: datetime
subject: SubjectSnapshotResponse
list_snapshot: ListSnapshotResponse
candidates: list[CandidateResponse]
class ScreeningRunCreateResponse(BaseModel):
run: ScreeningRunResponse
created: bool
class ScreeningRunListResponse(BaseModel):
runs: list[ScreeningRunSummaryResponse]
class ScreeningFreshnessResponse(BaseModel):
evidence_ref: str
run: ScreeningRunSummaryResponse
fresh: bool
reasons: list[str]
checked_at: datetime
current_list_snapshot_id: str | None
gate_decision: Literal[
"allow",
"block",
"review",
"degraded",
]
gate_reasons: list[str]
class RescreeningRequirementListResponse(BaseModel):
requirements: list[ScreeningFreshnessResponse]
class ReviewQueueItemResponse(BaseModel):
id: str
run_id: str
score: int
match_kind: str
review_status: str
subject_name: str | None
subject_type: str
entry_name: str
source_entry_id: str
list_source_version: str
created_at: datetime
class ReviewQueueResponse(BaseModel):
candidates: list[ReviewQueueItemResponse]
class CandidateDetailResponse(BaseModel):
candidate: CandidateResponse
run: ScreeningRunSummaryResponse
subject: SubjectSnapshotResponse
list_snapshot: ListSnapshotResponse
class DispositionCreateRequest(BaseModel):
decision: Literal[
"true_match",
"false_positive",
"needs_information",
"escalated",
]
reason: str = Field(min_length=3, max_length=10_000)
evidence_refs: list[str] = Field(
default_factory=list,
max_length=100,
)
exception_scope: Literal[
"candidate",
"subject_entry",
] = "candidate"
expires_at: datetime | None = None
review_at: datetime | None = None
override_reason: str | None = Field(
default=None,
max_length=10_000,
)
class DispositionResponse(BaseModel):
id: str
candidate_id: str
decision: str
reason: str
evidence_refs: list[str]
scope: str
expires_at: datetime | None
review_at: datetime | None
actor_account_id: str | None
actor_membership_id: str | None
actor_authority: dict[str, Any]
separation_status: str
override_reason: str | None
supersedes_id: str | None
created_at: datetime
class DispositionCreateResponse(BaseModel):
candidate: CandidateResponse
disposition: DispositionResponse
class DispositionListResponse(BaseModel):
dispositions: list[DispositionResponse]
__all__ = [
"CandidateDetailResponse",
"CandidateResponse",
"ConnectorSnapshotListResponse",
"ConnectorSnapshotResponse",
"DispositionCreateRequest",
"DispositionCreateResponse",
"DispositionListResponse",
"DispositionResponse",
"ListSnapshotImportRequest",
"ListSnapshotImportResponse",
"ListSnapshotListResponse",
"ListSnapshotResponse",
"ReviewQueueItemResponse",
"ReviewQueueResponse",
"RescreeningRequirementListResponse",
"ScreeningFreshnessCheckRequest",
"ScreeningFreshnessResponse",
"ScreeningRunCreateRequest",
"ScreeningRunCreateResponse",
"ScreeningRunListResponse",
"ScreeningRunResponse",
"ScreeningRunSummaryResponse",
"SubjectSnapshotResponse",
]