统一报告章节归并与类型展示

This commit is contained in:
kx_guo 2026-09-09 17:30:06 +08:00
parent 2f4d2260c8
commit 814da7ffc0

View File

@ -168,6 +168,7 @@ def aggregate_report_data(result: RiskAssessmentResult) -> dict[str, Any]:
supplier_cards = [card for card in cards if card.type == "supplier"]
material_cards = [card for card in cards if card.type == "material"]
solution_cards = [card for card in cards if card.type == "solution"]
standard_cards = [card for card in cards if card.type in {"standard", "compliance"}]
standards = _unique_by(
[*result.standard_judgment, *(item for card in cards for item in card.standards)],
lambda item: (item.category, item.risk_point, item.rule, item.status),
@ -182,10 +183,10 @@ def aggregate_report_data(result: RiskAssessmentResult) -> dict[str, Any]:
item.solution,
),
)
suggestions = _unique_by(
[*result.ai_suggestions, *(item for card in cards for item in card.suggestions)],
lambda item: item,
)
# Only base assessment suggestions belong to the prevention section.
# Card-specific suggestions stay with their owning section (supplier,
# material, standard/compliance or AI reasoning) to avoid duplication.
suggestions = _unique_by(list(result.ai_suggestions), lambda item: item)
detected_values = [item for card in cards for item in card.detected_values]
risks = [(card.title, risk) for card in solution_cards for risk in card.risks]
analyses = [card for card in solution_cards if card.analysis_base or card.confidence or card.warning]
@ -194,6 +195,7 @@ def aggregate_report_data(result: RiskAssessmentResult) -> dict[str, Any]:
"supplier_cards": supplier_cards,
"material_cards": material_cards,
"solution_cards": solution_cards,
"standard_cards": standard_cards,
"standards": standards,
"similar_cases": similar_cases,
"suggestions": suggestions,
@ -252,6 +254,9 @@ def build_risk_report_pdf(result: RiskAssessmentResult) -> bytes:
standards = report_data["standards"]
similar_cases = report_data["similar_cases"]
suggestions = report_data["suggestions"]
material_cards = report_data["material_cards"]
standard_cards = report_data["standard_cards"]
section_shift = 1 if material_cards else 0
story = [
Paragraph("新品风险预估报告", title),
Paragraph(f"蝶雅产品部 · 风险预估系统 · 自动生成 · {generated_at}", subtitle),
@ -308,7 +313,7 @@ def build_risk_report_pdf(result: RiskAssessmentResult) -> bytes:
order_text = f"合作 {supplier.cooperation_count} 次、" if supplier.cooperation_count is not None else ""
conclusion = f"{round(score)} 分 · {order_text}异常 {supplier.anomaly_count or 0} 次"
elif name == "标准符合性" and result.standard_judgment:
conclusion = f"{round(score)} 分 · 命中 {len(result.standard_judgment)} 条判定标准,待检测项需人工确认"
conclusion = f"{round(score)} 分 · 命中 {len(standards)} 条判定标准,待检测项需人工确认"
else:
conclusion = f"{round(score)} 分" + (f" · {reason}" if reason else "")
rows.append([f"{name}({weight:g}%)", conclusion])
@ -353,7 +358,6 @@ def build_risk_report_pdf(result: RiskAssessmentResult) -> bytes:
supplier_rows.append([label, value, "追加供应商画像数据"])
story += [Paragraph("三、供应商合作频次分析", section), table(supplier_rows, [38 * mm, 35 * mm, 87 * mm])]
material_cards = report_data["material_cards"]
if material_cards:
story += [Paragraph("四、物料风险分析", section)]
material_rows = [["物料/字段", "分析结果"]]
@ -370,7 +374,7 @@ def build_risk_report_pdf(result: RiskAssessmentResult) -> bytes:
)
story.append(table(material_rows, [45 * mm, 115 * mm]))
story += [Paragraph(f"五、相似案例匹配({len(similar_cases)} 条)", section)]
story += [Paragraph(f"{'五' if section_shift else '四'}、相似案例匹配({len(similar_cases)} 条)", section)]
if similar_cases:
story.append(
table(
@ -385,11 +389,11 @@ def build_risk_report_pdf(result: RiskAssessmentResult) -> bytes:
else:
story.append(Paragraph("暂无相关案例,RAG 相似案例检索未返回匹配结果。", small))
story += [Paragraph("六、预防建议", section)]
story += [Paragraph(f"{'六' if section_shift else '五'}、预防建议", section)]
for index, suggestion in enumerate(suggestions or ["优先针对低分维度安排专项验证,并明确责任人与完成节点。"], 1):
story.append(Paragraph(f"{index}. {suggestion}", body))
story += [
Paragraph("七、AI 推理方案(无历史案例场景)", section),
Paragraph(f"{'七' if section_shift else '六'}、AI 推理方案(无历史案例场景)", section),
Paragraph(
"本次评估已采用历史案例与标准库双引擎结论;若后续未匹配到足够历史案例,将基于相似品类、相似物料生成类比方案,并由标准库进行规则兜底。",
body,
@ -406,15 +410,28 @@ def build_risk_report_pdf(result: RiskAssessmentResult) -> bytes:
if card.warning:
story.append(Paragraph(f"风险提示:{card.warning}", body))
story += [Paragraph("八、标准判定结果", section)]
if standards:
story += [Paragraph(f"{'八' if section_shift else '七'}、标准判定结果", section)]
standard_rows = [
[f"{item.category} · {item.risk_point}", item.rule, item.source or "标准文件.xlsx", item.status]
for item in standards
]
for card in standard_cards:
if card.detected_values:
standard_rows.append(
[
"检测值",
"、".join(f"{item.get('name', '')}:{item.get('value', '')}" for item in card.detected_values),
card.title,
"待确认",
]
)
if card.note:
standard_rows.append(["补充说明", card.note, card.title, "待确认"])
if standard_rows:
story.append(
table(
[["判定维度", "判定标准", "标准来源", "当前状态"]]
+ [
[f"{item.category} · {item.risk_point}", item.rule, item.source or "标准文件.xlsx", item.status]
for item in standards
],
+ standard_rows,
[38 * mm, 55 * mm, 40 * mm, 27 * mm],
)
)
@ -525,7 +542,7 @@ def build_risk_report_docx(result: RiskAssessmentResult) -> bytes:
order_text = f"合作 {supplier.cooperation_count} 次、" if supplier.cooperation_count is not None else ""
conclusion = f"{round(score)} 分 · {order_text}异常 {supplier.anomaly_count or 0} 次"
elif name == "标准符合性" and result.standard_judgment:
conclusion = f"{round(score)} 分 · 命中 {len(result.standard_judgment)} 条判定标准,待检测项需人工确认"
conclusion = f"{round(score)} 分 · 命中 {len(standards)} 条判定标准,待检测项需人工确认"
else:
conclusion = f"{round(score)} 分" + (f" · {reason}" if reason else "")
rows.append([f"{name}({weight:g}%)", conclusion])