sophhub 0.4.21 → 0.4.22
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/package.json +1 -1
- package/skills/insurance-customer-policy/skill.json +12 -0
- package/skills/insurance-customer-policy/src/SKILL.md +121 -0
- package/skills/insurance-customer-policy/src/pyproject.toml +9 -0
- package/skills/insurance-customer-policy/src/scripts/cli.py +16 -0
- package/skills/insurance-customer-policy/src/scripts/cloud_insurance_full_test.py +785 -0
- package/skills/insurance-customer-policy/src/scripts/dashboard_all.py +205 -0
- package/skills/insurance-customer-policy/src/scripts/insurance_customer_cli/__init__.py +0 -0
- package/skills/insurance-customer-policy/src/scripts/insurance_customer_cli/__main__.py +4 -0
- package/skills/insurance-customer-policy/src/scripts/insurance_customer_cli/cli.py +816 -0
- package/skills/insurance-customer-policy/src/scripts/insurance_customer_cli/db.py +181 -0
- package/skills/insurance-customer-policy/src/scripts/insurance_customer_cli/insurance_paths.py +184 -0
- package/skills/insurance-customer-policy/src/scripts/run_e2e_smoke.py +164 -0
- package/skills/insurance-customer-policy/src/scripts/test_cloud_zero_config.py +217 -0
- package/skills/insurance-customer-policy/src/scripts/test_dashboard_all.py +113 -0
- package/skills/insurance-customer-policy/src/src/insurance_customer_cli/__init__.py +0 -0
- package/skills/insurance-customer-policy/src/src/insurance_customer_cli/__main__.py +4 -0
- package/skills/insurance-customer-policy/src/src/insurance_customer_cli/cli.py +816 -0
- package/skills/insurance-customer-policy/src/src/insurance_customer_cli/db.py +181 -0
- package/skills/insurance-customer-policy/src/src/insurance_customer_cli/insurance_paths.py +184 -0
- package/skills/insurance-product-analysis/skill.json +12 -0
- package/skills/insurance-product-analysis/src/SKILL.md +99 -0
- package/skills/insurance-product-analysis/src/pyproject.toml +9 -0
- package/skills/insurance-product-analysis/src/scripts/cli.py +16 -0
- package/skills/insurance-product-analysis/src/scripts/insurance_product_cli/__init__.py +0 -0
- package/skills/insurance-product-analysis/src/scripts/insurance_product_cli/__main__.py +4 -0
- package/skills/insurance-product-analysis/src/scripts/insurance_product_cli/cli.py +545 -0
- package/skills/insurance-product-analysis/src/scripts/insurance_product_cli/db.py +180 -0
- package/skills/insurance-product-analysis/src/scripts/insurance_product_cli/orchestrator.py +163 -0
- package/skills/insurance-product-analysis/src/scripts/run_e2e_smoke.py +202 -0
- package/skills/insurance-product-analysis/src/src/insurance_product_cli/__init__.py +0 -0
- package/skills/insurance-product-analysis/src/src/insurance_product_cli/__main__.py +4 -0
- package/skills/insurance-product-analysis/src/src/insurance_product_cli/cli.py +545 -0
- package/skills/insurance-product-analysis/src/src/insurance_product_cli/db.py +180 -0
- package/skills/insurance-product-analysis/src/src/insurance_product_cli/orchestrator.py +163 -0
- package/skills/insurance-sales-pipeline/skill.json +12 -0
- package/skills/insurance-sales-pipeline/src/SKILL.md +102 -0
- package/skills/insurance-sales-pipeline/src/pyproject.toml +9 -0
- package/skills/insurance-sales-pipeline/src/scripts/cli.py +16 -0
- package/skills/insurance-sales-pipeline/src/scripts/insurance_pipeline_cli/__init__.py +0 -0
- package/skills/insurance-sales-pipeline/src/scripts/insurance_pipeline_cli/__main__.py +4 -0
- package/skills/insurance-sales-pipeline/src/scripts/insurance_pipeline_cli/cli.py +496 -0
- package/skills/insurance-sales-pipeline/src/scripts/insurance_pipeline_cli/db.py +180 -0
- package/skills/insurance-sales-pipeline/src/scripts/insurance_pipeline_cli/orchestrator.py +36 -0
- package/skills/insurance-sales-pipeline/src/scripts/run_e2e_smoke.py +208 -0
- package/skills/insurance-sales-pipeline/src/src/insurance_pipeline_cli/__init__.py +0 -0
- package/skills/insurance-sales-pipeline/src/src/insurance_pipeline_cli/__main__.py +4 -0
- package/skills/insurance-sales-pipeline/src/src/insurance_pipeline_cli/cli.py +496 -0
- package/skills/insurance-sales-pipeline/src/src/insurance_pipeline_cli/db.py +180 -0
- package/skills/insurance-sales-pipeline/src/src/insurance_pipeline_cli/orchestrator.py +36 -0
- package/skills/insurance-schedule-renewal/skill.json +12 -0
- package/skills/insurance-schedule-renewal/src/SKILL.md +94 -0
- package/skills/insurance-schedule-renewal/src/pyproject.toml +9 -0
- package/skills/insurance-schedule-renewal/src/scripts/cli.py +16 -0
- package/skills/insurance-schedule-renewal/src/scripts/insurance_schedule_cli/__init__.py +0 -0
- package/skills/insurance-schedule-renewal/src/scripts/insurance_schedule_cli/__main__.py +4 -0
- package/skills/insurance-schedule-renewal/src/scripts/insurance_schedule_cli/cli.py +429 -0
- package/skills/insurance-schedule-renewal/src/scripts/insurance_schedule_cli/db.py +180 -0
- package/skills/insurance-schedule-renewal/src/scripts/insurance_schedule_cli/orchestrator.py +94 -0
- package/skills/insurance-schedule-renewal/src/scripts/run_e2e_smoke.py +218 -0
- package/skills/insurance-schedule-renewal/src/src/insurance_schedule_cli/__init__.py +0 -0
- package/skills/insurance-schedule-renewal/src/src/insurance_schedule_cli/__main__.py +4 -0
- package/skills/insurance-schedule-renewal/src/src/insurance_schedule_cli/cli.py +429 -0
- package/skills/insurance-schedule-renewal/src/src/insurance_schedule_cli/db.py +180 -0
- package/skills/insurance-schedule-renewal/src/src/insurance_schedule_cli/orchestrator.py +94 -0
- package/skills/insurance-shared-data/skill.json +20 -0
- package/skills/insurance-shared-data/src/SKILL.md +33 -0
- package/skills/insurance-shared-data/src/scripts/cloud_insurance_super_test.py +246 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cloud-style zero-configuration smoke test.
|
|
3
|
+
|
|
4
|
+
Simulates the cloud agent invoking the 4 insurance skills with **only**
|
|
5
|
+
the wrapper script paths documented in each ``SKILL.md`` — no PYTHONPATH,
|
|
6
|
+
no skill-specific env vars, an unrelated CWD, and a clean ``os.environ``
|
|
7
|
+
that explicitly drops ``PYTHONPATH`` if present.
|
|
8
|
+
|
|
9
|
+
Each skill is exercised with one representative read + one write to
|
|
10
|
+
prove the SQLite path defaults work without any setup, then
|
|
11
|
+
``dashboard_all.py`` is run to verify cross-skill aggregation works
|
|
12
|
+
through the wrappers.
|
|
13
|
+
|
|
14
|
+
Usage::
|
|
15
|
+
|
|
16
|
+
python3 skills/insurance-customer-policy/src/scripts/test_cloud_zero_config.py
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import subprocess
|
|
23
|
+
import sys
|
|
24
|
+
import tempfile
|
|
25
|
+
import time
|
|
26
|
+
from pathlib import Path
|
|
27
|
+
|
|
28
|
+
HERE = Path(__file__).resolve().parent # .../insurance-customer-policy/src/scripts
|
|
29
|
+
SKILLS_ROOT = HERE.parent.parent.parent # .../skills
|
|
30
|
+
|
|
31
|
+
CUSTOMER_CLI = SKILLS_ROOT / "insurance-customer-policy" / "src" / "scripts" / "cli.py"
|
|
32
|
+
PRODUCT_CLI = SKILLS_ROOT / "insurance-product-analysis" / "src" / "scripts" / "cli.py"
|
|
33
|
+
SCHEDULE_CLI = SKILLS_ROOT / "insurance-schedule-renewal" / "src" / "scripts" / "cli.py"
|
|
34
|
+
PIPELINE_CLI = SKILLS_ROOT / "insurance-sales-pipeline" / "src" / "scripts" / "cli.py"
|
|
35
|
+
DASHBOARD_ALL = HERE / "dashboard_all.py"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _clean_env(extra: dict[str, str] | None = None) -> dict[str, str]:
|
|
39
|
+
"""Build a minimal env that mimics cloud: no PYTHONPATH, only required vars."""
|
|
40
|
+
env: dict[str, str] = {}
|
|
41
|
+
for var in ("PATH", "SystemRoot", "TEMP", "TMP", "HOME", "USERPROFILE", "windir", "ComSpec"):
|
|
42
|
+
if var in os.environ:
|
|
43
|
+
env[var] = os.environ[var]
|
|
44
|
+
env["PYTHONIOENCODING"] = "utf-8"
|
|
45
|
+
if extra:
|
|
46
|
+
env.update(extra)
|
|
47
|
+
return env
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def run(cli: Path, args: list[str], extra_env: dict[str, str] | None = None,
|
|
51
|
+
cwd: Path | None = None) -> dict:
|
|
52
|
+
"""Run wrapper CLI with a clean env and arbitrary CWD; return parsed JSON."""
|
|
53
|
+
env = _clean_env(extra_env)
|
|
54
|
+
r = subprocess.run(
|
|
55
|
+
[sys.executable, str(cli), *args],
|
|
56
|
+
cwd=str(cwd) if cwd else None,
|
|
57
|
+
env=env,
|
|
58
|
+
capture_output=True,
|
|
59
|
+
text=True,
|
|
60
|
+
encoding="utf-8",
|
|
61
|
+
timeout=60,
|
|
62
|
+
)
|
|
63
|
+
if r.returncode not in (0, 1):
|
|
64
|
+
raise AssertionError(
|
|
65
|
+
f"\n{cli.name} {' '.join(args)} crashed (rc={r.returncode})\n"
|
|
66
|
+
f"STDOUT:\n{r.stdout}\nSTDERR:\n{r.stderr}"
|
|
67
|
+
)
|
|
68
|
+
out = (r.stdout or "").strip()
|
|
69
|
+
assert out, f"{cli.name} {' '.join(args)} produced no stdout. STDERR:\n{r.stderr}"
|
|
70
|
+
try:
|
|
71
|
+
return json.loads(out)
|
|
72
|
+
except json.JSONDecodeError as e:
|
|
73
|
+
raise AssertionError(f"{cli.name} {' '.join(args)} bad JSON: {e}\nSTDOUT:\n{out}")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main() -> int:
|
|
77
|
+
print(f"[cloud-smoke] starting; CWD will be a tempdir; env will drop PYTHONPATH")
|
|
78
|
+
print(f"[cloud-smoke] CUSTOMER_CLI = {CUSTOMER_CLI}")
|
|
79
|
+
|
|
80
|
+
assert CUSTOMER_CLI.is_file(), f"missing wrapper: {CUSTOMER_CLI}"
|
|
81
|
+
assert PRODUCT_CLI.is_file(), f"missing wrapper: {PRODUCT_CLI}"
|
|
82
|
+
assert SCHEDULE_CLI.is_file(), f"missing wrapper: {SCHEDULE_CLI}"
|
|
83
|
+
assert PIPELINE_CLI.is_file(), f"missing wrapper: {PIPELINE_CLI}"
|
|
84
|
+
assert DASHBOARD_ALL.is_file(), f"missing aggregator: {DASHBOARD_ALL}"
|
|
85
|
+
|
|
86
|
+
with tempfile.TemporaryDirectory() as raw:
|
|
87
|
+
tmp = Path(raw)
|
|
88
|
+
(tmp / "data").mkdir()
|
|
89
|
+
|
|
90
|
+
# Use isolated shared DB under the tempdir so the test never touches
|
|
91
|
+
# the user's real ~/.config dirs. 四个 skill 共用一库。
|
|
92
|
+
ts = int(time.time() * 1000)
|
|
93
|
+
shared_db = tmp / "data" / f"insurance-{ts}.sqlite3"
|
|
94
|
+
|
|
95
|
+
env_dbs = {
|
|
96
|
+
"INSURANCE_DB_PATH": str(shared_db),
|
|
97
|
+
"INSURANCE_CUSTOMER_DB_PATH": str(shared_db),
|
|
98
|
+
"INSURANCE_PRODUCT_DB_PATH": str(shared_db),
|
|
99
|
+
"INSURANCE_SCHEDULE_DB_PATH": str(shared_db),
|
|
100
|
+
"INSURANCE_PIPELINE_DB_PATH": str(shared_db),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# --- 1. customer skill: zero-config write + read ---
|
|
104
|
+
print("\n[1/6] customer skill via wrapper (cwd=tempdir, no PYTHONPATH)...")
|
|
105
|
+
d = run(CUSTOMER_CLI,
|
|
106
|
+
["customer-add", "--name", "云客户", "--phone", "13900000001",
|
|
107
|
+
"--age", "35", "--income", "300000"],
|
|
108
|
+
extra_env=env_dbs, cwd=tmp)
|
|
109
|
+
assert d.get("ok"), f"customer-add failed: {d}"
|
|
110
|
+
cust_id = d.get("id") or d.get("customer_id")
|
|
111
|
+
assert cust_id, f"customer-add missing id field: {d}"
|
|
112
|
+
d = run(CUSTOMER_CLI, ["customer-query", "--customer-id", cust_id],
|
|
113
|
+
extra_env=env_dbs, cwd=tmp)
|
|
114
|
+
assert d.get("ok") and d.get("count", 0) >= 1, d
|
|
115
|
+
assert d["customers"][0]["name"] == "云客户", d
|
|
116
|
+
print(f" ok customer={cust_id}")
|
|
117
|
+
|
|
118
|
+
# --- 2. customer dashboard ---
|
|
119
|
+
d = run(CUSTOMER_CLI, ["dashboard", "--json"], extra_env=env_dbs, cwd=tmp)
|
|
120
|
+
assert d.get("ok") and d.get("customers_total", 0) >= 1, d
|
|
121
|
+
print(f" ok dashboard customers_total={d['customers_total']}")
|
|
122
|
+
|
|
123
|
+
# --- 3. product skill: zero-config write + read ---
|
|
124
|
+
print("\n[2/6] product skill via wrapper...")
|
|
125
|
+
d = run(PRODUCT_CLI,
|
|
126
|
+
["product-add", "--name", "云重疾", "--company", "云保险",
|
|
127
|
+
"--type", "重疾险", "--coverage-range", "10万-100万",
|
|
128
|
+
"--premium-range", "3000-15000", "--age-range", "0-55岁"],
|
|
129
|
+
extra_env=env_dbs, cwd=tmp)
|
|
130
|
+
assert d.get("ok"), f"product-add failed: {d}"
|
|
131
|
+
# 共享库模式下,gap-analysis 直接查共享库中的客户/保单数据。
|
|
132
|
+
d = run(PRODUCT_CLI,
|
|
133
|
+
["gap-analysis", "--customer-id", cust_id],
|
|
134
|
+
extra_env=env_dbs, cwd=tmp)
|
|
135
|
+
assert d.get("ok"), f"product gap-analysis failed: {d}"
|
|
136
|
+
print(f" ok product-add + gap-analysis")
|
|
137
|
+
|
|
138
|
+
# --- 4. schedule skill ---
|
|
139
|
+
print("\n[3/6] schedule skill via wrapper...")
|
|
140
|
+
d = run(SCHEDULE_CLI, ["renewal-query", "--days", "30"],
|
|
141
|
+
extra_env=env_dbs, cwd=tmp)
|
|
142
|
+
assert d.get("ok"), f"schedule renewal-query failed: {d}"
|
|
143
|
+
d = run(SCHEDULE_CLI, ["dashboard", "--json"],
|
|
144
|
+
extra_env=env_dbs, cwd=tmp)
|
|
145
|
+
assert d.get("ok"), f"schedule dashboard failed: {d}"
|
|
146
|
+
print(f" ok schedule renewal-query + dashboard")
|
|
147
|
+
|
|
148
|
+
# --- 5. pipeline skill ---
|
|
149
|
+
print("\n[4/6] pipeline skill via wrapper...")
|
|
150
|
+
d = run(PIPELINE_CLI,
|
|
151
|
+
["deal-add", "--customer-id", cust_id, "--product", "云重疾",
|
|
152
|
+
"--product-type", "重疾险", "--stage", "在谈",
|
|
153
|
+
"--estimated-premium", "8000"],
|
|
154
|
+
extra_env=env_dbs, cwd=tmp)
|
|
155
|
+
assert d.get("ok"), f"deal-add failed: {d}"
|
|
156
|
+
d = run(PIPELINE_CLI, ["dashboard", "--json"], extra_env=env_dbs, cwd=tmp)
|
|
157
|
+
assert d.get("ok"), f"pipeline dashboard failed: {d}"
|
|
158
|
+
print(f" ok pipeline deal-add + dashboard")
|
|
159
|
+
|
|
160
|
+
# --- 6. cross-skill aggregation via dashboard_all.py ---
|
|
161
|
+
print("\n[5/6] dashboard_all aggregator via direct path...")
|
|
162
|
+
env = _clean_env(env_dbs)
|
|
163
|
+
r = subprocess.run(
|
|
164
|
+
[sys.executable, str(DASHBOARD_ALL)],
|
|
165
|
+
cwd=str(tmp),
|
|
166
|
+
env=env,
|
|
167
|
+
capture_output=True,
|
|
168
|
+
text=True,
|
|
169
|
+
encoding="utf-8",
|
|
170
|
+
timeout=60,
|
|
171
|
+
)
|
|
172
|
+
assert r.returncode == 0, f"dashboard_all rc={r.returncode}\nSTDERR:\n{r.stderr}"
|
|
173
|
+
agg = json.loads(r.stdout)
|
|
174
|
+
assert agg.get("ok"), f"dashboard_all not ok: {agg}"
|
|
175
|
+
assert agg["overview"]["customers_total"] >= 1, agg["overview"]
|
|
176
|
+
assert agg["overview"]["products_total"] >= 1, agg["overview"]
|
|
177
|
+
# 兼容旧版/新版聚合结构:
|
|
178
|
+
# - 旧版 skills: {skill_name: {ok: true, ...}}
|
|
179
|
+
# - 新版 skills: {"source": "shared-db"}
|
|
180
|
+
if isinstance(agg.get("skills"), dict):
|
|
181
|
+
for skill_name, skill_res in agg["skills"].items():
|
|
182
|
+
if isinstance(skill_res, dict):
|
|
183
|
+
assert skill_res.get("ok"), f"{skill_name} sub-result not ok: {skill_res}"
|
|
184
|
+
print(f" ok aggregator: {agg['overview']}")
|
|
185
|
+
|
|
186
|
+
# --- 7. verify default DB path (no env) creates dir under ~/.config ---
|
|
187
|
+
print("\n[6/6] default DB path probe (no env vars)...")
|
|
188
|
+
# Use a customized HOME so we don't pollute the real one.
|
|
189
|
+
fake_home = tmp / "fake_home"
|
|
190
|
+
fake_home.mkdir()
|
|
191
|
+
env_no_db = _clean_env()
|
|
192
|
+
if sys.platform == "win32":
|
|
193
|
+
env_no_db["USERPROFILE"] = str(fake_home)
|
|
194
|
+
env_no_db["HOME"] = str(fake_home)
|
|
195
|
+
r = subprocess.run(
|
|
196
|
+
[sys.executable, str(CUSTOMER_CLI), "customer-query"],
|
|
197
|
+
cwd=str(tmp),
|
|
198
|
+
env=env_no_db,
|
|
199
|
+
capture_output=True,
|
|
200
|
+
text=True,
|
|
201
|
+
encoding="utf-8",
|
|
202
|
+
timeout=30,
|
|
203
|
+
)
|
|
204
|
+
assert r.returncode == 0, f"default DB customer-query failed:\n{r.stderr}"
|
|
205
|
+
out = json.loads(r.stdout)
|
|
206
|
+
assert out.get("ok"), out
|
|
207
|
+
# Default path must have been created under fake_home/.config
|
|
208
|
+
expected_dir = fake_home / ".config" / "insurance"
|
|
209
|
+
assert expected_dir.is_dir(), f"default DB dir not created: {expected_dir}"
|
|
210
|
+
print(f" ok default DB dir auto-created at {expected_dir}")
|
|
211
|
+
|
|
212
|
+
print("\nAll cloud-style zero-configuration smoke tests passed.")
|
|
213
|
+
return 0
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Smoke test for dashboard_all.py — seeds tiny data into all 4 skill DBs
|
|
3
|
+
and verifies the aggregated card.
|
|
4
|
+
|
|
5
|
+
Run from anywhere::
|
|
6
|
+
|
|
7
|
+
python3 path/to/insurance-customer-policy/src/scripts/test_dashboard_all.py
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
from datetime import datetime, timedelta
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
HERE = Path(__file__).resolve().parent
|
|
20
|
+
SKILLS_ROOT = HERE.parent.parent.parent
|
|
21
|
+
|
|
22
|
+
CUSTOMER_PKG = SKILLS_ROOT / "insurance-customer-policy" / "src" / "src"
|
|
23
|
+
PRODUCT_PKG = SKILLS_ROOT / "insurance-product-analysis" / "src" / "src"
|
|
24
|
+
SCHEDULE_PKG = SKILLS_ROOT / "insurance-schedule-renewal" / "src" / "src"
|
|
25
|
+
PIPELINE_PKG = SKILLS_ROOT / "insurance-sales-pipeline" / "src" / "src"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def call(pkg: Path, module: str, *args: str) -> dict:
|
|
29
|
+
env = {**os.environ, "PYTHONPATH": str(pkg), "PYTHONIOENCODING": "utf-8"}
|
|
30
|
+
r = subprocess.run([sys.executable, "-m", module, *args],
|
|
31
|
+
env=env, capture_output=True, text=True,
|
|
32
|
+
encoding="utf-8", timeout=60)
|
|
33
|
+
if r.returncode not in (0, 1):
|
|
34
|
+
raise SystemExit(f"rc={r.returncode} module={module} args={args}\n{r.stdout}\n{r.stderr}")
|
|
35
|
+
return json.loads(r.stdout.strip() or "{}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main() -> int:
|
|
39
|
+
tmp = tempfile.mkdtemp(prefix="ins_dash_smoke_")
|
|
40
|
+
os.environ["INSURANCE_CUSTOMER_DB_PATH"] = str(Path(tmp) / "customer.sqlite3")
|
|
41
|
+
os.environ["INSURANCE_PRODUCT_DB_PATH"] = str(Path(tmp) / "product.sqlite3")
|
|
42
|
+
os.environ["INSURANCE_SCHEDULE_DB_PATH"] = str(Path(tmp) / "reminders.sqlite3")
|
|
43
|
+
os.environ["INSURANCE_PIPELINE_DB_PATH"] = str(Path(tmp) / "pipeline.sqlite3")
|
|
44
|
+
|
|
45
|
+
today = datetime.now()
|
|
46
|
+
in_15 = (today + timedelta(days=15)).strftime("%Y-%m-%d")
|
|
47
|
+
|
|
48
|
+
print("[seed] customers + policies")
|
|
49
|
+
for i, (name, age, income) in enumerate([("张先生", 35, 300000),
|
|
50
|
+
("李女士", 40, 100000),
|
|
51
|
+
("王先生", 50, 600000)], 1):
|
|
52
|
+
call(CUSTOMER_PKG, "insurance_customer_cli",
|
|
53
|
+
"customer-add", "--name", name, "--phone", f"138{i:08d}",
|
|
54
|
+
"--age", str(age), "--income", str(income))
|
|
55
|
+
call(CUSTOMER_PKG, "insurance_customer_cli",
|
|
56
|
+
"policy-add", "--customer-id", "C001", "--company", "A", "--product", "重疾",
|
|
57
|
+
"--type", "重疾险", "--coverage", "500000", "--premium", "8000",
|
|
58
|
+
"--effective-date", "2024-01-01", "--expire-date", in_15)
|
|
59
|
+
call(CUSTOMER_PKG, "insurance_customer_cli",
|
|
60
|
+
"policy-add", "--customer-id", "C003", "--company", "B", "--product", "年金",
|
|
61
|
+
"--type", "养老险", "--coverage", "1000000", "--premium", "60000",
|
|
62
|
+
"--effective-date", "2024-01-01")
|
|
63
|
+
|
|
64
|
+
print("[seed] product")
|
|
65
|
+
call(PRODUCT_PKG, "insurance_product_cli",
|
|
66
|
+
"product-add", "--name", "XX重疾2024", "--company", "A", "--type", "重疾险")
|
|
67
|
+
|
|
68
|
+
print("[seed] deal + target")
|
|
69
|
+
call(PIPELINE_PKG, "insurance_pipeline_cli",
|
|
70
|
+
"deal-add", "--customer-id", "C001", "--product-type", "重疾险",
|
|
71
|
+
"--stage", "在谈", "--estimated-premium", "8000")
|
|
72
|
+
month = today.strftime("%Y-%m")
|
|
73
|
+
call(PIPELINE_PKG, "insurance_pipeline_cli",
|
|
74
|
+
"target-track", "--month", month, "--target", "100000")
|
|
75
|
+
|
|
76
|
+
print("[run] dashboard_all.py --json")
|
|
77
|
+
r = subprocess.run([sys.executable, str(HERE / "dashboard_all.py")],
|
|
78
|
+
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
|
|
79
|
+
capture_output=True, text=True, encoding="utf-8", timeout=60)
|
|
80
|
+
if r.returncode != 0:
|
|
81
|
+
raise SystemExit(f"dashboard_all.py rc={r.returncode}\n{r.stdout}\n{r.stderr}")
|
|
82
|
+
data = json.loads(r.stdout)
|
|
83
|
+
assert data["ok"], data
|
|
84
|
+
ov = data["overview"]
|
|
85
|
+
print(f" overview customers={ov['customers_total']} policies={ov['policies_total']} "
|
|
86
|
+
f"premium={ov['total_premium']} products={ov['products_total']} renewal_30d={ov['renewal_30d']} "
|
|
87
|
+
f"active_deals={ov['active_deals']} target={ov['this_month_target']}")
|
|
88
|
+
assert ov["customers_total"] == 3, ov
|
|
89
|
+
assert ov["policies_total"] == 2, ov
|
|
90
|
+
assert ov["total_premium"] == 68000.0, ov
|
|
91
|
+
assert ov["products_total"] == 1, ov
|
|
92
|
+
assert ov["renewal_30d"] >= 1, ov
|
|
93
|
+
assert ov["active_deals"] >= 1, ov
|
|
94
|
+
assert ov["this_month_target"] == 100000, ov
|
|
95
|
+
assert ov["hi_value"] == 1, ov # C003 premium=60000 > 50000
|
|
96
|
+
# markdown sanity
|
|
97
|
+
assert "经营概览" in data["markdown"]
|
|
98
|
+
assert "客户:3" in data["markdown"]
|
|
99
|
+
|
|
100
|
+
print("[run] dashboard_all.py --md")
|
|
101
|
+
r2 = subprocess.run([sys.executable, str(HERE / "dashboard_all.py"), "--md"],
|
|
102
|
+
env={**os.environ, "PYTHONIOENCODING": "utf-8"},
|
|
103
|
+
capture_output=True, text=True, encoding="utf-8", timeout=60)
|
|
104
|
+
assert r2.returncode == 0
|
|
105
|
+
assert "经营概览" in r2.stdout
|
|
106
|
+
assert "保单" in r2.stdout
|
|
107
|
+
|
|
108
|
+
print("ALL OK")
|
|
109
|
+
return 0
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
raise SystemExit(main())
|
|
File without changes
|