Skip to main content

Calculation code comments Calculation controller

On GitHub: app/controllers/calculator_controller.rb

2 class CalculatorController < ApplicationController

We include code from the modules setting out forward calculations.

5   include Calculations::Forwards::BicameralBothHousesSitting
6   include Calculations::Forwards::BicameralSiEitherHouseSitting
7   include Calculations::Forwards::CommonsOnlySi
8   include Calculations::Forwards::CommonsOnlySittingDays
9   include Calculations::Forwards::Pnsi
10   include Calculations::Forwards::Treaty

We include code from the modules setting out backward calculations.

13   include Calculations::Backwards::BicameralBothHousesSitting
14   include Calculations::Backwards::BicameralSiEitherHouseSitting
15   include Calculations::Backwards::CommonsOnlySi
16   include Calculations::Backwards::CommonsOnlySittingDays
17   include Calculations::Backwards::Pnsi
18   include Calculations::Backwards::Treaty

This is the code to provide a list of calculators.

21   def index

We set the meta information for the page.

24     @page_title = "Calculators"
25     @description = "Calculators made available by #{$SITE_TITLE}."
26     @crumb << { label: 'Calculators', url: nil }
27     @section = 'calculators'
28   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.

31   def scrutiny_period

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

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

We set the meta information for the page.

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

47   def reverse_scrutiny_period

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

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

We set the meta information for the page.

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

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

63   def style

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

66     calculation_style = params['calculation-style']
67     @calculation_style = calculation_style.to_i if calculation_style

We set the meta information for the page.

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

This code runs the scrutiny period calculation.

79   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.

85     start_date = params['start-date']
  • the day count and
88     day_count = params['day-count']
  • either the type of the procedure, which we refer to by a number
91     procedure = params['procedure']
  • or the calculation style, which we also refer to by a number
94     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.

98     direction = params['direction']
99     @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 ...

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

... we call the insufficient information method.

106       insufficient_information

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

109     else

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

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

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

115         @crumb << { label: 'Calculators', url: calculator_list_url }
116         @section = 'calculators'

If the direction of the calculation is reverse ...

119         if @direction == 'reverse'

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

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

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

129         else

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

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

... and we render the day count form.

142         render :template => 'calculator/day_count_form'

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

145       else

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

148         if @procedure

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

151           case @procedure.id
  • Legislative Reform Orders, Public Body Orders, Localism Orders and enhanced affirmatives under the Investigatory Powers Act 2016
154             when 1, 17, 18, 19, 2, 4, 21, 22, 23
156               if @direction == 'reverse'
157                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting_backwards( @start_date, @day_count )
159                 if Date.today > @scrutiny_end_date
160                   @action_required = 'the instrument <em>would have been required to have been laid on or before</em>'
161                 else
162                   @action_required = 'the instrument must be laid <em>on or before</em>'
163                 end
164               else
165                 @start_date_type = "laying date"
166                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting_forwards( @start_date, @day_count )
167               end
  • Proposed Negative Statutory Instruments (PNSIs)
170             when 3
172               if @direction == 'reverse'
173                 @scrutiny_end_date = pnsi_calculation_backwards( @start_date, @day_count )
174                 @action_required = 'the instrument must be laid <em>before</em>'
175                 if Date.today >= @scrutiny_end_date
176                   @action_required = 'the instrument <em>would have been required to have been laid before</em>'
177                 else
178                   @action_required = 'the instrument must be laid <em>before</em>'
179                 end
180               else
181                 @scrutiny_end_date = pnsi_calculation_forwards( @start_date, @day_count )
182                 @start_date_type = "laying date"
183               end
  • Commons only negative Statutory Instruments
186             when 5
188               if @direction == 'reverse'
189                 @scrutiny_end_date = commons_only_si_calculation_backwards( @start_date, @day_count )
190                 @twenty_one_day_rule_date = @scrutiny_end_date + 21.days
192                 if Date.today > @scrutiny_end_date
193                   @action_required = 'the instrument <em>would have been required to have been laid on or before</em>'
194                 else
195                   @action_required = 'the instrument must be laid <em>on or before</em>'
196                 end
197               else
198                 @scrutiny_end_date = commons_only_si_calculation_forwards( @start_date, @day_count )
199                 @start_date_type = "laying date"
200               end
  • Commons and Lords negative Statutory Instruments
203             when 6
205               if @direction == 'reverse'
206                 @scrutiny_end_date = bicameral_si_either_house_sitting_calculation_backwards( @start_date, @day_count )
207                 if Date.today > @scrutiny_end_date
208                   @action_required = 'the instrument <em>would have been required to have been laid on or before</em>'
209                 else
210                   @action_required = 'the instrument must be laid <em>on or before</em>'
211                 end
212                 @twenty_one_day_rule_date = @scrutiny_end_date + 21.days
213               else
214                 @start_date_type = "laying date"
215                 @scrutiny_end_date = bicameral_si_either_house_sitting_calculation_forwards( @start_date, @day_count )
216               end
  • Proposed and draft affirmative remedial orders
219             when 13, 14
221               if @direction == 'reverse'
222                 @scrutiny_end_date = bicameral_si_either_house_sitting_calculation_backwards( @start_date, @day_count )
223                 if Date.today > @scrutiny_end_date
224                   @action_required = 'the instrument <em>would have been required to have been laid on or before</em>'
225                 else
226                   @action_required = 'the instrument must be laid <em>on or before</em>'
227                 end
228               else
229                 @start_date_type = "laying date"
230                 @scrutiny_end_date = bicameral_si_either_house_sitting_calculation_forwards( @start_date, @day_count )
231               end
  • Some Commons only made affirmative Statutory Instruments
234             when 7
236               if @direction == 'reverse'
237                 @scrutiny_end_date = commons_only_si_calculation_backwards( @start_date, @day_count )
238                 if Date.today > @scrutiny_end_date
239                   @action_required = 'the instrument <em>would have been required to have been made on or before</em>'
240                 else
241                   @action_required = 'the instrument must be made <em>on or before</em>'
242                 end
243               else
244                 @scrutiny_end_date = commons_only_si_calculation_forwards( @start_date, @day_count )
245                 @start_date_type = "making date"
246               end
  • Commons and Lords made affirmative Statutory Instruments where both Houses are sitting
249             when 8
251               if @direction == 'reverse'
252                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting_backwards( @start_date, @day_count )
253                 if Date.today > @scrutiny_end_date
254                   @action_required = 'the instrument <em>would have been required to have been made on or before</em>'
255                 else
256                   @action_required = 'the instrument must be made <em>on or before</em>'
257                 end
258               else
259                 @start_date_type = "making date"
260                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting_forwards( @start_date, @day_count )
261               end
  • Commons and Lords made affirmative Statutory Instruments where either House is sitting and made affirmative remedial orders
264             when 9, 15, 16
266               if @direction == 'reverse'
267                 @scrutiny_end_date = bicameral_si_either_house_sitting_calculation_backwards( @start_date, @day_count )
268                 if Date.today > @scrutiny_end_date
269                   @action_required = 'the instrument <em>would have been required to have been made on or before</em>'
270                 else
271                   @action_required = 'the instrument must be made <em>on or before</em>'
272                 end
273               else
274                 @start_date_type = "making date"
275                 @scrutiny_end_date = bicameral_si_either_house_sitting_calculation_forwards( @start_date, @day_count )
276               end
  • Treaty period A
279             when 10
281               if @direction == 'reverse'
282                 @scrutiny_end_date = treaty_calculation_backwards( @start_date, @day_count )
283                 if Date.today >= @scrutiny_end_date
284                   @action_required = 'the treaty <em>would have been required to have been laid before</em>'
285                 else
286                   @action_required = 'the treaty must be laid <em>before</em>'
287                 end
288               else
289                 @scrutiny_end_date = treaty_calculation_forwards( @start_date, @day_count )
290                 @start_date_type = "laying date"
291               end
  • Treaty period B
294             when 11
296               if @direction == 'reverse'
297                 @scrutiny_end_date = treaty_calculation_backwards( @start_date, @day_count )
298                 if Date.today >= @scrutiny_end_date
299                   @action_required = 'the Minister <em>would have been required to have made a statement before</em>'
300                 else
301                   @action_required = 'the Minister must make a statement <em>before</em>'
302                 end
303               else
304                 @scrutiny_end_date = treaty_calculation_forwards( @start_date, @day_count )
305                 @start_date_type = "date of Ministerial statement"
306               end
  • Published drafts under the European Union (Withdrawal) Act 2018
309             when 12
311               if @direction == 'reverse'
312                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting_backwards( @start_date, @day_count )
313                 if Date.today > @scrutiny_end_date
314                   @action_required = 'the draft <em>would have been required to have been published on or before</em>'
315                 else
316                   @action_required = 'the draft must be published <em>on or before</em>'
317                 end
318               else
319                 @start_date_type = "date of publication"
320                 @scrutiny_end_date = bicameral_calculation_both_houses_sitting_forwards( @start_date, @day_count )
321               end
  • National Policy Statements.
324             when 20
326               if @direction == 'reverse'
327                 @scrutiny_end_date = commons_only_sitting_days_backwards( @start_date, @day_count )
328                 if Date.today >= @scrutiny_end_date
329                   @action_required = 'the statement <em>would have been required to have been laid before</em>'
330                 else
331                   @action_required = 'the statement must be laid <em>before</em>'
332                 end
333              else
334                 @scrutiny_end_date = commons_only_sitting_days_forwards( @start_date, @day_count )
335                 @start_date_type = "laying date"
336               end
337           end

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

340         elsif @calculation_style

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

343           case @calculation_style
  • Calculation style 1
346             when 1
348               @scrutiny_end_date = bicameral_calculation_both_houses_sitting_forwards( @start_date, @day_count )
  • Calculation style 2
351             when 2
353               @scrutiny_end_date = bicameral_si_either_house_sitting_calculation_forwards( @start_date, @day_count )
  • Calculation style 3
356             when 3
358               @scrutiny_end_date = commons_only_si_calculation_forwards( @start_date, @day_count )
  • Calculation style 4
361             when 4
363               @scrutiny_end_date = pnsi_calculation_forwards( @start_date, @day_count )
  • Calculation style 5
366             when 5
368               @scrutiny_end_date = treaty_calculation_forwards( @start_date, @day_count )
  • Calculation style 6
371             when 6
373               @scrutiny_end_date = commons_only_sitting_days_forwards( @start_date, @day_count )
374             else

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

377               @missing_information << 'a valid calculation style'

... and call the insufficient information method.

380               insufficient_information
381           end
382         end

We set the generic meta information.

384         @json_url = request.original_fullpath.sub '?', '.json?'
385         @crumb << { label: 'Calculators', url: calculator_list_url }
386         @section = 'calculators'

If the direction of the calculation is reverse ...

389         if @direction == 'reverse'

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

392           @page_title = "Scrutiny start date calculation"
393           @multiline_page_title = "Calculators <span class='subhead'>Scrutiny start date calculation</span>".html_safe
394           @description = "A calculation to determine the estimated start date of scrutiny for instruments before Parliament."
395           @calendar_links << ['Anticipated start date of the scrutiny period', request.original_fullpath.sub( '?', '.ics?' )]
396           @crumb << { label: 'Scrutiny start date', url: reverse_calculator_form_url }
397           @crumb << { label: 'Calculation', url: nil }
398           @subsection = 'scrutiny-calculator-reverse'
400           render :template => 'calculator/scrutiny_reverse'

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

403         else

.. we set the meta information for the page.

406           @page_title = "Scrutiny end date calculation"
407           @multiline_page_title = "Calculators <span class='subhead'>Scrutiny end date calculation</span>".html_safe
408           @description = "A calculation to determine the anticipated end date of scrutiny for instruments before Parliament."
409           @json_url = request.original_fullpath.sub '?', '.json?'
410           @calendar_links << ['Anticipated end date of the scrutiny period', request.original_fullpath.sub( '?', '.ics?' )]
411           @crumb << { label: 'Scrutiny end date', url: calculator_form_url }
412           @crumb << { label: 'Calculation', url: nil }
413           @subsection = 'scrutiny-calculator'
414         end
415       end
416     end
417   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.

421   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.

424     calculation_can_proceed = true

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

427     @missing_information = []

We check for the presence of a valid start date.

If the start date is present ...

431     if start_date

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

434       begin
435          @start_date = Date.parse( start_date )

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

438       rescue ArgumentError

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

441         calculation_can_proceed = false

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

444         @missing_information << invalid_date_type_label
445       end

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

448     else

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

451       calculation_can_proceed = false

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

454       @missing_information << missing_date_type_label
455     end

We check for the presence of a valid day count.

If a day count has been passed ...

459     if day_count

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

462       @day_count = day_count.to_i

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

465       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.

470         @missing_information << 'a valid day count'
471       end

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

474     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.

479       @missing_information << 'a day count'
480     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 ...

484     if !procedure and !calculation_style

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

487       calculation_can_proceed = false

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

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

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

493     elsif procedure

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

496       @procedure = Procedure.find_by_id( procedure )

If we fail to find the procedure ...

499       unless @procedure

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

502         calculation_can_proceed = false

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

505         @missing_information << 'a valid procedure'
506       end

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

509     elsif calculation_style

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

512       @calculation_style = calculation_style.to_i

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

515       if is_calculation_style_invalid?( @calculation_style )

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

518         calculation_can_proceed = false

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

521         @missing_information << 'a valid calculation style'
522       end
523     end

We return the calculation can proceed boolean.

526     calculation_can_proceed
527   end

A method to check if the day count is valid.

530   def is_day_count_valid?( day_count )

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

533     is_day_count_valid = true

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

536     if day_count == 0 or day_count.negative?

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

539       is_day_count_valid = false
540     end

We return the is day count valid boolean.

543     is_day_count_valid
544   end

A method to check if the day count is invalid.

547   def is_day_count_invalid?( day_count )

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

550     !is_day_count_valid?( day_count )
551   end

A method to check if the calculation style is valid.

554   def is_calculation_style_valid?( calculation_style )

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

557     is_calculation_style_valid = true

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

560     if calculation_style == 0 or calculation_style.negative?

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

563       is_calculation_style_valid = false
564     end

We return the is calculation style valid boolean.

567     is_calculation_style_valid
568   end

A method to check if the calculation style is invalid.

571   def is_calculation_style_invalid?( calculation_style )

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

574     !is_calculation_style_valid?( calculation_style )
575   end

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

578   def insufficient_information

We set the generic meta information.

581     @crumb << { label: 'Calculators', url: calculator_list_url }
582     @section = 'calculators'

If the calculation is a reverse calculation ...

585     if @direction == 'reverse'

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

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

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

595     else

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

598       @page_title = "Scrutiny end date - more information required"
599       @multiline_page_title = "Calculators <span class='subhead'>Scrutiny end date - more information required</span>".html_safe
600       @description = "More information required for a calculation to determine the estimated end date of scrutiny for instruments before Parliament."
601       @crumb << { label: 'Scrutiny end date', url: calculator_form_url }
602       @subsection = 'scrutiny-calculator'
603     end

We set more generic meta information.

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

We display the not enough information message.

609     render :template => 'calculator/not_enough_information'
610   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.

615   def missing_date_type_label

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

618     if @direction == 'reverse'

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

621       'an end date'

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

624     else

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

627       'a start date'
628     end
629   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.

634   def invalid_date_type_label

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

637     if @direction == 'reverse'

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

640       'a valid end date'

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

643     else

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

646       'a valid start date'
647     end
648   end
649 end