Metadata-Version: 2.4
Name: structverify
Version: 0.3.6
Summary: Compliance & fact verification for documents — check your files against your own rulebook (PDF) or a data source, and read a plain True/False.
Author-email: "김예슬 (Yeseul Kim)" <yesul0718@gmail.com>
License: MIT License
        
        Copyright (c) 2026 김예슬 (Yeseul Kim) and StructVerify contributors
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/2026-StructVerify-Lab/structverify
Project-URL: Repository, https://github.com/2026-StructVerify-Lab/structverify
Project-URL: Issues, https://github.com/2026-StructVerify-Lab/structverify/issues
Keywords: compliance,fact-checking,verification,llm,rag,regulation,conformance
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Text Processing :: Linguistic
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic>=2.0
Requires-Dist: httpx>=0.25
Requires-Dist: pyyaml>=6.0
Requires-Dist: openai>=1.10
Requires-Dist: pdfplumber>=0.10
Requires-Dist: python-dotenv
Requires-Dist: json5>=0.9.0
Provides-Extra: kosis
Requires-Dist: asyncpg>=0.29; extra == "kosis"
Provides-Extra: korean
Requires-Dist: kss>=6.0; extra == "korean"
Provides-Extra: url
Requires-Dist: trafilatura>=1.8; extra == "url"
Requires-Dist: beautifulsoup4>=4.12; extra == "url"
Provides-Extra: db
Requires-Dist: sqlalchemy>=2.0; extra == "db"
Provides-Extra: docs
Requires-Dist: python-docx>=1.1; extra == "docs"
Provides-Extra: pdf-ocr
Requires-Dist: pymupdf>=1.23; extra == "pdf-ocr"
Provides-Extra: graph
Requires-Dist: neo4j>=5.0; extra == "graph"
Provides-Extra: adaptation
Requires-Dist: mlflow>=2.10; extra == "adaptation"
Requires-Dist: boto3>=1.34; extra == "adaptation"
Requires-Dist: peft>=0.8; extra == "adaptation"
Requires-Dist: transformers>=4.38; extra == "adaptation"
Requires-Dist: torch>=2.1; extra == "adaptation"
Provides-Extra: training
Requires-Dist: unsloth; extra == "training"
Requires-Dist: trl>=0.8; extra == "training"
Requires-Dist: peft>=0.8; extra == "training"
Requires-Dist: bitsandbytes>=0.43; extra == "training"
Requires-Dist: accelerate>=0.27; extra == "training"
Requires-Dist: datasets>=2.16; extra == "training"
Requires-Dist: transformers>=4.40; extra == "training"
Requires-Dist: torch>=2.2; extra == "training"
Provides-Extra: training-mac
Requires-Dist: mlx-lm>=0.18; extra == "training-mac"
Provides-Extra: platform
Requires-Dist: fastapi>=0.109; extra == "platform"
Requires-Dist: uvicorn>=0.27; extra == "platform"
Requires-Dist: sqlalchemy>=2.0; extra == "platform"
Requires-Dist: asyncpg>=0.29; extra == "platform"
Requires-Dist: celery>=5.3; extra == "platform"
Requires-Dist: redis>=5.0; extra == "platform"
Requires-Dist: snowflake-connector-python>=3.6; extra == "platform"
Requires-Dist: langfuse>=2.0; extra == "platform"
Requires-Dist: python-docx>=1.1; extra == "platform"
Provides-Extra: all
Requires-Dist: asyncpg>=0.29; extra == "all"
Requires-Dist: kss>=6.0; extra == "all"
Requires-Dist: trafilatura>=1.8; extra == "all"
Requires-Dist: python-docx>=1.1; extra == "all"
Requires-Dist: neo4j>=5.0; extra == "all"
Requires-Dist: mlflow>=2.10; extra == "all"
Requires-Dist: boto3>=1.34; extra == "all"
Requires-Dist: fastapi>=0.109; extra == "all"
Requires-Dist: uvicorn>=0.27; extra == "all"
Requires-Dist: sqlalchemy>=2.0; extra == "all"
Requires-Dist: celery>=5.3; extra == "all"
Requires-Dist: redis>=5.0; extra == "all"
Requires-Dist: snowflake-connector-python>=3.6; extra == "all"
Requires-Dist: langfuse>=2.0; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: ruff>=0.2; extra == "dev"
Requires-Dist: mypy>=1.8; extra == "dev"
Provides-Extra: peft
Requires-Dist: peft>=0.8; extra == "peft"
Requires-Dist: transformers>=4.38; extra == "peft"
Requires-Dist: torch>=2.1; extra == "peft"
Dynamic: license-file

# StructVerify

**문서가 규칙을 지켰는지, 수치가 사실인지 코드 한 줄로 검사하는 파이썬 라이브러리입니다.**
내 규정집(PDF)이나 내 데이터 소스(CSV · DB · 문서 · 공공통계)를 정답으로 삼아 문서를 대조하고,
결과에서 평범한 `True` / `False`를 읽습니다.

[![PyPI](https://img.shields.io/pypi/v/structverify)](https://pypi.org/project/structverify/)
![Python](https://img.shields.io/badge/python-3.10%2B-blue)
![License: MIT](https://img.shields.io/badge/license-MIT-green)
![Providers](https://img.shields.io/badge/LLM-upstage%20%7C%20openai%20%7C%20gemini%20%7C%20hcx-orange)
[![Docs](https://img.shields.io/badge/docs-structverify.yeseulkim.cloud-8250df)](http://structverify.yeseulkim.cloud)

![StructVerify 데모 - 규정집으로 문서를 검사하고 준수/위반을 판정](https://raw.githubusercontent.com/2026-StructVerify-Lab/structverify/main/docs/media/structverify_demo.gif)

```python
import structverify as sv

rules = sv.Ruleset.from_file("safety_standard.pdf", provider="upstage")
v = rules.check("총납 함량은 120mg/kg으로 측정되었다")

v.compliant     # False        <- 평범한 True/False
v.article       # "제3조 (총납) ... 100mg/kg 이하"
v.rule_value, v.claim_value, v.unit   # (100.0, 120.0, 'mg/kg')
```

**공식 문서**: [사용법](http://structverify.yeseulkim.cloud/docs/usage.html) ·
[레퍼런스](http://structverify.yeseulkim.cloud/docs/reference.html) ·
[학습 가이드](http://structverify.yeseulkim.cloud/docs/training.html) ·
[3분 데모 영상](http://structverify.yeseulkim.cloud/#video)

---

## 목차

- [무엇을 하는 라이브러리인가](#무엇을-하는-라이브러리인가)
- [설치](#설치)
- [빠른 시작](#빠른-시작)
- [데이터 소스: 무엇이든 정답으로](#데이터-소스-무엇이든-정답으로)
- [동작 구조](#동작-구조)
- [감독 학습 루프: 쓸수록 정확해지는 검증](#감독-학습-루프-쓸수록-정확해지는-검증)
- [결과 객체](#결과-객체)
- [설정 · 로깅 · 진행 대시보드](#설정--로깅--진행-대시보드)
- [검증 실적](#검증-실적)
- [예제](#예제)
- [프로젝트 구조](#프로젝트-구조)
- [테스트](#테스트)
- [알려진 한계](#알려진-한계)
- [라이선스](#라이선스)

---

## 무엇을 하는 라이브러리인가

기업과 기관에서는 시험성적서, 경영 보고서, 공시 자료 같은 문서가 정해진 기준과 실제
데이터에 부합하는지 사람이 규정집과 원본 데이터를 대조하며 수작업으로 확인합니다.
StructVerify는 이 대조 작업을 두 갈래의 API로 자동화합니다.

| 검증 종류 | 진입점 | 정답 기준 | 판정 |
|---|---|---|---|
| **규정 준수 검사** | `Ruleset` | 내 규정집 (PDF/텍스트) | `compliant` / `violation` / `unverifiable` |
| **사실 검증** | `sv.verify` · `Verifier` | 내 데이터 소스 (CSV · DB · 문서 · KOSIS 통계) | `match` / `mismatch` / `unverifiable` |

핵심 설계 원칙은 다음과 같습니다.

- **내 데이터를 정답으로.** 공공 데이터에 고정된 기존 사실검증 도구와 달리, 사용자의
  규정 PDF · 참조 CSV · 사내 DB를 그대로 정답 기준으로 삼습니다. 도메인에 독립적입니다.
- **판정은 코드가, 결정론적으로.** LLM은 기준값과 측정값을 추출하고 근거를 수집하는
  역할만 합니다. 준수/위반, 일치/불일치 판정은 허용오차와 한도 규칙에 따라 코드가
  결정론적으로 계산하므로 같은 입력에는 같은 판정이 나옵니다.
- **결과가 곧 불리언.** `.ok` · `.compliant` · `.violated`, `bool(result)`, 순회(iterable),
  `.to_dict()`. 함수에 점을 찍어 부르면 원하는 값이 바로 나옵니다.
- **원시 테이블만 있어도 검증.** 지표 정의가 없는 트랜잭션 테이블이라도 에이전트가
  스키마를 조사해 읽기 전용 SQL을 스스로 작성하고, 집계·비율 주장까지 검증합니다.
- **provider에 얽매이지 않음.** `upstage` · `openai` · `gemini` · `hcx` 중 무엇이든
  설정 한 줄로 전환하고, 직접 학습한 모델도 주입할 수 있습니다.
- **감독 학습 루프 내장.** 검증에서 축적된 확정 정답으로 자체 모델을 LoRA 파인튜닝하고,
  데이터 품질 검사 · 실시간 학습 감독 · 자가평가 후 채택까지 라이브러리가 관리합니다.

## 설치

```bash
pip install structverify              # 코어: 규정 준수 + CSV/문서 사실검증 (의존성 7개)
pip install "structverify[db]"        # + 회사 DB 검증 (SQLAlchemy: sqlite/postgres/Snowflake 등)
pip install "structverify[kosis]"     # + 공공통계(KOSIS) 사실검증
pip install "structverify[training]"  # + 감독 학습 루프 GPU 레시피 (NVIDIA, unsloth QLoRA)
pip install "structverify[training-mac]"  # + Apple Silicon 학습 (MLX)
pip install "structverify[all]"       # 전부
```

코어는 `pydantic · httpx · pyyaml · openai · pdfplumber · python-dotenv · json5`만 받습니다.
무거운 의존성은 전부 필요할 때만 extra로 설치되며, import도 지연 로딩이라 가볍습니다.
Python 3.10 이상을 지원합니다.

API 키는 인자로 직접 넘기거나(`api_key="up_..."`), 환경변수 또는 작업 디렉토리의
`.env` 파일에 두면 자동으로 읽습니다 (`UPSTAGE_API_KEY` · `OPENAI_API_KEY` ·
`GEMINI_API_KEY` · `NCP_API_KEY`).

## 빠른 시작

### 규정 준수 검사: 내 규정집으로

PDF/텍스트 규정집(안전기준 · 사내정책 · 표시기준 등)을 조항 단위로 색인하고, 문장이
그 규정을 지켰는지 판정합니다. 규정집만 있으면 외부 데이터 없이 바로 동작합니다.

```python
import structverify as sv

rules = sv.Ruleset.from_file("safety_standard.pdf", provider="upstage")
len(rules)                   # 색인된 조항 수

v = rules.check("총납 함량은 120mg/kg으로 측정되었다")
v.compliant                  # False
v.violated                   # True
v.article                    # "제3조 (총납) ... 100mg/kg 이하"
v.rule_value, v.claim_value, v.unit   # (100.0, 120.0, 'mg/kg')
v.reason                     # 판정 근거 (자연어)

# 문서 전체: 측정값이 있는 줄마다 자동 판정
for v in rules.check_file("시험성적서.pdf"):
    print("준수" if v.compliant else "위반", v.article, v.rule_value, v.claim_value)
```

조항이 많은 큰 규정집은 `agent=True`로 ReAct 에이전트 루프를 켭니다. 한 번의 검색으로
적용 조항을 놓칠 수 있을 때, 검색 - 판정 - 쿼리 재구성 - 재검색을 판정이 확정될 때까지
스스로 반복합니다.

```python
rules = sv.Ruleset.from_file("big_rulebook.pdf", provider="upstage", agent=True)
v = rules.check("화장품 납 함량은 30㎍/g으로 측정되었다")
v.violated      # True
v.iterations    # 몇 바퀴 만에 확정했는지 (1이면 한 번에, 2 이상이면 재검색함)
```

### 사실 검증: 데이터 소스와 대조

문서의 수치 주장을 설정된 데이터 소스와 대조합니다.

```python
import structverify as sv

# 한 줄 검증
report = sv.verify("과수농가 65세 이상 비율은 64.2%다", provider="upstage")
report.ok                    # 거짓 주장이 없으면 True
if report:                   # Report 자체가 True/False로 동작
    print("거짓 주장 없음")

# 엔진 재사용 (여러 문서를 검증할 때)
engine = sv.Verifier(provider="upstage", data=sv.DataSource.csv("reference.csv"))
report = engine.verify(long_text)
report.mismatches            # 반박된 주장만
for r in report:             # 순회 가능, len(report) == 주장 수
    print(r.verdict, r.confidence, r.value, r.reason)
```

PDF 파일도 그대로 넣을 수 있습니다: `engine.verify_document("경영실적보고서.pdf")`.
모든 동기 메서드는 `a` 접두사의 비동기 쌍을 가집니다 (`check`/`acheck`, `verify`/`averify`).

## 데이터 소스: 무엇이든 정답으로

`DataSource` 팩토리로 정답 기준을 연결합니다. 경로 문자열만 넘겨도 확장자로 추론합니다.

```python
sv.DataSource.csv("부채비율.csv")                          # 참조 통계 CSV
sv.DataSource.csv("t.csv", columns={"value": "amount"})    # 컬럼명 매핑
sv.DataSource.docs("policies/")                            # 회사 문서 폴더 (의미검색)
sv.DataSource.db("postgresql://...", table="kpi")          # 정돈된 지표 표
sv.DataSource.kosis()                                      # 내장 KOSIS 공공통계
```

### 에이전틱 DB 검증: 원시 테이블 그대로

지표 정의가 없는 원시 트랜잭션 테이블은 `agentic=True` 하나로 검증합니다. 에이전트가
스키마와 표본을 조사해 주장마다 읽기 전용 집계 SELECT를 자동 생성하며, 합계 · 평균 ·
비중 같은 집계/비율 주장까지 대조합니다. SQLAlchemy DSN이면 어떤 DB든 연결됩니다
(sqlite · PostgreSQL · MySQL · Snowflake 등).

```python
# 원시 주문 테이블에서 직접 검증
raw = sv.DataSource.db("snowflake://user:pw@account/DB/SCHEMA?warehouse=WH",
                       agentic=True, tables=["ORDERS", "LINEITEM"])
report = sv.verify("1998년 연간 순매출은 3.2조 달러를 기록했다", provider="upstage", data=raw)
report[0].verdict            # 'mismatch'  (실제 집계값과 대조한 결과)

# 지표가 수백 개인 대형 표는 임베딩 의미검색으로 자동 전환
sv.DataSource.db(dsn, table="BIG_KPI", use_embedding="auto", embed_threshold=200)
```

모든 SQL은 읽기 전용으로만 실행되며, 쓰기 구문은 실행 전에 차단됩니다.

## 동작 구조

![StructVerify 동작 구조](https://raw.githubusercontent.com/2026-StructVerify-Lab/structverify/main/docs/media/architecture.png)

- 문서에서 수치 주장을 탐지하면, 소스 프로파일러가 기준 자료를 먼저 조사해 검색 전략을
  수립하고, 주장 하나마다 RuntimeAgent(ReAct 루프)가 독립 실행됩니다.
- 에이전트는 Planner - 도구 실행(의미 검색 · 에이전틱 SQL · 검색어 재구성 · 표 샘플 조사) -
  Reflect를 반복하며 근거를 확보합니다. 근거가 부족하면 재계획하고, 최대 10회 반복하며,
  신뢰도 0.9 이상이면 조기 종료합니다.
- 시도 이력은 워크스페이스(`./agent_workspace`)의 작업 메모리에 기록되어 중복 검색을
  방지하고, 확정된 값은 `verified_facts` 캐시로 다음 주장에서 재사용됩니다.
- 준수/사실 판정은 LLM이 아닌 코드가 허용오차 · 한도 규칙으로 결정론적으로 계산합니다.
- 검증에서 축적된 확정 정답은 감독 학습 루프를 거쳐 개선된 모델로 파이프라인에
  다시 주입됩니다.

파이프라인 내부 구현(에이전트 루프 · 도구 11종 · 워크스페이스 캐시 · 설정 스키마)은
[docs/architecture-internals.md](docs/architecture-internals.md)에 정리되어 있습니다.

## 감독 학습 루프: 쓸수록 정확해지는 검증

검증에서 나온 확정 정답으로 자체 7B 모델을 LoRA 파인튜닝합니다. 단순한 학습 스크립트가
아니라 감독 3종이 학습의 앞 · 중간 · 뒤를 지킵니다.

| 감독 | 시점 | 역할 |
|---|---|---|
| **DataCurator** | 학습 전 | 중복 · 라벨 오류 · 태스크 편중을 격리하고 사유를 기록 |
| **TrainDoctor** | 학습 중 · 후 | 실시간 손실 감독(터미널 한 줄 UI). 수렴 정체나 발산이면 스스로 조기 종료하고, 학습 후 수렴 스텝 진단과 처방 제공 |
| **EvalGate** | 학습 후 | 학습 전/후 엔진을 같은 평가셋으로 채점해 개선된 경우에만 채택 (회귀 방지) |

```python
from structverify.training import LearningLoop

loop = LearningLoop(engine, base_model="unsloth/Qwen2.5-7B-Instruct")
loop.add_seed().add_reports(reports).add_jsonl("corrections.jsonl")   # 데이터 수집 (체이닝)
ds, curation = loop.prepare("train.jsonl")            # DataCurator: 품질검사 후 clean 데이터셋
loop.train(ds, "./adapter", backend="auto",           # NVIDIA는 QLoRA, Apple Silicon은 MLX
           run=True, early_stop=True, patience=200)   # 수렴하면 스스로 멈춤
loop.diagnose("./adapter/trainer_state.json")         # 사후 진단과 처방
gate = loop.evaluate(eval_set, tuned_engine=tuned)    # 개선 확인 후에만 채택
```

학습 중 터미널에는 감독 상태 한 줄만 갱신됩니다 (색상은 tty 자동 감지,
`NO_COLOR` / `SV_COLOR` 지원):

```text
🧠 336/2496 ▓░░░░░░░  13% · loss 0.108 ▆█▄▄▁▁ · lr 1.5e-04 · ETA 2:29 · ⚡1 · 🩺 수렴 유지
✂ step 336: 200스텝 동안 이동평균 개선 없음. 더 학습해도 이득이 없어 조기 종료합니다.
```

GPU 레시피는 extra로 제공됩니다. `[training]`은 NVIDIA(RTX 3060 · Colab T4/L4, unsloth
QLoRA 4bit), `[training-mac]`은 Apple Silicon(MLX)용입니다. 수집 · 품질검사 · 진단 · 평가
등 코어 기능은 GPU 없이 동작합니다. 학습 결과는
`python -m structverify.training.recipe.sample --adapter ./adapter`로 바로 확인합니다.

### 학습된 모델 주입

어댑터를 Ollama나 vLLM으로 서빙하고, `base_url`과 티어별 모델 오버라이드로 파이프라인에
주입합니다. 전체 교체가 아니라 학습된 자리만 점진적으로 전환할 수 있습니다.

```python
cfg = sv.build_config(provider="upstage", api_key="none", data=data)
cfg["llm"]["base_url"] = "http://localhost:11434/v1"   # 자체 서빙 주소
cfg["llm"]["models"] = {"structured": "sv-tuned"}      # 판정·추출 자리만 교체
tuned = sv.Verifier(config=cfg)

gate = EvalGate(base).evaluate(eval_set, tuned_engine=tuned)
if gate.accepted:
    engine = tuned            # 나빠졌으면 자동 거부
```

자세한 절차는 [학습 가이드](http://structverify.yeseulkim.cloud/docs/training.html)를
참조하세요.

## 결과 객체

| 객체 | 핵심 불리언 | 주요 필드 |
|---|---|---|
| `Verdict` (규정 준수) | `.compliant` · `.violated` · `bool(v)` | `.article` `.rule_value` `.claim_value` `.unit` `.reason` `.iterations` |
| `Report` (문서 단위) | `.ok` · `bool(report)` | `.results` `.matches` `.mismatches` `.unverifiable` (순회 가능, `len()`) |
| `Result` (주장 단위) | `.ok` · `.is_match` · `bool(r)` | `.verdict` `.claim` `.reason` `.confidence` `.value` `.source` |

모든 결과 객체는 `.to_dict()`(JSON 직렬화)와 사람이 읽기 좋은 `repr`을 지원합니다.
확인이 불가능한 주장은 억지로 판정하지 않고 `unverifiable`(보류)로 남깁니다. 근거 없이
일치/불일치를 보고하는 경우는 코드 단계에서 보류로 강등됩니다.

## 설정 · 로깅 · 진행 대시보드

```python
import structverify as sv

# 로깅: 콘솔 + 파일, verbose로 상세도 제어
sv.configure_logging("verification.log", level="INFO", verbose=False)

# config를 미리 만들어 재사용. 세부 키는 dict로 직접 조정
cfg = sv.build_config(
    provider="upstage",              # upstage | openai | gemini | hcx
    tolerance=2.0,                   # 수치 허용오차(%)
    data="reference.csv",
)
cfg["llm"]["max_concurrency"] = 2          # 429(rate limit)가 나면 낮추기
cfg["agent"]["loop"]["max_iterations"] = 6 # 주장당 에이전트 반복 상한
engine = sv.Verifier(config=cfg)

# 진행상황: 로컬 웹 대시보드 + 터미널 진행바
#   환경변수로도 제어 가능: SV_PROGRESS=off|terminal|web
with sv.progress_dashboard():              # 웹 대시보드가 브라우저로 열림
    report = engine.verify(text)
```

## 검증 실적

완성본 기준으로 실제 데이터에서 end-to-end 검증을 마쳤습니다.

**에이전틱 DB 검증 (Snowflake TPCH_SF10, 약 6천만 행 원장)**

원시 테이블(고객 150만 · 주문 1,500만 · 라인아이템 5,998만 행)을 정답으로, 오류 5건을
심어 둔 공식 경영실적 보고서 PDF의 수치 주장 20건을 검증했습니다.

| 판정 | 건수 | 내용 |
|---|---|---|
| 일치 (match) | 9 | 총매출 · 지역별 매출 · 고객 수 등 집계 주장 |
| 불일치 (mismatch) | 3 | 심어 둔 오류를 실제 집계값과 함께 적발 |
| 보류 (unverifiable) | 8 | 원장에서 확인 불가능한 주장을 무리하게 판정하지 않고 보류 |
| **오탐 (거짓 경보)** | **0** | 맞는 수치를 틀렸다고 판정한 사례 없음 |

**감독 학습 루프 (Google Colab L4 GPU)**

학습 데이터 1만 건으로 전체 루프(수집 - DataCurator - QLoRA 학습 - TrainDoctor -
어댑터 검증)를 실행했습니다. 실시간 감독의 조기 종료로 2,496스텝 예정 학습이
516스텝에 완료되었고, 산출 어댑터의 품질이 동일함을 확인했습니다.

이 외에 어린이제품 안전기준 · 화장품 안전기준 · 식품 영양표시 기준 등 실제 규정집으로
규정 준수 검사를 검증했습니다. pytest 테스트 226개가 통과합니다.

## 예제

시연 데이터가 동봉되어 있어 바로 실행됩니다. 각 파일 상단에 필요한 extra와 실행법이
적혀 있습니다.

| 예제 | 내용 |
|---|---|
| [01_quickstart_conformance.py](examples/01_quickstart_conformance.py) | 규정 준수 검사 빠른 시작 (Ruleset, 에이전트 모드) |
| [02_factcheck_csv.py](examples/02_factcheck_csv.py) | CSV를 정답으로 사실 검증 |
| [03_database_agentic.py](examples/03_database_agentic.py) | 회사 DB 검증: 정돈된 표와 에이전틱 모드 |
| [04_config_logging_progress.py](examples/04_config_logging_progress.py) | 설정 · 로깅 · 진행 대시보드 |
| [05_training_loop.py](examples/05_training_loop.py) | 감독 학습 루프 전체 흐름 |
| [06_custom_model_injection.py](examples/06_custom_model_injection.py) | 학습된 모델 주입과 EvalGate 채택 |

```bash
python examples/01_quickstart_conformance.py
```

## 프로젝트 구조

| 디렉토리 | 역할 |
|---|---|
| `structverify/core` | 파이프라인 · 데이터 모델 · 설정 로더 |
| `structverify/preprocessing` | PDF/DOCX/URL/텍스트 추출, 문장 분리 |
| `structverify/detection` | 수치 주장 탐지, 스키마 추출 |
| `structverify/agent` | RuntimeAgent ReAct 루프, 도구, 워크스페이스 |
| `structverify/retrieval` | 데이터 소스(CSV · DB · 문서 · KOSIS), 카탈로그 의미검색 |
| `structverify/verification` | 결정론 판정 (허용오차 · 한도 규칙 · 단위 계열 가드) |
| `structverify/training` | 감독 학습 루프 (DataCurator · TrainDoctor · EvalGate · GPU 레시피) |
| `structverify/library` | 고수준 API (`Ruleset` · `Verifier` · `DataSource` · `build_config`) |
| `sv_platform/` | FastAPI 플랫폼 (REST API · 인증 · Job). `structverify`를 감싸는 선택 구성요소 |

내부 구현 상세는 [docs/architecture-internals.md](docs/architecture-internals.md),
플랫폼은 [sv_platform/README.md](sv_platform/README.md)를 참조하세요.

## 테스트

```bash
pip install -e ".[dev]"
pytest                              # 전체 (226개 통과)
pytest structverify/agent/          # 모듈 단위
pytest -k "schema_inductor"         # 키워드 필터
```

## 알려진 한계

- 확인할 수 없는 주장은 보류(`unverifiable`)로 남기는 안전 우선 설계라, 근거가 부족한
  환경에서는 보류 비율이 높아질 수 있습니다.
- KOSIS 공공통계는 응답이 1~2년 지연되는 경우가 있어 최신 시점 주장은 보류될 수 있습니다.
- 행정구역 개명("강원도"와 "강원특별자치도" 등)이나 아주 오래된 통계 시리즈는 카탈로그
  매칭에 실패할 수 있습니다.
- 순위 · 예측 · 주관적 표현은 검증 가능한 수치가 없어 의도적으로 탐지 대상에서 제외합니다.

## 라이선스

**StructVerify는 [MIT 라이선스](LICENSE)입니다.** 자유롭게 사용 · 수정 · 배포할 수 있습니다.

의존성도 모두 permissive 라이선스만 사용합니다.

| 범위 | 패키지 | 라이선스 |
|---|---|---|
| **코어** | pydantic, pyyaml · httpx, python-dotenv, uvicorn · openai, json5, asyncpg, trafilatura, neo4j, mlflow, boto3, snowflake · pdfplumber, kss, redis, fastapi, sqlalchemy, langfuse, python-docx | MIT · BSD-3 · Apache-2.0 |
| **격리(opt-in)** | PyMuPDF (`[pdf-ocr]` extra) | AGPL-3.0 |

유일한 copyleft인 PyMuPDF(AGPL)는 고급 OCR PDF 파이프라인 전용으로 `[pdf-ocr]` extra에
격리했습니다. 기본 PDF 처리는 pdfplumber(MIT)를 사용하므로 `pip install structverify`와
`[all]` 어떤 경로로도 AGPL이 설치되지 않습니다.
