117 lines
5.1 KiB
Plaintext
117 lines
5.1 KiB
Plaintext
# opp_mining_clusters.dspy - 挖掘批次的类别排名(三级下钻第二级)
|
||
# 入参:batch_id。可见性规则源 = opp_mining_flow.list_clusters(含批次项目/org 校验)。
|
||
# 整行点击 → opp_mining_items.dspy 弹出类内需求明细(第三级,每条带来源URL)。
|
||
# 弹窗容器 id=opp_mine_clusters_pw(与列表页下钻脚本约定一致)。
|
||
|
||
import json as _json
|
||
|
||
uid = await get_user()
|
||
if not uid:
|
||
return {"widgettype": "Message", "options": {"title": "未登录", "message": "请先登录"}}
|
||
|
||
batch_id = ((params_kw or {}).get("batch_id") or "").strip()
|
||
if not batch_id:
|
||
return {"widgettype": "Message", "options": {"title": "缺少参数", "message": "缺少 batch_id"}}
|
||
|
||
dbname = get_module_dbname('pipeline-opportunity')
|
||
|
||
session_id = (params_kw or {}).get('session_id', '') or ''
|
||
pipeline_id = (params_kw or {}).get('pipeline_id', '') or 'opportunity_general'
|
||
_cur_pid = ''
|
||
_uorg = ''
|
||
try:
|
||
from pipeline_service.workspace import get_session_project_id
|
||
async with DBPools().sqlorContext(dbname) as sor:
|
||
_cur_pid = await get_session_project_id(sor, uid, session_id, pipeline_id) or ''
|
||
_urecs = await sor.sqlExe("SELECT orgid FROM users WHERE id=${u}$", {"u": uid})
|
||
await sor.sqlExe("COMMIT", {})
|
||
if _urecs:
|
||
_uorg = str(getattr(_urecs[0], "orgid", "") or "")
|
||
except Exception:
|
||
pass
|
||
|
||
_ctx = {"project_id": _cur_pid, "user_id": uid, "org_id": _uorg,
|
||
"pipeline_id": pipeline_id, "session_id": session_id}
|
||
|
||
async with DBPools().sqlorContext(dbname) as sor:
|
||
from pipeline_opportunity.opp_mining_flow import list_clusters
|
||
_ok, _res = await list_clusters(sor, _ctx, batch_id)
|
||
await sor.sqlExe("COMMIT", {})
|
||
|
||
items_url = entire_url("/pipeline-opportunity/api/opp_mining_items.dspy")
|
||
|
||
rows = []
|
||
if not _ok:
|
||
rows.append({"widgettype": "Text", "options": {
|
||
"text": str(_res)[:200], "color": "#dc2626", "padding": "12px", "halign": "left"}})
|
||
else:
|
||
clusters = _res if isinstance(_res, list) else []
|
||
if not clusters:
|
||
rows.append({"widgettype": "Text", "options": {
|
||
"text": "该批次没有聚类结果(可能范围内无需求数据)",
|
||
"color": "#64748b", "padding": "12px", "halign": "left"}})
|
||
for c in clusters:
|
||
cid = str(c.get("id") or "")
|
||
name = str(c.get("name") or "未命名")
|
||
cnt = int(c.get("doc_count") or 0)
|
||
share = float(c.get("share") or 0) * 100
|
||
rank = int(c.get("heat_rank") or 0)
|
||
try:
|
||
ev = _json.loads(c.get("naming_evidence") or "{}")
|
||
except Exception:
|
||
ev = {}
|
||
samples = ev.get("samples") or []
|
||
ev_txt = ("命名依据:" + " / ".join(str(s)[:24] for s in samples[:3])) if samples else ""
|
||
by_txt = "(规则命名)" if ev.get("by") == "rule" else ""
|
||
|
||
q_extra = []
|
||
if session_id:
|
||
q_extra.append("session_id=" + session_id)
|
||
if pipeline_id:
|
||
q_extra.append("pipeline_id=" + pipeline_id)
|
||
qstr = ("&" + "&".join(q_extra)) if q_extra else ""
|
||
script = ("var old=bricks.getWidgetById('opp_mine_items_pw',bricks.app);"
|
||
"if(old&&old.destroy){old.destroy();}"
|
||
"var r=await fetch(" + _json.dumps(items_url) + "+'?cluster_id='+encodeURIComponent(" + _json.dumps(cid) + ")+" + _json.dumps(qstr) + ");"
|
||
"var d=await r.json();if(d){bricks.widgetBuild(d,bricks.app);}")
|
||
rows.append({
|
||
"widgettype": "HBox",
|
||
"options": {"width": "100%", "gap": "10px", "alignItems": "center",
|
||
"padding": "10px 14px", "border": "1px solid #e2e8f0",
|
||
"borderRadius": "8px", "margin": "0 0 8px 0", "bgcolor": "#ffffff",
|
||
"cursor": "pointer"},
|
||
"binds": [{"wid": "self", "event": "click", "actiontype": "script",
|
||
"target": "self", "script": script}],
|
||
"subwidgets": [
|
||
{"widgettype": "Text", "options": {"text": "#%d" % rank, "cfontsize": 0.85,
|
||
"color": "#94a3b8"}},
|
||
{"widgettype": "VBox", "options": {"gap": "2px"}, "subwidgets": [
|
||
{"widgettype": "Text", "options": {"text": name + by_txt, "cfontsize": 1.0,
|
||
"color": "#0f172a", "halign": "left"}},
|
||
{"widgettype": "Text", "options": {"text": ev_txt, "cfontsize": 0.72,
|
||
"color": "#94a3b8", "halign": "left",
|
||
"wrap": True}},
|
||
]},
|
||
{"widgettype": "Filler"},
|
||
{"widgettype": "Text", "options": {"text": "%d 条 | %.1f%%" % (cnt, share),
|
||
"cfontsize": 0.85, "color": "#64748b"}},
|
||
{"widgettype": "Text", "options": {"text": "›", "cfontsize": 1.1, "color": "#94a3b8"}},
|
||
]})
|
||
|
||
return {
|
||
"id": "opp_mine_clusters_pw",
|
||
"widgettype": "PopupWindow",
|
||
"options": {"title": "类别排名(批次 %s)" % batch_id[:8], "width": "640px",
|
||
"height": "70%", "auto_open": True, "resizable": True},
|
||
"subwidgets": [{
|
||
"widgettype": "VBox",
|
||
"options": {"width": "100%", "height": "100%", "padding": "12px 16px",
|
||
"gap": "4px", "overflow": "auto"},
|
||
"subwidgets": [
|
||
{"widgettype": "Text", "options": {
|
||
"text": "点击任一类别,查看类内需求明细(每条带来源链接可查证)。",
|
||
"cfontsize": 0.78, "color": "#64748b", "halign": "left", "margin": "0 0 8px 0"}},
|
||
] + rows,
|
||
}],
|
||
}
|