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