endoreg-db 0.5.3__py3-none-any.whl → 0.6.1__py3-none-any.whl

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.

Potentially problematic release.


This version of endoreg-db might be problematic. Click here for more details.

Files changed (268) hide show
  1. endoreg_db/admin.py +90 -1
  2. endoreg_db/case_generator/case_generator.py +159 -0
  3. endoreg_db/case_generator/lab_sample_factory.py +33 -0
  4. endoreg_db/case_generator/utils.py +30 -0
  5. endoreg_db/data/__init__.py +50 -4
  6. endoreg_db/data/ai_model/data.yaml +7 -0
  7. endoreg_db/data/{label → ai_model_label}/label/data.yaml +27 -1
  8. endoreg_db/data/ai_model_label/label-set/data.yaml +21 -0
  9. endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +5 -0
  10. endoreg_db/data/ai_model_video_segmentation_label/base_segmentation.yaml +176 -0
  11. endoreg_db/data/ai_model_video_segmentation_labelset/data.yaml +20 -0
  12. endoreg_db/data/center/data.yaml +35 -5
  13. endoreg_db/data/contraindication/bleeding.yaml +11 -0
  14. endoreg_db/data/distribution/numeric/data.yaml +14 -0
  15. endoreg_db/data/endoscope/data.yaml +93 -0
  16. endoreg_db/data/examination_indication/endoscopy.yaml +8 -0
  17. endoreg_db/data/examination_indication_classification/endoscopy.yaml +8 -0
  18. endoreg_db/data/examination_indication_classification_choice/endoscopy.yaml +101 -0
  19. endoreg_db/data/finding/data.yaml +141 -0
  20. endoreg_db/data/finding_intervention/endoscopy.yaml +138 -0
  21. endoreg_db/data/finding_intervention_type/endoscopy.yaml +15 -0
  22. endoreg_db/data/finding_location_classification/colonoscopy.yaml +46 -0
  23. endoreg_db/data/finding_location_classification_choice/colonoscopy.yaml +240 -0
  24. endoreg_db/data/finding_morphology_classification/colonoscopy.yaml +48 -0
  25. endoreg_db/data/finding_morphology_classification_choice/colon_lesion_circularity_default.yaml +34 -0
  26. endoreg_db/data/finding_morphology_classification_choice/colon_lesion_nice.yaml +20 -0
  27. endoreg_db/data/finding_morphology_classification_choice/colon_lesion_paris.yaml +65 -0
  28. endoreg_db/data/finding_morphology_classification_choice/colon_lesion_planarity_default.yaml +56 -0
  29. endoreg_db/data/finding_morphology_classification_choice/colon_lesion_surface_intact_default.yaml +39 -0
  30. endoreg_db/data/finding_morphology_classification_choice/colonoscopy_size.yaml +57 -0
  31. endoreg_db/data/finding_morphology_classification_type/colonoscopy.yaml +79 -0
  32. endoreg_db/data/finding_type/data.yaml +30 -0
  33. endoreg_db/data/gender/data.yaml +17 -0
  34. endoreg_db/data/lab_value/cardiac_enzymes.yaml +7 -1
  35. endoreg_db/data/lab_value/coagulation.yaml +6 -1
  36. endoreg_db/data/lab_value/electrolytes.yaml +39 -1
  37. endoreg_db/data/lab_value/gastrointestinal_function.yaml +12 -0
  38. endoreg_db/data/lab_value/hematology.yaml +17 -2
  39. endoreg_db/data/lab_value/hormones.yaml +6 -0
  40. endoreg_db/data/lab_value/lipids.yaml +12 -3
  41. endoreg_db/data/lab_value/misc.yaml +5 -2
  42. endoreg_db/data/lab_value/renal_function.yaml +2 -1
  43. endoreg_db/data/lx_client_tag/base.yaml +54 -0
  44. endoreg_db/data/lx_client_type/base.yaml +30 -0
  45. endoreg_db/data/lx_permission/base.yaml +24 -0
  46. endoreg_db/data/lx_permission/endoreg.yaml +52 -0
  47. endoreg_db/data/medication_indication/anticoagulation.yaml +44 -49
  48. endoreg_db/data/names_first/first_names.yaml +51 -0
  49. endoreg_db/data/names_last/last_names.yaml +51 -0
  50. endoreg_db/data/network_device/data.yaml +30 -0
  51. endoreg_db/data/organ/data.yaml +29 -0
  52. endoreg_db/data/pdf_type/data.yaml +2 -1
  53. endoreg_db/data/report_reader_flag/ukw-examination-generic.yaml +4 -0
  54. endoreg_db/forms/__init__.py +3 -1
  55. endoreg_db/forms/examination_form.py +11 -0
  56. endoreg_db/forms/patient_finding_intervention_form.py +19 -0
  57. endoreg_db/forms/patient_form.py +26 -0
  58. endoreg_db/management/commands/__init__.py +0 -0
  59. endoreg_db/management/commands/load_ai_model_data.py +57 -23
  60. endoreg_db/management/commands/load_ai_model_label_data.py +59 -0
  61. endoreg_db/management/commands/load_base_db_data.py +160 -118
  62. endoreg_db/management/commands/{load_endoscope_type_data.py → load_contraindication_data.py} +3 -7
  63. endoreg_db/management/commands/load_disease_data.py +29 -7
  64. endoreg_db/management/commands/load_endoscope_data.py +68 -0
  65. endoreg_db/management/commands/load_examination_indication_data.py +65 -0
  66. endoreg_db/management/commands/load_finding_data.py +171 -0
  67. endoreg_db/management/commands/load_lab_value_data.py +3 -3
  68. endoreg_db/management/commands/load_lx_data.py +64 -0
  69. endoreg_db/management/commands/load_medication_data.py +83 -21
  70. endoreg_db/management/commands/load_name_data.py +37 -0
  71. endoreg_db/management/commands/{load_endoscopy_processor_data.py → load_organ_data.py} +7 -9
  72. endoreg_db/migrations/0001_initial.py +1206 -728
  73. endoreg_db/migrations/0002_alter_frame_image_alter_rawframe_image.py +23 -0
  74. endoreg_db/migrations/0003_alter_frame_image_alter_rawframe_image.py +23 -0
  75. endoreg_db/migrations/0004_alter_rawvideofile_file_alter_video_file.py +25 -0
  76. endoreg_db/migrations/0005_rawvideofile_frame_count_and_more.py +33 -0
  77. endoreg_db/migrations/0006_frame_extracted_rawframe_extracted.py +23 -0
  78. endoreg_db/migrations/0007_rename_pseudo_patient_video_patient_and_more.py +24 -0
  79. endoreg_db/migrations/0008_remove_reportfile_patient_examination_and_more.py +48 -0
  80. endoreg_db/models/__init__.py +331 -28
  81. endoreg_db/models/ai_model/__init__.py +1 -0
  82. endoreg_db/models/ai_model/ai_model.py +103 -0
  83. endoreg_db/models/ai_model/lightning/__init__.py +3 -0
  84. endoreg_db/models/ai_model/lightning/inference_dataset.py +53 -0
  85. endoreg_db/models/ai_model/lightning/multilabel_classification_net.py +155 -0
  86. endoreg_db/models/ai_model/lightning/postprocess.py +53 -0
  87. endoreg_db/models/ai_model/lightning/predict.py +172 -0
  88. endoreg_db/models/ai_model/lightning/prediction_visualizer.py +55 -0
  89. endoreg_db/models/ai_model/lightning/preprocess.py +68 -0
  90. endoreg_db/models/ai_model/lightning/run_visualizer.py +21 -0
  91. endoreg_db/models/ai_model/model_meta.py +232 -6
  92. endoreg_db/models/ai_model/model_type.py +13 -3
  93. endoreg_db/models/annotation/__init__.py +31 -2
  94. endoreg_db/models/annotation/anonymized_image_annotation.py +73 -18
  95. endoreg_db/models/annotation/binary_classification_annotation_task.py +94 -57
  96. endoreg_db/models/annotation/image_classification.py +73 -14
  97. endoreg_db/models/annotation/video_segmentation_annotation.py +52 -0
  98. endoreg_db/models/annotation/video_segmentation_labelset.py +20 -0
  99. endoreg_db/models/case/__init__.py +1 -0
  100. endoreg_db/models/{persons/patient/case → case}/case.py +4 -0
  101. endoreg_db/models/case_template/__init__.py +10 -1
  102. endoreg_db/models/case_template/case_template.py +57 -13
  103. endoreg_db/models/case_template/case_template_rule.py +5 -5
  104. endoreg_db/models/case_template/case_template_rule_value.py +19 -4
  105. endoreg_db/models/center/__init__.py +7 -0
  106. endoreg_db/models/center/center.py +31 -5
  107. endoreg_db/models/center/center_product.py +0 -1
  108. endoreg_db/models/center/center_resource.py +16 -2
  109. endoreg_db/models/center/center_waste.py +6 -1
  110. endoreg_db/models/contraindication/__init__.py +21 -0
  111. endoreg_db/models/data_file/__init__.py +38 -5
  112. endoreg_db/models/data_file/base_classes/__init__.py +6 -1
  113. endoreg_db/models/data_file/base_classes/abstract_frame.py +64 -15
  114. endoreg_db/models/data_file/base_classes/abstract_pdf.py +136 -0
  115. endoreg_db/models/data_file/base_classes/abstract_video.py +744 -138
  116. endoreg_db/models/data_file/base_classes/frame_helpers.py +17 -0
  117. endoreg_db/models/data_file/base_classes/prepare_bulk_frames.py +19 -0
  118. endoreg_db/models/data_file/base_classes/utils.py +80 -0
  119. endoreg_db/models/data_file/frame.py +22 -38
  120. endoreg_db/models/data_file/import_classes/__init__.py +4 -18
  121. endoreg_db/models/data_file/import_classes/raw_pdf.py +162 -90
  122. endoreg_db/models/data_file/import_classes/raw_video.py +239 -294
  123. endoreg_db/models/data_file/metadata/__init__.py +10 -0
  124. endoreg_db/models/data_file/metadata/pdf_meta.py +4 -0
  125. endoreg_db/models/data_file/metadata/sensitive_meta.py +265 -6
  126. endoreg_db/models/data_file/metadata/video_meta.py +116 -50
  127. endoreg_db/models/data_file/report_file.py +30 -63
  128. endoreg_db/models/data_file/video/__init__.py +6 -2
  129. endoreg_db/models/data_file/video/video.py +187 -16
  130. endoreg_db/models/data_file/video_segment.py +162 -55
  131. endoreg_db/models/disease.py +25 -2
  132. endoreg_db/models/emission/__init__.py +5 -1
  133. endoreg_db/models/emission/emission_factor.py +71 -6
  134. endoreg_db/models/event.py +51 -0
  135. endoreg_db/models/examination/__init__.py +6 -1
  136. endoreg_db/models/examination/examination.py +53 -12
  137. endoreg_db/models/examination/examination_indication.py +170 -0
  138. endoreg_db/models/examination/examination_time.py +31 -5
  139. endoreg_db/models/examination/examination_time_type.py +28 -4
  140. endoreg_db/models/examination/examination_type.py +28 -6
  141. endoreg_db/models/finding/__init__.py +11 -0
  142. endoreg_db/models/finding/finding.py +75 -0
  143. endoreg_db/models/finding/finding_intervention.py +60 -0
  144. endoreg_db/models/finding/finding_location_classification.py +94 -0
  145. endoreg_db/models/finding/finding_morphology_classification.py +89 -0
  146. endoreg_db/models/finding/finding_type.py +22 -0
  147. endoreg_db/models/hardware/endoscope.py +16 -0
  148. endoreg_db/models/hardware/endoscopy_processor.py +31 -19
  149. endoreg_db/models/label/label.py +35 -7
  150. endoreg_db/models/laboratory/lab_value.py +12 -3
  151. endoreg_db/models/logging/__init__.py +8 -1
  152. endoreg_db/models/lx/__init__.py +4 -0
  153. endoreg_db/models/lx/client.py +57 -0
  154. endoreg_db/models/lx/identity.py +34 -0
  155. endoreg_db/models/lx/permission.py +18 -0
  156. endoreg_db/models/lx/user.py +16 -0
  157. endoreg_db/models/medication/__init__.py +19 -1
  158. endoreg_db/models/medication/medication.py +7 -122
  159. endoreg_db/models/medication/medication_indication.py +50 -0
  160. endoreg_db/models/medication/medication_indication_type.py +34 -0
  161. endoreg_db/models/medication/medication_intake_time.py +26 -0
  162. endoreg_db/models/medication/medication_schedule.py +37 -0
  163. endoreg_db/models/network/__init__.py +7 -1
  164. endoreg_db/models/network/network_device.py +13 -8
  165. endoreg_db/models/organ/__init__.py +38 -0
  166. endoreg_db/models/other/__init__.py +19 -1
  167. endoreg_db/models/other/distribution/__init__.py +44 -0
  168. endoreg_db/models/other/distribution/base_value_distribution.py +20 -0
  169. endoreg_db/models/other/distribution/date_value_distribution.py +91 -0
  170. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +32 -0
  171. endoreg_db/models/other/distribution/numeric_value_distribution.py +97 -0
  172. endoreg_db/models/other/distribution/single_categorical_value_distribution.py +22 -0
  173. endoreg_db/models/other/distribution.py +1 -211
  174. endoreg_db/models/other/material.py +4 -0
  175. endoreg_db/models/other/transport_route.py +2 -1
  176. endoreg_db/models/patient/__init__.py +24 -0
  177. endoreg_db/models/patient/patient_examination.py +182 -0
  178. endoreg_db/models/patient/patient_finding.py +143 -0
  179. endoreg_db/models/patient/patient_finding_intervention.py +26 -0
  180. endoreg_db/models/patient/patient_finding_location.py +120 -0
  181. endoreg_db/models/patient/patient_finding_morphology.py +166 -0
  182. endoreg_db/models/persons/__init__.py +29 -2
  183. endoreg_db/models/persons/examiner/examiner.py +48 -4
  184. endoreg_db/models/persons/patient/__init__.py +1 -1
  185. endoreg_db/models/persons/patient/patient.py +227 -54
  186. endoreg_db/models/persons/patient/patient_disease.py +6 -0
  187. endoreg_db/models/persons/patient/patient_event.py +31 -1
  188. endoreg_db/models/persons/patient/patient_examination_indication.py +32 -0
  189. endoreg_db/models/persons/patient/patient_lab_sample.py +4 -2
  190. endoreg_db/models/persons/patient/patient_lab_value.py +37 -16
  191. endoreg_db/models/persons/patient/patient_medication.py +27 -12
  192. endoreg_db/models/persons/patient/patient_medication_schedule.py +62 -2
  193. endoreg_db/models/prediction/__init__.py +7 -1
  194. endoreg_db/models/prediction/image_classification.py +20 -6
  195. endoreg_db/models/prediction/video_prediction_meta.py +151 -89
  196. endoreg_db/models/product/__init__.py +10 -1
  197. endoreg_db/models/product/product.py +15 -2
  198. endoreg_db/models/product/product_group.py +8 -0
  199. endoreg_db/models/product/product_material.py +4 -0
  200. endoreg_db/models/product/product_weight.py +12 -0
  201. endoreg_db/models/product/reference_product.py +19 -3
  202. endoreg_db/models/quiz/__init__.py +8 -1
  203. endoreg_db/models/report_reader/__init__.py +6 -1
  204. endoreg_db/serializers/__init__.py +1 -1
  205. endoreg_db/serializers/annotation.py +2 -5
  206. endoreg_db/serializers/frame.py +1 -5
  207. endoreg_db/serializers/patient.py +26 -3
  208. endoreg_db/serializers/prediction.py +2 -7
  209. endoreg_db/serializers/raw_video_meta_validation.py +13 -0
  210. endoreg_db/serializers/video.py +6 -13
  211. endoreg_db/serializers/video_segmentation.py +492 -0
  212. endoreg_db/templates/admin/patient_finding_intervention.html +253 -0
  213. endoreg_db/templates/admin/start_examination.html +12 -0
  214. endoreg_db/templates/timeline.html +176 -0
  215. endoreg_db/urls.py +173 -0
  216. endoreg_db/utils/__init__.py +36 -1
  217. endoreg_db/utils/dataloader.py +45 -19
  218. endoreg_db/utils/dates.py +39 -0
  219. endoreg_db/utils/hashs.py +122 -4
  220. endoreg_db/utils/names.py +74 -0
  221. endoreg_db/utils/parse_and_generate_yaml.py +46 -0
  222. endoreg_db/utils/pydantic_models/__init__.py +6 -0
  223. endoreg_db/utils/pydantic_models/db_config.py +57 -0
  224. endoreg_db/utils/validate_endo_roi.py +19 -0
  225. endoreg_db/utils/validate_subcategory_dict.py +91 -0
  226. endoreg_db/utils/video/__init__.py +13 -0
  227. endoreg_db/utils/video/extract_frames.py +121 -0
  228. endoreg_db/utils/video/transcode_videofile.py +111 -0
  229. endoreg_db/views/__init__.py +2 -0
  230. endoreg_db/views/csrf.py +7 -0
  231. endoreg_db/views/patient_views.py +90 -0
  232. endoreg_db/views/raw_video_meta_validation_views.py +38 -0
  233. endoreg_db/views/report_views.py +96 -0
  234. endoreg_db/views/video_segmentation_views.py +149 -0
  235. endoreg_db/views/views_for_timeline.py +46 -0
  236. endoreg_db/views.py +0 -3
  237. endoreg_db-0.6.1.dist-info/METADATA +151 -0
  238. endoreg_db-0.6.1.dist-info/RECORD +420 -0
  239. {endoreg_db-0.5.3.dist-info → endoreg_db-0.6.1.dist-info}/WHEEL +1 -1
  240. endoreg_db/data/active_model/data.yaml +0 -3
  241. endoreg_db/data/label/label-set/data.yaml +0 -18
  242. endoreg_db/management/commands/delete_legacy_images.py +0 -19
  243. endoreg_db/management/commands/delete_legacy_videos.py +0 -17
  244. endoreg_db/management/commands/extract_legacy_video_frames.py +0 -18
  245. endoreg_db/management/commands/import_legacy_images.py +0 -94
  246. endoreg_db/management/commands/import_legacy_videos.py +0 -76
  247. endoreg_db/management/commands/load_label_data.py +0 -67
  248. endoreg_db/migrations/0002_anonymizedimagelabel_anonymousimageannotation_and_more.py +0 -55
  249. endoreg_db/migrations/0003_anonymousimageannotation_original_image_url_and_more.py +0 -39
  250. endoreg_db/migrations/0004_alter_rawpdffile_file.py +0 -20
  251. endoreg_db/migrations/0005_uploadedfile_alter_rawpdffile_file_anonymizedfile.py +0 -40
  252. endoreg_db/migrations/0006_alter_rawpdffile_file.py +0 -20
  253. endoreg_db/migrations/0007_networkdevicelogentry_datetime_and_more.py +0 -43
  254. endoreg_db/migrations/0008_networkdevicelogentry_aglnet_ip_and_more.py +0 -28
  255. endoreg_db/migrations/0009_alter_networkdevicelogentry_vpn_service_status.py +0 -18
  256. endoreg_db/migrations/0010_remove_networkdevicelogentry_hostname.py +0 -17
  257. endoreg_db/models/legacy_data/__init__.py +0 -3
  258. endoreg_db/models/legacy_data/image.py +0 -34
  259. endoreg_db/models/patient_examination/__init__.py +0 -35
  260. endoreg_db/utils/video_metadata.py +0 -87
  261. endoreg_db-0.5.3.dist-info/METADATA +0 -28
  262. endoreg_db-0.5.3.dist-info/RECORD +0 -319
  263. /endoreg_db/{models/persons/patient/case → case_generator}/__init__.py +0 -0
  264. /endoreg_db/data/{label → ai_model_label}/label-type/data.yaml +0 -0
  265. /endoreg_db/data/{model_type → ai_model_type}/data.yaml +0 -0
  266. /endoreg_db/{data/distribution/numeric/.init → management/__init__.py} +0 -0
  267. /endoreg_db/management/commands/{load_report_reader_flag.py → load_report_reader_flag_data.py} +0 -0
  268. {endoreg_db-0.5.3.dist-info → endoreg_db-0.6.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,18 @@
1
1
  from ..person import Person
2
2
  from django import forms
3
3
  from django.forms import DateInput
4
- from rest_framework import serializers
5
- from ...patient_examination import PatientExamination
4
+ from ...patient import PatientExamination
6
5
  from ...data_file import ReportFile
7
6
  from django.db import models
8
7
  from faker import Faker
9
8
  import random
10
9
  from datetime import datetime
10
+ from icecream import ic
11
+ from typing import TYPE_CHECKING
12
+
13
+ if TYPE_CHECKING:
14
+ from endoreg_db.models import Center, Gender
15
+
11
16
 
12
17
  class Patient(Person):
13
18
  """
@@ -22,33 +27,196 @@ class Patient(Person):
22
27
  phone (str): The phone number of the patient.
23
28
 
24
29
  """
25
- center = models.ForeignKey("Center", on_delete=models.CASCADE, blank=True, null=True)
26
-
30
+
31
+ # -----gc-08-dev--changings---
32
+ first_name = models.CharField(max_length=100)
33
+ last_name = models.CharField(max_length=100)
34
+ dob = models.DateField(null=True, blank=True)
35
+ gender = models.ForeignKey(
36
+ "Gender", on_delete=models.SET_NULL, null=True, blank=True
37
+ )
38
+ center = models.ForeignKey(
39
+ "Center", on_delete=models.SET_NULL, null=True, blank=True
40
+ )
41
+ patient_hash = models.CharField(max_length=255, blank=True, null=True)
27
42
 
28
43
  def __str__(self):
29
- return self.first_name + " " + self.last_name + " (" + str(self.dob) + ")"
30
-
31
- def get_unmatched_report_files(self): #field: self.report_files; filter: report_file.patient_examination = None
32
- '''Returns all report files for this patient that are not matched to a patient examination.'''
44
+ return f"{self.first_name} {self.last_name} ({self.dob})"
45
+
46
+ @classmethod
47
+ def get_or_create_pseudo_patient_by_hash(
48
+ cls,
49
+ patient_hash: str,
50
+ center: "Center" = None,
51
+ gender: "Gender" = None,
52
+ birth_month: int = None,
53
+ birth_year: int = None,
54
+ ):
55
+ from endoreg_db.utils import random_day_by_year, create_mock_patient_name
56
+
57
+ created = False
58
+
59
+ existing_pathient = cls.objects.filter(patient_hash=patient_hash).first()
60
+ if existing_pathient:
61
+ ic(f"Patient with hash {patient_hash} already exists")
62
+ ic(f"Returning existing patient: {existing_pathient}")
63
+ return existing_pathient, created
64
+
65
+ # If no patient with the given hash exists, create a new pseudo patient
66
+ assert center, "Center must be provided to create a new pseudo patient"
67
+ assert gender, "Gender must be provided to create a new pseudo patient"
68
+ assert birth_month, (
69
+ "Birth month must be provided to create a new pseudo patient"
70
+ )
71
+ assert birth_year, "Birth year must be provided to create a new pseudo patient"
72
+
73
+ pseudo_dob = random_day_by_year(birth_year)
74
+ gender_name = gender.name
75
+ first_name, last_name = create_mock_patient_name(gender_name)
76
+
77
+ ic(f"Creating pseudo patient with hash {patient_hash}")
78
+ ic(f"Generated name: {first_name} {last_name}")
79
+
80
+ patient = cls.objects.create(
81
+ first_name=first_name,
82
+ last_name=last_name,
83
+ dob=pseudo_dob,
84
+ patient_hash=patient_hash,
85
+ is_real_person=False,
86
+ )
87
+
88
+ patient.save()
89
+ created = True
90
+
91
+ return patient, created
92
+
93
+ def export_patient_examinations(self):
94
+ """
95
+ Get all associated PatientExaminations, ReportFiles, and Videos for the patient.
96
+ """
97
+ from endoreg_db.models import PatientExamination, ReportFile, Video
98
+
99
+ patient_examinations = PatientExamination.objects.filter(patient=self)
100
+ report_files, videos = [], []
101
+ for patient_examination in patient_examinations:
102
+ rr = patient_examination.reportfile_set.all()
103
+ vv = patient_examination.videos.all()
104
+
105
+ report_files.extend(rr)
106
+ videos.extend(vv)
107
+
108
+ return patient_examinations, report_files, videos
109
+
110
+ def get_dob(self) -> datetime.date:
111
+ dob: datetime.date = self.dob
112
+ return dob
113
+
114
+ def get_unmatched_report_files(
115
+ self,
116
+ ): # field: self.report_files; filter: report_file.patient_examination = None
117
+ """Returns all report files for this patient that are not matched to a patient examination."""
33
118
 
34
119
  return self.reportfile_set.filter(patient_examination=None)
35
120
 
36
- def get_unmatched_video_files(self): #field: self.videos; filter: video.patient_examination = None
37
- '''Returns all video files for this patient that are not matched to a patient examination.'''
121
+ def get_unmatched_video_files(
122
+ self,
123
+ ): # field: self.videos; filter: video.patient_examination = None
124
+ """Returns all video files for this patient that are not matched to a patient examination."""
38
125
  return self.videos.filter(patient_examination=None)
39
126
 
40
- def get_patient_examinations(self): #field: self.patient_examinations
41
- '''Returns all patient examinations for this patient ordered by date (most recent is first).'''
42
- return self.patient_examinations.order_by('-date')
43
-
44
- def create_examination_by_report_file(self, report_file:ReportFile):
45
- '''Creates a patient examination for this patient based on the given report file.'''
127
+ def get_patient_examinations(self): # field: self.patient_examinations
128
+ """Returns all patient examinations for this patient ordered by date (most recent is first)."""
129
+ return self.patient_examinations.order_by("-date")
130
+
131
+ def create_examination(
132
+ self,
133
+ examination_name_str: str = None,
134
+ date_start: datetime = None,
135
+ date_end: datetime = None,
136
+ ):
137
+ """Creates a patient examination for this patient."""
138
+
139
+ if examination_name_str:
140
+ from endoreg_db.models import Examination
141
+
142
+ examination = Examination.objects.get(name=examination_name_str)
143
+ patient_examination = PatientExamination(
144
+ patient=self,
145
+ examination=examination,
146
+ date_start=date_start,
147
+ date_end=date_end,
148
+ )
149
+
150
+ else:
151
+ patient_examination = PatientExamination(
152
+ patient=self, date_start=date_start, date_end=date_end
153
+ )
154
+
155
+ patient_examination.save()
156
+
157
+ return patient_examination
158
+
159
+ def create_examination_by_indication(
160
+ self, indication, date_start: datetime = None, date_end: datetime = None
161
+ ):
162
+ from endoreg_db.models import (
163
+ ExaminationIndication,
164
+ Examination,
165
+ PatientExaminationIndication,
166
+ )
167
+
168
+ assert isinstance(indication, ExaminationIndication)
169
+
170
+ examination = indication.get_examination()
171
+
172
+ assert isinstance(examination, Examination)
173
+
174
+ patient_examination = PatientExamination.objects.create(
175
+ patient=self,
176
+ examination=examination,
177
+ date_start=date_start,
178
+ date_end=date_end,
179
+ )
180
+
181
+ patient_examination.save()
182
+
183
+ patient_examination_indication = PatientExaminationIndication.objects.create(
184
+ patient_examination=patient_examination, examination_indication=indication
185
+ )
186
+ patient_examination_indication.save()
187
+
188
+ return patient_examination, patient_examination_indication
189
+
190
+ def create_event(
191
+ self,
192
+ event_name_str: str,
193
+ date_start: datetime = None,
194
+ date_end: datetime = None,
195
+ description: str = None,
196
+ ):
197
+ from endoreg_db.models import Event, PatientEvent
198
+
199
+ event = Event.objects.get(name=event_name_str)
200
+
201
+ if not date_start:
202
+ date_start = datetime.now()
203
+
204
+ patient_event = PatientEvent.objects.create(
205
+ patient=self,
206
+ event=event,
207
+ date_start=date_start,
208
+ )
209
+
210
+ return patient_event
211
+
212
+ def create_examination_by_report_file(self, report_file: ReportFile):
213
+ """Creates a patient examination for this patient based on the given report file."""
46
214
  patient_examination = PatientExamination(patient=self, report_file=report_file)
47
215
  patient_examination.save()
48
216
  return patient_examination
49
-
217
+
50
218
  @classmethod
51
- def get_random_gender(self, p_male=0.5, p_female=0.5):
219
+ def get_random_gender(cls, p_male=0.5, p_female=0.5):
52
220
  """
53
221
  Get a Gender object by name (male, female) from the database with given probability.
54
222
 
@@ -57,33 +225,28 @@ class Patient(Person):
57
225
  :return: Gender object selected based on given probabilities.
58
226
  """
59
227
  from endoreg_db.models import Gender
60
-
228
+
61
229
  # Extract names and probabilities
62
230
  gender_names = ["male", "female"]
63
- probabilities = [0.5, 0.5]
64
-
231
+ probabilities = [p_male, p_female]
232
+
65
233
  # Debug: print the names and probabilities
66
- print(f"Gender names: {gender_names}")
67
- print(f"Probabilities: {probabilities}")
68
-
234
+ # print(f"Gender names: {gender_names}")
235
+ # print(f"Probabilities: {probabilities}")
236
+
69
237
  # Select a gender based on the given probabilities
70
238
  selected_gender = random.choices(gender_names, probabilities)[0]
71
239
  # Debug: print the selected gender
72
- print(f"Selected gender: {selected_gender}")
73
-
240
+ # print(f"Selected gender: {selected_gender}")
241
+
74
242
  # Fetch the corresponding Gender object from the database
75
243
  gender_obj = Gender.objects.get(name=selected_gender)
76
-
77
- return gender_obj
78
244
 
245
+ return gender_obj
79
246
 
80
247
  @classmethod
81
- def get_random_age(self,
82
- min_age = 55,
83
- max_age = 90,
84
- mean_age = 65,
85
- std_age = 10,
86
- distribution = "normal"
248
+ def get_random_age(
249
+ cls, min_age=55, max_age=90, mean_age=65, std_age=10, distribution="normal"
87
250
  ):
88
251
  """
89
252
  Get a random age based on the given distribution.
@@ -99,11 +262,11 @@ class Patient(Person):
99
262
  age = int(random.normalvariate(mean_age, std_age))
100
263
  else:
101
264
  age = int(random.uniform(min_age, max_age))
102
-
265
+
103
266
  return age
104
-
267
+
105
268
  @classmethod
106
- def get_dob_from_age(self, age, current_date=None):
269
+ def get_dob_from_age(cls, age, current_date=None):
107
270
  """
108
271
  Get a date of birth based on the given age and current date.
109
272
 
@@ -119,24 +282,24 @@ class Patient(Person):
119
282
  # randomize the day and month by adding a random number of days (0-364) to the date
120
283
 
121
284
  return dob
122
-
285
+
123
286
  @classmethod
124
- def get_random_name_for_gender(self, gender_obj, locale="de_DE"):
287
+ def get_random_name_for_gender(cls, gender_obj, locale="de_DE"):
125
288
  gender = gender_obj.name
126
289
  fake = Faker(locale)
127
-
290
+
128
291
  if gender == "male":
129
292
  first_name = fake.first_name_male()
130
293
  last_name = fake.last_name_male()
131
294
 
132
- else:
295
+ else:
133
296
  first_name = fake.first_name_female()
134
297
  last_name = fake.last_name_female()
135
298
 
136
299
  return last_name, first_name
137
-
300
+
138
301
  @classmethod
139
- def create_generic(self, center="gplay_case_generator"):
302
+ def create_generic(cls, center="gplay_case_generator"):
140
303
  """
141
304
  Create a generic patient with random attributes.
142
305
 
@@ -144,6 +307,7 @@ class Patient(Person):
144
307
  :return: The created patient.
145
308
  """
146
309
  from endoreg_db.models import Center
310
+
147
311
  gender = Patient.get_random_gender()
148
312
  last_name, first_name = Patient.get_random_name_for_gender(gender)
149
313
 
@@ -155,7 +319,8 @@ class Patient(Person):
155
319
  patient = Patient.objects.create(
156
320
  first_name=first_name,
157
321
  last_name=last_name,
158
- dob=dob
322
+ dob=dob,
323
+ gender=gender,
159
324
  )
160
325
  patient.save()
161
326
  return patient
@@ -169,7 +334,11 @@ class Patient(Person):
169
334
  # calculate correct age based on current date including day and month
170
335
  current_date = datetime.now()
171
336
  dob = self.dob
172
- age = current_date.year - dob.year - ((current_date.month, current_date.day) < (dob.month, dob.day))
337
+ age = (
338
+ current_date.year
339
+ - dob.year
340
+ - ((current_date.month, current_date.day) < (dob.month, dob.day))
341
+ )
173
342
  return age
174
343
 
175
344
  def create_lab_sample(self, sample_type="generic", date=None, save=True):
@@ -182,19 +351,22 @@ class Patient(Person):
182
351
  :return: The created lab sample.
183
352
  """
184
353
  from endoreg_db.models import PatientLabSample, PatientLabSampleType
354
+
185
355
  if date is None:
186
356
  date = datetime.now()
187
357
 
188
358
  if isinstance(sample_type, str):
189
359
  sample_type = PatientLabSampleType.objects.get(name=sample_type)
190
- assert sample_type is not None, f"Sample type with name '{sample_type}' not found."#
360
+ assert sample_type is not None, (
361
+ f"Sample type with name '{sample_type}' not found."
362
+ ) #
191
363
  elif not isinstance(sample_type, PatientLabSampleType):
192
- raise ValueError("Sample type must be either a string or a PatientLabSampleType object.")
193
-
364
+ raise ValueError(
365
+ "Sample type must be either a string or a PatientLabSampleType object."
366
+ )
367
+
194
368
  patient_lab_sample = PatientLabSample.objects.create(
195
- patient=self,
196
- sample_type=sample_type,
197
- date=date
369
+ patient=self, sample_type=sample_type, date=date
198
370
  )
199
371
 
200
372
  if save:
@@ -202,15 +374,16 @@ class Patient(Person):
202
374
 
203
375
  return patient_lab_sample
204
376
 
377
+
205
378
  class PatientForm(forms.ModelForm):
206
379
  class Meta:
207
380
  model = Patient
208
- fields = '__all__'
381
+ fields = "__all__"
209
382
  widgets = {
210
- 'dob': DateInput(attrs={'type': 'date'}),
383
+ "dob": DateInput(attrs={"type": "date"}),
211
384
  }
212
385
 
213
386
  def __init__(self, *args, **kwargs):
214
387
  super().__init__(*args, **kwargs)
215
388
  for field in self.fields.values():
216
- field.widget.attrs['class'] = 'form-control'
389
+ field.widget.attrs["class"] = "form-control"
@@ -8,9 +8,15 @@ class PatientDisease(models.Model):
8
8
  classification_choices = models.ManyToManyField("DiseaseClassificationChoice")
9
9
  start_date = models.DateField(blank=True, null=True)
10
10
  end_date = models.DateField(blank=True, null=True)
11
+ numerical_descriptors = models.JSONField(default=dict)
12
+ subcategories = models.JSONField(default=dict)
11
13
 
12
14
  last_update = models.DateTimeField(auto_now=True)
13
15
 
14
16
  def __str__(self):
15
17
  return f"{self.patient} - {self.disease}"
16
18
 
19
+ class Meta:
20
+ # unique_together = ('patient', 'disease', 'start_date')
21
+ verbose_name = 'Patient Disease'
22
+ verbose_name_plural = 'Patient Diseases'
@@ -15,8 +15,38 @@ class PatientEvent(models.Model):
15
15
  date_start = models.DateField()
16
16
  date_end = models.DateField(blank=True, null=True)
17
17
  description = models.TextField(blank=True, null=True)
18
+ classification_choice = models.ForeignKey(
19
+ "EventClassificationChoice", on_delete=models.CASCADE, blank=True, null=True
20
+ )
21
+
22
+ subcategories = models.JSONField(default=dict)
23
+ numerical_descriptors = models.JSONField(default=dict)
18
24
 
19
25
  last_update = models.DateTimeField(auto_now=True)
20
26
 
21
27
  def __str__(self):
22
- return str(self.date_start) + ": " + self.event
28
+ return str(self.date_start) + ": " + self.event.name
29
+
30
+ def set_subcategories_from_classification_choice(self):
31
+ """
32
+ Sets the subcategories for this event from the classification choice.
33
+ """
34
+ from endoreg_db.models import EventClassificationChoice
35
+ if self.classification_choice:
36
+ self.classification_choice:EventClassificationChoice
37
+ self.subcategories = self.classification_choice.subcategories
38
+ self.save()
39
+
40
+ return self.subcategories
41
+
42
+ def set_numerical_descriptors_from_classification_choice(self):
43
+ """
44
+ Sets the numerical descriptors for this event from the classification choice.
45
+ """
46
+ from endoreg_db.models import EventClassificationChoice
47
+ if self.classification_choice:
48
+ self.classification_choice:EventClassificationChoice
49
+ self.numerical_descriptors = self.classification_choice.numerical_descriptors
50
+ self.save()
51
+
52
+ return self.numerical_descriptors
@@ -0,0 +1,32 @@
1
+ from django.db import models
2
+
3
+ class PatientExaminationIndication(models.Model):
4
+ '''A model to store the indication for a patient examination.'''
5
+ patient_examination = models.ForeignKey('PatientExamination', on_delete=models.CASCADE, related_name='indications')
6
+ examination_indication = models.ForeignKey('ExaminationIndication', on_delete=models.CASCADE)
7
+ indication_choice = models.ForeignKey('ExaminationIndicationClassificationChoice', on_delete=models.CASCADE, blank=True, null=True)
8
+
9
+ def __str__(self):
10
+ return f"{self.patient_examination} - {self.examination_indication}"
11
+
12
+ def get_examination(self):
13
+ from endoreg_db.models import Examination
14
+ pe = self.get_patient_examination()
15
+ e: Examination = pe.examination
16
+
17
+ return e
18
+
19
+ def get_patient_examination(self):
20
+ from endoreg_db.models import PatientExamination
21
+ pe: PatientExamination = self.patient_examination
22
+ return pe
23
+
24
+ def get_choices(self):
25
+ from endoreg_db.models import (
26
+ ExaminationIndicationClassificationChoice,
27
+ ExaminationIndication
28
+ )
29
+ examination_indication:ExaminationIndication = self.examination_indication
30
+ choices = [_ for _ in examination_indication.get_choices()]
31
+ return choices
32
+
@@ -58,10 +58,12 @@ class PatientLabSample(models.Model):
58
58
  date = models.DateTimeField()
59
59
 
60
60
  def __str__(self):
61
- return f"{self.patient} - {self.sample_type} - {self.date} ()"
61
+
62
+ formatted_datetime = self.date.strftime('%Y-%m-%d %H:%M')
63
+ return f"{self.patient} - {self.sample_type} - {formatted_datetime} ()"
62
64
 
63
65
  def get_values(self):
64
- return self.values
66
+ return self.values.all()
65
67
 
66
68
  @classmethod
67
69
  def create_by_patient(cls, patient=None, sample_type=None, date=None, save = True):
@@ -30,7 +30,11 @@ class PatientLabValue(models.Model):
30
30
  unit = models.ForeignKey('Unit', on_delete=models.CASCADE, blank=True, null=True)
31
31
 
32
32
  @classmethod
33
- def create_lab_value_by_sample(cls, sample=None, lab_value_name=None, value=None, value_str=None, unit=None):
33
+ def create_lab_value_by_sample(
34
+ cls, sample=None, lab_value_name=None,
35
+ value=None, value_str=None,
36
+ unit=None
37
+ ):
34
38
  from endoreg_db.models import LabValue
35
39
  patient = sample.patient
36
40
  lab_value = LabValue.objects.get(name=lab_value_name)
@@ -51,10 +55,26 @@ class PatientLabValue(models.Model):
51
55
  return pat_lab_val
52
56
 
53
57
  def __str__(self):
54
- _str = f'{self.lab_value} - {self.value} {self.unit} ({self.datetime})'
55
- print(_str)
58
+ formatted_datetime = self.datetime.strftime('%Y-%m-%d %H:%M')
59
+ # normal_range = self.get_normal_range()
60
+ norm_range_string = f'[{self.normal_range.get("min", "")} - {self.normal_range.get("max", "")}]'
61
+ _str = f'{self.lab_value} - {self.value} {self.unit} - {norm_range_string} ({formatted_datetime})'
56
62
  return _str
57
63
 
64
+ def get_normal_range(self):
65
+ from endoreg_db.models import LabValue, Patient
66
+ lab_value:LabValue = self.lab_value
67
+ patient:Patient = self.patient
68
+
69
+ age = patient.age()
70
+ gender = patient.gender
71
+
72
+ normal_range_dict = lab_value.get_normal_range(
73
+ age,gender
74
+ )
75
+ return normal_range_dict
76
+
77
+
58
78
  def set_min_norm_value(self, value, save = True):
59
79
  self.normal_range["min"] = value
60
80
  if save:
@@ -66,13 +86,11 @@ class PatientLabValue(models.Model):
66
86
  self.save()
67
87
 
68
88
  def set_norm_values_from_default(self):
69
- age = self.patient.age()
70
- gender = self.patient.gender
71
- min_value, max_value = self.lab_value.get_normal_range(age=age, gender=gender)
72
- self.set_min_norm_value(min_value, save = False)
73
- self.set_max_norm_value(max_value, save = False)
74
- self.save()
75
89
 
90
+ normal_range_dict = self.get_normal_range()
91
+ self.set_min_norm_value(normal_range_dict["min"], save = False)
92
+ self.set_max_norm_value(normal_range_dict["max"], save = False)
93
+ self.save()
76
94
 
77
95
  def set_unit_from_default(self):
78
96
  self.unit = self.lab_value.default_unit
@@ -109,8 +127,8 @@ class PatientLabValue(models.Model):
109
127
 
110
128
  patient:Patient = self.patient
111
129
 
112
- dob = patient.dob
113
- gender:Gender = patient.gender
130
+ dob = patient.dob #TODO: age specific norm values
131
+ gender:Gender = patient.gender # TODO: gender specific norm values
114
132
  lab_value:LabValue = self.lab_value
115
133
 
116
134
  assert self.lab_value, "Lab value must be set to set value by distribution"
@@ -121,7 +139,7 @@ class PatientLabValue(models.Model):
121
139
 
122
140
  if not distribution:
123
141
  warnings.warn(
124
- "No distribution set for lab value, assuming uniform numeric distribution based on normal values"
142
+ f"No distribution set for lab value {lab_value}, assuming uniform numeric distribution based on normal values"
125
143
  )
126
144
 
127
145
  if not self.normal_range.get("min", None) or not self.normal_range.get("max", None):
@@ -133,12 +151,12 @@ class PatientLabValue(models.Model):
133
151
  _name = "auto-" + self.lab_value.name + "-distribution-default-uniform"
134
152
  distribution = NumericValueDistribution(
135
153
  name = _name,
136
- min_value = _min,
137
- max_value = _max,
154
+ min_descriptor = _min,
155
+ max_max_desciptor = _max,
138
156
  distribution_type = "uniform"
139
157
  )
140
158
 
141
- value = distribution.generate_value()
159
+ value = distribution.generate_value(lab_value=lab_value)
142
160
  self.value = value
143
161
  if save:
144
162
  self.save()
@@ -153,7 +171,10 @@ class PatientLabValue(models.Model):
153
171
  return value
154
172
 
155
173
  elif isinstance(distribution, NumericValueDistribution):
156
- value = distribution.generate_value()
174
+ value = distribution.generate_value(
175
+ lab_value=lab_value,
176
+ patient=patient
177
+ )
157
178
  self.value = value
158
179
  if save:
159
180
  self.save()
@@ -8,11 +8,25 @@ class PatientMedication(models.Model):
8
8
  related_name="patient_medications", null=True
9
9
  )
10
10
 
11
- medication_schedules = models.ManyToManyField(
12
- 'MedicationSchedule'
11
+ medication = models.ForeignKey(
12
+ 'Medication', on_delete=models.CASCADE,
13
+ blank=True,
14
+ related_name='patient_medications'
15
+ )
16
+
17
+ intake_times = models.ManyToManyField(
18
+ 'MedicationIntakeTime',
19
+ related_name='patient_medications',
20
+ blank=True,
21
+ )
22
+
23
+ unit = models.ForeignKey(
24
+ 'Unit', on_delete=models.CASCADE,
25
+ null=True, blank=True
26
+ )
27
+ dosage = models.JSONField(
28
+ null=True, blank=True
13
29
  )
14
- unit = models.ForeignKey('Unit', on_delete=models.CASCADE)
15
- dosage = models.JSONField()
16
30
  active = models.BooleanField(default=True)
17
31
 
18
32
  objects = models.Manager()
@@ -27,18 +41,19 @@ class PatientMedication(models.Model):
27
41
  medication_indication: MedicationIndication
28
42
  patient_medication = cls.objects.create(patient=patient, medication_indication=medication_indication)
29
43
  patient_medication.save()
30
- patient_medication.set_schedules_from_indication()
31
44
 
32
45
  return patient_medication
33
46
 
34
- def set_schedules_from_indication(self):
35
- schedules = self.medication_indication.medication_schedules.all()
36
- self.medication_schedules.set(schedules)
37
- self.save()
38
47
 
39
48
  def __str__(self):
40
- indication = self.medication_indication
41
- schedules = self.medication_schedules.all()
42
- return f'{indication} - {schedules}'
49
+ intake_times = self.intake_times.all()
50
+ out = f"{self.medication} (Indication {self.medication_indication}) - "
51
+ out += f"{self.dosage} - {self.unit} - "
52
+
53
+
54
+ for intake_time in intake_times:
55
+ out += f"{intake_time} - "
56
+
57
+ return out
43
58
 
44
59