# 출처 장부 — 블로그 글 하나를 네 군데에

이 튜토리얼의 도구 사용법·한도·약관은 아래 1차 출처에서 확인했다. 확인일은 모두 2026-10-11.
구글 로그인이나 API 키가 필요해 직접 실행하지 못한 단계는 `실행 확인: 사용자`, 문서에서 답을 찾지 못한 것은 `확인 필요`로 남겼다.
첫 편(`../hakwon-parent-report/SOURCES.md`)과 같은 페이지(#1·#3·#4·#5·#8·#9·#10)는 이번에 다시 열어 확인했다.

## 확인한 사실

| # | 확인한 사실 | 쓰인 곳 | 출처 | 문서 갱신일 |
|:-:|---|---|---|---|
| 1 | 사용자 메뉴는 문서에 묶인(bound) 스크립트만 만들 수 있다. `onOpen` → `getUi().createMenu().addItem().addToUi()` | 가이드 1·2, 카드 6 | https://developers.google.com/apps-script/guides/menus | 2026-09-03 |
| 2 | 스크립트 속성: `PropertiesService.getScriptProperties().getProperty(key)` | Code.gs `callGemini_` | https://developers.google.com/apps-script/guides/properties | 2026-09-03 |
| 3 | `UrlFetchApp.fetch(url, params)`의 `method`·`contentType`·`headers`·`payload`·`muteHttpExceptions`, `HTTPResponse.getResponseCode()`·`getContentText()` | Code.gs `callGemini_` | https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app · …/http-response | 2026-05-15 · 2026-04-13 |
| 4 | `Range`: `setValues`, `setValue`, `getValues`. `Sheet`: `getLastRow`, `getRange`, `setFrozenRows`. `Spreadsheet`: `getSheetByName`, `insertSheet`, `getActiveSheet` 계열. `Session.getScriptTimeZone()`, `Utilities.formatDate()` — 메서드 앵커로 확인(2·3편과 동일) | Code.gs | https://developers.google.com/apps-script/reference/spreadsheet/range 외 | 2026-08-19 외 |
| 5 | 개인 계정 한도: URL Fetch 20,000회/일, 실행당 6분 | 가이드 한도 | https://developers.google.com/apps-script/guides/services/quotas | 2026-09-03 |
| 6 | `POST …/v1beta/models/{model}:generateContent`, 키는 `x-goog-api-key` 헤더, 응답 본문 경로 `candidates[0].content.parts[0].text`, 공식 예제 모델 `gemini-3.8-flash` | Code.gs `callGemini_`, `parseOutput` | https://ai.google.dev/api/generate-content · https://ai.google.dev/gemini-api/docs/text-generation | 표기 없음 · 2026-09-17 |
| 7 | JSON 응답 모드: `generationConfig.responseMimeType = "application/json"`과 `responseSchema`(대안 `responseJsonSchema`, "responseMimeType must also be set"). 레퍼런스의 JSON Mode 예제는 `OBJECT`·`ARRAY`·`STRING` 대문자 타입을 쓴다 | Code.gs `OUTPUT_SCHEMA`, `buildRequestBody` | https://ai.google.dev/api/generate-content (GenerationConfig) | 표기 없음 |
| 8 | 키 발급 aistudio.google.com/apikey. 키를 소스 관리·클라이언트 코드에 넣지 말 것 | 가이드 2 | https://ai.google.dev/gemini-api/docs/api-key | 2026-09-16 |
| 9 | 무료(Unpaid) 서비스: 사람 검토자가 입력·출력을 읽을 수 있음, 민감·기밀·개인 정보 제출 금지. 유료는 제품 개선에 쓰지 않음. 만 18세 이상 | 가이드 "시작하기 전에", 카드 4·5 | https://ai.google.dev/gemini-api/terms | 2026-04-28 |
| 10 | 구조화 출력 가이드는 현재 Interactions API(`/v1beta/interactions`, `response_format`) 기준으로 쓰여 있다. 이 튜토리얼은 #7의 generateContent 필드를 쓴다 | SOURCES 메모 | https://ai.google.dev/gemini-api/docs/structured-output | 2026-09-23 |

## 열려 있는 항목

| 항목 | 상태 | 닫는 방법 |
|---|---|---|
| 실제 호출에서 `responseSchema`대로 다섯 개짜리 배열이 안정적으로 오는지 | 확인 필요 — 코드는 길이가 안 맞으면 읽을 수 있는 오류를 내고 다시 누르게 함 | 실제 글 3편으로 ②를 눌러 본다 |
| 모델 이름 `gemini-3.8-flash`의 유효성(시점에 따라 바뀜) | 확인 필요 | 404가 나면 공식 문서의 현재 이름으로 `MODEL` 교체 |
| `generateContent`를 Interactions API로 옮길 시점 | 보류 — 폐기 표시 없음 | 폐기 공지가 나오면 `callGemini_`·`buildRequestBody`·`parseOutput`만 교체 |
| 권한 승인 창 문구, 한국어 메뉴 경로 | 실행 확인: 사용자 | 실제 화면과 대조 |
| ① → ② → 거절 확인 → ③ 전체 흐름 | 실행 확인: 사용자 | 가이드의 "혼자 다시 해 보기" 목록을 그대로 수행 |

## 문서 확인 없이 검증한 것

서비스에 의존하지 않는 함수(`headerMap`, `missingHeaders`, `findNames`, `parseTone`, `splitList`, `buildRequestBody`, `parseOutput`, `findBanned`, `numbersNotInSource`)는 `source/check.mjs`로 Node에서 실행해 확인했다.

```bash
node source/check.mjs
```

확인 내용: 학생명단 이름을 찾아 보고한다(가리지 않음, 한 글자 제외) / 톤 시트가 객체로 읽히고 금지어 씨앗이 랜딩 브랜드 금지어와 같다 / 요청에 JSON 모드와 스키마가 붙고, 본문은 나가되 링크는 나가지 않는다 / 정상 응답·JSON 아님·배열 길이 불일치·차단·메타 길이 초과를 각각 처리한다 / 금지어는 부분 일치·대소문자 무시 / 본문에 없는 숫자만 한 번씩 보고한다.

## 이 튜토리얼이 기대고 있지 않은 것

주제 선정 단계에서 국내 교육 사이트의 업무 자동화 유형 목록을 **주제 지도로만** 참고했다. 제목, 등장인물, 수치, 글의 전개는 가져오지 않았다. 자동 게시를 넣지 않은 것, 이름이 있으면 가리지 않고 거절하는 것, 톤을 시트에 둔 것, 숫자 검사는 작성자의 학원 운영과 랜딩 브랜드 톤 가이드에서 나온 설계다.
