生成AIの利用ルールは、一度作れば終わりではありません。価格、担当者、社内規程、入力データ、利用モデルが変わると、以前の確認条件が合わなくなることがあります。
この記事では、GoogleスプレッドシートとGoogle Apps Script(GAS)を使い、変更を検知したら影響するルールと停止対象を人間の確認キューへ追加する最小構成を作ります。AIによる自動承認や、顧客向け処理の自動再開は行いません。
この記事で作るもの
3枚のシートを使います。
-
EVENT_RULES: 変更種別、影響ルール、確認者、停止対象を定義 -
CHANGE_EVENTS: 発生した変更とレビュー状態を記録 -
REVIEW_LOG: 人間が行った判断と理由を記録
処理の流れは次の通りです。
- 商品、価格、担当者などの変更を登録する
- GASが変更種別に対応する影響ルールを抽出する
- 停止対象があればレビュー待ちとして記録する
- 担当者が通常例、境界例、停止例を確認する
- 権限を持つ人が承認、修正、却下を記録する
- 別の運用手順で停止中の処理を再開する
このコードは5までを支援します。外部返信や見積もりを再開する処理は実装しません。
EVENT_RULESシートを作る
1行目に次の見出しを置きます。
event_type | affected_rules | reviewer_role | priority | pause_actions | enabled
代表的な7種類を登録します。
product_changed | faq,proposal_scope | product_owner | medium | external_reply | TRUE
price_changed | price_reply,quote_draft,approval_threshold | sales_owner | high | external_reply,quote_generation | TRUE
owner_changed | approval_route,escalation | ops_owner | high | new_automation_activation | TRUE
policy_changed | input_policy,usage_scope,logging | policy_owner | high | external_reply,new_automation_activation | TRUE
input_changed | masking,storage,access_control | data_owner | high | ai_input,new_automation_activation | TRUE
ai_changed | output_review,evaluation_set | ai_ops_owner | high | new_automation_activation | TRUE
feedback_changed | faq,escalation,prohibited_response | cs_owner | medium | | TRUE
pause_actions はAI利用全体ではなく、レビュー中に止める必要がある処理だけに絞ります。たとえば価格変更時は、問い合わせ分類を続けながら、金額を含む外部返信と見積もり生成を止められます。
CHANGE_EVENTSとREVIEW_LOGを作る
CHANGE_EVENTS の見出しです。
event_id | detected_at | event_type | source_ref | summary | affected_rules | reviewer_role | priority | pause_actions | status
REVIEW_LOG の見出しです。
event_id | reviewed_at | reviewed_by | decision | reason | tested_normal | tested_boundary | tested_stop | next_review_at
source_ref には価格表や規程など、アクセス制御された正本のIDを入れます。顧客名、契約書本文、問い合わせ原文を変更ログへ複製しないでください。
GASで変更イベントを登録する
スプレッドシートに紐づくApps Scriptへ追加します。
const RULE_SHEET = 'EVENT_RULES';
const EVENT_SHEET = 'CHANGE_EVENTS';
const REVIEW_SHEET = 'REVIEW_LOG';
function rowsToObjects_(sheet) {
const values = sheet.getDataRange().getDisplayValues();
if (values.length < 2) return [];
const headers = values.shift();
return values
.filter((row) => row.some((cell) => cell !== ''))
.map((row) => Object.fromEntries(
headers.map((header, index) => [header, row[index]])
));
}
function splitList_(value) {
return String(value || '')
.split(',')
.map((item) => item.trim())
.filter(Boolean);
}
function findEventRule_(eventType) {
const sheet = SpreadsheetApp.getActive().getSheetByName(RULE_SHEET);
if (!sheet) throw new Error(`${RULE_SHEET} sheet not found`);
return rowsToObjects_(sheet).find((rule) =>
rule.event_type === eventType && rule.enabled === 'TRUE'
);
}
function registerChangeEvent(input) {
if (!input || !input.event_type || !input.source_ref || !input.summary) {
throw new Error('event_type, source_ref, summary are required');
}
const sheet = SpreadsheetApp.getActive().getSheetByName(EVENT_SHEET);
if (!sheet) throw new Error(`${EVENT_SHEET} sheet not found`);
const rule = findEventRule_(input.event_type);
const eventId = `CHG-${Utilities.getUuid()}`;
const reviewTask = rule
? {
affectedRules: splitList_(rule.affected_rules),
reviewerRole: rule.reviewer_role,
priority: rule.priority,
pauseActions: splitList_(rule.pause_actions),
status: 'review_pending',
}
: {
affectedRules: [],
reviewerRole: 'ops_owner',
priority: 'high',
pauseActions: ['new_automation_activation'],
status: 'manual_classification_required',
};
sheet.appendRow([
eventId,
new Date(),
input.event_type,
input.source_ref,
input.summary,
reviewTask.affectedRules.join(','),
reviewTask.reviewerRole,
reviewTask.priority,
reviewTask.pauseActions.join(','),
reviewTask.status,
]);
return { eventId, ...reviewTask };
}
未知の event_type を通常処理へ流さず、manual_classification_required にするのが重要です。分類が未定でも、新しい自動処理の有効化だけは保留できます。
人間のレビュー結果を記録する
承認者、理由、3種類のテスト結果が揃わない限り、完了扱いにしません。
function recordReview(input) {
const required = [
'event_id', 'reviewed_by', 'decision', 'reason',
'tested_normal', 'tested_boundary', 'tested_stop',
'next_review_at',
];
const missing = required.filter((key) => !input[key]);
if (missing.length) {
throw new Error(`missing fields: ${missing.join(', ')}`);
}
if (!['approved', 'revised', 'rejected'].includes(input.decision)) {
throw new Error('invalid decision');
}
const reviewSheet = SpreadsheetApp.getActive().getSheetByName(REVIEW_SHEET);
const eventSheet = SpreadsheetApp.getActive().getSheetByName(EVENT_SHEET);
if (!reviewSheet || !eventSheet) throw new Error('required sheet not found');
reviewSheet.appendRow([
input.event_id,
new Date(),
input.reviewed_by,
input.decision,
input.reason,
input.tested_normal,
input.tested_boundary,
input.tested_stop,
input.next_review_at,
]);
const rows = eventSheet.getDataRange().getValues();
const rowIndex = rows.findIndex((row, index) =>
index > 0 && row[0] === input.event_id
);
if (rowIndex < 0) throw new Error('event not found');
const statusColumn = rows[0].indexOf('status') + 1;
eventSheet.getRange(rowIndex + 1, statusColumn).setValue(
input.decision === 'rejected' ? 'rejected' : 'reviewed'
);
}
この関数が reviewed を付けても、停止中の処理を自動再開しません。実運用では、承認者の権限確認と再開手順を別に用意してください。
期限超過のレビューを抽出する
変更イベントが放置されていないか、時間主導トリガーで確認できます。
function listPendingReviews() {
const sheet = SpreadsheetApp.getActive().getSheetByName(EVENT_SHEET);
const pendingStatuses = new Set([
'review_pending',
'manual_classification_required',
]);
const pending = rowsToObjects_(sheet).filter((event) =>
pendingStatuses.has(event.status)
);
console.log(JSON.stringify(pending.map((event) => ({
event_id: event.event_id,
event_type: event.event_type,
reviewer_role: event.reviewer_role,
priority: event.priority,
detected_at: event.detected_at,
}))));
return pending;
}
ログ出力だけでは担当者に届きません。実運用では社内のタスク管理や通知先へ接続しますが、通知本文へ機密情報を入れないようにします。
動作確認
次のテストデータを用意します。
-
price_changed: 既知の高優先度イベント -
feedback_changed: 停止対象が空の既知イベント -
vendor_changed: ルールにない未知イベント
確認項目です。
- 価格変更で3つの影響ルールと2つの停止対象が入る
- 顧客反応の変更はレビュー待ちになるが、不要な処理停止を作らない
-
未知イベントは
manual_classification_requiredになる - 必須項目が欠けたレビューを保存できない
-
decisionが許可値以外なら拒否される - 通常例、境界例、停止例が揃わないと完了できない
- 顧客情報や秘密情報をイベントログへ複製していない
- レビュー完了だけで外部処理が自動再開しない
この構成で自動化しないこと
- 変更後のルールを正式版にする判断
- 価格、契約、返金、個人情報、法務・会計・人事の判断
- 顧客向け返信や見積もりの自動再開
- AIによる承認者の代行
- 正本のアクセス権変更
GASは変更の見落としを減らすために使い、ルールの妥当性や再開可否は責任者が判断します。
まとめ
AI運用ルールを古いままにしないためには、定期見直しに加えて、変更が起きた時点でレビューを始める仕組みが必要です。
最初は直近3か月で最も多かった変更種別を1つだけ EVENT_RULES へ登録し、影響ルール、確認者、停止対象を決めてください。AI導入前の業務整理では、何が変わったら見直すか、誰が承認するか、確認中に何を止めるかまで一緒に決めると運用しやすくなります。
