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返信の承認者交代をGASで止め漏れなく管理する:引き継ぎチェックリスト実装

0
Posted at

AIに問い合わせ返信の下書きを作らせても、承認者が休暇・異動で不在なら送信工程は進めてはいけません。実務では「代理担当を決めたつもりだが、正本を読めない」「引き継ぎ期限が切れている」といった状態が起きます。

この記事では、GoogleスプレッドシートとGoogle Apps Script(GAS)で、AI返信の承認者交代を検査する最小構成を作ります。AI APIの呼び出しや顧客への自動送信は実装せず、送信前に人間確認へ戻すゲートに限定します。

この記事で作るもの

  • REPLY_QUEUE: AI下書きと承認状態の台帳
  • APPROVERS: 承認者・代理担当・適用期間の管理表
  • HANDOFF_LOG: 引き継ぎ確認の履歴
  • scanApprovalHandoffs(): 承認可能かを判定して停止理由を記録する関数

AI返信の確認条件と人間確認の境界

シートを先に固定する

問い合わせ本文や顧客の個人情報は、引き継ぎログへコピーしません。必要な担当者が正本を開けるよう、IDと参照先だけを保存します。

REPLY_QUEUE
reply_id | inquiry_id | approver_role | status | source_ref | draft_version | review_due
R-001    | INQ-031    | reply_approver | PENDING_APPROVAL | crm://INQ-031 | v3 | 2026-09-10

APPROVERS
role | member_state | backup_role | effective_from | confirmed_at | source_ref
reply_approver | active | reply_approver_backup | 2026-09-03T09:00:00+09:00 | 2026-09-03T08:50:00+09:00 | drive://handoff/31

HANDOFF_LOG
reply_id | reason_code | checked_by_role | checked_at | status
R-001    | handoff_ready | reply_approver_backup | 2026-09-03T09:05:00+09:00 | OPEN

member_stateactiveawayinactive のような業務状態に絞ります。休暇理由、人事評価、顧客本文をAIへ渡さない設計にします。

停止条件をコードより先に決める

reason_code 条件 次の状態
approver_missing 承認役割が空欄 BLOCKED
handoff_not_effective 交代の適用開始前 BLOCKED
backup_unconfirmed 代理担当の確認日時がない BLOCKED
evidence_missing 正本・原文・下書き版の参照がない BLOCKED
review_expired 再確認期限を過ぎている BLOCKED
human_owner_required 契約・支払い・苦情・本人確認など HUMAN_REVIEW
handoff_ready 必須条件がそろっている PENDING_APPROVAL

ここでの handoff_ready は「自動送信してよい」ではありません。あくまで「現在の承認役割へ人間確認を依頼できる」状態です。

GASで引き継ぎゲートを検査する

まず、行をオブジェクトへ変換する共通関数と、日時・必須値の検査を用意します。

const SHEETS = {
  replies: 'REPLY_QUEUE',
  approvers: 'APPROVERS',
  log: 'HANDOFF_LOG',
};

function rowsToObjects_(sheet) {
  const values = sheet.getDataRange().getValues();
  if (values.length < 2) return [];
  const [headers, ...rows] = values;
  return rows.filter((row) => row.some(Boolean)).map((row) =>
    Object.fromEntries(headers.map((header, i) => [header, row[i]]))
  );
}

function isFuture_(value, now) {
  return value && new Date(value).getTime() > now.getTime();
}

function checkReplyHandoff_(reply, approver, now = new Date()) {
  if (!reply.reply_id || !reply.inquiry_id || !reply.approver_role) {
    return { code: 'approver_missing', state: 'BLOCKED' };
  }
  if (approver && isFuture_(approver.effective_from, now)) {
    return { code: 'handoff_not_effective', state: 'BLOCKED' };
  }
  if (!approver || !approver.backup_role || !approver.confirmed_at) {
    return { code: 'backup_unconfirmed', state: 'BLOCKED' };
  }
  if (!reply.source_ref || !reply.draft_version || !reply.review_due) {
    return { code: 'evidence_missing', state: 'BLOCKED' };
  }
  if (new Date(reply.review_due).getTime() <= now.getTime()) {
    return { code: 'review_expired', state: 'BLOCKED' };
  }
  if (['contract', 'payment', 'complaint', 'identity_check']
      .includes(reply.category)) {
    return { code: 'human_owner_required', state: 'HUMAN_REVIEW' };
  }
  return { code: 'handoff_ready', state: 'PENDING_APPROVAL' };
}

次に、承認者表を役割で引き、判定結果をログへ追記します。既存の返信行を送信済みに変更する処理は入れません。

function scanApprovalHandoffs() {
  const ss = SpreadsheetApp.getActive();
  const replies = rowsToObjects_(ss.getSheetByName(SHEETS.replies));
  const approvers = rowsToObjects_(ss.getSheetByName(SHEETS.approvers));
  const approverByRole = new Map(
    approvers.map((row) => [row.role, row])
  );
  const log = ss.getSheetByName(SHEETS.log);
  const now = new Date();

  replies
    .filter((reply) => reply.status === 'PENDING_APPROVAL')
    .forEach((reply) => {
      const result = checkReplyHandoff_(
        reply,
        approverByRole.get(reply.approver_role),
        now
      );
      log.appendRow([
        reply.reply_id,
        result.code,
        reply.approver_role || '',
        now.toISOString(),
        result.state,
      ]);
    });
}

引き継ぎ日に行うチェックリスト

  • PENDING_APPROVAL の返信を一覧化した
  • 現担当と代理担当の役割が決まっている
  • 交代の適用開始日時が現在時刻より前である
  • 正本、問い合わせ原文、下書き版を代理担当が参照できる
  • 通常・境界・停止のサンプルを各1件確認した
  • 期限切れを再生成でごまかさず、担当責任者へ戻した
  • 契約、支払い、苦情、本人確認は HUMAN_REVIEW にした
  • 翌営業日の再確認日と確認役割を記録した

トリガー設定と運用上の注意

時間主導トリガーで scanApprovalHandoffs を定期実行できます。ただし、トリガーが動いたことと返信を送れることは分けてください。GASは停止理由を記録するだけにし、送信処理・承認者の自動推測・権限変更は担当者が行います。

また、同じ返信を何度もログへ追加すると履歴が読みにくくなるため、本番では reply_id + reason_code + checked_at の重複方針を決めてください。ログの保存期間や削除は、社内の管理ルールに合わせて人間が確定します。

まとめ

承認者交代の自動化で守るべきものは、返信の速さだけではありません。誰が、どの正本を、いつまで確認し、どの条件で責任者へ戻すのかを残すことです。

まずは過去1週間の返信待ちを5件だけ使い、ゲート判定と停止理由が正しく記録されるか確認してください。空欄になった列が、次に整えるべきCRM、権限、FAQの改善点になります。

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?