From 6a4518ef2206327eee24bb92b27ee9fed1039992 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Thu, 3 Sep 2026 17:07:04 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BB=BB=E5=8A=A1=E6=A0=91=E8=BD=AE?= =?UTF-8?q?=E6=AC=A1=E8=8A=82=E7=82=B9=E6=98=BE=E7=A4=BA=E8=AF=A5=E8=BD=AE?= =?UTF-8?q?QC=E7=BB=93=E6=9E=9C=EF=BC=88=E4=BB=BB=E5=8A=A1=E5=B7=B2?= =?UTF-8?q?=E6=89=B9=E5=87=86=E2=89=A0QC=E9=80=9A=E8=BF=87=EF=BC=8C?= =?UTF-8?q?=E6=B6=88=E9=99=A4=E8=AF=AF=E5=AF=BC=EF=BC=89=EF=BC=9B=E7=BB=84?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=A0=87=E7=AD=BE=E7=94=A8=E6=9C=80=E6=96=B0?= =?UTF-8?q?QC=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- wwwroot/api/bid_task_tree.dspy | 61 ++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/wwwroot/api/bid_task_tree.dspy b/wwwroot/api/bid_task_tree.dspy index 221fe6b..5068d14 100644 --- a/wwwroot/api/bid_task_tree.dspy +++ b/wwwroot/api/bid_task_tree.dspy @@ -151,6 +151,14 @@ async with DBPools().sqlorContext(dbname) as sor: # ── 维度/类型分组 ── if role == 'agent.tender_analyst': + # 各维度最新一轮 QC 结果(组节点标签用,消除"任务已批准≠QC通过"误导) + latest_qc = {} + for rv in await sor.sqlExe( + "SELECT qc_type, fit_score, passed, round FROM bid_qc_reviews " + "WHERE project_id=${p}$ ORDER BY qc_type, round DESC", {"p": pid}) or []: + qt = getattr(rv, 'qc_type', '') or '' + if qt not in latest_qc: + latest_qc[qt] = rv groups = {} for t in role_tasks: key = (_params_of(t).get('analysis_dim') or '').strip() or '__legacy__' @@ -181,10 +189,16 @@ async with DBPools().sqlorContext(dbname) as sor: }) else: final_state = getattr(ts[-1], 'state', '') or '' + lq = latest_qc.get(key) + if lq: + lq_passed = str(getattr(lq, 'passed', '0')) == '1' + tail = "QC %.1f 分 %s" % (float(getattr(lq, 'fit_score', 0) or 0), + '通过' if lq_passed else '未通过') + else: + tail = "任务" + _state_zh.get(final_state, final_state) nodes.append({ "id": "grp:" + role + ":" + key, - "label": "{}({} 轮)最新[{}]".format(label, len(ts), - _state_zh.get(final_state, final_state)), + "label": "{}({} 轮)最新[{}]".format(label, len(ts), tail), "is_leaf": False, }) return json.dumps(nodes, ensure_ascii=False) @@ -277,6 +291,10 @@ async with DBPools().sqlorContext(dbname) as sor: except Exception: return {} + # 维度→QC类型映射(用于给 analyst 轮次挂该轮 QC 结果) + DIM_QC = {'scoring': 'scoring_items', 'quals': 'qualifications', + 'reqs_outline': 'doc_requirements', 'cost_benefit': 'cost_benefit'} + if role == 'agent.qc': # QC 组:按审核记录轮次升序展开(同一任务多轮复审各占一行) revs = await sor.sqlExe( @@ -313,7 +331,8 @@ async with DBPools().sqlorContext(dbname) as sor: "is_leaf": not (del_cnt.get(getattr(t, 'id', ''), 0) > 0)}) return json.dumps(nodes, ensure_ascii=False) - # analyst 组:按任务创建序(=轮次升序)展开 + # analyst 组:按任务创建序展开,轮次号与QC结果取自审核记录 + # (匹配规则:审核记录的 created_at 落在 [本任务updated_at, 下一任务created_at) 区间) ts = [] for t in all_tasks: if (getattr(t, 'role', '') or '').strip() != role: @@ -324,15 +343,45 @@ async with DBPools().sqlorContext(dbname) as sor: if k == key: ts.append(t) ts.sort(key=lambda x: str(getattr(x, 'created_at', '') or '')) + + qc_type = DIM_QC.get(key, '') + rev_by_round = {} + if qc_type: + for rv in await sor.sqlExe( + "SELECT round, fit_score, passed, created_at FROM bid_qc_reviews " + "WHERE project_id=${p}$ AND qc_type=${k}$ ORDER BY round ASC", + {"p": pid, "k": qc_type}) or []: + rev_by_round[int(getattr(rv, 'round', 0) or 0)] = rv + nodes = [] + used_rounds = set() for i, t in enumerate(ts): tid = getattr(t, 'id', '') st = getattr(t, 'state', '') or '' - upd = str(getattr(t, 'updated_at', '') or '')[5:16] + t_upd = str(getattr(t, 'updated_at', '') or '') + nxt_created = str(getattr(ts[i + 1], 'created_at', '') or '9999') if i + 1 < len(ts) else '9999' + matched = None + for rnd, rv in rev_by_round.items(): + if rnd in used_rounds: + continue + rv_t = str(getattr(rv, 'created_at', '') or '') + if t_upd and rv_t >= t_upd and rv_t < nxt_created: + matched = (rnd, rv) + break + if matched: + rnd, rv = matched + used_rounds.add(rnd) + passed = str(getattr(rv, 'passed', '0')) == '1' + sc = float(getattr(rv, 'fit_score', 0) or 0) + qc_label = "→ QC %.1f 分 %s" % (sc, '通过' if passed else '未通过') + else: + rnd = i + 1 + qc_label = '→ 未审核/审核记录未关联' + upd = t_upd[5:16] nodes.append({ "id": tid, - "label": "第%d轮 %s [%s] %s" % (i + 1, _state_icons.get(st, '\u2b1c'), - _state_zh.get(st, st), upd), + "label": "第%d轮 %s [任务%s] %s %s" % ( + rnd, _state_icons.get(st, '\u2b1c'), _state_zh.get(st, st), qc_label, upd), "is_leaf": not (step_cnt.get(tid, 0) > 0 or del_cnt.get(tid, 0) > 0), }) return json.dumps(nodes, ensure_ascii=False)