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,180 @@
|
|
|
1
|
+
"""Shared SQLite schema for insurance skills."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sqlite3
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def default_db_path() -> Path:
|
|
10
|
+
base = Path.home() / ".config" / "insurance"
|
|
11
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
12
|
+
return base / "insurance.sqlite3"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_db_path() -> Path:
|
|
16
|
+
for key in ("INSURANCE_DB_PATH", "INSURANCE_PRODUCT_DB_PATH"):
|
|
17
|
+
p = os.environ.get(key)
|
|
18
|
+
if p:
|
|
19
|
+
path = Path(p)
|
|
20
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
21
|
+
return path
|
|
22
|
+
return default_db_path()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def connect() -> sqlite3.Connection:
|
|
26
|
+
conn = sqlite3.connect(str(get_db_path()))
|
|
27
|
+
conn.row_factory = sqlite3.Row
|
|
28
|
+
conn.execute("PRAGMA foreign_keys=ON")
|
|
29
|
+
init_schema(conn)
|
|
30
|
+
return conn
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def init_schema(conn: sqlite3.Connection) -> None:
|
|
34
|
+
conn.executescript(
|
|
35
|
+
"""
|
|
36
|
+
CREATE TABLE IF NOT EXISTS customers (
|
|
37
|
+
id TEXT PRIMARY KEY,
|
|
38
|
+
name TEXT NOT NULL,
|
|
39
|
+
phone TEXT,
|
|
40
|
+
age INTEGER,
|
|
41
|
+
gender TEXT,
|
|
42
|
+
income REAL,
|
|
43
|
+
household_json TEXT NOT NULL DEFAULT '{}',
|
|
44
|
+
occupation TEXT,
|
|
45
|
+
birthday TEXT,
|
|
46
|
+
tags_json TEXT NOT NULL DEFAULT '[]',
|
|
47
|
+
fields_json TEXT NOT NULL DEFAULT '{}',
|
|
48
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
49
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
|
|
50
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
|
51
|
+
);
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_customers_phone ON customers(phone);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_customers_birthday ON customers(birthday);
|
|
54
|
+
|
|
55
|
+
CREATE TABLE IF NOT EXISTS policies (
|
|
56
|
+
id TEXT PRIMARY KEY,
|
|
57
|
+
customer_id TEXT NOT NULL REFERENCES customers(id) ON DELETE RESTRICT,
|
|
58
|
+
company TEXT NOT NULL,
|
|
59
|
+
product_name TEXT NOT NULL,
|
|
60
|
+
product_type TEXT NOT NULL,
|
|
61
|
+
coverage REAL NOT NULL DEFAULT 0,
|
|
62
|
+
premium REAL NOT NULL DEFAULT 0,
|
|
63
|
+
pay_period TEXT,
|
|
64
|
+
effective_date TEXT NOT NULL,
|
|
65
|
+
expire_date TEXT,
|
|
66
|
+
next_pay_date TEXT,
|
|
67
|
+
insured_name TEXT,
|
|
68
|
+
beneficiary TEXT,
|
|
69
|
+
notes TEXT,
|
|
70
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
71
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
|
|
72
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
|
73
|
+
);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_policies_customer ON policies(customer_id, status);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_policies_expire ON policies(expire_date, status);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_policies_next_pay ON policies(next_pay_date, status);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_policies_type ON policies(product_type, status);
|
|
78
|
+
|
|
79
|
+
CREATE TABLE IF NOT EXISTS followups (
|
|
80
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
81
|
+
customer_id TEXT NOT NULL REFERENCES customers(id) ON DELETE CASCADE,
|
|
82
|
+
content TEXT NOT NULL,
|
|
83
|
+
next_step TEXT,
|
|
84
|
+
next_date TEXT,
|
|
85
|
+
channel TEXT,
|
|
86
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
|
87
|
+
);
|
|
88
|
+
CREATE INDEX IF NOT EXISTS idx_followups_customer ON followups(customer_id, created_at DESC);
|
|
89
|
+
|
|
90
|
+
CREATE TABLE IF NOT EXISTS customer_fields (
|
|
91
|
+
name TEXT PRIMARY KEY,
|
|
92
|
+
type TEXT NOT NULL DEFAULT 'text'
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
CREATE TABLE IF NOT EXISTS products (
|
|
96
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
97
|
+
name TEXT NOT NULL UNIQUE,
|
|
98
|
+
company TEXT NOT NULL,
|
|
99
|
+
product_type TEXT NOT NULL,
|
|
100
|
+
coverage_min REAL,
|
|
101
|
+
coverage_max REAL,
|
|
102
|
+
premium_min REAL,
|
|
103
|
+
premium_max REAL,
|
|
104
|
+
age_min INTEGER,
|
|
105
|
+
age_max INTEGER,
|
|
106
|
+
waiting_period TEXT,
|
|
107
|
+
deductible TEXT,
|
|
108
|
+
renewal_terms TEXT,
|
|
109
|
+
highlights_json TEXT NOT NULL DEFAULT '[]',
|
|
110
|
+
target_audience TEXT,
|
|
111
|
+
file_path TEXT,
|
|
112
|
+
raw_text TEXT,
|
|
113
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
114
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
|
|
115
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
|
116
|
+
);
|
|
117
|
+
CREATE INDEX IF NOT EXISTS idx_products_type ON products(product_type, status);
|
|
118
|
+
CREATE INDEX IF NOT EXISTS idx_products_company ON products(company, status);
|
|
119
|
+
|
|
120
|
+
CREATE TABLE IF NOT EXISTS product_analysis_cache (
|
|
121
|
+
product_id INTEGER PRIMARY KEY REFERENCES products(id) ON DELETE CASCADE,
|
|
122
|
+
key_terms_json TEXT,
|
|
123
|
+
selling_points_json TEXT,
|
|
124
|
+
fit_audience_json TEXT,
|
|
125
|
+
pitch_script TEXT,
|
|
126
|
+
analyzed_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
CREATE TABLE IF NOT EXISTS deals (
|
|
130
|
+
id TEXT PRIMARY KEY,
|
|
131
|
+
customer_id TEXT NOT NULL,
|
|
132
|
+
customer_name TEXT,
|
|
133
|
+
product_name TEXT,
|
|
134
|
+
product_type TEXT,
|
|
135
|
+
stage TEXT NOT NULL,
|
|
136
|
+
estimated_premium REAL NOT NULL DEFAULT 0,
|
|
137
|
+
actual_premium REAL,
|
|
138
|
+
expected_close_date TEXT,
|
|
139
|
+
closed_at TEXT,
|
|
140
|
+
lost_reason TEXT,
|
|
141
|
+
lost_note TEXT,
|
|
142
|
+
note TEXT,
|
|
143
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
|
|
144
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
|
145
|
+
);
|
|
146
|
+
CREATE INDEX IF NOT EXISTS idx_deals_stage ON deals(stage, expected_close_date);
|
|
147
|
+
CREATE INDEX IF NOT EXISTS idx_deals_customer ON deals(customer_id);
|
|
148
|
+
|
|
149
|
+
CREATE TABLE IF NOT EXISTS deal_stage_log (
|
|
150
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
151
|
+
deal_id TEXT NOT NULL REFERENCES deals(id) ON DELETE CASCADE,
|
|
152
|
+
from_stage TEXT,
|
|
153
|
+
to_stage TEXT NOT NULL,
|
|
154
|
+
note TEXT,
|
|
155
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
|
156
|
+
);
|
|
157
|
+
CREATE INDEX IF NOT EXISTS idx_stage_log_deal ON deal_stage_log(deal_id, created_at);
|
|
158
|
+
|
|
159
|
+
CREATE TABLE IF NOT EXISTS monthly_targets (
|
|
160
|
+
month TEXT PRIMARY KEY,
|
|
161
|
+
target_premium REAL NOT NULL,
|
|
162
|
+
note TEXT,
|
|
163
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
|
|
164
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
CREATE TABLE IF NOT EXISTS sent_reminders (
|
|
168
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
169
|
+
reminder_type TEXT NOT NULL,
|
|
170
|
+
target_id TEXT NOT NULL,
|
|
171
|
+
bucket TEXT NOT NULL,
|
|
172
|
+
sent_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
|
|
173
|
+
status TEXT NOT NULL DEFAULT 'sent',
|
|
174
|
+
note TEXT,
|
|
175
|
+
UNIQUE (reminder_type, target_id, bucket)
|
|
176
|
+
);
|
|
177
|
+
CREATE INDEX IF NOT EXISTS idx_sent_target ON sent_reminders(target_id, reminder_type);
|
|
178
|
+
"""
|
|
179
|
+
)
|
|
180
|
+
conn.commit()
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Shared-db read helpers for insurance-product-analysis."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import sqlite3
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
|
|
8
|
+
from insurance_product_cli import db
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def gap_analysis(customer_id: str | None = None, gap_type: str | None = None,
|
|
12
|
+
all_customers: bool = False) -> dict:
|
|
13
|
+
conn = db.connect()
|
|
14
|
+
try:
|
|
15
|
+
need_types = ["重疾险", "医疗险", "意外险", "寿险", "养老险", "教育金"]
|
|
16
|
+
if customer_id:
|
|
17
|
+
row = conn.execute(
|
|
18
|
+
"SELECT id, name FROM customers WHERE id=? AND status='active'",
|
|
19
|
+
(customer_id,),
|
|
20
|
+
).fetchone()
|
|
21
|
+
if not row:
|
|
22
|
+
return {"ok": False, "error": "customer_not_found"}
|
|
23
|
+
results = [row]
|
|
24
|
+
elif all_customers or gap_type:
|
|
25
|
+
results = list(
|
|
26
|
+
conn.execute("SELECT id, name FROM customers WHERE status='active' ORDER BY id")
|
|
27
|
+
)
|
|
28
|
+
else:
|
|
29
|
+
return {"ok": False, "error": "need_customer_id_or_all"}
|
|
30
|
+
|
|
31
|
+
payload = []
|
|
32
|
+
for r in results:
|
|
33
|
+
owned = {
|
|
34
|
+
row["product_type"]
|
|
35
|
+
for row in conn.execute(
|
|
36
|
+
"SELECT DISTINCT product_type FROM policies WHERE customer_id=? AND status='active'",
|
|
37
|
+
(r["id"],),
|
|
38
|
+
)
|
|
39
|
+
if row["product_type"]
|
|
40
|
+
}
|
|
41
|
+
gaps = [t for t in need_types if t not in owned]
|
|
42
|
+
item = {"customer_id": r["id"], "name": r["name"], "owned_types": sorted(owned), "gaps": gaps}
|
|
43
|
+
if customer_id:
|
|
44
|
+
return {"ok": True, "result": item}
|
|
45
|
+
payload.append(item)
|
|
46
|
+
if gap_type:
|
|
47
|
+
payload = [x for x in payload if gap_type in x.get("gaps", [])]
|
|
48
|
+
return {"ok": True, "results": payload, "count": len(payload)}
|
|
49
|
+
finally:
|
|
50
|
+
conn.close()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def customer_query(customer_id: str | None = None, keyword: str | None = None) -> dict:
|
|
54
|
+
conn = db.connect()
|
|
55
|
+
try:
|
|
56
|
+
cur = conn.cursor()
|
|
57
|
+
if customer_id:
|
|
58
|
+
rows = list(cur.execute("SELECT * FROM customers WHERE id=? AND status='active'", (customer_id,)))
|
|
59
|
+
elif keyword:
|
|
60
|
+
kw = f"%{keyword}%"
|
|
61
|
+
rows = list(
|
|
62
|
+
cur.execute(
|
|
63
|
+
"SELECT * FROM customers WHERE status='active' AND (id LIKE ? OR name LIKE ? OR phone LIKE ?) "
|
|
64
|
+
"ORDER BY created_at DESC",
|
|
65
|
+
(kw, kw, kw),
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
rows = list(cur.execute("SELECT * FROM customers WHERE status='active' ORDER BY created_at DESC"))
|
|
70
|
+
customers: list[dict] = []
|
|
71
|
+
for row in rows:
|
|
72
|
+
d = dict(row)
|
|
73
|
+
d["policies_count"] = int(
|
|
74
|
+
conn.execute(
|
|
75
|
+
"SELECT COUNT(*) AS n FROM policies WHERE customer_id=? AND status='active'",
|
|
76
|
+
(row["id"],),
|
|
77
|
+
).fetchone()["n"]
|
|
78
|
+
)
|
|
79
|
+
last_fu = conn.execute(
|
|
80
|
+
"SELECT MAX(created_at) AS ts FROM followups WHERE customer_id=?",
|
|
81
|
+
(row["id"],),
|
|
82
|
+
).fetchone()["ts"]
|
|
83
|
+
d["last_followup_at"] = last_fu
|
|
84
|
+
for k in ("household_json", "tags_json", "fields_json"):
|
|
85
|
+
try:
|
|
86
|
+
d[k[:-5]] = json.loads(d.get(k) or "{}")
|
|
87
|
+
except json.JSONDecodeError:
|
|
88
|
+
d[k[:-5]] = {}
|
|
89
|
+
customers.append(d)
|
|
90
|
+
return {"ok": True, "customers": customers, "count": len(customers)}
|
|
91
|
+
finally:
|
|
92
|
+
conn.close()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def policy_query(customer_id: str | None = None, product_type: str | None = None) -> dict:
|
|
96
|
+
conn = db.connect()
|
|
97
|
+
try:
|
|
98
|
+
cur = conn.cursor()
|
|
99
|
+
if customer_id:
|
|
100
|
+
rows = list(
|
|
101
|
+
cur.execute(
|
|
102
|
+
"SELECT * FROM policies WHERE customer_id=? AND status='active' ORDER BY effective_date DESC",
|
|
103
|
+
(customer_id,),
|
|
104
|
+
)
|
|
105
|
+
)
|
|
106
|
+
elif product_type:
|
|
107
|
+
rows = list(
|
|
108
|
+
cur.execute(
|
|
109
|
+
"SELECT * FROM policies WHERE product_type=? AND status='active' ORDER BY effective_date DESC",
|
|
110
|
+
(product_type,),
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
else:
|
|
114
|
+
rows = list(cur.execute("SELECT * FROM policies WHERE status='active' ORDER BY effective_date DESC"))
|
|
115
|
+
return {"ok": True, "policies": [dict(r) for r in rows], "count": len(rows)}
|
|
116
|
+
finally:
|
|
117
|
+
conn.close()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def customer_segment() -> dict:
|
|
121
|
+
conn = db.connect()
|
|
122
|
+
try:
|
|
123
|
+
now = datetime.now()
|
|
124
|
+
d30 = (now - timedelta(days=30)).strftime("%Y-%m-%d %H:%M:%S")
|
|
125
|
+
d60 = (now - timedelta(days=60)).strftime("%Y-%m-%d %H:%M:%S")
|
|
126
|
+
rows = list(conn.execute("SELECT * FROM customers WHERE status='active' ORDER BY created_at DESC"))
|
|
127
|
+
hi_value: list[dict] = []
|
|
128
|
+
active: list[dict] = []
|
|
129
|
+
new: list[dict] = []
|
|
130
|
+
dormant: list[dict] = []
|
|
131
|
+
for r in rows:
|
|
132
|
+
cid = r["id"]
|
|
133
|
+
premium = float(
|
|
134
|
+
conn.execute(
|
|
135
|
+
"SELECT COALESCE(SUM(premium),0) AS s FROM policies WHERE customer_id=? AND status='active'",
|
|
136
|
+
(cid,),
|
|
137
|
+
).fetchone()["s"]
|
|
138
|
+
)
|
|
139
|
+
last_followup = conn.execute(
|
|
140
|
+
"SELECT MAX(created_at) AS ts FROM followups WHERE customer_id=?",
|
|
141
|
+
(cid,),
|
|
142
|
+
).fetchone()["ts"]
|
|
143
|
+
slot = {"customer_id": cid, "name": r["name"]}
|
|
144
|
+
if premium > 50000:
|
|
145
|
+
hi_value.append(slot)
|
|
146
|
+
if r["created_at"] and str(r["created_at"]) >= d30:
|
|
147
|
+
new.append(slot)
|
|
148
|
+
if last_followup:
|
|
149
|
+
if str(last_followup) >= d30:
|
|
150
|
+
active.append(slot)
|
|
151
|
+
elif str(last_followup) < d60:
|
|
152
|
+
dormant.append(slot)
|
|
153
|
+
elif r["created_at"] and str(r["created_at"]) < d60:
|
|
154
|
+
dormant.append(slot)
|
|
155
|
+
return {
|
|
156
|
+
"ok": True,
|
|
157
|
+
"hi_value": hi_value,
|
|
158
|
+
"active": active,
|
|
159
|
+
"new": new,
|
|
160
|
+
"dormant": dormant,
|
|
161
|
+
}
|
|
162
|
+
finally:
|
|
163
|
+
conn.close()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "insurance-sales-pipeline",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"types": ["store"],
|
|
5
|
+
"displayName": "保险销售漏斗管理",
|
|
6
|
+
"description": "保险经纪人销售漏斗:在谈/方案中/议价/已成交/已流失阶段管理,月度目标、成交预测、流失分析。",
|
|
7
|
+
"changelog": [
|
|
8
|
+
{ "version": "1.0.0", "date": "2026-05-09", "changes": ["初版 CLI:deal CRUD / stage / forecast / target-track / lost"] }
|
|
9
|
+
],
|
|
10
|
+
"createdAt": "2026-05-09",
|
|
11
|
+
"updatedAt": "2026-05-09"
|
|
12
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: insurance-sales-pipeline
|
|
3
|
+
description: "保险经纪人销售漏斗管理:商机管理、阶段推进(在谈/方案中/议价/已成交/已流失)、成交预测、月度目标跟踪、流失分析。触发:新建商机、在谈客户、推进阶段、这个月预计能出多少、目标完成、流失原因、看下数据。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 销售漏斗管理
|
|
7
|
+
|
|
8
|
+
## 经营概览("看下数据")
|
|
9
|
+
|
|
10
|
+
聚合脚本由 `insurance-customer-policy` 提供,直接读取共享库:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
python3 {baseDir}/../../insurance-customer-policy/src/scripts/dashboard_all.py --md
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
本 skill 的 `dashboard --json` 仅返回销售漏斗维度,聚合脚本并行调用全部 4 个 skill。
|
|
17
|
+
|
|
18
|
+
## 运行方式
|
|
19
|
+
|
|
20
|
+
**推荐(云端零配置 / 跨平台)**:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
python3 {baseDir}/scripts/cli.py <子命令> ...
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**高级用法**:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Linux / macOS
|
|
30
|
+
export PYTHONPATH="{baseDir}/src"
|
|
31
|
+
python3 -m insurance_pipeline_cli <子命令> ...
|
|
32
|
+
|
|
33
|
+
# Windows
|
|
34
|
+
cd "{baseDir}"
|
|
35
|
+
set PYTHONPATH=%CD%\src
|
|
36
|
+
python -m insurance_pipeline_cli <子命令> ...
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
默认数据库:
|
|
40
|
+
|
|
41
|
+
- Linux / macOS:`~/.config/insurance/insurance.sqlite3`
|
|
42
|
+
- Windows:`%USERPROFILE%\.config\insurance\insurance.sqlite3`
|
|
43
|
+
- 统一变量:`INSURANCE_DB_PATH`
|
|
44
|
+
- 兼容变量:`INSURANCE_PIPELINE_DB_PATH`
|
|
45
|
+
|
|
46
|
+
> `deal-add` 会在共享库中直接校验客户与产品是否存在,不再通过跨 skill 子进程转发;默认零配置开箱可用。
|
|
47
|
+
|
|
48
|
+
## 销售阶段定义
|
|
49
|
+
|
|
50
|
+
| 阶段 | 含义 | 成交概率 |
|
|
51
|
+
|------|------|----------|
|
|
52
|
+
| 在谈 | 已接触,了解需求,准备出方案 | 20% |
|
|
53
|
+
| 方案中 | 已出方案,客户在考虑 | 50% |
|
|
54
|
+
| 议价 | 客户基本认可,谈最终条款 | 80% |
|
|
55
|
+
| 已成交 | 保单已出 | 100% |
|
|
56
|
+
| 已流失 | 客户放弃或选了竞品 | 0% |
|
|
57
|
+
|
|
58
|
+
## 子命令
|
|
59
|
+
|
|
60
|
+
| 子命令 | 说明 |
|
|
61
|
+
|--------|------|
|
|
62
|
+
| `deal-add` | `--customer-id [--product --product-type --stage --estimated-premium --expected-close-date]` |
|
|
63
|
+
| `deal-query` | `--deal-id` / `--customer-id` / `--stage` / `--status active` |
|
|
64
|
+
| `deal-update` | `--deal-id --set JSON` |
|
|
65
|
+
| `deal-delete` | `--deal-id --yes` |
|
|
66
|
+
| `deal-stage` | `--deal-id --to <阶段> [--note --actual-premium --lost-reason --lost-note]` 自动写 `deal_stage_log` |
|
|
67
|
+
| `deal-forecast` | `--month YYYY-MM` 按 stage × 概率 × estimated 求和;分产品类型 group by |
|
|
68
|
+
| `target-track` | 写:`--month --target` ;读:`--month` 输出可视化进度卡 |
|
|
69
|
+
| `deal-lost` | 写:`--deal-id --reason <价格\|条款\|竞品\|自身\|其他> --note`;读:`--stats --month` 按原因分布 |
|
|
70
|
+
| `dashboard --json` | 经营概览(供入口聚合) |
|
|
71
|
+
|
|
72
|
+
## 示例
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
python3 {baseDir}/scripts/cli.py deal-add --customer-id C001 --product "XX重疾险" --product-type "重疾险" --stage "在谈" --estimated-premium 8000
|
|
76
|
+
python3 {baseDir}/scripts/cli.py deal-stage --deal-id D001 --to "方案中" --note "已出方案,客户正在看"
|
|
77
|
+
python3 {baseDir}/scripts/cli.py deal-stage --deal-id D001 --to "已成交" --actual-premium 9000
|
|
78
|
+
python3 {baseDir}/scripts/cli.py deal-forecast --month 2026-05
|
|
79
|
+
python3 {baseDir}/scripts/cli.py target-track --month 2026-05 --target 100000
|
|
80
|
+
python3 {baseDir}/scripts/cli.py deal-lost --deal-id D002 --reason 价格 --note "客户选了B公司,比我们便宜15%"
|
|
81
|
+
python3 {baseDir}/scripts/cli.py deal-lost --stats --month 2026-05
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 自然语言路由
|
|
85
|
+
|
|
86
|
+
| 用户说 | 调用命令 |
|
|
87
|
+
|--------|----------|
|
|
88
|
+
| "张先生想了解重疾险,新建一个商机" | `deal-add` |
|
|
89
|
+
| "现在有哪些在谈的客户?" | `deal-query --status active` |
|
|
90
|
+
| "张先生已经出方案了" | `deal-stage --to 方案中` |
|
|
91
|
+
| "这个月预计能出多少?" | `deal-forecast` |
|
|
92
|
+
| "这个月目标完成了多少?" | `target-track` |
|
|
93
|
+
| "设置本月目标 10 万" | `target-track --target 100000` |
|
|
94
|
+
| "张先生没签,记录一下" | `deal-lost` |
|
|
95
|
+
| "看下数据" | `dashboard --json`(同时聚合其他 3 个保险 skill 的 dashboard) |
|
|
96
|
+
|
|
97
|
+
## 与其他 Skill 协作
|
|
98
|
+
|
|
99
|
+
- **`insurance-customer-policy`**:deal 关联客户档案,`deal-add` 自动校验客户存在并填充 customer_name。
|
|
100
|
+
- **`insurance-product-analysis`**:可选关联产品;`customer-match` 的结果可作为 `deal-add` 的输入。
|
|
101
|
+
- **`insurance-schedule-renewal`**:商机推进 / 流失后,跟进提醒可基于 `followup-add`(在 customer-policy 侧)继续推进。
|
|
102
|
+
- 阶段推进话术建议参考 `insurance-sales-playbook` 的「三段式回应 / 7 天跟进节奏 / 异议处理卡」。
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cloud-friendly entrypoint for insurance-sales-pipeline."""
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
SCRIPTS_DIR = Path(__file__).resolve().parent
|
|
9
|
+
if str(SCRIPTS_DIR) not in sys.path:
|
|
10
|
+
sys.path.insert(0, str(SCRIPTS_DIR))
|
|
11
|
+
|
|
12
|
+
from insurance_pipeline_cli.cli import main
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
File without changes
|