cehrbert-data 0.0.4__tar.gz → 0.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/.github/workflows/python-build.yml +2 -2
  2. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/.github/workflows/tests.yml +5 -0
  3. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/PKG-INFO +2 -2
  4. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/apps/generate_training_data.py +22 -2
  5. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/spark_app_base.py +76 -30
  6. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/decorators/artificial_time_token_decorator.py +100 -32
  7. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/decorators/clinical_event_decorator.py +45 -36
  8. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/decorators/death_event_decorator.py +17 -10
  9. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/decorators/demographic_event_decorator.py +38 -8
  10. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/decorators/patient_event_decorator_base.py +26 -2
  11. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/decorators/token_priority.py +1 -0
  12. cehrbert_data-0.0.5/src/cehrbert_data/tools/connect_omop_visit.py +288 -0
  13. cehrbert_data-0.0.5/src/cehrbert_data/tools/ehrshot_to_omop.py +952 -0
  14. cehrbert_data-0.0.5/src/cehrbert_data/tools/extract_features.py +302 -0
  15. cehrbert_data-0.0.5/src/cehrbert_data/tools/prepare_ehrshot_cohorts.py +22 -0
  16. cehrbert_data-0.0.5/src/cehrbert_data/tools/update_omop_visit.py +60 -0
  17. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/utils/spark_parse_args.py +28 -2
  18. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/utils/spark_utils.py +85 -41
  19. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data.egg-info/PKG-INFO +2 -2
  20. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data.egg-info/SOURCES.txt +7 -0
  21. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/tests/integration_tests/test_generate_training_data.py +2 -0
  22. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/tests/integration_tests/test_hf_readmission.py +2 -0
  23. cehrbert_data-0.0.5/tests/integration_tests/test_hf_readmission_cohort_meds.py +43 -0
  24. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/tests/pyspark_test_base.py +12 -2
  25. cehrbert_data-0.0.5/tests/unit_tests/test_ehrshot_to_omop.py +353 -0
  26. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/.gitignore +0 -0
  27. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/.pre-commit-config.yaml +0 -0
  28. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/LICENSE +0 -0
  29. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/README.md +0 -0
  30. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/pyproject.toml +0 -0
  31. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept/._SUCCESS.crc +0 -0
  32. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept/.part-00000-4b12270c-f6c8-4b59-8e0f-fd588bd79386-c000.snappy.parquet.crc +0 -0
  33. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept/.part-00003-4b12270c-f6c8-4b59-8e0f-fd588bd79386-c000.snappy.parquet.crc +0 -0
  34. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept/.part-00010-4b12270c-f6c8-4b59-8e0f-fd588bd79386-c000.snappy.parquet.crc +0 -0
  35. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept/_SUCCESS +0 -0
  36. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept/part-00000-4b12270c-f6c8-4b59-8e0f-fd588bd79386-c000.snappy.parquet +0 -0
  37. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept/part-00003-4b12270c-f6c8-4b59-8e0f-fd588bd79386-c000.snappy.parquet +0 -0
  38. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept/part-00010-4b12270c-f6c8-4b59-8e0f-fd588bd79386-c000.snappy.parquet +0 -0
  39. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/._SUCCESS.crc +0 -0
  40. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/.part-00000-eafbd8be-3337-46da-89d3-20f79c2565d4-c000.snappy.parquet.crc +0 -0
  41. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/.part-00002-eafbd8be-3337-46da-89d3-20f79c2565d4-c000.snappy.parquet.crc +0 -0
  42. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/.part-00006-eafbd8be-3337-46da-89d3-20f79c2565d4-c000.snappy.parquet.crc +0 -0
  43. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/.part-00011-eafbd8be-3337-46da-89d3-20f79c2565d4-c000.snappy.parquet.crc +0 -0
  44. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/.part-00013-eafbd8be-3337-46da-89d3-20f79c2565d4-c000.snappy.parquet.crc +0 -0
  45. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/_SUCCESS +0 -0
  46. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/part-00000-eafbd8be-3337-46da-89d3-20f79c2565d4-c000.snappy.parquet +0 -0
  47. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/part-00002-eafbd8be-3337-46da-89d3-20f79c2565d4-c000.snappy.parquet +0 -0
  48. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/part-00006-eafbd8be-3337-46da-89d3-20f79c2565d4-c000.snappy.parquet +0 -0
  49. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/part-00011-eafbd8be-3337-46da-89d3-20f79c2565d4-c000.snappy.parquet +0 -0
  50. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_ancestor/part-00013-eafbd8be-3337-46da-89d3-20f79c2565d4-c000.snappy.parquet +0 -0
  51. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_relationship/._SUCCESS.crc +0 -0
  52. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_relationship/.part-00000-5752b472-8ba7-4189-ab69-8c92e46443aa-c000.snappy.parquet.crc +0 -0
  53. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_relationship/.part-00002-5752b472-8ba7-4189-ab69-8c92e46443aa-c000.snappy.parquet.crc +0 -0
  54. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_relationship/.part-00007-5752b472-8ba7-4189-ab69-8c92e46443aa-c000.snappy.parquet.crc +0 -0
  55. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_relationship/.part-00012-5752b472-8ba7-4189-ab69-8c92e46443aa-c000.snappy.parquet.crc +0 -0
  56. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_relationship/_SUCCESS +0 -0
  57. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_relationship/part-00000-5752b472-8ba7-4189-ab69-8c92e46443aa-c000.snappy.parquet +0 -0
  58. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_relationship/part-00002-5752b472-8ba7-4189-ab69-8c92e46443aa-c000.snappy.parquet +0 -0
  59. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_relationship/part-00007-5752b472-8ba7-4189-ab69-8c92e46443aa-c000.snappy.parquet +0 -0
  60. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/concept_relationship/part-00012-5752b472-8ba7-4189-ab69-8c92e46443aa-c000.snappy.parquet +0 -0
  61. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/condition_occurrence/._SUCCESS.crc +0 -0
  62. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/condition_occurrence/.part-00000-4eff03a1-cdcf-4c89-b0cd-9ce590b9b1eb-c000.snappy.parquet.crc +0 -0
  63. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/condition_occurrence/_SUCCESS +0 -0
  64. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/condition_occurrence/part-00000-4eff03a1-cdcf-4c89-b0cd-9ce590b9b1eb-c000.snappy.parquet +0 -0
  65. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/drug_exposure/._SUCCESS.crc +0 -0
  66. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/drug_exposure/.part-00000-10bbf1a4-a7da-416e-9703-58609c7edfad-c000.snappy.parquet.crc +0 -0
  67. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/drug_exposure/_SUCCESS +0 -0
  68. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/drug_exposure/part-00000-10bbf1a4-a7da-416e-9703-58609c7edfad-c000.snappy.parquet +0 -0
  69. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/observation_period/._SUCCESS.crc +0 -0
  70. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/observation_period/.part-00000-694316e5-cc95-49f1-9fad-5a7f377e2602-c000.snappy.parquet.crc +0 -0
  71. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/observation_period/_SUCCESS +0 -0
  72. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/observation_period/part-00000-694316e5-cc95-49f1-9fad-5a7f377e2602-c000.snappy.parquet +0 -0
  73. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/person/._SUCCESS.crc +0 -0
  74. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/person/.part-00000-7d789011-f361-48da-af6f-cfe102978b3a-c000.snappy.parquet.crc +0 -0
  75. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/person/_SUCCESS +0 -0
  76. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/person/part-00000-7d789011-f361-48da-af6f-cfe102978b3a-c000.snappy.parquet +0 -0
  77. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/procedure_occurrence/._SUCCESS.crc +0 -0
  78. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/procedure_occurrence/.part-00000-e73003c1-aed5-41c0-b2d4-eaccaccf044a-c000.snappy.parquet.crc +0 -0
  79. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/procedure_occurrence/_SUCCESS +0 -0
  80. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/procedure_occurrence/part-00000-e73003c1-aed5-41c0-b2d4-eaccaccf044a-c000.snappy.parquet +0 -0
  81. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/visit_occurrence/._SUCCESS.crc +0 -0
  82. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/visit_occurrence/.part-00000-e874b5f1-bf9e-4cb9-93bf-c309a47b0476-c000.snappy.parquet.crc +0 -0
  83. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/visit_occurrence/_SUCCESS +0 -0
  84. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/sample_data/omop_sample/visit_occurrence/part-00000-e874b5f1-bf9e-4cb9-93bf-c309a47b0476-c000.snappy.parquet +0 -0
  85. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/setup.cfg +0 -0
  86. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/__init__.py +0 -0
  87. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/__init__.py +0 -0
  88. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/apps/__init__.py +0 -0
  89. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/apps/generate_concept_similarity_table.py +0 -0
  90. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/apps/generate_hierarchical_bert_training_data.py +0 -0
  91. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/apps/generate_included_concept_list.py +0 -0
  92. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/apps/generate_information_content.py +0 -0
  93. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/apps/generate_required_labs.py +0 -0
  94. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/__init__.py +0 -0
  95. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/atrial_fibrillation.py +0 -0
  96. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/cabg.py +0 -0
  97. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/coronary_artery_disease.py +0 -0
  98. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/covid.py +0 -0
  99. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/covid_inpatient.py +0 -0
  100. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/death.py +0 -0
  101. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/heart_failure.py +0 -0
  102. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/ischemic_stroke.py +0 -0
  103. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/last_visit_discharged_home.py +0 -0
  104. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/query_builder.py +0 -0
  105. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/type_two_diabietes.py +0 -0
  106. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/cohorts/ventilation.py +0 -0
  107. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/config/__init__.py +0 -0
  108. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/config/output_names.py +0 -0
  109. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/const/__init__.py +0 -0
  110. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/const/__pycache__/__init__.cpython-311.pyc +0 -0
  111. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/const/__pycache__/common.cpython-311.pyc +0 -0
  112. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/const/artificial_tokens.py +0 -0
  113. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/const/common.py +0 -0
  114. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/decorators/__init__.py +0 -0
  115. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/__init__.py +0 -0
  116. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/afib_ischemic_stroke.py +0 -0
  117. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/cad_cabg_cohort.py +0 -0
  118. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/cad_hf_cohort.py +0 -0
  119. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/copd_readmission.py +0 -0
  120. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/covid_death.py +0 -0
  121. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/covid_ventilation.py +0 -0
  122. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/discharge_home_death.py +0 -0
  123. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/hf_readmission.py +0 -0
  124. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/hospitalization.py +0 -0
  125. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/hospitalization_mortality.py +0 -0
  126. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/prediction_cohorts/t2dm_hf_cohort.py +0 -0
  127. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/queries/__init__.py +0 -0
  128. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/queries/measurement_queries.py +0 -0
  129. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/tools/__init__.py +0 -0
  130. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/tools/download_omop_tables.py +0 -0
  131. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/utils/__init__.py +0 -0
  132. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data/utils/logging_utils.py +0 -0
  133. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data.egg-info/dependency_links.txt +0 -0
  134. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data.egg-info/requires.txt +0 -0
  135. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/src/cehrbert_data.egg-info/top_level.txt +0 -0
  136. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/tests/__init__.py +0 -0
  137. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/tests/integration_tests/__init__.py +0 -0
  138. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/tests/unit_tests/__init__.py +0 -0
  139. {cehrbert_data-0.0.4 → cehrbert_data-0.0.5}/tests/unit_tests/test_spark_utils.py +0 -0
@@ -68,7 +68,7 @@ jobs:
68
68
  name: python-package-distributions
69
69
  path: dist/
70
70
  - name: Sign the dists with Sigstore
71
- uses: sigstore/gh-action-sigstore-python@v2.1.1
71
+ uses: sigstore/gh-action-sigstore-python@v3.0.0
72
72
  with:
73
73
  inputs: >-
74
74
  ./dist/*.tar.gz
@@ -90,4 +90,4 @@ jobs:
90
90
  run: >-
91
91
  gh release upload
92
92
  '${{ github.ref_name }}' dist/**
93
- --repo '${{ github.repository }}'
93
+ --repo '${{ github.repository }}'
@@ -23,6 +23,11 @@ jobs:
23
23
  uses: actions/setup-python@v3
24
24
  with:
25
25
  python-version: "3.10"
26
+ - name: Set up Java 11
27
+ uses: actions/setup-java@v3
28
+ with:
29
+ java-version: "11" # specify the Java version here
30
+ distribution: "temurin" # or use 'adopt' or 'zulu', depending on your preference
26
31
  - name: Install dependencies
27
32
  run: |
28
33
  python -m pip install --upgrade pip
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: cehrbert_data
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: The Spark ETL tools for generating the CEHR-BERT and CEHR-GPT pre-training and finetuning data
5
5
  Author-email: Chao Pang <chaopang229@gmail.com>, Xinzhuo Jiang <xj2193@cumc.columbia.edu>, Krishna Kalluri <kk3326@cumc.columbia.edu>, Nishanth Parameshwar Pavinkurve <np2689@cumc.columbia.edu>, Karthik Natarajan <kn2174@cumc.columbia.edu>
6
6
  License: MIT License
@@ -41,13 +41,15 @@ def main(
41
41
  apply_age_filter: bool,
42
42
  include_death: bool,
43
43
  att_type: AttType,
44
+ inpatient_att_type: AttType,
44
45
  include_sequence_information_content: bool = False,
45
46
  exclude_demographic: bool = False,
46
47
  use_age_group: bool = False,
47
48
  with_drug_rollup: bool = True,
48
49
  include_inpatient_hour_token: bool = False,
49
50
  continue_from_events: bool = False,
50
- refresh_measurement: bool = False
51
+ refresh_measurement: bool = False,
52
+ aggregate_by_hour: bool = True,
51
53
  ):
52
54
  spark = SparkSession.builder.appName("Generate CEHR-BERT Training Data").getOrCreate()
53
55
 
@@ -71,6 +73,7 @@ def main(
71
73
  f"use_age_group: {use_age_group}\n"
72
74
  f"with_drug_rollup: {with_drug_rollup}\n"
73
75
  f"refresh_measurement: {refresh_measurement}\n"
76
+ f"aggregate_by_hour: {aggregate_by_hour}\n"
74
77
  )
75
78
 
76
79
  domain_tables = []
@@ -128,7 +131,8 @@ def main(
128
131
  processed_measurement = get_measurement_table(
129
132
  spark,
130
133
  input_folder,
131
- refresh=refresh_measurement
134
+ refresh=refresh_measurement,
135
+ aggregate_by_hour=aggregate_by_hour,
132
136
  )
133
137
  if patient_events:
134
138
  # Union all measurement records together with other domain records
@@ -165,9 +169,12 @@ def main(
165
169
  patient_demographic=person if gpt_patient_sequence else None,
166
170
  death=death,
167
171
  att_type=att_type,
172
+ inpatient_att_type=inpatient_att_type,
168
173
  exclude_demographic=exclude_demographic,
169
174
  use_age_group=use_age_group,
170
175
  include_inpatient_hour_token=include_inpatient_hour_token,
176
+ spark=spark,
177
+ persistence_folder=output_folder,
171
178
  )
172
179
  else:
173
180
  sequence_data = create_sequence_data(
@@ -325,12 +332,23 @@ if __name__ == "__main__":
325
332
  dest="refresh_measurement",
326
333
  action="store_true"
327
334
  )
335
+ parser.add_argument(
336
+ "--aggregate_by_hour",
337
+ dest="aggregate_by_hour",
338
+ action="store_true"
339
+ )
328
340
  parser.add_argument(
329
341
  "--att_type",
330
342
  dest="att_type",
331
343
  action="store",
332
344
  choices=[e.value for e in AttType],
333
345
  )
346
+ parser.add_argument(
347
+ "--inpatient_att_type",
348
+ dest="inpatient_att_type",
349
+ action="store",
350
+ choices=[e.value for e in AttType],
351
+ )
334
352
 
335
353
  ARGS = parser.parse_args()
336
354
 
@@ -352,10 +370,12 @@ if __name__ == "__main__":
352
370
  ARGS.apply_age_filter,
353
371
  ARGS.include_death,
354
372
  AttType(ARGS.att_type),
373
+ AttType(ARGS.inpatient_att_type),
355
374
  exclude_demographic=ARGS.exclude_demographic,
356
375
  use_age_group=ARGS.use_age_group,
357
376
  with_drug_rollup=ARGS.with_drug_rollup,
358
377
  include_inpatient_hour_token=ARGS.include_inpatient_hour_token,
359
378
  continue_from_events=ARGS.continue_from_events,
360
379
  refresh_measurement=ARGS.refresh_measurement,
380
+ aggregate_by_hour=ARGS.aggregate_by_hour,
361
381
  )
@@ -295,6 +295,7 @@ class NestedCohortBuilder:
295
295
  is_drug_roll_up_concept: bool = True,
296
296
  include_concept_list: bool = True,
297
297
  refresh_measurement: bool = False,
298
+ aggregate_by_hour: bool = True,
298
299
  is_new_patient_representation: bool = False,
299
300
  gpt_patient_sequence: bool = False,
300
301
  is_hierarchical_bert: bool = False,
@@ -306,9 +307,12 @@ class NestedCohortBuilder:
306
307
  is_observation_window_unbounded: bool = False,
307
308
  is_population_estimation: bool = False,
308
309
  att_type: AttType = AttType.CEHR_BERT,
310
+ inpatient_att_type: AttType = AttType.MIX,
309
311
  exclude_demographic: bool = True,
310
312
  use_age_group: bool = False,
311
313
  single_contribution: bool = False,
314
+ exclude_features: bool = True,
315
+ meds_format: bool = False,
312
316
  ):
313
317
  self._cohort_name = cohort_name
314
318
  self._input_folder = input_folder
@@ -340,15 +344,19 @@ class NestedCohortBuilder:
340
344
  self._is_prediction_window_unbounded = is_prediction_window_unbounded
341
345
  self._include_concept_list = include_concept_list
342
346
  self._refresh_measurement = refresh_measurement
347
+ self._aggregate_by_hour = aggregate_by_hour
343
348
  self._allow_measurement_only = allow_measurement_only
344
349
  self._output_data_folder = os.path.join(
345
350
  self._output_folder, re.sub("[^a-z0-9]+", "_", self._cohort_name.lower())
346
351
  )
347
352
  self._is_population_estimation = is_population_estimation
348
353
  self._att_type = att_type
354
+ self._inpatient_att_type = inpatient_att_type
349
355
  self._exclude_demographic = exclude_demographic
350
356
  self._use_age_group = use_age_group
351
357
  self._single_contribution = single_contribution
358
+ self._exclude_features = exclude_features
359
+ self._meds_format = meds_format
352
360
 
353
361
  self.get_logger().info(
354
362
  f"cohort_name: {cohort_name}\n"
@@ -377,12 +385,16 @@ class NestedCohortBuilder:
377
385
  f"is_prediction_window_unbounded: {is_prediction_window_unbounded}\n"
378
386
  f"include_concept_list: {include_concept_list}\n"
379
387
  f"refresh_measurement: {refresh_measurement}\n"
388
+ f"aggregate_by_hour: {aggregate_by_hour}\n"
380
389
  f"is_observation_window_unbounded: {is_observation_window_unbounded}\n"
381
390
  f"is_population_estimation: {is_population_estimation}\n"
382
391
  f"att_type: {att_type}\n"
392
+ f"inpatient_att_type: {inpatient_att_type}\n"
383
393
  f"exclude_demographic: {exclude_demographic}\n"
384
394
  f"use_age_group: {use_age_group}\n"
385
395
  f"single_contribution: {single_contribution}\n"
396
+ f"extract_features: {exclude_features}\n"
397
+ f"meds_format: {meds_format}\n"
386
398
  )
387
399
 
388
400
  self.spark = SparkSession.builder.appName(f"Generate {self._cohort_name}").getOrCreate()
@@ -508,13 +520,30 @@ class NestedCohortBuilder:
508
520
  )
509
521
  cohort = cohort.withColumn("record_rank", record_rank).where("record_rank == 1").drop("record_rank")
510
522
 
511
- ehr_records_for_cohorts = self.extract_ehr_records_for_cohort(cohort)
512
- # ehr_records_for_cohorts.show()
513
- cohort = (
514
- cohort.join(ehr_records_for_cohorts, ["person_id", "cohort_member_id"])
515
- .where(F.col("num_of_visits") >= self._num_of_visits)
516
- .where(F.col("num_of_concepts") >= self._num_of_concepts)
517
- )
523
+ if not self._exclude_features:
524
+ ehr_records_for_cohorts = self.extract_ehr_records_for_cohort(cohort)
525
+ cohort = (
526
+ cohort.join(ehr_records_for_cohorts, ["person_id", "cohort_member_id"])
527
+ .where(F.col("num_of_visits") >= self._num_of_visits)
528
+ .where(F.col("num_of_concepts") >= self._num_of_concepts)
529
+ )
530
+
531
+ person_id_column = "person_id"
532
+ index_date_column = "index_date"
533
+ if self._meds_format:
534
+ cohort = cohort.withColumnRenamed(
535
+ "person_id", "subject_id"
536
+ ).withColumnRenamed(
537
+ "index_date", "prediction_time"
538
+ ).withColumnRenamed(
539
+ "label", "boolean_value"
540
+ ).withColumn(
541
+ "prediction_time", F.to_timestamp("prediction_time")
542
+ ).withColumn(
543
+ "boolean_value", F.col("boolean_value").cast("boolean")
544
+ )
545
+ person_id_column = "subject_id"
546
+ index_date_column = "prediction_time"
518
547
 
519
548
  if self._is_prediction_window_unbounded:
520
549
  observation_period = self._dependency_dict[OBSERVATION_PERIOD]
@@ -529,9 +558,14 @@ class NestedCohortBuilder:
529
558
  # Add time_to_event
530
559
  cohort = cohort.withColumn(
531
560
  "study_end_date",
532
- F.coalesce(F.col("outcome_date"), F.date_add(cohort.index_date, self._prediction_window))
561
+ F.coalesce(
562
+ F.col("outcome_date"),
563
+ F.date_add(
564
+ cohort[index_date_column], self._prediction_window
565
+ )
566
+ )
533
567
  )
534
- cohort = cohort.withColumn("time_to_event", F.datediff("study_end_date", "index_date"))
568
+ cohort = cohort.withColumn("time_to_event", F.datediff("study_end_date", index_date_column))
535
569
 
536
570
  # if patient_splits is provided, we will
537
571
  if self._patient_splits_folder:
@@ -547,7 +581,7 @@ class NestedCohortBuilder:
547
581
  cohort.where('split="test"').write.mode("overwrite").parquet(os.path.join(self._output_data_folder, "test"))
548
582
  shutil.rmtree(os.path.join(self._output_data_folder, "temp"))
549
583
  else:
550
- cohort.orderBy("person_id", "cohort_member_id").write.mode("overwrite").parquet(self._output_data_folder)
584
+ cohort.orderBy(person_id_column, index_date_column).write.mode("overwrite").parquet(self._output_data_folder)
551
585
 
552
586
  def extract_ehr_records_for_cohort(self, cohort: DataFrame):
553
587
  """
@@ -566,47 +600,54 @@ class NestedCohortBuilder:
566
600
  with_drug_rollup=self._is_drug_roll_up_concept,
567
601
  include_concept_list=self._include_concept_list,
568
602
  refresh_measurement=self._refresh_measurement,
603
+ aggregate_by_hour=self._aggregate_by_hour,
569
604
  )
570
605
 
571
606
  # Duplicate the records for cohorts that allow multiple entries
572
- ehr_records = ehr_records.join(cohort, "person_id").select(
573
- [ehr_records[field_name] for field_name in ehr_records.schema.fieldNames()] + ["cohort_member_id"]
574
- )
607
+ ehr_records = ehr_records.alias("ehr").join(
608
+ cohort.alias("cohort"), F.col("ehr.person_id") == F.col("cohort.person_id")
609
+ ).select(
610
+ [F.col("ehr." + col) for col in ehr_records.columns] + [F.col("cohort.cohort_member_id")]
611
+ ).selectExpr("*")
575
612
 
576
613
  # Only allow the data records that occurred between the index date and the prediction window
577
614
  if self._is_population_estimation:
578
615
  if self._is_prediction_window_unbounded:
579
- record_window_filter = ehr_records["date"] <= F.current_date()
616
+ record_window_filter = F.col("ehr.date") <= F.current_date()
580
617
  else:
581
- record_window_filter = ehr_records["date"] <= F.date_add(cohort["index_date"], self._prediction_window)
618
+ record_window_filter = F.col("ehr.date") <= F.date_add(
619
+ F.col("cohort.index_date"), self._prediction_window
620
+ )
582
621
  else:
583
622
  # For patient level prediction, we remove all records post index date
584
623
  if self._is_observation_post_index:
585
- record_window_filter = ehr_records["date"].between(
586
- cohort["index_date"],
587
- F.date_add(cohort["index_date"], self._observation_window),
624
+ record_window_filter = F.col("ehr.date").between(
625
+ F.col("cohort.index_date"),
626
+ F.date_add(F.col("cohort.index_date"), self._observation_window),
588
627
  )
589
628
  else:
590
629
  if self._is_observation_window_unbounded:
591
- record_window_filter = ehr_records["date"] <= F.date_sub(
592
- cohort["index_date"], self._hold_off_window
630
+ record_window_filter = F.col("ehr.date") <= F.date_sub(
631
+ F.col("cohort.index_date"), self._hold_off_window
593
632
  )
594
633
  else:
595
- record_window_filter = ehr_records["date"].between(
634
+ record_window_filter = F.col("ehr.date").between(
596
635
  F.date_sub(
597
- cohort["index_date"],
636
+ F.col("cohort.index_date"),
598
637
  self._observation_window + self._hold_off_window,
599
638
  ),
600
- F.date_sub(cohort["index_date"], self._hold_off_window),
639
+ F.date_sub(F.col("cohort.index_date"), self._hold_off_window),
601
640
  )
602
641
 
603
- cohort_ehr_records = (
604
- ehr_records.join(
605
- cohort,
606
- (ehr_records.person_id == cohort.person_id) & (ehr_records.cohort_member_id == cohort.cohort_member_id),
607
- )
608
- .where(record_window_filter)
609
- .select([ehr_records[field_name] for field_name in ehr_records.schema.fieldNames()])
642
+ # Somehow the dataframe join does not work without using the alias
643
+ cohort_ehr_records = ehr_records.alias("ehr").join(
644
+ cohort.alias("cohort"),
645
+ (F.col("ehr.person_id") == F.col("cohort.person_id")) &
646
+ (F.col("ehr.cohort_member_id") == F.col("cohort.cohort_member_id")),
647
+ ).where(
648
+ record_window_filter
649
+ ).select(
650
+ [F.col("ehr." + field_name) for field_name in ehr_records.schema.fieldNames()]
610
651
  )
611
652
 
612
653
  if self._is_hierarchical_bert:
@@ -647,6 +688,7 @@ class NestedCohortBuilder:
647
688
  exclude_visit_tokens=self._exclude_visit_tokens,
648
689
  patient_demographic=(patient_demographic if self._gpt_patient_sequence else None),
649
690
  att_type=self._att_type,
691
+ inpatient_att_type=self._inpatient_att_type,
650
692
  exclude_demographic=self._exclude_demographic,
651
693
  use_age_group=self._use_age_group,
652
694
  )
@@ -748,6 +790,7 @@ def create_prediction_cohort(
748
790
  is_drug_roll_up_concept=spark_args.is_drug_roll_up_concept,
749
791
  include_concept_list=spark_args.include_concept_list,
750
792
  refresh_measurement=spark_args.refresh_measurement,
793
+ aggregate_by_hour=spark_args.aggregate_by_hour,
751
794
  is_new_patient_representation=spark_args.is_new_patient_representation,
752
795
  gpt_patient_sequence=spark_args.gpt_patient_sequence,
753
796
  is_hierarchical_bert=spark_args.is_hierarchical_bert,
@@ -763,7 +806,10 @@ def create_prediction_cohort(
763
806
  is_observation_window_unbounded=spark_args.is_observation_window_unbounded,
764
807
  is_population_estimation=spark_args.is_population_estimation,
765
808
  att_type=AttType(spark_args.att_type),
809
+ inpatient_att_type=AttType(spark_args.inpatient_att_type),
766
810
  exclude_demographic=spark_args.exclude_demographic,
767
811
  use_age_group=spark_args.use_age_group,
768
812
  single_contribution=spark_args.single_contribution,
813
+ exclude_features=spark_args.exclude_features,
814
+ meds_format=spark_args.meds_format,
769
815
  ).build()
@@ -1,14 +1,11 @@
1
- from pyspark.sql import DataFrame, functions as F, types as T, Window as W
1
+ import os.path
2
+
3
+ from pyspark.sql import SparkSession, DataFrame, functions as F, types as T, Window as W
2
4
 
3
5
  from ..const.common import NA
4
6
  from ..const.artificial_tokens import VS_TOKEN, VE_TOKEN
5
7
  from .patient_event_decorator_base import (
6
- PatientEventDecorator, AttType,
7
- time_day_token,
8
- time_week_token,
9
- time_month_token,
10
- time_mix_token,
11
- time_token_func
8
+ PatientEventDecorator, AttType, get_att_function
12
9
  )
13
10
  from .token_priority import (
14
11
  ATT_TOKEN_PRIORITY,
@@ -16,6 +13,7 @@ from .token_priority import (
16
13
  VISIT_TYPE_TOKEN_PRIORITY,
17
14
  DISCHARGE_TOKEN_PRIORITY,
18
15
  VE_TOKEN_PRIORITY,
16
+ FIRST_VISIT_HOUR_TOKEN_PRIORITY,
19
17
  get_inpatient_token_priority,
20
18
  get_inpatient_att_token_priority
21
19
  )
@@ -28,20 +26,28 @@ class AttEventDecorator(PatientEventDecorator):
28
26
  include_visit_type,
29
27
  exclude_visit_tokens,
30
28
  att_type: AttType,
29
+ inpatient_att_type: AttType,
31
30
  include_inpatient_hour_token: bool = False,
31
+ spark: SparkSession = None,
32
+ persistence_folder: str = None,
32
33
  ):
33
34
  self._visit_occurrence = visit_occurrence
34
35
  self._include_visit_type = include_visit_type
35
36
  self._exclude_visit_tokens = exclude_visit_tokens
36
37
  self._att_type = att_type
38
+ self._inpatient_att_type = inpatient_att_type
37
39
  self._include_inpatient_hour_token = include_inpatient_hour_token
40
+ super().__init__(spark=spark, persistence_folder=persistence_folder)
41
+
42
+ def get_name(self):
43
+ return "att_events"
38
44
 
39
45
  def _decorate(self, patient_events: DataFrame):
40
46
  if self._att_type == AttType.NONE:
41
47
  return patient_events
42
48
 
43
49
  # visits should the following columns (person_id,
44
- # visit_concept_id, visit_start_date, visit_occurrence_id, domain, concept_value)
50
+ # visit_concept_id, visit_start_date, visit_occurrence_id, domain)
45
51
  cohort_member_person_pair = patient_events.select("person_id", "cohort_member_id").distinct()
46
52
  valid_visit_ids = patient_events.groupby(
47
53
  "cohort_member_id",
@@ -55,9 +61,21 @@ class AttEventDecorator(PatientEventDecorator):
55
61
  F.max("concept_order").alias("max_concept_order"),
56
62
  )
57
63
 
64
+ # The visit records are joined to the cohort members (there could be multiple entries for the same patient)
65
+ # if multiple entries are present, we duplicate the visit records for those. If the visit_occurrence dataframe
66
+ # contains visits for each cohort member, then we need to add cohort_member_id to the joined expression as well.
67
+ if "cohort_member_id" in self._visit_occurrence.columns:
68
+ joined_expr = ["person_id", "cohort_member_id"]
69
+ else:
70
+ joined_expr = ["person_id"]
71
+
58
72
  visit_occurrence = (
59
- self._visit_occurrence.select(
73
+ self._visit_occurrence.join(
74
+ cohort_member_person_pair,
75
+ joined_expr
76
+ ).select(
60
77
  "person_id",
78
+ "cohort_member_id",
61
79
  F.col("visit_start_date").cast(T.DateType()).alias("date"),
62
80
  F.col("visit_start_date").cast(T.DateType()).alias("visit_start_date"),
63
81
  F.col("visit_start_datetime").cast(T.TimestampType()).alias("visit_start_datetime"),
@@ -65,14 +83,18 @@ class AttEventDecorator(PatientEventDecorator):
65
83
  "visit_concept_id",
66
84
  "visit_occurrence_id",
67
85
  F.lit("visit").alias("domain"),
68
- F.lit(0.0).alias("concept_value"),
86
+ F.lit(None).cast("float").alias("number_as_value"),
87
+ F.lit(None).cast("string").alias("concept_as_value"),
88
+ F.lit(0).alias("is_numeric_type"),
69
89
  F.lit(0).alias("concept_value_mask"),
70
90
  F.lit(0).alias("mlm_skip_value"),
71
91
  "age",
72
92
  "discharged_to_concept_id",
73
93
  )
74
- .join(valid_visit_ids, "visit_occurrence_id")
75
- .join(cohort_member_person_pair, ["person_id", "cohort_member_id"])
94
+ .join(
95
+ valid_visit_ids,
96
+ ["visit_occurrence_id", "cohort_member_id"]
97
+ )
76
98
  )
77
99
 
78
100
  # We assume outpatient visits end on the same day, therefore we start visit_end_date to visit_start_date due
@@ -89,7 +111,10 @@ class AttEventDecorator(PatientEventDecorator):
89
111
  visit_occurrence = visit_occurrence.withColumn("date_in_week", weeks_since_epoch_udf)
90
112
 
91
113
  # Cache visit for faster processing
92
- visit_occurrence.cache()
114
+ visit_occurrence = self.try_persist_data(
115
+ visit_occurrence,
116
+ os.path.join(self.get_name(), "visit_occurrence_temp"),
117
+ )
93
118
 
94
119
  visits = visit_occurrence.drop("discharged_to_concept_id")
95
120
 
@@ -134,18 +159,7 @@ class AttEventDecorator(PatientEventDecorator):
134
159
  )
135
160
 
136
161
  # Udf for calculating the time token
137
- if self._att_type == AttType.DAY:
138
- att_func = time_day_token
139
- elif self._att_type == AttType.WEEK:
140
- att_func = time_week_token
141
- elif self._att_type == AttType.MONTH:
142
- att_func = time_month_token
143
- elif self._att_type == AttType.MIX:
144
- att_func = time_mix_token
145
- else:
146
- att_func = time_token_func
147
-
148
- time_token_udf = F.udf(att_func, T.StringType())
162
+ time_token_udf = F.udf(get_att_function(self._att_type), T.StringType())
149
163
 
150
164
  att_tokens = (
151
165
  visits.withColumn("datetime", F.to_timestamp("date"))
@@ -191,6 +205,12 @@ class AttEventDecorator(PatientEventDecorator):
191
205
 
192
206
  artificial_tokens = artificial_tokens.drop("visit_end_date")
193
207
 
208
+ # Try persisting artificial events
209
+ artificial_tokens = self.try_persist_data(
210
+ artificial_tokens,
211
+ os.path.join(self.get_name(), "artificial_tokens"),
212
+ )
213
+
194
214
  # Retrieving the events that are ONLY linked to inpatient visits
195
215
  inpatient_visits = (
196
216
  visit_occurrence
@@ -201,6 +221,7 @@ class AttEventDecorator(PatientEventDecorator):
201
221
  inpatient_visits, ["visit_occurrence_id", "cohort_member_id"]
202
222
  )
203
223
 
224
+ inpatient_time_token_udf = F.udf(get_att_function(self._inpatient_att_type), T.StringType())
204
225
  # Fill in the visit_end_date if null (because some visits are still ongoing at the time of data extraction)
205
226
  # Bound the event dates within visit_start_date and visit_end_date
206
227
  # Generate a span rank to indicate the position of the group of events
@@ -245,6 +266,11 @@ class AttEventDecorator(PatientEventDecorator):
245
266
  # Add discharge events to the inpatient visits
246
267
  inpatient_events = inpatient_events.unionByName(discharge_events)
247
268
 
269
+ # Try persisting the inpatient events for fasting processing
270
+ inpatient_events = self.try_persist_data(
271
+ inpatient_events, os.path.join(self.get_name(), "inpatient_events")
272
+ )
273
+
248
274
  # Get the prev days_since_epoch
249
275
  inpatient_prev_date_udf = F.lag("date").over(
250
276
  W.partitionBy("cohort_member_id", "visit_occurrence_id").orderBy("concept_order")
@@ -264,16 +290,42 @@ class AttEventDecorator(PatientEventDecorator):
264
290
  )
265
291
  inpatient_att_token = F.when(
266
292
  F.col("hour_delta") < 24, F.concat(F.lit("i-H"), F.col("hour_delta"))
267
- ).otherwise(F.concat(F.lit("i-"), time_token_udf("time_delta")))
293
+ ).otherwise(F.concat(F.lit("i-"), inpatient_time_token_udf("time_delta")))
294
+
295
+ # We need to insert an ATT token between midnight and the visit start datetime
296
+ first_inpatient_hour_delta_udf = (
297
+ F.floor((F.unix_timestamp("visit_start_datetime") - F.unix_timestamp(
298
+ F.col("visit_start_datetime").cast("date"))) / 3600)
299
+ )
300
+
301
+ first_hour_tokens = (
302
+ visits.where(F.col("visit_concept_id").isin([9201, 262, 8971, 8920]))
303
+ .withColumn("hour_delta", first_inpatient_hour_delta_udf)
304
+ .where(F.col("hour_delta") > 0)
305
+ .withColumn("date", F.col("visit_start_date"))
306
+ .withColumn("datetime", F.to_timestamp("date"))
307
+ .withColumn("standard_concept_id", F.concat(F.lit("i-H"), F.col("hour_delta")))
308
+ .withColumn("visit_concept_order", F.col("min_visit_concept_order"))
309
+ .withColumn("concept_order", F.lit(0))
310
+ .withColumn("priority", F.lit(FIRST_VISIT_HOUR_TOKEN_PRIORITY))
311
+ .withColumn("unit", F.lit(NA))
312
+ .withColumn("event_group_id", F.lit(NA))
313
+ .drop("min_visit_concept_order", "max_visit_concept_order")
314
+ .drop("min_concept_order", "max_concept_order")
315
+ .drop("hour_delta", "visit_end_date")
316
+ )
317
+
268
318
  # Create ATT tokens within the inpatient visits
269
319
  inpatient_att_events = (
270
320
  inpatient_events.withColumn(
321
+ "time_stamp_hour", F.hour("datetime")
322
+ ).withColumn(
271
323
  "is_span_boundary",
272
324
  F.row_number().over(
273
- W.partitionBy("cohort_member_id", "visit_occurrence_id", "concept_order").orderBy("priority")
325
+ W.partitionBy("cohort_member_id", "visit_occurrence_id")
326
+ .orderBy("priority", "date", "time_stamp_hour")
274
327
  ),
275
328
  )
276
- .where(F.col("is_span_boundary") == 1)
277
329
  .withColumn("prev_date", inpatient_prev_date_udf)
278
330
  .withColumn("time_delta", inpatient_time_delta_udf)
279
331
  .withColumn("prev_datetime", inpatient_prev_datetime_udf)
@@ -284,12 +336,17 @@ class AttEventDecorator(PatientEventDecorator):
284
336
  .withColumn("visit_concept_order", F.col("visit_concept_order"))
285
337
  .withColumn("priority", get_inpatient_att_token_priority())
286
338
  .withColumn("concept_value_mask", F.lit(0))
287
- .withColumn("concept_value", F.lit(0.0))
339
+ .withColumn("number_as_value", F.lit(None).cast("float"))
340
+ .withColumn("concept_as_value", F.lit(None).cast("string"))
341
+ .withColumn("is_numeric_type", F.lit(0))
288
342
  .withColumn("unit", F.lit(NA))
289
343
  .withColumn("event_group_id", F.lit(NA))
290
344
  .drop("prev_date", "time_delta", "is_span_boundary")
291
- .drop("prev_datetime", "hour_delta")
345
+ .drop("prev_datetime", "hour_delta", "time_stamp_hour")
292
346
  )
347
+
348
+ # Insert the first hour tokens between the visit type and first medical event
349
+ inpatient_att_events = inpatient_att_events.unionByName(first_hour_tokens)
293
350
  else:
294
351
  # Create ATT tokens within the inpatient visits
295
352
  inpatient_att_events = (
@@ -306,17 +363,24 @@ class AttEventDecorator(PatientEventDecorator):
306
363
  .where(F.col("prev_date").isNotNull())
307
364
  .withColumn(
308
365
  "standard_concept_id",
309
- F.concat(F.lit("i-"), time_token_udf("time_delta")),
366
+ F.concat(F.lit("i-"), inpatient_time_token_udf("time_delta")),
310
367
  )
311
368
  .withColumn("visit_concept_order", F.col("visit_concept_order"))
312
369
  .withColumn("priority", get_inpatient_att_token_priority())
313
370
  .withColumn("concept_value_mask", F.lit(0))
314
- .withColumn("concept_value", F.lit(0.0))
371
+ .withColumn("number_as_value", F.lit(None).cast("float"))
372
+ .withColumn("concept_as_value", F.lit(None).cast("string"))
373
+ .withColumn("is_numeric_type", F.lit(0))
315
374
  .withColumn("unit", F.lit(NA))
316
375
  .withColumn("event_group_id", F.lit(NA))
317
376
  .drop("prev_date", "time_delta", "is_span_boundary")
318
377
  )
319
378
 
379
+ # Try persisting the inpatient att events
380
+ inpatient_att_events = self.try_persist_data(
381
+ inpatient_att_events, os.path.join(self.get_name(), "inpatient_att_events")
382
+ )
383
+
320
384
  self.validate(inpatient_events)
321
385
  self.validate(inpatient_att_events)
322
386
 
@@ -326,6 +390,10 @@ class AttEventDecorator(PatientEventDecorator):
326
390
  ["visit_occurrence_id", "cohort_member_id"],
327
391
  how="left_anti",
328
392
  )
393
+ # Try persisting the other events
394
+ other_events = self.try_persist_data(
395
+ other_events, os.path.join(self.get_name(), "other_events")
396
+ )
329
397
 
330
398
  patient_events = inpatient_events.unionByName(inpatient_att_events).unionByName(other_events)
331
399