- fp_calc.calc()返回键实锤核对: total_fp/counts/errors(原total_ufp/by_type兜底猜错,已修) - promote门禁修正: resolve_approval会把status置approved,confirmed只是中间态→门禁接受confirmed/approval_initiated/approved(原只认confirmed会导致审批通过后反而不能立项) - _cluster_ctx补项目隔离(对齐_check_batch_visible口径)+全personal类别如实拒绝可行性样本不足 - 八节模板兜底节=需求规格;四节存量解析口径零变化 - opp_approvals CRUD手改(项目过滤)被xls2ui重生成吃掉→已checkout还原,只保留opp_reports新列部分
117 lines
5.4 KiB
Plaintext
117 lines
5.4 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": {
|
||
"otext": "该批次没有聚类结果(可能范围内无需求数据)", "text": "该批次没有聚类结果(可能范围内无需求数据)", "i18n": True,
|
||
"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": {"otext": "${p0} 条 | ${p1}%", "text": "${p0} 条 | ${p1}%", "i18n": True, "i18n_params": {"p0": str('%d' % (cnt,)), "p1": str('%.1f' % (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": {
|
||
"otext": "点击任一类别,查看类内需求明细(每条带来源链接可查证)。", "text": "点击任一类别,查看类内需求明细(每条带来源链接可查证)。", "i18n": True,
|
||
"cfontsize": 0.78, "color": "#64748b", "halign": "left", "margin": "0 0 8px 0"}},
|
||
] + rows,
|
||
}],
|
||
}
|