코그니텀
경력근거: RAGFlow 기반 문서 처리, AI OCR, 개인정보 비식별화, OpenSearch 관리
※ 실제 산출물이 아닌 AI 예측 시안입니다.
코그니텀은 RAGFlow 기반 문서 처리와 AI OCR, OpenSearch 관리를 연구·개발해 공공기관과 기업 고객에게 AI 를 제공한다고 밝힙니다. 이 회사의 AI 답변은 고객이 올린 문서가 OCR 과 청크 색인을 거쳐 OpenSearch 에 들어가야 성립하므로, 한 단계에서 멈춘 파일은 곧 AI 가 답하지 못하는 질문이 됩니다. 이번 공고의 담당업무가 OpenSearch 기반 로그/지표 수집 및 분석 시스템 구성과 AI 서비스 운영환경 안정화라서, 그 로그를 고객 담당자가 읽는 판정으로 바꾸는 일을 골랐습니다. 단계별 로그 형식과 색인 구조는 공고에 없어 지원자의 파이프라인이 같은 모양으로 남깁니다. 이 포트폴리오는 FastAPI·Celery·OpenSearch 로 문서 처리 파이프라인을 돌리고 그 로그와 집계로 파일별 상태를 판정하는 백엔드를 보여줍니다. 공공기관 기록관리 실무나 OCR 모델 학습 전문성은 주장하지 않습니다.
담당업무에 OpenSearch 기반 로그·지표 수집과 분석 시스템 구성이 있고, 자격요건은 OpenSearch 나 Elasticsearch 의 데이터 구조를 이해하고 쿼리를 직접 쓰는 사람을 찾습니다. 이 편은 처리 단계마다 남는 로그와 청크 색인을 같은 OpenSearch 에 두고, 파일마다 마지막 로그와 쪽 수 집계를 견줘 판정합니다. 쪽 수는 cardinality 집계로 세면서 precision_threshold 를 3000 으로 적어 186쪽짜리 지침도 근삿값 없이 셉니다.
코그니텀은 RAGFlow 기반 문서 처리와 AI OCR, OpenSearch 관리를 연구·개발해 공공기관과 기업 고객에게 공급한다고 소개합니다. 공공기관 문서는 스캔본 PDF 와 한글(HWP) 파일이 섞여 들어와 OCR 과 변환 단계에서 쪽이 빠지기 쉬우므로, 판정을 OCR 에서 멈춤과 일부 쪽만 색인됨으로 따로 세웠습니다.
담당업무는 AI 모델 서빙, 로깅, 모니터링 등 운영환경 전반의 성능과 안정화이고, 우대사항에 Redis·Kafka·Celery 같은 비동기 처리, Prometheus·Grafana 모니터링, Kubernetes 운영이 있습니다. 처리 단계를 Celery 작업으로 잇고, 작업자가 메모리 부족으로 죽어 로그가 끊긴 파일을 1시간 넘게 멈춤으로 잡은 뒤, 3주차에 그 작업이 대기열로 돌아오게 고칩니다.
직원이 'AI 가 이 지침을 모른다'고 문의하면 정보화 담당자는 그 파일이 검색 색인에 들어갔는지부터 알아야 합니다. 이 관리자 페이지는 최근 7일에 올린 파일마다 OCR, 쪼개기, 임베딩, 색인 네 단계 가운데 어디서 멈췄는지를 다섯 판정으로 보여줍니다. 판정에는 업로드 목록의 쪽 수, OpenSearch 에 쌓인 단계별 처리 로그, 청크 색인을 파일별로 센 색인된 쪽 수가 쓰입니다. 로그는 도착 순서가 아니라 기록 시각 순으로 세워 마지막 줄을 읽고, 색인된 쪽이 전체의 90% 에 못 미치면 일부 쪽만 들어간 파일로 둡니다. 담당자와 코그니텀 엔지니어는 이 판정을 같이 보고 다시 처리할 파일과 원본을 바꿔 올릴 파일을 나눕니다.
코그니텀 플랫폼을 도입한 공공기관·기업에서 문서 AI 를 운영하는 정보화 담당자와, 그 기관에 플랫폼을 설치·지원하는 코그니텀 엔지니어. 'AI 가 이 자료를 모른다'는 직원 문의가 오면 관리자 페이지에서 그 파일이 어느 단계에서 멈췄는지 함께 보고, 다시 처리할지 원본을 바꿔 올릴지 정한다
처리 로그는 OpenSearch 에 쌓이지만 담당자 화면에는 업로드 목록의 처리 완료 표시만 나와서, 색인 작업이 성공 줄을 남겼는데 청크는 하나도 없는 파일이나 186쪽 중 121쪽만 들어간 파일도 정상으로 보입니다. 원인을 모르는 담당자는 같은 스캔본을 다시 올리고, 그래도 AI 가 모른다고 하면 모델을 의심합니다. 파일마다 로그의 마지막 줄과 색인 집계를 견주면, 다시 올려도 소용없는 OCR 실패와 다시 처리하면 풀리는 멈춘 작업이 서로 다른 묶음에 섭니다.
Docker Compose 로 FastAPI 업로드 API, Celery 작업자, Redis, OpenSearch 를 띄우고, 공공기관 홈페이지에 공개된 규정·지침 PDF 와 HWP 를 올려 OCR·쪼개기·임베딩·색인 네 단계를 돌립니다. 작업자는 단계가 끝날 때마다 pipeline-logs 인덱스에 성공·실패 줄을 남기고, 청크는 쪽 번호를 달고 doc-chunks 인덱스에 들어갑니다. 정보화 담당자는 워크스페이스를 골라 최근 7일 업로드가 다섯 판정으로 나뉜 화면을 보고, 파일을 펼쳐 기록 시각 순 로그와 색인된 쪽 수를 읽습니다. uploads·pipeline_logs·index_stats 는 이 실행이 그대로 내보낸 값입니다.
저해상도 스캔, 글자 층이 없는 붙임 쪽, 쪽 번호 매핑 충돌, 작업자 강제 종료, 빈 쪽처럼 원인을 알고 심은 파일 40개를 돌려 판정이 심은 고장과 맞는지 판정 정답표 탭에 남깁니다. 검색에 다 들어감으로 판정된 파일은 청크 세 개씩을 골라 Claude Haiku 4.5 가 직원 말투 질문을 쓰고, bge-m3 임베딩으로 kNN 검색해 그 파일이 상위 5개 안에 돌아오는지 검색 되돌려 보기 탭에 남깁니다. GitHub Actions 가 main 에 합칠 때마다 이미지를 빌드해 AWS EC2 한 대의 Docker Compose 로 배포합니다.
작업자를 kind 클러스터의 Kubernetes Deployment 로 옮기고 Celery 를 acks_late 로 바꿔, 메모리 한도를 넘겨 OOMKilled 된 작업이 대기열로 돌아오는지 봅니다. 색인 작업자는 bulk 응답의 항목별 errors 를 읽어 매핑 충돌을 index 실패 줄로 남깁니다. 단계별 처리 시간과 1시간 넘게 멈춘 파일 수는 Prometheus 로 내보내 Grafana 에서 보고, 판정 화면에서 파일 줄의 다시 처리를 누르면 멈춘 단계부터 작업이 다시 들어갑니다.
정보화 담당자가 ○○시 정책자료 워크스페이스를 열면 OCR 에서 멈춤 묶음에 민원처리_매뉴얼_스캔본.pdf 가 두 줄(F-24102 와 다음 날 다시 올린 F-24109) 서 있어, 다시 올린 파일도 같은 단계에서 실패했다고 한 화면에서 읽습니다.
정보공개_운영규정_개정.pdf(F-24103)는 index 단계까지 성공 줄이 있는데 색인 집계에 없어 색인에 아예 없음에 서고, 펼치면 embed 성공 10월 11일 09:15 와 집계 0건이 같은 줄에 나옵니다.
186쪽 중 121쪽만 들어간 2026_세출예산_집행지침.hwp 는 65.1% 로 일부 쪽만 색인됨에, 10쪽 중 9쪽인 공용차량_운영지침.pdf 는 정확히 90% 라 검색에 다 들어감에 섭니다. 마지막 로그가 10시 41분인 부서회의록_1008.hwp 는 판정 시각 13시 45분 기준 184분이 지나 1시간 넘게 멈춤에, 정확히 60분 전인 청사_출입관리_규정.hwp 는 목록 위 처리 중 줄에 남습니다.
OCR 실패 줄이 29분 늦게 도착한 개인정보_처리방침_2026.pdf 는 기록 시각 순 마지막 줄이 index 성공이라 검색에 다 들어감에 섭니다. 고장을 심은 파일 40개로 판정을 채점하면 파일마다 심은 고장과 판정이 한 줄씩 남습니다. 단, 고장과 정답을 모두 작성자가 정한 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 판정은 지원자 파이프라인이 공개 문서로 만든 로그의 자가채점이며, 실제 고객 문서에서 어느 판정이 얼마나 나오는지는 코그니텀 플랫폼의 실제 처리 로그와 색인이 있어야 측정됩니다. 이 포트폴리오는 그 측정을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 100dpi 스캔본과 붙임 그림 쪽을 일부러 만든 사람이 저라서, 판정이 그 고장을 맞혔다는 결과는 규칙이 제가 심은 원인을 되읽었다는 데까지입니다. 실제 고객 문서의 OCR 실패율이나 빈 쪽 비율은 이 실행에서 나오지 않습니다.
파일 11개 가운데 셋은 식의 실수 하나로 판정이 바뀌는 자리에 세웠습니다. 정확히 9/10 인 F-24105 는 0.9 '미달'을 '이하'로 쓰면 일부 쪽만 색인됨으로, 마지막 로그가 정확히 60분 전인 F-24107 은 '넘게'를 '이상'으로 쓰면 1시간 넘게 멈춤으로, OCR 실패 줄이 29분 늦게 도착한 F-24106 은 도착 순으로 마지막 줄을 고르면 OCR 에서 멈춤으로 바뀝니다. embed 줄이 index 줄보다 늦게 도착한 F-24108 은 어느 순서로 세워도 8/9 라 판정은 같고, 펼친 타임라인의 마지막 단계 표시만 달라집니다.
작업 로그가 성공이어도 검색된다는 뜻은 아닙니다. F-24103 처럼 bulk 요청이 HTTP 200 을 받고 항목별 매핑 오류를 놓치면 성공 줄은 있는데 색인은 0건인 파일이 생기므로, 판정은 로그만 믿지 않고 색인 집계를 같이 봅니다.
판정 다섯 값에 들어가지 않는 상태가 있습니다. 쪼개기나 임베딩 단계에서 실패한 파일은 OCR 실패도 색인 단계의 일도 아니어서, 목록 위 판정 밖 줄에 실패 단계와 메시지만 띄웁니다. 실제 고객 로그에서 이런 파일이 많으면 판정 값을 늘릴지 정합니다.
0.9 문턱은 쪽 수만 보고 쪽 내용은 보지 않습니다. F-24108 에서 빠진 1쪽이 결재 서명 쪽이라면 다시 처리할 필요가 없는데도 일부 쪽만 색인됨에 섭니다. 실제 도입에서는 고객 문서의 빈 쪽·서명 쪽 비율을 보고 문턱을 다시 잡습니다.
단계별 로그 형식과 OpenSearch 인덱스 구조는 공고에 없는 가정입니다. 코그니텀 플랫폼이 작업 진행률 같은 다른 형태로 상태를 남긴다면 마지막 줄을 읽는 방식부터 다시 짭니다. 이 포트폴리오는 파이프라인 로그와 색인 집계로 파일 상태를 판정하는 백엔드 운영을 보여주며, OCR 모델 학습이나 공공 기록관리 전문성은 주장하지 않습니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
판정 대상 목록입니다. 최근 7일에 올린 파일로, 쪽 수는 업로드 때 PyMuPDF 가 PDF 를 열어 세고 HWP 는 pyhwp(hwp5odt)로 ODT 로 바꾼 뒤 LibreOffice 로 만든 PDF 에서 셉니다. 이 쪽 수는 한글 프로그램이 매긴 쪽과 다를 수 있어 문서 안에 적힌 쪽 번호가 아니라 변환된 PDF 의 쪽을 기준으로 합니다. 민원봉사과가 같은 스캔본을 다음 날 다시 올린 F-24109 가 있고, 다른 워크스페이스(○○시 민원상담) 파일 두 개가 섞여 있어 워크스페이스 선택이 판정 대상을 정합니다.
| file_id | workspace_id | workspace_name | file_name | page_count | uploaded_at | uploaded_by |
|---|---|---|---|---|---|---|
| F-24101 | WS-0412 | ○○시 정책자료 | 2026_세출예산_집행지침.hwp | 186 | 2026-10-08T15:02:00+09:00 | 예산담당관 |
| F-24102 | WS-0412 | ○○시 정책자료 | 민원처리_매뉴얼_스캔본.pdf | 54 | 2026-10-10T13:55:00+09:00 | 민원봉사과 |
| F-24103 | WS-0412 | ○○시 정책자료 | 정보공개_운영규정_개정.pdf | 12 | 2026-10-11T09:03:00+09:00 | 정보화담당관 |
| F-24104 | WS-0412 | ○○시 정책자료 | 부서회의록_1008.hwp | 8 | 2026-10-13T10:05:00+09:00 | 총무과 |
| F-24105 | WS-0412 | ○○시 정책자료 | 공용차량_운영지침.pdf | 10 | 2026-10-09T11:20:00+09:00 | 총무과 |
아래 JSON Schema 에 맞는 업로드 목록 행을 더 만들어 주세요. {"type":"object","properties":{"file_id":{"type":"string"},"workspace_id":{"type":"string"},"workspace_name":{"type":"string"},"file_name":{"type":"string"},"page_count":{"type":"integer","minimum":1},"uploaded_at":{"type":"string","format":"date-time"},"uploaded_by":{"type":"string"}},"required":["file_id","workspace_id","workspace_name","file_name","page_count","uploaded_at","uploaded_by"]} 공공기관 부서가 올리는 규정·지침·회의록·업무보고 파일 이름(.pdf·.hwp)을 쓰고 쪽 수는 1~400 사이로 둡니다. 쉬운 데이터만 만들지 말 것 — 다음 경우를 의도적으로 섞으세요: (1) 같은 file_name 을 다른 file_id 로 하루 뒤 다시 올린 행, (2) 다른 workspace_id 의 행, (3) 1쪽짜리 파일과 300쪽 넘는 파일, (4) 판정 기준 시각 1시간 안에 올린 행. 모든 행은 위 스키마를 통과해야 하고, 형식을 깨뜨리는 시도는 따로 3건만 표시해 두세요.{"type":"object","properties":{"file_id":{"type":"string"},"workspace_id":{"type":"string"},"workspace_name":{"type":"string"},"file_name":{"type":"string"},"page_count":{"type":"integer","minimum":1},"uploaded_at":{"type":"string","format":"date-time"},"uploaded_by":{"type":"string"}},"required":["file_id","workspace_id","workspace_name","file_name","page_count","uploaded_at","uploaded_by"]}지원자 파이프라인의 Celery 작업자가 단계(ocr·chunk·embed·index)가 끝날 때마다 OpenSearch pipeline-logs 인덱스에 남긴 줄입니다. logged_at 은 작업자가 기록한 시각이고 ingested_at 은 OpenSearch 가 받은 시각입니다. F-24106 의 OCR 실패 줄은 작업자 로그 전송이 밀려 29분 늦게 도착했고, F-24108 의 embed 줄은 index 줄보다 나중에 도착해 배열 순서가 뒤집혀 있습니다. F-24104 는 임베딩 도중 작업자를 멈춰 chunk 성공 뒤로 줄이 없습니다.
| file_id | stage | status | logged_at | ingested_at | worker | message |
|---|---|---|---|---|---|---|
| F-24101 | ocr | 성공 | 2026-10-08T15:09:00+09:00 | 2026-10-08T15:09:04+09:00 | ocr-1 | — |
| F-24101 | chunk | 성공 | 2026-10-08T15:14:00+09:00 | 2026-10-08T15:14:03+09:00 | chunk-1 | — |
| F-24101 | embed | 성공 | 2026-10-08T15:19:00+09:00 | 2026-10-08T15:19:02+09:00 | embed-1 | — |
| F-24101 | index | 성공 | 2026-10-08T15:20:00+09:00 | 2026-10-08T15:20:02+09:00 | index-1 | — |
| F-24102 | ocr | 실패 | 2026-10-10T14:02:00+09:00 | 2026-10-10T14:02:05+09:00 | ocr-2 | 읽은 글자가 없는 쪽 54/54 · 쪽 평균 OCR 신뢰도 0.41 |
아래 JSON Schema 에 맞는 처리 로그 행을 더 만들어 주세요. {"type":"object","properties":{"file_id":{"type":"string"},"stage":{"type":"string","enum":["ocr","chunk","embed","index"]},"status":{"type":"string","enum":["성공","실패"]},"logged_at":{"type":"string","format":"date-time"},"ingested_at":{"type":"string","format":"date-time"},"worker":{"type":"string"},"message":{"type":["string","null"]}},"required":["file_id","stage","status","logged_at","ingested_at","worker","message"]} 파일 하나는 ocr → chunk → embed → index 순서로 단계마다 한 줄씩 남기고, 실패하면 그 단계 줄의 message 에 원인을 적고 다음 단계 줄을 만들지 않습니다. 쉬운 데이터만 만들지 말 것 — 다음 경우를 의도적으로 섞으세요: (1) ocr 실패 뒤 재시도로 ocr 성공이 이어지는 파일, (2) ingested_at 이 logged_at 보다 20분 넘게 늦은 줄, (3) 배열 순서와 logged_at 순서가 다른 파일, (4) index 성공 줄이 있지만 색인 집계에는 넣지 않을 파일, (5) chunk 나 embed 성공 뒤로 줄이 끊긴 파일. 모든 행은 위 스키마를 통과해야 하고, 형식을 깨뜨리는 시도는 따로 3건만 표시해 두세요.{"type":"object","properties":{"file_id":{"type":"string"},"stage":{"type":"string","enum":["ocr","chunk","embed","index"]},"status":{"type":"string","enum":["성공","실패"]},"logged_at":{"type":"string","format":"date-time"},"ingested_at":{"type":"string","format":"date-time"},"worker":{"type":"string"},"message":{"type":["string","null"]}},"required":["file_id","stage","status","logged_at","ingested_at","worker","message"]}doc-chunks 인덱스를 file_id 로 terms 집계하고 그 아래 page_no 를 cardinality 집계(precision_threshold 3000)해 낸 파일별 색인 현황입니다. 청크가 하나도 없는 파일은 집계 결과에 줄이 생기지 않습니다. 집계는 기간을 가리지 않아 9월에 올린 파일(F-23987·F-23990·F-24012·F-24031) 네 줄도 나오는데, 판정은 uploads 에서 출발하므로 이 줄은 쓰지 않습니다.
| file_id | indexed_pages | chunk_count |
|---|---|---|
| F-24101 | 121 | 388 |
| F-24105 | 9 | 27 |
| F-24106 | 23 | 71 |
| F-24108 | 8 | 24 |
| F-24110 | 14 | 46 |
아래 JSON Schema 에 맞는 파일별 색인 집계 행을 더 만들어 주세요. {"type":"object","properties":{"file_id":{"type":"string"},"indexed_pages":{"type":"integer","minimum":1},"chunk_count":{"type":"integer","minimum":1}},"required":["file_id","indexed_pages","chunk_count"]} indexed_pages 는 uploads 의 page_count 를 넘지 않게 하고 chunk_count 는 indexed_pages 의 2~4배로 둡니다. 쉬운 데이터만 만들지 말 것 — 다음 경우를 의도적으로 섞으세요: (1) indexed_pages ÷ page_count 가 정확히 0.9 인 파일과 0.89 근처인 파일, (2) 전체 쪽의 60~70% 만 들어간 파일, (3) uploads 기간 밖 file_id 의 행. 모든 행은 위 스키마를 통과해야 하고, 형식을 깨뜨리는 시도는 따로 3건만 표시해 두세요.{"type":"object","properties":{"file_id":{"type":"string"},"indexed_pages":{"type":"integer","minimum":1},"chunk_count":{"type":"integer","minimum":1}},"required":["file_id","indexed_pages","chunk_count"]}| Python | 업로드 API, Celery 작업 네 개, 판정 계산을 한 언어로 씁니다. |
| FastAPI | 파일 업로드 API, 판정 API, Jinja2 템플릿으로 그리는 판정 화면을 한 서비스로 띄웁니다. |
| Docker | api·worker·redis·opensearch·opensearch-dashboards 를 Docker Compose 한 파일로 묶어 로컬과 EC2 에서 같은 파이프라인을 띄웁니다. |
| OpenSearch | pipeline-logs 인덱스에 단계별 처리 로그를, doc-chunks 인덱스에 쪽 번호를 단 청크와 bge-m3 벡터를 둡니다. 파일별 색인된 쪽 수는 file_id terms 집계 아래 page_no cardinality 집계(precision_threshold 3000)로 셉니다. |
| Linux | EC2 Ubuntu 에서 작업자 컨테이너가 죽은 이유를 dmesg 의 OOM killer 기록과 docker logs 로 확인합니다. |
| CI/CD · GitHub Actions | PR 마다 판정 규칙 테스트(0.9 교차곱, 60분 문턱, 도착 순서 뒤집힘)를 돌리고, main 에 합치면 이미지를 빌드해 2주차부터 EC2 로 배포합니다. |
| Git 협업 | main 을 보호하고 기능 브랜치 PR 에서 판정 테스트가 통과해야 합칩니다. |
| 생성형 AI 이해 · Embedding | 임베딩 단계가 bge-m3 로 청크 벡터를 만들고, 2주차 검색 되돌려 보기가 같은 모델로 질문을 벡터로 바꿔 kNN 검색합니다. |
| Claude Haiku 4.5 | 2주차에 검색에 다 들어감 파일의 청크로 직원 말투 질문을 씁니다. |
| AI OCR · Tesseract | 글자 층이 없는 쪽을 kor 학습 데이터로 읽고, 쪽 평균 신뢰도가 0.60 에 못 미치는 쪽은 글자를 버립니다. 그런 쪽이 전체의 절반을 넘으면 ocr 실패 줄을 남깁니다. |
| Celery · Redis | 네 단계를 Celery chain 으로 잇고 Redis 를 브로커로 씁니다. 3주차에 acks_late 와 reject_on_worker_lost 를 켜 죽은 작업이 대기열로 돌아오게 합니다. |
| Prometheus · Grafana | 3주차에 단계별 처리 시간 히스토그램과 1시간 넘게 멈춘 파일 수를 내보내 대시보드로 봅니다. |
| Kubernetes | 3주차에 작업자를 kind 클러스터 Deployment 로 옮겨 메모리 한도와 OOMKilled 재시작을 재현합니다. |
| AWS | 2주차부터 EC2 한 대에 Docker Compose 로 배포해 운영합니다. |
✗ fail → F-24106 이 OCR 에서 멈춤으로 가면 정렬 키가 logged_at 이 아니라 ingested_at 인지 봅니다. 9/10·60분에서 판정이 뒤집히면 '미달'·'넘게' 부등호가 판정식과 같은 방향인지, 비율을 반올림한 표시값으로 견주고 있지 않은지 확인합니다.
✓ pass → 여기서 멈춰도 담당자가 문의 대응에 쓰는 판정 화면이 완성됩니다. 더 키우려면 고장을 심은 파일 40개로 판정을 채점하고, 다 들어간 파일이 실제로 검색되는지 보는 단계로 넘어갑니다.
✗ fail → 돌아오지 않은 질문이 표가 많은 쪽에 몰려 있으면, 문턱을 손대기 전에 쪼개기 단계가 표를 한 줄씩 끊고 있지 않은지 청크 원문부터 봅니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 멈춘 작업을 대기열로 되돌리고 단계별 처리 시간을 지표로 보는 운영 단계로 갑니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
verdict사유 라벨 · file_id + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
검색에 다 들어감검색 가능OCR 에서 멈춤OCR 멈춤일부 쪽만 색인됨일부 쪽만색인에 아예 없음색인 없음처리가 1시간 넘게 멈춰 있음1시간 넘게 멈춤색인 상태 판정입니다. 판정 대상 파일 하나가 다섯 판정 중 어디에 서는지와, 판정에 쓴 마지막 로그 줄, 색인된 쪽 수와 비율, 판정 기준 시각에서 거슬러 센 경과 분을 남기는 형식입니다. summary 는 코드가 판정별 문장 틀에 수치를 채워 만듭니다.
{"type":"object","properties":{"file_id":{"type":"string","description":"판정 대상 파일입니다. uploads.file_id 값입니다."},"workspace_id":{"type":"string"},"file_name":{"type":"string"},"verdict":{"type":"string","enum":["검색에 다 들어감","OCR 에서 멈춤","일부 쪽만 색인됨","색인에 아예 없음","처리가 1시간 넘게 멈춰 있음"]},"as_of":{"type":"string","format":"date-time","description":"판정 기준 시각입니다. 화면을 연 시각을 쓰고, 샘플은 2026-10-13T13:45:00+09:00 으로 고정합니다."},"page_count":{"type":"integer","minimum":1,"description":"uploads.page_count 값입니다."},"indexed_pages":{"type":["integer","null"],"minimum":1,"description":"index_stats.indexed_pages 값입니다. index_stats 에 그 file_id 가 없으면 null 입니다."},"chunk_count":{"type":["integer","null"],"minimum":1,"description":"index_stats.chunk_count 값입니다. 없으면 null 입니다."},"indexed_ratio":{"type":["number","null"],"minimum":0,"maximum":1,"description":"indexed_pages ÷ page_count 를 소수 셋째 자리로 반올림한 표시값입니다. 0.9 미달 여부는 반올림 전에 indexed_pages × 10 과 page_count × 9 를 정수로 견줍니다. indexed_pages 가 null 이면 null 입니다."},"last_stage":{"type":"string","enum":["ocr","chunk","embed","index"],"description":"그 file_id 의 pipeline_logs 를 logged_at 순으로 세운 마지막 줄의 stage 입니다. ingested_at 순서는 쓰지 않습니다."},"last_status":{"type":"string","enum":["성공","실패"]},"last_logged_at":{"type":"string","format":"date-time"},"minutes_since_last_log":{"type":"integer","minimum":0,"description":"as_of − last_logged_at 을 분으로 바꿔 소수점 아래를 버린 값입니다."},"embed_succeeded":{"type":"boolean","description":"그 file_id 의 pipeline_logs 에 stage=embed·status=성공 줄이 있는지입니다."},"evidence":{"type":"array","items":{"type":"object","properties":{"table":{"type":"string","enum":["uploads","pipeline_logs","index_stats"]},"field":{"type":"string"},"agg":{"type":"string","enum":["count","sum","avg","min","max"]},"value":{"type":["string","integer","number","null"]},"scope":{"type":"string"}},"required":["table","field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["file_id","workspace_id","file_name","verdict","as_of","page_count","indexed_pages","chunk_count","indexed_ratio","last_stage","last_status","last_logged_at","minutes_since_last_log","embed_succeeded","evidence","summary","hedge"]}[
{
"file_id": "F-24101",
"workspace_id": "WS-0412",
"file_name": "2026_세출예산_집행지침.hwp",
"verdict": "일부 쪽만 색인됨",
"as_of": "2026-10-13T13:45:00+09:00",
"page_count": 186,
"indexed_pages": 121,
"chunk_count": 388,
"indexed_ratio": 0.651,
"last_stage": "index",
"last_status": "성공",
"last_logged_at": "2026-10-08T15:20:00+09:00",
"minutes_since_last_log": 7105,
"embed_succeeded": true,
"evidence": [
{
"table": "uploads",
"field": "file_id",
"value": "F-24101"
},
{
"table": "uploads",
"field": "page_count",
"value": 186
},
{
"table": "index_stats",
"field": "indexed_pages",
"value": 121
},
{
"table": "index_stats",
"field": "chunk_count",
"value": 388
},
{
"table": "pipeline_logs",
"field": "stage",
"value": "index",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "logged_at",
"value": "2026-10-08T15:20:00+09:00",
"scope": "마지막 줄"
}
],
"summary": "186쪽 가운데 121쪽만 검색 색인에 들어가 65.1% 에 그칩니다.",
"hedge": "HWP 를 PDF 로 바꿔 처리한 파일이고 OCR 은 성공 줄을 남겼습니다. 어느 65쪽이 빠졌는지는 이 판정이 말하지 않아, 그림으로 붙인 붙임 쪽인지 본문인지는 담당자가 원본을 열어 확인합니다."
},
{
"file_id": "F-24102",
"workspace_id": "WS-0412",
"file_name": "민원처리_매뉴얼_스캔본.pdf",
"verdict": "OCR 에서 멈춤",
"as_of": "2026-10-13T13:45:00+09:00",
"page_count": 54,
"indexed_pages": null,
"chunk_count": null,
"indexed_ratio": null,
"last_stage": "ocr",
"last_status": "실패",
"last_logged_at": "2026-10-10T14:02:00+09:00",
"minutes_since_last_log": 4303,
"embed_succeeded": false,
"evidence": [
{
"table": "uploads",
"field": "file_id",
"value": "F-24102"
},
{
"table": "pipeline_logs",
"field": "stage",
"value": "ocr",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "status",
"value": "실패",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "logged_at",
"value": "2026-10-10T14:02:00+09:00",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "message",
"value": "읽은 글자가 없는 쪽 54/54 · 쪽 평균 OCR 신뢰도 0.41",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "stage",
"agg": "count",
"value": 0,
"scope": "마지막 줄 뒤 chunk 줄"
}
],
"summary": "OCR 단계에서 54쪽 모두 글자를 읽지 못해 10월 10일 14:02 뒤로 다음 단계가 시작되지 않았습니다.",
"hedge": "같은 이름의 파일을 다음 날 다시 올린 F-24109 도 같은 단계에서 실패했습니다. 다시 처리할지 원본을 더 높은 해상도로 스캔해 올릴지는 담당자가 정합니다."
},
{
"file_id": "F-24109",
"workspace_id": "WS-0412",
"file_name": "민원처리_매뉴얼_스캔본.pdf",
"verdict": "OCR 에서 멈춤",
"as_of": "2026-10-13T13:45:00+09:00",
"page_count": 54,
"indexed_pages": null,
"chunk_count": null,
"indexed_ratio": null,
"last_stage": "ocr",
"last_status": "실패",
"last_logged_at": "2026-10-11T10:26:00+09:00",
"minutes_since_last_log": 3079,
"embed_succeeded": false,
"evidence": [
{
"table": "uploads",
"field": "file_id",
"value": "F-24109"
},
{
"table": "uploads",
"field": "uploaded_at",
"value": "2026-10-11T10:20:00+09:00"
},
{
"table": "pipeline_logs",
"field": "stage",
"value": "ocr",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "status",
"value": "실패",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "logged_at",
"value": "2026-10-11T10:26:00+09:00",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "stage",
"agg": "count",
"value": 0,
"scope": "마지막 줄 뒤 chunk 줄"
}
],
"summary": "다시 올린 스캔본도 OCR 단계에서 54쪽 모두 글자를 읽지 못해 멈췄습니다.",
"hedge": "F-24102 와 같은 파일을 다시 올린 경우라 결과도 같습니다. 다시 올린 횟수는 판정에 쓰지 않습니다."
},
{
"file_id": "F-24103",
"workspace_id": "WS-0412",
"file_name": "정보공개_운영규정_개정.pdf",
"verdict": "색인에 아예 없음",
"as_of": "2026-10-13T13:45:00+09:00",
"page_count": 12,
"indexed_pages": null,
"chunk_count": null,
"indexed_ratio": null,
"last_stage": "index",
"last_status": "성공",
"last_logged_at": "2026-10-11T09:16:00+09:00",
"minutes_since_last_log": 3149,
"embed_succeeded": true,
"evidence": [
{
"table": "uploads",
"field": "file_id",
"value": "F-24103"
},
{
"table": "pipeline_logs",
"field": "stage",
"value": "embed"
},
{
"table": "pipeline_logs",
"field": "status",
"value": "성공"
},
{
"table": "pipeline_logs",
"field": "logged_at",
"value": "2026-10-11T09:15:00+09:00",
"scope": "embed 성공 줄"
},
{
"table": "pipeline_logs",
"field": "logged_at",
"value": "2026-10-11T09:16:00+09:00",
"scope": "마지막 줄(index 성공)"
},
{
"table": "index_stats",
"field": "file_id",
"agg": "count",
"value": 0,
"scope": "F-24103 집계 줄"
}
],
"summary": "임베딩은 10월 11일 09:15 에 성공했는데 검색 색인에는 이 파일의 청크가 하나도 없습니다.",
"hedge": "index 단계도 성공 줄을 남겼습니다. 색인 작업자가 bulk 요청의 HTTP 상태만 보고 항목별 오류를 읽지 않으면 이런 모양이 나오므로, 다시 처리하기 전에 bulk 응답부터 확인합니다."
},
{
"file_id": "F-24104",
"workspace_id": "WS-0412",
"file_name": "부서회의록_1008.hwp",
"verdict": "처리가 1시간 넘게 멈춰 있음",
"as_of": "2026-10-13T13:45:00+09:00",
"page_count": 8,
"indexed_pages": null,
"chunk_count": null,
"indexed_ratio": null,
"last_stage": "chunk",
"last_status": "성공",
"last_logged_at": "2026-10-13T10:41:00+09:00",
"minutes_since_last_log": 184,
"embed_succeeded": false,
"evidence": [
{
"table": "uploads",
"field": "file_id",
"value": "F-24104"
},
{
"table": "pipeline_logs",
"field": "stage",
"value": "chunk",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "status",
"value": "성공",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "logged_at",
"value": "2026-10-13T10:41:00+09:00",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "file_id",
"agg": "count",
"value": 2,
"scope": "F-24104 로그 줄"
}
],
"summary": "마지막 로그인 chunk 성공(10:41) 뒤로 184분 동안 다음 단계 줄이 오지 않았습니다.",
"hedge": "로그만으로는 임베딩 작업이 아직 도는지 작업자가 죽으며 사라졌는지 가리지 못합니다. 엔지니어가 작업자 상태를 본 뒤 다시 처리합니다."
},
{
"file_id": "F-24105",
"workspace_id": "WS-0412",
"file_name": "공용차량_운영지침.pdf",
"verdict": "검색에 다 들어감",
"as_of": "2026-10-13T13:45:00+09:00",
"page_count": 10,
"indexed_pages": 9,
"chunk_count": 27,
"indexed_ratio": 0.9,
"last_stage": "index",
"last_status": "성공",
"last_logged_at": "2026-10-09T11:30:00+09:00",
"minutes_since_last_log": 5895,
"embed_succeeded": true,
"evidence": [
{
"table": "uploads",
"field": "file_id",
"value": "F-24105"
},
{
"table": "uploads",
"field": "page_count",
"value": 10
},
{
"table": "index_stats",
"field": "indexed_pages",
"value": 9
},
{
"table": "index_stats",
"field": "chunk_count",
"value": 27
},
{
"table": "pipeline_logs",
"field": "stage",
"value": "index",
"scope": "마지막 줄"
},
{
"table": "pipeline_logs",
"field": "logged_at",
"value": "2026-10-09T11:30:00+09:00",
"scope": "마지막 줄"
}
],
"summary": "10쪽 가운데 9쪽이 검색 색인에 들어가 90% 를 채웠습니다.",
"hedge": "지원자가 마지막 쪽을 빈 쪽으로 만든 파일이라 9쪽이 이 파일에서 나올 수 있는 최대치입니다."
},
{
"file_id": "F-24106",
"workspace_id": "WS-0412",
"file_name": "개인정보_처리방침_2026.pdf",
"verdict": "검색에 다 들어감",
"as_of": "2026-10-13T13:45:00+09:00",
"page_count": 23,
"indexed_pages": 23,
"chunk_count": 71,
"indexed_ratio": 1,
"last_stage": "index",
"last_status": "성공",
"last_logged_at": "2026-10-12T09:15:00+09:00",
"minutes_since_last_log": 1710,
"embed_succeeded": true,
"evidence": [
{
"table": "uploads",
"field": "file_id",
"value": "F-24106"
},
{
"table": "uploads",
"field": "page_count",
"value": 23
},
{
"table": "index_stats",
"field": "indexed_pages",
"value": 23
},
{
"table": "pipeline_logs",
"field": "stage",
"value": "index",
"scope": "logged_at 순 마지막 줄"
},
{
"table": "pipeline_logs",
"field": "logged_at",
"value": "2026-10-12T09:15:00+09:00",
"scope": "logged_at 순 마지막 줄"
},
{
"table": "pipeline_logs",
"field": "ingested_at",
"value": "2026-10-12T09:31:10+09:00",
"scope": "늦게 도착한 ocr 실패 줄"
}
],
"summary": "23쪽이 모두 검색 색인에 들어갔고, 기록 시각 순 마지막 줄은 index 성공(09:15)입니다.",
"hedge": "09:02 에 기록된 OCR 실패 줄이 09:31 에 도착해 도착 순으로 세면 마지막 줄이 실패가 됩니다. 판정은 logged_at 순으로 셉니다."
},
{
"file_id": "F-24108",
"workspace_id": "WS-0412",
"file_name": "2026_업무보고_요약.pdf",
"verdict": "일부 쪽만 색인됨",
"as_of": "2026-10-13T13:45:00+09:00",
"page_count": 9,
"indexed_pages": 8,
"chunk_count": 24,
"indexed_ratio": 0.889,
"last_stage": "index",
"last_status": "성공",
"last_logged_at": "2026-10-12T16:21:00+09:00",
"minutes_since_last_log": 1284,
"embed_succeeded": true,
"evidence": [
{
"table": "uploads",
"field": "file_id",
"value": "F-24108"
},
{
"table": "uploads",
"field": "page_count",
"value": 9
},
{
"table": "index_stats",
"field": "indexed_pages",
"value": 8
},
{
"table": "index_stats",
"field": "chunk_count",
"value": 24
},
{
"table": "pipeline_logs",
"field": "logged_at",
"value": "2026-10-12T16:21:00+09:00",
"scope": "logged_at 순 마지막 줄(index 성공)"
},
{
"table": "pipeline_logs",
"field": "ingested_at",
"value": "2026-10-12T16:23:40+09:00",
"scope": "index 줄보다 늦게 도착한 embed 줄"
}
],
"summary": "9쪽 가운데 8쪽만 검색 색인에 들어가 88.9% 로 90% 에 못 미칩니다.",
"hedge": "빠진 1쪽이 결재 서명 쪽이라면 다시 처리할 필요가 없습니다. 판정은 쪽 수만 보고 쪽 내용은 보지 않습니다."
}
][자동 검증 · 코드가 세 표에서 다시 계산합니다] 1. verdict 가 enum 다섯 값 중 하나인지 확인합니다. 2. file_id·page_count·last_logged_at·as_of·evidence·summary 필드가 모든 샘플에 존재하는지 확인합니다. 3. indexed_ratio 가 0 이상 1 이하인지, minutes_since_last_log 가 0 이상인지 범위를 확인합니다. 4. evidence 의 table·field 가 uploads·pipeline_logs·index_stats 에 실재하는 표 이름과 컬럼 이름인지 대조합니다. 5. agg 가 없는 evidence 의 value 가 그 컬럼에 수록된 샘플 행의 원문 그대로인지 대조합니다. 6. minutes_since_last_log 를 as_of 와 그 file_id 의 logged_at 최댓값으로 다시 계산하고, indexed_ratio 를 indexed_pages ÷ page_count 로 다시 계산해 소수 셋째 자리까지 일치하는지 봅니다. 7. logged_at 순 마지막 줄이 stage=ocr·status=실패이고 그보다 늦은 chunk 줄이 없으면 verdict 는 OCR 에서 멈춤입니다. 성립 사례는 F-24102·F-24109, 불성립 사례는 도착 순으로는 OCR 실패 줄이 마지막인 F-24106 입니다. 8. 7번이 아니고 embed 성공 줄이 있는데 index_stats 에 그 file_id 가 없으면 verdict 는 색인에 아예 없음입니다. 성립 사례는 F-24103, 불성립 사례는 embed 성공 줄과 집계 줄이 모두 있는 F-24101 입니다. 9. 7·8번이 아니고 indexed_pages × 10 이 page_count × 9 보다 작으면 verdict 는 일부 쪽만 색인됨입니다. 성립 사례는 F-24101(121/186)·F-24108(8/9), 불성립 사례는 정확히 9/10 인 F-24105 입니다. 10. 7~9번이 아니고 minutes_since_last_log 가 60 을 넘으며 마지막 줄이 index 성공도 실패도 아니면 verdict 는 처리가 1시간 넘게 멈춰 있음입니다. 성립 사례는 F-24104(184분), 불성립 사례는 정확히 60분인 F-24107 이고, F-24107 은 판정하지 않고 처리 중 줄에 둡니다. 11. 7~10번이 아니고 마지막 줄이 index 성공이면 verdict 는 검색에 다 들어감입니다. 성립 사례는 F-24105·F-24106 입니다. 12. summary 가 존댓말 한 문장으로 끝나는지 확인합니다. [수동 검증 · 사람이 봅니다] 1. 정보화 담당자가 판정 묶음만 보고 다시 올릴 파일과 다시 처리할 파일을 나누는지, 문의 대응 한 번으로 봅니다. 2. hedge 를 읽은 코그니텀 엔지니어가 bulk 응답과 작업자 상태 중 무엇부터 확인할지 바로 정하는지 봅니다. [평가 러너 · 2주차] 1. 원인을 알고 심은 파일 40개를 돌려, 판정이 심은 고장과 맞은 파일 수를 셉니다. 2. 검색에 다 들어감 파일마다 Claude Haiku 4.5 가 쓴 질문 세 개로 kNN 검색해, 원래 파일이 상위 5개 안에 돌아온 질문 수를 셉니다.
근거: RAGFlow 기반 문서 처리, AI OCR, 개인정보 비식별화, OpenSearch 관리