3
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禁止にして自分の実力を測ってみた

3
Posted at

はじめに

みなさん生成AIは使っていますか?私は生成AIに頼りっぱなしの毎日です。
現在私はChatGPTとClaude Codeにサブスクリプション登録しており、何かを作るにしてもとりあえずAIにやり方を聞いたり、コードを書いてもらうことが多いです。
おかげで生産性は爆上がりしていますが、「なんかよくわかってないけど動くからいいや」ってことが増えてしまいました。昔と比べて、自分で考えたり、試行錯誤することが減った気がします。これは成長機会の損失なのではないでしょうか。
ということで今回はあえて、「AI禁止」でAWSを使ったシステムを作ってみます。
目的は、

  • 自分の実力がどのくらいか知る
  • AIに頼らないことでどのような効果があるか知る

という2つです。

最後、自分の中で納得いくものが完成したら、AIを解禁して、AIに自分の成果物をレビューしてもらおうと思います。
さて、AIでもぐうの音も出ないくらいのものを作れるでしょうか?楽しみですね!!

私は新卒2年目エンジニアで、1年ほど業務でAWSを利用しています。メインの業務は運用保守で、開発の経験はあまりありません。

この記事は実装しながら同時に書いているため、やや冗長な部分があると思います。私がどうやって実装していったかの備忘録的な意味合いもあるため、ご容赦ください。

作りたいもの

AWSの利用料金を定期的にLINE通知でしてくれるシステムを作ろうと思います。
こちらの記事を拝見して、これのAWS版作ってみたい!と思った次第です。

LINE通知の内容

  • 毎週月木の週2回、お昼12:00に通知
  • その月の1日~前日までの各AWSアカウントの料金とその合計を通知

ルール

NG事項

  • 生成AIの利用
  • 類似記事の閲覧(おそらく同じようなシステムの作り方を多くの先人たちが記事に残してくれてると思いますが、これを見たら簡単に作れてしまうので、今回はNGとします。)

OK事項

  • 公式ドキュメントの閲覧(AWS, LINE)
  • pythonの文法やエラーに関する記事の閲覧

やってみた

構成

とりあえず構成図を書いて、どんな風に実装していくかイメージします。

料金通知.jpg

おそらくこんな感じでいけるんじゃないでしょうか。
もっといい方法があるかもしれませんが、ぱっと思いついたのがこれしかなかったのでとりあえず実装に進みます。
ちなみに、私はハンズオン用や個人開発用に複数AWSアカウントを所持しており、Organizationsで一元管理しています。Organizations管理アカウントでは、各メンバーアカウントの料金も一元管理できるため、今回は管理アカウント内で上記構成を作成してみようと思います。

Lambda関数の実装

さて、いきなり困りました。私は自力でLambda関数を書いたことがありません。
毎回AIに書いてもらい、テストしてみてエラーがあれば、エラー文をそのままAIに投げて直してもらうというやり方しかやったことがありません。
幸いPythonは少し読み書きできるレベル(昔AtCoderで茶色になってやめたレベル)なので、Pythonで書いてみようと思います。
いきなり完璧なコードを書くのは私には無理なので、ステップを分けて1つずつ実装していきます。

  1. Cost Explorerから今月の合計料金を取得
  2. Cost ExplorerとOrganizationsから今月の各AWSアカウントの料金を取得
  3. 取得した内容をLINEで通知

頭のいいみなさんは気付いたと思いますが、各AWSアカウントの料金を足せば合計料金が分かることにこの時の私は気付いていません。

とりあえずBoto3の公式ドキュメントを読みながら頑張ってみます。Boto3とは、AWSサービスをPythonからアクセスするためのライブラリです。コード例なども載っているのがありがたいですね。

1. Cost Explorerから今月の合計料金を取得

ひとまずCost Explorerのドキュメントを見つけたので、こちらを参考にLambdaを書いていこうと思います。

っておい!!!
少しコード入力したら、Amazon Qがサジェスト出してくれてる!!
嬉しい機能ですが、今回だけは嬉しくないです。
image.png

知っておく必要はあまりないかもですが、左下の「Amazon Q」をクリックし、Pause Auto-Suggestions Currently RUNNNINGをクリックすると、サジェストをしない設定にできました。
image.png

boto3を参考にとりあえず合計金額だけを返してくれるコードを書いてみました。

私はPythonの基礎的な文法しか知らないかつ、可読性について勉強したことがありません。拙いコードかと思いますが、ご容赦ください。
この記事に出てくるコードは全て、あくまで参考程度に見ていただき、そのまま使うことはお勧めできません。

import json
import boto3

client = boto3.client('ce')

def lambda_handler(event, context):
    
    total_cost = get_costs()
    return {
        'statusCode': 200,
        'body': json.dumps(total_cost)
    }


def get_costs():
    response = client.get_cost_and_usage(
        TimePeriod={
            'Start': '2026-01-01',
            'End': '2026-01-31'
        },
        Granularity='MONTHLY',
        Metrics=[
            'AmortizedCost',
        ]
    )

    # 月の合計料金
    total_cost = response['ResultsByTime'][0]['Total']['AmortizedCost']['Amount']

    return total_cost

また、LambdaのIAMロールにAWSBillingReadOnlyAccessをアタッチし、タイムアウトも念のため1分に伸ばしてテストしてみたところ、、、

成功!!
image.png

Boto3のドキュメントに書いてある通りに実装するだけで、意外と簡単に合計料金を取得することができました。
※コンソール上でもほぼ同じ値であることを確認できました。

2. Cost Explorerから今月の各AWSアカウントの料金を取得

まずOrganizationsに所属しているメンバーアカウントの情報を取得する必要があるため、Organizationsのboto3ドキュメントを調べてみたところ、どうやらlist_accountsで組織内のすべてのアカウントを一覧表示できることが分かりました。

# メンバーアカウントの一覧を取得
def get_accounts():
    response = organizations.list_accounts()
    accounts_dict = {}
    
    for i in range(len(response['Accounts'])):
        accounts_dict[response['Accounts'][i]['Name']] = response['Accounts'][i]['Id']

    return accounts_dict

途中、range()を書き忘れてTypeError: 'int' object is not iterableというエラーが出たりしましたが、このエラーに関する記事を読み、ミスに気づくことができました。

続いて、各アカウントの料金を取得します。
先ほどのget_cost_and_usageにFilterを追加してアカウントIDを指定することで、各アカウントの料金を取得できることが分かりました。
そして出来上がったのがこちらになります。

import json
import boto3

ce = boto3.client('ce')
organizations = boto3.client('organizations')

def lambda_handler(event, context):
    accounts_cost = get_costs()

    return accounts_cost

# メンバーアカウントの一覧を取得
def get_accounts():
    response = organizations.list_accounts()
    accounts_dict = {}
    
    for i in range(len(response['Accounts'])):
        accounts_dict[response['Accounts'][i]['Name']] = response['Accounts'][i]['Id']

    return accounts_dict

# 各アカウントの月間コストを取得
def get_costs():
    accounts_dict = get_accounts()

    accounts_cost = {}
    for account_name, account_id in accounts_dict.items():
        response = ce.get_cost_and_usage(
            TimePeriod={
                'Start': '2026-01-01',
                'End': '2026-01-31'
            },
            Granularity='MONTHLY',
            Filter={
                'Dimensions': {
                    'Key': 'LINKED_ACCOUNT',
                    'Values': [
                        account_id,
                    ],
                    'MatchOptions': [
                        'EQUALS',
                    ]
                }
            },
            Metrics=[
                'AmortizedCost',
            ]
        )
        accounts_cost[account_name] = response['ResultsByTime'][0]['Total']['AmortizedCost']['Amount']
    
    # 合計料金を算出
    cost_list = [float(v) for v in accounts_cost.values()]
    accounts_cost['合計料金'] = sum(cost_list)
    
    return accounts_cost

なんとこのコードを書くのに1時間もかかってしまいました。相変わらず読みにくいコードかと思いますが、ご容赦ください。。。
ハマったポイントとしては、

  • Filter部分の書き方が間違っており、エラーが出まくったこと
  • 辞書型の扱いに慣れておらず、うまく値を取り出せなかったこと

などがありました。

そして最初は合計料金取得後、再度get_cost_and_usageを呼び出してアカウントごとの料金を取得しようとしていたのですが、アカウントごとの料金を足し算すればよくね?と思い立ち、コードを書き直したりしました。
また、出力はアカウントIDと料金の組み合わせにしていたのですが、アカウント名の方がわかりやすいと思い、変更を加えました。
現状の出力はこんな感じです。AWSアカウント名とその料金がJSON形式で返ってきています。

{
  "Organizaitons": "0.0000347034",
  "CloudWatch-handson": "0.8411071811",
  "AgentCore": "2.435154016",
  "LPIC": "0",
  "maari_handson": "1.3636264716",
  "合計料金": 4.6399223721
}

3. 取得した内容をLINEで通知

さて、最後にLINE通知の実装です。LINE通知については全く知識がありませんが、最初に引用させていただいた記事の中でLINE Messaging APIなるものがあることは知っていました。とりあえず公式ドキュメントを読むことから始めてみました。

親切にもリクエスト例が載っていたので、こちらを参考に実装してみます。

curl -v -X POST https://api.line.me/v2/bot/message/push \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {channel access token}' \
-H 'X-Line-Retry-Key: {UUID}' \
-d '{
    "to": "U4af4980629...",
    "messages":[
        {
            "type":"text",
            "text":"Hello, world1"
        },
        {
            "type":"text",
            "text":"Hello, world2"
        }
    ]
}'

まず、LINE側の情報として必要になるのが

  • 通知先のユーザーID
  • LINE Messaging APIのチャネルアクセストークン

の2つです。 これらはLambdaの環境変数に設定しておきます。

URLにアクセスする方法を知りませんでしたが、調べたところurllibというライブラリが使えそうだったので、こちらを利用して実装してみました。

出来上がったコードはこちら
import json
import boto3
import datetime
import calendar
import os
import urllib.request

ce = boto3.client('ce')
organizations = boto3.client('organizations')

LINE_USER_ID = os.environ['LINE_USER_ID']
LINE_CHANNEL_ACCESS_TOKEN = os.environ['LINE_CHANNEL_ACCESS_TOKEN']

def lambda_handler(event, context):
    first_date = get_first_date()
    last_date = get_last_date()
    accounts_dict = get_accounts()

    accounts_cost = get_costs(first_date, last_date, accounts_dict)
    message = create_message(first_date, last_date, accounts_cost)

    post_line(message)

# 月初の日付を取得
def get_first_date():
    today = datetime.date.today()
    first_date = today.replace(day=1)

    return str(first_date)

# 月末の日付を取得
def get_last_date():
    today = datetime.date.today()
    last_date = today.replace(day=calendar.monthrange(today.year, today.month)[1])

    return str(last_date)


# メンバーアカウントの一覧を取得
def get_accounts():
    response = organizations.list_accounts()
    accounts_dict = {}
    
    for i in range(len(response['Accounts'])):
        accounts_dict[response['Accounts'][i]['Name']] = response['Accounts'][i]['Id']

    return accounts_dict

# 各アカウントの月間コストを取得
def get_costs(first_date, last_date, accounts_dict):
    accounts_cost = {}
    for account_name, account_id in accounts_dict.items():
        response = ce.get_cost_and_usage(
            TimePeriod={
                'Start': first_date,
                'End': last_date
            },
            Granularity='MONTHLY',
            Filter={
                'Dimensions': {
                    'Key': 'LINKED_ACCOUNT',
                    'Values': [
                        account_id,
                    ],
                    'MatchOptions': [
                        'EQUALS',
                    ]
                }
            },
            Metrics=[
                'AmortizedCost',
            ]
        )
        cost = float(response['ResultsByTime'][0]['Total']['AmortizedCost']['Amount'])
        accounts_cost[account_name] = str(round(cost, 2))
    
    # 合計料金を算出
    cost_list = [float(v) for v in accounts_cost.values()]
    accounts_cost['合計料金'] = sum(cost_list)
    
    return accounts_cost

# LINEの通知メッセージ作成
def create_message(first_date, last_date, accounts_cost):
    message = []
    message.append(first_date + '~' + last_date + 'のAWS利用料金')
    message.append('')

    for account_name, account_cost in accounts_cost.items():
        message.append(account_name + '' + str(account_cost))

    return "\r\n".join(message)


def post_line(message):
    url = 'https://api.line.me/v2/bot/message/push'
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN
    }
    body = {
        "to": LINE_USER_ID,
        "messages":[
            {
                "type":"text",
                "text":message
            }
        ]
    }
    data=json.dumps(body).encode()

    req = urllib.request.Request(
        url,
        data=data,
        method="POST",
        headers=headers
    )
    urllib.request.urlopen(req)

このLambda関数を実行することで、ちゃんとLINEに通知が届くことを確認できました!!

EventBridgeで定期実行設定

最後にEventBridgeスケジュールでこのLambda関数を毎週月木の12:00に実行する設定を行います。
cron式は00 12 ? * MON,THU *としました。
image.png

サービスはAWS Lambdaを選択し、APIはInvokeを選択、ターゲットに先ほど作成したLambda関数を指定します。
あとはデフォルト設定のままルールを作成すればOKです。

動作確認

ちゃんと指定した時間にLINEが届きました!!
image.png

AI禁止にしてみて感じたこと

今回AI禁止でシステムを作ってみて感じたことは以下の通りです。

  • エラーの原因を自分で調べて解決することで、理解がとても深まる
  • 公式ドキュメント通りに実装すれば、意外と簡単に動く
  • でもめちゃくちゃ時間はかかる

今まで、AIに書いてもらったコードをAIに解説してもらうことで理解した気になっていましたが、いざ自分で書いてみると、全然理解できていなかったことに気づかされました。

AIにレビューしてもらう

さて、無事にシステムが完成しました。自分が思った通りに動いているし、各AWSアカウントの料金を定期的に知りたいという目的も達成できているので、割と満足しています。
ですが、果たしてAWSの構成やLambdaのコードは完璧だったのでしょうか?
正直、AWSの構成については問題ないと思っています。そして、Lambdaのコードについても自分で理解しながら書いたので自分にとっては読めるコードですが、一般的に言われる「可読性の高いコード」になっているかは自信がありません。

ということで、ここでAIを解禁し、コードレビューをしてもらおうと思います。
次のようなプロンプトでClaude Codeにレビューを依頼してみました。

AWSの利用料金を定期的にLINEに通知するシステムを作成しました。
あなたはAWSデベロッパーとして、AWS構成とLambdaコードをレビューしてください。

# AWS構成
Lambda関数でCost Explorer と Organizations から各AWSアカウントの利用料金を取得し、LINEに通知します。このLambdaをEventBridgeで定期実行する構成です。

# レビュー対象
- AWS構成
- lambda_function.py

# レビュー観点
- 可読性(命名、責務分離、例外処理、ログ、設定値の扱い)
- よりよい実装(改善案があれば理由つきで)
  - 実行コストを抑えられるか
  - 実行時間を短くできるか

# 出力形式
- AWS構成について、修正点があれば、修正点の一覧をマークダウン形式のファイルを作成してください。
- lambda_function.pyについて、修正点があればコメントで追加する形で修正してください。

AWS構成に関するレビュー

レビュー結果
# AWS構成レビュー

## 1. Lambda実行ロールのIAMポリシー

- **修正点**: IAMポリシーは最小権限の原則に従い、以下のアクションのみ許可すること
  - `ce:GetCostAndUsage`
  - `organizations:ListAccounts`
- **理由**: 不要な権限を付与するとセキュリティリスクが高まる。`ce:*``organizations:*`のようなワイルドカード指定は避ける

## 2. 環境変数の暗号化

- **修正点**: `LINE_CHANNEL_ACCESS_TOKEN`はLambdaの環境変数暗号化ヘルパー(KMS)を使用して暗号化するか、AWS Secrets Managerに格納すべき
- **理由**: 環境変数は平文でコンソールに表示されるため、アクセストークンのような機密情報は暗号化して管理すべき

## 3. EventBridgeのスケジュール

- **修正点**: 月末に実行する場合、当日のコストは反映が遅れる点に注意。前日までのコストを取得する設計にするか、翌月1日に前月分を通知する構成を検討すべき
- **理由**: Cost Explorerのデータは最大24時間の遅延がある

## 4. エラー通知

- **修正点**: Lambda関数の失敗時にSNSやCloudWatch Alarmで通知する仕組みを追加すべき
- **理由**: 通知が来ないこと自体に気づけない問題がある。Dead Letter Queue (DLQ)の設定も推奨
  1. たしかにIAMポリシーは最小権限の原則に従うべきですね。AWS管理のフルアクセス権限を付与していましたが、必要なアクションだけに絞るように修正します。
  2. Secrets Managerの存在を完全に忘れていました。。今回は個人利用なのでこのまま環境変数でいきますが、業務利用する際はSecrets Managerを使った方が安全ですね。
  3. Cost Explorerのデータ遅延については知りませんでした。ただ、今回のシステムを作成した目的は大体の料金を把握することなので、そこまでシビアに考えなくてもいいかなと思います。
  4. こちらについても、業務利用する際はあった方がいいですね。個人利用なので今回は見送ります。

2、3、4について、構成を考える際に考慮すらできていなかったので、非常に勉強になりました。自分が慣れていない部分を知れたのは大きな収穫です。

Lambdaに関するレビュー

レビュー結果
import json
import boto3
import datetime
import calendar
import os
import urllib.request

# [レビュー] boto3クライアントをモジュールレベルで生成するのはLambdaのベストプラクティス(コンテナ再利用時に接続を使い回せる)。この点は良い
ce = boto3.client('ce')
organizations = boto3.client('organizations')

LINE_USER_ID = os.environ['LINE_USER_ID']
LINE_CHANNEL_ACCESS_TOKEN = os.environ['LINE_CHANNEL_ACCESS_TOKEN']

def lambda_handler(event, context):
    # [レビュー] try-exceptで全体を囲み、エラー時にログ出力すべき。
    # 現状ではエラー発生時に原因特定が困難
    first_date = get_first_date()
    last_date = get_last_date()
    accounts_dict = get_accounts()

    accounts_cost = get_costs(first_date, last_date, accounts_dict)
    message = create_message(first_date, last_date, accounts_cost)

    post_line(message)

# 月初の日付を取得
def get_first_date():
    today = datetime.date.today()
    first_date = today.replace(day=1)

    return str(first_date)

# 月末の日付を取得
# [レビュー] Cost ExplorerのTimePeriod.Endは「排他的(その日を含まない)」仕様。
# 月末日を指定すると最終日のコストが含まれない。
# 修正案: last_date = (today.replace(day=1) + datetime.timedelta(days=32)).replace(day=1)
# として翌月1日を指定するか、today + timedelta(days=1) を使って「今日まで」のコストを取得する
def get_last_date():
    today = datetime.date.today()
    last_date = today.replace(day=calendar.monthrange(today.year, today.month)[1])

    return str(last_date)


# メンバーアカウントの一覧を取得
# [レビュー] list_accountsはページネーションがある(最大20件/回)。
# アカウント数が多い場合、NextTokenを使ったループまたはget_paginator('list_accounts')を使うべき。
# [レビュー] for文はPythonic に書ける: accounts_dict = {a['Name']: a['Id'] for a in response['Accounts']}
def get_accounts():
    response = organizations.list_accounts()
    accounts_dict = {}

    for i in range(len(response['Accounts'])):
        accounts_dict[response['Accounts'][i]['Name']] = response['Accounts'][i]['Id']

    return accounts_dict

# 各アカウントの月間コストを取得
# [レビュー][実行コスト・実行時間] アカウントごとに個別にAPIを呼んでいるため、アカウント数に比例してAPI呼び出し回数と実行時間が増える。
# 改善案: GROUP_BY に LINKED_ACCOUNT を指定すれば1回のAPI呼び出しで全アカウントのコストを取得できる。
# 例: ce.get_cost_and_usage(..., GroupBy=[{'Type': 'DIMENSION', 'Key': 'LINKED_ACCOUNT'}])
# これによりAPI呼び出し回数がN回→1回に削減され、実行時間とコスト両方を改善できる
def get_costs(first_date, last_date, accounts_dict):
    accounts_cost = {}
    for account_name, account_id in accounts_dict.items():
        response = ce.get_cost_and_usage(
            TimePeriod={
                'Start': first_date,
                'End': last_date
            },
            Granularity='MONTHLY',
            Filter={
                'Dimensions': {
                    'Key': 'LINKED_ACCOUNT',
                    'Values': [
                        account_id,
                    ],
                    'MatchOptions': [
                        'EQUALS',
                    ]
                }
            },
            Metrics=[
                'AmortizedCost',
            ]
        )
        cost = float(response['ResultsByTime'][0]['Total']['AmortizedCost']['Amount'])
        accounts_cost[account_name] = str(round(cost, 2))
    
    # 合計料金を算出
    cost_list = [float(v) for v in accounts_cost.values()]
    accounts_cost['合計料金'] = sum(cost_list)
    # [レビュー] 合計料金がfloat型のまま格納されているが、他のアカウントはstr型。create_messageでstr()変換しているので動作はするが、型を揃えるべき
    
    return accounts_cost

# LINEの通知メッセージ作成
def create_message(first_date, last_date, accounts_cost):
    message = []
    message.append(first_date + '~' + last_date + 'のAWS利用料金')
    message.append('')

    for account_name, account_cost in accounts_cost.items():
        message.append(account_name + '' + str(account_cost))

    return "\r\n".join(message)


# [レビュー] APIレスポンスのステータスコードを確認していない。送信失敗時に気づけない。
# [レビュー] URLは定数としてモジュール上部に定義すると可読性が上がる
def post_line(message):
    url = 'https://api.line.me/v2/bot/message/push'
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN
    }
    body = {
        "to": LINE_USER_ID,
        "messages":[
            {
                "type":"text",
                "text":message
            }
        ]
    }
    data=json.dumps(body).encode()

    req = urllib.request.Request(
        url,
        data=data,
        method="POST",
        headers=headers
    )
    urllib.request.urlopen(req)

Cost Explorerでコスト取得する部分で、FilterにGROUP_BYを使えば1回のAPI呼び出しで全アカウントのコストを取得できるのが驚きでした。今のままではアカウントの数だけLambdaの実行に時間とコストがかかってしまうので、修正は必須ですね。
ドキュメントの読み込みが足りていなかったなと反省です。

また、ページネーションの存在も知らなかったので、こちらも勉強になりました。まだアカウント数が少ないので問題になっていませんが、将来的に増えたときに対応できるように修正しておきたいと思います。

自力で書いたことでコードの理解度が高い分、AIからの指摘内容もすっと入ってきた気がします。
再度自力で修正し、AIにレビューしてもらう、というサイクルを回してみて、納得したら終わりにしようと思います。

最終的なLambda関数がこちら
import json
import logging
import boto3
import datetime
import os
import urllib.request

logger = logging.getLogger()
logger.setLevel(logging.INFO)

ce = boto3.client('ce')
organizations = boto3.client('organizations')

LINE_USER_ID = os.environ['LINE_USER_ID']
LINE_CHANNEL_ACCESS_TOKEN = os.environ['LINE_CHANNEL_ACCESS_TOKEN']
LINE_PUSH_URL = 'https://api.line.me/v2/bot/message/push'


def lambda_handler(event, context):
    try:
        first_date = get_first_date()
        last_date = get_last_date()
        accounts_dict = get_accounts()

        accounts_cost = get_costs(first_date, last_date, accounts_dict)
        message = create_message(first_date, last_date, accounts_cost)

        post_line(message)
        logger.info('LINE通知が正常に送信されました')
    except Exception as e:
        logger.error(f'エラーが発生しました: {e}', exc_info=True)
        raise


# 月初の日付を取得
def get_first_date():
    today = datetime.date.today()
    first_date = today.replace(day=1)
    return str(first_date)


# 翌月1日の日付を取得
def get_last_date():
    today = datetime.date.today()
    if today.month == 12:
        last_date = today.replace(year=today.year + 1, month=1, day=1)
    else:
        last_date = today.replace(month=today.month + 1, day=1)
    return str(last_date)


# メンバーアカウントの一覧を取得
def get_accounts():
    paginator = organizations.get_paginator('list_accounts')
    accounts_dict = {}
    for page in paginator.paginate():
        accounts_dict.update({a['Name']: a['Id'] for a in page['Accounts']})
    return accounts_dict


# 全アカウントの月間コストを一括取得
def get_costs(first_date, last_date, accounts_dict):
    id_to_name = {v: k for k, v in accounts_dict.items()}

    response = ce.get_cost_and_usage(
        TimePeriod={
            'Start': first_date,
            'End': last_date
        },
        Granularity='MONTHLY',
        Metrics=['AmortizedCost'],
        GroupBy=[{'Type': 'DIMENSION', 'Key': 'LINKED_ACCOUNT'}]
    )

    accounts_cost = {name: '0' for name in accounts_dict.keys()}
    total = 0.0

    for group in response['ResultsByTime'][0]['Groups']:
        account_id = group['Keys'][0]
        cost = round(float(group['Metrics']['AmortizedCost']['Amount']), 2)
        account_name = id_to_name.get(account_id, account_id)
        accounts_cost[account_name] = str(cost)
        total += cost

    accounts_cost['合計料金'] = str(round(total, 2))
    return accounts_cost


# LINEの通知メッセージ作成
def create_message(first_date, last_date, accounts_cost):
    message = []
    message.append(first_date + '~' + last_date + 'のAWS利用料金')
    message.append('')

    for account_name, account_cost in accounts_cost.items():
        message.append(account_name + '' + account_cost)

    return "\r\n".join(message)


# LINE送信
def post_line(message):
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + LINE_CHANNEL_ACCESS_TOKEN
    }
    body = {
        "to": LINE_USER_ID,
        "messages": [
            {
                "type": "text",
                "text": message
            }
        ]
    }
    data = json.dumps(body).encode()

    req = urllib.request.Request(
        LINE_PUSH_URL,
        data=data,
        method="POST",
        headers=headers
    )
    response = urllib.request.urlopen(req)
    if response.status != 200:
        raise RuntimeError(f'LINE API エラー: ステータスコード {response.status}')

おわりに

AI禁止にすることで、自分の苦手な部分に時間をかけて取り組むことができ、とても勉強になりました。
AIにコードの意味を解説してもらって理解した気になっていたけど、実際は自力で書けるほどには理解できていなかったことに気づけたのは良かったと思います。アウトプットの重要性を改めて実感しました。

ただ、やはり時間はかかるのでうまくAIを利用していく必要があります。AIに頼りすぎても上達しないし、頼らなさすぎても効率が悪いので、バランスが大事だと思います。

じゃあどうするの?っていう話ですが、一度AIに実装してもらい、そのコードを写経しながら勉強するという方法を実践してみようと考えています。
プログラミングに入門したての頃は、参考書に書いてあった例文を写経して文法などを勉強していました。実はあの頃の勉強法が一番自分に合っていたのかもしれません。
これもまた検証して記事にできたらいいなと思います。

最後までお読みいただきありがとうございました!

3
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
3
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?