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,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
preprocessing/sir_builder.py — SIR Tree 빌더 (v1: 기본 정제 + GraphRAG 문맥 엣지)
|
|
3
|
+
|
|
4
|
+
[김예슬 - 2026-04-24]
|
|
5
|
+
- _clean_text_basic() 추가:
|
|
6
|
+
· URL 제거, 연속 빈줄 정리, 연속 공백 정리 (최소 정제만)
|
|
7
|
+
· LLM 정제는 추후 use_llm_clean: true 옵션으로 활성화 예정
|
|
8
|
+
- 절대 char_offset 보정:
|
|
9
|
+
· 기존: segmenter 상대 offset 그대로 사용 (버그)
|
|
10
|
+
· 수정: block_start + 상대 offset → 절대 offset
|
|
11
|
+
- 전역 sent_id 부여:
|
|
12
|
+
· 기존: s0000, s0001 (블록 내 중복 가능)
|
|
13
|
+
· 수정: b0000s0000, b0001s0000 (문서 전체 유일)
|
|
14
|
+
- _detect_block_type(): LIST, TABLE 감지 추가
|
|
15
|
+
- extract_context_edges(): GraphRAG용 문맥 엣지 추출 추가
|
|
16
|
+
· NEXT_SENT: 문장 간 순서 (문맥 흐름 보존)
|
|
17
|
+
· IN_BLOCK: 문장 → 소속 문단
|
|
18
|
+
· IN_DOC: 문단 → 소속 문서
|
|
19
|
+
→ graph_builder.py에서 Neo4j에 저장하면
|
|
20
|
+
2-hop 탐색으로 "같은 기사 내 연관 수치" 발견 가능
|
|
21
|
+
|
|
22
|
+
[참고] Docling (IBM Research, 2024) — https://github.com/DS4SD/docling
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import re
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from structverify.core.schemas import (
|
|
30
|
+
BlockType, SIRBlock, SIRDocument, SourceOffset, SourceType,
|
|
31
|
+
)
|
|
32
|
+
from structverify.preprocessing.segmenter import split_sentences
|
|
33
|
+
from structverify.utils.logger import get_logger
|
|
34
|
+
|
|
35
|
+
logger = get_logger(__name__)
|
|
36
|
+
|
|
37
|
+
# ── 기본 정제 패턴 ────────────────────────────────────────────────────────
|
|
38
|
+
_URL_RE = re.compile(r'https?://\S+')
|
|
39
|
+
_BLANK_LINES = re.compile(r'\n{3,}')
|
|
40
|
+
_SPACES = re.compile(r'[ \t]{2,}')
|
|
41
|
+
|
|
42
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
43
|
+
# 메인 빌더
|
|
44
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
45
|
+
|
|
46
|
+
def build_sir(
|
|
47
|
+
raw_text: str,
|
|
48
|
+
source_type: SourceType,
|
|
49
|
+
source_uri: str | None = None,
|
|
50
|
+
) -> SIRDocument:
|
|
51
|
+
"""
|
|
52
|
+
MD 텍스트 → SIRDocument 변환.
|
|
53
|
+
|
|
54
|
+
처리 순서:
|
|
55
|
+
1) _clean_text_basic(): URL 제거, 빈줄 정리 (최소 정제)
|
|
56
|
+
2) \\n{2,} 기준 문단 분할
|
|
57
|
+
3) 블록 타입 감지 (HEADING / PARAGRAPH / LIST / TABLE)
|
|
58
|
+
4) kss 문장 분리 + 절대 offset 보정
|
|
59
|
+
5) graph_anchor_id 부여 (블록/문장 모두)
|
|
60
|
+
|
|
61
|
+
LLM 기반 심층 정제가 필요하면 추후 build_sir_async() 사용.
|
|
62
|
+
"""
|
|
63
|
+
cleaned = _clean_text_basic(raw_text)
|
|
64
|
+
|
|
65
|
+
blocks: list[SIRBlock] = []
|
|
66
|
+
search_pos = 0
|
|
67
|
+
block_seq = 0
|
|
68
|
+
|
|
69
|
+
for para in re.split(r'\n{2,}', cleaned):
|
|
70
|
+
para_stripped = para.strip()
|
|
71
|
+
|
|
72
|
+
# 빈 문단 스킵
|
|
73
|
+
if not para_stripped or len(para_stripped) < 2:
|
|
74
|
+
search_pos += len(para) + 2
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
block_type, level = _detect_block_type(para_stripped)
|
|
78
|
+
|
|
79
|
+
# 절대 시작 위치 계산
|
|
80
|
+
block_start = cleaned.find(para_stripped, search_pos)
|
|
81
|
+
if block_start < 0:
|
|
82
|
+
block_start = search_pos
|
|
83
|
+
block_end = block_start + len(para_stripped)
|
|
84
|
+
|
|
85
|
+
block_id = f"b{block_seq:04d}"
|
|
86
|
+
block_anchor = f"node:{block_id}"
|
|
87
|
+
|
|
88
|
+
# 문장 분리 + 절대 offset 보정
|
|
89
|
+
sentences = split_sentences(para_stripped)
|
|
90
|
+
for sent in sentences:
|
|
91
|
+
# 전역 유일 sent_id (블록ID + 문장ID 조합)
|
|
92
|
+
global_sent_id = f"{block_id}_{sent.sent_id}"
|
|
93
|
+
sent.sent_id = global_sent_id
|
|
94
|
+
sent.graph_anchor_id = f"node:{global_sent_id}"
|
|
95
|
+
# 상대 offset → 절대 offset
|
|
96
|
+
sent.char_offset_start = block_start + sent.char_offset_start
|
|
97
|
+
sent.char_offset_end = block_start + sent.char_offset_end
|
|
98
|
+
|
|
99
|
+
block = SIRBlock(
|
|
100
|
+
block_id=block_id,
|
|
101
|
+
type=block_type,
|
|
102
|
+
level=level,
|
|
103
|
+
content=para_stripped,
|
|
104
|
+
sentences=sentences,
|
|
105
|
+
graph_anchor_ids=[block_anchor],
|
|
106
|
+
entity_refs=_extract_entity_refs(para_stripped),
|
|
107
|
+
event_refs=_extract_event_refs(para_stripped),
|
|
108
|
+
source_offset=SourceOffset(
|
|
109
|
+
char_start=block_start,
|
|
110
|
+
char_end=block_end,
|
|
111
|
+
),
|
|
112
|
+
)
|
|
113
|
+
blocks.append(block)
|
|
114
|
+
search_pos = block_end
|
|
115
|
+
block_seq += 1
|
|
116
|
+
|
|
117
|
+
doc = SIRDocument(
|
|
118
|
+
source_type=source_type,
|
|
119
|
+
source_uri=source_uri,
|
|
120
|
+
blocks=blocks,
|
|
121
|
+
)
|
|
122
|
+
total_sents = sum(len(b.sentences) for b in blocks)
|
|
123
|
+
logger.info(f"SIR Tree: {len(blocks)} blocks, {total_sents} sentences [{source_type.value}]")
|
|
124
|
+
return doc
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
128
|
+
# GraphRAG 문맥 엣지
|
|
129
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
130
|
+
|
|
131
|
+
def extract_context_edges(doc: SIRDocument) -> list[dict[str, Any]]:
|
|
132
|
+
"""
|
|
133
|
+
GraphRAG용 문맥 엣지 추출.
|
|
134
|
+
|
|
135
|
+
graph_builder.py에서 이 함수를 호출하여 Neo4j에 저장하면
|
|
136
|
+
2-hop 탐색으로 "같은 기사 내 연관 수치"를 발견할 수 있다.
|
|
137
|
+
|
|
138
|
+
엣지 유형:
|
|
139
|
+
NEXT_SENT : 문장 → 다음 문장 (문맥 흐름 보존)
|
|
140
|
+
IN_BLOCK : 문장 → 소속 문단
|
|
141
|
+
IN_DOC : 문단 → 소속 문서
|
|
142
|
+
|
|
143
|
+
TODO [이수민]: graph_builder.py에서 이 함수 호출 후 Neo4j 저장
|
|
144
|
+
"""
|
|
145
|
+
edges: list[dict[str, Any]] = []
|
|
146
|
+
doc_node = f"node:doc:{doc.doc_id.hex[:8]}"
|
|
147
|
+
|
|
148
|
+
for block in doc.blocks:
|
|
149
|
+
block_anchor = block.graph_anchor_ids[0] if block.graph_anchor_ids else None
|
|
150
|
+
if not block_anchor:
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
# IN_DOC: 문단 → 문서
|
|
154
|
+
edges.append({
|
|
155
|
+
"from_node": block_anchor,
|
|
156
|
+
"to_node": doc_node,
|
|
157
|
+
"edge_type": "IN_DOC",
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
sents = block.sentences
|
|
161
|
+
for i, sent in enumerate(sents):
|
|
162
|
+
if not sent.graph_anchor_id:
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
# IN_BLOCK: 문장 → 문단
|
|
166
|
+
edges.append({
|
|
167
|
+
"from_node": sent.graph_anchor_id,
|
|
168
|
+
"to_node": block_anchor,
|
|
169
|
+
"edge_type": "IN_BLOCK",
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
# NEXT_SENT: 문장 → 다음 문장
|
|
173
|
+
if i + 1 < len(sents) and sents[i + 1].graph_anchor_id:
|
|
174
|
+
edges.append({
|
|
175
|
+
"from_node": sent.graph_anchor_id,
|
|
176
|
+
"to_node": sents[i + 1].graph_anchor_id,
|
|
177
|
+
"edge_type": "NEXT_SENT",
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
logger.debug(f"문맥 엣지 {len(edges)}개 추출")
|
|
181
|
+
return edges
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
185
|
+
# 내부 유틸
|
|
186
|
+
# ════════════════════════════════════════════════════════════════════════
|
|
187
|
+
|
|
188
|
+
def _clean_text_basic(raw: str) -> str:
|
|
189
|
+
"""
|
|
190
|
+
최소한의 공통 정제.
|
|
191
|
+
어떤 소스든 확실히 해당되는 것만 처리.
|
|
192
|
+
|
|
193
|
+
- URL 제거
|
|
194
|
+
- 연속 공백(탭) → 단일 공백
|
|
195
|
+
- 3줄 이상 빈 줄 → 2줄
|
|
196
|
+
|
|
197
|
+
노이즈 심화 정제(페이지번호, 이미지섹션 등)는
|
|
198
|
+
추후 LLM 기반 clean_text_with_llm()으로 처리 예정.
|
|
199
|
+
"""
|
|
200
|
+
text = _URL_RE.sub('', raw)
|
|
201
|
+
text = _SPACES.sub(' ', text)
|
|
202
|
+
text = _BLANK_LINES.sub('\n\n', text)
|
|
203
|
+
return text.strip()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _detect_block_type(text: str) -> tuple[BlockType, int | None]:
|
|
207
|
+
"""MD 블록 타입 감지."""
|
|
208
|
+
stripped = text.strip()
|
|
209
|
+
|
|
210
|
+
# 헤딩: # ~ ######
|
|
211
|
+
m = re.match(r'^(#{1,6})\s+', stripped)
|
|
212
|
+
if m:
|
|
213
|
+
return BlockType.HEADING, len(m.group(1))
|
|
214
|
+
|
|
215
|
+
# 테이블: | 로 시작
|
|
216
|
+
if stripped.startswith('|') and '|' in stripped:
|
|
217
|
+
return BlockType.TABLE, None
|
|
218
|
+
|
|
219
|
+
# 목록: -, *, 숫자.
|
|
220
|
+
if re.match(r'^[-*]\s+', stripped) or re.match(r'^\d+\.\s+', stripped):
|
|
221
|
+
return BlockType.LIST, None
|
|
222
|
+
|
|
223
|
+
return BlockType.PARAGRAPH, None
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def _extract_entity_refs(text: str) -> list[str]:
|
|
227
|
+
"""
|
|
228
|
+
기관명/단체명 추출.
|
|
229
|
+
현재 구현 안함.
|
|
230
|
+
TODO : 김예슬
|
|
231
|
+
"""
|
|
232
|
+
return []
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _extract_event_refs(text: str) -> list[str]:
|
|
236
|
+
"""날짜/시점 표현 추출.
|
|
237
|
+
현재 구현 안함.
|
|
238
|
+
TODO : 김예슬
|
|
239
|
+
"""
|
|
240
|
+
return []
|