from __future__ import annotations from dataclasses import dataclass from datetime import datetime from pathlib import Path from uuid import uuid4 from docx import Document @dataclass(frozen=True) class AnalysisReport: source_filename: str provider_name: str model_name: str matched_skills: list[str] summary: str findings: list[dict[str, str]] recommendations: list[str] raw_model_output: str def _safe_stem(filename: str) -> str: stem = Path(filename).stem or "analysis" safe = "".join(ch if ch.isalnum() or ch in ("-", "_") else "_" for ch in stem) return safe[:60] or "analysis" def _report_base_path(report: AnalysisReport, output_dir: Path, suffix: str) -> Path: output_dir.mkdir(parents=True, exist_ok=True) return output_dir / f"{_safe_stem(report.source_filename)}_{uuid4().hex[:8]}_analysis.{suffix}" def generate_markdown_report(report: AnalysisReport, output_dir: Path | str) -> Path: path = _report_base_path(report, Path(output_dir), "md") lines = [ "# DOCX 规范分析报告", "", "## 基本信息", "", f"- 源文件:{report.source_filename}", f"- 分析时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", f"- 模型供应商:{report.provider_name}", f"- 模型名称:{report.model_name}", f"- 命中技能:{', '.join(report.matched_skills) or '无'}", "", "## 总体结论", "", report.summary, "", "## 技能符合性矩阵", "", "| 状态 | 检查项 | 证据或说明 |", "| --- | --- | --- |", ] for finding in report.findings: lines.append( f"| {finding.get('status', '')} | {finding.get('item', '')} | {finding.get('evidence', '').replace('|', '/')} |" ) lines.extend(["", "## 修改建议", ""]) for item in report.recommendations: lines.append(f"- {item}") lines.extend(["", "## 模型分析原文", "", report.raw_model_output]) path.write_text("\n".join(lines), encoding="utf-8") return path def generate_docx_report(report: AnalysisReport, output_dir: Path | str) -> Path: path = _report_base_path(report, Path(output_dir), "docx") document = Document() document.add_heading("DOCX 规范分析报告", level=0) document.add_heading("基本信息", level=1) for label, value in [ ("源文件", report.source_filename), ("分析时间", datetime.now().strftime("%Y-%m-%d %H:%M:%S")), ("模型供应商", report.provider_name), ("模型名称", report.model_name), ("命中技能", ", ".join(report.matched_skills) or "无"), ]: document.add_paragraph(f"{label}:{value}") document.add_heading("总体结论", level=1) document.add_paragraph(report.summary) document.add_heading("技能符合性矩阵", level=1) table = document.add_table(rows=1, cols=3) table.style = "Table Grid" headers = table.rows[0].cells headers[0].text = "状态" headers[1].text = "检查项" headers[2].text = "证据或说明" for finding in report.findings: row = table.add_row().cells row[0].text = finding.get("status", "") row[1].text = finding.get("item", "") row[2].text = finding.get("evidence", "") document.add_heading("修改建议", level=1) for item in report.recommendations: document.add_paragraph(item, style="List Bullet") document.add_heading("模型分析原文", level=1) for line in report.raw_model_output.splitlines() or ["无"]: document.add_paragraph(line) document.add_paragraph("说明:模型分析结果需人工复核,不应直接作为正式审查结论。") document.save(path) return path