"""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)