AIに渡す売上CSVで、列名「売上」だけを残さない。集計条件を5列に分けた見本

売上CSVの売上という1列は、AIにとっては説明不足です。入金額なのか、返金前なのか、手数料を引いた後なのか。人が画面を見れば補える差でも、集計を任せると答えが分かれます。
今回は実在の販売データを使わず、3件だけの合成CSVで確かめました。相手は、キャンセル済みの注文を除き、販売手数料まで引いた金額を出す小さな表です。
AIへ渡す前に「売上」を分解する
最初に決めたのは、AIへの依頼文ではなくCSVの列です。gross_amount_yenは購入時の金額、refund_amount_yenは返金額、platform_fee_yenは販売手数料にします。order_statusは、集計に入れてよい注文を判断する列です。
作業用のディレクトリを作り、その中へ次のCSVをorders.csvとして保存します。
mkdir -p sales-csv-column-contract-sample
cd sales-csv-column-contract-sample
order_date,gross_amount_yen,refund_amount_yen,platform_fee_yen,order_status
2026-10-01,1200,0,120,paid
2026-10-02,1200,1200,120,canceled
2026-10-03,1500,0,150,paid
この5列を今回の契約としました。集計対象はorder_statusがpaidの行だけです。2行目はorder_statusがcanceledで、1200円を全額返金したキャンセル済みの注文なので、対象に入れません。手取りは、対象行のgross_amount_yen - refund_amount_yen - platform_fee_yenで計算します。
CSVにはヘッダー行を置き、各レコードのフィールド数をそろえます。RFC 4180は、ヘッダーを先頭行に置く場合、後続レコードと同じ数のフィールドを持たせる形を示しています。列名は単なる見出しではなく、次の処理の入力です。
合成データで先に数字を出す
同じディレクトリに、検査用のPythonファイルをcheck_sales_csv.pyとして保存します。orders.csvを読み、標準ライブラリのcsvとdecimalだけを使います。Decimalは金額を浮動小数点で足さないために使いました。
import csv
from decimal import Decimal
from pathlib import Path
path = Path('orders.csv')
required = [
'order_date',
'gross_amount_yen',
'refund_amount_yen',
'platform_fee_yen',
'order_status',
]
with path.open(encoding='utf-8', newline='') as f:
reader = csv.DictReader(f)
header = reader.fieldnames
rows = list(reader)
if header != required:
raise SystemExit(f'columns differ: {header}')
if not rows:
raise SystemExit('no data rows')
paid = [r for r in rows if r['order_status'] == 'paid']
gross = sum(Decimal(r['gross_amount_yen']) for r in paid)
refund = sum(Decimal(r['refund_amount_yen']) for r in paid)
fee = sum(Decimal(r['platform_fee_yen']) for r in paid)
print(f'paid_orders={len(paid)}')
print(f'net_sales_yen={gross - refund - fee}')
同じディレクトリで実行します。
python3 check_sales_csv.py
paid_orders=2
net_sales_yen=2430
paidの2行だけを対象にしたので、計算は1200 - 120 + 1500 - 150です。キャンセル済みの2行目を含めていないことが、数字の根拠です。
AIへ添えるのは、CSVと「paidだけを対象にし、手取りは3列の式で出す」という条件です。質問文だけに「今月の売上を出して」と書くより、解釈の余地は小さくなります。
1回目は列名だけを変えたら止まった
同じ順番のままgross_amount_yenをsalesへ変えたorders_bad_header.csvも作りました。人には似た意味に見えても、この見本では契約外。検査が止まらずに通ると、AI側だけで金額の意味を決めることになります。
sed 's/gross_amount_yen/sales/' orders.csv > orders_bad_header.csv
検査用のcheck_bad_header.pyは、読むファイルをorders_bad_header.csvにしただけで、列名の照合はcheck_sales_csv.pyと同じです。
import csv
from pathlib import Path
path = Path('orders_bad_header.csv')
required = [
'order_date',
'gross_amount_yen',
'refund_amount_yen',
'platform_fee_yen',
'order_status',
]
with path.open(encoding='utf-8', newline='') as f:
header = csv.DictReader(f).fieldnames
if header != required:
raise SystemExit(f'columns differ: {header}')
print('columns match')
python3 check_bad_header.py
columns differ: ['order_date', 'sales', 'refund_amount_yen', 'platform_fee_yen', 'order_status']
列の順番が同じでも、salesという名前だけでは、返金前の金額なのか、税を含むのかが決まりません。金額の意味まで決めて初めて役に立ちます。名前を短くするほど、説明を別の場所へ逃がす必要が出ます。
列名を変えること自体を禁止する必要はありません。変えるなら、CSVの列名、集計式、AIへ渡す指示の3か所を同時に直します。必要なのは、その差分を小さな検査で止められる状態です。
データが0行のCSVも止める
最初に載せた版は、if rows and ...の形で列名を照合していました。データ行が0件だと、この条件で照合ごと飛ばされます。見出しだけのCSVを渡すと、paid_orders=0とnet_sales_yen=0を出して終了コード0で終わりました。列名をsalesに変えた見出しだけのCSVでも結果は同じでした。0円はいかにもありそうな数字なので、入力が空という事実はその数字に隠れます。
そこで、照合を行の有無から切り離しました。DictReaderのfieldnamesを直接requiredと比べ、列名が合っていてもデータ行が0件なら止めます。上のcheck_sales_csv.pyは、この修正を入れた版です。見出しだけのCSVを2通り作り、読むファイルだけ替えて同じ検査をかけます。
head -n 1 orders.csv > header_only.csv
head -n 1 orders_bad_header.csv > header_only_bad.csv
for name in header_only header_only_bad; do
sed "s/orders.csv/$name.csv/" check_sales_csv.py > check_$name.py
python3 check_$name.py; echo "exit=$?"
done
no data rows
exit=1
columns differ: ['order_date', 'sales', 'refund_amount_yen', 'platform_fee_yen', 'order_status']
exit=1
どちらも終了コード1で止まります。空のファイルはcolumns differ: Noneで止まりました。正常な3行の見本だけを流していたときは、この穴は見えませんでした。
AIの出力形式と、元データの意味は別に守る
OpenAIのStructured Outputsは、JSON Schemaに従う応答を得る仕組みです。たとえばnet_sales_yen、対象注文数、除外した注文の説明を決まったキーで返させる用途に向きます。
ただし、出力のキーを固定しても、入力CSVの売上の意味は決まりません。この2つは役割が別です。
- CSVの列と集計式で、何を数えるかを決めます
- 構造化出力で、AIがどの形で報告するかを決めます
先に列の契約を置けば、AIを別のモデルや別の集計手段へ替えても、照合する場所が残ります。売上件数が少ないうちは、手作業で見直せる時期です。その時期に残すべきなのは、金額の意味。
出典
- RFC 4180: Common Format and MIME Type for Comma-Separated Values (CSV) Files — CSVのヘッダー行とフィールド数の扱いを確認しました。
- OpenAI Structured Outputs — JSON Schemaに従う出力形式を指定する仕組みを確認しました。


