@puyinkai/xiaobao-cli 0.1.8 → 0.1.9
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/dist/{api-client-zDBFgk0y.mjs → api-client-CDNjkArS.mjs} +1 -1
- package/dist/{api-CX99_4K7.mjs → api-fsHHWWa3.mjs} +1 -1
- package/dist/{audio-ShfvRomP.mjs → audio-CRSMqaKo.mjs} +2 -2
- package/dist/{auth-DpK81Vp5.mjs → auth-_3hBh87U.mjs} +1 -1
- package/dist/cli.mjs +10 -10
- package/dist/{consultant-CS9B42d3.mjs → consultant-E33hTGUO.mjs} +1 -1
- package/dist/{customer-B1cxRbJ9.mjs → customer-DcQ83oKD.mjs} +1 -1
- package/dist/{focus-DfNPPhm5.mjs → focus-XMRxnHPw.mjs} +1 -1
- package/dist/{list-DI1OD95x.mjs → list-B3nN4pd2.mjs} +1 -1
- package/dist/{list-CD08BRLk.mjs → list-CXquxzex.mjs} +1 -1
- package/dist/{list-CKlx8YuL.mjs → list-Cc4zcaNz.mjs} +1 -1
- package/dist/{list-DCcCS6LM.mjs → list-CnlQdzPp.mjs} +1 -1
- package/dist/{list-D56gEqiB.mjs → list-CrMOrnS-.mjs} +1 -1
- package/dist/{list-CnlFIFiY.mjs → list-DFdDcHFq.mjs} +1 -1
- package/dist/{list-Cu6WzPpA.mjs → list-dm9YaCG1.mjs} +1 -1
- package/dist/{project-BuQQewOs.mjs → project-BD0g4oAW.mjs} +1 -1
- package/dist/{qa-CKsKiQIm.mjs → qa-CdMcxCE_.mjs} +1 -1
- package/dist/{resistance-DsE-Ha6Y.mjs → resistance-6_V6At3T.mjs} +1 -1
- package/dist/{text-C3jhdPoe.mjs → text-C3Ap6L5n.mjs} +1 -1
- package/dist/{visit-CX0Mc1n6.mjs → visit-DoeECi8w.mjs} +1 -1
- package/dist/{whoami-CZVez-rm.mjs → whoami-z-76WaCP.mjs} +1 -1
- package/package.json +5 -1
- package/skills/wangxiaobao-audio-query/SKILL.md +2 -2
- package/skills/wangxiaobao-audio-wiki/SKILL.md +4 -4
- package/skills/wangxiaobao-customer-focus-query/SKILL.md +2 -2
- package/skills/wangxiaobao-customer-query/SKILL.md +2 -2
- package/skills/wangxiaobao-customer-resistance-query/SKILL.md +2 -2
- package/skills/wangxiaobao-html-to-pdf/SKILL.md +116 -0
- package/skills/wangxiaobao-html-to-pdf/scripts/html2pdf.py +132 -0
- package/skills/wangxiaobao-html-to-pdf/scripts/html2pdf_exact.py +92 -0
- package/skills/wangxiaobao-project-daily-report/SKILL.md +170 -0
- package/skills/wangxiaobao-project-daily-report/_meta.json +9 -0
- package/skills/wangxiaobao-project-daily-report/references/report_template.html +308 -0
- package/skills/wangxiaobao-project-daily-report/scripts/fetch_data.py +219 -0
- package/skills/wangxiaobao-project-daily-report/scripts/generate_report.py +1029 -0
- package/skills/wangxiaobao-project-daily-report/scripts/run.py +34 -0
- package/skills/wangxiaobao-project-weekly-report/SKILL.md +169 -0
- package/skills/wangxiaobao-project-weekly-report/scripts/generate_report.py +308 -0
- package/skills/wangxiaobao-project-weekly-report/scripts/html2pdf_fast.py +161 -0
- package/skills/wangxiaobao-project-weekly-report/scripts/logo_wangai.png +0 -0
- package/skills/wangxiaobao-project-weekly-report/scripts/logo_wangxiaobao.png +0 -0
- package/skills/wangxiaobao-project-weekly-report/scripts/prepare_data.py +682 -0
- package/skills/wangxiaobao-project-weekly-report/scripts/template.py +925 -0
- package/skills/wangxiaobao-quick-qa/SKILL.md +2 -2
- package/skills/wangxiaobao-shared/SKILL.md +5 -5
- package/skills/wangxiaobao-switch-project/SKILL.md +3 -3
- package/skills/wangxiaobao-visit-query/SKILL.md +2 -2
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
旺小宝项目日报数据拉取脚本
|
|
4
|
+
功能:拉取旺小宝API数据,生成日报所需数据
|
|
5
|
+
"""
|
|
6
|
+
import json
|
|
7
|
+
import subprocess
|
|
8
|
+
import os
|
|
9
|
+
import argparse
|
|
10
|
+
from collections import Counter
|
|
11
|
+
|
|
12
|
+
# 项目配置(优先级:命令行参数 > 环境变量 > 默认值)
|
|
13
|
+
TENANT_ID = os.environ.get("XIAOBAO_TENANT_ID", "")
|
|
14
|
+
PROJECT_ID = os.environ.get("XIAOBAO_PROJECT_ID", "")
|
|
15
|
+
PROJECT_NAME = os.environ.get("XIAOBAO_PROJECT_NAME", "<项目名>")
|
|
16
|
+
# KPI指标(示例值 500 / 21000,请按项目实际目标调整)
|
|
17
|
+
MONTH_VISIT_TARGET = int(os.environ.get("XIAOBAO_MONTH_VISIT_TARGET", "500"))
|
|
18
|
+
MONTH_SALES_TARGET = int(os.environ.get("XIAOBAO_MONTH_SALES_TARGET", "21000"))
|
|
19
|
+
TOKEN_PATH = os.path.join(os.path.expanduser("~"), ".openclaw/state/wangxiaobao/token.json")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_token():
|
|
23
|
+
"""获取旺小宝access token"""
|
|
24
|
+
with open(TOKEN_PATH, 'r') as f:
|
|
25
|
+
return json.load(f)['access_token']
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def api_call(endpoint, body, token):
|
|
29
|
+
"""调用旺小宝API"""
|
|
30
|
+
cmd = [
|
|
31
|
+
'curl', '-s', '-X', 'POST',
|
|
32
|
+
f'https://open-ai.wangxiaobao.com{endpoint}',
|
|
33
|
+
'-H', f'Authorization: Bearer {token}',
|
|
34
|
+
'-H', f'X-Tenant-Id: {TENANT_ID}',
|
|
35
|
+
'-H', f'X-Project-Id: {PROJECT_ID}',
|
|
36
|
+
'-H', 'Content-Type: application/json',
|
|
37
|
+
'-d', body
|
|
38
|
+
]
|
|
39
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
40
|
+
return json.loads(result.stdout)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def get_yesterday_visits(token, yesterday):
|
|
44
|
+
"""获取昨日来访数据"""
|
|
45
|
+
body = json.dumps({
|
|
46
|
+
"current": 1, "size": 100,
|
|
47
|
+
"startTime": f"{yesterday} 00:00:00",
|
|
48
|
+
"endTime": f"{yesterday} 23:59:59"
|
|
49
|
+
})
|
|
50
|
+
data = api_call("/ai-open/visits/page", body, token)
|
|
51
|
+
all_visits = data.get('data', {}).get('content', [])
|
|
52
|
+
# 过滤只保留昨日
|
|
53
|
+
return [v for v in all_visits if v.get('visitTime', '').startswith(yesterday)]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_month_visits(token, month_start, yesterday):
|
|
57
|
+
"""获取本月来访数据"""
|
|
58
|
+
body = json.dumps({
|
|
59
|
+
"current": 1, "size": 500,
|
|
60
|
+
"startTime": f"{month_start} 00:00:00",
|
|
61
|
+
"endTime": f"{yesterday} 23:59:59"
|
|
62
|
+
})
|
|
63
|
+
data = api_call("/ai-open/visits/page", body, token)
|
|
64
|
+
all_visits = data.get('data', {}).get('content', [])
|
|
65
|
+
# 过滤本月
|
|
66
|
+
return [v for v in all_visits if v.get('visitTime', '').startswith('2026-05-')]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_customers(token, month_start, yesterday):
|
|
70
|
+
"""获取客户画像数据"""
|
|
71
|
+
body = json.dumps({
|
|
72
|
+
"page": 1, "size": 500,
|
|
73
|
+
"startTime": f"{month_start} 00:00:00",
|
|
74
|
+
"endTime": f"{yesterday} 23:59:59"
|
|
75
|
+
})
|
|
76
|
+
data = api_call("/ai-open/customers/page", body, token)
|
|
77
|
+
return data.get('data', {}).get('content', [])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def get_stock_customers(token, three_months_ago, yesterday):
|
|
81
|
+
"""获取存量客户(近3月未成交)"""
|
|
82
|
+
body = json.dumps({
|
|
83
|
+
"page": 1, "size": 100,
|
|
84
|
+
"startTime": f"{three_months_ago} 00:00:00",
|
|
85
|
+
"endTime": f"{yesterday} 23:59:59"
|
|
86
|
+
})
|
|
87
|
+
data = api_call("/ai-open/customers/page", body, token)
|
|
88
|
+
all_custs = data.get('data', {}).get('content', [])
|
|
89
|
+
# 过滤未成交
|
|
90
|
+
return [c for c in all_custs if c.get('dealStatus') != 1]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def analyze_intent(customers):
|
|
94
|
+
"""分析意向等级分布"""
|
|
95
|
+
intent_dist = Counter()
|
|
96
|
+
for c in customers:
|
|
97
|
+
level = c.get('intentLevel', '未知') or '未知'
|
|
98
|
+
if level == '':
|
|
99
|
+
level = '未知'
|
|
100
|
+
intent_dist[level] += 1
|
|
101
|
+
return dict(intent_dist)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def analyze_channel(customers):
|
|
105
|
+
"""分析渠道分布"""
|
|
106
|
+
channel_dist = Counter()
|
|
107
|
+
for c in customers:
|
|
108
|
+
tags = c.get('dynamicTags', {})
|
|
109
|
+
channel = tags.get('五大类来访渠道', '未知')
|
|
110
|
+
if channel == '':
|
|
111
|
+
channel = '未知'
|
|
112
|
+
channel_dist[channel] += 1
|
|
113
|
+
return dict(channel_dist)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def main(date_str='2026-05-21'):
|
|
117
|
+
"""主函数"""
|
|
118
|
+
today = date_str
|
|
119
|
+
yesterday = '2026-05-20'
|
|
120
|
+
month_start = '2026-05-01'
|
|
121
|
+
three_months_ago = '2026-02-20'
|
|
122
|
+
|
|
123
|
+
print("="*60)
|
|
124
|
+
print(f"{PROJECT_NAME}日报数据拉取 - {today}")
|
|
125
|
+
print("="*60)
|
|
126
|
+
|
|
127
|
+
token = get_token()
|
|
128
|
+
|
|
129
|
+
# 1. 昨日来访
|
|
130
|
+
print("\n[1] 拉取昨日来访数据...")
|
|
131
|
+
yesterday_visits = get_yesterday_visits(token, yesterday)
|
|
132
|
+
print(f" 昨日来访: {len(yesterday_visits)}组")
|
|
133
|
+
|
|
134
|
+
# 2. 本月来访
|
|
135
|
+
print("\n[2] 拉取本月来访数据...")
|
|
136
|
+
month_visits = get_month_visits(token, month_start, yesterday)
|
|
137
|
+
print(f" 本月来访: {len(month_visits)}组")
|
|
138
|
+
|
|
139
|
+
# 3. 客户画像
|
|
140
|
+
print("\n[3] 拉取客户画像数据...")
|
|
141
|
+
customers = get_customers(token, month_start, yesterday)
|
|
142
|
+
print(f" 本月客户总数: {len(customers)}")
|
|
143
|
+
|
|
144
|
+
# 意向等级
|
|
145
|
+
intent_dist = analyze_intent(customers)
|
|
146
|
+
print("\n 意向等级分布:")
|
|
147
|
+
for level, cnt in sorted(intent_dist.items(), key=lambda x: -x[1]):
|
|
148
|
+
print(f" {level}: {cnt}组")
|
|
149
|
+
|
|
150
|
+
# 渠道分布
|
|
151
|
+
channel_dist = analyze_channel(customers)
|
|
152
|
+
print("\n 渠道分布:")
|
|
153
|
+
for ch, cnt in sorted(channel_dist.items(), key=lambda x: -x[1])[:5]:
|
|
154
|
+
print(f" {ch}: {cnt}组")
|
|
155
|
+
|
|
156
|
+
# 4. 存量客户
|
|
157
|
+
print("\n[4] 拉取存量客户数据...")
|
|
158
|
+
stock_customers = get_stock_customers(token, three_months_ago, yesterday)
|
|
159
|
+
print(f" 存量客户(未成交): {len(stock_customers)}组")
|
|
160
|
+
|
|
161
|
+
# 5. 成交客户
|
|
162
|
+
print("\n[5] 分析成交数据...")
|
|
163
|
+
deal_customers = [c for c in customers if c.get('dealStatus') == 1]
|
|
164
|
+
deal_intent = analyze_intent(deal_customers)
|
|
165
|
+
print(f" 本月成交: {len(deal_customers)}组")
|
|
166
|
+
print(" 成交意向等级:", deal_intent)
|
|
167
|
+
|
|
168
|
+
# 保存数据
|
|
169
|
+
report_data = {
|
|
170
|
+
'date': today,
|
|
171
|
+
'yesterday': yesterday,
|
|
172
|
+
'project_name': PROJECT_NAME,
|
|
173
|
+
'month_visit_target': MONTH_VISIT_TARGET,
|
|
174
|
+
'month_visit_actual': len(month_visits),
|
|
175
|
+
'yesterday_visits': len(yesterday_visits),
|
|
176
|
+
'yesterday_visit_list': [{
|
|
177
|
+
'time': v['visitTime'].split(' ')[1],
|
|
178
|
+
'advisor': v['userName'],
|
|
179
|
+
'count': v['visitCount'],
|
|
180
|
+
'duration': int(int(v.get('visitTimer', '0')) / 60000) if v.get('visitTimer', '0') != '0' else 0
|
|
181
|
+
} for v in yesterday_visits],
|
|
182
|
+
'month_sales_target': MONTH_SALES_TARGET,
|
|
183
|
+
'month_deals': len(deal_customers),
|
|
184
|
+
'visit_intent': intent_dist,
|
|
185
|
+
'channel_dist': channel_dist,
|
|
186
|
+
'stock_total': len(stock_customers),
|
|
187
|
+
'deal_intent': deal_intent
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
output_path = os.path.join(os.getcwd(), "final_report_data.json")
|
|
191
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
|
192
|
+
json.dump(report_data, f, ensure_ascii=False, indent=2)
|
|
193
|
+
|
|
194
|
+
print(f"\n数据已保存: {output_path}")
|
|
195
|
+
return report_data
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
if __name__ == "__main__":
|
|
199
|
+
parser = argparse.ArgumentParser(description="旺小宝项目日报数据拉取")
|
|
200
|
+
parser.add_argument("date", nargs="?", default="2026-05-21", help="日期 YYYY-MM-DD")
|
|
201
|
+
parser.add_argument("--tenant-id", help="旺小宝租户ID(覆盖环境变量)")
|
|
202
|
+
parser.add_argument("--project-id", help="旺小宝项目ID(覆盖环境变量)")
|
|
203
|
+
parser.add_argument("--project-name", help="项目名称(覆盖环境变量)")
|
|
204
|
+
parser.add_argument("--month-visit-target", type=int, help="月度来访目标")
|
|
205
|
+
parser.add_argument("--month-sales-target", type=int, help="月度销售目标(万元)")
|
|
206
|
+
args = parser.parse_args()
|
|
207
|
+
|
|
208
|
+
if args.tenant_id:
|
|
209
|
+
TENANT_ID = args.tenant_id
|
|
210
|
+
if args.project_id:
|
|
211
|
+
PROJECT_ID = args.project_id
|
|
212
|
+
if args.project_name:
|
|
213
|
+
PROJECT_NAME = args.project_name
|
|
214
|
+
if args.month_visit_target is not None:
|
|
215
|
+
MONTH_VISIT_TARGET = args.month_visit_target
|
|
216
|
+
if args.month_sales_target is not None:
|
|
217
|
+
MONTH_SALES_TARGET = args.month_sales_target
|
|
218
|
+
|
|
219
|
+
main(args.date)
|