한국GPT협회 표준교재

808. PDF 파싱과 문서 자동화

글자를 복사할 수 없는 이미지 PDF 제품 브로슈어를, 검색·재활용 가능한 구조화 데이터로 바꾼다

이미지 PDF에서 제품 데이터 뽑아내기

Learning Objectives
  • PDF의 두 종류를 구분하고, 어느 쪽인지에 따라 접근이 완전히 달라짐을 안다.
  • 문서의 해부도(어떤 요소가 어떤 규칙으로 배치되는가)를 말로 정의해 AI에게 전달한다.
  • 목표·규칙·기록 규약·합격 기준을 담은 통합 프롬프트를 한 번에 지시한다.
  • AI가 스스로 채점하고 고치며 반복하도록 정지 조건을 설계한다.
  • 시도마다 코드와 결과를 같은 이름으로 남겨, 회차별로 비교한다.
이해

PDF는 한 종류가 아니다

학습 안내읽기. 이 장은 실제 제품 브로슈어를 파싱한 프로젝트를 따라갑니다. 사내에 쌓인 카탈로그·사양서·검사성적서를 다루는 데 그대로 쓰이는 방식입니다.

"PDF에서 텍스트 뽑아줘"라는 요청이 어떤 날은 5분 만에 끝나고 어떤 날은 며칠이 걸리는 이유는, PDF라는 이름 아래 성격이 전혀 다른 두 가지가 섞여 있기 때문입니다.

텍스트 레이어 PDF이미지(스캔) PDF
정체글자가 문자 데이터로 들어 있음페이지 전체가 한 장의 사진
확인법뷰어에서 글자를 드래그해 복사하면 됨드래그해도 잡히지 않음
추출 방법라이브러리로 텍스트·좌표를 바로 읽음렌더링 → OCR 후 좌표로 구조를 재구성
난이도낮음 — 대부분 하루 안에 끝남높음 — 오인식·레이아웃 판단이 과제
주 실패표·다단 조판에서 읽는 순서가 뒤섞임글자 오인식, 공백 소실, 사진과 텍스트 혼동
First Question

가장 먼저 할 일은 어느 쪽인지 확인하는 것

이 판별을 건너뛰고 시작하면, 텍스트 PDF에 OCR을 돌려 멀쩡한 글자를 망가뜨리거나, 이미지 PDF에서 아무것도 못 뽑고 헤매게 됩니다. AI에게도 "이 PDF가 어느 쪽인지 먼저 확인해줘"를 첫 지시로 주는 편이 안전합니다.

PDF 진단페이지 확보텍스트 읽기구조 조립정확도 측정
이 장의 대상. 어려운 쪽인 이미지 PDF를 다룹니다. 인쇄용으로 만들어진 제품 브로슈어는 대부분 이쪽입니다 — 디자인이 그대로 보존되어야 하므로 페이지를 통째로 이미지로 굽습니다. 쉬운 쪽은 이 방법의 OCR 단계만 빼면 그대로 적용됩니다.
이해

문서의 해부도를 말로 정의한다

파싱 대상 브로슈어의 인쇄 12·13쪽 펼침면 — 왼쪽은 Semiconductor Production Equipment 카테고리에 ENTRON-EX W300과 RISE-300, 오른쪽은 Industrial Vacuum Equipment 카테고리에 DFB·COMBAT·FSC·H 네 제품이 실려 있다
파싱 대상 — 브로슈어 인쇄 12·13쪽 펼침면. PDF에서는 이 두 쪽이 한 장이고, 글자는 한 자도 복사되지 않는다(페이지 전체가 사진). 이 한 장을 놓고 디자이너가 지킨 규칙을 찾아내는 것이 이 절의 작업이다.

이 절과 이후 실습에서 쓰는 원본 PDF입니다. 텍스트 레이어가 전혀 없는 펼침면 이미지 PDF라, 열어서 글자를 복사해 보면 한 자도 잡히지 않는 것을 바로 확인할 수 있습니다. 01-input/에 넣고 시작하십시오.

OCR이 주는 것은 글자 조각과 좌표의 목록일 뿐입니다. "이 줄은 제품명이고 저 줄은 사양"이라는 판단은 아무도 해 주지 않습니다. 그 판단 규칙을 사람이 말로 정의해서 AI에게 넘겨야 합니다.

위 지면을 한참 들여다보면 규칙이 보입니다. 카테고리는 언제나 맨 위 가장 큰 검은 글자이고, 제품명은 언제나 파란색이며, 항목은 언제나 ■로 시작합니다. 디자이너가 지킨 규칙이 곧 파싱 규칙입니다.

요소지면에서 보이는 모습 (사람의 눈)기계에게 줄 판별 규칙
카테고리 페이지 최상단의 가장 큰 검은 글자. 12쪽 Semiconductor Production Equipment, 13쪽 Industrial Vacuum Equipment 머리말 구분선 위쪽에 있고, 줄 높이가 300 DPI 기준 46px 이상이며 잉크가 검정인 줄. 없는 쪽은 앞 쪽에서 물려받고 물려받았다는 표시를 남긴다
부제 카테고리 바로 아래 한 줄. Cutting-edge Products to Meet State-of-the-art… 그 아래에 가는 가로줄이 있어 머리말이 끝난다 구분선 위쪽 줄 중 카테고리를 뺀 나머지. 구분선은 가로 폭의 55% 이상이 이어지는 어두운 픽셀 행으로 찾는다
제품명 브랜드 블루로 인쇄된 중간 크기 글자. 이 줄에서 새 제품이 시작된다 줄의 잉크 색이 파랑(B≥110, B−R≥35)이고 줄 높이 30px 이상새 제품의 시작점. 다음 파란 줄 직전까지가 한 제품
모델 Model : ENTRON-EX W300 · Model : DFB 형태. 뒤나 다음 줄에 Multi-chamber Sputtering Systems 같은 설명이 붙는다 Model : 뒤 토큰이 모델명, 같은 줄의 나머지 또는 다음 줄이 설명. 제품군 제목 아래 개별 모델이 다시 나오는 경우도 잡는다
섹션 제목 Features · Applications — 브로슈어 전체에서 이 두 낱말뿐 검은 글자 줄의 내용이 두 낱말 중 하나와 일치하면 섹션 경계. 다음 섹션 제목이나 다음 제품까지가 그 섹션
항목 ■ 로 시작하면 1단계. - · : · 로 시작하면 그 아래 2단계. 긴 항목은 다음 줄로 넘어가되 들여쓰기가 유지된다 ■ 는 글자로 읽지 말고 도형으로 찾는다 — 정사각형(허용오차 25%), 채움 비율 90% 이상, 300 DPI에서 13~34px. 불릿 없이 이어지는 줄은 앞 항목에 이어 붙인다
제품 사진 흰 바탕에 놓인 사진 1~2장. 바로 위 줄이 캡션. 12쪽 아래 클린룸 띠는 제품 사진이 아니다 흰색이 아닌 픽셀 덩어리에서 OCR이 글자라고 한 영역을 지운 나머지. 사각형을 86% 이상 꽉 채우고 가로로 긴 덩어리는 장식 띠로 보아 버린다
이것이 이 장의 핵심 작업입니다. 코드를 쓰는 것보다 이 해부도를 정확히 관찰해 적는 것이 훨씬 중요합니다. 해부도가 정확하면 구현은 AI가 합니다. 반대로 해부도가 어설프면 아무리 좋은 도구를 써도 결과가 어긋납니다.

해부도를 그대로 프롬프트로 옮긴다

표의 오른쪽 열이 사실상 명세서입니다. 여기에 "이 이미지를 어떻게 다뤄라"를 덧붙이면 그대로 프롬프트가 됩니다. 아래는 위 펼침면 이미지를 첨부하고 규칙이 실제 지면과 맞는지 검증받는 단계입니다 — 본격적으로 시키기 전에 해부도의 오류를 먼저 걸러내는 짧은 확인 절차입니다.

해부도 검증 프롬프트 — 페이지 이미지를 붙여서
첨부한 이미지는 제품 브로슈어의 인쇄 12·13쪽 펼침면이야. 이 지면을 직접 보면서, 내가 적은 해부도가 실제와 맞는지 검증해 줘. # 내가 관찰한 요소별 규칙 - 카테고리 : 최상단 가장 큰 검은 글자. 머리말 구분선 위. 없는 쪽은 앞 쪽에서 상속. - 부제 : 카테고리 바로 아래 한 줄. 그 아래 가는 가로줄이 머리말의 끝. - 제품명 : 브랜드 블루로 인쇄된 중간 크기 글자에서 새 제품이 시작. - 모델 : "Model : ○○○" 형태. 같은 줄 뒤나 다음 줄이 제품 설명. - 섹션 제목 : "Features" / "Applications" 두 낱말만. - 항목 : ■ = 1단계, "- " ": " "▶ " = 그 아래 2단계. 줄바꿈된 항목은 이어 붙임. - 제품 사진 : 흰 바탕 위 사진 1~2장. 바로 위 줄이 캡션. 페이지 아래 전면 장식 띠는 제품 사진이 아니다. # 이 규칙을 이미지에서 구현할 방법 — 이렇게 갈 생각이야 - 페이지는 300 DPI로 렌더링한 뒤 OCR 한다. 펼침면은 좌우로 나눈다. - 글자 크기는 OCR 줄의 픽셀 높이로 재서 제목/본문을 가른다. - 잉크 색은 줄 영역의 글자 픽셀만 골라 평균 RGB로 파랑/검정을 판정한다. - ■ 불릿은 문자로 읽지 말고 도형으로 찾는다 (정사각형 · 채움 비율). - 제품 사진은 흰 종이가 아닌 픽셀 덩어리에서 OCR 글자 영역을 뺀 나머지로 찾는다. - 사진 영역을 먼저 확정하고 그 안의 OCR 줄을 버린 뒤 구조를 조립한다. # 확인해 줄 것 1. 이 이미지에서 위 규칙으로 잡히지 않는 예외가 있으면 전부 짚어 줘. 2. 각 임계값의 시작값을 제안해 줘 — 글자 높이 px, 파랑 판정 기준, 불릿 크기 범위, 장식 띠를 거를 채움 비율. 3. 내 규칙 중 애매하거나 위험한 것이 있으면 질문해 줘. 아직 코드는 쓰지 마.
예외를 미리 적어 두십시오. "카테고리가 없는 이어지는 페이지", "제품군 제목 아래 개별 모델이 다시 나오는 경우", "사진 위에 인쇄된 장비 표기" 같은 예외를 처음부터 적어 두면 나중에 규칙을 갈아엎는 일이 줄어듭니다. 실제 프로젝트에서 시간을 잡아먹는 것은 대부분 규칙이 아니라 예외입니다.
실습

한 번에 시킨다 — 통합 프롬프트

학습 안내직접 실행. 파싱할 PDF를 하나 준비합니다. 사내 카탈로그·사양서 무엇이든 좋습니다. 빈 폴더에 01-input/ 을 만들어 넣고 Codex로 엽니다.

여기서 흔한 오해를 하나 정리하고 갑니다. 파싱을 배울 때 사람들은 대개 "먼저 렌더링을 시키고, 되면 OCR을 시키고, 그 다음 구조를 조립시키고…" 처럼 프롬프트를 잘게 쪼개 순서대로 넣습니다. 사람이 매 단계 결과를 확인하고 다음 지시를 만드는 방식입니다.

바이브코딩은 그렇게 하지 않습니다. 전체 목표와 판정 기준을 한 번에 주고, 반복은 AI가 돕니다. 사람이 매 단계 개입하면 AI는 사람의 속도로만 일합니다.

Who Does What

사람은 기준을 정하고, 반복은 AI가 돈다

사람이 하는 일은 무엇을 만들지 · 무엇이 정답인지 · 언제 멈출지를 정의하는 것뿐입니다. 코드를 쓰고, 돌려 보고, 틀린 곳을 찾고, 값을 바꾸고, 다시 돌리는 지루한 반복은 전부 AI 몫입니다. 그러려면 프롬프트에 채점 방법과 정지 조건이 반드시 들어가야 합니다. 그것이 없으면 AI는 한 번 돌려 보고 "됐습니다"라고 말할 뿐입니다.

통합 프롬프트에 반드시 들어가야 하는 것
아래 여섯 가지 중 하나라도 빠지면 루프가 돌지 않습니다.
① 산출물무엇을 만드는가

JSON · 잘라낸 사진 · 단일 HTML 뷰어까지. "데이터만 뽑아줘"로 끝내면 결과를 눈으로 확인할 방법이 없다.

② 해부도무엇이 정답 구조인가

앞 절에서 관찰한 요소별 판별 규칙. 이것이 명세서다.

③ 기록 규약어떤 코드가 어떤 결과를 냈나

실행마다 날짜·시각 ID를 만들고, 결과와 코드 사본에 같은 ID를 붙인다. 되짚을 수 없는 개선은 개선이 아니다.

④ 채점누가 정답을 정하나

파싱이 끝나면 AI가 직접 PDF 지면을 읽어 자기 결과와 대조한다. 코드의 눈이 아니라 모델의 눈으로 본다.

⑤ 정지 조건언제 멈추나

텍스트 95% 이상 + 구조 완전 일치. 숫자가 없으면 AI는 첫 실행에서 멈춘다.

⑥ 조정값어디를 고치나

"몇 픽셀부터" 같은 값은 설정 파일 한 곳에. 코드 본문에 숫자를 박지 못하게 한다.

폴더 규약 — 번호가 곧 흐름. 프롬프트에 그대로 적어 준다
pdf-parser-Case-UK-brochure/
├─ 01-input/                    # 원본 PDF (건드리지 않음)
├─ 02-processing/               # 파서 코드 — 현재 버전
│   ├─ config.py                #   임계값·경로 — 튜닝은 여기서만
│   ├─ page_source.py           #   PDF → 인쇄 페이지 이미지
│   ├─ ocr_engine.py            #   OCR + 공백 복원 + 잉크 색 판정
│   ├─ layout.py                #   줄 목록 → 제품 구조로 조립
│   ├─ image_extract.py         #   제품 사진 검출 · 캡션 연결
│   ├─ parse.py                 #   전체 실행 진입점
│   ├─ evaluate.py              #   정답지 대비 점수
│   └─ ground_truth.json        #   AI가 지면을 직접 읽어 만든 정답
└─ 09-output/                   # 산출물 — 회차가 쌓인다
    ├─ {파일명}-{실행ID}.json       # 파싱 결과
    ├─ images/{실행ID}/            #   그 회차가 잘라낸 제품 사진
    ├─ code/{실행ID}/              #   그 결과를 만든 코드 사본 ★
    ├─ manifest.json              # 회차 목록 · 점수 · 바꾼 것
    └─ viewer.html                # 회차 드롭다운이 달린 뷰어

  실행ID = ulvac_Brochure_2026-20260808-015521   (파일명 + 날짜 + 시각)
★ 코드 사본이 왜 필요한가. 결과 JSON만 남기면 "이 결과를 만든 코드가 어떤 상태였는지"를 잃습니다. 4회차가 3회차보다 나빠졌을 때 되돌아갈 곳이 없습니다. 결과와 코드에 같은 실행 ID를 붙여 두면, 어느 회차든 그때의 코드를 그대로 꺼내 볼 수 있습니다. 개선 루프의 안전장치입니다.

아래가 실제로 한 번에 넣는 프롬프트 전문입니다. 길어 보이지만, 앞에서 정리한 여섯 가지를 순서대로 적었을 뿐입니다. 이걸 붙여 넣고 나면 사람은 결과를 기다립니다.

통합 프롬프트 — 이것 하나만 넣는다
01-input 폴더에 제품 브로슈어 PDF가 하나 있어. 이걸 검색·재활용 가능한 구조화 데이터로 바꾸는 도구 일체를 만들어 줘. 아래에 최종 산출물 · 문서 해부도 · 기록 규약 · 채점 방법 · 합격 기준을 다 적었어. 중간에 나에게 확인받지 말고, 합격 기준에 도달할 때까지 스스로 반복해 줘. ════ 1. 최종 산출물 ════ ① 제품 카탈로그 JSON — 인쇄 12~21쪽에 실린 제품 전부 ② 제품 사진 파일 — 페이지 이미지에서 잘라낸 것, 제품에 배정 ③ 단일 HTML 뷰어 — 더블클릭으로 열려서 ①②를 보여준다 ════ 2. 문서 해부도 (판별 규칙) ════ - 카테고리 : 페이지 최상단 가장 큰 검은 글자. 머리말 구분선 위. 없는 쪽은 앞 쪽에서 물려받고, 물려받았다는 표시를 남길 것. - 부제 : 카테고리 바로 아래 한 줄. 그 아래 가는 가로줄이 머리말의 끝. - 제품명·모델 : 브랜드 블루로 인쇄된 중간 크기 글자에서 새 제품이 시작된다. "Model : ○○○" 형태면 모델명, 설명은 같은 줄 뒤나 다음 줄. 제품군 제목 아래 개별 모델이 다시 나오는 경우도 둘 다 잡을 것. - 섹션 제목 : "Features" / "Applications" 두 낱말. - 항목 : ■ 로 시작하면 1단계, "- " ": " "▶ " 로 시작하면 그 아래 2단계. 여러 줄에 걸친 항목은 이어 붙일 것. - 제품 사진 : 흰 바탕에 놓인 사진 1~2장. 바로 위 줄이 캡션. 페이지 밑 전면 장식 띠는 제품 사진이 아니다. ════ 3. 이미지 처리 방침 ════ - 텍스트 레이어가 없으니 300 DPI로 렌더링한 뒤 OCR 한다. - 펼침면은 좌우로 나누고, 하단 인쇄 쪽번호를 읽어 인쇄 쪽을 확정한다. ("12쪽부터"는 PDF 몇 번째 장이 아니라 인쇄된 쪽번호 기준이다.) - OCR이 단어 사이 공백을 자주 빠뜨린다. 인식 결과가 아니라 이미지에서 비어 있는 세로 픽셀 열을 찾아 공백을 복원할 것. - 줄마다 잉크 색(파랑/검정)과 줄 높이(px)를 함께 반환할 것. - ■ 불릿은 글자로 읽지 말고 도형으로 찾을 것. - 사진 영역을 먼저 확정하고 그 안의 OCR 줄·불릿을 버린 뒤 구조를 조립할 것. 사진 위에 인쇄된 글자가 제품명으로 둔갑하는 것을 막기 위해서다. ════ 4. 폴더와 기록 규약 — 반드시 지킬 것 ════ 01-input/ 원본 · 02-processing/ 코드 · 09-output/ 산출물 로 나눈다. 시도할 때마다 실행ID = {PDF파일명}-{YYYYMMDD-HHMMSS} 를 새로 만들고, 09-output/{실행ID}.json 결과 데이터 09-output/images/{실행ID}/ 그 회차가 잘라낸 사진 09-output/code/{실행ID}/ 그 결과를 만든 코드 전체 사본 09-output/manifest.json 회차 목록에 한 줄 추가 (실행ID · 시각 · 점수 · 개수 · 이번에 바꾼 것) 결과와 코드가 같은 실행ID를 쓰게 해서, 어떤 코드가 어떤 결과를 냈는지 나중에 반드시 되짚을 수 있어야 해. 덮어쓰기 금지. ════ 5. 품질 검사 — 네가 직접 PDF를 봐라 ════ 파싱이 끝나면 네가 스스로 채점해. - 코드가 만든 JSON만 보지 말고, 해당 쪽의 페이지 이미지를 직접 열어 읽고 정답을 만들어 대조해. 코드의 눈이 아니라 네 눈으로 확인하는 거야. - 대표 쪽(12 · 15 · 17 · 21)은 ground_truth.json 으로 남겨 둬 — 다음 회차에 재사용한다. - 채점 항목 · 텍스트 정확도 : 문자 단위 일치율, 항목 길이로 가중 평균 · 구조 : 제품 수 · 제품명 · 섹션 수 · 사진 장수 · 페이지 헤더 - 비교 전에 유니코드 정규화를 할 것 — ℃/°C, 전각/반각, μ/u, 각종 하이픈·따옴표 차이로 틀렸다고 나오면 안 되니까. - 점수와 함께 "어느 쪽 어느 제품이 몇 %인지" 낮은 순 목록을 남겨. ════ 6. 합격 기준과 반복 규칙 ════ 합격 = 텍스트 정확도 95% 이상 AND 제품 수·사진 장수·페이지 헤더가 정답과 완전 일치. 합격하지 못하면 이 순서로 반복해. 1) 낮은 항목을 보고 원인을 가설로 적고 2) config.py 값이나 규칙을 고치고 3) 새 실행ID로 다시 돌리고 4) 이전 회차와 점수를 비교해 — 나빠졌으면 되돌린다 5) manifest.json 에 "무엇을 왜 고쳤는지" 한 줄로 남긴다 합격하거나, 3회 연속 개선이 없으면 멈추고 회차별 점수 표로 보고해 줘. ════ 7. 뷰어 ════ 09-output/viewer.html — 외부 파일 없이 더블클릭으로 열리는 단일 HTML. - 상단에 manifest.json 을 읽는 실행 회차 드롭다운. 고르면 그 회차의 JSON·사진으로 화면이 통째로 바뀐다. 그 회차의 점수 · 바꾼 것 · 코드 사본 경로도 함께 보여줄 것. - 왼쪽에 카테고리 목록, 오른쪽에 제품 카드 (사진 · 모델 · 설명 · Features · Applications). - 상단 검색창 — 제품명 · 설명 · 항목 본문을 모두 검색. - 파싱이 이상한 제품에 체크 표시를 남길 수 있게. ════ 8. 조정값 ════ "몇 픽셀부터" · "몇 % 이상" 같은 값은 전부 config.py 한 곳에 상수로 모으고, 왜 그 값인지 주석을 달아. 코드 본문에 숫자를 박아 넣지 마. ════ 9. 시작 전에 ════ 먼저 PDF를 진단해서 한 번만 알려 줘 — 텍스트 레이어 유무, 총 장수, 펼침면 여부, 인쇄 쪽번호와 PDF 장 번호의 어긋남. 그 다음 바로 진행해.
길다고 걱정하지 마십시오. 이 프롬프트를 쓰는 데 걸리는 시간은 30분 남짓이고, 그 대가로 사람이 개입하지 않는 몇 시간을 얻습니다. 반대로 이걸 아껴서 "PDF 파싱해줘"라고만 보내면, 이후 며칠 동안 사람이 계속 붙어 있어야 합니다.
실습

AI가 스스로 도는 루프를 옆에서 본다

프롬프트를 넣은 뒤 화면에서 벌어지는 일입니다. 코드를 만들고 → 돌리고 → 자기 결과를 지면과 대조해 채점하고 → 원인을 짚고 → 고쳐서 다시 돌립니다. 사람은 아무것도 하지 않습니다.

BUILD
코드 생성
해부도를 코드로
RUN
파싱 실행
새 실행ID로 결과·코드 저장
JUDGE
자체 채점
지면을 직접 읽어 대조
DIAGNOSE
원인 규명
틀린 항목 → 가설
FIX
코드 개선
합격까지 되돌아감
codex — pdf-parser-Case-UK-brochure
(위 통합 프롬프트 붙여넣기) [진단] 01-input/ulvac_Brochure_2026.pdf · 텍스트 레이어 없음 — 14장 전부 전면 JPEG, 추출 글자 수 0 · 1~12장이 펼침면(가로/세로 1.414) → 좌우 분할 필요 · 하단 인쇄 쪽번호 확인 — 인쇄 12쪽 = PDF 7장 왼쪽 → 300 DPI 렌더링 + OCR 방식으로 진행합니다. [코드 생성] config.py · page_source.py · ocr_engine.py · layout.py image_extract.py · parse.py · evaluate.py · viewer.html ──── 1회차 ulvac_Brochure_2026-20260808-011240 ──── parse products 46 · images 47 · 11.4s self-check 인쇄 12·15·17·21쪽 이미지를 직접 읽어 ground_truth.json 작성 → 대조 TEXT 88.1% ✗ 불합격 (목표 95% · 구조 일치) 진단 · 제품이 12개 많다. p12 'ULVAC', p14 'ei', p18 'ULVAC' … → 장비 사진 위에 인쇄된 라벨을 OCR이 읽어 제품명으로 잡았다 · 카테고리가 빈 제품 9건 (p17 · p19 · p20) → 이어지는 쪽에는 카테고리 제목이 인쇄돼 있지 않다 수정 parse.py 실행 순서 변경 — image_extract 를 layout 으로 옮기고 사진 영역 안의 OCR 줄·불릿을 먼저 제거 layout.py 에 카테고리 상속 규칙 추가 (inherited 표시 남김) ──── 2회차 ulvac_Brochure_2026-20260808-013012 ──── parse products 34 · images 46 · 10.9s TEXT 91.5% ✗ 불합격 개선 제품 수 정답 일치(34/34) · 카테고리 상속 19/19 진단 · 단어가 붙어 나온다 — 'Multi-chamberSputteringSystems' → OCR이 단어 사이 공백을 삼킨다. 인식 결과로는 복원할 수 없다 수정 ocr_engine.py — 이미지에서 비어 있는 세로 픽셀 열을 찾아 공백 복원. 경계 위치 보정계수 SPACE_BOUNDARY_ALPHA 를 config.py 에 신설 ──── 3회차 ulvac_Brochure_2026-20260808-013905 ──── sweep SPACE_BOUNDARY_ALPHA 0.00 → 1.00 (0.05 간격, 21회 재파싱) 0.55 → 92.8% | 0.65 → 94.0% | 0.70 → 94.3% | 0.75 → 94.1% 최적값 0.70 채택, 근거를 주석으로 남김 parse products 34 · images 46 · 11.2s TEXT 94.3% ✗ 불합격 — 목표 95% 미달 · 구조도 불일치 진단 · 사진 장수 46 vs 정답 37 → 페이지 하단 클린룸 장식 띠가 제품 사진으로 잡혔다 (5건) → 사진 하나가 내부 흰 여백 때문에 둘로 쪼개졌다 (4건) 수정 image_extract.py — 사각형을 꽉 채우고 가로로 긴 덩어리는 장식으로 제외. IMG_MERGE_GAP=30px 이내 덩어리는 한 장으로 병합 ──── 4회차 ulvac_Brochure_2026-20260808-014733 ──── parse products 34 · images 37 · 10.7s TEXT 94.6% ✗ 불합격 — 목표 95% 미달 · 페이지 헤더 15/19 진단 · 부제가 카테고리에 이어 붙었다 (p16 · p18 · p20 · p21) → 머리말의 끝을 글자 크기로만 판단해 경계가 흔들린다 · 'Not more than 3 x 10 -2 Pa' — 숫자와 부호 사이에 없는 공백이 생겼다 · 21쪽 충전기 4종만 88.0% — 'Model : UK-…' 줄이 설명으로 흘러들었다 → 이 쪽은 모델명이 파란색이 아니라 검정으로 인쇄돼 있다 수정 layout.py — 머리말 가로 구분선을 픽셀로 찾아 그 위/아래로 자름. ocr_engine.py — 숫자·지수 사이에는 공백 복원을 억제 layout.py — 제목 아래 첫 줄이 "Model : …" 이면 설명이 아니라 모델로 분리 ──── 5회차 ulvac_Brochure_2026-20260808-015521 ──── parse products 34 · images 37 · 10.6s TEXT 99.03% products 34/34 · headers 19/19 · images 34/34 ✓ 합격 — 반복을 멈춥니다. 남은 문제 3건 p15 'uGmni' 90.5% (Applications가 2열 표) p17 'EWE (for Film Capacitor)' 96.1% · p17 'EWK (for LiB/LiC)' 95.4% 산출물 09-output/ulvac_Brochure_2026-20260808-015521.json 09-output/images/ulvac_Brochure_2026-20260808-015521/ (37장) 09-output/code/ulvac_Brochure_2026-20260808-015521/ (코드 사본) 09-output/viewer.html · manifest.json (5회차 기록)
Zero Interventions

이 다섯 회차 동안 사람이 한 일은 없습니다

프롬프트를 한 번 넣고 기다렸을 뿐입니다. 이게 가능한 이유는 세 가지가 프롬프트에 들어 있었기 때문입니다 — 정답을 만드는 방법(지면을 직접 읽어라), 합격 기준(95% + 구조 일치), 되돌아갈 지점(회차별 코드 사본). 이 셋이 없으면 AI는 첫 실행에서 "완료했습니다"라고 말하고 멈춥니다.

사람이 개입해야 하는 순간

그렇다고 완전 방목은 아닙니다. 사람이 봐야 하는 지점은 두 곳입니다.

개입 지점무엇을 확인하나이 사례에서는
정답지가 진짜 정답인가AI가 만든 ground_truth.json을 지면과 눈으로 대조. 정답지가 틀리면 점수 전체가 거짓이다4쪽 분량 정답지를 15분 검토 — 2건 수정(하위 항목 계층 오류)
목표를 채운 방식이 정당한가점수를 올리려고 규칙이 아니라 예외를 하드코딩하지 않았는지. 다른 브로슈어에 그대로 쓸 수 있는가config.py 상수로만 조정 — 특정 제품명을 코드에 박은 곳 없음

AI가 만들어 낸 조정판 — config.py

"조정값은 한 곳에 모으고 왜 그 값인지 주석을 달아"라는 한 줄이 만들어 낸 결과물입니다. 주석에 근거가 남아 있어, 다음 브로슈어에서는 이 파일만 고쳐 재사용합니다.

02-processing/config.py — 조정 대상이 한곳에 모인 모습
# ── 페이지 선택 ─────────────────────────────
PRINTED_PAGE_FIRST = 12      # 하단에 인쇄된 쪽번호 기준
PRINTED_PAGE_LAST  = 21
SPREAD_ASPECT_MIN  = 1.15    # 이보다 넓으면 2쪽 펼침으로 본다

# ── 렌더링 ─────────────────────────────────
RENDER_DPI = 300

# ── OCR ────────────────────────────────────
SPACE_MIN_GAP_FRAC   = 0.18  # 줄 높이 대비 이만큼 비면 공백
SPACE_BOUNDARY_ALPHA = 0.70  # 공백이 글자 i와 i+1 사이 어디에 놓이는지
                             # 3회차에서 0.00~1.00 을 0.05 간격으로 훑어 찾은 최적값
MIN_TEXT_SCORE       = 0.55  # 이보다 흐린 줄은 버린다

# ── 글자 크기로 역할 판정(300 DPI 기준 픽셀) ──
FONT_CATEGORY_MIN    = 46    # 페이지 최상단 대제목
FONT_MODEL_MIN       = 30    # "Model : ○○○"
FONT_SUBCATEGORY_MIN = 32

# ── 잉크 색: 브랜드 블루 판정 ────────────────
BLUE_MIN_B     = 110
BLUE_B_OVER_R  = 35
BLUE_G_OVER_R  = 15

# ── 불릿 ■ (300 DPI에서 실측 21~25px) ────────
BULLET_MIN_PX      = 13      # DPI를 바꿔도 놓치지 않게 범위를 넓게 잡았다
BULLET_MAX_PX      = 34
BULLET_SOLIDITY    = 0.90    # 채워진 픽셀 / 사각형 넓이

# ── 제품 사진 ───────────────────────────────
IMG_MERGE_GAP        = 30    # 이보다 가까운 덩어리는 한 장의 사진 (3회차)
IMG_MAX_PER_PRODUCT  = 2     # 이 브로슈어는 제품당 최대 2장
IMG_DECOR_FILL       = 0.86  # 이보다 꽉 차고 넓으면 장식 띠 → 제외 (3회차)

# ── 머리말 구분선 ───────────────────────────
RULE_MIN_COVERAGE    = 0.55  # 가로 폭의 이만큼 이어지면 구분선 (4회차)
사례

사례 · 제품 브로슈어를 카탈로그 데이터로

실제 프로젝트의 결과입니다. 텍스트 레이어가 전혀 없는 펼침면 이미지 PDF에서 인쇄 12~21쪽을 골라, 제품 34종을 구조화 데이터로 뽑았습니다.

10
인쇄 쪽
34
제품
37
추출 사진
99.0%
텍스트 정확도

뽑아낸 제품 구성

인쇄 쪽카테고리제품 수
12Semiconductor Production Equipment2
13Industrial Vacuum Equipment4
14Electronic Device Production Equipment4
15SiC Power Device4
16–17VMS Production Equipment6
18–20Vacuum Components10
21Charging System for Electric Vehicle4

16~17쪽과 18~20쪽처럼 한 카테고리가 여러 쪽에 걸치는 경우가 있습니다. 이어지는 쪽에는 카테고리 제목이 인쇄되어 있지 않아, 앞 쪽에서 물려받는 규칙이 없으면 이 제품들의 분류가 통째로 비게 됩니다.

개선 궤적 — 실행 이력이 말해 주는 것

manifest.json에 쌓인 실행 기록입니다. 초반에는 숫자가 줄어드는 것이 개선이고, 구조가 잡힌 뒤부터는 개수가 아니라 점수가 오릅니다.

회차제품사진텍스트AI가 짚은 원인과 고친 것
1회차464788.1%제품 과다 검출 — 사진 위 라벨과 제품군 제목이 각각 제품으로 잡힘 → 사진 영역 우선 확정, 카테고리 상속
2회차344691.5%단어가 붙어 나옴 → 이미지 기반 공백 복원 도입
3회차344694.3%보정계수 21가지 자동 탐색 → 0.70 채택 / 사진 장수 불일치 발견
4회차343794.6%장식 띠 제외·분할 사진 병합 / 부제가 카테고리에 붙는 문제 발견
5회차343799.0%머리말 구분선·21쪽 모델명·숫자 공백까지 반영 → 21쪽 88%→100% · 헤더 19/19 · 첫 합격

최종 점수

python 02-processing/evaluate.py
평가 결과
  page 12: text  99.0%   products 2 gt / 2 parsed
  page 13: text  99.5%   products 4 gt / 4 parsed
  page 14: text  99.5%   products 4 gt / 4 parsed
  page 15: text  96.7%   products 4 gt / 4 parsed
  page 16: text  99.4%   products 3 gt / 3 parsed
  page 17: text  96.6%   products 3 gt / 3 parsed
  page 18: text  99.4%   products 3 gt / 3 parsed
  page 19: text  99.2%   products 3 gt / 3 parsed
  page 20: text  99.9%   products 4 gt / 4 parsed
  page 21: text 100.0%   products 4 gt / 4 parsed
==================================================================
  TEXT ACCURACY      : 99.03%   (target >= 95%)
  products matched   : 34/34
  page headers       : 19/19
  image counts exact : 34/34
==================================================================

3 issue lines:
  p15 'uGmni' text 90.5%
  p17 'EWE (for Film Capacitor)' text 96.1%
  p17 'EWK (for LiB/LiC)' text 95.4%

남은 문제도 정확히 보입니다. 3건뿐이고, 어느 쪽 어느 제품인지까지 나옵니다. 15쪽 uGmni는 Applications가 2열 표라 열 순서가 섞였고, 17쪽 두 제품은 :로 시작하는 하위 항목의 표기 차이입니다. 다음에 무엇을 고칠지가 이 목록에 그대로 적혀 있습니다.

100%를 목표로 하지 않습니다. 목표는 "어디가 틀렸는지 아는 상태"입니다. 99.0%이면서 어느 쪽 어느 제품이 약한지 아는 파서는, 정확도를 모르는 파서보다 훨씬 쓸모 있습니다. 사람이 확인할 곳을 지목해 주기 때문입니다.

이 사례의 산출물 받기

합격 회차(5회차)의 결과 일습입니다. 제품 34종이 담긴 파싱 JSON, 실행 이력 manifest.json, 그리고 PDF에서 잘라낸 제품 사진 37장이 들어 있습니다. 압축을 풀면 09-output/ 폴더가 나오며, 아래 실습의 뷰어를 그 옆에 두면 그대로 열립니다.

실습

이력 뷰어 — 회차를 골라 눈으로 비교한다

점수는 "어디가 몇 %"까지만 알려 줍니다. 왜 틀렸는지는 눈으로 봐야 압니다. 그래서 통합 프롬프트에 뷰어를 넣었습니다 — 상단 드롭다운에서 실행 회차를 고르면 그 회차의 결과로 화면이 통째로 바뀝니다.

4회차까지는 합격선에 닿지 못해 루프가 계속됐습니다. 정지 조건은 텍스트 95% 이상 그리고 구조 완전 일치 둘 다였고, 5회차에서 처음으로 둘을 동시에 만족하면서 AI가 스스로 멈췄습니다. 점수 하나만 조건으로 걸면, 글자는 맞는데 제품 수나 사진이 틀린 결과에서 루프가 멈춥니다.

학습 안내직접 조작. 아래는 그 뷰어의 모습입니다. 왼쪽 드롭다운에서 회차를 바꿔 보십시오. 파서가 무엇을 잘못 보고 있었는지가 화면에 그대로 드러납니다.

09-output/viewer.html
ULVAC Brochure 2026 · 제품 카탈로그인쇄 12~21쪽 · manifest.json 5개 회차
실행 회차 ▾
제품 46 / 정답 34 사진 47 / 정답 37 텍스트 88.1% 바꾼 것 최초 생성 code/…-011240/

카테고리

  • Semiconductor Production Equipment (5)
  • Industrial Vacuum Equipment (6)
  • Electronic Device Production Equip. (7)
  • SiC Power Device (4)
  • (카테고리 없음) 9 ⚠
  • Charging System for EV (4)
Model : ENTRON-EX W300
Multi-chamberSputteringSystems
FEATURES
  • ENTRON-EX W300 is the newest line-up developed based on existing.
  • Line up with detailed model (TiNK, CuRK) respond to various material.
공백이 사라진 곳 3 — OCR이 단어 사이를 삼켰다
ENTRON-EX W300 장비 사진 — 파싱 결과에 제품 사진으로 배정된 크롭p12-01-1.jpg
ULVAC
— 설명 없음 · Features 0개 · Applications 0개
⚠ 장비 사진 위에 인쇄된 로고를 제품명으로 잡았다
사진 없음
제품 34 / 정답 34 사진 46 / 정답 37 텍스트 94.3% 바꾼 것 공백 보정계수 0.70 채택 code/…-013905/

카테고리

  • Semiconductor Production Equipment (2)
  • Industrial Vacuum Equipment (4)
  • Electronic Device Production Equip. (4)
  • SiC Power Device (4)
  • VMS Production Equipment (6)
  • Vacuum Components (10)
  • Charging System for EV (4)
Model : ENTRON-EX W300
Multi-chamber Sputtering Systems
FEATURES
  • ENTRON-EX W300 is the newest line-up developed based on existing.
  • Line up with detailed model (TiNK, CuRK) respond to various material.
공백 복원됨 — 그러나 사진이 2장으로 늘었다
ENTRON-EX W300 장비 사진p12-01-1.jpg
Model : RISE-300
Batch System to Remove Native Oxide · 사진 2장
⚠ 페이지 하단 장식 띠가 제품 사진으로 잡혔다
페이지 하단의 클린룸 작업자 장식 띠 — 제품 사진이 아니지만 파싱 결과에 잘못 포함된 이미지p12-02-2.jpg ⚠
제품 34 / 정답 34 사진 37 / 정답 37 텍스트 99.03% 바꾼 것 머리말 구분선 · 숫자 공백 억제 code/…-015521/

카테고리

  • Semiconductor Production Equipment (2)
  • Industrial Vacuum Equipment (4)
  • Electronic Device Production Equip. (4)
  • SiC Power Device (4)
  • VMS Production Equipment (6)
  • Vacuum Components (10)
  • Charging System for EV (4)
Model : ENTRON-EX W300
Multi-chamber Sputtering Systems
FEATURES 6 · APPLICATIONS 1
  • Up to 8 process chambers (PVD, ALD, CVD, etc.) and 2 chambers (Degas, cool)
  • Mechanical throughput is 100wph or more using by ULVAC original new transfer robot.
ENTRON-EX W300 장비 사진p12-01-1.jpg
Model : RISE-300
Batch System to Remove Native Oxide
FEATURES 8 · APPLICATIONS 3
  • Damage-free (remote plasma and low-temperature process).
  • 50% lower self-aligned contact resistance compared to current wet process.
RISE-300 장비 사진p12-02-1.jpg

1회차에서는 사진 위 로고가 제품으로 둔갑했고 카테고리가 빈 제품이 9건이었습니다. 3회차에서는 제품 수는 맞았지만 페이지 하단 장식 띠가 제품 사진으로 들어와 있습니다. 5회차에서 둘 다 사라졌습니다. 점수 표에서는 "사진 46 vs 37"이라는 숫자였던 것이, 뷰어에서는 잘못 잡힌 그 이미지 자체로 보입니다.

뷰어는 결과물이자 개발 도구입니다. 회차 비교가 되는 뷰어를 만들어 두면, 설정을 바꿀 때마다 무엇이 달라졌는지 즉시 확인할 수 있습니다. AI가 스스로 도는 루프를 사람이 사후에 검증하는 창구이기도 합니다 — 통합 프롬프트에 뷰어를 반드시 넣는 이유입니다.

실제 산출물

통합 프롬프트로 만들어진 실제 뷰어입니다. 왼쪽은 카테고리별 전체 목록, 오른쪽은 사양으로 검색한 결과입니다.

뷰어 전체 목록 화면 — 왼쪽 사이드바에 카테고리별 제품 목록, 오른쪽에 제품 카드와 추출된 사진
전체 목록 — 상단에 실행 회차 드롭다운, 왼쪽에 카테고리별 34종, 오른쪽 카드에 모델·설명·Features·Applications와 PDF에서 잘라낸 사진
뷰어 검색 결과 화면 — sputtering system 으로 검색해 4개 카테고리에서 5개 제품만 남은 상태
사양 검색 — "sputtering system"으로 본문까지 훑어 4개 카테고리에 흩어진 5종만 남았다. 종이 카탈로그에서는 눈으로 훑어야 했던 일

그 뷰어 두 가지 버전입니다. 같은 09-output/*.json을 읽지만 보여주는 방식이 다릅니다 — 문서형은 카테고리·제품을 목록으로 훑는 데, 갤러리형은 추출된 사진을 한눈에 놓고 잘못 잡힌 이미지를 찾는 데 낫습니다.

뷰어는 서버로 열어야 합니다. 위 압축을 푼 09-output/ 옆에 뷰어 파일을 두고, 그 폴더에서 간단한 로컬 서버를 띄운 뒤 접속하십시오. 파일을 더블클릭해 file://로 열면 브라우저가 09-output/*.json 읽기를 막아 회차 목록이 자동으로 뜨지 않습니다(이때는 뷰어가 파일 선택창으로 대신 물어봅니다). 또 배포된 결과에는 합격 회차 하나만 들어 있어, 위 목업처럼 회차를 바꿔 가며 비교하려면 실습에서 회차를 직접 쌓아야 합니다.
실습

뽑아낸 데이터를 쓰는 법

JSON은 목적이 아니라 재료입니다. 종이 카탈로그였다면 불가능했던 일들이 여기서 열립니다.

검색제품 찾기

"챔버가 8개 이상인 장비", "SiC 관련 제품"처럼 사양으로 검색. 종이 카탈로그에서는 눈으로 훑어야 했던 일.

비교사양 비교표

여러 제품의 Features를 나란히 놓은 비교표를 자동 생성. 제안서·견적서에 그대로 붙인다.

번역다국어 카탈로그

구조가 유지된 채로 항목별 번역 → 레이아웃이 같은 국문판. 원문 대조가 쉽다.

챗봇제품 문의 응대

제품 단위로 쪼개진 데이터는 RAG 챗봇의 좋은 재료. 근거 제품·쪽번호를 함께 답하게 한다.

완료 기준
  • 대상 PDF가 텍스트 레이어형인지 이미지형인지 확인하고 그에 맞는 방법을 골랐다
  • 문서의 해부도(요소별 판별 규칙)를 표로 적어 두었다
  • 산출물·해부도·기록 규약·채점·정지 조건·조정값을 담은 통합 프롬프트를 한 번에 넣었다
  • AI가 스스로 채점하고 고치며 두 회차 이상 반복하는 것을 확인했다
  • 실행 결과와 그 결과를 만든 코드가 같은 실행 ID로 남아 있다
  • 뷰어에서 회차를 바꿔 가며 무엇이 달라졌는지 눈으로 확인했다
실습

부록 · 더 해보기

MISSION 1표가 들어간 사양서 다루기

제품 사양서는 대부분 가 핵심입니다. 통합 프롬프트의 해부도 항목에 "표의 괘선을 찾아 행·열을 복원하고, 셀을 좌표로 맞춰 표 데이터로 뽑는다"를 추가해 보십시오. 병합된 셀과 여러 쪽에 걸친 표가 난관입니다 — 그 예외를 해부도에 미리 적고 시작하십시오.

MISSION 2검사성적서 일괄 처리

양식이 같은 문서 수십 개라면 훨씬 쉽습니다. "폴더 안 PDF를 전부 처리해 한 장의 엑셀로 합쳐줘 — 파일명·측정 항목·값·판정"이라고 지시하고, 값이 규격을 벗어난 행을 표시하게 하십시오. 합격 기준은 "무작위 5개 문서를 직접 읽어 대조했을 때 전 항목 일치"로 잡습니다.

MISSION 3제품 카탈로그 챗봇

뽑아낸 JSON을 지식으로 삼아 제품 문의에 답하는 챗봇을 만들어 보십시오. "근거가 된 제품명과 인쇄 쪽번호를 반드시 함께" 답하게 하면, 답변을 원본에서 확인할 수 있습니다.

MISSION 4파싱 결과로 브로슈어 되돌리기

JSON에서 다시 문서를 만들어 보십시오 — 806에서 쓴 방식으로 제품별 소개 슬라이드를, 또는 국문 번역판 카탈로그를. 구조화가 제대로 되었는지 검증하는 가장 확실한 방법이기도 합니다.