endoreg-db 0.8.9.2__py3-none-any.whl → 0.8.9.10__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 (450) hide show
  1. endoreg_db/admin.py +10 -5
  2. endoreg_db/apps.py +4 -7
  3. endoreg_db/authz/auth.py +1 -0
  4. endoreg_db/authz/backends.py +1 -1
  5. endoreg_db/authz/management/commands/list_routes.py +2 -0
  6. endoreg_db/authz/middleware.py +8 -7
  7. endoreg_db/authz/permissions.py +21 -10
  8. endoreg_db/authz/policy.py +14 -19
  9. endoreg_db/authz/views_auth.py +14 -10
  10. endoreg_db/codemods/rename_datetime_fields.py +8 -1
  11. endoreg_db/exceptions.py +5 -2
  12. endoreg_db/forms/__init__.py +0 -1
  13. endoreg_db/forms/examination_form.py +4 -3
  14. endoreg_db/forms/patient_finding_intervention_form.py +30 -8
  15. endoreg_db/forms/patient_form.py +9 -13
  16. endoreg_db/forms/questionnaires/__init__.py +1 -1
  17. endoreg_db/forms/settings/__init__.py +4 -1
  18. endoreg_db/forms/unit.py +2 -1
  19. endoreg_db/helpers/count_db.py +17 -14
  20. endoreg_db/helpers/default_objects.py +2 -1
  21. endoreg_db/helpers/download_segmentation_model.py +4 -3
  22. endoreg_db/helpers/interact.py +0 -5
  23. endoreg_db/helpers/test_video_helper.py +33 -25
  24. endoreg_db/import_files/__init__.py +1 -1
  25. endoreg_db/import_files/context/__init__.py +1 -1
  26. endoreg_db/import_files/context/default_sensitive_meta.py +11 -9
  27. endoreg_db/import_files/context/ensure_center.py +4 -4
  28. endoreg_db/import_files/context/file_lock.py +3 -3
  29. endoreg_db/import_files/context/import_context.py +11 -12
  30. endoreg_db/import_files/context/validate_directories.py +1 -0
  31. endoreg_db/import_files/file_storage/create_report_file.py +57 -34
  32. endoreg_db/import_files/file_storage/create_video_file.py +64 -35
  33. endoreg_db/import_files/file_storage/sensitive_meta_storage.py +5 -2
  34. endoreg_db/import_files/file_storage/state_management.py +89 -122
  35. endoreg_db/import_files/file_storage/storage.py +5 -1
  36. endoreg_db/import_files/processing/report_processing/report_anonymization.py +24 -19
  37. endoreg_db/import_files/processing/sensitive_meta_adapter.py +3 -3
  38. endoreg_db/import_files/processing/video_processing/video_anonymization.py +18 -18
  39. endoreg_db/import_files/pseudonymization/k_anonymity.py +8 -9
  40. endoreg_db/import_files/pseudonymization/k_pseudonymity.py +16 -5
  41. endoreg_db/import_files/report_import_service.py +36 -30
  42. endoreg_db/import_files/video_import_service.py +27 -23
  43. endoreg_db/logger_conf.py +56 -40
  44. endoreg_db/management/__init__.py +1 -1
  45. endoreg_db/management/commands/__init__.py +1 -1
  46. endoreg_db/management/commands/check_auth.py +45 -38
  47. endoreg_db/management/commands/create_model_meta_from_huggingface.py +53 -2
  48. endoreg_db/management/commands/create_multilabel_model_meta.py +54 -19
  49. endoreg_db/management/commands/fix_missing_patient_data.py +105 -71
  50. endoreg_db/management/commands/fix_video_paths.py +75 -54
  51. endoreg_db/management/commands/import_report.py +1 -3
  52. endoreg_db/management/commands/list_routes.py +2 -0
  53. endoreg_db/management/commands/load_ai_model_data.py +8 -2
  54. endoreg_db/management/commands/load_ai_model_label_data.py +0 -1
  55. endoreg_db/management/commands/load_center_data.py +3 -3
  56. endoreg_db/management/commands/load_distribution_data.py +35 -38
  57. endoreg_db/management/commands/load_endoscope_data.py +0 -3
  58. endoreg_db/management/commands/load_examination_data.py +20 -4
  59. endoreg_db/management/commands/load_finding_data.py +18 -3
  60. endoreg_db/management/commands/load_gender_data.py +17 -24
  61. endoreg_db/management/commands/load_green_endoscopy_wuerzburg_data.py +95 -85
  62. endoreg_db/management/commands/load_information_source.py +0 -3
  63. endoreg_db/management/commands/load_lab_value_data.py +14 -3
  64. endoreg_db/management/commands/load_legacy_data.py +303 -0
  65. endoreg_db/management/commands/load_name_data.py +1 -2
  66. endoreg_db/management/commands/load_pdf_type_data.py +4 -8
  67. endoreg_db/management/commands/load_profession_data.py +0 -1
  68. endoreg_db/management/commands/load_report_reader_flag_data.py +0 -4
  69. endoreg_db/management/commands/load_requirement_data.py +6 -2
  70. endoreg_db/management/commands/load_unit_data.py +0 -4
  71. endoreg_db/management/commands/load_user_groups.py +5 -7
  72. endoreg_db/management/commands/model_input.py +169 -0
  73. endoreg_db/management/commands/register_ai_model.py +22 -16
  74. endoreg_db/management/commands/setup_endoreg_db.py +110 -32
  75. endoreg_db/management/commands/storage_management.py +14 -8
  76. endoreg_db/management/commands/summarize_db_content.py +154 -63
  77. endoreg_db/management/commands/train_image_multilabel_model.py +144 -0
  78. endoreg_db/management/commands/validate_video_files.py +82 -50
  79. endoreg_db/management/commands/video_validation.py +4 -6
  80. endoreg_db/migrations/0001_initial.py +112 -63
  81. endoreg_db/models/__init__.py +8 -0
  82. endoreg_db/models/administration/ai/active_model.py +5 -5
  83. endoreg_db/models/administration/ai/ai_model.py +41 -18
  84. endoreg_db/models/administration/ai/model_type.py +1 -0
  85. endoreg_db/models/administration/case/case.py +22 -22
  86. endoreg_db/models/administration/center/__init__.py +5 -5
  87. endoreg_db/models/administration/center/center.py +6 -2
  88. endoreg_db/models/administration/center/center_resource.py +18 -4
  89. endoreg_db/models/administration/center/center_shift.py +3 -1
  90. endoreg_db/models/administration/center/center_waste.py +6 -2
  91. endoreg_db/models/administration/person/__init__.py +1 -1
  92. endoreg_db/models/administration/person/employee/__init__.py +1 -1
  93. endoreg_db/models/administration/person/employee/employee_type.py +3 -1
  94. endoreg_db/models/administration/person/examiner/__init__.py +1 -1
  95. endoreg_db/models/administration/person/examiner/examiner.py +10 -2
  96. endoreg_db/models/administration/person/names/first_name.py +6 -4
  97. endoreg_db/models/administration/person/names/last_name.py +4 -3
  98. endoreg_db/models/administration/person/patient/__init__.py +1 -1
  99. endoreg_db/models/administration/person/patient/patient.py +0 -1
  100. endoreg_db/models/administration/person/patient/patient_external_id.py +0 -1
  101. endoreg_db/models/administration/person/person.py +1 -1
  102. endoreg_db/models/administration/product/__init__.py +7 -6
  103. endoreg_db/models/administration/product/product.py +6 -2
  104. endoreg_db/models/administration/product/product_group.py +9 -7
  105. endoreg_db/models/administration/product/product_material.py +9 -2
  106. endoreg_db/models/administration/product/reference_product.py +64 -15
  107. endoreg_db/models/administration/qualification/qualification.py +3 -1
  108. endoreg_db/models/administration/shift/shift.py +3 -1
  109. endoreg_db/models/administration/shift/shift_type.py +12 -4
  110. endoreg_db/models/aidataset/__init__.py +5 -0
  111. endoreg_db/models/aidataset/aidataset.py +193 -0
  112. endoreg_db/models/label/__init__.py +1 -1
  113. endoreg_db/models/label/label.py +10 -2
  114. endoreg_db/models/label/label_set.py +3 -1
  115. endoreg_db/models/label/label_video_segment/_create_from_video.py +6 -2
  116. endoreg_db/models/label/label_video_segment/label_video_segment.py +148 -44
  117. endoreg_db/models/media/__init__.py +12 -5
  118. endoreg_db/models/media/frame/__init__.py +1 -1
  119. endoreg_db/models/media/frame/frame.py +34 -8
  120. endoreg_db/models/media/pdf/__init__.py +2 -1
  121. endoreg_db/models/media/pdf/raw_pdf.py +11 -4
  122. endoreg_db/models/media/pdf/report_file.py +6 -2
  123. endoreg_db/models/media/pdf/report_reader/__init__.py +3 -3
  124. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +15 -5
  125. endoreg_db/models/media/video/create_from_file.py +20 -41
  126. endoreg_db/models/media/video/pipe_1.py +75 -30
  127. endoreg_db/models/media/video/pipe_2.py +37 -12
  128. endoreg_db/models/media/video/video_file.py +36 -24
  129. endoreg_db/models/media/video/video_file_ai.py +235 -70
  130. endoreg_db/models/media/video/video_file_anonymize.py +240 -65
  131. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -1
  132. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +3 -1
  133. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +30 -9
  134. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +95 -29
  135. endoreg_db/models/media/video/video_file_frames/_get_frame.py +13 -3
  136. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -1
  137. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +15 -3
  138. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +15 -3
  139. endoreg_db/models/media/video/video_file_frames/_get_frames.py +7 -2
  140. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +109 -23
  141. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +111 -27
  142. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +46 -13
  143. endoreg_db/models/media/video/video_file_io.py +85 -33
  144. endoreg_db/models/media/video/video_file_meta/__init__.py +6 -6
  145. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +17 -4
  146. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +28 -7
  147. endoreg_db/models/media/video/video_file_meta/get_fps.py +46 -13
  148. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +81 -20
  149. endoreg_db/models/media/video/video_file_meta/text_meta.py +61 -20
  150. endoreg_db/models/media/video/video_file_meta/video_meta.py +40 -12
  151. endoreg_db/models/media/video/video_file_segments.py +118 -27
  152. endoreg_db/models/media/video/video_metadata.py +25 -6
  153. endoreg_db/models/media/video/video_processing.py +54 -15
  154. endoreg_db/models/medical/__init__.py +3 -13
  155. endoreg_db/models/medical/contraindication/__init__.py +3 -1
  156. endoreg_db/models/medical/disease.py +18 -6
  157. endoreg_db/models/medical/event.py +6 -2
  158. endoreg_db/models/medical/examination/__init__.py +5 -1
  159. endoreg_db/models/medical/examination/examination.py +22 -6
  160. endoreg_db/models/medical/examination/examination_indication.py +23 -7
  161. endoreg_db/models/medical/examination/examination_time.py +6 -2
  162. endoreg_db/models/medical/finding/__init__.py +3 -1
  163. endoreg_db/models/medical/finding/finding.py +37 -12
  164. endoreg_db/models/medical/finding/finding_classification.py +27 -8
  165. endoreg_db/models/medical/finding/finding_intervention.py +19 -6
  166. endoreg_db/models/medical/finding/finding_type.py +3 -1
  167. endoreg_db/models/medical/hardware/__init__.py +1 -1
  168. endoreg_db/models/medical/hardware/endoscope.py +14 -2
  169. endoreg_db/models/medical/laboratory/__init__.py +1 -1
  170. endoreg_db/models/medical/laboratory/lab_value.py +139 -39
  171. endoreg_db/models/medical/medication/__init__.py +7 -3
  172. endoreg_db/models/medical/medication/medication.py +3 -1
  173. endoreg_db/models/medical/medication/medication_indication.py +3 -1
  174. endoreg_db/models/medical/medication/medication_indication_type.py +11 -3
  175. endoreg_db/models/medical/medication/medication_intake_time.py +3 -1
  176. endoreg_db/models/medical/medication/medication_schedule.py +3 -1
  177. endoreg_db/models/medical/patient/__init__.py +2 -10
  178. endoreg_db/models/medical/patient/medication_examples.py +3 -14
  179. endoreg_db/models/medical/patient/patient_disease.py +17 -5
  180. endoreg_db/models/medical/patient/patient_event.py +12 -4
  181. endoreg_db/models/medical/patient/patient_examination.py +52 -15
  182. endoreg_db/models/medical/patient/patient_examination_indication.py +15 -4
  183. endoreg_db/models/medical/patient/patient_finding.py +105 -29
  184. endoreg_db/models/medical/patient/patient_finding_classification.py +41 -12
  185. endoreg_db/models/medical/patient/patient_finding_intervention.py +11 -3
  186. endoreg_db/models/medical/patient/patient_lab_sample.py +6 -2
  187. endoreg_db/models/medical/patient/patient_lab_value.py +42 -10
  188. endoreg_db/models/medical/patient/patient_medication.py +25 -7
  189. endoreg_db/models/medical/patient/patient_medication_schedule.py +34 -10
  190. endoreg_db/models/metadata/model_meta.py +40 -12
  191. endoreg_db/models/metadata/model_meta_logic.py +51 -16
  192. endoreg_db/models/metadata/sensitive_meta.py +65 -28
  193. endoreg_db/models/metadata/sensitive_meta_logic.py +28 -26
  194. endoreg_db/models/metadata/video_meta.py +146 -39
  195. endoreg_db/models/metadata/video_prediction_logic.py +70 -21
  196. endoreg_db/models/metadata/video_prediction_meta.py +80 -27
  197. endoreg_db/models/operation_log.py +63 -0
  198. endoreg_db/models/other/__init__.py +10 -10
  199. endoreg_db/models/other/distribution/__init__.py +9 -7
  200. endoreg_db/models/other/distribution/base_value_distribution.py +3 -1
  201. endoreg_db/models/other/distribution/date_value_distribution.py +19 -5
  202. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +3 -1
  203. endoreg_db/models/other/distribution/numeric_value_distribution.py +34 -9
  204. endoreg_db/models/other/emission/__init__.py +1 -1
  205. endoreg_db/models/other/emission/emission_factor.py +9 -3
  206. endoreg_db/models/other/information_source.py +15 -5
  207. endoreg_db/models/other/material.py +3 -1
  208. endoreg_db/models/other/transport_route.py +3 -1
  209. endoreg_db/models/other/unit.py +6 -2
  210. endoreg_db/models/report/report.py +0 -1
  211. endoreg_db/models/requirement/requirement.py +84 -27
  212. endoreg_db/models/requirement/requirement_error.py +5 -6
  213. endoreg_db/models/requirement/requirement_evaluation/__init__.py +1 -1
  214. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +8 -8
  215. endoreg_db/models/requirement/requirement_evaluation/get_values.py +3 -3
  216. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +24 -8
  217. endoreg_db/models/requirement/requirement_operator.py +28 -8
  218. endoreg_db/models/requirement/requirement_set.py +34 -11
  219. endoreg_db/models/state/__init__.py +1 -0
  220. endoreg_db/models/state/audit_ledger.py +9 -2
  221. endoreg_db/models/{media → state}/processing_history/__init__.py +1 -3
  222. endoreg_db/models/state/processing_history/processing_history.py +136 -0
  223. endoreg_db/models/state/raw_pdf.py +0 -1
  224. endoreg_db/models/state/video.py +2 -4
  225. endoreg_db/models/utils.py +4 -2
  226. endoreg_db/queries/__init__.py +2 -6
  227. endoreg_db/queries/annotations/__init__.py +1 -3
  228. endoreg_db/queries/annotations/legacy.py +37 -26
  229. endoreg_db/root_urls.py +3 -4
  230. endoreg_db/schemas/examination_evaluation.py +3 -0
  231. endoreg_db/serializers/Frames_NICE_and_PARIS_classifications.py +249 -163
  232. endoreg_db/serializers/__init__.py +2 -8
  233. endoreg_db/serializers/administration/__init__.py +1 -2
  234. endoreg_db/serializers/administration/ai/__init__.py +0 -1
  235. endoreg_db/serializers/administration/ai/active_model.py +3 -1
  236. endoreg_db/serializers/administration/ai/ai_model.py +5 -3
  237. endoreg_db/serializers/administration/ai/model_type.py +3 -1
  238. endoreg_db/serializers/administration/center.py +7 -2
  239. endoreg_db/serializers/administration/gender.py +4 -2
  240. endoreg_db/serializers/anonymization.py +13 -13
  241. endoreg_db/serializers/evaluation/examination_evaluation.py +0 -1
  242. endoreg_db/serializers/examination/__init__.py +1 -1
  243. endoreg_db/serializers/examination/base.py +12 -13
  244. endoreg_db/serializers/examination/dropdown.py +6 -7
  245. endoreg_db/serializers/examination_serializer.py +3 -6
  246. endoreg_db/serializers/finding/__init__.py +1 -1
  247. endoreg_db/serializers/finding/finding.py +14 -7
  248. endoreg_db/serializers/finding_classification/__init__.py +3 -3
  249. endoreg_db/serializers/finding_classification/choice.py +3 -3
  250. endoreg_db/serializers/finding_classification/classification.py +2 -4
  251. endoreg_db/serializers/label_video_segment/__init__.py +5 -3
  252. endoreg_db/serializers/{label → label_video_segment}/image_classification_annotation.py +5 -5
  253. endoreg_db/serializers/label_video_segment/label/__init__.py +6 -0
  254. endoreg_db/serializers/{label → label_video_segment/label}/label.py +1 -1
  255. endoreg_db/serializers/label_video_segment/label_video_segment.py +338 -228
  256. endoreg_db/serializers/meta/__init__.py +1 -2
  257. endoreg_db/serializers/meta/sensitive_meta_detail.py +28 -13
  258. endoreg_db/serializers/meta/sensitive_meta_update.py +51 -46
  259. endoreg_db/serializers/meta/sensitive_meta_verification.py +19 -16
  260. endoreg_db/serializers/misc/__init__.py +2 -2
  261. endoreg_db/serializers/misc/file_overview.py +11 -7
  262. endoreg_db/serializers/misc/stats.py +10 -8
  263. endoreg_db/serializers/misc/translatable_field_mix_in.py +6 -6
  264. endoreg_db/serializers/misc/upload_job.py +32 -29
  265. endoreg_db/serializers/patient/__init__.py +2 -1
  266. endoreg_db/serializers/patient/patient.py +32 -15
  267. endoreg_db/serializers/patient/patient_dropdown.py +11 -3
  268. endoreg_db/serializers/patient_examination/__init__.py +1 -1
  269. endoreg_db/serializers/patient_examination/patient_examination.py +67 -40
  270. endoreg_db/serializers/patient_finding/__init__.py +1 -1
  271. endoreg_db/serializers/patient_finding/patient_finding.py +2 -1
  272. endoreg_db/serializers/patient_finding/patient_finding_classification.py +17 -9
  273. endoreg_db/serializers/patient_finding/patient_finding_detail.py +26 -17
  274. endoreg_db/serializers/patient_finding/patient_finding_intervention.py +7 -5
  275. endoreg_db/serializers/patient_finding/patient_finding_list.py +10 -11
  276. endoreg_db/serializers/patient_finding/patient_finding_write.py +36 -27
  277. endoreg_db/serializers/pdf/__init__.py +1 -3
  278. endoreg_db/serializers/requirements/requirement_schema.py +1 -6
  279. endoreg_db/serializers/sensitive_meta_serializer.py +100 -81
  280. endoreg_db/serializers/video/__init__.py +2 -2
  281. endoreg_db/serializers/video/{segmentation.py → video_file.py} +66 -47
  282. endoreg_db/serializers/video/video_file_brief.py +6 -2
  283. endoreg_db/serializers/video/video_file_detail.py +36 -23
  284. endoreg_db/serializers/video/video_file_list.py +4 -2
  285. endoreg_db/serializers/video/video_processing_history.py +54 -50
  286. endoreg_db/services/__init__.py +1 -1
  287. endoreg_db/services/anonymization.py +2 -2
  288. endoreg_db/services/examination_evaluation.py +40 -17
  289. endoreg_db/services/model_meta_from_hf.py +76 -0
  290. endoreg_db/services/polling_coordinator.py +101 -70
  291. endoreg_db/services/pseudonym_service.py +27 -22
  292. endoreg_db/services/report_import.py +6 -3
  293. endoreg_db/services/segment_sync.py +75 -59
  294. endoreg_db/services/video_import.py +6 -7
  295. endoreg_db/urls/__init__.py +2 -2
  296. endoreg_db/urls/ai.py +7 -25
  297. endoreg_db/urls/anonymization.py +61 -15
  298. endoreg_db/urls/auth.py +4 -4
  299. endoreg_db/urls/classification.py +4 -9
  300. endoreg_db/urls/examination.py +27 -18
  301. endoreg_db/urls/media.py +27 -34
  302. endoreg_db/urls/patient.py +11 -7
  303. endoreg_db/urls/requirements.py +3 -1
  304. endoreg_db/urls/root_urls.py +2 -3
  305. endoreg_db/urls/stats.py +24 -16
  306. endoreg_db/urls/upload.py +3 -11
  307. endoreg_db/utils/__init__.py +14 -15
  308. endoreg_db/utils/ai/__init__.py +1 -1
  309. endoreg_db/utils/ai/data_loader_for_model_input.py +262 -0
  310. endoreg_db/utils/ai/data_loader_for_model_training.py +262 -0
  311. endoreg_db/utils/ai/get.py +2 -1
  312. endoreg_db/utils/ai/inference_dataset.py +14 -15
  313. endoreg_db/utils/ai/model_training/config.py +117 -0
  314. endoreg_db/utils/ai/model_training/dataset.py +74 -0
  315. endoreg_db/utils/ai/model_training/losses.py +68 -0
  316. endoreg_db/utils/ai/model_training/metrics.py +78 -0
  317. endoreg_db/utils/ai/model_training/model_backbones.py +155 -0
  318. endoreg_db/utils/ai/model_training/model_gastronet_resnet.py +118 -0
  319. endoreg_db/utils/ai/model_training/trainer_gastronet_multilabel.py +771 -0
  320. endoreg_db/utils/ai/multilabel_classification_net.py +21 -6
  321. endoreg_db/utils/ai/predict.py +4 -4
  322. endoreg_db/utils/ai/preprocess.py +19 -11
  323. endoreg_db/utils/calc_duration_seconds.py +4 -4
  324. endoreg_db/utils/case_generator/lab_sample_factory.py +3 -4
  325. endoreg_db/utils/check_video_files.py +74 -47
  326. endoreg_db/utils/cropping.py +10 -9
  327. endoreg_db/utils/dataloader.py +11 -3
  328. endoreg_db/utils/dates.py +3 -4
  329. endoreg_db/utils/defaults/set_default_center.py +7 -6
  330. endoreg_db/utils/env.py +6 -2
  331. endoreg_db/utils/extract_specific_frames.py +24 -9
  332. endoreg_db/utils/file_operations.py +30 -18
  333. endoreg_db/utils/fix_video_path_direct.py +57 -41
  334. endoreg_db/utils/frame_anonymization_utils.py +157 -157
  335. endoreg_db/utils/hashs.py +3 -18
  336. endoreg_db/utils/links/requirement_link.py +96 -52
  337. endoreg_db/utils/ocr.py +30 -25
  338. endoreg_db/utils/operation_log.py +61 -0
  339. endoreg_db/utils/parse_and_generate_yaml.py +12 -13
  340. endoreg_db/utils/paths.py +6 -6
  341. endoreg_db/utils/permissions.py +40 -24
  342. endoreg_db/utils/pipelines/process_video_dir.py +50 -26
  343. endoreg_db/utils/product/sum_emissions.py +5 -3
  344. endoreg_db/utils/product/sum_weights.py +4 -2
  345. endoreg_db/utils/pydantic_models/__init__.py +3 -4
  346. endoreg_db/utils/requirement_operator_logic/_old/lab_value_operators.py +207 -107
  347. endoreg_db/utils/requirement_operator_logic/_old/model_evaluators.py +252 -65
  348. endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +27 -10
  349. endoreg_db/utils/setup_config.py +21 -5
  350. endoreg_db/utils/storage.py +3 -1
  351. endoreg_db/utils/translation.py +19 -15
  352. endoreg_db/utils/uuid.py +1 -0
  353. endoreg_db/utils/validate_endo_roi.py +12 -4
  354. endoreg_db/utils/validate_subcategory_dict.py +26 -24
  355. endoreg_db/utils/validate_video_detailed.py +207 -149
  356. endoreg_db/utils/video/__init__.py +7 -3
  357. endoreg_db/utils/video/extract_frames.py +30 -18
  358. endoreg_db/utils/video/names.py +11 -6
  359. endoreg_db/utils/video/streaming_processor.py +175 -101
  360. endoreg_db/utils/video/video_splitter.py +30 -19
  361. endoreg_db/views/Frames_NICE_and_PARIS_classifications_views.py +59 -50
  362. endoreg_db/views/__init__.py +0 -20
  363. endoreg_db/views/anonymization/__init__.py +6 -2
  364. endoreg_db/views/anonymization/media_management.py +2 -6
  365. endoreg_db/views/anonymization/overview.py +34 -1
  366. endoreg_db/views/anonymization/validate.py +79 -18
  367. endoreg_db/views/auth/__init__.py +1 -1
  368. endoreg_db/views/auth/keycloak.py +16 -14
  369. endoreg_db/views/examination/__init__.py +12 -15
  370. endoreg_db/views/examination/examination.py +5 -5
  371. endoreg_db/views/examination/examination_manifest_cache.py +5 -5
  372. endoreg_db/views/examination/get_finding_classification_choices.py +8 -5
  373. endoreg_db/views/examination/get_finding_classifications.py +9 -7
  374. endoreg_db/views/examination/get_findings.py +8 -10
  375. endoreg_db/views/examination/get_instruments.py +3 -2
  376. endoreg_db/views/examination/get_interventions.py +1 -1
  377. endoreg_db/views/finding/__init__.py +2 -2
  378. endoreg_db/views/finding/finding.py +58 -54
  379. endoreg_db/views/finding/get_classifications.py +1 -1
  380. endoreg_db/views/finding/get_interventions.py +1 -1
  381. endoreg_db/views/finding_classification/__init__.py +5 -5
  382. endoreg_db/views/finding_classification/finding_classification.py +5 -6
  383. endoreg_db/views/finding_classification/get_classification_choices.py +3 -4
  384. endoreg_db/views/media/__init__.py +13 -13
  385. endoreg_db/views/media/pdf_media.py +9 -9
  386. endoreg_db/views/media/sensitive_metadata.py +10 -7
  387. endoreg_db/views/media/video_media.py +4 -4
  388. endoreg_db/views/meta/__init__.py +1 -1
  389. endoreg_db/views/meta/sensitive_meta_list.py +20 -22
  390. endoreg_db/views/meta/sensitive_meta_verification.py +14 -11
  391. endoreg_db/views/misc/__init__.py +6 -34
  392. endoreg_db/views/misc/center.py +2 -1
  393. endoreg_db/views/misc/csrf.py +2 -1
  394. endoreg_db/views/misc/gender.py +2 -1
  395. endoreg_db/views/misc/stats.py +141 -106
  396. endoreg_db/views/patient/__init__.py +1 -3
  397. endoreg_db/views/patient/patient.py +141 -99
  398. endoreg_db/views/patient_examination/__init__.py +5 -5
  399. endoreg_db/views/patient_examination/patient_examination.py +43 -42
  400. endoreg_db/views/patient_examination/patient_examination_create.py +10 -15
  401. endoreg_db/views/patient_examination/patient_examination_detail.py +12 -15
  402. endoreg_db/views/patient_examination/patient_examination_list.py +21 -17
  403. endoreg_db/views/patient_examination/video.py +114 -80
  404. endoreg_db/views/patient_finding/__init__.py +1 -1
  405. endoreg_db/views/patient_finding/patient_finding.py +17 -10
  406. endoreg_db/views/patient_finding/patient_finding_optimized.py +127 -95
  407. endoreg_db/views/patient_finding_classification/__init__.py +1 -1
  408. endoreg_db/views/patient_finding_classification/pfc_create.py +35 -27
  409. endoreg_db/views/report/reimport.py +1 -1
  410. endoreg_db/views/report/report_stream.py +5 -8
  411. endoreg_db/views/requirement/__init__.py +2 -1
  412. endoreg_db/views/requirement/evaluate.py +7 -9
  413. endoreg_db/views/requirement/lookup.py +2 -3
  414. endoreg_db/views/requirement/lookup_store.py +0 -1
  415. endoreg_db/views/requirement/requirement_utils.py +2 -4
  416. endoreg_db/views/stats/__init__.py +4 -4
  417. endoreg_db/views/stats/stats_views.py +152 -115
  418. endoreg_db/views/video/__init__.py +18 -27
  419. endoreg_db/views/{ai → video/ai}/__init__.py +2 -2
  420. endoreg_db/views/{ai → video/ai}/label.py +20 -16
  421. endoreg_db/views/video/correction.py +5 -6
  422. endoreg_db/views/video/reimport.py +134 -99
  423. endoreg_db/views/video/segments_crud.py +134 -44
  424. endoreg_db/views/video/video_apply_mask.py +13 -12
  425. endoreg_db/views/video/video_correction.py +2 -1
  426. endoreg_db/views/video/video_download_processed.py +15 -15
  427. endoreg_db/views/video/video_meta_stats.py +7 -6
  428. endoreg_db/views/video/video_processing_history.py +3 -2
  429. endoreg_db/views/video/video_remove_frames.py +13 -12
  430. endoreg_db/views/video/video_stream.py +110 -82
  431. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/METADATA +9 -3
  432. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/RECORD +434 -431
  433. endoreg_db/management/commands/import_fallback_video.py +0 -203
  434. endoreg_db/management/commands/import_video.py +0 -422
  435. endoreg_db/management/commands/import_video_with_classification.py +0 -367
  436. endoreg_db/models/media/processing_history/processing_history.py +0 -96
  437. endoreg_db/serializers/label/__init__.py +0 -7
  438. endoreg_db/serializers/label_video_segment/_lvs_create.py +0 -149
  439. endoreg_db/serializers/label_video_segment/_lvs_update.py +0 -138
  440. endoreg_db/serializers/label_video_segment/_lvs_validate.py +0 -149
  441. endoreg_db/serializers/label_video_segment/label_video_segment_annotation.py +0 -99
  442. endoreg_db/serializers/label_video_segment/label_video_segment_update.py +0 -163
  443. endoreg_db/services/__old/pdf_import.py +0 -1487
  444. endoreg_db/services/__old/video_import.py +0 -1306
  445. endoreg_db/tasks/upload_tasks.py +0 -216
  446. endoreg_db/tasks/video_ingest.py +0 -161
  447. endoreg_db/tasks/video_processing_tasks.py +0 -327
  448. endoreg_db/views/misc/translation.py +0 -182
  449. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/WHEEL +0 -0
  450. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/licenses/LICENSE +0 -0
@@ -1,9 +1,14 @@
1
- from typing import TYPE_CHECKING, List, Optional, cast
1
+ from typing import TYPE_CHECKING, cast
2
2
 
3
3
  from django.db import models
4
4
 
5
5
  if TYPE_CHECKING:
6
- from endoreg_db.models import Examination, FindingIntervention, InformationSource, Requirement
6
+ from endoreg_db.models import (
7
+ Examination,
8
+ FindingIntervention,
9
+ InformationSource,
10
+ Requirement,
11
+ )
7
12
  from endoreg_db.utils.links.requirement_link import RequirementLinks
8
13
 
9
14
 
@@ -60,12 +65,21 @@ class ExaminationIndication(models.Model):
60
65
  objects = ExaminationIndicationManager()
61
66
 
62
67
  if TYPE_CHECKING:
63
- classifications = cast(models.manager.RelatedManager["ExaminationIndicationClassification"], classifications)
64
- expected_interventions = cast(models.manager.RelatedManager["FindingIntervention"], expected_interventions)
65
- information_sources = cast(models.manager.RelatedManager["InformationSource"], information_sources)
68
+ classifications = cast(
69
+ models.manager.RelatedManager["ExaminationIndicationClassification"],
70
+ classifications,
71
+ )
72
+ expected_interventions = cast(
73
+ models.manager.RelatedManager["FindingIntervention"], expected_interventions
74
+ )
75
+ information_sources = cast(
76
+ models.manager.RelatedManager["InformationSource"], information_sources
77
+ )
66
78
 
67
79
  @property
68
- def related_requirements(self) -> "models.manager.RelatedManager[Requirement]": ...
80
+ def related_requirements(
81
+ self,
82
+ ) -> "models.manager.RelatedManager[Requirement]": ...
69
83
 
70
84
  @property
71
85
  def examinations(self) -> "models.manager.RelatedManager[Examination]": ...
@@ -164,7 +178,9 @@ class ExaminationIndicationClassificationChoiceManager(models.Manager):
164
178
  Manager for ExaminationIndicationClassificationChoice with custom query methods.
165
179
  """
166
180
 
167
- def get_by_natural_key(self, name: str) -> "ExaminationIndicationClassificationChoice":
181
+ def get_by_natural_key(
182
+ self, name: str
183
+ ) -> "ExaminationIndicationClassificationChoice":
168
184
  """
169
185
  Retrieves an ExaminationIndicationClassificationChoice instance by its natural key.
170
186
 
@@ -37,8 +37,12 @@ class ExaminationTime(models.Model):
37
37
  )
38
38
 
39
39
  if TYPE_CHECKING:
40
- time_types = cast(models.manager.RelatedManager["ExaminationTimeType"], time_types)
41
- information_sources = cast(models.manager.RelatedManager["InformationSource"], information_sources)
40
+ time_types = cast(
41
+ models.manager.RelatedManager["ExaminationTimeType"], time_types
42
+ )
43
+ information_sources = cast(
44
+ models.manager.RelatedManager["InformationSource"], information_sources
45
+ )
42
46
 
43
47
  def __str__(self) -> str:
44
48
  """
@@ -2,7 +2,9 @@ from .finding import Finding
2
2
  from .finding_type import FindingType
3
3
 
4
4
  from .finding_classification import (
5
- FindingClassificationType, FindingClassification, FindingClassificationChoice,
5
+ FindingClassificationType,
6
+ FindingClassification,
7
+ FindingClassificationChoice,
6
8
  )
7
9
 
8
10
  from .finding_intervention import FindingIntervention, FindingInterventionType
@@ -12,11 +12,21 @@ class FindingManager(models.Manager):
12
12
  class Finding(models.Model):
13
13
  name = models.CharField(max_length=100, unique=True)
14
14
  description = models.TextField(blank=True, null=True)
15
- finding_types = models.ManyToManyField("FindingType", blank=True, related_name="findings")
16
- finding_interventions = models.ManyToManyField("FindingIntervention", blank=True, related_name="findings")
17
- caused_by_interventions = models.ManyToManyField("FindingIntervention", blank=True, related_name="causes_findings")
18
- finding_classifications = models.ManyToManyField("FindingClassification", blank=True, related_name="findings")
19
- information_sources = models.ManyToManyField("InformationSource", blank=True, related_name="findings")
15
+ finding_types = models.ManyToManyField(
16
+ "FindingType", blank=True, related_name="findings"
17
+ )
18
+ finding_interventions = models.ManyToManyField(
19
+ "FindingIntervention", blank=True, related_name="findings"
20
+ )
21
+ caused_by_interventions = models.ManyToManyField(
22
+ "FindingIntervention", blank=True, related_name="causes_findings"
23
+ )
24
+ finding_classifications = models.ManyToManyField(
25
+ "FindingClassification", blank=True, related_name="findings"
26
+ )
27
+ information_sources = models.ManyToManyField(
28
+ "InformationSource", blank=True, related_name="findings"
29
+ )
20
30
  objects = FindingManager()
21
31
 
22
32
  if TYPE_CHECKING:
@@ -30,9 +40,16 @@ class Finding(models.Model):
30
40
  PatientFindingClassification,
31
41
  )
32
42
 
33
- finding_types = cast(models.manager.RelatedManager["FindingType"], finding_types)
34
- finding_interventions = cast(models.manager.RelatedManager["FindingIntervention"], finding_interventions)
35
- finding_classifications = cast(models.manager.RelatedManager["FindingClassification"], finding_classifications)
43
+ finding_types = cast(
44
+ models.manager.RelatedManager["FindingType"], finding_types
45
+ )
46
+ finding_interventions = cast(
47
+ models.manager.RelatedManager["FindingIntervention"], finding_interventions
48
+ )
49
+ finding_classifications = cast(
50
+ models.manager.RelatedManager["FindingClassification"],
51
+ finding_classifications,
52
+ )
36
53
 
37
54
  def natural_key(self):
38
55
  """
@@ -55,7 +72,9 @@ class Finding(models.Model):
55
72
  """
56
73
  return self.finding_types.all()
57
74
 
58
- def get_classifications(self, classification_type: Optional[str] = None) -> models.QuerySet["FindingClassification"]:
75
+ def get_classifications(
76
+ self, classification_type: Optional[str] = None
77
+ ) -> models.QuerySet["FindingClassification"]:
59
78
  """
60
79
  Retrieve all classifications associated with this finding, optionally filtered by classification type.
61
80
 
@@ -66,7 +85,9 @@ class Finding(models.Model):
66
85
  List[FindingClassification]: List of related classification objects, filtered by type if specified.
67
86
  """
68
87
  if classification_type:
69
- return self.finding_classifications.filter(classification_types__name=classification_type)
88
+ return self.finding_classifications.filter(
89
+ classification_types__name=classification_type
90
+ )
70
91
  return self.finding_classifications.all()
71
92
 
72
93
  def get_location_classifications(self):
@@ -76,7 +97,9 @@ class Finding(models.Model):
76
97
  Returns:
77
98
  QuerySet: All FindingClassification instances linked to this finding where the classification type name is 'location' (case-insensitive).
78
99
  """
79
- return self.finding_classifications.filter(classification_types__name__iexact="location")
100
+ return self.finding_classifications.filter(
101
+ classification_types__name__iexact="location"
102
+ )
80
103
 
81
104
  def get_morphology_classifications(self):
82
105
  """
@@ -85,4 +108,6 @@ class Finding(models.Model):
85
108
  Returns:
86
109
  QuerySet: A queryset of FindingClassification instances associated with this finding and classified as 'morphology'.
87
110
  """
88
- return self.finding_classifications.filter(classification_types__name__iexact="morphology")
111
+ return self.finding_classifications.filter(
112
+ classification_types__name__iexact="morphology"
113
+ )
@@ -28,8 +28,12 @@ class FindingClassificationManager(models.Manager):
28
28
  class FindingClassification(models.Model):
29
29
  name = models.CharField(max_length=255, unique=True)
30
30
  description = models.TextField(blank=True)
31
- finding_types = models.ManyToManyField("FindingType", blank=True, related_name="finding_classifications")
32
- choices = models.ManyToManyField("FindingClassificationChoice", related_name="classifications", blank=True)
31
+ finding_types = models.ManyToManyField(
32
+ "FindingType", blank=True, related_name="finding_classifications"
33
+ )
34
+ choices = models.ManyToManyField(
35
+ "FindingClassificationChoice", related_name="classifications", blank=True
36
+ )
33
37
 
34
38
  classification_types = models.ManyToManyField(
35
39
  to=FindingClassificationType,
@@ -50,12 +54,27 @@ class FindingClassification(models.Model):
50
54
  objects = FindingClassificationManager()
51
55
 
52
56
  if TYPE_CHECKING:
53
- from endoreg_db.models import Examination, Finding, FindingType, InformationSource, PatientFindingClassification
54
-
55
- classification_types = cast(models.manager.RelatedManager["FindingClassificationType"], classification_types)
56
- choices = cast(models.manager.RelatedManager["FindingClassificationChoice"], choices)
57
- finding_types = cast(models.manager.RelatedManager["FindingType"], finding_types)
58
- information_sources = cast(models.manager.RelatedManager["InformationSource"], information_sources)
57
+ from endoreg_db.models import (
58
+ Examination,
59
+ Finding,
60
+ FindingType,
61
+ InformationSource,
62
+ PatientFindingClassification,
63
+ )
64
+
65
+ classification_types = cast(
66
+ models.manager.RelatedManager["FindingClassificationType"],
67
+ classification_types,
68
+ )
69
+ choices = cast(
70
+ models.manager.RelatedManager["FindingClassificationChoice"], choices
71
+ )
72
+ finding_types = cast(
73
+ models.manager.RelatedManager["FindingType"], finding_types
74
+ )
75
+ information_sources = cast(
76
+ models.manager.RelatedManager["InformationSource"], information_sources
77
+ )
59
78
 
60
79
  @property
61
80
  def findings(self) -> "models.manager.RelatedManager[Finding]": ...
@@ -11,7 +11,9 @@ class FindingInterventionManager(models.Manager):
11
11
  class FindingIntervention(models.Model):
12
12
  name = models.CharField(max_length=100, unique=True)
13
13
  description = models.TextField(blank=True, null=True)
14
- intervention_types = models.ManyToManyField("FindingInterventionType", blank=True, related_name="interventions")
14
+ intervention_types = models.ManyToManyField(
15
+ "FindingInterventionType", blank=True, related_name="interventions"
16
+ )
15
17
  information_sources = models.ManyToManyField(
16
18
  "InformationSource",
17
19
  related_name="finding_interventions",
@@ -20,10 +22,19 @@ class FindingIntervention(models.Model):
20
22
  objects = FindingInterventionManager()
21
23
 
22
24
  if TYPE_CHECKING:
23
- from endoreg_db.models import Contraindication, FindingInterventionType, InformationSource, LabValue
24
-
25
- intervention_types = cast(models.manager.RelatedManager["FindingInterventionType"], intervention_types)
26
- information_sources = cast(models.manager.RelatedManager["InformationSource"], information_sources)
25
+ from endoreg_db.models import (
26
+ Contraindication,
27
+ FindingInterventionType,
28
+ InformationSource,
29
+ LabValue,
30
+ )
31
+
32
+ intervention_types = cast(
33
+ models.manager.RelatedManager["FindingInterventionType"], intervention_types
34
+ )
35
+ information_sources = cast(
36
+ models.manager.RelatedManager["InformationSource"], information_sources
37
+ )
27
38
 
28
39
  def natural_key(self):
29
40
  return (self.name,)
@@ -46,7 +57,9 @@ class FindingInterventionType(models.Model):
46
57
  if TYPE_CHECKING:
47
58
 
48
59
  @property
49
- def interventions(self) -> "models.manager.RelatedManager[FindingIntervention]": ...
60
+ def interventions(
61
+ self,
62
+ ) -> "models.manager.RelatedManager[FindingIntervention]": ...
50
63
 
51
64
  def natural_key(self):
52
65
  return (self.name,)
@@ -27,7 +27,9 @@ class FindingType(models.Model):
27
27
  from endoreg_db.models import Examination, Finding, FindingClassification
28
28
 
29
29
  @property
30
- def finding_classifications(self) -> "models.manager.RelatedManager[FindingClassification]": ...
30
+ def finding_classifications(
31
+ self,
32
+ ) -> "models.manager.RelatedManager[FindingClassification]": ...
31
33
 
32
34
  def natural_key(self):
33
35
  return (self.name,)
@@ -5,4 +5,4 @@ __all__ = [
5
5
  "Endoscope",
6
6
  "EndoscopeType",
7
7
  "EndoscopyProcessor",
8
- ]
8
+ ]
@@ -13,8 +13,20 @@ class Endoscope(models.Model):
13
13
 
14
14
  name = models.CharField(max_length=255)
15
15
  sn = models.CharField(max_length=255)
16
- center = models.ForeignKey("Center", blank=True, null=True, on_delete=models.CASCADE, related_name="endoscopes")
17
- endoscope_type = models.ForeignKey("EndoscopeType", blank=True, null=True, on_delete=models.CASCADE, related_name="endoscopes")
16
+ center = models.ForeignKey(
17
+ "Center",
18
+ blank=True,
19
+ null=True,
20
+ on_delete=models.CASCADE,
21
+ related_name="endoscopes",
22
+ )
23
+ endoscope_type = models.ForeignKey(
24
+ "EndoscopeType",
25
+ blank=True,
26
+ null=True,
27
+ on_delete=models.CASCADE,
28
+ related_name="endoscopes",
29
+ )
18
30
 
19
31
  if TYPE_CHECKING:
20
32
  from endoreg_db.models import Center
@@ -2,4 +2,4 @@ from .lab_value import LabValue
2
2
 
3
3
  __all__ = [
4
4
  "LabValue",
5
- ]
5
+ ]
@@ -56,7 +56,9 @@ class LabValueManager(models.Manager):
56
56
  class LabValue(models.Model):
57
57
  name = models.CharField(max_length=255, unique=True)
58
58
  abbreviation = models.CharField(max_length=10, blank=True, null=True)
59
- default_unit = models.ForeignKey("Unit", on_delete=models.CASCADE, blank=True, null=True)
59
+ default_unit = models.ForeignKey(
60
+ "Unit", on_delete=models.CASCADE, blank=True, null=True
61
+ )
60
62
  numeric_precision = models.IntegerField(default=3)
61
63
  default_single_categorical_value_distribution = models.ForeignKey(
62
64
  "SingleCategoricalValueDistribution",
@@ -91,15 +93,22 @@ class LabValue(models.Model):
91
93
  normal_range_gender_dependent = models.BooleanField(default=False)
92
94
  normal_range_special_case = models.BooleanField(default=False)
93
95
  bound_adjustment_factor = models.FloatField(
94
- default=0.1, help_text="Factor for adjusting bounds when generating increased/decreased values, e.g., 0.1 for 10%."
96
+ default=0.1,
97
+ help_text="Factor for adjusting bounds when generating increased/decreased values, e.g., 0.1 for 10%.",
95
98
  )
96
99
  objects = LabValueManager()
97
100
 
98
101
  if TYPE_CHECKING:
99
102
  default_unit: models.ForeignKey["Unit|None"]
100
- default_single_categorical_value_distribution: models.ForeignKey["SingleCategoricalValueDistribution|None"]
101
- default_numerical_value_distribution: models.ForeignKey["NumericValueDistribution|None"]
102
- default_multiple_categorical_value_distribution: models.ForeignKey["MultipleCategoricalValueDistribution|None"]
103
+ default_single_categorical_value_distribution: models.ForeignKey[
104
+ "SingleCategoricalValueDistribution|None"
105
+ ]
106
+ default_numerical_value_distribution: models.ForeignKey[
107
+ "NumericValueDistribution|None"
108
+ ]
109
+ default_multiple_categorical_value_distribution: models.ForeignKey[
110
+ "MultipleCategoricalValueDistribution|None"
111
+ ]
103
112
  default_date_value_distribution: models.ForeignKey["DateValueDistribution|None"]
104
113
 
105
114
  @classmethod
@@ -158,7 +167,9 @@ class LabValue(models.Model):
158
167
  warnings.warn("No default distribution set for lab value")
159
168
  return None
160
169
 
161
- def get_normal_range(self, age: Optional[int] = None, gender: Optional["Gender"] = None):
170
+ def get_normal_range(
171
+ self, age: Optional[int] = None, gender: Optional["Gender"] = None
172
+ ):
162
173
  """
163
174
  Returns the normal range for this lab value, considering age and gender dependencies.
164
175
 
@@ -183,11 +194,15 @@ class LabValue(models.Model):
183
194
  gender_name_to_use = gender.name
184
195
  if gender_name_to_use not in current_range_source:
185
196
  warnings.warn(
186
- f"Normal range for gender '{gender_name_to_use}' not found for LabValue '{self.name}'. Defaulting to 'male' range.", UserWarning
197
+ f"Normal range for gender '{gender_name_to_use}' not found for LabValue '{self.name}'. Defaulting to 'male' range.",
198
+ UserWarning,
187
199
  )
188
200
  gender_name_to_use = "male"
189
201
  else:
190
- warnings.warn(f"Gender not provided for gender-dependent LabValue '{self.name}'. Defaulting to 'male' range.", UserWarning)
202
+ warnings.warn(
203
+ f"Gender not provided for gender-dependent LabValue '{self.name}'. Defaulting to 'male' range.",
204
+ UserWarning,
205
+ )
191
206
  gender_name_to_use = "male"
192
207
 
193
208
  # Attempt gender-specific lookup
@@ -202,17 +217,23 @@ class LabValue(models.Model):
202
217
  )
203
218
 
204
219
  # Fallback to general min/max if needed
205
- if (min_value is None or max_value is None) and isinstance(current_range_source, dict):
220
+ if (min_value is None or max_value is None) and isinstance(
221
+ current_range_source, dict
222
+ ):
206
223
  if min_value is None:
207
224
  min_value = current_range_source.get("min")
208
225
  if max_value is None:
209
226
  max_value = current_range_source.get("max")
210
227
 
211
228
  if age_dependent:
212
- warnings.warn(f"Age dependent normal range not implemented yet for LabValue '{self.name}'. Age: {age}.")
229
+ warnings.warn(
230
+ f"Age dependent normal range not implemented yet for LabValue '{self.name}'. Age: {age}."
231
+ )
213
232
 
214
233
  if special_case:
215
- warnings.warn(f"Special case normal range not implemented yet for LabValue '{self.name}'.")
234
+ warnings.warn(
235
+ f"Special case normal range not implemented yet for LabValue '{self.name}'."
236
+ )
216
237
 
217
238
  # Final contextual warning
218
239
  if min_value is None and max_value is None:
@@ -221,14 +242,20 @@ class LabValue(models.Model):
221
242
  if min_value is None:
222
243
  context_parts = []
223
244
  if gender_dependent:
224
- gender_repr = gender.name if gender and hasattr(gender, "name") else "None"
245
+ gender_repr = (
246
+ gender.name if gender and hasattr(gender, "name") else "None"
247
+ )
225
248
  if gender_name_to_use and gender_name_to_use != gender_repr:
226
- gender_repr = f"{gender_repr} (lookup attempted for: {gender_name_to_use})"
249
+ gender_repr = (
250
+ f"{gender_repr} (lookup attempted for: {gender_name_to_use})"
251
+ )
227
252
  context_parts.append(f"gender: {gender_repr}")
228
253
  if age_dependent:
229
254
  context_parts.append(f"age: {age}")
230
255
 
231
- warning_message = f"Could not determine a 'min' normal range for LabValue '{self.name}'"
256
+ warning_message = (
257
+ f"Could not determine a 'min' normal range for LabValue '{self.name}'"
258
+ )
232
259
  if context_parts:
233
260
  warning_message += f" with context ({', '.join(context_parts)})."
234
261
  else:
@@ -238,7 +265,9 @@ class LabValue(models.Model):
238
265
 
239
266
  return {"min": min_value, "max": max_value}
240
267
 
241
- def get_increased_value(self, patient: Optional["Patient"] = None): # -> Any | None:
268
+ def get_increased_value(
269
+ self, patient: Optional["Patient"] = None
270
+ ): # -> Any | None:
242
271
  """
243
272
  Returns a value that is considered increased for this lab value.
244
273
  It prioritizes sampling from a numerical distribution if available,
@@ -252,8 +281,14 @@ class LabValue(models.Model):
252
281
  if self.default_numerical_value_distribution:
253
282
  if patient:
254
283
  # Attempt to sample above the upper bound, or a high value if no bound
255
- for _ in range(10): # Try a few times to get a value if bounds are restrictive
256
- generated_value = self.default_numerical_value_distribution.generate_value(lab_value=self, patient=patient)
284
+ for _ in range(
285
+ 10
286
+ ): # Try a few times to get a value if bounds are restrictive
287
+ generated_value = (
288
+ self.default_numerical_value_distribution.generate_value(
289
+ lab_value=self, patient=patient
290
+ )
291
+ )
257
292
  if upper_bound is not None:
258
293
  if generated_value > upper_bound:
259
294
  return generated_value
@@ -263,28 +298,50 @@ class LabValue(models.Model):
263
298
  and hasattr(self.default_numerical_value_distribution, "stddev")
264
299
  and self.default_numerical_value_distribution.mean is not None
265
300
  and self.default_numerical_value_distribution.stddev is not None
266
- and generated_value > (self.default_numerical_value_distribution.mean + self.default_numerical_value_distribution.stddev)
301
+ and generated_value
302
+ > (
303
+ self.default_numerical_value_distribution.mean
304
+ + self.default_numerical_value_distribution.stddev
305
+ )
267
306
  ):
268
307
  return generated_value
269
308
  # Fallback if sampling fails to produce a clearly increased value
270
309
  if upper_bound is not None:
271
- return upper_bound + (abs(upper_bound * self.bound_adjustment_factor) if upper_bound != 0 else 1) # Increase by factor or 1
310
+ return upper_bound + (
311
+ abs(upper_bound * self.bound_adjustment_factor)
312
+ if upper_bound != 0
313
+ else 1
314
+ ) # Increase by factor or 1
272
315
  # If no upper bound and sampling didn't provide a clear high value, return a generated value as last resort
273
- return self.default_numerical_value_distribution.generate_value(lab_value=self, patient=patient)
316
+ return self.default_numerical_value_distribution.generate_value(
317
+ lab_value=self, patient=patient
318
+ )
274
319
  else: # No patient, cannot use distribution
275
320
  warnings.warn(
276
321
  f"Cannot use numerical distribution for {self.name} without patient context. Falling back to normal range logic for increased value."
277
322
  )
278
323
  if upper_bound is not None:
279
- return upper_bound + (abs(upper_bound * self.bound_adjustment_factor) if upper_bound != 0 else 1)
324
+ return upper_bound + (
325
+ abs(upper_bound * self.bound_adjustment_factor)
326
+ if upper_bound != 0
327
+ else 1
328
+ )
280
329
  else:
281
- warnings.warn(f"Cannot determine an increased value for {self.name} without an upper normal range or patient context for distribution.")
330
+ warnings.warn(
331
+ f"Cannot determine an increased value for {self.name} without an upper normal range or patient context for distribution."
332
+ )
282
333
  return None
283
334
 
284
335
  elif upper_bound is not None:
285
- return upper_bound + (abs(upper_bound * self.bound_adjustment_factor) if upper_bound != 0 else 1)
336
+ return upper_bound + (
337
+ abs(upper_bound * self.bound_adjustment_factor)
338
+ if upper_bound != 0
339
+ else 1
340
+ )
286
341
  else:
287
- warnings.warn(f"Cannot determine an increased value for {self.name} without a numerical distribution or an upper normal range.")
342
+ warnings.warn(
343
+ f"Cannot determine an increased value for {self.name} without a numerical distribution or an upper normal range."
344
+ )
288
345
  return None
289
346
 
290
347
  def get_normal_value(self, patient: Optional["Patient"] = None):
@@ -302,21 +359,33 @@ class LabValue(models.Model):
302
359
  if self.default_numerical_value_distribution:
303
360
  if patient:
304
361
  for _ in range(10): # Try a few times
305
- generated_value = self.default_numerical_value_distribution.generate_value(lab_value=self, patient=patient)
362
+ generated_value = (
363
+ self.default_numerical_value_distribution.generate_value(
364
+ lab_value=self, patient=patient
365
+ )
366
+ )
306
367
  if lower_bound is not None and upper_bound is not None:
307
368
  if lower_bound <= generated_value <= upper_bound:
308
369
  return generated_value
309
- elif lower_bound is not None and generated_value >= lower_bound: # No upper bound
370
+ elif (
371
+ lower_bound is not None and generated_value >= lower_bound
372
+ ): # No upper bound
310
373
  return generated_value
311
- elif upper_bound is not None and generated_value <= upper_bound: # No lower bound
374
+ elif (
375
+ upper_bound is not None and generated_value <= upper_bound
376
+ ): # No lower bound
312
377
  return generated_value
313
- elif lower_bound is None and upper_bound is None: # No range defined
378
+ elif (
379
+ lower_bound is None and upper_bound is None
380
+ ): # No range defined
314
381
  return generated_value
315
382
  # Fallback if sampling fails to produce a value in range
316
383
  if lower_bound is not None and upper_bound is not None:
317
384
  return (lower_bound + upper_bound) / 2.0
318
385
  # Return any generated value as last resort
319
- return self.default_numerical_value_distribution.generate_value(lab_value=self, patient=patient)
386
+ return self.default_numerical_value_distribution.generate_value(
387
+ lab_value=self, patient=patient
388
+ )
320
389
  else: # No patient, cannot use distribution
321
390
  warnings.warn(
322
391
  f"Cannot use numerical distribution for {self.name} without patient context. Falling back to normal range logic for normal value."
@@ -328,7 +397,10 @@ class LabValue(models.Model):
328
397
  elif upper_bound is not None:
329
398
  return upper_bound
330
399
  else:
331
- warnings.warn(f"Cannot determine a normal value for {self.name} without a normal range or patient context for distribution.", UserWarning)
400
+ warnings.warn(
401
+ f"Cannot determine a normal value for {self.name} without a normal range or patient context for distribution.",
402
+ UserWarning,
403
+ )
332
404
  return None
333
405
 
334
406
  elif lower_bound is not None and upper_bound is not None:
@@ -338,7 +410,9 @@ class LabValue(models.Model):
338
410
  elif upper_bound is not None: # Only max is defined
339
411
  return upper_bound
340
412
  else:
341
- warnings.warn(f"Cannot determine a normal value for {self.name} without a numerical distribution or a normal range.")
413
+ warnings.warn(
414
+ f"Cannot determine a normal value for {self.name} without a numerical distribution or a normal range."
415
+ )
342
416
  return None
343
417
 
344
418
  def get_decreased_value(self, patient: Optional["Patient"] = None):
@@ -355,7 +429,11 @@ class LabValue(models.Model):
355
429
  if self.default_numerical_value_distribution:
356
430
  if patient:
357
431
  for _ in range(10): # Try a few times
358
- generated_value = self.default_numerical_value_distribution.generate_value(lab_value=self, patient=patient)
432
+ generated_value = (
433
+ self.default_numerical_value_distribution.generate_value(
434
+ lab_value=self, patient=patient
435
+ )
436
+ )
359
437
  if lower_bound is not None:
360
438
  if generated_value < lower_bound:
361
439
  return generated_value
@@ -365,26 +443,48 @@ class LabValue(models.Model):
365
443
  and hasattr(self.default_numerical_value_distribution, "stddev")
366
444
  and self.default_numerical_value_distribution.mean is not None
367
445
  and self.default_numerical_value_distribution.stddev is not None
368
- and generated_value < (self.default_numerical_value_distribution.mean - self.default_numerical_value_distribution.stddev)
446
+ and generated_value
447
+ < (
448
+ self.default_numerical_value_distribution.mean
449
+ - self.default_numerical_value_distribution.stddev
450
+ )
369
451
  ):
370
452
  return generated_value
371
453
  # Fallback
372
454
  if lower_bound is not None:
373
- return lower_bound - (abs(lower_bound * self.bound_adjustment_factor) if lower_bound != 0 else 1) # Decrease by factor or 1
455
+ return lower_bound - (
456
+ abs(lower_bound * self.bound_adjustment_factor)
457
+ if lower_bound != 0
458
+ else 1
459
+ ) # Decrease by factor or 1
374
460
  # Return any generated value as last resort
375
- return self.default_numerical_value_distribution.generate_value(lab_value=self, patient=patient)
461
+ return self.default_numerical_value_distribution.generate_value(
462
+ lab_value=self, patient=patient
463
+ )
376
464
  else: # No patient, cannot use distribution
377
465
  warnings.warn(
378
466
  f"Cannot use numerical distribution for {self.name} without patient context. Falling back to normal range logic for decreased value."
379
467
  )
380
468
  if lower_bound is not None:
381
- return lower_bound - (abs(lower_bound * self.bound_adjustment_factor) if lower_bound != 0 else 1)
469
+ return lower_bound - (
470
+ abs(lower_bound * self.bound_adjustment_factor)
471
+ if lower_bound != 0
472
+ else 1
473
+ )
382
474
  else:
383
- warnings.warn(f"Cannot determine a decreased value for {self.name} without a lower normal range or patient context for distribution.")
475
+ warnings.warn(
476
+ f"Cannot determine a decreased value for {self.name} without a lower normal range or patient context for distribution."
477
+ )
384
478
  return None
385
479
 
386
480
  elif lower_bound is not None:
387
- return lower_bound - (abs(lower_bound * self.bound_adjustment_factor) if lower_bound != 0 else 1)
481
+ return lower_bound - (
482
+ abs(lower_bound * self.bound_adjustment_factor)
483
+ if lower_bound != 0
484
+ else 1
485
+ )
388
486
  else:
389
- warnings.warn(f"Cannot determine a decreased value for {self.name} without a numerical distribution or a lower normal range.")
487
+ warnings.warn(
488
+ f"Cannot determine a decreased value for {self.name} without a numerical distribution or a lower normal range."
489
+ )
390
490
  return None