全国オンライン対応受付 平日 9:00-18:00
お問い合わせ

開発記録 / AIFlex制作記 / Vol.15 AIFlex制作記 Vol.1

記事 01

汎用版AI添削システムを作った理由と2フォーム構成

そう連絡が来たとき、最初にやったのはコードのコピーだった。AiReview.gsを丸ごとコピーして、設定値3か所を書き換えた。AiReview_B.gsの完成だ。

2026-06-07 公開

「別の市でも同じシステムを使いたい」

そう連絡が来たとき、最初にやったのはコードのコピーだった。AiReview.gsを丸ごとコピーして、設定値3か所を書き換えた。AiReview_B.gsの完成だ。

これで動く。しかし「これでいいのか」という違和感が残った。3つ目の講座が来たらどうするか。4つ目が来たら?

変わるのは設定値だけで、ロジックは完全に同じだ。同じものが3つ、4つと増えていく設計は間違っている。


設定値の差分だけで作った2ファイル

AiReview.gs(A版)とAiReview_B.gs(B版)の違いは3行だけだ。

// A版
const AI_ROOT_FOLDER_ID = '0ANmtZhANtL7gUk9PVA';
const COURSE_NAME = 'ある創業カレッジ2026';
const REGION_EXAMPLE = 'その市';

// B版
const AI_ROOT_FOLDER_ID = '0AKcC-e5F91-8Uk9PVA';
const COURSE_NAME = '別の市の創業塾2026';
const REGION_EXAMPLE = '別の市市';

ロジックは全く同じだ。設定値の変更やバグ修正のたびに2か所を直す必要がある。バグを直したとき「こっちは直したけどあっちは?」という確認が必要になる。

設計を変えることにした。


Flex版の設計方針

AiReview_Flex.gsは「どの講座でも使える」ことを前提に設計した。

TypeFormのフィールドから講座名・地域・受講者情報を動的に取得する。固定値をコードに埋め込まない。

ファイル保存先はGoogle Driveの1つのルートフォルダの中にサブフォルダを作って管理する。

FLEX_ROOT_FOLDER_ID/
├── ある創業カレッジ2026/
│   ├── 田中太郎_2024-05-01/
│   └── 山田花子_2024-05-02/
└── 別の市の創業塾2026/
    └── 佐藤一郎_2024-06-01/

新しい講座を追加するときはTypeFormを設定するだけでよく、GASのコードを変更しない。


2フォーム構成を選んだ理由

AiReview.gsは1つのTypeFormで完結していた。受講者はフォームを開いて回答して送信するだけだ。

Flex版では2フォーム構成を採用した。

フォーム1(登録フォーム):メールアドレスと名前を入力して送信する。GASが即座に「添削フォームのURL」を記載した招待メールを送る。

フォーム2(添削フォーム):招待メールのURLを開いて事業計画書の回答を送信する。GASがGeminiで処理して添削レポートを保存する。

2フォームにした理由は「添削フォームのURLを制限したかった」からだ。フォーム1で登録した人だけが添削フォームのURLを手にできる。誰でも使えるようにすると、Gemini APIの使用量が無制限になってしまう。


form_idによる処理の分岐

TypeFormのWebhookリクエストにはどのフォームから来たかを示すform_response.form_idが含まれる。

function doPost(e) {
    const payload = JSON.parse(e.postData.contents);
    const formId = payload.form_response.form_id;

    if (formId === REGISTRATION_FORM_ID) {
        handleRegistration(payload);
    } else if (formId === REVIEW_FORM_ID) {
        handleReviewRequest(payload);
    }

    return ContentService.createTextOutput(
        JSON.stringify({ status: 'ok' })
    ).setMimeType(ContentService.MimeType.JSON);
}

1つのGASエンドポイントで2つのフォームを受け取る。form_idjwLurS10という形式だ。TypeFormのフォームURLから取得して定数として定義する。


フィールドタイトルのキーワードマッチ

受講者情報(名前・メールアドレス)はTypeFormのフィールドから取得する。しかしフィールドのIDはフォームごとに異なる。

Flex版ではフィールドのタイトルにキーワードが含まれるかどうかで値を取得する。

function getVal(fields, keyword) {
    const field = fields.find(f =>
        f.field && f.field.title && f.field.title.includes(keyword)
    );
    if (!field) return '';
    return field.text || field.email || field.url || '';
}

const name = getVal(fields, '氏名') || getVal(fields, '名前') || getVal(fields, 'お名前');
const email = getVal(fields, 'メール') || getVal(fields, 'email');

「氏名」「名前」「お名前」のどのラベルを使っても、同じコードで取得できる。TypeFormのフォームを自由に設計しても、GASのコードを変更する必要がない。


コードをコピーして設定値を変えた瞬間から、「ここは変えてあそこは変えていない」という管理コストが積み上がりはじめる。変わるものと変わらないものを分けることが、設計の仕事だ。


*次回は「スプレッドシートをキューとして使い、GASの30秒制限を回避する」*

*シンプルシステム株式会社 代表 伊藤勝彦*