# Scheme Management — Implementation Plan

**Answers:** `docs/Scheme-System/requirement-summary.md`
**Part of:** Acquiring Platform Transformation program (Workstream A) — see
`docs/Clearing-System/`, `docs/Terminal-Management/`, `docs/Switch-System/` for the others.
**Status:** see [`docs/ACQUIRING_PLATFORM_PROGRESS.md`](../ACQUIRING_PLATFORM_PROGRESS.md) for current phase-by-phase status — Phases 1-10 are done, Phases 11-13 sequenced below but not yet started.

---

## 1. Architecture Decision

New standalone umbrella app `scheme_core`, following the exact scaffold precedent set by
`dispute_core`/`risk_core` (see ADR-0001, ADR-0002 in `docs/adr/`): thin `Application` with
an `:enabled` config gate, a `Context` facade module, depending **only on `platform_core`**
so every other domain app depends on *it*, never the reverse.

**Non-disruption**: this is a wholly new app. No existing table, schema, or code path in
`dispute_core`, `settlement_core`, or anywhere else is modified to build it — it reads
nothing from and writes nothing to any existing app's tables in Phase 1. Merchants and
transactions on today's YSP-routed path are entirely unaffected by this module's existence.

---

## 2. Research Findings

Confirmed genuinely greenfield for ~80% of the requirement doc's scope. Prior art found:

- **`dispute_core`** has minimal soft-lookup tables: `dispute_schemes` (code, display_name,
  active, sort_order, logo_url) and `dispute_reason_codes` (scheme, code, description,
  category, default_response_days) — deliberately unconstrained, no FK to `DisputeCore.Case`,
  so imports never hard-fail on an unmapped value. Also `dispute_workflows` and
  `dispute_automation_rules` (per-scheme JSON config). **Left untouched** — see §7.
- **`settlement_core`** has `BinTable` (bin_prefix, card_type_id, card_brand, card_category,
  issuer_country, issuer_name, status) and `InterchangeRate` (card_type_id, scheme_name,
  rate_percentage, fixed_fee, effective_from/to, status) — flat lookups with no
  versioning/workflow, sourced from YSP dump files; `InterchangeRate`'s own moduledoc flags
  its data source as "PENDING — awaiting data file from YSP." **Left untouched.**
- **`mercury_device_middlelayer`**'s `switch/routing_rules.ex` does card-brand detection via
  hardcoded PAN-prefix pattern matching (`"4" -> "visa"`, `["51".."55"] -> "mastercard"`),
  and `config/upstream_networks.exs` hardcodes BIN-range routing rules per network — exactly
  what Phase 2's BIN/IIN master data should eventually replace. Not touched now (Workstream
  D is deferred).
- **`mw_risk`** has no scheme/BIN concepts — "high-risk BIN" fraud rules are expressed
  generically through its `risk_dictionaries`/`risk_lists` + `ReferenceDataCache` pattern
  (ETS, PubSub-invalidated). A future integration point, not built now.

---

## 3. Decisions Confirmed With User

1. Build as new standalone app `scheme_core` (not inside `dispute_core` or `settlement_core`).
2. `dispute_core`'s existing tables stay untouched; reconciliation deferred to Phase 6.
3. No dedicated Reporting Engine or API Gateway built as platform investments. `scheme_core`
   ships its own LiveView/CSV reports (Phase 11) and plain REST endpoints (Phase 13) for
   external consumers, matching the existing `ChargebackController` pattern —
   `mercury_device_middlelayer` and `mw_risk` consume these over HTTP with local caching on
   their end (both already carry HTTP client deps: `req`/`httpoison`/`cachex`).

---

## 4. Full Phased Roadmap

| # | Phase | Req §§ | Scope detail |
|---|---|---|---|
| 1 | **Foundation** | 7.1 (partial), 7.2, 7.6, 7.7 | Detailed in §5 below. |
| 2 | Card Products, BIN/IIN, Card Ranges | 7.3, 7.4, 7.5 | `CardProduct` (product_code, product_name, product_type [debit/credit/prepaid/commercial/corporate/virtual/token/fleet/gift], scheme_id, country_id, currency_id, status); `BinRange` (start_bin, end_bin, issuer, scheme_id, country_id, card_product_id, status) with versioning + BIN upload/download/lookup/search UI. Supersedes the switch's hardcoded BIN detection and `settlement_core.BinTable`. |
| 3 | EMV & Contactless Parameters | 7.8, 7.9 | `EmvAid` (aid, scheme_id, kernel_id, application_label, tac_default/denial/online, floor_limit, risk_parameters); `Capk` (rid, index, modulus, exponent, expiry, checksum); `ContactlessParameter` (scheme_id, contactless_limit, cvm_limit, offline_limit, reader_limit, consumer_device_limit). First structured EMV master data in the codebase — future input to `TmsCore.TerminalManagement`'s L3 config generation (Workstream C, §C3). |
| 4 | Network Routing & Network Parameters | 7.10, 7.11 | `RoutingConfig` (scheme_id, primary_network, secondary_network, failover_rules, domestic/international); `NetworkParameter` (iso_version, message_spec, processing_codes, function_codes, timeout, retry_count, network_windows). Feeds Workstream D once picked up. |
| 5 | Interchange & Scheme Fee Tables | 7.13, 7.14 | `InterchangeRate` (own table, distinct from `settlement_core`'s — scheme_id, card_product_id, mcc, transaction_type, currency_id, domestic/international, effective_from/to, rate); `SchemeFee` (fee_type [assessment/processing/cross_border/token/chargeback/arbitration/compliance/licensing], scheme_id, amount/rate, effective dates). Closes the "other fee structure not available" gap; feeds Workstream B's reconciliation bridge. |
| 6 | Reason Codes & Dispute Parameters | 7.15, 7.16 | `ReasonCode` (scheme_id FK not free-text, code, description, dispute_category, time_limits, required_documents); `DisputeParameter` (scheme_id, response_time, sla, chargeback_window, arbitration_window, evidence_requirements). **Where the `dispute_core` reconciliation decision gets made** — see §7. Not pre-decided. |
| 7 | Settlement Calendar & Holiday Calendar | 7.17, 7.18 | `SettlementCalendar` (scheme_id, cycle_type, cutoff_time, processing_window); `HolidayCalendar` (country_id, scheme_id nullable, holiday_date, holiday_type [national/scheme/bank/settlement]). No existing concept anywhere. Needed by Workstream B's clearing cycle scheduling. |
| 8 | Certification & Compliance Management | 7.19, 7.20 | `Certification` (scheme_id, certification_type [L2/L3/host/contactless/softpos], cert_id, issued_date, expiry_date, renewal_status) — **program-level** (org-wide), distinct from Workstream C's terminal-**instance**-level tracking; `ComplianceRecord` (scheme_id, compliance_type [PCI_DSS/PCI_PTS/EMV/scheme/key_rotation], status, audit_date, next_review_date). Needs scheduled expiry checks — first phase pulling in `Oban` (already a `platform_core` dep). |
| 9 | Scheme Bulletins + Notifications | 7.21, §14 | `Bulletin` (scheme_id, bulletin_number, bulletin_date, description, impact, implementation_status, effective_date). First phase needing outbound notifications. Decide then: add a `tms_core` dependency to reuse `TmsCore.AlertsCore`'s dispatch/channel/template engine (precedented — `dispute_core`'s `mix.exs` already depends on `tms_core`), or build local. **Recommend reuse** — `AlertsCore` is already a working generic rule→dispatch→multi-channel engine. |
| 10 | Merchant Category Rules | 7.12 | `MccRule` (mcc, description, allowed_transactions, cashback_rules, installment_rules, risk_category, restrictions). Deliberately late — needs Card Product + Country/Currency settled first, and borders MMS/MDR territory (out of scope). |
| 11 | Reports & Administration | 7.22, 7.23, §9 | Own LiveView/CSV report pages per data domain. RBAC roles formalized here: Scheme Administrator, Product Administrator, Operations, Settlement, Network, Compliance, Risk, Auditor, Read Only, API User. |
| 12 | Maker/Checker Approval Workflow (retrofit) | §10, §12 | Generalized draft→approval→publish workflow layered onto every master from Phases 1-11. A thin retrofit, not a rewrite, **because** every schema from Phase 1 onward already carries `status`/`effective_from`/`effective_to`/`version`/`created_by`/`updated_by`. Phase 12 only adds an `approvals` table (entity_type, entity_id, requested_by, approved_by, decision, decided_at) plus status-transition gating per context module. |
| 13 | External Lookup APIs & Bulk Import/Export | §15, §13 | REST lookup endpoints for `mercury_device_middlelayer`/`mw_risk`/Merchant Management. CSV bulk import/export Mix tasks following `settlement_core`'s `load_bin_table.ex`/`load_interchange_rates.ex` pattern (dry-run, validation, chunked upsert). |
| — | Future Enhancements (§17) | — | Explicitly out of scope for all phases (AI bulletin ingestion, scheme API sync, AI impact analysis, automatic BIN/fee updates, predictive renewal alerts). |

**Cross-cutting convention set in Phase 1, applied to every master table in every subsequent
phase:** `status`, `effective_from`, `effective_to`, `version`, `created_by`, `updated_by`.
This is what makes Phase 12's maker/checker retrofit a thin layer rather than a schema rewrite.

---

## 5. Phase 1 (detailed): Foundation

### New app: `apps/scheme_core`

- `mix.exs` — mirrors `apps/dispute_core/mix.exs`:
  `deps: [{:platform_core, in_umbrella: true}, {:ecto_sql, "~> 3.13.2"}, {:jason, "~> 1.2"}]`.
- `lib/scheme_core/application.ex` — mirrors `dispute_core`'s `:enabled` gate pattern.
- `lib/scheme_core/context.ex` — facade, mirrors `DisputeCore.Context`.

### Schemas

Table names avoid collision with `dispute_schemes`/`dispute_reason_codes`. All use
`@primary_key {:id, :id, autogenerate: true}` on `PlatformCore.Repo`:

1. **`SchemeCore.Scheme`** (`scheme_masters`) — `code` (free-text, lowercased like
   `DisputeCore.Scheme`, extensible per §2/§18's "future schemes without software changes"),
   `display_name`, `description`, `status` (`draft/active/suspended/inactive`),
   `processing_window`, `settlement_cycle_days`, `effective_from`, `effective_to`,
   `version`, `created_by`, `updated_by`. Lifecycle: `activate!/1`, `suspend!/1`,
   `deactivate!/1`, mirroring `TmsCore.TerminalManagement.ConfigFileVersion.activate!/1`.
2. **`SchemeCore.Country`** (`scheme_countries`) — ISO 3166 `country_code`, `name`,
   `domestic_rules` (map), `regional_restrictions` (map), `processing_rules` (map), `status`.
3. **`SchemeCore.Currency`** (`scheme_currencies`) — ISO 4217 `currency_code`, `name`,
   `decimal_precision`, `is_settlement_currency`, `is_processing_currency`, `status`.
4. **`SchemeCore.SchemeCountryMapping`** (`scheme_country_mappings`) — `scheme_id`,
   `country_id`, `status`.
5. **`SchemeCore.SchemeCurrencyMapping`** (`scheme_currency_mappings`) — `scheme_id`,
   `currency_id`, `status`.
6. **`SchemeCore.AuditLog`** (`scheme_audit_logs`) — `actor_id`, `action`, `entity_type`,
   `entity_id`, `before` (map), `after` (map). Written by every mutating context function
   via `SchemeCore.Context.record_audit/1`. Built once here, reused by every subsequent
   phase (§7.24 Audit) instead of ad-hoc audit fields per table.

### Migrations

Under `apps/da_product_app/priv/repo/migrations/` (the umbrella's existing single migration
path, per `docs/REPO_CONSOLIDATION_MIGRATION_PLAN.md`), module name
`DaProductApp.Repo.Migrations.*`.

### Context API

CRUD + lifecycle per schema, plus `list_active_schemes/0`, `find_scheme_by_code/1`,
`list_countries_for_scheme/1`, `list_currencies_for_scheme/1`, `record_audit/1`.

### UI (`platform_web`)

Mirrors `ChargebackLive.CardSchemes`'s proven full-CRUD pattern
(`apps/platform_web/lib/platform_web/live/chargeback_live/card_schemes.ex`):

- LiveViews: `live/scheme_live/dashboard.ex`, `schemes.ex`, `countries.ex`, `currencies.ex`.
- Router: `/admin/schemes`, `/admin/schemes/config`, `/admin/schemes/countries`,
  `/admin/schemes/currencies`.
- Menu (`lib/platform_web/menu/registry.ex`): new top-level section with those four entries.

### Seed data

`apps/da_product_app/priv/repo/seeds/scheme_core_seed.exs` — the 14 schemes from requirement
§3 (Visa, Mastercard, RuPay, Amex, JCB, UnionPay, Discover, Diners, Jaywan, Meeza, mada,
EFTPOS, Local/Domestic, one placeholder future scheme), plus a starter set of
countries/currencies.

---

## 6. Verification

- `mix compile` at umbrella root — confirms clean, one-directional dependency graph (no other
  app depends on `scheme_core` in Phase 1).
- `mix ecto.migrate`, run the seed file, confirm via `iex -S mix`
  (`SchemeCore.Context.list_active_schemes/0`).
- Browser walk-through of all four LiveViews — full CRUD + activate/suspend/deactivate.
- Confirm `dispute_core`'s existing LiveViews / CSV import / Mastercom sync are untouched —
  this phase must be purely additive per §3 decision #2.

---

## 7. Reconciliation Decisions

**`dispute_core.Scheme`/`ReasonCode` vs `scheme_core.Scheme`/`ReasonCode` — RESOLVED in
Phase 6 (2026-08-09): kept permanently separate, not reconciled.** Re-checked `dispute_core`'s
current state before deciding: its `Scheme`/`ReasonCode` were uncommitted, actively in-flight
work with a real live consumer (SLA due-date auto-computation in `DisputeCore.Context`),
deliberately soft/no-FK by design (own moduledoc: "never hard-fail"). The two now-built
tables serve genuinely different purposes — `dispute_core`'s is a fast, resilient,
never-fail lookup feeding the live case engine; `scheme_core.ReasonCode` is governed,
versioned master reference data (hard FK, full audit trail, richer fields per requirement
§7.15: `dispute_category`/`time_limits`/`required_documents`). Forcing a merge would have
compromised `dispute_core`'s deliberate simplicity for no real benefit. `dispute_core` was
left completely untouched — confirmed via `git status` before and after.

**`settlement_core`'s `BinTable`/`InterchangeRate` — still genuinely open.** Phases 2 and 5
both shipped their own separate tables (`scheme_core.BinRange`, `scheme_core.InterchangeRate`)
without touching `settlement_core`'s, per the same "leave existing tables alone" principle —
but unlike the `dispute_core` case, no explicit decision has been made yet on whether
`settlement_core` should eventually consume `scheme_core`'s richer data. Revisit when
`settlement_core` itself is being worked on, not speculatively here.
