taiwan-invoice-skill 2.0.0 → 2.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/README.md +131 -0
- package/assets/taiwan-invoice/SKILL.md +452 -0
- package/assets/taiwan-invoice/data/error-codes.csv +41 -0
- package/assets/taiwan-invoice/data/field-mappings.csv +27 -0
- package/assets/taiwan-invoice/data/operations.csv +11 -0
- package/assets/taiwan-invoice/data/providers.csv +4 -0
- package/assets/taiwan-invoice/data/tax-rules.csv +9 -0
- package/assets/taiwan-invoice/data/troubleshooting.csv +17 -0
- package/assets/taiwan-invoice/scripts/__pycache__/core.cpython-312.pyc +0 -0
- package/assets/taiwan-invoice/scripts/core.py +304 -0
- package/assets/taiwan-invoice/scripts/generate-invoice-service.py +642 -128
- package/assets/taiwan-invoice/scripts/recommend.py +340 -0
- package/assets/taiwan-invoice/scripts/search.py +201 -0
- package/assets/templates/base/quick-reference.md +85 -0
- package/assets/templates/platforms/{antigravity.json → agent.json} +6 -3
- package/assets/templates/platforms/claude.json +5 -2
- package/assets/templates/platforms/codebuddy.json +5 -2
- package/assets/templates/platforms/codex.json +5 -2
- package/assets/templates/platforms/continue.json +5 -2
- package/assets/templates/platforms/copilot.json +5 -2
- package/assets/templates/platforms/cursor.json +5 -2
- package/assets/templates/platforms/gemini.json +5 -2
- package/assets/templates/platforms/kiro.json +5 -2
- package/assets/templates/platforms/opencode.json +5 -2
- package/assets/templates/platforms/qoder.json +5 -2
- package/assets/templates/platforms/roocode.json +5 -2
- package/assets/templates/platforms/trae.json +5 -2
- package/assets/templates/platforms/windsurf.json +5 -2
- package/dist/index.js +265 -60
- package/package.json +3 -2
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Taiwan Invoice Skill - 加值中心推薦系統
|
|
4
|
+
基於使用者需求推薦最適合的電子發票加值中心
|
|
5
|
+
|
|
6
|
+
無外部依賴,純 Python 實現
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import csv
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import argparse
|
|
13
|
+
from typing import List, Dict, Any, Optional, Tuple
|
|
14
|
+
|
|
15
|
+
# 取得 data 目錄路徑
|
|
16
|
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
17
|
+
DATA_DIR = os.path.join(os.path.dirname(SCRIPT_DIR), 'data')
|
|
18
|
+
|
|
19
|
+
# 推薦規則定義
|
|
20
|
+
RECOMMENDATION_RULES = {
|
|
21
|
+
# 關鍵字 → (provider, weight, reason)
|
|
22
|
+
'穩定': [('ECPay', 3, '市佔率最高,系統穩定性佳')],
|
|
23
|
+
'市佔': [('ECPay', 3, '台灣電子發票市佔率領先')],
|
|
24
|
+
'文檔': [('ECPay', 2, '提供完整 API 文檔與 SDK')],
|
|
25
|
+
'sdk': [('ECPay', 2, '官方 SDK 支援多種語言')],
|
|
26
|
+
'高交易量': [('ECPay', 3, '適合高交易量電商')],
|
|
27
|
+
'電商': [('ECPay', 2, '電商整合經驗豐富')],
|
|
28
|
+
|
|
29
|
+
'簡單': [('SmilePay', 3, '整合流程最簡單')],
|
|
30
|
+
'快速': [('SmilePay', 3, '最快速完成整合')],
|
|
31
|
+
'小型': [('SmilePay', 2, '適合小型專案')],
|
|
32
|
+
'測試': [('SmilePay', 2, '測試環境設定簡單')],
|
|
33
|
+
'無加密': [('SmilePay', 3, '無需複雜加密流程')],
|
|
34
|
+
'便宜': [('SmilePay', 2, '費用較低')],
|
|
35
|
+
|
|
36
|
+
'api': [('Amego', 3, 'MIG 4.0 最新 API 標準')],
|
|
37
|
+
'設計': [('Amego', 2, 'API 設計優良')],
|
|
38
|
+
'新': [('Amego', 2, '採用最新技術標準')],
|
|
39
|
+
'mig': [('Amego', 3, '完整支援 MIG 4.0 規範')],
|
|
40
|
+
'標準': [('Amego', 2, 'API 設計符合業界標準')],
|
|
41
|
+
|
|
42
|
+
# B2B/B2C 相關
|
|
43
|
+
'b2b': [('ECPay', 1, 'B2B 發票功能完整'), ('Amego', 1, 'B2B 計算清晰')],
|
|
44
|
+
'b2c': [('ECPay', 1, 'B2C 市佔最高'), ('SmilePay', 1, 'B2C 整合簡單')],
|
|
45
|
+
'統編': [('ECPay', 1, 'B2B 統編發票經驗豐富')],
|
|
46
|
+
|
|
47
|
+
# 功能相關
|
|
48
|
+
'列印': [('ECPay', 2, '列印功能完整'), ('SmilePay', 1, '支援列印')],
|
|
49
|
+
'作廢': [('ECPay', 1, '作廢流程完整')],
|
|
50
|
+
'折讓': [('ECPay', 1, '折讓功能完整')],
|
|
51
|
+
'載具': [('ECPay', 1, '載具支援完整'), ('SmilePay', 1, '載具整合簡單')],
|
|
52
|
+
'捐贈': [('ECPay', 1, '捐贈功能完整')],
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# 反模式警告
|
|
56
|
+
ANTI_PATTERNS = {
|
|
57
|
+
'ECPay': [
|
|
58
|
+
('無技術資源', '加密流程較複雜,需要一定技術能力'),
|
|
59
|
+
('極簡整合', '如果只需最簡單整合,SmilePay 可能更適合'),
|
|
60
|
+
],
|
|
61
|
+
'SmilePay': [
|
|
62
|
+
('高交易量', '大型電商建議使用 ECPay 以確保穩定性'),
|
|
63
|
+
('複雜需求', 'API 功能相對基本,複雜需求可能受限'),
|
|
64
|
+
('b2b', 'B2B 發票功能較少文檔'),
|
|
65
|
+
],
|
|
66
|
+
'Amego': [
|
|
67
|
+
('市佔', '市佔率相對較低'),
|
|
68
|
+
('社群', '社群支援與範例相對較少'),
|
|
69
|
+
('穩定', '如果穩定性是首要考量,ECPay 更保險'),
|
|
70
|
+
],
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def load_providers() -> List[Dict[str, str]]:
|
|
75
|
+
"""載入加值中心資料"""
|
|
76
|
+
filepath = os.path.join(DATA_DIR, 'providers.csv')
|
|
77
|
+
if not os.path.exists(filepath):
|
|
78
|
+
return []
|
|
79
|
+
|
|
80
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
81
|
+
reader = csv.DictReader(f)
|
|
82
|
+
return list(reader)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def analyze_requirements(query: str) -> Dict[str, Tuple[int, List[str]]]:
|
|
86
|
+
"""
|
|
87
|
+
分析使用者需求,計算各加值中心分數
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
Dict[provider, (score, reasons)]
|
|
91
|
+
"""
|
|
92
|
+
query_lower = query.lower()
|
|
93
|
+
|
|
94
|
+
scores = {
|
|
95
|
+
'ECPay': (0, []),
|
|
96
|
+
'SmilePay': (0, []),
|
|
97
|
+
'Amego': (0, []),
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
# 根據關鍵字累計分數
|
|
101
|
+
for keyword, rules in RECOMMENDATION_RULES.items():
|
|
102
|
+
if keyword.lower() in query_lower:
|
|
103
|
+
for provider, weight, reason in rules:
|
|
104
|
+
current_score, reasons = scores[provider]
|
|
105
|
+
if reason not in reasons:
|
|
106
|
+
scores[provider] = (current_score + weight, reasons + [reason])
|
|
107
|
+
|
|
108
|
+
return scores
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def get_anti_pattern_warnings(query: str, recommended: str) -> List[str]:
|
|
112
|
+
"""取得反模式警告"""
|
|
113
|
+
query_lower = query.lower()
|
|
114
|
+
warnings = []
|
|
115
|
+
|
|
116
|
+
if recommended in ANTI_PATTERNS:
|
|
117
|
+
for keyword, warning in ANTI_PATTERNS[recommended]:
|
|
118
|
+
if keyword.lower() in query_lower:
|
|
119
|
+
warnings.append(warning)
|
|
120
|
+
|
|
121
|
+
return warnings
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def recommend(query: str, verbose: bool = False) -> Dict[str, Any]:
|
|
125
|
+
"""
|
|
126
|
+
推薦加值中心
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
query: 使用者需求描述
|
|
130
|
+
verbose: 是否輸出詳細資訊
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
推薦結果
|
|
134
|
+
"""
|
|
135
|
+
providers = load_providers()
|
|
136
|
+
scores = analyze_requirements(query)
|
|
137
|
+
|
|
138
|
+
# 排序取得推薦順序
|
|
139
|
+
sorted_providers = sorted(
|
|
140
|
+
scores.items(),
|
|
141
|
+
key=lambda x: x[1][0],
|
|
142
|
+
reverse=True
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
# 建立結果
|
|
146
|
+
recommended = sorted_providers[0][0]
|
|
147
|
+
recommended_score, recommended_reasons = sorted_providers[0]
|
|
148
|
+
|
|
149
|
+
# 如果沒有匹配任何關鍵字,給預設推薦
|
|
150
|
+
if recommended_score == 0:
|
|
151
|
+
recommended = 'ECPay'
|
|
152
|
+
recommended_reasons = ['市佔率最高,適合大多數場景', '文檔完整,社群支援豐富']
|
|
153
|
+
recommended_score = 1
|
|
154
|
+
|
|
155
|
+
# 取得加值中心詳細資訊
|
|
156
|
+
provider_info = None
|
|
157
|
+
for p in providers:
|
|
158
|
+
if p.get('provider') == recommended:
|
|
159
|
+
provider_info = p
|
|
160
|
+
break
|
|
161
|
+
|
|
162
|
+
# 取得警告
|
|
163
|
+
warnings = get_anti_pattern_warnings(query, recommended)
|
|
164
|
+
|
|
165
|
+
result = {
|
|
166
|
+
'query': query,
|
|
167
|
+
'recommended': recommended,
|
|
168
|
+
'score': recommended_score,
|
|
169
|
+
'reasons': recommended_reasons,
|
|
170
|
+
'warnings': warnings,
|
|
171
|
+
'alternatives': [],
|
|
172
|
+
'provider_info': provider_info,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# 加入替代方案
|
|
176
|
+
for provider, (score, reasons) in sorted_providers[1:]:
|
|
177
|
+
if score > 0:
|
|
178
|
+
result['alternatives'].append({
|
|
179
|
+
'provider': provider,
|
|
180
|
+
'score': score,
|
|
181
|
+
'reasons': reasons,
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
return result
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def format_ascii_box(result: Dict[str, Any]) -> str:
|
|
188
|
+
"""格式化為 ASCII Box 輸出"""
|
|
189
|
+
width = 70
|
|
190
|
+
lines = []
|
|
191
|
+
|
|
192
|
+
# 頂部邊框
|
|
193
|
+
lines.append('╔' + '═' * (width - 2) + '╗')
|
|
194
|
+
lines.append('║' + ' 🎯 加值中心推薦結果 '.center(width - 2) + '║')
|
|
195
|
+
lines.append('╠' + '═' * (width - 2) + '╣')
|
|
196
|
+
|
|
197
|
+
# 查詢內容
|
|
198
|
+
query_line = f' 需求: {result["query"]}'
|
|
199
|
+
if len(query_line) > width - 4:
|
|
200
|
+
query_line = query_line[:width - 7] + '...'
|
|
201
|
+
lines.append('║' + query_line.ljust(width - 2) + '║')
|
|
202
|
+
lines.append('╠' + '─' * (width - 2) + '╣')
|
|
203
|
+
|
|
204
|
+
# 推薦結果
|
|
205
|
+
recommended = result['recommended']
|
|
206
|
+
score = result['score']
|
|
207
|
+
lines.append('║' + f' ⭐ 推薦: {recommended} (信心分數: {score})'.ljust(width - 2) + '║')
|
|
208
|
+
lines.append('║' + ' '.ljust(width - 2) + '║')
|
|
209
|
+
|
|
210
|
+
# 推薦原因
|
|
211
|
+
lines.append('║' + ' 📋 推薦原因:'.ljust(width - 2) + '║')
|
|
212
|
+
for reason in result['reasons']:
|
|
213
|
+
reason_line = f' • {reason}'
|
|
214
|
+
if len(reason_line) > width - 4:
|
|
215
|
+
reason_line = reason_line[:width - 7] + '...'
|
|
216
|
+
lines.append('║' + reason_line.ljust(width - 2) + '║')
|
|
217
|
+
|
|
218
|
+
# 警告
|
|
219
|
+
if result['warnings']:
|
|
220
|
+
lines.append('║' + ' '.ljust(width - 2) + '║')
|
|
221
|
+
lines.append('║' + ' ⚠️ 注意事項:'.ljust(width - 2) + '║')
|
|
222
|
+
for warning in result['warnings']:
|
|
223
|
+
warning_line = f' • {warning}'
|
|
224
|
+
if len(warning_line) > width - 4:
|
|
225
|
+
warning_line = warning_line[:width - 7] + '...'
|
|
226
|
+
lines.append('║' + warning_line.ljust(width - 2) + '║')
|
|
227
|
+
|
|
228
|
+
# 替代方案
|
|
229
|
+
if result['alternatives']:
|
|
230
|
+
lines.append('║' + ' '.ljust(width - 2) + '║')
|
|
231
|
+
lines.append('╠' + '─' * (width - 2) + '╣')
|
|
232
|
+
lines.append('║' + ' 🔄 替代方案:'.ljust(width - 2) + '║')
|
|
233
|
+
for alt in result['alternatives']:
|
|
234
|
+
alt_line = f' • {alt["provider"]} (分數: {alt["score"]})'
|
|
235
|
+
lines.append('║' + alt_line.ljust(width - 2) + '║')
|
|
236
|
+
for reason in alt['reasons'][:2]: # 只顯示前 2 個原因
|
|
237
|
+
reason_line = f' - {reason}'
|
|
238
|
+
if len(reason_line) > width - 4:
|
|
239
|
+
reason_line = reason_line[:width - 7] + '...'
|
|
240
|
+
lines.append('║' + reason_line.ljust(width - 2) + '║')
|
|
241
|
+
|
|
242
|
+
# 加值中心資訊
|
|
243
|
+
if result['provider_info']:
|
|
244
|
+
info = result['provider_info']
|
|
245
|
+
lines.append('║' + ' '.ljust(width - 2) + '║')
|
|
246
|
+
lines.append('╠' + '─' * (width - 2) + '╣')
|
|
247
|
+
lines.append('║' + f' 📦 {info.get("display_name", recommended)} 資訊:'.ljust(width - 2) + '║')
|
|
248
|
+
lines.append('║' + f' 認證方式: {info.get("auth_method", "N/A")}'.ljust(width - 2) + '║')
|
|
249
|
+
lines.append('║' + f' 測試網址: {info.get("test_url", "N/A")}'.ljust(width - 2) + '║')
|
|
250
|
+
features = info.get('features', '')
|
|
251
|
+
if features:
|
|
252
|
+
feat_line = f' 特色: {features}'
|
|
253
|
+
if len(feat_line) > width - 4:
|
|
254
|
+
feat_line = feat_line[:width - 7] + '...'
|
|
255
|
+
lines.append('║' + feat_line.ljust(width - 2) + '║')
|
|
256
|
+
|
|
257
|
+
# 底部邊框
|
|
258
|
+
lines.append('╚' + '═' * (width - 2) + '╝')
|
|
259
|
+
|
|
260
|
+
return '\n'.join(lines)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def format_json(result: Dict[str, Any]) -> str:
|
|
264
|
+
"""格式化為 JSON 輸出"""
|
|
265
|
+
import json
|
|
266
|
+
return json.dumps(result, ensure_ascii=False, indent=2)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def format_simple(result: Dict[str, Any]) -> str:
|
|
270
|
+
"""格式化為簡單文字輸出"""
|
|
271
|
+
lines = []
|
|
272
|
+
lines.append(f"推薦加值中心: {result['recommended']}")
|
|
273
|
+
lines.append(f"信心分數: {result['score']}")
|
|
274
|
+
lines.append("")
|
|
275
|
+
lines.append("推薦原因:")
|
|
276
|
+
for reason in result['reasons']:
|
|
277
|
+
lines.append(f" - {reason}")
|
|
278
|
+
|
|
279
|
+
if result['warnings']:
|
|
280
|
+
lines.append("")
|
|
281
|
+
lines.append("注意事項:")
|
|
282
|
+
for warning in result['warnings']:
|
|
283
|
+
lines.append(f" - {warning}")
|
|
284
|
+
|
|
285
|
+
if result['alternatives']:
|
|
286
|
+
lines.append("")
|
|
287
|
+
lines.append("替代方案:")
|
|
288
|
+
for alt in result['alternatives']:
|
|
289
|
+
lines.append(f" - {alt['provider']} (分數: {alt['score']})")
|
|
290
|
+
|
|
291
|
+
return '\n'.join(lines)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def main():
|
|
295
|
+
parser = argparse.ArgumentParser(
|
|
296
|
+
description='Taiwan Invoice 加值中心推薦系統',
|
|
297
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
298
|
+
epilog="""
|
|
299
|
+
範例:
|
|
300
|
+
python recommend.py "電商 高交易量 穩定"
|
|
301
|
+
python recommend.py "簡單整合 快速上線" --format json
|
|
302
|
+
python recommend.py "API設計優先 MIG標準" --format simple
|
|
303
|
+
|
|
304
|
+
關鍵字範例:
|
|
305
|
+
穩定性: 穩定, 市佔, 高交易量, 電商
|
|
306
|
+
簡易性: 簡單, 快速, 小型, 測試
|
|
307
|
+
API品質: api, 設計, 新, mig, 標準
|
|
308
|
+
功能: b2b, b2c, 列印, 作廢, 折讓, 載具, 捐贈
|
|
309
|
+
"""
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
parser.add_argument('query', help='需求描述 (關鍵字以空格分隔)')
|
|
313
|
+
parser.add_argument(
|
|
314
|
+
'-f', '--format',
|
|
315
|
+
choices=['ascii', 'json', 'simple'],
|
|
316
|
+
default='ascii',
|
|
317
|
+
help='輸出格式 (預設: ascii)'
|
|
318
|
+
)
|
|
319
|
+
parser.add_argument(
|
|
320
|
+
'-v', '--verbose',
|
|
321
|
+
action='store_true',
|
|
322
|
+
help='顯示詳細資訊'
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
args = parser.parse_args()
|
|
326
|
+
|
|
327
|
+
# 執行推薦
|
|
328
|
+
result = recommend(args.query, args.verbose)
|
|
329
|
+
|
|
330
|
+
# 輸出結果
|
|
331
|
+
if args.format == 'json':
|
|
332
|
+
print(format_json(result))
|
|
333
|
+
elif args.format == 'simple':
|
|
334
|
+
print(format_simple(result))
|
|
335
|
+
else:
|
|
336
|
+
print(format_ascii_box(result))
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
if __name__ == '__main__':
|
|
340
|
+
main()
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Taiwan Invoice Skill - Search CLI
|
|
4
|
+
電子發票智能搜索命令行工具
|
|
5
|
+
|
|
6
|
+
用法:
|
|
7
|
+
python search.py "ecpay B2C" --domain operation
|
|
8
|
+
python search.py "1999 error" --domain error
|
|
9
|
+
python search.py "稅額計算" --domain tax
|
|
10
|
+
python search.py "綠界" --all
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import sys
|
|
15
|
+
from typing import Dict, List, Any
|
|
16
|
+
|
|
17
|
+
from core import (
|
|
18
|
+
search,
|
|
19
|
+
search_all,
|
|
20
|
+
detect_domain,
|
|
21
|
+
get_available_domains,
|
|
22
|
+
get_domain_info
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def format_ascii_box(title: str, content: List[str], width: int = 80) -> str:
|
|
27
|
+
"""
|
|
28
|
+
格式化 ASCII Box 輸出
|
|
29
|
+
"""
|
|
30
|
+
lines = []
|
|
31
|
+
lines.append('┌' + '─' * (width - 2) + '┐')
|
|
32
|
+
lines.append('│' + f' {title}'.ljust(width - 2) + '│')
|
|
33
|
+
lines.append('├' + '─' * (width - 2) + '┤')
|
|
34
|
+
|
|
35
|
+
for line in content:
|
|
36
|
+
# 處理過長的行
|
|
37
|
+
if len(line) > width - 4:
|
|
38
|
+
line = line[:width - 7] + '...'
|
|
39
|
+
lines.append('│ ' + line.ljust(width - 4) + ' │')
|
|
40
|
+
|
|
41
|
+
lines.append('└' + '─' * (width - 2) + '┘')
|
|
42
|
+
return '\n'.join(lines)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def format_result(result: Dict[str, Any], domain: str) -> List[str]:
|
|
46
|
+
"""
|
|
47
|
+
格式化單個搜索結果
|
|
48
|
+
"""
|
|
49
|
+
lines = []
|
|
50
|
+
score = result.get('_score', 0)
|
|
51
|
+
lines.append(f'Score: {score}')
|
|
52
|
+
lines.append('')
|
|
53
|
+
|
|
54
|
+
for key, value in result.items():
|
|
55
|
+
if key == '_score' or not value:
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
# 截斷過長的值
|
|
59
|
+
value_str = str(value)
|
|
60
|
+
if len(value_str) > 60:
|
|
61
|
+
value_str = value_str[:57] + '...'
|
|
62
|
+
|
|
63
|
+
lines.append(f'{key}: {value_str}')
|
|
64
|
+
|
|
65
|
+
return lines
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def format_domain_results(results: List[Dict[str, Any]], domain: str, query: str) -> str:
|
|
69
|
+
"""
|
|
70
|
+
格式化整個域的搜索結果
|
|
71
|
+
"""
|
|
72
|
+
if not results:
|
|
73
|
+
return f"No results found in '{domain}' for query: {query}"
|
|
74
|
+
|
|
75
|
+
output = []
|
|
76
|
+
output.append(f"\n{'='*60}")
|
|
77
|
+
output.append(f"Domain: {domain.upper()} | Query: {query} | Results: {len(results)}")
|
|
78
|
+
output.append('='*60)
|
|
79
|
+
|
|
80
|
+
for i, result in enumerate(results, 1):
|
|
81
|
+
lines = format_result(result, domain)
|
|
82
|
+
box = format_ascii_box(f'Result {i}', lines, width=60)
|
|
83
|
+
output.append(box)
|
|
84
|
+
output.append('')
|
|
85
|
+
|
|
86
|
+
return '\n'.join(output)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def format_all_results(results: Dict[str, List[Dict[str, Any]]], query: str) -> str:
|
|
90
|
+
"""
|
|
91
|
+
格式化所有域的搜索結果
|
|
92
|
+
"""
|
|
93
|
+
if not results:
|
|
94
|
+
return f"No results found for query: {query}"
|
|
95
|
+
|
|
96
|
+
output = []
|
|
97
|
+
output.append(f"\n{'#'*60}")
|
|
98
|
+
output.append(f"# SEARCH ALL DOMAINS: {query}")
|
|
99
|
+
output.append('#'*60)
|
|
100
|
+
|
|
101
|
+
for domain, domain_results in results.items():
|
|
102
|
+
output.append(format_domain_results(domain_results, domain, query))
|
|
103
|
+
|
|
104
|
+
return '\n'.join(output)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def list_domains():
|
|
108
|
+
"""
|
|
109
|
+
列出所有可用的搜索域
|
|
110
|
+
"""
|
|
111
|
+
print("\n可用的搜索域 (Available Domains):")
|
|
112
|
+
print("="*50)
|
|
113
|
+
|
|
114
|
+
for domain in get_available_domains():
|
|
115
|
+
info = get_domain_info(domain)
|
|
116
|
+
if info:
|
|
117
|
+
print(f"\n {domain}")
|
|
118
|
+
print(f" 檔案: {info['file']}")
|
|
119
|
+
print(f" 記錄數: {info['total_records']}")
|
|
120
|
+
print(f" 搜索欄位: {', '.join(info['search_cols'])}")
|
|
121
|
+
|
|
122
|
+
print()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def main():
|
|
126
|
+
parser = argparse.ArgumentParser(
|
|
127
|
+
description='Taiwan Invoice Skill - BM25 Search Engine',
|
|
128
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
129
|
+
epilog="""
|
|
130
|
+
Examples:
|
|
131
|
+
python search.py "ecpay B2C" # Auto-detect domain
|
|
132
|
+
python search.py "開立發票" --domain operation # Search operations
|
|
133
|
+
python search.py "10000016" --domain error # Search error codes
|
|
134
|
+
python search.py "統編" --domain field # Search field mappings
|
|
135
|
+
python search.py "B2B 稅額" --domain tax # Search tax rules
|
|
136
|
+
python search.py "列印空白" --domain troubleshoot # Search troubleshooting
|
|
137
|
+
python search.py "ECPay" --all # Search all domains
|
|
138
|
+
python search.py --list # List available domains
|
|
139
|
+
"""
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
parser.add_argument('query', nargs='?', help='Search query')
|
|
143
|
+
parser.add_argument('-d', '--domain', choices=get_available_domains(),
|
|
144
|
+
help='Search domain (auto-detect if not specified)')
|
|
145
|
+
parser.add_argument('-n', '--max-results', type=int, default=5,
|
|
146
|
+
help='Maximum results per domain (default: 5)')
|
|
147
|
+
parser.add_argument('-a', '--all', action='store_true',
|
|
148
|
+
help='Search all domains')
|
|
149
|
+
parser.add_argument('-l', '--list', action='store_true',
|
|
150
|
+
help='List available domains')
|
|
151
|
+
parser.add_argument('-f', '--format', choices=['ascii', 'simple', 'json'],
|
|
152
|
+
default='ascii', help='Output format (default: ascii)')
|
|
153
|
+
|
|
154
|
+
args = parser.parse_args()
|
|
155
|
+
|
|
156
|
+
# 列出域
|
|
157
|
+
if args.list:
|
|
158
|
+
list_domains()
|
|
159
|
+
return
|
|
160
|
+
|
|
161
|
+
# 檢查查詢
|
|
162
|
+
if not args.query:
|
|
163
|
+
parser.print_help()
|
|
164
|
+
return
|
|
165
|
+
|
|
166
|
+
query = args.query
|
|
167
|
+
|
|
168
|
+
# 搜索所有域
|
|
169
|
+
if args.all:
|
|
170
|
+
results = search_all(query, args.max_results)
|
|
171
|
+
|
|
172
|
+
if args.format == 'json':
|
|
173
|
+
import json
|
|
174
|
+
print(json.dumps(results, ensure_ascii=False, indent=2))
|
|
175
|
+
else:
|
|
176
|
+
print(format_all_results(results, query))
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
# 單域搜索
|
|
180
|
+
domain = args.domain
|
|
181
|
+
if not domain:
|
|
182
|
+
domain = detect_domain(query)
|
|
183
|
+
print(f"[Auto-detected domain: {domain}]")
|
|
184
|
+
|
|
185
|
+
results = search(query, domain, args.max_results)
|
|
186
|
+
|
|
187
|
+
if args.format == 'json':
|
|
188
|
+
import json
|
|
189
|
+
print(json.dumps(results, ensure_ascii=False, indent=2))
|
|
190
|
+
elif args.format == 'simple':
|
|
191
|
+
for i, result in enumerate(results, 1):
|
|
192
|
+
print(f"\n[{i}] Score: {result.get('_score', 0)}")
|
|
193
|
+
for key, value in result.items():
|
|
194
|
+
if key != '_score' and value:
|
|
195
|
+
print(f" {key}: {value}")
|
|
196
|
+
else:
|
|
197
|
+
print(format_domain_results(results, domain, query))
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
if __name__ == '__main__':
|
|
201
|
+
main()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
## When to Apply
|
|
2
|
+
|
|
3
|
+
Reference these guidelines when:
|
|
4
|
+
- Developing Taiwan E-Invoice issuance functionality
|
|
5
|
+
- Integrating ECPay, SmilePay, or Amego APIs
|
|
6
|
+
- Implementing B2C or B2B invoice logic
|
|
7
|
+
- Handling invoice printing, void, and allowance
|
|
8
|
+
- Troubleshooting invoice API integration issues
|
|
9
|
+
|
|
10
|
+
## Provider Quick Reference
|
|
11
|
+
|
|
12
|
+
| Priority | Task | Impact | Provider |
|
|
13
|
+
|----------|------|--------|----------|
|
|
14
|
+
| 1 | Amount Calculation | CRITICAL | All |
|
|
15
|
+
| 2 | Encryption/Signature | CRITICAL | All |
|
|
16
|
+
| 3 | B2B vs B2C Logic | HIGH | All |
|
|
17
|
+
| 4 | Print Response Handling | HIGH | All |
|
|
18
|
+
| 5 | Provider Binding | MEDIUM | All |
|
|
19
|
+
| 6 | Error Handling | MEDIUM | All |
|
|
20
|
+
| 7 | Carrier/Donation | LOW | B2C only |
|
|
21
|
+
|
|
22
|
+
## Quick Reference
|
|
23
|
+
|
|
24
|
+
### 1. Amount Calculation (CRITICAL)
|
|
25
|
+
|
|
26
|
+
- `b2c-tax-inclusive` - B2C uses tax-inclusive total
|
|
27
|
+
- `b2b-split-tax` - B2B requires pre-tax + tax split
|
|
28
|
+
- `round-tax` - Round tax: `Math.round(total - (total / 1.05))`
|
|
29
|
+
- `salesamount` - ECPay/Amego: Use SalesAmount for pre-tax
|
|
30
|
+
|
|
31
|
+
### 2. Encryption/Signature (CRITICAL)
|
|
32
|
+
|
|
33
|
+
- `ecpay-aes` - ECPay: AES-128-CBC with HashKey/HashIV
|
|
34
|
+
- `smilepay-verify` - SmilePay: Grvc + Verify_key params
|
|
35
|
+
- `amego-md5` - Amego: MD5(data + time + appKey)
|
|
36
|
+
- `url-encode` - Always URL encode before encryption
|
|
37
|
+
|
|
38
|
+
### 3. B2B vs B2C Logic (HIGH)
|
|
39
|
+
|
|
40
|
+
- `buyer-id-b2c` - B2C: BuyerIdentifier = "0000000000"
|
|
41
|
+
- `buyer-id-b2b` - B2B: BuyerIdentifier = actual 8-digit tax ID
|
|
42
|
+
- `no-carrier-b2b` - B2B: Cannot use carrier or donation
|
|
43
|
+
- `validate-taxid` - Validate 8-digit tax ID format
|
|
44
|
+
|
|
45
|
+
### 4. Print Response Handling (HIGH)
|
|
46
|
+
|
|
47
|
+
- `ecpay-html` - ECPay: Returns HTML, use window.document.write
|
|
48
|
+
- `smilepay-redirect` - SmilePay: Returns URL, use window.open
|
|
49
|
+
- `amego-pdf` - Amego: Returns PDF URL, use window.open
|
|
50
|
+
- `form-submit` - Some APIs require form POST submission
|
|
51
|
+
|
|
52
|
+
### 5. Provider Binding (MEDIUM)
|
|
53
|
+
|
|
54
|
+
- `save-provider` - Save invoiceProvider when issuing
|
|
55
|
+
- `save-random` - Save invoiceRandomNum for printing
|
|
56
|
+
- `match-provider` - Use issuing provider for print/void
|
|
57
|
+
|
|
58
|
+
### 6. Error Handling (MEDIUM)
|
|
59
|
+
|
|
60
|
+
- `log-raw-response` - Log complete raw response for debugging
|
|
61
|
+
- `ecpay-codes` - ECPay: Check RtnCode and RtnMsg
|
|
62
|
+
- `smilepay-codes` - SmilePay: Check Status field
|
|
63
|
+
- `amego-codes` - Amego: Check Code and Message
|
|
64
|
+
|
|
65
|
+
### 7. Carrier/Donation (B2C only)
|
|
66
|
+
|
|
67
|
+
- `carrier-mobile` - Mobile barcode: /XXXXXXX format
|
|
68
|
+
- `carrier-npc` - Natural person certificate
|
|
69
|
+
- `donation-code` - Donation: 3-7 digit love code
|
|
70
|
+
- `mutual-exclusive` - Carrier and donation are mutually exclusive
|
|
71
|
+
|
|
72
|
+
## Test Credentials
|
|
73
|
+
|
|
74
|
+
| Provider | Key Info |
|
|
75
|
+
|----------|----------|
|
|
76
|
+
| ECPay | MerchantID: 2000132, Stage URL |
|
|
77
|
+
| SmilePay | Grvc: SEI1000034, Test Tax ID: 80129529 |
|
|
78
|
+
| Amego | Tax ID: 12345678, test@amego.tw |
|
|
79
|
+
|
|
80
|
+
## How to Use
|
|
81
|
+
|
|
82
|
+
See the full skill documentation for detailed API references and code examples.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"platform": "antigravity",
|
|
3
|
-
"displayName": "
|
|
3
|
+
"displayName": "Antigravity / Generic Agent",
|
|
4
4
|
"installType": "full",
|
|
5
5
|
"folderStructure": {
|
|
6
6
|
"root": ".agent",
|
|
7
7
|
"skillPath": "skills/taiwan-invoice",
|
|
8
8
|
"filename": "SKILL.md"
|
|
9
9
|
},
|
|
10
|
+
"scriptPath": "skills/taiwan-invoice/scripts",
|
|
10
11
|
"frontmatter": {
|
|
11
12
|
"name": "taiwan-invoice",
|
|
12
13
|
"description": "Taiwan E-Invoice API integration specialist for ECPay, SmilePay, and Amego. Provides B2C/B2B invoice issuance, void, allowance, query, and print functionality."
|
|
@@ -14,8 +15,10 @@
|
|
|
14
15
|
"sections": {
|
|
15
16
|
"examples": true,
|
|
16
17
|
"references": true,
|
|
17
|
-
"scripts": true
|
|
18
|
+
"scripts": true,
|
|
19
|
+
"quickReference": false
|
|
18
20
|
},
|
|
19
21
|
"title": "Taiwan E-Invoice Skill",
|
|
20
|
-
"description": "Comprehensive guide for Taiwan E-Invoice API integration. Supports ECPay, SmilePay, and Amego providers with B2C/B2B invoice operations."
|
|
22
|
+
"description": "Comprehensive guide for Taiwan E-Invoice API integration. Supports ECPay, SmilePay, and Amego providers with B2C/B2B invoice operations.",
|
|
23
|
+
"skillOrWorkflow": "Skill"
|
|
21
24
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"skillPath": "skills/taiwan-invoice",
|
|
8
8
|
"filename": "SKILL.md"
|
|
9
9
|
},
|
|
10
|
+
"scriptPath": "skills/taiwan-invoice/scripts",
|
|
10
11
|
"frontmatter": {
|
|
11
12
|
"name": "taiwan-invoice",
|
|
12
13
|
"description": "Taiwan E-Invoice API integration specialist for ECPay, SmilePay, and Amego. Provides B2C/B2B invoice issuance, void, allowance, query, and print functionality. Includes encryption implementations (AES-128-CBC, MD5), tax calculations, and complete API specifications.",
|
|
@@ -15,8 +16,10 @@
|
|
|
15
16
|
"sections": {
|
|
16
17
|
"examples": true,
|
|
17
18
|
"references": true,
|
|
18
|
-
"scripts": true
|
|
19
|
+
"scripts": true,
|
|
20
|
+
"quickReference": true
|
|
19
21
|
},
|
|
20
22
|
"title": "Taiwan E-Invoice Skill",
|
|
21
|
-
"description": "Comprehensive guide for Taiwan E-Invoice API integration. Supports ECPay, SmilePay, and Amego providers with B2C/B2B invoice operations."
|
|
23
|
+
"description": "Comprehensive guide for Taiwan E-Invoice API integration. Supports ECPay, SmilePay, and Amego providers with B2C/B2B invoice operations.",
|
|
24
|
+
"skillOrWorkflow": "Skill"
|
|
22
25
|
}
|
|
@@ -7,12 +7,15 @@
|
|
|
7
7
|
"skillPath": "skills/taiwan-invoice",
|
|
8
8
|
"filename": "SKILL.md"
|
|
9
9
|
},
|
|
10
|
+
"scriptPath": "skills/taiwan-invoice/scripts",
|
|
10
11
|
"frontmatter": null,
|
|
11
12
|
"sections": {
|
|
12
13
|
"examples": true,
|
|
13
14
|
"references": true,
|
|
14
|
-
"scripts": true
|
|
15
|
+
"scripts": true,
|
|
16
|
+
"quickReference": false
|
|
15
17
|
},
|
|
16
18
|
"title": "Taiwan E-Invoice Skill",
|
|
17
|
-
"description": "Comprehensive guide for Taiwan E-Invoice API integration. Supports ECPay, SmilePay, and Amego providers with B2C/B2B invoice operations."
|
|
19
|
+
"description": "Comprehensive guide for Taiwan E-Invoice API integration. Supports ECPay, SmilePay, and Amego providers with B2C/B2B invoice operations.",
|
|
20
|
+
"skillOrWorkflow": "Skill"
|
|
18
21
|
}
|