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活用ログをGoogleスプレッドシートとGASで作る最小実装

0
Posted at

生成AIを業務に入れる時、最初から大きな管理画面を作る必要はありません。

ただし、AIを「誰が、何に、どの判断で使ったか」が残らないまま運用を始めると、あとから改善も説明も難しくなります。

この記事では、小さな会社でも始めやすい形として、GoogleスプレッドシートとGASで AI活用ログ を作る最小実装を紹介します。AI APIの呼び出しや自動送信ではなく、AIを使う前後の記録を残すための土台です。

作るもの

スプレッドシートに ai_usage_logweekly_summary を用意し、Apps Scriptで次を実装します。

  • AIを使った業務、入力種別、出力種別を記録する
  • 個人情報や契約・返金などの注意フラグを残す
  • 人間確認が必要なケースを自動で 要確認 にする
  • 週次で用途別の件数と要確認件数を集計する
  • 改善対象をFAQ、フォーム、CRM、承認ルールへ戻す

小さな会社のAI活用ログを、用途・注意フラグ・人間確認・改善先で整理するイメージ

完成形は、次のような流れです。

問い合わせ要約 / FAQ候補 / 返信下書き / 社内メモ整理
  ↓
AIを使う前後に ai_usage_log へ記録
  ↓
注意フラグと人間確認の要否を付ける
  ↓
週次で集計し、FAQ・フォーム・CRM・承認ルールを直す

なぜAI活用ログが必要か

AI活用ログは、監視のためだけの表ではありません。

小さな会社では、AI利用がチャットや個人のブラウザ上で始まりやすく、次の情報が残らないことがあります。

  • どの業務でAIを使ったか
  • どの情報をAIへ渡したか
  • 出力をそのまま使ったのか、人間が直したのか
  • なぜ人間確認に戻したのか
  • 次にFAQやフォームへ反映すべき改善点は何か

この記録がないと、「AIで何が楽になったか」も「どこが危ないか」も判断しにくくなります。

最初に作るべきなのは、AIの成果を大きく見せるダッシュボードではなく、現場が毎回同じ粒度で残せるログです。

ai_usage_log の列

まず、ai_usage_log というシートを作ります。

列名 用途
log_id AILOG-20260627-001 ログID
used_at 2026/06/27 10:30 AIを使った時刻
requester sales_member 依頼者または利用者
task_type inquiry_summary 用途
input_type inquiry_text AIへ渡した情報の種類
output_type reply_draft AIの出力種別
ai_tool ChatGPT / Gemini / Claude 利用ツール
data_level masked / internal / blocked 情報の扱い
risk_flags personal_data, contract 注意点
human_review_required TRUE 人間確認が必要か
review_status 未確認 / 確認済み / 差し戻し 確認状態
improvement_target faq / form / crm / approval_rule 改善先
note 料金表現を責任者確認へ 判断メモ

promptcustomer_name をそのまま残す列は作りません。

ログに必要なのは、個人情報や機密情報そのものではなく、業務判断として後から見返せる分類です。

セットアップ用GAS

次のコードをスプレッドシートに紐づくApps Scriptへ貼り付けます。

const SHEETS = {
  log: 'ai_usage_log',
  summary: 'weekly_summary',
};

const LOG_HEADERS = [
  'log_id',
  'used_at',
  'requester',
  'task_type',
  'input_type',
  'output_type',
  'ai_tool',
  'data_level',
  'risk_flags',
  'human_review_required',
  'review_status',
  'improvement_target',
  'note',
];

const SUMMARY_HEADERS = [
  'week_start',
  'task_type',
  'total_count',
  'review_required_count',
  'blocked_count',
  'main_improvement_target',
];

function setupAiUsageLog() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  ensureSheet_(ss, SHEETS.log, LOG_HEADERS);
  ensureSheet_(ss, SHEETS.summary, SUMMARY_HEADERS);
}

function ensureSheet_(ss, name, headers) {
  const sheet = ss.getSheetByName(name) || ss.insertSheet(name);
  const current = sheet.getRange(1, 1, 1, headers.length).getValues()[0];
  const isEmpty = current.every((value) => value === '');

  if (isEmpty) {
    sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
    sheet.setFrozenRows(1);
  }
}

ログを追加する関数

次に、AI利用の記録を追加する関数を作ります。

function addAiUsageLog(entry) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sheet = ss.getSheetByName(SHEETS.log);
  if (!sheet) throw new Error('setupAiUsageLog() を先に実行してください');

  const taskType = normalizeTaskType_(entry.taskType);
  const riskFlags = detectRiskFlags_(entry);
  const dataLevel = decideDataLevel_(riskFlags, entry.dataLevel);
  const humanReviewRequired = shouldReview_(taskType, riskFlags, dataLevel);

  sheet.appendRow([
    createLogId_(),
    entry.usedAt || new Date(),
    entry.requester || 'unknown',
    taskType,
    entry.inputType || 'unknown',
    entry.outputType || 'unknown',
    entry.aiTool || 'unknown',
    dataLevel,
    riskFlags.join(','),
    humanReviewRequired,
    '未確認',
    decideImprovementTarget_(taskType, riskFlags),
    entry.note || '',
  ]);
}

function normalizeTaskType_(value) {
  const text = String(value || '').toLowerCase();
  if (/inquiry|問い合わせ|相談/.test(text)) return 'inquiry_summary';
  if (/reply|返信|下書き/.test(text)) return 'reply_draft';
  if (/faq|よくある/.test(text)) return 'faq_candidate';
  if (/crm|営業|顧客/.test(text)) return 'crm_note';
  if (/article|sns|投稿|記事/.test(text)) return 'content_draft';
  return 'other';
}

function detectRiskFlags_(entry) {
  const text = [
    entry.inputType,
    entry.outputType,
    entry.note,
    entry.sampleText,
  ].map((value) => String(value || '')).join('\n');

  const flags = [];
  if (/住所|電話番号|メールアドレス|個人情報|本人確認/.test(text)) {
    flags.push('personal_data');
  }
  if (/契約|返金|解約|請求|支払い|料金/.test(text)) {
    flags.push('contract_or_money');
  }
  if (/パスワード|APIキー|Cookie|トークン|認証/.test(text)) {
    flags.push('secret_or_auth');
  }
  if (/クレーム|苦情|炎上|法務|税務|医療/.test(text)) {
    flags.push('sensitive_judgment');
  }
  return flags;
}

function decideDataLevel_(riskFlags, requestedLevel) {
  if (riskFlags.includes('secret_or_auth')) return 'blocked';
  if (riskFlags.includes('personal_data')) return 'masked';
  if (requestedLevel) return requestedLevel;
  return 'internal';
}

function shouldReview_(taskType, riskFlags, dataLevel) {
  if (dataLevel === 'blocked') return true;
  if (taskType === 'reply_draft' || taskType === 'content_draft') return true;
  if (riskFlags.length > 0) return true;
  return false;
}

function decideImprovementTarget_(taskType, riskFlags) {
  if (riskFlags.includes('secret_or_auth')) return 'approval_rule';
  if (riskFlags.includes('personal_data')) return 'form';
  if (taskType === 'faq_candidate') return 'faq';
  if (taskType === 'crm_note') return 'crm';
  if (taskType === 'reply_draft') return 'approval_rule';
  return 'none';
}

function createLogId_() {
  const now = new Date();
  const ymd = Utilities.formatDate(now, 'Asia/Tokyo', 'yyyyMMdd');
  const suffix = Utilities.getUuid().slice(0, 8).toUpperCase();
  return `AILOG-${ymd}-${suffix}`;
}

動作確認

Apps Scriptのエディタで setupAiUsageLog() を一度実行してから、次のテスト関数を実行します。

function testAddAiUsageLog() {
  addAiUsageLog({
    requester: 'cs_member',
    taskType: '問い合わせ要約',
    inputType: '問い合わせ本文',
    outputType: '要約',
    aiTool: 'ChatGPT',
    dataLevel: 'masked',
    sampleText: '料金について相談したい。電話番号はマスク済み。',
    note: '料金に触れるため確認へ',
  });

  addAiUsageLog({
    requester: 'sales_member',
    taskType: 'FAQ候補',
    inputType: '問い合わせ分類',
    outputType: 'FAQ案',
    aiTool: 'Gemini',
    sampleText: '導入までの流れに関する質問',
    note: 'FAQ化候補',
  });
}

1件目は contract_or_money が付き、人間確認が必要になります。2件目はFAQ改善へ回すログになります。

週次集計を作る

ログが溜まったら、用途別に件数を集計します。

function buildWeeklySummary() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const logSheet = ss.getSheetByName(SHEETS.log);
  const summarySheet = ss.getSheetByName(SHEETS.summary);
  if (!logSheet || !summarySheet) {
    throw new Error('setupAiUsageLog() を先に実行してください');
  }

  const values = logSheet.getDataRange().getValues();
  const headers = values.shift();
  const col = buildColumnMap_(headers);
  const groups = {};

  values.forEach((row) => {
    const usedAt = row[col.used_at];
    if (!usedAt) return;

    const weekStart = formatWeekStart_(new Date(usedAt));
    const taskType = String(row[col.task_type] || 'unknown');
    const key = `${weekStart}__${taskType}`;

    if (!groups[key]) {
      groups[key] = {
        weekStart,
        taskType,
        total: 0,
        review: 0,
        blocked: 0,
        targets: {},
      };
    }

    groups[key].total += 1;
    if (row[col.human_review_required] === true || row[col.human_review_required] === 'TRUE') {
      groups[key].review += 1;
    }
    if (String(row[col.data_level]) === 'blocked') {
      groups[key].blocked += 1;
    }

    const target = String(row[col.improvement_target] || 'none');
    groups[key].targets[target] = (groups[key].targets[target] || 0) + 1;
  });

  const rows = Object.values(groups).map((group) => [
    group.weekStart,
    group.taskType,
    group.total,
    group.review,
    group.blocked,
    mostFrequentKey_(group.targets),
  ]);

  summarySheet.clearContents();
  summarySheet.getRange(1, 1, 1, SUMMARY_HEADERS.length).setValues([SUMMARY_HEADERS]);
  if (rows.length > 0) {
    summarySheet.getRange(2, 1, rows.length, SUMMARY_HEADERS.length).setValues(rows);
  }
}

function buildColumnMap_(headers) {
  return Object.fromEntries(headers.map((header, index) => [String(header), index]));
}

function formatWeekStart_(date) {
  const copied = new Date(date);
  const day = copied.getDay();
  const diff = day === 0 ? -6 : 1 - day;
  copied.setDate(copied.getDate() + diff);
  return Utilities.formatDate(copied, 'Asia/Tokyo', 'yyyy-MM-dd');
}

function mostFrequentKey_(counts) {
  return Object.entries(counts)
    .sort((a, b) => b[1] - a[1])
    .map(([key]) => key)[0] || 'none';
}

週次で見るポイント

週次集計では、件数の多さだけを見ません。

次の3つを見ると、AI活用を改善へつなげやすくなります。

  • review_required_count が多い用途: 承認ルールやテンプレートが足りない
  • blocked_count が多い用途: AIへ渡す前のフォームやマスキングを見直す
  • main_improvement_target が偏る用途: FAQ、CRM、フォームのどこを直すべきか分かる

AI活用ログは「AIを何回使ったか」を競うための表ではありません。

止まった理由を次の運用改善へ戻すための表です。

運用ルール

最小運用では、次のルールだけでも始められます。

  • 顧客へ送る返信下書きは必ず人間確認にする
  • 個人情報や認証情報を含む場合は、AIへ送る前に止める
  • 料金、契約、返金に触れる場合は責任者確認にする
  • AI出力を外部公開する場合は、事実確認済みにする
  • 週1回、weekly_summary を見て改善先を1つだけ決める

大切なのは、ログを細かくしすぎないことです。

最初から20列以上にすると、入力されなくなります。まずは、用途、注意フラグ、人間確認、改善先だけを残します。

まとめ

AI活用ログを作ると、AI導入は「便利だった」で終わらなくなります。

  • 何に使ったか
  • どこで止めたか
  • 何を人間が確認したか
  • 次にどの運用を直すか

この4つが残ると、問い合わせ対応、FAQ整備、CRM改善、承認ルールの見直しへつなげやすくなります。

Miraigentでは、AIツールの導入前に、こうした活用ログ、例外ケース、人間確認ルールを先に整理することを重視しています。小さく始めるなら、まず1枚のスプレッドシートで「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?