Skip to main content

Repository: govuk-rota-generator

Category
Utilities

README

Generates a balanced rota, taking into account:

  1. Each developer’s availability and eligibility for different cover types.
  2. Team burden (it avoids allocating multiple devs from the same team on one shift).
  3. Different working patterns (e.g. devs who don’t work Fridays will automatically have cover assigned for that day).
  4. Partial availability (e.g. a developer may say they’re unavailable to do on-call at the weekend, but they would still be available to do in-hours shifts - the generator will make use of that).
  5. Bank holiday support - it uses the GOV.UK bank holiday API to detect bank holidays and ensure that the assigned on-call person that week is given the ‘in-hours’ shift in PagerDuty.

Setup

In GCP, there’s a govuk-rota-generator ‘project’ which has a google-sheet-fetcher ‘service account’ (which automatically has its own email address google-sheet-fetcher@govuk-rota-generator.iam.gserviceaccount.com).

The 2nd-line-support Google group is an ‘Owner’ of the service account, so anyone in that group should be able to create a service account key for local use. From the google-sheet-fetcher service account page:

  1. “Add Key” -> “JSON” -> “Create”
  2. This will download a JSON file.
  3. Store it as google_service_account_key.json (which is git-ignored) at the root of this repo.

Usage

Create a developer availability form

  1. Clone the form template
  2. Update the dates etc, but otherwise make no changes to the form structure
  3. Link it to a spreadsheet
  4. Share the spreadsheet with google-sheet-fetcher@govuk-rota-generator.iam.gserviceaccount.com, as an Editor. (Dismiss the warning about sharing with external email addresses)
  5. Add a worksheet / ‘tab’ called “Auto-generated draft rota” (which is where the draft rota will be pushed by govuk-rota-generator)
  6. Send out the form, gather responses

Get the data ready

Run the fetch_data script, passing the URL of your responses spreadsheet as a parameter, e.g.

ruby bin/fetch_data.rb https://docs.google.com/spreadsheets/d/abc123def456hij789/edit

This will generate a data/rota_inputs.yml file, combining your responses spreadsheet with the ‘Eligibility’ sheet of the List of GOV.UK Engineers (accessible by Senior Tech and 2nd Line Leads only).

The generated file will look something like:

---
dates:
- 01/04/2024
- 02/04/2024
- 03/04/2024
- 04/04/2024
- 05/04/2024
- 06/04/2024
- 07/04/2024
people:
- email: dev.eloper@digital.cabinet-office.gov.uk
  team: Unknown
  non_working_days: []
  can_do_roles:
  - :inhours_primary
  - :inhours_secondary
  - :inhours_primary_standby
  - :inhours_secondary_standby
  - :oncall_primary
  - :oncall_secondary
  forbidden_in_hours_days: []
  forbidden_on_call_days: []
  assigned_shifts: []

Generate the rota

Run ruby bin/generate_rota.rb https://docs.google.com/spreadsheets/d/abc123def456hij789/edit.

This generates a data/generated_rota.yml file, which has the same structure as the data/rota_inputs.yml file. But the script will also output a user-friendly CSV to the “Auto-generated draft rota” worksheet you set up earlier, or to STDOUT if you don’t provide the Google Sheet CLI parameter. The worksheet/CSV can be used as the data/rota.csv in pay-pagerduty (automating the overriding of PagerDuty schedules).

Note that you can tweak the weighting of each ‘role’ (e.g. oncall_primary) by editing the values in config/roles.yml, to influence how often folks are assigned to particular roles.

Check the fairness of the rota

This summarises the fairness of the rota. Before running the script, you’ll need to add two new worksheets to the spreadsheet:

  1. “Manually tweaked rota” - you should copy over the “Auto-generated draft rota” into this. You’re then free to tweak the output of the rota here, without worrying about losing all your changes next time you run the rota generator.
  2. “Fairness calculator” - blank worksheet, which will be populated by running the command below.

Run ruby bin/calculate_fairness.rb https://docs.google.com/spreadsheets/d/abc123def456hij789/edit.

Synchronise the rota with PagerDuty

govuk-rota-generator automatically synchronises the Google Sheet rota with PagerDuty on a regular basis.

The three ENV vars described in Run the synchroniser script, as well as the GOOGLE_SERVICE_ACCOUNT_KEY key from the setup phase, are stored as secrets on this repository. A failure notification is sent to Slack if the sync fails - the destination is determined by the SLACK_WEBHOOK_URL secret (reused from govuk-saas-config).

Every quarter, a developer will need to swap out the ROTA_TAB_NAME to reflect the name of the next rota that needs synchronising.

Below are the instructions for running the synchroniser manually.

Export an API key

You’ll need a PagerDuty API key associated with PagerDuty account that has “Global Admin” access (which can be configured in govuk-user-reviewer):

  1. Log into PagerDuty
  2. Navigate to “My Profile”
  3. Click on “User Settings”
  4. Click “Create API User Token”

Store this token somewhere safe, e.g. ~/pagerduty_token.txt.

Run the synchroniser script

You can now synchronise a rota with PagerDuty using:

$ export ROTA_SHEET_URL="https://docs.google.com/spreadsheets/d/<sheet id>/edit"
$ export ROTA_TAB_NAME="Q3 24/25 Rota"                                                                            
$ export PAGER_DUTY_API_KEY=$(more ~/pagerduty_token.txt)
$ bundle exec rake sync_pagerduty[false]

This will:

  1. Fetch the rota corresponding to ROTA_SHEET_URL and ROTA_TAB_NAME
  2. Map the roles in that rota to the roles in config/roles.yml, where it finds the corresponding PagerDuty schedule IDs
  3. Fetch the list of PagerDuty users and match these up with the users in your rota, warning on any names that are missing from PagerDuty (and skipping over those shifts)±
  4. Find conflicts between the PagerDuty schedule and the local rota, and apply overrides to fix them

± These engineers either don’t have PagerDuty accounts yet (because they lack Production Admin access - you can provide temporary access via govuk-user-reviewer), or the name derived from their email address doesn’t match the name PagerDuty has for them. For the latter, you can specify the mapping of the names in config/pagerduty_config_overrides.yml.

By default, the script will ask you to approve each override: y to override, n to skip, and exit to close the script altogether. This means you can do a ‘dry run’ of the synchroniser by choosing n each time.

If you wish to approve all of the overrides at once, you can pass true to the rake task:

bundle exec rake sync_pagerduty[true]