@@ -4,9 +4,263 @@ module Opensrp
44 #
55 # Base class which acts as an entry point for the rake task.
66 class Exporter
7+ class Config
8+ attr_reader :report_start , :report_end , :facilities , :time_window , :patients
9+
10+ def initialize config_file
11+ data = YAML . load_file ( config_file ) . deep_symbolize_keys . with_indifferent_access
12+ time_boundaries = data [ :time_boundaries ]
13+
14+ @report_start = if has_report_start? ( data )
15+ time_boundaries [ :report_start ]
16+ else
17+ DateTime . parse ( "2020-01-01" )
18+ end
19+ @report_end = if has_report_end? ( data )
20+ time_boundaries [ :report_end ]
21+ else
22+ DateTime . now
23+ end
24+ @facilities = data [ :facilities ]
25+ @time_bound = using_time_boundaries? data
26+ @time_window = @report_start ..@report_end
27+ @patients = get_patients data
28+ end
29+
30+ def time_bound?
31+ @time_bound || false
32+ end
33+
34+ def using_time_boundaries? ( config )
35+ config . has_key? :time_boundaries
36+ end
37+
38+ def has_report_start? ( config )
39+ using_time_boundaries? ( config ) && config [ :time_boundaries ] . has_key? ( :report_start )
40+ end
41+
42+ def has_report_end? ( config )
43+ using_time_boundaries? ( config ) && config [ :time_boundaries ] . has_key? ( :report_end )
44+ end
45+
46+ def get_patients config
47+ config [ :patients ] || [ ]
48+ end
49+ end
50+
51+ attr_reader :tally
52+
753 def self . export config , output
854 new ( config , output ) . call!
955 end
56+
57+ def initialize config_file , output_file , logger : nil
58+ raise "Config file should be YAML" unless %w[ yaml yml ] . include? ( config_file . split ( "." ) . last )
59+ raise "Output file should be JSON" unless output_file . split ( "." ) . last == "json"
60+
61+ if logger . nil?
62+ initialize_logger
63+ else
64+ @logger = logger
65+ end
66+
67+ @config = Config . new config_file
68+ @logger . info "Exporting data using config at #{ config_file } "
69+ @output = output_file
70+ @resources = [ ]
71+ @encounters = [ ]
72+ @tally = Hash . new ( 0 )
73+ end
74+
75+ def initialize_logger
76+ logfile = Rails . root . join ( "log" , "#{ Rails . env } .log" )
77+ @logger = ActiveSupport ::Logger . new ( logfile )
78+ @logger . extend ( ActiveSupport ::Logger . broadcast ( ActiveSupport ::Logger . new ( $stdout) ) )
79+ end
80+
81+ def call!
82+ @logger . info "Time Boundaries: [#{ @config . report_start } ..#{ @config . report_end } ]"
83+
84+ patients = select_patients from_facilities : @config . facilities . keys
85+ patients . each do |patient |
86+ export_patient_details patient
87+ export_blood_pressure_details patient
88+ export_blood_sugar_details patient
89+ export_prescription_drugs_details patient
90+ export_appointments_details patient
91+ export_medical_history_details patient
92+ end
93+
94+ @tally [ :encounters ] += @encounters . size
95+ @resources << OneOff ::Opensrp ::EncounterGenerator . new ( @encounters ) . generate
96+
97+ write_audit_trail patients
98+ end
99+
100+ def select_patients from_facilities : [ ]
101+ raise "No facility selected for export" if from_facilities . empty?
102+
103+ relation = Patient . where ( assigned_facility_id : from_facilities )
104+ if @config . patients . empty?
105+ relation
106+ else
107+ relation . where ( id : @config . patients )
108+ end
109+ end
110+
111+ def export_patient_details patient
112+ return unless @config . time_window . cover? ( patient . recorded_at )
113+
114+ patient_exporter = OneOff ::Opensrp ::PatientExporter . new ( patient , @config . facilities )
115+ @resources << patient_exporter . export
116+ @tally [ :patients ] += 1
117+
118+ @resources << patient_exporter . export_registration_questionnaire_response
119+ @tally [ :questionnaire_response ] += 1
120+
121+ @encounters << patient_exporter . export_registration_encounter
122+ end
123+
124+ def export_blood_pressure_details patient
125+ blood_pressures = if @config . time_bound?
126+ patient
127+ . blood_pressures
128+ . where ( recorded_at : @config . time_window )
129+ . or ( patient
130+ . blood_pressures
131+ . where ( updated_at : @config . time_window ) )
132+ else
133+ patient . blood_pressures
134+ end
135+ @logger . debug "Patient[##{ patient . id } ] has #{ blood_pressures . size } blood pressure readings."
136+ @tally [ :observation ] += blood_pressures . size
137+ blood_pressures . each do |bp |
138+ # This is technically an FHIR::Observation, with code set to a blood pressure code
139+ bp_exporter = OneOff ::Opensrp ::BloodPressureExporter . new ( bp , @config . facilities )
140+ @resources << bp_exporter . export
141+ @encounters << bp_exporter . export_encounter
142+ end
143+ end
144+
145+ def export_blood_sugar_details patient
146+ blood_sugars = if @config . time_bound?
147+ patient
148+ . blood_sugars
149+ . where ( recorded_at : @config . time_window )
150+ . or ( patient
151+ . blood_sugars
152+ . where ( updated_at : @config . time_window ) )
153+ else
154+ patient . blood_sugars
155+ end
156+ @logger . debug "Patient[##{ patient . id } ] has #{ blood_sugars . size } blood sugar readings."
157+ @tally [ :observation ] += blood_sugars . size
158+ blood_sugars . each do |bs |
159+ # This is technically an FHIR::Observation, with code set to a blood sugar code
160+ bs_exporter = OneOff ::Opensrp ::BloodSugarExporter . new ( bs , @config . facilities )
161+ if patient . medical_history . diabetes_no?
162+ @resources << bs_exporter . export_no_diabetes_observation
163+ end
164+ @resources << bs_exporter . export
165+ @encounters << bs_exporter . export_encounter
166+ end
167+ end
168+
169+ def export_prescription_drugs_details patient
170+ prescription_drugs = if @config . time_bound?
171+ patient
172+ . prescription_drugs
173+ . where ( created_at : @config . time_window )
174+ . or ( patient
175+ . prescription_drugs
176+ . where ( updated_at : @config . time_window ) )
177+ else
178+ patient . prescription_drugs
179+ end
180+ @logger . debug "Patient[##{ patient . id } ] has #{ prescription_drugs . size } drugs prescribed."
181+ @tally [ :flags ] += prescription_drugs . size
182+ prescription_drugs . each do |drug |
183+ drug_exporter = OneOff ::Opensrp ::PrescriptionDrugExporter . new ( drug , @config . facilities )
184+ @resources << drug_exporter . export_dosage_flag
185+ @encounters << drug_exporter . export_encounter
186+ end
187+ end
188+
189+ def export_appointments_details patient
190+ appointments = if @config . time_bound?
191+ patient
192+ . appointments
193+ . where ( created_at : @config . time_window )
194+ . or ( patient
195+ . appointments
196+ . where ( updated_at : @config . time_window ) )
197+ else
198+ patient . appointments
199+ end
200+ @logger . debug "Patient[##{ patient . id } ] has #{ appointments . size } appointments."
201+ @tally [ :appointments ] += appointments . size
202+ @tally [ :tasks ] += appointments . includes ( :call_results ) . where . not ( call_results : { id : nil } ) . size
203+ @tally [ :flags ] += appointments . includes ( :call_results ) . where . not ( call_results : { id : nil } ) . size
204+ appointments . each do |appointment |
205+ appointment_exporter = OneOff ::Opensrp ::AppointmentExporter . new ( appointment , @config . facilities )
206+ @resources << appointment_exporter . export
207+ if appointment . call_results . present?
208+ @resources << appointment_exporter . export_call_outcome_task
209+ @resources << appointment_exporter . export_call_outcome_flag
210+ end
211+ @encounters << appointment_exporter . export_encounter
212+ end
213+ end
214+
215+ def export_medical_history_details patient
216+ OneOff ::Opensrp ::MedicalHistoryExporter . new ( patient . medical_history , @config . facilities ) . then do |medical_history_exporter |
217+ @resources << medical_history_exporter . export
218+ @encounters << medical_history_exporter . export_encounter
219+ end
220+ @tally [ :conditions ] += 1
221+ end
222+
223+ def write_audit_trail patients
224+ CSV . open ( "audit_trail.csv" , "w" ) do |csv |
225+ csv << create_audit_record ( @config . facilities , patients . first ) . keys
226+ patients . each do |patient |
227+ csv << create_audit_record ( @config . facilities , patient ) . values
228+ end
229+ end
230+ end
231+
232+ def create_audit_record ( facilities , patient )
233+ return { } if patient . nil?
234+
235+ {
236+ patient_id : patient . id ,
237+ sri_lanka_personal_health_number : patient . business_identifiers . where ( identifier_type : "sri_lanka_personal_health_number" ) &.first &.identifier ,
238+ patient_bp_passport_number : patient . business_identifiers . where ( identifier_type : "simple_bp_passport" ) &.first &.identifier ,
239+ patient_name : patient . full_name ,
240+ patient_gender : patient . gender ,
241+ patient_date_of_birth : patient . date_of_birth || patient . age_updated_at - patient . age . years ,
242+ patient_address : patient . address ? patient . address . street_address : "" ,
243+ patient_telephone : patient . phone_numbers . pluck ( :number ) . join ( ";" ) ,
244+ patient_facility : facilities [ patient . assigned_facility_id ] [ :name ] ,
245+ patient_preferred_language : "Sinhala" ,
246+ patient_active : patient . status_active? ,
247+ patient_deceased : patient . status_dead? ,
248+ condition : ( "HTN" if patient . medical_history . hypertension_yes? ) || ( "DM" if patient . medical_history . diabetes_yes? ) ,
249+ blood_pressure : patient . latest_blood_pressure &.values_at ( :systolic , :diastolic ) &.join ( "/" ) ,
250+ bmi : nil ,
251+ appointment_date : patient . appointments . order ( device_updated_at : :desc ) . where ( status : "scheduled" ) &.first &.device_updated_at &.to_date &.iso8601 ,
252+ medication : patient . prescription_drugs . order ( device_updated_at : :desc ) . where ( is_deleted : false ) &.first &.values_at ( :name , :dosage ) &.join ( " " ) ,
253+ glucose_measure : patient . latest_blood_sugar &.blood_sugar_value . then { |bs | "%.2f" % bs if bs } ,
254+ glucose_measure_type : patient . latest_blood_sugar &.blood_sugar_type ,
255+ call_outcome : patient . appointments . order ( device_updated_at : :desc ) &.first &.call_results &.order ( device_created_at : :desc ) &.first &.result_type
256+ }
257+ end
258+
259+ private
260+
261+ def read config_file
262+ YAML . load_file ( config_file ) . deep_symbolize_keys . with_indifferent_access
263+ end
10264 end
11265 end
12266end
0 commit comments