Skip to content

Commit 731021d

Browse files
author
Al Davidson
authored
Merge pull request #4484 from nhsuk/add-reporting-api-vaccination-event-table-and-model
Add ReportingAPI::VaccinationEvent model
2 parents 581e1b9 + ed11899 commit 731021d

File tree

9 files changed

+928
-0
lines changed

9 files changed

+928
-0
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# frozen_string_literal: true
2+
3+
# Convenience methods for initializing an instance of the including class
4+
# with a mix of simple-valued attributes (e.g. {name: 'a name', thing_id: 123} )
5+
# and named objects (e.g. patient: (instance of Patient) ).
6+
# This enables attributes to be copied from the given objects to the corresponding
7+
# scoped attributes on the model
8+
# Example:
9+
# `obj = (class).new( name: 'my name', thing_id: 123, patient: (instance of patient) )`
10+
# results in obj getting these attributes:
11+
# ```
12+
# name = 'my name',
13+
# thing_id = 123,
14+
# patient_id = (id of patient instance)
15+
# patient_address_postcode = (postcode from patient instance)
16+
# patient_given_name = (given_name of patient instance)
17+
# ````
18+
# ...etc
19+
module ReportingAPI::DenormalizingConcern
20+
extend ActiveSupport::Concern
21+
22+
included do
23+
def initialize(attrs = {})
24+
attrs = attrs.to_h
25+
references = attrs.select { |_, v| v.is_a?(ApplicationRecord) }
26+
copy_attributes_from_references(references)
27+
28+
simple_valued_attrs = attrs.except(*references.keys)
29+
super(**entries_which_exist_in_attributes(simple_valued_attrs))
30+
end
31+
32+
def copy_attributes_from_references(references = {})
33+
attr_set = self.class.attribute_names.to_set
34+
35+
references.each do |name, record|
36+
if record
37+
copy_scoped_attributes(name, record, attr_set)
38+
else
39+
attr_set.grep(/^#{name}_/).each { |k| self[k] = nil }
40+
end
41+
end
42+
end
43+
44+
protected
45+
46+
# given a hash, return only the keys which exist in this class' attributes
47+
def entries_which_exist_in_attributes(attrs = {})
48+
attrs.slice(*self.class.attribute_names.map(&:to_sym)).symbolize_keys
49+
end
50+
51+
# given a prefix and object like :school, (School object)
52+
# copy each attribute from the given object to an attribute
53+
# on self, prefixed with the prefix
54+
# e.g. self.school_address_postcode = obj.postcode and so on
55+
def copy_scoped_attributes(
56+
prefix,
57+
record,
58+
attr_set = self.class.attribute_names.to_set
59+
)
60+
record.attributes&.each do |key, value|
61+
attr_name = "#{prefix}_#{key}"
62+
self[attr_name] = value if attr_set.include?(attr_name)
63+
end
64+
end
65+
end
66+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# frozen_string_literal: true
2+
3+
module ReportingAPI::EventConcern
4+
extend ActiveSupport::Concern
5+
6+
included do
7+
belongs_to :source, polymorphic: true
8+
belongs_to :patient
9+
10+
before_validation :set_patient_from_source,
11+
:set_event_timestamp_date_part_attributes,
12+
:set_patient_year_group
13+
14+
protected
15+
16+
def set_patient_from_source
17+
self.patient = source.try(:patient)
18+
end
19+
20+
def set_patient_year_group
21+
self.patient_year_group =
22+
patient.year_group(academic_year: event_timestamp.to_date.academic_year)
23+
end
24+
25+
def set_event_timestamp_date_part_attributes
26+
self.event_timestamp_day = event_timestamp.day
27+
self.event_timestamp_month = event_timestamp.month
28+
self.event_timestamp_year = event_timestamp.year
29+
30+
self.event_timestamp_academic_year = event_timestamp.to_date.academic_year
31+
end
32+
33+
def self.count_sql_where(comparison:, as:)
34+
"SUM(CASE WHEN #{comparison} THEN 1 ELSE 0 END) AS #{as}"
35+
end
36+
end
37+
end
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# frozen_string_literal: true
2+
3+
# == Schema Information
4+
#
5+
# Table name: reporting_api_vaccination_events
6+
#
7+
# id :bigint not null, primary key
8+
# event_timestamp :datetime not null
9+
# event_timestamp_academic_year :integer not null
10+
# event_timestamp_day :integer not null
11+
# event_timestamp_month :integer not null
12+
# event_timestamp_year :integer not null
13+
# event_type :string not null
14+
# location_address_postcode :string
15+
# location_address_town :string
16+
# location_local_authority_mhclg_code :string
17+
# location_local_authority_short_name :string
18+
# location_name :string
19+
# location_type :string
20+
# organisation_name :string
21+
# organisation_ods_code :string
22+
# patient_address_postcode :string
23+
# patient_address_town :string
24+
# patient_birth_academic_year :integer
25+
# patient_date_of_death :date
26+
# patient_gender_code :string
27+
# patient_home_educated :boolean
28+
# patient_local_authority_from_postcode_mhclg_code :string
29+
# patient_local_authority_from_postcode_short_name :string
30+
# patient_school_address_postcode :string
31+
# patient_school_address_town :string
32+
# patient_school_gias_local_authority_code :integer
33+
# patient_school_local_authority_mhclg_code :string
34+
# patient_school_local_authority_short_name :string
35+
# patient_school_name :string
36+
# patient_school_type :string
37+
# patient_year_group :integer
38+
# programme_type :string
39+
# source_type :string not null
40+
# team_name :string
41+
# vaccination_record_outcome :string
42+
# vaccination_record_performed_at :datetime
43+
# vaccination_record_uuid :uuid
44+
# created_at :datetime not null
45+
# updated_at :datetime not null
46+
# location_id :bigint
47+
# organisation_id :bigint
48+
# patient_id :bigint not null
49+
# patient_school_id :bigint
50+
# programme_id :bigint
51+
# source_id :bigint not null
52+
# team_id :bigint
53+
# vaccination_record_programme_id :bigint
54+
# vaccination_record_session_id :bigint
55+
#
56+
# Indexes
57+
#
58+
# index_reporting_api_vaccination_events_on_source (source_type,source_id)
59+
# ix_rve_ac_year_month (event_timestamp_academic_year,event_timestamp_month)
60+
# ix_rve_acyear_month_type (event_timestamp_academic_year,event_timestamp_month,event_type)
61+
# ix_rve_prog_acyear_month (programme_id,event_timestamp_academic_year,event_timestamp_month)
62+
# ix_rve_source_type_id (source_type,source_id)
63+
# ix_rve_team_acyr_month (team_id,event_timestamp_academic_year,event_timestamp_month)
64+
# ix_rve_tstamp (event_timestamp)
65+
#
66+
class ReportingAPI::VaccinationEvent < ApplicationRecord
67+
include ReportingAPI::DenormalizingConcern
68+
include ReportingAPI::EventConcern
69+
70+
def self.with_counts_of_outcomes
71+
select(
72+
count_sql_where(
73+
comparison: "vaccination_record_outcome = 'administered'",
74+
as: "total_vaccinations_performed"
75+
)
76+
)
77+
end
78+
79+
def self.with_count_of_patients_vaccinated
80+
# We want a count of just the distinct patient_ids
81+
# for whom the outcome was 'administered'.
82+
# If the outcome was not 'administered', this count should not include them
83+
select(<<-SQL)
84+
COUNT(
85+
DISTINCT(
86+
CASE WHEN vaccination_record_outcome = 'administered'
87+
THEN patient_id
88+
ELSE NULL
89+
END
90+
)
91+
) AS total_patients_vaccinated
92+
SQL
93+
end
94+
end
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# frozen_string_literal: true
2+
3+
class AddReportingAPIVaccinationEvents < ActiveRecord::Migration[8.0]
4+
def change
5+
create_table :reporting_api_vaccination_events do |t|
6+
t.string :event_type, null: false
7+
t.datetime :event_timestamp, null: false
8+
t.integer :event_timestamp_year, null: false
9+
t.integer :event_timestamp_month, null: false
10+
t.integer :event_timestamp_day, null: false
11+
t.integer :event_timestamp_academic_year, null: false
12+
13+
t.references :source, polymorphic: true, null: false
14+
15+
t.bigint :patient_id, null: false
16+
17+
t.string :patient_address_town
18+
t.string :patient_address_postcode
19+
t.string :patient_gender_code
20+
t.boolean :patient_home_educated
21+
t.date :patient_date_of_death
22+
t.integer :patient_birth_academic_year
23+
t.integer :patient_year_group
24+
25+
t.bigint :patient_school_id
26+
t.string :patient_school_name
27+
t.string :patient_school_address_town
28+
t.string :patient_school_address_postcode
29+
t.integer :patient_school_gias_local_authority_code
30+
t.string :patient_school_type
31+
32+
t.string :patient_school_local_authority_mhclg_code
33+
t.string :patient_school_local_authority_short_name
34+
35+
t.string :patient_local_authority_from_postcode_mhclg_code
36+
t.string :patient_local_authority_from_postcode_short_name
37+
38+
t.bigint :location_id
39+
t.string :location_name
40+
t.string :location_address_town
41+
t.string :location_address_postcode
42+
t.string :location_type
43+
44+
t.string :location_local_authority_mhclg_code
45+
t.string :location_local_authority_short_name
46+
47+
t.bigint :team_id
48+
t.string :team_name
49+
50+
t.bigint :organisation_id
51+
t.string :organisation_ods_code
52+
t.string :organisation_name
53+
54+
t.string :vaccination_record_outcome
55+
t.uuid :vaccination_record_uuid
56+
t.datetime :vaccination_record_performed_at
57+
t.bigint :vaccination_record_programme_id
58+
t.bigint :vaccination_record_session_id
59+
60+
t.bigint :programme_id
61+
t.string :programme_type
62+
63+
t.timestamps
64+
65+
t.index [:event_timestamp], name: "ix_rve_tstamp"
66+
t.index %i[event_timestamp_academic_year event_timestamp_month],
67+
name: "ix_rve_ac_year_month"
68+
t.index %i[source_type source_id], name: "ix_rve_source_type_id"
69+
t.index %i[
70+
event_timestamp_academic_year
71+
event_timestamp_month
72+
event_type
73+
],
74+
name: "ix_rve_acyear_month_type"
75+
t.index %i[
76+
programme_id
77+
event_timestamp_academic_year
78+
event_timestamp_month
79+
],
80+
name: "ix_rve_prog_acyear_month"
81+
t.index %i[team_id event_timestamp_academic_year event_timestamp_month],
82+
name: "ix_rve_team_acyr_month"
83+
end
84+
end
85+
end

0 commit comments

Comments
 (0)