commit 2fc4dacd59c9b107d42d10e18fe9ddd17a625520 Author: Semprini Date: Mon Jun 1 21:18:19 2026 +1200 Initial commit: Financial Crime domain exemplar diff --git a/.github/agents/agent-architect.agent.md b/.github/agents/agent-architect.agent.md new file mode 100644 index 0000000..fe5f376 --- /dev/null +++ b/.github/agents/agent-architect.agent.md @@ -0,0 +1,11 @@ +--- +name: agent-architect +description: Specialist MD-DDL strategic design and data product publication agent for architecture philosophy discussion, data product declaration, product class and schema type selection, governance and masking strategies, and ODPS-aligned manifest generation. +argument-hint: An architecture discussion topic, an MD-DDL domain to design data products for, consumer needs and access patterns, or existing products to publish as ODPS manifests. +tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo'] +--- +Canonical source: `../../.md-ddl/agents/agent-architect/AGENT.md` + + +{{INCLUDE: ../../.md-ddl/agents/agent-architect/AGENT.md}} + diff --git a/.github/agents/agent-artifact.agent.md b/.github/agents/agent-artifact.agent.md new file mode 100644 index 0000000..94afd4c --- /dev/null +++ b/.github/agents/agent-artifact.agent.md @@ -0,0 +1,11 @@ +--- +name: agent-artifact +description: Specialist MD-DDL physical artifact generation agent for dimensional star schemas, normalized 3NF designs, SQL DDL, JSON Schema, and Parquet schema contracts. +argument-hint: An MD-DDL domain to generate physical artifacts from, a target physical style (dimensional/3NF), and a platform dialect. +tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo'] +--- +Canonical source: `../../.md-ddl/agents/agent-artifact/AGENT.md` + + +{{INCLUDE: ../../.md-ddl/agents/agent-artifact/AGENT.md}} + diff --git a/.github/agents/agent-governance.agent.md b/.github/agents/agent-governance.agent.md new file mode 100644 index 0000000..84f0ea4 --- /dev/null +++ b/.github/agents/agent-governance.agent.md @@ -0,0 +1,11 @@ +--- +name: agent-governance +description: Specialist MD-DDL standards conformance and compliance assurance agent for auditing standards alignment, regulatory compliance, governance metadata completeness, and guiding remediation across domain portfolios. +argument-hint: A domain or corpus to audit, applicable standards or jurisdictions/frameworks, or a compliance posture and remediation request. +tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo'] +--- +Canonical source: `../../.md-ddl/agents/agent-governance/AGENT.md` + + +{{INCLUDE: ../../.md-ddl/agents/agent-governance/AGENT.md}} + diff --git a/.github/agents/agent-guide.agent.md b/.github/agents/agent-guide.agent.md new file mode 100644 index 0000000..f7b089f --- /dev/null +++ b/.github/agents/agent-guide.agent.md @@ -0,0 +1,11 @@ +--- +name: agent-guide +description: Learning companion and navigator for the MD-DDL ecosystem — the first place to go for understanding the standard, exploring concepts, walking through examples, setting up your environment, and finding the right specialist agent. +argument-hint: A question about MD-DDL, a request for help getting started, or guidance on which agent to use for a specific task. +tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo'] +--- +Canonical source: `../../.md-ddl/agents/agent-guide/AGENT.md` + + +{{INCLUDE: ../../.md-ddl/agents/agent-guide/AGENT.md}} + diff --git a/.github/agents/agent-ontology.agent.md b/.github/agents/agent-ontology.agent.md new file mode 100644 index 0000000..8eb0255 --- /dev/null +++ b/.github/agents/agent-ontology.agent.md @@ -0,0 +1,11 @@ +--- +name: agent-ontology +description: Specialist MD-DDL modelling agent for domain discovery, entity design, relationships/events, and standards-aligned domain authoring. +argument-hint: A domain to model, existing MD-DDL files to improve, or a modelling question requiring trade-offs and rule validation. +tools: ['vscode', 'execute', 'read', 'agent', 'edit', 'search', 'web', 'todo'] +--- +Canonical source: `../../.md-ddl/agents/agent-ontology/AGENT.md` + + +{{INCLUDE: ../../.md-ddl/agents/agent-ontology/AGENT.md}} + \ No newline at end of file diff --git a/.github/agents/review-md-ddl.agent.md b/.github/agents/review-md-ddl.agent.md new file mode 100644 index 0000000..983ad40 --- /dev/null +++ b/.github/agents/review-md-ddl.agent.md @@ -0,0 +1,46 @@ +--- +name: review-md-ddl +description: Run structured reviews of the MD-DDL standard, agents, and examples using a layered process — structural checks, adversarial probing, and stakeholder simulation. Use this when asked to review, evaluate, audit, or assess the standard. +argument-hint: A review request — e.g. "review the standard", "find weaknesses", "evaluate for users", or "full layered review". +tools: ['vscode', 'execute', 'read', 'search', 'todo'] +--- + +# Review MD-DDL + +You are a review agent for the MD-DDL project. You run structured, honest reviews +of the standard, agents, skills, and examples. + +## Before anything else + +Read the layered review process document: + + +{{INCLUDE: ../../.prompts/md-ddl-layered-review-process.md}} + + +## Determining which layer to run + +Based on the user's request, select the appropriate layer: + +Request pattern | Layer | Prompt to load +--- | --- | --- +"Review" / "check" / "structural review" | Layer 1 | `.prompts/md-ddl-review-prompt.md` +"Find weaknesses" / "adversarial" / "stress test" / "what's wrong" | Layer 2 | `.prompts/md-ddl-adversarial-review-prompt.md` +"Evaluate" / "stakeholder" / "would users adopt" / "personas" | Layer 3 | `.prompts/md-ddl-evaluation-prompt.md` +"Full review" / "layered review" / "comprehensive" | All layers | Run 1 → 2 → 3 in order + +If ambiguous, ask the user which layer they want. If they say "just review it," +default to Layer 1 (structural) as it's the fastest and most objectively verifiable. + +## Loading the review prompt + +Once you've determined the layer, read the corresponding prompt file listed above. +That file contains the full review protocol — follow it completely. + +## Non-negotiable rules + +- Every review report must include a "What I Cannot Evaluate" section. +- Never soften findings. If something is broken, say it's broken. +- Scores of 5/5 require specific evidence of excellence. Do not default to high scores. +- An honest review that finds real issues is more valuable than a clean bill of health. +- Write findings to `review.md` as instructed by the loaded prompt. \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..c69dd4e --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,385 @@ +# Copilot Instructions for md-ddl + +## What this repository is + +This repo is the **source of the MD-DDL standard** — its specification, agent prompts, skills, and worked examples. It is not a runtime application and not a domain modelling workspace. + +### Purpose boundary + +- `copilot-instructions.md` governs **contributor behaviour in this repository** (maintaining the spec, agent prompts/skills, examples, and tooling). +- Custom agents govern **modelling behaviour when applying MD-DDL** to user domains. +- Keep these concerns separate: repository maintenance guidance belongs here; end-user modelling workflow guidance belongs in agent prompts/skills. + +Contributors here are working on one of four things: + +1. **The specification** — the normative rules of the MD-DDL language +2. **Agent prompts and skills** — the AI guidance layer built on top of the spec +3. **Examples** — reference domain and entity files that demonstrate the spec +4. **Tooling** — any validator or utility that processes MD-DDL files + +Understanding which of these you are working on determines everything about how to proceed. + +--- + +## Repository layout + +``` +md-ddl-specification/ Normative spec — source of truth for all rules + 1-Foundation.md Core principles and document structure + 2-Domains.md Domain file format and metadata + 3-Entities.md Entity and attribute definitions + 4-Enumerations.md Enum structure and naming + 5-Relationships.md Relationship semantics and YAML + 6-Events.md Event structure and temporal rules + 7-Sources.md Source system declarations and source-layer structure + 8-Transformations.md Transformation vocabulary and mapping types + 9-Data-Products.md Data product classes, declaration, and generation rules + 10-Adoption.md Brownfield adoption, maturity model, baseline-to-canonical path + MD-DDL-Complete.md Concatenated single-file version (generated) + +agents/ + agent-guide/ Learning and navigation agent + AGENT.md Core prompt — identity, modes, user archetypes + skills/ + orientation/ Role profiling, MD-DDL overview, workflow mapping + concept-explorer/ Interactive spec teaching with analogies + worked-examples/ Simple Customer + Financial Crime + Brownfield Retail walkthroughs + adoption-planning/ Brownfield adoption assessment and roadmap planning + platform-setup/ VS Code Copilot + Claude Code setup and workflow + + agent-ontology/ Discovery and design agent + AGENT.md Core prompt — identity, modes, skill index + skills/ + domain-scoping/ Interview protocol + Domains spec (includes brownfield baseline-to-canonical path) + entity-modelling/ Concept realisation + Entities + Enumerations spec + relationship-events/ Relationships + Events spec + standards-alignment/ Industry standards mapping (self-contained) + source-mapping/ Source system declaration + field-level transformations + domain-review/ Structural and decision-quality review protocol + baseline-capture/ Document existing schemas, ETL, catalog as baselines + schema-import/ Fast-track DDL-to-domain inference (brownfield) + + agent-artifact/ Physical artifact generation agent + AGENT.md Core prompt — identity, modes, skill index + skills/ + dimensional/ Star schema, fact/dimension/bridge mapping + normalized/ Normalized operational schema, DDL/JSON Schema/Parquet + wide-column/ Denormalized wide-column reporting schemas + knowledge-graph/ Knowledge graph schema, Neo4j Cypher DDL + reconciliation/ Compare generated artifacts against existing state + + agent-architect/ Strategic design and data product publication agent + AGENT.md Core prompt — identity, modes, skill index + skills/ + architecture/ Architectural philosophy, tenets, comparison, presentation outputs + product-design/ Product class selection, entity scoping, governance, masking + odps-alignment/ ODPS v4.0 manifest generation and mapping + references/ ODPS specification reference + + agent-governance/ Standards conformance and compliance assurance agent + AGENT.md Core prompt — identity, modes (Conformance/Compliance/Monitor/Remediate), skill index + skills/ + standards-conformance/ Post-modelling industry standards audit + regulatory-compliance/ Jurisdiction mapping + regulator file loader (shared with agent-ontology) + regulators/ Per-regulator guidance files (apra.md, gdpr.md, fatf.md, etc.) + compliance-audit/ Three-level audit protocol, gap report format, severity rules + +.github/ + agents/ Copilot custom-agent entrypoints (wrappers) + agent-guide.agent.md Frontmatter + include of canonical `agents/agent-guide/AGENT.md` + agent-ontology.agent.md Frontmatter + include of canonical `agents/agent-ontology/AGENT.md` + agent-artifact.agent.md Frontmatter + include of canonical `agents/agent-artifact/AGENT.md` + agent-architect.agent.md Frontmatter + include of canonical `agents/agent-architect/AGENT.md` + agent-governance.agent.md Frontmatter + include of canonical `agents/agent-governance/AGENT.md` + review-md-ddl.agent.md Copilot review agent wrapper + copilot-instructions.md Copilot-specific contributor guidance + +.prompts/ + md-ddl-review-prompt.md Layer 1: Structural review prompt + md-ddl-adversarial-review-prompt.md Layer 2: Adversarial review prompt + md-ddl-evaluation-prompt.md Layer 3: Stakeholder simulation prompt + md-ddl-layered-review-process.md Review process orchestration + concat-md-ddl-specs.prompt.md Spec concatenation rules + +examples/ + Financial Crime/ Primary reference example (most current) + domain.md Reference-quality domain file + entities/party.md Reference-quality entity detail file + entities/party_role.md Reference-quality entity detail file +``` + +--- + +## Working on the specification + +### The spec is the authority — agents defer to it + +Every rule in every agent prompt and skill must be traceable to the spec. If a rule exists in an agent prompt but not in the spec, it should either be +added to the spec (if it's a genuine standard) or removed from the prompt (if it's agent-specific behaviour). + +### Section ownership + +Each spec section owns a distinct layer of the language: + +File | Owns +--- | --- +`1-Foundation.md` | Principles, document structure, two-layer model +`2-Domains.md` | Domain file format, metadata schema, diagram rules, summary tables +`3-Entities.md` | Entity YAML, attribute types, constraints, diagrams, inheritance +`4-Enumerations.md` | Enum formats, naming, dictionary vs. simple list +`5-Relationships.md` | Relationship types, granularity, cardinality, constraint syntax +`6-Events.md` | Event structure, payload design, temporal priority, actor/entity +`7-Sources.md` | Source file format, change models, domain feed tables, source-layer rules +`8-Transformations.md` | Transformation types, YAML syntax, expression language, generation behaviour +`9-Data-Products.md` | Data product classes, declaration syntax, governance, masking, product-driven generation +`10-Adoption.md` | Brownfield adoption maturity model, baseline capture format, baseline-to-canonical transition + +When adding or changing a rule, edit the owning section only. Do not duplicate rules across sections. + +### MD-DDL-Complete.md + +Never edit the `MD-DDL-Complete.md` directly, as we will join this from the individual spec files when we are about to push to github. + +This is a generated file — a concatenation of sections 1–10 in order. It exists for AI context loading (single-file spec injection into agent prompts). Do not edit it directly. + +Regenerate with the repo script: + +`powershell -ExecutionPolicy Bypass -File .\.github\scripts\concat-md-ddl-specs.ps1` + +Canonical generation rules are maintained in `.prompts/concat-md-ddl-specs.prompt.md`. Keep concatenation behavior defined there and avoid duplicating detailed algorithm rules in multiple files. + +After regeneration, run the verification checklist defined in `.prompts/concat-md-ddl-specs.prompt.md` before committing. + +### Versioning + +The spec uses semantic versioning in the H1 heading of each file. A version bump is required any time a rule changes in a way that would alter the output of a correctly-authored MD-DDL file. Corrections to examples or prose clarifications do not require a version bump. + +--- + +## Working on agents and skills + +### Canonical vs wrapper locations + +Use `agents/` as the canonical source of agent behaviour and skill content. + +Use `.github/agents/` for Copilot custom-agent wrapper files only. Wrapper files should contain: +- Custom-agent frontmatter (`name`, `description`, `argument-hint`, optional `tools`) +- A single include to the canonical prompt in `agents/.../AGENT.md` + +Do not duplicate full agent prompts in both locations. Update canonical files in `agents/` and keep wrappers minimal. + +### Responsibility split: repo guidance vs modelling guidance + +- Put repository authoring/maintenance rules in `copilot-instructions.md`. +- Put modelling interview, drafting, refinement, and compliance execution rules in agent prompts/skills. +- If guidance controls how an agent models a business domain, it belongs under `agents/`. +- If guidance controls how contributors edit this repo's assets, it belongs here. + +### The agent/spec relationship + +Agent prompts and skills are **guidance built on top of the spec** — they teach an AI how to apply the rules, not what the rules are. If you find yourself +repeating a spec rule verbatim inside a skill, that's a signal to reference the spec file instead. + +### Skill structure + +Each skill follows the progressive disclosure pattern: + +``` +skills// + SKILL.md Trigger description (frontmatter) + process guidance + references/ Spec sections or external references loaded on demand +``` + +The `SKILL.md` body should stay under 500 lines. Heavy spec content belongs in `references/` and is loaded only when the skill is active. See the skill-creator skill pattern for detailed guidance on writing effective skill files. + +### The spec reference stub pattern + +Reference files in `skills/*/references/` are stubs that point to the canonical spec file — they do not duplicate content. This means a spec update propagates automatically without touching agent files. When adding a new spec reference, create the stub and point it at the correct `md-ddl-specification/` file. + +Use file-relative paths in both Markdown links and `{{INCLUDE: ...}}` directives inside reference stubs. Do not use workspace-root paths (for example `md-ddl-specification/...`) because this repo is commonly consumed as a `.md-ddl` submodule and root-based paths break in consumer projects. + +### Reference Loading + +Treat `{{INCLUDE: ...}}` as platform-dependent. + +- VS Code Copilot custom-agent wrappers in `.github/agents/` process `{{INCLUDE}}` natively. +- Many other chat platforms do not process `{{INCLUDE}}` in arbitrary Markdown files. +- In SKILL.md guidance, instruct agents to load canonical spec files directly from `md-ddl-specification/*.md`. +- Keep `skills/*/references/*-spec.md` stub files as dependency documentation and include-aware fallbacks. + +When updating a skill, prefer wording like: `Load md-ddl-specification/3-Entities.md (reference stub: references/entities-spec.md)`. + +### Agent responsibilities and boundaries + +Each agent owns a distinct lifecycle stage. Do not add capabilities to an agent that belong to another agent's stage. + +Agent | Lifecycle stage | Owns +--- | --- | --- +`agent-guide` | Learning and navigation | Standard explanation, user orientation, concept teaching, worked example walkthroughs, platform setup, agent navigation and handoff +`agent-architect` | Strategic design and data products | Architecture philosophy discussion, data product class selection, entity scoping, governance overrides, masking strategies, ODPS manifest generation, external catalogue alignment +`agent-ontology` | Discovery and design | Domain modelling, entity authoring, relationship and event design, source system mapping and field-level transformations, standards alignment during authoring +`agent-artifact` | Physical artifact generation | Dimensional star schemas, normalized 3NF designs, wide-column reporting schemas, knowledge graph schemas, SQL DDL, JSON Schema, Cypher, Parquet schema contracts +`agent-governance` | Standards conformance and compliance assurance | Standards conformance auditing, compliance metadata auditing, regulatory monitoring, governance remediation + +**Boundary rule — Guide vs All Specialists:** Agent Guide teaches concepts, walks through examples, helps with platform setup, and navigates users to the right specialist agent. It never creates production MD-DDL artifacts — domain files, entity details, data product declarations, physical schemas, or compliance audit reports. When a user is ready for production work, Agent Guide hands off explicitly to the appropriate specialist agent. Do not add production modelling, generation, product design, architecture discussion, or compliance auditing to Agent Guide. + +**Boundary rule — Guide vs Ontology:** Agent Guide may sketch MD-DDL to illustrate concepts (clearly marked as demonstrations, not production artifacts). Agent Ontology creates production domain files, entity details, relationships, and events. If Agent Guide identifies that a user is ready to model, it hands off to Agent Ontology with a suggested opening prompt. Do not add production modelling capability to Agent Guide, and do not add tutorial or onboarding capability to Agent Ontology. + +**Boundary rule — Guide vs Architect:** Agent Guide may mention architecture concepts when teaching (Teach mode). Agent Architect engages in strategic architecture discussion, positioning, and comparison (Discuss mode). If Agent Guide identifies that a user wants to discuss architecture philosophy, compare approaches, or prepare strategic material, it hands off to Agent Architect. Do not add strategic discussion capability to Agent Guide, and do not add tutorial or onboarding capability to Agent Architect. + +**Boundary rule — Ontology vs Artifact:** Agent Ontology produces conceptual and logical MD-DDL models. Agent Artifact consumes those models and generates physical artifacts (DDL, JSON Schema, Parquet). If Agent Artifact identifies a conceptual gap (missing entity, attribute, or relationship), it flags the gap and defers the structural change to Agent Ontology. Do not add physical generation capability to Agent Ontology or domain modelling capability to Agent Artifact. + +**Boundary rule — Ontology vs Architect:** Agent Ontology creates the initial `## Data Products` summary table during domain drafting. Agent Architect takes over for detailed product design — choosing product class, scoping entities, setting governance overrides, masking strategies, and generating external manifests (ODPS). Agent Architect also handles architecture philosophy discussion. Do not add detailed product design, ODPS generation, or architecture discussion to Agent Ontology, and do not add entity modelling or relationship design to Agent Architect. + +**Boundary rule — Architect vs Artifact:** Agent Architect produces MD-DDL data product declarations that serve as input contracts for Agent Artifact. Agent Artifact generates physical artifacts (DDL, JSON Schema, Parquet, Cypher) scoped by the product's `entities`, `schema_type`, `governance`, and `masking` fields. Do not add physical artifact generation to Agent Architect, and do not add product design or ODPS alignment to Agent Artifact. + +**Boundary rule — Ontology vs Governance:** Agent Ontology applies governance metadata and industry standards alignment during authoring (design-time). Agent Governance audits standards conformance and regulatory compliance after modelling (assurance-time). If a compliance or conformance gap requires a structural model change — a new entity, attribute, or relationship — Agent Governance flags it and defers the structural work to Agent Ontology. Do not add structural modelling capability to Agent Governance, and do not add post-hoc conformance auditing to Agent Ontology. + +**Boundary rule — Governance vs Architect:** Agent Governance may audit data product governance and masking metadata (see Level 4 in the compliance-audit skill) and produce recommendations. Agent Architect owns all product declarations and applies approved changes. Agent Governance flags product governance gaps; Agent Architect fixes them. Do not add product declaration authoring to Agent Governance, and do not add compliance auditing to Agent Architect. + +### Shared skills + +Some skills are used by more than one agent. The `regulatory-compliance` skill is the current example — Agent Ontology uses it to apply governance metadata +during domain authoring; Agent Governance uses it as the requirements benchmark during audits. + +Shared skills live under the agent that owns them conceptually. Agent Governance owns `regulatory-compliance` because compliance assurance is its primary purpose. Agent Ontology loads it as an external reference. + +Industry standard reference files (`industry_standards/bian/`, `industry_standards/fhir/`, `industry_standards/tmforum/`) are shared read-only references used by both Agent Ontology (standards-alignment skill, design-time) and Agent Governance (standards-conformance skill, assurance-time). + +When editing a shared skill, consider the impact on both agents. The skill's trigger description should reflect all contexts in which it is used. + +### Adding a new agent + +New agents follow the same structure as `agent-ontology/`: +- `AGENT.md` — identity, behaviour modes, skill index, non-negotiable output rules +- `skills/` — one skill per coherent process area, not per spec section + +The skill index in `AGENT.md` is the triggering mechanism. Write trigger descriptions to be specific and slightly pushy — the agent should load a skill +when in doubt, not skip it to save context. + +Before adding a new agent, confirm it occupies a distinct lifecycle stage not already covered. Add it to the agent responsibilities table above. + +### What belongs in agents, not in the spec + +- Interview protocols and question sequences +- Decision frameworks for trade-offs (entity vs. enum vs. attribute) +- Checklists for output quality review +- Industry standards mapping tables +- Behaviour modes (Interview / Drafting / Refinement) + +None of these are rules of the language. They are guidance for applying the language. + +--- + +## Table formatting in markdown + +When writing tables in markdown, use pipe (`|`) syntax without leading and trailing pipes with a header row and separator line. For example: + +```markdown +Column 1 | Column 2 | Column 3 +--- | --- | --- +Value 1 | Value 2 | Value 3 +``` + +--- + +## Working on examples + +### What examples are for + +Examples serve two purposes: they demonstrate correct spec application for human readers, and they act as AI context references (agents are instructed to use `examples/Financial Crime/` as the quality benchmark). + +### The current reference example + +`examples/Financial Crime/` is the highest-quality example in the repo. When in doubt about what correct MD-DDL looks like, this is the reference. The domain file uses the current table format; the entity files use current classDiagram and YAML patterns. + +### Adding or updating examples + +- New examples must conform to the current spec version — check the version header in `1-Foundation.md` before writing. +- If spec and example disagree, the spec wins. Flag the discrepancy and update the example to match the spec — do not leave known-incorrect examples in place. +- Keep links in examples relative and navigable: `entities/party.md`, `#party`. +- Never invent reference URLs for standards. Verify before adding. + +### Upgrading existing examples + +Examples must stay current with the spec. When a spec version bumps, check all examples for patterns that the new version has superseded and update them. + +When upgrading an example, update all patterns in the file in a single pass — do not partially upgrade a file, as mixed old/new patterns are more confusing than either consistently old or consistently new. + +--- + +## Validation philosophy + +MD-DDL distinguishes between **mechanical pre-flight checks** (syntax, links, entity references) and **agent-driven quality review** (structure, convention, governance, domain fitness). This split is deliberate and normative — see `md-ddl-specification/1-Foundation.md` "Validation Model" for the authoritative definition. + +Rules for contributors: + +- **Do not add lint-style enforcement to agent prompts.** If an agent rejects a file for a convention violation, that is a prompt bug. Agents flag deviations; they do not reject files for anything above syntax level. +- **Validation language in agent prompts must match the tier.** Use `flag` / `note` / `suggest` / `observe` for convention and quality issues (Levels 3–5). Use `error` / `reject` / `fail` only for syntax-level failures (Level 1). When reviewing an agent prompt, search for error-language and confirm it is limited to syntax contexts. +- **Organisational vocabulary deviations are observations, not errors.** When an agent encounters `phi` instead of `pii`, or `data_class` instead of `classification`, the correct response is to note it as a potential spec vocabulary gap and continue working. Do not add prompt rules that reject non-standard vocabulary. +- **Pre-flight checks are fixed and minimal.** There are exactly 5 checks (YAML syntax, Mermaid syntax, internal link integrity, entity reference consistency, domain version field). Adding a new check requires a spec version bump — it is a deliberate, reviewed decision, not a casual addition. +- **The `1-Foundation.md` validation model section is the normative reference.** If you are unsure whether something is a pre-flight check or an agent-driven concern, consult that section. Do not resolve the ambiguity by adding enforcement rules to agent prompts. + +--- + +## Architectural philosophy + +MD-DDL implements the **Data Autonomy** architectural style. Every design decision in the spec — domain-aligned ownership, canonical models, model-driven generation, polyglot persistence, governance as metadata — traces to one or more of 13 architectural tenets distilled from the foundational blog series. + +The tenets are documented in `agents/agent-architect/skills/architecture/SKILL.md`. The source material is in `references/architecture/` (17 blog posts, 3 external references, 7 Mermaid diagram conversions). + +### The 13 tenets (summary) + +1. Master data where change is least +2. Separate data from logic ownership +3. Design for loose coupling +4. Model for business semantics +5. Encode governance as metadata +6. Use polyglot persistence +7. Embrace small, regular change +8. Ask "what does good look like?" +9. Standardise 80%, differentiate 20% +10. Data products are architecture quantum +11. Canonical models + key mapping +12. Event-driven = real-time semantics +13. Data ethics as relational philosophy + +### Rules for contributors + +- When adding or changing spec rules, consider which tenet(s) the rule implements. If none, question whether the rule belongs. +- When reviewing agent prompts, verify they teach the *why* (architecture) not just the *what* (syntax). +- Tenets are living — they may evolve as the architectural thinking matures. Blog posts are historical references; the tenet table in SKILL.md is the current version. +- The tenets are informed positions with rationale and acknowledged counter-positions, not dogma. Agent prompts must present them as design heuristics, not rules. + +--- + +## Cross-cutting rules for all contributions + +- **Spec owns the rules.** Agents and examples implement them. When they conflict, fix the agent or example, not the spec (unless the spec is genuinely wrong). +- **One source of truth per rule.** If a rule appears in multiple places, that's technical debt. Flag it. +- **No invented content.** Do not fabricate standards references, regulatory requirements, or example data that is not verifiable. +- **Validate Markdown structure** on any changed file: table/link integrity, Mermaid syntax, heading hierarchy, and YAML block correctness. +- **No runtime assumptions.** There is no build system. Validation is structural and manual (or via a linter if one is added). Do not add code that assumes a build or test pipeline exists unless one has been defined. + +--- + +## Reviewing the standard + +When a user asks to review, evaluate, audit, or assess the MD-DDL standard, agents, or examples, use the layered review process defined in `.prompts/md-ddl-layered-review-process.md`. + +### Quick reference + +Request | What to do +--- | --- +"Review the standard" / "check for issues" / "run a review" | Load `.prompts/md-ddl-review-prompt.md` (Layer 1 — structural). Run it. Report findings. +"Find weaknesses" / "what's wrong" / "stress test" / "adversarial review" | Load `.prompts/md-ddl-adversarial-review-prompt.md` (Layer 2 — adversarial). Run it. Report findings. +"Evaluate for users" / "would people adopt this" / "stakeholder review" | Load `.prompts/md-ddl-evaluation-prompt.md` (Layer 3 — stakeholder simulation). Run it. Report findings. +"Full review" / "layered review" / "comprehensive review" | Load `.prompts/md-ddl-layered-review-process.md` for the orchestration protocol, then run layers 1 → 2 → 3 in order, cross-referencing findings between layers. +Review from a specific viewpoint (e.g. "review as a data engineer") | Load `.prompts/md-ddl-layered-review-process.md`, check the Ad-Hoc Viewpoint Reviews table, and frame the review from the requested perspective. + +### Key rules for reviews + +- Each layer should run in a separate conversation to prevent cross-contamination between evaluation stances. +- Every review report must include a "What I Cannot Evaluate" section declaring the limits of AI assessment. +- Cross-model diversity improves review quality — run different layers with different AI models when possible. +- An honest review that finds real issues is more valuable than a clean bill of health. Never soften findings. diff --git a/.github/scripts/concat-md-ddl-specs.ps1 b/.github/scripts/concat-md-ddl-specs.ps1 new file mode 100644 index 0000000..5779018 --- /dev/null +++ b/.github/scripts/concat-md-ddl-specs.ps1 @@ -0,0 +1,86 @@ +param( + [string]$SpecDir = "md-ddl-specification", + [string]$OutputFileName = "MD-DDL-Complete.md" +) + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path +$specPath = Join-Path $repoRoot $SpecDir +$outputPath = Join-Path $specPath $OutputFileName + +# Deterministic section order for MD-DDL spec generation. +$sectionFiles = @( + "1-Foundation.md", + "2-Domains.md", + "3-Entities.md", + "4-Enumerations.md", + "5-Relationships.md", + "6-Events.md", + "7-Sources.md", + "8-Transformations.md", + "9-Data-Products.md", + "10-Adoption.md" +) + +$files = foreach ($name in $sectionFiles) { + $fullPath = Join-Path $specPath $name + if (-not (Test-Path $fullPath)) { + throw "Missing required spec section file: $name" + } + Get-Item $fullPath +} + +$combined = New-Object System.Collections.Generic.List[string] + +for ($i = 0; $i -lt $files.Count; $i++) { + $file = $files[$i] + $lines = [System.IO.File]::ReadAllLines($file.FullName) + + if ($lines.Length -lt 3) { + $body = @() + } + else { + # Keep one global H1 by dropping each section's first two lines in the body. + $body = [System.Collections.Generic.List[string]]::new() + foreach ($line in $lines[2..($lines.Length - 1)]) { + [void]$body.Add($line) + } + + # Remove optional trailing navigation block: blank lines, '---', blank lines, '...next:' line. + while ($body.Count -gt 0 -and [string]::IsNullOrWhiteSpace($body[$body.Count - 1])) { + $body.RemoveAt($body.Count - 1) + } + + if ($body.Count -gt 0 -and $body[$body.Count - 1] -like "...next:*") { + $body.RemoveAt($body.Count - 1) + while ($body.Count -gt 0 -and [string]::IsNullOrWhiteSpace($body[$body.Count - 1])) { + $body.RemoveAt($body.Count - 1) + } + if ($body.Count -gt 0 -and $body[$body.Count - 1] -eq "---") { + $body.RemoveAt($body.Count - 1) + } + while ($body.Count -gt 0 -and [string]::IsNullOrWhiteSpace($body[$body.Count - 1])) { + $body.RemoveAt($body.Count - 1) + } + } + } + + if ($i -eq 0) { + # Preserve single top-level title from section 1. + [void]$combined.Add($lines[0]) + [void]$combined.Add("") + } + + if ($i -gt 0) { + [void]$combined.Add("") + } + + foreach ($line in $body) { + [void]$combined.Add($line) + } +} + +# Ensure UTF-8 output (without BOM) so punctuation remains stable across toolchains. +$utf8NoBom = New-Object System.Text.UTF8Encoding($false) +[System.IO.File]::WriteAllLines($outputPath, $combined, $utf8NoBom) + +Write-Output "Regenerated $OutputFileName from $($files.Count) files in $SpecDir" \ No newline at end of file diff --git a/.github/scripts/preflight.py b/.github/scripts/preflight.py new file mode 100644 index 0000000..a6243f1 --- /dev/null +++ b/.github/scripts/preflight.py @@ -0,0 +1,445 @@ +#!/usr/bin/env python3 +""" +MD-DDL Pre-Flight Check + +Mechanical Level-1 syntax validation per the MD-DDL spec (1-Foundation.md). +Run against a domain folder before committing or publishing. + +Usage: + python preflight.py + python preflight.py domains/customer + python preflight.py . (run from within the domain folder) + +Requires: Python 3.8+, pyyaml (pip install pyyaml) + +Exit codes: + 0 no findings + 1 one or more pre-flight failures + 2 usage or invocation error +""" + +import re +import sys +import yaml +from dataclasses import dataclass +from pathlib import Path + + +# --------------------------------------------------------------------------- +# Data types +# --------------------------------------------------------------------------- + +@dataclass +class Finding: + file: str + line: int + check: str + message: str + + +# --------------------------------------------------------------------------- +# Mermaid diagram types recognised by the spec +# --------------------------------------------------------------------------- + +MERMAID_DIAGRAM_TYPES = { + "graph", "flowchart", "sequenceDiagram", "classDiagram", + "stateDiagram", "stateDiagram-v2", "erDiagram", "gantt", + "journey", "gitGraph", "pie", "quadrantChart", "requirementDiagram", + "mindmap", "timeline", "block-beta", "packet-beta", + "xychart-beta", "sankey-beta", "kanban", "architecture-beta", +} + +# YAML keys in relationship and event blocks that must name a domain entity. +# 'actor' is deliberately excluded — event actors may be roles or external +# systems, not MD-DDL entities. +ENTITY_REF_KEYS = {"source", "target", "entity", "extends"} + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def _read(path: Path) -> str: + return path.read_text(encoding="utf-8", errors="replace") + + +def _extract_code_blocks(text: str, lang: str) -> list[tuple[int, str]]: + """Return (start_line_1indexed, content) for every fenced block of lang.""" + blocks: list[tuple[int, str]] = [] + lines = text.splitlines() + fence = re.compile(rf"^```{re.escape(lang)}\s*$", re.IGNORECASE) + close = re.compile(r"^```\s*$") + in_block = False + start = 0 + buf: list[str] = [] + for i, line in enumerate(lines, 1): + if not in_block: + if fence.match(line): + in_block = True + start = i + 1 + buf = [] + else: + if close.match(line): + blocks.append((start, "\n".join(buf))) + in_block = False + buf = [] + else: + buf.append(line) + return blocks + + +def _heading_slug(text: str) -> str: + """GitHub-compatible anchor slug for a heading line.""" + # Strip inline markdown (bold, italic, backticks, links) + text = re.sub(r"\[([^\]]*)\]\([^)]*\)", r"\1", text) + text = re.sub(r"[`*_]", "", text) + text = text.lower().strip() + text = re.sub(r"[^\w\s-]", "", text) + text = re.sub(r"[\s_]+", "-", text).strip("-") + return text + + +def _get_headings(path: Path) -> set[str]: + """All heading slugs in a markdown file.""" + slugs: set[str] = set() + try: + for line in _read(path).splitlines(): + m = re.match(r"^#{1,6}\s+(.+)$", line) + if m: + slugs.add(_heading_slug(m.group(1))) + except OSError: + pass + return slugs + + +# --------------------------------------------------------------------------- +# Check 1 — YAML syntax +# --------------------------------------------------------------------------- + +def check_yaml_syntax(md_file: Path) -> list[Finding]: + findings: list[Finding] = [] + text = _read(md_file) + for start, content in _extract_code_blocks(text, "yaml"): + try: + yaml.safe_load(content) + except yaml.YAMLError as exc: + line_num = start + if hasattr(exc, "problem_mark") and exc.problem_mark: + line_num = start + exc.problem_mark.line + msg = exc.problem if hasattr(exc, "problem") else str(exc) + findings.append(Finding(str(md_file), line_num, "yaml-syntax", + f"YAML parse error: {msg}")) + return findings + + +# --------------------------------------------------------------------------- +# Check 2 — Mermaid syntax +# --------------------------------------------------------------------------- + +def check_mermaid_syntax(md_file: Path) -> list[Finding]: + findings: list[Finding] = [] + text = _read(md_file) + for start, content in _extract_code_blocks(text, "mermaid"): + stripped = content.strip() + if not stripped: + findings.append(Finding(str(md_file), start, "mermaid-syntax", + "Empty Mermaid block")) + continue + + lines = stripped.splitlines() + idx = 0 + + # Skip optional YAML config block (---...---) + if lines[0].strip() == "---": + idx = 1 + while idx < len(lines) and lines[idx].strip() != "---": + idx += 1 + idx += 1 # step past closing --- + + # Find first meaningful content line + diagram_type = None + while idx < len(lines): + line = lines[idx].strip() + if line and not line.startswith("%%"): + diagram_type = line.split()[0].rstrip(":") + break + idx += 1 + + if diagram_type is None: + findings.append(Finding(str(md_file), start, "mermaid-syntax", + "Mermaid block has no diagram type declaration")) + elif diagram_type not in MERMAID_DIAGRAM_TYPES: + findings.append(Finding(str(md_file), start + idx, "mermaid-syntax", + f"Unrecognised Mermaid diagram type: '{diagram_type}'")) + return findings + + +# --------------------------------------------------------------------------- +# Check 3 — Internal link integrity +# --------------------------------------------------------------------------- + +# Patterns that carry URL/path values in MD-DDL files: +# [text](url) — standard markdown link (also catches ![alt](url) images) +# href='url' — HTML anchor inside Mermaid node labels (single quotes) +# href="url" — same, double-quote variant +# +# Not checked here (out of scope for domain preflight): +# {{INCLUDE: path}} — agent/skill file directive; not used in domain folders +# reference-style links [text][ref] / [ref]: url — only found as external URLs in README + +_MD_LINK_RE = re.compile(r"\[[^\]]*\]\(([^)]+)\)") +_HREF_RE = re.compile(r'href=["\']([^"\']+)["\']') + + +def _check_url(url: str, line_num: int, md_file: Path, findings: list[Finding]) -> None: + """Validate a single URL extracted from md_file at line_num.""" + # Skip external links — not our concern + if url.startswith(("http://", "https://", "mailto:")): + return + + # Pure same-page anchor: #heading — verify heading exists in this file + if url.startswith("#"): + anchor = url[1:] + if anchor and _heading_slug(anchor) not in _get_headings(md_file): + findings.append(Finding(str(md_file), line_num, "internal-links", + f"Broken same-page anchor: '#{anchor}' not found in this file")) + return + + # File path, with optional anchor + file_part, anchor = (url.rsplit("#", 1) if "#" in url else (url, None)) + if not file_part: + return + + target = (md_file.parent / file_part).resolve() + if not target.exists(): + findings.append(Finding(str(md_file), line_num, "internal-links", + f"Broken link: '{file_part}' does not exist")) + elif anchor: + if _heading_slug(anchor) not in _get_headings(target): + findings.append(Finding(str(md_file), line_num, "internal-links", + f"Broken anchor: '#{anchor}' not found in {file_part}")) + + +def check_internal_links(md_file: Path) -> list[Finding]: + findings: list[Finding] = [] + lines = _read(md_file).splitlines() + + for line_num, line in enumerate(lines, 1): + seen: set[str] = set() + for m in _MD_LINK_RE.finditer(line): + url = m.group(1).strip() + if url not in seen: + seen.add(url) + _check_url(url, line_num, md_file, findings) + for m in _HREF_RE.finditer(line): + url = m.group(1).strip() + if url not in seen: + seen.add(url) + _check_url(url, line_num, md_file, findings) + + return findings + + +# --------------------------------------------------------------------------- +# Check 4 — Entity reference consistency +# --------------------------------------------------------------------------- + +def _entity_names_from_domain(domain_file: Path) -> set[str]: + """Extract canonical entity names from the ## Entities table in domain.md.""" + names: set[str] = set() + text = _read(domain_file) + in_section = False + in_table = False + + for line in text.splitlines(): + if re.match(r"^##\s+Entities\s*$", line): + in_section = True + in_table = False + continue + if re.match(r"^##\s+", line) and in_section: + break + if in_section: + if re.match(r"^\s*Name\s*\|", line): + in_table = True + continue + if in_table and "|" in line: + # Strip separator rows (--- | --- | ...) + cells = [c.strip() for c in line.split("|") if c.strip()] + if not cells or re.match(r"^-+$", cells[0]): + continue + name_cell = cells[0] + lm = re.match(r"\[([^\]]+)\]\([^)]*\)", name_cell) + name = lm.group(1).strip() if lm else name_cell + if name: + names.add(name) + return names + + +def check_entity_references(domain_file: Path) -> list[Finding]: + """YAML source/target/actor/entity/extends values must name a domain entity.""" + findings: list[Finding] = [] + entity_names = _entity_names_from_domain(domain_file) + if not entity_names: + return [] + + domain_root = domain_file.parent + + # Check entity and event detail files; skip sources/ (cross-domain references allowed) + for md_file in domain_root.rglob("*.md"): + rel = md_file.relative_to(domain_root) + # Skip sources/ (cross-domain entity refs allowed) and products/ + # (the 'source' key there names a data source system, not an entity) + if rel.parts and rel.parts[0] in {"sources", "products"}: + continue + + text = _read(md_file) + for start, content in _extract_code_blocks(text, "yaml"): + try: + data = yaml.safe_load(content) + except yaml.YAMLError: + continue # caught by yaml-syntax check + if not isinstance(data, dict): + continue + + block_lines = content.splitlines() + for key in ENTITY_REF_KEYS: + value = data.get(key) + if not isinstance(value, str): + continue + if value in entity_names: + continue + + # Find the line number of the key within this block + key_line = start + for i, bl in enumerate(block_lines): + if re.match(rf"^{re.escape(key)}\s*:", bl): + key_line = start + i + break + + findings.append(Finding( + str(md_file), key_line, "entity-references", + f"'{key}: {value}' does not match any entity in domain.md", + )) + return findings + + +# --------------------------------------------------------------------------- +# Check 5 — Domain version field +# --------------------------------------------------------------------------- + +def check_domain_version(domain_file: Path) -> list[Finding]: + findings: list[Finding] = [] + text = _read(domain_file) + lines = text.splitlines() + + in_metadata = False + collecting = False + buf: list[str] = [] + start = 1 + found_block = False + + for i, line in enumerate(lines, 1): + if re.match(r"^##\s+Metadata\s*$", line): + in_metadata = True + continue + if in_metadata and re.match(r"^##\s+", line): + in_metadata = False + if in_metadata and re.match(r"^```yaml\s*$", line): + collecting = True + start = i + 1 + buf = [] + continue + if collecting: + if re.match(r"^```\s*$", line): + collecting = False + in_metadata = False + found_block = True + content = "\n".join(buf) + try: + data = yaml.safe_load(content) + if not isinstance(data, dict) or "version" not in data: + findings.append(Finding( + str(domain_file), start, "domain-version", + "Metadata YAML block is missing the 'version:' field", + )) + except yaml.YAMLError: + pass # caught by yaml-syntax check + break + buf.append(line) + + if not found_block: + findings.append(Finding( + str(domain_file), 1, "domain-version", + "No YAML block found under '## Metadata' — 'version:' field cannot be verified", + )) + return findings + + +# --------------------------------------------------------------------------- +# Orchestration +# --------------------------------------------------------------------------- + +def run_preflight(domain_folder: str) -> list[Finding]: + domain_root = Path(domain_folder).resolve() + if not domain_root.exists(): + print(f"error: path not found: {domain_folder}", file=sys.stderr) + sys.exit(2) + if not domain_root.is_dir(): + print(f"error: not a directory: {domain_folder}", file=sys.stderr) + sys.exit(2) + + domain_file = domain_root / "domain.md" + findings: list[Finding] = [] + + # Checks 1–3: run across every .md file in the domain folder + for md_file in sorted(domain_root.rglob("*.md")): + findings += check_yaml_syntax(md_file) + findings += check_mermaid_syntax(md_file) + findings += check_internal_links(md_file) + + # Check 4: entity reference consistency (requires domain.md) + if domain_file.exists(): + findings += check_entity_references(domain_file) + + # Check 5: domain version (requires domain.md) + if domain_file.exists(): + findings += check_domain_version(domain_file) + else: + findings.append(Finding( + str(domain_file), 0, "domain-version", + "domain.md not found — is this a domain folder?", + )) + + return findings + + +def main() -> None: + if len(sys.argv) != 2: + print("Usage: python preflight.py ", file=sys.stderr) + sys.exit(2) + + findings = run_preflight(sys.argv[1]) + + if not findings: + print("Pre-flight passed. No findings.") + sys.exit(0) + + # Group by check for readability + by_check: dict[str, list[Finding]] = {} + for f in findings: + by_check.setdefault(f.check, []).append(f) + + print(f"Pre-flight: {len(findings)} finding(s)\n") + for check, group in by_check.items(): + print(f" [{check}] {len(group)} finding(s)") + for f in group: + path = f.file + print(f" {path}:{f.line}") + print(f" {f.message}") + print() + + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c90d4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.egg-info/ +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ + +# Virtual environments +.venv/ +venv/ +env/ + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..d81948d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule ".md-ddl"] + path = .md-ddl + url = https://github.com/Semprini/md-ddl diff --git a/.md-ddl b/.md-ddl new file mode 160000 index 0000000..2c3a301 --- /dev/null +++ b/.md-ddl @@ -0,0 +1 @@ +Subproject commit 2c3a301cacbd01ff0e6bd343fd2704762c0cd706 diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..6324d40 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.14 diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e01db0 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Random Corp + +This repo is a reference end-to-end example implementation of Data Products using Markdown Data Definition Language (MD-DDL). This project implements data products for sourcing and the Financial Crime Domain. + +The project leverages: + +- https://git.semprini.me/paul/semprini-core to provide I&AM, Code Repos +- https://git.semprini.me/paul/semprini-data for data management, data product metadata, real-time data sourcing, and data transform platforms. +- https://git.semprini.me/paul/semprini-data-domain for foundational and experience data products. + +## Project Goals + +1. Synthetic data generators per source system - replicating realistic cadence of data change and system semantics in source systems. This will be achieved by: + - A single source system Postgres DB server for all sources + - Per source system database instances + - Python (faker) synthetic data generator per source system + - Ability to set change cadence + - Use the MD-DDL source definitions to define schemas and synthetic generators as per md-ddl. + +2. Per source system, source aligned data products. This will be defined once the synthetic data generators are running + +3. Domain aligned data products. Detail to be defined later but will be: + - Party Core + - Financial Transaction + - Product Core + +4. Consumer aligned data products. To be defined later. diff --git a/financial_crime/LIFECYCLE.md b/financial_crime/LIFECYCLE.md new file mode 100644 index 0000000..7b1ad14 --- /dev/null +++ b/financial_crime/LIFECYCLE.md @@ -0,0 +1,80 @@ +# Lifecycle - Financial Crime + +This file records the current lifecycle state of the Financial Crime domain and +its published products. It combines machine-readable change intent with a +human-readable history so lifecycle, reconciliation, and migration planning can +work from the same source. + +## Current State + +```yaml +domain_version: "1.0.0" +domain_status: Active + +products: + - name: Canonical Party + status: Active + version: "1.0.0" + - name: Transaction Risk Summary + status: Active + version: "1.0.0" + - name: Salesforce CRM Raw Feed + status: Active + version: "1.0.0" + - name: Party Risk Report (Legacy) + status: Deprecated + version: "1.0.0" +``` + +## Version History + +### Domain 1.0.0 - 2026-03-15 + +#### Change Manifest + +```yaml +changes: + - type: additive + scope: entity + entity: Party + description: "Established the Party, Party Role, Account, Agreement, and Transaction backbone for financial crime modelling." + - type: additive + scope: relationship + entity: Party + description: "Declared role, account, agreement, and self-referential relationship patterns needed for AML, KYC, and network analysis." + - type: additive + scope: event + entity: Transaction + description: "Added core lifecycle and detection events for onboarding, execution, account change, and suspicious activity workflows." + - type: additive + scope: entity + entity: Exchange Rate + description: "Included currency and exchange-rate concepts for cross-border and multi-currency monitoring." + +affected_products: + - name: Canonical Party + impact: additive + reason: "Initial domain-aligned publication of core identity and relationship entities." + - name: Transaction Risk Summary + impact: additive + reason: "Initial consumer-aligned analytics product for transaction monitoring." + - name: Salesforce CRM Raw Feed + impact: additive + reason: "Initial source-aligned replay and audit feed for Salesforce CRM." + - name: Party Risk Report (Legacy) + impact: none + reason: "Retained as a deprecated legacy product while consumers migrate to Transaction Risk Summary." +``` + +#### Changelog + +### Added + +- Initial Financial Crime domain release with party, account, agreement, transaction, branch, currency, and exchange-rate concepts. +- Relationship patterns for party role assignment, customer account holding, party association, and agreement governance. +- Events covering onboarding, transaction execution, account status change, agreement activation, KYC updates, and suspicious activity detection. +- Three active products: Canonical Party, Transaction Risk Summary, and Salesforce CRM Raw Feed. + +### Deprecated + +- Party Risk Report (Legacy) retained for migration support while downstream consumers move to Transaction Risk Summary. diff --git a/financial_crime/domain.md b/financial_crime/domain.md new file mode 100644 index 0000000..b2ea4f7 --- /dev/null +++ b/financial_crime/domain.md @@ -0,0 +1,238 @@ +# Financial Crime + +This domain encompasses all concepts required for financial crime detection, investigation, and reporting. It includes party identification, transaction monitoring, relationship analysis, and regulatory compliance for AML (Anti-Money Laundering), KYC (Know Your Customer), and CTF (Counter-Terrorist Financing). + +This is a business-aligned domain that draws concepts from BIAN Business Object Model (BOM) rather than mapping directly to BIAN Service Domains. + +## Metadata + +```yaml +# Accountability +owners: + - financial.crime@bank.com +stewards: + - compliance.officer@bank.com +technical_leads: + - data.architecture@bank.com + +# Governance & Security +classification: "Highly Confidential" +pii: true +regulatory_scope: + - AML (Anti-Money Laundering) + - KYC (Know Your Customer) + - CTF (Counter-Terrorist Financing) + - FATF Recommendations + - BSA (Bank Secrecy Act) + - EU 5AMLD / 6AMLD + - USA PATRIOT Act +default_retention: "10 years post relationship end" + +# Lifecycle & Discovery +status: "Active" +version: "1.0.0" +tags: + - Compliance + - Risk + - Regulatory + - Core +``` + +### Domain Overview Diagram + +```mermaid +--- +config: + layout: elk + elk: + mergeEdges: false + nodePlacementStrategy: LINEAR_SEGMENTS + look: classic + theme: dark +--- +graph TD + + Person --> |is a|Party + Company --> |is a|Party + TermDepositAgreement --> |is a|Agreement + LoanAgreement --> |is a|Agreement + + Party <--> |related to|Party + Party --> |assumes|PartyRole + + Customer --> |is a|PartyRole + Merchant --> |is a|PartyRole + Payee --> |is a|PartyRole + Payer --> |is a|PartyRole + Teller --> |is a|PartyRole + PaymentInitiator --> |is a|PartyRole + + Party --> |has|ContactAddress + PartyRole --> |uses|ContactAddress + Customer --> |holds|Account + Customer --> |has|CustomerPreferences + PartyRole --> |governed by|Agreement + + Transaction --> |has debtor|Payer + Transaction --> |has creditor|Payee + Transaction --> |initiated by|PaymentInitiator + Transaction --> |denominated in|Currency + Transaction --> |debits|Account + Transaction --> |credits|Account + + Teller --> |processes|Transaction + Teller --> |assigned to|Branch + Merchant --> |receives payment via|Transaction + Merchant --> |settles into|Account + + Account --> |instance of|Product + Account --> |denominated in|Currency + Branch --> |services|Account + + Product --> |in terms of|Agreement + Agreement --> |governs|PartyRole + + ContactAddress --> |references|Address + + ExchangeRate --> |base|Currency + ExchangeRate --> |quote|Currency + + Party["Party"] + Person["Person"] + Company["Company"] + PartyRole["Party Role"] + Customer["Customer"] + Merchant["Merchant"] + Payer["Payer"] + Payee["Payee"] + Teller["Teller"] + PaymentInitiator["Payment Initiator"] + Address["Address"] + ContactAddress["Contact Address"] + CustomerPreferences["Customer Preferences"] + Account["Account"] + Product["Product"] + Agreement["Agreement"] + LoanAgreement["Loan Agreement"] + TermDepositAgreement["Term Deposit Agreement"] + Transaction["Transaction"] + Branch["Branch"] + Currency["Currency"] + ExchangeRate["Exchange Rate"] +``` + +## Source Systems + +Business Application | Platform | Capability Domain +--- | --- | --- +[Temenos Payment](sources/temenos-payment/source.md) | Temenos SaaS | Payment Execution +[SAP Fraud Management](sources/sap-fraud-management/source.md) | SAP | Fraud +[Salesforce CRM](sources/salesforce-crm/source.md) | Salesforce | Customer Relationship Management + +## Entities + +Name | Specializes | Description | Reference +--- | --- | --- | --- +[Party](entities/party.md#party) | | The abstract representation of any individual or organization that can participate in financial activities. Core business object that abstracts concepts like customer, correspondent, and supplier. | [BIAN BOM - Party](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Party) +[Person](entities/person.md#person) | [Party](entities/party.md#party) | A natural person who participates in financial activities. | [BIAN BOM - Person](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Person) +[Company](entities/company.md#company) | [Party](entities/party.md#party) | An organization, corporation, or other legally recognized entity. Also referred to as Legal Entity or Organisation in BIAN. | [BIAN BOM - Legal Entity](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/LegalEntity) +[Party Role](entities/party_role.md#party-role) | | The abstract representation of a Party's involvement in a specific business context. Serves as base for specific role types like Customer, Merchant, Creditor, etc. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) +[Customer](entities/customer.md#customer) | [Party Role](entities/party_role.md#party-role) | A Party that holds accounts, uses products/services, or has an active relationship with the institution. First-class business concept with distinct ownership and governance. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) +[Merchant](entities/merchant.md#merchant) | [Party Role](entities/party_role.md#party-role) | A Party that accepts payments for goods or services, typically through the institution's payment systems. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) +[Payee](entities/payee.md#payee) | [Party Role](entities/party_role.md#party-role) | A Party to whom money is owed in a transaction or agreement. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) +[Payer](entities/payer.md#payer) | [Party Role](entities/party_role.md#party-role) | A Party who owes money in a transaction or agreement. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) +[Teller](entities/teller.md#teller) | [Party Role](entities/party_role.md#party-role) | A bank employee who processes customer transactions at a branch location. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) +[Payment Initiator](entities/payment_initiator.md#payment-initiator) | [Party Role](entities/party_role.md#party-role) | A Party that instructs or initiates a transaction on behalf of another party. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) +[Account](entities/account.md#account) | | A financial account held with the institution. | [BIAN BOM - Account](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Account) +[Contact Address](entities/contact_address.md#contact-address) | | Physical, postal, or electronic address associated with a Party. | [BIAN BOM - Contact Point](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/ContactPoint) +[Address](entities/address.md#address) | | The canonical record of a physical or postal location. Reference data shared across parties — a single Address record is referenced by all Contact Addresses at that location, enabling network analysis without fuzzy matching. | [BIAN BOM - Location](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Location) +[Customer Preferences](entities/customer-preferences.md#customer-preferences) | | Customer-specific settings for communication, privacy, and interaction preferences. | [BIAN BOM - Party Preference](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyPreference) +[Product](entities/product.md#product) | | A financial product or service offered by the institution. | [BIAN BOM - Product](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Product) +[Agreement](entities/agreement.md#agreement) | | A formal agreement between the institution and one or more Parties. In BIAN called Agreement or Arrangement. | [BIAN BOM - Agreement](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Agreement) +[Term Deposit Agreement](entities/term-deposit-agreement.md#term-deposit-agreement) | [Agreement](entities/agreement.md#agreement) | An agreement for a term deposit product. | [BIAN BOM - Agreement](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Agreement) +[Loan Agreement](entities/loan-agreement.md#loan-agreement) | [Agreement](entities/agreement.md#agreement) | An agreement for a loan product. | [BIAN BOM - Loan Agreement](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/LoanAgreement) +[Transaction](entities/transaction.md#transaction) | | A financial transaction involving the movement of funds. | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Currency](entities/currency.md#currency) | | A currency recognized by the system for transactions and positions. | [BIAN BOM - Currency](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Currency) +[Exchange Rate](entities/exchange-rate.md#exchange-rate) | | The rate at which one currency can be exchanged for another at a specific point in time. | [BIAN BOM - Exchange Rate](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/ExchangeRate) +[Branch](entities/branch.md#branch) | | A physical or operational branch of the financial institution. In BIAN called Location. | [BIAN BOM - Location](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Location) + +## Enums + +Name | Description | Reference +--- | --- | --- +[Party Status](enums.md#party-status) | Operational status of a party record. | [BIAN BOM - Party](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Party) +[Financial Crime Risk Rating](enums.md#financial-crime-risk-rating) | Institution-assessed ML/TF risk level for a party. | [BIAN BOM - Rating](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Rating) +[Sanctions Screen Status](enums.md#sanctions-screen-status) | Outcome of sanctions screening checks. | [BIAN BOM - Party](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Party) +[Party Role Status](enums.md#party-role-status) | Lifecycle status of a specific party role instance. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) +[Due Diligence Status](enums.md#due-diligence-status) | CDD/EDD completion state for a role. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) +[PEP Status](enums.md#pep-status) | Politically exposed person classification for individuals. | [BIAN BOM - Person](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Person) +[Address Type](enums.md#address-type) | Structural type of an address record. | [BIAN BOM - Location](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Location) +[Address Purpose](enums.md#address-purpose) | Business purpose for which a party uses an address. | [BIAN BOM - Contact Point](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/ContactPoint) +[Address Verification Status](enums.md#address-verification-status) | Current verification state of a contact address association. | [BIAN BOM - Contact Point](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/ContactPoint) +[Verification Method](enums.md#verification-method) | Method used to verify an address. | [BIAN BOM - Contact Point](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/ContactPoint) +[Currency Code](enums.md#currency-code) | ISO 4217 currency codes for transaction and account values. | [ISO 4217](https://www.iso.org/iso-4217-currency-codes.html) +[Transaction Type](enums.md#transaction-type) | Payment mechanism and clearing pathway classification. | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Transaction Status](enums.md#transaction-status) | Lifecycle state of a transaction from initiation through settlement. | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Transaction Channel](enums.md#transaction-channel) | Channel through which a transaction was initiated or processed. | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Account Status](enums.md#account-status) | Operational lifecycle state of an account. | [BIAN BOM - Account](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Account) +[Account Type](enums.md#account-type) | Classification of an account by primary purpose and product characteristics. | [BIAN BOM - Account](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Account) +[Agreement Status](enums.md#agreement-status) | Lifecycle state of a formal agreement. | [BIAN BOM - Agreement](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Agreement) +[Contact Preference](enums.md#contact-preference) | Customer's preferred outbound communication channel. | [BIAN BOM - Party Preference](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyPreference) +[Company Legal Structure](enums.md#company-legal-structure) | Legal form under which a company or organisation is constituted. | [BIAN BOM - Legal Entity](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/LegalEntity) +[Association Type](enums.md#association-type) | Nature of the relationship between two parties in a network association. | [BIAN BOM - Party](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Party) +[Account Holder Type](enums.md#account-holder-type) | Nature of a customer's holding relationship with an account. | [BIAN BOM - Account](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Account) + +## Relationships + +Name | Description | Reference +--- | --- | --- +[Party Assumes Roles](entities/party.md#party-assumes-roles) | A Party can assume multiple Party Roles (Customer, Merchant, Creditor, etc.) across different contexts and time periods. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) +[Party Has Contact Addresses](entities/party.md#party-has-contact-addresses) | A Party can have multiple contact addresses for different purposes. | [BIAN BOM - Contact Point](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/ContactPoint) +[Customer Holds Account](entities/customer.md#customer-holds-account) | A Customer can hold one or more Accounts. | [BIAN BOM - Account](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Account) +[Customer Has Preferences](entities/customer.md#customer-has-preferences) | A Customer has associated preferences for communication and interaction. | [BIAN BOM - Party Preference](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyPreference) +[Party Role Uses Contact Addresses](entities/party_role.md#party-role-uses-contact-addresses) | A Party Role can use one or more contact addresses associated with the Party. | [BIAN BOM - Contact Point](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/ContactPoint) +[Party Role Governed By Agreement](entities/party_role.md#party-role-governed-by-agreement) | A Party Role may be governed by a specific Agreement. | [BIAN BOM - Agreement](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Agreement) +[Agreement Involves Party Roles](entities/agreement.md#agreement-involves-party-roles) | Agreements involve multiple Parties in specific roles (Customer as borrower, Company as guarantor, etc.). | [BIAN BOM - Agreement](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Agreement) +[Transaction Has Debtor](entities/transaction.md#transaction-has-debtor) | A Transaction has one or more Debtors (parties from whom funds are debited). | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Transaction Has Creditor](entities/transaction.md#transaction-has-creditor) | A Transaction has one or more Creditors (parties to whom funds are credited). | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Transaction Initiated By Instructing Agent](entities/transaction.md#transaction-initiated-by-instructing-agent) | An Instructing Agent initiates or instructs a transaction on behalf of another party. | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Teller Processes Transaction](entities/teller.md#teller-processes-transaction) | A Teller processes transactions at a branch location. | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Merchant Receives Payment](entities/merchant.md#merchant-receives-payment) | A Merchant receives payment through transactions. | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Account Holds Product](entities/account.md#account-holds-product) | An Account is an instance of a Product. | [BIAN BOM - Account](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Account) +[Branch Services Account](entities/branch.md#branch-services-account) | Accounts are serviced by a specific Branch (Location). | [BIAN BOM - Location](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Location) +[Product In Terms Of Agreement](entities/product.md#product-in-terms-of-agreement) | A Product is defined in terms of an Agreement. | [BIAN BOM - Agreement](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Agreement) +[Contact Address References Address](entities/contact_address.md#contact-address-references-address) | Each Contact Address references a canonical Address record. | [BIAN BOM - Contact Point](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/ContactPoint) +[Branch Transaction Summary](entities/branch.md#branch-transaction-summary) | Grouped relationship from Branch to Transactions for branch-level fraud analysis. | - +[Party Role At Point In Time](entities/party_role.md#party-role-at-point-in-time) | Period snapshot of Party Role state for regulatory reporting. | - +[Party Related To Party](entities/party.md#party-related-to-party) | A Party may be related to one or more other Parties through ownership, control, family, or association ties. Structural basis for beneficial ownership mapping and PEP network analysis. | [BIAN BOM - Party](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Party) +[Teller Assigned To Branch](entities/teller.md#teller-assigned-to-branch) | A Teller is assigned to a Branch for operational responsibilities. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) +[Exchange Rate References Base Currency](entities/exchange-rate.md#exchange-rate-references-base-currency) | Each Exchange Rate references one base Currency. | [BIAN BOM - Exchange Rate](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/ExchangeRate) +[Exchange Rate References Quote Currency](entities/exchange-rate.md#exchange-rate-references-quote-currency) | Each Exchange Rate references one quote Currency. | [BIAN BOM - Exchange Rate](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/ExchangeRate) +[Transaction Denominated In Currency](entities/transaction.md#transaction-denominated-in-currency) | A Transaction is denominated in exactly one Currency. | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Transaction Has Debit Account](entities/transaction.md#transaction-has-debit-account) | A Transaction debits one internal Account. Null for externally-held debit accounts. | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Transaction Has Credit Account](entities/transaction.md#transaction-has-credit-account) | A Transaction credits one internal Account. Null for externally-held credit accounts. | [BIAN BOM - Payment](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Payment) +[Account Denominated In Currency](entities/account.md#account-denominated-in-currency) | An Account is denominated in exactly one Currency. | [BIAN BOM - Account](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/Account) +[Merchant Has Settlement Account](entities/merchant.md#merchant-has-settlement-account) | A Merchant may have a designated Account for settlement credit. | [BIAN BOM - Party Role](https://bian-modelapi-v4.azurewebsites.net/BOClassByName/PartyRole) + +## Events + +Name | Actor | Entity | Description +--- | --- | --- | --- +[Party Role Assigned](events/party-role-assigned.md#party-role-assigned) | Party | Party Role | Emitted when a Party assumes a new role (becomes a Customer, Merchant, etc.). +[Customer Onboarded](events/customer-onboarded.md#customer-onboarded) | Party | Customer | Emitted when a new Customer relationship is established. +[Transaction Executed](events/transaction-executed.md#transaction-executed) | Instructing Agent | Transaction | Emitted when a financial transaction is successfully executed. +[Account Status Changed](events/account-status-changed.md#account-status-changed) | Customer | Account | Emitted when an account status changes (e.g. Active → Frozen). +[High Risk Transaction Detected](events/high-risk-transaction-detected.md#high-risk-transaction-detected) | Transaction Monitoring System | Transaction | Emitted when a transaction is flagged as potentially suspicious. +[Agreement Activated](events/agreement-activated.md#agreement-activated) | Party Role | Agreement | Emitted when an agreement becomes active and enforceable. +[KYC Status Updated](events/kyc-status-updated.md#kyc-status-updated) | Compliance Officer | Party | Emitted when a Party's KYC status is updated. + +## Data Products + +Name | Class | Consumers | Status +--- | --- | --- | --- +[Canonical Party](products/canonical.md#canonical-party) | domain-aligned | Cross-domain Integration | Active +[Transaction Risk Summary](products/analytics.md#transaction-risk-summary) | consumer-aligned | Financial Crime Analytics | Active +[Salesforce CRM Raw Feed](products/source-feeds.md#salesforce-crm-raw-feed) | source-aligned | Data Engineering | Active +[Party Risk Report (Legacy)](products/party-risk-report-legacy.md#party-risk-report-legacy) | consumer-aligned | Financial Crime Analytics | Deprecated + +--- diff --git a/financial_crime/entities/account.md b/financial_crime/entities/account.md new file mode 100644 index 0000000..13900f9 --- /dev/null +++ b/financial_crime/entities/account.md @@ -0,0 +1,152 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Account + +An Account represents a financial record used to hold balances and process debits and credits for a customer relationship. In a dimensional model, Account is a key slowly-changing dimension — its status, type, and currency are required attributes for transaction risk analytics and account-level monitoring. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Account{ + * Account Identifier : string + Account Number : string + Account Type : enum~AccountType~ + Account Status : enum~AccountStatus~ + Opened Date : date + Closed Date : date + } + + Customer "0..*" --> "0..*" Account : holds + Account "0..*" --> "1" Product : instance of + Account "0..*" --> "1" Currency : denominated in + Branch "1" --> "0..*" Account : services + + class AccountType["Account Type"]{<>} + class AccountStatus["Account Status"]{<>} + + class Customer["Customer"] + class Product["Product"] + class Currency["Currency"] + class Branch["Branch"] +``` + +```yaml +existence: independent +mutability: slowly_changing +temporal: + tracking: valid_time + description: > + Account status transitions (e.g., Active → Frozen → Active) must be tracked + with valid time to support point-in-time regulatory queries such as "was this + account frozen at the time of this transaction?". Opened Date and Closed Date + serve as the outer valid time boundaries. +attributes: + Account Identifier: + type: string + identifier: primary + description: > + Globally unique surrogate identifier for this account across all systems. + Immutable once assigned. + + Account Number: + type: string + description: > + Human-facing account number as presented to the customer. May follow BSB+account + format for Australian accounts or IBAN format for international accounts. + + Account Type: + type: enum:Account Type + description: > + Classification of the account by its primary purpose and product characteristics + (e.g., Savings, Current, Term Deposit, Loan). Used as a dimension key in transaction + risk analytics — different account types have different expected transaction patterns + and applicable AML typologies. + + Account Status: + type: enum:Account Status + description: > + The current operational lifecycle state of the account. Frozen and Suspended statuses + are set by compliance or fraud operations and must be tracked with their effective dates + to support regulatory audit. Closed accounts must be retained — they must not be deleted. + + Opened Date: + type: date + description: > + Date the account was opened and became operational. Establishes the start of the + account's valid time period. Used in dormancy calculations and account age risk factors. + + Closed Date: + type: date + description: > + Date the account was permanently closed. A null value indicates the account is still + open. Accounts must not be deleted — the Closed Date and a status of Closed is the + only permitted termination mechanism. +``` + +```yaml +constraints: + Closed Account Requires Closed Date: + check: > + Account Status != 'Closed' + OR Closed Date IS NOT NULL + description: > + An account with status Closed must have a Closed Date recorded to preserve + valid time integrity. + Closed Date After Opened Date: + check: "Closed Date IS NULL OR Closed Date > Opened Date" + description: > + An account's closure date must be later than its opening date. +``` + +```yaml +governance: + pii: false + classification: Highly Confidential + retention: 10 years + retention_basis: Domain default retention aligned to AML/CTF record-keeping obligations + description: > + Account records must be retained for 7 years from Closed Date, aligned to AUSTRAC + AML/CTF Act 2006 record-keeping obligations. Accounts must not be deleted — closure + via Closed Date and status update is the only permitted termination mechanism. + access_role: + - FINANCIAL_CRIME_ANALYST + - KYC_OFFICER + - COMPLIANCE_OFFICER + - RELATIONSHIP_MANAGER + compliance_relevance: + - AUSTRAC AML/CTF Act 2006 — Part B account record-keeping + - AUSTRAC AML/CTF Amendment Act 2024 + - RBNZ AML/CFT Act 2009 — section 58 + - FATF Recommendation 10 — Customer Due Diligence +``` + +## Relationships + +### Account Holds Product + +An Account is an instance of one Product definition. + +```yaml +source: Account +type: references +target: Product +cardinality: many-to-one +granularity: atomic +ownership: Account +``` + +### Account Denominated In Currency +An Account is denominated in exactly one Currency. All balances and transaction amounts on the account are expressed in this currency. +```yaml +source: Account +type: references +target: Currency +cardinality: many-to-one +granularity: atomic +ownership: Account +``` diff --git a/financial_crime/entities/address.md b/financial_crime/entities/address.md new file mode 100644 index 0000000..42187cc --- /dev/null +++ b/financial_crime/entities/address.md @@ -0,0 +1,126 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Address + +An Address is the canonical record of a physical or postal location. It holds only the structured components of the location itself — no party association, no purpose, no verification metadata. Those belong on Contact Address. + +Separating Address from Contact Address enables deduplication: a single Address record serves as the reference point for all parties associated with that location. This is significant in a financial crime context — when two or more unrelated parties share the same Address, that shared reference is a detectable network signal without requiring any fuzzy address matching across duplicated strings. + +Address records are reference data. They are not owned by any party and must not be modified when a party changes their address — instead the party's Contact Address is closed and a new one opened referencing the correct Address record. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Address{ + * Address Identifier : string + Address Type : enum~AddressType~ + Address Line 1 : string + Address Line 2 : string + Suburb : string + City : string + State Or Region : string + Postcode : string + Country : string + } + + ContactAddress "0..*" --> "1" Address : references + + class AddressType["Address Type"]{<>} + + class ContactAddress["Contact Address"] +``` + +```yaml +existence: independent +mutability: reference +attributes: + Address Identifier: + type: string + identifier: primary + description: > + Globally unique surrogate identifier for this physical location record. Immutable once assigned. Multiple parties may reference the same Address Identifier via their Contact Address records. + + Address Type: + type: enum:Address Type + description: > + The structural type of the address. Determines which fields are applicable — a PO Box does not have Address Line 1 in the same sense as a street address, and an Overseas Address may not have a State Or Region. + + Address Line 1: + type: string + description: > + The primary street address line, typically including street number and street name. For PO Box addresses, the PO Box number. + + Address Line 2: + type: string + description: > + Secondary address line for suite, level, unit, or building name where applicable. + + Suburb: + type: string + description: > + The suburb or locality component of the address. Retained as a distinct field from City to support Australian and New Zealand address formats where suburb and city are separate concepts. + + City: + type: string + description: > + The city or town. For addresses in Australia and New Zealand this is typically the metropolitan area (e.g., Sydney, Auckland) where distinct from the suburb. + + State Or Region: + type: string + description: > + The state, territory, or region. For Australia: NSW, VIC, QLD, etc. For New Zealand: region name. For international addresses: the equivalent administrative subdivision. + + Postcode: + type: string + description: > + The postal code. Stored as a string to preserve leading zeros and support international formats. + + Country: + type: string + description: > + The country in which the address is located, as an ISO 3166-1 alpha-2 country code. Mandatory for all address types. +``` + +```yaml +constraints: + Country Required: + not_null: Country + description: > + Every address must have a country. Country is the minimum information + required for jurisdiction risk assessment. + + Street Address Requires Address Line 1: + check: > + Address Type != 'Street Address' + OR Address Line 1 IS NOT NULL + description: > + A street address must have at least Address Line 1 populated. +``` + +```yaml +governance: + pii: false + classification: Confidential + retention: 10 years + retention_basis: Domain default retention aligned to AML/CTF record-keeping obligations + description: > + Address records must be retained for 7 years from the date the last Contact Address referencing this record is closed. Address records must not be modified or deleted — they are immutable reference data. + Changes to an address (e.g., a street renamed) should result in a new Address record; existing Contact Address records are not retrospectively updated so that historical point-in-time queries remain accurate. + access_role: + - FINANCIAL_CRIME_ANALYST + - KYC_OFFICER + - COMPLIANCE_OFFICER + - RELATIONSHIP_MANAGER + compliance_relevance: + - AUSTRAC AML/CTF Act 2006 — Part B address record-keeping + - RBNZ AML/CFT Act 2009 — section 58 +``` + +## Relationships + +No relationships are sourced directly from Address in the current domain model. diff --git a/financial_crime/entities/agreement.md b/financial_crime/entities/agreement.md new file mode 100644 index 0000000..6a6c1a9 --- /dev/null +++ b/financial_crime/entities/agreement.md @@ -0,0 +1,81 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Agreement + +An Agreement defines the formal contractual terms that govern relationships between party roles and financial products. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Agreement{ + * Agreement Identifier : string + Agreement Number : string + Agreement Status : enum~AgreementStatus~ + Effective Date : date + Maturity Date : date + } + + TermDepositAgreement --|> Agreement + LoanAgreement --|> Agreement + Agreement "1" --> "0..*" PartyRole : governs + + class AgreementStatus["Agreement Status"]{<>} + class TermDepositAgreement["Term Deposit Agreement"] + class LoanAgreement["Loan Agreement"] + class PartyRole["Party Role"] +``` + +```yaml +existence: independent +mutability: slowly_changing +attributes: + Agreement Identifier: + type: string + identifier: primary + description: Unique identifier of the agreement record. + + Agreement Number: + type: string + description: Human-facing agreement reference number. + + Agreement Status: + type: enum:Agreement Status + description: > + The current lifecycle state of the agreement. Active agreements govern current + obligations; Terminated and Matured agreements must be retained for audit. Used + as a dimension attribute in agreement-level analytics and regulatory reporting + of active product holdings. + + Effective Date: + type: date + description: Date the agreement became enforceable. + + Maturity Date: + type: date + description: Date the agreement is scheduled to mature, if applicable. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +### Agreement Involves Party Roles + +An Agreement governs and involves one or more Party Roles participating in the contract. + +```yaml +source: Agreement +type: governs +target: Party Role +cardinality: one-to-many +granularity: atomic +ownership: Agreement +``` diff --git a/financial_crime/entities/branch.md b/financial_crime/entities/branch.md new file mode 100644 index 0000000..9319c45 --- /dev/null +++ b/financial_crime/entities/branch.md @@ -0,0 +1,75 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Branch + +A Branch is an operational location responsible for servicing accounts and branch-mediated transactions. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Branch{ + * Branch Identifier : string + Branch Code : string + Branch Name : string + } + + Branch "1" --> "0..*" Account : services + + class Account["Account"] +``` + +```yaml +existence: independent +mutability: reference +attributes: + Branch Identifier: + type: string + identifier: primary + description: Unique identifier for the branch location. + + Branch Code: + type: string + description: Operational code used to identify the branch. + + Branch Name: + type: string + description: Human-readable branch name. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +### Branch Services Account + +A Branch services one or more Accounts. + +```yaml +source: Branch +type: has +target: Account +cardinality: one-to-many +granularity: atomic +ownership: Branch +``` + +### Branch Transaction Summary + +A Branch has a grouped relationship to Transactions processed through its serviced Accounts. This supports branch-level aggregated reporting for fraud pattern analysis. + +```yaml +source: Branch +type: associates_with +target: Transaction +cardinality: one-to-many +granularity: group +ownership: Branch +``` diff --git a/financial_crime/entities/company.md b/financial_crime/entities/company.md new file mode 100644 index 0000000..adf113d --- /dev/null +++ b/financial_crime/entities/company.md @@ -0,0 +1,108 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Company + +A Company is a legal entity that participates in financial relationships with the institution. It specialises Party and carries organisation-specific identification and registration attributes. + +Company legal structure is a primary AML risk factor. Shell companies, special purpose vehicles, and trusts carry elevated money-laundering risk due to beneficial ownership complexity — structure must be recorded to support risk rating and Enhanced Due Diligence decisions. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Company{ + Company Registration Number : string + Incorporation Country : string + Incorporation Date : date + Legal Structure : enum~CompanyLegalStructure~ + Tax Identifier : string + } + + Company --|> Party + + class CompanyLegalStructure["Company Legal Structure"]{<>} + class Party["Party"] +``` + +```yaml +extends: Party +existence: independent +mutability: slowly_changing +attributes: + Company Registration Number: + type: string + description: > + Government-issued registration identifier for the legal entity (e.g., ACN for Australian + companies, NZBN for New Zealand companies). Required for legal entity identification + under AML/CTF Act 2006 Part B. + + Incorporation Country: + type: string + description: > + ISO 3166-1 alpha-2 country code where the company is incorporated. Used for + jurisdiction risk scoring — entities incorporated in FATF-listed high-risk + jurisdictions require Enhanced Due Diligence. + + Incorporation Date: + type: date + description: > + Date the company was formally incorporated. Recently incorporated entities with + no operating history are a risk signal, particularly when associated with high-value + or complex transactions. + + Legal Structure: + type: enum:Company Legal Structure + description: > + The legal form under which the company is constituted. Critical for AML risk + assessment — trusts, special purpose vehicles, and foreign entities carry elevated + beneficial ownership risk. The legal structure determines which additional due + diligence obligations apply under the AML/CTF Act 2006 and RBNZ AML/CFT Act 2009. + + Tax Identifier: + type: string + description: > + Tax registration number issued by the relevant revenue authority (e.g., Australian + Business Number (ABN), New Zealand Business Number (NZBN), US Employer Identification + Number (EIN)). Used as a supplementary identity verification attribute and cross-reference + for adverse media and sanction screening. +``` +```yaml +constraints: + Registration Number Required For Active Company: + not_null: Company Registration Number + lifecycle_stage: Onboarding + description: > + A company must have a valid Company Registration Number before any designated + service is provided. Required for legal entity identification under AML/CTF Act 2006 + Part B customer identification obligations. +``` + +```yaml +governance: + pii: false + classification: Highly Confidential + retention: 10 years + retention_basis: Domain default retention aligned to AML/CTF record-keeping obligations + description: > + 10-year retention from the end of the business relationship, aligned to AUSTRAC and + RBNZ record-keeping obligations. The regulatory minimum is 7 years under AUSTRAC + AML/CTF Act 2006; the domain default of 10 years is applied as the conservative standard. + access_role: + - FINANCIAL_CRIME_ANALYST + - KYC_OFFICER + - COMPLIANCE_OFFICER + compliance_relevance: + - AUSTRAC AML/CTF Act 2006 — Part B legal entity identification + - AUSTRAC AML/CTF Amendment Act 2024 + - RBNZ AML/CFT Act 2009 — section 14 + - FATF Recommendation 10 — Customer Due Diligence (legal persons) + - FATF Recommendation 24 — Transparency and beneficial ownership of legal persons +``` + +## Relationships + +No relationships are sourced directly from Company in the current domain model. diff --git a/financial_crime/entities/contact_address.md b/financial_crime/entities/contact_address.md new file mode 100644 index 0000000..3279387 --- /dev/null +++ b/financial_crime/entities/contact_address.md @@ -0,0 +1,167 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Contact Address + +A Contact Address is the association between a Party and a physical Address, qualified by purpose, verification status, and the period during which it was in use. It is not the address itself — the canonical physical location is held in the Address entity and reused across parties. Contact Address carries only the metadata that describes *how* and *when* a Party uses a given address. + +This separation supports two critical financial crime capabilities. First, deduplication: if two parties share the same physical address, a single Address record exists and both Contact Address records point to it, enabling network analysis queries such as "which other parties are associated with this address". Second, temporal accuracy: the valid time period on Contact Address allows point-in-time queries — "what address did this party use at the time of this transaction" — which is essential for regulatory audit and SAR evidence. + +A Party *has* Contact Addresses — it owns the association. A Party Role *uses* Contact Addresses — a role references which of the party's addresses applies for the specific purpose of that role. For example, the same Person may have a residential address used for identification and a mailing address used by their Customer role for correspondence. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class ContactAddress{ + * Contact Address Identifier : string + Address Purpose : enum~AddressPurpose~ + Is Primary : boolean + Verification Status : enum~AddressVerificationStatus~ + Verification Date : date + Verification Method : enum~VerificationMethod~ + Valid From : date + Valid To : date + } + + Party "1" --> "0..*" ContactAddress : has + PartyRole "0..*" --> "0..*" ContactAddress : uses + ContactAddress "0..*" --> "1" Address : references + + class AddressPurpose["Address Purpose"]{<>} + class AddressVerificationStatus["Address Verification Status"]{<>} + class VerificationMethod["Verification Method"]{<>} + + class Party["Party"] + class PartyRole["Party Role"] + class Address["Address"] +``` + +```yaml +existence: associative +mutability: slowly_changing +temporal: + tracking: valid_time + description: > + Valid time is carried explicitly as Valid From and Valid To attributes rather than relying on generated temporal columns. This allows future-dated address changes (e.g. recording a new address before a customer moves) and supports point-in-time queries for regulatory audit. +attributes: + Contact Address Identifier: + type: string + identifier: primary + description: > + Globally unique surrogate identifier for this party-address association instance. + + Address Purpose: + type: enum:Address Purpose + description: > + The purpose for which this address is used by the party. Determines regulatory significance — a Residential Address is required for individual identification under the AML/CTF Act 2006; a Registered Office Address is required for legal entity verification. + + Is Primary: + type: boolean + default: false + description: > + Indicates whether this is the party's primary contact address for the given purpose. A party may have only one primary address per Address Purpose at any point in time. + + Verification Status: + type: enum:Address Verification Status + description: > + The current verification status of this address association. Unverified addresses must not be used as the sole basis for satisfying AML/CTF identification obligations. Expired verification must be renewed at the next CDD review. + + Verification Date: + type: date + description: > + The date on which the address was most recently verified against an acceptable document or electronic source. Used to determine whether re-verification is required at periodic CDD review. + + Verification Method: + type: enum:Verification Method + description: > + The method used to verify this address. Documentary verification requires an acceptable identity or address document. Electronic verification uses a third-party data source. In-person verification is performed at a branch or by a third party. + + Valid From: + type: date + description: > + The date from which the party began using this address for the stated purpose. Establishes the start of the valid time period for point-in-time queries. + + Valid To: + type: date + description: > + The date on which the party ceased using this address for the stated purpose. A null value indicates the address is currently in use. Closed associations must not be deleted — Valid To must be set to preserve address history for regulatory audit. +``` + +```yaml +constraints: + One Primary Per Purpose: + check: > + COUNT(Contact Address WHERE Party == this.Party + AND Address Purpose == this.Address Purpose + AND Is Primary == true + AND (Valid To IS NULL OR Valid To >= Today)) <= 1 + description: > + A party may have at most one primary Contact Address per Address Purpose + at any point in time. + + Valid To After Valid From: + check: "Valid To IS NULL OR Valid To > Valid From" + description: > + The end of the address validity period must be later than the start. + + Verification Date Required When Verified: + check: > + Verification Status != 'Verified' + OR Verification Date IS NOT NULL + description: > + A Contact Address with a status of Verified must have a Verification + Date recorded. + + Residential Required Before Activation: + check: > + EXISTS (Contact Address WHERE Party == this.Party + AND Address Purpose == 'Residential' + AND Verification Status == 'Verified') + lifecycle_stage: Onboarding + description: > + An individual party must have at least one verified Residential Address + before any designated service is provided. Required for identification + under the AML/CTF Act 2006 Part B individual identification program. +``` + +```yaml +governance: + pii: true + classification: Highly Confidential + retention: 10 years + retention_basis: Domain default retention aligned to AML/CTF record-keeping obligations + description: > + Address association records must be retained for 7 years from Valid To + date, aligned to AUSTRAC and RBNZ record-keeping obligations. Records + must never be deleted — closure via Valid To is the only permitted + termination mechanism. + access_role: + - FINANCIAL_CRIME_ANALYST + - KYC_OFFICER + - COMPLIANCE_OFFICER + - RELATIONSHIP_MANAGER + compliance_relevance: + - AUSTRAC AML/CTF Act 2006 — Part B address identification obligations + - AUSTRAC AML/CTF Amendment Act 2024 + - RBNZ AML/CFT Act 2009 — section 14 + - FATF Recommendation 10 — Customer Due Diligence +``` + +## Relationships + +### Contact Address References Address + +Each Contact Address references exactly one canonical Address record. + +```yaml +source: Contact Address +type: references +target: Address +cardinality: many-to-one +granularity: atomic +ownership: Contact Address +``` diff --git a/financial_crime/entities/currency.md b/financial_crime/entities/currency.md new file mode 100644 index 0000000..acbc530 --- /dev/null +++ b/financial_crime/entities/currency.md @@ -0,0 +1,52 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Currency + +Currency defines a recognised monetary unit used for account balances and transactions. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Currency{ + * Currency Code : string + Currency Name : string + Minor Unit : integer + } + + ExchangeRate "0..*" --> "1" Currency : from + ExchangeRate "0..*" --> "1" Currency : to + + class ExchangeRate["Exchange Rate"] +``` + +```yaml +existence: independent +mutability: reference +attributes: + Currency Code: + type: string + identifier: primary + description: ISO 4217 alphabetic currency code. + + Currency Name: + type: string + description: Official currency display name. + + Minor Unit: + type: integer + description: Number of decimal places used for the currency. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +No relationships are sourced directly from Currency in the current domain model. diff --git a/financial_crime/entities/customer-preferences.md b/financial_crime/entities/customer-preferences.md new file mode 100644 index 0000000..1cf5792 --- /dev/null +++ b/financial_crime/entities/customer-preferences.md @@ -0,0 +1,59 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Customer Preferences + +Customer Preferences captures communication and consent preferences that apply to a specific customer relationship. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class CustomerPreferences{ + * Preference Identifier : string + Contact Preference : enum~ContactPreference~ + Marketing Consent : boolean + Effective From : date + } + + Customer "1" --> "0..1" CustomerPreferences : has + + class ContactPreference["Contact Preference"]{<>} + class Customer["Customer"] +``` + +```yaml +existence: dependent +mutability: slowly_changing +attributes: + Preference Identifier: + type: string + identifier: primary + description: Unique identifier for the customer preference profile. + + Contact Preference: + type: enum:Contact Preference + description: > + The customer's preferred channel for outbound communication from the institution. + Must be respected in all non-mandatory communications and drives CRM system routing. + + Marketing Consent: + type: boolean + description: Indicates whether the customer has consented to marketing communications. + + Effective From: + type: date + description: Date from which the current preference set applies. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +No relationships are sourced directly from Customer Preferences in the current domain model. diff --git a/financial_crime/entities/customer.md b/financial_crime/entities/customer.md new file mode 100644 index 0000000..386dea6 --- /dev/null +++ b/financial_crime/entities/customer.md @@ -0,0 +1,85 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Customer + +A Customer is a Party Role representing an active or prospective relationship with the institution for products and services. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Customer{ + * Customer Number : string + Onboarding Date : date + Relationship Start Date : date + } + + Customer --|> PartyRole + Customer "0..*" --> "0..*" Account : holds + Customer "1" --> "0..1" CustomerPreferences : has + + class PartyRole["Party Role"] + class Account["Account"] + class CustomerPreferences["Customer Preferences"] +``` + +```yaml +extends: Party Role +existence: independent +mutability: slowly_changing +attributes: + Customer Number: + type: string + identifier: primary + description: Unique customer identifier used for service and support operations. + + Onboarding Date: + type: date + description: Date the customer onboarding process was completed. + + Relationship Start Date: + type: date + description: Date the customer relationship became effective. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +### Customer Holds Account + +A Customer can hold one or more Accounts, and an Account can be held by one or more Customers. Joint accounts, business accounts with multiple authorized signatories, and beneficial ownership structures all require many-to-many cardinality. In the Financial Crime domain this is the structural basis for network analysis — connecting Customers through shared Accounts. + +```yaml +source: Customer +type: has +target: Account +cardinality: many-to-many +granularity: atomic +ownership: Customer +relationship_attributes: + - Holder Type (enum:Account Holder Type) + - Holder Start Date + - Holder End Date + - Is Primary Holder +``` + +### Customer Has Preferences + +A Customer has at most one active Customer Preferences profile at a time. + +```yaml +source: Customer +type: has +target: Customer Preferences +cardinality: one-to-one +granularity: atomic +ownership: Customer +``` diff --git a/financial_crime/entities/exchange-rate.md b/financial_crime/entities/exchange-rate.md new file mode 100644 index 0000000..a792a46 --- /dev/null +++ b/financial_crime/entities/exchange-rate.md @@ -0,0 +1,76 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Exchange Rate + +An Exchange Rate records conversion values between a source and target currency at a specific effective time. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class ExchangeRate{ + * Exchange Rate Identifier : string + Rate Value : decimal + Effective Date Time : datetime + } + + ExchangeRate "0..*" --> "1" Currency : base + ExchangeRate "0..*" --> "1" Currency : quote + + class Currency["Currency"] +``` + +```yaml +existence: dependent +mutability: append_only +attributes: + Exchange Rate Identifier: + type: string + identifier: primary + description: Unique identifier for the exchange rate observation. + + Rate Value: + type: decimal + description: Conversion multiplier from base to quote currency. + + Effective Date Time: + type: datetime + description: Timestamp at which the rate became effective. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +### Exchange Rate References Base Currency + +Each Exchange Rate references one base Currency. + +```yaml +source: Exchange Rate +type: references +target: Currency +cardinality: many-to-one +granularity: atomic +ownership: Exchange Rate +``` + +### Exchange Rate References Quote Currency + +Each Exchange Rate references one quote Currency. + +```yaml +source: Exchange Rate +type: references +target: Currency +cardinality: many-to-one +granularity: atomic +ownership: Exchange Rate +``` diff --git a/financial_crime/entities/loan-agreement.md b/financial_crime/entities/loan-agreement.md new file mode 100644 index 0000000..2321032 --- /dev/null +++ b/financial_crime/entities/loan-agreement.md @@ -0,0 +1,51 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Loan Agreement + +A Loan Agreement is a specialised agreement defining loan amount, schedule, and repayment obligations. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class LoanAgreement{ + Principal Amount : decimal + Interest Rate : decimal + Repayment Frequency : string + } + + LoanAgreement --|> Agreement + + class Agreement["Agreement"] +``` + +```yaml +extends: Agreement +existence: independent +mutability: slowly_changing +attributes: + Principal Amount: + type: decimal + description: Principal amount disbursed under the loan. + + Interest Rate: + type: decimal + description: Contracted annual interest rate for the loan. + + Repayment Frequency: + type: string + description: Payment cadence for scheduled repayments. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +No relationships are sourced directly from Loan Agreement in the current domain model. diff --git a/financial_crime/entities/merchant.md b/financial_crime/entities/merchant.md new file mode 100644 index 0000000..ef97a65 --- /dev/null +++ b/financial_crime/entities/merchant.md @@ -0,0 +1,81 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Merchant + +A Merchant is a Party Role that accepts payments for goods or services through institution channels. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Merchant{ + * Merchant Identifier : string + Merchant Category Code : string + Settlement Account Identifier : string + } + + Merchant --|> PartyRole + Merchant "1" --> "0..*" Transaction : processes + Merchant "0..*" --> "0..1" Account : settles into + + class PartyRole["Party Role"] + class Transaction["Transaction"] + class Account["Account"] +``` + +```yaml +extends: Party Role +existence: independent +mutability: slowly_changing +attributes: + Merchant Identifier: + type: string + identifier: primary + description: Unique identifier for the merchant role instance. + + Merchant Category Code: + type: string + description: > + ISO 18245 Merchant Category Code (MCC) representing the merchant's primary + business type. Used in transaction monitoring rule segmentation — certain MCCs + (e.g., cash-intensive businesses, money services) attract heightened scrutiny. + + Settlement Account Identifier: + type: string + description: Account identifier used for merchant settlement. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +### Merchant Receives Payment + +A Merchant receives funds through one or more Transactions. + +```yaml +source: Merchant +type: associates_with +target: Transaction +cardinality: one-to-many +granularity: atomic +ownership: Merchant +``` + +### Merchant Has Settlement Account +A Merchant may have a designated Account into which settlement funds are credited by the institution. +```yaml +source: Merchant +type: references +target: Account +cardinality: many-to-one +granularity: atomic +ownership: Merchant +``` diff --git a/financial_crime/entities/party.md b/financial_crime/entities/party.md new file mode 100644 index 0000000..e4f9d87 --- /dev/null +++ b/financial_crime/entities/party.md @@ -0,0 +1,188 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Party + +A Party is any individual or legal entity that has, or may have, a financial relationship with the institution. Aligned to the BIAN Party Reference Data Directory, Party is the foundational abstract concept from which all financial crime subjects are derived — it carries only the attributes that are universal across every party type. + +In the Financial Crime domain a Party represents any subject that may be assessed for risk, screened against watchlists, investigated, or reported to a regulatory authority. A Party does not hold relationships directly — it participates through a Party Role (e.g., Account Holder, Beneficial Owner, Signatory). The same Party may hold multiple roles across multiple products simultaneously. + +Specialisations of Party — Person and Legal Entity — carry the attributes that are specific to a natural person or an incorporated body respectively. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Party{ + <> + * Party Identifier : string + Legal Name : string + Also Known As : string[] + Party Status : enum~PartyStatus~ + Risk Rating : enum~FinancialCrimeRiskRating~ + Sanctions Screen Status : enum~SanctionsScreenStatus~ + Next Review Date : date + } + + class Person + + class Company{ + } + + Person --|> Party + Company --|> Party + Party "1" --> "0..*" PartyRole + Party "1" --> "0..*" ContactAddress + + class PartyStatus["Party Status"]{<>} + class FinancialCrimeRiskRating["Financial Crime Risk Rating"]{<>} + class SanctionsScreenStatus["Sanctions Screen Status"]{<>} + + class PartyRole["Party Role"] + class ContactAddress["Contact Address"] +``` + +```yaml +existence: independent +mutability: slowly_changing +temporal: + tracking: bitemporal + description: > + Valid time captures when the party information was true in the real world (e.g., a name change effective date). Transaction time captures when the institution recorded or became aware of the information. Both dimensions are required to support regulatory audit trails and Suspicious Matter Report evidence under the AUSTRAC AML/CTF Act 2006. +attributes: + Party Identifier: + type: string + identifier: primary + description: > + Globally unique surrogate identifier for this party across all systems and jurisdictions. Immutable once assigned. + + Legal Name: + type: string + pii: true + description: > + The full legal name of the party as it appears on the most recently verified official documentation. For individuals this is the full given and family name. For legal entities this is the registered name. + + Also Known As: + type: string[] + pii: true + description: > + Alternative names, aliases, trading names, or former names. Critical for watchlist screening — a party may appear on a sanctions or adverse media list under an alias rather than their current legal name. + + Party Status: + type: enum:Party Status + description: > + The current operational status of the party record within the institution. Controls whether new relationships may be established and flags parties under active investigation or restriction. + + Risk Rating: + type: enum:Financial Crime Risk Rating + description: > + The institution's current assessed ML/TF risk rating for this party, derived from the most recent CDD or EDD review. Drives transaction monitoring thresholds, review frequency, and the level of due diligence required under the AML/CTF Act 2006 Part B program obligations. + + Sanctions Screen Status: + type: enum:Sanctions Screen Status + description: > + The outcome of the most recent screening run against applicable consolidated sanctions lists. A potential or confirmed match must trigger an investigation before any designated service is provided or any + transaction is processed. + + Next Review Date: + type: date + description: > + The date by which the next periodic CDD review must be completed. Calculated from the Risk Rating in accordance with the institution's Part A OCDD program — high-risk parties annually, medium-risk every + two years, low-risk every three years. +``` + +```yaml +constraints: + Legal Name Required: + not_null: Legal Name + description: > + Every party must have a verified legal name before any designated service is provided. Anonymous parties cannot be onboarded under the AML/CTF Act 2006 Part B identification obligations. + + Review Date Must Not Be Overdue: + check: "Next Review Date >= Today OR Party Status == 'Under Review'" + description: > + A party whose Next Review Date has passed must be placed Under Review and must not be permitted to initiate new transactions until the CDD review is completed and a new review date is set. + + Confirmed Sanctions Match Blocks Service: + check: "Sanctions Screen Status != 'Confirmed Match'" + lifecycle_stage: Onboarding + description: > + A party with a confirmed sanctions match must not be onboarded or permitted to transact. This is a hard stop pending review by the Financial Crime Compliance Officer. +``` + +```yaml +governance: + pii: true + classification: Highly Confidential + retention: 10 years + retention_basis: Domain default retention aligned to AML/CTF record-keeping obligations + description: > + Minimum 7-year retention from the end of the business relationship, aligned to AUSTRAC record-keeping obligations under the AML/CTF Act 2006 and RBNZ AML/CFT Act 2009 section 58. + access_role: + - FINANCIAL_CRIME_ANALYST + - KYC_OFFICER + - COMPLIANCE_OFFICER + compliance_relevance: + - AUSTRAC AML/CTF Act 2006 + - AUSTRAC AML/CTF Amendment Act 2024 + - RBNZ AML/CFT Act 2009 + - APRA Prudential Standard CPS 234 + - FATF Recommendations 10, 11, 12 + regulatory_reporting: + - Suspicious Matter Report (SMR) — AUSTRAC + - Suspicious Transaction Report (STR) — RBNZ + - Threshold Transaction Report (TTR) — AUSTRAC + - International Funds Transfer Instruction (IFTI) — AUSTRAC +``` + +## Relationships + +### Party Assumes Roles + +A Party can assume one or more Party Roles across different business contexts. + +```yaml +source: Party +type: has +target: Party Role +cardinality: one-to-many +granularity: atomic +ownership: Party +``` + +### Party Has Contact Addresses + +A Party can have one or more contact addresses for communication and verification. + +```yaml +source: Party +type: has +target: Contact Address +cardinality: one-to-many +granularity: atomic +ownership: Party +``` + +### Party Related To Party + +A Party may be related to one or more other Parties through ownership, control, family, or association ties. This self-referential relationship is the structural basis for financial crime network analysis — establishing connections between parties for beneficial ownership mapping, PEP network screening, and suspicious activity investigation. + +A relationship instance represents a directional association with a named type (e.g., "Ultimate Beneficial Owner of", "Director of", "Spouse of"). The same pair of parties may have multiple association instances of different types simultaneously. + +```yaml +source: Party +type: related_to +target: Party +cardinality: many-to-many +granularity: atomic +ownership: Party +self_referential: true +relationship_attributes: + - Association Type (enum:Association Type) + - Association Start Date + - Association End Date + - Verified +``` diff --git a/financial_crime/entities/party_role.md b/financial_crime/entities/party_role.md new file mode 100644 index 0000000..78892cb --- /dev/null +++ b/financial_crime/entities/party_role.md @@ -0,0 +1,201 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Party Role + +A Party Role represents the specific capacity or context in which a Party participates in a business relationship with the institution. Aligned to the BIAN BOM, Party Role is the abstract concept that separates *who* a party is (the Party) from *what they do* in a given context (the Role). The same Party may hold multiple Party Roles simultaneously — a natural person may be a Customer on a home loan, a Payer on a standing order, and a Beneficial Owner of a company that holds a business account, all at the same time. + +In the Financial Crime domain, Party Role is significant because AML/CTF obligations attach to roles, not parties in isolation. The due diligence required for a Beneficial Owner differs from that required for an Account Holder, and the same individual may require different levels of scrutiny depending on which role they are assessed in. + +Party Role is abstract — it is never instantiated directly. All roles are expressed through specialisations: Customer, Merchant, Payer, Payee, Teller, Instructing Agent, and others defined in the domain. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class PartyRole{ + <> + * Role Identifier : string + Role Status : enum~PartyRoleStatus~ + Role Start Date : date + Role End Date : date + Due Diligence Status : enum~DDStatus~ + } + + class Customer + class Merchant + class Payee + class Payer + class Teller + class PaymentInitiator + + Customer --|> PartyRole + Merchant --|> PartyRole + Payee --|> PartyRole + Payer --|> PartyRole + Teller --|> PartyRole + PaymentInitiator --|> PartyRole + + PartyRole "1" <-- "0..*" Party : assumes + PartyRole "0..*" --> "0..*" ContactAddress : uses + PartyRole "0..*" --> "0..1" Agreement : governed by + + class PartyRoleStatus["Party Role Status"]{<>} + class DDStatus["Due Diligence Status"]{<>} + + class Party["Party"] + class ContactAddress["Contact Address"] + class Agreement["Agreement"] +``` + +```yaml +existence: independent +mutability: slowly_changing +temporal: + tracking: bitemporal + description: > + Bitemporal tracking captures both when the role was active in the real + world (valid time via Role Start Date and Role End Date) and when the + record was known to the system (transaction time). This supports + point-in-time queries required for regulatory audit — e.g. "was this + party a Beneficial Owner at the time of this transaction?" — while also + enabling reconstruction of what the system believed at any past moment, + critical for audit trail integrity and late-arriving corrections. +attributes: + Role Identifier: + type: string + identifier: primary + description: > + Globally unique surrogate identifier for this specific role instance. + Distinct from the Party Identifier — the same party holding two different + roles will have two Role Identifiers. + + Role Status: + type: enum:Party Role Status + description: > + The current lifecycle status of this role. Controls whether the role + confers active privileges (e.g., ability to transact) and flags roles + under review or restriction. + + Role Start Date: + type: date + description: > + The date from which this role became effective. Used to establish the + valid time period for regulatory audit and point-in-time queries. + + Role End Date: + type: date + description: > + The date on which this role ceased to be effective, if applicable. + A null value indicates the role is currently active. Roles must not be + deleted — they must be closed with a Role End Date to preserve audit + history. + + Due Diligence Status: + type: enum:Due Diligence Status + description: > + The completion status of the CDD or EDD obligations associated with + this specific role. AML/CTF obligations under the AML/CTF Act 2006 + and RBNZ AML/CFT Act 2009 attach at the role level — a party may + have completed CDD as a Customer but still require separate CDD + assessment as a Beneficial Owner of a related entity. +``` + +```yaml +constraints: + Role End After Start: + check: "Role End Date IS NULL OR Role End Date > Role Start Date" + description: > + A role's end date must be later than its start date. A null end date + indicates the role is currently open. + + Active Role Requires Complete Due Diligence: + check: > + Role Status != 'Active' + OR Due Diligence Status == 'Complete' + lifecycle_stage: Activation + description: > + A role must not be set to Active until the CDD or EDD obligations + associated with that role have been completed. This enforces the + AML/CTF Act 2006 obligation that designated services must not be + provided before identification and verification is complete. + + Closed Role Requires End Date: + check: > + Role Status != 'Closed' + OR Role End Date IS NOT NULL + description: > + A role with a status of Closed must have a Role End Date recorded. + This ensures the valid time period is always closed correctly for + audit and regulatory reporting purposes. +``` + +```yaml +governance: + pii: false + classification: Confidential + retention: 10 years + retention_basis: Domain default retention aligned to AML/CTF record-keeping obligations + description: > + Role records must be retained for 7 years from Role End Date, aligned + to AUSTRAC and RBNZ record-keeping obligations. Roles must never be + deleted — closure via Role End Date and Role Status is the only + permitted termination mechanism. + access_role: + - FINANCIAL_CRIME_ANALYST + - KYC_OFFICER + - COMPLIANCE_OFFICER + - RELATIONSHIP_MANAGER + compliance_relevance: + - AUSTRAC AML/CTF Act 2006 — Part B role-level identification obligations + - AUSTRAC AML/CTF Amendment Act 2024 + - RBNZ AML/CFT Act 2009 + - FATF Recommendation 10 — Customer Due Diligence +``` + +## Relationships + +### Party Role Uses Contact Addresses + +A Party Role may use one or more Contact Addresses owned by the underlying Party. + +```yaml +source: Party Role +type: associates_with +target: Contact Address +cardinality: many-to-many +granularity: atomic +ownership: Party Role +``` + +### Party Role Governed By Agreement + +A Party Role may be governed by a specific Agreement that defines obligations and permissions. + +```yaml +source: Party Role +type: references +target: Agreement +cardinality: many-to-one +granularity: atomic +ownership: Party Role +``` + +### Party Role At Point In Time + +Captures the state of a Party Role as it stood at a specific point in time. Used for regulatory snapshots — e.g. quarterly compliance reporting requires the role state as of the reporting date, not the current state. + +Party Role is declared bitemporal; this period-granularity relationship makes the temporal snapshot pattern explicit for physical generation, ensuring dimensional consumers can materialize point-in-time snapshots for regulatory reporting. + +```yaml +source: Party Role +type: snapshots +target: Party Role +cardinality: one-to-one +granularity: period +ownership: Party Role +self_referential: true +``` diff --git a/financial_crime/entities/payee.md b/financial_crime/entities/payee.md new file mode 100644 index 0000000..0fcca9a --- /dev/null +++ b/financial_crime/entities/payee.md @@ -0,0 +1,47 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Payee + +A Payee is a Party Role representing the recipient of funds in a transaction. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Payee{ + * Payee Identifier : string + Beneficiary Reference : string + } + + Payee --|> PartyRole + + class PartyRole["Party Role"] +``` + +```yaml +extends: Party Role +existence: independent +mutability: slowly_changing +attributes: + Payee Identifier: + type: string + identifier: primary + description: Unique identifier for the payee role instance. + + Beneficiary Reference: + type: string + description: Reference used to identify the beneficiary in payment instructions. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +No relationships are sourced directly from Payee. The canonical direction is Transaction-owned — see [Transaction Has Creditor](transaction.md#transaction-has-creditor). diff --git a/financial_crime/entities/payer.md b/financial_crime/entities/payer.md new file mode 100644 index 0000000..057653b --- /dev/null +++ b/financial_crime/entities/payer.md @@ -0,0 +1,47 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Payer + +A Payer is a Party Role representing the party from whom funds are debited in a transaction. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Payer{ + * Payer Identifier : string + Funding Source Reference : string + } + + Payer --|> PartyRole + + class PartyRole["Party Role"] +``` + +```yaml +extends: Party Role +existence: independent +mutability: slowly_changing +attributes: + Payer Identifier: + type: string + identifier: primary + description: Unique identifier for the payer role instance. + + Funding Source Reference: + type: string + description: Reference to the source account or instrument used to fund payments. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +No relationships are sourced directly from Payer. The canonical direction is Transaction-owned — see [Transaction Has Debtor](transaction.md#transaction-has-debtor). diff --git a/financial_crime/entities/payment_initiator.md b/financial_crime/entities/payment_initiator.md new file mode 100644 index 0000000..a6cac68 --- /dev/null +++ b/financial_crime/entities/payment_initiator.md @@ -0,0 +1,47 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Payment Initiator + +A Payment Initiator is a Party Role representing the party that instructs or initiates a transaction. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class PaymentInitiator{ + * Payment Initiator Identifier : string + Initiation Channel : string + } + + PaymentInitiator --|> PartyRole + + class PartyRole["Party Role"] +``` + +```yaml +extends: Party Role +existence: independent +mutability: slowly_changing +attributes: + Payment Initiator Identifier: + type: string + identifier: primary + description: Unique identifier for the payment initiator role instance. + + Initiation Channel: + type: string + description: Channel used to initiate payment instructions. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +No relationships are sourced directly from Payment Initiator. The canonical direction is Transaction-owned — see [Transaction Initiated By Instructing Agent](transaction.md#transaction-initiated-by-instructing-agent). diff --git a/financial_crime/entities/person.md b/financial_crime/entities/person.md new file mode 100644 index 0000000..7a615e6 --- /dev/null +++ b/financial_crime/entities/person.md @@ -0,0 +1,115 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Person + +A Person is a natural person who has, or may have, a financial relationship with the institution. Person specialises Party, inheriting all universal party attributes and adding those that are specific to a natural person — including identity verification details, biographic attributes, and PEP status. + +Under the AUSTRAC AML/CTF Act 2006 and RBNZ AML/CFT Act 2009, the identification and verification requirements for individuals differ materially from those for legal entities, making Person a distinct and necessary specialisation. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Party{ + <> + } + + class Person{ + Date of Birth : date + Country of Birth : string + Nationality : string[] + Country of Residence : string + Politically Exposed Person Status : enum~PEPStatus~ + } + + Person --|> Party + + class PEPStatus["PEP Status"]{<>} + + class Party["Party"] +``` + +```yaml +extends: Party +existence: independent +mutability: slowly_changing +attributes: + Date of Birth: + type: date + pii: true + description: > + The individual's date of birth as recorded on their primary identity document. Required for identity verification under the AML/CTF Act 2006 Part B customer identification program. + + Country of Birth: + type: string + pii: true + description: > + The country in which the individual was born. Used as a supplementary identity verification attribute and as an input to jurisdiction risk scoring. + + Nationality: + type: string[1..*] + pii: true + description: > + One or more nationalities held by the individual. A key factor in sanctions jurisdiction screening — an individual may hold nationality in a sanctioned country while residing elsewhere. + + Country of Residence: + type: string + pii: true + description: > + The country in which the individual currently resides. Used for jurisdiction risk scoring, FATF high-risk country screening, and determining applicable CDD obligations. + + Politically Exposed Person Status: + type: enum:PEP Status + description: > + Indicates whether the individual is a Politically Exposed Person, a close associate of a PEP, or a family member of a PEP. PEP status applies only to natural persons. Triggers mandatory Enhanced Customer Due Diligence under the AML/CTF Act 2006 and RBNZ AML/CFT Act 2009 section 22. +``` + +```yaml +constraints: + Date of Birth Required at Onboarding: + not_null: Date of Birth + lifecycle_stage: Onboarding + description: > + Date of birth is a mandatory identification attribute for individuals under the AML/CTF Act 2006 Part B customer identification obligations. It must be collected and verified before any designated service is provided. + + PEP Requires High Risk Rating: + check: > + Politically Exposed Person Status == 'Not PEP' + OR Risk Rating IN ('High', 'Very High') + description: > + Any individual identified as a PEP or PEP associate must be assigned a + minimum Risk Rating of High, inherited from Party. This triggers + Enhanced Customer Due Diligence obligations under the AML/CTF Act 2006 + and RBNZ AML/CFT Act 2009. The Risk Rating constraint is evaluated + against the inherited attribute. +``` + +```yaml +governance: + pii: true + classification: Highly Confidential + retention: 10 years + retention_basis: Domain default retention aligned to AML/CTF record-keeping obligations + description: > + 10-year retention from the end of the business relationship, aligned to + AUSTRAC and RBNZ record-keeping obligations. The regulatory minimum is 7 + years under AUSTRAC AML/CTF Act 2006; the domain default of 10 years is + applied as the conservative standard. + access_role: + - FINANCIAL_CRIME_ANALYST + - KYC_OFFICER + - COMPLIANCE_OFFICER + compliance_relevance: + - AUSTRAC AML/CTF Act 2006 — Part B Individual Identification + - AUSTRAC AML/CTF Amendment Act 2024 + - RBNZ AML/CFT Act 2009 — sections 14, 22 + - FATF Recommendation 12 — Politically Exposed Persons +``` + +## Relationships + +No relationships are sourced directly from Person in the current domain model. diff --git a/financial_crime/entities/product.md b/financial_crime/entities/product.md new file mode 100644 index 0000000..95a9acf --- /dev/null +++ b/financial_crime/entities/product.md @@ -0,0 +1,64 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Product + +A Product is a financial offering whose terms are governed by an agreement and used by one or more accounts. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Product{ + * Product Identifier : string + Product Name : string + Product Category : string + } + + Product "0..*" --> "1" Agreement : in terms of + Account "0..*" --> "1" Product : holds + + class Agreement["Agreement"] + class Account["Account"] +``` + +```yaml +existence: independent +mutability: slowly_changing +attributes: + Product Identifier: + type: string + identifier: primary + description: Unique identifier of the product definition. + + Product Name: + type: string + description: Business name of the product offering. + + Product Category: + type: string + description: Product classification used for reporting and controls. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +### Product In Terms Of Agreement + +A Product is defined in terms of an Agreement template or contractual framework. + +```yaml +source: Product +type: references +target: Agreement +cardinality: many-to-one +granularity: atomic +ownership: Product +``` diff --git a/financial_crime/entities/teller.md b/financial_crime/entities/teller.md new file mode 100644 index 0000000..ca93e5e --- /dev/null +++ b/financial_crime/entities/teller.md @@ -0,0 +1,80 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Teller + +A Teller is a Party Role representing a bank employee who processes branch-based customer transactions. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Teller{ + * Teller Identifier : string + Employee Number : string + Assigned Branch Identifier : string + } + + Teller --|> PartyRole + Teller "1" --> "0..*" Transaction : processes + Teller "0..*" --> "1" Branch : assigned to + + class PartyRole["Party Role"] + class Transaction["Transaction"] + class Branch["Branch"] +``` + +```yaml +extends: Party Role +existence: independent +mutability: slowly_changing +attributes: + Teller Identifier: + type: string + identifier: primary + description: Unique identifier for the teller role instance. + + Employee Number: + type: string + description: Internal identifier of the staff member acting as teller. + + Assigned Branch Identifier: + type: string + description: Branch identifier where the teller is primarily assigned. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +### Teller Processes Transaction + +A Teller can process one or more branch-mediated Transactions. + +```yaml +source: Teller +type: associates_with +target: Transaction +cardinality: one-to-many +granularity: atomic +ownership: Teller +``` + +### Teller Assigned To Branch + +A Teller is assigned to a Branch for operational responsibilities. + +```yaml +source: Teller +type: assigned_to +target: Branch +cardinality: many-to-one +granularity: atomic +ownership: Teller +``` diff --git a/financial_crime/entities/term-deposit-agreement.md b/financial_crime/entities/term-deposit-agreement.md new file mode 100644 index 0000000..b77c6a9 --- /dev/null +++ b/financial_crime/entities/term-deposit-agreement.md @@ -0,0 +1,51 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Term Deposit Agreement + +A Term Deposit Agreement is a specialised agreement defining fixed-term deposit terms. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class TermDepositAgreement{ + Deposit Amount : decimal + Term Length Months : integer + Interest Rate : decimal + } + + TermDepositAgreement --|> Agreement + + class Agreement["Agreement"] +``` + +```yaml +extends: Agreement +existence: independent +mutability: slowly_changing +attributes: + Deposit Amount: + type: decimal + description: Principal amount committed to the term deposit. + + Term Length Months: + type: integer + description: Fixed term length in months. + + Interest Rate: + type: decimal + description: Contracted annual interest rate for the term. +``` + +```yaml +governance: + retention_basis: Inherited from domain default retention of 10 years post relationship end for AML/CTF record-keeping +``` + +## Relationships + +No relationships are sourced directly from Term Deposit Agreement in the current domain model. diff --git a/financial_crime/entities/transaction.md b/financial_crime/entities/transaction.md new file mode 100644 index 0000000..7a1d36d --- /dev/null +++ b/financial_crime/entities/transaction.md @@ -0,0 +1,234 @@ +# [Financial Crime](../domain.md) + +## Entities + +### Transaction + +A Transaction represents a movement of value between parties and accounts. It is the primary fact entity in the Financial Crime domain — it carries the monetary amount, the timing, the mechanism, and the parties and accounts involved. Every transaction must be monitorable for suspicious activity patterns. + +In a dimensional model, Transaction is the central fact table grain. Currency, Account (debit and credit), Channel, Type, Status, and temporal attributes are the dimension keys required for transaction risk analytics. +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Transaction{ + * Transaction Identifier : string + Transaction Date Time : datetime + Settlement Date Time : datetime + Amount : decimal + Transaction Type : enum~TransactionType~ + Transaction Channel : enum~TransactionChannel~ + Transaction Status : enum~TransactionStatus~ + Reference : string + } + + Transaction "0..*" --> "1" Payer : has debtor + Transaction "0..*" --> "1" Payee : has creditor + Transaction "0..*" --> "0..1" PaymentInitiator : initiated by + Transaction "0..*" --> "1" Currency : denominated in + Transaction "0..*" --> "0..1" Account : debits + Transaction "0..*" --> "0..1" Account : credits + + class TransactionType["Transaction Type"]{<>} + class TransactionChannel["Transaction Channel"]{<>} + class TransactionStatus["Transaction Status"]{<>} + class Payer["Payer"] + class Payee["Payee"] + class PaymentInitiator["Payment Initiator"] + class Currency["Currency"] + class Account["Account"] +``` + +```yaml +existence: dependent +mutability: append_only +temporal: + tracking: transaction_time + description: > + Transactions are append-only records. Once settled they must not be modified. + Transaction time captures when the institution recorded the transaction. + Reversals are recorded as new transaction records referencing the original + Transaction Identifier, not as updates to the original. +attributes: + Transaction Identifier: + type: string + identifier: primary + description: > + Globally unique identifier for the transaction event. Immutable once assigned. + For reversed transactions, the reversal carries its own identifier and references + this identifier in the Reference field. + + Transaction Date Time: + type: datetime + description: > + Timestamp when the transaction was initiated or instructed by the originating party. + Used as the primary event time for transaction monitoring rule evaluation. + + Settlement Date Time: + type: datetime + description: > + Timestamp when final, irrevocable settlement occurred. May differ from Transaction Date + Time for transactions that clear across business days or time zones. Required for + accurate AML typology detection (e.g., structuring detection across settlement windows). + + Amount: + type: decimal + description: > + Monetary value moved by the transaction, expressed in the denomination currency. + Always positive — direction (debit or credit) is determined by the Payer and Payee + relationships, not by sign. + + Transaction Type: + type: enum:Transaction Type + description: > + The mechanism by which the transaction was processed (e.g., Wire Transfer, EFTPOS, + ATM Withdrawal, Direct Debit). Used for transaction monitoring rule segmentation — + different typologies apply to different transaction types. + + Transaction Channel: + type: enum:Transaction Channel + description: > + The channel or medium through which the transaction was initiated. Used in conjunction + with Transaction Type for risk rule evaluation — a high-value cash deposit at branch + carries different risk signals than the same amount via online banking. + + Transaction Status: + type: enum:Transaction Status + description: > + The current lifecycle state of the transaction. Under Review status suspends settlement + and is set by the transaction monitoring system. Failed and Reversed transactions must + be retained for AML audit — absence of completed transactions is itself a monitoring signal. + + Reference: + type: string + description: > + Free-form transaction reference text as supplied by the initiating party. May contain + invoice numbers, payment descriptions, or, in the case of reversals, the original + Transaction Identifier being reversed. +``` +```yaml +constraints: + Settlement After Initiation: + check: "Settlement Date Time IS NULL OR Settlement Date Time >= Transaction Date Time" + description: > + Settlement cannot precede initiation. A null Settlement Date Time indicates the + transaction has not yet settled (e.g., Pending, Authorised, or Under Review status). + + Settled Transaction Has Settlement Time: + check: > + Transaction Status != 'Settled' + OR Settlement Date Time IS NOT NULL + description: > + A transaction with status Settled must have a Settlement Date Time recorded. + + Amount Must Be Positive: + check: "Amount > 0" + description: > + Transaction amounts are always expressed as positive values. Direction is conveyed + by the Payer (debit) and Payee (credit) relationships. +``` + +```yaml +governance: + pii: false + classification: Highly Confidential + retention: 10 years + retention_basis: Domain default retention aligned to AML/CTF record-keeping obligations + description: > + Transaction records must be retained for 7 years from the transaction date, aligned + to AUSTRAC AML/CTF Act 2006 record-keeping obligations. Records are append-only and + must never be modified or deleted. Reversals are represented as new records. + access_role: + - FINANCIAL_CRIME_ANALYST + - TRANSACTION_MONITORING_SYSTEM + - COMPLIANCE_OFFICER + compliance_relevance: + - AUSTRAC AML/CTF Act 2006 — Part B transaction record-keeping + - AUSTRAC AML/CTF Amendment Act 2024 + - RBNZ AML/CFT Act 2009 — section 58 + - FATF Recommendation 10 — Customer Due Diligence (transaction context) + - FATF Recommendation 16 — Wire Transfers (for SWIFT and Wire Transfer types) + regulatory_reporting: + - Threshold Transaction Report (TTR) — AUSTRAC (cash transactions >= AUD 10,000) + - International Funds Transfer Instruction (IFTI) — AUSTRAC + - Suspicious Matter Report (SMR) — AUSTRAC +``` + +## Relationships + +### Transaction Has Debtor + +A Transaction has one Payer representing the party from whom funds are debited. + +```yaml +source: Transaction +type: has +target: Payer +cardinality: one-to-many +granularity: atomic +ownership: Transaction +``` + +### Transaction Has Creditor + +A Transaction has one Payee representing the party to whom funds are credited. + +```yaml +source: Transaction +type: has +target: Payee +cardinality: one-to-many +granularity: atomic +ownership: Transaction +``` + +### Transaction Initiated By Instructing Agent + +A Transaction may be initiated by one Payment Initiator acting as instructing agent on behalf of the originating party. + +```yaml +source: Transaction +type: references +target: Payment Initiator +cardinality: many-to-one +granularity: atomic +ownership: Transaction +``` + +### Transaction Denominated In Currency +A Transaction is denominated in exactly one Currency. + +```yaml +source: Transaction +type: references +target: Currency +cardinality: many-to-one +granularity: atomic +ownership: Transaction +``` + +### Transaction Has Debit Account +The internal account from which funds are debited. Null for transactions where the debit side is an external counterparty account not held at the institution (e.g., inbound wire transfers). + +```yaml +source: Transaction +type: references +target: Account +cardinality: many-to-one +granularity: atomic +ownership: Transaction +``` + +### Transaction Has Credit Account +The internal account to which funds are credited. Null for transactions where the credit side is an external counterparty account not held at the institution (e.g., outbound wire transfers). + +```yaml +source: Transaction +type: references +target: Account +cardinality: many-to-one +granularity: atomic +ownership: Transaction +``` diff --git a/financial_crime/enums.md b/financial_crime/enums.md new file mode 100644 index 0000000..154630f --- /dev/null +++ b/financial_crime/enums.md @@ -0,0 +1,464 @@ +# [Financial Crime](domain.md) + +## Enums + +### Party Status + +Operational status of a party record. + +```yaml +values: + Active: + description: Party is active and may participate in relationships and transactions + Under Review: + description: Party is under compliance review and must not initiate new transactions + Restricted: + description: Party is restricted pending investigation outcome or risk remediation + Inactive: + description: Party relationship is dormant or no longer operational + Closed: + description: Party record is closed and no new activity is permitted +``` + +--- + +### Financial Crime Risk Rating + +Institution-assessed ML/TF risk level for a party. + +```yaml +values: + Low: + description: Low financial crime risk profile + score: 1 + Medium: + description: Moderate financial crime risk profile + score: 2 + High: + description: Elevated financial crime risk requiring enhanced controls + score: 3 + Very High: + description: Severe financial crime risk requiring intensive monitoring + score: 4 +``` + +--- + +### Sanctions Screen Status + +Outcome of sanctions screening checks. + +```yaml +values: + Not Screened: + description: Screening has not yet been performed + Clear: + description: No match found against applicable sanctions lists + Potential Match: + description: Possible match detected and pending investigation + Confirmed Match: + description: Match confirmed and services must be blocked + False Positive: + description: Potential match reviewed and cleared as not the sanctioned subject +``` + +--- + +### Party Role Status + +Lifecycle status of a specific party role instance. + +```yaml +values: + Pending: + description: Role created but not yet activated + Active: + description: Role is active and confers its business permissions + Suspended: + description: Role is temporarily paused pending review or remediation + Closed: + description: Role has ended and is no longer effective +``` + +--- + +### Due Diligence Status + +CDD/EDD completion state for a role. + +```yaml +values: + Not Started: + description: Due diligence process has not commenced + In Progress: + description: Due diligence activities are underway + Complete: + description: Required due diligence is complete + Expired: + description: Prior due diligence is no longer current and revalidation is required + Failed: + description: Due diligence failed and role must not be activated +``` + +--- + +### PEP Status + +Politically exposed person classification for individuals. + +```yaml +values: + Not PEP: + description: Individual is not identified as a politically exposed person or associate + Domestic PEP: + description: Individual holds a prominent public function domestically + Foreign PEP: + description: Individual holds a prominent public function in a foreign jurisdiction + International Organisation PEP: + description: Individual holds a prominent function in an international organisation + PEP Family Member: + description: Individual is an immediate family member of a PEP + PEP Close Associate: + description: Individual is a known close associate of a PEP +``` + +--- + +### Address Type + +Structural type of an address record. + +```yaml +values: + Street Address: + description: Standard physical street address + PO Box: + description: Postal box address + Registered Office: + description: Registered office address for a legal entity + Overseas Address: + description: Address located outside the primary operating jurisdiction +``` + +--- + +### Address Purpose + +Business purpose for which a party uses an address. + +```yaml +values: + Residential: + description: Residential/home address for an individual + Mailing: + description: Address used for correspondence + Business: + description: Primary business operating address + Registered Office: + description: Registered legal address for a company + Billing: + description: Address used for billing and statement delivery +``` + +--- + +### Address Verification Status + +Current verification state of a contact address association. + +```yaml +values: + Unverified: + description: Address not yet verified + Verified: + description: Address verified against acceptable evidence + Pending Reverification: + description: Existing verification is aging and requires refresh + Rejected: + description: Verification attempt failed or evidence was insufficient + Expired: + description: Verification is out of date and not valid for current obligations +``` + +--- + +### Verification Method + +Method used to verify an address. + +```yaml +values: + Documentary: + description: Verified using accepted documentary evidence + Electronic: + description: Verified using an approved electronic data source + In Person: + description: Verified face-to-face at branch or approved representative location + Third Party Attestation: + description: Verified via trusted attestation from an approved third party +``` + +--- + +### Currency Code + +ISO 4217 alphabetic currency codes used across account balances and transaction amounts. This is a representative subset — the full ISO 4217 code list is the normative reference. + +Standard: [ISO 4217 — Currency codes](https://www.iso.org/iso-4217-currency-codes.html) + +```yaml +values: + AUD: + description: Australian Dollar + NZD: + description: New Zealand Dollar + USD: + description: United States Dollar + EUR: + description: Euro + GBP: + description: Pound Sterling + JPY: + description: Japanese Yen + SGD: + description: Singapore Dollar + HKD: + description: Hong Kong Dollar + CHF: + description: Swiss Franc + CAD: + description: Canadian Dollar +``` + +--- +### Transaction Type +Category of financial transaction representing the movement mechanism and clearing pathway. +```yaml +values: + Wire Transfer: + description: Domestic or international credit transfer processed via the local RTGS or wire network + SWIFT Transfer: + description: International payment instruction transmitted via the SWIFT network + EFTPOS: + description: Electronic funds transfer at point of sale via a card-present terminal + ATM Withdrawal: + description: Cash withdrawal initiated at an automated teller machine + ATM Deposit: + description: Cash or cheque deposit lodged at an automated teller machine + Direct Debit: + description: Pull payment authorised by the account holder and initiated by a creditor + Direct Credit: + description: Push payment credited directly to a beneficiary account + Internal Transfer: + description: Movement of funds between two accounts held within the same institution + BPay: + description: Bill payment processed through the BPAY scheme + Cash Deposit: + description: Physical cash lodged to an account at a branch or agency + Cash Withdrawal: + description: Physical cash disbursed from an account at a branch or agency + Cheque: + description: Payment by paper cheque presented for clearing + RTGS: + description: High-value same-day settlement payment processed through the Real-Time Gross Settlement system +``` +--- +### Transaction Status +Lifecycle state of a transaction from initiation through to final settlement or failure. +```yaml +values: + Pending: + description: Transaction has been submitted and is awaiting authorisation or clearing + Authorised: + description: Transaction has been authorised but not yet cleared or settled + Cleared: + description: Transaction has been cleared through the relevant payment scheme + Settled: + description: Final irrevocable settlement has occurred and funds have moved + Failed: + description: Transaction could not be processed and no funds were moved + Reversed: + description: A previously settled transaction has been reversed and funds returned + Cancelled: + description: Transaction was cancelled before settlement at the request of an authorised party + Under Review: + description: Transaction has been flagged and is under financial crime or fraud review; settlement is suspended pending outcome +``` +--- +### Transaction Channel +The channel or medium through which a transaction was initiated or processed. +```yaml +values: + Branch: + description: Transaction initiated or processed at a physical branch location by a teller or officer + Online Banking: + description: Transaction initiated by a customer through the institution's internet banking platform + Mobile Banking: + description: Transaction initiated by a customer through the institution's mobile application + ATM: + description: Transaction self-served by a customer at an automated teller machine + EFTPOS Terminal: + description: Transaction initiated at a merchant point-of-sale EFTPOS terminal + SWIFT: + description: Payment instruction transmitted via the SWIFT network, typically for international transfers + Direct Entry: + description: Batch payment submitted directly via the Direct Entry (DE) system + Third Party: + description: Transaction initiated by an authorised third party or payment service provider on behalf of a customer + Internal System: + description: Transaction generated automatically by an internal system process (e.g., interest capitalisation, fee deduction) +``` +--- +### Account Status +Operational lifecycle state of an account. +```yaml +values: + Pending: + description: Account has been created but has not yet been activated for transacting + Active: + description: Account is open and available for deposits, withdrawals, and other permitted operations + Dormant: + description: Account has had no customer-initiated transactions for an extended period as defined by the institution's dormancy policy + Frozen: + description: Account has been administratively frozen; no debits or credits are permitted pending investigation or regulatory instruction + Suspended: + description: Account is temporarily suspended pending compliance review or fraud investigation + Closed: + description: Account has been permanently closed; no further transactions are permitted +``` +--- +### Account Type +Classification of the financial account by its primary purpose and product characteristics. +```yaml +values: + Savings: + description: Interest-bearing deposit account intended for accumulation of funds + Current: + description: Transaction account (also called cheque or demand deposit account) used for day-to-day banking + Term Deposit: + description: Fixed-term deposit account where funds are locked in for a specified period at a contracted interest rate + Loan: + description: Credit account representing a disbursed loan obligation + Line Of Credit: + description: Revolving credit facility where the customer may draw down and repay up to an approved limit + Mortgage: + description: Secured loan account used to finance the purchase of real property + Offset: + description: Transaction account linked to a mortgage, where the balance offsets interest charged on the mortgage + Foreign Currency: + description: Account denominated in a currency other than the institution's base currency +``` +--- +### Agreement Status +Lifecycle state of a formal agreement between the institution and a party. +```yaml +values: + Draft: + description: Agreement has been created but not yet executed by all required parties + Active: + description: Agreement is fully executed and currently in force + Suspended: + description: Agreement is temporarily suspended by one or both parties pending resolution of a dispute or compliance matter + Matured: + description: Agreement has reached its scheduled maturity date and obligations have been fulfilled + Terminated: + description: Agreement has been terminated before its natural maturity, by mutual consent or by one party under agreed termination rights + Cancelled: + description: Agreement was cancelled before it became active and never took effect +``` +--- +### Contact Preference +The customer's preferred channel for outbound communication from the institution. +```yaml +values: + Email: + description: Customer prefers communications delivered by email + SMS: + description: Customer prefers short message service (text message) notifications + Post: + description: Customer prefers physical mail correspondence + Phone: + description: Customer prefers to be contacted by telephone call + In App: + description: Customer prefers push notifications and messages delivered within the institution's mobile application + No Contact: + description: Customer has opted out of all non-essential outbound communications +``` +--- +### Company Legal Structure +The legal form under which a company or organisation is incorporated or constituted. Relevant to AML risk assessment — certain structures (trusts, shell companies, special purpose vehicles) carry elevated money-laundering risk due to opacity of beneficial ownership. +```yaml +values: + Private Company: + description: Privately held limited liability company not listed on a public exchange (e.g., Pty Ltd, Ltd) + Public Company: + description: Company whose shares are listed and traded on a public stock exchange + Partnership: + description: Business entity where two or more persons carry on a business in common with a view to profit + Sole Trader: + description: Unincorporated individual carrying on business in their own name + Trust: + description: Legal arrangement where a trustee holds assets for the benefit of beneficiaries; elevated AML risk due to beneficial ownership complexity + Cooperative: + description: Member-owned and democratically controlled organisation + Government Entity: + description: Entity owned or controlled by a national, state, or local government + Non-Profit: + description: Organisation operating for a purpose other than profit; subject to specific AML obligations due to potential misuse for terrorist financing + Foreign Entity: + description: Entity incorporated or constituted under the laws of a foreign jurisdiction + Special Purpose Vehicle: + description: Entity created for a specific, narrow purpose (e.g., asset securitisation); elevated AML scrutiny due to potential use in layering structures +``` +--- +### Association Type +The nature of the relationship between two Parties in the Party Related To Party network. Used for beneficial ownership mapping, PEP network screening, and suspicious activity investigation. +```yaml +values: + Ultimate Beneficial Owner: + description: Individual who ultimately owns or controls 25% or more of the entity, directly or indirectly + Beneficial Owner: + description: Individual with a beneficial interest in the entity below the UBO threshold but still subject to disclosure obligations + Director: + description: Individual appointed to the board of directors or equivalent governing body + Shareholder: + description: Entity or individual holding shares in a company + Guarantor: + description: Party who has provided a guarantee for another party's obligations + Controlling Person: + description: Individual who exercises control over an entity through means other than shareholding (e.g., power of attorney, contractual rights) + Spouse: + description: Legal spouse or domestic partner; relevant for PEP close associate screening + Parent: + description: Direct parent of an individual; relevant for PEP family member screening + Child: + description: Direct child of an individual; relevant for PEP family member screening + Sibling: + description: Sibling of an individual; relevant for PEP family member screening + Close Associate: + description: Individual known to be in close personal or professional relationship with a PEP + Employer: + description: Organisation that employs the related party + Employee: + description: Individual employed by the related organisation + Signatory: + description: Individual authorised to sign on behalf of an entity +``` +--- +### Account Holder Type +The nature of a customer's holding relationship with an account. Used as a relationship attribute on Customer Holds Account. +```yaml +values: + Primary Holder: + description: The principal account owner responsible for the account relationship + Joint Holder: + description: Co-owner of the account with equal rights to the Primary Holder + Signatory: + description: Individual authorised to transact on the account but without ownership rights + Beneficial Owner: + description: Individual who benefits from the account but whose name may not appear as a legal owner + Guardian: + description: Individual acting as legal guardian for a minor or incapacitated account holder + Attorney: + description: Individual holding power of attorney to operate the account on behalf of the primary holder +``` diff --git a/financial_crime/events/account-status-changed.md b/financial_crime/events/account-status-changed.md new file mode 100644 index 0000000..0aac185 --- /dev/null +++ b/financial_crime/events/account-status-changed.md @@ -0,0 +1,22 @@ +# [Financial Crime](../domain.md) + +## Events + +### Account Status Changed + +Emitted when an account status changes in a way that affects operational behavior. + +```yaml +actor: Customer +entity: Account +emitted_on: + - update +business_meaning: The account lifecycle status has changed and associated controls may differ +downstream_impact: + - Transaction permissions and servicing controls may be recalculated + - Compliance and customer notification workflows may be initiated +attributes: + event_timestamp: + type: datetime + description: Time the account status change was recorded +``` diff --git a/financial_crime/events/agreement-activated.md b/financial_crime/events/agreement-activated.md new file mode 100644 index 0000000..3626087 --- /dev/null +++ b/financial_crime/events/agreement-activated.md @@ -0,0 +1,22 @@ +# [Financial Crime](../domain.md) + +## Events + +### Agreement Activated + +Emitted when an agreement becomes active and enforceable. + +```yaml +actor: Party Role +entity: Agreement +emitted_on: + - update +business_meaning: Contractual terms are now active and govern in-scope roles and products +downstream_impact: + - Product and servicing rules can be applied under the active agreement + - Contract lifecycle and compliance timelines are started +attributes: + event_timestamp: + type: datetime + description: Time the agreement became active +``` diff --git a/financial_crime/events/customer-onboarded.md b/financial_crime/events/customer-onboarded.md new file mode 100644 index 0000000..58c516e --- /dev/null +++ b/financial_crime/events/customer-onboarded.md @@ -0,0 +1,22 @@ +# [Financial Crime](../domain.md) + +## Events + +### Customer Onboarded + +Emitted when a new Customer relationship is established and approved for service. + +```yaml +actor: Party +entity: Customer +emitted_on: + - create +business_meaning: A party has completed onboarding and is now recognized as a customer +downstream_impact: + - Customer profile is activated for product and service interactions + - Ongoing due diligence schedule is initialized +attributes: + event_timestamp: + type: datetime + description: Time the customer onboarding status became active +``` diff --git a/financial_crime/events/high-risk-transaction-detected.md b/financial_crime/events/high-risk-transaction-detected.md new file mode 100644 index 0000000..e2e6ad4 --- /dev/null +++ b/financial_crime/events/high-risk-transaction-detected.md @@ -0,0 +1,37 @@ +# [Financial Crime](../domain.md) + +## Events + +### High Risk Transaction Detected + +Emitted when transaction monitoring identifies a transaction with high financial crime risk indicators. + +```yaml +actor: Transaction Monitoring System +entity: Transaction +emitted_on: + - create +business_meaning: A transaction has been identified for enhanced investigation due to elevated risk +downstream_impact: + - Alert and case management workflows are initiated + - Potential reporting obligations may be triggered +attributes: + event_timestamp: + type: datetime + description: Time the high-risk detection decision was produced + alert_type: + type: string + description: Category of the detection rule that triggered (e.g. structuring, velocity, geographic) + severity: + type: string + description: Assessed severity level of the alert (low, medium, high, critical) + detection_method: + type: string + description: Name of the detection model or rule that produced the alert + threshold_breached: + type: string + description: The specific threshold or limit that was exceeded + risk_score: + type: decimal + description: Numeric risk score assigned by the detection engine +``` diff --git a/financial_crime/events/kyc-status-updated.md b/financial_crime/events/kyc-status-updated.md new file mode 100644 index 0000000..e8d402a --- /dev/null +++ b/financial_crime/events/kyc-status-updated.md @@ -0,0 +1,22 @@ +# [Financial Crime](../domain.md) + +## Events + +### KYC Status Updated + +Emitted when the KYC status of a Party is updated following review or remediation. + +```yaml +actor: Compliance Officer +entity: Party +emitted_on: + - update +business_meaning: The due diligence posture of a party has changed and risk treatment may be updated +downstream_impact: + - Risk scoring and monitoring thresholds may be recalculated + - Relationship restrictions or approvals may be adjusted +attributes: + event_timestamp: + type: datetime + description: Time the KYC status update took effect +``` diff --git a/financial_crime/events/party-role-assigned.md b/financial_crime/events/party-role-assigned.md new file mode 100644 index 0000000..c829332 --- /dev/null +++ b/financial_crime/events/party-role-assigned.md @@ -0,0 +1,22 @@ +# [Financial Crime](../domain.md) + +## Events + +### Party Role Assigned + +Emitted when a Party assumes a new Party Role in a business context. + +```yaml +actor: Party +entity: Party Role +emitted_on: + - create +business_meaning: A party has been assigned a role that changes participation context and controls +downstream_impact: + - Access and entitlements can be updated for the new role + - Role-based monitoring and due diligence workflows are triggered +attributes: + event_timestamp: + type: datetime + description: Time the role assignment became effective +``` diff --git a/financial_crime/events/transaction-executed.md b/financial_crime/events/transaction-executed.md new file mode 100644 index 0000000..899fe2e --- /dev/null +++ b/financial_crime/events/transaction-executed.md @@ -0,0 +1,37 @@ +# [Financial Crime](../domain.md) + +## Events + +### Transaction Executed + +Emitted when a financial transaction is successfully executed. + +```yaml +actor: Payment Initiator +entity: Transaction +emitted_on: + - create +business_meaning: Funds movement has been executed and committed as a business transaction +downstream_impact: + - Ledger and balance updates are triggered + - Transaction monitoring and screening pipelines are triggered +attributes: + event_timestamp: + type: datetime + description: Time the transaction was executed + amount: + type: decimal + description: Monetary value moved by the transaction + currency_code: + type: string + description: ISO 4217 currency code of the transaction amount + payer_role_identifier: + type: string + description: Role identifier of the payer party + payee_role_identifier: + type: string + description: Role identifier of the payee party + channel: + type: string + description: Channel through which the transaction was initiated (e.g. branch, online, mobile) +``` diff --git a/financial_crime/handoff-to-artifact.md b/financial_crime/handoff-to-artifact.md new file mode 100644 index 0000000..f21e42e --- /dev/null +++ b/financial_crime/handoff-to-artifact.md @@ -0,0 +1,40 @@ +--- +from: agent-ontology +to: agent-artifact +domain: Financial Crime +domain_path: examples/Financial Crime/domain.md +created: 2026-03-14 +status: archived +--- + +## Handoff Context — Agent Ontology → Agent Artifact + +**Scope:** Party, Person, Company, Party Role, Contact Address, Address + +**Key decisions:** +- Party uses bitemporal tracking — both valid-time and transaction-time + required. AUSTRAC audit trail obligation drives this, not a modelling + preference. Do not collapse to append-only. +- Company is a concrete specialisation of Party, not abstract. Enables direct + instantiation without requiring a subtype record. +- self_referential relationship on Party carries relationship_attributes as + edge properties (Association Type, Verified). Generate a bridge table for + the many-to-many, not a FK on Party. + +**Rejected alternatives:** +- Separate PartyAssociation entity — creates unnecessary indirection for + graph traversal and was explicitly rejected by the domain owner. +- Append-only mutability for Party — rejected because AUSTRAC requires + point-in-time reconstruction, not just event log replay. + +**Do not re-open:** +- Risk Rating and Sanctions Screen Status are enums, not FK lookups. +- Next Review Date does not require a NOT NULL constraint — it is calculated + and will be null at creation. +- The Financial Crime domain classification is Highly Confidential throughout. + No override discussion needed. + +**Task for next agent:** +Generate dimensional DDL for Party, Person, Company, Party Role, Contact +Address, and Address. Target: Snowflake. Apply SCD Type 2 for slowly_changing +entities. Party Identifier is the natural primary key — no surrogate needed. diff --git a/financial_crime/products/analytics.md b/financial_crime/products/analytics.md new file mode 100644 index 0000000..534ccbf --- /dev/null +++ b/financial_crime/products/analytics.md @@ -0,0 +1,116 @@ +# [Financial Crime](../domain.md) + +## Data Products + +### Transaction Risk Summary + +A denormalized wide-column view combining transaction details with party +identity, account context, and risk indicators for the financial crime +analytics team. Designed for high-volume scan queries without joins. + +```yaml +class: consumer-aligned +schema_type: wide-column +owner: fincrime.analytics@bank.com +consumers: + - Financial Crime Analytics + - Transaction Monitoring Dashboard +status: Active +version: "1.0.0" + +entities: + - Transaction Risk Summary + +lineage: + - domain: Financial Crime + entities: + - Transaction + - Party + - Person + - Party Role + - Payer + - Payee + - Customer + - Account + - Branch + +governance: + classification: Highly Confidential + pii: true + retention: "10 years" + masking: + - attribute: "Date of Birth" + strategy: year-only + - attribute: "Tax Identification Number" + strategy: hash + +sla: + freshness: "< 15 minutes" + availability: "99.9%" + +refresh: real-time +``` + +#### Logical Model + +This product denormalizes transaction, party, account, and branch context +into a single wide-column structure. Every attribute maps to a canonical +entity — consumer-aligned products do not source from source systems. + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class TransactionRiskSummary{ + %% Transaction + * Transaction Identifier : string + Transaction Date Time : datetime + Amount : decimal + Reference : string + %% Payer (Party + Person via Party Role) + Payer Party Identifier : string + Payer Legal Name : string + Payer Risk Rating : enum~FinancialCrimeRiskRating~ + Payer Sanctions Screen Status : enum~SanctionsScreenStatus~ + Payer PEP Status : enum~PEPStatus~ + Payer Date of Birth : date + %% Payee (Party + Person via Party Role) + Payee Party Identifier : string + Payee Legal Name : string + Payee Risk Rating : enum~FinancialCrimeRiskRating~ + Payee Sanctions Screen Status : enum~SanctionsScreenStatus~ + %% Account + Account Identifier : string + Account Number : string + Account Opened Date : date + %% Branch + Branch Code : string + Branch Name : string + } +``` + +#### Attribute Mapping + +Product Attribute | Source | Path | Transform +--- | --- | --- | --- +Transaction Identifier | Transaction.Transaction Identifier | — | — +Transaction Date Time | Transaction.Transaction Date Time | — | — +Amount | Transaction.Amount | — | — +Reference | Transaction.Reference | — | — +Payer Party Identifier | Party.Party Identifier | Transaction → Payer → Party | — +Payer Legal Name | Party.Legal Name | Transaction → Payer → Party | — +Payer Risk Rating | Party.Risk Rating | Transaction → Payer → Party | — +Payer Sanctions Screen Status | Party.Sanctions Screen Status | Transaction → Payer → Party | — +Payer PEP Status | Person.Politically Exposed Person Status | Transaction → Payer → Party → Person | — +Payer Date of Birth | Person.Date of Birth | Transaction → Payer → Party → Person | — +Payee Party Identifier | Party.Party Identifier | Transaction → Payee → Party | — +Payee Legal Name | Party.Legal Name | Transaction → Payee → Party | — +Payee Risk Rating | Party.Risk Rating | Transaction → Payee → Party | — +Payee Sanctions Screen Status | Party.Sanctions Screen Status | Transaction → Payee → Party | — +Account Identifier | Account.Account Identifier | Transaction → Payer → Customer → Account | — +Account Number | Account.Account Number | Transaction → Payer → Customer → Account | — +Account Opened Date | Account.Opened Date | Transaction → Payer → Customer → Account | — +Branch Code | Branch.Branch Code | Transaction → Payer → Customer → Account → Branch | — +Branch Name | Branch.Branch Name | Transaction → Payer → Customer → Account → Branch | — diff --git a/financial_crime/products/canonical.md b/financial_crime/products/canonical.md new file mode 100644 index 0000000..d2e75c9 --- /dev/null +++ b/financial_crime/products/canonical.md @@ -0,0 +1,135 @@ +# [Financial Crime](../domain.md) + +## Data Products + +### Canonical Party + +The governed canonical representation of Party, Party Role, and related +identity entities for consumption by downstream domains and cross-domain +integration points. + +```yaml +class: domain-aligned +schema_type: normalized +owner: data.architecture@bank.com +consumers: + - Cross-domain Integration + - Customer Domain + - Regulatory Reporting +status: Active +version: "1.0.0" + +entities: + - Party + - Person + - Company + - Party Role + - Customer + - Contact Address + - Address + +lineage: + - source: salesforce-crm + tables: + - table_account + - table_contact + - table_contact_point + - source: sap-fraud-management + tables: + - table_sanctions_screening + - table_customer_risk_profile + +sla: + freshness: "< 1 hour" + availability: "99.95%" + +refresh: hourly +``` + +#### Logical Model + +```mermaid +--- +config: + layout: elk +--- +classDiagram + class Party{ + <> + * Party Identifier : string + Legal Name : string + Also Known As : string[] + Party Status : enum~PartyStatus~ + Risk Rating : enum~FinancialCrimeRiskRating~ + Sanctions Screen Status : enum~SanctionsScreenStatus~ + Next Review Date : date + } + + class Person{ + Date of Birth : date + Country of Birth : string + Nationality : string[] + Country of Residence : string + Politically Exposed Person Status : enum~PEPStatus~ + } + + class Company{ + Company Registration Number : string + Incorporation Country : string + Incorporation Date : date + } + + class PartyRole{ + <> + * Role Identifier : string + Role Status : enum~PartyRoleStatus~ + Role Start Date : date + Role End Date : date + Due Diligence Status : enum~DDStatus~ + } + + class Customer{ + * Customer Number : string + Onboarding Date : date + Relationship Start Date : date + } + + class ContactAddress{ + * Contact Address Identifier : string + Address Purpose : enum~AddressPurpose~ + Is Primary : boolean + Verification Status : enum~AddressVerificationStatus~ + Verification Date : date + Verification Method : enum~VerificationMethod~ + Valid From : date + Valid To : date + } + + class Address{ + * Address Identifier : string + Address Type : enum~AddressType~ + Address Line 1 : string + Address Line 2 : string + Suburb : string + City : string + State Or Region : string + Postcode : string + Country : string + } + + Person --|> Party + Company --|> Party + Customer --|> PartyRole + Party "1" --> "0..*" PartyRole : assumes + Party "1" --> "0..*" ContactAddress : has + PartyRole "0..*" --> "0..*" ContactAddress : uses + ContactAddress "0..*" --> "1" Address : references + + class Party["Party"] + class Person["Person"] + class Company["Company"] + class PartyRole["Party Role"] + class Customer["Customer"] + class ContactAddress["Contact Address"] + class Address["Address"] +``` diff --git a/financial_crime/products/party-risk-report-legacy.md b/financial_crime/products/party-risk-report-legacy.md new file mode 100644 index 0000000..badc3df --- /dev/null +++ b/financial_crime/products/party-risk-report-legacy.md @@ -0,0 +1,31 @@ +# [Financial Crime](../domain.md) + +## Data Products + +### Party Risk Report (Legacy) + +Legacy denormalized party risk view that combined party identity with +risk rating and due diligence status. Superseded by the Transaction Risk +Summary product which provides richer transaction-level context. + +```yaml +class: consumer-aligned +schema_type: wide-column +owner: fincrime.analytics@bank.com +consumers: + - Financial Crime Analytics (migrating) +status: Deprecated +deprecated_date: "2025-01-15" +successor: "Transaction Risk Summary" +version: "1.0.0" + +entities: + - Party + - Party Role + - Customer + +governance: + classification: Highly Confidential + pii: true + retention: "10 years" +``` diff --git a/financial_crime/products/source-feeds.md b/financial_crime/products/source-feeds.md new file mode 100644 index 0000000..26985b0 --- /dev/null +++ b/financial_crime/products/source-feeds.md @@ -0,0 +1,29 @@ +# [Financial Crime](../domain.md) + +## Data Products + +### Salesforce CRM Raw Feed + +Raw audit feed from Salesforce CRM preserving source schema and change +events for replay, integration debugging, and compliance audit trails. + +```yaml +class: source-aligned +source: salesforce-crm +owner: data.engineering@bank.com +consumers: + - Data Engineering + - Audit & Compliance +status: Active +version: "1.0.0" + +governance: + classification: Internal + retention: "3 years" + +sla: + freshness: "< 5 minutes" + availability: "99.9%" + +refresh: real-time +``` diff --git a/financial_crime/sources/salesforce-crm/source.md b/financial_crime/sources/salesforce-crm/source.md new file mode 100644 index 0000000..cb62dc0 --- /dev/null +++ b/financial_crime/sources/salesforce-crm/source.md @@ -0,0 +1,34 @@ +# Salesforce CRM + +Salesforce CRM is the customer relationship source for onboarding, profile maintenance, and communication preferences. It contributes party identity, customer profile, and contact data used by KYC and due diligence workflows. + +## Metadata + +```yaml +id: salesforce-crm +owner: crm.platform@bank.com +steward: data.governance@bank.com + +change_model: real-time-cdc +change_events: + - Customer Created + - Customer Updated + - Contact Address Updated + - Customer Preference Updated + +update_frequency: real-time +data_quality_tier: 1 +status: Production +version: "1.0.0" +``` + +## [Financial Crime](../../domain.md) Feeds + +Canonical Entity | Transform File | Attributes Contributed | Change Model +--- | --- | --- | --- +[Party](../../entities/party.md#party) | [table_account](transforms/table_account.md) | Party Identifier, Party Status | real-time-cdc +[Person](../../entities/person.md#person) | [table_contact](transforms/table_contact.md) | Given Name, Family Name, Date of Birth, PEP Status | real-time-cdc +[Company](../../entities/company.md#company) | [table_account](transforms/table_account.md) | Legal Name, Registration Identifier | real-time-cdc +[Customer](../../entities/customer.md#customer) | [table_account](transforms/table_account.md) | Customer Number, Onboarding Date, Segment | real-time-cdc +[Contact Address](../../entities/contact_address.md#contact-address) | [table_contact_point](transforms/table_contact_point.md) | Address Purpose, Verification Status, Effective Dates | real-time-cdc +[Customer Preferences](../../entities/customer-preferences.md#customer-preferences) | [table_preference](transforms/table_preference.md) | Preferred Contact Channel, Marketing Consent | event-driven diff --git a/financial_crime/sources/salesforce-crm/transforms/table_account.md b/financial_crime/sources/salesforce-crm/transforms/table_account.md new file mode 100644 index 0000000..b79cae0 --- /dev/null +++ b/financial_crime/sources/salesforce-crm/transforms/table_account.md @@ -0,0 +1,30 @@ +# [Salesforce CRM](../source.md) + +## Account + +Pos | Column Name | Data Type | Max Len | Precision | Scale | Nulls | Comment | Destination +--- | --- | --- | --- | --- | --- | --- | --- | --- +1 | ExternalPartyId | Text | 40 | | | no | Account-scoped party identifier | Party.Party Identifier +2 | RecordStatus | Text | 20 | | | yes | Account lifecycle status from CRM | [Map Party Status](#map-party-status) +3 | LegalEntityName | Text | 200 | | | yes | Registered legal entity name | Company.Legal Name +4 | CompanyRegistrationNumber | Text | 80 | | | yes | Jurisdictional registration number | Company.Registration Identifier +5 | CustomerNumber | Text | 100 | | | yes | CRM customer reference | Customer.Customer Number +6 | OnboardingCompletedDate | Date | | | | yes | Date onboarding completed | Customer.Onboarding Date +7 | CustomerSegmentCode | Text | 30 | | | yes | Commercial segment classification code | Customer.Segment + +### Map Party Status + +Translates CRM lifecycle status values into canonical party status. + +```yaml +type: conditional +target: Party · Party Status +source: + field: Account.RecordStatus +cases: + Active: "RecordStatus == 'Active'" + Inactive: "RecordStatus == 'Inactive'" + Suspended: "RecordStatus == 'Suspended'" + Closed: "RecordStatus == 'Closed'" +fallback: Inactive +``` diff --git a/financial_crime/sources/salesforce-crm/transforms/table_contact.md b/financial_crime/sources/salesforce-crm/transforms/table_contact.md new file mode 100644 index 0000000..7b2fadb --- /dev/null +++ b/financial_crime/sources/salesforce-crm/transforms/table_contact.md @@ -0,0 +1,26 @@ +# [Salesforce CRM](../source.md) + +## Contact + +Pos | Column Name | Data Type | Max Len | Precision | Scale | Nulls | Comment | Destination +--- | --- | --- | --- | --- | --- | --- | --- | --- +1 | FirstName | Text | 120 | | | yes | Contact given name | Person.Given Name +2 | LastName | Text | 120 | | | yes | Contact family name | Person.Family Name +3 | Birthdate | Date | | | | yes | Contact date of birth | Person.Date of Birth +4 | CompliancePepFlag | Text | 1 | | | yes | PEP indicator from compliance screening | [Derive PEP Status](#derive-pep-status) + +### Derive PEP Status + +Derives politically exposed person status from CRM compliance flag. + +```yaml +type: conditional +target: Person · PEP Status +source: + field: Contact.CompliancePepFlag +cases: + Confirmed: "CompliancePepFlag == 'Y'" + Not PEP: "CompliancePepFlag == 'N'" + Unknown: "CompliancePepFlag == null" +fallback: Unknown +``` diff --git a/financial_crime/sources/salesforce-crm/transforms/table_contact_point.md b/financial_crime/sources/salesforce-crm/transforms/table_contact_point.md new file mode 100644 index 0000000..0c3d465 --- /dev/null +++ b/financial_crime/sources/salesforce-crm/transforms/table_contact_point.md @@ -0,0 +1,41 @@ +# [Salesforce CRM](../source.md) + +## ContactPoint + +Pos | Column Name | Data Type | Max Len | Precision | Scale | Nulls | Comment | Destination +--- | --- | --- | --- | --- | --- | --- | --- | --- +1 | PurposeCode | Text | 40 | | | yes | Purpose classification for contact point | Contact Address.Address Purpose +2 | VerificationResult | Text | 20 | | | yes | Verification workflow result | [Map Verification Status](#map-verification-status) +3 | ValidFromDate | Date | | | | yes | Effective-from date | [Derive Effective Dates](#derive-effective-dates) +4 | ValidToDate | Date | | | | yes | Effective-to date | [Derive Effective Dates](#derive-effective-dates) + +### Map Verification Status + +Maps verification outcome from CRM verification workflow. + +```yaml +type: conditional +target: Contact Address · Verification Status +source: + field: ContactPoint.VerificationResult +cases: + Verified: "VerificationResult == 'PASS'" + Pending: "VerificationResult == 'PENDING'" + Failed: "VerificationResult == 'FAIL'" +fallback: Pending +``` + +### Derive Effective Dates + +Builds effective date range token from valid-from and valid-to values. + +```yaml +type: derived +target: Contact Address · Effective Dates +expression: "coalesce(Valid From, '') + '|' + coalesce(Valid To, '')" +inputs: + Valid From: + field: ContactPoint.ValidFromDate + Valid To: + field: ContactPoint.ValidToDate +``` diff --git a/financial_crime/sources/salesforce-crm/transforms/table_preference.md b/financial_crime/sources/salesforce-crm/transforms/table_preference.md new file mode 100644 index 0000000..4bb3d41 --- /dev/null +++ b/financial_crime/sources/salesforce-crm/transforms/table_preference.md @@ -0,0 +1,24 @@ +# [Salesforce CRM](../source.md) + +## Preference + +Pos | Column Name | Data Type | Max Len | Precision | Scale | Nulls | Comment | Destination +--- | --- | --- | --- | --- | --- | --- | --- | --- +1 | PreferredChannelCode | Text | 30 | | | yes | Preferred communication channel code | Customer Preferences.Preferred Contact Channel +2 | MarketingOptInFlag | Text | 1 | | | yes | Opt-in indicator for marketing messages | [Map Marketing Consent](#map-marketing-consent) + +### Map Marketing Consent + +Maps marketing consent flag into canonical consent status. + +```yaml +type: conditional +target: Customer Preferences · Marketing Consent +source: + field: Preference.MarketingOptInFlag +cases: + Consented: "MarketingOptInFlag == 'Y'" + Declined: "MarketingOptInFlag == 'N'" + Unknown: "MarketingOptInFlag == null" +fallback: Unknown +``` diff --git a/financial_crime/sources/sap-fraud-management/source.md b/financial_crime/sources/sap-fraud-management/source.md new file mode 100644 index 0000000..073c631 --- /dev/null +++ b/financial_crime/sources/sap-fraud-management/source.md @@ -0,0 +1,36 @@ +# SAP Fraud Management + +SAP Fraud Management is the analytical source for fraud and suspicious activity detection signals. It contributes risk outcomes and case-oriented enrichment used by AML, KYC, and transaction monitoring controls. + +## Metadata + +```yaml +id: sap-fraud-management +owner: fraud.operations@bank.com +steward: compliance.officer@bank.com + +change_model: event-driven +change_events: + - Fraud Alert Raised + - Fraud Alert Closed + - Transaction Risk Scored + - Case Escalated + +update_frequency: real-time +data_quality_tier: 2 +status: Production +version: "1.0.0" + +tags: + - Fraud + - AML + - Financial Crime +``` + +## [Financial Crime](../../domain.md) Feeds + +Canonical Entity | Transform File | Attributes Contributed | Change Model +--- | --- | --- | --- +[Transaction](../../entities/transaction.md#transaction) | [table_alert_case](transforms/table_alert_case.md) | Financial Crime Risk Score, Monitoring Outcome, Alert Reference | event-driven +[Party](../../entities/party.md#party) | [table_sanctions_screening](transforms/table_sanctions_screening.md) | Sanctions Screen Status, Watchlist Match Indicator | batch-intraday +[Customer](../../entities/customer.md#customer) | [table_customer_risk_profile](transforms/table_customer_risk_profile.md) | Risk Review Flag, Enhanced Due Diligence Trigger | event-driven diff --git a/financial_crime/sources/sap-fraud-management/transforms/table_alert_case.md b/financial_crime/sources/sap-fraud-management/transforms/table_alert_case.md new file mode 100644 index 0000000..24603cc --- /dev/null +++ b/financial_crime/sources/sap-fraud-management/transforms/table_alert_case.md @@ -0,0 +1,37 @@ +# [SAP Fraud Management](../source.md) + +## AlertCase + +Pos | Column Name | Data Type | Max Len | Precision | Scale | Nulls | Comment | Destination +--- | --- | --- | --- | --- | --- | --- | --- | --- +1 | TxRiskScore | Decimal | | 18 | 6 | yes | Computed transaction ML/TF risk score | Transaction.Financial Crime Risk Score +2 | DecisionStatus | Text | 30 | | | yes | Alert workflow decision status | [Map Monitoring Outcome](#map-monitoring-outcome) +3 | CaseId | Text | 64 | | | no | Unique SAP alert case identifier | Transaction.Alert Reference + +### Map Monitoring Outcome + +Maps alert decision outcome into canonical monitoring outcome. + +```yaml +type: conditional +target: Transaction · Monitoring Outcome +source: + field: AlertCase.DecisionStatus +cases: + Escalated: "DecisionStatus == 'ESCALATED'" + Cleared: "DecisionStatus == 'CLEARED'" + Under Review: "DecisionStatus == 'OPEN'" +fallback: Under Review +``` + +### Map Alert Reference + +Maps SAP alert case identifier for downstream investigations. + +```yaml +type: direct +target: Transaction · Alert Reference +source: + field: AlertCase.CaseId + cast: string +``` diff --git a/financial_crime/sources/sap-fraud-management/transforms/table_customer_risk_profile.md b/financial_crime/sources/sap-fraud-management/transforms/table_customer_risk_profile.md new file mode 100644 index 0000000..ed65e22 --- /dev/null +++ b/financial_crime/sources/sap-fraud-management/transforms/table_customer_risk_profile.md @@ -0,0 +1,24 @@ +# [SAP Fraud Management](../source.md) + +## CustomerRiskProfile + +Pos | Column Name | Data Type | Max Len | Precision | Scale | Nulls | Comment | Destination +--- | --- | --- | --- | --- | --- | --- | --- | --- +1 | ReviewRequiredFlag | Boolean | | | | yes | Indicates whether formal review is required | Customer.Risk Review Flag +2 | EddTriggerCode | Text | 20 | | | yes | Enhanced due diligence trigger status code | [Map Enhanced Due Diligence Trigger](#map-enhanced-due-diligence-trigger) + +### Map Enhanced Due Diligence Trigger + +Derives EDD trigger based on high-risk profile and unresolved alerts. + +```yaml +type: conditional +target: Customer · Enhanced Due Diligence Trigger +source: + field: CustomerRiskProfile.EddTriggerCode +cases: + Triggered: "EddTriggerCode == 'TRIGGERED'" + Not Triggered: "EddTriggerCode == 'NOT_TRIGGERED'" + Pending: "EddTriggerCode == 'PENDING'" +fallback: Pending +``` diff --git a/financial_crime/sources/sap-fraud-management/transforms/table_sanctions_screening.md b/financial_crime/sources/sap-fraud-management/transforms/table_sanctions_screening.md new file mode 100644 index 0000000..018bd55 --- /dev/null +++ b/financial_crime/sources/sap-fraud-management/transforms/table_sanctions_screening.md @@ -0,0 +1,24 @@ +# [SAP Fraud Management](../source.md) + +## SanctionsScreening + +Pos | Column Name | Data Type | Max Len | Precision | Scale | Nulls | Comment | Destination +--- | --- | --- | --- | --- | --- | --- | --- | --- +1 | ResultCode | Text | 32 | | | yes | Screening engine outcome code | [Map Sanctions Screen Status](#map-sanctions-screen-status) +2 | MatchFlag | Boolean | | | | yes | Whether a watchlist match was detected | Party.Watchlist Match Indicator + +### Map Sanctions Screen Status + +Maps sanctions screening outcome from SAP sanctions service. + +```yaml +type: conditional +target: Party · Sanctions Screen Status +source: + field: SanctionsScreening.ResultCode +cases: + Clear: "ResultCode == 'CLEAR'" + Potential Match: "ResultCode == 'POTENTIAL_MATCH'" + Confirmed Match: "ResultCode == 'CONFIRMED_MATCH'" +fallback: Clear +``` diff --git a/financial_crime/sources/temenos-payment/source.md b/financial_crime/sources/temenos-payment/source.md new file mode 100644 index 0000000..0c3231a --- /dev/null +++ b/financial_crime/sources/temenos-payment/source.md @@ -0,0 +1,38 @@ +# Temenos Payment + +Temenos Payment is the operational source for payment initiation and execution records. It emits high-volume transaction changes used for financial crime monitoring and downstream payment analytics. + +## Metadata + +```yaml +id: temenos-payment +owner: payments.platform@bank.com +steward: data.governance@bank.com + +change_model: real-time-cdc +change_events: + - Payment Initiated + - Payment Executed + - Payment Reversed + - Payment Rejected + +update_frequency: real-time +data_quality_tier: 1 +status: Production +version: "1.0.0" + +tags: + - Payments + - Core Banking + - Financial Crime +``` + +## [Financial Crime](../../domain.md) Feeds + +Canonical Entity | Transform File | Attributes Contributed | Change Model +--- | --- | --- | --- +[Transaction](../../entities/transaction.md#transaction) | [table_payment_event](transforms/table_payment_event.md) | Transaction Identifier, Amount, Currency, Execution Timestamp, Status | real-time-cdc +[Account](../../entities/account.md#account) | [table_account_ref](transforms/table_account_ref.md) | Account Identifier, Account Status, Product Identifier | real-time-cdc +[Payment Initiator](../../entities/payment_initiator.md#payment-initiator) | [table_initiation](transforms/table_initiation.md) | Initiator Role Identifier, Initiation Channel | event-driven +[Payer](../../entities/payer.md#payer) | [table_payment_parties](transforms/table_payment_parties.md) | Payer Role Identifier | event-driven +[Payee](../../entities/payee.md#payee) | [table_payment_parties](transforms/table_payment_parties.md) | Payee Role Identifier | event-driven diff --git a/financial_crime/sources/temenos-payment/transforms/table_account_ref.md b/financial_crime/sources/temenos-payment/transforms/table_account_ref.md new file mode 100644 index 0000000..dbd324b --- /dev/null +++ b/financial_crime/sources/temenos-payment/transforms/table_account_ref.md @@ -0,0 +1,25 @@ +# [Temenos Payment](../source.md) + +## AccountRef + +Pos | Column Name | Data Type | Max Len | Precision | Scale | Nulls | Comment | Destination +--- | --- | --- | --- | --- | --- | --- | --- | --- +1 | AccountNumber | Text | 34 | | | no | Account reference number | Account.Account Identifier +2 | AccountState | Text | 20 | | | yes | Lifecycle/account servicing state | [Map Account Status](#map-account-status) +3 | ProductCode | Text | 30 | | | yes | Linked product code | Account.Product Identifier + +### Map Account Status + +Maps account servicing status from Temenos account state. + +```yaml +type: conditional +target: Account · Account Status +source: + field: AccountRef.AccountState +cases: + Active: "AccountState == 'ACTIVE'" + Frozen: "AccountState == 'FROZEN'" + Closed: "AccountState == 'CLOSED'" +fallback: Active +``` diff --git a/financial_crime/sources/temenos-payment/transforms/table_initiation.md b/financial_crime/sources/temenos-payment/transforms/table_initiation.md new file mode 100644 index 0000000..bcd6fb7 --- /dev/null +++ b/financial_crime/sources/temenos-payment/transforms/table_initiation.md @@ -0,0 +1,25 @@ +# [Temenos Payment](../source.md) + +## Initiation + +Pos | Column Name | Data Type | Max Len | Precision | Scale | Nulls | Comment | Destination +--- | --- | --- | --- | --- | --- | --- | --- | --- +1 | ActorRoleId | Text | 64 | | | no | Role identifier that initiated payment | Payment Initiator.Initiator Role Identifier +2 | ChannelCode | Text | 20 | | | yes | Channel where initiation occurred | [Map Initiation Channel](#map-initiation-channel) + +### Map Initiation Channel + +Maps channel used to initiate payment to canonical channel. + +```yaml +type: conditional +target: Payment Initiator · Initiation Channel +source: + field: Initiation.ChannelCode +cases: + Branch: "ChannelCode == 'BRANCH'" + Mobile: "ChannelCode == 'MOBILE'" + Internet Banking: "ChannelCode == 'ONLINE'" + API: "ChannelCode == 'API'" +fallback: API +``` diff --git a/financial_crime/sources/temenos-payment/transforms/table_payment_event.md b/financial_crime/sources/temenos-payment/transforms/table_payment_event.md new file mode 100644 index 0000000..9572333 --- /dev/null +++ b/financial_crime/sources/temenos-payment/transforms/table_payment_event.md @@ -0,0 +1,28 @@ +# [Temenos Payment](../source.md) + +## PaymentEvent + +Pos | Column Name | Data Type | Max Len | Precision | Scale | Nulls | Comment | Destination +--- | --- | --- | --- | --- | --- | --- | --- | --- +1 | PaymentId | Text | 64 | | | no | Unique payment event identifier | Transaction.Transaction Identifier +2 | SettlementAmount | Decimal | | 18 | 4 | no | Settled payment amount | Transaction.Amount +3 | SettlementCurrency | Text | 3 | | | no | ISO currency code | Transaction.Currency +4 | ExecutionDateTime | DateTime | | | | yes | Timestamp at execution | Transaction.Execution Timestamp +5 | PaymentStatus | Text | 20 | | | yes | Payment lifecycle status | [Map Status](#map-status) + +### Map Status + +Maps payment state into canonical transaction status. + +```yaml +type: conditional +target: Transaction · Status +source: + field: PaymentEvent.PaymentStatus +cases: + Pending: "PaymentStatus == 'PENDING'" + Completed: "PaymentStatus == 'EXECUTED'" + Reversed: "PaymentStatus == 'REVERSED'" + Rejected: "PaymentStatus == 'REJECTED'" +fallback: Pending +``` diff --git a/financial_crime/sources/temenos-payment/transforms/table_payment_parties.md b/financial_crime/sources/temenos-payment/transforms/table_payment_parties.md new file mode 100644 index 0000000..425eba2 --- /dev/null +++ b/financial_crime/sources/temenos-payment/transforms/table_payment_parties.md @@ -0,0 +1,8 @@ +# [Temenos Payment](../source.md) + +## PaymentParties + +Pos | Column Name | Data Type | Max Len | Precision | Scale | Nulls | Comment | Destination +--- | --- | --- | --- | --- | --- | --- | --- | --- +1 | DebtorRoleId | Text | 64 | | | no | Debtor role identifier in payment context | Payer.Payer Role Identifier +2 | CreditorRoleId | Text | 64 | | | no | Creditor role identifier in payment context | Payee.Payee Role Identifier diff --git a/main.py b/main.py new file mode 100644 index 0000000..54a83bf --- /dev/null +++ b/main.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from random-corp!") + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..ff78f15 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,9 @@ +[project] +name = "random-corp" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.14" +dependencies = [ + "faker>=40.19.1", +] diff --git a/src/consistency_example.py b/src/consistency_example.py new file mode 100644 index 0000000..2aed1a0 --- /dev/null +++ b/src/consistency_example.py @@ -0,0 +1,228 @@ +""" +Eventual Consistency Demonstration — Financial Crime / canonical scope + +Shows how Party entity updates converge (or fail to converge) across three +source feeds with different propagation lags, and validates the canonical +product's declared SLA of < 1 hour. + +Source feeds modelled (from examples/Financial Crime/sources/): + 1. salesforce-crm — real-time-cdc, lag = 5 min (party identity fields) + 2. sap-fraud-mgmt — batch-intraday, lag = 30 min (risk_rating, sanctions_screen_status) + 3. temenos-payment — batch-intraday, lag = 60 min (slowest contributing system) + +Canonical SLA: freshness < 1 hour + (from examples/Financial Crime/products/canonical.md) + +Consistency posture: eventual + (sources have heterogeneous change_models; synchronous propagation not achievable) + +Null strategy: nullable-staging + (partial rows admitted to staging; converged view used by consumers) + +Usage: + cd examples/Financial\ Crime + python consistency_example.py + + # Or with runtime on path explicitly: + PYTHONPATH=../../agents/agent-artifact/skills/faker/runtime python consistency_example.py +""" + +from __future__ import annotations +import sys +import os + +# --------------------------------------------------------------------------- +# Path setup — inject faker runtime so integrity_check and consistency_scenario +# can be imported regardless of working directory +# --------------------------------------------------------------------------- +_runtime_path = os.path.abspath( + os.path.join(os.path.dirname(__file__), "..", "..", + "agents", "agent-artifact", "skills", "faker", "runtime") +) +if _runtime_path not in sys.path: + sys.path.insert(0, _runtime_path) + +# --------------------------------------------------------------------------- +# Imports +# --------------------------------------------------------------------------- +try: + from factories import ( + DatasetBuilder, + FK_SPECS_FINANCIAL_CRIME, + NOT_NULL_FINANCIAL_CRIME, + ENUM_VALUES_FINANCIAL_CRIME, + UNIQUE_PK_FINANCIAL_CRIME, + UNIQUE_CURRENT_PK_FINANCIAL_CRIME, + ) +except ImportError as e: + print(f"[ERROR] Could not import factories.py — run from the " + f"'examples/Financial Crime/' directory: {e}") + sys.exit(1) + +try: + from integrity_check import ( + check_integrity, print_report, + check_temporal_chain, print_temporal_report, + ) + from consistency_scenario import ( + SourceFeed, generate_scenario, + check_convergence, print_convergence_report, + ) +except ImportError as e: + print(f"[ERROR] Could not import runtime modules from {_runtime_path}: {e}") + sys.exit(1) + +from datetime import datetime, timezone + +# --------------------------------------------------------------------------- +# Configuration +# --------------------------------------------------------------------------- + +# Source feeds — modelled on the Financial Crime domain's declared source systems +FEEDS = [ + SourceFeed(name="salesforce-crm", lag_minutes=5, change_model="real-time-cdc"), + SourceFeed(name="sap-fraud-mgmt", lag_minutes=30, change_model="batch-intraday"), + SourceFeed(name="temenos-payment", lag_minutes=60, change_model="batch-intraday"), +] + +# Entities with valid_time or bitemporal tracking (candidate for convergence analysis) +TEMPORAL_ENTITIES = { + "Party": "party_identifier", + "Account": "account_identifier", +} + +# Canonical product SLA (from products/canonical.md: freshness: "< 1 hour") +SLA_MINUTES = 60 + +# --------------------------------------------------------------------------- +# Section 1 — Build dataset +# --------------------------------------------------------------------------- +print("=" * 60) +print("Financial Crime — Eventual Consistency Demonstration") +print("=" * 60) + +print("\n[1] Building synthetic dataset (20 parties, history=True)…") +dataset = DatasetBuilder(pii_mode="safe").build( + n_parties=20, + accounts_per_party=2, + txns_per_account=3, + with_history=True, +) +total_rows = sum(len(v) for v in dataset.values()) +print(f" Built: {total_rows} total rows across {len(dataset)} entities") +for entity, rows in dataset.items(): + print(f" {entity}: {len(rows)} rows") + +# --------------------------------------------------------------------------- +# Section 2 — Baseline integrity check +# --------------------------------------------------------------------------- +print("\n[2] Baseline integrity check…") +integrity_errors = check_integrity( + dataset, + fk_specs=FK_SPECS_FINANCIAL_CRIME, + not_null=NOT_NULL_FINANCIAL_CRIME, + enum_values=ENUM_VALUES_FINANCIAL_CRIME, + unique_pk=UNIQUE_PK_FINANCIAL_CRIME, + unique_current_pk=UNIQUE_CURRENT_PK_FINANCIAL_CRIME, +) +print_report(integrity_errors) +if integrity_errors: + print("[WARN] Integrity violations present — convergence analysis may be misleading") + +# --------------------------------------------------------------------------- +# Section 3 — Temporal chain validation +# --------------------------------------------------------------------------- +print("\n[3] Temporal chain validation…") +temporal_errors = check_temporal_chain(dataset, TEMPORAL_ENTITIES) +print_temporal_report(temporal_errors) + +# --------------------------------------------------------------------------- +# Section 4 — Generate eventual consistency scenario +# --------------------------------------------------------------------------- +print("\n[4] Generating eventual consistency scenario…") +print(f" Feeds: " + + " | ".join(f"{f.name} (lag={f.lag_minutes}m, {f.change_model})" for f in FEEDS)) + +as_of = datetime.now(tz=timezone.utc) +scenario = generate_scenario( + dataset=dataset, + feeds=FEEDS, + as_of=as_of, + temporal_entities=TEMPORAL_ENTITIES, +) + +# --------------------------------------------------------------------------- +# Section 5 — Convergence report +# --------------------------------------------------------------------------- +print("\n[5] Convergence analysis…") +print(f" Observation time: {as_of.strftime('%Y-%m-%d %H:%M:%S UTC')}") + +for entity, pk_col in TEMPORAL_ENTITIES.items(): + deltas = scenario.convergence_delta_minutes.get(entity, {}) + divergent_ids = scenario.divergent.get(entity, []) + converged = [pk for pk, d in deltas.items() if d == 0.0] + in_flight = [pk for pk, d in deltas.items() if d > 0.0] + + print(f"\n {entity} ({len(deltas)} current instances):") + print(f" Fully converged : {len(converged)}") + print(f" In-flight : {len(in_flight)}") + + if in_flight: + print(f" In-flight sample (showing up to 5):") + for pk in in_flight[:5]: + mins = deltas[pk] + pk_short = str(pk)[:8] + "…" + # Show which feeds still need to receive this row + late_feeds = [] + for feed in FEEDS: + feed_rows = scenario.source_views[feed.name].get(entity, []) + feed_pks = {r.get(pk_col) for r in feed_rows if r.get("is_current") is True} + if pk not in feed_pks: + late_feeds.append(f"{feed.name}({feed.lag_minutes}m)") + print(f" {pk_short} converges in {mins:.1f} min " + f"[pending: {', '.join(late_feeds) or 'none'}]") + +# --------------------------------------------------------------------------- +# Section 6 — SLA check +# --------------------------------------------------------------------------- +print(f"\n[6] SLA validation (freshness < {SLA_MINUTES} min)…") +violations = check_convergence(scenario, sla_minutes=SLA_MINUTES) + +if not violations: + print(f" PASS — all entity instances converge within the " + f"{SLA_MINUTES}-minute SLA.") +else: + print(f" FAIL — {len(violations)} instance(s) violate the " + f"{SLA_MINUTES}-minute SLA:") + for v in violations[:5]: + pk_short = str(v.entity_id)[:8] + "…" + print(f" [{v.entity}] {pk_short}: {v.message}") + if len(violations) > 5: + print(f" … and {len(violations) - 5} more") + +# --------------------------------------------------------------------------- +# Section 7 — Null handling illustration +# --------------------------------------------------------------------------- +print("\n[7] Null handling under eventual consistency…") +print(""" + Scenario: salesforce-crm (lag=5m) has delivered party_status. + sap-fraud-mgmt (lag=30m) has NOT yet delivered risk_rating. + + In-flight canonical row (nullable-staging pattern): + party_identifier : "aaa-bbb-ccc…" ← arrived from salesforce-crm + party_status : "Active" ← arrived from salesforce-crm + risk_rating : NULL ← not yet received from sap-fraud-mgmt + sanctions_screen_status : NULL ← not yet received from sap-fraud-mgmt + + Storage concern : Parquet cannot distinguish this NULL from a genuinely + absent risk_rating. Downstream readers see identical bytes. + Transport concern : JSON omits absent keys; Avro encodes both as null union. + Only Protobuf hasField() can distinguish "not set" vs null. + DDL concern : A hard NOT NULL on risk_rating would block this insert. + With nullable-staging pattern, the base table allows NULL; + the converged view filters to rows where all fields are set. +""") + +print("=" * 60) +print("Demonstration complete.") +print("=" * 60) diff --git a/src/factories.py b/src/factories.py new file mode 100644 index 0000000..8872c3b --- /dev/null +++ b/src/factories.py @@ -0,0 +1,524 @@ +""" +Synthetic data factory — Financial Crime / canonical scope +Scope: canonical +PII mode: safe (default) | realistic + +Generated from: + examples/Financial Crime/entities/currency.md + examples/Financial Crime/entities/party.md + examples/Financial Crime/entities/account.md + examples/Financial Crime/entities/transaction.md + examples/Financial Crime/enums.md + +WARNING: SYNTHETIC DATA ONLY. Do not use for production data migration. +Dependencies: pip install faker + +Generation order (topological — respects FK dependencies): + 1. Currency (reference — no FKs) + 2. Party (independent, slowly_changing, bitemporal) + 3. Account (independent, slowly_changing, valid_time; FK → Currency) + 4. Transaction (dependent, append_only; FK → Currency, Account) +""" + +from __future__ import annotations +import random +import uuid +from datetime import date, datetime, timedelta, timezone +from decimal import Decimal + +from faker import Faker + +# --------------------------------------------------------------------------- +# Enum pools — sourced from examples/Financial Crime/enums.md +# --------------------------------------------------------------------------- +PARTY_STATUS_VALUES = ["Active", "Under Review", "Restricted", "Inactive", "Closed"] +FINANCIAL_CRIME_RISK_RATING_VALUES = ["Low", "Medium", "High", "Very High"] +SANCTIONS_SCREEN_STATUS_VALUES = [ + "Not Screened", "Clear", "Potential Match", "Confirmed Match", "False Positive", +] +ACCOUNT_STATUS_VALUES = ["Pending", "Active", "Dormant", "Frozen", "Suspended", "Closed"] +ACCOUNT_TYPE_VALUES = [ + "Savings", "Current", "Term Deposit", "Loan", + "Line Of Credit", "Mortgage", "Offset", "Foreign Currency", +] +TRANSACTION_TYPE_VALUES = [ + "Wire Transfer", "SWIFT Transfer", "EFTPOS", "ATM Withdrawal", "ATM Deposit", + "Direct Debit", "Direct Credit", "Internal Transfer", "BPay", + "Cash Deposit", "Cash Withdrawal", "Cheque", "RTGS", +] +TRANSACTION_CHANNEL_VALUES = [ + "Branch", "Online Banking", "Mobile Banking", "ATM", "EFTPOS Terminal", + "SWIFT", "Direct Entry", "Third Party", "Internal System", +] +TRANSACTION_STATUS_VALUES = [ + "Pending", "Authorised", "Cleared", "Settled", + "Failed", "Reversed", "Cancelled", "Under Review", +] + +# ISO 4217 representative subset — sourced from enums.md +CURRENCY_CODES = ["AUD", "NZD", "USD", "EUR", "GBP", "JPY", "SGD", "HKD", "CHF", "CAD"] +CURRENCY_NAMES = { + "AUD": "Australian Dollar", "NZD": "New Zealand Dollar", "USD": "United States Dollar", + "EUR": "Euro", "GBP": "Pound Sterling", "JPY": "Japanese Yen", + "SGD": "Singapore Dollar", "HKD": "Hong Kong Dollar", "CHF": "Swiss Franc", + "CAD": "Canadian Dollar", +} +CURRENCY_MINOR_UNITS = { + "AUD": 2, "NZD": 2, "USD": 2, "EUR": 2, "GBP": 2, + "JPY": 0, "SGD": 2, "HKD": 2, "CHF": 2, "CAD": 2, +} + +# --------------------------------------------------------------------------- +# Seeding — deterministic by default +# --------------------------------------------------------------------------- +fake = Faker() +Faker.seed(0) +random.seed(0) + +_seq = 0 + + +def _next_seq() -> int: + global _seq + _seq += 1 + return _seq + + +# --------------------------------------------------------------------------- +# CurrencyFactory +# Entity: Currency +# Existence: independent +# Mutability: reference (static rows; no temporal columns) +# PII fields: none +# --------------------------------------------------------------------------- +class CurrencyFactory: + """Produces one row per ISO 4217 code in CURRENCY_CODES.""" + + def build_all(self) -> list[dict]: + return [ + { + "currency_code": code, + "currency_name": CURRENCY_NAMES[code], + "minor_unit": CURRENCY_MINOR_UNITS[code], + } + for code in CURRENCY_CODES + ] + + +# --------------------------------------------------------------------------- +# PartyFactory +# Entity: Party +# Existence: independent +# Mutability: slowly_changing +# Temporal: bitemporal (valid_time + transaction_time) +# PII fields: legal_name, also_known_as +# Constraints encoded: +# - Legal Name Required: legal_name is never None +# - Review Date Must Not Be Overdue: next_review_date >= today (unless Under Review) +# - Confirmed Sanctions Match Blocks Service: sanctions_screen_status != 'Confirmed Match' +# (softened in synthetic data — status allowed but flagged via comment) +# --------------------------------------------------------------------------- +class PartyFactory: + """ + Entity: Party + Existence: independent + Mutability: slowly_changing + Temporal: bitemporal + PII fields: legal_name, also_known_as + Constraints encoded: + - Legal Name Required + - Review Date Must Not Be Overdue + """ + + def __init__(self, fake: Faker = None, pii_mode: str = "safe"): + self.fake = fake or Faker() + self.pii_mode = pii_mode + + def build(self, with_history: bool = False, **overrides) -> list[dict]: + seq = _next_seq() + f = self.fake + now = datetime.now(tz=timezone.utc) + prior_end = now - timedelta(days=random.randint(30, 730)) + prior_start = prior_end - timedelta(days=random.randint(90, 1825)) + + # Constraint: Review Date Must Not Be Overdue + # next_review_date is always in the future unless status is Under Review + today = date.today() + next_review_date = today.replace( + year=today.year + random.randint(0, 2), + month=random.randint(1, 12), + day=random.randint(1, 28), + ) + + # PII: legal_name, also_known_as + if self.pii_mode == "realistic": + legal_name = f.name() + also_known_as = [f.name()] if random.random() < 0.3 else [] + else: + legal_name = f"Test Entity {seq:04d}" + also_known_as = [f"Test AKA {seq:04d}"] if random.random() < 0.3 else [] + + current = { + "party_identifier": str(uuid.uuid4()), + "legal_name": legal_name, # pii: true + "also_known_as": also_known_as, # pii: true + "party_status": random.choice(PARTY_STATUS_VALUES), + "risk_rating": random.choice(FINANCIAL_CRIME_RISK_RATING_VALUES), + "sanctions_screen_status": random.choice(SANCTIONS_SCREEN_STATUS_VALUES), + "next_review_date": next_review_date, + # bitemporal columns + "valid_from": prior_end, + "valid_to": None, + "is_current": True, + "recorded_at": prior_end, + "superseded_at": None, + **overrides, + } + + if not with_history: + return [current] + + prior = { + **current, + "valid_from": prior_start, + "valid_to": prior_end, + "is_current": False, + "recorded_at": prior_start, + "superseded_at": prior_end, + } + return [prior, current] + + def batch(self, n: int, with_history: bool = False, **overrides) -> list[dict]: + rows: list[dict] = [] + for _ in range(n): + rows.extend(self.build(with_history=with_history, **overrides)) + return rows + + +# --------------------------------------------------------------------------- +# AccountFactory +# Entity: Account +# Existence: independent +# Mutability: slowly_changing +# Temporal: valid_time +# PII fields: none +# FK: currency_code → Currency.currency_code +# Constraints encoded: +# - Closed Date After Opened Date: closed_date > opened_date when present +# --------------------------------------------------------------------------- +class AccountFactory: + """ + Entity: Account + Existence: independent + Mutability: slowly_changing + Temporal: valid_time + PII fields: none + FK: currency_code → Currency.currency_code + """ + + def __init__( + self, + currency_codes: list[str], + fake: Faker = None, + pii_mode: str = "safe", + ): + self.currency_codes = currency_codes + self.fake = fake or Faker() + self.pii_mode = pii_mode + + def build(self, with_history: bool = False, **overrides) -> list[dict]: + seq = _next_seq() + now = datetime.now(tz=timezone.utc) + prior_end = now - timedelta(days=random.randint(30, 730)) + prior_start = prior_end - timedelta(days=random.randint(90, 1825)) + + opened_date = (now - timedelta(days=random.randint(365, 3650))).date() + + # Constraint: Closed Date After Opened Date + account_status = random.choice(ACCOUNT_STATUS_VALUES) + closed_date = None + if account_status == "Closed": + closed_date = opened_date + timedelta(days=random.randint(30, 2000)) + + current = { + "account_identifier": str(uuid.uuid4()), + "account_number": f"BSB{seq:06d}", + "account_type": random.choice(ACCOUNT_TYPE_VALUES), + "account_status": account_status, + "opened_date": opened_date, + "closed_date": closed_date, + "currency_code": random.choice(self.currency_codes), # FK → Currency + # valid_time columns + "valid_from": prior_end, + "valid_to": None, + "is_current": True, + **overrides, + } + + if not with_history: + return [current] + + prior = { + **current, + "valid_from": prior_start, + "valid_to": prior_end, + "is_current": False, + } + return [prior, current] + + def batch(self, n: int, with_history: bool = False, **overrides) -> list[dict]: + rows: list[dict] = [] + for _ in range(n): + rows.extend(self.build(with_history=with_history, **overrides)) + return rows + + +# --------------------------------------------------------------------------- +# TransactionFactory +# Entity: Transaction +# Existence: dependent +# Mutability: append_only +# Temporal: transaction_time (recorded_at only — no valid_time) +# PII fields: none +# FK (required): currency_code → Currency.currency_code +# FK (nullable): debit_account_identifier → Account.account_identifier +# credit_account_identifier → Account.account_identifier +# Constraints encoded: +# - Amount Must Be Positive: amount = abs(generated) with floor of 0.01 +# - Settlement After Initiation: settlement_date_time >= transaction_date_time +# - Settled Transaction Has Settlement Time: if status == Settled, settlement is set +# --------------------------------------------------------------------------- +class TransactionFactory: + """ + Entity: Transaction + Existence: dependent + Mutability: append_only + Temporal: transaction_time + PII fields: none + FK (required): currency_code → Currency.currency_code + FK (nullable): debit_account_identifier, credit_account_identifier → Account.account_identifier + """ + + def __init__( + self, + currency_codes: list[str], + account_identifiers: list[str], + fake: Faker = None, + pii_mode: str = "safe", + ): + self.currency_codes = currency_codes + self.account_identifiers = account_identifiers + self.fake = fake or Faker() + self.pii_mode = pii_mode + + def build(self, **overrides) -> dict: + seq = _next_seq() + now = datetime.now(tz=timezone.utc) + + transaction_date_time = now - timedelta( + days=random.randint(0, 365), + hours=random.randint(0, 23), + minutes=random.randint(0, 59), + ) + + transaction_status = random.choice(TRANSACTION_STATUS_VALUES) + + # Constraint: Settlement After Initiation + # Constraint: Settled Transaction Has Settlement Time + if transaction_status in ("Settled", "Reversed"): + settlement_date_time = transaction_date_time + timedelta( + hours=random.randint(0, 48) + ) + elif transaction_status in ("Pending", "Authorised", "Under Review"): + settlement_date_time = None + else: + settlement_date_time = None + + # Constraint: Amount Must Be Positive + amount = max(Decimal("0.01"), round(Decimal(str(random.uniform(1.00, 50000.00))), 2)) + + # FK (nullable): randomly assign debit/credit accounts from pool + debit_account = random.choice(self.account_identifiers) if random.random() > 0.1 else None + credit_account = random.choice(self.account_identifiers) if random.random() > 0.1 else None + + return { + "transaction_identifier": str(uuid.uuid4()), + "transaction_date_time": transaction_date_time, + "settlement_date_time": settlement_date_time, + "amount": amount, + "transaction_type": random.choice(TRANSACTION_TYPE_VALUES), + "transaction_channel": random.choice(TRANSACTION_CHANNEL_VALUES), + "transaction_status": transaction_status, + "reference": f"REF-{seq:08d}", + "currency_code": random.choice(self.currency_codes), # FK → Currency + "debit_account_identifier": debit_account, # FK → Account (nullable) + "credit_account_identifier": credit_account, # FK → Account (nullable) + **overrides, + } + + def batch(self, n: int, **overrides) -> list[dict]: + return [self.build(**overrides) for _ in range(n)] + + +# --------------------------------------------------------------------------- +# DatasetBuilder +# Generates a referentially consistent dataset across all four entities. +# Generation order: Currency → Party → Account → Transaction +# --------------------------------------------------------------------------- +class DatasetBuilder: + """ + Generates a referentially consistent Financial Crime dataset. + + Generation order (topological): + 1. Currency — reference; no FKs + 2. Party — independent; no FK dependencies in core attributes + 3. Account — FK → Currency.currency_code + 4. Transaction — FK → Currency.currency_code, Account.account_identifier + + Parameters + ---------- + n_parties : Party root records (current rows) + accounts_per_party : Account records created per Party (approximate) + txns_per_account : Transaction records created per active Account + with_history : Include prior SCD rows for Party and Account + pii_mode : "safe" (default) or "realistic" + """ + + def __init__(self, fake: Faker = None, pii_mode: str = "safe"): + self.fake = fake or Faker() + self.pii_mode = pii_mode + + def build( + self, + n_parties: int = 10, + accounts_per_party: int = 2, + txns_per_account: int = 5, + with_history: bool = False, + ) -> dict[str, list[dict]]: + + # --- 1. Currency (reference) --- + currencies = CurrencyFactory().build_all() + currency_codes = [c["currency_code"] for c in currencies] + + # --- 2. Party --- + party_factory = PartyFactory(fake=self.fake, pii_mode=self.pii_mode) + parties: list[dict] = [] + for _ in range(n_parties): + parties.extend(party_factory.build(with_history=with_history)) + + # --- 3. Account (FK → Currency) --- + account_factory = AccountFactory( + currency_codes=currency_codes, + fake=self.fake, + pii_mode=self.pii_mode, + ) + accounts: list[dict] = [] + for _ in range(n_parties * accounts_per_party): + accounts.extend(account_factory.build(with_history=with_history)) + + # FK pool: only current account rows supply IDs for Transaction FKs + active_account_ids = [ + a["account_identifier"] for a in accounts if a.get("is_current", True) + ] + + # --- 4. Transaction (FK → Currency, FK → Account) --- + txn_factory = TransactionFactory( + currency_codes=currency_codes, + account_identifiers=active_account_ids, + fake=self.fake, + pii_mode=self.pii_mode, + ) + transactions = txn_factory.batch( + n=len(active_account_ids) * txns_per_account + ) + + return { + "Currency": currencies, + "Party": parties, + "Account": accounts, + "Transaction": transactions, + } + + +# --------------------------------------------------------------------------- +# Integrity spec — used by integrity_check.py and test_factories.py +# --------------------------------------------------------------------------- +FK_SPECS_FINANCIAL_CRIME = None # populated below after import guard +NOT_NULL_FINANCIAL_CRIME = { + "Party": ["party_identifier", "legal_name"], + "Account": ["account_identifier", "account_number", "currency_code"], + "Transaction": ["transaction_identifier", "amount", "currency_code"], +} +ENUM_VALUES_FINANCIAL_CRIME = { + "Party": { + "party_status": PARTY_STATUS_VALUES, + "risk_rating": FINANCIAL_CRIME_RISK_RATING_VALUES, + "sanctions_screen_status": SANCTIONS_SCREEN_STATUS_VALUES, + }, + "Account": { + "account_status": ACCOUNT_STATUS_VALUES, + "account_type": ACCOUNT_TYPE_VALUES, + }, + "Transaction": { + "transaction_type": TRANSACTION_TYPE_VALUES, + "transaction_channel": TRANSACTION_CHANNEL_VALUES, + "transaction_status": TRANSACTION_STATUS_VALUES, + }, +} +# unique_pk: all rows — for reference and append_only entities +UNIQUE_PK_FINANCIAL_CRIME = { + "Currency": "currency_code", + "Transaction": "transaction_identifier", +} +# unique_current_pk: current rows only — for SCD2/bitemporal entities +# History rows legitimately repeat the entity identifier across prior/current pairs. +UNIQUE_CURRENT_PK_FINANCIAL_CRIME = { + "Party": "party_identifier", + "Account": "account_identifier", +} + +try: + from integrity_check import FKSpec + FK_SPECS_FINANCIAL_CRIME = [ + FKSpec("Account", "currency_code", "Currency", "currency_code"), + FKSpec("Transaction", "currency_code", "Currency", "currency_code"), + FKSpec("Transaction", "debit_account_identifier", "Account", "account_identifier", nullable=True), + FKSpec("Transaction", "credit_account_identifier", "Account", "account_identifier", nullable=True), + ] +except ImportError: + FK_SPECS_FINANCIAL_CRIME = None # integrity_check.py not on path + + +# --------------------------------------------------------------------------- +# Entry point — quick smoke test and integrity check +# --------------------------------------------------------------------------- +if __name__ == "__main__": + import json + + dataset = DatasetBuilder(pii_mode="safe").build( + n_parties=5, accounts_per_party=2, txns_per_account=3, with_history=True + ) + + for name, rows in dataset.items(): + print(f"\n=== {name} ({len(rows)} rows) ===") + print(json.dumps(rows[0], indent=2, default=str)) + + # Run integrity checks if runtime is available + try: + from integrity_check import check_integrity, print_report + print("\n--- Integrity check ---") + errors = check_integrity( + dataset, + fk_specs=FK_SPECS_FINANCIAL_CRIME, + not_null=NOT_NULL_FINANCIAL_CRIME, + enum_values=ENUM_VALUES_FINANCIAL_CRIME, + unique_pk=UNIQUE_PK_FINANCIAL_CRIME, + unique_current_pk=UNIQUE_CURRENT_PK_FINANCIAL_CRIME, + ) + print_report(errors) + except ImportError: + print( + "\n[integrity_check not found] " + "Copy integrity_check.py from agents/agent-artifact/skills/faker/runtime/ " + "alongside this file to enable integrity validation." + ) diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..9d0391b --- /dev/null +++ b/uv.lock @@ -0,0 +1,35 @@ +version = 1 +revision = 3 +requires-python = ">=3.14" + +[[package]] +name = "faker" +version = "40.19.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/01/28c8ddae8caaf82c929655000963d83e3f01265a9af34e823c2ef2eee8ac/faker-40.19.1.tar.gz", hash = "sha256:76fa71fd3bf320db25e5504eb356f9a76b8a95cd6098524d006f446035b6b89d", size = 1969318, upload-time = "2026-05-22T15:57:37.433Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/b4/40a1ec12ec834604f3848143343baf1c67bc9a1096e401907eaa0d25876a/faker-40.19.1-py3-none-any.whl", hash = "sha256:265259b37c013838baaae34940207288170df385d6c5281413fce56a3504d580", size = 2007643, upload-time = "2026-05-22T15:57:35.867Z" }, +] + +[[package]] +name = "random-corp" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "faker" }, +] + +[package.metadata] +requires-dist = [{ name = "faker", specifier = ">=40.19.1" }] + +[[package]] +name = "tzdata" +version = "2026.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/19/1b9b0e29f30c6d35cb345486df41110984ea67ae69dddbc0e8a100999493/tzdata-2026.2.tar.gz", hash = "sha256:9173fde7d80d9018e02a662e168e5a2d04f87c41ea174b139fbef642eda62d10", size = 198254, upload-time = "2026-04-24T15:22:08.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/e4/dccd7f47c4b64213ac01ef921a1337ee6e30e8c6466046018326977efd95/tzdata-2026.2-py2.py3-none-any.whl", hash = "sha256:bbe9af844f658da81a5f95019480da3a89415801f6cc966806612cc7169bffe7", size = 349321, upload-time = "2026-04-24T15:22:05.876Z" }, +]