계약서가 들어올 때마다 담당자가 PDF를 열어 당사자·금액·기간을 일일이 시스템에 입력했습니다. 양이 몰리면 입력이 밀리고, 옮겨 적는 과정에서 오타가 났습니다.
이 일은 '읽어서 옮기는' 단순 반복입니다. 그래서 읽는 단계는 OCR과 추출에, 검증 단계는 규칙에 맡기고, 사람은 예외만 보게 했습니다.
처리 단계
- 업로드된 계약서를 OCR로 텍스트화합니다.
- 당사자·금액·기간 등 핵심 항목을 자동 추출합니다.
- 필수 항목 누락·형식 오류를 검증해 플래그합니다.
- 정상 건은 시스템에 등록하고, 예외 건만 담당자에게 넘깁니다.
OCR 엔진을 어떻게 고르나 — 이 케이스에서 가장 많이 받는 질문
이전 버전의 이 글에는 스택에 그냥 OCR이라고만 적혀 있었습니다. 그런데 독자가 실제로 알고 싶은 단 하나가 바로 '어떤 OCR이냐' 입니다. 정답은 하나가 아니고, 아래 네 축으로 갈립니다.
- 한글 정확도 — 비라틴 문자 지원 여부는 엔진마다 크게 다릅니다. 영문 벤치마크 숫자는 한글 계약서에 그대로 적용되지 않습니다. 반드시 실제 계약서 표본으로 직접 재보세요.
- 표 인식 — 계약서에는 금액·기간이 표 안에 들어 있는 경우가 많습니다. 그런데 표 구조 인식과 비라틴 문자 인식을 동시에 잘하는 엔진은 드뭅니다. 한글 계약서는 정확히 그 조합입니다. n8n 커뮤니티에서도 표가 많은 문서에서는 범용 OCR보다 문서 구조 전용 서비스가 낫다는 관찰이 반복적으로 올라옵니다.
- 비용 구조 — 페이지당 과금인지 문서당인지, 무료 구간이 있는지. 월 100건과 월 10,000건은 전혀 다른 선택이 됩니다. 도입 전에 예상 물량 × 단가를 먼저 계산하세요.
- 데이터 반출 가능 여부 — 국내 기업 담당자가 가장 먼저 묻는 질문입니다. 계약서를 외부 API로 보내도 되는지가 조직 정책상 막혀 있으면 나머지 세 축은 볼 필요가 없습니다. 그 경우 로컬에서 도는 OCR + 로컬 LLM 조합만 후보가 됩니다. 저희 온프렘 케이스가 그 구성을 다룹니다.
스캔본(이미지 PDF)은 되나
이게 두 번째로 많이 받는 질문입니다. 텍스트 레이어가 있는 PDF와, 종이를 스캔해 이미지만 들어 있는 PDF는 완전히 다릅니다. 후자는 일반 PDF 텍스트 추출 노드로는 아무것도 나오지 않습니다. 빈 문자열이 나오는데 에러는 아니어서, 그대로 두면 '추출 성공, 내용 없음'으로 조용히 통과합니다.
- 먼저 계약서가 어느 쪽인지 확인하세요. PDF를 뷰어에서 열어 본문 텍스트를 드래그해 선택할 수 있으면 텍스트 레이어가 있는 것입니다.
- 이미지 PDF라면 반드시 OCR 단계가 필요합니다. 텍스트 추출만으로는 되지 않습니다.
- 아래 검증 규칙이 이 상황의 안전망입니다. 추출이 빈 값이면 필수 항목 누락으로 잡혀
needs_review가 되므로, 빈 계약서가 등록되는 일은 일어나지 않습니다.
값을 지어내지 않는다 — 검증 규칙
추출은 절대 100%가 되지 않습니다. 그래서 이 워크플로우의 핵심 원칙은 하나입니다. 필수 항목이 하나라도 비었거나 형식이 틀리면 자동 등록하지 않고, 이유와 함께 사람에게 넘깁니다. 계약서에 잘못된 금액이 들어가는 것은 금액이 비어 있는 것보다 훨씬 나쁩니다.
필수 항목: party_a · party_b · amount · start_date · end_date
금액 파싱 ₩ , 원 쉼표 공백을 제거한 뒤 숫자인지 확인
"11,000,000원" → 11000000
"별도 협의" → 실패 → needs_review
날짜 파싱 2026-01-31 / 2026.01.31 / 2026/01/31 / 20260131 모두 허용
2026-02-31 → 존재하지 않는 날짜 → 실패 → needs_review
교차 검증 종료일 < 시작일 → needs_review2026-02-31 같은 날짜를 잡는 게 생각보다 중요합니다. 대부분의 언어에서 이런 날짜는 에러 없이 3월 3일로 조용히 넘어갑니다. 계약 종료일이 이틀 밀린 채로 시스템에 등록되고, 아무도 모릅니다. 그래서 이 워크플로우는 파싱한 날짜를 다시 문자열로 되돌려 원본과 같은지 대조합니다.추출 스키마
CREATE TABLE IF NOT EXISTS contracts (
id bigserial PRIMARY KEY,
source_file text,
party_a text,
party_b text,
amount numeric,
start_date date,
end_date date,
auto_renew boolean NOT NULL DEFAULT false,
status text NOT NULL DEFAULT 'needs_review', -- registered|needs_review
review_reasons jsonb NOT NULL DEFAULT '[]'::jsonb,
created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS contracts_status_idx ON contracts (status, created_at DESC);review_reasons에 왜 걸렸는지가 그대로 남습니다. 담당자가 해당 건을 열었을 때 무엇을 확인해야 하는지 바로 알 수 있고, 시간이 지나면 이 컬럼을 집계해 어떤 항목이 가장 자주 실패하는지 볼 수 있습니다. 그게 다음 개선 지점입니다.
필요한 것
- n8n — 업로드를 외부에서 받아야 하므로 인터넷에서 접근 가능한 주소가 필요합니다.
- Postgres 데이터베이스 하나 — 계약 테이블 하나.
- OCR / 문서추출 서비스 — 위 네 축으로 고른 것. 무료 구간이 있는 서비스로 표본 테스트부터 하는 것을 권합니다.
- Slack Bot User OAuth Token —
chat:write,channels:read. 검토 필요 건 알림용입니다. - 실제 계약서 표본 20~30장 — 정확도를 재려면 반드시 필요합니다. 이 표본 없이 엔진을 고르면 도입 후에 다시 고르게 됩니다.
₩ 기호가 붙은 금액, 구분자 없는 날짜(20260101), 금액 누락, 금액이 "별도 협의" 같은 문자열인 경우, 존재하지 않는 날짜(`2026-02-31`), 종료일이 시작일보다 빠른 경우, 당사자 누락. "11,000,000원"이 숫자 11000000으로 파싱되는 것도 확인했습니다. 노드 타입·버전은 기존 배포 워크플로우와 동일한 조합입니다. 확인하지 않은 것: 특정 OCR 엔진의 한글·표 인식 정확도 — 저희가 측정하지 않았으므로 이 문서에는 어떤 엔진의 정확도 수치도 적지 않았습니다. 본문의 12분 → 1분, −90%는 초기 도입 사례의 수치이며 이 워크플로우로 재측정한 값이 아닙니다. 마지막 검증: 2026-08-15.설정 순서 (30분 + 엔진 선정 시간)
- 표본부터 모으세요 — 실제 계약서 20~30장. 스캔본과 텍스트 PDF가 섞여 있으면 더 좋습니다.
- 데이터 반출 가능 여부 확인 — 조직 정책상 계약서를 외부 API로 보내도 되는지 먼저 확인합니다. 안 되면 후보가 로컬 엔진으로 좁혀집니다.
- 엔진 2~3개로 표본 테스트 — 무료 구간을 이용해 같은 표본을 넣고 항목별로 몇 개나 맞히는지 셉니다. 이 결과를 표로 남겨두세요.
- 테이블 생성 — 위 SQL을 실행합니다.
- Postgres·Slack 자격증명 등록 — 각각 Test로 확인하고, Slack 채널에
/invite @봇이름. - 워크플로우 import — n8n → Workflows →
...→ Import from File. - OCR 노드 채우기 — 고른 엔진의 엔드포인트·인증·요청 본문을 넣습니다. 응답이
party_a,party_b,amount,start_date,end_date를 반환하도록 매핑하세요. - Activate 후 업로드 경로 연결 —
Contract Upload Webhook의 Production URL을 업로드 폼에 연결합니다. - 일부러 깨진 계약서로 테스트 — 금액이 지워진 계약서를 한 장 올려보세요.
needs_review로 저장되고 Slack에 이유가 떠야 합니다. 정상 건만 테스트하면 이 워크플로우의 절반을 테스트하지 않은 것입니다.
예외 상황이 있으면 어떻게 되나
- 스캔 품질이 나쁜 계약서 — 추출값이 비거나 깨져 나옵니다. 필수 항목 검증에 걸려
needs_review가 되므로 잘못된 값이 등록되지 않습니다. - 손글씨 서명란·도장 — 이 워크플로우는 서명 유무를 판단하지 않습니다. 서명 확인이 필요한 업무라면 사람 검토 단계를 반드시 남겨두세요.
- 한 PDF에 여러 계약서 — 첫 계약서 기준으로만 추출됩니다. 분리 업로드를 전제로 하며, 자동 분할이 필요하면 별도 단계가 필요합니다.
- HWP 첨부 — PDF가 아니면 이 워크플로우로 들어오지 않습니다. 업로드 단계에서 PDF 변환을 거치게 하거나, 해당 형식을 읽는 노드를 앞에 추가해야 합니다.
- 금액이 '별도 협의'인 계약 — 실무에서 실제로 있습니다. 형식 오류로 잡혀
needs_review가 됩니다. 이런 계약이 많다면 `amount`를 필수에서 빼고 별도 상태로 관리하는 편이 낫습니다. 필수 항목 목록은Validate Extracted Fields노드 맨 위에서 바꿀 수 있습니다. - OCR 서비스가 다운된 경우 — 해당 실행이 에러로 끝납니다. 업로드는 이미 200을 응답했으므로 사용자에게는 성공으로 보입니다. 재처리 경로가 필요하면 앞선 정산·결제 케이스처럼 큐 구조를 도입하세요. 기본 구조에는 큐가 없습니다.
계약서 한 장 한 장 입력하던 게 거짓말 같아요. 이제 올리기만 합니다.— 총무팀 담당자