Skip to main content

ADR-0062: Typed SubjectEntity + PurposeProfile models

Status: Accepted Date: 2026-06-16 Supersedes: none Superseded by: none Deciders: Adrian (Soft4U), Claude Opus 4.8

Decision context:

  • Latency: none — pure in-memory Pydantic models + a rule helper.
  • Dependency surface: no new packages. Adds two model modules + a purpose_requirements helper.
  • Debuggability: typed fields + computed signals (address_divergence, sdd_eligible) are explicit and unit-tested.
  • Reversibility: branch revert; additive (no existing model changed).
  • Blast radius: additive; the existing CompanyProfile fact-bag and JSONB layers are untouched.
  • Alternative considered: keep the untyped fact-bag (rejected — the gaps are structural, not config).

Context

  • The onboarded business (AMLR §1 SubjectEntity) was an untyped CompanyProfile.facts bag + scattered Case columns, with no first-class register_name, no principal_place_of_business distinct from registered_address (their divergence is a risk signal), and no is_regulated_entity / listed_on_regulated_market flags (which drive SDD eligibility / UBO exemptions).
  • Purpose & intended nature (AMLR Art. 20(1)(c), §5 PurposeProfile) were assembled ad-hoc from loose additional_data JSONB at memo time; stated_purpose was free text, not a monitorable enum baseline.

Decision

Add two typed Pydantic models + small rule helpers:

  • SubjectEntity (subject_entity.py): first-class register_name, registered_address vs principal_place_of_business, is_regulated_entity, listed_on_regulated_market; computed address_divergence (risk signal) and sdd_eligible.
  • PurposeProfile (purpose_profile.py): StatedPurpose enum, ExpectedActivity (volumes/values/ geos/counterparties), source_of_funds, source_of_wealth; missing_purpose_requirements(profile, risk_tier) enforces SoF at standard/high and SoW at high (configurable).

Consequences

Positive

  • SubjectEntity + PurposeProfile are first-class typed models; address divergence and SDD eligibility are computed signals; stated_purpose is an enum baseline for transaction monitoring; SoF/SoW gaps are tier-aware.

Negative

  • Two more models alongside the existing fact-bag/JSONB — intentional (typed > untyped for AMLR §1/§5); populating them from the existing layers + risk-engine consumption of address_divergence are config follow-ups.

Neutral

  • No persistence/migration; field-level requirement stays configurable (draft RTS Art. 28(1)).

Alternatives Considered

Alternative 1: Keep the untyped CompanyProfile fact-bag

Rejected — the issues are explicit that these are structural (typed) gaps, not config. An enum stated_purpose and a first-class principal_place_of_business cannot be expressed as loose facts while serving as a monitoring baseline / risk signal.