132 lines
3.9 KiB
Python
132 lines
3.9 KiB
Python
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
KEY_CONTRACT_TERMS = {
|
|
"qpi": [
|
|
"problem_owner",
|
|
"time_scale",
|
|
"dominant_scarcity",
|
|
"evidence_gap",
|
|
"classification",
|
|
"no_call"
|
|
],
|
|
"intellectual_archaeology": [
|
|
"should_call",
|
|
"recommended_max_depth",
|
|
"stop_reason",
|
|
"no_deeper_reason",
|
|
"validation_needed",
|
|
"action_implication"
|
|
]
|
|
}
|
|
|
|
|
|
def read_json(path):
|
|
return json.loads(path.read_text(encoding="utf-8"))
|
|
|
|
|
|
def card_path_for_model(root, model_id):
|
|
return root / "cards" / f"{model_id}.md"
|
|
|
|
|
|
def check_model_card(root, model_path):
|
|
model = read_json(model_path)
|
|
model_id = model["model_id"]
|
|
card_path = card_path_for_model(root, model_id)
|
|
errors = []
|
|
manual_review = []
|
|
|
|
if not card_path.exists():
|
|
return [f"missing card {card_path.relative_to(root).as_posix()}"], []
|
|
|
|
text = card_path.read_text(encoding="utf-8")
|
|
hard_values = [
|
|
model_id,
|
|
model["model_name"],
|
|
model["model_type"],
|
|
model["pipeline_position"],
|
|
model["regression_status"],
|
|
model["version"],
|
|
model["last_updated"],
|
|
model.get("status", "")
|
|
]
|
|
stability_level = model.get("stability_profile", {}).get("stability_level")
|
|
if stability_level:
|
|
hard_values.append(stability_level)
|
|
|
|
for value in hard_values:
|
|
if value and value not in text:
|
|
errors.append(f"{model_id} card missing hard value {value}")
|
|
|
|
for source_id in model.get("source_articles", []):
|
|
if source_id not in text:
|
|
errors.append(f"{model_id} card missing source article {source_id}")
|
|
|
|
for excerpt_id in model.get("source_evidence", []):
|
|
if excerpt_id not in text:
|
|
errors.append(f"{model_id} card missing source evidence {excerpt_id}")
|
|
|
|
for term in KEY_CONTRACT_TERMS.get(model_id, []):
|
|
if term not in text:
|
|
errors.append(f"{model_id} card missing key contract term {term}")
|
|
|
|
manual_review.append(f"{model_id}: long Chinese definitions, examples, and risk notes require human semantic review.")
|
|
return errors, manual_review
|
|
|
|
|
|
def write_report(root, errors, manual_review):
|
|
report_path = root / "reports" / "model_card_sync_report_v0.2.md"
|
|
lines = [
|
|
"# Model Card Sync Report v0.2",
|
|
"",
|
|
f"Status: `{'PASS' if not errors else 'FAIL'}`",
|
|
"",
|
|
]
|
|
if errors:
|
|
lines.append("## Errors")
|
|
lines.append("")
|
|
lines.extend(f"- {error}" for error in errors)
|
|
lines.append("")
|
|
else:
|
|
lines.append("## Hard-Field Result")
|
|
lines.append("")
|
|
lines.append("- Model IDs, names, types, pipeline positions, source IDs, evidence IDs, stability fields, regression status, version, and last_updated are present in Markdown cards.")
|
|
lines.append("- Key output contract and depth-control terms are present.")
|
|
lines.append("")
|
|
|
|
lines.append("## Manual Review Items")
|
|
lines.append("")
|
|
lines.extend(f"- {item}" for item in manual_review)
|
|
report_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
return report_path
|
|
|
|
|
|
def check_sync(root):
|
|
root = Path(root)
|
|
errors = []
|
|
manual_review = []
|
|
for model_path in sorted((root / "models").glob("*.model.json")):
|
|
model_errors, model_manual_review = check_model_card(root, model_path)
|
|
errors.extend(model_errors)
|
|
manual_review.extend(model_manual_review)
|
|
report_path = write_report(root, errors, manual_review)
|
|
return errors, report_path
|
|
|
|
|
|
def main():
|
|
root = Path(__file__).resolve().parents[1]
|
|
errors, report_path = check_sync(root)
|
|
print(f"model/card sync report written to {report_path}")
|
|
if errors:
|
|
for error in errors:
|
|
print(f"ERROR: {error}")
|
|
return 1
|
|
print("model/card sync passed")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|