<commit_before>module Synthea
  module Modules
    class Lifecycle < Synthea::Rules
      attr_accessor :male_growth, :male_weight, :female_growth, :female_weight
      attr_accessor :races, :ethnicity, :blood_types

      def initialize
        super
        @male_growth = Synthea::Utils::Distribution.normal(Synthea::Config.lifecycle.growth_rate_male_average, Synthea::Config.lifecycle.growth_rate_male_stddev)
        @male_weight = Synthea::Utils::Distribution.normal(Synthea::Config.lifecycle.weight_gain_male_average, Synthea::Config.lifecycle.weight_gain_male_stddev)
        @female_growth = Synthea::Utils::Distribution.normal(Synthea::Config.lifecycle.growth_rate_female_average, Synthea::Config.lifecycle.growth_rate_female_stddev)
        @female_weight = Synthea::Utils::Distribution.normal(Synthea::Config.lifecycle.weight_gain_female_average, Synthea::Config.lifecycle.weight_gain_female_stddev)
      end

      # People are born
      rule :birth, [], [:age] do |time, entity|
        unless entity.had_event?(:birth)
          entity[:age] = 0
          entity[:name_first] = Faker::Name.first_name
          entity[:name_last] = Faker::Name.last_name
          if Synthea::Config.population.append_hash_to_person_names == true
            entity[:name_first] = "#{entity[:name_first]}#{(entity[:name_first].hash % 999)}"
            entity[:name_last] = "#{entity[:name_last]}#{(entity[:name_last].hash % 999)}"
          end
          entity[:gender] ||= gender
          entity[:race] ||= Synthea::World::Demographics::RACES.pick
          entity[:ethnicity] ||= Synthea::World::Demographics::ETHNICITY[entity[:race]].pick
          entity[:blood_type] = Synthea::World::Demographics::BLOOD_TYPES[entity[:race]].pick
          entity[:sexual_orientation] = Synthea::World::Demographics::SEXUAL_ORIENTATION.pick.to_s
          entity[:fingerprint] = Synthea::Fingerprint.generate if Synthea::Config.population.generate_fingerprints
          entity[:first_language] ||= Synthea::World::Demographics::LANGUAGES_BY_ETHNICITY[entity[:ethnicity]].pick

          # new babies are average weight and length for American newborns
          entity.set_vital_sign(:height, 51.0, 'cm')
          entity.set_vital_sign(:weight, 3.5, 'kg')
          entity[:multiple_birth] = rand(3) + 1 if rand < Synthea::Config.lifecycle.prevalence_of_twins
          entity.events.create(time, :birth, :birth, true)
          entity.events.create(time, :encounter, :birth)
          entity.events.create(time, :symptoms_cause_encounter, :birth)

          # determine lat/long coordinates of address within MA
          location_data = Synthea::Location.select_point(entity[:city])
          entity[:coordinates_address] = location_data['point']
          zip_code = Synthea::Location.get_zipcode(location_data['city'])
          entity[:address] = {
            'line' => [Faker::Address.street_address],
            'city' => location_data['city'],
            'state' => 'MA',
            'postalCode' => zip_code,
            'country' => 'US'
          }
          entity[:address]['line'] << Faker::Address.secondary_address if rand < 0.5
          entity[:city] = location_data['city']

          # telephone
          entity[:telephone] = Faker::PhoneNumber.phone_number

          # birthplace
          entity[:birth_place] = {
            'city' => Synthea::Location.select_point['city'],
            'state' => 'MA',
            'country' => 'US'
          }

          # parents
          mothers_name = Faker::Name.first_name
          mothers_name = "#{mothers_name}#{(mothers_name.hash % 999)}" if Synthea::Config.population.append_hash_to_person_names == true

          mothers_surname = Faker::Name.last_name
          mothers_surname = "#{mothers_surname}#{(mothers_surname.hash % 999)}" if Synthea::Config.population.append_hash_to_person_names == true
          entity[:name_mother] = "#{mothers_name} #{mothers_surname}"

          fathers_name = Faker::Name.first_name
          fathers_name = "#{fathers_name}#{(fathers_name.hash % 999)}" if Synthea::Config.population.append_hash_to_person_names == true
          entity[:name_father] = "#{fathers_name} #{entity[:name_last]}"

          # identifiers
          entity[:identifier_ssn] = "999-#{rand(10..99)}-#{rand(1000..9999)}"

          entity[:med_changes] = Hash.new { |hsh, key| hsh[key] = [] }
          choose_socioeconomic_values(entity)
        end
      end

      # People age
      rule :age, [:birth, :age], [:age] do |time, entity|
        if entity.alive?(time)
          birthdate = entity.event(:birth).time
          age = entity[:age]
          entity[:age] = ((time.to_i - birthdate.to_i) / 1.year).floor
          if entity[:age] > age
            dt = nil
            begin
              dt = DateTime.new(time.year, birthdate.month, birthdate.mday, birthdate.hour, birthdate.min, birthdate.sec, birthdate.formatted_offset)
            rescue StandardError
              # this person was born on a leap-day
              dt = time
            end
            entity.events.create(dt.to_time, :grow, :age)
          end
          # stuff happens when you're an adult
          if entity[:age] == 16
            # you get a driver's license
            entity[:identifier_drivers] = "S999#{rand(10_000..99_999)}" unless entity[:identifier_drivers]
          elsif entity[:age] == 18
            # you get respect
            if entity[:gender] == 'M'
              entity[:name_prefix] = 'Mr.' unless entity[:name_prefix]
            else
              entity[:name_prefix] = 'Ms.' unless entity[:name_prefix]
            end
          elsif entity[:age] == 20 && entity[:identifier_passport].nil?
            # you might get a passport
            entity[:identifier_passport] = (rand(0..1) == 1)
            entity[:identifier_passport] = "X#{rand(10_000_000..99_999_999)}X" if entity[:identifier_passport]
          elsif entity[:age] == 27 && !entity[:marital_status] # median age of marriage (26 for women, 28 for men)
            if rand < 0.8
              # you might get married
              entity[:marital_status] = 'M'
              if entity[:gender] == 'F'
                entity[:name_prefix] = 'Mrs.'
                entity[:name_maiden] = entity[:name_last]
                entity[:name_last] = Faker::Name.last_name
                entity[:name_last] = "#{entity[:name_last]}#{(entity[:name_last].hash % 999)}"
              end
            else
              entity[:marital_status] = 'S'
            end
            # this doesn't account for divorces or widows right now
          elsif entity[:age] == 30
            # you might get overeducated
            entity[:name_suffix] = %w(PhD JD MD).sample if entity[:ses] && entity[:ses][:education] >= 0.95 && !entity[:name_suffix]
          end
        end
      end

      # People grow
      rule :grow, [:age, :gender], [:height, :weight, :bmi] do |time, entity|
        # Assume a linear growth rate until average size is achieved at age 20
        # TODO consider genetics, social determinants of health, etc
        if entity.alive?(time)
          unprocessed_events = entity.events.unprocessed.select { |e| e.type == :grow }
          unprocessed_events.each do |event|
            entity.events.process(event)
            age = entity[:age]
            gender = entity[:gender]
            height = entity.get_vital_sign_value(:height)
            weight = entity.get_vital_sign_value(:weight)
            if age <= 20
              if gender == 'M'
                height += @male_growth.call # centimeters
                weight += @male_weight.call # kilograms
              elsif gender == 'F'
                height += @female_growth.call # centimeters
                weight += @female_weight.call # kilograms
              end
            elsif age <= Synthea::Config.lifecycle.adult_max_weight_age
              # getting older and fatter
              range = Synthea::Config.lifecycle.adult_weight_gain
              adult_weight_gain = rand(range.first..range.last)
              weight += adult_weight_gain
            elsif age >= Synthea::Config.lifecycle.geriatric_weight_loss_age
              # getting older and wasting away
              range = Synthea::Config.lifecycle.geriatric_weight_loss
              geriatric_weight_loss = rand(range.first..range.last)
              weight -= geriatric_weight_loss
            end
            # set the BMI
            entity.set_vital_sign(:height, height, 'cm')
            entity.set_vital_sign(:weight, weight, 'kg')
            entity.set_vital_sign(:bmi, calculate_bmi(height, weight), 'kg/m2')
          end
        end
      end

      rule :diabetic_vital_signs, [:gender, :weight, :bmi], [:blood_glucose, :blood_pressure] do |_time, entity|
        range = case entity['diabetic_kidney_damage']
                when 1
                  Synthea::Config.metabolic.basic_panel.creatinine_clearance.mild_kidney_damage
                when 2
                  Synthea::Config.metabolic.basic_panel.creatinine_clearance.moderate_kidney_damage
                when 3
                  Synthea::Config.metabolic.basic_panel.creatinine_clearance.severe_kidney_damage
                when 4
                  Synthea::Config.metabolic.basic_panel.creatinine_clearance.esrd
                else
                  if entity[:gender] == 'F'
                    Synthea::Config.metabolic.basic_panel.creatinine_clearance.normal.female
                  else
                    Synthea::Config.metabolic.basic_panel.creatinine_clearance.normal.male
                  end
                end
        creatinine_clearance = rand(range.first..range.last)
        entity.set_vital_sign(:egfr, creatinine_clearance, 'mL/min/{1.73_m2}')

        creatinine = begin
                       reverse_calculate_creatine(entity, creatinine_clearance)
                     rescue
                       1.0
                     end
        entity.set_vital_sign(:creatinine, creatinine, 'mg/dL')

        bmi = entity.vital_sign(:bmi)
        if bmi
          bmi = bmi[:value]
          bloodglucose = blood_glucose(bmi, entity[:prediabetes], entity[:diabetes])

          # How much does A1C need to be lowered to get to goal?
          # Metformin and sulfonylureas may lower A1C 1.5 to 2 percentage points,
          # GLP-1 agonists and DPP-4 inhibitors 0.5 to 1 percentage point on average, and
          # insulin as much as 6 points or more, depending on where you start.
          # -- http://www.diabetesforecast.org/2013/mar/your-a1c-achieving-personal-blood-glucose-goals.html
          # [:metformin, :glp1ra, :sglt2i, :basal_insulin, :prandial_insulin]
          #     mono        bi      tri        insulin          insulin++
          record = entity.record_synthea

          bloodglucose -= 1.5 if record.medication_active?('24_hr_metformin_hydrochloride_500_mg_extended_release_oral_tablet'.to_sym)
          bloodglucose -= 0.5 if record.medication_active?('3_ml_liraglutide_6_mg/ml_pen_injector'.to_sym)
          bloodglucose -= 0.5 if record.medication_active?('canagliflozin_100_mg_oral_tablet'.to_sym)
          bloodglucose -= 3.0 if record.medication_active?('insulin_human,_isophane_70_unt/ml_/_regular_insulin,_human_30_unt/ml_injectable_suspension_[humulin]'.to_sym)
          bloodglucose -= 6.0 if record.medication_active?('insulin_lispro_100_unt/ml_injectable_solution_[humalog]'.to_sym)
          entity.set_vital_sign(:blood_glucose, bloodglucose.round(1), '%')
        end

        # estimate values
        if entity['hypertension']
          entity.set_vital_sign(:systolic_blood_pressure, pick(Synthea::Config.metabolic.blood_pressure.hypertensive.systolic), 'mmHg')
          entity.set_vital_sign(:diastolic_blood_pressure, pick(Synthea::Config.metabolic.blood_pressure.hypertensive.diastolic), 'mmHg')
        else
          entity.set_vital_sign(:systolic_blood_pressure, pick(Synthea::Config.metabolic.blood_pressure.normal.systolic), 'mmHg')
          entity.set_vital_sign(:diastolic_blood_pressure, pick(Synthea::Config.metabolic.blood_pressure.normal.diastolic), 'mmHg')
        end
        # calculate the components of a lipid panel
        index = 0
        index = 1 if entity['diabetes_severity']
        index = entity['diabetes_severity'] if entity['diabetes_severity']
        cholesterol = Synthea::Config.metabolic.lipid_panel.cholesterol
        triglycerides = Synthea::Config.metabolic.lipid_panel.triglycerides
        hdl = Synthea::Config.metabolic.lipid_panel.hdl

        entity.set_vital_sign(:total_cholesterol, rand(cholesterol[index]..cholesterol[index + 1]), 'mg/dL')
        entity.set_vital_sign(:triglycerides, rand(triglycerides[index]..triglycerides[index + 1]), 'mg/dL')
        entity.set_vital_sign(:hdl, rand(hdl[index + 1]..hdl[index]), 'mg/dL')

        ldl = entity.get_vital_sign_value(:total_cholesterol) - entity.get_vital_sign_value(:hdl) - (0.2 * entity.get_vital_sign_value(:triglycerides))
        entity.set_vital_sign(:ldl, ldl.to_i, 'mg/dL')

        # calculate the components of a metabolic panel and associated observations
        normal = Synthea::Config.metabolic.basic_panel.normal
        metabolic_panel = {
          urea_nitrogen: rand(normal.urea_nitrogen.first..normal.urea_nitrogen.last),
          creatinine: rand(normal.creatinine.first..normal.creatinine.last),
          calcium: rand(normal.calcium.first..normal.calcium.last)
        }

        electrolytes_panel = {
          chloride: rand(normal.chloride.first..normal.chloride.last),
          potassium: rand(normal.potassium.first..normal.potassium.last),
          carbon_dioxide: rand(normal.co2.first..normal.co2.last),
          sodium: rand(normal.sodium.first..normal.sodium.last)
        }

        # calculate glucose out of the normal
        glucose = Synthea::Config.metabolic.basic_panel.glucose
        index = 2 if index > 2
        metabolic_panel[:glucose] = rand(glucose[index]..glucose[index + 1])
        # calculate creatine values
        range = nil
        if entity[:gender] && entity[:gender] == 'M'
          range = Synthea::Config.metabolic.basic_panel.creatinine_clearance.normal.male
        else
          range = Synthea::Config.metabolic.basic_panel.creatinine_clearance.normal.female
        end
        creatinine_clearance = rand(range.first..range.last)
        metabolic_panel[:creatinine] = begin
                                         reverse_calculate_creatine(entity, creatinine_clearance)
                                       rescue
                                         1.0
                                       end
        range = Synthea::Config.metabolic.basic_panel.microalbumin_creatine_ratio.normal
        entity.set_vital_sign(:microalbumin_creatine_ratio, rand(range.first..range.last), 'mg/g')
        if creatinine_clearance > 60
          entity.set_vital_sign(:egfr, '>60', 'mL/min/{1.73_m2}')
        else
          entity.set_vital_sign(:egfr, creatinine_clearance, 'mL/min/{1.73_m2}')
        end

        metabolic_panel.each { |k, v| entity.set_vital_sign(k, v, 'mg/dL') }<commit_msg>Change 60 from string to number because it breaks FHIR exporter which can only handle quantities.<commit_after>module Synthea
  module Modules
    class Lifecycle < Synthea::Rules
      attr_accessor :male_growth, :male_weight, :female_growth, :female_weight
      attr_accessor :races, :ethnicity, :blood_types

      def initialize
        super
        @male_growth = Synthea::Utils::Distribution.normal(Synthea::Config.lifecycle.growth_rate_male_average, Synthea::Config.lifecycle.growth_rate_male_stddev)
        @male_weight = Synthea::Utils::Distribution.normal(Synthea::Config.lifecycle.weight_gain_male_average, Synthea::Config.lifecycle.weight_gain_male_stddev)
        @female_growth = Synthea::Utils::Distribution.normal(Synthea::Config.lifecycle.growth_rate_female_average, Synthea::Config.lifecycle.growth_rate_female_stddev)
        @female_weight = Synthea::Utils::Distribution.normal(Synthea::Config.lifecycle.weight_gain_female_average, Synthea::Config.lifecycle.weight_gain_female_stddev)
      end

      # People are born
      rule :birth, [], [:age] do |time, entity|
        unless entity.had_event?(:birth)
          entity[:age] = 0
          entity[:name_first] = Faker::Name.first_name
          entity[:name_last] = Faker::Name.last_name
          if Synthea::Config.population.append_hash_to_person_names == true
            entity[:name_first] = "#{entity[:name_first]}#{(entity[:name_first].hash % 999)}"
            entity[:name_last] = "#{entity[:name_last]}#{(entity[:name_last].hash % 999)}"
          end
          entity[:gender] ||= gender
          entity[:race] ||= Synthea::World::Demographics::RACES.pick
          entity[:ethnicity] ||= Synthea::World::Demographics::ETHNICITY[entity[:race]].pick
          entity[:blood_type] = Synthea::World::Demographics::BLOOD_TYPES[entity[:race]].pick
          entity[:sexual_orientation] = Synthea::World::Demographics::SEXUAL_ORIENTATION.pick.to_s
          entity[:fingerprint] = Synthea::Fingerprint.generate if Synthea::Config.population.generate_fingerprints
          entity[:first_language] ||= Synthea::World::Demographics::LANGUAGES_BY_ETHNICITY[entity[:ethnicity]].pick

          # new babies are average weight and length for American newborns
          entity.set_vital_sign(:height, 51.0, 'cm')
          entity.set_vital_sign(:weight, 3.5, 'kg')
          entity[:multiple_birth] = rand(3) + 1 if rand < Synthea::Config.lifecycle.prevalence_of_twins
          entity.events.create(time, :birth, :birth, true)
          entity.events.create(time, :encounter, :birth)
          entity.events.create(time, :symptoms_cause_encounter, :birth)

          # determine lat/long coordinates of address within MA
          location_data = Synthea::Location.select_point(entity[:city])
          entity[:coordinates_address] = location_data['point']
          zip_code = Synthea::Location.get_zipcode(location_data['city'])
          entity[:address] = {
            'line' => [Faker::Address.street_address],
            'city' => location_data['city'],
            'state' => 'MA',
            'postalCode' => zip_code,
            'country' => 'US'
          }
          entity[:address]['line'] << Faker::Address.secondary_address if rand < 0.5
          entity[:city] = location_data['city']

          # telephone
          entity[:telephone] = Faker::PhoneNumber.phone_number

          # birthplace
          entity[:birth_place] = {
            'city' => Synthea::Location.select_point['city'],
            'state' => 'MA',
            'country' => 'US'
          }

          # parents
          mothers_name = Faker::Name.first_name
          mothers_name = "#{mothers_name}#{(mothers_name.hash % 999)}" if Synthea::Config.population.append_hash_to_person_names == true

          mothers_surname = Faker::Name.last_name
          mothers_surname = "#{mothers_surname}#{(mothers_surname.hash % 999)}" if Synthea::Config.population.append_hash_to_person_names == true
          entity[:name_mother] = "#{mothers_name} #{mothers_surname}"

          fathers_name = Faker::Name.first_name
          fathers_name = "#{fathers_name}#{(fathers_name.hash % 999)}" if Synthea::Config.population.append_hash_to_person_names == true
          entity[:name_father] = "#{fathers_name} #{entity[:name_last]}"

          # identifiers
          entity[:identifier_ssn] = "999-#{rand(10..99)}-#{rand(1000..9999)}"

          entity[:med_changes] = Hash.new { |hsh, key| hsh[key] = [] }
          choose_socioeconomic_values(entity)
        end
      end

      # People age
      rule :age, [:birth, :age], [:age] do |time, entity|
        if entity.alive?(time)
          birthdate = entity.event(:birth).time
          age = entity[:age]
          entity[:age] = ((time.to_i - birthdate.to_i) / 1.year).floor
          if entity[:age] > age
            dt = nil
            begin
              dt = DateTime.new(time.year, birthdate.month, birthdate.mday, birthdate.hour, birthdate.min, birthdate.sec, birthdate.formatted_offset)
            rescue StandardError
              # this person was born on a leap-day
              dt = time
            end
            entity.events.create(dt.to_time, :grow, :age)
          end
          # stuff happens when you're an adult
          if entity[:age] == 16
            # you get a driver's license
            entity[:identifier_drivers] = "S999#{rand(10_000..99_999)}" unless entity[:identifier_drivers]
          elsif entity[:age] == 18
            # you get respect
            if entity[:gender] == 'M'
              entity[:name_prefix] = 'Mr.' unless entity[:name_prefix]
            else
              entity[:name_prefix] = 'Ms.' unless entity[:name_prefix]
            end
          elsif entity[:age] == 20 && entity[:identifier_passport].nil?
            # you might get a passport
            entity[:identifier_passport] = (rand(0..1) == 1)
            entity[:identifier_passport] = "X#{rand(10_000_000..99_999_999)}X" if entity[:identifier_passport]
          elsif entity[:age] == 27 && !entity[:marital_status] # median age of marriage (26 for women, 28 for men)
            if rand < 0.8
              # you might get married
              entity[:marital_status] = 'M'
              if entity[:gender] == 'F'
                entity[:name_prefix] = 'Mrs.'
                entity[:name_maiden] = entity[:name_last]
                entity[:name_last] = Faker::Name.last_name
                entity[:name_last] = "#{entity[:name_last]}#{(entity[:name_last].hash % 999)}"
              end
            else
              entity[:marital_status] = 'S'
            end
            # this doesn't account for divorces or widows right now
          elsif entity[:age] == 30
            # you might get overeducated
            entity[:name_suffix] = %w(PhD JD MD).sample if entity[:ses] && entity[:ses][:education] >= 0.95 && !entity[:name_suffix]
          end
        end
      end

      # People grow
      rule :grow, [:age, :gender], [:height, :weight, :bmi] do |time, entity|
        # Assume a linear growth rate until average size is achieved at age 20
        # TODO consider genetics, social determinants of health, etc
        if entity.alive?(time)
          unprocessed_events = entity.events.unprocessed.select { |e| e.type == :grow }
          unprocessed_events.each do |event|
            entity.events.process(event)
            age = entity[:age]
            gender = entity[:gender]
            height = entity.get_vital_sign_value(:height)
            weight = entity.get_vital_sign_value(:weight)
            if age <= 20
              if gender == 'M'
                height += @male_growth.call # centimeters
                weight += @male_weight.call # kilograms
              elsif gender == 'F'
                height += @female_growth.call # centimeters
                weight += @female_weight.call # kilograms
              end
            elsif age <= Synthea::Config.lifecycle.adult_max_weight_age
              # getting older and fatter
              range = Synthea::Config.lifecycle.adult_weight_gain
              adult_weight_gain = rand(range.first..range.last)
              weight += adult_weight_gain
            elsif age >= Synthea::Config.lifecycle.geriatric_weight_loss_age
              # getting older and wasting away
              range = Synthea::Config.lifecycle.geriatric_weight_loss
              geriatric_weight_loss = rand(range.first..range.last)
              weight -= geriatric_weight_loss
            end
            # set the BMI
            entity.set_vital_sign(:height, height, 'cm')
            entity.set_vital_sign(:weight, weight, 'kg')
            entity.set_vital_sign(:bmi, calculate_bmi(height, weight), 'kg/m2')
          end
        end
      end

      rule :diabetic_vital_signs, [:gender, :weight, :bmi], [:blood_glucose, :blood_pressure] do |_time, entity|
        range = case entity['diabetic_kidney_damage']
                when 1
                  Synthea::Config.metabolic.basic_panel.creatinine_clearance.mild_kidney_damage
                when 2
                  Synthea::Config.metabolic.basic_panel.creatinine_clearance.moderate_kidney_damage
                when 3
                  Synthea::Config.metabolic.basic_panel.creatinine_clearance.severe_kidney_damage
                when 4
                  Synthea::Config.metabolic.basic_panel.creatinine_clearance.esrd
                else
                  if entity[:gender] == 'F'
                    Synthea::Config.metabolic.basic_panel.creatinine_clearance.normal.female
                  else
                    Synthea::Config.metabolic.basic_panel.creatinine_clearance.normal.male
                  end
                end
        creatinine_clearance = rand(range.first..range.last)
        entity.set_vital_sign(:egfr, creatinine_clearance, 'mL/min/{1.73_m2}')

        creatinine = begin
                       reverse_calculate_creatine(entity, creatinine_clearance)
                     rescue
                       1.0
                     end
        entity.set_vital_sign(:creatinine, creatinine, 'mg/dL')

        bmi = entity.vital_sign(:bmi)
        if bmi
          bmi = bmi[:value]
          bloodglucose = blood_glucose(bmi, entity[:prediabetes], entity[:diabetes])

          # How much does A1C need to be lowered to get to goal?
          # Metformin and sulfonylureas may lower A1C 1.5 to 2 percentage points,
          # GLP-1 agonists and DPP-4 inhibitors 0.5 to 1 percentage point on average, and
          # insulin as much as 6 points or more, depending on where you start.
          # -- http://www.diabetesforecast.org/2013/mar/your-a1c-achieving-personal-blood-glucose-goals.html
          # [:metformin, :glp1ra, :sglt2i, :basal_insulin, :prandial_insulin]
          #     mono        bi      tri        insulin          insulin++
          record = entity.record_synthea

          bloodglucose -= 1.5 if record.medication_active?('24_hr_metformin_hydrochloride_500_mg_extended_release_oral_tablet'.to_sym)
          bloodglucose -= 0.5 if record.medication_active?('3_ml_liraglutide_6_mg/ml_pen_injector'.to_sym)
          bloodglucose -= 0.5 if record.medication_active?('canagliflozin_100_mg_oral_tablet'.to_sym)
          bloodglucose -= 3.0 if record.medication_active?('insulin_human,_isophane_70_unt/ml_/_regular_insulin,_human_30_unt/ml_injectable_suspension_[humulin]'.to_sym)
          bloodglucose -= 6.0 if record.medication_active?('insulin_lispro_100_unt/ml_injectable_solution_[humalog]'.to_sym)
          entity.set_vital_sign(:blood_glucose, bloodglucose.round(1), '%')
        end

        # estimate values
        if entity['hypertension']
          entity.set_vital_sign(:systolic_blood_pressure, pick(Synthea::Config.metabolic.blood_pressure.hypertensive.systolic), 'mmHg')
          entity.set_vital_sign(:diastolic_blood_pressure, pick(Synthea::Config.metabolic.blood_pressure.hypertensive.diastolic), 'mmHg')
        else
          entity.set_vital_sign(:systolic_blood_pressure, pick(Synthea::Config.metabolic.blood_pressure.normal.systolic), 'mmHg')
          entity.set_vital_sign(:diastolic_blood_pressure, pick(Synthea::Config.metabolic.blood_pressure.normal.diastolic), 'mmHg')
        end
        # calculate the components of a lipid panel
        index = 0
        index = 1 if entity['diabetes_severity']
        index = entity['diabetes_severity'] if entity['diabetes_severity']
        cholesterol = Synthea::Config.metabolic.lipid_panel.cholesterol
        triglycerides = Synthea::Config.metabolic.lipid_panel.triglycerides
        hdl = Synthea::Config.metabolic.lipid_panel.hdl

        entity.set_vital_sign(:total_cholesterol, rand(cholesterol[index]..cholesterol[index + 1]), 'mg/dL')
        entity.set_vital_sign(:triglycerides, rand(triglycerides[index]..triglycerides[index + 1]), 'mg/dL')
        entity.set_vital_sign(:hdl, rand(hdl[index + 1]..hdl[index]), 'mg/dL')

        ldl = entity.get_vital_sign_value(:total_cholesterol) - entity.get_vital_sign_value(:hdl) - (0.2 * entity.get_vital_sign_value(:triglycerides))
        entity.set_vital_sign(:ldl, ldl.to_i, 'mg/dL')

        # calculate the components of a metabolic panel and associated observations
        normal = Synthea::Config.metabolic.basic_panel.normal
        metabolic_panel = {
          urea_nitrogen: rand(normal.urea_nitrogen.first..normal.urea_nitrogen.last),
          creatinine: rand(normal.creatinine.first..normal.creatinine.last),
          calcium: rand(normal.calcium.first..normal.calcium.last)
        }

        electrolytes_panel = {
          chloride: rand(normal.chloride.first..normal.chloride.last),
          potassium: rand(normal.potassium.first..normal.potassium.last),
          carbon_dioxide: rand(normal.co2.first..normal.co2.last),
          sodium: rand(normal.sodium.first..normal.sodium.last)
        }

        # calculate glucose out of the normal
        glucose = Synthea::Config.metabolic.basic_panel.glucose
        index = 2 if index > 2
        metabolic_panel[:glucose] = rand(glucose[index]..glucose[index + 1])
        # calculate creatine values
        range = nil
        if entity[:gender] && entity[:gender] == 'M'
          range = Synthea::Config.metabolic.basic_panel.creatinine_clearance.normal.male
        else
          range = Synthea::Config.metabolic.basic_panel.creatinine_clearance.normal.female
        end
        creatinine_clearance = rand(range.first..range.last)
        metabolic_panel[:creatinine] = begin
                                         reverse_calculate_creatine(entity, creatinine_clearance)
                                       rescue
                                         1.0
                                       end
        range = Synthea::Config.metabolic.basic_panel.microalbumin_creatine_ratio.normal
        entity.set_vital_sign(:microalbumin_creatine_ratio, rand(range.first..range.last), 'mg/g')
        if creatinine_clearance > 60
          entity.set_vital_sign(:egfr, 60, 'mL/min/{1.73_m2}')
        else
          entity.set_vital_sign(:egfr, creatinine_clearance, 'mL/min/{1.73_m2}')
        end

        metabolic_panel.each { |k, v| entity.set_vital_sign(k, v, 'mg/dL') }
{'fraction_non_alphanumeric': 0.11160493827160493, 'fraction_numerical': 0.012049382716049382, 'mean_word_length': 2.4910929778186417, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 30, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7453032', 'n_tokens_mistral': 9980, 'n_tokens_neox': 8901, 'n_words': 1565}