Finance — Payment Reconciliation Flow
A month-end payment reconciliation, walked from the gateway settlement file down to the lines posted to the ledger. Typed FlowScript for startup finance. Keywords: cap table, captable, equity, dilution, funding round.
Make it your own.
// A month-end payment reconciliation, walked from the gateway
// settlement file down to the lines posted to the ledger. Both passes —
// the automatic match and the manual break queue — have to add up, and
// the reconciled total is derived from the two rather than asserted.
cohort settlements_received {
n: 48620 source "Gateway settlement file, 2026-03"
}
cohort ingested {
n: 48602
from: settlements_received
excluded: {
malformed_row: 11
duplicate_settlement_id: 7
}
}
// Pass one: everything the matcher could clear without a human.
cohort matched_exact {
n: 46318
from: ingested
excluded: {
amount_mismatch: 1204
missing_ledger_entry: 806
currency_rounding: 274
}
}
// Pass two: the 2 284 breaks pass one could not clear.
cohort breaks_investigated {
n: 2284 source "Reconciliation break queue, 2026-03"
}
cohort breaks_cleared {
n: 2091
from: breaks_investigated
excluded: {
awaiting_bank_confirmation: 138
escalated_to_dispute: 55
}
}
// Two passes merge, so the total is derived: 46 318 matched plus 2 091
// cleared by hand.
cohort reconciled {
n: = matched_exact.n + breaks_cleared.n
}
cohort posted_to_ledger {
n: 48387
from: reconciled
excluded: {
held_for_manual_review: 15
fx_revaluation_pending: 7
}
}
kpi auto_match_rate {
title: "Lines matched without a human touching them"
baseline: = pct(matched_exact.n, ingested.n)
target: 98
unit: "%"
}
kpi break_ageing {
title: "Median age of an open break"
baseline: 6
target: 2
unit: "days"
}
kpi close_day {
title: "Business day the ledger closes"
baseline: 7
target: 3
unit: "days"
}
// A funnel is a strictly narrowing cascade: every stage is a subset of
// the one above it, and the renderer sizes each band as n / base with no
// clamp. The automatic match is NOT a stage of that cascade — it is one
// of two streams, and the 2 091 breaks cleared by hand rejoin below it.
// This chain used to read received -> clean -> matched (46 318) ->
// posted (48 387), which drew a final band WIDER than the one above it
// and printed "104.5% step conv" in the success colour. Nothing caught
// it: funnel_step chains carry no arithmetic check, so unlike the cohort
// cascade above, a wrong n here is silent.
//
// So the stages are the lines still in the run, each one derived from
// the cohort it restates rather than retyped beside it. Retyping is what
// let the two halves of this document disagree in the first place.
funnel_step received { n: = settlements_received.n, title: "Settlement lines received" }
funnel_step clean { n: = ingested.n, from: received, title: "Ingested" }
funnel_step reconciled_lines { n: = reconciled.n, from: clean, title: "Reconciled — matcher plus cleared breaks" }
funnel_step posted { n: = posted_to_ledger.n, from: reconciled_lines, title: "Posted to ledger" }
note funnel_is_not_the_cascade {
text: "The funnel view shows the lines that stayed in the run: received, ingested, reconciled, posted. It cannot show the automatic match as a stage, because 2 091 lines the matcher could not clear were cleared by hand and rejoin the flow below it — a funnel band drawn for the 46 318 auto-matched lines would be followed by a wider one and a conversion over 100%. The auto-match share is a ratio, not a stage, and is reported as the auto_match_rate KPI."
anchor: reconciled
}
view reconciliation: consort(settlements_received)
view breaks: funnel(received)