
#Dify×GAS 手書きの保健室来室記録をスプレッドシートに自動で転記するアプリの作り方(DSLファイル、スプシ プレゼントあり)
小学校で働くささです。
以前の記事で紹介した「保健室来室記録の手書きの紙をスプレッドシートに自動的に転記するアプリ」が大きな反響で、作り方を知りたいとのリクエストいただきました。
#Dify × #GAS × #Google Workspace で 保健室の働き方を改革
— ささ (@zawasasa) February 16, 2025
📝保健室来室時の状況をメモ書き ・健康観察カードの記入 ・保健室来室カード
どんな手書きメモでもOK! AIが内容を理解して必要な情報を抽出→スプシに入力→チャットで担任とも共有 pic.twitter.com/32m6Jik0Fi
非エンジニアの素人ですが、できるだけ詳しく解説していきます。質問あればコメントなどで教えてください。
Difyのログインは終わっているという前提で進めます。
技術書としては稚拙ですが頑張って書いてみます。
実際に手を動かして作業することが使い方・作り方を覚える近道です。また学習用に実際のファイルもお渡しすることはできます。
学びの場としてご利用ください。
<注意>
これは作り方の一例、実践例の紹介です。実際に運用はしていません。
別のアプローチでワークフローを組むこともできそうです。
個人名をLLMに読み込ませることはやめましょう。
実際の運用判断は各自でお願いします。
アプリの全体像
大きく分けてDify側の動きとGAS側の動きになります。
Dify上で大規模言語モデル(LLM)を活用して実現しました。具体的には、Google GeminiAPIを使って画像内の情報を抽出し、JSON形式で出力。
そのデータをGoogle Apps Scriptで受け取り、スプレッドシートに書き込む仕組みを構築しました。
こうすることでスプシをデータベース的に活用できます。また、このデータをさらに連携することでチャットやメールで共有する仕組みもできます。(チャット機能は今回は割愛)

準備 ①_Dify側の作成
新規→ワークフロー→任意の名前をつけてください。
また、GeminiAPIを想定しています。
JSONの形式で渡す仕様になっています。

フロー①開始ブロック
入力フィールドを追加します。ここではimageとしておきます。
フィールドタイプは、単一ファイルを選択、サポートされたファイルタイプは画像をチェックします。
こうすると、 画像を入力できるようになります。

imageを追加

フロー②LLMブロック(画像読み取り→JSON形式で出力)
今回はGeminiAPIを使用します。モデルの選択をします。(Gemini) JSONスキーマを以下のように入力します。

モデルの中にあるJSONスキーマをオンにしてどのようにJSONを出力させたいかを指定します。(重要)
ちなみにここがうまくいかず、私は一日ほど時間がかかってしまいました。以下は実際に使用したスキーマ文です。参考に。なおLLMのモデルによってはうまくいかないです。注意してください。
{
"type": "object",
"required": [
"date",
"grade",
"class",
"name",
"condition",
"illness",
"injury",
"injury_location",
"temperature",
"treatment",
"emergency",
"family_contact",
"notes"
],
"properties": {
"date": {
"type": "string",
"description": "記録日時(yyyy/MM/dd HH:mm形式)"
},
"grade": {
"type": "string",
"description": "学年(1-6)"
},
"class": {
"type": "string",
"description": "クラス(1-9)"
},
"name": {
"type": "string",
"description": "生徒の名前"
},
"condition": {
"type": "string",
"description": "症状や状態"
},
"illness": {
"type": "string",
"description": "診断された病気名"
},
"injury": {
"type": "string",
"description": "怪我の種類"
},
"injury_location": {
"type": "string",
"description": "怪我が発生した場所(教室、体育館、校庭など)"
},
"temperature": {
"type": "string",
"description": "体温(35.0-40.0の範囲、または「不明」)"
},
"treatment": {
"type": "string",
"description": "行った処置内容"
},
"emergency": {
"type": "string",
"enum": ["あり", "なし"],
"description": "緊急性の有無"
},
"family_contact": {
"type": "string",
"enum": ["あり", "なし"],
"description": "家庭への連絡有無"
},
"notes": {
"type": "string",
"description": "特記事項"
}
}
}
ビジョンを設定します。
変数はさきほど設定したimageにします。

システムプロンプトとユーザープロンプトを入力しておきます。 geminiAPIは システム プロンプトとユーザプロンプト両方必要になります。注意してください。
今回は、JSON形式の 出力サンプルを渡しておきました。
(システムプロンプト)
画像に書いてある情報を抽出してJSON形式で出力してください。
(ユーザープロンプト)
例)
{
"date": "2025/2/15 12:15",
"name": "あかぎ たろう",
"temperature": "37.8",
"condition": "きもちわるい はきけもあり",
"illness": "なし",
"injury": "なし",
"injury_location": "なし",
"treatment": "2時間目からの水はのませるのOS-1をコップでのませる",
"emergency": "なし",
"family_contact": "すぐに家庭へれんらく",
"notes": "2時間目から",
"grade": "5",
"class": "1"
}
フロー③HTTPリクエスト ブロック(読み取り結果のJSONをGASに渡す)
API:「POST」に設定します。 後で登場するGASのAPIのURLをコピペします。このURLはこの後設定する。 Google Apps Script(GAS)をデプロイすると取得できます。
ボディ:「JSON」を選択します。ここにさきほどのLLMブロックかの出力結果textを入れます。ちなみにJSON形式で出力されているはずです。

フロー④終了ブロック
終了ブロックでつなげば終了です。
必要に応じて、終了ブロックの前に「スプレッドシートに書き込みが成功したか、失敗したか」を表示させるような「IF/ELSE」の分岐ブロックを追加させのもありですね。

準備② スプレッドシート側
カラム名(列名)を入力しておきます。以下は私の例です。任意でよいのですが、必ずJSONスキーマ、例 の形式と対応させてください。ちなみに記録日時だけは処理したタイムスタンプを自動で取得して記録するようにしています。(後述のGASコードで紹介)
シート名は「来室記録」にしておきます。(任意だがこのあとのGASのコードと一致させること)
来室日時 学年 組 名前 状態 病気 怪我 怪我の場所 体温 対応 緊急性 家庭への連絡 メモ 記録日時

準備③ GAS側
さきほどのスプレッドシートから「拡張機能」→Apps Sciptを選択してエディタ画面を開く

以下のコードをペーストしてください。
スプシのIDはご自身のものをコピペしてください。

function doPost(e) {
try {
// リクエストデータのバリデーション
if (!e.postData || !e.postData.contents) {
throw new Error('POSTデータが見つかりません');
}
// リクエストのJSONデータを解析
var jsonData = JSON.parse(e.postData.contents);
// スプレッドシートとシートを取得
var spreadsheet = SpreadsheetApp.openById('ここにスプレッドシートのIDを入れる');//さっきのスプシのIDを入れてください
if (!spreadsheet) {
throw new Error('スプレッドシートが見つかりません');
}
var sheet = spreadsheet.getSheetByName('来室記録');
if (!sheet) {
sheet = spreadsheet.insertSheet('来室記録');
}
// 現在の日時を取得(日本時間)
var recordDate = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy/MM/dd HH:mm:ss');
// データを配列として準備
var rowData = [
jsonData.date || '', // 来室日時
jsonData.grade || '', // 学年
jsonData.class || '', // クラス
jsonData.name || '', // 名前
jsonData.condition || '', // 状態
jsonData.illness || '', // 病気
jsonData.injury || '', // 怪我
jsonData.injury_location || '', // 怪我の場所
jsonData.temperature || '', // 体温
jsonData.treatment || '', // 対応
jsonData.emergency || '', // 緊急性
jsonData.familiy_contact || '', // 家庭への連絡
jsonData.notes || '', // メモ
recordDate // 記録日時
];
// ヘッダー行の確認と追加
if (sheet.getLastRow() === 0) {
var headers = [
'来室日時', '学年', 'クラス', '名前', '状態', '病気',
'怪我', '怪我の場所', '体温', '対応', '緊急性',
'家庭への連絡', 'メモ', '記録日時'
];
sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
}
// データを追加
var nextRow = sheet.getLastRow() + 1;
sheet.getRange(nextRow, 1, 1, rowData.length).setValues([rowData]);
// 成功ステータスを返す
return ContentService.createTextOutput(JSON.stringify({
status: 'success',
message: '記録が保存され、Google Chatに通知されました',
data: {
row: nextRow,
values: rowData,
chatNotified: true
}
}))
.setMimeType(ContentService.MimeType.JSON);
} catch (error) {
// エラー発生時の処理
return ContentService.createTextOutput(JSON.stringify({
status: 'error',
message: error.toString(),
detail: error.stack
}))
.setMimeType(ContentService.MimeType.JSON);
}
}
}
デプロイをします。デプロイ→新しいデプロイ

新しいデプロイ
種類:ウェブアプリ
説明:任意で
次のユーザーとして実行:自分
アクセスできるユーザー:全員

URLをコピーする。→準備①のDifyのHTTPリクエストブロックにペーストする。


準備④最後にDifyでアプリを公開する。


このURLを共有したり職員ポータルサイトに埋め込んだりして使用します。
最後に
うまく説明できたかどうか怪しいのですが、ひとまずこんな感じです。もっと良い方法あったらコメントで教えてください。あとでまたGoogleチャットで連絡するコードは追記していきます。
また、DifyとGASの学習用として、紹介した実際のスプシとDSLファイルもリクエストいただければお渡しすることも可能です。
無断配布、複製、商用利用を約束していただき、身分を明らかにしていだける方にはお渡ししたいと思います。できればたくさんの方に届いてほしいのでXなどでのSNSでの拡散もお願いしたいです。
「スキ」していただきた上で、こちらのフォームからどうぞ。いっしょに学びましょう。
AIの教育的利用、学校現場での活用を探るコミュニティーがあります。私も参加させていただいてます。めちゃくちゃ勉強になるコミュニティーなので、一緒に学びましょう。 最近参加してくれる方が増えてすごく嬉しいです。
上の招待が期限切れになっていたらコメントなどでおしえてください。