Reconcile a supplier or customer statement against your accounting ledger: match invoices, credit notes, and payments, flag mismatches and missing items, compare balances. No dependencies in the core -- works with any data source.
Project description
recskit
Reconcile a supplier or customer statement against your own accounting ledger: match invoices, credit notes, and payments; flag mismatches and missing items on either side; compare balances. The core has zero dependencies and doesn't care where your data came from -- CSV, a PDF you parsed by hand, Sage, Xero, QuickBooks, anything.
If this saves you time, consider buying me a coffee.
What problem this solves
Checking a supplier statement against your own ledger by eye is slow and error-prone, especially once an account has more than a handful of open items. Two things make it worse than it looks:
- A tied-up total balance doesn't mean every line is right. Two errors can cancel out -- a mismatched invoice and a missing one can add up to the same total as if nothing were wrong. recskit checks every line, not just the bottom-line figure.
- Reference matching is a trap. A short reference like
991can turn up as a substring of an unrelated, older invoice's reference (INV-99910) on a long-standing account. Match on substring first and you'll silently pair the statement line with the wrong invoice, reporting a confident-looking "mismatch" instead of correctly reporting no match at all. recskit always tries an exact reference match before ever falling back to substring matching.
Features
- Matches invoices/credit notes by reference (with support for statements that print two references for the same line), payments by amount first then reference
- Exact-match-first, substring-fallback matching order -- see above
- Configurable payment-terms due-date calculation (flat N days, N days from the start of the following month, N days from end of month, or end of the following month with no offset)
- Flags items in your ledger that never show up on the statement at all (commonly an old item sitting in a brought-forward balance)
- Zero required dependencies in the core -- add the optional
sageextra only if you want the bundled Sage 50 adapter - Two worked examples: pure CSV (no external system at all), and Sage 50 via sagekit
Installation
pip install -r requirements.txt # core only, zero dependencies
pip install -e . # or, as an editable package
pip install -e ".[sage]" # + the optional Sage 50 adapter
Usage
from recskit import StatementItem, LedgerInvoice, LedgerPayment, reconcile_statement
items = [
StatementItem(type="invoice", ref="INV-100", amount=500.00),
StatementItem(type="invoice", ref="INV-101", amount=320.00),
StatementItem(type="payment", ref="bacs", amount=300.00),
]
ledger_invoices = [
LedgerInvoice(ref="INV-100", amount=500.00, outstanding=500.00),
LedgerInvoice(ref="INV-101", amount=300.00, outstanding=300.00),
]
ledger_payments = [LedgerPayment(ref="BACS-9001", amount=300.00)]
result = reconcile_statement(items, ledger_invoices, ledger_payments, statement_balance=800.00)
print(result.agreed, result.issue_count, result.balance_difference)
for r in result.mismatched_items + result.missing_items:
print(r.item.ref, r.status, r.note)
Payment-terms due dates
from datetime import date
from recskit import calc_due_date
calc_due_date(date(2026, 5, 14), 30) # 30 days from invoice date
calc_due_date(date(2026, 5, 14), 29, "start_of_following_month") # 29 days from 1 June
calc_due_date(date(2026, 5, 3), 14, "end_of_month") # 14 days from 31 May
calc_due_date(date(2026, 5, 3), 0, "end_of_following_month") # 30 June, terms_days ignored
Examples
python examples/reconcile_csv.py # runs on the bundled sample CSVs
python examples/reconcile_csv.py statement.csv ledger.csv 1063.45 # your own files
python examples/reconcile_sage.py # requires the "sage" extra + a live Sage 50
reconcile_csv.py needs no external system at all -- a good starting
point for wiring recskit up to whatever export your own accounting
software produces. reconcile_sage.py shows the same reconciliation
against a live Sage 50 purchase ledger account, using
recskit.sage_adapter to fetch the ledger side and
sagekit for the ODBC
connection and column auto-detection.
Concept reference
| Concept | Meaning |
|---|---|
StatementItem |
One line from the statement you're checking (invoice, credit note, or payment) |
LedgerInvoice / LedgerPayment |
The equivalent transaction as it exists in your own ledger |
MatchResult.status |
"ok", "mismatch" (found but wrong amount), or "missing" (no match at all) |
extra_in_ledger |
Ledger invoices never referenced by any statement line -- check these aren't being missed, or are simply old brought-forward items |
balance_difference |
ledger_balance - statement_balance |
Bring your own data source
The reconciliation logic never touches a database, file, or network --
you build StatementItem / LedgerInvoice / LedgerPayment lists
however suits your setup, and call reconcile_statement(). Options:
- Parse a PDF or CSV statement export yourself (see
examples/reconcile_csv.py) - Pull the ledger side from Sage 50 with
recskit.sage_adapter(seeexamples/reconcile_sage.py) - Write your own adapter for Xero, QuickBooks, or anything else --
sage_adapter.pyis about 100 lines and a reasonable template to copy
Testing
pip install pytest
pytest
The core test suite has no dependencies to mock -- it's all plain Python objects in, plain Python objects out.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| A statement line matches the wrong ledger invoice | Shouldn't happen -- recskit always tries an exact ref match before substring | If you see this, please open an issue with the two refs involved |
| Balance agrees but you still see mismatches/missing items | This is expected and correct -- see "What problem this solves" above | Investigate each flagged line; a tied total doesn't guarantee every line is right |
| Payment shows as missing despite being in the ledger | Amount differs by more than the tolerance, and the reference also doesn't appear as a substring | Widen tolerance in match_payment(), or check the payment was recorded correctly |
ImportError on sagekit |
The sage extra wasn't installed |
pip install -e ".[sage]", or install sagekit directly |
License
MIT -- see LICENSE. Do whatever you like with this; a credit or a Ko-fi tip is appreciated but never required.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file recskit-0.1.0.tar.gz.
File metadata
- Download URL: recskit-0.1.0.tar.gz
- Upload date:
- Size: 19.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d3d92c2514b80f76f1e1cc98f0430bd8e2d4fc638d19953bb3bb863523278d3d
|
|
| MD5 |
961f96851c183e69f9fdc9e82aae924f
|
|
| BLAKE2b-256 |
466ab3e068d475cbfda985d21833ed9fb9994751fadfdfeaddc8b5fb468e87aa
|
File details
Details for the file recskit-0.1.0-py3-none-any.whl.
File metadata
- Download URL: recskit-0.1.0-py3-none-any.whl
- Upload date:
- Size: 16.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5e7ebec8b6b0cbb7d3e6c3299b04cd4c599aff7bf1900ec6e32e8ef279735194
|
|
| MD5 |
c075825d0b8c0efe7c4f9def0eced469
|
|
| BLAKE2b-256 |
098d23f7508cdda486a3995ef5850db1db783eaa3d963705c876e5b9c0e4d4fe
|