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)
|
return json.dumps(nodes, ensure_ascii=False)
|
||||||
|
|
||||||
if node_id.startswith('role:'):
|
if node_id.startswith('role:'):
|
||||||
# 该角色分组下的任务(只列顶层任务)
|
# 该角色分组下的任务。
|
||||||
|
# 维度型角色(analyst 按 analysis_dim、qc 按 qc_type):先按维度/类型聚合,
|
||||||
|
# 一次过=叶子节点;多轮=组节点,子节点按轮次升序(第1轮→第N轮)。
|
||||||
role = node_id[5:]
|
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:
|
for t in all_tasks:
|
||||||
r = (getattr(t, 'role', '') or '').strip()
|
r = (getattr(t, 'role', '') or '').strip()
|
||||||
if role == '__other__':
|
if role == '__other__':
|
||||||
@ -127,20 +145,120 @@ async with DBPools().sqlorContext(dbname) as sor:
|
|||||||
if (getattr(t, 'parent_id', '') or '').strip():
|
if (getattr(t, 'parent_id', '') or '').strip():
|
||||||
continue
|
continue
|
||||||
tid = getattr(t, 'id', '')
|
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 ''
|
title = getattr(t, 'title', '') or ''
|
||||||
state = getattr(t, 'state', '') or ''
|
state = getattr(t, 'state', '') or ''
|
||||||
if not tid or not title:
|
|
||||||
continue
|
|
||||||
icon = _state_icons.get(state, '\u2b1c')
|
icon = _state_icons.get(state, '\u2b1c')
|
||||||
zh = _state_zh.get(state, state)
|
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 \
|
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)
|
any((getattr(c, 'parent_id', '') or '').strip() == tid for c in all_tasks)
|
||||||
nodes.append({
|
nodes.append({
|
||||||
"id": tid,
|
"id": tid,
|
||||||
"label": "{} [{}] {}".format(icon, zh, title[:60]),
|
"label": "{} [{}] {}".format(icon, zh, title[:60]),
|
||||||
"is_leaf": not has_child,
|
"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)
|
return json.dumps(nodes, ensure_ascii=False)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user