0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GASで「教員校務メモ」をAI要約する最小構成: サーバーサイド実行・再実行しやすい設計

0
Posted at

教員の校務では、会議メモ、保護者対応メモ、週案のたたき台、行事準備の記録など、「短いけれど後で整理したい文章」が日々たまります。

この記事では、Googleスプレッドシートに入力した校務メモを、Google Apps ScriptからAI APIへ送り、要約・次アクション・確認事項を同じ行へ書き戻す最小構成を作ります。

ポイントは次の3つです。

  • AI APIキーをフロントエンドに出さず、GASのサーバーサイドだけで使う
  • 1行ずつ処理して、途中で失敗しても再実行しやすくする
  • 教育現場で扱いやすいように、個人情報を入れない運用を前提にする

作るもの

スプレッドシートに次の列を用意します。

内容
A メモ
B 要約
C 次アクション
D 確認事項
E 処理状態
F 処理日時

A列に校務メモを入力し、GASを実行するとBからF列に結果を書き込みます。

事前準備

  1. Googleスプレッドシートを作成する
  2. 1行目に メモ, 要約, 次アクション, 確認事項, 処理状態, 処理日時 を入れる
  3. メニューから 拡張機能Apps Script を開く
  4. 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();
}

実行手順

  1. A列に校務メモを入力する
  2. Apps Scriptで summarizeSchoolMemos を実行する
  3. 初回のみ権限を許可する
  4. BからF列に結果が入ることを確認する

例えばA列に次のようなメモを入れます。

金曜の学年会で、来週の校外学習について持ち物連絡と雨天時動線を再確認する。しおりの最終版は木曜午前までに共有。

出力例は次のようになります。

要約 次アクション 確認事項
来週の校外学習について、持ち物連絡・雨天時動線・しおり共有時期を整理する必要がある。 金曜の学年会で確認し、しおり最終版を木曜午前までに共有する。 雨天時動線の確定内容、持ち物連絡の配布方法

途中で失敗しても再実行しやすくする工夫

このコードでは、E列の処理状態が done の行はスキップします。

そのため、APIエラーや実行時間の上限で途中停止しても、再度 summarizeSchoolMemos を実行すれば未処理行だけを続きから処理できます。

また、maxRowsPerRun10 にしているため、いきなり大量の行を処理しません。まず少数で結果を確認し、問題なければ数を増やす運用にできます。

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が推測した部分をそのまま事実扱いしない

まずは会議メモや行事準備メモのような、個人情報を含めずに扱える文章から試すのが現実的です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?