@kansei-link/bantou 0.2.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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -0
  3. package/data/exclusion-rules/README.md +104 -0
  4. package/data/exclusion-rules/jp-tax-baseline-v1.json +185 -0
  5. package/data/exclusion-rules-schema.json +109 -0
  6. package/data/keyword-dict/README.md +91 -0
  7. package/data/keyword-dict/jp-tax-baseline-v1.json +398 -0
  8. package/data/keyword-dict-schema.json +117 -0
  9. package/data/tax-rules/jp-tax-rules-v1.json +170 -0
  10. package/dist/adapters/csv-parser.d.ts +11 -0
  11. package/dist/adapters/csv-parser.js +133 -0
  12. package/dist/adapters/freee-csv-adapter.d.ts +14 -0
  13. package/dist/adapters/freee-csv-adapter.js +67 -0
  14. package/dist/adapters/generic-adapter.d.ts +20 -0
  15. package/dist/adapters/generic-adapter.js +73 -0
  16. package/dist/adapters/index.d.ts +23 -0
  17. package/dist/adapters/index.js +386 -0
  18. package/dist/adapters/types.d.ts +111 -0
  19. package/dist/adapters/types.js +9 -0
  20. package/dist/adapters/yayoi-adapter.d.ts +46 -0
  21. package/dist/adapters/yayoi-adapter.js +181 -0
  22. package/dist/bin/freee-doctor.d.ts +3 -0
  23. package/dist/bin/freee-doctor.js +15 -0
  24. package/dist/classifier/claude-classifier.d.ts +24 -0
  25. package/dist/classifier/claude-classifier.js +154 -0
  26. package/dist/classifier/keyword-classifier.d.ts +22 -0
  27. package/dist/classifier/keyword-classifier.js +124 -0
  28. package/dist/classifier/keyword-match.d.ts +21 -0
  29. package/dist/classifier/keyword-match.js +57 -0
  30. package/dist/classifier/normalize.d.ts +3 -0
  31. package/dist/classifier/normalize.js +27 -0
  32. package/dist/classifier/two-stage-classifier.d.ts +21 -0
  33. package/dist/classifier/two-stage-classifier.js +51 -0
  34. package/dist/classifier/types.d.ts +31 -0
  35. package/dist/classifier/types.js +3 -0
  36. package/dist/connectors/freee.d.ts +115 -0
  37. package/dist/connectors/freee.js +177 -0
  38. package/dist/exclusion/exclusion-checker.d.ts +10 -0
  39. package/dist/exclusion/exclusion-checker.js +162 -0
  40. package/dist/freee-doctor.d.ts +26 -0
  41. package/dist/freee-doctor.js +82 -0
  42. package/dist/index.d.ts +3 -0
  43. package/dist/index.js +656 -0
  44. package/dist/memory/cockpit-memory.d.ts +73 -0
  45. package/dist/memory/cockpit-memory.js +473 -0
  46. package/dist/memory/types.d.ts +114 -0
  47. package/dist/memory/types.js +11 -0
  48. package/dist/pipeline/confidence-router.d.ts +38 -0
  49. package/dist/pipeline/confidence-router.js +129 -0
  50. package/dist/pipeline/nightly-pipeline.d.ts +44 -0
  51. package/dist/pipeline/nightly-pipeline.js +497 -0
  52. package/dist/pipeline/types.d.ts +84 -0
  53. package/dist/pipeline/types.js +12 -0
  54. package/dist/reports/monthly-report.d.ts +64 -0
  55. package/dist/reports/monthly-report.js +230 -0
  56. package/dist/secrets.d.ts +14 -0
  57. package/dist/secrets.js +86 -0
  58. package/dist/tax-rules/tax-rule-engine.d.ts +103 -0
  59. package/dist/tax-rules/tax-rule-engine.js +449 -0
  60. package/dist/tax-rules/types.d.ts +103 -0
  61. package/dist/tax-rules/types.js +7 -0
  62. package/package.json +74 -0
@@ -0,0 +1,386 @@
1
+ // CSV import orchestrator.
2
+ //
3
+ // Auto-detects CSV format (弥生/freee/MF/generic), parses rows,
4
+ // runs each through the full classification pipeline, and returns
5
+ // structured results grouped by routing action.
6
+ import { parseCsv } from './csv-parser.js';
7
+ import { YayoiAdapter } from './yayoi-adapter.js';
8
+ import { FreeeCsvAdapter } from './freee-csv-adapter.js';
9
+ import { GenericCsvAdapter } from './generic-adapter.js';
10
+ /**
11
+ * Import CSV and run through the full classification pipeline.
12
+ *
13
+ * @param csvText - Raw CSV text (UTF-8).
14
+ * @param classifier - TwoStageClassifier instance.
15
+ * @param exclusion - ExclusionChecker instance.
16
+ * @param router - ConfidenceRouter instance.
17
+ * @param opts.source - Force a specific source format (skip auto-detection).
18
+ * @param opts.mapping - Column mapping for generic CSV.
19
+ */
20
+ export async function importCsv(csvText, classifier, exclusion, router, opts = {}) {
21
+ // 1. Parse CSV
22
+ const { headers, rows } = parseCsv(csvText);
23
+ if (headers.length === 0 || rows.length === 0) {
24
+ return emptyResult('CSV is empty or has no data rows');
25
+ }
26
+ // 2. Detect or use specified adapter
27
+ const adapter = resolveAdapter(headers, opts.source, opts.mapping);
28
+ if (!adapter) {
29
+ return emptyResult(`CSV format not recognized. Headers: [${headers.slice(0, 5).join(', ')}...]. ` +
30
+ 'Specify source="generic" with column mapping, or export from 弥生/freee in UTF-8.');
31
+ }
32
+ // 3. Parse all rows
33
+ const warnings = [];
34
+ const parsed = [];
35
+ let skipped = 0;
36
+ for (let i = 0; i < rows.length; i++) {
37
+ const rowNumber = i + 2; // 1-based, +1 for header row
38
+ const { transaction, skip_reason } = adapter.parseRow(rows[i], rowNumber);
39
+ if (transaction) {
40
+ parsed.push({ rowNumber, transaction });
41
+ }
42
+ else {
43
+ skipped++;
44
+ if (skip_reason && skipped <= 10) {
45
+ warnings.push(`Row ${rowNumber}: ${skip_reason}`);
46
+ }
47
+ }
48
+ }
49
+ if (skipped > 10) {
50
+ warnings.push(`... and ${skipped - 10} more skipped rows`);
51
+ }
52
+ if (parsed.length === 0) {
53
+ return emptyResult('No valid transactions found in CSV', warnings);
54
+ }
55
+ // 4. Run each transaction through the pipeline
56
+ const autoRegister = [];
57
+ const autoRegisterWithLog = [];
58
+ const humanReview = [];
59
+ const excludedTxns = [];
60
+ for (const { rowNumber, transaction } of parsed) {
61
+ const ct = await classifyTransaction(rowNumber, transaction, classifier, exclusion, router, opts.memory, opts.taxRuleEngine);
62
+ switch (ct.action) {
63
+ case 'auto_register':
64
+ autoRegister.push(ct);
65
+ break;
66
+ case 'auto_register_with_log':
67
+ autoRegisterWithLog.push(ct);
68
+ break;
69
+ case 'human_review':
70
+ if (ct.excluded) {
71
+ excludedTxns.push(ct);
72
+ }
73
+ else {
74
+ humanReview.push(ct);
75
+ }
76
+ break;
77
+ }
78
+ }
79
+ // 5. Build result
80
+ const totalClassified = autoRegister.length + autoRegisterWithLog.length;
81
+ const totalProcessed = parsed.length;
82
+ const classificationRate = totalProcessed > 0
83
+ ? ((totalClassified / totalProcessed) * 100).toFixed(1) + '%'
84
+ : '0%';
85
+ // Save memory after processing all rows
86
+ if (opts.memory) {
87
+ opts.memory.save();
88
+ }
89
+ const result = {
90
+ ok: true,
91
+ source: adapter.source,
92
+ source_label: adapter.label,
93
+ total_rows: rows.length,
94
+ parsed_count: parsed.length,
95
+ skipped_count: skipped,
96
+ warnings,
97
+ auto_register: autoRegister,
98
+ auto_register_with_log: autoRegisterWithLog,
99
+ human_review: humanReview,
100
+ excluded: excludedTxns,
101
+ summary: {
102
+ auto_register_count: autoRegister.length,
103
+ auto_register_with_log_count: autoRegisterWithLog.length,
104
+ human_review_count: humanReview.length,
105
+ excluded_count: excludedTxns.length,
106
+ classification_rate: classificationRate,
107
+ },
108
+ csv_output: buildCsvOutput([...autoRegister, ...autoRegisterWithLog, ...humanReview, ...excludedTxns]),
109
+ markdown_report: buildMarkdownReport(adapter, parsed.length, skipped, autoRegister, autoRegisterWithLog, humanReview, excludedTxns),
110
+ };
111
+ return result;
112
+ }
113
+ // ============================================================
114
+ // Internal helpers
115
+ // ============================================================
116
+ async function classifyTransaction(rowNumber, transaction, classifier, exclusion, router, memory, taxRuleEngine) {
117
+ // Stage 0: Exclusion
118
+ const exc = exclusion.check(transaction);
119
+ // Stage 1+2: Classification (only if not excluded)
120
+ let cls = null;
121
+ let memorySource;
122
+ if (!exc.excluded) {
123
+ // Memory recall before classification
124
+ if (memory) {
125
+ const recall = memory.recallPattern(transaction);
126
+ if (recall.found && recall.source === 'correction' && recall.correction) {
127
+ cls = {
128
+ classified: true,
129
+ category_id: recall.correction.to_category_id,
130
+ category_name_ja: recall.correction.to_category_name_ja,
131
+ freee_account_code: recall.correction.to_freee_account_code,
132
+ tax_code: recall.correction.to_tax_code,
133
+ confidence: 'high',
134
+ match_reason: recall.reason,
135
+ classifier_version: 'memory-correction',
136
+ };
137
+ memorySource = 'correction';
138
+ }
139
+ else if (recall.found && recall.source === 'pattern' && recall.pattern) {
140
+ cls = {
141
+ classified: true,
142
+ category_id: recall.pattern.category_id,
143
+ category_name_ja: recall.pattern.category_name_ja,
144
+ freee_account_code: recall.pattern.freee_account_code,
145
+ tax_code: recall.pattern.tax_code,
146
+ confidence: recall.confidence,
147
+ match_reason: recall.reason,
148
+ classifier_version: 'memory-pattern',
149
+ };
150
+ memorySource = 'pattern';
151
+ }
152
+ }
153
+ // Fall through to normal classification if memory miss
154
+ if (!cls) {
155
+ cls = await classifier.classify(transaction);
156
+ }
157
+ }
158
+ // Routing
159
+ const routing = router.route(exc, cls, {
160
+ amount: transaction.amount,
161
+ partner_name: transaction.partner_name,
162
+ is_new_partner: false, // CSV import = no partner DB to check
163
+ date: transaction.date,
164
+ });
165
+ // Remember classification for future recall (only successful auto-classifications)
166
+ if (memory && !memorySource && cls?.classified && (routing.action === 'auto_register' || routing.action === 'auto_register_with_log')) {
167
+ memory.rememberClassification(transaction, cls.category_id, cls.category_name_ja, cls.confidence, 'keyword', // CSV import is always Stage 1 in practice
168
+ cls.freee_account_code, typeof cls.tax_code === 'number' ? cls.tax_code : undefined);
169
+ }
170
+ // Tax Rule Engine: post-classification refinements
171
+ let taxCodeOverride;
172
+ let taxCodeReason;
173
+ let assetTier;
174
+ let assetWarning;
175
+ let withholdingAmount;
176
+ let withholdingRate;
177
+ let consumptionTaxRate;
178
+ let consumptionTaxReason;
179
+ let taxRuleWarnings;
180
+ if (taxRuleEngine && cls?.classified) {
181
+ const taxResult = taxRuleEngine.applyRules(transaction, cls);
182
+ if (taxResult.tax_code_override !== undefined) {
183
+ taxCodeOverride = taxResult.tax_code_override;
184
+ taxCodeReason = taxResult.tax_code_reason;
185
+ }
186
+ if (taxResult.asset_tier && taxResult.asset_tier !== 'expense') {
187
+ assetTier = taxResult.asset_tier;
188
+ assetWarning = taxResult.asset_warning;
189
+ }
190
+ if (taxResult.withholding) {
191
+ withholdingAmount = taxResult.withholding.withholding_amount;
192
+ withholdingRate = taxResult.withholding.rate_description;
193
+ }
194
+ if (taxResult.consumption_tax_rate !== undefined) {
195
+ consumptionTaxRate = taxResult.consumption_tax_rate;
196
+ consumptionTaxReason = taxResult.consumption_tax_reason;
197
+ }
198
+ if (taxResult.warnings.length > 0) {
199
+ taxRuleWarnings = taxResult.warnings;
200
+ }
201
+ }
202
+ return {
203
+ row_number: rowNumber,
204
+ transaction,
205
+ excluded: exc.excluded,
206
+ exclusion_rule: exc.rule_id,
207
+ exclusion_reason: exc.reason,
208
+ classified: cls?.classified ?? false,
209
+ category_id: cls?.category_id,
210
+ category_name_ja: cls?.category_name_ja,
211
+ confidence: cls?.confidence,
212
+ matched_keyword: cls?.matched_keyword,
213
+ stage: cls?.stage,
214
+ freee_account_code: cls?.freee_account_code,
215
+ tax_code: taxCodeOverride ?? cls?.tax_code,
216
+ tax_code_override: taxCodeOverride,
217
+ tax_code_reason: taxCodeReason,
218
+ asset_tier: assetTier,
219
+ asset_warning: assetWarning,
220
+ withholding_amount: withholdingAmount,
221
+ withholding_rate: withholdingRate,
222
+ consumption_tax_rate: consumptionTaxRate,
223
+ consumption_tax_reason: consumptionTaxReason,
224
+ tax_rule_warnings: taxRuleWarnings,
225
+ action: routing.action,
226
+ routing_flags: routing.flags,
227
+ routing_reasons: routing.reasons,
228
+ };
229
+ }
230
+ function resolveAdapter(headers, source, mapping) {
231
+ // If source is explicitly specified
232
+ if (source === 'yayoi') {
233
+ const a = new YayoiAdapter();
234
+ a.detectFormat(headers);
235
+ return a;
236
+ }
237
+ if (source === 'freee_export') {
238
+ const a = new FreeeCsvAdapter();
239
+ a.detectFormat(headers);
240
+ return a;
241
+ }
242
+ if (source === 'generic' && mapping) {
243
+ return new GenericCsvAdapter(mapping);
244
+ }
245
+ // Auto-detect: try each adapter in priority order
246
+ const adapters = [
247
+ new YayoiAdapter(),
248
+ new FreeeCsvAdapter(),
249
+ ];
250
+ for (const adapter of adapters) {
251
+ if (adapter.detectFormat(headers)) {
252
+ return adapter;
253
+ }
254
+ }
255
+ // Fallback: if mapping provided, use generic
256
+ if (mapping) {
257
+ return new GenericCsvAdapter(mapping);
258
+ }
259
+ return null;
260
+ }
261
+ function emptyResult(error, warnings = []) {
262
+ return {
263
+ ok: false,
264
+ source: 'generic',
265
+ source_label: 'Unknown',
266
+ total_rows: 0,
267
+ parsed_count: 0,
268
+ skipped_count: 0,
269
+ warnings: [error, ...warnings],
270
+ auto_register: [],
271
+ auto_register_with_log: [],
272
+ human_review: [],
273
+ excluded: [],
274
+ summary: {
275
+ auto_register_count: 0,
276
+ auto_register_with_log_count: 0,
277
+ human_review_count: 0,
278
+ excluded_count: 0,
279
+ classification_rate: '0%',
280
+ },
281
+ };
282
+ }
283
+ /**
284
+ * Build a CSV output with classification results appended as new columns.
285
+ * Can be re-imported into 弥生 or used for review.
286
+ */
287
+ function buildCsvOutput(transactions) {
288
+ const headers = [
289
+ '行番号', '日付', '金額', '摘要', '取引先',
290
+ '分類結果', '勘定科目', '信頼度', 'アクション', 'フラグ',
291
+ ];
292
+ const lines = [headers.join(',')];
293
+ for (const ct of transactions) {
294
+ const cols = [
295
+ String(ct.row_number),
296
+ ct.transaction.date,
297
+ String(ct.transaction.amount),
298
+ csvEscape(ct.transaction.memo),
299
+ csvEscape(ct.transaction.partner_name || ''),
300
+ csvEscape(ct.category_name_ja || (ct.excluded ? `除外: ${ct.exclusion_rule}` : '未分類')),
301
+ ct.freee_account_code ? String(ct.freee_account_code) : '',
302
+ ct.confidence || '',
303
+ ct.action,
304
+ ct.routing_flags.join(';'),
305
+ ];
306
+ lines.push(cols.join(','));
307
+ }
308
+ return lines.join('\n');
309
+ }
310
+ function csvEscape(value) {
311
+ if (value.includes(',') || value.includes('"') || value.includes('\n')) {
312
+ return '"' + value.replace(/"/g, '""') + '"';
313
+ }
314
+ return value;
315
+ }
316
+ /**
317
+ * Build a Markdown report summarizing the import results.
318
+ */
319
+ function buildMarkdownReport(adapter, parsedCount, skippedCount, autoRegister, autoRegisterWithLog, humanReview, excluded) {
320
+ const total = parsedCount;
321
+ const autoCount = autoRegister.length;
322
+ const autoLogCount = autoRegisterWithLog.length;
323
+ const reviewCount = humanReview.length;
324
+ const excludedCount = excluded.length;
325
+ const classRate = total > 0
326
+ ? (((autoCount + autoLogCount) / total) * 100).toFixed(1)
327
+ : '0';
328
+ let md = `# CSV Import Report\n\n`;
329
+ md += `**Source**: ${adapter.label}\n`;
330
+ md += `**Date**: ${new Date().toISOString().slice(0, 10)}\n\n`;
331
+ md += `## Summary\n\n`;
332
+ md += `| Metric | Value |\n|---|---|\n`;
333
+ md += `| Total rows | ${total + skippedCount} |\n`;
334
+ md += `| Parsed | ${total} |\n`;
335
+ md += `| Skipped | ${skippedCount} |\n`;
336
+ md += `| Auto-register (high confidence) | ${autoCount} |\n`;
337
+ md += `| Auto-register with log (medium) | ${autoLogCount} |\n`;
338
+ md += `| Human review required | ${reviewCount} |\n`;
339
+ md += `| Excluded (Stage 0) | ${excludedCount} |\n`;
340
+ md += `| **Classification rate** | **${classRate}%** |\n\n`;
341
+ // Category breakdown
342
+ const categoryMap = new Map();
343
+ for (const ct of [...autoRegister, ...autoRegisterWithLog]) {
344
+ const cat = ct.category_name_ja || '不明';
345
+ const existing = categoryMap.get(cat) || { count: 0, total: 0 };
346
+ existing.count++;
347
+ existing.total += ct.transaction.amount;
348
+ categoryMap.set(cat, existing);
349
+ }
350
+ if (categoryMap.size > 0) {
351
+ md += `## Category Breakdown\n\n`;
352
+ md += `| Category | Count | Total Amount |\n|---|---|---|\n`;
353
+ const sorted = [...categoryMap.entries()].sort((a, b) => b[1].total - a[1].total);
354
+ for (const [cat, { count, total }] of sorted) {
355
+ md += `| ${cat} | ${count} | ¥${total.toLocaleString()} |\n`;
356
+ }
357
+ md += '\n';
358
+ }
359
+ // Human review items
360
+ if (humanReview.length > 0) {
361
+ md += `## Human Review Required (${reviewCount} items)\n\n`;
362
+ md += `| Row | Date | Amount | Memo | Reason |\n|---|---|---|---|---|\n`;
363
+ for (const ct of humanReview.slice(0, 20)) {
364
+ const reasons = ct.routing_flags.join(', ') || ct.routing_reasons[0] || '';
365
+ md += `| ${ct.row_number} | ${ct.transaction.date} | ¥${ct.transaction.amount.toLocaleString()} | ${ct.transaction.memo.slice(0, 30)} | ${reasons} |\n`;
366
+ }
367
+ if (humanReview.length > 20) {
368
+ md += `| ... | ... | ... | ... | +${humanReview.length - 20} more |\n`;
369
+ }
370
+ md += '\n';
371
+ }
372
+ // Excluded items
373
+ if (excluded.length > 0) {
374
+ md += `## Excluded Transactions (${excludedCount} items)\n\n`;
375
+ md += `| Row | Date | Amount | Memo | Rule |\n|---|---|---|---|---|\n`;
376
+ for (const ct of excluded.slice(0, 10)) {
377
+ md += `| ${ct.row_number} | ${ct.transaction.date} | ¥${ct.transaction.amount.toLocaleString()} | ${ct.transaction.memo.slice(0, 30)} | ${ct.exclusion_rule || ''} |\n`;
378
+ }
379
+ if (excluded.length > 10) {
380
+ md += `| ... | ... | ... | ... | +${excluded.length - 10} more |\n`;
381
+ }
382
+ md += '\n';
383
+ }
384
+ return md;
385
+ }
386
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,111 @@
1
+ import { Transaction } from '../classifier/types.js';
2
+ /** Supported CSV source platforms. */
3
+ export type CsvSource = 'yayoi' | 'freee_export' | 'mf_export' | 'generic';
4
+ /**
5
+ * Column mapping for generic CSV import.
6
+ * User specifies which columns map to Transaction fields.
7
+ */
8
+ export interface ColumnMapping {
9
+ date: string;
10
+ amount: string;
11
+ memo: string;
12
+ partner_name?: string;
13
+ type?: string;
14
+ }
15
+ /**
16
+ * A single parsed CSV row before conversion to Transaction.
17
+ */
18
+ export interface ParsedCsvRow {
19
+ /** Original row index (1-based, excluding header). */
20
+ row_number: number;
21
+ /** Raw column values keyed by header name. */
22
+ raw: Record<string, string>;
23
+ /** Parsed Transaction (null if row was skipped/invalid). */
24
+ transaction: Transaction | null;
25
+ /** Skip reason (null if successfully parsed). */
26
+ skip_reason: string | null;
27
+ }
28
+ /**
29
+ * Result of CSV import + classification pipeline.
30
+ */
31
+ export interface ImportResult {
32
+ ok: boolean;
33
+ source: CsvSource;
34
+ /** Source format detected or specified. */
35
+ source_label: string;
36
+ total_rows: number;
37
+ parsed_count: number;
38
+ skipped_count: number;
39
+ warnings: string[];
40
+ auto_register: ClassifiedTransaction[];
41
+ auto_register_with_log: ClassifiedTransaction[];
42
+ human_review: ClassifiedTransaction[];
43
+ excluded: ClassifiedTransaction[];
44
+ summary: {
45
+ auto_register_count: number;
46
+ auto_register_with_log_count: number;
47
+ human_review_count: number;
48
+ excluded_count: number;
49
+ classification_rate: string;
50
+ };
51
+ csv_output?: string;
52
+ markdown_report?: string;
53
+ }
54
+ /**
55
+ * A transaction that has been through the full pipeline
56
+ * (exclusion + classification + routing).
57
+ */
58
+ export interface ClassifiedTransaction {
59
+ row_number: number;
60
+ transaction: Transaction;
61
+ excluded: boolean;
62
+ exclusion_rule?: string;
63
+ exclusion_reason?: string;
64
+ classified: boolean;
65
+ category_id?: string;
66
+ category_name_ja?: string;
67
+ confidence?: 'high' | 'medium' | 'low' | 'none';
68
+ matched_keyword?: string;
69
+ stage?: number;
70
+ freee_account_code?: number;
71
+ tax_code?: number;
72
+ tax_code_override?: number;
73
+ tax_code_reason?: string;
74
+ asset_tier?: string;
75
+ asset_warning?: string;
76
+ withholding_amount?: number;
77
+ withholding_rate?: string;
78
+ consumption_tax_rate?: number;
79
+ consumption_tax_reason?: string;
80
+ invoice_valid?: boolean;
81
+ invoice_deduction_rate?: number;
82
+ invoice_warnings?: string[];
83
+ tax_rule_warnings?: string[];
84
+ action: 'auto_register' | 'auto_register_with_log' | 'human_review';
85
+ routing_flags: string[];
86
+ routing_reasons: string[];
87
+ }
88
+ /**
89
+ * Interface for platform-specific CSV adapters.
90
+ */
91
+ export interface CsvAdapter {
92
+ readonly source: CsvSource;
93
+ readonly label: string;
94
+ /**
95
+ * Detect whether the given CSV headers match this adapter's format.
96
+ * @param headers - Array of column header strings from the first row.
97
+ * @returns true if this adapter can parse the CSV.
98
+ */
99
+ detectFormat(headers: string[]): boolean;
100
+ /**
101
+ * Parse a single CSV row into a Transaction.
102
+ * @param row - Key-value record (header → value).
103
+ * @param rowNumber - 1-based row index.
104
+ * @returns Parsed Transaction, or null with reason if row should be skipped.
105
+ */
106
+ parseRow(row: Record<string, string>, rowNumber: number): {
107
+ transaction: Transaction | null;
108
+ skip_reason: string | null;
109
+ };
110
+ }
111
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,9 @@
1
+ // CSV adapter types for multi-platform import support.
2
+ //
3
+ // Supported sources:
4
+ // - yayoi: 弥生会計 仕訳日記帳 CSV export
5
+ // - freee_export: freee 取引CSV export
6
+ // - mf_export: MoneyForward CSV export (= Phase 1.B stub)
7
+ // - generic: 汎用 CSV (= user specifies column mapping)
8
+ export {};
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,46 @@
1
+ import { Transaction } from '../classifier/types.js';
2
+ import { CsvAdapter, CsvSource } from './types.js';
3
+ export declare class YayoiAdapter implements CsvAdapter {
4
+ readonly source: CsvSource;
5
+ readonly label = "\u5F25\u751F\u4F1A\u8A08 CSV";
6
+ private format;
7
+ detectFormat(headers: string[]): boolean;
8
+ parseRow(row: Record<string, string>, rowNumber: number): {
9
+ transaction: Transaction | null;
10
+ skip_reason: string | null;
11
+ };
12
+ /**
13
+ * Parse Format A: 仕訳日記帳 (double-entry).
14
+ *
15
+ * Logic:
16
+ * - Uses 借方金額 as amount (expense side). If 0, uses 貸方金額 (income side).
17
+ * - Combines 摘要 + 仕訳メモ for the memo field.
18
+ * - Skips rows where 識別フラグ indicates non-transaction lines (headers, totals).
19
+ */
20
+ private parseFullRow;
21
+ /**
22
+ * Parse Format B: 簡易帳簿 (single-entry).
23
+ */
24
+ private parseSimpleRow;
25
+ /**
26
+ * Parse date from various 弥生 formats:
27
+ * - "2026/05/01" (Western calendar)
28
+ * - "2026-05-01" (ISO)
29
+ * - "R08/05/01" (和暦 令和)
30
+ * - "H28/05/01" (和暦 平成)
31
+ * Returns ISO format "YYYY-MM-DD" or null.
32
+ */
33
+ private parseDate;
34
+ /**
35
+ * Parse amount string, removing commas and handling negative values.
36
+ * "12,000" → 12000, "-3,000" → 3000 (absolute value for classification).
37
+ */
38
+ private parseAmount;
39
+ /**
40
+ * Try to extract partner name from 弥生 Format A.
41
+ * Format A doesn't have a dedicated partner column, but some users
42
+ * put the partner name in 借方補助科目 or 貸方補助科目.
43
+ */
44
+ private extractPartner;
45
+ }
46
+ //# sourceMappingURL=yayoi-adapter.d.ts.map