@perrylink/dsh-bankrec 0.1.0-alpha.1 → 0.1.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/core/camt.d.ts +6 -0
- package/lib/core/camt.js +22 -0
- package/lib/plugin/tools.js +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
# @perrylink/dsh-bankrec
|
|
2
2
|
Bank CSV/GL ingest, 1:1 matching and 3-sheet reconciliation workpaper — SKU-06 S1 public/synthetic demo. **Partial, not GA**: 0 real firms; user-approved public/synthetic (2026-10-31).
|
|
3
|
-
Tools: `bankrec.ingest`, `bankrec.ingest_camt` (CAMT.053), `bankrec.match`, `bankrec.export` (Bank/GL/Matches xlsx). Env `DSH_BANKREC_HOME`. Apache-2.0.
|
|
3
|
+
Tools: `bankrec.ingest`, `bankrec.ingest_camt` (CAMT.053), `bankrec.reconcile_camt`, `bankrec.match`, `bankrec.export` (Bank/GL/Matches xlsx). Env `DSH_BANKREC_HOME`. Apache-2.0.
|
package/lib/core/camt.d.ts
CHANGED
|
@@ -1,2 +1,8 @@
|
|
|
1
1
|
import type { TxnRecord } from '../plugin/storage.js';
|
|
2
2
|
export declare function parseCamt(text: string, accountOverride?: string, evidenceSha?: string): TxnRecord[];
|
|
3
|
+
export interface CamtBalance {
|
|
4
|
+
type: string;
|
|
5
|
+
amount_cents: number;
|
|
6
|
+
date: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseCamtBalances(text: string): CamtBalance[];
|
package/lib/core/camt.js
CHANGED
|
@@ -42,3 +42,25 @@ export function parseCamt(text, accountOverride, evidenceSha) {
|
|
|
42
42
|
throw new BankrecError('E_BR_CSV_INVALID', 'CAMT has no entries');
|
|
43
43
|
return out;
|
|
44
44
|
}
|
|
45
|
+
export function parseCamtBalances(text) {
|
|
46
|
+
let root;
|
|
47
|
+
try {
|
|
48
|
+
root = parser.parse(text);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
throw new BankrecError('E_BR_CSV_INVALID', 'CAMT XML is not parseable');
|
|
52
|
+
}
|
|
53
|
+
const statements = asArray(asObject(root?.Document)?.BkToCstmrStmt?.Stmt);
|
|
54
|
+
const balances = [];
|
|
55
|
+
for (const stmt of statements) {
|
|
56
|
+
for (const bal of asArray(stmt?.Bal)) {
|
|
57
|
+
const type = scalar(bal?.Tp?.CdOrPrtry?.Cd);
|
|
58
|
+
const raw = scalar(bal?.Amt);
|
|
59
|
+
if (!type || !raw)
|
|
60
|
+
continue;
|
|
61
|
+
const sign = scalar(bal?.CdtDbtInd).toUpperCase() === 'DBIT' ? -1 : 1;
|
|
62
|
+
balances.push({ type, amount_cents: sign * cents(raw), date: scalar(bal?.Dt?.Dt) || scalar(bal?.Dt?.DtTm).slice(0, 10) });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return balances;
|
|
66
|
+
}
|
package/lib/plugin/tools.js
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import * as XLSX from 'xlsx';
|
|
5
5
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
6
6
|
import { BankrecError, parseCsv } from '../core/bankrec.js';
|
|
7
|
-
import { parseCamt } from '../core/camt.js';
|
|
7
|
+
import { parseCamt, parseCamtBalances } from '../core/camt.js';
|
|
8
8
|
const text = (t) => [{ type: 'text', text: t }];
|
|
9
9
|
const sha = (v) => createHash('sha256').update(v).digest('hex');
|
|
10
10
|
export function createTools(deps) {
|
|
@@ -25,5 +25,7 @@ export function createTools(deps) {
|
|
|
25
25
|
const camt = defineTool({ name: 'bankrec.ingest_camt', description: 'Ingest CAMT.053 XML bank statement entries; supports DBIT/CRDT and stores bank rows. Local R1 write.', parameters: { camt_xml: { type: 'string', required: true }, account_id: { type: 'string' } }, output: { schema: { type: 'object', additionalProperties: false, properties: { rows: { type: 'number', required: true }, accounts: { type: 'array', items: { type: 'string' }, required: true }, periods: { type: 'array', items: { type: 'string' }, required: true } } }, render: (_a, v) => text(`CAMT rows=${v.rows}`) }, async execute(a) { const rows = parseCamt(a.camt_xml, a.account_id); for (const t of rows)
|
|
26
26
|
await deps.transactions.put(t.txn_id, t); return { rows: rows.length, accounts: [...new Set(rows.map(r => r.account_id))], periods: [...new Set(rows.map(r => r.period))] }; } });
|
|
27
27
|
const exportTool = defineTool({ name: 'bankrec.export', description: 'Export 3-sheet reconciliation xlsx (Bank, GL, Matches). Local R1 write.', parameters: { account_id: { type: 'string', required: true }, period: { type: 'string', required: true } }, output: { schema: { type: 'object', additionalProperties: false, properties: { path: { type: 'string', required: true }, sha256: { type: 'string', required: true }, sheets: { type: 'number', required: true } } }, render: (_a, v) => text(`export ${v.sheets} sheets`) }, async execute(args) { const all = [...deps.transactions.entries()].map(([, v]) => v).filter(t => t.account_id === args.account_id && t.period === args.period); const ms = [...deps.matches.entries()].map(([, v]) => v); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(all.filter(t => t.side === 'bank').map(t => ({ date: t.date, amount_cents: t.amount_cents, reference: t.reference, txn_id: t.txn_id }))), 'Bank'); XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(all.filter(t => t.side === 'gl').map(t => ({ date: t.date, amount_cents: t.amount_cents, reference: t.reference, txn_id: t.txn_id }))), 'GL'); XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(ms), 'Matches'); const dir = path.join(deps.baseDir, 'exports'); await mkdir(dir, { recursive: true }); const p = path.join(dir, `${args.account_id}-${args.period}.xlsx`); const b = Buffer.from(XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' })); await writeFile(p, b); return { path: p, sha256: sha(b), sheets: wb.SheetNames.length }; } });
|
|
28
|
-
|
|
28
|
+
const reconcile = defineTool({ name: 'bankrec.reconcile_camt', description: 'Check CAMT.053 opening/closing balance continuity against the sum of entries; read-only.', parameters: { camt_xml: { type: 'string', required: true } }, output: { schema: { type: 'object', additionalProperties: false, properties: { opening_cents: { type: 'number', required: true }, closing_cents: { type: 'number', required: true }, net_cents: { type: 'number', required: true }, difference_cents: { type: 'number', required: true }, balanced: { type: 'boolean', required: true } } }, render: (_a, v) => text(`CAMT balance ${v.balanced ? 'ok' : 'DIFF'} (${v.difference_cents})`) }, async execute(a) { const balances = parseCamtBalances(a.camt_xml); const rows = parseCamt(a.camt_xml); const open = balances.find(b => /^OPBD|^PRCD/.test(b.type)); const close = balances.find(b => /^CLBD|^CLAV/.test(b.type)); if (!open || !close)
|
|
29
|
+
throw new BankrecError('E_BR_CSV_INVALID', 'CAMT lacks opening/closing balance'); const net = rows.reduce((n, r) => n + r.amount_cents, 0); const diff = open.amount_cents + net - close.amount_cents; return { opening_cents: open.amount_cents, closing_cents: close.amount_cents, net_cents: net, difference_cents: diff, balanced: diff === 0 }; } });
|
|
30
|
+
return [ingest, camt, match, reconcile, exportTool];
|
|
29
31
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perrylink/dsh-bankrec",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
4
|
"description": "Bank statement ingest, 1:1 matching and reconciliation workpaper (SKU-06 S1 public/synthetic demo).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/plugin/index.js",
|