feat(cli): kebab inspect ocr-stats + ocr-failures (Enhancement 3 + wire schema additive minor)

Two new wire schemas land as additive minor: ocr_stats.v1 (corpus-wide
aggregate — total_events, success_rate, p50/p90/p99/max_ms, by_engine,
top-10 by_doc by failure count) and ocr_failures.v1 (per-doc or
corpus-wide recent failures, with --doc-id + --limit). Both ship via
new CLI subcommands `kebab inspect ocr-stats` / `inspect ocr-failures`.

App gains four facade methods: inspect_ocr_stats /
inspect_ocr_failures plus their *_with_config companions — required by
CLAUDE.md "the facade rule" so `--config <path>` is honored. The CLI
dispatch arms thread cfg explicitly into the _with_config form.

Runtime introspection emit (WIRE_SCHEMAS in schema.rs) gains two
entries; the meta JSON Schema (schema.schema.json) is untouched
because its wire.schemas is pattern-based, not enum-based.

ingest_log::percentiles extended to (p50, p90, p99, max). p99 surfaces
only via inspect ocr-stats; IngestSummary (round 1) stays 3-percentile.

SKILL.md synced with the two new schemas (AC-13).

Closure r2 G2 (facade *_with_config pair) + G3 (runtime emit, not
meta schema file) + closure r1 F4 (p99) resolved.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-28 06:13:08 +00:00
parent 4e451c9f7c
commit d9ec7b8dc3
9 changed files with 476 additions and 7 deletions

View File

@@ -358,6 +358,17 @@ enum InspectWhat {
Doc { id: String },
/// Inspect a single chunk by ID.
Chunk { id: String },
/// Corpus-wide OCR statistics (total events, latency percentiles, engine breakdown).
OcrStats,
/// Recent OCR failures, optionally filtered by document ID.
OcrFailures {
/// Filter failures to a single document UUID.
#[arg(long)]
doc_id: Option<String>,
/// Maximum number of failure rows to return.
#[arg(long, default_value_t = 10)]
limit: usize,
},
}
#[derive(Subcommand, Debug)]
@@ -691,6 +702,21 @@ fn run(cli: &Cli) -> anyhow::Result<()> {
);
Ok(())
}
InspectWhat::OcrStats => {
let cfg = kebab_config::Config::load(cli.config.as_deref())?;
let app = kebab_app::App::open_with_config(cfg.clone())?;
let stats = app.inspect_ocr_stats_with_config(&cfg)?;
println!("{}", serde_json::to_string(&stats)?);
Ok(())
}
InspectWhat::OcrFailures { doc_id, limit } => {
let cfg = kebab_config::Config::load(cli.config.as_deref())?;
let app = kebab_app::App::open_with_config(cfg.clone())?;
let failures =
app.inspect_ocr_failures_with_config(&cfg, doc_id.as_deref(), *limit)?;
println!("{}", serde_json::to_string(&failures)?);
Ok(())
}
},
Cmd::Fetch { what } => {