教員の校務では、会議メモ、保護者対応メモ、週案のたたき台、行事準備の記録など、「短いけれど後で整理したい文章」が日々たまります。
この記事では、Googleスプレッドシートに入力した校務メモを、Google Apps ScriptからAI APIへ送り、要約・次アクション・確認事項を同じ行へ書き戻す最小構成を作ります。
ポイントは次の3つです。
- AI APIキーをフロントエンドに出さず、GASのサーバーサイドだけで使う
- 1行ずつ処理して、途中で失敗しても再実行しやすくする
- 教育現場で扱いやすいように、個人情報を入れない運用を前提にする
作るもの
スプレッドシートに次の列を用意します。
| 列 | 内容 |
|---|---|
| A | メモ |
| B | 要約 |
| C | 次アクション |
| D | 確認事項 |
| E | 処理状態 |
| F | 処理日時 |
A列に校務メモを入力し、GASを実行するとBからF列に結果を書き込みます。
事前準備
- Googleスプレッドシートを作成する
- 1行目に
メモ,要約,次アクション,確認事項,処理状態,処理日時を入れる - メニューから
拡張機能→Apps Scriptを開く - AI APIキーをスクリプトプロパティに保存する
APIキーはコードに直接書かず、スクリプトプロパティへ保存します。
Apps Scriptエディタで プロジェクトの設定 → スクリプト プロパティ を開き、次を追加します。
| プロパティ | 値 |
|---|---|
OPENAI_API_KEY |
利用するAPIキー |
コード
Apps Scriptに次のコードを貼り付けます。
const CONFIG = {
sheetName: 'シート1',
model: 'gpt-4.1-mini',
maxRowsPerRun: 10,
};
function summarizeSchoolMemos() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(CONFIG.sheetName);
if (!sheet) {
throw new Error(`シートが見つかりません: ${CONFIG.sheetName}`);
}
const apiKey = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
if (!apiKey) {
throw new Error('スクリプトプロパティ OPENAI_API_KEY が未設定です');
}
const lastRow = sheet.getLastRow();
if (lastRow < 2) return;
const values = sheet.getRange(2, 1, lastRow - 1, 6).getValues();
let processed = 0;
for (let i = 0; i < values.length; i++) {
if (processed >= CONFIG.maxRowsPerRun) break;
const rowNumber = i + 2;
const [memo, summary, nextAction, checkPoint, status] = values[i];
if (!memo || status === 'done') {
continue;
}
try {
const result = callAiForSchoolMemo_(String(memo), apiKey);
sheet.getRange(rowNumber, 2, 1, 5).setValues([[
result.summary,
result.nextAction,
result.checkPoint,
'done',
new Date(),
]]);
processed++;
Utilities.sleep(800);
} catch (error) {
sheet.getRange(rowNumber, 5, 1, 2).setValues([[
`error: ${error.message}`,
new Date(),
]]);
}
}
}
function callAiForSchoolMemo_(memo, apiKey) {
const prompt = [
'あなたは学校現場の校務整理を支援するアシスタントです。',
'入力メモをもとに、要約・次アクション・確認事項をJSONで返してください。',
'児童生徒、保護者、教職員の個人情報を推測で補わないでください。',
'不明点は確認事項に回してください。',
'',
'入力メモ:',
memo,
].join('\n');
const payload = {
model: CONFIG.model,
messages: [
{
role: 'system',
content: '出力はJSONのみ。キーは summary, nextAction, checkPoint の3つ。',
},
{
role: 'user',
content: prompt,
},
],
temperature: 0.2,
response_format: { type: 'json_object' },
};
const response = UrlFetchApp.fetch('https://api.openai.com/v1/chat/completions', {
method: 'post',
contentType: 'application/json',
headers: {
Authorization: `Bearer ${apiKey}`,
},
payload: JSON.stringify(payload),
muteHttpExceptions: true,
});
const statusCode = response.getResponseCode();
const body = response.getContentText();
if (statusCode < 200 || statusCode >= 300) {
throw new Error(`AI API request failed: ${statusCode} ${body.slice(0, 300)}`);
}
const data = JSON.parse(body);
const content = data.choices?.[0]?.message?.content;
if (!content) {
throw new Error('AI API response content is empty');
}
const parsed = JSON.parse(content);
return {
summary: normalizeText_(parsed.summary),
nextAction: normalizeText_(parsed.nextAction),
checkPoint: normalizeText_(parsed.checkPoint),
};
}
function normalizeText_(value) {
if (value === null || value === undefined) return '';
return String(value).trim();
}
実行手順
- A列に校務メモを入力する
- Apps Scriptで
summarizeSchoolMemosを実行する - 初回のみ権限を許可する
- BからF列に結果が入ることを確認する
例えばA列に次のようなメモを入れます。
金曜の学年会で、来週の校外学習について持ち物連絡と雨天時動線を再確認する。しおりの最終版は木曜午前までに共有。
出力例は次のようになります。
| 要約 | 次アクション | 確認事項 |
|---|---|---|
| 来週の校外学習について、持ち物連絡・雨天時動線・しおり共有時期を整理する必要がある。 | 金曜の学年会で確認し、しおり最終版を木曜午前までに共有する。 | 雨天時動線の確定内容、持ち物連絡の配布方法 |
途中で失敗しても再実行しやすくする工夫
このコードでは、E列の処理状態が done の行はスキップします。
そのため、APIエラーや実行時間の上限で途中停止しても、再度 summarizeSchoolMemos を実行すれば未処理行だけを続きから処理できます。
また、maxRowsPerRun を 10 にしているため、いきなり大量の行を処理しません。まず少数で結果を確認し、問題なければ数を増やす運用にできます。
maxRowsPerRun: 10,
大量処理したい場合でも、最初は10件程度にしておくと、プロンプトのずれや列の指定ミスに気づきやすくなります。
落とし穴1: APIキーをコードに書かない
次のようにAPIキーをコードへ直接書くのは避けます。
const apiKey = 'sk-...';
Apps Scriptのコードは共有設定やコピーで広がることがあります。APIキーはスクリプトプロパティに保存し、コードからは次のように読み出します。
const apiKey = PropertiesService.getScriptProperties().getProperty('OPENAI_API_KEY');
落とし穴2: 個人情報をそのまま入れない
校務メモには、児童生徒名、保護者名、連絡先、健康情報、家庭状況などが含まれやすいです。
AI APIへ送る前提のメモでは、個人名を記号に置き換えるなど、入力時点で情報を減らす運用にします。
児童A、保護者B、担任C
「あとでAIに送るかもしれないメモ」は、最初から匿名化しやすい書き方にしておくと扱いやすくなります。
落とし穴3: JSON出力でもパース失敗は起きる
response_format: { type: 'json_object' } を指定しても、APIのエラー応答、通信失敗、想定外の空レスポンスは起きえます。
そのため、次の3段階で確認しています。
- HTTPステータスコードが成功範囲か
-
choices[0].message.contentがあるか -
JSON.parseできるか
if (statusCode < 200 || statusCode >= 300) {
throw new Error(`AI API request failed: ${statusCode} ${body.slice(0, 300)}`);
}
失敗した行はE列に error: ... を書くので、該当行だけ内容を修正して再実行できます。
落とし穴4: プロンプトで「推測しない」を明示する
校務の整理では、AIがもっともらしく補完してしまうと困る場面があります。
この記事のコードでは、プロンプトに次を入れています。
児童生徒、保護者、教職員の個人情報を推測で補わないでください。
不明点は確認事項に回してください。
「足りない情報を埋める」のではなく、「足りない情報を確認事項として返す」方向に寄せると、校務メモの整理に使いやすくなります。
メニューから実行できるようにする
毎回Apps Scriptエディタを開くのが面倒な場合は、スプレッドシートにメニューを追加します。
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('校務AI')
.addItem('メモを整理する', 'summarizeSchoolMemos')
.addToUi();
}
この関数を同じプロジェクトに追加すると、スプレッドシートを開いたときに 校務AI メニューが表示されます。
まとめ
GASとスプレッドシートを組み合わせると、校務メモの整理を小さく自動化できます。
最初から大きな業務システムにせず、次のような設計にしておくと試しやすいです。
- 入力と出力をスプレッドシート上で見えるようにする
- 処理済みフラグを持たせ、再実行しやすくする
- APIキーはスクリプトプロパティで管理する
- 個人情報を送らない前提でメモの書き方を整える
- AIが推測した部分をそのまま事実扱いしない
まずは会議メモや行事準備メモのような、個人情報を含めずに扱える文章から試すのが現実的です。