@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.
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/data/exclusion-rules/README.md +104 -0
- package/data/exclusion-rules/jp-tax-baseline-v1.json +185 -0
- package/data/exclusion-rules-schema.json +109 -0
- package/data/keyword-dict/README.md +91 -0
- package/data/keyword-dict/jp-tax-baseline-v1.json +398 -0
- package/data/keyword-dict-schema.json +117 -0
- package/data/tax-rules/jp-tax-rules-v1.json +170 -0
- package/dist/adapters/csv-parser.d.ts +11 -0
- package/dist/adapters/csv-parser.js +133 -0
- package/dist/adapters/freee-csv-adapter.d.ts +14 -0
- package/dist/adapters/freee-csv-adapter.js +67 -0
- package/dist/adapters/generic-adapter.d.ts +20 -0
- package/dist/adapters/generic-adapter.js +73 -0
- package/dist/adapters/index.d.ts +23 -0
- package/dist/adapters/index.js +386 -0
- package/dist/adapters/types.d.ts +111 -0
- package/dist/adapters/types.js +9 -0
- package/dist/adapters/yayoi-adapter.d.ts +46 -0
- package/dist/adapters/yayoi-adapter.js +181 -0
- package/dist/bin/freee-doctor.d.ts +3 -0
- package/dist/bin/freee-doctor.js +15 -0
- package/dist/classifier/claude-classifier.d.ts +24 -0
- package/dist/classifier/claude-classifier.js +154 -0
- package/dist/classifier/keyword-classifier.d.ts +22 -0
- package/dist/classifier/keyword-classifier.js +124 -0
- package/dist/classifier/keyword-match.d.ts +21 -0
- package/dist/classifier/keyword-match.js +57 -0
- package/dist/classifier/normalize.d.ts +3 -0
- package/dist/classifier/normalize.js +27 -0
- package/dist/classifier/two-stage-classifier.d.ts +21 -0
- package/dist/classifier/two-stage-classifier.js +51 -0
- package/dist/classifier/types.d.ts +31 -0
- package/dist/classifier/types.js +3 -0
- package/dist/connectors/freee.d.ts +115 -0
- package/dist/connectors/freee.js +177 -0
- package/dist/exclusion/exclusion-checker.d.ts +10 -0
- package/dist/exclusion/exclusion-checker.js +162 -0
- package/dist/freee-doctor.d.ts +26 -0
- package/dist/freee-doctor.js +82 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +656 -0
- package/dist/memory/cockpit-memory.d.ts +73 -0
- package/dist/memory/cockpit-memory.js +473 -0
- package/dist/memory/types.d.ts +114 -0
- package/dist/memory/types.js +11 -0
- package/dist/pipeline/confidence-router.d.ts +38 -0
- package/dist/pipeline/confidence-router.js +129 -0
- package/dist/pipeline/nightly-pipeline.d.ts +44 -0
- package/dist/pipeline/nightly-pipeline.js +497 -0
- package/dist/pipeline/types.d.ts +84 -0
- package/dist/pipeline/types.js +12 -0
- package/dist/reports/monthly-report.d.ts +64 -0
- package/dist/reports/monthly-report.js +230 -0
- package/dist/secrets.d.ts +14 -0
- package/dist/secrets.js +86 -0
- package/dist/tax-rules/tax-rule-engine.d.ts +103 -0
- package/dist/tax-rules/tax-rule-engine.js +449 -0
- package/dist/tax-rules/types.d.ts +103 -0
- package/dist/tax-rules/types.js +7 -0
- package/package.json +74 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.1.0",
|
|
3
|
+
"locale": "ja-JP",
|
|
4
|
+
|
|
5
|
+
"overseas_saas_providers": [
|
|
6
|
+
"aws", "amazon web services",
|
|
7
|
+
"google cloud", "gcp",
|
|
8
|
+
"azure", "microsoft azure",
|
|
9
|
+
"openai", "anthropic", "claude",
|
|
10
|
+
"cursor",
|
|
11
|
+
"github", "gitlab",
|
|
12
|
+
"cloudflare",
|
|
13
|
+
"vercel", "netlify", "heroku",
|
|
14
|
+
"digitalocean", "linode",
|
|
15
|
+
"stripe",
|
|
16
|
+
"figma", "adobe", "canva",
|
|
17
|
+
"slack", "discord",
|
|
18
|
+
"zoom",
|
|
19
|
+
"notion", "asana", "trello", "jira",
|
|
20
|
+
"dropbox", "datadog", "sentry",
|
|
21
|
+
"twilio", "sendgrid", "mailgun",
|
|
22
|
+
"hubspot", "intercom",
|
|
23
|
+
"1password", "lastpass",
|
|
24
|
+
"postman", "linear"
|
|
25
|
+
],
|
|
26
|
+
|
|
27
|
+
"overseas_ad_platforms": [
|
|
28
|
+
"google ads", "google adwords", "adwords",
|
|
29
|
+
"meta", "facebook ads", "instagram ads",
|
|
30
|
+
"twitter ads", "x ads",
|
|
31
|
+
"linkedin ads",
|
|
32
|
+
"tiktok ads",
|
|
33
|
+
"youtube ads",
|
|
34
|
+
"pinterest ads",
|
|
35
|
+
"reddit ads",
|
|
36
|
+
"apple search ads"
|
|
37
|
+
],
|
|
38
|
+
|
|
39
|
+
"domestic_telecom_providers": [
|
|
40
|
+
"docomo", "ドコモ",
|
|
41
|
+
"au", "kddi",
|
|
42
|
+
"softbank", "ソフトバンク",
|
|
43
|
+
"ntt", "ntt東日本", "ntt西日本", "nttコミュニケーションズ",
|
|
44
|
+
"さくらインターネット", "さくら",
|
|
45
|
+
"ocn",
|
|
46
|
+
"フレッツ", "flets",
|
|
47
|
+
"biglobe", "ビッグローブ",
|
|
48
|
+
"so-net", "ソネット",
|
|
49
|
+
"お名前.com", "onamae",
|
|
50
|
+
"iij", "インターネットイニシアティブ",
|
|
51
|
+
"楽天モバイル", "rakuten mobile",
|
|
52
|
+
"uqモバイル", "uq mobile",
|
|
53
|
+
"ワイモバイル", "y!mobile"
|
|
54
|
+
],
|
|
55
|
+
|
|
56
|
+
"domestic_ad_platforms": [
|
|
57
|
+
"yahoo!広告", "yahoo広告", "yda", "ydn",
|
|
58
|
+
"line ads", "line広告",
|
|
59
|
+
"smartnews広告", "gunosy広告",
|
|
60
|
+
"indeed"
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
"jct_indicator_keywords": [
|
|
64
|
+
"jct",
|
|
65
|
+
"消費税",
|
|
66
|
+
"インボイス",
|
|
67
|
+
"invoice",
|
|
68
|
+
"適格請求書"
|
|
69
|
+
],
|
|
70
|
+
|
|
71
|
+
"asset_capitalization": {
|
|
72
|
+
"expense_max": 99999,
|
|
73
|
+
"lump_sum_3yr_max": 199999,
|
|
74
|
+
"sme_immediate_max": 299999
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
"withholding_tax": {
|
|
78
|
+
"bracket_1_ceiling": 1000000,
|
|
79
|
+
"rate_bracket_1": 0.1021,
|
|
80
|
+
"rate_bracket_2": 0.2042
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
"consumption_tax": {
|
|
84
|
+
"standard_rate": 10,
|
|
85
|
+
"reduced_rate": 8,
|
|
86
|
+
"exempt_rate": 0,
|
|
87
|
+
|
|
88
|
+
"newspaper_keywords": [
|
|
89
|
+
"新聞", "日経", "朝日", "読売", "毎日", "産経",
|
|
90
|
+
"newspaper", "nikkei"
|
|
91
|
+
],
|
|
92
|
+
|
|
93
|
+
"food_beverage_keywords": [
|
|
94
|
+
"弁当", "ケータリング", "テイクアウト", "持ち帰り",
|
|
95
|
+
"出前", "デリバリー", "uber eats", "ubereats",
|
|
96
|
+
"出前館", "wolt", "menu",
|
|
97
|
+
"コンビニ", "スーパー"
|
|
98
|
+
],
|
|
99
|
+
|
|
100
|
+
"takeout_delivery_keywords": [
|
|
101
|
+
"テイクアウト", "持ち帰り", "出前", "デリバリー",
|
|
102
|
+
"uber eats", "ubereats", "出前館", "wolt",
|
|
103
|
+
"menu", "弁当", "仕出し"
|
|
104
|
+
],
|
|
105
|
+
|
|
106
|
+
"food_purchase_keywords": [
|
|
107
|
+
"コンビニ", "セブンイレブン", "ローソン", "ファミマ",
|
|
108
|
+
"スーパー", "イオン", "西友", "ライフ",
|
|
109
|
+
"成城石井", "紀ノ国屋", "オーケー",
|
|
110
|
+
"食品", "飲料", "お茶", "コーヒー豆",
|
|
111
|
+
"菓子", "お菓子", "スイーツ"
|
|
112
|
+
],
|
|
113
|
+
|
|
114
|
+
"catering_with_service_keywords": [
|
|
115
|
+
"ケータリング", "出張料理", "パーティー",
|
|
116
|
+
"配膳", "サービス料"
|
|
117
|
+
],
|
|
118
|
+
|
|
119
|
+
"residential_rent_keywords": [
|
|
120
|
+
"住居", "住宅", "マンション", "アパート",
|
|
121
|
+
"居住用", "住居用", "自宅", "社宅",
|
|
122
|
+
"residential"
|
|
123
|
+
],
|
|
124
|
+
|
|
125
|
+
"non_taxable_categories": [
|
|
126
|
+
"salary",
|
|
127
|
+
"membership_fee",
|
|
128
|
+
"insurance",
|
|
129
|
+
"taxes_dues",
|
|
130
|
+
"donation",
|
|
131
|
+
"condolence"
|
|
132
|
+
],
|
|
133
|
+
|
|
134
|
+
"non_taxable_reasons": {
|
|
135
|
+
"salary": "給与 — 消費税対象外",
|
|
136
|
+
"membership_fee": "諸会費 — 原則として消費税対象外",
|
|
137
|
+
"insurance": "保険料 — 消費税非課税(保険業法に基づく保険契約)",
|
|
138
|
+
"taxes_dues": "租税公課 — 消費税不課税(国・地方への公的支出)",
|
|
139
|
+
"donation": "寄付金 — 消費税不課税(対価性なし)",
|
|
140
|
+
"condolence": "慶弔費 — 消費税不課税(対価性なし)"
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
"invoice_system": {
|
|
145
|
+
"registration_prefix": "T",
|
|
146
|
+
"registration_digits": 13,
|
|
147
|
+
"transitional_periods": [
|
|
148
|
+
{
|
|
149
|
+
"start": "2023-10-01",
|
|
150
|
+
"end": "2026-09-30",
|
|
151
|
+
"deduction_rate": 80,
|
|
152
|
+
"label": "経過措置第1期 — 仕入税額控除80%"
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"start": "2026-10-01",
|
|
156
|
+
"end": "2029-09-30",
|
|
157
|
+
"deduction_rate": 50,
|
|
158
|
+
"label": "経過措置第2期 — 仕入税額控除50%"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"start": "2029-10-01",
|
|
162
|
+
"end": "9999-12-31",
|
|
163
|
+
"deduction_rate": 0,
|
|
164
|
+
"label": "経過措置終了 — 仕入税額控除不可"
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"small_business_threshold": 10000,
|
|
168
|
+
"small_business_note": "1万円未満の課税仕入れは、少額特例により帳簿のみで仕入税額控除可(基準期間の課税売上高1億円以下の事業者、2029年9月まで)"
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse CSV text into array of header-keyed row objects.
|
|
3
|
+
*
|
|
4
|
+
* @param csvText - Raw CSV text (UTF-8).
|
|
5
|
+
* @returns { headers, rows } where each row is Record<headerName, value>.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseCsv(csvText: string): {
|
|
8
|
+
headers: string[];
|
|
9
|
+
rows: Record<string, string>[];
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=csv-parser.d.ts.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Simple CSV parser for Japanese accounting CSV files.
|
|
2
|
+
//
|
|
3
|
+
// Handles:
|
|
4
|
+
// - UTF-8 BOM (common in Japanese CSV exports)
|
|
5
|
+
// - Quoted fields with embedded commas and newlines
|
|
6
|
+
// - Empty lines
|
|
7
|
+
// - CRLF / LF line endings
|
|
8
|
+
//
|
|
9
|
+
// Note: Shift-JIS support is NOT included. Users must export as UTF-8
|
|
10
|
+
// (弥生: エクスポート → 文字コード: UTF-8 を選択).
|
|
11
|
+
/**
|
|
12
|
+
* Parse CSV text into array of header-keyed row objects.
|
|
13
|
+
*
|
|
14
|
+
* @param csvText - Raw CSV text (UTF-8).
|
|
15
|
+
* @returns { headers, rows } where each row is Record<headerName, value>.
|
|
16
|
+
*/
|
|
17
|
+
export function parseCsv(csvText) {
|
|
18
|
+
// Strip BOM
|
|
19
|
+
let text = csvText;
|
|
20
|
+
if (text.charCodeAt(0) === 0xFEFF) {
|
|
21
|
+
text = text.slice(1);
|
|
22
|
+
}
|
|
23
|
+
const lines = splitCsvLines(text);
|
|
24
|
+
if (lines.length === 0) {
|
|
25
|
+
return { headers: [], rows: [] };
|
|
26
|
+
}
|
|
27
|
+
const headers = parseCsvLine(lines[0]).map(h => h.trim());
|
|
28
|
+
const rows = [];
|
|
29
|
+
for (let i = 1; i < lines.length; i++) {
|
|
30
|
+
const line = lines[i].trim();
|
|
31
|
+
if (!line)
|
|
32
|
+
continue; // skip empty lines
|
|
33
|
+
const values = parseCsvLine(line);
|
|
34
|
+
const row = {};
|
|
35
|
+
for (let j = 0; j < headers.length; j++) {
|
|
36
|
+
row[headers[j]] = (values[j] ?? '').trim();
|
|
37
|
+
}
|
|
38
|
+
rows.push(row);
|
|
39
|
+
}
|
|
40
|
+
return { headers, rows };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Split CSV text into lines, respecting quoted fields that span multiple lines.
|
|
44
|
+
*/
|
|
45
|
+
function splitCsvLines(text) {
|
|
46
|
+
const lines = [];
|
|
47
|
+
let current = '';
|
|
48
|
+
let inQuote = false;
|
|
49
|
+
for (let i = 0; i < text.length; i++) {
|
|
50
|
+
const ch = text[i];
|
|
51
|
+
if (ch === '"') {
|
|
52
|
+
// Check for escaped quote ("")
|
|
53
|
+
if (inQuote && i + 1 < text.length && text[i + 1] === '"') {
|
|
54
|
+
current += '""';
|
|
55
|
+
i++; // skip next quote
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
inQuote = !inQuote;
|
|
59
|
+
current += ch;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if ((ch === '\n' || ch === '\r') && !inQuote) {
|
|
63
|
+
// Line break outside quotes = end of line
|
|
64
|
+
if (ch === '\r' && i + 1 < text.length && text[i + 1] === '\n') {
|
|
65
|
+
i++; // skip \n in \r\n
|
|
66
|
+
}
|
|
67
|
+
lines.push(current);
|
|
68
|
+
current = '';
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
current += ch;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Last line (no trailing newline)
|
|
75
|
+
if (current) {
|
|
76
|
+
lines.push(current);
|
|
77
|
+
}
|
|
78
|
+
return lines;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Parse a single CSV line into field values.
|
|
82
|
+
* Handles quoted fields and escaped double-quotes.
|
|
83
|
+
*/
|
|
84
|
+
function parseCsvLine(line) {
|
|
85
|
+
const fields = [];
|
|
86
|
+
let i = 0;
|
|
87
|
+
while (i <= line.length) {
|
|
88
|
+
if (i === line.length) {
|
|
89
|
+
// Trailing comma → empty field
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
if (line[i] === '"') {
|
|
93
|
+
// Quoted field
|
|
94
|
+
let value = '';
|
|
95
|
+
i++; // skip opening quote
|
|
96
|
+
while (i < line.length) {
|
|
97
|
+
if (line[i] === '"') {
|
|
98
|
+
if (i + 1 < line.length && line[i + 1] === '"') {
|
|
99
|
+
value += '"';
|
|
100
|
+
i += 2;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
i++; // skip closing quote
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
value += line[i];
|
|
109
|
+
i++;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
fields.push(value);
|
|
113
|
+
// Skip comma after quoted field
|
|
114
|
+
if (i < line.length && line[i] === ',') {
|
|
115
|
+
i++;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// Unquoted field
|
|
120
|
+
const nextComma = line.indexOf(',', i);
|
|
121
|
+
if (nextComma === -1) {
|
|
122
|
+
fields.push(line.slice(i));
|
|
123
|
+
i = line.length;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
fields.push(line.slice(i, nextComma));
|
|
127
|
+
i = nextComma + 1;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return fields;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=csv-parser.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Transaction } from '../classifier/types.js';
|
|
2
|
+
import { CsvAdapter, CsvSource } from './types.js';
|
|
3
|
+
export declare class FreeeCsvAdapter implements CsvAdapter {
|
|
4
|
+
readonly source: CsvSource;
|
|
5
|
+
readonly label = "freee CSV \u30A8\u30AF\u30B9\u30DD\u30FC\u30C8";
|
|
6
|
+
detectFormat(headers: string[]): boolean;
|
|
7
|
+
parseRow(row: Record<string, string>, rowNumber: number): {
|
|
8
|
+
transaction: Transaction | null;
|
|
9
|
+
skip_reason: string | null;
|
|
10
|
+
};
|
|
11
|
+
private parseDate;
|
|
12
|
+
private parseAmount;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=freee-csv-adapter.d.ts.map
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// freee CSV export adapter.
|
|
2
|
+
//
|
|
3
|
+
// Parses the freee 取引データ CSV export format.
|
|
4
|
+
//
|
|
5
|
+
// freee export path: 取引 → 一覧 → CSVエクスポート
|
|
6
|
+
// Encoding: UTF-8 (freee defaults to UTF-8)
|
|
7
|
+
//
|
|
8
|
+
// Typical columns:
|
|
9
|
+
// 収支区分, 取引日, 決済日, 取引先, 勘定科目, 税区分, 金額, 税額,
|
|
10
|
+
// 備考, 品目, 部門, メモタグ, セグメント1, セグメント2, セグメント3
|
|
11
|
+
const FREEE_REQUIRED = ['取引日', '金額'];
|
|
12
|
+
const FREEE_DETECTION = ['勘定科目', '税区分', '収支区分'];
|
|
13
|
+
export class FreeeCsvAdapter {
|
|
14
|
+
source = 'freee_export';
|
|
15
|
+
label = 'freee CSV エクスポート';
|
|
16
|
+
detectFormat(headers) {
|
|
17
|
+
const h = headers.map(s => s.trim());
|
|
18
|
+
// Must have 取引日 + 金額, and at least one freee-specific column
|
|
19
|
+
return (FREEE_REQUIRED.every(req => h.includes(req)) &&
|
|
20
|
+
FREEE_DETECTION.some(det => h.includes(det)));
|
|
21
|
+
}
|
|
22
|
+
parseRow(row, rowNumber) {
|
|
23
|
+
// Parse date
|
|
24
|
+
const dateStr = row['取引日']?.trim();
|
|
25
|
+
if (!dateStr) {
|
|
26
|
+
return { transaction: null, skip_reason: '取引日が空' };
|
|
27
|
+
}
|
|
28
|
+
const date = this.parseDate(dateStr);
|
|
29
|
+
if (!date) {
|
|
30
|
+
return { transaction: null, skip_reason: `日付パース失敗: "${dateStr}"` };
|
|
31
|
+
}
|
|
32
|
+
// Parse amount
|
|
33
|
+
const amountStr = row['金額']?.trim();
|
|
34
|
+
const amount = this.parseAmount(amountStr);
|
|
35
|
+
if (!amount || amount === 0) {
|
|
36
|
+
return { transaction: null, skip_reason: '金額が0またはパース失敗' };
|
|
37
|
+
}
|
|
38
|
+
// Build memo: 備考 or 勘定科目 or fallback
|
|
39
|
+
const bikou = row['備考']?.trim() || '';
|
|
40
|
+
const kanjou = row['勘定科目']?.trim() || '';
|
|
41
|
+
const memoTag = row['メモタグ']?.trim() || '';
|
|
42
|
+
const memo = bikou || kanjou || memoTag || `取引 ${date}`;
|
|
43
|
+
// Partner name
|
|
44
|
+
const partner_name = row['取引先']?.trim() || undefined;
|
|
45
|
+
return {
|
|
46
|
+
transaction: { amount, memo, date, partner_name },
|
|
47
|
+
skip_reason: null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
parseDate(dateStr) {
|
|
51
|
+
// freee uses "YYYY-MM-DD" or "YYYY/MM/DD"
|
|
52
|
+
const match = dateStr.match(/^(\d{4})[\/\-](\d{1,2})[\/\-](\d{1,2})$/);
|
|
53
|
+
if (match) {
|
|
54
|
+
const [, y, m, d] = match;
|
|
55
|
+
return `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
parseAmount(amountStr) {
|
|
60
|
+
if (!amountStr)
|
|
61
|
+
return 0;
|
|
62
|
+
const cleaned = amountStr.replace(/[,、¥¥\s]/g, '');
|
|
63
|
+
const num = parseInt(cleaned, 10);
|
|
64
|
+
return isNaN(num) ? 0 : Math.abs(num);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=freee-csv-adapter.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Transaction } from '../classifier/types.js';
|
|
2
|
+
import { CsvAdapter, CsvSource, ColumnMapping } from './types.js';
|
|
3
|
+
export declare class GenericCsvAdapter implements CsvAdapter {
|
|
4
|
+
readonly source: CsvSource;
|
|
5
|
+
readonly label = "\u6C4E\u7528 CSV";
|
|
6
|
+
private mapping;
|
|
7
|
+
constructor(mapping: ColumnMapping);
|
|
8
|
+
/**
|
|
9
|
+
* Generic adapter always returns true for detectFormat
|
|
10
|
+
* (it's the fallback when no other adapter matches).
|
|
11
|
+
*/
|
|
12
|
+
detectFormat(_headers: string[]): boolean;
|
|
13
|
+
parseRow(row: Record<string, string>, rowNumber: number): {
|
|
14
|
+
transaction: Transaction | null;
|
|
15
|
+
skip_reason: string | null;
|
|
16
|
+
};
|
|
17
|
+
private parseDate;
|
|
18
|
+
private parseAmount;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=generic-adapter.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Generic CSV adapter with user-specified column mapping.
|
|
2
|
+
//
|
|
3
|
+
// Used when the CSV doesn't match any known format (弥生/freee/MF).
|
|
4
|
+
// User provides a ColumnMapping to specify which columns contain date, amount, memo.
|
|
5
|
+
export class GenericCsvAdapter {
|
|
6
|
+
source = 'generic';
|
|
7
|
+
label = '汎用 CSV';
|
|
8
|
+
mapping;
|
|
9
|
+
constructor(mapping) {
|
|
10
|
+
this.mapping = mapping;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generic adapter always returns true for detectFormat
|
|
14
|
+
* (it's the fallback when no other adapter matches).
|
|
15
|
+
*/
|
|
16
|
+
detectFormat(_headers) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
parseRow(row, rowNumber) {
|
|
20
|
+
// Date
|
|
21
|
+
const dateStr = row[this.mapping.date]?.trim();
|
|
22
|
+
if (!dateStr) {
|
|
23
|
+
return { transaction: null, skip_reason: `日付列 "${this.mapping.date}" が空` };
|
|
24
|
+
}
|
|
25
|
+
const date = this.parseDate(dateStr);
|
|
26
|
+
if (!date) {
|
|
27
|
+
return { transaction: null, skip_reason: `日付パース失敗: "${dateStr}"` };
|
|
28
|
+
}
|
|
29
|
+
// Amount
|
|
30
|
+
const amountStr = row[this.mapping.amount]?.trim();
|
|
31
|
+
const amount = this.parseAmount(amountStr);
|
|
32
|
+
if (!amount || amount === 0) {
|
|
33
|
+
return { transaction: null, skip_reason: '金額が0またはパース失敗' };
|
|
34
|
+
}
|
|
35
|
+
// Memo
|
|
36
|
+
const memo = row[this.mapping.memo]?.trim() || '';
|
|
37
|
+
if (!memo) {
|
|
38
|
+
return { transaction: null, skip_reason: `摘要列 "${this.mapping.memo}" が空` };
|
|
39
|
+
}
|
|
40
|
+
// Partner (optional)
|
|
41
|
+
const partner_name = this.mapping.partner_name
|
|
42
|
+
? row[this.mapping.partner_name]?.trim() || undefined
|
|
43
|
+
: undefined;
|
|
44
|
+
return {
|
|
45
|
+
transaction: { amount, memo, date, partner_name },
|
|
46
|
+
skip_reason: null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
parseDate(dateStr) {
|
|
50
|
+
// Try common date formats
|
|
51
|
+
// "YYYY/MM/DD", "YYYY-MM-DD", "YYYY.MM.DD"
|
|
52
|
+
const match = dateStr.match(/^(\d{4})[\/\-\.](\d{1,2})[\/\-\.](\d{1,2})$/);
|
|
53
|
+
if (match) {
|
|
54
|
+
const [, y, m, d] = match;
|
|
55
|
+
return `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`;
|
|
56
|
+
}
|
|
57
|
+
// "MM/DD/YYYY" (US format)
|
|
58
|
+
const usMatch = dateStr.match(/^(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})$/);
|
|
59
|
+
if (usMatch) {
|
|
60
|
+
const [, m, d, y] = usMatch;
|
|
61
|
+
return `${y}-${m.padStart(2, '0')}-${d.padStart(2, '0')}`;
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
parseAmount(amountStr) {
|
|
66
|
+
if (!amountStr)
|
|
67
|
+
return 0;
|
|
68
|
+
const cleaned = amountStr.replace(/[,、¥¥$\s]/g, '');
|
|
69
|
+
const num = parseFloat(cleaned);
|
|
70
|
+
return isNaN(num) ? 0 : Math.abs(Math.round(num));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=generic-adapter.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CsvSource, ColumnMapping, ImportResult } from './types.js';
|
|
2
|
+
import { TwoStageClassifier } from '../classifier/two-stage-classifier.js';
|
|
3
|
+
import { ExclusionChecker } from '../exclusion/exclusion-checker.js';
|
|
4
|
+
import { ConfidenceRouter } from '../pipeline/confidence-router.js';
|
|
5
|
+
import { CockpitMemory } from '../memory/cockpit-memory.js';
|
|
6
|
+
import { TaxRuleEngine } from '../tax-rules/tax-rule-engine.js';
|
|
7
|
+
/**
|
|
8
|
+
* Import CSV and run through the full classification pipeline.
|
|
9
|
+
*
|
|
10
|
+
* @param csvText - Raw CSV text (UTF-8).
|
|
11
|
+
* @param classifier - TwoStageClassifier instance.
|
|
12
|
+
* @param exclusion - ExclusionChecker instance.
|
|
13
|
+
* @param router - ConfidenceRouter instance.
|
|
14
|
+
* @param opts.source - Force a specific source format (skip auto-detection).
|
|
15
|
+
* @param opts.mapping - Column mapping for generic CSV.
|
|
16
|
+
*/
|
|
17
|
+
export declare function importCsv(csvText: string, classifier: TwoStageClassifier, exclusion: ExclusionChecker, router: ConfidenceRouter, opts?: {
|
|
18
|
+
source?: CsvSource;
|
|
19
|
+
mapping?: ColumnMapping;
|
|
20
|
+
memory?: CockpitMemory;
|
|
21
|
+
taxRuleEngine?: TaxRuleEngine;
|
|
22
|
+
}): Promise<ImportResult>;
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|