india2actual 0.1.0

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.
Files changed (70) hide show
  1. package/.env.example +23 -0
  2. package/LICENSE +21 -0
  3. package/README.md +280 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +295 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/env-file.d.ts +38 -0
  8. package/dist/env-file.js +40 -0
  9. package/dist/env-file.js.map +1 -0
  10. package/dist/extract/csv.d.ts +13 -0
  11. package/dist/extract/csv.js +60 -0
  12. package/dist/extract/csv.js.map +1 -0
  13. package/dist/extract/html-table.d.ts +3 -0
  14. package/dist/extract/html-table.js +77 -0
  15. package/dist/extract/html-table.js.map +1 -0
  16. package/dist/extract/index.d.ts +21 -0
  17. package/dist/extract/index.js +44 -0
  18. package/dist/extract/index.js.map +1 -0
  19. package/dist/extract/pdf.d.ts +47 -0
  20. package/dist/extract/pdf.js +439 -0
  21. package/dist/extract/pdf.js.map +1 -0
  22. package/dist/extract/sniff.d.ts +12 -0
  23. package/dist/extract/sniff.js +57 -0
  24. package/dist/extract/sniff.js.map +1 -0
  25. package/dist/extract/spreadsheetml.d.ts +3 -0
  26. package/dist/extract/spreadsheetml.js +93 -0
  27. package/dist/extract/spreadsheetml.js.map +1 -0
  28. package/dist/extract/types.d.ts +20 -0
  29. package/dist/extract/types.js +2 -0
  30. package/dist/extract/types.js.map +1 -0
  31. package/dist/extract/xlsx.d.ts +2 -0
  32. package/dist/extract/xlsx.js +64 -0
  33. package/dist/extract/xlsx.js.map +1 -0
  34. package/dist/interpret/header.d.ts +24 -0
  35. package/dist/interpret/header.js +57 -0
  36. package/dist/interpret/header.js.map +1 -0
  37. package/dist/interpret/roundtrip.d.ts +29 -0
  38. package/dist/interpret/roundtrip.js +89 -0
  39. package/dist/interpret/roundtrip.js.map +1 -0
  40. package/dist/interpret/rows.d.ts +54 -0
  41. package/dist/interpret/rows.js +151 -0
  42. package/dist/interpret/rows.js.map +1 -0
  43. package/dist/interpret/synonyms.d.ts +27 -0
  44. package/dist/interpret/synonyms.js +55 -0
  45. package/dist/interpret/synonyms.js.map +1 -0
  46. package/dist/interpret/validate.d.ts +27 -0
  47. package/dist/interpret/validate.js +93 -0
  48. package/dist/interpret/validate.js.map +1 -0
  49. package/dist/interpret/values.d.ts +22 -0
  50. package/dist/interpret/values.js +131 -0
  51. package/dist/interpret/values.js.map +1 -0
  52. package/dist/merchants-file.d.ts +14 -0
  53. package/dist/merchants-file.js +40 -0
  54. package/dist/merchants-file.js.map +1 -0
  55. package/dist/narration/merchants.d.ts +46 -0
  56. package/dist/narration/merchants.js +161 -0
  57. package/dist/narration/merchants.js.map +1 -0
  58. package/dist/narration/parse.d.ts +14 -0
  59. package/dist/narration/parse.js +659 -0
  60. package/dist/narration/parse.js.map +1 -0
  61. package/dist/narration/types.d.ts +25 -0
  62. package/dist/narration/types.js +2 -0
  63. package/dist/narration/types.js.map +1 -0
  64. package/dist/out/csv.d.ts +21 -0
  65. package/dist/out/csv.js +40 -0
  66. package/dist/out/csv.js.map +1 -0
  67. package/dist/out/push.d.ts +74 -0
  68. package/dist/out/push.js +103 -0
  69. package/dist/out/push.js.map +1 -0
  70. package/package.json +65 -0
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The transaction families Indian banks encode into the narration/particulars
3
+ * column. `other` covers interest postings, charges, cheques and anything else
4
+ * we do not recognise — it is not an error.
5
+ */
6
+ export type NarrationKind = 'upi' | 'neft' | 'imps' | 'rtgs' | 'pos' | 'ach' | 'atm' | 'other';
7
+ export type ParsedNarration = {
8
+ kind: NarrationKind;
9
+ /**
10
+ * Display payee. Guaranteed non-empty: when nothing can be extracted this
11
+ * falls back to the raw narration, so the worst case matches what Actual
12
+ * would have shown anyway.
13
+ */
14
+ merchant: string;
15
+ /** Virtual Payment Address, e.g. `swiggy@ybl`, when present. */
16
+ vpa?: string;
17
+ /**
18
+ * Bank-issued UPI reference (UTR/RRN). Only set when we are confident it is
19
+ * genuinely per-transaction unique, because a non-unique value here would
20
+ * make Actual collapse distinct transactions. See `extractRef`.
21
+ */
22
+ ref?: string;
23
+ /** The narration exactly as the bank wrote it. */
24
+ raw: string;
25
+ };
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/narration/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,21 @@
1
+ import type { StatementTransaction } from '../interpret/rows.js';
2
+ /**
3
+ * Columns chosen to match what Actual's import dialog can actually map:
4
+ * date, payee, notes and amount.
5
+ *
6
+ * Note the deliberate compromise on notes. Actual's CSV field mapping has no
7
+ * `imported_payee` slot (only date, amount, payee, notes, category, in/out and
8
+ * inflow/outflow), so the raw narration goes into Notes to keep it visible.
9
+ * The API path does not have this limitation and sets `imported_payee`
10
+ * properly — this is the one place the CSV route is lossier.
11
+ *
12
+ * `Reference` is emitted for human inspection; leave it unmapped on import.
13
+ *
14
+ * This header is also the signature the round-trip path matches on, so that
15
+ * the tool can read its own output back and push a CSV you have reviewed.
16
+ * Renaming or reordering a column here means updating
17
+ * `interpret/roundtrip.ts`; a test asserts the two agree.
18
+ */
19
+ export declare const COLUMNS: readonly ["Date", "Payee", "Notes", "Amount", "Reference"];
20
+ export declare function toCsv(transactions: StatementTransaction[]): string;
21
+ export declare function writeCsv(path: string, transactions: StatementTransaction[]): Promise<void>;
@@ -0,0 +1,40 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { stringify } from 'csv-stringify/sync';
3
+ /**
4
+ * Columns chosen to match what Actual's import dialog can actually map:
5
+ * date, payee, notes and amount.
6
+ *
7
+ * Note the deliberate compromise on notes. Actual's CSV field mapping has no
8
+ * `imported_payee` slot (only date, amount, payee, notes, category, in/out and
9
+ * inflow/outflow), so the raw narration goes into Notes to keep it visible.
10
+ * The API path does not have this limitation and sets `imported_payee`
11
+ * properly — this is the one place the CSV route is lossier.
12
+ *
13
+ * `Reference` is emitted for human inspection; leave it unmapped on import.
14
+ *
15
+ * This header is also the signature the round-trip path matches on, so that
16
+ * the tool can read its own output back and push a CSV you have reviewed.
17
+ * Renaming or reordering a column here means updating
18
+ * `interpret/roundtrip.ts`; a test asserts the two agree.
19
+ */
20
+ export const COLUMNS = [
21
+ 'Date',
22
+ 'Payee',
23
+ 'Notes',
24
+ 'Amount',
25
+ 'Reference',
26
+ ];
27
+ export function toCsv(transactions) {
28
+ const rows = transactions.map(transaction => [
29
+ transaction.date,
30
+ transaction.payee,
31
+ transaction.raw,
32
+ transaction.amount.toFixed(2),
33
+ transaction.ref ?? '',
34
+ ]);
35
+ return stringify(rows, { header: true, columns: [...COLUMNS] });
36
+ }
37
+ export async function writeCsv(path, transactions) {
38
+ await writeFile(path, toCsv(transactions), 'utf8');
39
+ }
40
+ //# sourceMappingURL=csv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csv.js","sourceRoot":"","sources":["../../src/out/csv.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAI/C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,MAAM;IACN,OAAO;IACP,OAAO;IACP,QAAQ;IACR,WAAW;CACH,CAAC;AAEX,MAAM,UAAU,KAAK,CAAC,YAAoC;IACxD,MAAM,IAAI,GAAG,YAAY,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3C,WAAW,CAAC,IAAI;QAChB,WAAW,CAAC,KAAK;QACjB,WAAW,CAAC,GAAG;QACf,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;QAC7B,WAAW,CAAC,GAAG,IAAI,EAAE;KACtB,CAAC,CAAC;IAEH,OAAO,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,YAAoC;IAEpC,MAAM,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC;AACrD,CAAC"}
@@ -0,0 +1,74 @@
1
+ import type { StatementTransaction } from '../interpret/rows.js';
2
+ /**
3
+ * The subset of Actual's `ImportTransactionEntity` we populate. Declared
4
+ * locally so this module type-checks without `@actual-app/api` installed —
5
+ * it is an optional peer dependency, since the CSV path is the default and
6
+ * the API package pulls in a native SQLite build.
7
+ */
8
+ export type ActualImportTransaction = {
9
+ date: string;
10
+ /** Integer paise. */
11
+ amount: number;
12
+ payee_name: string;
13
+ imported_payee: string;
14
+ notes: string;
15
+ imported_id?: string;
16
+ cleared: boolean;
17
+ };
18
+ export type PushConfig = {
19
+ serverURL: string;
20
+ password: string;
21
+ syncId: string;
22
+ dataDir: string;
23
+ /** End-to-end encryption password, for encrypted budget files. */
24
+ encryptionPassword?: string;
25
+ /** Account name (case-insensitive) or id. */
26
+ account: string;
27
+ dryRun: boolean;
28
+ };
29
+ export type PushResult = {
30
+ accountName: string;
31
+ accountId: string;
32
+ added: number;
33
+ updated: number;
34
+ errors: string[];
35
+ dryRun: boolean;
36
+ };
37
+ /** Rupees to integer paise, matching Actual's own `amountToInteger`. */
38
+ export declare function toPaise(amount: number): number;
39
+ /**
40
+ * Map parsed statement rows onto Actual's import shape.
41
+ *
42
+ * Two deliberate choices:
43
+ *
44
+ * - `payee_name` gets the cleaned merchant and `imported_payee` the original
45
+ * narration, which is exactly what those fields are for: the payee collapses
46
+ * correctly while Actual's payee matching still sees the raw text. This is
47
+ * the one thing the CSV output cannot do, since Actual's CSV field mapping
48
+ * has no `imported_payee` slot.
49
+ * - `notes` also gets the narration, duplicating `imported_payee`. That
50
+ * duplication looked redundant and was originally left out, which was wrong:
51
+ * `imported_payee` is not a column you can read at a glance, whereas Notes
52
+ * is one you can see, search and filter. Without it the narration is
53
+ * effectively invisible after a push.
54
+ * - `imported_id` is only set when a reference survived the uniqueness checks.
55
+ * Omitting it is the safe default: Actual falls back to matching on date and
56
+ * amount within a week, whereas a non-unique id makes it treat distinct
57
+ * transactions as the same one and drop them.
58
+ */
59
+ export declare function toImportEntities(transactions: StatementTransaction[]): ActualImportTransaction[];
60
+ type ActualAccount = {
61
+ id: string;
62
+ name: string;
63
+ closed?: boolean;
64
+ };
65
+ export declare function resolveAccount(accounts: ActualAccount[], wanted: string): ActualAccount;
66
+ /**
67
+ * Send transactions to Actual.
68
+ *
69
+ * `@actual-app/api` is imported dynamically so the converter works without it
70
+ * installed, and so a missing install produces an actionable message rather
71
+ * than a module-resolution error at startup.
72
+ */
73
+ export declare function pushTransactions(transactions: StatementTransaction[], config: PushConfig): Promise<PushResult>;
74
+ export {};
@@ -0,0 +1,103 @@
1
+ /** Rupees to integer paise, matching Actual's own `amountToInteger`. */
2
+ export function toPaise(amount) {
3
+ return Math.round(amount * 100);
4
+ }
5
+ /**
6
+ * Map parsed statement rows onto Actual's import shape.
7
+ *
8
+ * Two deliberate choices:
9
+ *
10
+ * - `payee_name` gets the cleaned merchant and `imported_payee` the original
11
+ * narration, which is exactly what those fields are for: the payee collapses
12
+ * correctly while Actual's payee matching still sees the raw text. This is
13
+ * the one thing the CSV output cannot do, since Actual's CSV field mapping
14
+ * has no `imported_payee` slot.
15
+ * - `notes` also gets the narration, duplicating `imported_payee`. That
16
+ * duplication looked redundant and was originally left out, which was wrong:
17
+ * `imported_payee` is not a column you can read at a glance, whereas Notes
18
+ * is one you can see, search and filter. Without it the narration is
19
+ * effectively invisible after a push.
20
+ * - `imported_id` is only set when a reference survived the uniqueness checks.
21
+ * Omitting it is the safe default: Actual falls back to matching on date and
22
+ * amount within a week, whereas a non-unique id makes it treat distinct
23
+ * transactions as the same one and drop them.
24
+ */
25
+ export function toImportEntities(transactions) {
26
+ return transactions.map(transaction => ({
27
+ date: transaction.date,
28
+ amount: toPaise(transaction.amount),
29
+ payee_name: transaction.payee,
30
+ imported_payee: transaction.raw,
31
+ notes: transaction.raw,
32
+ ...(transaction.ref ? { imported_id: transaction.ref } : {}),
33
+ // Statement rows have already settled at the bank.
34
+ cleared: true,
35
+ }));
36
+ }
37
+ export function resolveAccount(accounts, wanted) {
38
+ const needle = wanted.trim().toLowerCase();
39
+ const match = accounts.find(account => account.id === wanted) ??
40
+ accounts.find(account => account.name.trim().toLowerCase() === needle);
41
+ if (!match) {
42
+ const available = accounts
43
+ .filter(account => !account.closed)
44
+ .map(account => ` ${account.name}`)
45
+ .join('\n');
46
+ throw new Error(`No account matching "${wanted}". Open accounts:\n${available || ' (none)'}`);
47
+ }
48
+ return match;
49
+ }
50
+ /**
51
+ * Send transactions to Actual.
52
+ *
53
+ * `@actual-app/api` is imported dynamically so the converter works without it
54
+ * installed, and so a missing install produces an actionable message rather
55
+ * than a module-resolution error at startup.
56
+ */
57
+ export async function pushTransactions(transactions, config) {
58
+ let api;
59
+ try {
60
+ // Non-literal specifier on purpose: it keeps TypeScript from trying to
61
+ // resolve an optional dependency that may not be installed.
62
+ const specifier = '@actual-app/api';
63
+ api = (await import(specifier));
64
+ }
65
+ catch {
66
+ throw new Error('The --push option needs the Actual API package:\n' +
67
+ ' npm install @actual-app/api');
68
+ }
69
+ await api.init({
70
+ dataDir: config.dataDir,
71
+ serverURL: config.serverURL,
72
+ password: config.password,
73
+ });
74
+ try {
75
+ await api.downloadBudget(config.syncId, config.encryptionPassword
76
+ ? { password: config.encryptionPassword }
77
+ : undefined);
78
+ const accounts = await api.getAccounts();
79
+ const account = resolveAccount(accounts, config.account);
80
+ const result = await api.importTransactions(account.id, toImportEntities(transactions), {
81
+ dryRun: config.dryRun,
82
+ defaultCleared: true,
83
+ // Actual title-cases imported payees by default, which lowercases
84
+ // first: `DMart` would become `Dmart` and `HDFC ... SIP` would become
85
+ // `Hdfc ... Sip`. The names here are already deliberately cased.
86
+ payeeNameNormalization: 'original',
87
+ });
88
+ return {
89
+ accountName: account.name,
90
+ accountId: account.id,
91
+ added: result.added?.length ?? 0,
92
+ updated: result.updated?.length ?? 0,
93
+ errors: (result.errors ?? []).map(error => error.message),
94
+ dryRun: config.dryRun,
95
+ };
96
+ }
97
+ finally {
98
+ // Always shut down: this flushes the sync and closes the budget, and
99
+ // leaving it open corrupts the local cache for the next run.
100
+ await api.shutdown();
101
+ }
102
+ }
103
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"push.js","sourceRoot":"","sources":["../../src/out/push.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "india2actual",
3
+ "version": "0.1.0",
4
+ "description": "Convert Indian bank statements (CSV, XLS, PDF) into Actual Budget transactions with real merchant names",
5
+ "license": "MIT",
6
+ "author": "emilgeo",
7
+ "homepage": "https://github.com/emilgeo/india2actual#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/emilgeo/india2actual.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/emilgeo/india2actual/issues"
14
+ },
15
+ "keywords": [
16
+ "actual",
17
+ "actual-budget",
18
+ "budgeting",
19
+ "personal-finance",
20
+ "india",
21
+ "indian-banks",
22
+ "upi",
23
+ "bank-statement",
24
+ "csv",
25
+ "pdf",
26
+ "importer",
27
+ "converter"
28
+ ],
29
+ "bin": {
30
+ "india2actual": "dist/cli.js"
31
+ },
32
+ "type": "module",
33
+ "files": [
34
+ "dist",
35
+ ".env.example"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsc",
39
+ "dev": "tsx src/cli.ts",
40
+ "test": "vitest --run",
41
+ "test:watch": "vitest",
42
+ "typecheck": "tsc --noEmit",
43
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
44
+ },
45
+ "dependencies": {
46
+ "csv-parse": "^7.0.2",
47
+ "csv-stringify": "^6.5.0",
48
+ "exceljs": "^4.4.0",
49
+ "fast-xml-parser": "^5.11.1",
50
+ "node-html-parser": "^9.0.4",
51
+ "pdfjs-dist": "^6.3.289"
52
+ },
53
+ "optionalDependencies": {
54
+ "@actual-app/api": "^26.9.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^22.10.0",
58
+ "tsx": "^4.19.0",
59
+ "typescript": "^5.7.0",
60
+ "vitest": "^5.0.1"
61
+ },
62
+ "engines": {
63
+ "node": ">=22"
64
+ }
65
+ }