# ============================================================================= # 🎁 惊喜 1: 项目健康度仪表盘 # ============================================================================= # 用途: 一键生成项目健康度总览 HTML(明早桌面打开即用) # 跑法: python scripts/dashboard.py # 产物: docs/dashboard.html # ============================================================================= import os import json import subprocess from datetime import datetime from pathlib import Path PROJECT_ROOT = Path(__file__).resolve().parent.parent OUTPUT = PROJECT_ROOT / "docs" / "dashboard.html" def count_lines(glob_pattern: str) -> int: """统计符合 glob 的代码总行数""" import glob total = 0 for f in glob.glob(glob_pattern, recursive=True): if os.path.isfile(f): try: with open(f, "r", encoding="utf-8", errors="ignore") as fp: total += sum(1 for _ in fp) except Exception: pass return total def count_files(glob_pattern: str) -> int: import glob return sum(1 for f in glob.glob(glob_pattern, recursive=True) if os.path.isfile(f)) def git_info() -> dict: """拿 git 仓库信息""" try: result = { "branch": subprocess.run( ["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=PROJECT_ROOT, capture_output=True, text=True ).stdout.strip(), "last_commit": subprocess.run( ["git", "log", "-1", "--format=%h %s"], cwd=PROJECT_ROOT, capture_output=True, text=True ).stdout.strip(), "commit_count": subprocess.run( ["git", "rev-list", "--count", "HEAD"], cwd=PROJECT_ROOT, capture_output=True, text=True ).stdout.strip(), } return result except Exception as e: return {"error": str(e)} def main(): # 1. 代码统计 stats = { "backend_python_files": count_files("backend/app/**/*.py"), "backend_python_lines": count_lines("backend/app/**/*.py"), "frontend_admin_files": count_files("frontend-admin/src/**/*.{vue,ts,js}"), "frontend_agent_files": count_files("frontend-agent/src/**/*.{vue,ts,js}"), "frontend_h5_files": count_files("frontend-h5/src/**/*.{vue,ts,js}"), "frontend_portal_files": count_files("frontend-portal/src/**/*.{vue,ts,js}"), "docs_files": count_files("docs/**/*.md"), "scripts_files": count_files("scripts/**/*.sh"), "tests_files": count_files("backend/tests/**/*.py"), } # 2. 文档统计 docs_path = PROJECT_ROOT / "docs" doc_categories = { "评审报告": len(list((docs_path / "评审报告").glob("*.md"))) if (docs_path / "评审报告").exists() else 0, "审计报告": len(list((docs_path / "审计报告").glob("*.md"))) if (docs_path / "审计报告").exists() else 0, "ADRs": len(list((docs_path / "ADRs").glob("*.md"))) if (docs_path / "ADRs").exists() else 0, "SOPs": len(list((docs_path / "SOPs").glob("*.md"))) if (docs_path / "SOPs").exists() else 0, "路线图": len(list((docs_path / "路线图").glob("*.md"))) if (docs_path / "路线图").exists() else 0, } # 3. 风险统计(解析风险跟踪表) risk_file = docs_path / "风险跟踪表.md" risk_stats = {"P0_remaining": 0, "P1": 0, "P2": 0, "P3": 0, "M": 0, "L": 0} if risk_file.exists(): text = risk_file.read_text(encoding="utf-8") for level in ["P0", "P1", "P2", "P3", "M", "L"]: risk_stats[f"{level}_total"] = text.count(f"### {level}-") risk_stats[f"{level}_remaining"] = text.count(f"### {level}-") - text.count("✅") # 4. Git 信息 g = git_info() # 5. 模板渲染 html = f"""