#!/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())