digraph QUE230 {
  labelloc="t";
  label="QUE-230 — Camelot-correspondence table extraction (CamelotCorrespondenceExtractor)";
  rankdir=TB;
  splines=true;
  concentrate=false;
  fontname="Helvetica"; fontsize=16;
  node [fontname="Helvetica", fontsize=11, style="filled,rounded", shape=box, fillcolor="#d6f5d6", color="#5a5a5a"];
  edge [fontname="Helvetica", fontsize=9, color="#5a5a5a"];

  // kinds:  green=deterministic/text-layer   blue=vision LLM   gray=Camelot   amber=output   red=no-extract
  src      [label="PDF source", shape=cylinder, fillcolor="#eeeeee"];
  render   [label="render_pages\n→ page images", fillcolor="#eeeeee"];

  subgraph cluster_parallel {
    label="Step 1 — extract + detect (concurrent)"; style="rounded"; color="#999999"; fontname="Helvetica"; fontsize=11;
    camelot [label="Camelot lattice + stream\nrun_camelot_flavors_parallel\n(CPU)", fillcolor="#eeeeee"];
    detect  [label="TableDetector.detect\n(vision LLM)\nsets count + order + rough bbox", fillcolor="#cfe8ff"];
  }

  subgraph cluster_corr {
    label="Step 2 — correspondence (per page, deterministic)"; style="rounded"; color="#999999"; fontsize=11;
    repair  [label="Box repair\ntabular_bands + repair_box\n(snap box to text layer)", fillcolor="#cfe8ff00", color="#2e7d32"];
    match   [label="2D overlap match\ncoverage_fraction argmax  (≥ 0.20)"];
    combined[label="Combined chunk\n(≥2 tables → kept whole, flagged)"];
    orphans [label="Orphan queue\n(completeness extension)"];
  }

  subgraph cluster_assemble {
    label="Step 3 — assemble + audit + fill"; style="rounded"; color="#999999"; fontsize=11;
    assemble[label="assemble_cells → structured grid"];
    audit   [label="Completeness audit\n(text layer, deterministic)"];
    fill    [label="round_off_grid\n(fill edge truncation from text layer)"];
    md      [label="grid_to_markdown\n(rendered once at output)"];
  }

  subgraph cluster_recovery {
    label="Report, don't drop — recovery ladder (for tables no chunk matched)"; style="rounded"; color="#cc5500"; fontsize=11;
    rec1    [label="Targeted stream @ detector box\ncamelot_targeted"];
    d2      [label="recovered?", shape=diamond, fillcolor="#fff2cc"];
    grid    [label="GridLocator escalation (Set-of-Mark)\nlocate → nearest_grid_region\n(grid IDs, immune to bbox drift)", fillcolor="#cfe8ff"];
    rec2    [label="Targeted stream @ grid region"];
    d3      [label="recovered?", shape=diamond, fillcolor="#fff2cc"];
    dne     [label="detected_not_extracted\n(reported, never silently dropped)", fillcolor="#ffd6d6", color="#c62828"];
  }

  out [label="ExtractedTable[]\nextracted / incomplete / detected_not_extracted", shape=oval, fillcolor="#fff2cc"];

  // main flow
  src -> render;
  render -> camelot;
  render -> detect;

  camelot -> match [label="chunks"];
  detect  -> repair [label="boxes"];
  repair  -> match;

  match -> combined [label="1 chunk owns ≥2"];
  match -> assemble [label="best chunk per table"];
  match -> orphans;
  orphans -> assemble [style=dashed, label="extend"];
  combined -> out [label="extracted"];

  assemble -> audit -> fill -> md -> out;

  // recovery ladder (escalation = dashed orange)
  match -> rec1 [style=dashed, color="#cc5500", label="no chunk matched"];
  rec1 -> d2;
  d2 -> md [label="yes → finalize"];
  d2 -> grid [style=dashed, color="#cc5500", label="no → escalate"];
  grid -> rec2 -> d3;
  d3 -> md [label="yes → finalize"];
  d3 -> dne [style=dashed, color="#cc5500", label="no"];
  dne -> out;

  // legend
  subgraph cluster_legend {
    label="legend"; fontsize=10; color="#cccccc"; style="rounded";
    lg_det [label="deterministic / text layer", fillcolor="#d6f5d6", fontsize=9];
    lg_vis [label="vision LLM", fillcolor="#cfe8ff", fontsize=9];
    lg_cam [label="Camelot", fillcolor="#eeeeee", fontsize=9];
    lg_err [label="no-extract (reported)", fillcolor="#ffd6d6", fontsize=9];
    lg_det -> lg_vis -> lg_cam -> lg_err [style=invis];
  }
}
