26화 2026-06-22

기일지정서를 읽고 일정을 만들기까지

지난 화에서 gog 인증을 끝내, 에이전트가 구글 드라이브와 캘린더에 손을 댈 수 있게 됐다. 이번 화는 그 손으로 실제 문서를 다룬다. 목표는 이렇다.

스캔 폴더에 올라온 기일지정서를 가져와, 로컬 모델에게 읽히고, 그 내용으로 캘린더에 일정을 만든다.

만들려는 전체 시스템은 부품이 여럿이다. 새 파일 감지, 다운로드, 모델 판독, 종류 판별, 정보 추출, 파일 이동, 일정 등록, 통보. 이번에는 그중 가장 안쪽 부품 — PDF 한 장을 받아 모델이 내용을 정확히 뽑아내고, 그 정보로 일정이 만들어지는지 — 를 손으로 끝까지 돌려 검증한다. 자동 감지나 자동 이동 같은 바깥 껍데기는 이 안쪽이 확실해진 뒤의 일이다.


드라이브에서 파일 찾기

서류는 스캐너를 거쳐 드라이브의 Ai Test > SCAN 폴더로 올라온다. 이 폴더를 gog로 들여다본다. 폴더를 지정하려면 폴더의 고유 ID가 필요한데, 이 ID는 드라이브 웹에서 해당 폴더에 들어갔을 때 주소창의 /folders/ 뒤에 붙는 문자열이다.

스크린샷 2026-06-22 23-26-10.png

gog drive ls --parent <SCAN폴더ID>

gog drive ls는 폴더 안 파일 목록을 보여주는 명령이고, --parent로 들여다볼 폴더를 지정한다. 기일지정서 한 장을 올려둔 상태에서 실행하니 이렇게 나왔다.

ID                                 NAME        TYPE  SIZE      MODIFIED          OWNER
1Cylvd...cka70                     123abc.PDF  file  873.5 KB  2026-06-22 13:16  ...

ID 열의 값이 이 파일의 고유 식별자다. 다운로드할 때 이 값을 쓴다. 웹 화면에서 본 파일명·크기와 일치하니, 인증된 gog가 이 폴더를 제대로 읽고 있다는 것이 확인됐다.


파일 받기

파일 ID로 ATOM에 내려받는다. 검증용이라 임시 폴더(/tmp)에 받았다.

gog drive download <파일ID> --out /tmp/123abc.pdf

gog drive download는 파일을 내려받는 명령이고, --out으로 저장할 경로와 이름을 지정한다. 받은 뒤 제대로 내려왔는지 확인했다.

file /tmp/123abc.pdf

file은 파일의 실제 종류를 알려주는 명령이다.

/tmp/123abc.pdf: PDF document, version 1.4, 3 page(s)

PDF document, 3 page(s). 3페이지짜리 공판기일통지서가 그대로 내려왔다.


PDF를 이미지로 바꾸기

이번 작업에 쓸 모델은 qwen3-vl:8b-instruct다. 인쇄된 형사기록을 읽혀 검증해둔 비전 모델이다. 비전 모델은 이미지를 입력으로 받는다. PDF를 그대로 먹지 못하므로, 페이지별 이미지로 먼저 쪼개야 한다.

pdftoppm -png -r 200 /tmp/123abc.pdf /tmp/123abc

pdftoppm은 PDF를 페이지별 이미지로 변환하는 명령이다. -png는 출력 형식, -r 200은 해상도(200 DPI — 인쇄 텍스트가 또렷하게 읽히는 정도다. 너무 낮으면 글자가 뭉개지고, 너무 높으면 이미지가 커져 모델이 느려진다), 마지막 /tmp/123abc는 출력 파일명의 앞부분이다. 결과로 123abc-1.png, 123abc-2.png, 123abc-3.png 세 장이 만들어졌다. 페이지별 크기를 보니 본문이 가장 많은 1페이지가 가장 컸고, 거의 빈 2페이지가 가장 작았다.


모델에게 그대로 읽혀보기

먼저 아무 지시 없이 “보이는 글자를 그대로 옮겨라”만 시켰다. “기일을 뽑아라” 같은 지시를 먼저 주면, 모델이 문서를 제대로 못 읽었어도 그럴듯한 답을 지어낼 여지가 생긴다. 그래서 날것으로 무엇을 읽어내는지부터 봤다.

ollama run qwen3-vl:8b-instruct "이 문서의 내용을 그대로 읽어서 텍스트로 옮겨줘. 추측하지 말고 보이는 글자만." /tmp/123abc-1.png

ollama run <모델> "<프롬프트>" <이미지경로> 형식이다. 1페이지 이미지를 넣었다. 결과는 거의 정확했다.

OO지방법원
공판기일통지서
사 건   2025노OOOO  스토킹범죄의처벌등에관한법률위반
피 고 인   김OO
위 사건에 관하여 다음과 같이 공판기일이 지정되었음을 통지합니다.
공 판 기 일   2026. 6. 11.(목) 15:00
개 정 장 소   OO지방법원 408호 법정
2026. 5. 25.
법원사무관   김OO
...

법원, 사건번호, 죄명, 피고인, 공판기일, 장소를 전부 제 위치에 옮겼다. 인쇄 텍스트라 글자 인식이 깨끗했다. 안내문구 한 곳에서 “전화안내”를 “전화인내”로 잘못 읽은 것 외에는 오독이 없었는데, 그 부분은 우리가 쓸 정보가 아니라 실무에는 영향이 없다. 24화에서 자필·표 구조는 위험했지만 인쇄 텍스트는 잘 읽혔던 결론과 일관된다.

여기서 눈여겨볼 점이 하나 있다. 1페이지에는 날짜가 두 개 있다. 문서 작성일 “2026. 5. 25.”와 공판기일 “2026. 6. 11.”이다. 모델은 둘을 섞지 않고 각각 제 위치에 옮겼다.


필요한 정보만 뽑아내기

순수 판독이 됐으니, 한 단계 올렸다. 자동 처리에서 모델이 실제로 해야 할 일은 “그냥 읽기”가 아니라 “이게 기일지정서인지 판단하고, 필요한 값만 구조화해서 내놓기”다. JSON 형식으로 뽑게 했다.

ollama run qwen3-vl:8b-instruct "이 문서를 보고 아래 형식의 JSON만 출력해. 다른 말 하지 마.
{
  \"문서종류\": \"\",
  \"기일지정서여부\": true 또는 false,
  \"피고인\": \"\",
  \"사건번호\": \"\",
  \"기일일시\": \"YYYY-MM-DD HH:MM\",
  \"장소\": \"\"
}
주의: '기일일시'는 공판기일을 뜻한다. 문서 작성일이나 발송일이 아니다. 문서에 날짜가 여러 개면 '공판기일'이라고 적힌 날짜만 골라라." /tmp/123abc-1.png

프롬프트에 두 가지를 못 박았다. JSON 외에 다른 말을 하지 말 것, 그리고 날짜가 여럿이면 공판기일만 고를 것. 결과는 이랬다.

{
  "문서종류": "공판기일통지서",
  "기일지정서여부": true,
  "피고인": "김OO",
  "사건번호": "2025노OOOO",
  "기일일시": "2026-06-11 15:00",
  "장소": "OO지방법원 408호 법정"
}

전 항목이 맞았다. 기일지정서여부true로 나온 것은 자동 분류의 출발점이고, 피고인은 폴더명이 될 값이며, 기일일시는 일정 등록에 쓸 값이다. 특히 기일일시가 작성일(2026-05-25)이 아니라 공판기일(2026-06-11)로 나온 것은, 프롬프트의 “공판기일만 골라라” 지시를 모델이 따랐다는 뜻이다. JSON 형식도 깨지지 않아, 그대로 파싱해 쓸 수 있다.


페이지 전체를 줘도 흔들리지 않는가

여기까지는 1페이지만 줬다. 그런데 스캔 폴더에는 페이지 수가 제각각인 온갖 문서가 올라온다. “1페이지만 읽는다”고 고정할 수 없으니, 페이지 수에 상관없이 문서 전체를 보고 판단해야 한다. 그래서 3페이지를 모두 넣고 같은 작업을 시켰다.

이 문서에서 3페이지를 다 주는 것은 검증 측면에서 중요하다. 3페이지에는 피고인이 아니라 **서류를 송달받는 변호사(법무법인 OO 담당변호사 배OO)**의 정보가 있다. 모델이 당사자와 수신인을 혼동하면, 피고인이 아닌 변호사 이름으로 폴더가 만들어질 수 있다. 그래서 프롬프트에 그 점을 명시했다.

이미지 경로 세 개를 명령 끝에 나란히 붙이고, 프롬프트에는 피고인과 수신 변호사를 혼동하지 말라는 주의를 더했다. 명령 형태는 이렇다.

ollama run qwen3-vl:8b-instruct "<프롬프트>" \
  /tmp/123abc-1.png /tmp/123abc-2.png /tmp/123abc-3.png

프롬프트에 넣은 내용은 다음과 같다.

이 문서는 여러 페이지로 이루어진 하나의 문서다. 모든 페이지를 종합해서 보고, 아래 형식의 JSON만 출력해. 다른 말 하지 마.

{ “문서종류”, “기일지정서여부”(true/false), “피고인”, “사건번호”, “기일일시”(YYYY-MM-DD HH:MM), “장소” }

주의사항

  • ‘피고인’은 형사사건의 당사자다. 서류를 송달받는 변호사나 법무법인 이름과 혼동하지 마라. ‘피고인’이라고 명시된 사람만 적어라.
  • ‘기일일시’는 공판기일이다. 문서 작성일이나 발송일이 아니다. 여러 날짜가 있으면 ‘공판기일’ 또는 ‘예정 기일’로 적힌 날짜만 골라라.

결과는 1페이지만 줬을 때와 동일했다.

{
  "문서종류": "공판기일통지서",
  "기일지정서여부": true,
  "피고인": "김OO",
  "사건번호": "2025노OOOO",
  "기일일시": "2026-06-11 15:00",
  "장소": "OO지방법원 408호 법정"
}

피고인은 여전히 김OO다. 3페이지의 변호사 이름에 끌려가지 않았다. 기일일시도 1페이지의 공판기일과 3페이지의 “예정 기일”이 일치하는 값으로 유지됐다. 페이지를 더 줘도, 여러 사람·여러 날짜 속에서 맞는 값을 골라냈다.


뽑은 정보로 일정 만들기

이제 추출한 정보로 캘린더에 일정을 만든다. 실제 업무 캘린더를 건드리지 않으려고, 검증용으로 만들어둔 별도 test 캘린더에 넣기로 했다. 캘린더 목록과 각 캘린더의 ID는 이렇게 본다.

gog calendar calendars

목록에서 test 캘린더의 ID(...@group.calendar.google.com 형태)를 확인했다.

일정 생성 명령은 gog calendar create <calendarId>이고, 제목·시작·종료·장소를 플래그로 넘긴다. 시작·종료 시각은 RFC3339 형식으로 넣어야 하는데, 이는 2026-06-11T15:00:00+09:00처럼 날짜와 시간 사이에 T를 넣고 끝에 시간대를 붙이는 형식이다. 한국시간이라 +09:00을 명시한다. 이 시간대를 빼면 UTC로 해석돼 일정이 9시간 어긋나므로 반드시 붙인다.

만들기 전에 --dry-run으로 먼저 확인했다. 이 옵션은 실제로 일정을 만들지 않고, “이런 일정을 만들 것”이라는 내용만 출력한다.

gog calendar create <test캘린더ID> \
  --summary "[공판기일] 김OO 2025노OOOO" \
  --from "2026-06-11T15:00:00+09:00" \
  --to "2026-06-11T16:00:00+09:00" \
  --location "OO지방법원 408호 법정" \
  --description "스토킹범죄의처벌등에관한법률위반 / 제1형사부(항소) / 출처: 공판기일통지서" \
  --dry-run

제목은 피고인과 사건번호로 한눈에 식별되게 구성했고, 종료시각은 통지서에 없으므로 1시간으로 잡았다. --description에 죄명·재판부·출처를 적어둔 것은, 나중에 캘린더에서 이 일정이 어느 문서에서 만들어졌는지 추적하기 위해서다. dry-run 출력에서 시작·종료가 6월 11일 15시~16시로, 시간대가 한국시간으로 잡힌 것을 확인했다.

확인됐으니 --dry-run만 떼고 실제로 만들었다.

id        bdfh5qqkfj3goe1m8mtu5oorss
summary   [공판기일] 김OO 2025노OOOO
start     2026-06-11T15:00:00+09:00
start-day-of-week  Thursday
...

생성된 일정의 ID가 나왔고, start-day-of-weekThursday로 찍혔다. 통지서의 “(목)” 표기와 일치한다 — 요일까지 맞으니 날짜가 정확하다는 교차 확인이 된 셈이다.


일정이 실제로 들어갔는지 확인

“만들었다”는 출력만으로는 부족하다. 캘린더를 다시 조회해, 실제로 들어갔는지 직접 확인했다.

gog calendar events <test캘린더ID> --from 2026-06-11 --to 2026-06-12
ID                          START                      SUMMARY
bdfh5qqkfj3goe1m8mtu5oorss  2026-06-11T15:00:00+09:00  [공판기일] 김OO 2025노OOOO

방금 만든 일정이 그대로 조회됐다. 웹 캘린더에서도 6월 11일 목요일 15시~16시 자리에 제목·장소·설명이 모두 들어간 것을 확인했다.


노코드로도 같은 흐름을 만들 수 있다

사실 오늘 손으로 돌린 흐름은 코드를 몰라도 만들 수 있다. Make 같은 노코드 자동화 도구를 쓰면, 블록을 끌어다 연결하는 것만으로 비슷한 파이프라인이 된다. 실제로 Make에서 같은 흐름을 구성해본 적이 있다.

스크린샷 2026-06-22 23-18-07.png

블록 하나하나가 오늘 한 단계와 그대로 대응된다.

Make 블록오늘 한 단계
Google Drive — Watch Files in a Folder폴더 감시
Google Drive — Download a File파일 다운로드
PDF.co — Convert from PDFPDF를 텍스트/이미지로 변환
OpenAI — Generate a completion모델로 내용 추출
JSON — Parse JSON추출 결과 파싱
Google Calendar — Create an Event일정 생성

흐름의 뼈대는 동일하다. 차이는 변환과 모델 판독을 어디서 하느냐에 있다. Make 구성은 PDF 변환을 PDF.co에서, 내용 판독을 OpenAI에서 처리한다. 즉 문서 내용이 이 외부 서비스들을 거친다. 오늘 한 방식은 이 두 단계를 모두 ATOM 안에서 처리한다. 이미 검증된 흐름이라는 점이 확인됐으니, 같은 흐름을 로컬에서 돌리는 것이 이 세팅기가 하려는 일이다.


오늘의 정리

오늘은 전체 흐름을 손으로 한 번 끝까지 돌렸다.

이로써 자동 처리 시스템의 가장 안쪽 부품 — 문서를 읽어 정보를 뽑고, 그 정보로 일정을 만드는 일 — 이 작동한다는 것을 확인했다.

이제 남은 것은 오늘 손으로 한 단계들을 하나로 묶어 자동화하는 일이다. 다음 화에서 만들어본다.

#법률실무#구글워크스페이스#자동화