Skip to content

Repository files navigation

Banking & AML-FT Platform

Demo Notice: This project is a DEMO / educational reference implementation. It is NOT production-ready. It reproduces architectural patterns from a real-world AML-FT system but with significant simplifications (see Design Decisions for details). Do not use as-is in production environments.

A monorepo containing two integrated applications for AML-FT (Anti-Money Laundering / Financing of Terrorism):

  1. banking-app (port 8080) - Core banking data: companies, users, bank accounts, transactions, payment cards
  2. aml-ft-app (port 8081) - Detection system (rules, transaction investigations, notices, reports)

Architecture Overview

┌─────────────────────────────────────────────────────────────────────────────────────┐
│                            Banking & AML-FT Platform                                │
│                                                                                     │
│  ┌────────────────────────────────────────────────────────────────────────────┐     │
│  │                         PostgreSQL / SQLite                                │     │
│  │                                                                            │     │
│  │  ┌────────────────────────────────┐       ┌──────────────────────────────┐ │     │
│  │  │ banking schema                 │       │    aml_ft schema             │ │     │
│  │  │                                │       │                              │ │     │
│  │  │ - companies                    │       │ - rule_params                │ │     │
│  │  │ - users                        │       │ - rules                      │ │     │
│  │  │ - bank_accounts                │       │ - transaction_investigations │ │     │
│  │  │ - transactions                 │       │ - notices                    │ │     │
│  │  │ - payment_cards                │       │ - reports                    │ │     │
│  │  │ - audit_logs                   │       │ - operators                  │ │     │
│  │  └────────────────────────────────┘       └──────────────────────────────┘ │     │
│  └────────────────────────────────────────────────────────────────────────────┘     │
│                                        ▲                                            │
│                                   read/analyze                                      │
│                                        │                                            │
│  ┌──────────────────────────────┐      │      ┌──────────────────────────────┐      │
│  │     Banking App              │      │      │     AML-FT App               │      │
│  │     (port 8080)              │      │      │     (port 8081)              │      │
│  │                              │      │      │                              │      │
│  │  ┌───────────────────────┐   │      │      │  ┌───────────────────────┐   │      │
│  │  │ API                   │   │      │      │  │ API                   │   │      │
│  │  │ (FastAPI)             │   │      │      │  │ (FastAPI)             │   │      │
│  │  └───────────────────────┘   │      │      │  └───────────────────────┘   │      │
│  │                              │      │      │  ┌───────────────────────┐   │      │
│  │  ┌───────────────────────┐   │◀─────┼─────▶│  │ RabbitMQ              │   │      │
│  │  │ RabbitMQ              │   │      │      │  │ Consumer              │   │      │
│  │  │ Publisher             │   │      │      │  └───────────────────────┘   │      │
│  │  └───────────────────────┘   │      │      │                              │      │
│  │                              │      │      │  ┌───────────────────────┐   │      │
│  │  - CRUD entities             │      │      │  │ Detection             │   │      │
│  │  - Validations               │      │      │  │ Rules                 │   │      │
│  │  - Event push                │      │      │  │ (ThreadPool)          │   │      │
│  └──────────────────────────────┘      │      └──────────────────────────────┘      │
│                                        │                                            │
└────────────────────────────────────────┼────────────────────────────────────────────┘
                                         ▼
                                  Event Flow Diagram:
                                
          ┌─────────────┐      ┌──────────┐      ┌─────────────┐
          │  Banking    │      │ RabbitMQ │      │  AML-FT     │
          │   App       │─────▶│  Queue   │─────▶│   App       │
          │ creates     │      │          │      │ runs rules  │
          │ transaction │      │          │      │ creates     │
          │             │      │          │      │ rules       │
          └─────────────┘      └──────────┘      └─────────────┘

Data Flow

  1. Entity Created - User creates transaction via Banking App API
  2. Stored in DB - Transaction saved to SQLite/PostgreSQL
  3. Published to Queue - Event published to RabbitMQ (transaction.created)
  4. Consumed by AML-FT - RabbitMQ consumer receives event
  5. Rules Executed - DetectionAdapter runs all matching rules in parallel
  6. Rules Created - For each triggered rule, a Rule record is created
  7. TransactionInvestigation/Notice - If thresholds exceeded, TransactionInvestigation or Notice created

Two Applications

Banking App (banking-app/)

Core banking data management.

Entities:

  • Company - Legal entities (SIREN, NAF code, address)
  • User - Persons linked to companies (KYC level, nationality)
  • BankAccount - IBAN/BIC accounts
  • Transaction - Money transfers (PAYIN/PAYOUT)
  • PaymentCard - Credit/debit cards

API Endpoints:

  • GET/POST /api/companies
  • GET/POST /api/users?company_id=xxx
  • GET/POST /api/bank-accounts?company_id=xxx
  • GET/POST /api/transactions?bank_account_id=xxx
  • GET/POST /api/payment-cards?user_id=xxx or ?bank_account_id=xxx

Port: 8080

Features:

  • Hexagonal architecture (ports/adapters)
  • RESTful API with FastAPI
  • RabbitMQ event publishing
  • Pydantic validation (SIREN, IBAN, BIC, country codes)
  • Soft delete via status field

AML-FT App (aml-ft-app/)

Anti-Money Laundering / Financing of Terrorism detection.

Entities:

  • RuleParam - Detection rule configurations
  • Rule - Individual rule results triggered by detection rules
  • TransactionInvestigation - Transaction investigation decisions
  • Notice - Investigation cases
  • Report - Investigation workflows
  • ReportMessage - Messages attached to reports
  • Operator - Back-office users

API Endpoints:

  • GET/POST /api/rule-params
  • GET/POST /api/rules
  • GET/POST /api/transaction-investigations
  • GET/POST /api/notices
  • GET/POST /api/reports
  • GET/POST /api/operators

Port: 8081

Features:

  • Event-driven detection via RabbitMQ
  • Parallel rule execution (ThreadPoolExecutor)
  • investigation_score vs notice_score thresholds
  • NiceGUI back-office UI (port 8082)
  • Sanctions/PEP name screening (rapidfuzz fuzzy matching)
  • Hexagonal architecture

Shared Package

shared/
├── shared/
│   ├── __init__.py
│   ├── context_session.py    # Async session management
│   ├── adapter_base.py       # Common adapter utilities
│   └── dependencies.py       # FastAPI dependencies
└── pyproject.toml

Provides:

  • SessionContext - Manages async DB sessions
  • apply_deleted_filter, get_deleted_status, paginate_query - Soft delete utilities
  • get_session_dependency - FastAPI dependency injection

Tech Stack

Component Technology
Framework FastAPI
ORM SQLAlchemy 2.0 (async)
Database SQLite (dev), PostgreSQL (prod)
Message Queue RabbitMQ (aio-pika)
Validation Pydantic
UI Framework NiceGUI (aml-ft-app only)
Testing Pytest, pytest-asyncio
Linting Ruff
Type Checking Mypy
Package Manager Poetry

Running Both Apps

Prerequisites

  • Python 3.12+
  • Poetry
  • RabbitMQ (optional, for event-driven mode)

Installation

# Install banking-app
cd banking-app
poetry install

# Install aml-ft-app
cd ../aml-ft-app
poetry install

Running Banking App

cd banking-app
poetry run python main.py
# API: http://127.0.0.1:8080

Running AML-FT App

cd aml-ft-app
poetry run python main.py
# API: http://127.0.0.1:8081

Running AML-FT Back-office UI

cd aml-ft-app
poetry run python ui.py
# UI: http://127.0.0.1:8082

Seeding Data

# Seed banking data (5 companies, 6 users, 11 accounts, 27 transactions)
cd banking-app
poetry run python scripts/seed.py

# Seed rule params and operators
cd ../aml-ft-app
poetry run python scripts/seed.py

Running Tests

# Banking app tests
cd banking-app
poetry run pytest

# AML-FT app tests
cd aml-ft-app
poetry run pytest

Manual Detection Trigger

curl -X POST http://127.0.0.1:8081/api/detection/evaluate \
  -H "Content-Type: application/json" \
  -d '{
    "entity_type": "TRANSACTION",
    "entity_data": {
      "id": "tx-123",
      "amount": 15000,
      "currency": "EUR",
      "company_id": "comp-456"
    }
  }'

Project Structure

aml-ft-platform/
├── README.md                         # This file
│
├── shared/                           # Shared utilities
│   ├── pyproject.toml
│   └── shared/
│       ├── __init__.py
│       ├── context_session.py
│       ├── adapter_base.py
│       └── dependencies.py
│
├── banking-app/                     # Core banking application
│   ├── main.py                      # FastAPI entry point
│   ├── config.py                    # Settings
│   ├── pyproject.toml               # Dependencies
│   ├── banking/
│   │   ├── models.py                # SQLAlchemy models
│   │   ├── ports/                   # Interface definitions
│   │   ├── adapters/                # Implementations
│   │   └── api/                     # REST routes
│   └── scripts/
│       └── seed.py                  # Data seeding
│
└── aml-ft-app/                      # AML-FT detection application
    ├── main.py                      # FastAPI entry point
    ├── config.py                    # Settings
    ├── ui.py                        # NiceGUI UI
    ├── pyproject.toml               # Dependencies
    ├── aml_ft/
    │   ├── models.py                # SQLAlchemy models
    │   ├── enums.py                 # Status enums
    │   ├── adapters/                # Business logic
    │   ├── rules/                   # Detection rules
    │   ├── services/                # Business services
    │   ├── ports/                   # Interfaces
    │   └── api/                     # REST routes
    ├── ui_pages/                    # NiceGUI pages
    ├── scripts/
    │   └── seed.py   # Seed data
    └── tests/         # Test suite

Configuration

Banking App

# Environment variables
DATABASE_URL=sqlite:///banking.db
RABBITMQ_URL=amqp://guest:guest@localhost:5672/
API_PREFIX=/api
HOST=0.0.0.0
PORT=8080

AML-FT App

# Environment variables
DATABASE_URL=sqlite:///./banking.db
BANKING_API_URL=http://127.0.0.1:8080
RABBITMQ_HOST=localhost
RABBITMQ_PORT=5672
RABBITMQ_USER=guest
RABBITMQ_PASSWORD=guest
TX_INVESTIGATION_THRESHOLD=7
NOTICE_THRESHOLD=50
JWT_SECRET=dev-secret-key

Thresholds

Threshold Default Description
tx_investigation_threshold 7 Create TransactionInvestigation if sum(rule.investigation_score) >= 7
notice_threshold 50 Create Notice if sum(rule.notice_score) >= 50

Detection Rules

Rules are organized by entity type. All thresholds are class constants pulled directly from the rule source files — see each class for the exact values. Rules execute in parallel via ThreadPoolExecutor in the detection engine.

Transaction Rules (9 rules)

rule_name Class rule_group Condition Data Dependency
TRANSACTION_HIGH_AMOUNT HighAmountRule HIGH_AMOUNT amount >= 9 900 EUR None — base event data
TRANSACTION_CASH_DEPOSIT CashDepositRule HIGH_AMOUNT payment_method == "CASH" AND amount >= 4 900 EUR None — base event data
TRANSACTION_COMPANY_NEW_HIGH_VALUE NewCompanyHighValueRule HIGH_AMOUNT amount >= 4 900 EUR AND company registered ≤30 days ago company_registration_date (EntityEnricher)
TRANSACTION_VELOCITY_HIGH VelocityRule VELOCITY >10 transactions on the same account in 24h window last_24_hours_bank_account_transactions (Redis)
TRANSACTION_PATTERN_SIMILAR_AMOUNTS AmountPatternRule SUSPICIOUS_PATTERN ≥3 transactions with identical (type, amount) in 24h window last_24_hours_bank_account_transactions (Redis)
TRANSACTION_RAPID_SEQUENCE RapidTransactionsRule SUSPICIOUS_PATTERN ≥9 transactions on the same account within 4 minutes last_60_minutes_bank_account_transactions_count + time window (Redis)
TRANSACTION_CARD_HIGH_VALUE HighValueCardRule CARD_FRAUD payment_method == "CARD" AND amount >= 4 900 EUR None — base event data
TRANSACTION_CARD_VISHING CardVishingRule CARD_FRAUD ≥3 identical-amount CARD moneyout transactions in 60min (30d fallback) last_60_minutes_bank_account_transactions (Redis)
TRANSACTION_HIGH_RISK_COUNTRY HighRiskCountryRule HIGH_RISK_COUNTRY Sender/recipient country is in HIGH_RISK_COUNTRIES_FOR_SENDING (19 sanctioned) or TAX_HAVEN_COUNTRIES (10) sender_country, recipient_country (EntityEnricher)

User Rules (3 rules)

rule_name Class rule_group Condition Data Dependency
USER_RISKY_COUNTRY RiskyCountryRule RISKY_COUNTRY User's country is in the HIGH_RISK_COUNTRIES list (29 sanctioned + tax-haven) None — base country field
USER_MULTIPLE_ACCOUNTS MultipleAccountsRule MULTIPLE_ACCOUNTS bank_account_count > 3 bank_account_count (EntityEnricher)
USER_VISHING VishingRule CARD_FRAUD >3 virtual payment cards created in the last 7 days payment_cards (EntityEnricher)

Company Rules (2 rules)

rule_name Class rule_group Condition Data Dependency
COMPANY_NEW NewCompanyRule COMPANY_AGE Company registered ≤30 days ago company_registration_date (EntityEnricher)
COMPANY_HIGH_VOLUME HighVolumeRule HIGH_VOLUME Monthly tx count ≥170 OR monthly volume ≥600 000 EUR monthly_volume (EntityEnricher via Redis)

Note: Data marked as "(EntityEnricher)" is populated by the EntityEnricher service, which queries Redis projections (TransactionHistory, VolumeAggregation, etc.) with a REST API fallback to banking-app. Data marked as "(Redis)" comes directly from Redis projections without a REST fallback. Rules with "None" operate on raw fields from the event payload.

Sanctions / PEP Name Screening

The AML-FT app screens company and user names against an in-memory sanctions/PEP-style list via two rules:

  • COMPANY_SANCTIONS_MATCH — company name against the list
  • USER_SANCTIONS_MATCH — user name against the list

Matching uses rapidfuzz fuzzy matching with a default threshold of 0.87, tunable via rule parameters. Each rule combines the name check with a high-risk country check (either one triggers). The transaction-level high-risk country rule (TRANSACTION_HIGH_RISK_COUNTRY) is unchanged.

CSV import skipped. A CSV import feature for the sanctions list should have been implemented, but for simplicity the list is seeded from a hardcoded dict in aml-ft-app/scripts/seed.py (list source synthetic_demo). A real implementation would add CSV import.

Limitations. This is a demo simplification, not a production-grade control. It has no phonetic awareness (e.g. Catherine vs Katherine), no date-of-birth or address disambiguation, and no alias handling.

Shared-Recipient Detection

Noise risk. Shared-recipient detection (TRANSACTION_SHARED_RECIPIENT) fires when a recipient IBAN is paid by 2+ otherwise-unrelated companies in a 90-day window. Legitimate high-fan-in recipients (utilities, tax authorities, landlords, payment processors) can naturally trigger it; known-good payees can be added to the trusted-recipient allowlist (/api/trusted-recipients, manageable from the back-office UI). Expect noise beyond curated demo data.

Hashing trade-off. Recipient IBANs are stored only as sha256 hashes (data minimization). Status/debug output shows opaque hex keys, which trades debuggability for not holding raw account numbers.

Demo

Prerequisites

Quick Start — Automated (one command)

# 1. Install & start Redis (one-time)
brew install redis && brew services start redis

# 2. From project root, run the demo
bash demo.sh

The demo.sh script handles everything — cleans stale processes and databases, seeds data, starts both apps and the NiceGUI UI in the background, creates entities, runs detection, and prints a summary. The back-office UI is available at http://127.0.0.1:8082 (default login: admin / admin).

Quick Start — Step by Step

If you prefer to run each step manually:

# 1. From project root, clean state
rm -f banking-app/banking.db aml-ft-app/aml_ft.db
redis-cli FLUSHALL

# 2. Seed both apps
(cd banking-app && poetry run python scripts/seed.py)
(cd aml-ft-app && poetry run python scripts/seed.py)

# 3. Start banking-app (terminal 1)
cd banking-app && poetry run python main.py

# 4. Start aml-ft-app (terminal 2)
cd aml-ft-app && NOTICE_THRESHOLD=35 poetry run python main.py

# 5. Run the demo (terminal 3) — creates entities and triggers detection
bash demo.sh

# 6. Open the back-office UI (port 8082) — manage notices, investigations, reports
#    Visit http://127.0.0.1:8082 (default login: admin / admin)

Note: The demo.sh script starts the NiceGUI UI automatically. If you run the demo from scratch (bash demo.sh), step 6 is handled for you.

What the Demo Does

Creates 5 companies, 11 bank accounts, 6 users, and 8 transaction scenarios across all 5 companies. The demo automatically starts three services:

Service Port Description
Banking App 8080 Core banking CRUD API
AML-FT App 8081 Detection API (rules, investigations, notices)
NiceGUI UI 8082 Back-office interface for operators

Detection targets:

Company Activity Notices Created
C1 - HRT-Corp High-risk country transactions RISKY_COUNTRY, HIGH_RISK_COUNTRY
C2 - NewCo Ventures Card activity pattern detection CARD_ACTIVITY_PATTERN
C3 - VolumeCorp High amounts + batch patterns HIGH_AMOUNT, SUSPICIOUS_PATTERN

Viewing Projection Data

After the demo, view stored projection data:

curl http://localhost:8081/api/projections/bank_account/data
curl http://localhost:8081/api/projections/transaction_history/data
curl http://localhost:8081/api/projections/volume_aggregation/data
curl http://localhost:8081/api/projections/card_activity/data

Expected Results

After running the demo:

  • ~5 Notices across 3 companies (mix of solo-trigger and combo-trigger)
  • ~7 TransactionInvestigations (most require 2+ rules combining)
  • ~25+ Rule records created

View results in the back-office UI at http://127.0.0.1:8082 (login: admin / admin), or via the API:

# Notices
curl http://127.0.0.1:8081/api/notices?limit=20

# Transaction investigations
curl http://127.0.0.1:8081/api/transaction-investigations?limit=20

# Triggered rules
curl http://127.0.0.1:8081/api/rules?limit=50

Continuous Integration

The repo ships a four-job GitHub Actions pipeline (.github/workflows/ci.yml) that runs on every push and pull request to main:

Job Purpose
banking-app-ci Lint, format check, tests with coverage floor, and type checking for banking-app/
aml-ft-app-ci Lint, format check, tests with coverage floor, and type checking for aml-ft-app/
docker-build Builds both Docker images (repo-root build context, GitHub Actions cache, retry on transient install failures)
e2e-smoke Boots the isolated ci-e2e stack, seeds the AML-FT app, runs the end-to-end smoke script, always tears the stack down

Each app job runs the same quality gate:

  • ruff check . — lint
  • ruff format . --check — format check
  • pytest --cov=<app> --cov-fail-under=<floor> — tests with a coverage floor: banking 80% and aml_ft 65%
  • mypy . — blocking type check (52 files clean in banking-app, 167 in aml-ft-app)

docker-build builds both images with the build context at the repository root, uses GitHub Actions cache (cache-mode: max) to reuse layers across runs, and retries installs to ride over transient PyPI timeouts.

e2e-smoke brings up the isolated -p ci-e2e stack (docker-compose.yml + docker-compose.e2e.yml), seeds the AML-FT app, runs banking-app/scripts/e2e_smoke.py against both apps, and always cleans up (down -v) even on failure.

Running the same checks locally

# Banking app
cd banking-app
poetry run ruff check .
poetry run ruff format . --check
poetry run pytest --cov=banking --cov-fail-under=80
poetry run mypy .

# AML-FT app
cd aml-ft-app
poetry run ruff check .
poetry run ruff format . --check
poetry run pytest --cov=aml_ft --cov-fail-under=65
poetry run mypy .

Pre-commit Hooks

# Install hooks
pre-commit install

# Run hooks manually
pre-commit run --all-files

Architecture Decisions

Key design choices and known gaps are documented in docs/architecture-decisions.md. Topics include:

  • Hexagonal (ports/adapters) structure and why
  • Single Transaction model vs payment-method-specific entities
  • Simplified state machine (PENDING/VALIDATED/CANCELED)
  • Unidirectional event flow (RabbitMQ one way, HTTP the other)
  • Idempotency via Idempotency-Key header
  • Rule thresholds configurable via RuleParam (code constants as fallback defaults)
  • Thin events + Redis projections
  • Company-level notice locking and single-instance limitation
  • Observability: structured JSON logging via structlog; metrics/tracing intentionally omitted for demo scope
  • Production-readiness gaps (secrets management)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages