structverify 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- structverify/__init__.py +83 -0
- structverify/adaptation/__init__.py +0 -0
- structverify/adaptation/adapter_trainer.py +341 -0
- structverify/adaptation/feedback_store.py +31 -0
- structverify/adaptation/kosis_crawler.py +317 -0
- structverify/adaptation/sample_builder.py +149 -0
- structverify/adaptation/synthetic_generator.py +320 -0
- structverify/adaptation/update_embeddings.py +178 -0
- structverify/agent/__init__.py +21 -0
- structverify/agent/builder_agent.py +226 -0
- structverify/agent/conformance_agent.py +171 -0
- structverify/agent/dependency_planner.py +151 -0
- structverify/agent/indexing_agent.py +153 -0
- structverify/agent/indexing_planner.py +169 -0
- structverify/agent/integration_example.py +182 -0
- structverify/agent/loop.py +1165 -0
- structverify/agent/memory.py +207 -0
- structverify/agent/planner.py +817 -0
- structverify/agent/prompts/__init__.py +15 -0
- structverify/agent/prompts/planner_prompts.py +219 -0
- structverify/agent/prompts/reflect_prompts.py +387 -0
- structverify/agent/reflect.py +227 -0
- structverify/agent/runtime_agent.py +1272 -0
- structverify/agent/schemas.py +262 -0
- structverify/agent/source_profiler.py +229 -0
- structverify/agent/tools/__init__.py +64 -0
- structverify/agent/tools/base.py +222 -0
- structverify/agent/tools/calculate.py +244 -0
- structverify/agent/tools/catalog_search.py +859 -0
- structverify/agent/tools/deep_explore.py +293 -0
- structverify/agent/tools/explore_catalog.py +423 -0
- structverify/agent/tools/fetch_evidence.py +922 -0
- structverify/agent/tools/finish.py +423 -0
- structverify/agent/tools/meta_explore.py +267 -0
- structverify/agent/tools/query_rewriter.py +134 -0
- structverify/agent/tools/read_original.py +144 -0
- structverify/agent/tools/replan.py +365 -0
- structverify/agent/workspace.py +958 -0
- structverify/api.py +804 -0
- structverify/config/default.yaml +350 -0
- structverify/core/__init__.py +0 -0
- structverify/core/config_loader.py +30 -0
- structverify/core/pipeline.py +280 -0
- structverify/core/schemas.py +362 -0
- structverify/detection/__init__.py +26 -0
- structverify/detection/_config.py +163 -0
- structverify/detection/_llm.py +24 -0
- structverify/detection/candidate/__init__.py +1 -0
- structverify/detection/candidate/heuristic.py +60 -0
- structverify/detection/candidate/llm.py +51 -0
- structverify/detection/candidate_scorer.py +81 -0
- structverify/detection/claim_detector.py +164 -0
- structverify/detection/claims/__init__.py +1 -0
- structverify/detection/claims/worthiness.py +142 -0
- structverify/detection/domain/__init__.py +1 -0
- structverify/detection/domain/classify.py +84 -0
- structverify/detection/domain/preview.py +36 -0
- structverify/detection/domain/registry.py +99 -0
- structverify/detection/domain_classifier.py +75 -0
- structverify/detection/prompts/__init__.py +1 -0
- structverify/detection/prompts/candidate.py +38 -0
- structverify/detection/prompts/claim_worthiness.py +48 -0
- structverify/detection/prompts/domain.py +41 -0
- structverify/detection/prompts/schema.py +508 -0
- structverify/detection/prompts_loader.py +167 -0
- structverify/detection/schema/__init__.py +1 -0
- structverify/detection/schema/expand.py +83 -0
- structverify/detection/schema/induce.py +441 -0
- structverify/detection/schema/regenerate.py +162 -0
- structverify/detection/schema/temporal_hints.py +130 -0
- structverify/detection/schema/validate.py +193 -0
- structverify/detection/schema_inductor.py +112 -0
- structverify/detection/synthetic_generator.py +270 -0
- structverify/explanation/__init__.py +0 -0
- structverify/explanation/_config.py +18 -0
- structverify/explanation/_llm.py +25 -0
- structverify/explanation/explainer.py +183 -0
- structverify/explanation/fallback.py +29 -0
- structverify/explanation/formatters.py +75 -0
- structverify/explanation/prompts/__init__.py +1 -0
- structverify/explanation/prompts/match.py +27 -0
- structverify/explanation/prompts/mismatch.py +20 -0
- structverify/explanation/prompts/multihop.py +16 -0
- structverify/explanation/prompts/unverifiable.py +17 -0
- structverify/graph/__init__.py +0 -0
- structverify/graph/claim_graph.py +226 -0
- structverify/graph/document_graph.py +487 -0
- structverify/graph/graph_builder.py +238 -0
- structverify/graph/graph_multihop.py +335 -0
- structverify/graph/graph_store.py +281 -0
- structverify/graph/provenance.py +52 -0
- structverify/memory/__init__.py +44 -0
- structverify/memory/agent_memory.py +142 -0
- structverify/memory/embedder.py +69 -0
- structverify/memory/exemplar_store.py +241 -0
- structverify/memory/normalizer.py +91 -0
- structverify/memory/schema.py +119 -0
- structverify/memory/storage/__init__.py +29 -0
- structverify/memory/storage/jsonl_store.py +117 -0
- structverify/memory/working_memory.py +370 -0
- structverify/preprocessing/Dockerfile.scraper +27 -0
- structverify/preprocessing/__init__.py +0 -0
- structverify/preprocessing/extractor.py +574 -0
- structverify/preprocessing/pdf/__init__.py +16 -0
- structverify/preprocessing/pdf/fields.py +95 -0
- structverify/preprocessing/pdf/markdown.py +107 -0
- structverify/preprocessing/pdf/models.py +34 -0
- structverify/preprocessing/pdf/ocr.py +172 -0
- structverify/preprocessing/pdf/pipeline.py +74 -0
- structverify/preprocessing/pdf/reader.py +119 -0
- structverify/preprocessing/pdf/scoring.py +61 -0
- structverify/preprocessing/scraper_sandbox.py +561 -0
- structverify/preprocessing/segmenter.py +48 -0
- structverify/preprocessing/sir_builder.py +240 -0
- structverify/progress.py +591 -0
- structverify/retrieval/__init__.py +0 -0
- structverify/retrieval/base.py +208 -0
- structverify/retrieval/base_connector.py +85 -0
- structverify/retrieval/catalog_ranker.py +300 -0
- structverify/retrieval/catalog_search.py +583 -0
- structverify/retrieval/chunking.py +92 -0
- structverify/retrieval/custom_csv_source.py +386 -0
- structverify/retrieval/custom_db_source.py +396 -0
- structverify/retrieval/custom_docs_source.py +152 -0
- structverify/retrieval/dimension_resolver.py +281 -0
- structverify/retrieval/evidence_subgraph.py +63 -0
- structverify/retrieval/kosis_connector.py +1192 -0
- structverify/retrieval/kosis_relevance.py +142 -0
- structverify/retrieval/kosis_source.py +1541 -0
- structverify/retrieval/query_builder.py +72 -0
- structverify/retrieval/registry.py +133 -0
- structverify/retrieval/relevance_judge.py +141 -0
- structverify/retrieval/row_matcher.py +267 -0
- structverify/storage/__init__.py +0 -0
- structverify/storage/db_manager.py +157 -0
- structverify/storage/dwh_manager.py +92 -0
- structverify/storage/init_db.py +99 -0
- structverify/storage/raw_storage.py +29 -0
- structverify/training/__init__.py +26 -0
- structverify/training/curator.py +124 -0
- structverify/training/dataset.py +134 -0
- structverify/training/doctor.py +99 -0
- structverify/training/evalgate.py +96 -0
- structverify/training/generate.py +101 -0
- structverify/training/loop.py +116 -0
- structverify/training/recipe/train_mlx.py +99 -0
- structverify/training/recipe/train_qlora.py +104 -0
- structverify/training/tasks.py +79 -0
- structverify/utils/__init__.py +0 -0
- structverify/utils/embedding_client.py +248 -0
- structverify/utils/llm_client.py +809 -0
- structverify/utils/logger.py +81 -0
- structverify/verification/__init__.py +0 -0
- structverify/verification/_config.py +45 -0
- structverify/verification/adapters.py +405 -0
- structverify/verification/conformance.py +117 -0
- structverify/verification/decide_verdict.py +216 -0
- structverify/verification/decide_verdict_agent.py +454 -0
- structverify/verification/growth_diff.py +267 -0
- structverify/verification/row_match.py +345 -0
- structverify/verification/units.py +64 -0
- structverify/verification/verdict_thresholds.py +232 -0
- structverify/verification/verifier.py +84 -0
- structverify-0.3.0.dist-info/METADATA +903 -0
- structverify-0.3.0.dist-info/RECORD +168 -0
- structverify-0.3.0.dist-info/WHEEL +5 -0
- structverify-0.3.0.dist-info/licenses/LICENSE +21 -0
- structverify-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""[리팩] fallback 프로필 오차 구간 판정 — verifier._verdict_from_error 분리"""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
|
|
7
|
+
from structverify.core.schemas import (
|
|
8
|
+
Claim,
|
|
9
|
+
Evidence,
|
|
10
|
+
MismatchType,
|
|
11
|
+
VerificationResult,
|
|
12
|
+
VerdictType,
|
|
13
|
+
)
|
|
14
|
+
from structverify.utils.logger import get_logger
|
|
15
|
+
|
|
16
|
+
from ._config import get_verification_settings
|
|
17
|
+
|
|
18
|
+
logger = get_logger(__name__)
|
|
19
|
+
|
|
20
|
+
_THRESHOLD_GTE_KEYWORDS = (
|
|
21
|
+
"넘기", "넘어", "넘는", "넘은", "넘었", "돌파", "초과",
|
|
22
|
+
"이상", "웃돌", "상회", "넘게",
|
|
23
|
+
)
|
|
24
|
+
_THRESHOLD_LTE_KEYWORDS = (
|
|
25
|
+
"미만", "이하", "밑돌", "하회", "못 미", "못미", "안 되", "안되",
|
|
26
|
+
)
|
|
27
|
+
_INCREASE_SFX = ("증가율", "상승률")
|
|
28
|
+
_DECREASE_SFX = ("감소율", "하락률")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _agent_thresholds(config: dict | None) -> dict[str, float]:
|
|
32
|
+
settings = get_verification_settings(config, "agent")
|
|
33
|
+
return {
|
|
34
|
+
"value_match_tolerance": float(settings.get("value_match_tolerance", 0.05)),
|
|
35
|
+
"growth_rate_match_pp": float(settings.get("growth_rate_match_pp", 1.5)),
|
|
36
|
+
"growth_rate_unverifiable_pp": float(
|
|
37
|
+
settings.get("growth_rate_unverifiable_pp", 5.0)
|
|
38
|
+
),
|
|
39
|
+
"difference_rel_tolerance": float(
|
|
40
|
+
settings.get("difference_rel_tolerance", 0.10)
|
|
41
|
+
),
|
|
42
|
+
"difference_min_tolerance": float(
|
|
43
|
+
settings.get("difference_min_tolerance", 0.02)
|
|
44
|
+
),
|
|
45
|
+
"difference_unverifiable_multiplier": float(
|
|
46
|
+
settings.get("difference_unverifiable_multiplier", 3.0)
|
|
47
|
+
),
|
|
48
|
+
"calculate_simple_tolerance": float(
|
|
49
|
+
settings.get("calculate_simple_tolerance", 0.01)
|
|
50
|
+
),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def classify_atomic_ratio_agent(
|
|
55
|
+
diff_ratio: float,
|
|
56
|
+
config: dict | None = None,
|
|
57
|
+
) -> tuple[VerdictType, float]:
|
|
58
|
+
"""loop 일반 수치 비교 — value_match_tolerance (기본 5%)."""
|
|
59
|
+
tol = _agent_thresholds(config)["value_match_tolerance"]
|
|
60
|
+
if diff_ratio < tol:
|
|
61
|
+
return VerdictType.MATCH, 0.85
|
|
62
|
+
return VerdictType.MISMATCH, 0.7
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def classify_growth_rate_pp_agent(
|
|
66
|
+
diff_pp: float,
|
|
67
|
+
config: dict | None = None,
|
|
68
|
+
) -> tuple[VerdictType, float]:
|
|
69
|
+
"""loop 증가율 %p 구간 — ≤1.5 MATCH, ≤5 UNVERIFIABLE."""
|
|
70
|
+
t = _agent_thresholds(config)
|
|
71
|
+
if diff_pp <= t["growth_rate_match_pp"]:
|
|
72
|
+
return VerdictType.MATCH, 0.8
|
|
73
|
+
if diff_pp <= t["growth_rate_unverifiable_pp"]:
|
|
74
|
+
return VerdictType.UNVERIFIABLE, 0.4
|
|
75
|
+
return VerdictType.MISMATCH, 0.7
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def classify_difference_gap_agent(
|
|
79
|
+
gap: float,
|
|
80
|
+
claimed_diff: float,
|
|
81
|
+
config: dict | None = None,
|
|
82
|
+
) -> tuple[VerdictType, float]:
|
|
83
|
+
"""loop 차이값 — tol=max(|claimed|×10%, min_tol)."""
|
|
84
|
+
t = _agent_thresholds(config)
|
|
85
|
+
tol = max(abs(claimed_diff) * t["difference_rel_tolerance"], t["difference_min_tolerance"])
|
|
86
|
+
if gap <= tol:
|
|
87
|
+
return VerdictType.MATCH, 0.8
|
|
88
|
+
if gap <= tol * t["difference_unverifiable_multiplier"]:
|
|
89
|
+
return VerdictType.UNVERIFIABLE, 0.4
|
|
90
|
+
return VerdictType.MISMATCH, 0.7
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def classify_calculate_simple_agent(
|
|
94
|
+
diff_ratio: float,
|
|
95
|
+
config: dict | None = None,
|
|
96
|
+
) -> tuple[VerdictType, float]:
|
|
97
|
+
"""loop calculate 일반 수치 — 기본 1% (5%와 별도 유지)."""
|
|
98
|
+
tol = _agent_thresholds(config)["calculate_simple_tolerance"]
|
|
99
|
+
if diff_ratio < tol:
|
|
100
|
+
return VerdictType.MATCH, 0.8
|
|
101
|
+
return VerdictType.MISMATCH, 0.7
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def detect_threshold_direction(claim: Claim) -> str | None:
|
|
105
|
+
"""부등식 주장 방향 — gte / lte / None (loop._detect_threshold_direction).
|
|
106
|
+
|
|
107
|
+
claim_text + schema.modifier에서 키워드 탐지.
|
|
108
|
+
'넘는/이상' → gte, '미만/이하' → lte. 양쪽 키워드 동시 존재 시 None.
|
|
109
|
+
"""
|
|
110
|
+
text = claim.claim_text or ""
|
|
111
|
+
modifier = ""
|
|
112
|
+
if claim.schema is not None:
|
|
113
|
+
modifier = (claim.schema.modifier or "")
|
|
114
|
+
haystack = f"{text} {modifier}"
|
|
115
|
+
|
|
116
|
+
has_gte = any(kw in haystack for kw in _THRESHOLD_GTE_KEYWORDS)
|
|
117
|
+
has_lte = any(kw in haystack for kw in _THRESHOLD_LTE_KEYWORDS)
|
|
118
|
+
if has_gte and has_lte:
|
|
119
|
+
return None
|
|
120
|
+
if has_gte:
|
|
121
|
+
return "gte"
|
|
122
|
+
if has_lte:
|
|
123
|
+
return "lte"
|
|
124
|
+
return None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def growth_rate_direction_mismatch(
|
|
128
|
+
indicator: str,
|
|
129
|
+
calc_rate: float,
|
|
130
|
+
) -> bool:
|
|
131
|
+
"""증가/감소 방향 불일치 — loop 부호 가드 (패치 J).
|
|
132
|
+
|
|
133
|
+
indicator가 '증가율'로 끝나는데 계산값이 음수이거나,
|
|
134
|
+
'감소율'로 끝나는데 계산값이 양수이면 True.
|
|
135
|
+
"""
|
|
136
|
+
ind = (indicator or "").strip()
|
|
137
|
+
expects_inc = any(ind.endswith(s) for s in _INCREASE_SFX)
|
|
138
|
+
expects_dec = any(ind.endswith(s) for s in _DECREASE_SFX)
|
|
139
|
+
return (expects_inc and calc_rate < 0) or (expects_dec and calc_rate > 0)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _fallback_thresholds(config: dict | None) -> dict[str, float]:
|
|
143
|
+
settings = get_verification_settings(config, "fallback")
|
|
144
|
+
return {
|
|
145
|
+
"match_max_error": float(settings.get("match_max_error", 0.10)),
|
|
146
|
+
"unverifiable_max_error": float(settings.get("unverifiable_max_error", 0.30)),
|
|
147
|
+
"mismatch_max_error": float(settings.get("mismatch_max_error", 0.90)),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def classify_error_rate_fallback(
|
|
152
|
+
error_rate: float,
|
|
153
|
+
config: dict | None = None,
|
|
154
|
+
) -> tuple[VerdictType, float, bool]:
|
|
155
|
+
"""오차율 → (verdict, confidence, mismatch_type_필요여부).
|
|
156
|
+
|
|
157
|
+
fallback v7 구간:
|
|
158
|
+
≤ match_max → MATCH
|
|
159
|
+
≤ unverifiable_max → UNVERIFIABLE
|
|
160
|
+
> mismatch_max → UNVERIFIABLE (표 매칭 오류 의심)
|
|
161
|
+
그 외 → MISMATCH
|
|
162
|
+
"""
|
|
163
|
+
t = _fallback_thresholds(config)
|
|
164
|
+
diff_pct = error_rate * 100
|
|
165
|
+
|
|
166
|
+
if error_rate <= t["match_max_error"]:
|
|
167
|
+
return VerdictType.MATCH, min(0.95, 1.0 - error_rate), False
|
|
168
|
+
|
|
169
|
+
if error_rate <= t["unverifiable_max_error"]:
|
|
170
|
+
logger.info(
|
|
171
|
+
f"검증 결과: unverifiable (오차: {diff_pct:.1f}% — 유사하나 확신 없음)"
|
|
172
|
+
)
|
|
173
|
+
return VerdictType.UNVERIFIABLE, 0.4, False
|
|
174
|
+
|
|
175
|
+
if error_rate > t["mismatch_max_error"]:
|
|
176
|
+
logger.info(
|
|
177
|
+
f"검증 결과: unverifiable (오차: {diff_pct:.1f}% — 테이블 매칭 오류 의심)"
|
|
178
|
+
)
|
|
179
|
+
return VerdictType.UNVERIFIABLE, 0.3, False
|
|
180
|
+
|
|
181
|
+
return VerdictType.MISMATCH, min(0.9, error_rate), True
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def verdict_from_error(
|
|
185
|
+
claim: Claim,
|
|
186
|
+
evidence: Evidence,
|
|
187
|
+
error_rate: float,
|
|
188
|
+
best_match: dict | None,
|
|
189
|
+
config: dict,
|
|
190
|
+
classify_mismatch: Callable[[Claim, Evidence, float, dict], MismatchType],
|
|
191
|
+
) -> VerificationResult:
|
|
192
|
+
"""오차율 → VerificationResult (fallback 프로필).
|
|
193
|
+
|
|
194
|
+
[v3] factcheck_test.py v7 구간 — config.verification.profiles.fallback
|
|
195
|
+
[v6.15] 시점 미상 + tier3 best_match → UNVERIFIABLE
|
|
196
|
+
"""
|
|
197
|
+
diff_pct = error_rate * 100
|
|
198
|
+
|
|
199
|
+
schema_tp = (
|
|
200
|
+
claim.schema.time_period if claim.schema and claim.schema.time_period else ""
|
|
201
|
+
)
|
|
202
|
+
has_year = bool(re.search(r"\d{4}", schema_tp))
|
|
203
|
+
matched_tier3 = bool(best_match and best_match.get("_tier") == 3)
|
|
204
|
+
if not has_year and matched_tier3:
|
|
205
|
+
logger.info(
|
|
206
|
+
f"검증 결과: unverifiable "
|
|
207
|
+
f"(오차: {diff_pct:.1f}% — 시점 미상 + 시점 매칭 실패, 가짜 매칭 위험) "
|
|
208
|
+
f"→ 엉뚱한 evidence 제거"
|
|
209
|
+
)
|
|
210
|
+
return VerificationResult(
|
|
211
|
+
claim_id=claim.claim_id,
|
|
212
|
+
verdict=VerdictType.UNVERIFIABLE,
|
|
213
|
+
confidence=0.25,
|
|
214
|
+
evidence=None,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
verdict, conf, need_mtype = classify_error_rate_fallback(error_rate, config)
|
|
218
|
+
mtype: MismatchType | None = None
|
|
219
|
+
|
|
220
|
+
if verdict == VerdictType.MATCH:
|
|
221
|
+
logger.info(f"검증 결과: match (오차: {diff_pct:.1f}%)")
|
|
222
|
+
elif verdict == VerdictType.MISMATCH and need_mtype:
|
|
223
|
+
mtype = classify_mismatch(claim, evidence, diff_pct, config)
|
|
224
|
+
logger.info(f"검증 결과: mismatch (오차: {diff_pct:.1f}%)")
|
|
225
|
+
|
|
226
|
+
return VerificationResult(
|
|
227
|
+
claim_id=claim.claim_id,
|
|
228
|
+
verdict=verdict,
|
|
229
|
+
confidence=conf,
|
|
230
|
+
evidence=evidence,
|
|
231
|
+
mismatch_type=mtype,
|
|
232
|
+
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
verification/verifier.py — Deterministic Verification Engine (Step 8) v3
|
|
3
|
+
|
|
4
|
+
수치 비교는 LLM이 아닌 deterministic engine이 수행 (hallucination 방지).
|
|
5
|
+
|
|
6
|
+
[신준수]
|
|
7
|
+
- 수치 비교 로직 및 불일치 유형 세분화 구현 담당
|
|
8
|
+
|
|
9
|
+
[김예슬 - 2026-05-06 / v3]
|
|
10
|
+
- factcheck_test.py v7(박재윤) numeric_check 로직 전면 반영
|
|
11
|
+
· normalize_value: 천명개월 예외 처리
|
|
12
|
+
· is_same_unit_type: 천명개월 예외 처리
|
|
13
|
+
· 전체 행 탐색: evidence.raw_response["row"] 전체 순회하여 best match
|
|
14
|
+
· 오차 구간: ≤10% MATCH / 10~30% UNVERIFIABLE / 30~90% MISMATCH / >90% UNVERIFIABLE
|
|
15
|
+
· value=0.0 → UNVERIFIABLE
|
|
16
|
+
· 연도 ±2년 필터
|
|
17
|
+
|
|
18
|
+
[설계 원칙]
|
|
19
|
+
- Step 8은 의도적으로 LLM을 사용하지 않습니다
|
|
20
|
+
- 수치 비교에 LLM을 쓰면 hallucination이 발생할 수 있음 → deterministic만 사용
|
|
21
|
+
- 자연어 설명은 Step 9(explainer.py)에서 LLM이 생성
|
|
22
|
+
|
|
23
|
+
[참고] FEVER (Thorne et al., NAACL 2018)
|
|
24
|
+
SUPPORTS/REFUTES/NEI 3단계 판정 → match/mismatch/unverifiable 매핑
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# 수정자: 박재윤
|
|
28
|
+
# 수정 날짜: 2026-05-14
|
|
29
|
+
# 수정 내용: 연도 필터 ±2 → 정확 일치로 변경
|
|
30
|
+
# · 기존: abs(claim_year - kv_year) > 2 → 다른 연도 데이터 비교 허용
|
|
31
|
+
# · 변경: abs(claim_year - kv_year) > 0 → 연도 불일치 시 무조건 skip
|
|
32
|
+
# · 이유: 2026년 기사 수치를 2024년 KOSIS 연간 평균과 비교하는 오판정 방지
|
|
33
|
+
"""
|
|
34
|
+
# 수정자: 신준수
|
|
35
|
+
# 수정 날짜: 2026-04-27
|
|
36
|
+
# 수정 내용: _classify_mismatch 우선순위 분기 및 헬퍼(연도·집단·과장 임계) 구현
|
|
37
|
+
# 수정자: 김예슬
|
|
38
|
+
# 수정 날짜: 2026-05-06
|
|
39
|
+
# 수정 내용: normalize_value / is_same_unit_type / 90% 임계 추가
|
|
40
|
+
# [2026-05-14 | 이수민] memory/v1: working memory 도메인 가드 추가
|
|
41
|
+
# - verify_claim() 시그니처에 memory: DocumentWorkingMemory 인자 추가
|
|
42
|
+
# - evidence.category_path와 memory.domain 불일치 시 UNVERIFIABLE(DOMAIN_MISMATCH) 반환
|
|
43
|
+
# - 거절된 stat_id는 memory.rejected_stat_ids에 기록 (false match 방지)
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
from typing import TYPE_CHECKING
|
|
47
|
+
|
|
48
|
+
from structverify.core.schemas import Claim, Evidence, VerificationResult
|
|
49
|
+
# [리팩] Evidence → NormalizedInput
|
|
50
|
+
from .adapters import from_evidence
|
|
51
|
+
# [리팩] 판정 메인 진입
|
|
52
|
+
from .decide_verdict import decide_verdict
|
|
53
|
+
|
|
54
|
+
if TYPE_CHECKING:
|
|
55
|
+
from structverify.memory.working_memory import DocumentWorkingMemory
|
|
56
|
+
from structverify.graph.claim_graph import ClaimGraph
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def verify_claim(
|
|
60
|
+
claim: Claim,
|
|
61
|
+
evidence: Evidence | None,
|
|
62
|
+
config: dict | None = None,
|
|
63
|
+
graph: "ClaimGraph | None" = None,
|
|
64
|
+
memory: "DocumentWorkingMemory | None" = None,
|
|
65
|
+
) -> VerificationResult:
|
|
66
|
+
"""
|
|
67
|
+
공식 통계와 기사 수치를 비교하여 판정 (LLM 미사용).
|
|
68
|
+
|
|
69
|
+
[v3] factcheck_test.py v7 로직 전면 반영
|
|
70
|
+
[v6 멀티홉] graph가 있으면 claim의 시점을 그래프에서 resolved된 절대 시점으로
|
|
71
|
+
보정하여 KOSIS row 매칭에 사용. claim.schema.time_period가
|
|
72
|
+
"작년" 같은 상대 표현이어도 그래프 traverse로 2023이 나옴.
|
|
73
|
+
[v7 이수민 2026-05-14] memory 도메인 가드:
|
|
74
|
+
memory가 있고 evidence.category_path가 문서 도메인과 어긋나면
|
|
75
|
+
DOMAIN_MISMATCH로 UNVERIFIABLE 반환 (false match 방지).
|
|
76
|
+
[리팩] 판정 본문 → decide_verdict(profile="fallback") 위임.
|
|
77
|
+
"""
|
|
78
|
+
config = config or {}
|
|
79
|
+
normalized, early = from_evidence(
|
|
80
|
+
claim, evidence, graph=graph, memory=memory,
|
|
81
|
+
)
|
|
82
|
+
if early is not None:
|
|
83
|
+
return early
|
|
84
|
+
return decide_verdict(claim, normalized, config, profile="fallback")
|