Files
ljwx-deploy/scripts/knowledge/validate_candidate_artifacts.py
T

125 lines
4.0 KiB
Python
Raw 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
"""Validate candidate knowledge artifacts committed for review."""
from __future__ import annotations
import argparse
import datetime as dt
import sys
from pathlib import Path
try:
import jsonschema
except ImportError as exc:
raise SystemExit(
f"缺少依赖 jsonschema,请使用 uvx --with jsonschema --with pyyaml python <script>\n{exc}"
) from exc
try:
import yaml
except ImportError as exc:
raise SystemExit(
f"缺少依赖 PyYAML,请使用 uvx --with pyyaml --with jsonschema python <script>\n{exc}"
) from exc
class ValidationError(Exception):
"""Raised when a candidate artifact is invalid."""
def normalize_yaml_value(value: object) -> object:
"""Normalize YAML-decoded scalars to contract-friendly JSON values."""
if isinstance(value, dt.datetime):
return value.isoformat()
if isinstance(value, dt.date):
return value.isoformat()
if isinstance(value, list):
return [normalize_yaml_value(item) for item in value]
if isinstance(value, dict):
return {str(key): normalize_yaml_value(item) for key, item in value.items()}
return value
def load_yaml_mapping(path: Path) -> dict[str, object]:
raw = yaml.safe_load(path.read_text(encoding="utf-8"))
if raw is None:
return {}
if not isinstance(raw, dict):
raise ValidationError(f"{path} 根节点必须是 mapping")
normalized = normalize_yaml_value(raw)
if not isinstance(normalized, dict):
raise ValidationError(f"{path} 根节点必须是 mapping")
return {str(key): value for key, value in normalized.items()}
def load_validator(path: Path) -> jsonschema.Draft202012Validator:
schema = load_yaml_mapping(path)
jsonschema.Draft202012Validator.check_schema(schema)
return jsonschema.Draft202012Validator(schema)
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Validate candidate artifacts under knowledge/candidates"
)
parser.add_argument("--root", type=Path, default=Path("."), help="Repository root")
return parser.parse_args()
def main() -> int:
args = parse_args()
repo_root = args.root.resolve()
candidate_root = repo_root / "knowledge/candidates"
candidate_schema_path = repo_root / "platform/artifacts/artifact.schema.yaml"
insight_schema_path = (
repo_root / "platform/extraction/conversation-insight.schema.yaml"
)
if not candidate_root.exists():
print("candidate artifact 校验通过(未发现 knowledge/candidates 目录)")
return 0
candidate_validator = load_validator(candidate_schema_path)
insight_validator = load_validator(insight_schema_path)
yaml_files = sorted(candidate_root.rglob("*.yaml"))
candidate_files = [
path for path in yaml_files if not path.name.endswith(".insight.yaml")
]
insight_files = [path for path in yaml_files if path.name.endswith(".insight.yaml")]
errors: list[str] = []
for artifact_file in candidate_files:
payload = load_yaml_mapping(artifact_file)
try:
candidate_validator.validate(payload)
except jsonschema.ValidationError as exc:
errors.append(
f"{artifact_file} 不符合 candidate artifact schema: {exc.message}"
)
for insight_file in insight_files:
payload = load_yaml_mapping(insight_file)
try:
insight_validator.validate(payload)
except jsonschema.ValidationError as exc:
errors.append(
f"{insight_file} 不符合 conversation insight schema: {exc.message}"
)
if errors:
print("candidate artifact 校验失败:")
for message in errors:
print(f"- {message}")
return 1
print(
"candidate artifact 校验通过"
f"(artifact={len(candidate_files)} 个, insight={len(insight_files)} 个)"
)
return 0
if __name__ == "__main__":
sys.exit(main())