File size: 4,854 Bytes
0e6887b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
"""InformationExtractor + BackReferenceValidator 测试(任务 6)。

覆盖 Property 1(无捏造)/ 需求 4.1、4.2、4.5、9.5。
"""

from __future__ import annotations

from kernel.task_sheet import DocumentProfile, Intent
from kernel.understanding import BackReferenceValidator, InformationExtractor


SOURCE = "有关物质检测结果 总杂% 0.00 含量 99.19 限度 总杂≤2.0%"


def test_extractor_skips_header_label_as_value():
    llm_out = {
        "items": [
            {"field": "value", "value": "样品名称", "source_ref": "样品名称"},  # 表头,应剔除
            {"field": "value", "value": "0.00", "source_ref": "总杂% 0.00",
             "group": {"attribute": "总杂"}},
        ]
    }
    items = InformationExtractor(llm=None).extract(SOURCE, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out)
    values = [it.value for it in items]
    assert "样品名称" not in values
    assert "0.00" in values


def test_extractor_empty_without_llm_and_no_cache():
    items = InformationExtractor(llm=None).extract(SOURCE, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY)
    assert items == []


def test_validator_drops_unlocatable_items():
    llm_out = {
        "items": [
            {"field": "value", "value": "0.00", "source_ref": "总杂% 0.00"},   # 可定位
            {"field": "value", "value": "-10", "source_ref": "降解 -10%"},      # 原文不存在
        ]
    }
    items = InformationExtractor(llm=None).extract(SOURCE, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out)
    kept, missing = BackReferenceValidator.validate(items, SOURCE)
    kept_vals = [it.value for it in kept]
    assert "0.00" in kept_vals
    assert "-10" not in kept_vals
    assert "value" in missing


def test_validator_whitespace_insensitive_match():
    llm_out = {"items": [{"field": "spec_limit", "value": "总杂≤2.0%", "source_ref": "总杂 ≤ 2.0%"}]}
    items = InformationExtractor(llm=None).extract(SOURCE, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out)
    kept, missing = BackReferenceValidator.validate(items, SOURCE)
    assert len(kept) == 1
    assert missing == []


def test_validator_no_fabrication_property():
    # Property 1:保留项要么数值存在于原文,要么引文为原文子串;捏造项被丢弃。
    import re
    llm_out = {
        "items": [
            {"field": "value", "value": "0.00", "source_ref": "总杂% 0.00"},
            {"field": "value", "value": "fake", "source_ref": "不存在的内容xyz"},
        ]
    }
    items = InformationExtractor(llm=None).extract(SOURCE, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out)
    kept, _ = BackReferenceValidator.validate(items, SOURCE)
    norm_hay = re.sub(r"\s+", "", SOURCE)
    assert all(it.value != "fake" for it in kept)  # 捏造文字项被丢弃
    for it in kept:
        nums = re.findall(r"-?\d+(?:\.\d+)?", it.value)
        grounded = (all(n in SOURCE or any(float(n) == float(m) for m in
                    re.findall(r"-?\d+(?:\.\d+)?", SOURCE)) for n in nums)
                    if nums else re.sub(r"\s+", "", it.source_ref) in norm_hay)
        assert grounded


def test_validator_value_centric_keeps_when_number_present():
    """逐属性提示词下,source_ref 非连续子串但数值在原文 → 应保留(回归修复)。"""
    source = "涂布厚度/mm 膜厚/mm 溶化时限/s 0.5 0.05 60"
    llm_out = {
        "items": [
            {"field": "膜厚", "value": "0.05", "source_ref": "膜厚 0.05",
             "group": {"attribute": "膜厚"}},
        ]
    }
    items = InformationExtractor(llm=None).extract(source, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out)
    kept, missing = BackReferenceValidator.validate(items, source)
    assert len(kept) == 1
    assert kept[0].value == "0.05"
    assert missing == []


def test_validator_drops_fabricated_number_not_in_source():
    source = "涂布厚度/mm 膜厚/mm 0.5 0.05 60"
    llm_out = {
        "items": [
            {"field": "膜厚", "value": "99.99", "source_ref": "膜厚 99.99",
             "group": {"attribute": "膜厚"}},
        ]
    }
    items = InformationExtractor(llm=None).extract(source, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out)
    kept, missing = BackReferenceValidator.validate(items, source)
    assert kept == []
    assert "膜厚" in missing


def test_validator_tolerates_float_format_difference():
    source = "含量 99.50 %"
    llm_out = {"items": [{"field": "含量", "value": "99.5", "source_ref": "含量 99.5"}]}
    items = InformationExtractor(llm=None).extract(source, DocumentProfile(), Intent.DESCRIPTIVE_SUMMARY, llm_out=llm_out)
    kept, _ = BackReferenceValidator.validate(items, source)
    assert len(kept) == 1  # 99.5 ↔ 99.50 视为同值