184 lines
6.0 KiB
Python
184 lines
6.0 KiB
Python
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())
|