Skip to content

Comments

【重冨】serverside_challenge_2#77

Open
shigenius wants to merge 38 commits intoenechange:masterfrom
shigenius:master
Open

【重冨】serverside_challenge_2#77
shigenius wants to merge 38 commits intoenechange:masterfrom
shigenius:master

Conversation

@shigenius
Copy link

概要

お世話になっております。重冨です。
ご案内いただきましたチャレンジ課題「serverside_challenge_2」電気料金シミュレーション機能を実装いたしました。
課題内容

こちらに技術構成、開発環境用のセットアップ方法、シミュレーションページURLなど記載しております。
README.md

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

対応内容

  • 電気料金のシミュレーションAPIの実装 GET /plans/prices
  • 「フロントエンドを実装し、実装したAPIを利用して料金を表示する」 GET /plans
  • 料金データをDBで管理できるようにする(ER図
    • seedはcsvで定義したデータを取り込む形式
  • 各rspec実装

動作確認手順:初回セットアップ~シミュレーションまで

$ git clone git@github.com:shigenius/enechange-coding-challenge.git
$ cd enechange-coding-challenge/serverside_challenge_2/challenge
$ ./scripts/setup.sh

http://localhost:3000/plans にアクセス
必要なパラメータを入力または選択し、シミュレーション実行

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

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.

チャレンジ提出ありがとうございます。
拝見させていただき、読み手への配慮が随所に感じられる実装がとても素晴らしいと感じました!

  • 適切にDB設計されている点
  • YARDドキュメントが丁寧に記載されている点
  • テストが充実しており、仕様や想定ケースがしっかりとカバーされている点

細かな点をいくつか質問させていただきましたので、お手隙の際にご確認・ご回答いただけますと幸いです。

def self.calc_price(basic_fee:, usage_charge:, usage:)
fee = basic_fee.fee
charge = usage_charge.calc_charge(usage)
(fee + charge).floor # TODO: 小数点以下の扱い

Choose a reason for hiding this comment

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

NITS

不要なTODOコメントが残っています。

Copy link
Author

Choose a reason for hiding this comment

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

ありがとうございます。
TODOコメントは削除いたしました。
5518ac9

Comment on lines 30 to 31
basic_fee = plan.basic_fees.first
usage_charge = plan.usage_charges.first

Choose a reason for hiding this comment

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

Q

1つのプランに対して複数のbasic_feesやusage_chargesが存在する場合、どのレコードが選択されるかが不明確ですが、firstで最初のレコードを取得する意図はなんでしょうか?

Copy link
Author

Choose a reason for hiding this comment

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

コメントいただきありがとうございます。

今回 Plan.by_ampere_and_usageによって取得されるひとつのplanに対するbasic_feesとusage_chargesは、必ず単一になる想定でした。
(現在は、こちらのコメント通り計算ロジックの変更に伴い、usage_chargesは複数取るように変更しました)

ここからは旧計算ロジック前提のお話になりますが、
まずbasic_feesはampareでユニーク制約があり、usage_chargesはレコード間でusage_lowerからusage_upperの範囲が重複しないようにアプリ側でバリデーションを設けております。
また、Plan.by_ampere_and_usageではbasic_feesとusage_chargesに対してjoinsとincludesとmergeを用いることで、先読みした関連レコードは条件で絞り込まれたものがキャッシュされます。
(またINNER JOINとなるため、basic_feesまたはusages_chargesがヒットしなかったplansは結果に含まれない)

そのため下記該当コードの通り、先読みした関連レコードは1件のみ取得される想定だったため、firstを用いていました。

basic_fee = plan.basic_fees.first # plan.basic_feesの時点で1レコードのみ
usage_charge = plan.usage_charges.first # plan.usage_chargesの時点で1レコードのみ

私も正直なところこのfirstを用いている箇所は、分かりにくくあまり綺麗じゃないなあと思いつつ書いていました...。
調べたところ、今回のケースではfirstの代わりにsoleメソッドを使うのが良さそうだったので、そちらに変更しました 🙇
cbec282

soleによりエラーが出るタイミングとしては、APIを実行した時にデータが不整合状態だった場合になるので、ユースケースの想定によっては、単一のレコードじゃない場合エラーではなくスキップにしたほうがいいかもしれません)

@@ -0,0 +1,5 @@
id,provider_id,name
1,1,従量電灯B
Copy link

@ksugiura-ene ksugiura-ene Oct 6, 2025

Choose a reason for hiding this comment

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

NITS

細かい内容ですがCSVにidが入っておりidを明示的に指定した場合、createしたレコードのidがsequenceのcurrvalとずれてしまいます。
idの採番はpsqlに任せた方が良いと思いました。
Seed後に以下のようにnameだけ指定した場合uniq制約エラーが発生します。

Provider.create!(name: 'aa')
  TRANSACTION (0.5ms)  BEGIN
  Provider Create (2.2ms)  INSERT INTO "providers" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id"  [["name", "aa"], ["created_at", "2025-10-06 11:22:09.290308"], ["updated_at", "2025-10-06 11:22:09.290308"]]
  TRANSACTION (0.4ms)  ROLLBACK
/usr/local/bundle/gems/activerecord-7.0.8/lib/active_record/connection_adapters/postgresql_adapter.rb:768:in `exec_params': PG::UniqueViolation: ERROR:  duplicate key value violates unique constraint "providers_pkey" (ActiveRecord::RecordNotUnique)
DETAIL:  Key (id)=(1) already exists.
/usr/local/bundle/gems/activerecord-7.0.8/lib/active_record/connection_adapters/postgresql_adapter.rb:768:in `exec_params': ERROR:  duplicate key value violates unique constraint "providers_pkey" (PG::UniqueViolation)
DETAIL:  Key (id)=(1) already exists.

Copy link
Author

Choose a reason for hiding this comment

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

ありがとうございます。
たしかにIDはseedで固定すべきではありませんでした 🙇

今回はcodeというカラムを追加し、各外部キーもcodeを見るようにカラム名ならびに定義を変更いたしました。
この変更に伴い今回は既存migrationファイルを直接変更したため、お手数ですが以下の手順で更新をお願いいたします。

docker compose run --rm web rails db:reset

094eb4d

Choose a reason for hiding this comment

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

ありがとうございます。対応を確認いたしました。

basic_fee = plan.basic_fees.first
usage_charge = plan.usage_charges.first

{
Copy link

@ksugiura-ene ksugiura-ene Oct 6, 2025

Choose a reason for hiding this comment

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

私の手元で計算しましたが、従量料金の計算ロジックが間違っているように見えます。
120kwhは同じ結果でしたが、121kwhで大きく計算が異なっています。
段階的な料金計算ロジックが実装されていないように見受けられます。

  • このアプリケーションの実行結果
40A,120kwh,東京電力エナジーパートナー,従量電灯B,3529円
40A,121kwh,東京電力エナジーパートナー,従量電灯B,4348円
  • 私の手計算
40A,120kwh,東京電力エナジーパートナー,従量電灯B,3529円
40A,120kwh,東京電力エナジーパートナー,従量電灯B,3556円
スクリーンショット 2025-10-06 21 38 57

添付の画像(READMEにリンクがあります)を参照すると121kWhの場合以下の計算ロジックになります。

  • 基本料金 = 1144.00
  • 19円88銭 x 120kWh = 3529.6
  • 26円48銭 x (使用電力量-120kWh) = 26.48
    => 3556.08 となりました。

Copy link
Author

Choose a reason for hiding this comment

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

ありがとうございます。
仰るとおり従量料金の計算ロジックを間違えておりました...。
ご共有いただいた参照先を確認したところ、仰る通りの計算方法が正として理解しました。

これに伴い以下の通りコードを修正いたしました(主な修正点)

  • 従量料金の計算ロジックを正しいものに変更
  • 計算の都合上、usage_charge.usage_lowerについて、一つ前のレベルのusage_charge.usage_upperと同値となることを前提に変更
    • たとえば [0, 120], [121, 300][0, 120], [120, 300]
    • 従来のデータ定義では、usage_lower = 0とそれ以外で従量料金の計算ロジックが少し異なってしまうため、同じように扱えるようにした

コメントいただきました40A、121kWhのパターンの実際の計算結果のスクリーンショットを添付いたします。

image

e6f0a8b

Copy link

@ksugiura-ene ksugiura-ene Oct 8, 2025

Choose a reason for hiding this comment

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

ありがとうございます。
実装および計算結果を確認させていただきました。

@shigenius
Copy link
Author

コメントありがとうございます!
指摘箇所を確認して修正いたします。
修正が終わりましたら再度コメントいたしますので少々お待ち下さい。

cursor[bot]

This comment was marked as outdated.

.where("(usage_lower < ? AND (usage_upper IS NULL OR usage_upper > ?))",
upper,
usage_lower)

Copy link

Choose a reason for hiding this comment

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

Bug: Validation Flaw: Incorrectly Flags Adjacent Ranges

The no_overlapping_ranges validation has a logical flaw. Its current query incorrectly flags non-overlapping ranges, including valid adjacent ranges (like [0,120] and [120,300]), as overlaps. This contradicts the intended allowance for touching boundaries.

Fix in Cursor Fix in Web

Copy link
Author

Choose a reason for hiding this comment

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

(like [0,120] and [120,300])

おそらくこのテストケースが該当するが、期待値validとしてrspecはパスしているため問題なく動作している認識です。

context '新規下限 = 既存上限' do
let(:new_charge) { build(:usage_charge, plan:, usage_lower: 200, usage_upper: 300) }
it_behaves_like 'valid'
end

@shigenius
Copy link
Author

@sugita-seiya @ksugiura-ene

お世話になっております。
コメントいただいた箇所につきまして、コード修正ならびにコメントの返答をいたしましたのでご確認のほどよろしくお願いいたします。

なお、今回の修正でDB定義とseedの変更がありますので、動作確認の際には以下のコマンドでDBを更新していただけますと幸いです。

docker compose run --rm web rails db:reset

(更新後のER図

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.

3 participants