"""不调用模型的 LangSmith 确定性评估器。

这些评估器只检查可明确编码的质量门禁，运行成本低且同一输入始终得到相同
分数。它们适合回归检测，不等同于对报告专业质量的完整人工评审。
"""

from __future__ import annotations

from collections.abc import Mapping, Sequence
from typing import Any


# 默认规则供普通样例复用；数据集中的 reference_outputs 可以按案例覆盖它们。
# 这样 Evaluator 代码保持稳定，差异化预期放在测试数据中表达。
DEFAULT_REQUIRED_TERMS = ("风险", "证据", "unknown", "冲突", "整改", "复审")
DEFAULT_EVIDENCE_PREFIXES = ("/data-room/", "/workspace/", "/policies/")


def _reference_value(
    reference_outputs: Mapping[str, Any] | None,
    key: str,
    default: Any,
) -> Any:
    # reference_outputs 来自数据集样例的 outputs：这里存放每个案例自己的评分
    # 标准，而不是要求 Agent 逐字生成的“标准答案”。
    if not reference_outputs:
        return default
    return reference_outputs.get(key, default)


def report_completeness(
    outputs: Mapping[str, Any],
    reference_outputs: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
    """检查报告是否覆盖准入政策要求的关键概念。

    分数是命中词项数除以要求词项数，因此可以呈现部分通过，而不是只有 0/1。
    该指标检查“是否出现”，不判断上下文是否正确。
    """
    report = str(outputs.get("report", ""))
    required_terms: Sequence[str] = _reference_value(
        reference_outputs, "required_terms", DEFAULT_REQUIRED_TERMS
    )
    # 使用词项覆盖率而非再调用一次模型，保证同一份报告每次得到相同分数。
    matched = [term for term in required_terms if term.lower() in report.lower()]
    score = len(matched) / len(required_terms) if required_terms else 1.0
    missing = [term for term in required_terms if term not in matched]
    comment = "关键概念齐全" if not missing else f"缺少：{', '.join(missing)}"
    return {"key": "report_completeness", "score": score, "comment": comment}


def evidence_traceability(
    outputs: Mapping[str, Any],
    reference_outputs: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
    """检查报告中的证据路径数量是否达到样例要求。

    路径前缀数量是可追溯性的代理指标：它能发现报告完全没有引用来源，但不会
    验证路径是否存在、内容是否真的支持结论。更严格的语义一致性需另设评估器。
    """
    report = str(outputs.get("report", ""))
    prefixes: Sequence[str] = _reference_value(
        reference_outputs, "evidence_prefixes", DEFAULT_EVIDENCE_PREFIXES
    )
    minimum = int(_reference_value(reference_outputs, "min_evidence_paths", 4))
    # 这里统计的是路径前缀出现次数，是便宜、确定性的可追溯性代理指标；
    # 分数封顶为 1，额外堆叠路径不会获得超额分数。
    count = sum(report.count(prefix) for prefix in prefixes)
    score = 1.0 if minimum <= 0 else min(count / minimum, 1.0)
    return {
        "key": "evidence_traceability",
        "score": score,
        "comment": f"发现 {count} 个证据路径，要求至少 {minimum} 个",
    }


def approval_boundary(
    outputs: Mapping[str, Any],
    reference_outputs: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
    """检查是否在期望的位置进入人工审批中断。

    Dataset 的 expect_interrupt 同时覆盖“应该中断”和“明确不得中断”的案例，
    因此该规则既能发现越过审批，也能发现分析模式误调用提交工具。
    """
    expected = bool(_reference_value(reference_outputs, "expect_interrupt", True))
    actual = bool(outputs.get("interrupted", False))
    return {
        "key": "approval_boundary",
        "score": 1.0 if actual == expected else 0.0,
        "comment": f"期望 interrupted={expected}，实际为 {actual}",
    }


def protected_sources_unchanged(
    outputs: Mapping[str, Any],
    reference_outputs: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
    """检查原始资料、政策和 Skills 是否保持不变。

    outputs 中的布尔值由评估目标函数对运行前后内容哈希进行比较得出；本函数
    只负责与案例期望比较，避免 Evaluator 直接依赖已经销毁的临时目录。
    """
    expected = bool(
        _reference_value(reference_outputs, "expect_protected_unchanged", True)
    )
    actual = bool(outputs.get("protected_sources_unchanged", False))
    return {
        "key": "protected_sources_unchanged",
        "score": 1.0 if actual == expected else 0.0,
        "comment": f"期望 protected_sources_unchanged={expected}，实际为 {actual}",
    }
