feat: 任务树维度分组——analyst按4维度/qc按类型聚合,一次过=叶子,多轮=组节点+轮次升序子节点
This commit is contained in:
parent
b3a8a15fd1
commit
7039c4980e
@ -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)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user