ClaudeでAWSのタグ別showbackレポートを作る — 未配賦の原因を月次で追う
AWSのCost and Usage Reportをチーム単位のタグで集計し、タグのない未配賦コストの割合と原因をClaudeに仕分けさせる月次の手順です。集計はスクリプト、原因づけはClaudeと分けます。
showback(チーム別のコスト可視化)の月次レポートは、AWSのCost and Usage Report(CUR)をチームタグで集計し、タグのない「未配賦」がどれだけ残ったかを示す形で作れます。ここでの分担は、集計をPythonスクリプトに固定し、未配賦の原因づけと配賦ルール案の下書きをClaudeに任せる、というものです。数字を出す役はコードに、読み解く役はClaudeに置きます。
事業損益の共通費をExcelで配る話は事業別損益の共通費配賦の記事にあります。ここで扱うのはクラウドの請求データで、配る前に「そもそもタグが付いていない請求」を洗い出す作業です。
全行をClaudeに読ませず、集計結果だけを読ませる
CURは行数が多くなりがちです。リソース単位の出力(INCLUDE_RESOURCES)を有効にすると、サービスごとの合計ではなくリソースごとに行ができ、行数もファイルサイズも大きく増えます。この全行をチャットに貼っても、合計は合いません。
分け方は単純です。
- 集計(チーム別合計、未配賦の割合、未配賦の内訳)は
pandasのスクリプトで出す - Claudeには、集計後の小さなCSVと、未配賦の内訳だけを渡す
- Claudeの役割は「この未配賦はなぜ生じたか」の仮説と、配賦ルール案の下書きに絞る
こうしておくと、毎月同じスクリプトを回すだけで前月と比べられます。Claudeが数字を暗算で出すことも避けられます。
先にタグの状態を確かめる
未配賦の多くは、タグの運用で決まります。集計の前に、AWS側の仕様を3つ押さえておきます。
未配賦を生むタグの仕様
有効化しないと出ない
ユーザー定義タグは、Billing and Cost Managementコンソールの「Cost allocation tags」でキーを有効化して初めて請求レポートに出ます。タグを付けてから有効化の画面に現れるまで最大24時間、有効化されるまでさらに最大24時間かかります。
遡って付かない
タグは、タグを作る前に作成されたリソースには適用されません。有効化したのが9月でも、8月分の行にはタグ列が空のまま残ります。
レポート上の名前
ユーザー定義タグは、レポート上で
user:の接頭辞が付いた列になります。aws:はAWS生成タグの予約接頭辞で、利用者は付けられません。
有効化の操作ができるのは、組織の管理アカウント、または組織に属さない単独アカウントだけです。メンバーアカウントがほかの組織へ移ると、有効化済みだったタグは非アクティブに戻り、新しい管理アカウントで有効化し直します。
過去月を揃えたいときは、タグのバックフィルを使います。最大12か月分まで、現在の有効化状態を過去月に適用できます。ただしリソースに過去から同じタグが付いていることが条件で、付いていなかった月に値が生えることはありません。依頼は24時間に1回までで、反映はCost Explorer、Data Exports、CURの更新サイクル(24時間に1回)を待ちます。
集計スクリプトを固定する
次のスクリプトは、チームタグ別の合計と、未配賦の内訳を2つのCSVに書き出します。列名は、書き出し方や設定で変わります。先頭の定数は、自分のCURのヘッダーを開いて合わせてください。
import pandas as pd
TAG = "resourceTags/user:Team" # 配賦に使うタグの列
COST = "lineItem/UnblendedCost" # 集計するコスト列
KIND = "lineItem/LineItemType" # 課金の種類(Usage, Tax など)
SERVICE = "product/ProductName" # サービス名の列。自分のCURで確認
df = pd.read_csv("cur-2026-09.csv", usecols=[TAG, COST, KIND, SERVICE])
# 空白と大文字小文字のゆれをそろえ、空のものを未配賦にする
tag = df[TAG].fillna("").str.strip().str.lower()
df["team"] = tag.where(tag != "", "(未配賦)")
total = df[COST].sum()
by_team = df.groupby("team")[COST].sum().sort_values(ascending=False)
summary = by_team.to_frame("cost")
summary["share"] = (summary["cost"] / total).round(4)
summary.to_csv("team_summary.csv")
untagged = df[df["team"] == "(未配賦)"]
untagged.groupby([KIND, SERVICE])[COST].sum() \
.sort_values(ascending=False).head(30) \
.to_csv("untagged_by_service.csv")
print(f"total={total:.2f} untagged_share={summary.loc['(未配賦)', 'share']:.1%}")列名の書き方には注意が要ります。CUR 2.0は列の集合を固定し、一部の列をキーと値の入れ子の列にまとめます。入れ子のキーをオプションで個別の列に展開すると、従来のCURの形に近づけられます。どちらの形式で出したかで、TAG に入れる列名は変わります。
lineItem/LineItemType を集計の軸に入れているのは、未配賦の中身を切り分けるためです。この列には Usage、Tax、Credit、Refund、Fee、SavingsPlanCoveredUsage、SavingsPlanNegation などが入ります。税やクレジットはリソースに紐づかないので、タグがなくて当然の行です。
合計が請求書と合うかを先に見る
集計の直後に、total をBillsページの合計と突き合わせます。コスト配分レポートはタグ付きとタグなしの両方を含み、請求サイクルの終わりには、その合計がBillsページなどほかの請求レポートの合計と一致する仕様です。ここが合わないなら、使っているコスト列の選び方か、月の取り違えを疑います。割引がある月は、割引後の lineItem/NetUnblendedCost が出力に加わります。どの列で請求書と合わせるかを決めて、毎月同じ列で回します。
未配賦の原因づけをClaudeに任せる
集計ができたら、team_summary.csv と untagged_by_service.csv の2枚だけをClaudeに渡します。Claude Codeを使うなら、2枚を置いた summary/ フォルダだけを読める状態にします。元のCURは、作業ディレクトリに置かないのが無難です。
summary/team_summary.csv と summary/untagged_by_service.csv を読んで。
1. 未配賦の割合と、上位の費目を3行で要約する
2. LineItemType と サービス名から、未配賦の原因を次の4分類に振る
(a)タグの付け忘れ (b)タグ有効化前の月
(c)リソースに紐づかない課金 (d)対応が不明
3. 各分類に、根拠にした行を引用する
4. (d)は推測で埋めず、人が調べる項目として列挙する
数値はCSVにある値だけを使い、再計算した値には「再計算」と書く。4つ目の指示が要です。原因が分からない行を推測で埋めさせると、レポートが説明のつかない数字を断定します。「不明」を出口として用意しておけば、人が調べる行だけが残ります。
返ってくる内容の形は、たとえば次のようになります。数値は形を示すための架空の値です。
| 分類 | 根拠の行 | 割合(未配賦内) | 次に誰が動くか |
|---|---|---|---|
| (c)紐づかない課金 | 根拠の行Tax、Credit | 割合(未配賦内)22% | 次に誰が動くか配賦ルールで決める |
| (a)付け忘れ | 根拠の行Usage、EC2、S3 | 割合(未配賦内)55% | 次に誰が動くか各チームにタグ付けを依頼 |
| (b)有効化前 | 根拠の行8月分の Usage 全般 | 割合(未配賦内)15% | 次に誰が動くかバックフィルを検討 |
| (d)不明 | 根拠の行共通のネットワーク系 | 割合(未配賦内)8% | 次に誰が動くか基盤チームに確認 |
この分類は、Claudeの出力を鵜呑みにするためのものではありません。根拠に引いた行を、実際のCSVで1つずつ開いて確かめる作業が残ります。
落とし穴
Marketplaceの行は、元のEC2のタグを引き継ぐ
AWS Marketplaceの課金行には、関連するAmazon EC2インスタンスのタグが付きます。EC2側のタグが抜けていると、Marketplaceの購入分もまとめて未配賦に出ます。分類が(a)でも原因はMarketplaceの行そのものでなく、EC2のタグ漏れです。
配賦ルール案を作らせる
未配賦を「ゼロにする」ことはまず現実的ではありません。税、サポート、共通ネットワークのように、特定のチームに紐づかない費用が残るためです。そこで、配賦の方針をルールとして文章に落とし、人が承認する流れにします。
Claudeには次の3種類の中から、費目ごとに案を出させます。
| ルール | 向く費目 | 注意点 |
|---|---|---|
| 直接配賦 | 向く費目タグが付いたリソース | 注意点タグの値のゆれ(大文字小文字、綴り)を先にそろえる |
| 比例配賦 | 向く費目税、クレジット | 注意点タグ付き分の比率で按分する。比率の根拠は月ごとに変わる |
| 共通基盤として別枠 | 向く費目共有ネットワーク、監視 | 注意点配らずに別枠で見せる選択肢もある |
比例配賦は、各チームの金額を「タグ付きの合計」で割った比率で未配賦を按分する方法です。計算はスクリプトに足します。
tagged = summary.drop(index="(未配賦)")
ratio = tagged["cost"] / tagged["cost"].sum()
allocated = tagged["cost"] + ratio * summary.loc["(未配賦)", "cost"]この計算で、全チームの配賦後の合計は元の total に一致します。一致しなければ、スクリプトのどこかで行が落ちています。
コンテナ上でEC2を共有している場合は、CUR 2.0の設定 INCLUDE_SPLIT_COST_ALLOCATION_DATA を有効にすると、1台のインスタンスの費用をコンテナ単位に割り当てるデータ(split_line_item_* の列)が加わります。タグだけでは分けられない共有インスタンスは、この機能で分けられるかを先に調べると、人手の按分が減ります。
Claude APIで集計も任せる場合
スクリプトを自分で回す代わりに、Claude APIのコード実行ツールにCSVを渡して、集計から任せる方法もあります。ファイルをFiles APIにアップロードし、container_upload ブロックで参照します。
import anthropic
from pathlib import Path
client = anthropic.Anthropic()
up = client.files.upload(file=Path("cur-daily-2026-09.csv"))
resp = client.messages.create(
model="claude-opus-5-5",
max_tokens=4096,
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "チームタグ別に合計し、未配賦の割合を出して"},
{"type": "container_upload", "file_id": up.id},
],
}],
tools=[{"type": "code_execution_20250825", "name": "code_execution"}],
)コードはAnthropic側のサンドボックスで実行され、pandas と openpyxl などがあらかじめ入っています。選ぶ前に、次の条件を見比べてください。
| 観点 | ローカルのスクリプト + Claude Code | コード実行ツール |
|---|---|---|
| 請求データの置き場 | ローカルのスクリプト + Claude Code手元に残る | コード実行ツールFiles APIとコンテナに置かれる |
| 保持期間 | ローカルのスクリプト + Claude Code自分で決める | コード実行ツールコンテナのデータは最長30日 |
| ZDR(ゼロデータ保持) | ローカルのスクリプト + Claude Code該当しない | コード実行ツール対象外 |
| 実行環境の制約 | ローカルのスクリプト + Claude Code手元のマシン次第 | コード実行ツールメモリ5GiB、ディスク5GiB、CPU 1つ。外部通信なし |
| 料金 | ローカルのスクリプト + Claude Codeトークン代のみ | コード実行ツール実行時間課金(月1,550時間まで無料、超過は1コンテナ1時間0.05ドル、最小5分) |
CURにはアカウントIDやリソースIDが含まれます。コード実行ツールに渡すなら、日次に集計したうえで、アカウントIDを仮名に置き換えたCSVにするのが安全です。ZDRの対象外という条件は、請求データを社外に出さない社内規程があるチームでは、選ぶ段階で効いてきます。
もう1点、料金の細部があります。ファイルをリクエストに含めると、ツールを呼ばなくてもファイルがコンテナに事前読み込みされるため、実行時間は課金されます。毎月1回の定例なら無料枠に収まる量ですが、確認用に何度も回すときは気にしておく点です。
毎月の手順
月次showbackの流れ
- 1
CURを出す
前月分が確定してから出力します。タグを有効化したばかりの月は、バックフィルの結果が反映されるまで24時間ほど待ちます。
- 2
スクリプトで集計する
team_summary.csvとuntagged_by_service.csvを作り、totalをBillsページと照合します。 - 3
Claudeで原因づけする
2枚のCSVだけを渡し、4分類と根拠の行を出させます。(d)の行は人が調べます。
- 4
ルール案を承認する
比例配賦の対象費目と別枠にする費目を、財務と各チームの責任者で決めます。
- 5
タグの穴を塞ぐ
付け忘れの上位リソースに、各チームがタグを付けます。
未配賦の割合は、毎月同じ表に並べると傾向が見えます。何%までなら許容するかは、自社で決める閾値です。ここに汎用の目安はありません。
使われなくなったタグ名は、コンソールの「Last used month」列で見つかります。タグキーが最後に使われた月で、現在どのリソースにも付いていないキーは - と表示されます。チームの統廃合のあとに、古いキーを整理する材料になります。
タグとCUR周りの落とし穴
- タグに機密情報を入れない。タグはレポートに列として載ります
- 同じキーを1つのリソースに2回付けることはできず、リクエストは拒否されます。大文字小文字違いの別キー(
Teamとteam)を作ると、別の列に分かれて集計が割れます CreditやRefundは、請求の確定後に追加されることがあります。確定後に月をまたいで数字が動くのは、このためです- Cost ExplorerのCSVで使用タイプまで掘る作業は、別の手順です。急増の原因調査はCost ExplorerのCSVを使用タイプまで分解する手順で扱っています
- 予算と実績の差異をExcelで追うなら、月次予実対比レポートの差異分析の流れが使えます
まとめ
showbackの精度は、Claudeの賢さより、タグの有効化とタグ付けの運用で決まります。集計はスクリプトに固定し、請求書との合計照合を毎月の最初の検算にします。Claudeには未配賦の分類と、根拠の行の引用、そして「不明」を不明のまま返す仕事を任せます。配賦ルールの最終判断は、費用を負担するチームの責任者が下します。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- platform.claude.com/docs/en/agents-and-tools/tool-use/code-execution-tool
- docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/cost-alloc-tags.html
- docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/activating-tags.html
- docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/custom-tags.html
- docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/cost-allocation-backfill.…
- docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/cost-allocation-tags-time…
- docs.aws.amazon.com/cur/latest/userguide/table-dictionary-cur2.html
- docs.aws.amazon.com/cur/latest/userguide/Lineitem-columns.html