0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AI判断ログを古いまま使わない:6つの変更イベントをGASで検知する

0
Posted at

AIを業務に組み込んだ後に起きやすい事故は、モデルが突然壊れることだけではありません。担当者や入力項目が変わったのに、以前の判断ログを現在の手本として使い続けることも危険です。

本記事では、Googleスプレッドシートに保存したAI判断ログへ、更新が必要になる6つの変更イベントを記録し、期限が来た行をGASで抽出する最小構成を作ります。GASは候補の検出までに限定し、ログを現行ルールとして採用するか、保持・更新・停止のどれにするかは人間が決めます。

問い合わせを根拠付きで人へ引き継ぐレビューゲート

対象読者と完成状態

  • AIで問い合わせ分類や返信案作成を試している管理者
  • 判断ログをSheetsに残しているが、見直しの合図が決まっていない担当者
  • GASで定期確認の候補を作りたい人

完成状態では、JUDGMENT_LOGS に記録した変更イベントから、見直し期限を過ぎたログを一覧化できます。AIが「このログを更新すべき」と確定する処理や、古いログの削除処理は作りません。

1. シートを2つに分ける

観測した事実と、人間が確定した運用判断を同じ行で上書きしないようにします。

JUDGMENT_LOGS
log_id, task_name, rule_version, decision, source_ref,
observed_at, review_due_at, change_event, status, owner

REVIEW_DECISIONS
review_id, log_id, action, reason, reviewer, reviewed_at, next_review_at

action は retain、update、retire の3値に固定します。decision はAIの出力ではなく、その時点で人間が採用した業務上の判断です。顧客本文、氏名、メールアドレス、認証情報はログへ複製せず、必要なら社内の正本を指す参照IDだけを保存します。

2. 更新を検討する6つのイベント

change_event 例 まず確認するもの
owner_changed 担当者・確認者が交代した 役割と代理確認
input_changed 入力フォームや必須項目が変わった 入力と出力の対応
scope_changed 対象業務や対象顧客を広げた 対象外と停止条件
ai_changed モデル、プロンプト、設定を変えた 比較用の再検証
exception_changed 契約・苦情などの扱いを変えた 人間確認への戻し先
reaction_changed 差し戻しや問い合わせ傾向が変わった FAQ・ルールの改善先

6つは自動更新の命令ではありません。イベントが起きたら、該当ログを人間のレビューキューへ入れるためのトリガーです。

3. 変更イベントを登録する設定

イベント名を自由記述にすると、input-change と input_changed のような揺れで集計できなくなります。設定値をコード側で固定します。

const LOG_SHEET = 'JUDGMENT_LOGS';
const REVIEW_SHEET = 'REVIEW_DECISIONS';
const EVENT_TYPES = [
  'owner_changed',
  'input_changed',
  'scope_changed',
  'ai_changed',
  'exception_changed',
  'reaction_changed',
];
const ACTIONS = ['retain', 'update', 'retire'];

function assertOneOf_(value, values, label) {
  if (!values.includes(value)) {
    throw new Error(`${label} must be one of: ${values.join(', ')}`);
  }
}

function registerChangeEvent(logId, eventType, sourceRef, owner) {
  assertOneOf_(eventType, EVENT_TYPES, 'eventType');
  if (!String(logId).trim() || !String(sourceRef).trim()) {
    throw new Error('logId and sourceRef are required');
  }

  const sheet = SpreadsheetApp.getActive().getSheetByName(LOG_SHEET);
  if (!sheet) throw new Error(`Sheet not found: ${LOG_SHEET}`);
  sheet.appendRow([
    Utilities.getUuid(),
    logId,
    '',
    '',
    sourceRef,
    new Date(),
    new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
    eventType,
    'review_required',
    owner,
  ]);
}

sourceRef は変更を確認できる社内の文書IDやチケット番号を想定しています。顧客の本文をセルへ貼り付けるための引数ではありません。実際の運用では、フォーム送信や変更管理表からこの関数を呼び出し、入力値を検証してから登録します。

4. 見直し期限を過ぎたログを抽出する

列番号を直接参照せず、ヘッダーから列を作ります。シートに列を追加しても、別の値を誤って読むリスクを下げられます。

function headerMap_(headers) {
  return Object.fromEntries(
    headers.map((value, index) => [String(value).trim(), index])
  );
}

function findReviewDueLogs(now = new Date()) {
  const sheet = SpreadsheetApp.getActive().getSheetByName(LOG_SHEET);
  if (!sheet) throw new Error(`Sheet not found: ${LOG_SHEET}`);
  const values = sheet.getDataRange().getValues();
  if (values.length < 2) return [];

  const map = headerMap_(values.shift());
  const required = ['log_id', 'review_due_at', 'change_event', 'status', 'owner'];
  const missing = required.filter((key) => map[key] === undefined);
  if (missing.length) throw new Error(`Missing columns: ${missing.join(', ')}`);

  return values
    .map((row, index) => ({row, rowNumber: index + 2}))
    .filter(({row}) => {
      const dueAt = row[map.review_due_at];
      return row[map.status] === 'review_required'
        && dueAt instanceof Date
        && dueAt <= now
        && EVENT_TYPES.includes(String(row[map.change_event]));
    })
    .map(({row, rowNumber}) => ({
      rowNumber,
      logId: String(row[map.log_id]),
      eventType: String(row[map.change_event]),
      owner: String(row[map.owner]),
    }));
}

この関数の返り値をChat通知やメール送信へ直結させる場合も、通知は「レビュー候補がある」という事実にとどめます。update や retire を自動入力したり、顧客への返信を送ったりはしません。

5. 人間のレビュー結果を保存する

レビュー担当者が根拠と次回確認日を残してから、対象ログの状態を更新します。

function saveReviewDecision(logId, action, reason, reviewer, nextReviewAt) {
  assertOneOf_(action, ACTIONS, 'action');
  if (![logId, reason, reviewer, nextReviewAt].every((value) => String(value).trim())) {
    throw new Error('logId, reason, reviewer and nextReviewAt are required');
  }

  const sheet = SpreadsheetApp.getActive().getSheetByName(REVIEW_SHEET);
  if (!sheet) throw new Error(`Sheet not found: ${REVIEW_SHEET}`);
  sheet.appendRow([
    Utilities.getUuid(), logId, action, reason, reviewer, new Date(), nextReviewAt,
  ]);
}

reason には「入力項目が変わったため、旧ログは保持するが現行手順の根拠にはしない」のように、次の担当者が判断を再現できる短い説明を残します。料金、契約、返金、苦情、個人情報の扱いをAIやGASに決めさせないことも明記します。

運用チェックリスト

  • 変更イベントを6つの固定値から選べる
  • ログの正本と判断結果の保存先を分けている
  • 変更の根拠を参照IDで追える
  • 見直し期限と担当者が空欄のままになっていない
  • retain / update / retire を人間が確定する
  • 個人情報、認証情報、顧客本文をログへ複製していない
  • 更新後のFAQ、フォーム、CRM、手順書の戻し先を記録した
  • 停止や手作業への復旧条件を確認した

まとめ

AI判断ログは、保存して終わりではありません。担当者、入力、対象範囲、AI設定、例外、現場の反応が変わった時に、古いログをそのまま手本にしないための見直しが必要です。

まずは直近10件のログに6つのイベントを付け、期限切れの候補を人間が retain / update / retire に分けてください。Miraigentでは、AIを使うことだけでなく、使わない・止める・更新する判断を次の担当者へ渡せる状態から業務設計を始めます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?