# System Architecture: MercuryPay Fraud & AML Detection Platform

Version: 1.0 | Date: 2026-05-21 | Status: Draft

---

## 1. Executive Summary

A native extension to mw-core (Phoenix Umbrella) for real-time fraud and AML detection.
Adapted from Jube (open-source AML engine, AGPLv3) to the Elixir/BEAM runtime.

Targets:
- < 50ms synchronous risk scoring per transaction
- 665+ fraud features across 15 entity dimensions
- Hybrid detection: rule-based + ML inference (Axon)
- Full AML case management with audit trails
- Multi-tenant isolation per merchant

---

## 2. Core Design Principle (from Jube)

Three categories of state — never mix them:

| State Type         | Jube                        | mw-core                              |
|--------------------|-----------------------------|--------------------------------------|
| Immutable config   | Thread-local + background   | ETS GenServer + PubSub hot reload    |
| Mutable entity state | Redis + Local LRU         | Redis (Redix) + ETS two-tier cache   |
| Persistent audit   | PostgreSQL                  | MySQL (existing infra_repo)          |

---

## 3. Synchronous Scoring Path (hot path, every transaction)

  Inbound TX
      │
  [mw_router.Pipeline]
      ├─ RateLimit          (~0.1ms)
      ├─ SchemaValidator    (~0.5ms)
      ├─ RouteTable         (~0.1ms, ETS)
      ├─ CircuitBreaker     (~0.1ms)
      ├─ IdempotencyPlug    (~0.5ms)
      ├─ RiskScoringPlug    (~8–15ms) ← NEW
      │       ├─ 1. FeatureHydrator    (ETS hit ~0.2ms, Redis miss ~2ms)
      │       ├─ 2. AbstractionEngine  (ratios, time deltas ~1ms)
      │       ├─ 3. ActivationEngine   (threshold rules ~1ms)
      │       └─ 4. ModelServer        (Nx.Serving batch ~5ms)
      ├─ Dispatch → adapter_banking  (~20–40ms CBS)
      └─ Audit → mw_audit

Response includes: { risk_score, risk_decision, fired_rules, feature_snapshot }

Decision thresholds:
  score > 0.90 OR hard rule fired  → DECLINE
  0.60 < score ≤ 0.90              → REVIEW
  score ≤ 0.60, no rules           → APPROVE

---

## 4. Asynchronous Velocity Update Path

After response is dispatched (fire-and-forget):

  Phoenix.PubSub.broadcast("risk:velocity", tx_event)
      │
  [MwRisk.VelocityPipeline] (Broadway)
      │
      ├─ Entity: Card token
      ├─ Entity: Merchant ID
      ├─ Entity: Card+Merchant composite
      ├─ Entity: Card BIN
      ├─ Entity: Cardholder Email
      ├─ Entity: Shopper IP
      ├─ Entity: Acceptor / Acceptor Device
      └─ Entity: MCC
              │
  [infra_feature_store.TtlCounter]
      Redis INCRBYFLOAT (counters)
      Redis ZADD (journal sorted set)
      Redis HSET (payload latest)
              │
  PubSub: "feature_cache:invalidated" → all nodes refresh ETS

Target: full velocity update < 200ms after response.

---

## 5. New Apps Added to Umbrella

  apps/mw_risk/              ← scoring engine, rules, Nx.Serving, Oban workers
  apps/infra_feature_store/  ← two-tier ETS+Redis feature cache

  Extended apps:
  apps/infra_repo/           ← 6 new Ecto schemas
  apps/infra_queue/          ← VelocityPipeline Broadway consumer
  apps/gateway_web/          ← fraud investigator LiveView dashboard
  apps/mw_router/            ← RiskScoringPlug inserted in pipeline

---

## 6. Jube Concept → mw-core Mapping (summary)

  EntityAnalysisModel     → mw_risk.RiskModel (per-tenant DB config)
  AbstractionRule         → mw_risk.AbstractionEngine
  ActivationRule          → mw_risk.ActivationEngine + risk_activation_rules table
  TtlCounter              → infra_feature_store.TtlCounter (Redis INCRBYFLOAT)
  Journal (Sorted Set)    → infra_feature_store.Journal (Redis ZADD/ZRANGEBYSCORE)
  PayloadLatest           → infra_feature_store.PayloadLatest (Redis Hash)
  Local LRU Cache         → infra_feature_store.EtsTierCache
  SearchKeyCache          → MwRisk.FeaturePrecalcWorker (Oban)
  ExhaustiveTraining      → MwRisk.ModelTrainer (Axon, Oban)
  CasesAutomation         → MwRisk.CasesAutomationWorker (Oban)
  Sanctions Loader        → MwRisk.SanctionsLoader (Oban)
  Cache Pruning           → infra_feature_store.PruningWorker (Oban)

---

## 7. Deployment Roles (mirrors Jube docker-compose split)

Role A — Real-Time Scoring (horizontally scaled):
  RISK_SCORING_ENABLED=true
  RISK_VELOCITY_PIPELINE=true
  RISK_MODEL_SERVING=true

Role B — Background Workers (1–2 instances):
  RISK_MODEL_TRAINING=true
  RISK_LABEL_INGESTION=true
  RISK_CACHE_PRUNING=true
  RISK_SANCTIONS_LOADER=true
  RISK_FEATURE_PRECALC=true
  RISK_CASES_AUTOMATION=true
