feat(analysis): position-aware OCR seam dedup for sliced pages

Boundary text duplicated across slices (often with slightly different,
cropped transcriptions) survived the text-only merge. The OCR pass now
asks for a per-piece vertical position and the merge uses it:

- OcrResult gains optional `y` (fraction 0..1 of the slice); the Pass-A
  OCR schema/prompt request it (combined path leaves it None). Not
  persisted.
- merge_ocr takes each slice's working-image y-band and maps `y` to a
  page-global position. A seam pair is a duplicate when position-close
  (same kind) OR text-similar, so a mis-OCR'd boundary line is caught even
  when the text differs. The kept copy is the one more central in its slice
  (less cropped); falls back to keep-longer when positions are missing.
- Self-calibrates the model's y values (pixels vs. fraction) and ignores
  degenerate columns, so a bad localizer can't over-merge.

Tests: position pairs differing texts and keeps the less-cropped copy;
text-only fallback (dedup/keep-longer/non-adjacent/order) still holds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-13 23:17:55 +02:00
parent 85d65f5eda
commit 25aba3ac58
9 changed files with 153 additions and 44 deletions

View File

@@ -211,13 +211,19 @@ pub struct VisionAnalysis {
pub safety_flag: SafetyFlag,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]
pub struct OcrResult {
#[serde(default)]
pub text: String,
/// Free string from the model; mapped via [`OcrKind::from_model_str`].
#[serde(default)]
pub kind: String,
/// Optional vertical center of the text as a fraction (0.0 top … 1.0
/// bottom) of the slice image, requested in the OCR pass to dedup
/// duplicates across slice seams by position. `None` for the combined
/// single-call path. Internal-only — not persisted.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub y: Option<f64>,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq)]