보고서 도구 확장 — 리포트 프롬프트 & JSON→Notion 테스트

마지막 업데이트 2026-07-29

보고서 도구 확장 — 리포트 프롬프트 & JSON→Notion 테스트: 증상: 이 문서는 이렇게 읽으면 됩니다, 원인: 코드로 보는 핵심 지점, 수정·검증: 흐름 ① 프롬프트 테스트 — 브라우저에서 외부 Gemini 서버까지 흐름
동작 흐름 요약
  1. 증상: 이 문서는 이렇게 읽으면 됩니다
  2. 원인: 코드로 보는 핵심 지점
  3. 수정·검증: 흐름 ① 프롬프트 테스트 — 브라우저에서 외부 Gemini 서버까지
문서 읽는 법 · 변경 검토식

이 문서는 이렇게 읽으면 됩니다

보고서 도구 확장 — 리포트 프롬프트 & JSON→Notion 테스트 — 커밋 리뷰 (1d3172a0)의 핵심을 변경 검토식으로 먼저 안내합니다. 기술적 결론과 원문 근거는 아래 본문에 보존되어 있습니다.

핵심 흐름 펼쳐 보기
  1. 변경 목적과 주요 흐름을 먼저 파악합니다.
  2. 핵심 diff와 영향 범위를 따라갑니다.
  3. 관전 포인트와 테스트로 위험을 점검합니다.
  • 무엇이, 왜 바뀌었나
  • 흐름 ① 프롬프트 테스트 — 브라우저에서 외부 Gemini 서버까지
  • 흐름 ② JSON→Notion — 변환·발행은 web이 직접 수행
1d3172a0 모카 · 2026-07-22 Feature 14 files +3,253 −238

PR #911/main/report 페이지를 회기별 편집 테이블 하나에서 탭 3개(회기 보고서 세팅 · 프롬프트 테스트 · JSON 노션 테스트)로 재구성한다. 실제 AI 보고서 생성은 외부 리포터 서버(Gemini)가 맡고, web은 인증·키 은닉 프록시 / JSON→Notion 문서 변환·발행 / 학습플랜 프롬프트 저장 / 테스트 UI·폴링을 담당하는 오케스트레이션 계층이다.

리뷰 시 먼저 볼 지점 3가지: ① 두 curriculum 라우트에 parseReport* 검증기가 통째로 복붙됨, ② 테스트 탭이 dev에서 프록시/인증을 우회해 127.0.0.1:8787을 직접 호출(prod만 /api 경유), ③ updateCurriculum이 리포트 필드를 항상 덮어쓰는데 데이터 소실은 API 머지 로직으로만 방어된다.

무엇이, 왜 바뀌었나

PR #889에서 만든 회기 보고서 전사 파이프라인의 후속 작업으로, 그때 생긴 "보고서 탭"을 프롬프트 실험·검증 도구로 확장한다. 순변경은 +3,253/−238이고 대부분 신규 파일 추가이며, 기존 회기 편집 로직은 페이지에서 세팅 탭으로 위치만 이동했다.

흐름 ① 프롬프트 테스트 — 브라우저에서 외부 Gemini 서버까지

1

폼 입력 + 로그 CSV 변환

report-test.tsx

아동명·MP3(≤8)·수업로그·프롬프트 입력. 붙여넣은 탭 표를 Speaker,Text,Source CSV로 변환.

2

인증 프록시 (prod)

POST /api/reports/test

admin/dev 인증 후 X-Service-API-Key를 붙여 외부 서버로 formData 전달.

3

외부 생성 + 상태 폴링

GET /api/reports/test/[jobId]

Gemini 서버가 생성, 3초 간격 폴링으로 queued→completed 추적.

흐름 ② JSON→Notion — 변환·발행은 web이 직접 수행

1

결과 JSON 붙여넣기

report-json-test.tsx

2

JSON 파싱 + 블록 변환

report-document.ts

구조화 JSON → Notion 블록(표/콜아웃/progress/metrics), 2,000자 분할.

3

Notion 페이지 생성

report-json-notion.ts

NOTION_API_KEY로 직접 호출, 100블록 단위 페이지네이션.

코드로 보는 핵심 지점

1. 페이지가 3-탭 셸로 축소되고 로직은 섹션으로 분리

apps/web/components/pages/report-page.tsx - // 학습플랜 선택 → 회기별 SessionReport 편집 테이블 (단일 화면, ~200줄) +type ReportTab = "session" | "test" | "json-test"; + {activeTab === "session" && <ReportSessionSettings />} + {activeTab === "test" && <ReportTest />} + {activeTab === "json-test" && <ReportJsonTest />}

2. curriculum에 optional 속성 2개 + update는 항상 SET(|| null)

apps/web/types/db/curriculum.types.ts +export type ReportSessionPrompts = Record<string, string>; // 회차(1부터) → 프롬프트 + reportCommonPrompt?: string; + reportSessionPrompts?: ReportSessionPrompts; apps/web/lib/db-queries.ts — updateCurriculum + ":reportCommonPrompt": curriculum.reportCommonPrompt || null, + ":reportSessionPrompts": curriculum.reportSessionPrompts || null,

UpdateExpression이 두 필드를 부분 업데이트가 아니라 매번 SET한다. 필드를 뺀 채 호출하면 null로 지워진다.

3. 데이터 소실은 API PUT 핸들러의 "기존 값 머지"로만 방어

apps/web/app/api/curriculums/[id]/route.ts — putHandler + reportCommonPrompt: + reportCommonPrompt === undefined + ? existingCurriculum.reportCommonPrompt // body에 없으면 기존 값 유지 + : reportCommonPrompt || undefined,

curriculum-page.tsx의 카테고리/순서 변경 등 20여 곳이 updateCurriculum을 호출하지만, 이 머지 덕분에 리포트 프롬프트가 날아가지 않는다. 방어는 API 계층에만 존재한다.

4. 테스트 탭의 dev 우회 — prod만 프록시를 탄다

apps/web/components/sections/report-test.tsx +function getReportTestEndpoint(path = "") { + if (process.env.NODE_ENV !== "production") { + return `http://127.0.0.1:8787/report-tests${path}`; // 브라우저가 외부 서버 직접 호출 + } + return `/api/reports/test${path}`; // prod만 인증 프록시 경유 +}

dev에서는 인증·X-Service-API-Key 프록시(test/route.ts)가 실행되지 않는다. 반면 JSON 노션 탭은 dev에서도 항상 /api/reports/json-test를 탄다 — 두 탭의 경로 전략이 다르다.

5. Notion 페이지 생성 — 100블록 초과분 append

apps/web/lib/report-json-notion.ts + children: blocks.slice(0, NOTION_BLOCK_LIMIT), // 첫 페이지에 100개 + for (let index = 100; index < blocks.length; index += 100) { + await requestNotion(apiKey, `/blocks/${page.id}/children`, { ... });

레이어별 변경 요약

레이어파일핵심 변경
Pagecomponents/pages/report-page.tsx단일 테이블 → 3탭 셸 (−205줄)
FE 섹션sections/report-session-settings.tsx신규 527줄. 회기 편집 + 공통/개별 프롬프트 UI, 편집 모달
FE 섹션sections/report-test.tsx신규 575줄. MP3+로그 폼, 로그→CSV 변환, 상태 폴링
FE 섹션sections/report-json-test.tsx신규 169줄. 결과 JSON→Notion 발행 UI
APIapi/reports/test/route.ts신규. 외부 서버 프록시(GET 프롬프트, POST 폼)
APIapi/reports/test/[jobId]/route.ts신규. 작업 상태 폴링 프록시
APIapi/reports/json-test/route.ts신규. JSON→Notion 생성 엔드포인트
APIapi/curriculums/route.ts · [id]/route.ts리포트 프롬프트 파싱·검증, 기존 값 머지
Liblib/report-document.ts신규 741줄. JSON→Notion 블록 변환기 + 스키마 + 폴백
Liblib/report-json-notion.ts신규 91줄. Notion API 클라이언트
Liblib/report-evaluation-design-sample.ts신규 764줄. 테스트용 샘플 JSON(데이터)
DBlib/db-queries.ts · types/db/curriculum.types.tscurriculum에 리포트 프롬프트 필드 2개

리뷰 관전 포인트

구조

검증기 복붙. parseReportCommonPrompt/parseReportSessionPromptsapi/curriculums/route.ts[id]/route.ts에 완전히 동일하게 중복 정의됐다. 회차 키 정규식(^[1-9]\d*$) 등 규칙이 갈라질 위험이 있으니 공용 모듈로 추출 권장.

동작 확인

dev 우회 비대칭. 프롬프트 테스트 탭은 dev에서 127.0.0.1:8787을 직접 호출해 인증·프록시가 검증되지 않는다. JSON 노션 탭은 dev에서도 /api 경유라 로컬에도 NOTION_* 키가 필요하다. 두 탭의 경로 전략 차이가 의도된 것인지 확인 필요.

데이터 소실

update가 항상 덮어씀. updateCurriculum(|| null)은 부분 업데이트가 아니다. API PUT의 기존 값 머지로만 방어되므로, 배치/스크립트가 db 함수를 필드 없이 직접 호출하면 프롬프트가 초기화된다.

하드코딩

워크스페이스 slug 기본값. NOTION_APP_WORKSPACE_SLUG 기본값이 "succulent-glitter-109"로 코드에 박혀 있다. 다른 워크스페이스에 배포하면 생성된 문서 링크가 잘못된 slug를 가리킨다.

엄격도

파서 두 갈래. json-test는 parseGeneratedReportDocument(hasOnlyKeys 엄격 검증)만 사용 — Gemini가 규격 외 키를 하나라도 넣으면 전체가 REPORT_GENERATION_INVALID_JSON(400)으로 실패한다. 같은 파일의 관대한 parseReportDocument(dynamic 폴백)는 이 경로에서 안 쓰인다.

영향 범위

롤아웃 안전. DB는 optional 속성 추가라 마이그레이션·백필 불필요, 기존 curriculum 무변경. 신규 API는 admin/dev 전용. env 미설정 시 각각 *_NOT_CONFIGURED/503으로 안전하게 실패한다.

관련 문서

관련 문서: 회기 보고서 전사 파이프라인 — 기획과 구현 (75282732) — PR #889에서 만든 보고서 탭·전사→보고서→Notion 파이프라인을 이 PR이 프롬프트 실험/JSON 테스트 도구로 확장한다.