endoreg-db 0.8.6.1__py3-none-any.whl → 0.8.8.0__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 (360) hide show
  1. endoreg_db/authz/auth.py +74 -0
  2. endoreg_db/authz/backends.py +168 -0
  3. endoreg_db/authz/management/commands/list_routes.py +18 -0
  4. endoreg_db/authz/middleware.py +83 -0
  5. endoreg_db/authz/permissions.py +127 -0
  6. endoreg_db/authz/policy.py +218 -0
  7. endoreg_db/authz/views_auth.py +66 -0
  8. endoreg_db/config/env.py +13 -8
  9. endoreg_db/data/__init__.py +8 -31
  10. endoreg_db/data/_examples/disease.yaml +55 -0
  11. endoreg_db/data/_examples/disease_classification.yaml +13 -0
  12. endoreg_db/data/_examples/disease_classification_choice.yaml +62 -0
  13. endoreg_db/data/_examples/event.yaml +64 -0
  14. endoreg_db/data/_examples/examination.yaml +72 -0
  15. endoreg_db/data/_examples/finding/anatomy_colon.yaml +128 -0
  16. endoreg_db/data/_examples/finding/colonoscopy.yaml +40 -0
  17. endoreg_db/data/_examples/finding/colonoscopy_bowel_prep.yaml +56 -0
  18. endoreg_db/data/_examples/finding/complication.yaml +16 -0
  19. endoreg_db/data/_examples/finding/data.yaml +105 -0
  20. endoreg_db/data/_examples/finding/examination_setting.yaml +16 -0
  21. endoreg_db/data/_examples/finding/medication_related.yaml +18 -0
  22. endoreg_db/data/_examples/finding/outcome.yaml +12 -0
  23. endoreg_db/data/_examples/finding_classification/colonoscopy_bowel_preparation.yaml +68 -0
  24. endoreg_db/data/_examples/finding_classification/colonoscopy_jnet.yaml +22 -0
  25. endoreg_db/data/_examples/finding_classification/colonoscopy_kudo.yaml +25 -0
  26. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_circularity.yaml +20 -0
  27. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_planarity.yaml +24 -0
  28. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_size.yaml +68 -0
  29. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_surface.yaml +20 -0
  30. endoreg_db/data/_examples/finding_classification/colonoscopy_location.yaml +80 -0
  31. endoreg_db/data/_examples/finding_classification/colonoscopy_lst.yaml +21 -0
  32. endoreg_db/data/_examples/finding_classification/colonoscopy_nice.yaml +20 -0
  33. endoreg_db/data/_examples/finding_classification/colonoscopy_paris.yaml +26 -0
  34. endoreg_db/data/_examples/finding_classification/colonoscopy_sano.yaml +22 -0
  35. endoreg_db/data/_examples/finding_classification/colonoscopy_summary.yaml +53 -0
  36. endoreg_db/data/_examples/finding_classification/complication_generic.yaml +25 -0
  37. endoreg_db/data/_examples/finding_classification/examination_setting_generic.yaml +40 -0
  38. endoreg_db/data/_examples/finding_classification/histology_colo.yaml +51 -0
  39. endoreg_db/data/_examples/finding_classification/intervention_required.yaml +26 -0
  40. endoreg_db/data/_examples/finding_classification/medication_related.yaml +23 -0
  41. endoreg_db/data/_examples/finding_classification/visualized.yaml +33 -0
  42. endoreg_db/data/_examples/finding_classification_choice/bowel_preparation.yaml +78 -0
  43. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_circularity_default.yaml +32 -0
  44. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_jnet.yaml +15 -0
  45. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_kudo.yaml +23 -0
  46. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_lst.yaml +15 -0
  47. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_nice.yaml +17 -0
  48. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_paris.yaml +57 -0
  49. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_planarity_default.yaml +49 -0
  50. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_sano.yaml +14 -0
  51. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_surface_intact_default.yaml +36 -0
  52. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_location.yaml +229 -0
  53. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_not_complete_reason.yaml +19 -0
  54. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_size.yaml +82 -0
  55. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +15 -0
  56. endoreg_db/data/_examples/finding_classification_choice/complication_generic_types.yaml +15 -0
  57. endoreg_db/data/_examples/finding_classification_choice/examination_setting_generic_types.yaml +15 -0
  58. endoreg_db/data/_examples/finding_classification_choice/histology.yaml +24 -0
  59. endoreg_db/data/_examples/finding_classification_choice/histology_polyp.yaml +20 -0
  60. endoreg_db/data/_examples/finding_classification_choice/outcome.yaml +19 -0
  61. endoreg_db/data/_examples/finding_classification_choice/yes_no_na.yaml +11 -0
  62. endoreg_db/data/_examples/finding_classification_type/colonoscopy_basic.yaml +48 -0
  63. endoreg_db/data/_examples/finding_intervention/endoscopy.yaml +43 -0
  64. endoreg_db/data/_examples/finding_intervention/endoscopy_colonoscopy.yaml +168 -0
  65. endoreg_db/data/_examples/finding_intervention/endoscopy_egd.yaml +128 -0
  66. endoreg_db/data/_examples/finding_intervention/endoscopy_ercp.yaml +32 -0
  67. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_lower.yaml +9 -0
  68. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_upper.yaml +36 -0
  69. endoreg_db/data/_examples/finding_intervention_type/endoscopy.yaml +15 -0
  70. endoreg_db/data/_examples/finding_type/data.yaml +43 -0
  71. endoreg_db/data/_examples/requirement/age.yaml +26 -0
  72. endoreg_db/data/_examples/requirement/colonoscopy_baseline_austria.yaml +45 -0
  73. endoreg_db/data/_examples/requirement/disease_cardiovascular.yaml +79 -0
  74. endoreg_db/data/_examples/requirement/disease_classification_choice_cardiovascular.yaml +41 -0
  75. endoreg_db/data/_examples/requirement/disease_hepatology.yaml +12 -0
  76. endoreg_db/data/_examples/requirement/disease_misc.yaml +12 -0
  77. endoreg_db/data/_examples/requirement/disease_renal.yaml +96 -0
  78. endoreg_db/data/_examples/requirement/endoscopy_bleeding_risk.yaml +59 -0
  79. endoreg_db/data/_examples/requirement/event_cardiology.yaml +251 -0
  80. endoreg_db/data/_examples/requirement/event_requirements.yaml +145 -0
  81. endoreg_db/data/_examples/requirement/finding_colon_polyp.yaml +50 -0
  82. endoreg_db/data/_examples/requirement/gender.yaml +25 -0
  83. endoreg_db/data/_examples/requirement/lab_value.yaml +441 -0
  84. endoreg_db/data/_examples/requirement/medication.yaml +93 -0
  85. endoreg_db/data/_examples/requirement_operator/age.yaml +13 -0
  86. endoreg_db/data/_examples/requirement_operator/lab_operators.yaml +129 -0
  87. endoreg_db/data/_examples/requirement_operator/model_operators.yaml +96 -0
  88. endoreg_db/data/_examples/requirement_set/01_endoscopy_generic.yaml +48 -0
  89. endoreg_db/data/_examples/requirement_set/colonoscopy_austria_screening.yaml +57 -0
  90. endoreg_db/data/_examples/yaml_examples.xlsx +0 -0
  91. endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +4 -3
  92. endoreg_db/data/event_classification/data.yaml +4 -0
  93. endoreg_db/data/event_classification_choice/data.yaml +9 -0
  94. endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +43 -70
  95. endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +22 -52
  96. endoreg_db/data/finding_classification/colonoscopy_location.yaml +31 -62
  97. endoreg_db/data/finding_classification/histology_colo.yaml +28 -36
  98. endoreg_db/data/requirement/colon_polyp_intervention.yaml +49 -0
  99. endoreg_db/data/requirement/coloreg_colon_polyp.yaml +49 -0
  100. endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +31 -12
  101. endoreg_db/data/requirement_set/01_laboratory.yaml +13 -0
  102. endoreg_db/data/requirement_set/02_endoscopy_bleeding_risk.yaml +46 -0
  103. endoreg_db/data/requirement_set/90_coloreg.yaml +178 -0
  104. endoreg_db/data/requirement_set/_old_ +109 -0
  105. endoreg_db/data/requirement_set_type/data.yaml +21 -0
  106. endoreg_db/data/setup_config.yaml +4 -4
  107. endoreg_db/data/tag/requirement_set_tags.yaml +21 -0
  108. endoreg_db/exceptions.py +5 -2
  109. endoreg_db/helpers/data_loader.py +1 -1
  110. endoreg_db/management/commands/create_model_meta_from_huggingface.py +21 -10
  111. endoreg_db/management/commands/create_multilabel_model_meta.py +299 -129
  112. endoreg_db/management/commands/import_video.py +9 -10
  113. endoreg_db/management/commands/import_video_with_classification.py +1 -1
  114. endoreg_db/management/commands/init_default_ai_model.py +1 -1
  115. endoreg_db/management/commands/list_routes.py +18 -0
  116. endoreg_db/management/commands/load_center_data.py +12 -12
  117. endoreg_db/management/commands/load_requirement_data.py +60 -31
  118. endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
  119. endoreg_db/management/commands/setup_endoreg_db.py +3 -3
  120. endoreg_db/management/commands/storage_management.py +271 -203
  121. endoreg_db/migrations/0001_initial.py +1799 -1300
  122. endoreg_db/migrations/0002_requirementset_depends_on.py +18 -0
  123. endoreg_db/migrations/_old/0001_initial.py +1857 -0
  124. endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +68 -0
  125. endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +77 -0
  126. endoreg_db/migrations/_old/0005_merge_20251111_1003.py +14 -0
  127. endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +68 -0
  128. endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +89 -0
  129. endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +27 -0
  130. endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +21 -0
  131. endoreg_db/models/__init__.py +78 -123
  132. endoreg_db/models/administration/__init__.py +21 -42
  133. endoreg_db/models/administration/ai/active_model.py +2 -2
  134. endoreg_db/models/administration/ai/ai_model.py +7 -6
  135. endoreg_db/models/administration/case/__init__.py +1 -15
  136. endoreg_db/models/administration/case/case.py +3 -3
  137. endoreg_db/models/administration/case/case_template/__init__.py +2 -14
  138. endoreg_db/models/administration/case/case_template/case_template.py +2 -124
  139. endoreg_db/models/administration/case/case_template/case_template_rule.py +2 -268
  140. endoreg_db/models/administration/case/case_template/case_template_rule_value.py +2 -85
  141. endoreg_db/models/administration/case/case_template/case_template_type.py +2 -25
  142. endoreg_db/models/administration/center/center.py +33 -19
  143. endoreg_db/models/administration/center/center_product.py +12 -9
  144. endoreg_db/models/administration/center/center_resource.py +25 -19
  145. endoreg_db/models/administration/center/center_shift.py +21 -17
  146. endoreg_db/models/administration/center/center_waste.py +16 -8
  147. endoreg_db/models/administration/person/__init__.py +2 -0
  148. endoreg_db/models/administration/person/employee/employee.py +10 -5
  149. endoreg_db/models/administration/person/employee/employee_qualification.py +9 -4
  150. endoreg_db/models/administration/person/employee/employee_type.py +12 -6
  151. endoreg_db/models/administration/person/examiner/examiner.py +13 -11
  152. endoreg_db/models/administration/person/patient/__init__.py +2 -0
  153. endoreg_db/models/administration/person/patient/patient.py +103 -100
  154. endoreg_db/models/administration/person/patient/patient_external_id.py +37 -0
  155. endoreg_db/models/administration/person/person.py +4 -0
  156. endoreg_db/models/administration/person/profession/__init__.py +8 -4
  157. endoreg_db/models/administration/person/user/portal_user_information.py +11 -7
  158. endoreg_db/models/administration/product/product.py +20 -15
  159. endoreg_db/models/administration/product/product_material.py +17 -18
  160. endoreg_db/models/administration/product/product_weight.py +12 -8
  161. endoreg_db/models/administration/product/reference_product.py +23 -55
  162. endoreg_db/models/administration/qualification/qualification.py +7 -3
  163. endoreg_db/models/administration/qualification/qualification_type.py +7 -3
  164. endoreg_db/models/administration/shift/scheduled_days.py +8 -5
  165. endoreg_db/models/administration/shift/shift.py +16 -12
  166. endoreg_db/models/administration/shift/shift_type.py +23 -31
  167. endoreg_db/models/label/__init__.py +7 -8
  168. endoreg_db/models/label/annotation/image_classification.py +10 -9
  169. endoreg_db/models/label/annotation/video_segmentation_annotation.py +8 -5
  170. endoreg_db/models/label/label.py +15 -15
  171. endoreg_db/models/label/label_set.py +19 -6
  172. endoreg_db/models/label/label_type.py +1 -1
  173. endoreg_db/models/label/label_video_segment/_create_from_video.py +5 -8
  174. endoreg_db/models/label/label_video_segment/label_video_segment.py +76 -102
  175. endoreg_db/models/label/video_segmentation_label.py +4 -0
  176. endoreg_db/models/label/video_segmentation_labelset.py +4 -3
  177. endoreg_db/models/media/frame/frame.py +22 -22
  178. endoreg_db/models/media/pdf/raw_pdf.py +110 -182
  179. endoreg_db/models/media/pdf/report_file.py +25 -29
  180. endoreg_db/models/media/pdf/report_reader/report_reader_config.py +30 -46
  181. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
  182. endoreg_db/models/media/video/__init__.py +1 -0
  183. endoreg_db/models/media/video/create_from_file.py +48 -56
  184. endoreg_db/models/media/video/pipe_2.py +8 -9
  185. endoreg_db/models/media/video/video_file.py +150 -108
  186. endoreg_db/models/media/video/video_file_ai.py +288 -74
  187. endoreg_db/models/media/video/video_file_anonymize.py +38 -38
  188. endoreg_db/models/media/video/video_file_frames/__init__.py +3 -1
  189. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -8
  190. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +7 -9
  191. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +9 -8
  192. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +38 -45
  193. endoreg_db/models/media/video/video_file_frames/_get_frame.py +6 -8
  194. endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +4 -18
  195. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -3
  196. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +7 -6
  197. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +6 -8
  198. endoreg_db/models/media/video/video_file_frames/_get_frames.py +6 -8
  199. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +15 -25
  200. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +26 -23
  201. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +23 -14
  202. endoreg_db/models/media/video/video_file_io.py +109 -62
  203. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +3 -3
  204. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +5 -3
  205. endoreg_db/models/media/video/video_file_meta/get_fps.py +37 -34
  206. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +19 -25
  207. endoreg_db/models/media/video/video_file_meta/text_meta.py +41 -38
  208. endoreg_db/models/media/video/video_file_meta/video_meta.py +14 -7
  209. endoreg_db/models/media/video/video_file_segments.py +24 -17
  210. endoreg_db/models/media/video/video_metadata.py +19 -35
  211. endoreg_db/models/media/video/video_processing.py +96 -95
  212. endoreg_db/models/medical/contraindication/__init__.py +13 -3
  213. endoreg_db/models/medical/disease.py +22 -16
  214. endoreg_db/models/medical/event.py +31 -18
  215. endoreg_db/models/medical/examination/__init__.py +13 -6
  216. endoreg_db/models/medical/examination/examination.py +17 -18
  217. endoreg_db/models/medical/examination/examination_indication.py +26 -25
  218. endoreg_db/models/medical/examination/examination_time.py +16 -6
  219. endoreg_db/models/medical/examination/examination_time_type.py +9 -6
  220. endoreg_db/models/medical/examination/examination_type.py +3 -4
  221. endoreg_db/models/medical/finding/finding.py +38 -39
  222. endoreg_db/models/medical/finding/finding_classification.py +37 -48
  223. endoreg_db/models/medical/finding/finding_intervention.py +27 -22
  224. endoreg_db/models/medical/finding/finding_type.py +13 -12
  225. endoreg_db/models/medical/hardware/endoscope.py +20 -26
  226. endoreg_db/models/medical/hardware/endoscopy_processor.py +2 -2
  227. endoreg_db/models/medical/laboratory/lab_value.py +62 -91
  228. endoreg_db/models/medical/medication/medication.py +22 -10
  229. endoreg_db/models/medical/medication/medication_indication.py +29 -3
  230. endoreg_db/models/medical/medication/medication_indication_type.py +25 -14
  231. endoreg_db/models/medical/medication/medication_intake_time.py +31 -19
  232. endoreg_db/models/medical/medication/medication_schedule.py +27 -16
  233. endoreg_db/models/medical/organ/__init__.py +15 -12
  234. endoreg_db/models/medical/patient/medication_examples.py +1 -5
  235. endoreg_db/models/medical/patient/patient_disease.py +20 -23
  236. endoreg_db/models/medical/patient/patient_event.py +19 -22
  237. endoreg_db/models/medical/patient/patient_examination.py +48 -54
  238. endoreg_db/models/medical/patient/patient_examination_indication.py +16 -14
  239. endoreg_db/models/medical/patient/patient_finding.py +122 -139
  240. endoreg_db/models/medical/patient/patient_finding_classification.py +44 -49
  241. endoreg_db/models/medical/patient/patient_finding_intervention.py +8 -19
  242. endoreg_db/models/medical/patient/patient_lab_sample.py +28 -23
  243. endoreg_db/models/medical/patient/patient_lab_value.py +82 -89
  244. endoreg_db/models/medical/patient/patient_medication.py +27 -38
  245. endoreg_db/models/medical/patient/patient_medication_schedule.py +28 -36
  246. endoreg_db/models/medical/risk/risk.py +7 -6
  247. endoreg_db/models/medical/risk/risk_type.py +8 -5
  248. endoreg_db/models/metadata/model_meta.py +60 -29
  249. endoreg_db/models/metadata/model_meta_logic.py +125 -18
  250. endoreg_db/models/metadata/pdf_meta.py +19 -24
  251. endoreg_db/models/metadata/sensitive_meta.py +102 -85
  252. endoreg_db/models/metadata/sensitive_meta_logic.py +192 -173
  253. endoreg_db/models/metadata/video_meta.py +51 -31
  254. endoreg_db/models/metadata/video_prediction_logic.py +16 -23
  255. endoreg_db/models/metadata/video_prediction_meta.py +29 -33
  256. endoreg_db/models/other/distribution/date_value_distribution.py +89 -29
  257. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +21 -5
  258. endoreg_db/models/other/distribution/numeric_value_distribution.py +114 -53
  259. endoreg_db/models/other/distribution/single_categorical_value_distribution.py +4 -3
  260. endoreg_db/models/other/emission/emission_factor.py +18 -8
  261. endoreg_db/models/other/gender.py +10 -5
  262. endoreg_db/models/other/information_source.py +25 -25
  263. endoreg_db/models/other/material.py +9 -5
  264. endoreg_db/models/other/resource.py +6 -4
  265. endoreg_db/models/other/tag.py +10 -5
  266. endoreg_db/models/other/transport_route.py +13 -8
  267. endoreg_db/models/other/unit.py +10 -6
  268. endoreg_db/models/other/waste.py +6 -5
  269. endoreg_db/models/requirement/requirement.py +580 -272
  270. endoreg_db/models/requirement/requirement_error.py +85 -0
  271. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +268 -0
  272. endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +3 -6
  273. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +90 -64
  274. endoreg_db/models/requirement/requirement_operator.py +36 -33
  275. endoreg_db/models/requirement/requirement_set.py +74 -57
  276. endoreg_db/models/state/__init__.py +4 -4
  277. endoreg_db/models/state/abstract.py +2 -2
  278. endoreg_db/models/state/anonymization.py +12 -0
  279. endoreg_db/models/state/audit_ledger.py +46 -47
  280. endoreg_db/models/state/label_video_segment.py +9 -0
  281. endoreg_db/models/state/raw_pdf.py +40 -46
  282. endoreg_db/models/state/sensitive_meta.py +6 -2
  283. endoreg_db/models/state/video.py +58 -53
  284. endoreg_db/models/upload_job.py +32 -55
  285. endoreg_db/models/utils.py +1 -2
  286. endoreg_db/root_urls.py +21 -2
  287. endoreg_db/serializers/__init__.py +0 -2
  288. endoreg_db/serializers/anonymization.py +18 -10
  289. endoreg_db/serializers/meta/report_meta.py +1 -1
  290. endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
  291. endoreg_db/serializers/misc/file_overview.py +11 -99
  292. endoreg_db/serializers/requirements/requirement_sets.py +92 -22
  293. endoreg_db/serializers/video/segmentation.py +2 -1
  294. endoreg_db/serializers/video/video_processing_history.py +20 -5
  295. endoreg_db/services/anonymization.py +75 -73
  296. endoreg_db/services/lookup_service.py +37 -24
  297. endoreg_db/services/pdf_import.py +166 -68
  298. endoreg_db/services/storage_aware_video_processor.py +140 -114
  299. endoreg_db/services/video_import.py +193 -283
  300. endoreg_db/urls/__init__.py +7 -20
  301. endoreg_db/urls/media.py +108 -67
  302. endoreg_db/urls/root_urls.py +29 -0
  303. endoreg_db/utils/__init__.py +15 -5
  304. endoreg_db/utils/ai/multilabel_classification_net.py +116 -20
  305. endoreg_db/utils/case_generator/__init__.py +3 -0
  306. endoreg_db/utils/dataloader.py +88 -16
  307. endoreg_db/utils/defaults/set_default_center.py +32 -0
  308. endoreg_db/utils/names.py +22 -16
  309. endoreg_db/utils/permissions.py +2 -1
  310. endoreg_db/utils/pipelines/process_video_dir.py +1 -1
  311. endoreg_db/utils/requirement_operator_logic/model_evaluators.py +414 -127
  312. endoreg_db/utils/setup_config.py +8 -5
  313. endoreg_db/utils/storage.py +115 -0
  314. endoreg_db/utils/validate_endo_roi.py +8 -2
  315. endoreg_db/utils/video/ffmpeg_wrapper.py +184 -188
  316. endoreg_db/views/__init__.py +0 -10
  317. endoreg_db/views/anonymization/media_management.py +198 -163
  318. endoreg_db/views/anonymization/overview.py +4 -1
  319. endoreg_db/views/anonymization/validate.py +174 -40
  320. endoreg_db/views/media/__init__.py +2 -0
  321. endoreg_db/views/media/pdf_media.py +131 -152
  322. endoreg_db/views/media/sensitive_metadata.py +46 -6
  323. endoreg_db/views/media/video_media.py +89 -82
  324. endoreg_db/views/media/video_segments.py +2 -3
  325. endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
  326. endoreg_db/views/patient/patient.py +5 -4
  327. endoreg_db/views/pdf/pdf_stream.py +20 -21
  328. endoreg_db/views/pdf/reimport.py +11 -32
  329. endoreg_db/views/requirement/evaluate.py +188 -187
  330. endoreg_db/views/requirement/lookup.py +17 -3
  331. endoreg_db/views/requirement/requirement_utils.py +89 -0
  332. endoreg_db/views/video/__init__.py +0 -2
  333. endoreg_db/views/video/correction.py +2 -2
  334. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
  335. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +341 -245
  336. endoreg_db/models/administration/permissions/__init__.py +0 -44
  337. endoreg_db/models/media/video/video_file_frames.py +0 -0
  338. endoreg_db/models/metadata/frame_ocr_result.py +0 -0
  339. endoreg_db/models/rule/__init__.py +0 -13
  340. endoreg_db/models/rule/rule.py +0 -27
  341. endoreg_db/models/rule/rule_applicator.py +0 -224
  342. endoreg_db/models/rule/rule_attribute_dtype.py +0 -17
  343. endoreg_db/models/rule/rule_type.py +0 -20
  344. endoreg_db/models/rule/ruleset.py +0 -17
  345. endoreg_db/serializers/video/video_metadata.py +0 -105
  346. endoreg_db/urls/report.py +0 -48
  347. endoreg_db/urls/video.py +0 -61
  348. endoreg_db/utils/case_generator/case_generator.py +0 -159
  349. endoreg_db/utils/case_generator/utils.py +0 -30
  350. endoreg_db/views/report/__init__.py +0 -9
  351. endoreg_db/views/report/report_list.py +0 -112
  352. endoreg_db/views/report/report_with_secure_url.py +0 -28
  353. endoreg_db/views/report/start_examination.py +0 -7
  354. endoreg_db/views.py +0 -0
  355. /endoreg_db/data/{requirement_set → _examples/requirement_set}/endoscopy_bleeding_risk.yaml +0 -0
  356. /endoreg_db/migrations/{0002_add_video_correction_models.py → _old/0002_add_video_correction_models.py} +0 -0
  357. /endoreg_db/migrations/{0003_add_center_display_name.py → _old/0003_add_center_display_name.py} +0 -0
  358. /endoreg_db/{models/media/video/refactor_plan.md → views/pdf/pdf_stream_views.py} +0 -0
  359. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
  360. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,14 +1,80 @@
1
- import datetime # Add import
2
- from datetime import timedelta # Add import
1
+ import calendar
2
+ import datetime
3
+ from datetime import timedelta
3
4
  from typing import TYPE_CHECKING
4
5
 
5
6
  if TYPE_CHECKING:
6
- from endoreg_db.utils.links.requirement_link import RequirementLinks
7
7
  from endoreg_db.models.requirement.requirement import Requirement
8
+ from endoreg_db.utils.links.requirement_link import RequirementLinks
8
9
  # from endoreg_db.models import Unit # Potentially needed for dynamic unit handling
9
10
 
10
11
 
11
12
  # Helper function to check if a date is within the timeframe specified by a Requirement
13
+ def _normalize_timeframe_unit(requirement: "Requirement") -> str | None:
14
+ """Derives a canonical timeframe unit string from the requirement."""
15
+ if not requirement.unit:
16
+ return None
17
+
18
+ name = (requirement.unit.name or "").strip().lower()
19
+ abbreviation = (requirement.unit.abbreviation or "").strip().lower()
20
+
21
+ unit_aliases = {
22
+ "hour": "hours",
23
+ "hours": "hours",
24
+ "h": "hours",
25
+ "day": "days",
26
+ "days": "days",
27
+ "d": "days",
28
+ "week": "weeks",
29
+ "weeks": "weeks",
30
+ "w": "weeks",
31
+ "month": "months",
32
+ "months": "months",
33
+ "m": "months",
34
+ "year": "years",
35
+ "years": "years",
36
+ "y": "years",
37
+ }
38
+
39
+ if name in unit_aliases:
40
+ return unit_aliases[name]
41
+ if abbreviation in unit_aliases:
42
+ return unit_aliases[abbreviation]
43
+ return None
44
+
45
+
46
+ def _add_months(base_date: datetime.date, months: int) -> datetime.date:
47
+ """Shifts ``base_date`` by the given number of months, clamping the day when needed."""
48
+ if months == 0:
49
+ return base_date
50
+
51
+ total_months = base_date.month - 1 + months
52
+ year = base_date.year + total_months // 12
53
+ month = total_months % 12 + 1
54
+ day = min(base_date.day, calendar.monthrange(year, month)[1])
55
+ return base_date.replace(year=year, month=month, day=day)
56
+
57
+
58
+ def _shift_date_by_unit(base_date: datetime.date, value: int, unit: str) -> datetime.date:
59
+ """Returns ``base_date`` shifted by ``value`` units using the supported unit set.
60
+
61
+ Hour-level offsets are not supported because ``base_date`` is a ``date``
62
+ object without time information; callers must switch to datetime-aware
63
+ handling before requesting hourly shifts.
64
+ """
65
+ if unit == "days":
66
+ return base_date + timedelta(days=value)
67
+ if unit == "hours":
68
+ raise NotImplementedError("Hour-level timeframe comparisons require datetime-aware inputs; _shift_date_by_unit only operates on date objects.")
69
+ if unit == "weeks":
70
+ return base_date + timedelta(weeks=value)
71
+ if unit == "months":
72
+ return _add_months(base_date, value)
73
+ if unit == "years":
74
+ return _add_months(base_date, value * 12)
75
+ raise NotImplementedError(f"Timeframe unit '{unit}' is not supported.")
76
+
77
+
12
78
  def _is_date_in_timeframe(date_to_check: datetime.date | None, requirement: "Requirement") -> bool:
13
79
  """
14
80
  Checks if a given date falls within the timeframe specified by a Requirement.
@@ -16,8 +82,9 @@ def _is_date_in_timeframe(date_to_check: datetime.date | None, requirement: "Req
16
82
  The timeframe is defined by `numeric_value_min` and `numeric_value_max` on the
17
83
  Requirement, interpreted relative to the current date.
18
84
 
19
- Currently, this function only supports timeframes specified in "days".
20
- If the Requirement's unit is not "days", a NotImplementedError will be raised.
85
+ Currently supports day/week/month/year windows. Hour-level units are
86
+ explicitly rejected because the timeframe calculations operate on dates
87
+ rather than datetimes.
21
88
 
22
89
  Args:
23
90
  date_to_check: The date to evaluate. If None, returns False.
@@ -30,44 +97,39 @@ def _is_date_in_timeframe(date_to_check: datetime.date | None, requirement: "Req
30
97
  necessary timeframe information (unit, min/max values).
31
98
 
32
99
  Raises:
33
- NotImplementedError: If the requirement.unit.name is not 'days' (case-insensitive).
100
+ NotImplementedError: If the requirement.unit resolves to an unsupported
101
+ unit (e.g. hours) or cannot be normalised.
34
102
  """
35
103
  if date_to_check is None:
36
104
  return False
37
- if not requirement.unit or requirement.numeric_value_min is None or requirement.numeric_value_max is None:
38
- return False # Not enough information for timeframe evaluation
39
-
40
- # For now, primarily supporting 'days'. Extend if other units are common.
41
- if requirement.unit.name.lower() != "days":
42
- raise NotImplementedError(
43
- f"Timeframe unit '{requirement.unit.name}' is not supported. "
44
- "Currently, only 'days' is implemented for timeframe checks."
45
- )
105
+ if requirement.numeric_value_min is None or requirement.numeric_value_max is None:
106
+ return False # Not enough information for timeframe evaluation
107
+
108
+ unit = _normalize_timeframe_unit(requirement)
109
+ if not unit:
110
+ raise NotImplementedError("Timeframe unit could not be resolved from requirement's Unit name/abbreviation.")
46
111
 
47
112
  today = datetime.date.today()
48
- # numeric_value_min is typically negative for "days ago" (e.g., -30 for 30 days ago)
49
- # numeric_value_max is typically 0 for "today"
50
113
  timeframe_start_delta = int(requirement.numeric_value_min)
51
114
  timeframe_end_delta = int(requirement.numeric_value_max)
52
115
 
53
- start_date_bound = today + timedelta(days=timeframe_start_delta)
54
- end_date_bound = today + timedelta(days=timeframe_end_delta)
116
+ start_date_bound = _shift_date_by_unit(today, timeframe_start_delta, unit)
117
+ end_date_bound = _shift_date_by_unit(today, timeframe_end_delta, unit)
118
+
119
+ if start_date_bound > end_date_bound:
120
+ start_date_bound, end_date_bound = end_date_bound, start_date_bound
55
121
 
56
122
  return start_date_bound <= date_to_check <= end_date_bound
57
123
 
58
124
 
59
- def _evaluate_models_match_any(
60
- requirement_links: "RequirementLinks",
61
- input_links: "RequirementLinks",
62
- **kwargs
63
- ) -> bool:
125
+ def _evaluate_models_match_any(requirement_links: "RequirementLinks", input_links: "RequirementLinks", **kwargs) -> bool:
64
126
  """
65
127
  Checks if the requirement_links matches any of the input_links.
66
-
128
+
67
129
  Args:
68
130
  requirement_links: The reference set of requirement links to compare against.
69
131
  input_links: The aggregated requirement links from the input objects.
70
-
132
+
71
133
  Returns:
72
134
  True if the input set of requirement links matches according to requirement_links.match_any; otherwise, False.
73
135
  """
@@ -77,8 +139,8 @@ def _evaluate_models_match_any(
77
139
  def _evaluate_models_match_any_in_timeframe(
78
140
  requirement_links: "RequirementLinks",
79
141
  input_links: "RequirementLinks",
80
- requirement: "Requirement", # Explicitly pass Requirement
81
- **kwargs # Keep for consistency, though 'requirement' is the main one used here
142
+ requirement: "Requirement", # Explicitly pass Requirement
143
+ **kwargs, # Keep for consistency, though 'requirement' is the main one used here
82
144
  ) -> bool:
83
145
  """
84
146
  Checks if any relevant model in input_links matches a model specified in
@@ -86,6 +148,9 @@ def _evaluate_models_match_any_in_timeframe(
86
148
 
87
149
  Currently focuses on PatientEvent instances and their dates.
88
150
  """
151
+ if not _has_timeframe_configuration(requirement):
152
+ return False
153
+
89
154
  active_req_links_dict = requirement_links.active()
90
155
  if not active_req_links_dict:
91
156
  # If the Requirement itself doesn't specify any models to match (e.g., requirement.events is empty),
@@ -95,16 +160,16 @@ def _evaluate_models_match_any_in_timeframe(
95
160
 
96
161
  # --- Handle PatientEvents ---
97
162
  # Check if the requirement is concerned with events
98
- if requirement_links.events: # This list contains Event model instances
99
- required_event_models = set(requirement_links.events) # Target Event models from the Requirement
100
-
163
+ if requirement_links.events: # This list contains Event model instances
164
+ required_event_models = set(requirement_links.events) # Target Event models from the Requirement
165
+
101
166
  # input_links.patient_events contains PatientEvent instances provided as input
102
167
  for patient_event_instance in input_links.patient_events:
103
168
  # Check if the event of the current PatientEvent instance is one of the target events
104
169
  if patient_event_instance.event in required_event_models:
105
170
  # If it is, check if this PatientEvent's date is within the timeframe
106
171
  if _is_date_in_timeframe(patient_event_instance.date, requirement):
107
- return True # Found a matching event within the timeframe
172
+ return True # Found a matching event within the timeframe
108
173
 
109
174
  # --- Handle Other Model Types (Example: PatientLabValue) ---
110
175
  # if requirement_links.lab_values:
@@ -119,17 +184,62 @@ def _evaluate_models_match_any_in_timeframe(
119
184
  #
120
185
  # if _is_date_in_timeframe(date_to_check, requirement):
121
186
  # return True
122
-
187
+
123
188
  # If the code reaches here, no matching model within the timeframe was found
124
189
  # for any of the categories specified in requirement_links.
125
190
  return False
126
191
 
127
192
 
128
- def _evaluate_models_match_all(
129
- requirement_links: "RequirementLinks",
130
- input_links: "RequirementLinks",
131
- **kwargs
193
+ def _evaluate_models_match_all_in_timeframe(
194
+ requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs
195
+ ) -> bool:
196
+ if not _evaluate_models_match_all(requirement_links, input_links, **kwargs):
197
+ return False
198
+
199
+ if not requirement_links.events:
200
+ return True
201
+
202
+ if not _has_timeframe_configuration(requirement):
203
+ return False
204
+
205
+ required_events = set(requirement_links.events)
206
+ patient_events = getattr(input_links, "patient_events", [])
207
+ if not patient_events:
208
+ return False
209
+
210
+ for event in required_events:
211
+ found_in_timeframe = False
212
+ for patient_event in patient_events:
213
+ if getattr(patient_event, "event", None) == event:
214
+ if _is_date_in_timeframe(getattr(patient_event, "date", None), requirement):
215
+ found_in_timeframe = True
216
+ break
217
+ if not found_in_timeframe:
218
+ return False
219
+ return True
220
+
221
+ if requirement.unit is None:
222
+ return False
223
+
224
+
225
+ def _evaluate_models_match_none_in_timeframe(
226
+ requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs
132
227
  ) -> bool:
228
+ if not requirement_links.events:
229
+ return True
230
+
231
+ if not _has_timeframe_configuration(requirement):
232
+ return False
233
+
234
+ required_events = set(requirement_links.events)
235
+ for patient_event in getattr(input_links, "patient_events", []):
236
+ if getattr(patient_event, "event", None) in required_events:
237
+ if _is_date_in_timeframe(getattr(patient_event, "date", None), requirement):
238
+ return False
239
+ return True
240
+
241
+
242
+ def _evaluate_models_match_all(requirement_links: "RequirementLinks", input_links: "RequirementLinks", **kwargs) -> bool:
133
243
  """
134
244
  Evaluates if all active links in requirement_links are present in input_links.
135
245
 
@@ -146,62 +256,217 @@ def _evaluate_models_match_all(
146
256
  True if all specified items in requirement_links are found in input_links,
147
257
  False otherwise.
148
258
  """
149
- active_req_links = requirement_links.active() # Get dict of non-empty lists from requirement
259
+ active_req_links = requirement_links.active() # Get dict of non-empty lists from requirement
150
260
 
151
- if not active_req_links: # If the requirement specifies no actual items to link
152
- return True # Vacuously true, as there are no conditions to fail
261
+ if not active_req_links: # If the requirement specifies no actual items to link
262
+ return True # Vacuously true, as there are no conditions to fail
153
263
 
154
264
  for link_category_name, req_items_list in active_req_links.items():
155
265
  input_items_list = getattr(input_links, link_category_name, [])
156
-
266
+
157
267
  try:
158
268
  set_input_items = set(input_items_list)
159
269
  set_req_items = set(req_items_list)
160
- except TypeError:
270
+ except TypeError:
161
271
  for req_item in req_items_list:
162
272
  if req_item not in input_items_list:
163
- return False
164
- continue
273
+ return False
274
+ continue
165
275
 
166
276
  if not set_req_items.issubset(set_input_items):
167
- return False
168
-
277
+ return False
278
+
279
+ return True
280
+
281
+
282
+ def _evaluate_models_match_none(requirement_links: "RequirementLinks", input_links: "RequirementLinks", **kwargs) -> bool:
283
+ """Returns True when no required models are present in the input links."""
284
+ active_req_links = requirement_links.active()
285
+ if not active_req_links:
286
+ return True
287
+
288
+ for link_category_name, req_items_list in active_req_links.items():
289
+ input_items_list = getattr(input_links, link_category_name, [])
290
+ if not input_items_list:
291
+ continue
292
+ try:
293
+ if set(req_items_list) & set(input_items_list):
294
+ return False
295
+ except TypeError:
296
+ for req_item in req_items_list:
297
+ if req_item in input_items_list:
298
+ return False
169
299
  return True
170
300
 
171
- def _evaluate_age_gte(
301
+
302
+ def _count_matching_models(
303
+ requirement_links: "RequirementLinks",
304
+ input_links: "RequirementLinks",
305
+ ) -> int:
306
+ """Counts how many required models are present in the input links."""
307
+ active_req_links = requirement_links.active()
308
+ if not active_req_links:
309
+ return 0
310
+
311
+ match_count = 0
312
+ for link_category_name, req_items_list in active_req_links.items():
313
+ input_items_list = getattr(input_links, link_category_name, [])
314
+ if not input_items_list:
315
+ continue
316
+ try:
317
+ match_count += len(set(req_items_list) & set(input_items_list))
318
+ except TypeError:
319
+ for req_item in req_items_list:
320
+ if req_item in input_items_list:
321
+ match_count += 1
322
+ return match_count
323
+
324
+
325
+ def _resolve_expected_count(requirement: "Requirement") -> int | None:
326
+ """Extracts the expected count from numeric fields on the requirement."""
327
+ if requirement.numeric_value is not None:
328
+ return int(requirement.numeric_value)
329
+ if requirement.numeric_value_min is not None:
330
+ return int(requirement.numeric_value_min)
331
+ return None
332
+
333
+
334
+ def _has_timeframe_configuration(requirement: "Requirement") -> bool:
335
+ """Checks whether timeframe boundaries and unit are configured."""
336
+ return (
337
+ requirement.unit is not None
338
+ and requirement.numeric_value_min is not None
339
+ and requirement.numeric_value_max is not None
340
+ and _normalize_timeframe_unit(requirement) is not None
341
+ )
342
+
343
+
344
+ def _count_matching_events_in_timeframe(
172
345
  requirement_links: "RequirementLinks",
173
346
  input_links: "RequirementLinks",
174
347
  requirement: "Requirement",
175
- **kwargs
348
+ ) -> int:
349
+ """Counts required events that have a matching patient event within the timeframe."""
350
+ required_events = set(requirement_links.events)
351
+ if not required_events:
352
+ return 0
353
+
354
+ patient_events = getattr(input_links, "patient_events", [])
355
+ if not patient_events:
356
+ return 0
357
+
358
+ matched_events = 0
359
+ for event in required_events:
360
+ for patient_event in patient_events:
361
+ if getattr(patient_event, "event", None) != event:
362
+ continue
363
+ if _is_date_in_timeframe(getattr(patient_event, "date", None), requirement):
364
+ matched_events += 1
365
+ break
366
+ return matched_events
367
+
368
+
369
+ def _evaluate_models_match_n(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
370
+ expected = _resolve_expected_count(requirement)
371
+ if expected is None:
372
+ return False
373
+ return _count_matching_models(requirement_links, input_links) == expected
374
+
375
+
376
+ def _evaluate_models_match_n_or_more(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
377
+ threshold = _resolve_expected_count(requirement)
378
+ if threshold is None:
379
+ return False
380
+ return _count_matching_models(requirement_links, input_links) >= max(threshold, 0)
381
+
382
+
383
+ def _evaluate_models_match_n_or_less(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
384
+ limit = _resolve_expected_count(requirement)
385
+ if limit is None:
386
+ return False
387
+ return _count_matching_models(requirement_links, input_links) <= limit
388
+
389
+
390
+ def _evaluate_models_match_count_in_range(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
391
+ if requirement.numeric_value_min is None or requirement.numeric_value_max is None:
392
+ return False
393
+
394
+ lower = int(requirement.numeric_value_min)
395
+ upper = int(requirement.numeric_value_max)
396
+ if lower > upper:
397
+ lower, upper = upper, lower
398
+
399
+ match_count = _count_matching_models(requirement_links, input_links)
400
+ return lower <= match_count <= upper
401
+
402
+
403
+ def _evaluate_models_match_n_in_timeframe(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
404
+ if not _has_timeframe_configuration(requirement):
405
+ return False
406
+
407
+ expected = _resolve_expected_count(requirement)
408
+ if expected is None:
409
+ return False
410
+
411
+ return _count_matching_events_in_timeframe(requirement_links, input_links, requirement) == expected
412
+
413
+
414
+ def _evaluate_models_match_n_or_more_in_timeframe(
415
+ requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs
416
+ ) -> bool:
417
+ if not _has_timeframe_configuration(requirement):
418
+ return False
419
+
420
+ threshold = _resolve_expected_count(requirement)
421
+ if threshold is None:
422
+ return False
423
+
424
+ return _count_matching_events_in_timeframe(requirement_links, input_links, requirement) >= max(threshold, 0)
425
+
426
+
427
+ def _evaluate_models_match_n_or_less_in_timeframe(
428
+ requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs
176
429
  ) -> bool:
430
+ if not _has_timeframe_configuration(requirement):
431
+ return False
432
+
433
+ limit = _resolve_expected_count(requirement)
434
+ if limit is None:
435
+ return False
436
+
437
+ return _count_matching_events_in_timeframe(requirement_links, input_links, requirement) <= limit
438
+
439
+
440
+ def _evaluate_age_gte(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
177
441
  """
178
442
  Checks if any patient in the input has an age greater than or equal to the requirement's numeric_value.
179
-
443
+
180
444
  Args:
181
445
  requirement_links: The RequirementLinks object from the Requirement model (not used for age checks).
182
446
  input_links: The aggregated RequirementLinks object from the input arguments.
183
447
  requirement: The Requirement instance containing the minimum age in numeric_value.
184
448
  **kwargs: Additional keyword arguments, should contain 'original_input_args' with the original Patient instances.
185
-
449
+
186
450
  Returns:
187
451
  True if any patient in the input has an age >= requirement.numeric_value, False otherwise.
188
452
  """
189
- from endoreg_db.models.administration.person.patient import Patient
190
453
  import logging
191
-
454
+
455
+ from endoreg_db.models.administration.person.patient import Patient
456
+
192
457
  logger = logging.getLogger(__name__)
193
-
458
+
194
459
  if requirement.numeric_value is None:
195
460
  logger.debug("age_gte: requirement.numeric_value is None, returning False")
196
461
  return False # Cannot evaluate without a minimum age requirement
197
-
462
+
198
463
  min_age = requirement.numeric_value
199
464
  logger.debug(f"age_gte: Checking if any patient has age >= {min_age}")
200
-
465
+
201
466
  # Check if we have Patient instances in the original_input_args
202
- original_args = kwargs.get('original_input_args', [])
467
+ original_args = kwargs.get("original_input_args", [])
203
468
  logger.debug(f"age_gte: Found {len(original_args)} original input arguments: {[type(arg).__name__ for arg in original_args]}")
204
-
469
+
205
470
  for i, arg in enumerate(original_args):
206
471
  logger.debug(f"age_gte: Checking argument {i}: {type(arg).__name__}")
207
472
  if isinstance(arg, Patient):
@@ -213,7 +478,7 @@ def _evaluate_age_gte(
213
478
  else:
214
479
  logger.debug(f"age_gte: Patient age {patient_age} < {min_age} or is None")
215
480
  # Handle QuerySets of patients
216
- elif hasattr(arg, 'model') and issubclass(arg.model, Patient):
481
+ elif hasattr(arg, "model") and issubclass(arg.model, Patient):
217
482
  logger.debug(f"age_gte: Found Patient QuerySet with {arg.count()} patients")
218
483
  for patient in arg:
219
484
  patient_age = patient.age()
@@ -223,62 +488,52 @@ def _evaluate_age_gte(
223
488
  return True
224
489
  else:
225
490
  logger.debug(f"age_gte: Argument {i} is not a Patient or Patient QuerySet: {type(arg)}")
226
-
491
+
227
492
  logger.debug(f"age_gte: No patient found with age >= {min_age}, returning False")
228
493
  return False
229
494
 
230
495
 
231
- def _evaluate_age_lte(
232
- requirement_links: "RequirementLinks",
233
- input_links: "RequirementLinks",
234
- requirement: "Requirement",
235
- **kwargs
236
- ) -> bool:
496
+ def _evaluate_age_lte(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
237
497
  """
238
498
  Checks if any patient in the input has an age less than or equal to the requirement's numeric_value.
239
-
499
+
240
500
  Args:
241
501
  requirement_links: The RequirementLinks object from the Requirement model (not used for age checks).
242
502
  input_links: The aggregated RequirementLinks object from the input arguments.
243
503
  requirement: The Requirement instance containing the maximum age in numeric_value.
244
504
  **kwargs: Additional keyword arguments, should contain 'original_input_args' with the original Patient instances.
245
-
505
+
246
506
  Returns:
247
507
  True if any patient in the input has an age <= requirement.numeric_value, False otherwise.
248
508
  """
249
509
  from endoreg_db.models.administration.person.patient import Patient
250
-
510
+
251
511
  if requirement.numeric_value is None:
252
512
  return False # Cannot evaluate without a maximum age requirement
253
-
513
+
254
514
  max_age = requirement.numeric_value
255
-
515
+
256
516
  # Check if we have Patient instances in the original_input_args
257
- original_args = kwargs.get('original_input_args', [])
517
+ original_args = kwargs.get("original_input_args", [])
258
518
  for arg in original_args:
259
519
  if isinstance(arg, Patient):
260
520
  patient_age = arg.age()
261
521
  if patient_age is not None and patient_age <= max_age:
262
522
  return True
263
523
  # Handle QuerySets of patients
264
- elif hasattr(arg, 'model') and issubclass(arg.model, Patient):
524
+ elif hasattr(arg, "model") and issubclass(arg.model, Patient):
265
525
  for patient in arg:
266
526
  patient_age = patient.age()
267
527
  if patient_age is not None and patient_age <= max_age:
268
528
  return True
269
-
529
+
270
530
  return False
271
531
 
272
532
 
273
- def dispatch_operator_evaluation(
274
- operator_name: str,
275
- requirement_links: "RequirementLinks",
276
- input_links: "RequirementLinks",
277
- **kwargs
278
- ) -> bool:
533
+ def dispatch_operator_evaluation(operator_name: str, requirement_links: "RequirementLinks", input_links: "RequirementLinks", **kwargs) -> bool:
279
534
  """
280
535
  Dispatches the evaluation to the appropriate function based on the operator name.
281
-
536
+
282
537
  Args:
283
538
  operator_name: The name of the operator to evaluate.
284
539
  requirement_links: The RequirementLinks object from the Requirement model.
@@ -288,81 +543,113 @@ def dispatch_operator_evaluation(
288
543
 
289
544
  Returns:
290
545
  True if the condition defined by the operator is met, False otherwise.
291
-
546
+
292
547
  Raises:
293
548
  NotImplementedError: If the evaluation logic for the operator's name is not implemented.
294
549
  """
550
+ from endoreg_db.models.requirement.requirement import Requirement # Runtime import for isinstance
551
+
295
552
  from .lab_value_operators import LAB_VALUE_OPERATOR_FUNCTIONS
296
- from endoreg_db.models.requirement.requirement import Requirement # Runtime import for isinstance
297
553
 
298
554
  eval_func = None
299
- requirement = kwargs.get("requirement") # Get requirement for operators that need it
555
+ requirement = kwargs.get("requirement") # Get requirement for operators that need it
556
+
557
+ def _kwargs_without_requirement() -> dict:
558
+ return {k: v for k, v in kwargs.items() if k != "requirement"}
300
559
 
301
560
  if operator_name == "models_match_any":
302
561
  eval_func = _evaluate_models_match_any
303
- return eval_func(
304
- requirement_links=requirement_links,
305
- input_links=input_links,
306
- **kwargs
307
- )
562
+ return eval_func(requirement_links=requirement_links, input_links=input_links, **kwargs)
308
563
  elif operator_name == "models_match_all":
309
564
  eval_func = _evaluate_models_match_all
310
- return eval_func(
311
- requirement_links=requirement_links,
312
- input_links=input_links,
313
- **kwargs
314
- )
565
+ return eval_func(requirement_links=requirement_links, input_links=input_links, **kwargs)
566
+ elif operator_name == "models_match_none":
567
+ eval_func = _evaluate_models_match_none
568
+ return eval_func(requirement_links=requirement_links, input_links=input_links, **kwargs)
315
569
  elif operator_name == "models_match_any_in_timeframe":
316
570
  # 'requirement' is already extracted from kwargs via requirement = kwargs.get("requirement")
317
- if not isinstance(requirement, Requirement): # Ensure requirement is present and correct type
571
+ if not isinstance(requirement, Requirement): # Ensure requirement is present and correct type
318
572
  raise ValueError("models_match_any_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
319
-
320
- # Create a new kwargs dict for the call, excluding 'requirement' to avoid passing it twice,
321
- # as it's already an explicit parameter for _evaluate_models_match_any_in_timeframe.
322
- kwargs_for_eval = {k: v for k, v in kwargs.items() if k != 'requirement'}
323
-
573
+ kwargs_for_eval = _kwargs_without_requirement()
324
574
  eval_func = _evaluate_models_match_any_in_timeframe
325
575
  return eval_func(
326
576
  requirement_links=requirement_links,
327
577
  input_links=input_links,
328
- requirement=requirement, # Pass the requirement instance explicitly
329
- **kwargs_for_eval # Pass the remaining kwargs
578
+ requirement=requirement, # Pass the requirement instance explicitly
579
+ **kwargs_for_eval, # Pass the remaining kwargs
580
+ )
581
+ elif operator_name == "models_match_all_in_timeframe":
582
+ if not isinstance(requirement, Requirement):
583
+ raise ValueError("models_match_all_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
584
+ kwargs_for_eval = _kwargs_without_requirement()
585
+ return _evaluate_models_match_all_in_timeframe(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
586
+ elif operator_name == "models_match_none_in_timeframe":
587
+ if not isinstance(requirement, Requirement):
588
+ raise ValueError("models_match_none_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
589
+ kwargs_for_eval = _kwargs_without_requirement()
590
+ return _evaluate_models_match_none_in_timeframe(
591
+ requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval
592
+ )
593
+ elif operator_name == "models_match_n":
594
+ if not isinstance(requirement, Requirement):
595
+ raise ValueError("models_match_n operator requires a valid 'requirement' instance in kwargs.")
596
+ kwargs_for_eval = _kwargs_without_requirement()
597
+ return _evaluate_models_match_n(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
598
+ elif operator_name == "models_match_n_or_more":
599
+ if not isinstance(requirement, Requirement):
600
+ raise ValueError("models_match_n_or_more operator requires a valid 'requirement' instance in kwargs.")
601
+ kwargs_for_eval = _kwargs_without_requirement()
602
+ return _evaluate_models_match_n_or_more(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
603
+ elif operator_name == "models_match_n_or_less":
604
+ if not isinstance(requirement, Requirement):
605
+ raise ValueError("models_match_n_or_less operator requires a valid 'requirement' instance in kwargs.")
606
+ kwargs_for_eval = _kwargs_without_requirement()
607
+ return _evaluate_models_match_n_or_less(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
608
+ elif operator_name == "models_match_count_in_range":
609
+ if not isinstance(requirement, Requirement):
610
+ raise ValueError("models_match_count_in_range operator requires a valid 'requirement' instance in kwargs.")
611
+ kwargs_for_eval = _kwargs_without_requirement()
612
+ return _evaluate_models_match_count_in_range(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
613
+ elif operator_name == "models_match_n_in_timeframe":
614
+ if not isinstance(requirement, Requirement):
615
+ raise ValueError("models_match_n_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
616
+ kwargs_for_eval = _kwargs_without_requirement()
617
+ return _evaluate_models_match_n_in_timeframe(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
618
+ elif operator_name == "models_match_n_or_more_in_timeframe":
619
+ if not isinstance(requirement, Requirement):
620
+ raise ValueError("models_match_n_or_more_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
621
+ kwargs_for_eval = _kwargs_without_requirement()
622
+ return _evaluate_models_match_n_or_more_in_timeframe(
623
+ requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval
624
+ )
625
+ elif operator_name == "models_match_n_or_less_in_timeframe":
626
+ if not isinstance(requirement, Requirement):
627
+ raise ValueError("models_match_n_or_less_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
628
+ kwargs_for_eval = _kwargs_without_requirement()
629
+ return _evaluate_models_match_n_or_less_in_timeframe(
630
+ requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval
330
631
  )
331
632
  elif operator_name in LAB_VALUE_OPERATOR_FUNCTIONS:
332
- if not isinstance(requirement, Requirement): # Ensure requirement is present and correct type
333
- raise ValueError(f"Lab value operator \'{operator_name}\' requires a valid 'requirement' instance in kwargs.")
334
-
633
+ if not isinstance(requirement, Requirement): # Ensure requirement is present and correct type
634
+ raise ValueError(f"Lab value operator '{operator_name}' requires a valid 'requirement' instance in kwargs.")
635
+
335
636
  eval_func = LAB_VALUE_OPERATOR_FUNCTIONS[operator_name]
336
- return eval_func(
337
- input_links=input_links,
338
- requirement=requirement,
339
- operator_kwargs=kwargs
340
- )
637
+ return eval_func(input_links=input_links, requirement=requirement, operator_kwargs=kwargs)
341
638
  elif operator_name == "age_gte":
342
639
  if not isinstance(requirement, Requirement):
343
640
  raise ValueError("age_gte operator requires a valid 'requirement' instance in kwargs.")
344
-
641
+
345
642
  # Create a new kwargs dict for the call, excluding 'requirement' to avoid passing it twice
346
- kwargs_for_eval = {k: v for k, v in kwargs.items() if k != 'requirement'}
347
-
348
- return _evaluate_age_gte(
349
- requirement_links=requirement_links,
350
- input_links=input_links,
351
- requirement=requirement,
352
- **kwargs_for_eval
353
- )
643
+ kwargs_for_eval = {k: v for k, v in kwargs.items() if k != "requirement"}
644
+
645
+ return _evaluate_age_gte(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
354
646
  elif operator_name == "age_lte":
355
647
  if not isinstance(requirement, Requirement):
356
648
  raise ValueError("age_lte operator requires a valid 'requirement' instance in kwargs.")
357
-
649
+
358
650
  # Create a new kwargs dict for the call, excluding 'requirement' to avoid passing it twice
359
- kwargs_for_eval = {k: v for k, v in kwargs.items() if k != 'requirement'}
360
-
361
- return _evaluate_age_lte(
362
- requirement_links=requirement_links,
363
- input_links=input_links,
364
- requirement=requirement,
365
- **kwargs_for_eval
366
- )
651
+ kwargs_for_eval = {k: v for k, v in kwargs.items() if k != "requirement"}
652
+
653
+ return _evaluate_age_lte(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
367
654
  else:
368
655
  raise NotImplementedError(f"Evaluation logic for operator '{operator_name}' is not implemented.")