키즈노트 알림장의 행사·준비물을 LLM(Grok)으로 분석해 매일 자동으로 Google 캘린더에 등록합니다.
운영 중 Cron: 매일 09:00 KST
키즈노트는 어린이집/유치원에서 학부모에게 공지를 전달하는 앱인데, 행사 일정과 준비물이 자유 서술된 알림장 형태로 올라옵니다. 학부모가 매번 읽고 캘린더에 옮겨 적는 작업을 자동화하는 것이 이 프로젝트의 목표입니다.
키즈노트 알림장 (선생님/원장님 공지)
Grok LLM 으로 행사·준비물 추출
Google Calendar 이벤트 (자동 등록)
┌──────────────┐ ① 매일 09:00 KST ┌──────────────────┐
│ node-cron │ ─────────────────────────────▶ │ runOnce() │
└──────────────┘ └────────┬─────────┘
│
┌─────────────────────────────────────────────────────────┘
│
▼ ② 키즈노트 로그인 → /api/v1_2/children/.../reports/
┌──────────────────────┐ cookies.json 으로 세션 캐시
│ KidsNoteClient │ (만료되면 자동 재로그인)
│ src/kidsnote.js │
└──────────┬───────────┘
│ reports[]
▼ ③ state.db 로 신규 리포트 필터링
┌──────────────────────┐ processed_reports 테이블에 이미 본 ID 기록
│ State (SQLite WAL) │ → 같은 리포트는 다시 LLM 호출 안 함
│ src/state.js │
└──────────┬───────────┘
│ new reports[]
▼ ④ teacher / admin 작성 글만, 본문 ≥ 20자 인 것만 LLM 호출
┌──────────────────────┐ xAI Grok API (OpenAI SDK 호환)
│ EventExtractor │ response_format: json_object
│ src/extractor.js │ SYSTEM_PROMPT → 이벤트/준비물 JSON
└──────────┬───────────┘
│ events[] (정규화: key, dedupKey 부여)
▼ ⑤ 과거 일정 / 글로벌 중복 / 이미 등록된 일정 필터
┌──────────────────────┐ dedup_key = sha1(normalize(title)|start_date)
│ Dedup 로직 (main.js)│ → 여러 알림장이 같은 행사 언급해도 1번만 등록
└──────────┬───────────┘
│ unique events[]
▼ ⑥ Google Calendar API events.insert
┌──────────────────────┐ OAuth2 token.json (refresh_token 자동 갱신)
│ CalendarClient │ description 에 원본 공지 본문 첨부
│ src/calendar.js │
└──────────────────────┘
processed_reports 에 기록해 두 번 분석하지 않고, ② 다른 알림장에서 같은 행사가 또 언급되어도 dedup_key 로 캘린더에는 한 번만 등록됩니다.INCLUDE_PAST_DAYS 환경변수로 "오늘 이후"만 등록하도록 컷오프. 캘린더가 지나간 일정으로 어지러워지지 않습니다.data/cookies.json 에 저장해 매번 로그인하지 않고, 만료 시 자동 재로그인.googleapis 의 tokens 이벤트로 갱신된 토큰을 data/token.json 에 다시 저장.data/ 폴더를 마운트해 컨테이너 재생성/재부팅에도 state.db / token / cookies 유지.kidsnote-calendar-sync/
├── src/
│ ├── main.js # 파이프라인 오케스트레이션 + cron 스케줄링
│ ├── kidsnote.js # 키즈노트 로그인/리포트 fetch
│ ├── extractor.js # Grok LLM 으로 이벤트 추출 + 정규화
│ ├── calendar.js # Google Calendar 클라이언트
│ ├── state.js # SQLite (better-sqlite3) 상태 관리
│ └── logger.js
├── scripts/
│ ├── google-auth.js # 최초 1회 OAuth 인증 (readline 방식)
│ └── test-extract.js # LLM 추출만 테스트
├── data/ # volume mount (gitignore)
│ ├── credentials.json # Google OAuth 클라이언트
│ ├── token.json # access/refresh 토큰
│ ├── cookies.json # 키즈노트 세션
│ └── state.db # SQLite (WAL)
├── docker-compose.yml
├── Dockerfile
└── .env
| 항목 | 어디서 |
|---|---|
KIDSNOTE_USERNAME / PASSWORD | 키즈노트 로그인 계정 (카카오 OAuth 계정 ✗) |
KIDSNOTE_CHILD_ID | F12 → Network → reports 요청 URL의 children/{여기} |
KIDSNOTE_CLASS_ID | 같은 URL의 ?cls={여기} |
KIDSNOTE_CENTER_ID | 같은 URL의 ¢er_id={여기} |
KIDSNOTE_ENROLLMENT_ID | 같은 요청의 x-enrollment 헤더 |
GROK_API_KEY | console.x.ai 에서 API Key 발급 |
.env 작성cp .env.example .env
# 위에서 수집한 값으로 채우기
docker compose build
kidsnote-sync)data/credentials.json 으로 저장docker compose run --rm kidsnote-calendar-sync node scripts/google-auth.js
http://localhost/?code=... 로 리다이렉트 (연결 실패 페이지가 떠도 정상)code= 뒤 부분을 복사해 터미널에 붙여넣기data/token.json 생성 완료 → 이후 자동 갱신docker compose run --rm -e CRON_SCHEDULE= kidsnote-calendar-sync node src/main.js --once
docker compose up -d # 백그라운드 시작
docker compose logs -f # 실시간 로그
docker compose ps # 상태 확인
.env 의 CRON_SCHEDULE=0 9 * * * 기준 매일 오전 9시(KST)에 자동 실행됩니다. 컨테이너 시작 시에도 1회 즉시 실행합니다. restart: unless-stopped 이므로 호스트 재부팅 후에도 자동 시작.
| 동작 | 명령 |
|---|---|
| 수동 1회 실행 | docker compose exec kidsnote-calendar-sync node src/main.js --once |
| 재시작 | docker compose restart |
| .env 수정 후 적용 | docker compose up -d --force-recreate |
| 정지 | docker compose down |
알림장에서 행사·준비물을 어떻게 추출하는지가 이 시스템의 핵심입니다. 정확한 시스템 프롬프트와 호출 설정, 정규화 로직을 별도 문서로 정리했습니다.
kidsnote-extractor-prompt.md
프롬프트 수정 시: src/extractor.js 의 SYSTEM_PROMPT 상수를 직접 편집한 뒤 docker compose up -d --force-recreate. 이미 처리된 리포트는 다시 분석되지 않으니, 특정 리포트를 재추출하려면 state.db 의 해당 행을 삭제하세요.
| 증상 | 해결 |
|---|---|
| OAuth "access blocked: 인증되지 않은 앱" | Cloud Console → OAuth 동의 화면 → 테스트 사용자에 본인 Gmail 추가 |
| 키즈노트 로그인 실패 | 비밀번호 확인. 카카오 OAuth 계정은 지원되지 않습니다. data/cookies.json 삭제 후 재시도 |
| 이벤트가 중복 등록되지 않음 | 정상. state.db 의 calendar_events.dedup_key 가 중복을 막습니다. 재등록 원하면 해당 행 삭제 |
| 일정이 잘못 추출됨 | src/extractor.js 의 SYSTEM_PROMPT 수정 → 재시작 |
| "Insufficient Permission" 에러 | 현재 scope 은 calendar.events 만. calendarList.list 등 다른 API 는 불가 (의도된 최소 권한) |
| Google 토큰 만료 | refresh_token 으로 자동 갱신. 그래도 안 되면 data/token.json 삭제 후 scripts/google-auth.js 재실행 |