141 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Deterministic Markdown reports with escaped untrusted text and explicit data boundaries."""
import html
from datetime import date
from app.schemas.integration_risk import IntegrationRiskRequest
from app.schemas.risk_prediction import RiskAssessmentResult
from app.services.database_risk_scoring import normalize_category, normalize_material
from app.services.purchase_orders import calendar_year_window
from app.services.reports import LEVEL_LABELS
def md_text(value: object) -> str:
if value is None or value == "":
return "未提供"
value = html.escape(str(value), quote=False).replace("\r", " ").replace("\n", " ")
for char in ("\\", "`", "*", "_", "[", "]", "|", "#", "!"):
value = value.replace(char, "\\" + char)
return value
def render_markdown_report(
payload: IntegrationRiskRequest,
result: RiskAssessmentResult,
as_of: date,
operator_name: str,
) -> str:
product = payload.assessment
start, _ = calendar_year_window(as_of, 3)
info = (
("评估对象", product.product_name or product.subject_id),
("对象编号", product.subject_id),
("请求编号", payload.request_id),
("提交品类", product.product_category),
("评分品类", normalize_category(product.product_category) or "无法归类,品类维度使用默认 50 分"),
("规格", product.specification),
("包装原文", product.material_type),
("评分物料标签", normalize_material(product.material_type)),
("供应商", product.supplier),
("加工厂", product.factory),
("开发阶段", product.stage_new),
("已知缺陷等级", product.defect_level),
("评估人", operator_name),
("数据截止日期", as_of),
("评估背景", product.description),
)
lines = ["# 新品风险评估报告", "", "## 一、评估信息", "", "| 项目 | 内容 |", "|---|---|"]
lines.extend(f"| {label} | {md_text(value)} |" for label, value in info)
lines.extend(
[
"",
"## 二、综合风险结论",
"",
f"- 综合安全得分:{result.score:.2f}(越高风险越低)",
f"- 风险等级:{LEVEL_LABELS[result.level.value]}",
f"- 模型版本:{md_text(result.model_version)}",
]
)
if result.alert_text:
lines.append(f"- 数据与建议提示:{md_text(result.alert_text)}")
lines.extend(
[
"",
"## 三、七维风险评分",
"",
"| 风险维度 | 得分 | 权重 | 数据状态 | 说明 |",
"|---|---:|---:|---|---|",
]
)
lines.extend(
f"| {md_text(item.dimension)} | {item.score:.2f} | {item.weight:g}% | "
f"{md_text(item.status)} | {md_text(item.reason)} |"
for item in result.dimension_scores
)
lines.extend(["", "## 四、历史统计与相似异常案例", ""])
if result.category_warning:
lines.append(f"- 评分品类历史异常:{result.category_warning.anomaly_count} 条。")
if result.supplier_freq:
freq = result.supplier_freq
lines.extend(
[
f"- 供应商:{md_text(freq.supplier_name)};合作次数:{md_text(freq.cooperation_count)};"
f"历史异常:{md_text(freq.anomaly_count)}。",
f"- 供应商统计说明:{md_text(freq.note)}",
]
)
lines.extend(["", "| 日期 | 产品 | 异常现象 | 原因 | 历史处理方案 | 相似度 |", "|---|---|---|---|---|---:|"])
for case in result.similar_cases:
similarity = f"{case.similarity:.1%}" if case.similarity is not None else "未提供"
lines.append(
"| "
+ " | ".join(
md_text(value)
for value in (
case.date,
case.product,
case.problem or case.description,
case.root_cause,
case.solution,
similarity,
)
)
+ " |"
)
if not result.similar_cases:
lines.extend(["", "暂无匹配的历史案例,不能据此认定产品无风险。"])
lines.extend(["", "## 五、标准规则", "", "| 风险点 | 判定规则 | 来源 | 状态 |", "|---|---|---|---|"])
lines.extend(
"| "
+ " | ".join(
md_text(value)
for value in (
rule.risk_point,
rule.rule,
rule.source,
rule.status,
)
)
+ " |"
for rule in result.standard_judgment
)
if not result.standard_judgment:
lines.extend(["", "暂无匹配标准,需由品控补充核验;不代表已通过合规或检测判定。"])
lines.extend(["", "## 六、风险预防建议", ""])
lines.extend(f"{index}. {md_text(value)}" for index, value in enumerate(result.ai_suggestions, 1))
lines.extend(
[
"",
"## 七、数据口径说明",
"",
f"- 数据来自风险平台本地异常案例、采购订单及启用的标准规则;统计区间为 {start} 至 {as_of}。",
"- 沿用现有评分口径,以截止年份往前 3 年的 1 月 1 日为区间起点。",
"- 包装组合沿用现有关键词归一化规则,只使用一个匹配物料标签进行评分。",
"- 缺少巡检、检测结果或可用历史记录的维度按现有默认分处理,并在明细中标注。",
"- 缺陷维度基于历史 A/B/C 记录计算,不预测新品实际缺陷等级。",
"- 标准匹配和预防建议不替代实际检测与品控审核;最终放行由业务和品控人员确认。",
"",
]
)
return "\n".join(lines)