Files

247 lines
8.4 KiB
Python
Raw Permalink 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.
#!/usr/bin/env python3
"""
docmap 输出质量验证脚本
检查生成的文档是否符合规范要求
检查项分两级:
- 失败项(failed):结构/内容缺失,退出码非零
- 警告项(warning):可疑但不阻断(如 stateDiagram、模糊表述),不影响退出码
"""
import re
import sys
from datetime import datetime
from pathlib import Path
from dataclasses import dataclass
from typing import List
# 模糊表述"等"的白名单(合理用法,不算模糊)
DENG_WHITELIST = [
"等于", "等待", "对等", "等级", "等同", "同等", "均等",
"不等", "稍等", "优等", "劣等", "等比", "等值",
]
@dataclass
class ValidationResult:
passed: bool
message: str
file: str = ""
warning: bool = False # True 表示警告项,不计入失败、不影响退出码
class DocmapValidator:
def __init__(self, outputs_dir: str):
self.outputs_dir = Path(outputs_dir)
self.results: List[ValidationResult] = []
def validate(self) -> List[ValidationResult]:
"""执行所有验证"""
self.results = []
# 检查目录结构
self._check_directory_structure()
# 验证架构文档
self._validate_architecture_doc()
# 验证模块文档
self._validate_module_docs()
# 验证能力模型
self._validate_capability_doc()
return self.results
def _check_directory_structure(self):
"""检查输出目录结构"""
if not self.outputs_dir.exists():
self.results.append(ValidationResult(
False, "输出目录不存在", str(self.outputs_dir)
))
return
# 检查必需文件
required_files = ["01-产品整体架构.md", "03-系统能力模型.md"]
for f in required_files:
path = self.outputs_dir / f
if not path.exists():
self.results.append(ValidationResult(
False, f"缺少必需文件: {f}", str(self.outputs_dir)
))
# 检查模块目录
module_dir = self.outputs_dir / "02-功能模块"
if not module_dir.exists():
self.results.append(ValidationResult(
False, "缺少功能模块目录", str(self.outputs_dir)
))
def _validate_architecture_doc(self):
"""验证架构文档"""
doc_path = self.outputs_dir / "01-产品整体架构.md"
if not doc_path.exists():
return
content = doc_path.read_text(encoding='utf-8')
# 检查 7 个必需章节
required_sections = [
"产品定位",
"系统整体架构",
"业务架构图",
"核心业务流程",
"数据架构",
"权限与角色体系",
"系统扩展点分析"
]
for section in required_sections:
if section not in content:
self.results.append(ValidationResult(
False, f"缺少章节: {section}", str(doc_path)
))
# 检查 Mermaid 图(至少 2 个)
mermaid_count = content.count("```mermaid")
if mermaid_count < 2:
self.results.append(ValidationResult(
False, f"Mermaid 图数量不足: 需要至少 2 个,实际 {mermaid_count} 个", str(doc_path)
))
else:
self.results.append(ValidationResult(
True, f"Mermaid 图数量: {mermaid_count} 个", str(doc_path)
))
# 检查模糊表述(警告项,不阻断)
fuzzy_patterns = [r"等情况", r"等多种", r"其他相关", r"等等", r"诸如此类"]
for pattern in fuzzy_patterns:
matches = re.findall(pattern, content)
if matches:
self.results.append(ValidationResult(
False, f"发现模糊表述 '{pattern}': 出现 {len(matches)} 次",
str(doc_path), warning=True
))
def _validate_module_docs(self):
"""验证模块文档"""
module_dir = self.outputs_dir / "02-功能模块"
if not module_dir.exists():
return
module_files = list(module_dir.glob("*.md"))
if not module_files:
self.results.append(ValidationResult(
False, "功能模块目录为空", str(module_dir)
))
return
for module_file in module_files:
content = module_file.read_text(encoding='utf-8')
# 检查功能清单表格
if "| 功能名称 |" not in content and "|功能名称|" not in content:
self.results.append(ValidationResult(
False, "缺少功能清单表格", str(module_file)
))
# 检查状态流转 Mermaid(警告项,不阻断:无状态机的模块可豁免)
if "```mermaid" not in content or "stateDiagram" not in content:
self.results.append(ValidationResult(
False, "缺少状态流转 Mermaid 图", str(module_file), warning=True
))
# 检查模糊表述"等"(警告项,不阻断;白名单词不算命中)
hit_lines = []
for lineno, line in enumerate(content.split('\n'), 1):
stripped = line
for word in DENG_WHITELIST:
stripped = stripped.replace(word, "")
if "等" in stripped:
hit_lines.append(lineno)
if hit_lines:
self.results.append(ValidationResult(
False,
f"可能包含模糊表述 '等'(行: {', '.join(map(str, hit_lines))})",
str(module_file), warning=True
))
def _validate_capability_doc(self):
"""验证能力模型文档"""
doc_path = self.outputs_dir / "03-系统能力模型.md"
if not doc_path.exists():
return
content = doc_path.read_text(encoding='utf-8')
# 检查能力依赖关系图
if "```mermaid" not in content:
self.results.append(ValidationResult(
False, "缺少能力依赖关系图", str(doc_path)
))
# 检查平台级/业务定制能力区分
if "平台级能力" not in content or "业务定制能力" not in content:
self.results.append(ValidationResult(
False, "缺少平台级/业务定制能力区分", str(doc_path)
))
def generate_report(self) -> str:
"""生成验证报告"""
passed = [r for r in self.results if r.passed]
warnings = [r for r in self.results if not r.passed and r.warning]
failed = [r for r in self.results if not r.passed and not r.warning]
report = ["# docmap 输出质量验证报告\n"]
report.append(f"**验证时间:** {datetime.now().isoformat()}\n")
report.append(f"**输出目录:** {self.outputs_dir}\n")
report.append(f"**通过项:** {len(passed)}\n")
report.append(f"**警告项:** {len(warnings)}\n")
report.append(f"**失败项:** {len(failed)}\n\n")
if failed:
report.append("## ❌ 失败项\n")
for r in failed:
report.append(f"- **{r.file}**: {r.message}\n")
report.append("\n")
if warnings:
report.append("## ⚠️ 警告项(不阻断)\n")
for r in warnings:
report.append(f"- **{r.file}**: {r.message}\n")
report.append("\n")
if passed:
report.append("## ✅ 通过项\n")
for r in passed:
report.append(f"- **{r.file}**: {r.message}\n")
return "\n".join(report)
def main():
if len(sys.argv) < 2:
print("Usage: python validate_output.py <outputs_directory>")
sys.exit(1)
outputs_dir = sys.argv[1]
validator = DocmapValidator(outputs_dir)
validator.validate()
report = validator.generate_report()
print(report)
# 保存报告
report_path = Path(outputs_dir).parent / "validation_report.md"
report_path.write_text(report, encoding='utf-8')
print(f"\n报告已保存: {report_path}")
# 仅失败项影响退出码;警告项不阻断
failed = [r for r in validator.results if not r.passed and not r.warning]
sys.exit(1 if failed else 0)
if __name__ == "__main__":
main()