この記事は、初回相談フォームの回答を、AI導入の優先度、注意点、人間確認の有無まで見える診断キューに変えるための実装メモです。
AI APIは呼びません。Googleフォームとスプレッドシート、Google Apps Scriptだけで、フォーム回答を「そのまま読むログ」から「次に確認するべき診断キュー」へ変換します。
作るもの
Googleフォームの回答シートから、次の2枚のシートを作ります。
- consultation_responses: 初回相談フォーム回答
- diagnosis_queue: AI導入診断キュー
- diagnosis_rules: 判定ルール
完成形は、初回相談を受けたあとに、どの相談を先に見るか、どの情報はAIへ渡さないか、どの担当者が確認するかを小さく揃える台帳です。
なぜ初回相談フォームを診断キューにするのか
AI導入相談の入口では、次のような回答が混ざります。
- 問い合わせ対応を早くしたい
- FAQを整えたい
- 営業メモをCRMへ残したい
- 予約や日程調整を自動化したい
- 社内ナレッジを検索しやすくしたい
- 個人情報や契約情報を含む可能性がある
- そもそも何から始めるべきかわからない
フォーム回答をそのまま読むだけだと、担当者は毎回同じ判断をします。
- これはどの業務領域か
- AI化の前にフォームやFAQを直すべきか
- 人間確認が必要な内容か
- 個人情報や契約情報が混ざっていないか
- 次に聞くべき質問は何か
診断キューを作る目的は、AIで自動回答することではありません。
初回相談を「読む」だけで終わらせず、導入順、確認者、注意点、次アクションへ分解することです。
consultation_responses シート
Googleフォームの回答シートは、例として次の列を想定します。
| 列 | 項目 | 例 |
|---|---|---|
| A | timestamp | 2026/06/28 11:18 |
| B | response_id | RES-20260628-001 |
| C | company_name | 株式会社サンプル |
| D | contact_email | info@example.com |
| E | business_area | 問い合わせ対応 |
| F | current_tool | Googleフォームとスプレッドシート |
| G | monthly_volume | 80 |
| H | pain_point | 返信漏れとFAQ不足 |
| I | contains_sensitive | いいえ |
| J | desired_outcome | まず問い合わせを整理したい |
| K | free_text | 料金や導入手順の質問が多い |
ここで重要なのは、自由記述だけに頼らないことです。
月間件数、業務領域、現在の管理方法、センシティブ情報の有無を分けておくと、AI導入前の優先度を機械的に見やすくできます。
diagnosis_rules シート
判定ルールは、スプレッドシート上で編集できるようにします。
| rule_key | field | match_type | keyword | score | risk_flag | next_action |
|---|---|---|---|---|---|---|
| inquiry_area | business_area | contains | 問い合わせ | 3 | false | FAQ候補と返信下書き条件を確認 |
| high_volume | monthly_volume | gte | 50 | 2 | false | 月間件数が多いため優先確認 |
| sensitive | contains_sensitive | equals | はい | 0 | true | AI投入前に人間確認 |
| contract_keyword | free_text | contains | 契約 | 0 | true | 契約条件を含む可能性として確認 |
| crm_tool | current_tool | contains | CRM | 1 | false | CRM項目とステータスを確認 |
ルールは複雑でなくて構いません。
最初はキーワード、数値しきい値、危険フラグだけで十分です。
diagnosis_queue シート
GASで生成する診断キューです。
| 項目 | 例 |
|---|---|
| created_at | 2026/06/28 11:20 |
| diagnosis_id | DIA-20260628-001 |
| source_response_id | RES-20260628-001 |
| priority_score | 5 |
| workflow_area | 問い合わせ対応 |
| risk_level | low |
| human_review_required | false |
| suggested_first_step | FAQ候補ログと問い合わせ分類を作る |
| reviewer | operations_owner |
| status | waiting_review |
| decision_note | 月間件数が多く、問い合わせ対応が主課題 |
診断キューは、営業管理のためだけではなく、AI導入前の整理にも使います。
どの相談を先に見るかだけでなく、どこまでAIに任せてよいかを判断する入口になります。
GASコード
次のコードは、フォーム回答から診断キューを作る最小実装です。
const SHEETS = {
responses: 'consultation_responses',
rules: 'diagnosis_rules',
queue: 'diagnosis_queue',
};
function buildDiagnosisQueue() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const responseSheet = ss.getSheetByName(SHEETS.responses);
const rulesSheet = ss.getSheetByName(SHEETS.rules);
const queueSheet = getOrCreateSheet_(ss, SHEETS.queue);
const responses = readObjects_(responseSheet);
const rules = readObjects_(rulesSheet);
const existingIds = getExistingSourceIds_(queueSheet);
const now = new Date();
const output = [];
responses.forEach((response) => {
if (!response.response_id || existingIds.has(String(response.response_id))) {
return;
}
const diagnosis = evaluateResponse_(response, rules, now);
output.push(toQueueRow_(diagnosis));
});
if (queueSheet.getLastRow() === 0) {
queueSheet.appendRow([
'created_at',
'diagnosis_id',
'source_response_id',
'priority_score',
'workflow_area',
'risk_level',
'human_review_required',
'suggested_first_step',
'reviewer',
'status',
'decision_note',
]);
}
if (output.length > 0) {
queueSheet
.getRange(queueSheet.getLastRow() + 1, 1, output.length, output[0].length)
.setValues(output);
}
}
function evaluateResponse_(response, rules, now) {
const matchedRules = rules.filter((rule) => matchesRule_(response, rule));
const score = matchedRules.reduce((sum, rule) => sum + Number(rule.score || 0), 0);
const hasRisk = matchedRules.some((rule) => String(rule.risk_flag).toLowerCase() === 'true');
const workflowArea = response.business_area || '未分類';
return {
createdAt: now,
diagnosisId: buildDiagnosisId_(now, response.response_id),
sourceResponseId: response.response_id,
priorityScore: score,
workflowArea,
riskLevel: hasRisk ? 'needs_review' : score >= 4 ? 'medium' : 'low',
humanReviewRequired: hasRisk,
suggestedFirstStep: buildFirstStep_(response, matchedRules, hasRisk),
reviewer: hasRisk ? 'operations_owner' : 'cs_owner',
status: 'waiting_review',
decisionNote: matchedRules.map((rule) => rule.next_action).filter(Boolean).join(' / '),
};
}
function matchesRule_(response, rule) {
const field = String(rule.field || '');
const value = response[field];
const keyword = String(rule.keyword || '');
const matchType = String(rule.match_type || 'contains');
if (matchType === 'contains') {
return String(value || '').includes(keyword);
}
if (matchType === 'equals') {
return String(value || '') === keyword;
}
if (matchType === 'gte') {
return Number(value || 0) >= Number(keyword || 0);
}
return false;
}
function buildFirstStep_(response, matchedRules, hasRisk) {
if (hasRisk) {
return 'AI投入前にセンシティブ情報と人間確認条件を確認する';
}
const area = String(response.business_area || '');
if (area.includes('問い合わせ')) {
return '問い合わせ分類、FAQ候補、人間確認ルールを作る';
}
if (area.includes('営業') || area.includes('CRM')) {
return 'CRM項目、ステータス、送信抑止リストを確認する';
}
if (matchedRules.length === 0) {
return '業務領域と導入目的を追加ヒアリングする';
}
return '業務棚卸しと自動化しない範囲を確認する';
}
function toQueueRow_(diagnosis) {
return [
diagnosis.createdAt,
diagnosis.diagnosisId,
diagnosis.sourceResponseId,
diagnosis.priorityScore,
diagnosis.workflowArea,
diagnosis.riskLevel,
diagnosis.humanReviewRequired,
diagnosis.suggestedFirstStep,
diagnosis.reviewer,
diagnosis.status,
diagnosis.decisionNote,
];
}
function readObjects_(sheet) {
const values = sheet.getDataRange().getValues();
const headers = values.shift().map((header) => String(header).trim());
return values
.filter((row) => row.some((cell) => cell !== ''))
.map((row) => Object.fromEntries(headers.map((key, index) => [key, row[index]])));
}
function getExistingSourceIds_(sheet) {
if (sheet.getLastRow() < 2) return new Set();
const values = sheet.getRange(2, 3, sheet.getLastRow() - 1, 1).getValues();
return new Set(values.map((row) => String(row[0])));
}
function buildDiagnosisId_(date, responseId) {
const ymd = Utilities.formatDate(date, 'Asia/Tokyo', 'yyyyMMdd');
const suffix = String(responseId).replace(/[^0-9A-Za-z]/g, '').slice(-6);
return `DIA-${ymd}-${suffix}`;
}
function getOrCreateSheet_(ss, name) {
return ss.getSheetByName(name) || ss.insertSheet(name);
}
サンプルルール
diagnosis_rules は、最初は次のような内容で始められます。
rule_key,field,match_type,keyword,score,risk_flag,next_action
inquiry_area,business_area,contains,問い合わせ,3,false,FAQ候補と返信下書き条件を確認
high_volume,monthly_volume,gte,50,2,false,月間件数が多いため優先確認
sensitive,contains_sensitive,equals,はい,0,true,AI投入前に人間確認
contract_keyword,free_text,contains,契約,0,true,契約条件を含む可能性として確認
crm_tool,current_tool,contains,CRM,1,false,CRM項目とステータスを確認
診断キューで見るべき項目
診断キューができたら、次の順に見ます。
| 観点 | 見ること |
|---|---|
| human_review_required | AIへ渡す前に人間確認が必要か |
| priority_score | 先に対応すべき相談か |
| workflow_area | 問い合わせ、CRM、FAQ、営業などの領域 |
| suggested_first_step | どの台帳やルールから作るか |
| decision_note | なぜその判断になったか |
スコアは厳密な評価ではありません。
担当者が最初に見る順番と、確認漏れを減らすための並び替えに使います。
AIへ送らない情報を先に分ける
初回相談フォームは、個人情報や契約情報が混ざりやすい入口です。
診断キューをAI下書きや要約に使う場合でも、次の情報は本文投入の前に分けます。
- 氏名、メールアドレス、電話番号
- 契約番号、請求、返金、解約に関する具体情報
- 顧客固有の内部事情
- 法務、医療、税務など専門判断に近い相談
- 秘密情報、APIキー、パスワード、認証情報
今回のGASは、外部API送信をしません。
AI連携を後から足す場合も、診断キューの human_review_required と risk_level を見て、安全側に倒す設計にします。
運用チェックリスト
公開や社外送信の前に、次を確認します。
- フォーム回答に response_id がある
- 連絡先項目と相談本文が分かれている
- センシティブ情報の有無を選択式で聞いている
- 診断ルールを人間が編集できる場所に置いている
- 未分類の相談を自動処理へ流していない
- 診断キューに確認担当と次アクションが残っている
- AIへ送らない情報を先に分けている
ここでは自動化しないこと
この記事の実装では、次のことはしません。
- AI APIへフォーム本文を送る
- 顧客へ自動返信する
- 契約、返金、法務、医療、税務に関する判断を自動化する
- 個人情報を含む自由記述をそのまま外部サービスへ送る
- スコアだけで受注確度や売上を断定する
初回相談フォームの目的は、AIで即時回答することではありません。
AI導入前に、どの業務から整えるべきか、どこで人間が確認すべきかを見える状態にすることです。
まとめ
初回相談フォームは、受け付けるだけでなく、AI導入診断キューへ変換できます。
- フォーム回答に業務領域、件数、注意情報を分けて持たせる
- diagnosis_rules でスコアとリスクを判定する
- diagnosis_queue に優先度、確認担当、次アクションを残す
- センシティブ情報や契約系の相談はAI投入前に止める
- AI導入は、ツール選定より先に入口の整理から始める
Miraigentでは、問い合わせフォーム、FAQ、CRM、人間確認ルールをまとめて見直し、AI導入前の無料診断として整理しています。
