Skip to content

Conversation

@monakam01
Copy link

@monakam01 monakam01 commented Jul 16, 2025

概要

  • serverside_challenge_2課題(Readme)における下記を対応
    • 基本項目
      • 料金データをDBで管理できるようにする は DB管理へ移行済み。
        (最終実装はコミット: #2371165(コミットメッセージ: correct seed.rb)に checkout することで確認いただけます。)
    • 追加項目の下記2項目
      • 料金データをDBで管理できるようにする
      • 本番環境へのデプロイ
  • 本番環境をRender.comに最新コミットにてデプロイ済み ( As of 2025/7/28 2025/7/31)

実装内容(仕様)

  • APIの実装
  • 仕様
    • 家庭で利用している電気契約アンペア数、直近の使用量(kWh)を送信することで、各電力会社のプランごとの料金を返す
    • 対応する電力会社・プランはReadme を参照
  • リクエスト
    • メソッド:POST GET
    • クエリパラメータ
      • amp: 契約アンペア数
        • 10 / 15 / 20 / 30 / 40 / 50 / 60 のいずれか
      • meter_rate: 使用量(kWh)
        • 0以上の整数
    • リクエストエンドポイント: /api/v1/simulate_all_plan
    • レスポンス(JSON形式):[{ provider_name: [電力会社名], plan_name: [プラン名], price: [電気料金] }, …]
  • 計算について
    • 主な計算は上記 Readme を参考に実装。
    • 端数(小数点以下)金額 について
      • 全電力会社について東京電力エナジーパートナー東京ガスLooopでんき 最終的な金額から小数点第一位以下を切捨て。

DB設計

image

エラー関連

  • リクエストパラメータに不備がある場合にステータスコード400で下記(例)のJSONを返す。
[
    {
        "error": {
            "code": 400,
            "message": "リクエストパラメータ 'amp' が不足しています。\nリクエストパラメータ 'meter_rate' の値が不正です。0以上の整数を指定してください。\n"
        }
    }
]

メッセージパターン

# 内容 エラーメッセージ
1 amp パラメータが無い リクエストパラメータ 'amp' が不足しています。\n
2 meter_rate パラメータが無い リクエストパラメータ 'meter_rate' が不足しています。\n
3 amp パラメータの値が不正値 リクエストパラメータ 'meter_rate' の値が不正です。0以上の整数を指定してください。\n
4 meter_rate パラメータの値が不正値 リクエストパラメータ 'amp' の値が不正です。'10 / 15 / 20 / 30 / 40 / 50 / 60' のいずれかの値を指定してください。\n

特記事項

  • データのCSV管理にActiveHashを使用
    • 最終実装はコミット: #2371165(コミットメッセージ: correct seed.rb)に checkout することで確認いただけます
  • エンドポイント、Controller のネームスペースとして/api/v1を使用。
    • 今後のAPIの仕様変更時の開発、API利用者への利便性を考慮
  • ロジック変更ぜずに既存プランへの契約アンペア数の追加、従量課金設定の追加可能(後方追加)
    • レコードの修正、追加で対応可能

今後対応したい課題

  • DONE: テスト実装(Rspc)
  • API動作確認のためのフロント画面の実装
  • 料金データ管理のための管理画面(Admin画面)の実装
  • 料金体系変更時の時限設定(変更タイミングで料金設定を自動変更)
  • DB管理方法の再検討
    • 本番デプロイ毎にデータベースをリフレッシュ(rake db:migrate:reset && rake db:seed)するため、料金設定以外のデータをDB管理する必要が出た場合に運用方法の再検討が必要

動作確認

サーバー環境

  • 本場環境
    • Render.comに最新コミットをデプロイ済み
  • ローカル環境
    $ docker compose up
    $ docker compose exec web bash
    root@0fcf1ebe5546:/app# rails db:create
    root@0fcf1ebe5546:/app# rails db:migrate
    root@0fcf1ebe5546:/app# rails db:seed
    

ローカルツール

APIツール(Postman等)

項目 内容
method POST GET
url localhost:3000/api/v1/simulate_all_plan
query params:
amp (契約アンペア)
10 / 15 / 20 / 30 / 40 / 50 / 60 いずれかの値
query params:
meter_rate (使用量)
0以上の整数

Curlコマンド

  curl -X GET "localhost:3000/api/v1/simulate_all_plan?amp=[契約アンペア]&meter_rate=[使用量]"

レスポンス

  • 例 (JSON形式):
[
    {
        "provider_name": "東京電力エナジーパートナー",
        "plan_name": "従量電灯B",
        "price": 8898
    },
    {
        "provider_name": "東京電力エナジーパートナー",
        "plan_name": "スタンダードS",
        "price": 12038
    },
    {
        "provider_name": "東京ガス",
        "plan_name": "ずっとも電気1",
        "price": 8874
    },
    {
        "provider_name": "Looopでんき",
        "plan_name": "おうちプラン",
        "price": 8668
    }
]

動作確認メモ

  • 下記の送信パラメータパターンにて、想定の金額(計算結果)となることをローカル・本番環境にて確認済。
image

テスト

  • ローカルでのRSpec実行結果
image

備考

小数点以下の金額の処理について

  • 計算結果金額の小数点以下の端数を切捨てとする根拠とした各電力会社(3社)の資料

1. 東京電力エナジーパートナー

  • 計算図表内に下記の記載あり

計算方法
(小数点以下切捨て)

出展: スタンダードプラン(関東)|電気料金プラン|東京電力エナジーパートナー株式会社

2. 東京ガス

  • 料金について > (2) 料金計算方法 に注意書きあり

注) 小数点以下切り捨て

出展: 電気料金メニュー「基本プラン」契約・料金の仕組み

3. Looop電気

  1. 単位及び端数処理
    ...
    (4) 料金その他の計算における合計金額の単位は1円とし、その端数は切り捨てま
    す。ただし、消費税等相当額を加算して申し受ける場合には、消費税が課される
    金額及び消費税等相当額の単位はそれぞれ1円とし、その端数はそれぞれ切り捨
    てます。

出展: 電気供給約款【低圧】株式会社 Looop

@monakam01 monakam01 closed this Jul 16, 2025
@monakam01 monakam01 reopened this Jul 25, 2025
@monakam01 monakam01 changed the title Dev branch 【チャレンジ課題】電気料金を返すAPIの実装 monakam01 Jul 28, 2025
Copy link

@sugita-seiya sugita-seiya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@monakam01
提出ありがとうございます!

内容はこれから詳しく確認させていただきますが、確認したところテストが見当たらなかったため、テストの追加をお願いできればと思います。
弊社ではテストコードを非常に大事にしており、実装と同様にテストも品質を支える重要な要素と考えていますので、ぜひご対応お願いします。

Copy link

@sugita-seiya sugita-seiya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

いくつかコメントさせていただきました。

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

全体的に、コントローラーにすべての責務が集中してしまっている印象を受けました。

具体的には、以下のような処理がすべて controller に記述されているため、保守性や可読性の観点から、責務ごとに適切に分離した方が良いと考えています。

  • リクエスト/レスポンスの処理
  • ビジネスロジック(例:料金計算など)
  • パラメータのバリデーションチェック

現時点で大きく実装を変更するのは大変かと思いますので、「今後リファクタリングする場合に、どのように責務を分けて実装するべきか」について、テキストベースで構いませんので、考えを共有いただけると嬉しいです。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コントローラの処理を最小限にし、処理を分離いたしました。db2b5d6

  • 分離先
    • PowerSupplyPlanSimulator(service class)
    • PowerSupplyPlan(model)
    • MeterRateCharge(model)

end
end

def build_200_message(amp, meter_rate)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

内容を見る限り、バリデーション処理およびエラーメッセージの生成を行っているように見受けられます。
一方でメソッド名からは、HTTP 200のレスポンスを構築するような印象を受けるため、命名と実装の責務が一致していないと感じました。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Q]
加えて、バリデーションはcontroller内で実施するのが適切でしょうか?意図があれば教えていただきたいです。

Copy link
Author

@monakam01 monakam01 Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sugita-seiya
メソッド名を validate_params とし、調整いたしました。
db2b5d6#diff-362c058661917b8d89d680d4aadeea1020721ce0f8fe3d34b943450d01f2be99R34

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mochikichi
モデルに依存しないパラメータのため、controller 内で書いておりましたが、新設したPoweSupplyPlanSimulator サービスクラス内にて行うよう変更いたしました。
db2b5d6#diff-362c058661917b8d89d680d4aadeea1020721ce0f8fe3d34b943450d01f2be99R34

Comment on lines 53 to 75
if max_rate.nil? && min_rate <= meter_rate
# 最大料金ステップの計算
return (meter_rate - min_rate + 1) * price
end

if min_rate.zero?
# 最小料金ステップの計算
if max_rate <= meter_rate
# 該当ステップの満額請求計算
max_rate * price
else
# 該当ステップの使用量までを計算
meter_rate * price
end
elsif (min_rate..max_rate).include?(meter_rate)
# 中間料金ステップの計算
# 該当ステップの使用量までを計算
(meter_rate - min_rate + 1) * price
else
# 該当ステップの満額請求計算
(max_rate - min_rate + 1) * price
end
end

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

条件分岐が多く、ネストが深くなっているため「ガード節を活用」、「メソッド分割」してネストを浅くしたいです。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MeterRateCharge モデル内にて分岐処理を調整しました。
db2b5d6#diff-aed437e48cb04cf29acee4e9b374fec1823f2c6695befe5eb45120f1bf3c0876R12

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

credentialsはどこにも使用されていなさそうです。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

追加項目 の本番デプロイ対応のためmaster.key とセットで追加しておりました。
削除してしまうと本番デプロイに失敗するため、このままとさせていただければと思います。
master.key は本番環境の環境変数に設定(git管理外)しているため、差分としてはございません。

# root "articles#index"
namespace :api do
namespace :v1 do
post "simulate_all_plan", to: "power_supply_plan#simulate_all"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q

POSTメソッドを選定した理由があれば教えていただけると嬉しいです!

Copy link
Author

@monakam01 monakam01 Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

深い意図なくPOSTメソッドを使用しておりましたが、
今回の要件ではデータの更新に関連しないこと、送信データも機密情報でも大きなデータでも無いことから、
GETの方が適切と判断し変更いたしました。ce69d19

Copy link
Collaborator

@mochikichi mochikichi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

課題に取り組んでいただきありがとうございます!

いくつかコメントさせていただきましたのでご確認よろしくお願いします!

return meter_rate * price if min_rate.zero? && max_rate.nil?

# 使用量がステップ請求額に満たない場合0を返す
return 0 if meter_rate < min_rate
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[want]
calculate_if_step_applicableメソッドで、計算が不要なステップでも毎回0を返すループを実行しているのは非効率です。使用量に応じて必要なステップのみを処理するよう、早期リターンや条件分岐の最適化を検討してみていただけると良いと思います。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

end
end

def build_200_message(amp, meter_rate)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Q]
加えて、バリデーションはcontroller内で実施するのが適切でしょうか?意図があれば教えていただきたいです。

end

def valid_amp?(amp)
[10, 15, 20, 30, 40, 50, 60].include?(amp.to_i)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[want]
マジックナンバーになっているので定数化できると良いかと思います。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[10, 15, 20, 30, 40, 50, 60].include?(amp.to_i)
end

def errro_result(result)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nits]
errorのタイポかと思われます。

@monakam01
Copy link
Author

@sugita-seiya @mochikichi
レビューいただきありがとうございました。
修正のうえ、頂いたコメントへ返信させていただきました。
18d89ba にてテストコードを追加いたしました。

ご確認のほどよろしくお願いいたします。

@mochikichi
Copy link
Collaborator

修正対応いただきありがとうございました!内容確認させていただきました。

@sugita-seiya
Copy link

@monakam01
お忙しい中、ご対応いただきありがとうございました!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants