-
Notifications
You must be signed in to change notification settings - Fork 46
【チャレンジ課題】電気料金の情報を返すAPIの作成 #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
785e88e
ecf2095
c8ea62b
05da7c3
c13d6d6
aa9ee61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,73 @@ | ||
| # README | ||
| # 電気料金出力API | ||
| ## 概要 | ||
| 与えられたリクエスト値を元に、一致する電力会社/プランの料金を返却するAPIです。 | ||
|
|
||
| This README would normally document whatever steps are necessary to get the | ||
| application up and running. | ||
| ## API仕様 | ||
| ### エンドポイント | ||
| ``` | ||
| GET electricity_charges/calculate | ||
| ``` | ||
|
|
||
| Things you may want to cover: | ||
| ### リクエスト値 | ||
|
|
||
| * Ruby version | ||
| | パラメータ名 | 説明 | データ型 | パラメータ制限 | 備考 | | ||
| |--------|---------|------|----------------------------|---------| | ||
| | ampere | 契約アンペア数 | int | 10/15/20/30/40/50/60 のいずれか | 単位: A | | ||
| | usage | 電力使用量 | int | 0以上の整数 | 単位: kWh | | ||
|
|
||
| * System dependencies | ||
| ### レスポンス | ||
| | パラメータ名 | 説明 | データ型 | 備考 | | ||
| |---------------|-------|--------|----| | ||
| | provider_name | 電力会社名 | string | | | ||
| | plan_name | プラン名 | string | | | ||
| | price | 値段 | float | | | ||
|
|
||
| * Configuration | ||
| ### レスポンスのサンプル | ||
| #### 一致するプランがある場合 | ||
|
|
||
| * Database creation | ||
| ```json lines | ||
| // GET http://localhost:3000/electricity_charges/calculate?ampere=30&usage=10 | ||
|
|
||
| * Database initialization | ||
| [ | ||
| { | ||
| "provider_name": "東京電力エナジーパートナー", | ||
| "plan_name": "従量電灯B", | ||
| "price": 1056.8 | ||
| }, | ||
| { | ||
| "provider_name": "東京電力エナジーパートナー", | ||
| "plan_name": "スタンダードS", | ||
| "price": 1233.25 | ||
| }, | ||
| { | ||
| "provider_name": "東京ガス", | ||
| "plan_name": "ずっとも電気1", | ||
| "price": 1094.7 | ||
| }, | ||
| { | ||
| "provider_name": "Looopでんき", | ||
| "plan_name": "おうちプラン", | ||
| "price": 288.0 | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| * How to run the test suite | ||
| #### 一致するプランがない場合 | ||
|
|
||
| * Services (job queues, cache servers, search engines, etc.) | ||
| ```json | ||
| [ | ||
| { | ||
| "message": "ampereに一致するプランが登録されていません" | ||
| } | ||
| ] | ||
| ``` | ||
|
|
||
| * Deployment instructions | ||
|
|
||
| * ... | ||
| #### パラメータが不正な場合 | ||
|
|
||
| ```json | ||
|
|
||
| { | ||
| "error": "ampereの値が正しくありません" | ||
| } | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| class ElectricityCharges::CalculateController < ApplicationController | ||
| include ElectricChargesHelper | ||
| def calc | ||
| error, status = validate_params(params) | ||
| if error | ||
| return render json: error, status: status | ||
| end | ||
|
|
||
| calc_result, calc_status = import_price_from_yaml(params[:ampere].to_i, params[:usage].to_i) | ||
|
|
||
| render json: calc_result, status: calc_status | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| module ElectricChargesHelper | ||
| VALID_CONTRACT_AMPERES = %w(10 15 20 30 40 50 60).freeze | ||
| VALID_USAGE_REGEX = /\A\d+\z/.freeze | ||
|
|
||
| # リクエストパラメータのバリデーション | ||
| def validate_params(request_params) | ||
| # パラメータが不足しているとき | ||
| if request_params[:ampere].nil? || request_params[:usage].nil? | ||
| return { error: I18n.t('errors.empty_parameter') }, :bad_request | ||
| end | ||
|
|
||
| # ampereは特定値である必要がある | ||
| unless VALID_CONTRACT_AMPERES.include?(request_params[:ampere]) | ||
| return { error: I18n.t('errors.invalid_contract_ampere') }, :bad_request | ||
| end | ||
|
|
||
| # usageは0以上の整数である必要がある | ||
| usage = request_params[:usage].to_i | ||
| unless VALID_USAGE_REGEX.match?(request_params[:usage]) | ||
| return { error: I18n.t('errors.invalid_usage') }, :bad_request | ||
| end | ||
| if usage < 0 | ||
| return { error: I18n.t('errors.invalid_usage') }, :bad_request | ||
| end | ||
|
|
||
| nil | ||
| end | ||
|
|
||
| # yamlからパラメータ読み込み | ||
| def import_price_from_yaml(ampere, usage) | ||
| results = [] | ||
| electricity_charges_data = YAML.load_file("config/electricity_charges.yml") | ||
| # 基本料金 | ||
| electricity_charges_data["basic_price"].each do |provider| | ||
| provider["plans"].each do |plan| | ||
| if plan["amperes"].has_key?(ampere) | ||
| results << { | ||
| "provider_name": provider["provider_name"], | ||
| "plan_name": plan["plan"], | ||
| "price": plan["amperes"][ampere] | ||
| } | ||
| end | ||
| end | ||
| end | ||
|
|
||
| # ampereのパラメータに紐づくデータがない場合はその旨を返す | ||
| if results.empty? | ||
| results << { message: I18n.t('message.empty_result_by_ampere') } | ||
| return results, 404 | ||
| end | ||
|
|
||
| # 従量料金 | ||
| results.each do |result| | ||
| provider_usage_price_data = electricity_charges_data["usage_price"].select{|provider| provider["provider_name"] == result[:provider_name]} | ||
| provider_usage_price_data.each do |data| | ||
| plans = data["plans"].select {|plan| plan["plan"] == result[:plan_name]} | ||
| plans.each do |plan| | ||
| thresholds = plan["thresholds"] | ||
| keys = thresholds.keys.map(&:to_i).sort.reverse | ||
| keys.each do |key| | ||
| if usage >= key | ||
| unit_price_kwh = thresholds[key] | ||
| usage_price = (unit_price_kwh * usage).round(2) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1000kwhの使用量でAPI実行した場合に、東京電力・従量電灯Bの場合に、電力量料金が 1000kwh * 30.57 になっており、段階的に「使用量 * 単価」の計算がされていないようです。 https://www.tepco.co.jp/ep/private/plan2/chargelist04.html#sec03 |
||
| result[:price] += usage_price | ||
| break | ||
| end | ||
| end | ||
| end | ||
| end | ||
| end | ||
|
|
||
| [results, 200] | ||
| end | ||
| end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| basic_price: | ||
| - provider_name: 東京電力エナジーパートナー | ||
| plans: | ||
| - plan: 従量電灯B | ||
| amperes: | ||
| 10: 286.00 | ||
| 15: 429.00 | ||
| 20: 572.00 | ||
| 30: 858.00 | ||
| 40: 1144.00 | ||
| 50: 1430.00 | ||
| 60: 1716.00 | ||
| - plan: スタンダードS | ||
| amperes: | ||
| 10: 311.75 | ||
| 15: 467.63 | ||
| 20: 623.50 | ||
| 30: 935.25 | ||
| 40: 1247.00 | ||
| 50: 1558.75 | ||
| 60: 1870.50 | ||
| - provider_name: 東京ガス | ||
| plans: | ||
| - plan: ずっとも電気1 | ||
| amperes: | ||
| 30: 858.00 | ||
| 40: 1144.00 | ||
| 50: 1430.00 | ||
| 60: 1716.00 | ||
| - provider_name: Looopでんき | ||
| plans: | ||
| - plan: おうちプラン | ||
| amperes: | ||
| 10: 0.00 | ||
| 15: 0.00 | ||
| 20: 0.00 | ||
| 30: 0.00 | ||
| 40: 0.00 | ||
| 50: 0.00 | ||
| 60: 0.00 | ||
| usage_price: | ||
| - provider_name: 東京電力エナジーパートナー | ||
| plans: | ||
| - plan: 従量電灯B | ||
| thresholds: | ||
| 0: 19.88 | ||
| 121: 26.48 | ||
| 301: 30.57 | ||
| - plan: スタンダードS | ||
| thresholds: | ||
| 0: 29.80 | ||
| 121: 36.40 | ||
| 301: 40.49 | ||
| - provider_name: 東京ガス | ||
| plans: | ||
| - plan: ずっとも電気1 | ||
| thresholds: | ||
| 0: 23.67 | ||
| 141: 23.88 | ||
| 351: 26.41 | ||
| - provider_name: Looopでんき | ||
| plans: | ||
| - plan: おうちプラン | ||
| thresholds: | ||
| 0: 28.8 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,4 +30,11 @@ | |
| # available at https://guides.rubyonrails.org/i18n.html. | ||
|
|
||
| en: | ||
| hello: "Hello world" | ||
| errors: | ||
| empty_parameter: 'リクエストパラメータにampereとusageを設定してください' | ||
| invalid_parameter_type: 'ampereとusageはいずれも数値を設定してください' | ||
| invalid_contract_ampere: 'ampereの値が正しくありません' | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. どのような値を設定すれば正しいのか判別できるエラーメッセージだと親切かと思いました。 |
||
| invalid_usage: 'usageは0以上の整数を設定してください' | ||
| message: | ||
| empty_result_by_ampere: 'ampereに一致するプランが登録されていません' | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,5 @@ | ||
| Rails.application.routes.draw do | ||
| # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html | ||
|
|
||
| # Defines the root path route ("/") | ||
| # root "articles#index" | ||
| namespace :electricity_charges do | ||
| get 'calculate', to: 'calculate#calc' | ||
| end | ||
| end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| require "test_helper" | ||
|
|
||
| class ElectricityCharges::CalculateControllerTest < ActionDispatch::IntegrationTest | ||
| test '正常系: 契約アンペア数 10A, 使用量 0kWh' do | ||
| expected = [ | ||
| { provider_name: '東京電力エナジーパートナー', plan_name: '従量電灯B', price: 286.0 }, | ||
| { provider_name: '東京電力エナジーパートナー', plan_name: 'スタンダードS', price: 311.75 }, | ||
| { provider_name: 'Looopでんき', plan_name: 'おうちプラン', price: 0.0 }, | ||
| ] | ||
| get electricity_charges_calculate_url, params: { ampere: 10, usage: 0 } | ||
| assert_response :success | ||
| assert_equal(expected.to_json, response.body) | ||
| end | ||
|
|
||
| test '正常系: 契約アンペア数 30A, 使用量 100kWh' do | ||
| expected = [ | ||
| { provider_name: '東京電力エナジーパートナー', plan_name: '従量電灯B', price: 2846.0 }, | ||
| { provider_name: '東京電力エナジーパートナー', plan_name: 'スタンダードS', price: 3915.25 }, | ||
| { provider_name: '東京ガス', plan_name: 'ずっとも電気1', price: 3225.0 }, | ||
| { provider_name: 'Looopでんき', plan_name: 'おうちプラン', price: 2880.0 }, | ||
| ] | ||
| get electricity_charges_calculate_url, params: { ampere: 30, usage: 100 } | ||
| assert_response :success | ||
| assert_equal(expected.to_json, response.body) | ||
| end | ||
|
|
||
| test '正常系: 契約アンペア数 60A, 使用量 1000kWh' do | ||
| expected = [ | ||
| { provider_name: '東京電力エナジーパートナー', plan_name: '従量電灯B', price: 32286.0 }, | ||
| { provider_name: '東京電力エナジーパートナー', plan_name: 'スタンダードS', price: 42360.5 }, | ||
| { provider_name: '東京ガス', plan_name: 'ずっとも電気1', price: 28126.0 }, | ||
| { provider_name: 'Looopでんき', plan_name: 'おうちプラン', price: 28800.0 }, | ||
| ] | ||
| get electricity_charges_calculate_url, params: { ampere: 60, usage: 1000 } | ||
| assert_response :success | ||
| assert_equal(expected.to_json, response.body) | ||
| end | ||
|
|
||
| test '異常系: 契約アンペア数 0A' do | ||
| get electricity_charges_calculate_url, params: { ampere: 0, usage: 100 } | ||
| assert_response :bad_request | ||
| assert_equal({ 'error' => 'ampereの値が正しくありません' }.to_json, response.body) | ||
| end | ||
|
|
||
| test '異常系: 契約アンペア数 70A' do | ||
| get electricity_charges_calculate_url, params: { ampere: 70, usage: 100 } | ||
| assert_response :bad_request | ||
| assert_equal({ 'error' => 'ampereの値が正しくありません' }.to_json, response.body) | ||
| end | ||
|
|
||
| test '異常系: 使用量 -1kWh' do | ||
| get electricity_charges_calculate_url, params: { ampere: 30, usage: -1 } | ||
| assert_response :bad_request | ||
| assert_equal({ 'error' => 'usageは0以上の整数を設定してください' }.to_json, response.body) | ||
| end | ||
|
|
||
| test '異常系: 使用量 1.5kWh (型が不正)' do | ||
| get electricity_charges_calculate_url, params: { ampere: 30, usage: 1.5 } | ||
| assert_response :bad_request | ||
| assert_equal({ 'error' => 'usageは0以上の整数を設定してください' }.to_json, response.body) | ||
| end | ||
|
|
||
| test '異常系: 契約アンペア数 "abc" (型が不正)' do | ||
| get electricity_charges_calculate_url, params: { ampere: "abc", usage: 100 } | ||
| assert_response :bad_request | ||
| assert_equal({ 'error' => 'ampereの値が正しくありません' }.to_json, response.body) | ||
| end | ||
|
|
||
| test '異常系: 使用量 "abc" (型が不正)' do | ||
| get electricity_charges_calculate_url, params: { ampere: 30, usage: "abc" } | ||
| assert_response :bad_request | ||
| assert_equal({"error"=>"usageは0以上の整数を設定してください"}.to_json, response.body) | ||
| end | ||
|
|
||
| test '異常系: パラメータが空' do | ||
| get electricity_charges_calculate_url, params: {} | ||
| assert_response :bad_request | ||
| assert_equal({"error"=>"リクエストパラメータにampereとusageを設定してください"}.to_json, response.body) | ||
| end | ||
| end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import_price_from_yaml関数が、ymlからデータを読み込む以上の処理をしています。
関数内の処理を別関数に切り出すなどした方が保守性・可読性は上がると思いましたが、いかがでしょうか。