Run the Cash Application Engine
| Owner | Classification | Version | Effective | Next review | Status |
|---|---|---|---|---|---|
| Platform Engineering | Internal | 0.3 | 2026-09-04 | 2026-12-04 | Draft |
Purpose. Operating guide for the cash-application engine — how to trigger it, what it processes, how to confirm a run, and the prerequisites that silently disable it. Rule-level detail (the CID rules, the 24 match + 12 remittance rules, the Layer-2 judge internals) is reference material and is not repeated here.
When to run it
The engine runs after a bank-item import to identify payers and apply cash, or on demand to re-process the unresolved set. It is the cashapplication Temporal workflow: customer identification (CID) → invoice matching → straight-through clearing (STP).
Two execution paths (bulk vs live)
CID runs on two substrates by design, for performance. Both read the same rule catalog and decision thresholds, so a payment the live path resolves lands the same way in a bulk run — only the execution mechanics differ.
| Path | Trigger | Substrate | Latency |
|---|---|---|---|
| Bulk | Run Engine / schedule | Set-based over the whole eligible population | Minutes (full dataset) |
| Live | User clicks Smart ID on a single payment | Per-item, on demand | Sub-second |
The two paths are not symmetrical, and that is intentional. The live path is tuned for sub-second response and therefore runs a high-precision subset of the rules — it omits the set-based, scan-heavy rules (lockbox resolution, cross-subsidiary amount disambiguation, fuzzy token overlap) that only make sense in a batch pass. So the correct expectation is: a payment live identifies, bulk also identifies; bulk resolves more. If a payment live couldn't place but the next bulk run does, that is by design — not a stale config. For the exact rule-by-rule split, see the Cash Application Intelligence reference.
Trigger
| Method | Detail |
|---|---|
| In-app (bulk) | The Run Engine action — post-import CTA on analyst/payments, and on admin/data-import. |
| In-app (live) | The Smart ID action on a single payment — runs the per-item CID path and returns in under a second. |
| API | POST /api/o/finance/cash/v1/workflows/runengine (optional body { "batchSize": <n> }). Permission: cashapp:workflow:execute. |
The bulk methods async-start the same workflow. The workflow worker (wfw) and Temporal must be running for the bulk run to execute; the trigger returns a handle immediately, not a result. The live Smart ID path is synchronous and does not depend on wfw.
Stages
- CID — identify the payer from bank-item text against customers, aliases, and open invoices.
- Remittance-driven CID — identify from a linked remittance's invoice references.
- Invoice matching — match identified payments to open invoices (1:1, N:1, partial, variance).
- Conflict detection — downgrade invoices claimed by multiple payments to suggestions.
- Approval routing / STP — auto-clear clean matches; route the rest for approval.
Rule definitions live in the cash-application intelligence reference (matching engine, CID rules, Layer-2 judge, trace format). This guide covers operation only.
CID behaviors a support engineer sees
Full rule detail is in the intelligence reference; these two rules drive most "why did/didn't it identify this?" questions.
Alias identification (CID-004)
The engine matches known customer aliases against the payment's name and narrative fields, in two ways:
- Exact field match — an alias equals the name/narrative field. Scores 95.
- Narrative substring — an alias of 5 or more characters appears as a substring within the narrative. Scores 75 — a suggestion only, below the
auto_match_threshold(85), so it surfaces for review rather than auto-identifying.
Example: a payment narrated ACH CREDIT ACME CORP PMT is matched to Acme Corporation via the ACME CORP alias (substring, score 75). The 5-character minimum keeps short aliases from firing on incidental text.
Amount ambiguity (CID-008)
When a payment's amount matches open invoices for more than one customer, the engine does not guess. It surfaces every candidate customer (up to amount_max_customers, default 3) and escalates the ambiguity to the Layer-2 AI evaluator, which disambiguates using context such as a prior-payment relationship. If the amount maps to more customers than the cap, it is treated as noise and suppressed rather than surfaced.
Configuration knobs
Thresholds and caps a support engineer can tune live in finbase.matching_config on the CASH-CID row (shared by both execution paths). Scoring constants are code-level.
| Knob | Default | Where it lives | Effect |
|---|---|---|---|
auto_match_threshold | 85 | matching_config column (CASH-CID row) | Score at/above which CID auto-identifies |
suggest_threshold | 50 | matching_config column (CASH-CID row) | Score at/above which CID surfaces a suggestion |
amount_max_customers | 3 | matching_config.parameters (JSON); env CID_AMOUNT_MAX_CUSTOMERS as fallback | CID-008 fan-out cap — above this the amount is suppressed as noise |
ambiguity_gap | 15 | code-level constant | Minimum score gap between top two candidates to treat the top as unambiguous |
short_circuit | 95 | code-level constant | Score at which CID stops evaluating further rules (an exact-alias-grade hit) |
multi_rule_bonus | 10 | code-level constant | Score added when multiple rules independently agree on the same customer |
Code-level constants change only via release; the matching_config values are data and can be tuned per tenant without a deploy. Because both execution paths read the same CASH-CID row, a threshold change applies to bulk and live alike.
What a run touches (blast radius)
A full run is scoped by column state, not by a global rebuild — curated and resolved data is left alone.
| Set | Processed? | Basis |
|---|---|---|
Items with no customer (New or Unidentified) | 🟢 | CID work-set is customernumber IS NULL / '' / '000000' |
| Items already carrying a customer | ⚪ | skipped by CID — identified payers are never downgraded |
| Identified-but-unapplied items | 🟢 | matched — matchingstatus NOT IN ('Matched','Cleared') |
Matched / Cleared items | ⚪ | skipped by matching |
| Manual / Assisted matches | ⚪ | protected; the engine learns aliases from them rather than overwriting |
The CID work-set is keyed on customernumber, not on the status literal — so both New and Unidentified items are eligible.
Idempotency
Re-running is safe:
- Recommendations are written delete-then-insert, scoped by
masterbankitemsid— no duplicates. intelligencelogis append-only by design (a per-run audit trail). Readers take the latest row (createdon DESC), so a re-run adds history without corrupting state. A reset clears the log to return to a clean baseline.
Status lifecycle
New → Identified → Matched → Cleared
→ Unidentified (ambiguous / no candidate)
→ Suggestions Available / Partial MatchThe front-end CID action renders only for status='Unidentified'. A freshly seeded or imported item at status='New' shows no CID control until the engine flips an ambiguous item to Unidentified. This is the most common "the button is missing" report — the engine has not run.
Prerequisites that silently disable the engine
These fail green: balance checks stay valid while the engine produces nothing.
| Requirement | Symptom if missing |
|---|---|
matching_config + matching_rule populated (data, not schema) | Engine runs and matches nothing; no error |
CID rules (CASH-CID) + thresholds (auto_match_threshold, suggest_threshold; see Configuration knobs) | No identification or no auto-apply |
amountaspervendor populated on AR items | Payment silently drops out of CEI/DSO metrics |
Run verify-engine-readiness.sql before relying on a run — it is the check that catches the silent engine-death.
Verify a run
-- status distribution moved as expected
SELECT status, count(*) FROM cash.masterbankitems GROUP BY status ORDER BY 2 DESC;
-- decisions were logged (latest per item)
SELECT processtype, count(*) FROM finbase.intelligencelog
WHERE sourceobject = 'masterbankitems' GROUP BY processtype;Then confirm engine readiness and FIG alignment:
psql < scripts/demo/verify-engine-readiness.sql # engine alive
psql < scripts/demo/verify-fig-alignment.sql # balances + JV→recon tieLayer-2 judge and Finni
- Layer-2 judge — LLM disambiguation for an ambiguous CID (e.g. the CID-008 multi-customer fan-out above) or match. It is not part of the bulk run; it fires on demand when an analyst requests it, and requires the agents service (
:10013) and a reachable model. The bulk engine only flags ambiguity and stores the candidate customers for the judge to disambiguate. - Finni — the cash-application assistant surface (recommendations, natural-language payment queries, identify/match actions). It invokes the same identification and matching logic on demand.
Both are LLM features; their model, tokenization, and data-handling posture is governed under AI Governance.
Operational constraints
- The
wfwworker must be running. In development it bundles workflows from source on respawn; in production a changedcashwfworkflow requiresbuild:workflowbefore deploy. - Temporal latency: a full-dataset run is minutes-long. For demonstrations, run the engine ahead of time and ship the materialised snapshot rather than running live. A scoped re-run after a reset processes only the unresolved set (guards skip everything resolved) and completes in seconds.
- The Layer-2 judge is on demand and depends on the agents service and model availability.
Deployment (Railway)
| Requirement | Status |
|---|---|
wfw deployed and running; build:workflow run for changed cashwf workflows | ⚪ verify |
matching_config + matching_rule seeded | ⚪ verify |
verify-engine-readiness.sql all-green on the target | ⚪ verify |
| Agents service reachable for Layer-2 (if the demo exercises it) | ⚪ verify |
DEMO_MODE=true on the API service (for demo reset endpoints) | ⚪ verify |
Related
- How-to: Email & Document Ingestion — how a payment/remittance arrives
- AI governance: AI Governance — Layer-2 / Finni model posture
Revision history
| Version | Date | Author | Change |
|---|---|---|---|
| 0.1 | 2026-08-29 | Platform Engineering | Initial draft — trigger, stages, blast radius, idempotency, lifecycle, prerequisites, verification, L2/Finni, constraints, Railway checklist. |
| 0.2 | 2026-09-01 | Platform Engineering | Documented the bulk vs live (Smart ID) execution paths, CID-004 alias identification and CID-008 amount-ambiguity behaviors, and the configuration knobs (thresholds, fan-out cap, scoring constants). |
| 0.3 | 2026-09-04 | Platform Engineering | Corrected the bulk-vs-live framing: the paths are intentionally asymmetric (live is a high-precision, low-latency subset; bulk runs the set-based rules too), not guaranteed-identical. Cross-linked the new Cash Application Intelligence reference for the rule-by-rule split. |