import argparse import json import sys from dataclasses import dataclass from datetime import date from pathlib import Path IGNORED_CARD_NAMES = {"README.md", "card_index.md"} @dataclass class RebuildResult: drift_found: bool differences: list report_path: Path def read_json(path): return json.loads(path.read_text(encoding="utf-8")) def write_text(path, text): path.parent.mkdir(parents=True, exist_ok=True) path.write_text(text, encoding="utf-8") def regression_case_counts(root): path = root / "tests" / "regression_cases.json" if not path.exists(): return {} data = read_json(path) counts = {} for case in data.get("regression_cases", []): model_id = case.get("model_id") if model_id: counts[model_id] = counts.get(model_id, 0) + 1 return counts def card_path_for_model(root, model_id): path = root / "cards" / f"{model_id}.md" if path.exists(): return path.relative_to(root).as_posix() return f"cards/{model_id}.md" def existing_index_version(root): path = root / "models" / "model_index.json" if not path.exists(): return "0.1" try: data = read_json(path) except json.JSONDecodeError: return "0.1" return data.get("index_version", "0.1") def build_model_index(root): counts = regression_case_counts(root) models = [] for model_path in sorted((root / "models").glob("*.model.json")): model = read_json(model_path) model_id = model["model_id"] models.append({ "model_id": model_id, "model_name": model["model_name"], "model_type": model["model_type"], "pipeline_position": model["pipeline_position"], "model_file": model_path.relative_to(root).as_posix(), "card_file": card_path_for_model(root, model_id), "source_article_count": len(model.get("source_articles", [])), "source_evidence_count": len(model.get("source_evidence", [])), "regression_case_count": counts.get(model_id, 0), "stability_level": model.get("stability_profile", {}).get("stability_level"), "regression_status": model.get("regression_status"), "status": model.get("status"), "last_updated": model.get("last_updated") }) return { "index_version": existing_index_version(root), "last_updated": date.today().isoformat(), "models": sorted(models, key=lambda item: item["model_id"]) } def render_json(data): return json.dumps(data, ensure_ascii=False, indent=2) + "\n" def render_card_index(model_index): lines = [ "# Card Index", "", "| Model ID | 模型名称 | 类型 | 流程位置 | Card | Model JSON | 稳固性 | 回归状态 | 状态 |", "| --- | --- | --- | --- | --- | --- | --- | --- | --- |", ] for entry in model_index["models"]: lines.append( "| {model_id} | {model_name} | {model_type} | {pipeline_position} | " "`{card_file}` | `{model_file}` | {stability_level} | {regression_status} | {status} |".format(**entry) ) return "\n".join(lines) + "\n" def collect_differences(root, expected_model_index_text, expected_card_index_text): checks = [ (root / "models" / "model_index.json", expected_model_index_text, "models/model_index.json"), (root / "cards" / "card_index.md", expected_card_index_text, "cards/card_index.md"), ] differences = [] for path, expected_text, label in checks: if not path.exists(): differences.append(f"{label} is missing") continue actual_text = path.read_text(encoding="utf-8") if actual_text != expected_text: differences.append(f"{label} differs from generated index") return differences def write_report(root, mode, differences): report_path = root / "reports" / "index_rebuild_report.md" lines = [ "# Index Rebuild Report", "", f"Mode: `{mode}`", f"Status: `{'DRIFT' if differences else 'PASS'}`", "", ] if differences: lines.append("## Differences") lines.append("") lines.extend(f"- {difference}" for difference in differences) else: lines.append("## Result") lines.append("") lines.append("- models/model_index.json matches generated model registry.") lines.append("- cards/card_index.md matches generated card index.") write_text(report_path, "\n".join(lines) + "\n") return report_path def rebuild_indexes(root, write=False): root = Path(root) model_index = build_model_index(root) model_index_text = render_json(model_index) card_index_text = render_card_index(model_index) if write: write_text(root / "models" / "model_index.json", model_index_text) write_text(root / "cards" / "card_index.md", card_index_text) differences = [] mode = "write" else: differences = collect_differences(root, model_index_text, card_index_text) mode = "check" report_path = write_report(root, mode, differences) return RebuildResult(bool(differences), differences, report_path) def main(): parser = argparse.ArgumentParser(description="Rebuild or check model and card indexes.") group = parser.add_mutually_exclusive_group(required=True) group.add_argument("--check", action="store_true", help="Check generated indexes against files without writing them.") group.add_argument("--write", action="store_true", help="Write generated indexes to disk.") args = parser.parse_args() root = Path(__file__).resolve().parents[1] result = rebuild_indexes(root, write=args.write) print(f"index rebuild report written to {result.report_path}") if result.drift_found: for difference in result.differences: print(f"DRIFT: {difference}") return 1 print("index check passed") return 0 if __name__ == "__main__": sys.exit(main())