AIに問い合わせ返信を下書きさせると、作成数は増えても確認担当者の処理能力は急に増えません。確認待ちを無制限に積むと、古い下書きが混ざり、何から見るべきか分からなくなります。
この記事では、GoogleスプレッドシートとGASで、返信下書きの確認待ち件数を数え、WIP(Work In Progress)上限を超えたら新しい下書きを HOLD にする最小構成を作ります。AI APIの呼び出し、顧客への自動送信、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に任せる範囲を広げる前に、確認待ちの状態と停止理由を業務ログへ固定します。
