space ocr
가이드아티클요금문서

감사 추적이 가능한 문서 OCR

대부분의 OCR은 그냥 믿을 수밖에 없는 텍스트만 돌려줍니다. space-ocr는 모든 값을 출처와 함께 반환합니다. data.cells[path] 에 box·quad 좌표와 대조 근거인 evidence 가 붙고, 사람이 확인해야 할 경로는 data.review.flagged 에 모입니다.

문서에서 데이터를 뽑아내는 건 데모로 보여주기는 쉽지만 신뢰하기는 어렵습니다. 모델이 청구서를 읽고 total: 2,045 를 돌려주면, 그 어떤 신뢰도 점수로도 시원하게 답이 안 나오는 질문 하나가 남죠. 이게 정말 페이지에 인쇄된 숫자일까, 아니면 모델이 만들어낸 값일까? 한 번 슬쩍 조회하는 정도라면 상관없습니다. 하지만 회계, 보험 청구 처리, 컴플라이언스처럼 나중에 감사를 받게 되는 업무라면 "모델을 믿어라"는 건 통제 수단이 아닙니다.

감사 추적(audit trail) 이 이 문제를 해결합니다. 값만 덜렁 돌려주는 게 아니라, 모든 필드가 검증된 페이지 위치와 함께 돌아옵니다. 그래서 사람이든 다른 시스템이든 그 값이 읽힌 정확한 픽셀로 바로 이동해 직접 확인할 수 있죠. 이게 바로 그냥 답과, 근거를 댈 수 있는 답의 차이입니다.

직접 확인해 보세요: 모든 값이 원본으로 거슬러 갑니다

아래 필드 위에 마우스를 올려 보세요. 영수증에 표시되는 박스가 바로 그 값이 읽힌 위치이고, 각 필드는 그 위치와 함께 자신의 검토 상태를 가지고 있습니다.

Receipts with extracted-field bounding boxes
Verified fields
KINSHO · 合計 2,045
ライフ · 合計 4,286

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 는 답을 따로 저장하고 조회하고 인용할 수 있는 층으로 갈라서 돌려줍니다.

  1. data.values — 무엇을 읽었는가. 요청한 스키마 그대로이고 예약 키가 섞이지 않아 그대로 데이터베이스에 넣을 수 있습니다.
  2. data.cells[path].box 와 .quad — 어디서 읽었는가. box 는 { xmin, ymin, xmax, ymax } 축 정렬 사각형으로 0–1000 정규화 격자(0,0 = 좌상단, 1000,1000 = 우하단) 위에 놓입니다. quad 는 순서가 정해진 네 점이고, 기울기 보정을 하지 않으므로 페이지의 기울기를 그대로 따라갑니다. 경로 문법은 전 구간 공통입니다 — total, items[0].price.
  3. data.cells[path].evidence — 어떤 근거가 있었는가. text_match 가 문자 대조 자체이고, source 는 좌표가 어떤 경로로 풀렸는지 알려줍니다. match_ratio 는 그 값의 글자 중 페이지에서 위치가 확인된 비율이며(0.85 이상이면 확신 매칭), printed_text 는 그 좌표에서 OCR 이 읽은 글자라 values 와 정확 일치로 대조할 때 씁니다.
  4. data.cells[path].verified 와 .review — 사람 없이 수용해도 되는가. verified 는 문자 점수가 아니라 판정입니다. review 에 사유가 하나라도 실리면 false, 대조가 돌았고 아무것도 서지 않았으면 true, 아무것도 서지 않았는데 대조할 상대가 없었으면 null 입니다(행 유니언은 기하 정보만 갖습니다). review.reasons 는 길이가 1이어도 배열이고 랭킹 순이라 0번이 대표 사유입니다.
  5. data.review.flagged — 사람이 볼 작업 목록. 항목은 { path, reasons } 한 쌍이고, 확인할 건수는 flagged.length 그 자체입니다.
  6. data.normalized — 인쇄된 표기와 계산에 쓸 값의 분리. 스칼라 타입(또는 pattern·enum 을 준 string)을 선언한 필드가 있을 때만 붙고, 같은 읽기를 그 타입으로 해석해 values 는 건드리지 않은 채 옆에 놓습니다.

위치와 근거가 값을 따라다니기 때문에 결과는 블랙박스가 아닙니다. 박스를 그리거나, 경로와 좌표를 인용하거나, 표시된 필드를 OCR 재실행 없이 다시 확인할 수 있습니다.

✓ Verified

좌표는 모델 말만 믿고 정하지 않습니다. 언어 모델은 각 값의 텍스트와, 어떤 단어 토큰을 사용했는지에 대한 힌트만 반환할 뿐 박스 자체는 절대 내놓지 않습니다. 그러면 엔진이 그 텍스트를 비전 OCR이 페이지에서 실제로 검출한 심볼과 글자 단위로 대조합니다. 그래서 박스는 그 글자들이 실제로 발견된 진짜 픽셀 위에 떨어지고, 각 값에는 매칭 비율(match ratio) — 글자 중 실제로 위치가 확인된 비율 — 이 매겨집니다. 모델의 토큰 힌트는 노이즈가 섞일 수 있어서(반복되는 행끼리 힌트가 뒤바뀌기도 합니다) 무턱대고 믿지 않고, 열·행 일관성 검사로 검증합니다. 핵심은 AI가 틀릴 수 없다는 게 아니라, 페이지와 어긋나는 값이 조용히 통과하지 않고 검토 대상으로 드러난다는 것입니다. 대조할 상대가 없었던 항목 — 행 유니언은 기하 정보만 갖습니다 — 은 통과했다고 말하는 대신 verified: null 로 보고합니다.

값을 클릭하면 픽셀로 바로 이동

앱에서는 이게 하나의 상호작용이 됩니다. 셀을 클릭하면 원본 이미지에서 그 값이 나온 정확한 박스가 강조되고, 확대된 크롭과 연결선이 함께 표시되죠. 배치 작업을 빠르게 점검하기에 이만한 방법이 없습니다. 문서 전체를 훑을 필요 없이 시선이 곧장 해당 지점으로 향하니까요.

아무 셀이나 클릭 → 원본 이미지에서 해당 영역이 환하게 강조됩니다.

수정 내역도 감사할 수 있습니다

감사 추적은 기계가 내놓은 출력만의 이야기가 아닙니다. 사람이 무엇을 바꿨는지도 포함됩니다. 셀을 수정하면 space-ocr는 여러분의 수정 내용을 원본 OCR 값과 별도로 저장합니다. Original 툴팁이 엔진이 처음 읽은 값을 항상 보여주므로, 검토자는 기계가 읽은 값과 사람이 덮어쓴 값을 나란히 비교할 수 있습니다.

셀을 수정해도 원본 OCR 값은 Original 툴팁 아래에 그대로 보존됩니다.

API에서도, 모든 값에 들어 있습니다

이건 UI에만 있는 기능이 아닙니다. POST /ocr/fields 가 반환하는 data.cells 는 total, items[0].price 같은 경로를 키로 쓰는 flat 맵이고, 항목마다 box, quad, verified, review, evidence 가 들어 있습니다. data.review.flagged[].path 도 같은 문법이라, 검토 목록의 항목에서 그 좌표로 바로 찾아갈 수 있습니다. 저장된 시트를 GET /view 로 조회하면 이 맵이 기본으로 함께 옵니다. boxes=0 을 붙이면 빠지는 것은 행의 cells 맵뿐이고, values·review·image 는 그대로 돌아옵니다.

POST /ocr/fields → 응답 (일부 생략)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
{
  "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 계약 어휘이니 코드로 분기하고, 아직 모르는 코드에는 범용 문구를 띄우도록 만들어 두세요.

실무에서 값을 검증하는 방법

  1. 추출 결과 열기
    시트를 열거나 GET /view 를 호출하세요. 각 값은 경로로 지정되고, data.cells[path] 에 box, quad, review, evidence 가 들어 있습니다.
  2. 값 클릭하기
    셀을 클릭하면 그 값이 읽힌 원본 이미지의 정확한 영역이 강조됩니다.
  3. 근거와 검토 목록 확인하기
    match_ratio 가 1.0이면 모든 글자의 위치가 확인됐다는 뜻이고, 0.85 이상이면 확신 매칭으로 봅니다. 엔진이 결론짓지 못한 값은 low_ratio, text_mismatch 같은 사유와 함께 data.review.flagged 에 섭니다.
  4. 필요하면 수정하기
    셀을 수정해 값을 덮어쓰세요. 원본 OCR 값은 감사 추적을 위해 Original 툴팁 아래에 그대로 보존됩니다.
OCR 감사 추적(audit trail)이란 무엇인가요?
감사 추적이란 추출된 모든 값을 원본 문서 위의 정확한 위치까지 거슬러 추적할 수 있다는 뜻입니다. space-ocr에서는 각 값이 경로로 data.cells 에서 조회되고, 거기에 box, 페이지 기울기를 따라가는 네 점짜리 quad, 그리고 대조 근거인 evidence 가 들어 있습니다. 그래서 결과를 그냥 믿는 게 아니라 인용하고 다시 확인할 수 있습니다.
AI가 바운딩 박스를 그냥 지어낼 수도 있지 않나요?
모델은 좌표를 반환하지 않습니다. 값의 텍스트와, 어떤 단어를 사용했는지에 대한 힌트만 줄 뿐입니다. 그러면 엔진이 그 텍스트를 비전 OCR이 페이지에서 실제로 검출한 심볼과 글자 단위로 대조하고, 그중 얼마나 발견됐는지를 match_ratio로 보고합니다. 모델의 토큰 힌트 역시 무턱대고 믿지 않고 열·행 일관성으로 교차 검증합니다. 그래서 박스는 모델이 '그럴 것이라 생각하는' 위치가 아니라, 값의 글자들이 실제로 발견된 위치를 반영합니다. 이 대조가 잡아내는 것은 어긋남입니다. 페이지와 맞지 않는 값은 조용히 통과하지 않고 data.review.flagged 에 올라옵니다. 다만 이것이 값의 정확성을 증명하지는 않습니다. 두 엔진이 같은 오독에 합의할 수도 있으므로, required·enum·pattern 처럼 직접 선언한 규칙을 함께 돌리는 편이 좋습니다.
좌표는 픽셀 단위로 반환되나요?
API는 0–1000 정규화 격자(0,0 좌상단부터 1000,1000 우하단까지)로 반환하며, 이미지 해상도와 무관합니다. 픽셀로 변환하려면 pixel_x = box.xmin / 1000 × data.image.width 를 사용하세요. data.image 는 EXIF 방향을 세우고 필요하면 축소한 뒤 실제로 판독한 페이지의 크기이므로, 보낸 파일이 아니라 이 값을 기준으로 삼으세요.
검증에 추가 비용이 들거나 OCR이 다시 실행되나요?
아니요. 좌표는 표준 응답의 일부이며, 저장된 시트를 GET /view 로 조회해도 OCR이 다시 실행되거나 비용이 청구되지 않습니다. boxes=0 을 붙이면 행의 cells 맵만 빠지고 values·review·image 는 그대로 돌아옵니다.

여러분의 문서로 직접 써보세요

무료 플랜 — 월 100크레딧, 신용카드 불필요. 모든 값이 페이지 위치 정보와 함께 돌아옵니다.

관련