endoreg-db 0.6.4__py3-none-any.whl → 0.8.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 (779) hide show
  1. endoreg_db/admin.py +26 -26
  2. endoreg_db/api_urls.py +4 -0
  3. endoreg_db/apps.py +12 -0
  4. endoreg_db/assets/dummy_model.ckpt +1 -0
  5. endoreg_db/codemods/readme.md +88 -0
  6. endoreg_db/codemods/rename_datetime_fields.py +92 -0
  7. endoreg_db/config/env.py +101 -0
  8. endoreg_db/data/__init__.py +12 -0
  9. endoreg_db/data/ai_model/data.yaml +1 -1
  10. endoreg_db/data/ai_model_label/label/polyp_classification.yaml +52 -0
  11. endoreg_db/data/ai_model_label/label-set/data.yaml +20 -1
  12. endoreg_db/data/ai_model_label/label-set/polyp_classifications.yaml +25 -0
  13. endoreg_db/data/center/data.yaml +13 -12
  14. endoreg_db/data/center_shift/ukw.yaml +9 -0
  15. endoreg_db/data/db_summary.csv +58 -0
  16. endoreg_db/data/db_summary.xlsx +0 -0
  17. endoreg_db/data/disease/misc.yaml +1 -2
  18. endoreg_db/data/endoscopy_processor/data.yaml +3 -0
  19. endoreg_db/data/event/cardiology.yaml +0 -13
  20. endoreg_db/data/examination/examinations/data.yaml +14 -9
  21. endoreg_db/data/examination_indication/endoscopy.yaml +30 -30
  22. endoreg_db/data/examination_indication_classification/endoscopy.yaml +11 -11
  23. endoreg_db/data/examination_requirement_set/colonoscopy.yaml +15 -0
  24. endoreg_db/data/finding/anatomy_colon.yaml +128 -0
  25. endoreg_db/data/finding/colonoscopy.yaml +40 -0
  26. endoreg_db/data/finding/colonoscopy_bowel_prep.yaml +56 -0
  27. endoreg_db/data/finding/complication.yaml +16 -0
  28. endoreg_db/data/finding/data.yaml +3 -46
  29. endoreg_db/data/finding/examination_setting.yaml +16 -0
  30. endoreg_db/data/finding/medication_related.yaml +18 -0
  31. endoreg_db/data/finding/outcome.yaml +12 -0
  32. endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +95 -0
  33. endoreg_db/data/finding_classification/colonoscopy_jnet.yaml +22 -0
  34. endoreg_db/data/finding_classification/colonoscopy_kudo.yaml +25 -0
  35. endoreg_db/data/finding_classification/colonoscopy_lesion_circularity.yaml +20 -0
  36. endoreg_db/data/finding_classification/colonoscopy_lesion_planarity.yaml +24 -0
  37. endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +68 -0
  38. endoreg_db/data/finding_classification/colonoscopy_lesion_surface.yaml +20 -0
  39. endoreg_db/data/finding_classification/colonoscopy_location.yaml +80 -0
  40. endoreg_db/data/finding_classification/colonoscopy_lst.yaml +21 -0
  41. endoreg_db/data/finding_classification/colonoscopy_nice.yaml +20 -0
  42. endoreg_db/data/finding_classification/colonoscopy_paris.yaml +26 -0
  43. endoreg_db/data/finding_classification/colonoscopy_sano.yaml +22 -0
  44. endoreg_db/data/finding_classification/colonoscopy_summary.yaml +53 -0
  45. endoreg_db/data/finding_classification/complication_generic.yaml +25 -0
  46. endoreg_db/data/finding_classification/examination_setting_generic.yaml +40 -0
  47. endoreg_db/data/finding_classification/histology_colo.yaml +51 -0
  48. endoreg_db/data/finding_classification/intervention_required.yaml +26 -0
  49. endoreg_db/data/finding_classification/medication_related.yaml +23 -0
  50. endoreg_db/data/finding_classification/visualized.yaml +33 -0
  51. endoreg_db/data/finding_classification_choice/bowel_preparation.yaml +78 -0
  52. endoreg_db/data/{finding_morphology_classification_choice → finding_classification_choice}/colon_lesion_circularity_default.yaml +0 -2
  53. endoreg_db/data/finding_classification_choice/colon_lesion_jnet.yaml +15 -0
  54. endoreg_db/data/finding_classification_choice/colon_lesion_kudo.yaml +23 -0
  55. endoreg_db/data/finding_classification_choice/colon_lesion_lst.yaml +15 -0
  56. endoreg_db/data/{finding_morphology_classification_choice → finding_classification_choice}/colon_lesion_nice.yaml +4 -7
  57. endoreg_db/data/{finding_morphology_classification_choice → finding_classification_choice}/colon_lesion_paris.yaml +0 -8
  58. endoreg_db/data/{finding_morphology_classification_choice → finding_classification_choice}/colon_lesion_planarity_default.yaml +6 -13
  59. endoreg_db/data/finding_classification_choice/colon_lesion_sano.yaml +14 -0
  60. endoreg_db/data/{finding_morphology_classification_choice → finding_classification_choice}/colon_lesion_surface_intact_default.yaml +3 -6
  61. endoreg_db/data/{finding_location_classification_choice/colonoscopy.yaml → finding_classification_choice/colonoscopy_location.yaml} +11 -22
  62. endoreg_db/data/finding_classification_choice/colonoscopy_not_complete_reason.yaml +19 -0
  63. endoreg_db/data/finding_classification_choice/colonoscopy_size.yaml +82 -0
  64. endoreg_db/data/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +15 -0
  65. endoreg_db/data/finding_classification_choice/complication_generic_types.yaml +15 -0
  66. endoreg_db/data/finding_classification_choice/examination_setting_generic_types.yaml +15 -0
  67. endoreg_db/data/finding_classification_choice/histology.yaml +24 -0
  68. endoreg_db/data/finding_classification_choice/histology_polyp.yaml +20 -0
  69. endoreg_db/data/finding_classification_choice/outcome.yaml +19 -0
  70. endoreg_db/data/finding_classification_choice/yes_no_na.yaml +11 -0
  71. endoreg_db/data/finding_classification_type/colonoscopy_basic.yaml +48 -0
  72. endoreg_db/data/finding_intervention/endoscopy_colonoscopy.yaml +8 -3
  73. endoreg_db/data/finding_morphology_classification_type/colonoscopy.yaml +6 -6
  74. endoreg_db/data/finding_type/data.yaml +23 -10
  75. endoreg_db/data/gender/data.yaml +8 -1
  76. endoreg_db/data/information_source/annotation.yaml +6 -0
  77. endoreg_db/data/information_source/prediction.yaml +7 -0
  78. endoreg_db/data/information_source_type/data.yaml +8 -0
  79. endoreg_db/data/lab_value/misc.yaml +43 -0
  80. endoreg_db/data/medication/anticoagulation.yaml +5 -5
  81. endoreg_db/data/medication/tah.yaml +5 -5
  82. endoreg_db/data/medication_intake_time/base.yaml +4 -4
  83. endoreg_db/data/names_first/first_names.yaml +3 -0
  84. endoreg_db/data/pdf_type/data.yaml +26 -2
  85. endoreg_db/data/qualification/endoscopy.yaml +36 -0
  86. endoreg_db/data/qualification/m2.yaml +39 -0
  87. endoreg_db/data/qualification/outpatient_clinic.yaml +35 -0
  88. endoreg_db/data/qualification/sonography.yaml +36 -0
  89. endoreg_db/data/qualification_type/base.yaml +29 -0
  90. endoreg_db/data/report_reader_flag/rkh-histology-generic.yaml +10 -0
  91. endoreg_db/data/report_reader_flag/ukw-histology-generic.yaml +5 -0
  92. endoreg_db/data/requirement/age.yaml +26 -0
  93. endoreg_db/data/requirement/colonoscopy_baseline_austria.yaml +45 -0
  94. endoreg_db/data/requirement/disease_cardiovascular.yaml +6 -6
  95. endoreg_db/data/requirement/disease_classification_choice_cardiovascular.yaml +9 -6
  96. endoreg_db/data/requirement/disease_hepatology.yaml +1 -1
  97. endoreg_db/data/requirement/disease_misc.yaml +3 -3
  98. endoreg_db/data/requirement/disease_renal.yaml +18 -2
  99. endoreg_db/data/requirement/{colonoscopy_indications.yaml → endoscopy_bleeding_risk.yaml} +6 -3
  100. endoreg_db/data/requirement/event_cardiology.yaml +17 -17
  101. endoreg_db/data/requirement/event_requirements.yaml +145 -0
  102. endoreg_db/data/requirement/finding_colon_polyp.yaml +50 -0
  103. endoreg_db/data/requirement/gender.yaml +25 -0
  104. endoreg_db/data/requirement/lab_value.yaml +352 -31
  105. endoreg_db/data/requirement/medication.yaml +93 -0
  106. endoreg_db/data/requirement_operator/age.yaml +13 -0
  107. endoreg_db/data/requirement_operator/lab_operators.yaml +36 -35
  108. endoreg_db/data/requirement_operator/model_operators.yaml +13 -7
  109. endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +48 -0
  110. endoreg_db/data/requirement_set/colonoscopy_austria_screening.yaml +57 -0
  111. endoreg_db/data/requirement_set/endoscopy_bleeding_risk.yaml +42 -2
  112. endoreg_db/data/requirement_type/requirement_types.yaml +82 -0
  113. endoreg_db/data/shift/endoscopy.yaml +21 -0
  114. endoreg_db/data/shift_type/base.yaml +35 -0
  115. endoreg_db/data/tag/requirement_set_tags.yaml +11 -0
  116. endoreg_db/data/unit/concentration.yaml +23 -0
  117. endoreg_db/exceptions.py +19 -0
  118. endoreg_db/forms/patient_finding_intervention_form.py +4 -5
  119. endoreg_db/forms/patient_form.py +7 -6
  120. endoreg_db/forms/questionnaires/__init__.py +1 -1
  121. endoreg_db/forms/questionnaires/tto_questionnaire.py +19 -19
  122. endoreg_db/helpers/count_db.py +45 -0
  123. endoreg_db/helpers/data_loader.py +208 -0
  124. endoreg_db/helpers/default_objects.py +359 -0
  125. endoreg_db/helpers/download_segmentation_model.py +31 -0
  126. endoreg_db/helpers/interact.py +6 -0
  127. endoreg_db/helpers/test_video_helper.py +119 -0
  128. endoreg_db/logger_conf.py +140 -0
  129. endoreg_db/management/__init__.py +1 -0
  130. endoreg_db/management/commands/__init__.py +1 -0
  131. endoreg_db/management/commands/anonymize_video.py +0 -0
  132. endoreg_db/management/commands/check_auth.py +125 -0
  133. endoreg_db/management/commands/create_multilabel_model_meta.py +214 -0
  134. endoreg_db/management/commands/fix_missing_patient_data.py +172 -0
  135. endoreg_db/management/commands/fix_video_paths.py +165 -0
  136. endoreg_db/management/commands/import_fallback_video.py +203 -0
  137. endoreg_db/management/commands/import_report.py +298 -0
  138. endoreg_db/management/commands/import_video.py +422 -0
  139. endoreg_db/management/commands/import_video_with_classification.py +367 -0
  140. endoreg_db/management/commands/init_default_ai_model.py +112 -0
  141. endoreg_db/management/commands/load_ai_model_data.py +2 -7
  142. endoreg_db/management/commands/load_base_db_data.py +1 -0
  143. endoreg_db/management/commands/load_endoscope_data.py +2 -2
  144. endoreg_db/management/commands/load_examination_indication_data.py +2 -3
  145. endoreg_db/management/commands/load_finding_data.py +49 -92
  146. endoreg_db/management/commands/load_green_endoscopy_wuerzburg_data.py +0 -1
  147. endoreg_db/management/commands/load_information_source.py +13 -7
  148. endoreg_db/management/commands/load_name_data.py +37 -0
  149. endoreg_db/management/commands/load_qualification_data.py +59 -0
  150. endoreg_db/management/commands/load_requirement_data.py +30 -6
  151. endoreg_db/management/commands/load_shift_data.py +60 -0
  152. endoreg_db/management/commands/load_tag_data.py +57 -0
  153. endoreg_db/management/commands/register_ai_model.py +1 -1
  154. endoreg_db/management/commands/start_filewatcher.py +106 -0
  155. endoreg_db/management/commands/storage_management.py +548 -0
  156. endoreg_db/management/commands/summarize_db_content.py +189 -0
  157. endoreg_db/management/commands/validate_video.py +204 -0
  158. endoreg_db/management/commands/validate_video_files.py +161 -0
  159. endoreg_db/management/commands/video_validation.py +22 -0
  160. endoreg_db/migrations/0001_initial.py +625 -813
  161. endoreg_db/migrations/0002_add_video_correction_models.py +52 -0
  162. endoreg_db/models/__init__.py +270 -307
  163. endoreg_db/models/administration/__init__.py +116 -0
  164. endoreg_db/models/{ai_model → administration/ai}/__init__.py +6 -1
  165. endoreg_db/models/administration/ai/active_model.py +35 -0
  166. endoreg_db/models/administration/ai/ai_model.py +156 -0
  167. endoreg_db/models/{ai_model → administration/ai}/model_type.py +6 -1
  168. endoreg_db/models/administration/case/__init__.py +19 -0
  169. endoreg_db/models/administration/case/case.py +114 -0
  170. endoreg_db/models/{case_template → administration/case/case_template}/case_template.py +3 -3
  171. endoreg_db/models/{case_template → administration/case/case_template}/case_template_rule.py +3 -10
  172. endoreg_db/models/{case_template → administration/case/case_template}/case_template_rule_value.py +2 -4
  173. endoreg_db/models/{case_template → administration/case/case_template}/case_template_type.py +1 -3
  174. endoreg_db/models/{center → administration/center}/__init__.py +3 -1
  175. endoreg_db/models/administration/center/center.py +61 -0
  176. endoreg_db/models/administration/center/center_product.py +64 -0
  177. endoreg_db/models/{center → administration/center}/center_resource.py +19 -3
  178. endoreg_db/models/administration/center/center_shift.py +88 -0
  179. endoreg_db/models/administration/center/center_waste.py +30 -0
  180. endoreg_db/models/administration/permissions/__init__.py +44 -0
  181. endoreg_db/models/administration/person/__init__.py +24 -0
  182. endoreg_db/models/administration/person/employee/__init__.py +3 -0
  183. endoreg_db/models/administration/person/employee/employee.py +35 -0
  184. endoreg_db/models/administration/person/employee/employee_qualification.py +39 -0
  185. endoreg_db/models/administration/person/employee/employee_type.py +42 -0
  186. endoreg_db/models/administration/person/examiner/__init__.py +4 -0
  187. endoreg_db/models/administration/person/examiner/examiner.py +54 -0
  188. endoreg_db/models/administration/person/names/__init__.py +0 -0
  189. endoreg_db/models/{persons → administration/person/names}/first_name.py +1 -1
  190. endoreg_db/models/{persons → administration/person/names}/last_name.py +2 -3
  191. endoreg_db/models/administration/person/patient/__init__.py +5 -0
  192. endoreg_db/models/administration/person/patient/patient.py +460 -0
  193. endoreg_db/models/administration/person/profession/__init__.py +24 -0
  194. endoreg_db/models/administration/person/user/__init__.py +5 -0
  195. endoreg_db/models/administration/person/user/portal_user_information.py +37 -0
  196. endoreg_db/models/administration/product/product.py +97 -0
  197. endoreg_db/models/administration/product/product_group.py +39 -0
  198. endoreg_db/models/administration/product/product_material.py +54 -0
  199. endoreg_db/models/{product → administration/product}/product_weight.py +9 -0
  200. endoreg_db/models/{product → administration/product}/reference_product.py +26 -11
  201. endoreg_db/models/administration/qualification/__init__.py +7 -0
  202. endoreg_db/models/administration/qualification/qualification.py +37 -0
  203. endoreg_db/models/administration/qualification/qualification_type.py +35 -0
  204. endoreg_db/models/administration/shift/__init__.py +9 -0
  205. endoreg_db/models/administration/shift/scheduled_days.py +69 -0
  206. endoreg_db/models/administration/shift/shift.py +51 -0
  207. endoreg_db/models/administration/shift/shift_type.py +108 -0
  208. endoreg_db/models/label/__init__.py +24 -1
  209. endoreg_db/models/label/annotation/__init__.py +12 -0
  210. endoreg_db/models/label/annotation/image_classification.py +84 -0
  211. endoreg_db/models/label/annotation/video_segmentation_annotation.py +66 -0
  212. endoreg_db/models/label/label.py +45 -74
  213. endoreg_db/models/label/label_set.py +53 -0
  214. endoreg_db/models/label/label_type.py +29 -0
  215. endoreg_db/models/label/label_video_segment/__init__.py +3 -0
  216. endoreg_db/models/label/label_video_segment/_create_from_video.py +41 -0
  217. endoreg_db/models/label/label_video_segment/label_video_segment.py +511 -0
  218. endoreg_db/models/label/video_segmentation_label.py +31 -0
  219. endoreg_db/models/{annotation → label}/video_segmentation_labelset.py +7 -0
  220. endoreg_db/models/media/__init__.py +14 -0
  221. endoreg_db/models/media/frame/__init__.py +3 -0
  222. endoreg_db/models/media/frame/frame.py +111 -0
  223. endoreg_db/models/media/pdf/__init__.py +11 -0
  224. endoreg_db/models/media/pdf/raw_pdf.py +608 -0
  225. endoreg_db/models/media/pdf/report_file.py +162 -0
  226. endoreg_db/models/media/pdf/report_reader/report_reader_config.py +77 -0
  227. endoreg_db/models/media/video/__init__.py +4 -0
  228. endoreg_db/models/media/video/create_from_file.py +336 -0
  229. endoreg_db/models/media/video/pipe_1.py +207 -0
  230. endoreg_db/models/media/video/pipe_2.py +105 -0
  231. endoreg_db/models/media/video/refactor_plan.md +0 -0
  232. endoreg_db/models/media/video/video_file.py +680 -0
  233. endoreg_db/models/media/video/video_file_ai.py +443 -0
  234. endoreg_db/models/media/video/video_file_anonymize.py +348 -0
  235. endoreg_db/models/media/video/video_file_frames/__init__.py +47 -0
  236. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +22 -0
  237. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +23 -0
  238. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +104 -0
  239. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +174 -0
  240. endoreg_db/models/media/video/video_file_frames/_get_frame.py +28 -0
  241. endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +27 -0
  242. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +20 -0
  243. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +27 -0
  244. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +34 -0
  245. endoreg_db/models/media/video/video_file_frames/_get_frames.py +27 -0
  246. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +129 -0
  247. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +129 -0
  248. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +65 -0
  249. endoreg_db/models/media/video/video_file_frames.py +0 -0
  250. endoreg_db/models/media/video/video_file_io.py +166 -0
  251. endoreg_db/models/media/video/video_file_meta/__init__.py +22 -0
  252. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +45 -0
  253. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +39 -0
  254. endoreg_db/models/media/video/video_file_meta/get_fps.py +147 -0
  255. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +143 -0
  256. endoreg_db/models/media/video/video_file_meta/text_meta.py +134 -0
  257. endoreg_db/models/media/video/video_file_meta/video_meta.py +70 -0
  258. endoreg_db/models/media/video/video_file_meta.py +11 -0
  259. endoreg_db/models/media/video/video_file_segments.py +209 -0
  260. endoreg_db/models/medical/__init__.py +146 -0
  261. endoreg_db/models/{contraindication → medical/contraindication}/__init__.py +1 -5
  262. endoreg_db/models/{disease.py → medical/disease.py} +60 -52
  263. endoreg_db/models/{event.py → medical/event.py} +31 -54
  264. endoreg_db/models/{examination → medical/examination}/__init__.py +1 -1
  265. endoreg_db/models/medical/examination/examination.py +148 -0
  266. endoreg_db/models/{examination → medical/examination}/examination_indication.py +64 -35
  267. endoreg_db/models/{examination → medical/examination}/examination_time.py +0 -4
  268. endoreg_db/models/{examination → medical/examination}/examination_time_type.py +1 -8
  269. endoreg_db/models/{examination → medical/examination}/examination_type.py +1 -7
  270. endoreg_db/models/medical/finding/__init__.py +18 -0
  271. endoreg_db/models/medical/finding/finding.py +96 -0
  272. endoreg_db/models/medical/finding/finding_classification.py +142 -0
  273. endoreg_db/models/{finding → medical/finding}/finding_intervention.py +2 -10
  274. endoreg_db/models/medical/finding/finding_type.py +35 -0
  275. endoreg_db/models/medical/hardware/__init__.py +8 -0
  276. endoreg_db/models/{hardware → medical/hardware}/endoscope.py +28 -23
  277. endoreg_db/models/medical/laboratory/__init__.py +5 -0
  278. endoreg_db/models/medical/laboratory/lab_value.py +419 -0
  279. endoreg_db/models/{medication → medical/medication}/medication.py +1 -3
  280. endoreg_db/models/{medication → medical/medication}/medication_indication_type.py +8 -3
  281. endoreg_db/models/{medication → medical/medication}/medication_intake_time.py +21 -3
  282. endoreg_db/models/{medication → medical/medication}/medication_schedule.py +13 -5
  283. endoreg_db/models/{organ → medical/organ}/__init__.py +3 -6
  284. endoreg_db/models/medical/patient/__init__.py +56 -0
  285. endoreg_db/models/medical/patient/medication_examples.py +38 -0
  286. endoreg_db/models/medical/patient/patient_disease.py +63 -0
  287. endoreg_db/models/medical/patient/patient_event.py +75 -0
  288. endoreg_db/models/medical/patient/patient_examination.py +249 -0
  289. endoreg_db/models/{persons → medical}/patient/patient_examination_indication.py +21 -9
  290. endoreg_db/models/medical/patient/patient_finding.py +357 -0
  291. endoreg_db/models/medical/patient/patient_finding_classification.py +207 -0
  292. endoreg_db/models/{patient → medical/patient}/patient_finding_intervention.py +15 -1
  293. endoreg_db/models/medical/patient/patient_lab_sample.py +148 -0
  294. endoreg_db/models/{persons → medical}/patient/patient_lab_value.py +40 -15
  295. endoreg_db/models/medical/patient/patient_medication.py +104 -0
  296. endoreg_db/models/medical/patient/patient_medication_schedule.py +136 -0
  297. endoreg_db/models/{risk → medical/risk}/risk_type.py +0 -4
  298. endoreg_db/models/{data_file/metadata → metadata}/__init__.py +6 -0
  299. endoreg_db/models/metadata/frame_ocr_result.py +0 -0
  300. endoreg_db/models/metadata/model_meta.py +193 -0
  301. endoreg_db/models/metadata/model_meta_logic.py +236 -0
  302. endoreg_db/models/{data_file/metadata → metadata}/pdf_meta.py +28 -13
  303. endoreg_db/models/metadata/sensitive_meta.py +288 -0
  304. endoreg_db/models/metadata/sensitive_meta_logic.py +643 -0
  305. endoreg_db/models/metadata/video_meta.py +332 -0
  306. endoreg_db/models/metadata/video_prediction_logic.py +190 -0
  307. endoreg_db/models/metadata/video_prediction_meta.py +270 -0
  308. endoreg_db/models/other/__init__.py +17 -0
  309. endoreg_db/models/other/distribution/date_value_distribution.py +0 -2
  310. endoreg_db/models/other/distribution/numeric_value_distribution.py +30 -2
  311. endoreg_db/models/{emission → other/emission}/emission_factor.py +15 -6
  312. endoreg_db/models/{persons → other}/gender.py +8 -3
  313. endoreg_db/models/other/information_source.py +159 -0
  314. endoreg_db/models/other/material.py +10 -2
  315. endoreg_db/models/other/resource.py +6 -2
  316. endoreg_db/models/other/tag.py +27 -0
  317. endoreg_db/models/other/transport_route.py +13 -2
  318. endoreg_db/models/{unit.py → other/unit.py} +16 -6
  319. endoreg_db/models/other/waste.py +10 -3
  320. endoreg_db/models/requirement/requirement.py +556 -114
  321. endoreg_db/models/requirement/requirement_evaluation/__init__.py +4 -132
  322. endoreg_db/models/requirement/requirement_evaluation/get_values.py +40 -0
  323. endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +9 -0
  324. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +80 -87
  325. endoreg_db/models/requirement/requirement_operator.py +132 -14
  326. endoreg_db/models/requirement/requirement_set.py +181 -21
  327. endoreg_db/models/rule/__init__.py +13 -0
  328. endoreg_db/models/{rules → rule}/rule.py +6 -3
  329. endoreg_db/models/{rules → rule}/rule_attribute_dtype.py +0 -2
  330. endoreg_db/models/{rules → rule}/rule_type.py +0 -2
  331. endoreg_db/models/{rules → rule}/ruleset.py +0 -2
  332. endoreg_db/models/state/__init__.py +12 -0
  333. endoreg_db/models/state/abstract.py +11 -0
  334. endoreg_db/models/state/audit_ledger.py +150 -0
  335. endoreg_db/models/state/label_video_segment.py +22 -0
  336. endoreg_db/models/state/raw_pdf.py +187 -0
  337. endoreg_db/models/state/sensitive_meta.py +46 -0
  338. endoreg_db/models/state/video.py +232 -0
  339. endoreg_db/models/upload_job.py +99 -0
  340. endoreg_db/models/utils.py +135 -0
  341. endoreg_db/models/video_metadata.py +66 -0
  342. endoreg_db/models/video_processing.py +153 -0
  343. endoreg_db/renames.yml +8 -0
  344. endoreg_db/root_urls.py +9 -0
  345. endoreg_db/schemas/__init__.py +0 -0
  346. endoreg_db/schemas/examination_evaluation.py +27 -0
  347. endoreg_db/serializers/Frames_NICE_and_PARIS_classifications.py +775 -0
  348. endoreg_db/serializers/__init__.py +147 -10
  349. endoreg_db/serializers/{raw_pdf_meta_validation.py → _old/raw_pdf_meta_validation.py} +3 -3
  350. endoreg_db/serializers/{raw_video_meta_validation.py → _old/raw_video_meta_validation.py} +18 -14
  351. endoreg_db/serializers/_old/video.py +71 -0
  352. endoreg_db/serializers/administration/__init__.py +14 -0
  353. endoreg_db/serializers/administration/ai/__init__.py +10 -0
  354. endoreg_db/serializers/administration/ai/active_model.py +10 -0
  355. endoreg_db/serializers/administration/ai/ai_model.py +18 -0
  356. endoreg_db/serializers/administration/ai/model_type.py +10 -0
  357. endoreg_db/serializers/administration/center.py +9 -0
  358. endoreg_db/serializers/administration/gender.py +9 -0
  359. endoreg_db/serializers/anonymization.py +69 -0
  360. endoreg_db/serializers/evaluation/examination_evaluation.py +1 -0
  361. endoreg_db/serializers/examination/__init__.py +10 -0
  362. endoreg_db/serializers/examination/base.py +46 -0
  363. endoreg_db/serializers/examination/dropdown.py +21 -0
  364. endoreg_db/serializers/examination_serializer.py +12 -0
  365. endoreg_db/serializers/finding/__init__.py +5 -0
  366. endoreg_db/serializers/finding/finding.py +54 -0
  367. endoreg_db/serializers/finding_classification/__init__.py +7 -0
  368. endoreg_db/serializers/finding_classification/choice.py +19 -0
  369. endoreg_db/serializers/finding_classification/classification.py +13 -0
  370. endoreg_db/serializers/label/__init__.py +7 -0
  371. endoreg_db/serializers/label/image_classification_annotation.py +62 -0
  372. endoreg_db/serializers/label/label.py +15 -0
  373. endoreg_db/serializers/label_video_segment/__init__.py +7 -0
  374. endoreg_db/serializers/label_video_segment/_lvs_create.py +149 -0
  375. endoreg_db/serializers/label_video_segment/_lvs_update.py +138 -0
  376. endoreg_db/serializers/label_video_segment/_lvs_validate.py +149 -0
  377. endoreg_db/serializers/label_video_segment/label_video_segment.py +344 -0
  378. endoreg_db/serializers/label_video_segment/label_video_segment_annotation.py +99 -0
  379. endoreg_db/serializers/label_video_segment/label_video_segment_update.py +163 -0
  380. endoreg_db/serializers/meta/__init__.py +19 -0
  381. endoreg_db/serializers/meta/pdf_file_meta_extraction.py +115 -0
  382. endoreg_db/serializers/meta/report_meta.py +53 -0
  383. endoreg_db/serializers/meta/sensitive_meta_detail.py +162 -0
  384. endoreg_db/serializers/meta/sensitive_meta_update.py +148 -0
  385. endoreg_db/serializers/meta/sensitive_meta_verification.py +59 -0
  386. endoreg_db/serializers/meta/video_meta.py +39 -0
  387. endoreg_db/serializers/misc/__init__.py +14 -0
  388. endoreg_db/serializers/misc/file_overview.py +152 -0
  389. endoreg_db/serializers/misc/stats.py +33 -0
  390. endoreg_db/serializers/misc/translatable_field_mix_in.py +44 -0
  391. endoreg_db/serializers/misc/upload_job.py +71 -0
  392. endoreg_db/serializers/misc/vop_patient_data.py +120 -0
  393. endoreg_db/serializers/patient/__init__.py +11 -0
  394. endoreg_db/serializers/patient/patient.py +86 -0
  395. endoreg_db/serializers/patient/patient_dropdown.py +27 -0
  396. endoreg_db/serializers/patient_examination/__init__.py +7 -0
  397. endoreg_db/serializers/patient_examination/patient_examination.py +141 -0
  398. endoreg_db/serializers/patient_finding/__init__.py +15 -0
  399. endoreg_db/serializers/patient_finding/patient_finding.py +31 -0
  400. endoreg_db/serializers/patient_finding/patient_finding_classification.py +39 -0
  401. endoreg_db/serializers/patient_finding/patient_finding_detail.py +53 -0
  402. endoreg_db/serializers/patient_finding/patient_finding_intervention.py +26 -0
  403. endoreg_db/serializers/patient_finding/patient_finding_list.py +41 -0
  404. endoreg_db/serializers/patient_finding/patient_finding_write.py +126 -0
  405. endoreg_db/serializers/pdf/__init__.py +5 -0
  406. endoreg_db/serializers/pdf/anony_text_validation.py +85 -0
  407. endoreg_db/serializers/report/__init__.py +9 -0
  408. endoreg_db/serializers/report/mixins.py +45 -0
  409. endoreg_db/serializers/report/report.py +105 -0
  410. endoreg_db/serializers/report/report_list.py +22 -0
  411. endoreg_db/serializers/report/secure_file_url.py +26 -0
  412. endoreg_db/serializers/requirements/requirement_schema.py +25 -0
  413. endoreg_db/serializers/requirements/requirement_sets.py +29 -0
  414. endoreg_db/serializers/sensitive_meta_serializer.py +282 -0
  415. endoreg_db/serializers/video/__init__.py +7 -0
  416. endoreg_db/serializers/video/segmentation.py +263 -0
  417. endoreg_db/serializers/video/video_file_brief.py +10 -0
  418. endoreg_db/serializers/video/video_file_detail.py +83 -0
  419. endoreg_db/serializers/video/video_file_list.py +67 -0
  420. endoreg_db/serializers/video/video_metadata.py +105 -0
  421. endoreg_db/serializers/video/video_processing_history.py +153 -0
  422. endoreg_db/services/__init__.py +5 -0
  423. endoreg_db/services/anonymization.py +223 -0
  424. endoreg_db/services/examination_evaluation.py +149 -0
  425. endoreg_db/services/finding_description_service.py +0 -0
  426. endoreg_db/services/lookup_service.py +241 -0
  427. endoreg_db/services/lookup_store.py +122 -0
  428. endoreg_db/services/ollama_api_docs.py +1528 -0
  429. endoreg_db/services/pdf_import.py +963 -0
  430. endoreg_db/services/polling_coordinator.py +288 -0
  431. endoreg_db/services/pseudonym_service.py +89 -0
  432. endoreg_db/services/requirements_object.py +147 -0
  433. endoreg_db/services/segment_sync.py +155 -0
  434. endoreg_db/services/storage_aware_video_processor.py +344 -0
  435. endoreg_db/services/video_import.py +1118 -0
  436. endoreg_db/tasks/upload_tasks.py +207 -0
  437. endoreg_db/tasks/video_ingest.py +157 -0
  438. endoreg_db/tasks/video_processing_tasks.py +327 -0
  439. endoreg_db/urls/__init__.py +70 -0
  440. endoreg_db/urls/anonymization.py +32 -0
  441. endoreg_db/urls/auth.py +16 -0
  442. endoreg_db/urls/classification.py +39 -0
  443. endoreg_db/urls/examination.py +54 -0
  444. endoreg_db/urls/files.py +6 -0
  445. endoreg_db/urls/label_video_segment_validate.py +33 -0
  446. endoreg_db/urls/label_video_segments.py +44 -0
  447. endoreg_db/urls/media.py +229 -0
  448. endoreg_db/urls/patient.py +19 -0
  449. endoreg_db/urls/report.py +48 -0
  450. endoreg_db/urls/requirements.py +13 -0
  451. endoreg_db/urls/stats.py +46 -0
  452. endoreg_db/urls/upload.py +20 -0
  453. endoreg_db/urls/video.py +61 -0
  454. endoreg_db/urls.py +6 -283
  455. endoreg_db/utils/__init__.py +66 -57
  456. endoreg_db/utils/ai/__init__.py +9 -0
  457. endoreg_db/{models/ai_model/utils.py → utils/ai/get.py} +1 -4
  458. endoreg_db/{models/ai_model/lightning → utils/ai}/inference_dataset.py +0 -1
  459. endoreg_db/{models/ai_model/lightning → utils/ai}/multilabel_classification_net.py +14 -10
  460. endoreg_db/{models/ai_model/lightning → utils/ai}/postprocess.py +15 -5
  461. endoreg_db/utils/ai/predict.py +291 -0
  462. endoreg_db/{models/ai_model/lightning → utils/ai}/preprocess.py +1 -1
  463. endoreg_db/utils/calc_duration_seconds.py +24 -0
  464. endoreg_db/utils/case_generator/__init__.py +0 -0
  465. endoreg_db/utils/check_video_files.py +148 -0
  466. endoreg_db/utils/dataloader.py +50 -12
  467. endoreg_db/utils/dates.py +21 -0
  468. endoreg_db/utils/env.py +33 -0
  469. endoreg_db/utils/extract_specific_frames.py +72 -0
  470. endoreg_db/utils/file_operations.py +29 -1
  471. endoreg_db/utils/fix_video_path_direct.py +141 -0
  472. endoreg_db/utils/frame_anonymization_utils.py +463 -0
  473. endoreg_db/utils/links/__init__.py +0 -0
  474. endoreg_db/utils/links/requirement_link.py +193 -0
  475. endoreg_db/utils/mime_types.py +0 -0
  476. endoreg_db/utils/names.py +2 -0
  477. endoreg_db/utils/paths.py +100 -82
  478. endoreg_db/utils/permissions.py +143 -0
  479. endoreg_db/utils/pipelines/Readme.md +235 -0
  480. endoreg_db/utils/pipelines/__init__.py +0 -0
  481. endoreg_db/utils/pipelines/process_video_dir.py +120 -0
  482. endoreg_db/utils/product/__init__.py +0 -0
  483. endoreg_db/utils/product/sum_emissions.py +20 -0
  484. endoreg_db/utils/product/sum_weights.py +18 -0
  485. endoreg_db/utils/pydantic_models/db_config.py +1 -1
  486. endoreg_db/utils/requirement_helpers.py +0 -0
  487. endoreg_db/utils/requirement_operator_logic/__init__.py +0 -0
  488. endoreg_db/utils/requirement_operator_logic/lab_value_operators.py +578 -0
  489. endoreg_db/utils/requirement_operator_logic/model_evaluators.py +368 -0
  490. endoreg_db/utils/translation.py +27 -0
  491. endoreg_db/utils/validate_video_detailed.py +357 -0
  492. endoreg_db/utils/video/__init__.py +19 -6
  493. endoreg_db/utils/video/extract_frames.py +37 -70
  494. endoreg_db/utils/video/ffmpeg_wrapper.py +772 -0
  495. endoreg_db/utils/video/names.py +42 -0
  496. endoreg_db/utils/video/streaming_processor.py +312 -0
  497. endoreg_db/utils/video/video_splitter.py +94 -0
  498. endoreg_db/views/Frames_NICE_and_PARIS_classifications_views.py +238 -0
  499. endoreg_db/views/__init__.py +282 -2
  500. endoreg_db/views/anonymization/__init__.py +27 -0
  501. endoreg_db/views/anonymization/media_management.py +454 -0
  502. endoreg_db/views/anonymization/overview.py +216 -0
  503. endoreg_db/views/anonymization/validate.py +66 -0
  504. endoreg_db/views/auth/__init__.py +13 -0
  505. endoreg_db/views/{views.py → auth/keycloak.py} +19 -13
  506. endoreg_db/views/examination/__init__.py +33 -0
  507. endoreg_db/views/examination/examination.py +37 -0
  508. endoreg_db/views/examination/examination_manifest_cache.py +26 -0
  509. endoreg_db/views/examination/get_finding_classification_choices.py +59 -0
  510. endoreg_db/views/examination/get_finding_classifications.py +36 -0
  511. endoreg_db/views/examination/get_findings.py +41 -0
  512. endoreg_db/views/examination/get_instruments.py +18 -0
  513. endoreg_db/views/examination/get_interventions.py +14 -0
  514. endoreg_db/views/finding/__init__.py +9 -0
  515. endoreg_db/views/finding/finding.py +112 -0
  516. endoreg_db/views/finding/get_classifications.py +14 -0
  517. endoreg_db/views/finding/get_interventions.py +17 -0
  518. endoreg_db/views/finding_classification/__init__.py +13 -0
  519. endoreg_db/views/finding_classification/base.py +0 -0
  520. endoreg_db/views/finding_classification/finding_classification.py +42 -0
  521. endoreg_db/views/finding_classification/get_classification_choices.py +55 -0
  522. endoreg_db/views/label/__init__.py +5 -0
  523. endoreg_db/views/label/label.py +15 -0
  524. endoreg_db/views/label_video_segment/__init__.py +16 -0
  525. endoreg_db/views/label_video_segment/create_lvs_from_annotation.py +44 -0
  526. endoreg_db/views/label_video_segment/get_lvs_by_name_and_video.py +50 -0
  527. endoreg_db/views/label_video_segment/label_video_segment.py +77 -0
  528. endoreg_db/views/label_video_segment/label_video_segment_by_label.py +174 -0
  529. endoreg_db/views/label_video_segment/label_video_segment_detail.py +73 -0
  530. endoreg_db/views/label_video_segment/update_lvs_from_annotation.py +46 -0
  531. endoreg_db/views/label_video_segment/validate.py +226 -0
  532. endoreg_db/views/media/__init__.py +45 -0
  533. endoreg_db/views/media/pdf_media.py +386 -0
  534. endoreg_db/views/media/segments.py +71 -0
  535. endoreg_db/views/media/sensitive_metadata.py +314 -0
  536. endoreg_db/views/media/video_media.py +272 -0
  537. endoreg_db/views/media/video_segments.py +596 -0
  538. endoreg_db/views/meta/__init__.py +15 -0
  539. endoreg_db/views/meta/available_files_list.py +146 -0
  540. endoreg_db/views/meta/report_meta.py +53 -0
  541. endoreg_db/views/meta/sensitive_meta_detail.py +148 -0
  542. endoreg_db/views/meta/sensitive_meta_list.py +104 -0
  543. endoreg_db/views/meta/sensitive_meta_verification.py +71 -0
  544. endoreg_db/views/misc/__init__.py +63 -0
  545. endoreg_db/views/misc/center.py +13 -0
  546. endoreg_db/views/misc/gender.py +14 -0
  547. endoreg_db/views/misc/secure_file_serving_view.py +80 -0
  548. endoreg_db/views/misc/secure_file_url_view.py +84 -0
  549. endoreg_db/views/misc/secure_url_validate.py +79 -0
  550. endoreg_db/views/misc/stats.py +220 -0
  551. endoreg_db/views/misc/translation.py +182 -0
  552. endoreg_db/views/misc/upload_views.py +240 -0
  553. endoreg_db/views/patient/__init__.py +5 -0
  554. endoreg_db/views/patient/patient.py +210 -0
  555. endoreg_db/views/patient_examination/DEPRECATED_video_backup.py +164 -0
  556. endoreg_db/views/patient_examination/__init__.py +11 -0
  557. endoreg_db/views/patient_examination/patient_examination.py +140 -0
  558. endoreg_db/views/patient_examination/patient_examination_create.py +63 -0
  559. endoreg_db/views/patient_examination/patient_examination_detail.py +66 -0
  560. endoreg_db/views/patient_examination/patient_examination_list.py +68 -0
  561. endoreg_db/views/patient_examination/video.py +194 -0
  562. endoreg_db/views/patient_finding/__init__.py +7 -0
  563. endoreg_db/views/patient_finding/base.py +0 -0
  564. endoreg_db/views/patient_finding/patient_finding.py +64 -0
  565. endoreg_db/views/patient_finding/patient_finding_optimized.py +259 -0
  566. endoreg_db/views/patient_finding_classification/__init__.py +5 -0
  567. endoreg_db/views/patient_finding_classification/pfc_create.py +67 -0
  568. endoreg_db/views/patient_finding_location/__init__.py +5 -0
  569. endoreg_db/views/patient_finding_location/pfl_create.py +70 -0
  570. endoreg_db/views/patient_finding_morphology/__init__.py +5 -0
  571. endoreg_db/views/patient_finding_morphology/pfm_create.py +70 -0
  572. endoreg_db/views/pdf/__init__.py +11 -0
  573. endoreg_db/views/pdf/pdf_media.py +239 -0
  574. endoreg_db/views/pdf/pdf_stream_views.py +127 -0
  575. endoreg_db/views/pdf/reimport.py +161 -0
  576. endoreg_db/views/report/__init__.py +9 -0
  577. endoreg_db/views/report/report_list.py +112 -0
  578. endoreg_db/views/report/report_with_secure_url.py +28 -0
  579. endoreg_db/views/report/start_examination.py +7 -0
  580. endoreg_db/views/requirement/__init__.py +10 -0
  581. endoreg_db/views/requirement/evaluate.py +279 -0
  582. endoreg_db/views/requirement/lookup.py +483 -0
  583. endoreg_db/views/requirement/lookup_store.py +252 -0
  584. endoreg_db/views/requirement_lookup/lookup.py +0 -0
  585. endoreg_db/views/requirement_lookup/lookup_store.py +0 -0
  586. endoreg_db/views/stats/__init__.py +13 -0
  587. endoreg_db/views/stats/stats_views.py +229 -0
  588. endoreg_db/views/video/__init__.py +64 -0
  589. endoreg_db/views/video/correction.py +672 -0
  590. endoreg_db/views/video/reimport.py +195 -0
  591. endoreg_db/views/video/segmentation.py +274 -0
  592. endoreg_db/views/video/task_status.py +49 -0
  593. endoreg_db/views/{views_for_timeline.py → video/timeline.py} +3 -3
  594. endoreg_db/views/video/video_analyze.py +52 -0
  595. endoreg_db/views/video/video_apply_mask.py +48 -0
  596. endoreg_db/views/video/video_correction.py +21 -0
  597. endoreg_db/views/video/video_download_processed.py +58 -0
  598. endoreg_db/views/video/video_examination_viewset.py +329 -0
  599. endoreg_db/views/video/video_media.py +158 -0
  600. endoreg_db/views/video/video_meta.py +29 -0
  601. endoreg_db/views/video/video_processing_history.py +24 -0
  602. endoreg_db/views/video/video_remove_frames.py +48 -0
  603. endoreg_db/views/video/video_reprocess.py +40 -0
  604. endoreg_db/views/video/video_stream.py +306 -0
  605. endoreg_db-0.8.2.dist-info/METADATA +384 -0
  606. endoreg_db-0.8.2.dist-info/RECORD +790 -0
  607. endoreg_db/data/agl_service/data.yaml +0 -19
  608. endoreg_db/data/finding_location_classification/colonoscopy.yaml +0 -46
  609. endoreg_db/data/finding_morphology_classification/colonoscopy.yaml +0 -48
  610. endoreg_db/data/finding_morphology_classification_choice/colonoscopy_size.yaml +0 -57
  611. endoreg_db/management/commands/_load_model_template.py +0 -41
  612. endoreg_db/management/commands/delete_all.py +0 -18
  613. endoreg_db/management/commands/fetch_legacy_image_dataset.py +0 -32
  614. endoreg_db/management/commands/fix_auth_permission.py +0 -20
  615. endoreg_db/management/commands/load_active_model_data.py +0 -45
  616. endoreg_db/management/commands/load_g_play_data.py +0 -113
  617. endoreg_db/management/commands/load_logging_data.py +0 -39
  618. endoreg_db/management/commands/load_lx_data.py +0 -64
  619. endoreg_db/management/commands/load_medication_indication_data.py +0 -63
  620. endoreg_db/management/commands/load_medication_indication_type_data.py +0 -41
  621. endoreg_db/management/commands/load_medication_intake_time_data.py +0 -41
  622. endoreg_db/management/commands/load_medication_schedule_data.py +0 -55
  623. endoreg_db/management/commands/load_network_data.py +0 -57
  624. endoreg_db/migrations/0002_alter_frame_image_alter_rawframe_image.py +0 -23
  625. endoreg_db/migrations/0003_alter_frame_image_alter_rawframe_image.py +0 -23
  626. endoreg_db/migrations/0004_alter_rawvideofile_file_alter_video_file.py +0 -25
  627. endoreg_db/migrations/0005_rawvideofile_frame_count_and_more.py +0 -33
  628. endoreg_db/migrations/0006_frame_extracted_rawframe_extracted.py +0 -23
  629. endoreg_db/migrations/0007_rename_pseudo_patient_video_patient_and_more.py +0 -24
  630. endoreg_db/migrations/0008_remove_reportfile_patient_examination_and_more.py +0 -48
  631. endoreg_db/migrations/0009_requirementoperator_requirementsettype_and_more.py +0 -154
  632. endoreg_db/models/ai_model/active_model.py +0 -9
  633. endoreg_db/models/ai_model/ai_model.py +0 -90
  634. endoreg_db/models/ai_model/lightning/__init__.py +0 -3
  635. endoreg_db/models/ai_model/lightning/predict.py +0 -172
  636. endoreg_db/models/ai_model/lightning/prediction_visualizer.py +0 -55
  637. endoreg_db/models/ai_model/lightning/run_visualizer.py +0 -21
  638. endoreg_db/models/ai_model/model_meta.py +0 -240
  639. endoreg_db/models/annotation/__init__.py +0 -32
  640. endoreg_db/models/annotation/anonymized_image_annotation.py +0 -115
  641. endoreg_db/models/annotation/binary_classification_annotation_task.py +0 -117
  642. endoreg_db/models/annotation/image_classification.py +0 -86
  643. endoreg_db/models/annotation/video_segmentation_annotation.py +0 -52
  644. endoreg_db/models/case/__init__.py +0 -1
  645. endoreg_db/models/case/case.py +0 -34
  646. endoreg_db/models/center/center.py +0 -51
  647. endoreg_db/models/center/center_product.py +0 -33
  648. endoreg_db/models/center/center_waste.py +0 -16
  649. endoreg_db/models/data_file/__init__.py +0 -39
  650. endoreg_db/models/data_file/base_classes/__init__.py +0 -7
  651. endoreg_db/models/data_file/base_classes/abstract_frame.py +0 -98
  652. endoreg_db/models/data_file/base_classes/abstract_pdf.py +0 -127
  653. endoreg_db/models/data_file/base_classes/abstract_video.py +0 -806
  654. endoreg_db/models/data_file/base_classes/frame_helpers.py +0 -17
  655. endoreg_db/models/data_file/base_classes/prepare_bulk_frames.py +0 -19
  656. endoreg_db/models/data_file/base_classes/utils.py +0 -58
  657. endoreg_db/models/data_file/frame.py +0 -29
  658. endoreg_db/models/data_file/import_classes/__init__.py +0 -18
  659. endoreg_db/models/data_file/import_classes/processing_functions/__init__.py +0 -35
  660. endoreg_db/models/data_file/import_classes/processing_functions/pdf.py +0 -28
  661. endoreg_db/models/data_file/import_classes/processing_functions/video.py +0 -260
  662. endoreg_db/models/data_file/import_classes/raw_pdf.py +0 -254
  663. endoreg_db/models/data_file/import_classes/raw_video.py +0 -290
  664. endoreg_db/models/data_file/metadata/sensitive_meta.py +0 -290
  665. endoreg_db/models/data_file/metadata/video_meta.py +0 -199
  666. endoreg_db/models/data_file/report_file.py +0 -56
  667. endoreg_db/models/data_file/video/__init__.py +0 -11
  668. endoreg_db/models/data_file/video/import_meta.py +0 -25
  669. endoreg_db/models/data_file/video/video.py +0 -196
  670. endoreg_db/models/data_file/video_segment.py +0 -214
  671. endoreg_db/models/examination/examination.py +0 -67
  672. endoreg_db/models/finding/__init__.py +0 -11
  673. endoreg_db/models/finding/finding.py +0 -75
  674. endoreg_db/models/finding/finding_location_classification.py +0 -94
  675. endoreg_db/models/finding/finding_morphology_classification.py +0 -89
  676. endoreg_db/models/finding/finding_type.py +0 -22
  677. endoreg_db/models/hardware/__init__.py +0 -2
  678. endoreg_db/models/information_source.py +0 -65
  679. endoreg_db/models/laboratory/__init__.py +0 -1
  680. endoreg_db/models/laboratory/lab_value.py +0 -162
  681. endoreg_db/models/logging/__init__.py +0 -11
  682. endoreg_db/models/logging/agl_service.py +0 -19
  683. endoreg_db/models/logging/base.py +0 -22
  684. endoreg_db/models/logging/log_type.py +0 -23
  685. endoreg_db/models/logging/network_device.py +0 -27
  686. endoreg_db/models/lx/__init__.py +0 -4
  687. endoreg_db/models/lx/client.py +0 -57
  688. endoreg_db/models/lx/identity.py +0 -34
  689. endoreg_db/models/lx/permission.py +0 -18
  690. endoreg_db/models/lx/user.py +0 -16
  691. endoreg_db/models/network/__init__.py +0 -9
  692. endoreg_db/models/network/agl_service.py +0 -38
  693. endoreg_db/models/network/network_device.py +0 -58
  694. endoreg_db/models/network/network_device_type.py +0 -23
  695. endoreg_db/models/other/distribution.py +0 -5
  696. endoreg_db/models/patient/__init__.py +0 -24
  697. endoreg_db/models/patient/patient_examination.py +0 -182
  698. endoreg_db/models/patient/patient_finding.py +0 -143
  699. endoreg_db/models/patient/patient_finding_location.py +0 -120
  700. endoreg_db/models/patient/patient_finding_morphology.py +0 -166
  701. endoreg_db/models/permissions/__init__.py +0 -44
  702. endoreg_db/models/persons/__init__.py +0 -34
  703. endoreg_db/models/persons/examiner/__init__.py +0 -2
  704. endoreg_db/models/persons/examiner/examiner.py +0 -60
  705. endoreg_db/models/persons/examiner/examiner_type.py +0 -2
  706. endoreg_db/models/persons/patient/__init__.py +0 -8
  707. endoreg_db/models/persons/patient/patient.py +0 -389
  708. endoreg_db/models/persons/patient/patient_disease.py +0 -22
  709. endoreg_db/models/persons/patient/patient_event.py +0 -52
  710. endoreg_db/models/persons/patient/patient_lab_sample.py +0 -108
  711. endoreg_db/models/persons/patient/patient_medication.py +0 -59
  712. endoreg_db/models/persons/patient/patient_medication_schedule.py +0 -88
  713. endoreg_db/models/persons/portal_user_information.py +0 -27
  714. endoreg_db/models/prediction/__init__.py +0 -8
  715. endoreg_db/models/prediction/image_classification.py +0 -51
  716. endoreg_db/models/prediction/video_prediction_meta.py +0 -306
  717. endoreg_db/models/product/product.py +0 -110
  718. endoreg_db/models/product/product_group.py +0 -27
  719. endoreg_db/models/product/product_material.py +0 -28
  720. endoreg_db/models/questionnaires/__init__.py +0 -114
  721. endoreg_db/models/quiz/__init__.py +0 -9
  722. endoreg_db/models/quiz/quiz_answer.py +0 -41
  723. endoreg_db/models/quiz/quiz_question.py +0 -54
  724. endoreg_db/models/report_reader/report_reader_config.py +0 -53
  725. endoreg_db/models/rules/__init__.py +0 -5
  726. endoreg_db/queries/get/__init__.py +0 -6
  727. endoreg_db/queries/get/center.py +0 -42
  728. endoreg_db/queries/get/model.py +0 -13
  729. endoreg_db/queries/get/patient.py +0 -14
  730. endoreg_db/queries/get/patient_examination.py +0 -20
  731. endoreg_db/queries/get/report_file.py +0 -33
  732. endoreg_db/queries/get/video.py +0 -31
  733. endoreg_db/serializers/ai_model.py +0 -19
  734. endoreg_db/serializers/annotation.py +0 -14
  735. endoreg_db/serializers/center.py +0 -11
  736. endoreg_db/serializers/examination.py +0 -33
  737. endoreg_db/serializers/frame.py +0 -9
  738. endoreg_db/serializers/hardware.py +0 -21
  739. endoreg_db/serializers/label.py +0 -22
  740. endoreg_db/serializers/patient.py +0 -33
  741. endoreg_db/serializers/prediction.py +0 -10
  742. endoreg_db/serializers/raw_pdf_anony_text_validation.py +0 -137
  743. endoreg_db/serializers/report_file.py +0 -7
  744. endoreg_db/serializers/video.py +0 -20
  745. endoreg_db/serializers/video_segmentation.py +0 -574
  746. endoreg_db/tests.py +0 -3
  747. endoreg_db/utils/legacy_ocr.py +0 -201
  748. endoreg_db/utils/video/transcode_videofile.py +0 -111
  749. endoreg_db/views/patient_views.py +0 -90
  750. endoreg_db/views/raw_pdf_anony_text_validation_views.py +0 -95
  751. endoreg_db/views/raw_pdf_meta_validation_views.py +0 -111
  752. endoreg_db/views/raw_video_meta_validation_views.py +0 -148
  753. endoreg_db/views/report_views.py +0 -96
  754. endoreg_db/views/video_segmentation_views.py +0 -166
  755. endoreg_db-0.6.4.dist-info/METADATA +0 -161
  756. endoreg_db-0.6.4.dist-info/RECORD +0 -470
  757. /endoreg_db/{case_generator/__init__.py → api/serializers/finding_descriptions.py} +0 -0
  758. /endoreg_db/{queries/get/annotation.py → api/views/finding_descriptions.py} +0 -0
  759. /endoreg_db/{queries/get/prediction.py → data/shift/m2.yaml} +0 -0
  760. /endoreg_db/{queries/get/video_import_meta.py → factories/__init__.py} +0 -0
  761. /endoreg_db/{queries/get/video_prediction_meta.py → helpers/__init__.py} +0 -0
  762. /endoreg_db/models/{case_template → administration/case/case_template}/__init__.py +0 -0
  763. /endoreg_db/models/{persons → administration/person}/person.py +0 -0
  764. /endoreg_db/models/{product → administration/product}/__init__.py +0 -0
  765. /endoreg_db/models/{report_reader → media/pdf/report_reader}/__init__.py +0 -0
  766. /endoreg_db/models/{report_reader → media/pdf/report_reader}/report_reader_flag.py +0 -0
  767. /endoreg_db/models/{hardware → medical/hardware}/endoscopy_processor.py +0 -0
  768. /endoreg_db/models/{medication → medical/medication}/__init__.py +0 -0
  769. /endoreg_db/models/{medication → medical/medication}/medication_indication.py +0 -0
  770. /endoreg_db/models/{risk → medical/risk}/__init__.py +0 -0
  771. /endoreg_db/models/{risk → medical/risk}/risk.py +0 -0
  772. /endoreg_db/models/{emission → other/emission}/__init__.py +0 -0
  773. /endoreg_db/models/{rules → rule}/rule_applicator.py +0 -0
  774. /endoreg_db/{case_generator → utils/case_generator}/case_generator.py +0 -0
  775. /endoreg_db/{case_generator → utils/case_generator}/lab_sample_factory.py +0 -0
  776. /endoreg_db/{case_generator → utils/case_generator}/utils.py +0 -0
  777. /endoreg_db/views/{csrf.py → misc/csrf.py} +0 -0
  778. {endoreg_db-0.6.4.dist-info → endoreg_db-0.8.2.dist-info}/WHEEL +0 -0
  779. {endoreg_db-0.6.4.dist-info → endoreg_db-0.8.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,643 @@
1
+ import logging
2
+ import os
3
+ import random
4
+ import re # Neu hinzugefügt für Regex-Pattern
5
+ from hashlib import sha256
6
+ from datetime import datetime, timedelta, date
7
+ from typing import TYPE_CHECKING, Dict, Any, Optional, Type
8
+
9
+ from django.utils import timezone
10
+ from django.db import transaction
11
+
12
+ # Assuming these utils are correctly located
13
+ from endoreg_db.utils.hashs import get_patient_hash, get_patient_examination_hash
14
+ from endoreg_db.utils import guess_name_gender
15
+
16
+ # Import models needed for logic, use local imports inside functions if needed to break cycles
17
+ from ..administration import Center, Examiner, Patient, FirstName, LastName
18
+ from ..other import Gender
19
+ from ..medical import PatientExamination
20
+ from ..state import SensitiveMetaState
21
+
22
+
23
+ if TYPE_CHECKING:
24
+ from .sensitive_meta import SensitiveMeta # Import model for type hinting
25
+
26
+ logger = logging.getLogger(__name__)
27
+ SECRET_SALT = os.getenv("DJANGO_SALT", "default_salt")
28
+ DEFAULT_UNKNOWN_NAME = "unknown"
29
+
30
+ # Regex-Pattern für verschiedene Datumsformate
31
+ ISO_RX = re.compile(r"^\d{4}-\d{2}-\d{2}$")
32
+ DE_RX = re.compile(r"^\d{2}\.\d{2}\.\d{4}$")
33
+
34
+
35
+ def parse_any_date(s: str) -> Optional[date]:
36
+ """
37
+ Parst Datumsstring mit Priorität auf deutsches Format (DD.MM.YYYY).
38
+
39
+ Unterstützte Formate:
40
+ 1. DD.MM.YYYY (Priorität) - deutsches Format
41
+ 2. YYYY-MM-DD (Fallback) - ISO-Format
42
+ 3. Erweiterte Fallbacks über dateparser
43
+
44
+ Args:
45
+ s: Datumsstring zum Parsen
46
+
47
+ Returns:
48
+ date-Objekt oder None bei ungültigem/fehlendem Input
49
+ """
50
+ if not s:
51
+ return None
52
+
53
+ s = s.strip()
54
+
55
+ # 1. German dd.mm.yyyy (PRIORITÄT)
56
+ if DE_RX.match(s):
57
+ try:
58
+ dd, mm, yyyy = s.split(".")
59
+ return date(int(yyyy), int(mm), int(dd))
60
+ except ValueError as e:
61
+ logger.warning(f"Invalid German date format '{s}': {e}")
62
+ return None
63
+
64
+ # 2. ISO yyyy-mm-dd (Fallback für Rückwärtskompatibilität)
65
+ if ISO_RX.match(s):
66
+ try:
67
+ return date.fromisoformat(s)
68
+ except ValueError as e:
69
+ logger.warning(f"Invalid ISO date format '{s}': {e}")
70
+ return None
71
+
72
+ # 3. Extended fallbacks
73
+ try:
74
+ # Try standard datetime parsing
75
+ return datetime.fromisoformat(s).date()
76
+ except Exception:
77
+ pass
78
+
79
+ try:
80
+ # Try dateparser with German locale preference
81
+ import dateparser
82
+ dt = dateparser.parse(
83
+ s,
84
+ settings={
85
+ "DATE_ORDER": "DMY",
86
+ "PREFER_DAY_OF_MONTH": "first"
87
+ }
88
+ )
89
+ return dt.date() if dt else None
90
+ except Exception as e:
91
+ logger.debug(f"Dateparser fallback failed for '{s}': {e}")
92
+ return None
93
+
94
+
95
+ def format_date_german(d: Optional[date]) -> str:
96
+ """
97
+ Formatiert date-Objekt als deutsches Datumsformat (DD.MM.YYYY).
98
+
99
+ Args:
100
+ d: date-Objekt oder None
101
+
102
+ Returns:
103
+ Formatiertes Datum als String oder leerer String bei None
104
+ """
105
+ if not d:
106
+ return ""
107
+ return d.strftime("%d.%m.%Y")
108
+
109
+
110
+ def format_date_iso(d: Optional[date]) -> str:
111
+ """
112
+ Formatiert date-Objekt als ISO-Format (YYYY-MM-DD).
113
+
114
+ Args:
115
+ d: date-Objekt oder None
116
+
117
+ Returns:
118
+ Formatiertes Datum als String oder leerer String bei None
119
+ """
120
+ if not d:
121
+ return ""
122
+ return d.isoformat()
123
+
124
+
125
+ def generate_random_dob() -> datetime:
126
+ """Generates a random timezone-aware datetime between 1920-01-01 and 2000-12-31."""
127
+ start_date = date(1920, 1, 1)
128
+ end_date = date(2000, 12, 31)
129
+ time_between_dates = end_date - start_date
130
+ days_between_dates = time_between_dates.days
131
+ random_number_of_days = random.randrange(days_between_dates)
132
+ random_date = start_date + timedelta(days=random_number_of_days)
133
+ random_datetime = datetime.combine(random_date, datetime.min.time())
134
+ return timezone.make_aware(random_datetime)
135
+
136
+
137
+ def generate_random_examination_date() -> date:
138
+ """Generates a random date within the last 20 years."""
139
+ today = date.today()
140
+ start_date = today - timedelta(days=20 * 365) # Approximate 20 years back
141
+ time_between_dates = today - start_date
142
+ days_between_dates = time_between_dates.days
143
+ random_number_of_days = random.randrange(days_between_dates)
144
+ random_date = start_date + timedelta(days=random_number_of_days)
145
+ return random_date
146
+
147
+
148
+ def update_name_db(first_name: Optional[str], last_name: Optional[str]):
149
+ """Adds first and last names to the respective lookup tables if they don't exist."""
150
+ if first_name:
151
+ FirstName.objects.get_or_create(name=first_name)
152
+ if last_name:
153
+ LastName.objects.get_or_create(name=last_name)
154
+
155
+
156
+ def calculate_patient_hash(instance: "SensitiveMeta", salt: str = SECRET_SALT) -> str:
157
+ """Calculates the patient hash for the instance."""
158
+ dob = instance.patient_dob
159
+ first_name = instance.patient_first_name
160
+ last_name = instance.patient_last_name
161
+ center = instance.center
162
+
163
+ if not dob:
164
+ raise ValueError("Patient DOB is required to calculate patient hash.")
165
+ if not center:
166
+ raise ValueError("Center is required to calculate patient hash.")
167
+
168
+ hash_str = get_patient_hash(
169
+ first_name=first_name,
170
+ last_name=last_name,
171
+ dob=dob,
172
+ center=center.name, # Use center name
173
+ salt=salt,
174
+ )
175
+ return sha256(hash_str.encode()).hexdigest()
176
+
177
+
178
+ def calculate_examination_hash(instance: "SensitiveMeta", salt: str = SECRET_SALT) -> str:
179
+ """Calculates the examination hash for the instance."""
180
+ dob = instance.patient_dob
181
+ first_name = instance.patient_first_name
182
+ last_name = instance.patient_last_name
183
+ examination_date = instance.examination_date
184
+ center = instance.center
185
+
186
+ if not dob:
187
+ raise ValueError("Patient DOB is required to calculate examination hash.")
188
+ if not examination_date:
189
+ raise ValueError("Examination date is required to calculate examination hash.")
190
+ if not center:
191
+ raise ValueError("Center is required to calculate examination hash.")
192
+
193
+
194
+ hash_str = get_patient_examination_hash(
195
+ first_name=first_name,
196
+ last_name=last_name,
197
+ dob=dob,
198
+ examination_date=examination_date,
199
+ center=center.name, # Use center name
200
+ salt=salt,
201
+ )
202
+ return sha256(hash_str.encode()).hexdigest()
203
+
204
+
205
+ def create_pseudo_examiner_logic(instance: "SensitiveMeta") -> "Examiner":
206
+ """Creates or retrieves the pseudo examiner based on instance data."""
207
+ first_name = instance.examiner_first_name
208
+ last_name = instance.examiner_last_name
209
+ center = instance.center # Should be set before calling save
210
+
211
+ if not first_name or not last_name or not center:
212
+ logger.warning(f"Incomplete examiner info for SensitiveMeta (pk={instance.pk}). Using default examiner.")
213
+ # Ensure default center exists or handle appropriately
214
+ try:
215
+ default_center = Center.objects.get_by_natural_key("endoreg_db_demo")
216
+ except Center.DoesNotExist:
217
+ logger.error("Default center 'endoreg_db_demo' not found. Cannot create default examiner.")
218
+ raise ValueError("Default center 'endoreg_db_demo' not found.")
219
+
220
+ examiner, _created = Examiner.custom_get_or_create(
221
+ first_name="Unknown", last_name="Unknown", center=default_center
222
+ )
223
+ else:
224
+ examiner, _created = Examiner.custom_get_or_create(
225
+ first_name=first_name, last_name=last_name, center=center
226
+ )
227
+
228
+ return examiner
229
+
230
+
231
+ def get_or_create_pseudo_patient_logic(instance: "SensitiveMeta") -> "Patient":
232
+ """Gets or creates the pseudo patient based on instance data."""
233
+ # Ensure necessary fields are set
234
+ if not instance.patient_hash:
235
+ instance.patient_hash = calculate_patient_hash(instance)
236
+ if not instance.center:
237
+ raise ValueError("Center must be set before creating pseudo patient.")
238
+ if not instance.patient_gender:
239
+ raise ValueError("Patient gender must be set before creating pseudo patient.")
240
+ if not instance.patient_dob:
241
+ raise ValueError("Patient DOB must be set before creating pseudo patient.")
242
+
243
+ dob = instance.patient_dob
244
+ year = dob.year
245
+ month = dob.month
246
+
247
+ patient, _created = Patient.get_or_create_pseudo_patient_by_hash(
248
+ patient_hash=instance.patient_hash,
249
+ center=instance.center,
250
+ gender=instance.patient_gender,
251
+ birth_year=year,
252
+ birth_month=month,
253
+ )
254
+ return patient
255
+
256
+
257
+ def get_or_create_pseudo_patient_examination_logic(instance: "SensitiveMeta") -> "PatientExamination":
258
+ """Gets or creates the pseudo patient examination based on instance data."""
259
+ # Ensure necessary fields are set
260
+ if not instance.patient_hash:
261
+ instance.patient_hash = calculate_patient_hash(instance)
262
+ if not instance.examination_hash:
263
+ instance.examination_hash = calculate_examination_hash(instance)
264
+
265
+ # Ensure the pseudo patient exists first, as PatientExamination might depend on it
266
+ if not instance.pseudo_patient_id:
267
+ pseudo_patient = get_or_create_pseudo_patient_logic(instance)
268
+ instance.pseudo_patient_id = pseudo_patient.pk # Assign FK directly
269
+
270
+ patient_examination, _created = PatientExamination.get_or_create_pseudo_patient_examination_by_hash(
271
+ patient_hash=instance.patient_hash,
272
+ examination_hash=instance.examination_hash,
273
+ # Optionally pass pseudo_patient if the method requires it
274
+ # pseudo_patient=instance.pseudo_patient
275
+ )
276
+ return patient_examination
277
+
278
+
279
+ @transaction.atomic # Ensure all operations within save succeed or fail together
280
+ def perform_save_logic(instance: "SensitiveMeta") -> "Examiner":
281
+ """
282
+ Contains the core logic for preparing a SensitiveMeta instance for saving.
283
+ Handles data generation (dates), hash calculation, and linking pseudo-entities.
284
+ Returns the Examiner instance to be linked via M2M after the main save.
285
+ """
286
+
287
+ # --- Pre-Save Checks and Data Generation ---
288
+
289
+ # 1. Ensure DOB and Examination Date exist
290
+ if not instance.patient_dob:
291
+ logger.debug(f"SensitiveMeta (pk={instance.pk}): Patient DOB missing, generating random.")
292
+ instance.patient_dob = generate_random_dob()
293
+ if not instance.examination_date:
294
+ logger.debug(f"SensitiveMeta (pk={instance.pk}): Examination date missing, generating random.")
295
+ instance.examination_date = generate_random_examination_date()
296
+
297
+ # 2. Ensure Center exists (should be set before calling save)
298
+ if not instance.center:
299
+ raise ValueError("Center must be set before saving SensitiveMeta.")
300
+
301
+ # 3. Ensure Gender exists (should be set before calling save, e.g., during creation/update)
302
+ if not instance.patient_gender:
303
+ # Attempt to guess if names are available
304
+ first_name = instance.patient_first_name or DEFAULT_UNKNOWN_NAME
305
+ gender = guess_name_gender(first_name)
306
+ if not gender:
307
+ raise ValueError("Patient gender could not be determined and must be set before saving.")
308
+ instance.patient_gender = gender
309
+
310
+
311
+ # 4. Calculate Hashes (depends on DOB, Exam Date, Center, Names)
312
+ instance.patient_hash = calculate_patient_hash(instance)
313
+ instance.examination_hash = calculate_examination_hash(instance)
314
+
315
+ # 5. Get or Create Pseudo Patient (depends on hash, center, gender, dob)
316
+ # Assign directly to the FK field to avoid premature saving issues
317
+ pseudo_patient = get_or_create_pseudo_patient_logic(instance)
318
+ instance.pseudo_patient_id = pseudo_patient.pk
319
+
320
+ # 6. Get or Create Pseudo Examination (depends on hashes)
321
+ # Assign directly to the FK field
322
+ pseudo_examination = get_or_create_pseudo_patient_examination_logic(instance)
323
+ instance.pseudo_examination_id = pseudo_examination.pk
324
+
325
+ # 7. Get or Create Pseudo Examiner (depends on names, center)
326
+ # This needs to happen *after* the main instance has a PK for M2M linking.
327
+ # We create/get it here and return it to the main save method.
328
+ examiner_instance = create_pseudo_examiner_logic(instance)
329
+
330
+ # 8. Ensure SensitiveMetaState exists (will be checked/created *after* main save)
331
+
332
+ # Return the examiner instance so the model's save method can handle M2M linking
333
+ return examiner_instance
334
+
335
+
336
+ def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str, Any]) -> "SensitiveMeta":
337
+ """Logic to create a SensitiveMeta instance from a dictionary."""
338
+
339
+ field_names = {f.name for f in cls._meta.get_fields() if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)}
340
+ selected_data = {k: v for k, v in data.items() if k in field_names}
341
+
342
+ # --- Convert patient_dob if it's a date object ---
343
+ dob = selected_data.get("patient_dob")
344
+ if isinstance(dob, date) and not isinstance(dob, datetime):
345
+ # Convert date to datetime at the start of the day and make it timezone-aware
346
+ aware_dob = timezone.make_aware(datetime.combine(dob, datetime.min.time()))
347
+ selected_data["patient_dob"] = aware_dob
348
+ logger.debug("Converted patient_dob from date to aware datetime: %s", aware_dob)
349
+ elif isinstance(dob, str):
350
+ # Handle string DOB - check if it's a field name or actual date
351
+ if dob == "patient_dob" or dob in ["patient_first_name", "patient_last_name", "examination_date"]:
352
+ logger.warning("Skipping invalid patient_dob value '%s' - appears to be field name", dob)
353
+ selected_data.pop("patient_dob", None) # Remove invalid value
354
+ else:
355
+ # Try to parse as date string
356
+ try:
357
+ import dateparser
358
+ parsed_dob = dateparser.parse(dob, languages=['de'], settings={'DATE_ORDER': 'DMY'})
359
+ if parsed_dob:
360
+ aware_dob = timezone.make_aware(parsed_dob.replace(hour=0, minute=0, second=0, microsecond=0))
361
+ selected_data["patient_dob"] = aware_dob
362
+ logger.debug("Parsed string patient_dob '%s' to aware datetime: %s", dob, aware_dob)
363
+ else:
364
+ logger.warning("Could not parse patient_dob string '%s', removing from data", dob)
365
+ selected_data.pop("patient_dob", None)
366
+ except Exception as e:
367
+ logger.warning("Error parsing patient_dob string '%s': %s, removing from data", dob, e)
368
+ selected_data.pop("patient_dob", None)
369
+ # --- End Conversion ---
370
+
371
+ # Similar validation for examination_date
372
+ exam_date = selected_data.get("examination_date")
373
+ if isinstance(exam_date, str):
374
+ if exam_date == "examination_date" or exam_date in ["patient_first_name", "patient_last_name", "patient_dob"]:
375
+ logger.warning("Skipping invalid examination_date value '%s' - appears to be field name", exam_date)
376
+ selected_data.pop("examination_date", None)
377
+ else:
378
+ # Try to parse as date string
379
+ try:
380
+ # First try simple ISO format for YYYY-MM-DD
381
+ if len(exam_date) == 10 and exam_date.count('-') == 2:
382
+ try:
383
+ from datetime import datetime as dt
384
+ parsed_date = dt.strptime(exam_date, '%Y-%m-%d').date()
385
+ selected_data["examination_date"] = parsed_date
386
+ logger.debug("Parsed ISO examination_date '%s' to date: %s", exam_date, parsed_date)
387
+ except ValueError:
388
+ # Fall back to dateparser for complex formats
389
+ import dateparser
390
+ parsed_date = dateparser.parse(exam_date, languages=['de'], settings={'DATE_ORDER': 'DMY'})
391
+ if parsed_date:
392
+ selected_data["examination_date"] = parsed_date.date()
393
+ logger.debug("Parsed string examination_date '%s' to date: %s", exam_date, parsed_date.date())
394
+ else:
395
+ logger.warning("Could not parse examination_date string '%s', removing from data", exam_date)
396
+ selected_data.pop("examination_date", None)
397
+ else:
398
+ # Use dateparser for non-ISO formats
399
+ import dateparser
400
+ parsed_date = dateparser.parse(exam_date, languages=['de'], settings={'DATE_ORDER': 'DMY'})
401
+ if parsed_date:
402
+ selected_data["examination_date"] = parsed_date.date()
403
+ logger.debug("Parsed string examination_date '%s' to date: %s", exam_date, parsed_date.date())
404
+ else:
405
+ logger.warning("Could not parse examination_date string '%s', removing from data", exam_date)
406
+ selected_data.pop("examination_date", None)
407
+ except Exception as e:
408
+ logger.warning("Error parsing examination_date string '%s': %s, removing from data", exam_date, e)
409
+ selected_data.pop("examination_date", None)
410
+
411
+ # Handle Center
412
+ center_name = data.get("center_name")
413
+ if not center_name:
414
+ raise ValueError("center_name is required in data dictionary.")
415
+ try:
416
+ center = Center.objects.get_by_natural_key(center_name)
417
+ selected_data["center"] = center
418
+ except Center.DoesNotExist as exc:
419
+ raise ValueError(f"Center with name '{center_name}' does not exist.") from exc
420
+
421
+ # Handle Names and Gender
422
+ first_name = selected_data.get("patient_first_name") or DEFAULT_UNKNOWN_NAME
423
+ last_name = selected_data.get("patient_last_name") or DEFAULT_UNKNOWN_NAME
424
+ selected_data["patient_first_name"] = first_name # Ensure defaults are set
425
+ selected_data["patient_last_name"] = last_name
426
+
427
+ patient_gender_input = selected_data.get("patient_gender")
428
+
429
+ if isinstance(patient_gender_input, Gender):
430
+ # Already a Gender object, nothing to do
431
+ pass
432
+ elif isinstance(patient_gender_input, str):
433
+ # Input is a string (gender name)
434
+ try:
435
+ selected_data["patient_gender"] = Gender.objects.get(name=patient_gender_input)
436
+ except Gender.DoesNotExist:
437
+ logger.warning(f"Gender with name '{patient_gender_input}' provided but not found. Attempting to guess or use default.")
438
+ # Fall through to guessing logic if provided string name is invalid
439
+ patient_gender_input = None # Reset to trigger guessing
440
+
441
+ if not isinstance(selected_data.get("patient_gender"), Gender): # If not already a Gender object (e.g. was None, or string lookup failed)
442
+ gender_name_to_use = guess_name_gender(first_name)
443
+ if not gender_name_to_use:
444
+ logger.warning(f"Could not guess gender for name '{first_name}'. Setting Gender to unknown.")
445
+ gender_name_to_use = "unknown"
446
+ try:
447
+ selected_data["patient_gender"] = Gender.objects.get(name=gender_name_to_use)
448
+ except Gender.DoesNotExist:
449
+ # This should ideally not happen if "unknown" gender is guaranteed to exist
450
+ raise ValueError(f"Default or guessed gender '{gender_name_to_use}' does not exist in Gender table.")
451
+
452
+ # Update name DB
453
+ update_name_db(first_name, last_name)
454
+
455
+ # Instantiate without saving yet
456
+ sensitive_meta = cls(**selected_data)
457
+
458
+ # Call save once at the end. This triggers the custom save logic.
459
+ sensitive_meta.save() # This will call perform_save_logic internally
460
+
461
+ return sensitive_meta
462
+
463
+
464
+ def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, Any]) -> "SensitiveMeta":
465
+ """Logic to update a SensitiveMeta instance from a dictionary."""
466
+ field_names = {f.name for f in instance._meta.get_fields() if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)}
467
+ # Exclude FKs that should not be updated directly from dict keys (handled separately or via save logic)
468
+ excluded_fields = {'pseudo_patient', 'pseudo_examination'}
469
+ selected_data = {k: v for k, v in data.items() if k in field_names and k not in excluded_fields}
470
+
471
+ # Handle potential Center update
472
+ center_name = data.get("center_name")
473
+ if center_name:
474
+ try:
475
+ center = Center.objects.get_by_natural_key(center_name)
476
+ instance.center = center # Update center directly
477
+ except Center.DoesNotExist as exc:
478
+ logger.warning(f"Center '{center_name}' not found during update. Keeping existing center.")
479
+ selected_data.pop('center', None) # Remove from dict if not found
480
+
481
+ # Set examiner names if provided, before calling save
482
+ examiner_first_name = data.get("examiner_first_name")
483
+ examiner_last_name = data.get("examiner_last_name")
484
+ if examiner_first_name is not None: # Allow setting empty strings
485
+ instance.examiner_first_name = examiner_first_name
486
+ if examiner_last_name is not None:
487
+ instance.examiner_last_name = examiner_last_name
488
+
489
+ # Handle patient_gender specially with graceful error handling
490
+ patient_gender_input = data.get("patient_gender")
491
+ if patient_gender_input is not None:
492
+ try:
493
+ if isinstance(patient_gender_input, Gender):
494
+ selected_data["patient_gender"] = patient_gender_input
495
+ elif isinstance(patient_gender_input, str):
496
+ gender_input_clean = patient_gender_input.strip()
497
+ # Try direct case-insensitive DB lookup first
498
+ gender_obj = Gender.objects.filter(name__iexact=gender_input_clean).first()
499
+ if gender_obj:
500
+ selected_data["patient_gender"] = gender_obj
501
+ logger.debug(f"Successfully matched gender string '{patient_gender_input}' to Gender object via iexact lookup")
502
+ else:
503
+ # Use mapping helper for fallback
504
+ mapped = _map_gender_string_to_standard(gender_input_clean)
505
+ if mapped:
506
+ gender_obj = Gender.objects.filter(name__iexact=mapped).first()
507
+ if gender_obj:
508
+ selected_data["patient_gender"] = gender_obj
509
+ logger.info(f"Mapped gender '{patient_gender_input}' to '{mapped}' via fallback mapping")
510
+ else:
511
+ logger.warning(f"Mapped gender '{patient_gender_input}' to '{mapped}', but no such Gender in DB. Trying 'unknown'.")
512
+ unknown_gender = Gender.objects.filter(name__iexact='unknown').first()
513
+ if unknown_gender:
514
+ selected_data["patient_gender"] = unknown_gender
515
+ logger.warning(f"Using 'unknown' gender as fallback for '{patient_gender_input}'")
516
+ else:
517
+ logger.error(f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update.")
518
+ selected_data.pop("patient_gender", None)
519
+ else:
520
+ # Last resort: try to get 'unknown' gender
521
+ unknown_gender = Gender.objects.filter(name__iexact='unknown').first()
522
+ if unknown_gender:
523
+ selected_data["patient_gender"] = unknown_gender
524
+ logger.warning(f"Using 'unknown' gender as fallback for '{patient_gender_input}' (no mapping)")
525
+ else:
526
+ logger.error(f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update.")
527
+ selected_data.pop("patient_gender", None)
528
+ else:
529
+ logger.warning(f"Unexpected patient_gender type {type(patient_gender_input)}: {patient_gender_input}. Skipping gender update.")
530
+ selected_data.pop("patient_gender", None)
531
+ except Exception as e:
532
+ logger.exception(f"Error handling patient_gender '{patient_gender_input}': {e}. Skipping gender update.")
533
+ selected_data.pop("patient_gender", None)
534
+
535
+ # Update other attributes from selected_data
536
+ patient_name_changed = False
537
+ for k, v in selected_data.items():
538
+ # Avoid overwriting examiner names if they were just explicitly set
539
+ if k not in ["examiner_first_name", "examiner_last_name"] or \
540
+ (k == "examiner_first_name" and examiner_first_name is None) or \
541
+ (k == "examiner_last_name" and examiner_last_name is None):
542
+
543
+ try:
544
+ # --- Convert patient_dob if it's a date object ---
545
+ value_to_set = v
546
+ if k == "patient_dob":
547
+ if isinstance(v, date) and not isinstance(v, datetime):
548
+ aware_dob = timezone.make_aware(datetime.combine(v, datetime.min.time()))
549
+ value_to_set = aware_dob
550
+ logger.debug("Converted patient_dob from date to aware datetime during update: %s", aware_dob)
551
+ elif isinstance(v, str):
552
+ # Handle string DOB - check if it's a field name or actual date
553
+ if v == "patient_dob" or v in ["patient_first_name", "patient_last_name", "examination_date"]:
554
+ logger.warning("Skipping invalid patient_dob value '%s' during update - appears to be field name", v)
555
+ continue # Skip this field
556
+ else:
557
+ # Try to parse as date string
558
+ try:
559
+ import dateparser
560
+ parsed_dob = dateparser.parse(v, languages=['de'], settings={'DATE_ORDER': 'DMY'})
561
+ if parsed_dob:
562
+ value_to_set = timezone.make_aware(parsed_dob.replace(hour=0, minute=0, second=0, microsecond=0))
563
+ logger.debug("Parsed string patient_dob '%s' during update to aware datetime: %s", v, value_to_set)
564
+ else:
565
+ logger.warning("Could not parse patient_dob string '%s' during update, skipping", v)
566
+ continue
567
+ except Exception as e:
568
+ logger.warning("Error parsing patient_dob string '%s' during update: %s, skipping", v, e)
569
+ continue
570
+ elif k == "examination_date" and isinstance(v, str):
571
+ if v == "examination_date" or v in ["patient_first_name", "patient_last_name", "patient_dob"]:
572
+ logger.warning("Skipping invalid examination_date value '%s' during update - appears to be field name", v)
573
+ continue
574
+ else:
575
+ # Try to parse as date string
576
+ try:
577
+ import dateparser
578
+ parsed_date = dateparser.parse(v, languages=['de'], settings={'DATE_ORDER': 'DMY'})
579
+ if parsed_date:
580
+ value_to_set = parsed_date.date()
581
+ logger.debug("Parsed string examination_date '%s' during update to date: %s", v, value_to_set)
582
+ else:
583
+ logger.warning("Could not parse examination_date string '%s' during update, skipping", v)
584
+ continue
585
+ except Exception as e:
586
+ logger.warning("Error parsing examination_date string '%s' during update: %s, skipping", v, e)
587
+ continue
588
+ # --- End Conversion ---
589
+
590
+ # Check if patient name is changing
591
+ if k in ["patient_first_name", "patient_last_name"] and getattr(instance, k) != value_to_set:
592
+ patient_name_changed = True
593
+
594
+ setattr(instance, k, value_to_set) # Use value_to_set
595
+
596
+ except Exception as e:
597
+ logger.error(f"Error setting attribute '{k}' to '{v}': {e}. Skipping this field.")
598
+ continue
599
+
600
+ # Update name DB if patient names changed
601
+ if patient_name_changed:
602
+ try:
603
+ update_name_db(instance.patient_first_name, instance.patient_last_name)
604
+ except Exception as e:
605
+ logger.warning(f"Error updating name database: {e}")
606
+
607
+ # Call save - this will trigger the full save logic including hash recalculation etc.
608
+ try:
609
+ instance.save()
610
+ except Exception as e:
611
+ logger.error(f"Error saving SensitiveMeta instance: {e}")
612
+ raise
613
+
614
+ return instance
615
+
616
+
617
+ def update_or_create_sensitive_meta_from_dict(
618
+ cls: Type["SensitiveMeta"],
619
+ data: Dict[str, Any],
620
+ instance: Optional["SensitiveMeta"] = None
621
+ ) -> "SensitiveMeta":
622
+ """Logic to update or create a SensitiveMeta instance from a dictionary."""
623
+ # Check if the instance already exists based on unique fields
624
+ if instance:
625
+ # Update the existing instance
626
+ return update_sensitive_meta_from_dict(instance, data), False
627
+ else:
628
+ # Create a new instance
629
+ return create_sensitive_meta_from_dict(cls, data), True
630
+
631
+
632
+ def _map_gender_string_to_standard(gender_str: str) -> Optional[str]:
633
+ """Maps various gender string inputs to standard gender names used in the DB."""
634
+ mapping = {
635
+ 'male': ['male', 'm', 'männlich', 'man'],
636
+ 'female': ['female', 'f', 'weiblich', 'woman'],
637
+ 'unknown': ['unknown', 'unbekannt', 'other', 'diverse', '']
638
+ }
639
+ gender_lower = gender_str.strip().lower()
640
+ for standard, variants in mapping.items():
641
+ if gender_lower in variants:
642
+ return standard
643
+ return None