Skip to content

Repository files navigation

payment-router

CI Python 3.11-3.13 License: MIT

Explainable, multi-objective routing simulator for cross-border payments.

Given a source currency, target currency, amount, and cost/time preference, the simulator builds a payment graph across Wise quotes, SEPA transfers, an explicit SWIFT correspondent-banking scenario, and a CNY-bound CIPS scenario. It then compares direct and multi-hop routes without hiding where the numbers came from.

Important

This is an early-stage teaching and research project, not a production quote, transfer, compliance, or financial-advice system. Never use its output to initiate or promise a real payment.

Why this exists

Most public comparison tools show one provider's headline quote. Real cross-border transfers can accumulate fixed fees, percentage fees, FX spread, and delay across several institutions. payment-router makes that structure inspectable:

  • parallel providers remain distinct instead of being collapsed into one edge;
  • cost, time, and recipient amount are shown together;
  • same-currency rails such as SEPA and SEPA Instant can be compared directly;
  • every fee, time, and FX component carries its own provenance classification;
  • live, source-backed, and teaching-assumption values are never presented as equally certain.

Example

uv run remit route USD CNY 1000 --prefer=cheapest
uv run remit route USD CNY 1000 --top-n=3
uv run remit decide USD CNY 1000
uv run remit sensitivity USD CNY 1000
uv run remit compare USD CNY 1000 --on 2024-01-02
uv run remit breakeven USD CNY --min 10 --max 100000
uv run remit regime USD CNY --min 10 --max 100000 --amount-samples 12 --weight-steps 60
uv run remit route HKD CNY 10000 --top-n=3
uv run remit route EUR EUR 1000 --top-n=3
uv run remit sources
uv run remit serve

The CLI renders a selected route, hop-by-hop fees and timing, recipient amount, and a Mermaid diagram. decide compares cheapest, fastest, and balanced profiles against the same graph. sensitivity sweeps the cost/time weight and shows exactly where the winning route flips. compare prices one corridor at two ECB rate dates and reports what the rate regime alone changed. breakeven sweeps the amount instead, showing which route wins at which size and where the winner flips. regime combines the amount and preference axes into one sampled map and summarizes four-neighbour connected winner regions.

Web console

remit serve starts a local web console at http://127.0.0.1:8000 on top of the same routing engine the CLI uses:

  • corridor form with amount, currency swap, cheapest/fastest/balanced preference, and top-1/3/5 candidates;
  • per-route stat tiles (recipient amount, total fees, estimated time), a hop-by-hop flow diagram with live intermediate balances, and copyable Mermaid source;
  • a side-by-side decision board for the three profiles with the same tradeoff note the CLI prints;
  • provenance badges on every route and hop, provider warnings, and the full auditable source registry;
  • a profile-comparison chart when the three profiles pick different routes;
  • a rate-date comparison view: pick a past ECB fixing and see the same corridor priced under both dates side by side, with the mid-rate, fee, and recipient-amount deltas;
  • a Break-even view: which route wins across a logarithmic amount axis, and the bracket where the winner changes;
  • a Sensitivity view: a regime strip showing which route wins as the cost/time weight sweeps from all-time to all-cost, per-route timing range bars, a balanced-stability note, and qualitative timing caveats;
  • a Regime map view: a two-dimensional sampled area chart with logarithmic amount on the horizontal axis and cost weight 0–1 on the vertical axis, plus route and connected-region legends. It uses the same self-contained palette in light and dark themes and never fills unsampled cells;
  • shareable URLs (every query updates the address bar and can be bookmarked or sent; the back button restores previous results) and recent-search chips;
  • a short-lived quote session cache so switching preference or top-N reuses the freshly built graph instead of re-querying live providers, with a visible fetched/cached freshness indicator;
  • light/dark themes; the page itself is fully self-contained with no CDN or external requests.

Start it with uv run remit serve (add --open to launch a browser, or --host/--port to change the binding).

AI insight (optional)

When Anthropic credentials are available to the server process (ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, or an ant auth login profile), the console shows an AI insight panel that streams a Claude-generated reading of the current result: the bottom-line pick, the trade-off that matters, and a caveat grounded in the provenance labels. The explanation is generated strictly from the JSON the console is displaying, answers in your browser language, and always repeats the simulator disclaimer. Without credentials the panel is hidden and the console works exactly as before — no other feature depends on it.

The default model is claude-opus-4-8; set PAYMENT_ROUTER_AI_MODEL to override. The API surface is POST /api/explain (server-sent events).

The JSON API behind it is documented at /api/docs (/api/meta, /api/route, /api/decide, /api/sensitivity, /api/compare, /api/breakeven, /api/regime, /api/sources). The console is a local tool, not a deployment target: it adds no authentication, persistence, or payment initiation surface.

What is implemented

  • Wise: live unauthenticated quote fields from the public guest quote API.
  • SEPA: EUR-to-EUR SCT model with a source-backed one-business-day target and an explicitly estimated sender fee.
  • SEPA Instant: EUR-to-EUR SCT Inst model with the source-backed 10-second maximum and an explicitly estimated sender fee.
  • SWIFT scenario: configurable correspondent-hop simulation. The topology is source-backed; all numeric hop parameters are labelled ESTIMATED.
  • CIPS scenario: CNY-destination teaching model with a deliberately shorter default chain than SWIFT. Its cross-border RMB role, direct/indirect participant structure, and 5×24+4 operating window are source-backed; every numeric parameter remains ESTIMATED.
  • FX sources: a frozen teaching table by default; --fx live switches to ECB euro reference rates via Frankfurter with an on-disk snapshot cache and explicit stale/fallback behavior.
  • Supported currencies: USD, EUR, GBP, CNY, HKD, and SGD. SEPA remains EUR-only, while CIPS is limited to CNY-target corridors.
  • Routing: normalized cost/time Dijkstra selection plus edge-expanded top-N enumeration that preserves parallel payment networks.
  • Historical comparison: remit compare prices one corridor at two ECB rate dates. Only the FX table moves between the runs, and providers that quote at request time are excluded from both sides, so the deltas isolate the rate regime rather than mixing in a provider-set difference.
  • Break-even analysis: remit breakeven finds the amounts at which the best route changes — fixed fees dominate small transfers, FX spread dominates large ones. Coarse geometric sampling plus bisection locates a crossing precisely without a provider request per unit of precision, and the result is always a bracket rather than a single figure.
  • Two-dimensional regime analysis: remit regime samples geometric amount columns and cost/time-weight rows together. Each amount builds one graph and every weight in that column reuses it, so graph builds equal amount samples, not grid cells. Equal route signatures are summarized into four-neighbour connected regions; every boundary remains a sampled interval rather than an interpolated threshold.
  • Timing ranges and sensitivity: every hop carries a [min, max] time window (SEPA scheme-maximum semantics plus registered SWIFT and CIPS scenario bands), routes aggregate them into displayed ranges, and remit sensitivity sweeps the cost/time weight to show where the winning route flips and how stable the balanced choice is.
  • Resilience: bounded concurrent quote collection, deterministic warnings, invalid-response isolation, and fallback to the next fundable route.
  • Explanations: terminal decision board, Markdown comparisons, and Mermaid route diagrams.
  • Web console: optional FastAPI backend plus a dependency-free single-page frontend sharing the CLI's routing service layer (remit serve).
  • Quality: Python 3.11-3.13 CI, strict pytest configuration, expanded Ruff rules, package-build validation, and 296 automated tests.

Quick start

Prerequisites: Python 3.11 or newer and uv.

git clone https://github.com/qinhzy/payment-router.git
cd payment-router
uv sync --dev
uv run remit --version
uv run remit networks
uv run remit route USD CNY 100

Run the full local verification suite:

uv run pytest -x
uv run ruff check .
uv run ruff format --check .
uv build
uv lock --check

Provenance model

DataSource has three deliberately narrow meanings:

Classification Meaning
VERIFIED Read from a live response or stated by the linked primary source
INDUSTRY_AVERAGE A documented aggregate or median with a reproducible citation
ESTIMATED A transparent teaching assumption or a value derived using one

The overall classification of a quote must equal its least-trusted component. For example, a Wise rate and ETA can be VERIFIED, while its normalized USD fee is ESTIMATED because the current simulator uses a frozen FX table for that conversion. The quote summary is therefore also ESTIMATED.

See Data sources and assumptions for the complete registry, source links, checked dates, values, and caveats. The same registry is available in the CLI with remit sources.

Architecture

src/payment_router/
|-- networks/          # Wise, SEPA, SWIFT, and CIPS adapters/models
|-- core/
|   |-- models.py      # quote, hop, route, and metric provenance models
|   |-- fx.py          # pluggable FX sources (frozen table / live ECB)
|   `-- graph.py       # concurrent MultiDiGraph construction
|-- router.py          # single-route and edge-distinct top-N routing
|-- analysis.py        # shared route signatures for analysis modules
|-- regime.py          # sampled amount x preference connected regions
|-- decision.py        # cheapest/fastest/balanced comparison
|-- provenance.py      # auditable evidence registry
|-- service.py         # shared request/session layer for CLI and web
|-- visualizer.py      # Mermaid and Markdown rendering
|-- web/
|   |-- app.py         # FastAPI API + static console (optional `web` extra)
|   |-- schemas.py     # JSON views reusing the CLI's number formatting
|   `-- static/        # self-contained single-page frontend
`-- cli.py             # Typer/Rich command-line interface

The detailed algorithm, invariants, and boundaries are documented in Architecture.

Known limitations

  • Only USD, EUR, GBP, CNY, HKD, and SGD are supported.
  • The default frozen FX mid-rates make runs reproducible but not market-current; --fx live uses ECB reference rates, which are daily indicative fixings rather than tradable quotes.
  • CLI runs resolve the FX source once at startup, so a long command sequence uses one rate table throughout. remit serve --fx live re-checks for a newer ECB publication every 30 minutes (--fx-refresh-minutes tunes or disables it); the rate date it displays is always the one routing is using.
  • The graph quotes each corridor at the source-equivalent amount; later-hop live quotes can differ because the actual arriving amount is path-dependent.
  • Wise delivery estimates for an already funded balance can understate the time of later hops in a simulated multi-hop route.
  • SEPA, SWIFT, and CIPS fees are scenario assumptions, not bank tariffs.
  • Break-even and regime boundaries are sampling results. They are only known to fall between adjacent tested amounts or weights and are never smoothed into exact market thresholds.
  • CIPS is modelled only for CNY-target corridors. Its two-hop default is a teaching abstraction, and the published operating window is not an end-to-end delivery promise.
  • Geography, bank participation, compliance, holidays, cut-off times, and liquidity are outside the MVP model.

Roadmap

  • v0.3: local web console over a shared routing service layer (shipped).
  • v0.4: pluggable ECB/Frankfurter FX provider with cached, reproducible snapshots and explicit fallback behavior (shipped).
  • v0.5: independent multi-hop timing model and sensitivity analysis (shipped).
  • v0.6: source-backed corridor expansion and an RMB-focused CIPS scenario (shipped).
  • v0.7: historical comparison without turning the simulator into an online payment service (shipped).
  • v0.8: break-even analysis across the amount axis (shipped).
  • v0.9: two-dimensional amount × preference regime maps (this release).

Contributing and security

Contributions are welcome. Read CONTRIBUTING.md, especially the evidence requirements for financial assumptions. Security reports should follow SECURITY.md. User-visible changes are recorded in CHANGELOG.md.

License

MIT

About

Multi-objective Dijkstra routing simulator for cross-border payments. Models Wise, SEPA, and SWIFT with documented data sources.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages