#!/usr/bin/env python3 """算法测量结果契约 1.0 的零第三方依赖校验器。""" from __future__ import annotations import json import math import re import sys from pathlib import Path from typing import Any METRIC_CODE_RE = re.compile(r"^[a-z0-9]+(?:[._-][a-z0-9]+)*$") TASK_NO_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$") TASK_NAME_PREFIX_RE = re.compile(r"^任务\s*\d+") FINAL_STATUSES = {"pass", "warning", "fail"} def is_number(value: Any) -> bool: return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value) def validate(payload: Any) -> list[str]: errors: list[str] = [] if not isinstance(payload, dict): return ["根节点必须是 JSON 对象"] if not isinstance(payload.get("schemaVersion"), int) or payload["schemaVersion"] < 1: errors.append("schemaVersion 必须是大于等于 1 的整数") if payload.get("measurementContractVersion") != "1.0": errors.append('measurementContractVersion 必须为 "1.0"') if payload.get("status") not in {"completed", "error"}: errors.append("status 必须为 completed 或 error") measurements = payload.get("measurements") if payload.get("status") == "completed" and not isinstance(measurements, list): errors.append("completed 结果必须包含 measurements 数组") measurements = [] elif measurements is None: measurements = [] elif not isinstance(measurements, list): errors.append("measurements 必须是数组") measurements = [] unique_keys: set[tuple[str, str, str]] = set() for index, item in enumerate(measurements): path = f"measurements[{index}]" if not isinstance(item, dict): errors.append(f"{path} 必须是对象") continue task_no = item.get("taskNo") metric_code = item.get("metricCode") name = item.get("name") position = item.get("position", "") value = item.get("value", ...) status = item.get("status") if not isinstance(task_no, str) or not TASK_NO_RE.fullmatch(task_no): errors.append(f"{path}.taskNo 必须是非空规范字符串,数字任务号也要加引号") if not isinstance(metric_code, str) or not METRIC_CODE_RE.fullmatch(metric_code): errors.append(f"{path}.metricCode 格式无效") if not isinstance(name, str) or not name.strip(): errors.append(f"{path}.name 必须是非空字符串") elif TASK_NAME_PREFIX_RE.search(name): errors.append(f"{path}.name 不得包含任务号前缀,请使用 taskNo 字段") if position and not isinstance(position, str): errors.append(f"{path}.position 必须是字符串") if value is ... or (value is not None and not is_number(value)): errors.append(f"{path}.value 必须是有限数值或 null") if status not in FINAL_STATUSES: errors.append(f"{path}.status 必须为 pass、warning 或 fail,正式结果不得使用 info") if value is None and status == "pass": errors.append(f"{path} 的 value 为 null 时不能判定为 pass") if "baseline" in item and (not is_number(item["baseline"]) or item["baseline"] <= 0): errors.append(f"{path}.baseline 必须是正数") if "tolerance" in item and (not is_number(item["tolerance"]) or item["tolerance"] < 0): errors.append(f"{path}.tolerance 必须是非负有限数值") if isinstance(task_no, str) and isinstance(metric_code, str) and isinstance(position, str): key = (task_no, metric_code, position) if key in unique_keys: errors.append(f"{path} 与其他正式测量项重复:taskNo + metricCode + position 必须唯一") unique_keys.add(key) extra = payload.get("extra", {}) if extra is not None and not isinstance(extra, dict): errors.append("extra 必须是对象") extra = {} intermediate = extra.get("intermediateMetrics", []) if isinstance(extra, dict) else [] if not isinstance(intermediate, list): errors.append("extra.intermediateMetrics 必须是数组") intermediate = [] for index, item in enumerate(intermediate): path = f"extra.intermediateMetrics[{index}]" if not isinstance(item, dict): errors.append(f"{path} 必须是对象") continue if any(field in item for field in ("taskNo", "status", "tolerance")): errors.append(f"{path} 不得包含 taskNo、status 或 tolerance") code = item.get("metricCode") if not isinstance(code, str) or not METRIC_CODE_RE.fullmatch(code): errors.append(f"{path}.metricCode 格式无效") if not isinstance(item.get("name"), str) or not item["name"].strip(): errors.append(f"{path}.name 必须是非空字符串") value = item.get("value", ...) if value is ... or (value is not None and not is_number(value)): errors.append(f"{path}.value 必须是有限数值或 null") return errors def main() -> int: if len(sys.argv) != 2: print(f"用法: {Path(sys.argv[0]).name} /path/to/result.json", file=sys.stderr) return 2 path = Path(sys.argv[1]) try: payload = json.loads(path.read_text(encoding="utf-8"), parse_constant=lambda value: (_ for _ in ()).throw(ValueError(f"非法数值 {value}"))) except (OSError, json.JSONDecodeError, ValueError) as exc: print(f"校验失败: 无法读取标准 JSON:{exc}", file=sys.stderr) return 1 errors = validate(payload) if errors: print(f"校验失败,共 {len(errors)} 个问题:", file=sys.stderr) for error in errors: print(f"- {error}", file=sys.stderr) return 1 print(f"校验通过:{path}") return 0 if __name__ == "__main__": raise SystemExit(main())