AIの対象業務や確認ルールを変更した後、「いつ、誰が、何を確認するか」が決まっていないと、古い判断がそのまま使われます。
この記事では、GoogleスプレッドシートとGoogle Apps Script(GAS)で、変更イベントから再確認日を計算し、確認待ちの一覧を作る最小構成を紹介します。GASが導入可否や顧客影響を判断する仕組みではありません。期限の設定と候補の抽出までを補助し、再確認・承認・停止解除は人が行います。
作るもの
AI_CHANGE_REVIEWS シートに、変更一件を一行で記録します。
review_id,work_id,change_type,changed_at,impact_level,
source_ref,review_owner,review_due_at,review_state,
exception_rule,next_action,last_reviewed_at
change_type は、たとえば次のように固定します。
RULE # 判定・プロンプト・確認ルールの変更
SOURCE # FAQ、手順書、正本データの変更
INPUT # フォームやCRMの入力項目の変更
OWNER # 担当者・確認者・代理の変更
EXCEPTION # 例外や停止条件の追加・変更
再確認日を決める6項目
変更があった日だけでなく、影響と確認者を記録します。
| 項目 | 記録する内容 | 例 |
|---|---|---|
| 変更イベント | 何が変わったか | FAQの回答条件を更新 |
| 変更日時 | 現行版が有効になった日時 | 2026-10-06 09:00 |
| 影響度 | 通常・境界・停止のどこに影響するか | 境界 |
| 正本 | 比較する現行資料 | FAQ v4 |
| 確認者 | 業務の判断をする役割 | サポート責任者 |
| 再確認日 | 人が見直しを始める日時 | 2026-10-08 09:00 |
再確認日は「その日に自動で新ルールへ切り替える日時」ではありません。変更の影響を人が確認する開始日時です。
先に状態を固定する
変更後の案件は、次の3状態に分けます。
CONTINUE # 変更の影響がなく、通常の確認で進められる
REVIEW # 影響する可能性があり、人が再確認する
HOLD # 情報不足・重大な影響・確認者不在で手動へ戻す
期限が来たから CONTINUE に移す処理は作りません。REVIEW または HOLD のまま、確認者が根拠を見て状態を更新します。
GASで入力値を検査する
まず、見出しと状態を検査します。シート名や値をコードに固定しすぎず、入力ミスを早く見つけることを目的にします。
const SHEET_NAME = 'AI_CHANGE_REVIEWS';
const REQUIRED_HEADERS = [
'review_id', 'work_id', 'change_type', 'changed_at', 'impact_level',
'source_ref', 'review_owner', 'review_due_at', 'review_state',
'exception_rule', 'next_action', 'last_reviewed_at'
];
const REVIEW_STATES = ['CONTINUE', 'REVIEW', 'HOLD'];
const CHANGE_TYPES = ['RULE', 'SOURCE', 'INPUT', 'OWNER', 'EXCEPTION'];
function validateChangeReviews() {
const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME);
if (!sheet) throw new Error(SHEET_NAME + ' sheet is missing');
const values = sheet.getDataRange().getValues();
if (values.length === 0) throw new Error('header row is missing');
const headers = values.shift().map(String);
const index = Object.fromEntries(headers.map((name, i) => [name, i]));
const missing = REQUIRED_HEADERS.filter((name) => index[name] === undefined);
if (missing.length) throw new Error('Missing headers: ' + missing.join(', '));
const errors = [];
values.forEach((row, offset) => {
if (row.every((cell) => cell === '')) return;
const line = offset + 2;
const state = String(row[index.review_state] || '');
const changeType = String(row[index.change_type] || '');
const dueAt = row[index.review_due_at];
if (!CHANGE_TYPES.includes(changeType)) {
errors.push('row ' + line + ': invalid change_type');
}
if (!REVIEW_STATES.includes(state)) {
errors.push('row ' + line + ': invalid review_state');
}
if (!String(row[index.review_owner] || '').trim()) {
errors.push('row ' + line + ': review_owner is required');
}
if (!dueAt || Number.isNaN(new Date(dueAt).getTime())) {
errors.push('row ' + line + ': review_due_at is required');
}
if (state !== 'CONTINUE' && !String(row[index.exception_rule] || '').trim()) {
errors.push('row ' + line + ': exception_rule is required');
}
});
if (errors.length) throw new Error(errors.join('\n'));
return {ok: true, rows: values.length};
}
変更から再確認日を計算する
最初は影響度ごとに確認間隔を決めます。日数は会社の業務特性に合わせて変更してください。ここで行うのは日付の候補作りであり、確認の完了ではありません。
const REVIEW_INTERVAL_DAYS = {
normal: 7,
boundary: 2,
stop: 0
};
function suggestReviewDueAt(changedAt, impactLevel) {
const level = String(impactLevel || '').toLowerCase();
if (!(changedAt instanceof Date) || Number.isNaN(changedAt.getTime())) {
throw new Error('changedAt must be a valid Date');
}
if (REVIEW_INTERVAL_DAYS[level] === undefined) {
throw new Error('unknown impact level: ' + impactLevel);
}
const due = new Date(changedAt.getTime());
due.setDate(due.getDate() + REVIEW_INTERVAL_DAYS[level]);
return due;
}
stop を0日にしているのは、停止条件に該当する変更を後回しにしないためです。ただし、0日になった行を自動再開させず、必ず HOLD として人へ戻します。
確認期限が来た行を一覧化する
期限を過ぎた行の next_action を更新します。状態を進めないため、時間主導トリガーで毎朝実行しても導入範囲は変わりません。
function listDueReviews() {
const sheet = SpreadsheetApp.getActive().getSheetByName(SHEET_NAME);
const values = sheet.getDataRange().getValues();
const headers = values.shift().map(String);
const index = Object.fromEntries(headers.map((name, i) => [name, i]));
const now = new Date();
return values.flatMap((row, offset) => {
const dueAt = new Date(row[index.review_due_at]);
const state = String(row[index.review_state] || '');
if (!['REVIEW', 'HOLD'].includes(state) || dueAt.getTime() > now.getTime()) {
return [];
}
return [{
row: offset + 2,
reviewId: String(row[index.review_id] || ''),
owner: String(row[index.review_owner] || ''),
nextAction: '変更の影響を確認し、根拠と状態を人が更新する'
}];
});
}
通知を追加する場合も、メールやチャットへ顧客本文を含めず、review_id と確認先だけを送ります。
変更後の確認手順
- 変更ID、有効日時、旧版、現行版を正本から確認する
- 変更境界の前後で処理された案件を候補化する
- 通常・境界・停止の1件ずつを選ぶ
- 入力情報、参照先、承認状態、外部送信の有無を人が確認する
-
CONTINUE、REVIEW、HOLDと根拠を記録する - FAQ、フォーム、承認ルール、手動キューの改善先を更新する
自動化しない境界
- 顧客への返信や外部送信の最終判断
- 契約、料金、返金、法的な権利義務の判断
- 個人情報・機密情報をAIへ渡してよいかの判断
-
HOLDからの自動再開、確認者不在時の代理承認 - 重大な苦情や安全上の問題がある案件の継続
迷う場合は HOLD にして、原文を扱える担当者と手動の戻し先を記録します。シートには顧客本文やメールアドレスをコピーせず、権限管理された原本の参照先だけを置きます。
まとめ
AI導入の変更後に必要なのは、変更を告知することだけではありません。
- 変更イベントと有効日時を固定する
- 影響度、正本、確認者、再確認日を記録する
- 期限を自動再開ではなく、人間レビューの候補化に使う
- 通常・境界・停止のケースを確認する
- 判断結果をFAQ、フォーム、承認ルールへ戻す
まずは直近の変更一件を AI_CHANGE_REVIEWS に登録し、再確認日を決めてください。Miraigentでも、AIを広げる前に「変更後、次はいつ誰が見るか」を確認することを、運用診断の入口にしています。
