33module Api
44 module V3
55 class ScenariosController < BaseController
6- rescue_from Scenario ::YearInterpolator ::InterpolationError do |ex |
7- render json : { errors : [ ex . message ] } , status : :bad_request
8- end
9-
106 load_resource except : %i[ show create destroy dump ]
117 load_and_authorize_resource class : Scenario , only : %i[ index show destroy dump ]
128
@@ -20,12 +16,21 @@ class ScenariosController < BaseController
2016 authorize! ( :update , Scenario )
2117 end
2218
19+ before_action only : %i[ interpolate_collection ] do
20+ # Authorize create here because we load the resources explicity in the action
21+ authorize! ( :create , Scenario )
22+
23+ load_batch_scenarios
24+ end
25+
2326 before_action only : %i[ dashboard merit ] do
2427 authorize! ( :read , @scenario )
2528 end
2629
2730 before_action only : %i[ interpolate ] do
2831 authorize! ( :clone , @scenario )
32+
33+ load_start_scenario
2934 end
3035
3136 before_action only : %i[ couple uncouple ] do
@@ -207,22 +212,57 @@ def create
207212 render json : { errors : @scenario . errors . to_hash } , status : :unprocessable_content
208213 end
209214
210- # POST /api/v3/scenarios/interpolate
215+ # POST /api/v3/scenarios/:id/ interpolate
211216 def interpolate
212- @interpolated = Scenario ::YearInterpolator . call (
213- @scenario , params . require ( :end_year ) . to_i , current_user
217+ result = Scenario ::YearInterpolator . call (
218+ scenario : @scenario ,
219+ year : interpolate_params . require ( :end_year ) . to_i ,
220+ start_scenario : @start_scenario ,
221+ user : current_user
214222 )
215223
216- Scenario . transaction do
217- @interpolated . save!
218- end
219-
220- render json : ScenarioSerializer . new ( self , @interpolated )
224+ result . either (
225+ lambda { |scenario |
226+ Scenario . transaction { scenario . save! }
227+ render json : ScenarioSerializer . new ( self , scenario )
228+ } ,
229+ lambda { |errors |
230+ render json : { errors : errors . values . flatten } , status : :unprocessable_content
231+ }
232+ )
221233 rescue ActionController ::ParameterMissing
222- render (
223- status : :bad_request ,
224- json : { errors : [ 'Interpolated scenario must have an end year' ] }
234+ render json : { errors : [ 'Interpolated scenario must have an end year' ] } ,
235+ status : :bad_request
236+ end
237+
238+ # POST /api/v3/scenarios/interpolate
239+ #
240+ # Creates interpolated scenarios for each target end year between the given scenarios.
241+ # For example: Given a list of scenario_ids for scenarios with end_years [2030, 2040, 2050]
242+ # and given the target end_years [2025, 2035, 2045], this endpoint creates:
243+ #
244+ # - A 2025 scenario interpolated between the 2030 scenario's start_year and end_year
245+ # - A 2035 scenario interpolated between the 2030 and 2040 scenarios
246+ # - A 2045 scenario interpolated between the 2040 and 2050 scenarios
247+ #
248+ def interpolate_collection
249+ result = Scenario ::BatchYearInterpolator . call (
250+ scenarios : @scenarios ,
251+ end_years : params . require ( :end_years ) . map ( &:to_i ) ,
252+ user : current_user
225253 )
254+
255+ result . either (
256+ lambda { |scenarios |
257+ Scenario . transaction { scenarios . each ( &:save! ) }
258+ render json : scenarios . map { |s | ScenarioSerializer . new ( self , s ) }
259+ } ,
260+ lambda { |errors |
261+ render json : { errors : } , status : :unprocessable_content
262+ }
263+ )
264+ rescue ActionController ::ParameterMissing => e
265+ render json : { errors : [ e . message ] } , status : :bad_request
226266 end
227267
228268 # PUT-PATCH /api/v3/scenarios/:id
@@ -408,18 +448,6 @@ def export
408448
409449 private
410450
411- def find_preset_or_scenario
412- @scenario =
413- Preset . get ( params [ :id ] ) . try ( :to_scenario ) ||
414- Scenario . find_for_calculation ( params [ :id ] )
415-
416- render_not_found ( errors : [ 'Scenario not found' ] ) unless @scenario
417- end
418-
419- def find_scenario
420- @scenario = Scenario . find_for_calculation ( params [ :id ] )
421- end
422-
423451 # Internal: All the request parameters, filtered.
424452 #
425453 # Returns a ActionController::Parameters
@@ -516,6 +544,34 @@ def include_curves_in_merit?
516544 merit_parameters [ :include_curves ] != 'false'
517545 end
518546
547+ def interpolate_params
548+ params . permit ( :end_year , :start_scenario_id )
549+ end
550+
551+ # Internal: Load batch resources, render not found when not found or
552+ # inaccessible
553+ def load_batch_scenarios
554+ scenario_ids = params . require ( :scenario_ids )
555+ @scenarios = Scenario . accessible_by ( current_ability ) . where ( id : scenario_ids )
556+
557+ return unless @scenarios . length != scenario_ids . length
558+
559+ render json : { errors : [
560+ "scenarios not found: #{ scenario_ids - @scenarios . map ( &:id ) } "
561+ ] } , status : :not_found
562+ end
563+
564+ # Internal: load start scenario needed for interpolation
565+ def load_start_scenario
566+ if ( start_scenario_id = interpolate_params [ :start_scenario_id ] )
567+ @start_scenario = Scenario . find ( start_scenario_id )
568+
569+ authorize! ( :read , @start_scenario )
570+ else
571+ @start_scenario = nil
572+ end
573+ end
574+
519575 def force_uncouple
520576 result = nil
521577 serializer = nil
0 commit comments