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