監査証跡つきのドキュメントOCR
ほとんどのOCRは、信じるしかないテキストを返すだけ。space-ocr は抽出したすべての値に出所を添えて返します——data.cells[path] の box と quad の座標、照合の根拠である evidence、そして人が見るべきパスをまとめた data.review.flagged です。
ドキュメントからデータを抽出するのは、デモで見せるのは簡単でも、信用するのは難しいものです。モデルが請求書を読み取り、total: 2,045 と返してきても、どんな信頼度スコアでも本当には答えてくれない疑問が残ります。それはページに実際に印字されている数字なのか、それともモデルが生成したものなのか? その場限りの照会ならそれで構いません。しかし、経理、保険金支払、コンプライアンス、あるいは監査の対象になりうる業務では、「モデルを信じる」というのは管理体制とは言えません。
監査証跡がこれを解決します。むき出しの値ではなく、すべてのフィールドがページ上の検証済みの位置情報とともに返ってくる——だから人(あるいは別のシステム)が、値を読み取った正確なピクセルへ一足飛びに移動して、それを確認できます。これが、単なる答えと、根拠を示して説明できる答えとの違いです。
実際に見てみる:どの値も元の位置にさかのぼれる
下のフィールドにカーソルを合わせてみてください。レシート上のボックスが、その値を読み取った場所です——そして各フィールドは、その位置情報とあわせて自身の検証状態を持っています。

Each value with a box carries a verified on-page location — in data.cells[path], that is box + 4-point quad + evidence.match_ratio — on a 0–1000 normalized grid (0,0 top-left → 1000,1000 bottom-right), the same shape the live API returns. Hover a field to trace it back to the pixels it came from.
「検証済みの値」が実際に持っているもの
根拠を示せる結果とは、数値ひとつにスコアをひとつ添えたものではありません。POST /ocr/fields は、答えを別々に保存・照会・引用できる層に分けて返します。
data.values— 何を読んだか。リクエストしたスキーマそのままで、予約キーが混ざらないので、そのままデータベースへ入れられます。data.cells[path].boxと.quad— どこから読んだか。boxは{ xmin, ymin, xmax, ymax }の軸に沿った矩形で、0–1000 正規化のグリッド上(0,0 = 左上、1000,1000 = 右下)にあります。quadは順序づけられた4点で、傾き補正を一切行わないためページの傾きにそのまま追従します。パスの文法は全体で共通です(total、items[0].price)。data.cells[path].evidence— どんな根拠があったか。text_matchが文字照合そのもの、sourceが座標の解決経路、match_ratioはその値の文字のうちページ上で位置を特定できた割合(0.85 以上で確信を持ったマッチ)です。printed_textはその座標で OCR が読み取った文字なので、valuesと完全一致で突合したいときに使えます。data.cells[path].verifiedと.review— 人手を介さずに受け入れてよいか。verifiedは文字スコアではなく判定です。reviewに理由がひとつでも載ればfalse、照合が走って何も立たなければtrue、何も立たないが照合する相手が無ければnullになります(行ユニオンは幾何情報だけを持ちます)。review.reasonsは常に配列で、ランキング順、先頭が代表理由です。data.review.flagged— 人が見る作業リスト。各要素は{ path, reasons }の組で、確認すべき件数はflagged.lengthそのものです。data.normalized— 印字された表記と、計算に使う値の分離。スカラー型(あるいはpattern・enumを付けた string)を宣言したフィールドがあるときだけ付き、同じ読みをその型に解釈した値をvaluesに触れずに並べます。
位置情報と根拠が値と一緒に付いてくるので、結果はブラックボックスではありません。ボックスを描画したり、パスと座標を引用したり、フラグの立ったフィールドをOCRを再実行せずに再確認したりできます。
座標はモデルの言い分を鵜呑みにしたものではありません。 言語モデルが返すのは各値のテキスト——そしてどの単語トークンを使ったかのヒント——だけで、ボックスそのものは決して返しません。エンジンはそのうえで、そのテキストを、ビジョンOCRがページ上で実際に検出したシンボルと文字単位で照合します。だからボックスは、それらの文字が見つかった本物のピクセルに着地し、各値にはマッチ率——その文字のうち実際に位置を特定できた割合——が付きます。モデルのトークンヒントはノイズを含みうる(繰り返し行のあいだで取り違えることもある)ため、列・行の一貫性チェックによって、鵜呑みにせず検証されます。要点は、AIが間違えないということではなく、ページと食い違う値が黙って通らず、要確認として表に出てくるということです。照合する相手が無かった項目——行ユニオンは幾何情報だけを持ちます——は、合格を騙らずに verified: null とだけ報告します。
値をクリックすれば、そのピクセルにたどり着く
アプリ上では、これがインタラクションになります。任意のセルをクリックすると、元画像上でその値が読み取られた正確なボックスがハイライトされ、拡大したクロップと接続線が表示されます。バッチをざっと検査するのにこれ以上速い方法はありません——ドキュメント全体を見渡すのではなく、視線がそのまま該当箇所に向かいます。
修正もまた監査可能
監査証跡は、機械の出力だけの話ではありません——人が何を変更したかも含みます。セルを編集すると、space-ocr はあなたの修正を元のOCR値とは別に保存します。Original ツールチップが、エンジンが最初に読み取った内容を常に示すので、レビュアーは機械の値と人による上書きの両方を並べて確認できます。
APIにも、すべての値に備わっている
これはUIだけの機能ではありません。POST /ocr/fields が返す data.cells は、total・items[0].price のようなパスをキーにしたフラットなマップで、各エントリが box、quad、verified、review、evidence を持ちます。data.review.flagged[].path も同じ文法なので、要確認の項目からその座標へ直接引けます。GET /view で保存済みのシートを照会すると、このマップはデフォルトで一緒に付いてきます——boxes=0 を付けて落ちるのは行の cells マップだけで、values・review・image はそのまま返ります。
{
"status": "success",
"data": {
"values": {
"total": "2,045",
"items": [
{ "qty": "2", "price": "780" }
]
},
"cells": {
"total": {
"box": { "xmin": 595, "ymin": 974, "xmax": 781, "ymax": 1000 },
"quad": [
{ "x": 594, "y": 975 }, { "x": 781, "y": 972 },
{ "x": 781, "y": 998 }, { "x": 595, "y": 1000 }
],
"verified": true,
"review": null,
"evidence": {
"text_match": true,
"source": "vision_symbol_match",
"match_ratio": 1.0,
"printed_text": "2,045"
}
},
"items[0].price": {
"box": { "xmin": 693, "ymin": 640, "xmax": 781, "ymax": 668 },
"quad": [
{ "x": 693, "y": 641 }, { "x": 781, "y": 640 },
{ "x": 781, "y": 667 }, { "x": 693, "y": 668 }
],
"verified": false,
"review": { "reasons": ["text_mismatch"] },
"evidence": {
"text_match": false,
"source": "vision_symbol_match",
"match_ratio": 0.67,
"printed_text": "180"
}
}
},
"review": {
"unit": "field",
"flagged": [
{ "path": "items[0].price", "reasons": ["text_mismatch"] }
],
"by_reason": { "text_mismatch": 1 }
},
"normalized": { "total": 2045 },
"image": { "width": 1654, "height": 2339 }
}
}evidence.source は、各座標がどのように解決されたかを教えてくれます——vision_symbol_match は通常の文字照合パスで、その match_ratio を伴います。token_id は単語トークンのヒントが使われたことを意味します。ログに残したり、フィルタリングしたり、レビュアーに提示したりできるメタデータです。弱いマッチはこのキーの中に隠れるのではなく、review.reasons に low_ratio・weak_source・low_ocr_confidence といったコードとして表れ、同じパスが data.review.flagged にも並びます。理由コードはAPI契約の語彙です——コードで分岐し、まだ知らないコードには汎用の文言を出す作りにしてください。
実際に値を検証する手順
- 抽出結果を開くシートを開くか GET /view を呼び出します——各値はパスで指定でき、data.cells[path] が box、quad、review、evidence を持っています。
- 値をクリックするセルをクリックすると、その値が読み取られた元画像上の正確な領域がハイライトされます。
- 根拠と要確認リストを読むmatch_ratio が 1.0 ならすべての文字の位置が特定できたことを意味し、0.85 以上で確信を持ったマッチとみなされます。エンジンが決めきれなかったものは、low_ratio や text_mismatch といった理由とともに data.review.flagged に並びます。
- 必要なら修正するセルを編集して上書きします——元のOCR値は監査証跡のために Original ツールチップの下に保持されます。