Files
zk-data-agent/skills/intervention-data/scripts/validate_intervention_records.py
T
2026-05-19 11:59:49 +08:00

221 lines
9.2 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
"""校验单句精确干预和正则干预候选 TSV。"""
from __future__ import annotations
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Any
from intervention_common import (
DEFAULT_EXACT_SOURCE,
DEFAULT_REGEX_SOURCE,
build_source_summary,
domain_compatible,
historical_targets,
infer_configured_dispatch_domain,
infer_dispatch_domain_for_device,
load_exact_records,
load_regex_records,
normalize_device,
normalize_target,
parse_candidate_lines,
validate_target,
json_dumps,
)
def load_input_text(path: str | None) -> str:
if path:
return Path(path).read_text(encoding="utf-8")
return sys.stdin.read()
def validate_exact_rows(rows: list[list[str]], args: argparse.Namespace) -> dict[str, Any]:
summary = build_source_summary(Path(args.exact_source), Path(args.regex_source))
devices = set(summary["devices"])
known_historical_targets = historical_targets(summary)
existing: dict[tuple[str, str], list[Any]] = {}
for record in load_exact_records(Path(args.exact_source)):
existing.setdefault((record.device, record.query), []).append(record)
results = []
for index, row in enumerate(rows, start=1):
errors: list[str] = []
warnings: list[str] = []
if len(row) != 4:
errors.append(f"单句精确干预必须是 4 列,当前 {len(row)}")
results.append({"line": index, "valid": False, "errors": errors, "warnings": warnings, "row": row})
continue
device, query, target, dispatch_domain = [cell.strip() for cell in row]
device = normalize_device(device)
target = normalize_target(target)
if device not in devices:
errors.append(f"设备不在历史枚举中:{device},可选:{', '.join(sorted(devices))}")
if not query:
errors.append("query 不能为空")
if "\t" in query or "\n" in query or "\r" in query:
errors.append("query 不能包含制表符或换行")
target_result = validate_target(target)
if not target_result["valid"]:
if target in known_historical_targets:
warnings.append("target 未通过 label-master 知识库校验,但历史干预文件中已存在;建议后续补齐标签知识库")
target_result["normalized_output"] = target
else:
errors.extend([f"target 不合法:{message}" for message in target_result.get("errors", [])])
if not dispatch_domain:
errors.append("下发domain 不能为空")
inferred = infer_dispatch_domain_for_device(target, device, summary)
if inferred and dispatch_domain and dispatch_domain != inferred["dispatch_domain"]:
message = (
f"下发domain 与推断映射不一致:当前 {dispatch_domain}"
f"推断为 {inferred['dispatch_domain']},来源 {inferred.get('source', 'historical')},置信度 {inferred.get('confidence')}"
)
if args.strict_domain:
errors.append(message)
else:
warnings.append(message)
if not inferred:
warnings.append("未在历史精确干预表中找到该 target 的下发domain 映射,需要人工确认")
configured = infer_configured_dispatch_domain(target, device, summary)
if configured and dispatch_domain and not domain_compatible(dispatch_domain, configured["dispatch_domain"]):
warnings.append(
f"下发domain 与 type_match_agent 配置不兼容:当前 {dispatch_domain}"
f"配置 {configured['matched_key']} -> {configured['dispatch_domain']}"
)
if not configured:
warnings.append("type_match_agent 下发表中未找到该 Agent tag 的设备/通用映射")
conflicts = existing.get((device, query), [])
for item in conflicts:
if item.target == target and item.dispatch_domain == dispatch_domain:
warnings.append(f"历史文件已存在相同单句干预:line {item.line_no}")
else:
errors.append(
f"历史文件存在同设备同 query 的不同干预:line {item.line_no}"
f"{item.target} / {item.dispatch_domain}"
)
results.append(
{
"line": index,
"valid": not errors,
"errors": errors,
"warnings": warnings,
"normalized": {
"device": device,
"query": query,
"target": target_result.get("normalized_output", target),
"dispatch_domain": dispatch_domain,
},
"configured_dispatch_domain": configured,
}
)
return summarize_results(results)
def validate_regex_rows(rows: list[list[str]], args: argparse.Namespace) -> dict[str, Any]:
summary = build_source_summary(Path(args.exact_source), Path(args.regex_source))
devices = set(summary["devices"])
known_historical_targets = historical_targets(summary)
existing: dict[tuple[str, str], list[Any]] = {}
for record in load_regex_records(Path(args.regex_source)):
existing.setdefault((record.device, record.pattern), []).append(record)
results = []
for index, row in enumerate(rows, start=1):
errors: list[str] = []
warnings: list[str] = []
if len(row) != 3:
errors.append(f"正则干预必须是 3 列,当前 {len(row)}")
results.append({"line": index, "valid": False, "errors": errors, "warnings": warnings, "row": row})
continue
device, pattern, target = [cell.strip() for cell in row]
device = normalize_device(device)
target = normalize_target(target)
if device not in devices:
errors.append(f"设备不在历史枚举中:{device},可选:{', '.join(sorted(devices))}")
if not pattern:
errors.append("正则不能为空")
else:
try:
re.compile(pattern)
except re.error as exc:
errors.append(f"正则无法编译:{exc}")
if "query#" not in pattern:
errors.append("正则必须包含 query# 片段")
if not pattern.startswith("^") or not pattern.endswith("$"):
warnings.append("历史常见正则通常使用 ^...$ 完整锚定,建议确认是否需要锚定")
target_result = validate_target(target)
if not target_result["valid"]:
if target in known_historical_targets:
warnings.append("target 未通过 label-master 知识库校验,但历史干预文件中已存在;建议后续补齐标签知识库")
target_result["normalized_output"] = target
else:
errors.extend([f"target 不合法:{message}" for message in target_result.get("errors", [])])
conflicts = existing.get((device, pattern), [])
for item in conflicts:
if item.target == target:
warnings.append(f"历史文件已存在相同正则干预:line {item.line_no}")
else:
errors.append(
f"历史文件存在同设备同正则的不同干预:line {item.line_no}{item.target}"
)
results.append(
{
"line": index,
"valid": not errors,
"errors": errors,
"warnings": warnings,
"normalized": {
"device": device,
"pattern": pattern,
"target": target_result.get("normalized_output", target),
},
}
)
return summarize_results(results)
def summarize_results(results: list[dict[str, Any]]) -> dict[str, Any]:
return {
"valid": all(item["valid"] for item in results),
"total": len(results),
"invalid": sum(1 for item in results if not item["valid"]),
"warning_count": sum(len(item.get("warnings", [])) for item in results),
"results": results,
}
def main() -> int:
parser = argparse.ArgumentParser(description="校验干预 TSV 候选")
parser.add_argument("--mode", choices=["exact", "regex"], required=True, help="干预类型")
parser.add_argument("--input", help="候选 TSV 文件路径;不传则读取 stdin")
parser.add_argument("--exact-source", default=str(DEFAULT_EXACT_SOURCE), help="历史单句精确干预 TSV")
parser.add_argument("--regex-source", default=str(DEFAULT_REGEX_SOURCE), help="历史正则干预 TSV")
parser.add_argument("--strict-domain", action="store_true", help="下发domain 和历史多数映射不一致时直接报错")
args = parser.parse_args()
text = load_input_text(args.input)
rows = parse_candidate_lines(text)
if args.mode == "exact":
payload = validate_exact_rows(rows, args)
else:
payload = validate_regex_rows(rows, args)
print(json_dumps(payload))
return 0 if payload["valid"] else 1
if __name__ == "__main__":
raise SystemExit(main())