feat(scripts): Phase 2 markdown backfill — script + README
- scripts/phase2_backfill.py: 5 subcommands
- inventory: pending PDFs dry-run CSV with skip forecast
- select-canary: stratified 40 sample (seed 20260503)
- enqueue: one-shot from sample CSV (--no-dry-run gate)
- nightly-enqueue: cron-friendly with disable flag / marker /ready /
active-queue threshold (oldest_age stuck guard) / DB pool guards
- post-report: final state CSV + 1D baseline comparison MD
- evals/markdown/README.md: Phase 2 section appended
- plan: ~/.claude/plans/iridescent-gathering-clover.md
- depends on Phase 1B handwritten skip 7d0fca2 (marker_worker side guard)
This commit is contained in:
@@ -117,3 +117,114 @@ docker compose exec fastapi python /app/scripts/phase1d_pilot.py select \
|
||||
```
|
||||
|
||||
**enqueue 의 `--yes` 또는 `--no-dry-run` 류 실행은 별도 사용자 승인 + 야간 단발 sweep 윈도우 (23:00~03:00 KST) 안에서만**. 30건 backfill = marker-service BATCH_SIZE=1 × 평균 5분/건 ≈ 2.5h.
|
||||
|
||||
---
|
||||
|
||||
# Phase 2 — Full Backfill (legacy pending PDFs)
|
||||
|
||||
> Plan: `~/.claude/plans/iridescent-gathering-clover.md`
|
||||
> Script: `scripts/phase2_backfill.py` (subcommands: inventory / select-canary / enqueue / nightly-enqueue / post-report)
|
||||
|
||||
## 목적
|
||||
|
||||
1D pilot 결과 = engineering go signal. legacy pending PDF (1D 후 잔여 ~237건) 을 marker_worker 로 변환해 `md_status='success'` 누적. **신규 업로드 우선권 보존, 야간 저부하 sweep, DB state 기반 idempotent checkpoint**.
|
||||
|
||||
진행 로드맵: **2-A dry-run inventory → 2-B canary 40건 → 2-C nightly sweep ~4-5 nights → 2-D post-report**.
|
||||
|
||||
## 파일
|
||||
|
||||
| 파일 | 역할 | 갱신 시점 |
|
||||
|---|---|---|
|
||||
| `phase2_inventory.csv` | pending PDFs dry-run inventory + skip forecast | 2-A 종료 (commit, 1회) |
|
||||
| `phase2_canary_sample.csv` | stratified 40건 canary sample (시드 `20260503`) | 2-B(a) (commit) |
|
||||
| `phase2_canary_result.md` | canary 결과 요약 + 1D 비교 + GO/HALT 결정 근거 | 2-B 종료 (commit) |
|
||||
| `phase2_nightly_log.tsv` | 야간 sweep 한 줄/일 (date / enqueued / active_queue_at_start / active_queue_oldest_age_min / pending_pool_remaining / abort_reason / marker_ready) | append 매 sweep, 주 1회 commit |
|
||||
| `phase2_post_report.csv` | Phase 2 sweep 처리된 doc 별 final state + quality | 2-D (commit) |
|
||||
| `phase2_post_report.md` | 처리 분포 + 1D baseline 비교 + skip/failed/outlier 목록 | 2-D (commit) |
|
||||
|
||||
## Subcommand 사용법
|
||||
|
||||
### inventory (read-only, dry-run)
|
||||
```bash
|
||||
docker exec hyungi_document_server-fastapi-1 python /app/scripts/phase2_backfill.py inventory \
|
||||
--output /app/evals/markdown/phase2_inventory.csv
|
||||
```
|
||||
- pending PDFs 전체에 대해 doc_id / file_size / text_density / doc_type / forecast_skip_reason 적재.
|
||||
- forecast_skip_reason ∈ {unsupported_extension / doctype_skip / handwritten_hint / over_max_pages_estimated / none}. 'none' = 변환 시도 후보.
|
||||
- handwritten_hint = title/path 에 `필기|손글씨|handwritten|handwriting` 매칭 (marker_worker 의 7d0fca2 룰 미러).
|
||||
- over_max_pages_estimated = file_size > 25MB proxy. 실 page_count 는 marker_worker 가 PyMuPDF 로 결정.
|
||||
|
||||
### select-canary (재현성 시드)
|
||||
```bash
|
||||
docker exec hyungi_document_server-fastapi-1 python /app/scripts/phase2_backfill.py select-canary \
|
||||
--inventory /app/evals/markdown/phase2_inventory.csv \
|
||||
--output /app/evals/markdown/phase2_canary_sample.csv \
|
||||
--seed 20260503
|
||||
```
|
||||
- 40건 buckets: large 6 / scan_likely 2 / study_note 10 / Academic_Paper 8 / Reference 6 / {Standard,Manual,Specification} 4 / {Note,Report,Memo,NULL} 4
|
||||
- inventory 의 `forecast_skip_reason='none'` 만 선정 후보.
|
||||
- 시드 고정 → 재실행 시 동일 sample.
|
||||
|
||||
### enqueue (one-shot, 사용자 승인 게이트)
|
||||
```bash
|
||||
# dry-run (default)
|
||||
docker exec hyungi_document_server-fastapi-1 python /app/scripts/phase2_backfill.py enqueue \
|
||||
--csv /app/evals/markdown/phase2_canary_sample.csv
|
||||
|
||||
# actual (사용자 승인 후)
|
||||
docker exec hyungi_document_server-fastapi-1 python /app/scripts/phase2_backfill.py enqueue \
|
||||
--csv /app/evals/markdown/phase2_canary_sample.csv --no-dry-run
|
||||
```
|
||||
- marker-service `/ready` 사전 검증.
|
||||
- `enqueue_stage` idempotent — 중복 호출 안전.
|
||||
|
||||
### nightly-enqueue (cron / manual)
|
||||
```bash
|
||||
docker exec hyungi_document_server-fastapi-1 python /app/scripts/phase2_backfill.py nightly-enqueue \
|
||||
--limit 50 --max-active-queue 5 \
|
||||
--log-tsv /app/evals/markdown/phase2_nightly_log.tsv
|
||||
```
|
||||
- 가드 순서: disable flag (`/tmp/phase2_disable`) → marker /ready → active_queue ≤ threshold → DB pool 비어있지 않음 → enqueue.
|
||||
- 매 sweep log_tsv 한 줄. abort_reason ∈ {disable_flag / marker_unhealthy / active_queue_threshold / pool_empty / empty}.
|
||||
- pool_empty = Phase 2 자연 완료 신호 (cron 제거 hard gate trigger).
|
||||
|
||||
### post-report
|
||||
```bash
|
||||
docker exec hyungi_document_server-fastapi-1 python /app/scripts/phase2_backfill.py post-report \
|
||||
--output-csv /app/evals/markdown/phase2_post_report.csv \
|
||||
--output-md /app/evals/markdown/phase2_post_report.md \
|
||||
--phase2-start 2026-05-03T00:00:00Z
|
||||
```
|
||||
- `--phase2-start` ISO timestamp 이후 `md_generated_at` 만 집계 (Phase 2 코드 push 시점 권장).
|
||||
- 1D baseline (success 92% / elapsed_p50 34s / text_length_ratio_p50 1.15) 와 비교.
|
||||
- outlier 후보: elapsed_ms > 300s, text_length_ratio < 0.5 또는 > 10, 신규 warning 종류.
|
||||
|
||||
## 의사결정 게이트
|
||||
|
||||
### 2-B canary GO/HALT
|
||||
- success ≥ 36/40 (90%) AND failed ≤ 2 AND skipped ≤ 6 → **2-C 진입 GO**
|
||||
- 위 미충족 → HALT, 사용자 보고 후 재검토
|
||||
|
||||
### 2-C nightly abort
|
||||
- 1 night 안에 failed > 5 → script 가 disable flag 자동 생성
|
||||
- marker-service `/ready` 실패 → 그 sweep 건너뜀, 다음 sweep 재시도
|
||||
- active_queue_oldest_age_min > 60 (stuck 임계) → log 에 [warn], 사용자 morning check 으로 판단
|
||||
|
||||
### 2-D 종료 hard gate (Phase 2 closed 선언 직전)
|
||||
- (cron 모드) `crontab -l | grep phase2_backfill` 결과 비어 있어야 함
|
||||
- `~/.phase2_disable` 파일 정리 됨
|
||||
- pending PDF (`md_status='pending'`, `file_format='pdf'`) ≤ 5
|
||||
- processing_queue markdown active = 0
|
||||
|
||||
## 1D 와의 차이
|
||||
|
||||
| 항목 | 1D | Phase 2 |
|
||||
|---|---|---|
|
||||
| 목적 | failure mode 진단 | 풀 변환 |
|
||||
| 대상 | 30건 stratified | 237건 잔여 |
|
||||
| sample_source | existing_success + controlled_backfill | controlled_backfill only |
|
||||
| 처리 모드 | one-shot cron (1회) | nightly cron (~4-5 nights) |
|
||||
| 평가 | 사용자 5축 rubric | marker 자가 metrics + 1D baseline 비교 |
|
||||
| anchor 보존 | doc 4809 forced_include | (재처리 안 함) |
|
||||
| handwritten | over-sample (3건) | marker_worker 자동 skip 신뢰 |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user