@idriszade/ma3-reconciliation-engine
v0.1.0
Published
Multi-source reconciliation: match records across 2-4 systems, identify discrepancies, LLM-classify exceptions
Maintainers
Readme
MA3 — Reconciliation Engine
Cross-source record matching for any structured data: bank vs ledger, PO vs receipt vs invoice, billing vs accounting. Returns matched records, discrepancies, and unmatched items with LLM-generated explanations.
Pipeline
[input] -> validate -> normalizeMatchKey (pure) -> matchRecords (pure, exact or fuzzy)
-> if any discrepancies or unmatched: buildDiscrepancySummary (pure) -> LLM classify
-> merge classifications -> computeSummary (pure) -> [matched + discrepancies + unmatched + meta]Key design: matching is 100% pure computation with no LLM involvement. LLM fires only when exceptions exist (discrepancies or unmatched records). A perfect reconciliation never calls the LLM.
Tool surface
runMA3(input, deps?)— returnsResult<MA3Output, MA3Error>normalizeMatchKey(key)— pure lowercase + trim, exportedmatchRecords(sources, strategy, toleranceAmount, toleranceDays)— pure matcher, exportedbuildDiscrepancySummary(discrepancies, unmatched)— pure prompt builder, exported- MCP tool:
ma3_reconciliation_engine
Input (key fields)
| Field | Type | Required |
|---|---|---|
| sources | Source[] (min 2) | yes |
| sources[].sourceId | string | yes |
| sources[].sourceName | string | yes |
| sources[].records | Record[] (min 1) | yes |
| sources[].records[].recordId | string | yes |
| sources[].records[].matchKey | string | yes |
| sources[].records[].amount | number | no |
| sources[].records[].date | string | no |
| matchStrategy | 'exact'|'fuzzy' | yes |
| toleranceAmount | number | yes |
| toleranceDays | number | yes |
Output (key fields)
matched[] (records that align across all sources), discrepancies[] with matchKey, discrepancyType ('amount_mismatch'|'date_mismatch'|'missing_in_source'|'duplicate'), severity, explanation, suggestedResolution; unmatched[] with likelyReason; summary.totalRecords, summary.matchedCount, summary.discrepancyCount, summary.unmatchedCount, summary.matchRate (0–1)
Test coverage
| Layer | File | Tests | What it verifies |
|---|---|---|---|
| Unit — normalizeMatchKey | tests/pipeline.test.ts | 3 | Lowercase+trim, already-normalized, empty string |
| Unit — matchRecords exact | tests/pipeline.test.ts | 4 | Perfect match, missing in one source, partial 3-source coverage, duplicate detection |
| Unit — matchRecords fuzzy | tests/pipeline.test.ts | 4 | Amounts within tolerance matched, outside tolerance=amount_mismatch, dates outside tolerance=date_mismatch, dates within tolerance matched |
| Unit — buildDiscrepancySummary | tests/pipeline.test.ts | 1 | Includes both DISCREPANCIES and UNMATCHED sections with correct keys |
| Integration | tests/pipeline.test.ts | 4 | All-matched skips LLM, discrepancy present calls LLM and merges, validation error, process failure prefix |
| Property | tests/pipeline.test.ts | 2 | matchedCount + discrepancyCount + unmatchedCount <= totalRecords + matchedCount, matchRate always in [0, 1] |
| Scenario | tests/scenario.test.ts | 7 | Bank vs ledger exact match, amount mismatch ($100.00 vs $100.50), 3-source PO/receipt/invoice, all-unmatched (no common keys), perfect match skips LLM (spy), duplicate detection, minimal 2-source 1-record |
| Business | tests/business.test.ts | 5 | Payroll 3-source GL discrepancy ($432.50 tax gap), multi-currency wire matching by key, batch payment splitting (1 bank vs 3 ledger), fuzzy date timezone tolerance (1 day), month-end 50-transaction perfect close (LLM not called) |
| Cross-atom | packages/meta-flow-tests/ | 3 | MA3 in Flow 3 (discrepancy→MA1 adjustment), Flow 5 (full cycle), Flow 6 invariant (schema compatibility) |
Business scenarios tested
| Scenario | Domain | Business invariant |
|---|---|---|
| Payroll 3-source GL discrepancy | Accounting | ADP + bank match; GL shows $432.50 gap → missed tax adjustment; amount_mismatch with suggested resolution |
| Multi-currency wire matching | Accounts receivable | Different descriptions (wire ref vs invoice name) but same matchKey and amount → matched; descriptions preserved |
| Batch payment splitting | Accounts receivable | Single $10K bank deposit vs 3 invoices → all 4 records unmatched; likelyReason populated per record |
| Fuzzy timezone matching | Finance | Bank posts 2026-05-01, ledger 2026-04-30 (UTC-8 lag) → matched at toleranceDays=1 |
| Month-end 50-transaction close | Finance | 50 transactions matched exactly → LLM not called (throwing stub verifies); matchRate=1 |
Composition flows
- Flow 3: MA3 flags
wire-2026-04-30asamount_mismatch($12,500 bank vs $10,000 ledger) → MA1 receivesdiscrepancy.explanationanddiscrepancy.suggestedResolutionas a context item and approves the $2,500 Finance Manager adjustment - Flow 5: participates in Meridian Corp month-end as the bank-vs-ledger reconciliation step; one match (
payroll-apr) and one discrepancy (wire-2026-04-30) feed the downstream MA1 approval - Flow 6 (invariant): MA3 discrepancy fields (
explanation,suggestedResolution) map cleanly to MA1 context items;decision.reasoningis truthy — schema compatibility proven in isolation
Error codes
| Code | Type | When |
|---|---|---|
| ma3_input_invalid | validation | Schema validation fails (fewer than 2 sources, empty records array) |
| ma3_classification_<code> | Propagated from process | LLM process returns a ProcessError; the atom prefixes its own code |
Running
pnpm vitest run atoms/ma3-reconciliation-engine