feat: 任务树维度分组——analyst按4维度/qc按类型聚合,一次过=叶子,多轮=组节点+轮次升序子节点

This commit is contained in:
yumoqing 2026-09-03 16:55:28 +08:00
parent b3a8a15fd1
commit 7039c4980e

View File

@ -114,9 +114,27 @@ async with DBPools().sqlorContext(dbname) as sor:
return json.dumps(nodes, ensure_ascii=False)
if node_id.startswith('role:'):
# 该角色分组下的任务(只列顶层任务)
# 该角色分组下的任务。
# 维度型角色(analyst 按 analysis_dim、qc 按 qc_type):先按维度/类型聚合,
# 一次过=叶子节点;多轮=组节点,子节点按轮次升序(第1轮→第N轮)。
role = node_id[5:]
nodes = []
def _params_of(t):
try:
return json.loads(getattr(t, 'params', '') or '{}')
except Exception:
return {}
# QC 任务 → (qc_type, round) 映射(review 表的 round 是权威轮次)
qc_map = {}
if role == 'agent.qc':
for r in await sor.sqlExe(
"SELECT task_id, qc_type, round FROM bid_qc_reviews WHERE project_id=${p}$",
{"p": pid}) or []:
qc_map[getattr(r, 'task_id', '')] = (getattr(r, 'qc_type', '') or '',
int(getattr(r, 'round', 0) or 0))
role_tasks = []
for t in all_tasks:
r = (getattr(t, 'role', '') or '').strip()
if role == '__other__':
@ -127,20 +145,120 @@ async with DBPools().sqlorContext(dbname) as sor:
if (getattr(t, 'parent_id', '') or '').strip():
continue
tid = getattr(t, 'id', '')
if not tid or not (getattr(t, 'title', '') or ''):
continue
role_tasks.append(t)
# ── 维度/类型分组 ──
if role in ('agent.tender_analyst', 'agent.qc'):
groups = {}
for t in role_tasks:
if role == 'agent.tender_analyst':
key = (_params_of(t).get('analysis_dim') or '').strip() or '__legacy__'
else:
key = qc_map.get(getattr(t, 'id', ''), ('', 0))[0] or '__none__'
groups.setdefault(key, []).append(t)
for key in groups:
groups[key].sort(key=lambda x: str(getattr(x, 'created_at', '') or ''))
dim_labels = {
'scoring': '评分项与得分规则', 'quals': '资质清单与要求',
'reqs_outline': '投标文件要求与章节骨架', 'cost_benefit': '成本收益分析',
'__legacy__': '全量解析(维度拆分前)',
'scoring_items': '评分项 QC', 'qualifications': '资质 QC',
'doc_requirements': '投标文件要求 QC', 'chapter_outline': '章节骨架 QC',
'__none__': 'QC 审核',
}
order = ['scoring', 'quals', 'reqs_outline', 'cost_benefit', '__legacy__',
'scoring_items', 'qualifications', 'doc_requirements',
'chapter_outline', '__none__']
nodes = []
for key in order + [k for k in groups if k not in order]:
if key not in groups:
continue
ts = groups[key]
label = dim_labels.get(key, key)
if len(ts) == 1:
# 一次过:叶子节点
t = ts[0]
st = getattr(t, 'state', '') or ''
nodes.append({
"id": getattr(t, 'id', ''),
"label": "{} [{}] {}".format(_state_icons.get(st, '\u2b1c'),
_state_zh.get(st, st), label),
"is_leaf": not (step_cnt.get(getattr(t, 'id', ''), 0) > 0 or
del_cnt.get(getattr(t, 'id', ''), 0) > 0),
})
else:
# 多轮:组节点 + 轮次升序子节点
final_state = getattr(ts[-1], 'state', '') or ''
nodes.append({
"id": "grp:" + role + ":" + key,
"label": "{}({} 轮)最新[{}]".format(label, len(ts),
_state_zh.get(final_state, final_state)),
"is_leaf": False,
})
return json.dumps(nodes, ensure_ascii=False)
# ── 非维度型角色:平铺任务列表(原逻辑)──
nodes = []
for t in role_tasks:
tid = getattr(t, 'id', '')
title = getattr(t, 'title', '') or ''
state = getattr(t, 'state', '') or ''
if not tid or not title:
continue
icon = _state_icons.get(state, '\u2b1c')
zh = _state_zh.get(state, state)
upd = str(getattr(t, 'updated_at', '') or '')[5:16]
has_child = step_cnt.get(tid, 0) > 0 or del_cnt.get(tid, 0) > 0 or \
any((getattr(c, 'parent_id', '') or '').strip() == tid for c in all_tasks)
nodes.append({
"id": tid,
"label": "{} [{}] {}".format(icon, zh, title[:60]),
"is_leaf": not has_child,
"upd": upd,
})
return json.dumps(nodes, ensure_ascii=False)
if node_id.startswith('grp:'):
# 轮次子节点:按轮数升序(第1轮→第N轮)
_, role, key = node_id.split(':', 2)
def _params_of2(t):
try:
return json.loads(getattr(t, 'params', '') or '{}')
except Exception:
return {}
qc_map = {}
if role == 'agent.qc':
for r in await sor.sqlExe(
"SELECT task_id, qc_type, round FROM bid_qc_reviews WHERE project_id=${p}$",
{"p": pid}) or []:
qc_map[getattr(r, 'task_id', '')] = (getattr(r, 'qc_type', '') or '',
int(getattr(r, 'round', 0) or 0))
ts = []
for t in all_tasks:
if (getattr(t, 'role', '') or '').strip() != role:
continue
if (getattr(t, 'parent_id', '') or '').strip():
continue
if role == 'agent.tender_analyst':
k = (_params_of2(t).get('analysis_dim') or '').strip() or '__legacy__'
else:
k = qc_map.get(getattr(t, 'id', ''), ('', 0))[0] or '__none__'
if k == key:
ts.append(t)
ts.sort(key=lambda x: str(getattr(x, 'created_at', '') or ''))
nodes = []
for i, t in enumerate(ts):
tid = getattr(t, 'id', '')
st = getattr(t, 'state', '') or ''
rnd = qc_map.get(tid, ('', 0))[1] if role == 'agent.qc' else 0
rnd_label = "第%d轮" % (rnd or (i + 1))
upd = str(getattr(t, 'updated_at', '') or '')[5:16]
nodes.append({
"id": tid,
"label": "{} {} [{}] {}".format(rnd_label, _state_icons.get(st, '\u2b1c'),
_state_zh.get(st, st), upd),
"is_leaf": not (step_cnt.get(tid, 0) > 0 or del_cnt.get(tid, 0) > 0),
})
return json.dumps(nodes, ensure_ascii=False)