Skip to main content

Calculation code comments Calculation controller

On GitHub: app/controllers/calculator_controller.rb

2 class CalculatorController < ApplicationController

Include code from each of the modules for the different styles of calculation.

5   include Calculations::BicameralBothHousesSitting
6   include Calculations::BicameralSiEitherHouseSitting
7   include Calculations::CommonsOnlySi
8   include Calculations::CommonsOnlySittingDays
9   include Calculations::Pnsi
10   include Calculations::Treaty
11   include Calculations::BicameralBothHousesSittingReverse
12   include Calculations::BicameralSiEitherHouseSittingReverse
13   include Calculations::CommonsOnlySiReverse
14   include Calculations::CommonsOnlySittingDaysReverse
15   include Calculations::PnsiReverse
16   include Calculations::TreatyReverse

This is the code to provide a list of calculators.

19   def index

We set the meta information for the page.

22     @page_title = "Calculators"
23     @description = "Calculators made available by #{$SITE_TITLE}."
24     @crumb << { label: 'Calculators', url: nil }
25     @section = 'calculators'
26   end

This is the code to provide information for the form that users can fill in to calculate the end date of a scrutiny period by procedure.

29   def scrutiny_period

We find all the active procedures in display order - to populate the procedure radio buttons on the form.

32     @procedures = Procedure.all.where( 'active is true' ).order( 'display_order asc' )

We set the meta information for the page.

35     @page_title = "Scrutiny end date calculator"
36     @multiline_page_title = "Calculators <span class='subhead'>Scrutiny end date</span>".html_safe
37     @description = "A calculator to determine the estimated end date of scrutiny for instruments before Parliament."
38     @crumb << { label: 'Calculators', url: calculator_list_url }
39     @crumb << { label: 'Scrutiny end date', url: nil }
40     @section = 'calculators'
41     @subsection = 'scrutiny-calculator'
42   end

This is the code to provide information for the form that users can fill in to calculate the start date of a scrutiny period by procedure.

45   def reverse_scrutiny_period

We find all the active procedures in display order - to populate the procedure radio buttons on the form.

48     @procedures = Procedure.all.where( 'active is true' ).order( 'display_order asc' )

We set the meta information for the page.

51     @page_title = "Scrutiny start date calculator"
52     @multiline_page_title = "Calculators <span class='subhead'>Scrutiny start date</span>".html_safe
53     @description = "A calculator to determine the estimated start date of scrutiny for instruments before Parliament."
54     @crumb << { label: 'Calculators', url: calculator_list_url }
55     @crumb << { label: 'Scrutiny start date', url: nil }
56     @section = 'calculators'
57     @subsection = 'scrutiny-calculator-reverse'
58   end

This is the code to provide information for the form that users wishing to run a specific calculation style can fill in.

61   def style

We get the calculation style if it's been passed as a parameter.

64     calculation_style = params['calculation-style']
65     @calculation_style = calculation_style.to_i if calculation_style

We set the meta information for the page.

68     @page_title = "Scrutiny end date calculator for a calculation style"
69     @multiline_page_title = "Calculators <span class='subhead'>Scrutiny end date for a calculation style</span>".html_safe
70     @description = "A calculator to determine the estimated end date of scrutiny for instruments before Parliament for a given calculation style."
71     @crumb << { label: 'About', url: meta_list_url }
72     @crumb << { label: 'Librarian tools', url: meta_librarian_tools_url }
73     @crumb << { label: 'Scrutiny period calculator by calculation style', url: nil }
74   end

This code runs the scrutiny period calculation.

77   def calculate

In order to calculate the anticipated end date of the scrutiny period for a forward calculation or the start date of the scrutiny period for a reverse calculation, we need:

  • the start date, for example: "2020-05-06"

Note that this is the start date of the calculation. For a reverse calculation, this will be the end date of the scrutiny period.

83     start_date = params['start-date']
  • the day count and
86     day_count = params['day-count']
  • either the type of the procedure, which we refer to by a number
89     procedure = params['procedure']
  • or the calculation style, which we also refer to by a number
92     calculation_style = params['calculation-style']

Optionally, we may have been passed a direction parameter.

This is populated with 'reverse' if we intend the calculation to return an anticipated start date given an end date.

96     direction = params['direction']
97     @direction = direction

Calling this method also sets instance variables for start date, day count, procedure and calculation style.

If we don't have enough information to proceed with the calculation ...

101     unless calculation_can_proceed?( start_date, day_count, procedure, calculation_style )

... we call the insufficient information method.

104       insufficient_information

Otherwise, if we do have enough information to proceed with the calculation ...

107     else

... if the day count has not been provided or the day count is invalid ...

110       if !@day_count or is_day_count_invalid?( @day_count )

We set generic meta information for both forward and reverse calculations.

113         @crumb << { label: 'Calculators', url: calculator_list_url }
114         @section = 'calculators'

If the direction of the calculation is reverse ...

117         if @direction == 'reverse'

... we set the meta information for the page ...

120           @page_title = "Scrutiny start date - number of days to count required"
121           @multiline_page_title = "Calculators <span class='subhead'>Scrutiny start date - number of days to count required</span>".html_safe
122           @description = "Number of days to count required for a calculation to determine the estimated start date of scrutiny for instruments before Parliament."
123           @crumb << { label: 'Scrutiny start date', url: reverse_calculator_form_url }
124           @subsection = 'scrutiny-calculator-reverse'

Otherwise, if the direction of calculation is not reverse ...

127         else

... we set the meta information for the page ...

130           @page_title = "Scrutiny end date - number of days to count required"
131           @multiline_page_title = "Calculators <span class='subhead'>Scrutiny end date - number of days to count required</span>".html_safe
132           @description = "Number of days to count required for a calculation to determine the estimated end date of scrutiny for instruments before Parliament."
133           @crumb << { label: 'Scrutiny end date', url: calculator_form_url }
134           @crumb << { label: 'Number of days to count required', url: nil }
135           @subsection = 'scrutiny-calculator'
136         end
137         @crumb << { label: 'Number of days to count required', url: nil }

... and we render the day count form.

140         render :template => 'calculator/day_count_form'

Otherwise, if the day count has been provided and the day count is not invalid ...

143       else

If the procedure has been passed as a parameter ...

146         if @procedure

... to calculate the anticipated end date, we select the calculation based on the type of procedure:

149           case @procedure.id
  • Legislative Reform Orders, Public Body Orders, Localism Orders and enhanced affirmatives under the Investigatory Powers Act 2016
152             when 1, 17, 18, 19, 2, 4, 21, 22, 23
154               if @direction == 'reverse'
155                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting_reverse( @start_date, @day_count )
156                 @message = "In order to meet the target end date, the instrument must be <em>laid on or before</em> the anticipated start date of the scrutiny period."
157               else
158                 @start_date_type = "laying date"
159                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting( @start_date, @day_count )
160               end
  • Proposed Statutory Instruments (PNSIs)
163             when 3
165               if @direction == 'reverse'
166                 @scrutiny_end_date = pnsi_calculation_reverse( @start_date, @day_count )
167                 @message = "In order to meet the target end date, the instrument must be <em>laid earlier than</em> the anticipated start date of the scrutiny period."
168               else
169                 @scrutiny_end_date = pnsi_calculation( @start_date, @day_count )
170                 @start_date_type = "laying date"
171               end
  • Commons only negative Statutory Instruments
174             when 5
176               if @direction == 'reverse'
177                 @scrutiny_end_date = commons_only_si_calculation_reverse( @start_date, @day_count )
178                 @message = "In order to meet the target end date, the instrument must be <em>laid on or before</em> the anticipated start date of the scrutiny period."
179               else
180                 @scrutiny_end_date = commons_only_si_calculation( @start_date, @day_count )
181                 @start_date_type = "laying date"
182               end
  • Commons and Lords negative Statutory Instruments, proposed and draft affirmative remedial orders
185             when 6, 13, 14
187               if @direction == 'reverse'
188                 @scrutiny_end_date = bicameral_si_either_house_sitting_calculation_reverse( @start_date, @day_count )
189                 @message = "In order to meet the target end date, the instrument must be <em>laid on or before</em> the anticipated start date of the scrutiny period."
190               else
191                 @start_date_type = "laying date"
192                 @scrutiny_end_date = bicameral_si_either_house_sitting_calculation( @start_date, @day_count )
193               end
  • Some Commons only made affirmative Statutory Instruments
196             when 7
198               if @direction == 'reverse'
199                 @scrutiny_end_date = commons_only_si_calculation_reverse( @start_date, @day_count )
200                 @message = "In order to meet the target end date, the instrument must be <em>made on or before</em> the anticipated start date of the scrutiny period."
201               else
202                 @scrutiny_end_date = commons_only_si_calculation( @start_date, @day_count )
203                 @start_date_type = "making date"
204               end
  • Commons and Lords made affirmative Statutory Instruments where both Houses are sitting
207             when 8
209               if @direction == 'reverse'
210                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting_reverse( @start_date, @day_count )
211                 @message = "In order to meet the target end date, the instrument must be <em>made on or before</em> the anticipated start date of the scrutiny period."
212               else
213                 @start_date_type = "making date"
214                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting( @start_date, @day_count )
215               end
  • Commons and Lords made affirmative Statutory Instruments where either House is sitting and made affirmative remedial orders
218             when 9, 15, 16
220               if @direction == 'reverse'
221                 @scrutiny_end_date = bicameral_si_either_house_sitting_calculation_reverse( @start_date, @day_count )
222                 @message = "In order to meet the target end date, the instrument must be <em>made on or before</em> the anticipated start date of the scrutiny period."
223               else
224                 @start_date_type = "making date"
225                 @scrutiny_end_date = bicameral_si_either_house_sitting_calculation( @start_date, @day_count )
226               end
  • Treaty period A
229             when 10
231               if @direction == 'reverse'
232                 @scrutiny_end_date = treaty_calculation_reverse( @start_date, @day_count )
233                 @message = "In order to meet the target end date, the treaty must be <em>laid earlier than</em> the anticipated start date of the scrutiny period."
234               else
235                 @scrutiny_end_date = treaty_calculation( @start_date, @day_count )
236                 @start_date_type = "laying date"
237               end
  • Treaty period B
240             when 11
242               if @direction == 'reverse'
243                 @scrutiny_end_date = treaty_calculation_reverse( @start_date, @day_count )
244                 @message = "In order to meet the target end date, the ministerial statement must be <em>made earlier than</em> the anticipated start date of the scrutiny period."
245               else
246                 @scrutiny_end_date = treaty_calculation( @start_date, @day_count )
247                 @start_date_type = "date of Ministerial statement"
248               end
  • Published drafts under the European Union (Withdrawal) Act 2018
251             when 12
253               if @direction == 'reverse'
254                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting_reverse( @start_date, @day_count )
255                 @message = "In order to meet the target end date, the draft must be <em>published on or before</em> the anticipated start date of the scrutiny period."
256               else
257                 @start_date_type = "date of publication"
258                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting( @start_date, @day_count )
259               end
  • National Policy Statements.
262             when 20
264               if @direction == 'reverse'
265                 @scrutiny_end_date = commons_only_sitting_days_reverse( @start_date, @day_count )
266                 @message = "In order to meet the target end date, the national policy statement must be <em>laid earlier than</em> the anticipated start date of the scrutiny period."
267              else
268                 @scrutiny_end_date = commons_only_sitting_days( @start_date, @day_count )
269                 @start_date_type = "laying date"
270               end
271           end

Otherwise, if the calculation style has been selected ...

274         elsif @calculation_style

... to calculate the anticipated end date, we select the calculation based on the calculation style:

277           case @calculation_style
  • Calculation style 1
280             when 1
282               @scrutiny_end_date = bicameral_calculation_both_houses_sitting( @start_date, @day_count )
  • Calculation style 2
285             when 2
287               @scrutiny_end_date = bicameral_si_either_house_sitting_calculation( @start_date, @day_count )
  • Calculation style 3
290             when 3
292               @scrutiny_end_date = commons_only_si_calculation( @start_date, @day_count )
  • Calculation style 4
295             when 4
297               @scrutiny_end_date = pnsi_calculation( @start_date, @day_count )
  • Calculation style 5
300             when 5
302               @scrutiny_end_date = treaty_calculation( @start_date, @day_count )
  • Calculation style 6
305             when 6
307               @scrutiny_end_date = commons_only_sitting_days( @start_date, @day_count )
308             else

... we add a reason to the missing information array ...

311               @missing_information << 'a valid calculation style'

... and call the insufficient information method.

314               insufficient_information
315           end
316         end

We set the generic meta information.

318         @json_url = request.original_fullpath.sub '?', '.json?'
319         @crumb << { label: 'Calculators', url: calculator_list_url }
320         @section = 'calculators'

If the direction of the calculation is reverse ...

323         if @direction == 'reverse'

... we set the meta information for the page.

326           @page_title = "Scrutiny start date calculation"
327           @multiline_page_title = "Calculators <span class='subhead'>Scrutiny start date calculation</span>".html_safe
328           @description = "A calculation to determine the estimated start date of scrutiny for instruments before Parliament."
329           @calendar_links << ['Anticipated start date of the scrutiny period', request.original_fullpath.sub( '?', '.ics?' )]
330           @crumb << { label: 'Scrutiny start date', url: reverse_calculator_form_url }
331           @crumb << { label: 'Calculation', url: nil }
332           @subsection = 'scrutiny-calculator-reverse'
334           render :template => 'calculator/scrutiny_reverse'

Otherwise, if the direction of the calculation is not reverse ...

337         else

.. we set the meta information for the page.

340           @page_title = "Scrutiny end date calculation"
341           @multiline_page_title = "Calculators <span class='subhead'>Scrutiny end date calculation</span>".html_safe
342           @description = "A calculation to determine the anticipated end date of scrutiny for instruments before Parliament."
343           @json_url = request.original_fullpath.sub '?', '.json?'
344           @calendar_links << ['Anticipated end date of the scrutiny period', request.original_fullpath.sub( '?', '.ics?' )]
345           @crumb << { label: 'Scrutiny end date', url: calculator_form_url }
346           @crumb << { label: 'Calculation', url: nil }
347           @subsection = 'scrutiny-calculator'
348         end
349       end
350     end
351   end

A method to check if the scrutiny period calculation can proceed.

This method also creates the start date, day count, procedure and calculation style as instance variables.

355   def calculation_can_proceed?( start_date, day_count, procedure, calculation_style )

We create a variable to hold a boolean, determining if the scrutiny period calculation can proceed.

358     calculation_can_proceed = true

We create an array to hold any errors we find as a result of missing information.

361     @missing_information = []

We check for the presence of a valid start date.

If the start date is present ...

365     if start_date

... we attempt to convert the start date string into a date, storing the result as a instance variable.

368       begin
369          @start_date = Date.parse( start_date )

If the start date string cannot be converted into a date ...

372       rescue ArgumentError

... we flag that the calculation cannot proceed ...

375         calculation_can_proceed = false

... and add a reason to the missing information array.

378         @missing_information << invalid_date_type_label
379       end

Otherwise, if the start date is not present ...

382     else

... we flag that the calculation cannot proceed ...

385       calculation_can_proceed = false

... and add a reason to the missing information array.

388       @missing_information << missing_date_type_label
389     end

We check for the presence of a valid day count.

If a day count has been passed ...

393     if day_count

... we convert the day count to an integer, storing the result as a instance variable.

396       @day_count = day_count.to_i

If the day count is an invalid day count ...

399       if is_day_count_invalid?( @day_count )

... we do not flag that the calculation cannot proceed because this will be picked up by the day count form.

We add a reason to the missing information array.

404         @missing_information << 'a valid day count'
405       end

Otherwise, if a day count has not been passed ...

408     else

... we do not flag that the calculation cannot proceed because this will be picked up by the day count form.

We add a reason to the missing information array.

413       @missing_information << 'a day count'
414     end

We check for the presence of a valid procedure or a valid calculation style.

If the calculation has been passed neither a procedure, nor a calculation style ...

418     if !procedure and !calculation_style

... we flag that the calculation cannot proceed ...

421       calculation_can_proceed = false

... and add a reason to the missing information array.

424       @missing_information << 'a procedure or calculation style'

Otherwise, if the calculation has been passed a procedure ...

427     elsif procedure

... we attempt to find the procedure, storing the result as a instance variable.

430       @procedure = Procedure.find_by_id( procedure )

If we fail to find the procedure ...

433       unless @procedure

... we flag that the calculation cannot proceed ...

436         calculation_can_proceed = false

... and add a reason to the missing information array.

439         @missing_information << 'a valid procedure'
440       end

Otherwise, if the calculation has been passed a calculation style ...

443     elsif calculation_style

... we convert the calculation style ID to an integer, storing the result as a instance variable.

446       @calculation_style = calculation_style.to_i

If the calculation style is an invalid calculation style ...

449       if is_calculation_style_invalid?( @calculation_style )

... we flag that the calculation cannot proceed ...

452         calculation_can_proceed = false

... and add a reason to the missing information array.

455         @missing_information << 'a valid calculation style'
456       end
457     end

We return the calculation can proceed boolean.

460     calculation_can_proceed
461   end

A method to check if the day count is valid.

464   def is_day_count_valid?( day_count )

We create a boolean to hold the validity of the day count.

467     is_day_count_valid = true

If the day count is zero or a negative number ...

470     if day_count == 0 or day_count.negative?

... we set the is day count valid boolean to false.

473       is_day_count_valid = false
474     end

We return the is day count valid boolean.

477     is_day_count_valid
478   end

A method to check if the day count is invalid.

481   def is_day_count_invalid?( day_count )

We flip the boolean returned by the is day count valid method.

484     !is_day_count_valid?( day_count )
485   end

A method to check if the calculation style is valid.

488   def is_calculation_style_valid?( calculation_style )

We create a boolean to hold the validity of the calculation style.

491     is_calculation_style_valid = true

If the calculation style is zero or a negative number ...

494     if calculation_style == 0 or calculation_style.negative?

... we set the is calculation style valid boolean to false.

497       is_calculation_style_valid = false
498     end

We return the is calculation style valid boolean.

501     is_calculation_style_valid
502   end

A method to check if the calculation style is invalid.

505   def is_calculation_style_invalid?( calculation_style )

We flip the boolean returned by the is calculation style valid method.

508     !is_calculation_style_valid?( calculation_style )
509   end

A method to report that we have insufficient information for the scrutiny period calculation to proceed.

512   def insufficient_information

We set the generic meta information.

515     @crumb << { label: 'Calculators', url: calculator_list_url }
516     @section = 'calculators'

If the calculation is a reverse calculation ...

519     if @direction == 'reverse'

... we set the meta information for the page ...

522       @page_title = "Scrutiny start date - more information required"
523       @multiline_page_title = "Calculators <span class='subhead'>Scrutiny start date - more information required</span>".html_safe
524       @description = "More information required for a calculation to determine the estimated start date of scrutiny for instruments before Parliament."
525       @crumb << { label: 'Scrutiny start date', url: reverse_calculator_form_url }
526       @subsection = 'scrutiny-calculator-reverse'

Otherwise, if the calculation is not a reverse calculation ...

529     else

... we set the meta information for the page ...

532       @page_title = "Scrutiny end date - more information required"
533       @multiline_page_title = "Calculators <span class='subhead'>Scrutiny end date - more information required</span>".html_safe
534       @description = "More information required for a calculation to determine the estimated end date of scrutiny for instruments before Parliament."
535       @crumb << { label: 'Scrutiny end date', url: calculator_form_url }
536       @subsection = 'scrutiny-calculator'
537     end

We set more generic meta information.

540     @crumb << { label: 'More information required', url: nil }

We display the not enough information message.

543     render :template => 'calculator/not_enough_information'
544   end

A method to return the label of any missing date type.

For the end date scrutiny period calculator, this will be the start date.

For the start date scrutiny period calculator, this will be the end date.

549   def missing_date_type_label

If the calculation is a reverse calculation, calculating a start date ...

552     if @direction == 'reverse'

... the missing date parameter is an end date.

555       'an end date'

Otherwise, if the calculation is a forward calculation, calculating an end date ...

558     else

... the missing date parameter is a start date.

561       'a start date'
562     end
563   end

A method to return the label of any invalid date type.

For the end date scrutiny period calculator, this will be the start date.

For the start date scrutiny period calculator, this will be the end date.

568   def invalid_date_type_label

If the calculation is a reverse calculation, calculating a start date ...

571     if @direction == 'reverse'

... the missing date parameter is an end date.

574       'a valid end date'

Otherwise, if the calculation is a forward calculation, calculating an end date ...

577     else

... the missing date parameter is a start date.

580       'a valid start date'
581     end
582   end
583 end