feat(config): add [pdf.ocr] section — qwen2.5vl:3b default, opt-in + env overrides + doc(app): PdfOcrOpts field doc (Step 4 I-1)
Step 5 (Group F) of v0.20.0 sub-item 1 (scanned PDF OCR) plan +
Step 4 reviewer Important I-1 fix (PdfOcrOpts field doc) 동봉.
F1 — `kebab-config::PdfCfg` + `PdfOcrCfg` + 4 default fn:
- PdfCfg { ocr: PdfOcrCfg }.
- PdfOcrCfg with 11 field (enabled/always_on/engine/model/endpoint/
languages/max_pixels/request_timeout_secs/valid_ratio_threshold/
min_char_count/lang_hint).
- defaults: opt-in (enabled=false), qwen2.5vl:3b, 0.5 threshold, 20 char.
- mirror of image OCR cfg pattern (spec §4.5).
Config struct extension:
- `pdf: PdfCfg` field with `#[serde(default = "PdfCfg::defaults")]`.
11 env var override (parallel to KEBAB_IMAGE_OCR_*):
KEBAB_PDF_OCR_{ENABLED,ALWAYS_ON,ENGINE,MODEL,ENDPOINT,LANGUAGES,
MAX_PIXELS,REQUEST_TIMEOUT_SECS,VALID_RATIO_THRESHOLD,MIN_CHAR_COUNT,
LANG_HINT}.
F2 — `crates/kebab-config/tests/pdf_ocr.rs` (신규):
- toml roundtrip (11 field).
- defaults (opt-in + qwen2.5vl:3b).
- env override (4 key sample + default preservation).
F3 (Step 4 I-1) — `pdf_ocr_apply.rs` 4 public item 의 doc comment:
- PdfOcrOpts struct + 6 field.
- PdfOcrSummary struct + 2 field.
- apply_ocr_to_pdf_pages fn (Errors block 포함).
- PdfOcrProgress enum + 2 variant + 5 field.
body 변경 0, doc-only.
spec: docs/superpowers/specs/2026-05-27-pdf-scanned-ocr-spec.md (§4.5)
plan: docs/superpowers/plans/2026-05-27-pdf-scanned-ocr-plan.md (Step 5 F1+F2)
prior: 9f003ef (Step 4) — code reviewer Important I-1 resolution
contract: §9 (additive minor wire bump — Step 7)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
80
crates/kebab-config/tests/pdf_ocr.rs
Normal file
80
crates/kebab-config/tests/pdf_ocr.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
// crates/kebab-config/tests/pdf_ocr.rs
|
||||
//
|
||||
// Integration tests for [pdf.ocr] config section (v0.20.0 sub-item 1).
|
||||
|
||||
use std::collections::HashMap;
|
||||
use kebab_config::{Config, PdfCfg};
|
||||
|
||||
// Test 1: toml roundtrip — spec §4.5 line 1034-1047 example block.
|
||||
// Config requires many required fields; test the [pdf] section via PdfCfg wrapper.
|
||||
#[derive(serde::Deserialize)]
|
||||
struct PdfWrapper { pdf: PdfCfg }
|
||||
|
||||
#[test]
|
||||
fn pdf_ocr_toml_roundtrip() {
|
||||
let toml = r#"
|
||||
[pdf.ocr]
|
||||
enabled = true
|
||||
always_on = false
|
||||
engine = "ollama-vision"
|
||||
model = "qwen2.5vl:7b"
|
||||
endpoint = "http://192.168.0.47:11434"
|
||||
languages = ["eng", "kor"]
|
||||
max_pixels = 3072
|
||||
request_timeout_secs = 900
|
||||
valid_ratio_threshold = 0.6
|
||||
min_char_count = 30
|
||||
lang_hint = "kor"
|
||||
"#;
|
||||
let w: PdfWrapper = toml::from_str(toml).expect("parse toml");
|
||||
let ocr = &w.pdf.ocr;
|
||||
assert!(ocr.enabled);
|
||||
assert!(!ocr.always_on);
|
||||
assert_eq!(ocr.engine, "ollama-vision");
|
||||
assert_eq!(ocr.model, "qwen2.5vl:7b");
|
||||
assert_eq!(ocr.endpoint.as_deref(), Some("http://192.168.0.47:11434"));
|
||||
assert_eq!(ocr.languages, vec!["eng".to_string(), "kor".to_string()]);
|
||||
assert_eq!(ocr.max_pixels, 3072);
|
||||
assert_eq!(ocr.request_timeout_secs, 900);
|
||||
assert!((ocr.valid_ratio_threshold - 0.6).abs() < 1e-6);
|
||||
assert_eq!(ocr.min_char_count, 30);
|
||||
assert_eq!(ocr.lang_hint.as_deref(), Some("kor"));
|
||||
}
|
||||
|
||||
// Test 2: defaults — opt-in, qwen2.5vl:3b model, 0.5 threshold, 20 min_char.
|
||||
#[test]
|
||||
fn pdf_ocr_defaults_off_with_qwen_3b() {
|
||||
let cfg = Config::defaults();
|
||||
assert_eq!(cfg.pdf.ocr.enabled, false);
|
||||
assert_eq!(cfg.pdf.ocr.always_on, false);
|
||||
assert_eq!(cfg.pdf.ocr.engine, "ollama-vision");
|
||||
assert_eq!(cfg.pdf.ocr.model, "qwen2.5vl:3b");
|
||||
assert!(cfg.pdf.ocr.endpoint.is_none());
|
||||
assert_eq!(cfg.pdf.ocr.languages, vec!["eng".to_string(), "kor".to_string()]);
|
||||
assert_eq!(cfg.pdf.ocr.max_pixels, 2048);
|
||||
assert_eq!(cfg.pdf.ocr.request_timeout_secs, 600);
|
||||
assert!((cfg.pdf.ocr.valid_ratio_threshold - 0.5).abs() < 1e-6);
|
||||
assert_eq!(cfg.pdf.ocr.min_char_count, 20);
|
||||
assert_eq!(cfg.pdf.ocr.lang_hint.as_deref(), Some("kor"));
|
||||
}
|
||||
|
||||
// Test 3: env var override — 4 keys 의 typical override case.
|
||||
#[test]
|
||||
fn pdf_ocr_env_overrides() {
|
||||
let mut env: HashMap<String, String> = HashMap::new();
|
||||
env.insert("KEBAB_PDF_OCR_ENABLED".to_string(), "true".to_string());
|
||||
env.insert("KEBAB_PDF_OCR_MODEL".to_string(), "qwen2.5vl:7b".to_string());
|
||||
env.insert("KEBAB_PDF_OCR_ALWAYS_ON".to_string(), "true".to_string());
|
||||
env.insert("KEBAB_PDF_OCR_VALID_RATIO_THRESHOLD".to_string(), "0.75".to_string());
|
||||
|
||||
let cfg = Config::defaults().apply_env(&env);
|
||||
|
||||
assert_eq!(cfg.pdf.ocr.enabled, true);
|
||||
assert_eq!(cfg.pdf.ocr.model, "qwen2.5vl:7b");
|
||||
assert_eq!(cfg.pdf.ocr.always_on, true);
|
||||
assert!((cfg.pdf.ocr.valid_ratio_threshold - 0.75).abs() < 1e-6);
|
||||
|
||||
// 다른 env var 가 default 보존
|
||||
assert_eq!(cfg.pdf.ocr.engine, "ollama-vision");
|
||||
assert_eq!(cfg.pdf.ocr.min_char_count, 20);
|
||||
}
|
||||
Reference in New Issue
Block a user