@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,38 @@
|
|
|
1
|
+
import { ClassificationResult, ExclusionResult } from '../classifier/types.js';
|
|
2
|
+
import { RoutingDecision } from './types.js';
|
|
3
|
+
export interface RoutingContext {
|
|
4
|
+
amount: number;
|
|
5
|
+
partner_name?: string;
|
|
6
|
+
is_new_partner: boolean;
|
|
7
|
+
date: string;
|
|
8
|
+
}
|
|
9
|
+
export interface RoutingConfig {
|
|
10
|
+
/** Transactions above this amount → human_review regardless of confidence. Default: 1,000,000 JPY */
|
|
11
|
+
high_amount_threshold: number;
|
|
12
|
+
/** Days of month considered "monthly close period". Default: [1, 2, 3, 4, 5] */
|
|
13
|
+
monthly_close_days: number[];
|
|
14
|
+
/** Whether to enforce monthly close override. Default: true */
|
|
15
|
+
monthly_close_override: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare class ConfidenceRouter {
|
|
18
|
+
private config;
|
|
19
|
+
constructor(config?: Partial<RoutingConfig>);
|
|
20
|
+
/**
|
|
21
|
+
* Route a transaction to an action based on classification, exclusion, and business rules.
|
|
22
|
+
*
|
|
23
|
+
* @param exclusion - Result from ExclusionChecker.check()
|
|
24
|
+
* @param classification - Result from TwoStageClassifier.classify() (null if exclusion hit)
|
|
25
|
+
* @param context - Business context: amount, partner info, date
|
|
26
|
+
* @returns RoutingDecision with action, reasons, and flags
|
|
27
|
+
*/
|
|
28
|
+
route(exclusion: ExclusionResult, classification: (ClassificationResult & {
|
|
29
|
+
stage?: 1 | 2 | 'unclassified';
|
|
30
|
+
}) | null, context: RoutingContext): RoutingDecision;
|
|
31
|
+
getConfig(): Readonly<RoutingConfig>;
|
|
32
|
+
/**
|
|
33
|
+
* Extract day-of-month from YYYY-MM-DD string without timezone ambiguity.
|
|
34
|
+
* Avoids new Date() UTC/local issues by parsing the string directly.
|
|
35
|
+
*/
|
|
36
|
+
private extractDay;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=confidence-router.d.ts.map
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// Confidence-based routing engine for the nightly batch pipeline.
|
|
2
|
+
//
|
|
3
|
+
// Implements the decision matrix from CLAUDE.md:
|
|
4
|
+
//
|
|
5
|
+
// Priority 1 (hard stops — always human_review):
|
|
6
|
+
// - Stage 0 excluded
|
|
7
|
+
// - Unclassified (no category)
|
|
8
|
+
// - Amount > 1,000,000 JPY
|
|
9
|
+
// - New partner (取引先マスタ未登録)
|
|
10
|
+
// - Monthly close period (1-5日)
|
|
11
|
+
//
|
|
12
|
+
// Priority 2 (confidence-based):
|
|
13
|
+
// - high → auto_register (freee に自動仕訳)
|
|
14
|
+
// - medium → auto_register_with_log (自動仕訳 + 確認queue mirror)
|
|
15
|
+
// - low → human_review (確認queue のみ)
|
|
16
|
+
//
|
|
17
|
+
// Practitioner playbook reference:
|
|
18
|
+
// "過去 3 ヶ月以内に同じ取引先 + 同じ金額 ± 5% で同じ category 分類実績あり → AI 自動 OK"
|
|
19
|
+
// "keyword 辞書 high confidence match → AI 自動 OK"
|
|
20
|
+
// "100 万円超 → 税理士確認必須"
|
|
21
|
+
// "新規取引先 → 税理士確認必須"
|
|
22
|
+
const DEFAULT_CONFIG = {
|
|
23
|
+
high_amount_threshold: 1_000_000,
|
|
24
|
+
monthly_close_days: [1, 2, 3, 4, 5],
|
|
25
|
+
monthly_close_override: true,
|
|
26
|
+
};
|
|
27
|
+
// ============================================================
|
|
28
|
+
// Router
|
|
29
|
+
// ============================================================
|
|
30
|
+
export class ConfidenceRouter {
|
|
31
|
+
config;
|
|
32
|
+
constructor(config = {}) {
|
|
33
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Route a transaction to an action based on classification, exclusion, and business rules.
|
|
37
|
+
*
|
|
38
|
+
* @param exclusion - Result from ExclusionChecker.check()
|
|
39
|
+
* @param classification - Result from TwoStageClassifier.classify() (null if exclusion hit)
|
|
40
|
+
* @param context - Business context: amount, partner info, date
|
|
41
|
+
* @returns RoutingDecision with action, reasons, and flags
|
|
42
|
+
*/
|
|
43
|
+
route(exclusion, classification, context) {
|
|
44
|
+
const flags = [];
|
|
45
|
+
const reasons = [];
|
|
46
|
+
// ── Priority 1: Hard-stop checks (force human_review) ──────────
|
|
47
|
+
// 1. Stage 0 exclusion — 7 除外 rule に該当 = 絶対に自動 register しない
|
|
48
|
+
if (exclusion.excluded) {
|
|
49
|
+
flags.push('excluded');
|
|
50
|
+
reasons.push(`Stage 0 除外: ${exclusion.rule_name_ja || exclusion.rule_id}`
|
|
51
|
+
+ (exclusion.reason ? ` — ${exclusion.reason}` : ''));
|
|
52
|
+
return { action: 'human_review', reasons, flags };
|
|
53
|
+
}
|
|
54
|
+
// 2. Unclassified — keyword + AI ともにマッチせず
|
|
55
|
+
if (!classification || !classification.classified) {
|
|
56
|
+
flags.push('unclassified');
|
|
57
|
+
reasons.push('分類不能: keyword辞書・AI fallback ともにマッチせず、人間判断が必要');
|
|
58
|
+
return { action: 'human_review', reasons, flags };
|
|
59
|
+
}
|
|
60
|
+
// 3-5: Accumulate flags but don't return yet (multiple can apply)
|
|
61
|
+
// 3. High amount (> 1,000,000 JPY)
|
|
62
|
+
if (context.amount > this.config.high_amount_threshold) {
|
|
63
|
+
flags.push('high_amount');
|
|
64
|
+
reasons.push(`高額取引: ¥${context.amount.toLocaleString()}`
|
|
65
|
+
+ ` > ¥${this.config.high_amount_threshold.toLocaleString()} — 要確認`);
|
|
66
|
+
}
|
|
67
|
+
// 4. New partner — 取引先マスタ未登録
|
|
68
|
+
if (context.is_new_partner && context.partner_name) {
|
|
69
|
+
flags.push('new_partner');
|
|
70
|
+
reasons.push(`新規取引先「${context.partner_name}」— 取引先マスタ未登録`);
|
|
71
|
+
}
|
|
72
|
+
// 5. Monthly close period (月初 1-5 日)
|
|
73
|
+
if (this.config.monthly_close_override) {
|
|
74
|
+
const day = this.extractDay(context.date);
|
|
75
|
+
if (day > 0 && this.config.monthly_close_days.includes(day)) {
|
|
76
|
+
flags.push('monthly_close_period');
|
|
77
|
+
reasons.push(`月次決算期間 (${day}日) — 安全側で人間確認`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Any hard-stop flag → force human_review regardless of confidence
|
|
81
|
+
const hardStopFlags = ['high_amount', 'new_partner', 'monthly_close_period'];
|
|
82
|
+
if (flags.some(f => hardStopFlags.includes(f))) {
|
|
83
|
+
// Still report the classification result in reasons for context
|
|
84
|
+
if (classification.category_name_ja) {
|
|
85
|
+
reasons.push(`(参考) 分類結果: ${classification.category_name_ja}`
|
|
86
|
+
+ ` — confidence: ${classification.confidence}`);
|
|
87
|
+
}
|
|
88
|
+
return { action: 'human_review', reasons, flags };
|
|
89
|
+
}
|
|
90
|
+
// ── Priority 2: Confidence-based routing ───────────────────────
|
|
91
|
+
switch (classification.confidence) {
|
|
92
|
+
case 'high':
|
|
93
|
+
reasons.push(`高信頼度: ${classification.category_name_ja}`
|
|
94
|
+
+ (classification.matched_keyword ? ` (keyword: "${classification.matched_keyword}")` : ''));
|
|
95
|
+
return { action: 'auto_register', reasons, flags };
|
|
96
|
+
case 'medium':
|
|
97
|
+
flags.push('medium_confidence');
|
|
98
|
+
reasons.push(`中信頼度: ${classification.category_name_ja}`
|
|
99
|
+
+ ` — 自動登録 + 確認キューに mirror`);
|
|
100
|
+
return { action: 'auto_register_with_log', reasons, flags };
|
|
101
|
+
case 'low':
|
|
102
|
+
flags.push('low_confidence');
|
|
103
|
+
reasons.push(`低信頼度: ${classification.category_name_ja}`
|
|
104
|
+
+ ` — AI の確信度が低いため人間判断が必要`);
|
|
105
|
+
return { action: 'human_review', reasons, flags };
|
|
106
|
+
default:
|
|
107
|
+
// 'none' or unexpected value
|
|
108
|
+
flags.push('unclassified');
|
|
109
|
+
reasons.push('信頼度不明 — 人間判断が必要');
|
|
110
|
+
return { action: 'human_review', reasons, flags };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
getConfig() {
|
|
114
|
+
return this.config;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Extract day-of-month from YYYY-MM-DD string without timezone ambiguity.
|
|
118
|
+
* Avoids new Date() UTC/local issues by parsing the string directly.
|
|
119
|
+
*/
|
|
120
|
+
extractDay(dateStr) {
|
|
121
|
+
const parts = dateStr.split('-');
|
|
122
|
+
if (parts.length === 3) {
|
|
123
|
+
const day = parseInt(parts[2], 10);
|
|
124
|
+
return isNaN(day) ? 0 : day;
|
|
125
|
+
}
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=confidence-router.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { FreeeConnector } from '../connectors/freee.js';
|
|
2
|
+
import { TwoStageClassifier } from '../classifier/two-stage-classifier.js';
|
|
3
|
+
import { ExclusionChecker } from '../exclusion/exclusion-checker.js';
|
|
4
|
+
import { CockpitMemory } from '../memory/cockpit-memory.js';
|
|
5
|
+
import { NightlyRunResult } from './types.js';
|
|
6
|
+
export interface NightlyPipelineConfig {
|
|
7
|
+
/** Always true in Phase 1.A — write-back pending Phase 1.B */
|
|
8
|
+
dry_run: boolean;
|
|
9
|
+
/** Max companies processed in parallel. Default: 3 (freee rate limit safe) */
|
|
10
|
+
concurrency: number;
|
|
11
|
+
/** Max deals fetched per company. Default: 100 */
|
|
12
|
+
deals_per_company: number;
|
|
13
|
+
/** Override: only process these company IDs (default: all accessible) */
|
|
14
|
+
company_ids?: number[];
|
|
15
|
+
/** Optional date range filter (YYYY-MM-DD) */
|
|
16
|
+
period_start?: string;
|
|
17
|
+
period_end?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class NightlyPipeline {
|
|
20
|
+
private connector;
|
|
21
|
+
private classifier;
|
|
22
|
+
private exclusion;
|
|
23
|
+
private router;
|
|
24
|
+
private memory;
|
|
25
|
+
private config;
|
|
26
|
+
constructor(connector: FreeeConnector, classifier: TwoStageClassifier, exclusion: ExclusionChecker, config?: Partial<NightlyPipelineConfig>, memory?: CockpitMemory);
|
|
27
|
+
/**
|
|
28
|
+
* Run the full Full nightly batch.
|
|
29
|
+
*
|
|
30
|
+
* Pipeline:
|
|
31
|
+
* 1. List all accessible companies (or use override list)
|
|
32
|
+
* 2. For each company (concurrency-limited parallel):
|
|
33
|
+
* a. Fetch unprocessed deals (status = 'unsettled')
|
|
34
|
+
* b. Fetch existing partners (for new-partner detection)
|
|
35
|
+
* c. For each deal: exclusion → classify → route
|
|
36
|
+
* d. Aggregate per-company results
|
|
37
|
+
* 3. Aggregate all company results
|
|
38
|
+
* 4. Generate Batch summary (Slack-ready format)
|
|
39
|
+
*/
|
|
40
|
+
run(): Promise<NightlyRunResult>;
|
|
41
|
+
private processCompany;
|
|
42
|
+
private processDeal;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=nightly-pipeline.d.ts.map
|