受付フォームやCRMからCSVを書き出し、Googleスプレッドシートへ取り込む運用では、取込処理を先に自動化すると「列が増えた」「許可していない振り分け先が入った」「同じ案件IDが二度入った」といった差分を見落としやすくなります。
この記事では、GAS(Google Apps Script)でCSVを取込前に検査し、条件外なら候補を一件も返さず HOLD にする最小実装を作ります。成功しても人間確認の前段までにとどめ、Sheetsへの書き込み、AIへの送信、顧客への返信は自動化しません。
この記事で作るもの
検査対象は、次の二列だけを持つCSVです。
case_id,queue
CASE-0001,general
CASE-0002,human_review
| 検査項目 | 受け入れる条件 |
|---|---|
| ヘッダー | 「case_id,queue」の二列をこの順で持つ |
| 案件ID | 「CASE-」 + 半角数字4桁 |
| 振り分け先 | 「general」 / 「technical」 / 「human_review」のいずれか |
| データ件数 | ヘッダーを除いて1〜100件 |
| 入力長 | BOMを除くJavaScriptの length が50,000以下 |
ここでの「成功」は READY_FOR_REVIEW です。既存台帳との重複や、実際に取り込んでよいかは別の確認工程に残します。
1. 取込結果の契約を先に決める
不正な行が見つかった時に、正常な行だけを返すと部分取込につながります。そのため、結果は次の二種類に限定します。
- READY_FOR_REVIEW: 全行が条件内。候補を人が確認する
- HOLD: どこか一つでも条件外。candidates: [] を返す
エラー理由には本文や顧客情報を含めず、調査用のコードだけを残します。
2. CSVを検証するGAS
次のコードをApps ScriptのV8ランタイムへ置きます。CSVの解釈は単純な split(',') ではなく、標準の Utilities.parseCsv に任せます。
function hold_(reason, record) {
return {
state: 'HOLD',
reason,
record: record || 0,
candidates: [],
};
}
function previewIntakeCsv(csvText) {
if (typeof csvText !== 'string' || csvText.length === 0 || csvText.length > 50000) {
return hold_('INPUT_LENGTH');
}
const withoutBom = csvText.charCodeAt(0) === 0xFEFF
? csvText.slice(1)
: csvText;
let rows;
try {
rows = Utilities.parseCsv(withoutBom);
} catch (error) {
return hold_('CSV_PARSE');
}
return validateIntakeRows_(rows);
}
function validateIntakeRows_(rows) {
if (!Array.isArray(rows) || rows.length < 2) {
return hold_('NO_DATA');
}
if (rows.length > 101) {
return hold_('ROW_LIMIT');
}
const header = rows[0];
if (!Array.isArray(header) || header.length !== 2 ||
header[0] !== 'case_id' || header[1] !== 'queue') {
return hold_('HEADER', 1);
}
const allowedQueues = new Set(['general', 'technical', 'human_review']);
const seen = new Set();
const candidates = [];
for (let index = 1; index < rows.length; index += 1) {
const row = rows[index];
const record = index + 1;
if (!Array.isArray(row) || row.length !== 2) {
return hold_('COLUMN_COUNT', record);
}
const [caseId, queue] = row;
if (typeof caseId !== 'string' || !/^CASE-[0-9]{4}$/.test(caseId)) {
return hold_('CASE_ID', record);
}
if (typeof queue !== 'string' || !allowedQueues.has(queue)) {
return hold_('QUEUE', record);
}
if (seen.has(caseId)) {
return hold_('DUPLICATE_ID', record);
}
seen.add(caseId);
candidates.push({ caseId, queue });
}
return {
state: 'READY_FOR_REVIEW',
reason: '',
record: 0,
candidates,
};
}
record はヘッダーを1とした論理レコード番号です。引用符の中に改行があるCSVでは、物理行番号とは一致しません。エラーの返却値に入力値を入れないため、ログへ出す場合も理由コードと件数だけにします。
3. 架空データで境界を確認する
実データを使わず、次のようなテスト関数をApps Script上で実行します。
function testPreviewIntakeCsv() {
const header = 'case_id,queue\\n';
const cases = [
[header + 'CASE-0001,general', 'READY_FOR_REVIEW', ''],
[header + 'CASE-0001,unknown', 'HOLD', 'QUEUE'],
[header + 'CASE-0001,general\\nCASE-0001,technical', 'HOLD', 'DUPLICATE_ID'],
['queue,case_id\\ngeneral,CASE-0001', 'HOLD', 'HEADER'],
[header + 'CASE-0001,general,extra', 'HOLD', 'COLUMN_COUNT'],
[header + 'CASE-0001,general\\nCASE-0002,technical', 'READY_FOR_REVIEW', ''],
];
cases.forEach(([csv, expectedState, expectedReason], index) => {
const actual = previewIntakeCsv(csv);
if (actual.state !== expectedState || actual.reason !== expectedReason) {
throw new Error('case ' + (index + 1) + ' failed');
}
if (actual.state === 'HOLD' && actual.candidates.length !== 0) {
throw new Error('case ' + (index + 1) + ' returned partial candidates');
}
});
}
本番へ置く前に、少なくとも次を追加で確認します。
- 100件は受け入れ、101件は ROW_LIMIT になる
- 50,000文字と50,001文字を分けられる
- BOM、CRLF、末尾改行、空行の扱いが想定どおりである
- 引用符を含むCSVをGASの実パーサーで確認する
- 同じ入力を二度検証しても、Sheetsや外部サービスが変更されない
- 不正が後半にあっても、先に見つかった候補が返らない
4. Sheetsへ書き込む前に残す確認項目
READY_FOR_REVIEW は取込完了ではありません。次の確認が終わるまで、書き込み処理を呼び出さない運用にします。
- 出力元と対象期間が想定どおりか
- CSVの件数と候補件数が一致するか
- 既存台帳に同じ案件IDがないか
- 振り分け先の定義が現在の正本と一致するか
- 失敗時に元ファイルへ戻れるか
- 取込を承認した担当者と確認時刻を記録したか
GASからSheetsへ書き込む段階では、二重登録を防ぐための再実行キー、途中失敗時の扱い、書き込み後の読み戻しも別途設計します。この記事の検証関数には、そこまでの副作用を持たせていません。
5. AIへ渡す情報の境界
CSV原本や顧客情報をそのままAIへ送る設計にはしません。AIを使う場合も、許可された架空データや件数集計から手順の説明案を作る範囲にとどめ、案件IDの補完や未知の振り分け先の推測をさせないようにします。
また、検証に通ったからといって、返信送信やCRM更新を許可するわけではありません。取込、AI処理、顧客への連絡は、それぞれ人間確認と停止条件を持つ別工程です。
まとめ
CSVの取込前に、列構成・許可値・案件ID重複を検査し、一つでも不正なら候補全体を空にすると部分取込を避けやすくなります。
まずは顧客情報を含まない二件のCSVで、正常系と保留系を一回ずつ確認してください。Miraigentでは、AIを業務へつなぐ前に、入力条件と人へ戻す場所を小さく固定する設計を重視しています。
