← 목록
공개 범위 안내 — 이 사이트는 도구 설계·기획 문서만 공개합니다. 분석 대상 원문(수행안내서 전문, 제안요청서, 제안서 판독본)과 특정 제안서에 대한 분석 결과는 포함하지 않습니다. 문서에 남은 실명·연락처는 마스킹 처리했습니다.

PIA-Auditor — 기술스택 선정서 및 개발기획서

대상 05_분석툴_기능명세서.md v1.0 구현
문서 버전 v1.0 · 2026-08-04
전제 본 문서의 모든 수치는 이번 세션에서 실측한 값(문서 크기, 하드웨어 사양, 변환 결과)에 기반한다. 추정치는 추정치라고 표기했다.


1. 요약 — 선정 결과 한 장

계층 선정 핵심 근거
언어/런타임 Python 3.12 (uv 관리) PDF·한글·임베딩 생태계가 Python 우선. 기존 pdf2md.py 자산 승계
PDF 텍스트/렌더 PyMuPDF (기본) / pypdfium2 + pdfplumber (라이선스 대안) 검증 완료. AGPL 이슈로 인터페이스 추상화 필수 (§4.2)
HWP pyhwp → 실패 시 LibreOffice headless → 실패 시 PDF 동반본 3단 폴백
이미지 판독 Claude vision (기본) / PaddleOCR (로컬 전용 모드) maindev에 GPU 없음 → 로컬 VLM 불가, OCR은 CPU 가능
LLM Opus 5 / Sonnet 5 / 판정 없음 3단 배치 (§4.5) 단계별 난이도에 맞춘 비용 배분
근거 추출 Claude Citations API cited_text + page_location을 API가 직접 반환 → M9 환각 차단의 1차 방어선
임베딩 BGE-M3 (CPU) Anthropic은 임베딩 API 미제공. GPU 없어 CPU 배치 추론
저장소 SQLite (+ sqlite-vec) 단일 파일·서버 불필요·재현성. 벡터 규모가 작음(수천 청크)
오케스트레이션 자체 DAG + 콘텐츠 해시 캐시 재현성 NFR이 어차피 결정론적 캐시를 요구
리포팅 Jinja2 → MD / xlsxwriter → XLSX / 기존 build_site.py → HTML 이미 동작하는 자산 존재
CLI/설정/검증 Typer / pydantic-settings / pytest + 골든셋

가장 중요한 아키텍처 결정 하나: 수행안내서 456쪽이 Claude의 1M 컨텍스트 윈도우에 통째로 들어간다. 따라서 기준문서에 대한 RAG(청킹→검색→재구성)를 만들지 않는다. 대신 안내서에서 추출한 지식베이스(121지표 + 12체크리스트)를 프롬프트 캐시 프리픽스에 상주시킨다. 근거와 비용 계산은 §5.


2. 실측 제약조건

2.1 실행 환경 (maindev, 실측)

CPU     24 코어        MEM  62 GB (가용 33 GB)
GPU     없음  ← 결정적 제약
DISK    652 GB 여유
Python  3.10.12 (시스템)
있음    docker, git, node
없음    uv, poetry, soffice(LibreOffice), tesseract, nvidia 드라이버

GPU 부재가 세 가지를 강제한다.
1. 로컬 비전 모델(Qwen2.5-VL 등)로 슬라이드를 판독하는 선택지가 사라진다 → 클라우드 vision 또는 CPU OCR.
2. 임베딩은 CPU 배치 추론 (BGE-M3, 수천 청크 기준 수 분 — 실용적).
3. 리랭커(cross-encoder) 도입은 비용 대비 효과가 나쁘다 → 도입하지 않는다.

2.2 데이터 실측치

문서 쪽/장 문자 수 성격
수행안내서 456쪽 408,506 기준문서, 표 다수
제안요청서 81쪽 73,475 요구사항 정의서 = 전부 표
제안요약서 50슬라이드 90,556 (판독 후) 텍스트 레이어 0 (원본 1,618자)

2.3 보안 제약

입력 문서에 미공개 사업정보와 실명 인력정보가 들어 있다(수탁사 목록, 투입인력 경력·자격). 이는 스택 선정에 직접 영향을 준다.


3. 선정 원칙

  1. 검증된 것을 승계한다. tools/pdf2md.py는 456쪽·81쪽 문서로 동작을 확인했다. 재작성하지 않는다.
  2. 결정론을 LLM보다 앞에 둔다. 룰로 판정 가능한 것에 LLM을 쓰지 않는다 — 비용·재현성·오탐 모두에서 진다.
  3. 서버를 늘리지 않는다. Postgres·Redis·벡터DB·워크플로우 엔진 없이 SQLite 단일 파일로 간다. 다중 사용자 웹이 필요해지면 그때 바꾼다.
  4. 라이선스를 나중에 확인하지 않는다. 사내 도구로 시작해도 배포·SaaS 전환 시 막히는 선택은 처음부터 추상화한다.
  5. API 계약을 기억이 아니라 문서에서 가져온다. 모델 파라미터는 세대마다 바뀐다(§4.6이 그 사례).

4. 스택 결정 (ADR)

4.1 언어/런타임 — Python 3.12, uv

항목 내용
결정 Python 3.12, 패키지·가상환경은 uv
대안 TypeScript/Node (웹 UI 유리, PDF·한글 생태계 열세) / Python 3.10 유지
근거 PyMuPDF·pdfplumber·sentence-transformers·anthropic SDK 모두 Python 1급. maindev의 3.10.12는 시스템 Python이므로 건드리지 않고 uv가 3.12를 별도 설치·고정
uv 선택 이유 uv.lock이 재현성 NFR과 직결. poetry 대비 설치 속도·락파일 정확성 우위
리스크 uv 미설치 상태 → 부트스트랩 스크립트에 설치 단계 포함

4.2 PDF 처리 — PyMuPDF, 단 추상화 필수 ⚠️

항목 내용
결정 기본 백엔드 PyMuPDF. PdfBackend 인터페이스 뒤에 격리하고 pypdfium2 + pdfplumber 대체 백엔드를 Phase 1에 함께 구현
라이선스 PyMuPDF는 AGPL-3.0 (또는 상용). 사내 도구는 무방하나 배포·SaaS 전환 시 소스 공개 의무가 발생한다. 나중에 발견하면 M2 전체를 재작성해야 한다
대체 경로 검증 필요한 것은 글자 단위 bbox(M2-F01 띄어쓰기 복원의 입력). pdfplumber(MIT)의 page.charsx0/x1/top/bottom/size/fontname을 제공 → 알고리즘 이식 가능. 렌더링은 pypdfium2(BSD/Apache)
트레이드오프 대체 경로는 느리고 표 추출 품질이 낮다 → 기본은 PyMuPDF, 라이선스 전환 시 스위치
인터페이스 chars(page) / lines(page) / tables(page) / render(page, dpi) / page_count() — 5개 메서드면 충분

4.3 HWP — 3단 폴백

순위 수단 비고
1 pyhwp (HWP v5) GPL — PyMuPDF와 동일 격리 원칙 적용
2 LibreOffice headless (soffice --convert-to pdf) maindev 미설치 → Docker 이미지로 격리 실행
3 동반 PDF 사용 이번 케이스가 여기 해당([붙임1] 제안요청서-마사회.hwp.pdf가 함께 존재)

HWPX는 zip+XML이므로 표준 라이브러리로 직접 파싱한다.

4.4 이미지 판독 — Claude vision 기본 / PaddleOCR 로컬

모드 수단 근거
기본 Claude vision (image 블록, base64) 이번 세션에서 50슬라이드 전량 판독 성공. 표·조직도·타임라인의 구조 보존이 OCR보다 우월 — GFM 표로 바로 나온다
--offline PaddleOCR (한국어 모델, CPU) 보안 제약(§2.3) 대응. 구조 보존은 포기하고 텍스트만
미채택 로컬 VLM GPU 없음
미채택 Tesseract 미설치 + 한국어 표 구조 열세

해상도 정책: Opus 5 / Sonnet 5는 고해상도 비전(장변 2576px, 최대 4784 토큰/이미지)을 지원한다. 이번 세션은 110 dpi(장변 약 1180px)로 충분히 판독됐다 — 슬라이드는 원래 저밀도 문서다. 기본 110 dpi, 판독 실패 시 200 dpi 재시도. 기본을 고해상도로 두면 이미지 토큰이 3배가 되므로 올리지 않는다.

4.5 LLM — 단계별 3단 배치

Anthropic Claude API. 모델 ID는 날짜 접미사 없는 정확한 문자열을 쓴다.

단계 모델 effort 이유
M3 슬라이드 판독 claude-sonnet-5 (기본) / claude-opus-5 (실패 재시도) medium 판독은 구조 인식 작업. Sonnet 5도 고해상도 비전을 지원하며 입력가가 Opus의 60%
M6 커버리지 판정 claude-sonnet-5 + Batch API medium 요구사항 42건 × 문서 = 고volume·저난이도. 배치로 50% 추가 할인, 지연은 무관(비대화형)
M7 방법론 룰 claude-opus-5 high 16개 룰 중 R-DOMAIN-01·R-METH-01 같은 판단은 이번 분석의 핵심 가치. 여기서 아끼면 도구가 무의미
M8 문서위생 LLM 없음 정규식·구조 검사
M9 인용 검증 LLM 없음 문자열 대조

프롬프트 캐싱 (핵심 비용 레버):
- 캐시 프리픽스 = 지식베이스(121지표 + 12체크리스트) + 룰 정의. 문서별로 바뀌지 않는다.
- 캐시 읽기 ≈ 입력가의 0.1배, 쓰기 1.25배(5분 TTL) / 2배(1시간 TTL).
- 5분 TTL 기준 2회 요청부터 이득. 한 번의 분석이 수십 회 호출이므로 항상 이득.
- 주의: 최소 캐시 가능 프리픽스가 모델별로 다르다 — Opus 5는 512토큰, Sonnet 5는 1024토큰. 지식베이스는 수만 토큰이므로 문제없다.
- 캐시 무효화 방지: 프리픽스에 타임스탬프·UUID·요청 ID를 넣지 않는다. JSON 직렬화는 sort_keys=True. 도구 목록은 이름순 정렬 고정.

구조화 출력: Finding 스키마(§05 M10-F02)를 Pydantic으로 정의하고 client.messages.parse()로 검증받는다. 파싱 실패·재시도 루프를 직접 짜지 않는다.

Citations API — M9의 1차 방어선:

{"type": "document", "source": {...}, "citations": {"enabled": True}}

응답이 cited_textpage_location(1-indexed)을 직접 반환한다. 인용을 모델에게 "적어달라"고 요청하는 대신 API가 구조적으로 보장하므로, M9-F04의 문자열 대조는 2차 검증이 된다. 단 구조화 출력(output_config.format)과 동시 사용 시 400 — 두 경로를 분리해야 한다:
- 근거 추출 호출 → Citations 사용, 자유 텍스트
- 판정 호출 → 구조화 출력 사용, 근거는 앞 단계 결과를 입력으로 받음

4.6 ⚠️ 재현성 — 온도 0은 쓸 수 없다 (05 문서 정정)

05_분석툴_기능명세서.md의 비기능 요구사항에 "LLM 온도 0" 이라고 적었는데, 이는 현행 모델에서 틀렸다. Claude Opus 5 / Sonnet 5는 temperature·top_p·top_k파라미터 자체로 거부한다(400). 05 문서는 정정했다.

재현성은 다른 세 축으로 확보한다.

수단
출력 형식 고정 구조화 출력(JSON 스키마) + strict: true 도구. 자유 서술을 파싱하지 않는다
근거 고정 인용 검증(M9-F04) — 원문에 없는 인용은 Finding 폐기. 표현이 흔들려도 근거는 흔들리지 않는다
회귀 고정 골든셋 13건 재현 테스트를 CI에 건다. "같은 문장이 나오는가"가 아니라 "같은 Finding이 나오는가" 를 본다

추가로 model·프롬프트 해시·지식베이스 버전·룰 버전을 매 실행 기록한다. 결정론이 아니라 감사 가능성이 목표다.

4.7 임베딩 — BGE-M3 (CPU)

항목 내용
결정 BAAI/bge-m3 via sentence-transformers, CPU 배치
Anthropic 임베딩 API는 없다 Claude API는 임베딩 엔드포인트를 제공하지 않는다. 임베딩은 별도 조달
왜 로컬 보안 제약(§2.3) + GPU 없어도 CPU로 감당 가능한 규모(수천 청크)
용도 한정 M6-F01 후보 매칭 1순위 필터로만. 최종 판정은 LLM. 임베딩 점수로 직접 판정하지 않는다
한국어 BGE-M3는 다국어 학습 모델로 한국어 성능 검증됨. 대안 KURE-v1도 후보
리스크 최초 모델 다운로드 ~2.3GB. CPU 추론 지연 → 배치 크기 조정

4.8 저장소 — SQLite

항목 내용
결정 SQLite 단일 파일 (run.db), 벡터는 sqlite-vec 확장 또는 numpy 인메모리
대안 Postgres + pgvector (서버 필요) / LanceDB (파일이지만 의존성 추가)
근거 지표 121개 + 요구사항 42개 + 청크 수천 개. 벡터DB를 도입할 규모가 아니다. 단일 파일은 실행 결과를 통째로 첨부·비교·재현할 수 있다
전환 조건 다중 사용자 웹 UI(Phase 7) 도입 시 Postgres 검토

4.9 오케스트레이션 — 자체 DAG + 콘텐츠 해시 캐시

Prefect·Dagster·Airflow를 쓰지 않는다. 이유:

@stage(inputs=["doc.pdf"], version="m2-v3")
def convert(doc): ...          # 입력 해시 + 코드 버전 → 캐시 키

LLM 호출 결과도 같은 캐시에 들어간다 → 재실행 시 API 비용 0. 개발 중 가장 큰 비용 절감 수단이다.

4.10 나머지

영역 선정 비고
리포팅 Jinja2 → Markdown, xlsxwriter → XLSX, 기존 build_site.py → HTML HTML 렌더러는 이미 동작
CLI Typer pia-audit run/convert/kb-build/report
설정 pydantic-settings + config.yaml 룰·모델·임계값 외부화
스키마 Pydantic v2 구조화 출력과 직결
테스트 pytest + 스냅샷 + 골든셋 §07 참조
로깅 structlog(JSON) + 토큰/비용 회계 비용 회계는 NFR이 아니라 Phase 1 필수 — 없으면 최적화 판단 불가
토큰 계산 client.messages.count_tokens tiktoken 금지 — OpenAI 토크나이저이며 Claude에서 15~20% 과소계산

5. 비용 모델

5.1 단가 (2026-06 기준, 백만 토큰당)

모델 입력 출력 컨텍스트
claude-opus-5 $5.00 $25.00 1M
claude-sonnet-5 $3.00 (~2026-08-31 도입가 $2.00) $15.00 (도입가 $10.00) 1M
claude-haiku-4-5 $1.00 $5.00 200K

캐시 읽기 ≈ 입력가 × 0.1 / 캐시 쓰기 = 입력가 × 1.25(5분) 또는 × 2(1시간) / Batch API = 전 항목 50%

5.2 왜 RAG를 만들지 않는가

안내서 408,506자를 토큰으로 환산하면 — 한국어+마크다운 혼합 기준 대략 1.5~2.5자/토큰으로 잡아 약 165K~270K 토큰(반드시 count_tokens로 실측할 것).

방식 프리픽스 토큰 Opus 5 캐시 읽기 비용/호출 구현 복잡도
안내서 원문 전체를 캐시 ~200K ~$0.10 낮음
지식베이스만 캐시 ~20K ~$0.01 중간(M4 필요)
RAG (청킹·검색·재구성) ~5K ~$0.003 높음

중간을 택한다. RAG는 비용을 10배 더 줄이지만, 청킹 경계에서 지표가 잘리고 검색 실패가 조용한 누락으로 이어진다 — 이 도구의 핵심 실패 모드(§05 원칙 3)와 정면 충돌한다. 지식베이스 방식은 121개 지표를 항상 전부 본다는 보장을 유지하면서 비용을 10배 줄인다. 이것이 M4가 "정리"가 아니라 압축 단계인 이유다.

5.3 1회 분석 비용 추정 (이번 마사회 건 규모)

단계 호출 모델 추정
M3 슬라이드 판독 50 Sonnet 5 ~$1.5
M6 커버리지 42 Sonnet 5 + Batch ~$0.8
M7 방법론 룰 16 Opus 5 ~$1.6
M8/M9 없음 $0
합계 약 $4~6 / 건

이 숫자의 지위: 실측이 아니라 단가 × 추정 토큰이다. Phase 1의 토큰 회계가 붙는 즉시 실측으로 교체한다. 다만 자릿수는 신뢰할 만하다 — 컨설턴트 1인시 대비 압도적으로 싸다는 판단에는 충분하다.

5.4 개발 중 비용 통제

  1. 콘텐츠 해시 캐시(§4.9) — 재실행 비용 0
  2. 골든셋 고정 — 매번 새 문서로 테스트하지 않는다
  3. Batch API — 비대화형 단계 전부
  4. count_tokens 사전 호출 — 대형 요청 전 예상 비용 로깅

6. 개발기획

6.1 목표와 비목표

목표 공공 개인정보 컨설팅 산출물의 준거성 격차를 근거와 함께 자동 검출한다
성공 정의 이번 세션에서 사람이 찾은 13개 Finding을 도구가 재현하고, 준거성 양호한 문서에서 HIGH 오탐 ≤1건
비목표 컨설팅 산출물 자동 작성. 법률 자문. 정성 품질("충실히 작성되었는가") 자동 판정

6.2 사용자와 가치

사용자 사용 시점 대체하는 것
발주기관 평가담당 제안서 평가 제안서 N건 × 요구사항 42개 수작업 대조
발주기관 검수담당 중간·완료보고 RFP 산출정보 대비 납품물 대조
컨설팅사 PM 제출 전 미기입·결번·누락 셀프체크
컨설팅사 품질조직 납품 전 안내서 체크리스트 12항목 자가진단

6.3 성공지표

지표 목표 측정
골든셋 재현율 13/13 CI 회귀
HIGH 오탐 ≤1건/문서 양호 문서 5건
인용 무결성 불일치 0 전수 문자열 대조
변환 정확도 띄어쓰기 ≥95%, 표 셀 손실 0 샘플 10건 육안
1회 분석 비용 ≤$10 토큰 회계
분석 소요 ≤30분 (이미지 문서 포함) 실행 로그

6.4 리스크 등록부

# 리스크 영향 대응
R1 PyMuPDF AGPL — 배포·SaaS 전환 시 소스 공개 의무 높음 Phase 1에 백엔드 추상화 + MIT/Apache 대체 경로 동시 구현 (§4.2)
R2 API 계약 변경 — 모델 세대마다 파라미터가 바뀜(§4.6이 실제 사례) 높음 모델 파라미터를 설정 파일 한 곳에 격리. 모델 ID·베타 헤더 버전 고정. 업그레이드는 골든셋 회귀로 검증
R3 기준문서 개정 — 안내서는 3년 주기 + 수시 개정 M4-F05 버전 diff. 개정 시 지표 추가/삭제 자동 산출
R4 오탐 신뢰 붕괴 — HIGH 오탐 한 건이 도구 전체 신뢰를 깎는다 높음 INSUFFICIENT_EVIDENCE 3값 판정. 요약본 입력 시 MISSING 자동 강등
R5 보안 — 미공개 사업정보·실명 인력정보의 외부 전송 높음 --offline 모드 1급 지원. 전송 항목 로깅. 렌더 이미지 처리 후 파기
R6 GPU 부재 — 로컬 대안 경로가 좁다 임베딩 CPU 배치로 감당. 로컬 VLM은 범위 밖으로 확정
R7 의미 동등성 미탐지 — 다른 용어로 같은 절차를 기술한 경우 MISSING은 사람 확인 필수로 명시. 임베딩 후보를 3개까지 LLM에 제시

6.5 일정·투입 (개발자 1인 기준)

Phase 기간 누적
P1 변환 기반 2주 2주
P2 결정론 검사 2주 4주
P3 지식베이스 3주 7주
P4 룰 엔진 3주 10주
P5 이미지 판독 2주 12주
P6 리포팅 2주 14주
P7 웹 UI (선택) 3주 17주

P2 종료(4주) 시점에 이미 실사용 가능하다. LLM 없이 미기입·결번·산출물 누락을 잡아내며, 이번 세션 §4의 6건이 전부 P2 범위다. 상세 슬라이스 계획은 07_개발계획서_vertical_slice.md.

6.6 도입 전제


부록 A. 의존성 초안

[project]
requires-python = ">=3.12"
dependencies = [
  "pymupdf>=1.28",          # AGPL — 4.2 참조
  "pdfplumber>=0.11",       # MIT, 대체 백엔드
  "pypdfium2>=4",           # BSD/Apache, 대체 렌더러
  "anthropic>=0.116",
  "pydantic>=2.9",
  "pydantic-settings>=2",
  "typer>=0.12",
  "jinja2>=3.1",
  "xlsxwriter>=3.2",
  "sentence-transformers>=3",
  "structlog>=24",
]
[project.optional-dependencies]
offline = ["paddleocr>=2.8", "paddlepaddle>=2.6"]
hwp     = ["pyhwp>=0.1"]      # GPL — 4.3 참조

부록 B. 라이선스 요약

패키지 라이선스 조치
PyMuPDF AGPL-3.0 / 상용 ⚠️ 추상화 격리 (§4.2)
pyhwp GPL ⚠️ 서브프로세스 격리 검토
pdfplumber / pypdfium2 / anthropic / pydantic / typer / jinja2 / xlsxwriter MIT · BSD · Apache-2.0 제약 없음
BGE-M3 MIT 제약 없음
PaddleOCR Apache-2.0 제약 없음

AGPL·GPL 2건이 이 스택의 유일한 법적 리스크다. 사내 도구로만 쓴다면 문제없고, 외부 제공 계획이 생기면 §4.2의 대체 경로로 전환한다. 전환 비용을 낮게 유지하는 것이 Phase 1의 설계 목표 중 하나다.