Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a305942
Sc 17007 (#5718)
Gyan-Gupta-Rtsl Nov 17, 2025
6dd851b
Removing screened patients from reporting_patient_states calculations…
aagrawalrtsl Nov 20, 2025
685bc0b
removing screening patients from latest_blood_pressures_per_patient_p…
aagrawalrtsl Nov 25, 2025
5afa147
standardrb fix
Nov 26, 2025
3c644dc
removing screened patients from blood_pressures_per_facility_per_days…
aagrawalrtsl Nov 27, 2025
b779598
removing screened patients from reporting_patient_blood_pressures (#5…
aagrawalrtsl Nov 28, 2025
179975f
Merge branch 'master' into screening_phase_1
Nov 28, 2025
8d57a39
standardrb fix
Nov 28, 2025
47bc8fe
sc-17251: handled backward compatibility for screening flow (#5726)
Gyan-Gupta-Rtsl Nov 28, 2025
ffa1fbb
removing screened patients from reporting_patient_blood_sugars (#5731)
aagrawalrtsl Dec 2, 2025
0b2e699
sc-17251 backfill: prefer patient.recorded_at for diagnosed_dates for…
Gyan-Gupta-Rtsl Dec 3, 2025
6549f36
removing screened patients from reporting_overdue_calls (#5732)
aagrawalrtsl Dec 3, 2025
271c4d3
removing screened patients from reporting_patient_visits (#5734)
aagrawalrtsl Dec 4, 2025
4c49351
removing screened patients from reporting_prescriptions (#5736)
aagrawalrtsl Dec 5, 2025
db8c055
removing screened_patients from reporting_patient_follow_ups (#5739)
aagrawalrtsl Dec 8, 2025
6a0cf21
removing screened patients from reporting_facility_appointment_schedu…
aagrawalrtsl Dec 9, 2025
b6012fb
sc-17428 covering one edge case of screening (#5738)
Gyan-Gupta-Rtsl Dec 11, 2025
bac4850
removing screened patients from reporting_facility_daily_follow_ups_a…
aagrawalrtsl Dec 11, 2025
ba6a59b
removing screened patients from materialized_patient_summaries (#5743)
aagrawalrtsl Dec 15, 2025
0099689
Merge branch 'master' into screening_phase_1
Dec 15, 2025
7cdac68
adding diagnosed_confirmed_at index to patients
Dec 15, 2025
15b93f7
Merge branch 'master' into screening_phase_1
Jan 5, 2026
3ca4ed4
Merge branch 'master' into screening_phase_1
Jan 8, 2026
739313e
Sc 17651 (#5748)
Gyan-Gupta-Rtsl Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion app/controllers/api/v3/medical_histories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ def merge_if_valid(medical_history_params)
.merge(metadata)

medical_history = MedicalHistory.merge(record_params)
{record: medical_history}

if medical_history.errors.any?
{errors_hash: errors_hash(medical_history)}
else
{record: medical_history}
end
end
end

Expand All @@ -48,6 +53,8 @@ def medical_histories_params
:smoking,
:smokeless_tobacco,
:cholesterol,
:htn_diagnosed_at,
:dm_diagnosed_at,
:created_at,
:updated_at
)
Expand Down
42 changes: 42 additions & 0 deletions app/models/concerns/diagnosed_confirmed_at_sync.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module DiagnosedConfirmedAtSync
extend ActiveSupport::Concern

included do
after_commit :sync_diagnosed_confirmed_at, on: [:create, :update]
end

private

def sync_diagnosed_confirmed_at
patient = extract_patient
return unless patient

mh = patient.medical_history
return unless mh

valid_htn = mh.htn_diagnosed_at.present? && %w[yes no].include?(mh.hypertension)
valid_dm = mh.dm_diagnosed_at.present? && %w[yes no].include?(mh.diabetes)

return unless valid_htn || valid_dm

earliest = [
(mh.htn_diagnosed_at if valid_htn),
(mh.dm_diagnosed_at if valid_dm)
].compact.min

return if earliest.blank?

if patient.diagnosed_confirmed_at.nil?
patient.update_columns(diagnosed_confirmed_at: earliest)
end
end

def extract_patient
case self
when Patient
self
when MedicalHistory
patient
end
end
end
85 changes: 82 additions & 3 deletions app/models/medical_history.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
class MedicalHistory < ApplicationRecord
include Mergeable
include DiagnosedConfirmedAtSync

belongs_to :patient, optional: true
belongs_to :user, optional: true

Expand Down Expand Up @@ -29,15 +31,92 @@ class MedicalHistory < ApplicationRecord
enum chronic_kidney_disease: MEDICAL_HISTORY_ANSWERS, _prefix: true
enum receiving_treatment_for_hypertension: MEDICAL_HISTORY_ANSWERS, _prefix: true
enum receiving_treatment_for_diabetes: MEDICAL_HISTORY_ANSWERS, _prefix: true
enum diabetes: MEDICAL_HISTORY_ANSWERS, _prefix: true
enum hypertension: MEDICAL_HISTORY_ANSWERS, _prefix: true
enum diagnosed_with_hypertension: MEDICAL_HISTORY_ANSWERS, _prefix: true
enum smoking: MEDICAL_HISTORY_ANSWERS, _prefix: true
enum smokeless_tobacco: MEDICAL_HISTORY_ANSWERS, _prefix: true
enum diabetes: MEDICAL_HISTORY_ANSWERS.merge(suspected: "suspected"), _prefix: true
enum hypertension: MEDICAL_HISTORY_ANSWERS.merge(suspected: "suspected"), _prefix: true
enum diagnosed_with_hypertension: MEDICAL_HISTORY_ANSWERS.merge(suspected: "suspected"), _prefix: true

scope :for_sync, -> { with_discarded }

before_validation :backfill_diagnosed_dates
before_validation :silently_enforce_medical_history_rules

def indicates_hypertension_risk?
prior_heart_attack_yes? || prior_stroke_yes?
end

private

def silently_enforce_medical_history_rules
enforce_date_rules_silently
enforce_one_way_enums_silently
end

def backfill_diagnosed_dates
return if htn_diagnosed_at.present? || dm_diagnosed_at.present?
return if hypertension_suspected? || diabetes_suspected?

source = patient&.recorded_at
return unless source.present?

if %w[yes no].include?(hypertension&.to_s)
self.htn_diagnosed_at ||= source
end

if %w[yes no].include?(diabetes&.to_s)
self.dm_diagnosed_at ||= source
end
end

def enforce_one_way_enums_silently
%i[hypertension diabetes diagnosed_with_hypertension].each do |attr|
prev = send("#{attr}_was")
curr = send(attr)

next if prev.blank? || prev.to_s == curr.to_s

prev_s = prev.to_s
curr_s = curr.to_s

if prev_s == "suspected"
next
end

if %w[yes no].include?(prev_s) && curr_s == "suspected"
write_attribute(attr, prev_s)

case attr
when :hypertension
write_attribute(:htn_diagnosed_at, htn_diagnosed_at_was)
when :diabetes
write_attribute(:dm_diagnosed_at, dm_diagnosed_at_was)
end

Rails.logger.info(
"[MedicalHistory] Prevented change of #{attr} from #{curr_s} -> #{prev_s} for medical_history_id=#{id || "new"}"
)
next
end
end
end

def enforce_date_rules_silently
self.htn_diagnosed_at = nil unless %w[yes no].include?(hypertension&.to_s)
self.dm_diagnosed_at = nil unless %w[yes no].include?(diabetes&.to_s)

if htn_diagnosed_at_was.present? && !timestamps_equal?(htn_diagnosed_at, htn_diagnosed_at_was)
write_attribute(:htn_diagnosed_at, htn_diagnosed_at_was)
Rails.logger.info("[MedicalHistory] Silently preserved existing htn_diagnosed_at for id=#{id || "new"}")
end

if dm_diagnosed_at_was.present? && !timestamps_equal?(dm_diagnosed_at, dm_diagnosed_at_was)
write_attribute(:dm_diagnosed_at, dm_diagnosed_at_was)
Rails.logger.info("[MedicalHistory] Silently preserved existing dm_diagnosed_at for id=#{id || "new"}")
end
end

def timestamps_equal?(a, b)
a&.change(usec: 0) == b&.change(usec: 0)
end
end
1 change: 1 addition & 0 deletions app/models/patient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class Patient < ApplicationRecord
include Mergeable
include Hashable
include PatientReportable
include DiagnosedConfirmedAtSync

GENDERS = Rails.application.config.country[:supported_genders].freeze
STATUSES = %w[active dead migrated unresponsive inactive].freeze
Expand Down
8 changes: 5 additions & 3 deletions app/schema/api/v3/models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -304,12 +304,14 @@ def medical_history
chronic_kidney_disease: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys},
receiving_treatment_for_hypertension: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys},
receiving_treatment_for_diabetes: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys},
diabetes: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys},
hypertension: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys},
diagnosed_with_hypertension: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys},
diabetes: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys + ["suspected"]},
hypertension: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys + ["suspected"]},
diagnosed_with_hypertension: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys + ["suspected"]},
smoking: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys},
smokeless_tobacco: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys},
cholesterol_value: {type: :number},
htn_diagnosed_at: {"$ref" => "#/definitions/nullable_timestamp"},
dm_diagnosed_at: {"$ref" => "#/definitions/nullable_timestamp"},
deleted_at: {"$ref" => "#/definitions/nullable_timestamp"},
created_at: {"$ref" => "#/definitions/timestamp"},
updated_at: {"$ref" => "#/definitions/timestamp"}
Expand Down
2 changes: 2 additions & 0 deletions app/validators/api/v3/medical_history_payload_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class Api::V3::MedicalHistoryPayloadValidator < Api::V3::PayloadValidator
:smoking,
:smokeless_tobacco,
:cholesterol,
:htn_diagnosed_at,
:dm_diagnosed_at,
:created_at,
:updated_at
)
Expand Down
1 change: 1 addition & 0 deletions app/validators/api/v3/patient_payload_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Api::V3::PatientPayloadValidator < Api::V3::PayloadValidator
:reminder_consent,
:deleted_reason,
:skip_facility_authorization,
:diagnosed_confirmed_at,
:eligible_for_reassignment
)

Expand Down
1 change: 1 addition & 0 deletions config/initializers/filter_parameter_logging.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module ParameterFiltering
status
updated_at
v2
diagnosed_confirmed_at
].freeze

ALLOWED_REGEX = /(^|_)ids?|#{Regexp.union(ALLOWED_ATTRIBUTES)}/.freeze
Expand Down
1 change: 1 addition & 0 deletions config/schema_descriptions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ reporting_patient_states:
diabetes_treatment_outcome_in_last_2_months: "For the visiting period of the last 2 months, is this patient's diabetes treatment outcome bs_under_200, bs_200_to_300, bs_over_300, missed_visit, or visited_no_bp?"
diabetes_treatment_outcome_in_quarter: "For the visiting period of the current quarter, is this patient's diabetes treatment outcome bs_under_200, bs_200_to_300, bs_over_300, missed_visit, or visited_no_bp?"
titrated: "True, if the patient had an increase in dosage of any hypertension drug in a visit this month."
diagnosed_confirmed_at: "Time (in UTC) at which the patient had it's first diagnosis completed, also differentiating him from screened patients."
reporting_facilities:
description: "List of Simple facililities with size, type, and geographical information. These facilities are used to segment reports by region."
columns:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class AddDiagnosedConfirmedAtToPatients < ActiveRecord::Migration[6.1]
def up
add_column :patients, :diagnosed_confirmed_at, :datetime

execute <<~SQL.squish
UPDATE patients
SET diagnosed_confirmed_at = recorded_at
WHERE diagnosed_confirmed_at IS NULL
AND recorded_at IS NOT NULL;
SQL
end

def down
remove_column :patients, :diagnosed_confirmed_at
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class AddDiagnosisTimestampsToMedicalHistories < ActiveRecord::Migration[6.1]
def up
add_column :medical_histories, :htn_diagnosed_at, :datetime
add_column :medical_histories, :dm_diagnosed_at, :datetime

say_with_time "Backfilling diagnosis timestamps for existing medical histories" do
MedicalHistory.includes(:patient).find_each(batch_size: 1000) do |mh|
next unless mh.patient

htn_time =
if %w[yes no].include?(mh.diagnosed_with_hypertension) ||
%w[yes no].include?(mh.hypertension)
mh.patient.recorded_at
end

dm_time =
if %w[yes no].include?(mh.diabetes)
mh.patient.recorded_at
end

next unless htn_time || dm_time

mh.update_columns(
htn_diagnosed_at: htn_time,
dm_diagnosed_at: dm_time
)
end
end
end

def down
remove_column :medical_histories, :htn_diagnosed_at
remove_column :medical_histories, :dm_diagnosed_at
end
end
Loading