Model status: The released ONNX checkpoint runs on this Space's CPU. All records are fictional. The output is a review proposal—not an approval or journal entry.
Training Specialized HRMs, Explained Like You Are Five
Think of an HRM as a child learning to solve little boxes of accounting puzzles.
Each training example is one box containing:
- The puzzle pieces.
- The rules for which pieces may fit together.
- The correct answer.
- Any special reason the puzzle needs adult review.
The HRM studies thousands of solved boxes and learns how to solve new ones.
The basic dataset shape
Do not think of the dataset as one enormous spreadsheet where every row is unrelated. It should be a collection of complete cases:
Case 1
├── Company and accounting period
├── Input records
├── Possible relationships
└── Correct answer
Case 2
├── Company and accounting period
├── Input records
├── Possible relationships
└── Correct answer
In our bank-reconciliation model, one case looks like:
Input:
20 bank transactions
19 general-ledger entries
Correct answer:
Bank row 1 → GL row 8
Bank row 2 → GL row 3
Bank row 3 → unmatched bank fee
...
The entire group is one example because the model must reason globally: two bank transactions cannot both consume the same GL entry.
Example: accounts-payable matching
Suppose we want an HRM that performs invoice-to-purchase-order matching.
A human-readable training example could look like this:
{
"case_id": "AP-2025-0042",
"as_of_date": "2025-10-31",
"context": {
"company": "COMPANY_A",
"currency": "USD"
},
"inputs": {
"invoice": {
"invoice_id": "INV-104",
"vendor": "VENDOR_17",
"invoice_date": "2025-10-14",
"total_cents": 125000,
"description": "Office chairs"
},
"purchase_orders": [
{
"po_id": "PO-88",
"vendor": "VENDOR_17",
"total_cents": 125000,
"description": "20 office chairs"
},
{
"po_id": "PO-91",
"vendor": "VENDOR_17",
"total_cents": 140000,
"description": "Office desks"
}
],
"receipts": [
{
"receipt_id": "REC-31",
"po_id": "PO-88",
"quantity_received": 20
}
]
},
"answer": {
"purchase_order": "PO-88",
"receipts": ["REC-31"],
"disposition": "MATCH",
"exception": "NONE"
}
}
That example teaches the model:
Invoice
INV-104belongs toPO-88and receiptREC-31.
An exception example might instead say:
{
"answer": {
"purchase_order": "PO-88",
"receipts": ["REC-31"],
"disposition": "REVIEW",
"exception": "PRICE_VARIANCE"
}
}
Now it learns both matching and exception handling.
What the model actually sees
The data adapter turns the readable records into arrays:
Invoice features:
amount, date, vendor, currency, description
PO features:
amount, date, vendor, currency, description
Candidate mask:
PO-88 is allowed
PO-91 is allowed
unrelated-company POs are forbidden
Answer:
correct candidate index = PO-88
exception class = NONE
In tensor terms:
input rows [cases, maximum rows, features]
candidate mask [cases, invoice rows, PO rows]
match targets [cases, invoice rows]
exception target [cases, invoice rows]
padding masks [cases, maximum rows]
Padding lets one case contain 10 records and another contain 20 while still fitting into fixed-size batches.
Examples for other CFO specialists
| Specialist | Puzzle pieces | Correct answer |
|---|---|---|
| AP matching | Invoices, PO lines, receipts | Which records match and why review is needed |
| Intercompany | Due-to and due-from entries | Correct counterparty pairs and elimination group |
| Journal review | Journal lines, accounts, policy context | Accept, review, or reject; violated control |
| Expense audit | Expense, receipt, employee, policy | Valid, duplicate, missing receipt, policy violation |
| Close management | Tasks, dependencies, status, owners | Next task, correct order, blocker category |
| Revenue recognition | Contract, invoice, delivery, obligations | Allocation and recognition schedule |
| Variance analysis | Actuals, budget, operational drivers | Driver classification and materiality |
| Cash forecasting | Historical and scheduled cash flows | Future cash balances and uncertainty bands |
HRMs are especially attractive when there are several related records, multi-step reasoning, global constraints, and an answer you can objectively check.
For ordinary forecasting, traditional time-series and regression baselines may still be better. An HRM should have to beat those baselines.
How to create a new specialist
1. Define the puzzle
Write down exactly:
What information is available before the decision?
What answer should the model produce?
What rules must never be violated?
When should it abstain and ask a human?
If you cannot define the correct answer precisely, you cannot train the model reliably.
2. Find trustworthy answers
Good labels come from:
- approved reconciliations;
- finalized invoice matches;
- controller-reviewed journal decisions;
- completed close workflows;
- resolved exceptions; or
- carefully generated synthetic cases.
Avoid using unreviewed historical decisions as truth. Historical accounting records can contain mistakes.
3. Separate inputs from answers
Never accidentally show the model the answer inside its input.
For example, do not give it:
{
"matched_po_id": "PO-88"
}
as an input when PO-88 is the answer.
Other dangerous leakage fields include:
- final reconciliation status;
- approval timestamp;
- payment created from the matched invoice;
- exception resolution code;
- downstream journal-entry ID; and
- fields populated only after review.
4. Include difficult and rare cases
A useful dataset needs more than ordinary matches:
Exact matches
Small amount differences
Date differences
Missing references
Duplicate invoices
Partial receipts
Split payments
Aggregated payments
Currency differences
Reversals
Wrong entities
Unmatched records
Policy exceptions
Ambiguous cases requiring review
If the model never sees duplicate invoices during training, it will not magically understand duplicate invoices later.
5. Split by time
A safe split looks like:
Train: January 2023 – June 2025
Validation: July 2025 – September 2025
Test: October 2025 – December 2025
Do not randomly put almost-identical records from the same transaction into both training and testing. That lets the model memorize instead of reason.
For an even harder test, hold out an entire company, vendor group, or accounting system.
6. Build an adapter and output head
You usually cannot point our current model at a new CSV and expect it to work.
The existing model has outputs specifically designed for:
- bank-to-GL pair scores;
- unmatched scores; and
- six reconciliation exception classes.
For AP matching, we could reuse much of the architecture and replace the row types and exception classes.
For close planning, we would need a different output head that predicts next actions or task order.
For forecasting, we would need a numerical prediction head and a different loss function.
A practical folder format
A clean specialist dataset could use:
ap-matching/
├── schema.json
├── train.jsonl
├── validation.jsonl
├── test.jsonl
└── manifest.json
Each line in train.jsonl is one complete case, not one invoice row.
The manifest records:
{
"dataset_version": "1.0",
"created_at": "2026-07-23",
"source_system": "anonymized-read-only-export",
"train_period": ["2023-01", "2025-06"],
"validation_period": ["2025-07", "2025-09"],
"test_period": ["2025-10", "2025-12"],
"currencies": ["USD"],
"label_method": "controller-approved final disposition"
}
The simplest rule is:
One example should contain everything needed to solve one complete accounting puzzle, followed by an answer key created from a trustworthy outcome.
For multiple specialists, give each HRM its own dataset and answer type. Then connect their proposals through the typed Go control layer instead of mixing AP, forecasting, reconciliation, and close-management records into one undifferentiated training file.
Model and evaluation · Source and controls · Educational use only