endoreg-db 0.8.8.0__py3-none-any.whl → 0.8.9.2__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 (402) hide show
  1. endoreg_db/data/__init__.py +22 -8
  2. endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +0 -1
  3. endoreg_db/data/examination/examinations/data.yaml +114 -14
  4. endoreg_db/data/examination/time-type/data.yaml +0 -3
  5. endoreg_db/data/examination_indication/endoscopy.yaml +108 -173
  6. endoreg_db/data/examination_indication_classification/endoscopy.yaml +0 -70
  7. endoreg_db/data/examination_indication_classification_choice/endoscopy.yaml +33 -37
  8. endoreg_db/data/finding/00_generic.yaml +35 -0
  9. endoreg_db/data/finding/00_generic_complication.yaml +9 -0
  10. endoreg_db/data/finding/01_gastroscopy_baseline.yaml +88 -0
  11. endoreg_db/data/finding/01_gastroscopy_observation.yaml +113 -0
  12. endoreg_db/data/finding/02_colonoscopy_baseline.yaml +53 -0
  13. endoreg_db/data/finding/02_colonoscopy_hidden.yaml +119 -0
  14. endoreg_db/data/finding/02_colonoscopy_observation.yaml +152 -0
  15. endoreg_db/data/finding_classification/00_generic.yaml +44 -0
  16. endoreg_db/data/finding_classification/00_generic_histology.yaml +28 -0
  17. endoreg_db/data/finding_classification/00_generic_lesion.yaml +52 -0
  18. endoreg_db/data/finding_classification/{colonoscopy_bowel_preparation.yaml → 02_colonoscopy_baseline.yaml} +35 -20
  19. endoreg_db/data/finding_classification/02_colonoscopy_histology.yaml +13 -0
  20. endoreg_db/data/finding_classification/02_colonoscopy_other.yaml +12 -0
  21. endoreg_db/data/finding_classification/02_colonoscopy_polyp.yaml +101 -0
  22. endoreg_db/data/finding_classification_choice/{yes_no_na.yaml → 00_generic.yaml} +5 -1
  23. endoreg_db/data/finding_classification_choice/{examination_setting_generic_types.yaml → 00_generic_baseline.yaml} +10 -2
  24. endoreg_db/data/finding_classification_choice/{complication_generic_types.yaml → 00_generic_complication.yaml} +1 -1
  25. endoreg_db/data/finding_classification_choice/{histology.yaml → 00_generic_histology.yaml} +1 -4
  26. endoreg_db/data/finding_classification_choice/00_generic_lesion.yaml +158 -0
  27. endoreg_db/data/finding_classification_choice/{bowel_preparation.yaml → 02_colonoscopy_bowel_preparation.yaml} +1 -30
  28. endoreg_db/data/{_examples/finding_classification_choice/colonoscopy_not_complete_reason.yaml → finding_classification_choice/02_colonoscopy_generic.yaml} +1 -1
  29. endoreg_db/data/finding_classification_choice/{histology_polyp.yaml → 02_colonoscopy_histology.yaml} +1 -1
  30. endoreg_db/data/{_examples/finding_classification_choice/colonoscopy_location.yaml → finding_classification_choice/02_colonoscopy_location.yaml} +23 -4
  31. endoreg_db/data/finding_classification_choice/02_colonoscopy_other.yaml +34 -0
  32. endoreg_db/data/finding_classification_choice/02_colonoscopy_polyp_advanced_imaging.yaml +76 -0
  33. endoreg_db/data/{_examples/finding_classification_choice/colon_lesion_paris.yaml → finding_classification_choice/02_colonoscopy_polyp_morphology.yaml} +26 -8
  34. endoreg_db/data/finding_classification_choice/02_colonoscopy_size.yaml +27 -0
  35. endoreg_db/data/finding_classification_type/{colonoscopy_basic.yaml → 00_generic.yaml} +18 -13
  36. endoreg_db/data/finding_classification_type/02_colonoscopy.yaml +9 -0
  37. endoreg_db/data/finding_intervention/00_generic_endoscopy.yaml +59 -0
  38. endoreg_db/data/finding_intervention/00_generic_endoscopy_ablation.yaml +44 -0
  39. endoreg_db/data/finding_intervention/00_generic_endoscopy_bleeding.yaml +55 -0
  40. endoreg_db/data/finding_intervention/00_generic_endoscopy_resection.yaml +85 -0
  41. endoreg_db/data/finding_intervention/00_generic_endoscopy_stenosis.yaml +17 -0
  42. endoreg_db/data/finding_intervention/00_generic_endoscopy_stent.yaml +9 -0
  43. endoreg_db/data/finding_intervention/01_gastroscopy.yaml +19 -0
  44. endoreg_db/data/finding_intervention/04_eus.yaml +39 -0
  45. endoreg_db/data/finding_intervention/05_ercp.yaml +3 -0
  46. endoreg_db/data/finding_type/data.yaml +8 -12
  47. endoreg_db/data/requirement/01_patient_data.yaml +93 -0
  48. endoreg_db/data/requirement_operator/new_operators.yaml +36 -0
  49. endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +0 -2
  50. endoreg_db/data/requirement_set/90_coloreg.yaml +20 -8
  51. endoreg_db/exceptions.py +0 -1
  52. endoreg_db/forms/examination_form.py +1 -1
  53. endoreg_db/helpers/data_loader.py +124 -52
  54. endoreg_db/helpers/default_objects.py +116 -81
  55. endoreg_db/import_files/__init__.py +27 -0
  56. endoreg_db/import_files/context/__init__.py +7 -0
  57. endoreg_db/import_files/context/default_sensitive_meta.py +81 -0
  58. endoreg_db/import_files/context/ensure_center.py +17 -0
  59. endoreg_db/import_files/context/file_lock.py +66 -0
  60. endoreg_db/import_files/context/import_context.py +43 -0
  61. endoreg_db/import_files/context/validate_directories.py +56 -0
  62. endoreg_db/import_files/file_storage/__init__.py +15 -0
  63. endoreg_db/import_files/file_storage/create_report_file.py +76 -0
  64. endoreg_db/import_files/file_storage/create_video_file.py +75 -0
  65. endoreg_db/import_files/file_storage/sensitive_meta_storage.py +39 -0
  66. endoreg_db/import_files/file_storage/state_management.py +496 -0
  67. endoreg_db/import_files/file_storage/storage.py +36 -0
  68. endoreg_db/import_files/import_service.md +26 -0
  69. endoreg_db/import_files/processing/__init__.py +11 -0
  70. endoreg_db/import_files/processing/report_processing/report_anonymization.py +94 -0
  71. endoreg_db/import_files/processing/sensitive_meta_adapter.py +51 -0
  72. endoreg_db/import_files/processing/video_processing/video_anonymization.py +107 -0
  73. endoreg_db/import_files/pseudonymization/fake.py +52 -0
  74. endoreg_db/import_files/pseudonymization/k_anonymity.py +182 -0
  75. endoreg_db/import_files/pseudonymization/k_pseudonymity.py +128 -0
  76. endoreg_db/import_files/pseudonymization/pseudonymize.py +0 -0
  77. endoreg_db/import_files/report_import_service.py +141 -0
  78. endoreg_db/import_files/video_import_service.py +150 -0
  79. endoreg_db/management/commands/import_report.py +130 -65
  80. endoreg_db/management/commands/import_video_with_classification.py +1 -1
  81. endoreg_db/management/commands/load_ai_model_data.py +5 -5
  82. endoreg_db/management/commands/load_ai_model_label_data.py +9 -7
  83. endoreg_db/management/commands/load_base_db_data.py +5 -134
  84. endoreg_db/management/commands/load_contraindication_data.py +14 -16
  85. endoreg_db/management/commands/load_disease_classification_choices_data.py +15 -18
  86. endoreg_db/management/commands/load_disease_classification_data.py +15 -18
  87. endoreg_db/management/commands/load_disease_data.py +25 -28
  88. endoreg_db/management/commands/load_endoscope_data.py +20 -27
  89. endoreg_db/management/commands/load_event_data.py +14 -16
  90. endoreg_db/management/commands/load_examination_data.py +31 -44
  91. endoreg_db/management/commands/load_examination_indication_data.py +20 -21
  92. endoreg_db/management/commands/load_finding_data.py +52 -80
  93. endoreg_db/management/commands/load_information_source.py +21 -23
  94. endoreg_db/management/commands/load_lab_value_data.py +17 -26
  95. endoreg_db/management/commands/load_medication_data.py +13 -12
  96. endoreg_db/management/commands/load_organ_data.py +15 -19
  97. endoreg_db/management/commands/load_pdf_type_data.py +19 -18
  98. endoreg_db/management/commands/load_profession_data.py +14 -17
  99. endoreg_db/management/commands/load_qualification_data.py +20 -23
  100. endoreg_db/management/commands/load_report_reader_flag_data.py +17 -19
  101. endoreg_db/management/commands/load_requirement_data.py +14 -20
  102. endoreg_db/management/commands/load_risk_data.py +7 -6
  103. endoreg_db/management/commands/load_shift_data.py +20 -23
  104. endoreg_db/management/commands/load_tag_data.py +8 -11
  105. endoreg_db/management/commands/load_unit_data.py +17 -19
  106. endoreg_db/management/commands/start_filewatcher.py +46 -37
  107. endoreg_db/management/commands/validate_video_files.py +1 -5
  108. endoreg_db/migrations/0001_initial.py +1360 -1812
  109. endoreg_db/models/administration/person/patient/patient.py +72 -46
  110. endoreg_db/models/label/__init__.py +2 -2
  111. endoreg_db/models/label/annotation/video_segmentation_annotation.py +18 -26
  112. endoreg_db/models/label/label_video_segment/label_video_segment.py +23 -1
  113. endoreg_db/models/media/pdf/raw_pdf.py +136 -64
  114. endoreg_db/models/media/pdf/report_reader/report_reader_config.py +34 -10
  115. endoreg_db/models/media/processing_history/__init__.py +5 -0
  116. endoreg_db/models/media/processing_history/processing_history.py +96 -0
  117. endoreg_db/models/media/video/create_from_file.py +101 -31
  118. endoreg_db/models/media/video/video_file.py +125 -105
  119. endoreg_db/models/media/video/video_file_io.py +31 -26
  120. endoreg_db/models/medical/contraindication/README.md +1 -0
  121. endoreg_db/models/medical/examination/examination.py +28 -8
  122. endoreg_db/models/medical/examination/examination_indication.py +13 -79
  123. endoreg_db/models/medical/examination/examination_time.py +8 -3
  124. endoreg_db/models/medical/finding/finding.py +5 -12
  125. endoreg_db/models/medical/finding/finding_classification.py +18 -37
  126. endoreg_db/models/medical/finding/finding_intervention.py +7 -9
  127. endoreg_db/models/medical/hardware/endoscope.py +6 -0
  128. endoreg_db/models/medical/patient/medication_examples.py +5 -1
  129. endoreg_db/models/medical/patient/patient_finding.py +1 -1
  130. endoreg_db/models/metadata/pdf_meta.py +22 -10
  131. endoreg_db/models/metadata/sensitive_meta.py +3 -0
  132. endoreg_db/models/metadata/sensitive_meta_logic.py +200 -124
  133. endoreg_db/models/other/information_source.py +27 -6
  134. endoreg_db/models/report/__init__.py +0 -0
  135. endoreg_db/models/report/images.py +0 -0
  136. endoreg_db/models/report/report.py +6 -0
  137. endoreg_db/models/requirement/requirement.py +59 -399
  138. endoreg_db/models/requirement/requirement_operator.py +86 -98
  139. endoreg_db/models/state/audit_ledger.py +4 -5
  140. endoreg_db/models/state/raw_pdf.py +69 -30
  141. endoreg_db/models/state/video.py +65 -49
  142. endoreg_db/models/upload_job.py +33 -9
  143. endoreg_db/models/utils.py +27 -23
  144. endoreg_db/queries/__init__.py +3 -1
  145. endoreg_db/schemas/examination_evaluation.py +1 -1
  146. endoreg_db/serializers/__init__.py +2 -8
  147. endoreg_db/serializers/label_video_segment/label_video_segment.py +2 -29
  148. endoreg_db/serializers/meta/__init__.py +1 -6
  149. endoreg_db/serializers/misc/sensitive_patient_data.py +50 -26
  150. endoreg_db/serializers/patient_examination/patient_examination.py +3 -3
  151. endoreg_db/serializers/pdf/anony_text_validation.py +39 -23
  152. endoreg_db/serializers/video/video_file_list.py +65 -34
  153. endoreg_db/services/__old/pdf_import.py +1487 -0
  154. endoreg_db/services/__old/video_import.py +1306 -0
  155. endoreg_db/services/anonymization.py +63 -26
  156. endoreg_db/services/lookup_service.py +28 -28
  157. endoreg_db/services/lookup_store.py +2 -2
  158. endoreg_db/services/pdf_import.py +0 -1480
  159. endoreg_db/services/report_import.py +10 -0
  160. endoreg_db/services/video_import.py +6 -1165
  161. endoreg_db/tasks/upload_tasks.py +79 -70
  162. endoreg_db/tasks/video_ingest.py +8 -4
  163. endoreg_db/urls/__init__.py +0 -14
  164. endoreg_db/urls/ai.py +32 -0
  165. endoreg_db/urls/media.py +21 -24
  166. endoreg_db/utils/dataloader.py +87 -57
  167. endoreg_db/utils/paths.py +110 -46
  168. endoreg_db/utils/pipelines/Readme.md +1 -1
  169. endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +97 -0
  170. endoreg_db/utils/video/ffmpeg_wrapper.py +217 -52
  171. endoreg_db/views/__init__.py +85 -173
  172. endoreg_db/views/ai/__init__.py +8 -0
  173. endoreg_db/views/ai/label.py +155 -0
  174. endoreg_db/views/anonymization/media_management.py +8 -7
  175. endoreg_db/views/anonymization/overview.py +97 -68
  176. endoreg_db/views/anonymization/validate.py +25 -21
  177. endoreg_db/views/media/__init__.py +5 -20
  178. endoreg_db/views/media/pdf_media.py +109 -65
  179. endoreg_db/views/media/sensitive_metadata.py +163 -148
  180. endoreg_db/views/meta/__init__.py +0 -8
  181. endoreg_db/views/misc/__init__.py +1 -7
  182. endoreg_db/views/misc/upload_views.py +94 -93
  183. endoreg_db/views/report/__init__.py +7 -0
  184. endoreg_db/views/{pdf → report}/reimport.py +45 -24
  185. endoreg_db/views/{pdf/pdf_stream.py → report/report_stream.py} +40 -32
  186. endoreg_db/views/requirement/lookup_store.py +22 -90
  187. endoreg_db/views/video/__init__.py +23 -22
  188. endoreg_db/views/video/correction.py +201 -172
  189. endoreg_db/views/video/reimport.py +1 -1
  190. endoreg_db/views/{media/video_segments.py → video/segments_crud.py} +75 -37
  191. endoreg_db/views/video/{video_meta.py → video_meta_stats.py} +2 -2
  192. endoreg_db/views/video/video_stream.py +7 -8
  193. {endoreg_db-0.8.8.0.dist-info → endoreg_db-0.8.9.2.dist-info}/METADATA +2 -2
  194. {endoreg_db-0.8.8.0.dist-info → endoreg_db-0.8.9.2.dist-info}/RECORD +217 -335
  195. {endoreg_db-0.8.8.0.dist-info → endoreg_db-0.8.9.2.dist-info}/WHEEL +1 -1
  196. endoreg_db/data/_examples/disease.yaml +0 -55
  197. endoreg_db/data/_examples/disease_classification.yaml +0 -13
  198. endoreg_db/data/_examples/disease_classification_choice.yaml +0 -62
  199. endoreg_db/data/_examples/event.yaml +0 -64
  200. endoreg_db/data/_examples/examination.yaml +0 -72
  201. endoreg_db/data/_examples/finding/anatomy_colon.yaml +0 -128
  202. endoreg_db/data/_examples/finding/colonoscopy.yaml +0 -40
  203. endoreg_db/data/_examples/finding/colonoscopy_bowel_prep.yaml +0 -56
  204. endoreg_db/data/_examples/finding/complication.yaml +0 -16
  205. endoreg_db/data/_examples/finding/data.yaml +0 -105
  206. endoreg_db/data/_examples/finding/examination_setting.yaml +0 -16
  207. endoreg_db/data/_examples/finding/medication_related.yaml +0 -18
  208. endoreg_db/data/_examples/finding/outcome.yaml +0 -12
  209. endoreg_db/data/_examples/finding_classification/colonoscopy_bowel_preparation.yaml +0 -68
  210. endoreg_db/data/_examples/finding_classification/colonoscopy_jnet.yaml +0 -22
  211. endoreg_db/data/_examples/finding_classification/colonoscopy_kudo.yaml +0 -25
  212. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_circularity.yaml +0 -20
  213. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_planarity.yaml +0 -24
  214. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_size.yaml +0 -68
  215. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_surface.yaml +0 -20
  216. endoreg_db/data/_examples/finding_classification/colonoscopy_location.yaml +0 -80
  217. endoreg_db/data/_examples/finding_classification/colonoscopy_lst.yaml +0 -21
  218. endoreg_db/data/_examples/finding_classification/colonoscopy_nice.yaml +0 -20
  219. endoreg_db/data/_examples/finding_classification/colonoscopy_paris.yaml +0 -26
  220. endoreg_db/data/_examples/finding_classification/colonoscopy_sano.yaml +0 -22
  221. endoreg_db/data/_examples/finding_classification/colonoscopy_summary.yaml +0 -53
  222. endoreg_db/data/_examples/finding_classification/complication_generic.yaml +0 -25
  223. endoreg_db/data/_examples/finding_classification/examination_setting_generic.yaml +0 -40
  224. endoreg_db/data/_examples/finding_classification/histology_colo.yaml +0 -51
  225. endoreg_db/data/_examples/finding_classification/intervention_required.yaml +0 -26
  226. endoreg_db/data/_examples/finding_classification/medication_related.yaml +0 -23
  227. endoreg_db/data/_examples/finding_classification/visualized.yaml +0 -33
  228. endoreg_db/data/_examples/finding_classification_choice/bowel_preparation.yaml +0 -78
  229. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_circularity_default.yaml +0 -32
  230. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_jnet.yaml +0 -15
  231. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_kudo.yaml +0 -23
  232. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_lst.yaml +0 -15
  233. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_nice.yaml +0 -17
  234. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_planarity_default.yaml +0 -49
  235. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_sano.yaml +0 -14
  236. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_surface_intact_default.yaml +0 -36
  237. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_size.yaml +0 -82
  238. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +0 -15
  239. endoreg_db/data/_examples/finding_classification_choice/complication_generic_types.yaml +0 -15
  240. endoreg_db/data/_examples/finding_classification_choice/examination_setting_generic_types.yaml +0 -15
  241. endoreg_db/data/_examples/finding_classification_choice/histology.yaml +0 -24
  242. endoreg_db/data/_examples/finding_classification_choice/histology_polyp.yaml +0 -20
  243. endoreg_db/data/_examples/finding_classification_choice/outcome.yaml +0 -19
  244. endoreg_db/data/_examples/finding_classification_choice/yes_no_na.yaml +0 -11
  245. endoreg_db/data/_examples/finding_classification_type/colonoscopy_basic.yaml +0 -48
  246. endoreg_db/data/_examples/finding_intervention/endoscopy.yaml +0 -43
  247. endoreg_db/data/_examples/finding_intervention/endoscopy_colonoscopy.yaml +0 -168
  248. endoreg_db/data/_examples/finding_intervention/endoscopy_egd.yaml +0 -128
  249. endoreg_db/data/_examples/finding_intervention/endoscopy_ercp.yaml +0 -32
  250. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_lower.yaml +0 -9
  251. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_upper.yaml +0 -36
  252. endoreg_db/data/_examples/finding_intervention_type/endoscopy.yaml +0 -15
  253. endoreg_db/data/_examples/finding_type/data.yaml +0 -43
  254. endoreg_db/data/_examples/requirement/age.yaml +0 -26
  255. endoreg_db/data/_examples/requirement/gender.yaml +0 -25
  256. endoreg_db/data/_examples/requirement_set/01_endoscopy_generic.yaml +0 -48
  257. endoreg_db/data/_examples/requirement_set/colonoscopy_austria_screening.yaml +0 -57
  258. endoreg_db/data/_examples/requirement_set/endoscopy_bleeding_risk.yaml +0 -52
  259. endoreg_db/data/_examples/yaml_examples.xlsx +0 -0
  260. endoreg_db/data/finding/anatomy_colon.yaml +0 -128
  261. endoreg_db/data/finding/colonoscopy.yaml +0 -40
  262. endoreg_db/data/finding/colonoscopy_bowel_prep.yaml +0 -56
  263. endoreg_db/data/finding/complication.yaml +0 -16
  264. endoreg_db/data/finding/data.yaml +0 -105
  265. endoreg_db/data/finding/examination_setting.yaml +0 -16
  266. endoreg_db/data/finding/medication_related.yaml +0 -18
  267. endoreg_db/data/finding/outcome.yaml +0 -12
  268. endoreg_db/data/finding_classification/colonoscopy_jnet.yaml +0 -22
  269. endoreg_db/data/finding_classification/colonoscopy_kudo.yaml +0 -25
  270. endoreg_db/data/finding_classification/colonoscopy_lesion_circularity.yaml +0 -20
  271. endoreg_db/data/finding_classification/colonoscopy_lesion_planarity.yaml +0 -24
  272. endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +0 -38
  273. endoreg_db/data/finding_classification/colonoscopy_lesion_surface.yaml +0 -20
  274. endoreg_db/data/finding_classification/colonoscopy_location.yaml +0 -49
  275. endoreg_db/data/finding_classification/colonoscopy_lst.yaml +0 -21
  276. endoreg_db/data/finding_classification/colonoscopy_nice.yaml +0 -20
  277. endoreg_db/data/finding_classification/colonoscopy_paris.yaml +0 -26
  278. endoreg_db/data/finding_classification/colonoscopy_sano.yaml +0 -22
  279. endoreg_db/data/finding_classification/colonoscopy_summary.yaml +0 -53
  280. endoreg_db/data/finding_classification/complication_generic.yaml +0 -25
  281. endoreg_db/data/finding_classification/examination_setting_generic.yaml +0 -40
  282. endoreg_db/data/finding_classification/histology_colo.yaml +0 -43
  283. endoreg_db/data/finding_classification/intervention_required.yaml +0 -26
  284. endoreg_db/data/finding_classification/medication_related.yaml +0 -23
  285. endoreg_db/data/finding_classification/visualized.yaml +0 -33
  286. endoreg_db/data/finding_classification_choice/colon_lesion_circularity_default.yaml +0 -32
  287. endoreg_db/data/finding_classification_choice/colon_lesion_jnet.yaml +0 -15
  288. endoreg_db/data/finding_classification_choice/colon_lesion_kudo.yaml +0 -23
  289. endoreg_db/data/finding_classification_choice/colon_lesion_lst.yaml +0 -15
  290. endoreg_db/data/finding_classification_choice/colon_lesion_nice.yaml +0 -17
  291. endoreg_db/data/finding_classification_choice/colon_lesion_paris.yaml +0 -57
  292. endoreg_db/data/finding_classification_choice/colon_lesion_planarity_default.yaml +0 -49
  293. endoreg_db/data/finding_classification_choice/colon_lesion_sano.yaml +0 -14
  294. endoreg_db/data/finding_classification_choice/colon_lesion_surface_intact_default.yaml +0 -36
  295. endoreg_db/data/finding_classification_choice/colonoscopy_location.yaml +0 -229
  296. endoreg_db/data/finding_classification_choice/colonoscopy_not_complete_reason.yaml +0 -19
  297. endoreg_db/data/finding_classification_choice/colonoscopy_size.yaml +0 -82
  298. endoreg_db/data/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +0 -15
  299. endoreg_db/data/finding_classification_choice/outcome.yaml +0 -19
  300. endoreg_db/data/finding_intervention/endoscopy.yaml +0 -43
  301. endoreg_db/data/finding_intervention/endoscopy_colonoscopy.yaml +0 -168
  302. endoreg_db/data/finding_intervention/endoscopy_egd.yaml +0 -128
  303. endoreg_db/data/finding_intervention/endoscopy_ercp.yaml +0 -32
  304. endoreg_db/data/finding_intervention/endoscopy_eus_lower.yaml +0 -9
  305. endoreg_db/data/finding_intervention/endoscopy_eus_upper.yaml +0 -36
  306. endoreg_db/data/finding_morphology_classification_type/colonoscopy.yaml +0 -79
  307. endoreg_db/data/requirement/age.yaml +0 -26
  308. endoreg_db/data/requirement/colonoscopy_baseline_austria.yaml +0 -45
  309. endoreg_db/data/requirement/disease_cardiovascular.yaml +0 -79
  310. endoreg_db/data/requirement/disease_classification_choice_cardiovascular.yaml +0 -41
  311. endoreg_db/data/requirement/disease_hepatology.yaml +0 -12
  312. endoreg_db/data/requirement/disease_misc.yaml +0 -12
  313. endoreg_db/data/requirement/disease_renal.yaml +0 -96
  314. endoreg_db/data/requirement/endoscopy_bleeding_risk.yaml +0 -59
  315. endoreg_db/data/requirement/event_cardiology.yaml +0 -251
  316. endoreg_db/data/requirement/event_requirements.yaml +0 -145
  317. endoreg_db/data/requirement/finding_colon_polyp.yaml +0 -50
  318. endoreg_db/data/requirement/gender.yaml +0 -25
  319. endoreg_db/data/requirement/lab_value.yaml +0 -441
  320. endoreg_db/data/requirement/medication.yaml +0 -93
  321. endoreg_db/data/requirement_operator/age.yaml +0 -13
  322. endoreg_db/data/requirement_operator/lab_operators.yaml +0 -129
  323. endoreg_db/data/requirement_operator/model_operators.yaml +0 -96
  324. endoreg_db/management/commands/init_default_ai_model.py +0 -112
  325. endoreg_db/management/commands/reset_celery_schedule.py +0 -9
  326. endoreg_db/management/commands/validate_video.py +0 -204
  327. endoreg_db/migrations/0002_requirementset_depends_on.py +0 -18
  328. endoreg_db/migrations/_old/0001_initial.py +0 -1857
  329. endoreg_db/migrations/_old/0002_add_video_correction_models.py +0 -52
  330. endoreg_db/migrations/_old/0003_add_center_display_name.py +0 -30
  331. endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +0 -68
  332. endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +0 -77
  333. endoreg_db/migrations/_old/0005_merge_20251111_1003.py +0 -14
  334. endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +0 -68
  335. endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +0 -89
  336. endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +0 -27
  337. endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +0 -21
  338. endoreg_db/renames.yml +0 -8
  339. endoreg_db/serializers/_old/raw_pdf_meta_validation.py +0 -223
  340. endoreg_db/serializers/_old/raw_video_meta_validation.py +0 -179
  341. endoreg_db/serializers/_old/video.py +0 -71
  342. endoreg_db/serializers/meta/pdf_file_meta_extraction.py +0 -115
  343. endoreg_db/serializers/meta/report_meta.py +0 -53
  344. endoreg_db/serializers/report/__init__.py +0 -9
  345. endoreg_db/serializers/report/mixins.py +0 -45
  346. endoreg_db/serializers/report/report.py +0 -105
  347. endoreg_db/serializers/report/report_list.py +0 -22
  348. endoreg_db/serializers/report/secure_file_url.py +0 -26
  349. endoreg_db/services/requirements_object.py +0 -147
  350. endoreg_db/services/storage_aware_video_processor.py +0 -370
  351. endoreg_db/urls/files.py +0 -6
  352. endoreg_db/urls/label_video_segment_validate.py +0 -33
  353. endoreg_db/urls/label_video_segments.py +0 -46
  354. endoreg_db/views/label/__init__.py +0 -5
  355. endoreg_db/views/label/label.py +0 -15
  356. endoreg_db/views/label_video_segment/__init__.py +0 -16
  357. endoreg_db/views/label_video_segment/create_lvs_from_annotation.py +0 -44
  358. endoreg_db/views/label_video_segment/get_lvs_by_name_and_video.py +0 -50
  359. endoreg_db/views/label_video_segment/label_video_segment.py +0 -77
  360. endoreg_db/views/label_video_segment/label_video_segment_by_label.py +0 -174
  361. endoreg_db/views/label_video_segment/label_video_segment_detail.py +0 -73
  362. endoreg_db/views/label_video_segment/update_lvs_from_annotation.py +0 -46
  363. endoreg_db/views/label_video_segment/validate.py +0 -226
  364. endoreg_db/views/media/segments.py +0 -71
  365. endoreg_db/views/meta/available_files_list.py +0 -146
  366. endoreg_db/views/meta/report_meta.py +0 -53
  367. endoreg_db/views/meta/sensitive_meta_detail.py +0 -85
  368. endoreg_db/views/misc/secure_file_serving_view.py +0 -80
  369. endoreg_db/views/misc/secure_file_url_view.py +0 -84
  370. endoreg_db/views/misc/secure_url_validate.py +0 -79
  371. endoreg_db/views/patient_examination/DEPRECATED_video_backup.py +0 -164
  372. endoreg_db/views/patient_finding_location/__init__.py +0 -5
  373. endoreg_db/views/patient_finding_location/pfl_create.py +0 -70
  374. endoreg_db/views/patient_finding_morphology/__init__.py +0 -5
  375. endoreg_db/views/patient_finding_morphology/pfm_create.py +0 -70
  376. endoreg_db/views/pdf/__init__.py +0 -8
  377. endoreg_db/views/video/segmentation.py +0 -274
  378. endoreg_db/views/video/task_status.py +0 -49
  379. endoreg_db/views/video/timeline.py +0 -46
  380. endoreg_db/views/video/video_analyze.py +0 -52
  381. /endoreg_db/data/requirement/{colon_polyp_intervention.yaml → old/colon_polyp_intervention.yaml} +0 -0
  382. /endoreg_db/data/{_examples/requirement → requirement/old}/colonoscopy_baseline_austria.yaml +0 -0
  383. /endoreg_db/data/requirement/{coloreg_colon_polyp.yaml → old/coloreg_colon_polyp.yaml} +0 -0
  384. /endoreg_db/data/{_examples/requirement → requirement/old}/disease_cardiovascular.yaml +0 -0
  385. /endoreg_db/data/{_examples/requirement → requirement/old}/disease_classification_choice_cardiovascular.yaml +0 -0
  386. /endoreg_db/data/{_examples/requirement → requirement/old}/disease_hepatology.yaml +0 -0
  387. /endoreg_db/data/{_examples/requirement → requirement/old}/disease_misc.yaml +0 -0
  388. /endoreg_db/data/{_examples/requirement → requirement/old}/disease_renal.yaml +0 -0
  389. /endoreg_db/data/{_examples/requirement → requirement/old}/endoscopy_bleeding_risk.yaml +0 -0
  390. /endoreg_db/data/{_examples/requirement → requirement/old}/event_cardiology.yaml +0 -0
  391. /endoreg_db/data/{_examples/requirement → requirement/old}/event_requirements.yaml +0 -0
  392. /endoreg_db/data/{_examples/requirement → requirement/old}/finding_colon_polyp.yaml +0 -0
  393. /endoreg_db/{urls/sensitive_meta.py → data/requirement/old/gender.yaml} +0 -0
  394. /endoreg_db/data/{_examples/requirement → requirement/old}/lab_value.yaml +0 -0
  395. /endoreg_db/data/{_examples/requirement → requirement/old}/medication.yaml +0 -0
  396. /endoreg_db/data/{_examples/requirement_operator → requirement_operator/_old}/age.yaml +0 -0
  397. /endoreg_db/data/{_examples/requirement_operator → requirement_operator/_old}/lab_operators.yaml +0 -0
  398. /endoreg_db/data/{_examples/requirement_operator → requirement_operator/_old}/model_operators.yaml +0 -0
  399. /endoreg_db/{views/pdf/pdf_stream_views.py → import_files/pseudonymization/__init__.py} +0 -0
  400. /endoreg_db/utils/requirement_operator_logic/{lab_value_operators.py → _old/lab_value_operators.py} +0 -0
  401. /endoreg_db/utils/requirement_operator_logic/{model_evaluators.py → _old/model_evaluators.py} +0 -0
  402. {endoreg_db-0.8.8.0.dist-info → endoreg_db-0.8.9.2.dist-info}/licenses/LICENSE +0 -0
@@ -1,28 +1,15 @@
1
1
  import logging
2
2
  from subprocess import run
3
- from typing import TYPE_CHECKING, Dict, List, Union, cast, Tuple
3
+ from typing import TYPE_CHECKING, Dict, List, Tuple, Union, cast
4
4
 
5
5
  from django.db import models
6
+ from pydantic import BaseModel
6
7
 
7
8
  from endoreg_db.utils.links.requirement_link import RequirementLinks
8
9
 
9
-
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
12
 
13
- def _validate_requirement_configuration(instance: "Requirement") -> bool:
14
- """Ensures requirement fixtures declare both requirement_types and operators."""
15
- if not instance.requirement_types.exists():
16
- raise ValueError(
17
- f"Requirement '{instance.name}' must be associated with at least one RequirementType."
18
- )
19
- if not instance.operators.exists():
20
- raise ValueError(
21
- f"Requirement '{instance.name}' must be associated with at least one RequirementOperator."
22
- )
23
- return True
24
-
25
-
26
13
  QuerySet = models.QuerySet
27
14
 
28
15
  if TYPE_CHECKING:
@@ -130,33 +117,43 @@ class Requirement(models.Model):
130
117
 
131
118
  name = models.CharField(max_length=100, unique=True)
132
119
  description = models.TextField(blank=True, null=True)
133
-
120
+
121
+ operator_instructions = models.TextField(
122
+ help_text="semicolon-separated list of target attributes for the requirement",
123
+ )
124
+
125
+ @property
126
+ def operator_instructions_parsed(self):
127
+ from endoreg_db.models.requirement.requirement_operator import RequirementOperator
128
+
129
+ instructions = RequirementOperator.parse_instructions(self.operator_instructions)
130
+ return instructions
134
131
 
135
132
  numeric_value = models.FloatField(
136
133
  blank=True,
137
134
  null=True,
138
- help_text="Numeric value for the requirement. If not set, the requirement is not used in calculations.",
135
+ help_text="Numeric value for the requirement. ons.",
139
136
  )
140
137
  numeric_value_min = models.FloatField(
141
138
  blank=True,
142
139
  null=True,
143
- help_text="Minimum numeric value for the requirement. If not set, the requirement is not used in calculations.",
140
+ help_text="Minimum numeric value for the requirement. ons.",
144
141
  )
145
142
  numeric_value_max = models.FloatField(
146
143
  blank=True,
147
144
  null=True,
148
- help_text="Maximum numeric value for the requirement. If not set, the requirement is not used in calculations.",
145
+ help_text="Maximum numeric value for the requirement. ons.",
149
146
  )
150
147
  string_value = models.CharField(
151
148
  max_length=100,
152
149
  blank=True,
153
150
  null=True,
154
- help_text="String value for the requirement. If not set, the requirement is not used in calculations.",
151
+ help_text="String value for the requirement. ons.",
155
152
  )
156
153
  string_values = models.TextField(
157
154
  blank=True,
158
155
  null=True,
159
- help_text=" ','-separated list of string values for the requirement.If not set, the requirement is not used in calculations.",
156
+ help_text=" ','-separated list of string values for the requirement.ons.",
160
157
  )
161
158
  objects = RequirementManager()
162
159
 
@@ -166,9 +163,9 @@ class Requirement(models.Model):
166
163
  related_name="linked_requirements",
167
164
  )
168
165
 
169
- operators = models.ManyToManyField(
166
+ operator = models.ForeignKey(
170
167
  "RequirementOperator",
171
- blank=True,
168
+ on_delete=models.CASCADE,
172
169
  related_name="required_in",
173
170
  )
174
171
 
@@ -271,12 +268,8 @@ class Requirement(models.Model):
271
268
  )
272
269
 
273
270
  if TYPE_CHECKING:
274
- requirement_types = cast(
275
- models.manager.RelatedManager["RequirementType"], requirement_types
276
- )
277
- operators = cast(
278
- models.manager.RelatedManager["RequirementOperator"], operators
279
- )
271
+ requirement_types = cast(models.manager.RelatedManager["RequirementType"], requirement_types)
272
+ operator = models.ForeignKey["RequirementOperator"]
280
273
  # requirement_sets = cast(models.manager.RelatedManager["RequirementSet"], requirement_sets)
281
274
  examinations = cast(models.manager.RelatedManager["Examination"], examinations)
282
275
  examination_indications = cast(
@@ -299,9 +292,7 @@ class Requirement(models.Model):
299
292
  models.manager.RelatedManager["FindingClassificationChoice"],
300
293
  finding_classification_choices,
301
294
  )
302
- finding_interventions = cast(
303
- models.manager.RelatedManager["FindingIntervention"], finding_interventions
304
- )
295
+ finding_interventions = cast(models.manager.RelatedManager["FindingIntervention"], finding_interventions)
305
296
  medications = cast(models.manager.RelatedManager["Medication"], medications)
306
297
  medication_indications = cast(
307
298
  models.manager.RelatedManager["MedicationIndication"],
@@ -311,9 +302,7 @@ class Requirement(models.Model):
311
302
  models.manager.RelatedManager["MedicationIntakeTime"],
312
303
  medication_intake_times,
313
304
  )
314
- medication_schedules = cast(
315
- models.manager.RelatedManager["MedicationSchedule"], medication_schedules
316
- )
305
+ medication_schedules = cast(models.manager.RelatedManager["MedicationSchedule"], medication_schedules)
317
306
  genders = cast(models.manager.RelatedManager["Gender"], genders)
318
307
 
319
308
  def natural_key(self):
@@ -328,13 +317,6 @@ class Requirement(models.Model):
328
317
  """Returns the name of the requirement as its string representation."""
329
318
  return str(self.name)
330
319
 
331
- # override save method to add a validation step; requirements need at least one operator and at least one requirement type
332
- # def save(self, *args, **kwargs):
333
- # _valid = _validate_requirement_configuration(
334
- # self,
335
- # )
336
- # super().save(*args, **kwargs)
337
-
338
320
  @property
339
321
  def expected_models(
340
322
  self,
@@ -388,32 +370,18 @@ class Requirement(models.Model):
388
370
  # requirement_sets is not part of RequirementLinks (avoids circular import); collect other related models
389
371
  models_dict = RequirementLinks(
390
372
  examinations=[_ for _ in self.examinations.all() if _ is not None],
391
- examination_indications=[
392
- _ for _ in self.examination_indications.all() if _ is not None
393
- ],
373
+ examination_indications=[_ for _ in self.examination_indications.all() if _ is not None],
394
374
  lab_values=[_ for _ in self.lab_values.all() if _ is not None],
395
375
  diseases=[_ for _ in self.diseases.all() if _ is not None],
396
- disease_classification_choices=[
397
- _ for _ in self.disease_classification_choices.all() if _ is not None
398
- ],
376
+ disease_classification_choices=[_ for _ in self.disease_classification_choices.all() if _ is not None],
399
377
  events=[_ for _ in self.events.all() if _ is not None],
400
378
  findings=[_ for _ in self.findings.all() if _ is not None],
401
- finding_classifications=[
402
- _ for _ in self.finding_classifications.all() if _ is not None
403
- ],
404
- finding_classification_choices=[
405
- _ for _ in self.finding_classification_choices.all() if _ is not None
406
- ],
407
- finding_interventions=[
408
- _ for _ in self.finding_interventions.all() if _ is not None
409
- ],
379
+ finding_classifications=[_ for _ in self.finding_classifications.all() if _ is not None],
380
+ finding_classification_choices=[_ for _ in self.finding_classification_choices.all() if _ is not None],
381
+ finding_interventions=[_ for _ in self.finding_interventions.all() if _ is not None],
410
382
  medications=[_ for _ in self.medications.all() if _ is not None],
411
- medication_indications=[
412
- _ for _ in self.medication_indications.all() if _ is not None
413
- ],
414
- medication_intake_times=[
415
- _ for _ in self.medication_intake_times.all() if _ is not None
416
- ],
383
+ medication_indications=[_ for _ in self.medication_indications.all() if _ is not None],
384
+ medication_intake_times=[_ for _ in self.medication_intake_times.all() if _ is not None],
417
385
  )
418
386
  return models_dict
419
387
 
@@ -437,63 +405,7 @@ class Requirement(models.Model):
437
405
  """
438
406
  return self.links.active()
439
407
 
440
- def _parse_string_values(self) -> Dict[str, str]:
441
- """Parses the optional ``string_values`` field into a dictionary.
442
-
443
- Values follow a simple ``key=value`` syntax separated by commas. Entries
444
- without an equals sign are treated as boolean flags (value ``"true"``).
445
- """
446
- if not self.string_values:
447
- return {}
448
-
449
- parsed: Dict[str, str] = {}
450
- for raw_entry in self.string_values.split(","):
451
- entry = raw_entry.strip()
452
- if not entry:
453
- continue
454
- if "=" in entry:
455
- key, value = entry.split("=", 1)
456
- parsed[key.strip()] = value.strip()
457
- else:
458
- parsed[entry] = "true"
459
- return parsed
460
-
461
- def _resolve_queryset_config(self, kwargs: Dict) -> tuple[str, int | None]:
462
- """Derives queryset evaluation settings from kwargs or ``string_values``.
463
-
464
- Supported modes:
465
- - ``any`` (default): at least one item may satisfy the requirement.
466
- - ``all``: every item in the queryset must satisfy the requirement.
467
- - ``min_count``/``at_least``/``min``: at least *n* items must satisfy.
468
- """
469
- settings = self._parse_string_values()
470
-
471
- mode_raw = kwargs.get("queryset_mode") or settings.get("qs_mode") or "any"
472
- mode = str(mode_raw).strip().lower()
473
- mode_aliases = {
474
- "min": "min_count",
475
- "at_least": "min_count",
476
- "minimum": "min_count",
477
- }
478
- mode = mode_aliases.get(mode, mode)
479
- if mode not in {"any", "all", "min_count"}:
480
- mode = "any"
481
-
482
- min_count_raw = kwargs.get("queryset_min_count")
483
- if min_count_raw is None:
484
- for candidate_key in ("qs_min_count", "qs_min", "qs_count", "qs_required"):
485
- if candidate_key in settings:
486
- min_count_raw = settings[candidate_key]
487
- break
488
-
489
- try:
490
- min_count = int(min_count_raw) if min_count_raw is not None else None
491
- except (TypeError, ValueError):
492
- min_count = None
493
-
494
- return mode, min_count
495
-
496
- def evaluate(self, *args, mode: str, **kwargs):
408
+ def evaluate(self, input_obj):
497
409
  """
498
410
  Evaluates whether the requirement is satisfied for the given input models using linked operators and gender constraints.
499
411
 
@@ -511,230 +423,21 @@ class Requirement(models.Model):
511
423
 
512
424
  If the requirement specifies genders, only input containing a patient with a matching gender will be considered valid for evaluation.
513
425
  """
426
+ is_valid: bool = False
514
427
 
515
- try:
516
- _validate_requirement_configuration(self)
517
- except Exception as e:
518
- logger.warning(str(e))
519
- # TODO Review, Optimize or remove
520
- if mode not in ["strict", "loose"]:
521
- raise ValueError(f"Invalid mode: {mode}. Use 'strict' or 'loose'.")
428
+ requirement_req_links = self.active_links
522
429
 
523
- evaluate_result_list_func = all if mode == "strict" else any
430
+ # expected_models = self.expected_models
524
431
 
525
- requirement_req_links = self.links
526
- expected_models = self.expected_models
432
+ operator = self.operator
433
+ assert isinstance(operator, RequirementOperator)
527
434
 
528
- operators = list(self.operators.all())
529
- has_operators = bool(operators)
530
- requirement_has_conditions = bool(requirement_req_links.active())
531
- queryset_mode, queryset_min_count = self._resolve_queryset_config(kwargs)
435
+ operator_instructions = self.operator_instructions_parsed
532
436
 
533
- # helpers to avoid passing a complex tuple to isinstance/issubclass which confuses type checkers
534
- def _is_expected_instance(obj) -> bool:
535
- for cls in expected_models:
536
- if isinstance(cls, type):
537
- try:
538
- if isinstance(obj, cls):
539
- return True
540
- except Exception:
541
- # cls might not be a runtime type
542
- continue
543
- return False
544
-
545
- def _is_queryset_of_expected(qs) -> bool:
546
- if not isinstance(qs, models.QuerySet) or not hasattr(qs, "model"):
547
- return False
548
- for cls in expected_models:
549
- if isinstance(cls, type):
550
- try:
551
- if issubclass(qs.model, cls):
552
- return True
553
- except Exception:
554
- continue
555
- return False
556
-
557
- # Aggregate RequirementLinks from all input arguments
558
- aggregated_input_links_data = {}
559
- processed_inputs_count = 0
560
-
561
- for _input in args:
562
- # Check if the input is an instance of any of the expected model types
563
- if not _is_expected_instance(_input):
564
- # Allow QuerySets of expected models
565
- if _is_queryset_of_expected(_input):
566
- if not _input.exists():
567
- # Empty queryset: enforce stricter modes immediately
568
- if queryset_mode == "all":
569
- return False
570
- if queryset_mode == "min_count":
571
- required = (
572
- queryset_min_count
573
- if queryset_min_count is not None
574
- else 1
575
- )
576
- if required > 0:
577
- return False
578
- continue
579
-
580
- queryset_results: List[bool] = []
581
- queryset_true_count = 0
582
- queryset_item_count = 0
583
-
584
- for item in _input:
585
- if not hasattr(item, "links") or not isinstance(
586
- item.links, RequirementLinks
587
- ):
588
- raise TypeError(
589
- f"Item {item} of type {type(item)} in QuerySet does not have a valid .links attribute of type RequirementLinks."
590
- )
591
-
592
- item_active_links = item.links.active()
593
- item_input_links = RequirementLinks(**item_active_links)
594
-
595
- for link_key, link_list in item_active_links.items():
596
- if link_key not in aggregated_input_links_data:
597
- aggregated_input_links_data[link_key] = []
598
- aggregated_input_links_data[link_key].extend(link_list)
599
-
600
- per_item_args = tuple(
601
- item if arg is _input else arg for arg in args
602
- )
603
- op_kwargs = kwargs.copy()
604
- op_kwargs["requirement"] = self
605
- op_kwargs["original_input_args"] = per_item_args
606
-
607
- if has_operators:
608
- item_operator_results: List[bool] = []
609
- for operator in operators:
610
- try:
611
- operator_result = operator.evaluate(
612
- requirement_links=requirement_req_links,
613
- input_links=item_input_links,
614
- **op_kwargs,
615
- )
616
- item_operator_results.append(operator_result)
617
- except Exception as exc:
618
- logger.debug(
619
- f"Operator {operator.name} evaluation failed for item {item}: {exc}"
620
- )
621
- item_operator_results.append(False)
622
- item_result = (
623
- evaluate_result_list_func(item_operator_results)
624
- if item_operator_results
625
- else True
626
- )
627
- else:
628
- item_result = not requirement_has_conditions
629
-
630
- queryset_results.append(item_result)
631
- if item_result:
632
- queryset_true_count += 1
633
- queryset_item_count += 1
634
- processed_inputs_count += 1
635
-
636
- if queryset_mode == "all":
637
- if queryset_item_count == 0 or not all(queryset_results):
638
- return False
639
- elif queryset_mode == "min_count":
640
- required = (
641
- queryset_min_count if queryset_min_count is not None else 1
642
- )
643
- if queryset_true_count < max(required, 0):
644
- return False
645
- # queryset_mode == "any" imposes no extra constraint here
646
- continue # Move to the next arg after processing queryset
647
- else:
648
- raise TypeError(
649
- f"Input type {type(_input)} is not among expected models: {self.expected_models} nor a QuerySet of expected models."
650
- )
651
-
652
- # Process single model instance
653
- if not hasattr(_input, "links") or not isinstance(
654
- _input.links, RequirementLinks
655
- ):
656
- raise TypeError(
657
- f"Input {_input} of type {type(_input)} does not have a valid .links attribute of type RequirementLinks."
658
- )
659
-
660
- active_input_links = _input.links.active() # Get dict of non-empty lists
661
- for link_key, link_list in active_input_links.items():
662
- if link_key not in aggregated_input_links_data:
663
- aggregated_input_links_data[link_key] = []
664
- aggregated_input_links_data[link_key].extend(link_list)
665
- processed_inputs_count += 1
666
-
667
- if (
668
- not processed_inputs_count and args
669
- ): # If args were provided but none were processable (e.g. all empty querysets)
670
- # This situation implies no relevant data was provided for evaluation against the requirement.
671
- # Depending on operator logic (e.g., "requires at least one matching item"), this might lead to False.
672
- # For "models_match_any", an empty input_links will likely result in False if requirement_req_links is not empty.
673
- pass
674
-
675
- # Deduplicate items within each list after aggregation
676
- for key in aggregated_input_links_data:
677
- try:
678
- # Using dict.fromkeys to preserve order and remove duplicates for hashable items
679
- aggregated_input_links_data[key] = list(
680
- dict.fromkeys(aggregated_input_links_data[key])
681
- )
682
- except TypeError:
683
- # Fallback for non-hashable items (though Django models are hashable)
684
- temp_list = []
685
- for item in aggregated_input_links_data[key]:
686
- if item not in temp_list:
687
- temp_list.append(item)
688
- aggregated_input_links_data[key] = temp_list
689
-
690
- final_input_links = RequirementLinks(**aggregated_input_links_data)
691
-
692
- # Gender strict check: if this requirement has genders, only pass if patient.gender is in the set
693
- genders_exist = self.genders.exists()
694
- if genders_exist:
695
- # Import here to avoid circular import
696
- from endoreg_db.models.administration.person.patient import Patient
697
-
698
- patient = None
699
- for arg in args:
700
- if isinstance(arg, Patient):
701
- patient = arg
702
- break
703
- if patient is None or patient.gender is None:
704
- return False
705
- if not self.genders.filter(pk=patient.gender.pk).exists():
706
- return False
707
-
708
- if (
709
- not has_operators
710
- ): # If a requirement has no operators, its evaluation is ambiguous.
711
- if not requirement_has_conditions: # No conditions in requirement
712
- return True # Vacuously true if requirement itself is empty
713
- return False # Cannot be satisfied if requirement has conditions but no operators to check them
714
-
715
- operator_results = []
716
- for operator in operators:
717
- # Prepare kwargs for the operator, including the current Requirement instance
718
- op_kwargs = (
719
- kwargs.copy()
720
- ) # Start with kwargs passed to Requirement.evaluate
721
- op_kwargs["requirement"] = self # Add the Requirement instance itself
722
- op_kwargs["original_input_args"] = (
723
- args # Add the original input arguments for operators that need them (e.g., age operators)
724
- )
725
- operator_results.append(
726
- operator.evaluate(
727
- requirement_links=requirement_req_links,
728
- input_links=final_input_links,
729
- **op_kwargs,
730
- )
731
- )
732
-
733
- is_valid = evaluate_result_list_func(operator_results)
437
+ is_valid = operator.evaluate(input_links)
734
438
 
735
439
  return is_valid
736
440
 
737
-
738
441
  def evaluate_with_details(self, *args, mode: str, **kwargs) -> Tuple[bool, str]:
739
442
  """
740
443
  Evaluates whether the requirement is satisfied for the given input models using linked operators and gender constraints.
@@ -763,9 +466,7 @@ class Requirement(models.Model):
763
466
  code="INVALID_MODE",
764
467
  technical_message=f"Invalid mode: {mode}. Use 'strict' or 'loose'.",
765
468
  user_message=(
766
- "Diese Voraussetzung ist intern mit einem ungültigen "
767
- "Bewertungsmodus konfiguriert und kann aktuell nicht "
768
- "korrekt geprüft werden."
469
+ "Diese Voraussetzung ist intern mit einem ungültigen Bewertungsmodus konfiguriert und kann aktuell nicht korrekt geprüft werden."
769
470
  ),
770
471
  )
771
472
 
@@ -816,20 +517,14 @@ class Requirement(models.Model):
816
517
  if queryset_mode == "all":
817
518
  return (
818
519
  False,
819
- "Für diese Voraussetzung müssen alle passenden Einträge vorliegen, "
820
- "aber es wurden keine entsprechenden Datensätze gefunden.",
520
+ "Für diese Voraussetzung müssen alle passenden Einträge vorliegen, aber es wurden keine entsprechenden Datensätze gefunden.",
821
521
  )
822
522
  if queryset_mode == "min_count":
823
- required = (
824
- queryset_min_count
825
- if queryset_min_count is not None
826
- else 1
827
- )
523
+ required = queryset_min_count if queryset_min_count is not None else 1
828
524
  if required > 0:
829
525
  return (
830
526
  False,
831
- f"Für diese Voraussetzung werden mindestens {required} passende "
832
- "Einträge benötigt, es wurden jedoch keine gefunden.",
527
+ f"Für diese Voraussetzung werden mindestens {required} passende Einträge benötigt, es wurden jedoch keine gefunden.",
833
528
  )
834
529
  # queryset_mode == "any" bei leerem QS -> neutral (keine zusätzliche Einschränkung)
835
530
  continue
@@ -839,15 +534,12 @@ class Requirement(models.Model):
839
534
  queryset_item_count = 0
840
535
 
841
536
  for item in _input:
842
- if not hasattr(item, "links") or not isinstance(
843
- item.links, RequirementLinks
844
- ):
537
+ if not hasattr(item, "links") or not isinstance(item.links, RequirementLinks):
845
538
  raise RequirementEvaluationError(
846
539
  requirement=self,
847
540
  code="MISSING_LINKS_ATTR",
848
541
  technical_message=(
849
- f"Item {item} of type {type(item)} in QuerySet does not "
850
- f"have a valid .links attribute of type RequirementLinks."
542
+ f"Item {item} of type {type(item)} in QuerySet does not have a valid .links attribute of type RequirementLinks."
851
543
  ),
852
544
  user_message=(
853
545
  "Für einen Datensatz fehlen die intern benötigten Verknüpfungen, "
@@ -865,9 +557,7 @@ class Requirement(models.Model):
865
557
  aggregated_input_links_data[link_key] = []
866
558
  aggregated_input_links_data[link_key].extend(link_list)
867
559
 
868
- per_item_args = tuple(
869
- item if arg is _input else arg for arg in args
870
- )
560
+ per_item_args = tuple(item if arg is _input else arg for arg in args)
871
561
  op_kwargs = kwargs.copy()
872
562
  op_kwargs["requirement"] = self
873
563
  op_kwargs["original_input_args"] = per_item_args
@@ -890,11 +580,7 @@ class Requirement(models.Model):
890
580
  exc,
891
581
  )
892
582
  item_operator_results.append(False)
893
- item_result = (
894
- evaluate_result_list_func(item_operator_results)
895
- if item_operator_results
896
- else True
897
- )
583
+ item_result = evaluate_result_list_func(item_operator_results) if item_operator_results else True
898
584
  else:
899
585
  # keine Operatoren -> Bedingung erfüllt, wenn Requirement selbst keine Bedingungen hat
900
586
  item_result = not requirement_has_conditions
@@ -913,14 +599,11 @@ class Requirement(models.Model):
913
599
  "Für diese Voraussetzung müssen alle relevanten Einträge die Bedingung erfüllen.",
914
600
  )
915
601
  elif queryset_mode == "min_count":
916
- required = (
917
- queryset_min_count if queryset_min_count is not None else 1
918
- )
602
+ required = queryset_min_count if queryset_min_count is not None else 1
919
603
  if queryset_true_count < max(required, 0):
920
604
  return (
921
605
  False,
922
- f"Für diese Voraussetzung werden mindestens {max(required, 0)} "
923
- f"passende Einträge benötigt (gefunden: {queryset_true_count}).",
606
+ f"Für diese Voraussetzung werden mindestens {max(required, 0)} passende Einträge benötigt (gefunden: {queryset_true_count}).",
924
607
  )
925
608
  # queryset_mode == "any": keine zusätzliche Einschränkung
926
609
  continue
@@ -929,32 +612,18 @@ class Requirement(models.Model):
929
612
  raise RequirementEvaluationError(
930
613
  requirement=self,
931
614
  code="INVALID_INPUT_TYPE",
932
- technical_message=(
933
- f"Input type {type(_input)} is not among expected models: "
934
- f"{self.expected_models} nor a QuerySet of expected models."
935
- ),
936
- user_message=(
937
- "Diese Voraussetzung wurde mit einem nicht passenden Datentyp "
938
- "aufgerufen und kann aktuell nicht korrekt geprüft werden."
939
- ),
615
+ technical_message=(f"Input type {type(_input)} is not among expected models: {self.expected_models} nor a QuerySet of expected models."),
616
+ user_message=("Diese Voraussetzung wurde mit einem nicht passenden Datentyp aufgerufen und kann aktuell nicht korrekt geprüft werden."),
940
617
  meta={"input_type": str(type(_input))},
941
618
  )
942
619
 
943
620
  # Einzelinstanz erwarteten Typs
944
- if not hasattr(_input, "links") or not isinstance(
945
- _input.links, RequirementLinks
946
- ):
621
+ if not hasattr(_input, "links") or not isinstance(_input.links, RequirementLinks):
947
622
  raise RequirementEvaluationError(
948
623
  requirement=self,
949
624
  code="MISSING_LINKS_ATTR",
950
- technical_message=(
951
- f"Input {_input} of type {type(_input)} does not have a valid "
952
- f".links attribute of type RequirementLinks."
953
- ),
954
- user_message=(
955
- "Für die Auswertung dieser Voraussetzung fehlen die intern "
956
- "benötigten Verknüpfungsinformationen."
957
- ),
625
+ technical_message=(f"Input {_input} of type {type(_input)} does not have a valid .links attribute of type RequirementLinks."),
626
+ user_message=("Für die Auswertung dieser Voraussetzung fehlen die intern benötigten Verknüpfungsinformationen."),
958
627
  meta={"input_type": str(type(_input))},
959
628
  )
960
629
 
@@ -971,9 +640,7 @@ class Requirement(models.Model):
971
640
  # Deduplizieren der aggregierten Links
972
641
  for key in aggregated_input_links_data:
973
642
  try:
974
- aggregated_input_links_data[key] = list(
975
- dict.fromkeys(aggregated_input_links_data[key])
976
- )
643
+ aggregated_input_links_data[key] = list(dict.fromkeys(aggregated_input_links_data[key]))
977
644
  except TypeError:
978
645
  # Fallback für nicht-hashbare Items
979
646
  tmp: list = []
@@ -1004,8 +671,7 @@ class Requirement(models.Model):
1004
671
  if not self.genders.filter(pk=patient.gender.pk).exists():
1005
672
  return (
1006
673
  False,
1007
- "Diese Voraussetzung gilt nur für bestimmte Geschlechter und ist "
1008
- "für diesen Patienten nicht erfüllt.",
674
+ "Diese Voraussetzung gilt nur für bestimmte Geschlechter und ist für diesen Patienten nicht erfüllt.",
1009
675
  )
1010
676
 
1011
677
  # --- Fall: keine Operatoren -----------------------------------------
@@ -1033,9 +699,7 @@ class Requirement(models.Model):
1033
699
  **op_kwargs,
1034
700
  )
1035
701
  operator_results.append(operator_result)
1036
- operator_details.append(
1037
- f"{operator.name}: {'erfüllt' if operator_result else 'nicht erfüllt'}"
1038
- )
702
+ operator_details.append(f"{operator.name}: {'erfüllt' if operator_result else 'nicht erfüllt'}")
1039
703
  except Exception as e:
1040
704
  operator_results.append(False)
1041
705
  operator_details.append(f"{operator.name}: technischer Fehler ({e})")
@@ -1054,11 +718,7 @@ class Requirement(models.Model):
1054
718
  elif len(operator_results) == 1:
1055
719
  details = operator_details[0]
1056
720
  else:
1057
- failed_details = [
1058
- detail
1059
- for detail, result in zip(operator_details, operator_results)
1060
- if not result
1061
- ]
721
+ failed_details = [detail for detail, result in zip(operator_details, operator_results) if not result]
1062
722
  if failed_details:
1063
723
  details = "; ".join(failed_details)
1064
724
  else:
@@ -1072,4 +732,4 @@ class Requirement(models.Model):
1072
732
  # nicht kritisch
1073
733
  pass
1074
734
 
1075
- return bool(is_valid), details
735
+ return bool(is_valid), details