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.
Files changed (168) hide show
  1. structverify/__init__.py +83 -0
  2. structverify/adaptation/__init__.py +0 -0
  3. structverify/adaptation/adapter_trainer.py +341 -0
  4. structverify/adaptation/feedback_store.py +31 -0
  5. structverify/adaptation/kosis_crawler.py +317 -0
  6. structverify/adaptation/sample_builder.py +149 -0
  7. structverify/adaptation/synthetic_generator.py +320 -0
  8. structverify/adaptation/update_embeddings.py +178 -0
  9. structverify/agent/__init__.py +21 -0
  10. structverify/agent/builder_agent.py +226 -0
  11. structverify/agent/conformance_agent.py +171 -0
  12. structverify/agent/dependency_planner.py +151 -0
  13. structverify/agent/indexing_agent.py +153 -0
  14. structverify/agent/indexing_planner.py +169 -0
  15. structverify/agent/integration_example.py +182 -0
  16. structverify/agent/loop.py +1165 -0
  17. structverify/agent/memory.py +207 -0
  18. structverify/agent/planner.py +817 -0
  19. structverify/agent/prompts/__init__.py +15 -0
  20. structverify/agent/prompts/planner_prompts.py +219 -0
  21. structverify/agent/prompts/reflect_prompts.py +387 -0
  22. structverify/agent/reflect.py +227 -0
  23. structverify/agent/runtime_agent.py +1272 -0
  24. structverify/agent/schemas.py +262 -0
  25. structverify/agent/source_profiler.py +229 -0
  26. structverify/agent/tools/__init__.py +64 -0
  27. structverify/agent/tools/base.py +222 -0
  28. structverify/agent/tools/calculate.py +244 -0
  29. structverify/agent/tools/catalog_search.py +859 -0
  30. structverify/agent/tools/deep_explore.py +293 -0
  31. structverify/agent/tools/explore_catalog.py +423 -0
  32. structverify/agent/tools/fetch_evidence.py +922 -0
  33. structverify/agent/tools/finish.py +423 -0
  34. structverify/agent/tools/meta_explore.py +267 -0
  35. structverify/agent/tools/query_rewriter.py +134 -0
  36. structverify/agent/tools/read_original.py +144 -0
  37. structverify/agent/tools/replan.py +365 -0
  38. structverify/agent/workspace.py +958 -0
  39. structverify/api.py +804 -0
  40. structverify/config/default.yaml +350 -0
  41. structverify/core/__init__.py +0 -0
  42. structverify/core/config_loader.py +30 -0
  43. structverify/core/pipeline.py +280 -0
  44. structverify/core/schemas.py +362 -0
  45. structverify/detection/__init__.py +26 -0
  46. structverify/detection/_config.py +163 -0
  47. structverify/detection/_llm.py +24 -0
  48. structverify/detection/candidate/__init__.py +1 -0
  49. structverify/detection/candidate/heuristic.py +60 -0
  50. structverify/detection/candidate/llm.py +51 -0
  51. structverify/detection/candidate_scorer.py +81 -0
  52. structverify/detection/claim_detector.py +164 -0
  53. structverify/detection/claims/__init__.py +1 -0
  54. structverify/detection/claims/worthiness.py +142 -0
  55. structverify/detection/domain/__init__.py +1 -0
  56. structverify/detection/domain/classify.py +84 -0
  57. structverify/detection/domain/preview.py +36 -0
  58. structverify/detection/domain/registry.py +99 -0
  59. structverify/detection/domain_classifier.py +75 -0
  60. structverify/detection/prompts/__init__.py +1 -0
  61. structverify/detection/prompts/candidate.py +38 -0
  62. structverify/detection/prompts/claim_worthiness.py +48 -0
  63. structverify/detection/prompts/domain.py +41 -0
  64. structverify/detection/prompts/schema.py +508 -0
  65. structverify/detection/prompts_loader.py +167 -0
  66. structverify/detection/schema/__init__.py +1 -0
  67. structverify/detection/schema/expand.py +83 -0
  68. structverify/detection/schema/induce.py +441 -0
  69. structverify/detection/schema/regenerate.py +162 -0
  70. structverify/detection/schema/temporal_hints.py +130 -0
  71. structverify/detection/schema/validate.py +193 -0
  72. structverify/detection/schema_inductor.py +112 -0
  73. structverify/detection/synthetic_generator.py +270 -0
  74. structverify/explanation/__init__.py +0 -0
  75. structverify/explanation/_config.py +18 -0
  76. structverify/explanation/_llm.py +25 -0
  77. structverify/explanation/explainer.py +183 -0
  78. structverify/explanation/fallback.py +29 -0
  79. structverify/explanation/formatters.py +75 -0
  80. structverify/explanation/prompts/__init__.py +1 -0
  81. structverify/explanation/prompts/match.py +27 -0
  82. structverify/explanation/prompts/mismatch.py +20 -0
  83. structverify/explanation/prompts/multihop.py +16 -0
  84. structverify/explanation/prompts/unverifiable.py +17 -0
  85. structverify/graph/__init__.py +0 -0
  86. structverify/graph/claim_graph.py +226 -0
  87. structverify/graph/document_graph.py +487 -0
  88. structverify/graph/graph_builder.py +238 -0
  89. structverify/graph/graph_multihop.py +335 -0
  90. structverify/graph/graph_store.py +281 -0
  91. structverify/graph/provenance.py +52 -0
  92. structverify/memory/__init__.py +44 -0
  93. structverify/memory/agent_memory.py +142 -0
  94. structverify/memory/embedder.py +69 -0
  95. structverify/memory/exemplar_store.py +241 -0
  96. structverify/memory/normalizer.py +91 -0
  97. structverify/memory/schema.py +119 -0
  98. structverify/memory/storage/__init__.py +29 -0
  99. structverify/memory/storage/jsonl_store.py +117 -0
  100. structverify/memory/working_memory.py +370 -0
  101. structverify/preprocessing/Dockerfile.scraper +27 -0
  102. structverify/preprocessing/__init__.py +0 -0
  103. structverify/preprocessing/extractor.py +574 -0
  104. structverify/preprocessing/pdf/__init__.py +16 -0
  105. structverify/preprocessing/pdf/fields.py +95 -0
  106. structverify/preprocessing/pdf/markdown.py +107 -0
  107. structverify/preprocessing/pdf/models.py +34 -0
  108. structverify/preprocessing/pdf/ocr.py +172 -0
  109. structverify/preprocessing/pdf/pipeline.py +74 -0
  110. structverify/preprocessing/pdf/reader.py +119 -0
  111. structverify/preprocessing/pdf/scoring.py +61 -0
  112. structverify/preprocessing/scraper_sandbox.py +561 -0
  113. structverify/preprocessing/segmenter.py +48 -0
  114. structverify/preprocessing/sir_builder.py +240 -0
  115. structverify/progress.py +591 -0
  116. structverify/retrieval/__init__.py +0 -0
  117. structverify/retrieval/base.py +208 -0
  118. structverify/retrieval/base_connector.py +85 -0
  119. structverify/retrieval/catalog_ranker.py +300 -0
  120. structverify/retrieval/catalog_search.py +583 -0
  121. structverify/retrieval/chunking.py +92 -0
  122. structverify/retrieval/custom_csv_source.py +386 -0
  123. structverify/retrieval/custom_db_source.py +396 -0
  124. structverify/retrieval/custom_docs_source.py +152 -0
  125. structverify/retrieval/dimension_resolver.py +281 -0
  126. structverify/retrieval/evidence_subgraph.py +63 -0
  127. structverify/retrieval/kosis_connector.py +1192 -0
  128. structverify/retrieval/kosis_relevance.py +142 -0
  129. structverify/retrieval/kosis_source.py +1541 -0
  130. structverify/retrieval/query_builder.py +72 -0
  131. structverify/retrieval/registry.py +133 -0
  132. structverify/retrieval/relevance_judge.py +141 -0
  133. structverify/retrieval/row_matcher.py +267 -0
  134. structverify/storage/__init__.py +0 -0
  135. structverify/storage/db_manager.py +157 -0
  136. structverify/storage/dwh_manager.py +92 -0
  137. structverify/storage/init_db.py +99 -0
  138. structverify/storage/raw_storage.py +29 -0
  139. structverify/training/__init__.py +26 -0
  140. structverify/training/curator.py +124 -0
  141. structverify/training/dataset.py +134 -0
  142. structverify/training/doctor.py +99 -0
  143. structverify/training/evalgate.py +96 -0
  144. structverify/training/generate.py +101 -0
  145. structverify/training/loop.py +116 -0
  146. structverify/training/recipe/train_mlx.py +99 -0
  147. structverify/training/recipe/train_qlora.py +104 -0
  148. structverify/training/tasks.py +79 -0
  149. structverify/utils/__init__.py +0 -0
  150. structverify/utils/embedding_client.py +248 -0
  151. structverify/utils/llm_client.py +809 -0
  152. structverify/utils/logger.py +81 -0
  153. structverify/verification/__init__.py +0 -0
  154. structverify/verification/_config.py +45 -0
  155. structverify/verification/adapters.py +405 -0
  156. structverify/verification/conformance.py +117 -0
  157. structverify/verification/decide_verdict.py +216 -0
  158. structverify/verification/decide_verdict_agent.py +454 -0
  159. structverify/verification/growth_diff.py +267 -0
  160. structverify/verification/row_match.py +345 -0
  161. structverify/verification/units.py +64 -0
  162. structverify/verification/verdict_thresholds.py +232 -0
  163. structverify/verification/verifier.py +84 -0
  164. structverify-0.3.0.dist-info/METADATA +903 -0
  165. structverify-0.3.0.dist-info/RECORD +168 -0
  166. structverify-0.3.0.dist-info/WHEEL +5 -0
  167. structverify-0.3.0.dist-info/licenses/LICENSE +21 -0
  168. structverify-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,350 @@
1
+ # StructVerify Lab v2.0 โ€” ๊ธฐ๋ณธ ์„ค์ •
2
+ app:
3
+ name: "StructVerify Lab"
4
+ version: "0.2.0"
5
+ debug: true
6
+
7
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
8
+ # Phase A ์ถ”๊ฐ€ ์„น์…˜ โ€” config/default.yaml ๋์— *์ถ”๊ฐ€*ํ•˜์„ธ์š”.
9
+ # ๊ธฐ์กด ์„น์…˜ (llm, embedding, kosis, ...) ์€ *๊ทธ๋Œ€๋กœ* ์œ ์ง€.
10
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
11
+
12
+ # === Agent (๋ฉ€ํ‹ฐํ„ด + memory + agentic ๊ฒ€์ฆ) ===
13
+ # Phase A์—์„œ๋Š” *์ž๋ฆฌ๋งŒ* ์ถ”๊ฐ€. ์‹ค์ œ ํ˜ธ์ถœ์€ Phase D (loop) ํ†ตํ•ฉ ํ›„.
14
+ agent:
15
+ enabled: true # Phase A๋Š” false ์œ ์ง€. Phase D์—์„œ true.
16
+
17
+ workspace:
18
+ backend: "local" # local | minio | s3 (์ง€๊ธˆ์€ local๋งŒ ๊ตฌํ˜„)
19
+ local_path: "./agent_workspace" # workspace ๋””๋ ‰ํ† ๋ฆฌ
20
+ persist_after_job: true # dev=true (๋””๋ฒ„๊น…), prod=false (์ •๋ฆฌ)
21
+ cleanup_after_days: 7 # persist=false ์‹œ ๋ฉฐ์น  ํ›„ ์ž๋™ ์‚ญ์ œ
22
+ # [2026-05-21] workspace ๊ฒฉ๋ฆฌ ๋‹จ์œ„:
23
+ # - "doc_hash" : ๋ณธ๋ฌธ ํ…์ŠคํŠธ md5 hash ๊ธฐ์ค€. *๊ฐ™์€ ๋ณธ๋ฌธ์ด๋ฉด ๊ฐ™์€ ๋””๋ ‰ํ† ๋ฆฌ*๋ผ
24
+ # KOSIS fetch ์บ์‹œ(verified_facts/sibling_evidence/successful_stat_ids)๊ฐ€
25
+ # API ํ˜ธ์ถœ ์‚ฌ์ด์— ์‚ด์•„์žˆ์–ด ๊ฐ™์€ ๊ธฐ์‚ฌ ์žฌ๊ฒ€์ฆ์ด ๋น ๋ฆ„. ๋‹จ์ : KOSIS
26
+ # ์ชฝ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ฐฑ์‹ ๋ผ๋„ stale ์บ์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ + ๋‹ค๋ฅธ ์œ ์ €๊ฐ€ ๊ฐ™์€ ํ…์ŠคํŠธ
27
+ # ๋„ฃ์œผ๋ฉด ์บ์‹œ ๊ณต์œ  (๋ฉ€ํ‹ฐ-ํ…Œ๋„ŒํŠธ ๊ฒฉ๋ฆฌ ํ•„์š”ํ•˜๋ฉด ๋ถ€์ ํ•ฉ).
28
+ # - "job_id" : API job_id (sv_platform์ด ๋ฐœ๊ธ‰ํ•œ UUID) ๊ธฐ์ค€. *๋งค ์š”์ฒญ๋งˆ๋‹ค cold*
29
+ # start๋ผ ์ •ํ™•์„ฑโ†‘ ์†๋„โ†“. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋‹จ๋… ์‚ฌ์šฉ(CLI ๋“ฑ)์—์„œ external_job_id
30
+ # ๋ฏธ์ œ๊ณต ์‹œ fresh UUID๋กœ ํด๋ฐฑ.
31
+ scope: "job_id"
32
+ # ์™ธ๋ถ€์—์„œ ์ฃผ์ž…ํ•œ job_id (scope="job_id"์ผ ๋•Œ ์‚ฌ์šฉ). sv_platform์ด ์ž๋™ set.
33
+ # ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ง์ ‘ ์‚ฌ์šฉ ์‹œ None ๋‘๋ฉด ํ˜ธ์ถœ๋‹น fresh UUID ๋ฐœ๊ธ‰.
34
+ external_job_id: null
35
+
36
+ memory:
37
+ backend: "file" # file | redis (์ง€๊ธˆ์€ file๋งŒ)
38
+ max_length_chars: 50000 # memory.md ์ฝ์„ ๋•Œ LLM์— ๋„˜๊ธฐ๋Š” ์ƒํ•œ
39
+ summarize_threshold: 30000 # ์ด ์ด์ƒ์ด๋ฉด LLM ์š”์•ฝ (Phase D+)
40
+
41
+ loop:
42
+ mode: "reflect"
43
+ max_iterations: 10 # claim๋‹น reflect ์ตœ๋Œ€ ํšŸ์ˆ˜
44
+ enable_reflection: true # false๋ฉด plan๋งŒ ๋”ฐ๋ผ๊ฐ (๋ฉ€ํ‹ฐํ„ด X)
45
+ single_pass_fallback: true # agent ์‹คํŒจ/์˜ˆ์‚ฐ ์ดˆ๊ณผ ์‹œ v6.14 ๋‹จ์ผ ํŒจ์Šค
46
+ early_stop_on_confidence: 0.9 # ์ด์ƒ์ด๋ฉด finish ๊ถŒ์žฅ
47
+
48
+ llm:
49
+ # llm.models ์—์„œ *์ด๋ฆ„*๋งŒ ์ฐธ์กฐ โ€” ๊ธฐ์กด ์ธํ”„๋ผ ์žฌ์‚ฌ์šฉ
50
+ plan_model: "structured" # โ†’ HCX-007 (JSON ์ •ํ™•)
51
+ reflect_model: "light" # โ†’ HCX-DASH-002 (๋น„์šฉ โ†“, ๋ฐ˜๋ณต ํ˜ธ์ถœ)
52
+ explain_model: "heavy" # โ†’ HCX-003
53
+
54
+ budget:
55
+ max_tokens_per_job: 100000
56
+ max_concurrent_claims: 3 # ๋™์‹œ ์ฒ˜๋ฆฌ claim ์ˆ˜
57
+ cost_alert_threshold_krw: 1000
58
+
59
+ cache:
60
+ enabled: true
61
+ ttl_hours: 24
62
+ backend: "memory" # memory | redis
63
+
64
+ # === Data sources (KOSIS๋งŒ โ†’ multi-source ์ผ๋ฐ˜ํ™”) ===
65
+ # ๊ธฐ์กด kosis: ์„น์…˜์€ *๊ทธ๋Œ€๋กœ ์œ ์ง€*. ์—ฌ๊ธฐ์„œ๋Š” *์ถ”์ƒํ™” ๋ ˆ์ด์–ด*๋งŒ ์ถ”๊ฐ€.
66
+ data_sources:
67
+ enabled: ["kosis"] # ํšŒ์‚ฌ: ["custom_db", "custom_csv"]
68
+ default_source: "kosis"
69
+
70
+ # KOSIS ๋ฐ์ดํ„ฐ์†Œ์Šค โ€” ๊ฒ€์ƒ‰/ranking/๊ด€๋ จ์„ฑ ๊ฐ€๋“œ ์„ค์ •
71
+ # ์ฃผ์˜: ์—ฌ๊ธฐ 'kosis' ์„น์…˜์€ KOSISDataSource ์ƒ์„ฑ์ž์— ์ „๋‹ฌ๋จ (self._connector_config).
72
+ # ๊ธฐ์กด top-level 'kosis:' ์„น์…˜(API URL, catalog ๋นŒ๋“œ ๋“ฑ)๊ณผ *๋ณ„๋„*.
73
+ kosis:
74
+ # [2026-05-26] catalog_ranker โ€” LLM batch ranking์œผ๋กœ ํ‚ค์›Œ๋“œ ๊ฐ€๋“œ + per-table
75
+ # relevance_judge๋ฅผ ํ†ตํ•ฉ ๋Œ€์ฒด. catalog_search + explore_catalog ๊ฒฐ๊ณผ๋ฅผ unionํ•ด
76
+ # ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(ํ‘œ ์ด๋ฆ„, ๊ธฐ๊ด€, category_path)์™€ ํ•จ๊ป˜ LLM์— 1ํšŒ ์ „๋‹ฌ โ†’ ์˜๋ฏธ ์ ์ˆ˜ ์ˆœ์œ„.
77
+ catalog_ranker:
78
+ enabled: true # false๋ฉด ๊ธฐ์กด ํ‚ค์›Œ๋“œ ๊ฐ€๋“œ(Indicator Semantic Guard) ์‚ฌ์šฉ
79
+ score_threshold: 0.15 # ์ด ๋ฏธ๋งŒ์€ reject (์•ˆ์ „๋ง: ๊ฑฐ๋ถ€๋œ ํ‘œ๋„ try_ids ๋งˆ์ง€๋ง‰์— ์ถ”๊ฐ€)
80
+ pool_limit: 20 # ranker์— ๋ณด๋‚ผ ํ›„๋ณด ์ตœ๋Œ€ ๊ฐœ์ˆ˜
81
+ max_try: 10 # ranker ์ˆœ์œ„๋กœ try_ids ๋งŒ๋“ค ๋•Œ ์ƒํ•œ
82
+ model_tier: "light" # HCX-DASH-002
83
+
84
+ # [2026-05-26] remove_relevance_judge โ€” true๋ฉด kosis_source์˜ per-table
85
+ # relevance_judge LLM ํ˜ธ์ถœ์„ skip. catalog_ranker๊ฐ€ ์ด๋ฏธ ์˜๋ฏธ ํŒ๋‹จํ•˜๋ฏ€๋กœ
86
+ # ์ค‘๋ณต. ๋‹จ, 1์ฐจ ๋„์ž… ์‹œ์—” ์•ˆ์ „๋ง์œผ๋กœ *false*(๋‘˜ ๋‹ค ์ž‘๋™)๋กœ ๋‘๊ณ , 18๋Œ€/1336๊ฐœ
87
+ # ๋‘ ์ผ€์ด์Šค ๋ชจ๋‘ OK ํ™•์ธ๋˜๋ฉด true๋กœ ์ „ํ™˜.
88
+ relevance_guard:
89
+ enabled: true # false๋ฉด per-table relevance_judge ์™„์ „ ๋น„ํ™œ์„ฑ
90
+ llm_fallback: true # P32: ๋ฃฐ ๊ฑฐ๋ถ€ ์‹œ LLM ์˜๋ฏธ ํŒ๋‹จ 1ํšŒ
91
+ model_tier: "light"
92
+ row_match_llm_fallback: true # P33c: _select_best_row ๋งค์นญ 0๊ฑด์ผ ๋•Œ LLM rescue
93
+ row_match_model_tier: "light"
94
+
95
+ # custom_csv โ€” ํšŒ์‚ฌ ์—…๋กœ๋“œ ํŒŒ์ผ
96
+ custom_csv:
97
+ enabled: false
98
+ base_path: "./uploads/custom"
99
+ catalog_json: "" # ํ‘œ ๋ชฉ๋ก JSON (ํ•„์ˆ˜)
100
+ embedding_cached: true
101
+
102
+ # custom_db โ€” ํšŒ์‚ฌ ์ž์ฒด DB
103
+ custom_db:
104
+ enabled: false
105
+ dsn_env: "CUSTOM_DB_DSN" # .env์—์„œ ๋กœ๋“œ
106
+ catalog_table: "stat_tables"
107
+ data_table_prefix: "data_"
108
+
109
+ # ์™ธ๋ถ€ API plugin (ํ™•์žฅ ์ž๋ฆฌ)
110
+ external:
111
+ world_bank:
112
+ enabled: false
113
+ base_url: "https://api.worldbank.org/v2"
114
+ oecd:
115
+ enabled: false
116
+
117
+ # === Domain (๋„๋ฉ”์ธ/์–ธ์–ด ์ผ๋ฐ˜ํ™”) ===
118
+ # ํ•œ๊ตญ์–ด KOSIS ๋„๋ฉ”์ธ ์™ธ โ€” ํšŒ์‚ฌ๊ฐ€ *์ž์ฒด ๋ผ๋ฒจ/๋ถ„๋ฅ˜์ฒด๊ณ„* ์‚ฌ์šฉ ๊ฐ€๋Šฅ.
119
+ domain:
120
+ language: "ko" # ko | en | mixed
121
+
122
+ # ๋ถ„๋ฅ˜ ๋ผ๋ฒจ (ํ˜„์žฌ ์ฝ”๋“œ์— ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๊ฒƒ โ†’ config๋กœ)
123
+ classifier_labels_path: "" # YAML ํŒŒ์ผ ๊ฒฝ๋กœ, ๋นˆ ๊ฐ’์ด๋ฉด inline ์‚ฌ์šฉ
124
+ classifier_labels:
125
+ - id: "population"
126
+ desc: "์ธ๊ตฌ/๊ฐ€๊ตฌ (์ถœ์ƒ, ์‚ฌ๋ง, ํ˜ผ์ธ, ๊ณ ๋ นํ™”, ์ธ๊ตฌ๊ตฌ์กฐ)"
127
+ - id: "economy"
128
+ desc: "๊ฒฝ์ œ/์‚ฐ์—…/๊ธˆ์œต"
129
+ - id: "labor"
130
+ desc: "๋…ธ๋™/๊ณ ์šฉ"
131
+ - id: "health"
132
+ desc: "๋ณด๊ฑด/๋ณต์ง€"
133
+ - id: "education"
134
+ desc: "๊ต์œก/๋ฌธํ™”"
135
+ # ํšŒ์‚ฌ ์ถ”๊ฐ€ ์˜ˆ:
136
+ # - id: "internal_sales"
137
+ # desc: "์‚ฌ๋‚ด ๋งค์ถœ/ํŒ๋งค ์ง€ํ‘œ"
138
+
139
+ schema_taxonomy_path: "" # parent_path ํŠธ๋ฆฌ YAML (์„ ํƒ)
140
+ extra_schema_fields: [] # ClaimSchema ํ™•์žฅ ํ•„๋“œ ์ด๋ฆ„๋“ค
141
+
142
+ llm:
143
+ provider: "upstage" # hcx | openai | upstage [conformance ํ…Œ์ŠคํŠธ: HCX ํ‚ค ์—†์–ด upstage]
144
+ # [v2 - ๊น€์˜ˆ์Šฌ] HCX ํ‚ค๊ฐ€ ์—†์œผ๋ฉด Upstage(Solar)๋กœ ์ „ํ™˜ โ€” OpenAI ํ˜ธํ™˜์ด๋ผ base_url๋งŒ ๋‹ค๋ฆ„.
145
+ # provider: "upstage"
146
+ # api_key_env: "UPSTAGE_API_KEY" # .env ์— UPSTAGE_API_KEY ์„ธํŒ…
147
+ # base_url: "https://api.upstage.ai/v1"
148
+ # models: { heavy: "solar-pro", light: "solar-mini", structured: "solar-pro" }
149
+ # (pip install openai ํ•„์š”. โ€ป ์ž„๋ฒ ๋”ฉ/์นดํƒˆ๋กœ๊ทธ๋Š” ์•„์ง HCX-EMB ๊ธฐ์ค€ โ€” ์ž„๋ฒ ๋”ฉ ์ „ํ™˜์€ ๋ฐ•์žฌ์œค ํŒŒํŠธ)
150
+ models:
151
+ heavy: "HCX-003" # ๋ณต์žกํ•œ ํƒœ์Šคํฌ (check-worthiness, ์„ค๋ช… ์ƒ์„ฑ) โ€” v1 API
152
+ light: "HCX-DASH-002" # ๋น ๋ฅธ ๋ถ„๋ฅ˜ (๋„๋ฉ”์ธ, candidate scoring) โ€” v3 API
153
+ structured: "HCX-007" # JSON ๊ตฌ์กฐํ™” ์ถ”์ถœ (schema_inductor ์ „์šฉ) โ€” v3 Structured Outputs
154
+ reasoning: "HCX-003" # ๋ณตํ•ฉ ๊ทผ๊ฑฐ ์ข…ํ•ฉ
155
+ temperature: 0.1
156
+ max_tokens: 4096 # [2026-05-21] 2048 โ†’ 4096: reflect์˜ finish action์—์„œ explanation์ด ๊ธธ์–ด์ ธ JSON์ด ์ž˜๋ฆฌ๋Š” ์ผ€์ด์Šค ๋ฐฉ์ง€. HCX-DASH-002 ํ•œ๋„ ๋‚ด.
157
+ api_key_env: "NCP_API_KEY" # .env์— ์„ธํŒ…
158
+ # [2026-05-21] ํ”„๋กœ์„ธ์Šค ์ „์—ญ HCX ํ˜ธ์ถœ ์‚ฌ์ด ์ตœ์†Œ ๊ฐ„๊ฒฉ(ms). 0์ด๋ฉด ๋น„ํ™œ์„ฑ.
159
+ # 22:24 ๋กœ๊ทธ ๋ถ„์„: 250ms(=4req/s)๋กœ ๊น”์•„๋„ 21๊ฑด candidate ์ฒ˜๋ฆฌ ์ค‘ ์ง€์†์  429 ๋ฐœ์ƒ.
160
+ # HCX ์‹ค์ œ sustained ํ•œ๋„๋Š” 1~2 req/s ์ถ”์ • โ†’ 600ms๋กœ ์ƒํ–ฅ.
161
+ # ๊ถŒ์žฅ๊ฐ’:
162
+ # - 250ms (4 req/s) : ๋„ˆ๋ฌด ๊ณต๊ฒฉ์ , 429 ๋นˆ๋ฐœ
163
+ # - 500ms (2 req/s) : ๋ณดํ†ต
164
+ # - 600ms (โ‰ˆ1.6 r/s) : ์•ˆ์ „ ๊ธฐ๋ณธ๊ฐ’
165
+ # - 1000ms (1 req/s) : ๋งค์šฐ ์•ˆ์ „, ๋А๋ฆผ
166
+ min_call_interval_ms: 600 # ํ˜ธ์ถœ ์‚ฌ์ด ์ตœ์†Œ ๊ฐ„๊ฒฉ(ms) โ€” rate limit
167
+ max_concurrency: 4 # โ˜… ํ•œ ๋ฒˆ์— ๋ณด๋‚ผ ์ตœ๋Œ€ ๋™์‹œ LLM ์š”์ฒญ ์ˆ˜ (429 ๋ฐฉ์ง€ โ€” ๋‚ฎ์ถœ์ˆ˜๋ก ์•ˆ์ „)
168
+
169
+ embedding:
170
+ provider: "upstage" # [conformance] HCX ํ‚ค ์—†์–ด upstage (embedding-query/passage)
171
+ model: "HCX-EMB-V2" # provider!=hcx๋ฉด ๋ฌด์‹œ๋จ โ†’ embedding-query ์ž๋™
172
+ api_key_env: "UPSTAGE_API_KEY"
173
+
174
+ reranker:
175
+ provider: "hcx"
176
+ model: "HCX-RERANKER" # ๋ฆฌ๋žญ์ปค ๋ชจ๋ธ
177
+ api_key_env: "NCP_API_KEY"
178
+
179
+ kosis:
180
+ base_url: "https://kosis.kr/openapi"
181
+ api_key_env: "KOSIS_API_KEY"
182
+ pgvector_dsn_env: "PGVECTOR_DSN"
183
+ timeout: 30
184
+ catalog:
185
+ rebuild: false # true: ๊ฐ•์ œ ์žฌ์ˆ˜์ง‘, false: DB์— ์žˆ์œผ๋ฉด skip
186
+ embed_batch_size: 100 # ์ž„๋ฒ ๋”ฉ ๋ฐฐ์น˜ ํฌ๊ธฐ
187
+ min_rows: 1000 # ์ด ์ˆ˜ ์ด์ƒ์ด๋ฉด "์ด๋ฏธ ๊ตฌ์ถ•๋จ"์œผ๋กœ ํŒ๋‹จ
188
+ # [P20 2026-05-22] KOSIS API raw ์‘๋‹ต ์บ์‹œ TTL (์‹œ๊ฐ„).
189
+ # ๊ฐ™์€ (stat_id, prdSe, startPrdDe, endPrdDe) ์กฐํ•ฉ ์žฌํ˜ธ์ถœ ์‹œ API ํ˜ธ์ถœ ์—†์ด ์บ์‹œ ์‚ฌ์šฉ.
190
+ # workspace ๋‹จ์œ„ ํŒŒ์ผ ์บ์‹œ (agent_workspace/job_<id>/kosis_cache/<key>.json).
191
+ # scope=doc_hash ๋ชจ๋“œ๋ฉด ๊ฐ™์€ ๋ณธ๋ฌธ ์žฌ๊ฒ€์ฆ ์‹œ ๋ชจ๋“  KOSIS API ํ˜ธ์ถœ skip.
192
+ # - 0 : ์˜๊ตฌ ์บ์‹œ (๋งŒ๋ฃŒ ์•ˆ ๋จ, ๊ฐ€์žฅ ๋น ๋ฆ„. KOSIS ๊ฐฑ์‹  ์‹œ ์ˆ˜๋™ cleanup ํ•„์š”)
193
+ # - 24 : 24์‹œ๊ฐ„ (๊ธฐ๋ณธ๊ฐ’, KOSIS ์ผ์ผ ๊ฐฑ์‹  ๋Œ€์‘)
194
+ # - 168 : 1์ฃผ์ผ
195
+ cache_ttl_hours: 24
196
+
197
+ # [P32 2026-05-22] fetch_evidence์˜ *v6.17 ํ‘œ ๊ด€๋ จ์„ฑ ๊ฐ€๋“œ*์— LLM fallback ์ถ”๊ฐ€.
198
+ # ๋ฃฐ๋ฒ ์ด์Šค _is_table_relevant()๊ฐ€ ํ† ํฐ ๋งค์นญ์œผ๋กœ ๊ฑฐ๋ถ€ํ–ˆ์„ ๋•Œ, LLM์ด ํ•œ ๋ฒˆ ๋”
199
+ # *์˜๋ฏธ์ *์œผ๋กœ ํŒ๋‹จํ•ด false negative ํšŒ๋ณต.
200
+ # ์˜ˆ: indicator="์ฒด์™ธ ์ถฉ๊ฒฉํŒŒ ์‡„์„์ˆ  ์žฅ๋น„ ์ˆ˜" + table="์‹œ๊ตฐ๊ตฌ๋ณ„ ์ฃผ์š” ์˜๋ฃŒ์žฅ๋น„
201
+ # ํ˜„ํ™ฉ" โ€” ๋ฃฐ: "์žฅ๋น„"(2๊ธ€์ž, len<3 ๋ฃฐ์— ๋ง‰ํž˜) + "์‡„์„์ˆ "(table์— ์—†์Œ) โ†’ ๊ฑฐ๋ถ€.
202
+ # LLM: ํ‘œ๊ฐ€ "์˜๋ฃŒ์žฅ๋น„"์˜ ์ƒ์œ„ ๋ถ„๋ฅ˜์ด๋ฏ€๋กœ row ๊ฒ€์ƒ‰ํ•˜๋ฉด ์ •๋‹ต ์ฐพ์„ ๊ฐ€๋Šฅ์„ฑ โ†‘ โ†’ ํ†ต๊ณผ.
203
+ # ๋ฃฐ *ํ†ต๊ณผ* ์‹œ์—” LLM ํ˜ธ์ถœ ์•ˆ ํ•จ (์†๋„). ๋ฃฐ ๊ฑฐ๋ถ€ ์‹œ์—๋งŒ LLM 1ํšŒ (~1์ดˆ).
204
+ relevance_guard:
205
+ llm_fallback: true # false๋ฉด ๋ฃฐ๋ฒ ์ด์Šค๋งŒ (P32 ์ด์ „ ๋™์ž‘)
206
+ model_tier: "light" # HCX-DASH-002. heavy/structured/reasoning ๊ฒฉ์ƒ ๊ฐ€๋Šฅ.
207
+ # [P33c 2026-05-22] row ๋งค์นญ ๋‹จ๊ณ„ LLM fallback. _select_best_row๊ฐ€ indicator
208
+ # ๋ฃฐ ๋งค์นญ 0๊ฑด์ผ ๋•Œ row์˜ unique ITM_NM/C_NM list๋ฅผ LLM์— ๋˜์ ธ ์˜๋ฏธ์  ๋งค์นญ
209
+ # ์‹๋ณ„. KOSIS ํ‘œ row๊ฐ€ *๊ณ„์ธต ๋ถ„๋ฅ˜*๋ผ ์ƒ์œ„ ์นดํ…Œ๊ณ ๋ฆฌ ITM_NM๋งŒ ๋งค์นญ๋˜๊ณ  ์„ธ๋ถ€
210
+ # row๋ฅผ ๋ชป ์ฐพ๋Š” ์ผ€์ด์Šค (์˜ˆ: "์ฒด์™ธ ์ถฉ๊ฒฉํŒŒ ์‡„์„์ˆ  ์žฅ๋น„" vs ITM_NM='์ง„๋‹จ๋ฐฉ์‚ฌ์„ 
211
+ # ยทํŠน์ˆ˜์˜๋ฃŒ์žฅ๋น„')์—์„œ ์ง„์งœ row ์‹๋ณ„.
212
+ row_match_llm_fallback: true
213
+ row_match_model_tier: "light"
214
+
215
+ # [P34 2026-05-22] Dimension resolver โ€” KOSIS ํ‘œ์˜ N์ฐจ์› ์Šฌ๋ผ์ด์Šค ์ฝ”๋“œ ๊ฒฐ์ •.
216
+ # ๊ธฐ๋ณธ connector๋Š” cmmt_rows[0]์˜ ITM_ID/OBJ_ID(ํ•ฉ๊ณ„ ์ฝ”๋“œ)๋ฅผ ๋ฐ•์•„ *์„ธ๋ถ€ row
217
+ # ๋ˆ„๋ฝ*. ์ด๊ฑธ ๋ฉ”ํƒ€+LLM์œผ๋กœ ๋™์  ๊ฒฐ์ •ํ•ด์„œ ์ง„์งœ ์ •๋‹ต ์Šฌ๋ผ์ด์Šค๋งŒ ๋ฐ›์Œ.
218
+ # ํ๋ฆ„: fetch ์ง์ „ โ†’ getMeta(ITM/OBJL01~04) ๋ณ‘๋ ฌ โ†’ LLM์ด claim ๋งค์นญ ์ฝ”๋“œ ์„ ํƒ
219
+ # โ†’ fetch_params.dim_overrides์— ๋ฐ•์Œ โ†’ connector๊ฐ€ itmId/objL1~8 ์‚ฌ์šฉ.
220
+ # ๊ฐ™์€ stat_id๋Š” process-level cache๋กœ 1ํšŒ๋งŒ ํ˜ธ์ถœ.
221
+ dimension_resolver:
222
+ enabled: true
223
+ model_tier: "light" # HCX-DASH-002
224
+
225
+ database:
226
+ # ๋กœ์ปฌ ๊ฐœ๋ฐœ์šฉ DSN โ€” ์‹ค์ œ ๋น„๋ฐ€๋ฒˆํ˜ธ๋Š” ๋„ฃ์ง€ ๋ง๊ณ  ๊ฐ์ž ํ™˜๊ฒฝ์— ๋งž๊ฒŒ ์„ค์ •ํ•˜์„ธ์š”.
227
+ # (๊ทœ์ • ์ค€์ˆ˜ ๊ฒ€์‚ฌ ๋“ฑ ์ฝ”์–ด ๊ธฐ๋Šฅ์€ DB๊ฐ€ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.)
228
+ url: "postgresql://localhost:5432/structverify"
229
+
230
+ graph:
231
+ provider: "neo4j" # "neo4j" | "memgraph"
232
+ uri: "bolt://localhost:7687"
233
+ user: "neo4j"
234
+ password_env: "NEO4J_PASSWORD"
235
+
236
+ storage:
237
+ provider: "minio"
238
+ endpoint: "http://localhost:9000"
239
+ bucket: "structverify-raw"
240
+ access_key_env: "MINIO_ACCESS_KEY"
241
+ secret_key_env: "MINIO_SECRET_KEY"
242
+
243
+ redis:
244
+ url: "redis://localhost:6379/0"
245
+
246
+ dwh:
247
+ provider: "snowflake" # "snowflake" | "bigquery" | "clickhouse"
248
+ snowflake:
249
+ account_env: "SNOWFLAKE_ACCOUNT"
250
+ user_env: "SNOWFLAKE_USER"
251
+ password_env: "SNOWFLAKE_PASSWORD"
252
+ warehouse: "STRUCTVERIFY_WH"
253
+ database: "STRUCTVERIFY_DB"
254
+ schema: "PUBLIC"
255
+
256
+ adaptation:
257
+ feedback_threshold: 10 # ํ•™์Šต ํŠธ๋ฆฌ๊ฑฐ ์ตœ์†Œ ํ”ผ๋“œ๋ฐฑ ์ˆ˜
258
+ eval_min_score: 0.85 # ๋ฐฐํฌ ๊ฒŒ์ดํŠธ ์ตœ์†Œ ์ ์ˆ˜
259
+ mlflow_tracking_uri: "http://localhost:5000"
260
+
261
+ verification:
262
+ tolerance_percent: 1.0
263
+ min_confidence: 0.7
264
+
265
+ # [P21 2026-05-22] catalog_search ์˜๋ฏธ ์ •ํ™•๋„ ๋ณด๊ฐ• โ€” LLM rerank
266
+ # [P25 2026-05-22] ์ผ์‹œ ๋น„ํ™œ์„ฑ โ€” row_preview๊ฐ€ LLM์ด ์ž˜๋ชป๋œ ํ‘œ(DT_HIRA48_1,
267
+ # DT_HIRA4Q ๊ฐ™์€ *์„ธ๋ถ€ ๋ถ„๋ฅ˜ ํ‘œ*)๋ฅผ ์ •๋‹ต(DT_35003_A7 ๊ฐ™์€ *์‹œ๊ตฐ๊ตฌ๋ณ„ ์ผ๋ฐ˜ ํ‘œ*)
268
+ # ๋ณด๋‹ค ์šฐ๋Œ€ํ•˜๋Š” ๊ฒฝํ–ฅ. ๊ฒŒ๋‹ค๊ฐ€ preview fetch๊ฐ€ KOSIS API์— 5 candidates ร— 3 prdSe
269
+ # = 15๊ฐœ ๋™์‹œ ํ˜ธ์ถœ์„ ๋งŒ๋“ค์–ด "Server disconnected" ์—๋Ÿฌ + 30~50์ดˆ ์ง€์—ฐ ์œ ๋ฐœ.
270
+ # ์šฐ์„  catalog ์ž„๋ฒ ๋”ฉ + prior_success score(P21A)๋งŒ ์‚ฌ์šฉ. rerank ์ •ํ™•๋„ ๊ฐœ์„ ์€
271
+ # ๋‹ค์Œ ๋‹จ๊ณ„(prompt ๊ฐ•ํ™” + preview Y-only + LLM ๋ชจ๋ธ ๊ฒฉ์ƒ ๋“ฑ)์—์„œ ์ฒ˜๋ฆฌ.
272
+ catalog_search:
273
+ # rerank_mode:
274
+ # - "none" : catalog ์ž„๋ฒ ๋”ฉ + prior score๋งŒ (ํ˜„์žฌ ๊ธฐ๋ณธ)
275
+ # - "table_name" : top N ํ‘œ ์ด๋ฆ„๋งŒ LLM์— ๋…ธ์ถœํ•ด์„œ best ์„ ํƒ (์ €๋น„์šฉ)
276
+ # - "row_preview" : top N ํ‘œ์˜ sample row 1๊ฐœ fetch + LLM์— ๋…ธ์ถœ (์ •ํ™•๋„โ†‘ ๋น„์šฉโ†‘)
277
+ # [2026-05-26 ํšŒ๊ท€] row_preview rerank๊ฐ€ LLM ์ž์œ  ์ถ”์ถœ ๋…ธ์ด์ฆˆ์™€ ๊ฒฐํ•ฉํ•ด ์ •๋‹ต ํ‘œ๋ฅผ
278
+ # ์˜คํžˆ๋ ค *๋‹ค๋ฅธ ๋ฌด๊ด€ ํ‘œ*๋กœ ์˜ฎ๊ธฐ๋Š” ์ผ€์ด์Šค ๋ฐœ์ƒ. ์–ด์ œ ์ž˜ ๋๋˜ ๋ฒ ์ด์Šค(none)๋กœ ๋˜๋Œ๋ฆผ.
279
+ # ํ‘œ ์„ ํƒ ์ •ํ™•๋„ ๊ฐœ์„ ์€ ๋ณ„๋„ ์ž‘์—…(indicator semantic guard ๋“ฑ)์œผ๋กœ ์ฒ˜๋ฆฌ.
280
+ rerank_mode: "none"
281
+ rerank_top_n: 5
282
+ rerank_min_score_gap: 0.15
283
+
284
+ # [P28 2026-05-22] Deep Exploration โ€” row ๋‹จ์„œ ๊ธฐ๋ฐ˜ ์™ธ์‚ฝ reasoning.
285
+ # pgvector catalog์€ *ํ‘œ ์ด๋ฆ„*๋งŒ ์ž„๋ฒ ๋”ฉ โ†’ row-level keyword (์˜ˆ: "์ฒด์™ธ ์ถฉ๊ฒฉํŒŒ
286
+ # ์‡„์„์ˆ ", "์น˜๋ฃŒ ๊ฐ€๋Šฅ ์‚ฌ๋ง๋ฅ ")๋Š” cosine ๊ฒ€์ƒ‰์—์„œ ์ž˜ ์•ˆ ์žกํž˜. P21B์˜ row_preview
287
+ # rerank๋ฅผ ๊ต์ฒดํ•˜๋Š” ์ƒˆ mechanism (P25์—์„œ P21B๋Š” disable๋จ):
288
+ # - top 3 ํ›„๋ณด ร— Y prdSe ร— 5 row๋งŒ fetch (KOSIS ๋ถ€ํ•˜ โ†“, P25 ํšŒํ”ผ)
289
+ # - prompt๊ฐ€ "best ๋‹จ์ˆœ ์„ ํƒ"์ด ์•„๋‹ˆ๋ผ "row ๋‹จ์„œ ์™ธ์‚ฝ reasoning"
290
+ # - LLM์ด "none"์ด๋ฉด ํ˜ธ์ถœ์ž์— ์‹ ํ˜ธ โ†’ reflect๊ฐ€ query refinement ๊ฒฐ์ •
291
+ # - P20 KOSIS cache hit์ด๋ฉด ๋น„์šฉ 0
292
+ # ๋ฐœ๋™ ์กฐ๊ฑด (๋‘˜ ์ค‘ ํ•˜๋‚˜):
293
+ # - T1: top1 score < trigger_low_score OR (top1-top2) < trigger_score_gap
294
+ # - T2: catalog_search input์— force_explore=true (fetch ์‹คํŒจ ํ›„ reflect๊ฐ€ ์‚ฌ์šฉ)
295
+ deep_explore:
296
+ enabled: true
297
+ top_n: 3 # sample row ๊ฐ€์ ธ์˜ฌ ์ƒ์œ„ ํ›„๋ณด ์ˆ˜
298
+ rows_per_table: 5 # ํ‘œ๋‹น LLM์— ๋ณด์—ฌ์ค„ row ์ˆ˜
299
+ trigger_low_score: 0.6 # top1 < ์ด ๊ฐ’์ด๋ฉด T1 ๋ฐœ๋™
300
+ trigger_score_gap: 0.1 # top1-top2 < ์ด ๊ฐ’์ด๋ฉด T1 ๋ฐœ๋™
301
+ max_per_claim: 2 # claim๋‹น ์ตœ๋Œ€ ๋ฐœ๋™ ํšŸ์ˆ˜ (loop ๋ฐฉ์ง€)
302
+ # ์‚ฌ์šฉํ•  HCX ๋ชจ๋ธ tier โ€” llm.models์˜ ํ‚ค (heavy/light/structured/reasoning).
303
+ # row ํŒจํ„ด ์˜๋ฏธ ๋น„๊ต๊ฐ€ ์ฃผ์ž‘์—…์ด๋ผ light(HCX-DASH-002) ์ถฉ๋ถ„. ์ •ํ™•๋„ ๋”
304
+ # ํ•„์š”ํ•˜๋ฉด "heavy"(HCX-003) ๋˜๋Š” "structured"(HCX-007)๋กœ ๊ฒฉ์ƒ ๊ฐ€๋Šฅ.
305
+ model_tier: "light"
306
+ # [P30 2026-05-22] force_explore=true ์‹œ ์‚ฌ์šฉํ•  ๋ฐฉ์‹.
307
+ # - "meta" (๊ธฐ๋ณธ, ๊ถŒ์žฅ) : KOSIS getMeta(ITM/OBJ) โ€” ํ‘œ๋‹น ~1์ดˆ. ITM_NM list์—
308
+ # indicator ํ‚ค์›Œ๋“œ ์ง์ ‘ ๋งค์นญ ํ™•์ธ ๊ฐ€๋Šฅ. Catalog ํ›„๋ณด
309
+ # ์•ˆ์— ์ •๋‹ต์ด *์žˆ๋Š”๋ฐ* cosine ์ ์ˆ˜๋กœ ๋ฌปํžŒ ์ผ€์ด์Šค์— ๊ฐ•ํ•จ.
310
+ # - "row_preview" : ํ‘œ ์ „์ฒด row preview โ€” ํ‘œ๋‹น 22~26์ดˆ. Catalog ํ‘œ ์ด๋ฆ„
311
+ # ์œผ๋ก  ๋ชจ๋ฅด์ง€๋งŒ row ๋ถ„๋ฅ˜ ํŒจํ„ด์œผ๋กœ ์™ธ์‚ฝํ•ด์•ผ ํ•˜๋Š” ์ผ€์ด์Šค.
312
+ # input์˜ explore_mode๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ๊ฒŒ ์šฐ์„ . ์—†์œผ๋ฉด ์ด default ์‚ฌ์šฉ.
313
+ explore_mode: "meta"
314
+ # meta ๋ชจ๋“œ ์˜ต์…˜ โ€” OBJ(๋ถ„๋ฅ˜ ํ•ญ๋ชฉ, ์ง€์—ญ/์—ฐ๋ น ๋“ฑ) ๋ฉ”ํƒ€๋„ ๊ฐ€์ ธ์˜ฌ์ง€.
315
+ # true: ITM + OBJL01 ๋‘˜ ๋‹ค โ†’ ์ •ํ™•๋„ โ†‘ ํ˜ธ์ถœ ์ˆ˜ 2๋ฐฐ.
316
+ # false: ITM๋งŒ โ†’ 1/2 ํ˜ธ์ถœ.
317
+ include_obj: true
318
+
319
+ # [P30 2026-05-22] Query rewriter โ€” catalog_search ์ง์ „์— LLM์ด query ๋ณ€ํ˜•.
320
+ # input.query_rewrite=true์ผ ๋•Œ๋งŒ ๋ฐœ๋™ (reflect๊ฐ€ ๋ช…์‹œ ํ˜ธ์ถœ). row-level keyword
321
+ # (์˜ˆ: "์ฒด์™ธ ์ถฉ๊ฒฉํŒŒ ์‡„์„์ˆ  ์žฅ๋น„")๋กœ๋Š” catalog ํ›„๋ณด์— ์ •๋‹ต ํ‘œ๊ฐ€ ์ง„์ž…์กฐ์ฐจ ์•ˆ ๋˜๋Š”
322
+ # ์ผ€์ด์Šค์— ์‚ฌ์šฉ. LLM์ด "์‹œ๊ตฐ๊ตฌ๋ณ„ ์˜๋ฃŒ์žฅ๋น„" ๊ฐ™์€ *ํ‘œ ์ด๋ฆ„ ์นœํ™” ์–ดํœ˜*๋กœ ๋ณ€ํ˜• โ†’
323
+ # ๊ฐ ๋ณ€ํ˜•์œผ๋กœ catalog ๊ฒ€์ƒ‰ โ†’ ํ•ฉ์ง‘ํ•ฉ ๋ฐ˜ํ™˜.
324
+ query_rewriter:
325
+ enabled: true
326
+ n_variations: 3 # ๋ณ€ํ˜• query ๊ฐœ์ˆ˜ (์›๋ณธ ์™ธ์— ์ถ”๊ฐ€๋กœ ์ƒ์„ฑํ•  ๊ฐœ์ˆ˜)
327
+ model_tier: "light"
328
+
329
+ candidate_detection:
330
+ enabled: true
331
+ threshold: 0.65
332
+ use_surface_signals: true
333
+ teacher_llm_fallback: true
334
+ # [2026-05-21] LLM ๋™์‹œ ํ˜ธ์ถœ ์ƒํ•œ.
335
+ # candidate scoring(step 1) + check-worthiness(step 2) ๋‘ ๋‹จ๊ณ„๊ฐ€ ๊ฐ๊ฐ ์ด ๊ฐ’์„
336
+ # Semaphore ์ƒํ•œ์œผ๋กœ ์‚ฌ์šฉ. HCX-003์€ *์ดˆ๋‹น ์š”์ฒญ ์ˆ˜* ์ œํ•œ์ด ๋นก์„ธ์„œ 5 ์ด์ƒ์ด๋ฉด
337
+ # 429 Too Many Requests ํญ์ฃผ(2026-05-21 21:42 ๋กœ๊ทธ โ€” 5 concurrent โ†’ ์ „๋ถ€ 429).
338
+ # 22:24 ์žฌํ…Œ์ŠคํŠธ: 4 concurrent + min_call_interval_ms=250๋„ ์ง€์† 429 ๋ฐœ์ƒ.
339
+ # โ†’ 2๋กœ ํ•˜ํ–ฅ. min_call_interval_ms์™€ ํ•จ๊ป˜ *์‹ค์ œ ์ฒ˜๋ฆฌ๋Ÿ‰*์„ ๊ฒฐ์ •.
340
+ concurrency: 2
341
+
342
+ observability:
343
+ langfuse_enabled: false
344
+ log_level: "INFO"
345
+
346
+ preprocessing:
347
+ sandbox_backend: "docker" # "exec" | "docker" | "e2b"
348
+ use_llm_clean: true
349
+
350
+ enable_feedback: false # ํ”ผ๋“œ๋ฐฑ/ํ•™์Šต ๋ฃจํ”„ ๋น„ํ™œ์„ฑํ™”
File without changes
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+ import os
3
+ from pathlib import Path
4
+
5
+ # ํŒจํ‚ค์ง€์— ๋ฒˆ๋“ค๋œ ๊ธฐ๋ณธ ์„ค์ • โ€” pip ์„ค์น˜ ํ›„์—๋„ ํ•ญ์ƒ ์กด์žฌํ•œ๋‹ค.
6
+ _PACKAGED_DEFAULT = Path(__file__).parent.parent / "config" / "default.yaml"
7
+ # ๊ฐœ๋ฐœ ์ฒดํฌ์•„์›ƒ(backend/config/default.yaml) ์˜ค๋ฒ„๋ผ์ด๋“œ โ€” ์žˆ์œผ๋ฉด ์šฐ์„ .
8
+ _REPO_DEFAULT = Path(__file__).parent.parent.parent / "config" / "default.yaml"
9
+ _REPO_ENV = Path(__file__).parent.parent.parent / ".env"
10
+
11
+
12
+ def load_config(path: str | None = None) -> dict:
13
+ """์„ค์ •์„ ๋กœ๋“œํ•œ๋‹ค.
14
+
15
+ ์šฐ์„ ์ˆœ์œ„: ๋ช…์‹œ ``path`` โ†’ ๊ฐœ๋ฐœ ์ฒดํฌ์•„์›ƒ(backend/config/default.yaml) โ†’
16
+ ํŒจํ‚ค์ง€ ๋ฒˆ๋“ค(structverify/config/default.yaml). ์–ด๋А ๊ฒƒ๋„ ์—†์œผ๋ฉด ``{}``.
17
+ ์ด ํด๋ฐฑ ๋•๋ถ„์— ``pip install`` ํ›„์—๋„(๋ฆฌํฌ ๋ฐ–์—์„œ๋„) ๊ธฐ๋ณธ ์„ค์ •์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.
18
+ """
19
+ from dotenv import load_dotenv
20
+ # ๊ฐœ๋ฐœ ์ฒดํฌ์•„์›ƒ์ด๋ฉด .env ์ž๋™ ๋กœ๋“œ โ€” ์—†์œผ๋ฉด ์กฐ์šฉํžˆ ๋ฌด์‹œ๋œ๋‹ค.
21
+ if _REPO_ENV.exists():
22
+ load_dotenv(_REPO_ENV)
23
+
24
+ import yaml
25
+ if path is None:
26
+ path = str(_REPO_DEFAULT if _REPO_DEFAULT.exists() else _PACKAGED_DEFAULT)
27
+ if not os.path.exists(path):
28
+ return {}
29
+ with open(path, "r", encoding="utf-8") as f:
30
+ return yaml.safe_load(f) or {}
@@ -0,0 +1,280 @@
1
+ """
2
+ # ์ˆ˜์ •์ž: ๋ฐ•์žฌ์œค
3
+ # ์ˆ˜์ • ๋‚ ์งœ: 2026-04-29
4
+ # ์ˆ˜์ • ๋‚ด์šฉ: DBManager ์ดˆ๊ธฐํ™” ๋ฐ save_claims, save_results ์—ฐ๋™ ๊ตฌํ˜„
5
+
6
+ # [DONE] DBManager ์ดˆ๊ธฐํ™” ์—ฐ๋™
7
+ # [DONE] save_claims ํŒŒ์ดํ”„๋ผ์ธ ์—ฐ๊ฒฐ
8
+ # [DONE] save_results ํŒŒ์ดํ”„๋ผ์ธ ์—ฐ๊ฒฐ
9
+ # [DONE] ๊ธฐ์‚ฌ ํ…์ŠคํŠธ ํ•ด์‹œ๋กœ doc_id ๊ณ ์ • (์žฌ์‹คํ–‰ ์‹œ ์ค‘๋ณต ๋ฐฉ์ง€)
10
+ # [DONE] save_claims source_type ํŒŒ๋ผ๋ฏธํ„ฐ ์ถ”๊ฐ€
11
+ # [TODO] RawStorage ์ดˆ๊ธฐํ™” (MinIO/S3 ์—…๋กœ๋“œ)
12
+ # [TODO] DWHManager ์ดˆ๊ธฐํ™” (Snowflake)
13
+ # [TODO] GraphStore ์ดˆ๊ธฐํ™” (Neo4j)
14
+ # [TODO] save_document ๊ตฌํ˜„ (db_manager.py)
15
+ # [๊น€์˜ˆ์Šฌ - 2026-04-30 / v3]
16
+ # - config ๊ธฐ๋ณธ๊ฐ’ load_config() ์ ์šฉ
17
+ # - feedback/ํ•™์Šต: enable_feedback=false ์ด๋ฉด Step 11~12 skip
18
+ # - runtime_agent์— kosis llm config ์ „๋‹ฌ (LLM Agent catalog ๊ฒ€์ƒ‰์šฉ)
19
+
20
+ core/pipeline.py โ€” v2.1 ๊ฒ€์ฆ ํŒŒ์ดํ”„๋ผ์ธ (13๋‹จ๊ณ„)
21
+
22
+ โ‘  ์ž…๋ ฅ โ†’ โ‘ก ์ „์ฒ˜๋ฆฌ+SIR Tree โ†’ โ‘ข ๋„๋ฉ”์ธ ํŒ๋ณ„
23
+ โ†’ โ‘ฃ Sentence Candidate Detection + Claim Detection
24
+ โ†’ โ‘ค Schema Induction โ†’ โ‘ฅ Graph Construction
25
+ โ†’ โ‘ฆ Retrieval+Evidence Subgraph โ†’ โ‘ง Verification
26
+ โ†’ โ‘จ Explanation+Provenance โ†’ โ‘ฉ Human Review
27
+ โ†’ โ‘ช Feedback Logging โ†’ โ‘ซ Adaptation Trigger โ†’ โ‘ฌ Report
28
+
29
+ [๊น€์˜ˆ์Šฌ]
30
+ - ๊ธฐ์กด Step 4์˜ "์ˆ˜์น˜ ํฌํ•จ ๋ฌธ์žฅ ํ•„ํ„ฐ๋ง + Claim Detection" ๊ตฌ์กฐ๋ฅผ
31
+ "๋ฌธ์žฅ ๋‹จ์œ„ candidate scoring + Claim Detection" ๊ตฌ์กฐ๋กœ ํ™•์žฅ
32
+ - has_numeric regex๋Š” ๋ณด์กฐ surface signal๋กœ๋งŒ ์‚ฌ์šฉ
33
+ - ์‹ค์ œ ๊ฒ€์ฆ ํ›„๋ณด ์—ฌ๋ถ€๋Š” candidate_score / candidate_label ๊ธฐ์ค€์œผ๋กœ ํŒ๋‹จ
34
+ - ์ „์ฒด ํŒŒ์ดํ”„๋ผ์ธ ํ๋ฆ„ ๋ฐ Agent ๊ฐ„ ์—ฐ๊ฒฐ ๊ด€๋ฆฌ
35
+
36
+ [์ฐธ๊ณ ] ReAct (Yao et al., ICLR 2023) โ€” https://github.com/ysymyth/ReAct
37
+ Thoughtโ†’Actionโ†’Observation ์ˆœํ™˜ ๊ธฐ๋ฐ˜ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ํŒจํ„ด
38
+ """
39
+ from __future__ import annotations
40
+
41
+ from structverify.core.schemas import (
42
+ SourceType,
43
+ SIRDocument,
44
+ VerificationReport,
45
+ )
46
+ from structverify.core.config_loader import load_config
47
+ from structverify.preprocessing.extractor import extract_text
48
+ from structverify.preprocessing.sir_builder import build_sir
49
+ from structverify.agent.runtime_agent import RuntimeAgent
50
+ from structverify.storage.db_manager import DBManager
51
+ from structverify.utils.logger import get_logger
52
+
53
+ logger = get_logger(__name__)
54
+
55
+
56
+ class VerificationPipeline:
57
+ """
58
+ 13๋‹จ๊ณ„ ๊ฒ€์ฆ ํŒŒ์ดํ”„๋ผ์ธ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ.
59
+
60
+ ์‚ฌ์šฉ๋ฒ•:
61
+ pipeline = VerificationPipeline()
62
+ report = await pipeline.run("https://news.example.com/article", "url")
63
+
64
+ Step 4 ๋ณ€๊ฒฝ์‚ฌํ•ญ:
65
+ ๊ธฐ์กด:
66
+ has_numeric=True ๋ฌธ์žฅ ์„ ๋ณ„ โ†’ LLM check-worthiness
67
+ ๋ณ€๊ฒฝ:
68
+ sentence candidate scoring โ†’ ์ƒ์œ„ ํ›„๋ณด์— ๋Œ€ํ•ด LLM check-worthiness
69
+
70
+ ์ฆ‰, Claim Detection ์•ž๋‹จ์— "Sentence Candidate Detection"์ด ์ถ”๊ฐ€๋˜์—ˆ๋‹ค.
71
+ """
72
+ def __init__(self, config: dict | None = None):
73
+ # [v3 ๊น€์˜ˆ์Šฌ] config ์—†์œผ๋ฉด default.yaml ์ž๋™ ๋กœ๋“œ
74
+ self.config = config if config is not None else load_config()
75
+
76
+ # runtime_agent์— kosis + llm ์„ค์ • ํ•ฉ์ณ์„œ ์ „๋‹ฌ
77
+ # โ†’ KOSISConnector๊ฐ€ LLM Agent(catalog ๊ฒ€์ƒ‰)์— llm ์„ค์ • ํ•„์š”
78
+ agent_config = {
79
+ **self.config,
80
+ "kosis": {
81
+ **self.config.get("kosis", {}),
82
+ "llm": self.config.get("llm", {}),
83
+ },
84
+ }
85
+ self.runtime_agent = RuntimeAgent(config=agent_config)
86
+
87
+ # [v1] - ๋ฐ•์žฌ์œค: DBManager ์ดˆ๊ธฐํ™” ์—ฐ๋™
88
+ self.db_manager = DBManager(config=self.config.get("database", {}))
89
+
90
+ # [v3 ๊น€์˜ˆ์Šฌ] ํ”ผ๋“œ๋ฐฑ/ํ•™์Šต ํ™œ์„ฑํ™” ์—ฌ๋ถ€ (false๋ฉด Step 11~12 skip)
91
+ self.enable_feedback = bool(self.config.get("enable_feedback", False))
92
+
93
+ # TODO [๊น€์˜ˆ์Šฌ]: BuilderAgent ์ดˆ๊ธฐํ™” (๋น„๋™๊ธฐ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ•™์Šต ๋ฃจํ”„)
94
+ # self.builder_agent = BuilderAgent(config=self.config)
95
+
96
+ # TODO [๋ฐ•์žฌ์œค]: RawStorage ์ดˆ๊ธฐํ™” (MinIO/S3 ์—…๋กœ๋“œ)
97
+ # self.raw_storage = RawStorage(config=self.config.get("storage", {}))
98
+
99
+ # TODO [๋ฐ•์žฌ์œค]: DWHManager ์ดˆ๊ธฐํ™” (Snowflake)
100
+ # self.dwh_manager = DWHManager(config=self.config.get("dwh", {}))
101
+
102
+ # TODO [๋ฐ•์žฌ์œค]: GraphStore ์ดˆ๊ธฐํ™” (Neo4j)
103
+ # self.graph_store = GraphStore(config=self.config.get("graph", {}))
104
+
105
+ async def run(
106
+ self,
107
+ source: str,
108
+ source_type: str = "text",
109
+ source_text: str | None = None,
110
+ ) -> VerificationReport:
111
+ """
112
+ source ์ž…๋ ฅ์„ ๋ฐ›์•„ ์ „์ฒด ๊ฒ€์ฆ ํŒŒ์ดํ”„๋ผ์ธ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค.
113
+
114
+ Args:
115
+ source: ์›๋ณธ ์ž…๋ ฅ (ํ…์ŠคํŠธ/URL/PDF ๊ฒฝ๋กœ ๋“ฑ)
116
+ source_type: "text" | "url" | "pdf" | "docx"
117
+ source_text: *์˜ต์…˜* โ€” ์ด๋ฏธ ์ถ”์ถœ๋œ ๋ณธ๋ฌธ ํ…์ŠคํŠธ๊ฐ€ ์žˆ์œผ๋ฉด ์ „๋‹ฌ.
118
+ URL/PDF ์ž…๋ ฅ์—์„œ sv_platform์ด ๋ฏธ๋ฆฌ ์ถ”์ถœํ•ด Job.source_data์—
119
+ ์ €์žฅํ•œ ๋’ค ๊ทธ ํ…์ŠคํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ๋„˜๊ฒจ์ฃผ๋ฉด *์ค‘๋ณต ์ถ”์ถœ ์—†์ด* ํŒŒ์ดํ”„๋ผ์ธ
120
+ ์ง„ํ–‰. (์‹ค์‹œ๊ฐ„ partial ์‘๋‹ต ๋งค์นญ์—๋„ ์‚ฌ์šฉ๋จ.)
121
+ None์ด๋ฉด source์—์„œ extract_text() ์ง์ ‘ ํ˜ธ์ถœ.
122
+
123
+ Returns:
124
+ VerificationReport: ์ „์ฒด ๊ฒ€์ฆ ๊ฒฐ๊ณผ ๋ณด๊ณ ์„œ
125
+ """
126
+ src = SourceType(source_type)
127
+ logger.info(f"ํŒŒ์ดํ”„๋ผ์ธ ์‹œ์ž‘: {src.value}")
128
+ import structverify.progress as _progress
129
+ _enabled = list((self.config.get("data_sources", {}) or {}).get("enabled", []) or [])
130
+ _progress.emit("start", source=", ".join(_enabled) or src.value)
131
+
132
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
133
+ # Step 1~2: ์ž…๋ ฅ โ†’ ์ „์ฒ˜๋ฆฌ โ†’ SIR Tree
134
+ # โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
135
+ # TODO [์ด์ˆ˜๋ฏผ]: extractor.py โ€” URL/PDF/DOCX ์‹ค์ œ ์ถ”์ถœ ๋กœ์ง ๊ตฌํ˜„
136
+ # - URL: trafilatura.extract() ํ˜ธ์ถœ
137
+ # - PDF: PyMuPDF(fitz) ํŽ˜์ด์ง€๋ณ„ ํ…์ŠคํŠธ ์ถ”์ถœ
138
+ # - DOCX: python-docx ๋‹จ๋ฝ ์ถ”์ถœ
139
+ if source_text is not None and source_text.strip():
140
+ # sv_platform์ด ์‚ฌ์ „ ์ถ”์ถœํ•ด ๋„˜๊ธด ๊ฒฝ์šฐ โ€” ์ค‘๋ณต ํ˜ธ์ถœ ํšŒํ”ผ
141
+ raw_text = source_text
142
+ logger.info(f"ํŒŒ์ดํ”„๋ผ์ธ: ์‚ฌ์ „ ์ถ”์ถœ ๋ณธ๋ฌธ ์‚ฌ์šฉ ({len(raw_text)}์ž)")
143
+ else:
144
+ raw_text = await extract_text(source, src)
145
+
146
+ # TODO [์ด์ˆ˜๋ฏผ]: sir_builder.py โ€” SIR Tree ๋ณ€ํ™˜ ๊ฒ€์ฆ
147
+ # - block ๋ถ„ํ•  + ๋ฌธ์žฅ ๋ถ„๋ฆฌ + ์ ˆ๋Œ€ offset ๋ณด์ • ํ™•์ธ
148
+ # - entity_refs, event_refs ์ถ”์ถœ (ํ˜„์žฌ regex placeholder โ†’ NER ๊ต์ฒด ์˜ˆ์ •)
149
+ sir_doc = build_sir(
150
+ raw_text,
151
+ src,
152
+ source_uri=source if src == SourceType.URL else None,
153
+ )
154
+ # [v2] - ๋ฐ•์žฌ์œค: ๊ธฐ์‚ฌ ํ…์ŠคํŠธ ํ•ด์‹œ๋กœ doc_id ๊ณ ์ • (์žฌ์‹คํ–‰ ์‹œ ์ค‘๋ณต ๋ฐฉ์ง€)
155
+ import hashlib
156
+ from uuid import UUID
157
+ text_hash = hashlib.md5(raw_text.encode()).hexdigest()
158
+ sir_doc.doc_id = UUID(text_hash)
159
+ # [2026-05-21] URL/PDF ์ž…๋ ฅ์˜ ์ถ”์ถœ๋œ ๋ณธ๋ฌธ์„ SIRDocument์— ๋ณด์กด.
160
+ # sv_platform์ด Job.source_data๋กœ ๋ณต์‚ฌํ•ด ํ”„๋ก ํŠธ "์›๋ฌธ" ํŒจ๋„ ๋ Œ๋”์— ์‚ฌ์šฉ.
161
+ sir_doc.raw_text = raw_text
162
+ logger.info(f"SIR Tree: {len(sir_doc.blocks)} blocks")
163
+
164
+ # TODO [๋ฐ•์žฌ์œค]: Step 1.5 โ€” ์›๋ณธ ํ…์ŠคํŠธ โ†’ Raw Storage(S3/MinIO) ์ €์žฅ
165
+ # await self.raw_storage.upload(source, raw_text, metadata={"source_type": src.value})
166
+
167
+ # TODO [๋ฐ•์žฌ์œค]: Step 2.5 โ€” SIR Document โ†’ PostgreSQL ์ €์žฅ
168
+ # await self.db_manager.save_document(sir_doc)
169
+
170
+ # Step 3~9: Runtime Agent ์‹คํ–‰
171
+ claims, results, nodes, edges = await self.runtime_agent.process(sir_doc)
172
+
173
+ # [v1] - ๋ฐ•์žฌ์œค: Claims/Results โ†’ PostgreSQL ์ €์žฅ (ํ”Œ๋žซํผ ์ „์šฉ).
174
+ # ์˜คํ”ˆ์†Œ์Šค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ฒฝ๋กœ์—๋Š” postgres๊ฐ€ ์—†์œผ๋ฏ€๋กœ ์ €์žฅ ์‹คํŒจ๊ฐ€
175
+ # ๊ฒ€์ฆ ์ž์ฒด๋ฅผ ์ฃฝ์ด์ง€ ์•Š๊ฒŒ ๊ฐ์‹ผ๋‹ค (psycopg2 ๋ฏธ์„ค์น˜/์—ฐ๊ฒฐ๋ถˆ๊ฐ€ โ†’ ๊ฒฝ๊ณ  ํ›„ ์ง„ํ–‰).
176
+ try:
177
+ if claims:
178
+ await self.db_manager.save_claims(claims, domain=sir_doc.detected_domain, source_type=src.value)
179
+ logger.info(f"Claims ์ €์žฅ ์™„๋ฃŒ: {len(claims)}๊ฑด")
180
+ if results:
181
+ await self.db_manager.save_results(results, claims)
182
+ logger.info(f"Results ์ €์žฅ ์™„๋ฃŒ: {len(results)}๊ฑด")
183
+ except ImportError as e:
184
+ logger.debug(f"๊ฒฐ๊ณผ ์ €์žฅ ์Šคํ‚ต (DB ๋“œ๋ผ์ด๋ฒ„ ์—†์Œ โ€” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ชจ๋“œ): {e}")
185
+ except Exception as e: # noqa: BLE001 โ€” ์ €์žฅ ์‹คํŒจ๊ฐ€ ๊ฒ€์ฆ ๊ฒฐ๊ณผ๋ฅผ ๋ฌดํšจํ™”ํ•˜์ง€ ์•Š์Œ
186
+ logger.warning(f"๊ฒฐ๊ณผ ์ €์žฅ ์‹คํŒจ (๊ฒ€์ฆ ๊ฒฐ๊ณผ๋Š” ์œ ํšจ): {e}")
187
+
188
+ # TODO [๋ฐ•์žฌ์œค]: Nodes/Edges โ†’ Neo4j ์ €์žฅ
189
+ # await self.graph_store.merge_nodes(nodes)
190
+ # await self.graph_store.merge_edges(edges)
191
+
192
+ # Step 10~13: ํ”ผ๋“œ๋ฐฑ/ํ•™์Šต ๋ฃจํ”„ (enable_feedback=true ์ผ ๋•Œ๋งŒ ํ™œ์„ฑํ™”)
193
+ # [v3 ๊น€์˜ˆ์Šฌ] enable_feedback=false (๊ธฐ๋ณธ๊ฐ’) โ†’ skip
194
+ # ํ™œ์„ฑํ™”: config.yaml์—์„œ enable_feedback: true
195
+ if self.enable_feedback:
196
+ pass
197
+ # TODO [๊น€์˜ˆ์Šฌ]: Step 10 โ€” Human Review ์ธํ„ฐํŽ˜์ด์Šค ์—ฐ๋™
198
+ # TODO [๊น€์˜ˆ์Šฌ]: Step 11 โ€” Feedback Logging
199
+ # TODO [๊น€์˜ˆ์Šฌ]: Step 12 โ€” Adaptation Trigger
200
+ # TODO [๊น€์˜ˆ์Šฌ]: Step 13 โ€” Report ๋ Œ๋”๋ง
201
+ else:
202
+ logger.debug("Step 10~13 skip (enable_feedback=false)")
203
+
204
+ # TODO [๋ฐ•์žฌ์œค]: DWH ์ ์žฌ (๊ฒ€์ฆ ๋กœ๊ทธ, ๋ชจ๋ธ ์„ฑ๋Šฅ, LLM ๋น„์šฉ)
205
+ # await self.dwh_manager.load_verification_logs([...])
206
+
207
+ report = VerificationReport(
208
+ document=sir_doc,
209
+ claims=claims,
210
+ results=results,
211
+ graph_nodes=nodes,
212
+ graph_edges=edges,
213
+ domain_pack_used=sir_doc.detected_domain,
214
+ )
215
+ logger.info(f"ํŒŒ์ดํ”„๋ผ์ธ ์™„๋ฃŒ: {len(results)} results")
216
+ _progress.emit("done", count=len(results))
217
+ return report
218
+
219
+
220
+ async def verify_text(text: str, config: dict | None = None) -> VerificationReport:
221
+ """
222
+ ์ตœ์ƒ์œ„ API โ€” ํ…์ŠคํŠธ ์ž…๋ ฅ โ†’ ๊ฒ€์ฆ ๋ณด๊ณ ์„œ
223
+
224
+ Args:
225
+ text: ๊ฒ€์ฆํ•  ์›๋ฌธ ํ…์ŠคํŠธ
226
+ config: ์„ ํƒ์  ์„ค์ • dict
227
+
228
+ Returns:
229
+ VerificationReport
230
+ """
231
+ return await VerificationPipeline(config).run(text, "text")
232
+
233
+
234
+ # [v2 - ๊น€์˜ˆ์Šฌ] ๋ฌธ์„œ ์ž…๋ ฅ(URL/PDF/DOCX/TEXT) ๊ณต๊ฐœ ์ง„์ž…์ 
235
+ async def verify_document(
236
+ source: str,
237
+ source_type: str = "url",
238
+ config: dict | None = None,
239
+ source_text: str | None = None,
240
+ ) -> VerificationReport:
241
+ """
242
+ ์ตœ์ƒ์œ„ API โ€” ๋ฌธ์„œ ์ž…๋ ฅ(URL/PDF/DOCX/TEXT) โ†’ ๊ฒ€์ฆ ๋ณด๊ณ ์„œ.
243
+
244
+ Args:
245
+ source: URL ยท ํŒŒ์ผ ๊ฒฝ๋กœ ยท ๋˜๋Š” ๋ณธ๋ฌธ ํ…์ŠคํŠธ
246
+ source_type: "url" | "pdf" | "docx" | "text"
247
+ config: ์„ ํƒ์  ์„ค์ • dict
248
+ source_text: ์ด๋ฏธ ์ถ”์ถœ๋œ ๋ณธ๋ฌธ์ด ์žˆ์œผ๋ฉด ์ „๋‹ฌ(์ค‘๋ณต ์ถ”์ถœ ๋ฐฉ์ง€)
249
+
250
+ Returns:
251
+ VerificationReport
252
+ """
253
+ return await VerificationPipeline(config).run(source, source_type, source_text)
254
+
255
+
256
+ # [v2 - ๊น€์˜ˆ์Šฌ] ๊ฐ์ฒดํ˜• ์ง„์ž…์  โ€” ๊ฐ™์€ config๋กœ ์—ฌ๋Ÿฌ ๋ฒˆ ๊ฒ€์ฆํ•  ๋•Œ (ํ•จ์ˆ˜ํ˜•์€ ์ผํšŒ์„ฑ).
257
+ class VerificationEngine:
258
+ """config๋ฅผ ํ•œ ๋ฒˆ ์ฃผ์ž…ํ•ด ๋‘๊ณ  ์—ฌ๋Ÿฌ ๋ฌธ์„œ๋ฅผ ๊ฒ€์ฆํ•˜๋Š” ๊ฐ์ฒดํ˜• ์ง„์ž…์ .
259
+
260
+ - ํ•จ์ˆ˜ํ˜•(`verify_text`/`verify_document`): ๋งค๋ฒˆ config๋ฅผ ๋ฐ›๋Š” *์ผํšŒ์„ฑ* ํ˜ธ์ถœ์— ํŽธํ•จ.
261
+ - ๊ฐ์ฒดํ˜•(`VerificationEngine`): ์—”์ง„์„ ๋งŒ๋“ค์–ด ๋‘๊ณ  ์žฌ์‚ฌ์šฉ.
262
+
263
+ engine = VerificationEngine(config)
264
+ r1 = await engine.verify_text("๋ฌธ์žฅ1")
265
+ r2 = await engine.verify_document(url, source_type="url")
266
+ """
267
+
268
+ def __init__(self, config: dict | None = None):
269
+ self._pipeline = VerificationPipeline(config)
270
+
271
+ async def verify_text(self, text: str) -> VerificationReport:
272
+ return await self._pipeline.run(text, "text")
273
+
274
+ async def verify_document(
275
+ self,
276
+ source: str,
277
+ source_type: str = "url",
278
+ source_text: str | None = None,
279
+ ) -> VerificationReport:
280
+ return await self._pipeline.run(source, source_type, source_text)