Reconciliation engine
A separate, advisory-locked scheduler that runs categorized integrity checks continuously.
Categories
| Cat | Meaning | Cadence |
|---|---|---|
| A | Internal integrity | Every cycle (default 3600s) |
| B | Derivation | Every cycle |
| C | External (broker) | Longer cadence (default 86400s) |
| D | Lifecycle (aging) | Every cycle |
A + B are cheap (SQL-only). C hits live brokers, throttled to daily. D is cheap.
The check list
| Check | Category | Where | Both books? |
|---|---|---|---|
trial_balance_zero | A | checks/trial_balance.py | ✓ |
journal_line_completeness | A | checks/journal_lines.py | ✓ |
source_txn_uniqueness | A | checks/uniqueness.py | ✓ |
dr_cr_sign_convention | A | checks/sign.py | ✓ |
wallet_derivation | B | checks/wallet.py | ✓ |
account_totals | B | checks/accounts.py | ✓ |
snapshot_reconcile | B | checks/snapshot.py | ✓ |
gtn_ledger_recon | C | UAE only | UAE |
zag_holdings_recon | C | UAE only | UAE |
viewtrade_ledger_recon | C | India only | India |
viewtrade_holdings_recon | C | India only | India |
lrs_viewtrade_tie_out | C | India (degrades OK on UAE) | ✓ (safe) |
pending_flow_age | D | Both | ✓ |
Every India-only check is registered on both books. When it runs on UAE the
query against India-only tables comes back empty and the check returns ok.
Each check returns
ReconCheckResult(
check_name="lrs_viewtrade_tie_out",
category="C",
status="ok" | "warn" | "fail",
metrics={...},
ran_at=datetime,
)
A warn or fail also gets:
- Variances — a list of specific rows that broke it.
- Alert callback — if
ALERT_WEBHOOK_URLis set, a POST fires (fire-and-forget). - Optional persistence — if
alert_persist_enabled=True, a row lands inreconciliation_variancesfor history.
Running a check
Config:
| Setting | Default | Effect |
|---|---|---|
RECON_SCHEDULE_ENABLED | true (UAE) | master switch |
RECON_INTERVAL_SECONDS | 3600 | A/B/D cadence |
RECON_EXTERNAL_INTERVAL_SECONDS | 86400 | C cadence |
RECON_SCHEDULE_INITIAL_DELAY_SECONDS | 60 | boot delay |
INDIA_RECON_ENABLED | false | India scheduler master switch |
INDIA_RECON_INTERVAL_SECONDS | 3600 | India cadence |
ALERT_WEBHOOK_URL | unset | drift-alert POST target |
Manually — POST /v1/reconciliation/run (optional category filter):
curl -X POST -H "X-Ledger-Admin-Token: localdev" \
-H "Content-Type: application/json" \
http://localhost:8078/v1/reconciliation/run \
-d '{"category":"C"}'
Reading — GET /v1/reconciliation/runs + GET /v1/reconciliation/runs/{id}.
The three India firm-recon endpoints
Complement the scheduled Category-C checks with on-demand fetches:
| Endpoint | What |
|---|---|
GET /v1/india/ledger-recon | Per-customer ledger cash+securities vs live VT balance |
GET /v1/india/holdings-recon | Per (customer, instrument) share-count tie-out |
GET /v1/india/reconciliation | LRS↔ViewTrade per-customer tie-out |
All three fetch live broker data (bounded concurrency, per-customer failure isolation) and are read-only.
When a check fails
- Look at the variances — the detail is in
metadata.varianceson the run row. - Trace to a transaction — variances almost always name a
txn_idor asource_txn_id. - Decide the class:
- Data issue — reverse the offending transaction, fix ingest, re-sync.
- Code issue — fix the rule, migrate the data if needed, add a test.
- Timing — often self-heals on the next cycle.
- Confirm — re-run the check manually.