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実装:WIP上限と停止理由を記録する

0
Last updated at Posted at 2026-09-03

AIに問い合わせ返信を下書きさせると、作成数は増えても確認担当者の処理能力は急に増えません。確認待ちを無制限に積むと、古い下書きが混ざり、何から見るべきか分からなくなります。

この記事では、GoogleスプレッドシートとGASで、返信下書きの確認待ち件数を数え、WIP(Work In Progress)上限を超えたら新しい下書きを HOLD にする最小構成を作ります。AI APIの呼び出し、顧客への自動送信、AIによる優先順位の最終決定は扱いません。

作るもの

AI返信の下書きを人間確認へ渡すゲート

REPLY_QUEUE シートを作り、1行目を次の列にします。

draft_id,inquiry_id,created_at,priority,review_status,reviewer,due_at,hold_reason,updated_at

review_status は次の値に限定します。

DRAFTED -> READY_FOR_REVIEW -> IN_REVIEW -> APPROVED
                    └───────────────> HOLD

APPROVED は人が確認した結果だけに使います。GASは READY_FOR_REVIEW と HOLD の振り分けまでを担当します。

WIP上限を設定ファイルにする

担当者の人数や一日の確認時間に合わせて、上限をコードの外から変更できるようにします。

const REVIEW_QUEUE_CONFIG = Object.freeze({
  maxReadyItems: 10,
  statusesCountedAsWip: ['READY_FOR_REVIEW', 'IN_REVIEW'],
  holdStatus: 'HOLD',
  holdReason: 'review_wip_limit_exceeded',
});

この数値は性能保証ではなく、確認負荷を観測するための運用上限です。上限を増やす前に、期限切れ、契約・料金・返金、苦情、個人情報、根拠不一致の件数を確認します。

列名で行を読み込む

列番号を直接参照すると、担当者が列を追加した時に別の値を更新する可能性があります。ヘッダー名から行をオブジェクト化します。

function readQueue_() {
  const sheet = SpreadsheetApp.getActiveSpreadsheet()
    .getSheetByName('REPLY_QUEUE');
  if (!sheet) throw new Error('REPLY_QUEUE sheet is missing');

  const values = sheet.getDataRange().getValues();
  if (values.length < 2) return { sheet, headers: values[0] || [], rows: [] };

  const headers = values[0].map(String);
  const rows = values.slice(1).map((rowValues, index) => {
    const row = { _rowNumber: index + 2 };
    headers.forEach((header, column) => {
      row[header] = rowValues[column];
    });
    return row;
  });
  return { sheet, headers, rows };
}

上限を超えた下書きを保留する

新しい順・重要度順などの並びを勝手に変えず、シート上の順序を尊重します。実際の優先順位は担当者が確認できる列に置き、GASは上限超過だけを判定します。

function applyReviewWipLimit() {
  const { sheet, rows } = readQueue_();
  const wip = rows.filter((row) =>
    REVIEW_QUEUE_CONFIG.statusesCountedAsWip.includes(row.review_status)
  ).length;

  let available = Math.max(
    REVIEW_QUEUE_CONFIG.maxReadyItems - wip,
    0
  );

  rows.filter((row) => row.review_status === 'DRAFTED')
    .forEach((row) => {
      if (available > 0) {
        sheet.getRange(row._rowNumber, 5, 1, 2).setValues([
          ['READY_FOR_REVIEW', row.reviewer || '']
        ]);
        available -= 1;
        return;
      }

      sheet.getRange(row._rowNumber, 5, 1, 2).setValues([
        [REVIEW_QUEUE_CONFIG.holdStatus, row.reviewer || '']
      ]);
      sheet.getRange(row._rowNumber, 8).setValue(
        REVIEW_QUEUE_CONFIG.holdReason
      );
    });
}

本番では、同時実行で上限を超えないよう LockService を使い、更新後に件数を再計算します。また、HOLD を自動で READY_FOR_REVIEW へ戻すのではなく、保留理由を見て人が再開します。

期限と停止理由を別に扱う

WIP上限は処理量の制御であり、安全性の判定ではありません。次の条件は、上限に空きがあっても HOLD または人間確認へ戻します。

条件 状態 理由コード例
根拠URL・確認日がない HOLD evidence_missing
期限を過ぎている HOLD due_date_expired
契約・料金・返金・苦情・個人情報 HOLD sensitive_topic
確認担当者が不在 HOLD reviewer_unavailable

下書きの本文や顧客情報をキュー管理用の集計へコピーせず、draft_id と元ログへの参照だけを残します。

運用チェックリスト

  • READY_FOR_REVIEW と IN_REVIEW をWIPとして数えている
  • 上限超過時に新規下書きを HOLD へ止めている
  • 保留理由と再確認条件を記録している
  • 上限を増やす前に確認負荷と期限切れを見ている
  • 契約、料金、返金、苦情、個人情報を自動送信していない
  • APPROVED は人の確認操作だけで設定される
  • 顧客本文や秘密情報を集計表へ複製していない

WIP上限は、AIの生成量を競うための数字ではありません。確認できる量を超えた下書きを止め、担当者が「なぜ止まっているか」を追えるようにするための境界です。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?