인공지능팩토리
경력근거: 데이터 품질 이상을 자동 판정·재처리하는 파이프라인(샘플링·검수 포함)을 만듭니다.
공고는 FDE 가 산림·에너지·원자력 분야 등 공공기관 에이전트 사업에 투입되어 'HWP/PDF 등 기관 문서의 파싱·청킹·임베딩' 으로 RAG 파이프라인을 만들고, 'LLM 튜닝용 QA 데이터셋 생성 파이프라인(LLM 기반 생성·검증 포함)' 과 '데이터 품질 이상을 자동 판정·재처리하는 파이프라인(샘플링·검수 포함)' 을 구축한다고 적습니다. 산출물도 '산출물(보고서·데이터셋·벡터 DB) 납품' 이라 데이터셋 자체가 발주처 검수를 받는 납품물입니다. 같은 사업에서 RAG 용 파싱본도 튜닝용 QA 데이터셋도 FDE 가 만들기 때문에, 파싱본을 QA 의 정답 기준으로 쓰는 검수가 FDE 한 사람의 작업 흐름 안에서 닫힙니다. 우대사항의 한국어 문서(HWP/HWPX, PDF, 스캔 문서) 파싱 경험은 머리글·쪽 번호·표를 걷어 내는 전처리에, Evals·LLM-as-a-judge·HITL 경험은 2·3주차의 평가와 표본 검수에 닿습니다. 기관 문서는 공고가 밝히지 않으므로, 지원자는 공개된 기관 규정 HWP 파일을 직접 파싱하고 그 파싱본으로 LLM 에 QA 를 만들게 해 같은 모양의 입력을 만듭니다. 이 포트폴리오는 LLM 이 만든 QA 를 기관 문서 파싱본에 맞대 전량 판정하는 데이터 품질 파이프라인을 보여줍니다. 원자력 안전 규정이나 공공기관 사업 수행 전문성은 주장하지 않습니다.
주요업무에 'LLM 튜닝용 QA 데이터셋 생성 파이프라인(LLM 기반 생성·검증 포함)을 구축하고, 데이터 품질 이상을 자동 판정·재처리하는 파이프라인(샘플링·검수 포함)을 만듭니다' 가 있습니다. 1주차 판정이 그 자동 판정이고, 2주차에 걸린 답을 다시 쓰게 해 같은 검사를 다시 거치는 일이 재처리, 3주차 표본 검수가 샘플링·검수입니다.
에이전트 지식 기반 구축 업무가 'HWP/PDF 등 기관 문서의 파싱·청킹·임베딩' 으로 시작하고, 우대사항에 한국어 문서(HWP/HWPX, PDF, 스캔 문서) 파싱 경험이 있습니다. 이 CLI 는 그 파싱본의 쪽별 청크를 정답의 기준으로 쓰므로, 머리글 반복·쪽 번호·줄글로 풀린 표 같은 HWP 파싱의 흔적을 숫자·명사 비교 전에 걷어 냅니다.
FDE 는 산림·에너지·원자력 분야 등 공공기관 에이전트 사업에 투입되고, 제안·착수부터 산출물(보고서·데이터셋·벡터 DB) 납품까지 전 주기를 맡습니다. 데이터셋이 납품 산출물이라, 발주처 검수에 들어가기 전에 원문과 다른 쌍을 가려내는 일이 FDE 의 몫입니다.
인공지능팩토리가 공공기관에 납품하는 에이전트 사업에서, 기관 문서로 LLM 튜닝용 질문·답변(QA) 데이터셋을 만드는 FDE 가 발주처에 넘기기 전에 돌리는 CLI 입니다. LLM 이 만든 질문·답변 파일과 HWP 기관 문서의 쪽별 파싱본을 넣으면, 쌍마다 근거로 단 쪽이 파싱본에 있는지, 답의 숫자·단위가 그 쪽 원문에 있는지, 답의 명사가 그 쪽 원문과 얼마나 겹치는지, 띄어쓰기·문장부호만 다른 같은 질문이 먼저 만들어졌는지를 봐서 다섯 판정 가운데 하나로 정합니다. 견주기 전에 쪽마다 되풀이되는 머리글 줄과 쪽 번호 줄을 지우고, 줄글로 풀린 표는 칸 단위로 숫자를 읽습니다. 결과는 판정별 목록과 근거 원문을 담은 검수결과.md 와, 납품 가능한 쌍만 남긴 JSONL 파일로 나옵니다.
공공기관 에이전트 사업에서 LLM 생성 파이프라인으로 튜닝용 QA 데이터셋을 만들어 발주처에 납품하는 FDE(데이터 엔지니어). LLM 이 만든 수천 쌍 가운데 HWP 원문과 숫자가 다르거나 엉뚱한 쪽을 근거로 단 답이 섞이는데, 샘플링 검수로는 일부만 보고 넘기게 된다
LLM 이 만든 질문·답변은 문장이 매끄러워 읽어서는 틀린 곳이 잘 보이지 않고, 수천 쌍 가운데 몇십 쌍만 샘플링해 보면 원문과 다른 숫자가 섞인 쌍이 그대로 납품됩니다. 쌍마다 근거 문서와 쪽이 달려 있고 파싱본에는 쪽별 원문이 있으므로, 숫자·단위와 명사를 그 쪽 원문과 기계적으로 견주면 전량을 볼 수 있습니다. 다만 HWP 파싱본에는 쪽마다 되풀이되는 머리글과 쪽 번호, 줄글로 풀린 표가 섞여 있어 그대로 견주면 머리글 낱말 덕에 엉뚱한 답이 통과합니다. 이 흔적을 먼저 걷어 낸 뒤 견줘야 판정이 섭니다.
FDE 가 qa_audit.py 에 질문·답변 JSONL 과 HWP 문서의 쪽별 파싱본(parquet)을 넣으면, 쌍마다 다섯 판정 가운데 하나와 근거 원문, 걸린 숫자·명사가 검수결과.md 로 나오고 납품 가능한 쌍만 qa_pairs_pass.jsonl 로 남습니다. 숫자·명사를 견주기 전에 같은 문서 여러 쪽에 되풀이되는 머리글 줄과 '- 41 -' 꼴 쪽 번호 줄을 지우고, 줄글로 풀린 표는 칸 단위로 숫자를 읽습니다. 입력은 공개된 기관 규정 HWP 를 PDF 로 바꿔 쪽별로 파싱한 파일과, 그 파싱본으로 LLM 이 만든 QA 에 결함 몇 쌍을 손으로 심은 파일로 시작합니다.
숫자가 다르거나 근거 쪽에 답이 없는 쌍은 Claude Haiku 4.5 가 근거 쪽 원문만 보고 답을 다시 쓰고, 다시 쓴 답은 1주차와 같은 숫자·명사 검사를 통과해야 납품 목록에 들어갑니다. 판정이 맞는지는 원문 숫자 바꾸기·근거 쪽 밀기·띄어쓰기만 바꾼 질문 복제를 심은 QA 300쌍으로 세고, scikit-learn 으로 판정별 정밀도(걸린 쌍 가운데 실제 결함 비율)와 재현율(심은 결함 가운데 걸린 비율)을 리포트에 붙입니다. 2주차부터 GitHub Actions 가 Docker 이미지를 만들고 AWS CodeDeploy 로 팀 검수 서버에 올립니다.
납품 가능 쌍을 문서·쪽 구간별로 나눠 표본을 뽑고, Claude Sonnet 을 LLM-as-a-judge(모델이 채점자 역할)로 같은 표본을 채점하게 해 규칙 판정과 갈린 쌍을 검수 시트(엑셀) 맨 위에 올립니다. FDE 는 그 시트로 사람 검수(HITL)를 돌리고, 사람이 걸러 낸 쌍은 다음 회차 명사 겹침 기준을 정하는 데이터로 남습니다. 파싱·QA 생성·검수·재처리 네 단계는 Airflow DAG 하나로 묶어 문서가 추가될 때마다 같은 순서로 돕니다.
FDE 가 '방사선 안전관리 규정' 과 '방사선관리구역 출입관리 지침' 파싱본에 QA 11쌍을 넣어 qa_audit.py 를 돌리면 납품 가능 5쌍과 걸린 6쌍이 검수결과.md 로 나옵니다. 맨 앞은 원문 표가 '분기 1회' 인데 '연 2회' 라고 답한 QA-01377 과, 쪽 번호 41 을 옮겨 '41일마다' 라고 답한 QA-01988 입니다.
검수결과.md 에서 QA-02210 을 보면 답의 명사 8개 가운데 원문에 나오는 것이 '방사선'·'안전' 2개뿐이라 25.0% 로 걸렸고, 지운 머리글 줄 '방사선 안전관리 규정 | 제3편 시설관리' 가 같이 적혀 있습니다. 근거를 REG-07 212쪽으로 단 QA-03056 은 파싱본이 198쪽에서 끝나 근거 문서를 못 찾음으로, 근거 쪽이 비어 있는(null) QA-02987 도 같은 판정으로 나옵니다.
결함을 심은 QA 300쌍을 돌리면 쌍마다 심은 결함과 판정이 나란히 기록되고, FDE 는 둘이 다른 쌍만 골라 어느 전처리에서 갈렸는지 봅니다. 판정별 정밀도(걸린 쌍 가운데 실제로 결함을 심은 쌍의 비율)와 재현율(심은 결함 가운데 걸린 비율)도 리포트에 붙습니다. 단, 결함과 데이터를 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
쪽 번호가 문자열로 들어온 행, 답이 빈 문자열인 쌍, 청크가 하나도 없는 문서 같은 깨진 입력 30종을 넣어도 검수결과.md 의 형식이 깨지지 않고, 판정할 수 없는 쌍은 이유를 적어 따로 모읍니다.
위 수치는 작성자가 만든 더미데이터 자가채점이며, 이 검수가 발주처 검수 반려를 실제로 줄이는지는 인공지능팩토리 사업의 실제 기관 문서·QA 데이터셋과 발주처 검수 결과가 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
11쌍 중 6쌍이 걸렸다는 수치는 제가 숫자를 바꾸고 쪽을 밀고 질문을 복제해 심은 쌍을 판정이 도로 찾아낸 것이라, 검수기가 제 손을 읽었다는 뜻에서 멈춥니다. 실제 LLM 이 만든 QA 에 이런 결함이 얼마나 섞이는지는 담겨 있지 않습니다.
HWP 파싱본은 쪽마다 '방사선 안전관리 규정 | 제3편 시설관리' 머리글과 '- 41 -' 쪽 번호가 글로 남습니다. QA-02210 은 그 머리글 낱말(규정·시설·관리)만 겹치는 답이라 머리글을 지우지 않으면 명사 겹침이 62.5% 로 통과하고, 지우면 25.0% 로 걸립니다. QA-01988 의 '41일' 은 단위를 빼고 숫자만 견주면 쪽 번호 41 과 맞아 통과합니다. QA-04101 은 QA-00912 와 띄어쓰기·물음표 앞 공백만 다른 질문이라 공백을 지우지 않으면 중복으로 잡히지 않고, 근거 쪽이 null 인 QA-02987 은 빈 쪽을 0 이나 1 로 읽으면 엉뚱한 쪽과 견줘집니다. 줄글로 풀린 41쪽 표의 '비상 발전기 | 주 1회 | 전기팀' 은 칸 단위로 읽어야 QA-03311 의 1회가 원문에 있는 것으로 셉니다.
숫자·단위 쌍만 견주므로 '연·분기·월·주' 같은 주기 낱말은 판정에 들지 않습니다. 원문 표의 '월 1회' 를 '주 1회' 로 바꿔 쓴 답은 같은 1회라 통과하고, '분기 1회' 를 '연 4회' 로 옮긴 맞는 답은 숫자 다름으로 걸립니다. 주기 낱말을 쌍에 넣을지는 2주차 결함 심기 결과를 보고 정합니다.
명사 겹침 50% 는 낱말이 겹치는지만 봅니다. QA-03702 는 원문에 없는 '경비실·신분증' 을 말하는데도 '출입·신청서' 가 겹쳐 50.0% 로 납품 가능이 나옵니다. 명사 수 자체도 형태소 분석기가 정합니다 — kiwipiepy 0.24.0 은 '일시' 를 명사로 세지 않고 '연·주' 는 명사로 세서, 분석기 버전이 바뀌면 기준 근처의 쌍은 판정이 바뀔 수 있습니다. 이런 쌍을 사람이 보게 하려고 3주차에 표본 검수와 LLM-as-a-judge 비교를 둡니다.
HWP 는 쪽이 고정돼 있지 않아 PDF 로 바꾸는 프로그램에 따라 쪽 번호가 달라집니다. QA 를 만들 때와 파싱할 때 다른 변환본을 쓰면 근거 쪽이 통째로 밀려 정상 쌍이 근거 쪽에 답이 없음으로 걸리므로, 두 단계가 같은 변환본을 쓰게 파이프라인을 묶습니다.
실제 기관 문서는 외부 API 로 보낼 수 없는 경우가 많습니다. 2주차 다시 쓰기를 vLLM 으로 띄운 오픈 모델로 바꾸는 일과, 스캔 문서의 OCR(이미지에서 글자를 읽어 내는 처리) 결과를 같은 검수에 넣는 일은 도입 단계 과제로 둡니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
LLM 이 기관 문서 파싱본으로 만든 튜닝용 질문·답변 쌍입니다. 쌍마다 답의 근거로 단 문서와 쪽이 있고, qa_id 는 만든 순서대로 붙습니다. 원문 표와 다른 숫자, 쪽 번호를 옮긴 숫자, 머리글 낱말만 겹치는 답, 파싱본 쪽 수를 넘는 인용, 근거 쪽이 비어 있는(null) 인용, 띄어쓰기만 다른 질문이 섞여 있습니다.
| qa_id | question | answer | doc_id | page | gen_batch |
|---|---|---|---|---|---|
| QA-00655 | 출입 통제 설비는 얼마나 자주 점검합니까? | 출입 통제 설비는 월 1회 시설팀이 점검합니다. | REG-07 | 41 | B01 |
| QA-00912 | 방사선관리구역 출입 기준은? | 방사선관리구역에는 출입 교육을 연 1회 이수하고 개인 선량계를 착용한… | REG-03 | 12 | B01 |
| QA-01377 | 방사선 측정기 점검 주기는 얼마입니까? | 방사선 측정기는 연 2회 점검합니다. | REG-07 | 41 | B02 |
| QA-01988 | 출입통제 설비는 얼마나 자주 점검합니까? | 출입 통제 설비는 41일마다 점검합니다. | REG-07 | 41 | B02 |
| QA-02210 | 관리구역 출입 기록은 누가 어디에 제출합니까? | 방사선 안전관리 규정에 따라 시설관리 담당자가 매월 감사실에 제출합니다. | REG-07 | 88 | B03 |
아래 JSON Schema 에 맞는 질문·답변 행을 더 만들어 주세요. {"type":"object","properties":{"qa_id":{"type":"string"},"question":{"type":"string"},"answer":{"type":"string"},"doc_id":{"type":"string"},"page":{"type":["integer","null"],"minimum":1},"gen_batch":{"type":"string"}},"required":["qa_id","question","answer","doc_id","page","gen_batch"]} 쉬운 데이터만 만들지 마세요. 다음 경우를 의도적으로 섞으세요: (1) 근거 쪽 원문 표의 숫자를 다른 숫자로 바꾼 답, (2) 근거 쪽의 쪽 번호를 숫자로 옮겨 쓴 답, (3) 머리글(문서 이름·편 이름) 낱말만 겹치고 본문과는 무관한 답, (4) 파싱본 마지막 쪽보다 뒤를 근거로 단 쌍, (5) page 가 null 인 쌍, (6) 앞선 쌍의 질문을 띄어쓰기·문장부호만 바꿔 다시 만든 쌍. qa_id 는 만든 순서대로 커지게 하고, 모든 행은 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 따로 3건만 표시해 두세요.{"type":"object","properties":{"qa_id":{"type":"string"},"question":{"type":"string"},"answer":{"type":"string"},"doc_id":{"type":"string"},"page":{"type":["integer","null"],"minimum":1},"gen_batch":{"type":"string"}},"required":["qa_id","question","answer","doc_id","page","gen_batch"]}공개된 기관 규정 HWP 를 PDF 로 바꿔 쪽별로 파싱한 청크입니다. 쪽마다 머리말(문서 이름 | 편 이름)과 꼬리말 쪽 번호('- 41 -')가 글로 남아 있고, 표는 칸을 ' | ' 로 이은 줄글로 풀려 있습니다. 한 쪽이 청크 두 개로 나뉜 쪽(REG-07 41쪽)과 표 머리행이 다음 쪽에 되풀이되는 쪽(42쪽)이 있고, REG-07 파싱본은 198쪽에서 끝납니다.
| doc_id | source_file | page | chunk_no | chunk_text |
|---|---|---|---|---|
| REG-03 | 방사선관리구역_출입관리_지침.hwp | 1 | 1 | 방사선관리구역 출입관리 지침 2026. 1. 개정 방사선안전팀 |
| REG-03 | 방사선관리구역_출입관리_지침.hwp | 12 | 1 | 방사선관리구역 출입관리 지침 | 제2장 출입 통제 제8조(출입 자격)… |
| REG-03 | 방사선관리구역_출입관리_지침.hwp | 13 | 1 | 방사선관리구역 출입관리 지침 | 제2장 출입 통제 제9조(일시 출입)… |
| REG-07 | 방사선_안전관리_규정.hwp | 1 | 1 | 방사선 안전관리 규정 제3편 시설관리 2026년 개정판 |
| REG-07 | 방사선_안전관리_규정.hwp | 41 | 1 | 방사선 안전관리 규정 | 제3편 시설관리 제27조(설비 점검) ① 설… |
아래 JSON Schema 에 맞는 쪽별 청크 행을 더 만들어 주세요. {"type":"object","properties":{"doc_id":{"type":"string"},"source_file":{"type":"string"},"page":{"type":"integer","minimum":1},"chunk_no":{"type":"integer","minimum":1},"chunk_text":{"type":"string"}},"required":["doc_id","source_file","page","chunk_no","chunk_text"]} 쉬운 데이터만 만들지 마세요. 다음 경우를 의도적으로 섞으세요: (1) 첫 줄이 '문서 이름 | 편 이름' 머리말이고 마지막 줄이 '- 쪽 -' 꼬리말인 본문 쪽, (2) 칸을 ' | ' 로 이은 줄글 표가 들어 있는 쪽, (3) 한 쪽이 chunk_no 1·2 두 청크로 나뉜 쪽, (4) 표 머리행이 다음 쪽 첫머리에 되풀이되는 쪽, (5) 머리말이 없는 표지 쪽. 같은 doc_id 의 source_file 은 하나로 두고, 모든 행은 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 따로 3건만 표시해 두세요.{"type":"object","properties":{"doc_id":{"type":"string"},"source_file":{"type":"string"},"page":{"type":"integer","minimum":1},"chunk_no":{"type":"integer","minimum":1},"chunk_text":{"type":"string"}},"required":["doc_id","source_file","page","chunk_no","chunk_text"]}| Python | qa_audit.py CLI 와 머리글 제거·숫자 추출·판정 코드를 씁니다. |
| pandas | 쪽별 파싱본 parquet 을 읽어 doc_id·page 로 청크를 잇고, 문서별 마지막 쪽과 판정별 건수를 집계합니다. |
| HWP/HWPX 파싱 | HWP 를 LibreOffice(H2Orestart 확장)로 PDF 로 바꾼 뒤 PyMuPDF 로 쪽별 글을 뽑아 doc_chunks 를 만듭니다. 머리말·꼬리말이 쪽마다 글로 남는 것이 이 단계의 흔적입니다. |
| Kiwi | 답과 원문에서 일반·고유 명사를 뽑아 겹침 비율을 셉니다. 인터넷 없이 도는 한국어 형태소 분석기이고, 분절이 버전마다 달라 kiwipiepy 0.24.0 으로 고정해 Docker 이미지에 넣습니다. |
| scikit-learn | 2주차에 심은 결함과 판정을 견줘 판정별 정밀도·재현율을 냅니다. |
| Claude Haiku 4.5 | 1주차 입력용 QA 를 파싱본으로 만들고, 2주차에 걸린 답을 근거 쪽 원문만 보고 다시 씁니다. |
| Claude Sonnet | 3주차에 표본 쌍을 LLM-as-a-judge 로 채점해 규칙 판정과 갈린 쌍을 찾습니다. |
| Claude Code | CLI 와 전처리 구현을 맡기고, 하드 케이스 테스트와 판정 결과를 직접 검토해 받아들입니다. |
| Docker | CLI 와 Kiwi 모델 파일을 한 이미지로 묶어 팀 서버와 현장 서버에서 같은 결과를 냅니다. |
| GitHub | 머리글 반복·쪽 번호·줄글 표·파싱본 쪽 수를 넘는 인용 테스트가 통과해야 이미지를 만드는 Actions 를 둡니다. |
| AWS CodeDeploy | 2주차부터 검수 이미지를 팀 검수 서버에 배포합니다. |
| PostgreSQL | 3주차에 회차별 판정과 사람 검수 결과를 쌓아 문서별 걸림 비율을 봅니다. |
| Airflow | 3주차에 파싱·QA 생성·검수·재처리를 DAG 하나로 묶습니다. |
✗ fail → QA-02210 이 통과하면 머리글 줄을 지우지 못한 것이라, 같은 문서 여러 쪽의 첫 줄을 모아 되풀이되는 앞부분을 찾는 단계부터 고칩니다.
✓ pass → 그대로 멈춰도 FDE 가 납품 전에 전량을 가려내는 완결품입니다. 더 키우려면 걸린 답 다시 쓰기와 결함을 심은 300쌍 평가를 붙이고 AWS CodeDeploy 로 팀 서버에 올립니다.
✗ fail → 다시 쓴 답이 계속 숫자에서 걸리면 프롬프트에 넣는 본문을 표 칸 단위로 다시 자르고, 표본 검수는 미룹니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 표본 검수 시트와 LLM-as-a-judge 비교, Airflow DAG 로 넓힙니다.
QA 납품 검수 판정입니다. 질문·답변 한 쌍이 다섯 판정 중 어디에 서는지와, 판정에 쓴 근거 쪽 원문의 숫자·명사, 지운 머리글·쪽 번호 줄, 중복이면 먼저 만든 쌍을 남기는 형식입니다. 검수결과.md 의 한 줄이 이 객체 하나에서 나오고, summary 는 규칙이 수치를 채우는 고정 문형입니다.
{"type":"object","properties":{"qa_id":{"type":"string"},"doc_id":{"type":"string"},"page":{"type":["integer","null"]},"verdict":{"type":"string","enum":["납품 가능","근거 문서를 못 찾음","답의 숫자가 원문과 다름","근거 쪽에 답이 없음","같은 질문 중복"],"description":"걸린 판정이 여럿이면 근거 문서를 못 찾음, 답의 숫자가 원문과 다름, 근거 쪽에 답이 없음, 같은 질문 중복 순으로 앞선 하나입니다. 하나도 걸리지 않으면 납품 가능입니다"},"also_found":{"type":"array","items":{"type":"string","enum":["근거 문서를 못 찾음","답의 숫자가 원문과 다름","근거 쪽에 답이 없음","같은 질문 중복"]},"description":"verdict 로 고르지 않은 나머지 판정입니다"},"answer_numbers":{"type":"array","items":{"type":"string"},"description":"answer 에서 뽑은 숫자·단위 쌍입니다. 쉼표와 숫자·단위 사이 띄어쓰기를 지운 꼴(예: 3 년 → 3년)로 적습니다"},"source_numbers":{"type":"array","items":{"type":"string"},"description":"같은 doc_id·page 의 청크를 chunk_no 순으로 이어 stripped_lines 를 지운 본문에서 뽑은 숫자·단위 쌍입니다. 근거 쪽이 없으면 빈 배열입니다"},"unmatched_numbers":{"type":"array","items":{"type":"string"},"description":"answer_numbers 가운데 source_numbers 에 없는 쌍입니다"},"answer_nouns":{"type":"array","items":{"type":"string"},"description":"Kiwi 형태소 분석으로 answer 에서 뽑은 일반·고유 명사(NNG·NNP)의 중복 없는 목록입니다"},"matched_nouns":{"type":"array","items":{"type":"string"},"description":"answer_nouns 가운데 stripped_lines 를 지운 본문의 명사에도 있는 것입니다"},"noun_overlap_pct":{"type":["number","null"],"minimum":0,"maximum":100,"description":"정적 계산값입니다. matched_nouns 수 ÷ answer_nouns 수 × 100 을 소수 첫째 자리에서 반올림합니다. 기준은 CLI 옵션 --min-overlap(기본 50)이고, 근거 쪽이 없으면 null 입니다"},"duplicate_of":{"type":["string","null"],"description":"question 에서 띄어쓰기·문장부호를 지운 글이 같은 쌍 가운데 qa_id 가 가장 앞선 쌍입니다. 없으면 null 입니다"},"stripped_lines":{"type":"array","items":{"type":"string"},"description":"숫자·명사를 견주기 전에 지운 머리글 줄과 쪽 번호 줄입니다"},"evidence":{"type":"array","items":{"type":"object","properties":{"table":{"type":"string","enum":["qa_pairs","doc_chunks"]},"row":{"type":["string","null"],"description":"qa_pairs 는 qa_id, doc_chunks 는 '문서 p.쪽 #청크' 입니다. agg 항목이면 null 입니다"},"field":{"type":"string"},"value":{"type":["string","integer","null"]},"quote":{"type":"string","description":"chunk_text 의 한 줄 안에서 그대로 딴 구절입니다"},"agg":{"type":"string","enum":["count","sum","avg","min","max"],"description":"같은 doc_id 의 doc_chunks 행에서 모은 값이면 적습니다"}},"required":["table","row","field"]}},"summary":{"type":"string"}},"required":["qa_id","doc_id","page","verdict","also_found","answer_numbers","source_numbers","unmatched_numbers","answer_nouns","matched_nouns","noun_overlap_pct","duplicate_of","stripped_lines","evidence","summary"]}[
{
"qa_id": "QA-00912",
"doc_id": "REG-03",
"page": 12,
"verdict": "납품 가능",
"also_found": [],
"answer_numbers": [
"1회"
],
"source_numbers": [
"8조",
"1회"
],
"unmatched_numbers": [],
"answer_nouns": [
"방사선",
"관리",
"구역",
"출입",
"교육",
"연",
"이수",
"개인",
"선량계",
"착용",
"사람"
],
"matched_nouns": [
"방사선",
"관리",
"구역",
"출입",
"교육",
"연",
"이수",
"개인",
"선량계",
"착용",
"사람"
],
"noun_overlap_pct": 100,
"duplicate_of": null,
"stripped_lines": [
"방사선관리구역 출입관리 지침 | 제2장 출입 통제",
"- 12 -"
],
"evidence": [
{
"table": "qa_pairs",
"row": "QA-00912",
"field": "answer",
"value": "방사선관리구역에는 출입 교육을 연 1회 이수하고 개인 선량계를 착용한 사람만 출입할 수 있습니다."
},
{
"table": "qa_pairs",
"row": "QA-00912",
"field": "page",
"value": 12
},
{
"table": "doc_chunks",
"row": "REG-03 p.12 #1",
"field": "chunk_text",
"quote": "방사선관리구역에는 출입 교육을 연 1회 이수하고 개인 선량계를 착용한 사람만 출입할 수 있다."
}
],
"summary": "REG-03 12쪽 본문에 답의 숫자 1회가 있고 답의 명사 11개가 모두 나와 납품할 수 있습니다."
},
{
"qa_id": "QA-03311",
"doc_id": "REG-07",
"page": 41,
"verdict": "납품 가능",
"also_found": [],
"answer_numbers": [
"1회"
],
"source_numbers": [
"27조",
"1회"
],
"unmatched_numbers": [],
"answer_nouns": [
"비상",
"발전기",
"주",
"전기",
"팀",
"점검"
],
"matched_nouns": [
"비상",
"발전기",
"주",
"전기",
"팀",
"점검"
],
"noun_overlap_pct": 100,
"duplicate_of": null,
"stripped_lines": [
"방사선 안전관리 규정 | 제3편 시설관리",
"- 41 -"
],
"evidence": [
{
"table": "qa_pairs",
"row": "QA-03311",
"field": "answer",
"value": "비상 발전기는 주 1회 전기팀이 점검합니다."
},
{
"table": "qa_pairs",
"row": "QA-03311",
"field": "page",
"value": 41
},
{
"table": "doc_chunks",
"row": "REG-07 p.41 #2",
"field": "chunk_text",
"quote": "비상 발전기 | 주 1회 | 전기팀"
}
],
"summary": "41쪽 표의 '비상 발전기 | 주 1회 | 전기팀' 칸에서 1회를 읽어 답의 숫자와 맞고, 답의 명사 6개가 모두 나와 납품할 수 있습니다."
},
{
"qa_id": "QA-00655",
"doc_id": "REG-07",
"page": 41,
"answer_numbers": [
"1회"
],
"source_numbers": [
"27조",
"1회"
],
"unmatched_numbers": [],
"answer_nouns": [
"출입",
"통제",
"설비",
"월",
"시설",
"팀",
"점검"
],
"matched_nouns": [
"출입",
"통제",
"설비",
"월",
"시설",
"팀",
"점검"
],
"noun_overlap_pct": 100,
"duplicate_of": null,
"stripped_lines": [
"방사선 안전관리 규정 | 제3편 시설관리",
"- 41 -"
],
"verdict": "납품 가능",
"also_found": [],
"evidence": [
{
"table": "qa_pairs",
"row": "QA-00655",
"field": "answer",
"value": "출입 통제 설비는 월 1회 시설팀이 점검합니다."
},
{
"table": "qa_pairs",
"row": "QA-00655",
"field": "page",
"value": 41
},
{
"table": "doc_chunks",
"row": "REG-07 p.41 #2",
"field": "chunk_text",
"quote": "출입 통제 설비 | 월 1회 | 시설팀"
}
],
"summary": "41쪽 표의 '출입 통제 설비 | 월 1회 | 시설팀' 칸에서 1회를 읽어 답의 숫자와 맞고, 답의 명사 7개가 모두 나와 납품할 수 있습니다."
},
{
"qa_id": "QA-02455",
"doc_id": "REG-07",
"page": 88,
"answer_numbers": [
"3년"
],
"source_numbers": [
"52조",
"3년"
],
"unmatched_numbers": [],
"answer_nouns": [
"출입",
"기록",
"보존"
],
"matched_nouns": [
"출입",
"기록",
"보존"
],
"noun_overlap_pct": 100,
"duplicate_of": null,
"stripped_lines": [
"방사선 안전관리 규정 | 제3편 시설관리",
"- 88 -"
],
"verdict": "납품 가능",
"also_found": [],
"evidence": [
{
"table": "qa_pairs",
"row": "QA-02455",
"field": "answer",
"value": "출입 기록은 3 년간 보존합니다."
},
{
"table": "qa_pairs",
"row": "QA-02455",
"field": "page",
"value": 88
},
{
"table": "doc_chunks",
"row": "REG-07 p.88 #1",
"field": "chunk_text",
"quote": "3년간 보존한다"
}
],
"summary": "답의 '3 년' 은 띄어쓰기를 지워 3년으로 맞추면 88쪽 본문의 3년간과 같고, 답의 명사 3개가 모두 나와 납품할 수 있습니다."
},
{
"qa_id": "QA-01377",
"doc_id": "REG-07",
"page": 41,
"verdict": "답의 숫자가 원문과 다름",
"also_found": [],
"answer_numbers": [
"2회"
],
"source_numbers": [
"27조",
"1회"
],
"unmatched_numbers": [
"2회"
],
"answer_nouns": [
"방사선",
"측정기",
"연",
"점검"
],
"matched_nouns": [
"방사선",
"측정기",
"점검"
],
"noun_overlap_pct": 75,
"duplicate_of": null,
"stripped_lines": [
"방사선 안전관리 규정 | 제3편 시설관리",
"- 41 -"
],
"evidence": [
{
"table": "qa_pairs",
"row": "QA-01377",
"field": "answer",
"value": "방사선 측정기는 연 2회 점검합니다."
},
{
"table": "qa_pairs",
"row": "QA-01377",
"field": "page",
"value": 41
},
{
"table": "doc_chunks",
"row": "REG-07 p.41 #2",
"field": "chunk_text",
"quote": "방사선 측정기 | 분기 1회 | 방사선안전팀"
}
],
"summary": "41쪽 표는 방사선 측정기를 분기 1회 점검한다고 적는데 답은 연 2회라고 해, 원문에 없는 숫자 2회가 있습니다."
},
{
"qa_id": "QA-01988",
"doc_id": "REG-07",
"page": 41,
"verdict": "답의 숫자가 원문과 다름",
"also_found": [
"같은 질문 중복"
],
"answer_numbers": [
"41일"
],
"source_numbers": [
"27조",
"1회"
],
"unmatched_numbers": [
"41일"
],
"answer_nouns": [
"출입",
"통제",
"설비",
"점검"
],
"matched_nouns": [
"출입",
"통제",
"설비",
"점검"
],
"noun_overlap_pct": 100,
"duplicate_of": "QA-00655",
"stripped_lines": [
"방사선 안전관리 규정 | 제3편 시설관리",
"- 41 -"
],
"evidence": [
{
"table": "qa_pairs",
"row": "QA-01988",
"field": "answer",
"value": "출입 통제 설비는 41일마다 점검합니다."
},
{
"table": "qa_pairs",
"row": "QA-01988",
"field": "question",
"value": "출입통제 설비는 얼마나 자주 점검합니까?"
},
{
"table": "qa_pairs",
"row": "QA-00655",
"field": "question",
"value": "출입 통제 설비는 얼마나 자주 점검합니까?"
},
{
"table": "doc_chunks",
"row": "REG-07 p.41 #2",
"field": "chunk_text",
"quote": "출입 통제 설비 | 월 1회 | 시설팀"
},
{
"table": "doc_chunks",
"row": "REG-07 p.41 #2",
"field": "chunk_text",
"quote": "- 41 -"
}
],
"summary": "답의 41일은 쪽 번호 줄 '- 41 -' 을 지운 본문 어디에도 없고 표는 월 1회라고 적어 숫자가 다르며, 먼저 만든 QA-00655 와 같은 질문이기도 합니다."
},
{
"qa_id": "QA-02210",
"doc_id": "REG-07",
"page": 88,
"verdict": "근거 쪽에 답이 없음",
"also_found": [],
"answer_numbers": [],
"source_numbers": [
"52조",
"3년"
],
"unmatched_numbers": [],
"answer_nouns": [
"방사선",
"안전",
"관리",
"규정",
"시설",
"담당자",
"감사실",
"제출"
],
"matched_nouns": [
"방사선",
"안전"
],
"noun_overlap_pct": 25,
"duplicate_of": null,
"stripped_lines": [
"방사선 안전관리 규정 | 제3편 시설관리",
"- 88 -"
],
"evidence": [
{
"table": "qa_pairs",
"row": "QA-02210",
"field": "answer",
"value": "방사선 안전관리 규정에 따라 시설관리 담당자가 매월 감사실에 제출합니다."
},
{
"table": "qa_pairs",
"row": "QA-02210",
"field": "page",
"value": 88
},
{
"table": "doc_chunks",
"row": "REG-07 p.88 #1",
"field": "chunk_text",
"quote": "출입 기록은 출입 통제 단말에 자동으로 남기며 3년간 보존한다."
},
{
"table": "doc_chunks",
"row": "REG-07 p.88 #1",
"field": "chunk_text",
"quote": "방사선 안전관리 규정 | 제3편 시설관리"
}
],
"summary": "머리글 줄을 지운 88쪽 본문에는 답의 명사 8개 가운데 방사선·안전 2개만 나와 25.0% 로 기준 50% 에 못 미칩니다."
},
{
"qa_id": "QA-03702",
"doc_id": "REG-03",
"page": 13,
"answer_numbers": [],
"source_numbers": [
"9조"
],
"unmatched_numbers": [],
"answer_nouns": [
"출입",
"신청서",
"경비실",
"신분증"
],
"matched_nouns": [
"출입",
"신청서"
],
"noun_overlap_pct": 50,
"duplicate_of": null,
"stripped_lines": [
"방사선관리구역 출입관리 지침 | 제2장 출입 통제",
"- 13 -"
],
"verdict": "납품 가능",
"also_found": [],
"evidence": [
{
"table": "qa_pairs",
"row": "QA-03702",
"field": "answer",
"value": "일시 출입자는 출입 신청서를 내고 경비실에 신분증을 맡깁니다."
},
{
"table": "qa_pairs",
"row": "QA-03702",
"field": "page",
"value": 13
},
{
"table": "doc_chunks",
"row": "REG-03 p.13 #1",
"field": "chunk_text",
"quote": "일시 출입자는 출입 신청서를 하루 전까지 제출하고"
}
],
"summary": "답의 명사 4개 가운데 출입·신청서 2개가 13쪽에 나와 50.0% 로 기준 50 과 같아 걸리지 않습니다. 경비실·신분증은 원문에 없지만 이 판정은 낱말이 겹치는 비율만 봅니다."
},
{
"qa_id": "QA-03056",
"doc_id": "REG-07",
"page": 212,
"verdict": "근거 문서를 못 찾음",
"also_found": [],
"answer_numbers": [
"15분"
],
"source_numbers": [],
"unmatched_numbers": [],
"answer_nouns": [
"방사선",
"비상",
"발령",
"현장",
"지휘자",
"이내",
"대책",
"실",
"보고"
],
"matched_nouns": [],
"noun_overlap_pct": null,
"duplicate_of": null,
"stripped_lines": [],
"evidence": [
{
"table": "qa_pairs",
"row": "QA-03056",
"field": "doc_id",
"value": "REG-07"
},
{
"table": "qa_pairs",
"row": "QA-03056",
"field": "page",
"value": 212
},
{
"table": "doc_chunks",
"row": null,
"field": "page",
"agg": "max",
"value": 198
}
],
"summary": "REG-07 파싱본은 198쪽에서 끝나 답이 근거로 단 212쪽이 없습니다."
},
{
"qa_id": "QA-02987",
"doc_id": "REG-03",
"page": null,
"verdict": "근거 문서를 못 찾음",
"also_found": [],
"answer_numbers": [],
"source_numbers": [],
"unmatched_numbers": [],
"answer_nouns": [
"관리",
"구역",
"안",
"음식물",
"섭취",
"흡연",
"금지"
],
"matched_nouns": [],
"noun_overlap_pct": null,
"duplicate_of": null,
"stripped_lines": [],
"evidence": [
{
"table": "qa_pairs",
"row": "QA-02987",
"field": "doc_id",
"value": "REG-03"
},
{
"table": "qa_pairs",
"row": "QA-02987",
"field": "page",
"value": null
}
],
"summary": "답이 근거 쪽을 적지 않아(page 가 null) REG-03 의 어느 쪽과도 견줄 수 없습니다."
},
{
"qa_id": "QA-04101",
"doc_id": "REG-03",
"page": 12,
"verdict": "같은 질문 중복",
"also_found": [],
"answer_numbers": [
"1회"
],
"source_numbers": [
"8조",
"1회"
],
"unmatched_numbers": [],
"answer_nouns": [
"출입",
"교육",
"연",
"이수",
"개인",
"선량계",
"착용"
],
"matched_nouns": [
"출입",
"교육",
"연",
"이수",
"개인",
"선량계",
"착용"
],
"noun_overlap_pct": 100,
"duplicate_of": "QA-00912",
"stripped_lines": [
"방사선관리구역 출입관리 지침 | 제2장 출입 통제",
"- 12 -"
],
"evidence": [
{
"table": "qa_pairs",
"row": "QA-04101",
"field": "question",
"value": "방사선 관리구역 출입 기준은 ?"
},
{
"table": "qa_pairs",
"row": "QA-00912",
"field": "question",
"value": "방사선관리구역 출입 기준은?"
}
],
"summary": "띄어쓰기와 물음표 앞 공백을 지우면 먼저 만든 QA-00912 의 질문과 같아, 나중에 만든 이 쌍을 중복으로 뺍니다."
}
][자동 검증 · 코드가 두 표에서 다시 계산합니다] 1. verdict 가 납품 가능·근거 문서를 못 찾음·답의 숫자가 원문과 다름·근거 쪽에 답이 없음·같은 질문 중복 다섯 값 중 하나인지, also_found 가 납품 가능을 뺀 네 값 중 하나인지 확인합니다. 2. 필수 필드 qa_id·doc_id·page·verdict·also_found·answer_numbers·source_numbers·unmatched_numbers·answer_nouns·matched_nouns·noun_overlap_pct·duplicate_of·stripped_lines·evidence·summary 가 모두 존재하는지 확인합니다. 3. evidence 의 field 가 qa_pairs·doc_chunks 에 실재하는 컬럼인지 대조합니다. 4. agg 가 없는 evidence 의 value 가 그 컬럼에 수록된 샘플 행 값 그대로인지, quote 가 doc_chunks.chunk_text 원문의 substring 인지 대조합니다. QA-02987 의 page 는 null 그대로 실립니다. 5. stripped_lines 를 다시 계산합니다. 같은 doc_id 의 청크를 page·chunk_no 순으로 이어, 두 쪽 이상에서 첫 줄의 ' | ' 앞부분이 같으면 그 첫 줄을 머리글로, '- 숫자 -' 꼴 마지막 줄을 쪽 번호로 지웁니다. MAN-02 처럼 파싱본에 한 쪽만 있는 문서는 머리글을 지우지 않습니다. 6. source_numbers·answer_numbers·unmatched_numbers 를 다시 계산하고, noun_overlap_pct 를 matched_nouns 수 ÷ answer_nouns 수 × 100 으로 다시 계산합니다. 명사는 kiwipiepy 0.24.0 의 NNG·NNP 를 등장 순서대로 중복 없이 모은 것이고, 숫자·단위 쌍은 등장 순서입니다. '전기팀' 이 '전기'·'팀' 으로, '연 1회' 의 '연' 이 명사로 갈리는 것은 분석기가 정한 그대로 둡니다. 7. 다시 계산해 qa_pairs 의 doc_id·page 짝이 doc_chunks 의 doc_id·page 에 없으면 판정에 근거 문서를 못 찾음이 들어갑니다. 성립 사례는 파싱본이 198쪽에서 끝나는 REG-07 의 212쪽을 단 QA-03056 과 page 가 null 인 QA-02987, 불성립 사례는 REG-07 41쪽을 단 QA-03311 입니다. 8. unmatched_numbers 가 하나라도 있으면 판정에 답의 숫자가 원문과 다름이 들어갑니다. 성립 사례는 QA-01377('2회')과 쪽 번호 줄을 지운 뒤의 QA-01988('41일'), 불성립 사례는 '3 년' 을 '3년' 으로 맞춘 QA-02455 와 표 칸의 '1회' 를 읽은 QA-03311 입니다. 9. 다시 계산한 noun_overlap_pct 가 기준(기본 50) 미만이면 판정에 근거 쪽에 답이 없음이 들어갑니다. 성립 사례는 머리글을 지운 뒤 25.0% 인 QA-02210, 불성립 사례는 기준과 같은 50.0% 인 QA-03702 입니다. 10. 다시 계산한 duplicate_of 가 null 이 아니면 판정에 같은 질문 중복이 들어갑니다. 성립 사례는 QA-00912 를 가리키는 QA-04101, 불성립 사례는 먼저 만든 QA-00912 입니다. 11. 판정이 둘 이상 걸리면 verdict 는 근거 문서 → 숫자 → 명사 → 중복 순서로 먼저 걸린 값이고 나머지는 also_found 에 남습니다. 성립 사례는 QA-01988(대표 숫자 다름, 같이 걸린 중복), 불성립 사례는 하나만 걸린 QA-01377 입니다. 12. summary 가 존댓말로 끝나는지 확인합니다. [수동 검증 · 사람이 봅니다] 1. FDE 가 검수결과.md 의 걸린 쌍만 읽고 다시 생성할지, 근거를 다시 달지, 지울지 정하는지 봅니다. 2. 발주처 담당자에게 검수결과.md 의 판정별 집계를 설명 없이 보여 줄 수 있는지 봅니다. [평가 러너 · 2주차] 1. 결함을 심은 QA 300쌍에서 심은 결함과 판정이 같은 쌍을 세고 판정별 정밀도·재현율을 냅니다. 2. 다시 쓴 답 가운데 1주차 검사를 다시 통과한 쌍과 다시 걸린 쌍을 셉니다.