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で改善キューへ戻す:FAQ・フォーム・承認ルールを分けて直す

0
Posted at

AIに問い合わせ返信の下書きを作らせると、レビュー担当者からの差し戻しが増えることがあります。差し戻しを担当者の修正で終わらせると、同じ不足が次の案件でも繰り返されます。

この記事では、GoogleスプレッドシートとGoogle Apps Script(GAS)で、差し戻し理由を FAQFORMAPPROVALSOURCESCOPE の改善キューへ分類します。AI APIの呼び出し、顧客への自動送信、FAQの自動公開は扱いません。

この記事で作るもの

AI返信のレビュー結果を人間確認から改善キューへ戻す流れ

  • 返信レビューを1行で記録する ReplyReview シート
  • 差し戻し理由から改善先を決めるGAS
  • 同じ案件を何度もキューへ積まない重複防止
  • 改善担当者が確認できる ReplyImprovements シート

レビュー結果は人間が確定します。GASは記録の検証と改善候補の整理だけを行い、送信や設定変更は行いません。

まず差し戻し理由を5種類に固定する

自由記述だけでは、週次レビューで似た問題をまとめられません。最初は次の5種類に絞ります。

reason_code 典型例 戻し先
FAQ_GAP 正しい回答の根拠がFAQにない FAQの追加・更新候補
FORM_GAP 受付時に必要な条件が取れていない フォーム項目候補
APPROVAL_GAP 誰が決めるかが記録されていない 承認ルール候補
SOURCE_STALE 参照したFAQや規程が古い 正本・更新期限の確認
SCOPE_RISK 契約・返金・苦情など対象外の内容 人間確認・対象外ルール

reason_code は原因の候補であり、AIが決める最終結論ではありません。レビュー担当者が根拠を確認して選択します。

シートを準備する

ReplyReview の1行目に次の見出しを置きます。

review_id,inquiry_id,decision,reason_code,reason_note,source_refs,reviewer,reviewed_at,improvement_status

サンプルには実際の氏名、メールアドレス、契約番号、問い合わせ本文を入れません。

RV-001,INQ-001,revise,FORM_GAP,利用開始希望日の入力欄がない,FAQ-12,role_support,2026-09-13T09:30:00+09:00,open

decisionsendrevisehold の3値にします。send は担当者が送信前確認を終えた記録であり、自動送信の許可ではありません。

GASでレビュー行を検証する

const REVIEW_SHEET = 'ReplyReview';
const IMPROVEMENT_SHEET = 'ReplyImprovements';
const DECISIONS = new Set(['send', 'revise', 'hold']);
const REASONS = new Set([
  'FAQ_GAP', 'FORM_GAP', 'APPROVAL_GAP', 'SOURCE_STALE', 'SCOPE_RISK',
]);

const REQUIRED_HEADERS = [
  'review_id', 'inquiry_id', 'decision', 'reason_code', 'reason_note',
  'source_refs', 'reviewer', 'reviewed_at', 'improvement_status',
];

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

function validateReviewRows() {
  const sheet = SpreadsheetApp.getActive().getSheetByName(REVIEW_SHEET);
  if (!sheet) throw new Error(`${REVIEW_SHEET} シートがありません`);

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

  const index = headerIndex_(values[0]);
  REQUIRED_HEADERS.forEach((name) => {
    if (index[name] === undefined) throw new Error(`列がありません: ${name}`);
  });

  const errors = [];
  values.slice(1).forEach((row, offset) => {
    const line = offset + 2;
    const get = (name) => String(row[index[name]] ?? '').trim();
    if (!get('review_id') && !get('inquiry_id')) return;
    if (!get('review_id')) errors.push(`${line}行目: review_id が空です`);
    if (!get('inquiry_id')) errors.push(`${line}行目: inquiry_id が空です`);
    if (!DECISIONS.has(get('decision'))) {
      errors.push(`${line}行目: decision は send/revise/hold のいずれかです`);
    }
    const reason = get('reason_code');
    if (get('decision') !== 'send' && !REASONS.has(reason)) {
      errors.push(`${line}行目: 差し戻し・保留には有効な reason_code が必要です`);
    }
    if (get('decision') !== 'send' && !get('reason_note')) {
      errors.push(`${line}行目: 差し戻し・保留には reason_note が必要です`);
    }
    if (!get('reviewer') || !get('reviewed_at')) {
      errors.push(`${line}行目: reviewer と reviewed_at が必要です`);
    }
  });
  return { checked: values.length - 1, errors };
}

validateReviewRows() の戻り値にエラーがあれば、改善キューを作る前に人が修正します。空欄を OTHER で埋めると、改善先が分からなくなるためです。

改善キューへ重複なく戻す

同じ inquiry_id について同じ reason_code が何度も記録されても、改善キューは1件にまとめます。レビュー履歴そのものは ReplyReview に残します。

const DESTINATION = {
  FAQ_GAP: 'FAQ',
  FORM_GAP: 'FORM',
  APPROVAL_GAP: 'APPROVAL',
  SOURCE_STALE: 'SOURCE',
  SCOPE_RISK: 'SCOPE',
};

function createImprovementQueue() {
  const result = validateReviewRows();
  if (result.errors.length) throw new Error(result.errors.join('\n'));

  const book = SpreadsheetApp.getActive();
  const source = book.getSheetByName(REVIEW_SHEET);
  const target = book.getSheetByName(IMPROVEMENT_SHEET)
    || book.insertSheet(IMPROVEMENT_SHEET);
  const values = source.getDataRange().getValues();
  const index = headerIndex_(values[0]);
  const existing = target.getLastRow() < 2 ? []
    : target.getRange(2, 1, target.getLastRow() - 1, 3).getValues();
  const keys = new Set(existing.map((row) => `${row[1]}:${row[2]}`));
  const additions = [];

  values.slice(1).forEach((row) => {
    const decision = String(row[index.decision] || '').trim();
    const reason = String(row[index.reason_code] || '').trim();
    if (decision === 'send' || !DESTINATION[reason]) return;
    const inquiryId = String(row[index.inquiry_id] || '').trim();
    const key = `${inquiryId}:${reason}`;
    if (!inquiryId || keys.has(key)) return;
    keys.add(key);
    additions.push([
      `IMP-${inquiryId}-${reason}`,
      inquiryId,
      reason,
      DESTINATION[reason],
      String(row[index.reason_note] || '').trim(),
      'open',
      new Date(),
    ]);
  });

  if (target.getLastRow() === 0) {
    target.appendRow([
      'improvement_id', 'inquiry_id', 'reason_code', 'destination',
      'reason_note', 'status', 'created_at',
    ]);
  }
  if (additions.length) {
    target.getRange(target.getLastRow() + 1, 1, additions.length, additions[0].length)
      .setValues(additions);
  }
  return { added: additions.length };
}

改善キューの destination は作業先の候補です。FAQの公開、フォームの変更、承認ルールの変更は、それぞれの責任者が根拠と影響を確認してから行います。

運用で分ける3つの修正

  • その場の修正: 今回の返信だけを直す。根拠があり、再発条件がない場合。
  • 正本の修正: FAQや規程が古い、または説明不足の場合。正本の所有者が更新する。
  • 入口の修正: フォームや受付項目が足りず、毎回同じ確認が必要な場合。入力項目と必須条件を見直す。

承認ルールや対象外範囲に問題がある場合は、AIのプロンプトを先に変えません。責任者・停止条件・記録先を決めてから、必要なら設定を更新します。

チェックリスト

  • 差し戻し理由を自由記述だけで終わらせていない
  • source_refs から現行のFAQや規程へ戻れる
  • FAQ、フォーム、承認ルールの改善先を分けている
  • 同じ問い合わせ・理由を改善キューへ重複登録しない
  • 改善の承認者と見直し日を別途決めている
  • 実顧客の本文や個人情報を改善ログへ複製していない
  • AIに最終承認やFAQ公開をさせていない

まとめ

AI返信のレビューを一件ごとの修正で終わらせず、差し戻し理由を次の改善先へ戻すと、FAQ不足・フォーム不足・承認ルール不足を分けて扱えます。GASは候補の整理までに留め、正本の更新と社外送信は人が確定します。

Miraigentで業務を整理するときも、確認担当者へ「同じ差し戻しが起きたら、どの正本へ戻しますか」と尋ねるところから始めます。

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?