380 lines
9.4 KiB
Python
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",
|
|
]
|