endoreg-db 0.8.9.2__py3-none-any.whl → 0.8.9.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

Files changed (450) hide show
  1. endoreg_db/admin.py +10 -5
  2. endoreg_db/apps.py +4 -7
  3. endoreg_db/authz/auth.py +1 -0
  4. endoreg_db/authz/backends.py +1 -1
  5. endoreg_db/authz/management/commands/list_routes.py +2 -0
  6. endoreg_db/authz/middleware.py +8 -7
  7. endoreg_db/authz/permissions.py +21 -10
  8. endoreg_db/authz/policy.py +14 -19
  9. endoreg_db/authz/views_auth.py +14 -10
  10. endoreg_db/codemods/rename_datetime_fields.py +8 -1
  11. endoreg_db/exceptions.py +5 -2
  12. endoreg_db/forms/__init__.py +0 -1
  13. endoreg_db/forms/examination_form.py +4 -3
  14. endoreg_db/forms/patient_finding_intervention_form.py +30 -8
  15. endoreg_db/forms/patient_form.py +9 -13
  16. endoreg_db/forms/questionnaires/__init__.py +1 -1
  17. endoreg_db/forms/settings/__init__.py +4 -1
  18. endoreg_db/forms/unit.py +2 -1
  19. endoreg_db/helpers/count_db.py +17 -14
  20. endoreg_db/helpers/default_objects.py +2 -1
  21. endoreg_db/helpers/download_segmentation_model.py +4 -3
  22. endoreg_db/helpers/interact.py +0 -5
  23. endoreg_db/helpers/test_video_helper.py +33 -25
  24. endoreg_db/import_files/__init__.py +1 -1
  25. endoreg_db/import_files/context/__init__.py +1 -1
  26. endoreg_db/import_files/context/default_sensitive_meta.py +11 -9
  27. endoreg_db/import_files/context/ensure_center.py +4 -4
  28. endoreg_db/import_files/context/file_lock.py +3 -3
  29. endoreg_db/import_files/context/import_context.py +11 -12
  30. endoreg_db/import_files/context/validate_directories.py +1 -0
  31. endoreg_db/import_files/file_storage/create_report_file.py +57 -34
  32. endoreg_db/import_files/file_storage/create_video_file.py +64 -35
  33. endoreg_db/import_files/file_storage/sensitive_meta_storage.py +5 -2
  34. endoreg_db/import_files/file_storage/state_management.py +89 -122
  35. endoreg_db/import_files/file_storage/storage.py +5 -1
  36. endoreg_db/import_files/processing/report_processing/report_anonymization.py +24 -19
  37. endoreg_db/import_files/processing/sensitive_meta_adapter.py +3 -3
  38. endoreg_db/import_files/processing/video_processing/video_anonymization.py +18 -18
  39. endoreg_db/import_files/pseudonymization/k_anonymity.py +8 -9
  40. endoreg_db/import_files/pseudonymization/k_pseudonymity.py +16 -5
  41. endoreg_db/import_files/report_import_service.py +36 -30
  42. endoreg_db/import_files/video_import_service.py +27 -23
  43. endoreg_db/logger_conf.py +56 -40
  44. endoreg_db/management/__init__.py +1 -1
  45. endoreg_db/management/commands/__init__.py +1 -1
  46. endoreg_db/management/commands/check_auth.py +45 -38
  47. endoreg_db/management/commands/create_model_meta_from_huggingface.py +53 -2
  48. endoreg_db/management/commands/create_multilabel_model_meta.py +54 -19
  49. endoreg_db/management/commands/fix_missing_patient_data.py +105 -71
  50. endoreg_db/management/commands/fix_video_paths.py +75 -54
  51. endoreg_db/management/commands/import_report.py +1 -3
  52. endoreg_db/management/commands/list_routes.py +2 -0
  53. endoreg_db/management/commands/load_ai_model_data.py +8 -2
  54. endoreg_db/management/commands/load_ai_model_label_data.py +0 -1
  55. endoreg_db/management/commands/load_center_data.py +3 -3
  56. endoreg_db/management/commands/load_distribution_data.py +35 -38
  57. endoreg_db/management/commands/load_endoscope_data.py +0 -3
  58. endoreg_db/management/commands/load_examination_data.py +20 -4
  59. endoreg_db/management/commands/load_finding_data.py +18 -3
  60. endoreg_db/management/commands/load_gender_data.py +17 -24
  61. endoreg_db/management/commands/load_green_endoscopy_wuerzburg_data.py +95 -85
  62. endoreg_db/management/commands/load_information_source.py +0 -3
  63. endoreg_db/management/commands/load_lab_value_data.py +14 -3
  64. endoreg_db/management/commands/load_legacy_data.py +303 -0
  65. endoreg_db/management/commands/load_name_data.py +1 -2
  66. endoreg_db/management/commands/load_pdf_type_data.py +4 -8
  67. endoreg_db/management/commands/load_profession_data.py +0 -1
  68. endoreg_db/management/commands/load_report_reader_flag_data.py +0 -4
  69. endoreg_db/management/commands/load_requirement_data.py +6 -2
  70. endoreg_db/management/commands/load_unit_data.py +0 -4
  71. endoreg_db/management/commands/load_user_groups.py +5 -7
  72. endoreg_db/management/commands/model_input.py +169 -0
  73. endoreg_db/management/commands/register_ai_model.py +22 -16
  74. endoreg_db/management/commands/setup_endoreg_db.py +110 -32
  75. endoreg_db/management/commands/storage_management.py +14 -8
  76. endoreg_db/management/commands/summarize_db_content.py +154 -63
  77. endoreg_db/management/commands/train_image_multilabel_model.py +144 -0
  78. endoreg_db/management/commands/validate_video_files.py +82 -50
  79. endoreg_db/management/commands/video_validation.py +4 -6
  80. endoreg_db/migrations/0001_initial.py +112 -63
  81. endoreg_db/models/__init__.py +8 -0
  82. endoreg_db/models/administration/ai/active_model.py +5 -5
  83. endoreg_db/models/administration/ai/ai_model.py +41 -18
  84. endoreg_db/models/administration/ai/model_type.py +1 -0
  85. endoreg_db/models/administration/case/case.py +22 -22
  86. endoreg_db/models/administration/center/__init__.py +5 -5
  87. endoreg_db/models/administration/center/center.py +6 -2
  88. endoreg_db/models/administration/center/center_resource.py +18 -4
  89. endoreg_db/models/administration/center/center_shift.py +3 -1
  90. endoreg_db/models/administration/center/center_waste.py +6 -2
  91. endoreg_db/models/administration/person/__init__.py +1 -1
  92. endoreg_db/models/administration/person/employee/__init__.py +1 -1
  93. endoreg_db/models/administration/person/employee/employee_type.py +3 -1
  94. endoreg_db/models/administration/person/examiner/__init__.py +1 -1
  95. endoreg_db/models/administration/person/examiner/examiner.py +10 -2
  96. endoreg_db/models/administration/person/names/first_name.py +6 -4
  97. endoreg_db/models/administration/person/names/last_name.py +4 -3
  98. endoreg_db/models/administration/person/patient/__init__.py +1 -1
  99. endoreg_db/models/administration/person/patient/patient.py +0 -1
  100. endoreg_db/models/administration/person/patient/patient_external_id.py +0 -1
  101. endoreg_db/models/administration/person/person.py +1 -1
  102. endoreg_db/models/administration/product/__init__.py +7 -6
  103. endoreg_db/models/administration/product/product.py +6 -2
  104. endoreg_db/models/administration/product/product_group.py +9 -7
  105. endoreg_db/models/administration/product/product_material.py +9 -2
  106. endoreg_db/models/administration/product/reference_product.py +64 -15
  107. endoreg_db/models/administration/qualification/qualification.py +3 -1
  108. endoreg_db/models/administration/shift/shift.py +3 -1
  109. endoreg_db/models/administration/shift/shift_type.py +12 -4
  110. endoreg_db/models/aidataset/__init__.py +5 -0
  111. endoreg_db/models/aidataset/aidataset.py +193 -0
  112. endoreg_db/models/label/__init__.py +1 -1
  113. endoreg_db/models/label/label.py +10 -2
  114. endoreg_db/models/label/label_set.py +3 -1
  115. endoreg_db/models/label/label_video_segment/_create_from_video.py +6 -2
  116. endoreg_db/models/label/label_video_segment/label_video_segment.py +148 -44
  117. endoreg_db/models/media/__init__.py +12 -5
  118. endoreg_db/models/media/frame/__init__.py +1 -1
  119. endoreg_db/models/media/frame/frame.py +34 -8
  120. endoreg_db/models/media/pdf/__init__.py +2 -1
  121. endoreg_db/models/media/pdf/raw_pdf.py +11 -4
  122. endoreg_db/models/media/pdf/report_file.py +6 -2
  123. endoreg_db/models/media/pdf/report_reader/__init__.py +3 -3
  124. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +15 -5
  125. endoreg_db/models/media/video/create_from_file.py +20 -41
  126. endoreg_db/models/media/video/pipe_1.py +75 -30
  127. endoreg_db/models/media/video/pipe_2.py +37 -12
  128. endoreg_db/models/media/video/video_file.py +36 -24
  129. endoreg_db/models/media/video/video_file_ai.py +235 -70
  130. endoreg_db/models/media/video/video_file_anonymize.py +240 -65
  131. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -1
  132. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +3 -1
  133. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +30 -9
  134. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +95 -29
  135. endoreg_db/models/media/video/video_file_frames/_get_frame.py +13 -3
  136. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -1
  137. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +15 -3
  138. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +15 -3
  139. endoreg_db/models/media/video/video_file_frames/_get_frames.py +7 -2
  140. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +109 -23
  141. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +111 -27
  142. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +46 -13
  143. endoreg_db/models/media/video/video_file_io.py +85 -33
  144. endoreg_db/models/media/video/video_file_meta/__init__.py +6 -6
  145. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +17 -4
  146. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +28 -7
  147. endoreg_db/models/media/video/video_file_meta/get_fps.py +46 -13
  148. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +81 -20
  149. endoreg_db/models/media/video/video_file_meta/text_meta.py +61 -20
  150. endoreg_db/models/media/video/video_file_meta/video_meta.py +40 -12
  151. endoreg_db/models/media/video/video_file_segments.py +118 -27
  152. endoreg_db/models/media/video/video_metadata.py +25 -6
  153. endoreg_db/models/media/video/video_processing.py +54 -15
  154. endoreg_db/models/medical/__init__.py +3 -13
  155. endoreg_db/models/medical/contraindication/__init__.py +3 -1
  156. endoreg_db/models/medical/disease.py +18 -6
  157. endoreg_db/models/medical/event.py +6 -2
  158. endoreg_db/models/medical/examination/__init__.py +5 -1
  159. endoreg_db/models/medical/examination/examination.py +22 -6
  160. endoreg_db/models/medical/examination/examination_indication.py +23 -7
  161. endoreg_db/models/medical/examination/examination_time.py +6 -2
  162. endoreg_db/models/medical/finding/__init__.py +3 -1
  163. endoreg_db/models/medical/finding/finding.py +37 -12
  164. endoreg_db/models/medical/finding/finding_classification.py +27 -8
  165. endoreg_db/models/medical/finding/finding_intervention.py +19 -6
  166. endoreg_db/models/medical/finding/finding_type.py +3 -1
  167. endoreg_db/models/medical/hardware/__init__.py +1 -1
  168. endoreg_db/models/medical/hardware/endoscope.py +14 -2
  169. endoreg_db/models/medical/laboratory/__init__.py +1 -1
  170. endoreg_db/models/medical/laboratory/lab_value.py +139 -39
  171. endoreg_db/models/medical/medication/__init__.py +7 -3
  172. endoreg_db/models/medical/medication/medication.py +3 -1
  173. endoreg_db/models/medical/medication/medication_indication.py +3 -1
  174. endoreg_db/models/medical/medication/medication_indication_type.py +11 -3
  175. endoreg_db/models/medical/medication/medication_intake_time.py +3 -1
  176. endoreg_db/models/medical/medication/medication_schedule.py +3 -1
  177. endoreg_db/models/medical/patient/__init__.py +2 -10
  178. endoreg_db/models/medical/patient/medication_examples.py +3 -14
  179. endoreg_db/models/medical/patient/patient_disease.py +17 -5
  180. endoreg_db/models/medical/patient/patient_event.py +12 -4
  181. endoreg_db/models/medical/patient/patient_examination.py +52 -15
  182. endoreg_db/models/medical/patient/patient_examination_indication.py +15 -4
  183. endoreg_db/models/medical/patient/patient_finding.py +105 -29
  184. endoreg_db/models/medical/patient/patient_finding_classification.py +41 -12
  185. endoreg_db/models/medical/patient/patient_finding_intervention.py +11 -3
  186. endoreg_db/models/medical/patient/patient_lab_sample.py +6 -2
  187. endoreg_db/models/medical/patient/patient_lab_value.py +42 -10
  188. endoreg_db/models/medical/patient/patient_medication.py +25 -7
  189. endoreg_db/models/medical/patient/patient_medication_schedule.py +34 -10
  190. endoreg_db/models/metadata/model_meta.py +40 -12
  191. endoreg_db/models/metadata/model_meta_logic.py +51 -16
  192. endoreg_db/models/metadata/sensitive_meta.py +65 -28
  193. endoreg_db/models/metadata/sensitive_meta_logic.py +28 -26
  194. endoreg_db/models/metadata/video_meta.py +146 -39
  195. endoreg_db/models/metadata/video_prediction_logic.py +70 -21
  196. endoreg_db/models/metadata/video_prediction_meta.py +80 -27
  197. endoreg_db/models/operation_log.py +63 -0
  198. endoreg_db/models/other/__init__.py +10 -10
  199. endoreg_db/models/other/distribution/__init__.py +9 -7
  200. endoreg_db/models/other/distribution/base_value_distribution.py +3 -1
  201. endoreg_db/models/other/distribution/date_value_distribution.py +19 -5
  202. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +3 -1
  203. endoreg_db/models/other/distribution/numeric_value_distribution.py +34 -9
  204. endoreg_db/models/other/emission/__init__.py +1 -1
  205. endoreg_db/models/other/emission/emission_factor.py +9 -3
  206. endoreg_db/models/other/information_source.py +15 -5
  207. endoreg_db/models/other/material.py +3 -1
  208. endoreg_db/models/other/transport_route.py +3 -1
  209. endoreg_db/models/other/unit.py +6 -2
  210. endoreg_db/models/report/report.py +0 -1
  211. endoreg_db/models/requirement/requirement.py +84 -27
  212. endoreg_db/models/requirement/requirement_error.py +5 -6
  213. endoreg_db/models/requirement/requirement_evaluation/__init__.py +1 -1
  214. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +8 -8
  215. endoreg_db/models/requirement/requirement_evaluation/get_values.py +3 -3
  216. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +24 -8
  217. endoreg_db/models/requirement/requirement_operator.py +28 -8
  218. endoreg_db/models/requirement/requirement_set.py +34 -11
  219. endoreg_db/models/state/__init__.py +1 -0
  220. endoreg_db/models/state/audit_ledger.py +9 -2
  221. endoreg_db/models/{media → state}/processing_history/__init__.py +1 -3
  222. endoreg_db/models/state/processing_history/processing_history.py +136 -0
  223. endoreg_db/models/state/raw_pdf.py +0 -1
  224. endoreg_db/models/state/video.py +2 -4
  225. endoreg_db/models/utils.py +4 -2
  226. endoreg_db/queries/__init__.py +2 -6
  227. endoreg_db/queries/annotations/__init__.py +1 -3
  228. endoreg_db/queries/annotations/legacy.py +37 -26
  229. endoreg_db/root_urls.py +3 -4
  230. endoreg_db/schemas/examination_evaluation.py +3 -0
  231. endoreg_db/serializers/Frames_NICE_and_PARIS_classifications.py +249 -163
  232. endoreg_db/serializers/__init__.py +2 -8
  233. endoreg_db/serializers/administration/__init__.py +1 -2
  234. endoreg_db/serializers/administration/ai/__init__.py +0 -1
  235. endoreg_db/serializers/administration/ai/active_model.py +3 -1
  236. endoreg_db/serializers/administration/ai/ai_model.py +5 -3
  237. endoreg_db/serializers/administration/ai/model_type.py +3 -1
  238. endoreg_db/serializers/administration/center.py +7 -2
  239. endoreg_db/serializers/administration/gender.py +4 -2
  240. endoreg_db/serializers/anonymization.py +13 -13
  241. endoreg_db/serializers/evaluation/examination_evaluation.py +0 -1
  242. endoreg_db/serializers/examination/__init__.py +1 -1
  243. endoreg_db/serializers/examination/base.py +12 -13
  244. endoreg_db/serializers/examination/dropdown.py +6 -7
  245. endoreg_db/serializers/examination_serializer.py +3 -6
  246. endoreg_db/serializers/finding/__init__.py +1 -1
  247. endoreg_db/serializers/finding/finding.py +14 -7
  248. endoreg_db/serializers/finding_classification/__init__.py +3 -3
  249. endoreg_db/serializers/finding_classification/choice.py +3 -3
  250. endoreg_db/serializers/finding_classification/classification.py +2 -4
  251. endoreg_db/serializers/label_video_segment/__init__.py +5 -3
  252. endoreg_db/serializers/{label → label_video_segment}/image_classification_annotation.py +5 -5
  253. endoreg_db/serializers/label_video_segment/label/__init__.py +6 -0
  254. endoreg_db/serializers/{label → label_video_segment/label}/label.py +1 -1
  255. endoreg_db/serializers/label_video_segment/label_video_segment.py +338 -228
  256. endoreg_db/serializers/meta/__init__.py +1 -2
  257. endoreg_db/serializers/meta/sensitive_meta_detail.py +28 -13
  258. endoreg_db/serializers/meta/sensitive_meta_update.py +51 -46
  259. endoreg_db/serializers/meta/sensitive_meta_verification.py +19 -16
  260. endoreg_db/serializers/misc/__init__.py +2 -2
  261. endoreg_db/serializers/misc/file_overview.py +11 -7
  262. endoreg_db/serializers/misc/stats.py +10 -8
  263. endoreg_db/serializers/misc/translatable_field_mix_in.py +6 -6
  264. endoreg_db/serializers/misc/upload_job.py +32 -29
  265. endoreg_db/serializers/patient/__init__.py +2 -1
  266. endoreg_db/serializers/patient/patient.py +32 -15
  267. endoreg_db/serializers/patient/patient_dropdown.py +11 -3
  268. endoreg_db/serializers/patient_examination/__init__.py +1 -1
  269. endoreg_db/serializers/patient_examination/patient_examination.py +67 -40
  270. endoreg_db/serializers/patient_finding/__init__.py +1 -1
  271. endoreg_db/serializers/patient_finding/patient_finding.py +2 -1
  272. endoreg_db/serializers/patient_finding/patient_finding_classification.py +17 -9
  273. endoreg_db/serializers/patient_finding/patient_finding_detail.py +26 -17
  274. endoreg_db/serializers/patient_finding/patient_finding_intervention.py +7 -5
  275. endoreg_db/serializers/patient_finding/patient_finding_list.py +10 -11
  276. endoreg_db/serializers/patient_finding/patient_finding_write.py +36 -27
  277. endoreg_db/serializers/pdf/__init__.py +1 -3
  278. endoreg_db/serializers/requirements/requirement_schema.py +1 -6
  279. endoreg_db/serializers/sensitive_meta_serializer.py +100 -81
  280. endoreg_db/serializers/video/__init__.py +2 -2
  281. endoreg_db/serializers/video/{segmentation.py → video_file.py} +66 -47
  282. endoreg_db/serializers/video/video_file_brief.py +6 -2
  283. endoreg_db/serializers/video/video_file_detail.py +36 -23
  284. endoreg_db/serializers/video/video_file_list.py +4 -2
  285. endoreg_db/serializers/video/video_processing_history.py +54 -50
  286. endoreg_db/services/__init__.py +1 -1
  287. endoreg_db/services/anonymization.py +2 -2
  288. endoreg_db/services/examination_evaluation.py +40 -17
  289. endoreg_db/services/model_meta_from_hf.py +76 -0
  290. endoreg_db/services/polling_coordinator.py +101 -70
  291. endoreg_db/services/pseudonym_service.py +27 -22
  292. endoreg_db/services/report_import.py +6 -3
  293. endoreg_db/services/segment_sync.py +75 -59
  294. endoreg_db/services/video_import.py +6 -7
  295. endoreg_db/urls/__init__.py +2 -2
  296. endoreg_db/urls/ai.py +7 -25
  297. endoreg_db/urls/anonymization.py +61 -15
  298. endoreg_db/urls/auth.py +4 -4
  299. endoreg_db/urls/classification.py +4 -9
  300. endoreg_db/urls/examination.py +27 -18
  301. endoreg_db/urls/media.py +27 -34
  302. endoreg_db/urls/patient.py +11 -7
  303. endoreg_db/urls/requirements.py +3 -1
  304. endoreg_db/urls/root_urls.py +2 -3
  305. endoreg_db/urls/stats.py +24 -16
  306. endoreg_db/urls/upload.py +3 -11
  307. endoreg_db/utils/__init__.py +14 -15
  308. endoreg_db/utils/ai/__init__.py +1 -1
  309. endoreg_db/utils/ai/data_loader_for_model_input.py +262 -0
  310. endoreg_db/utils/ai/data_loader_for_model_training.py +262 -0
  311. endoreg_db/utils/ai/get.py +2 -1
  312. endoreg_db/utils/ai/inference_dataset.py +14 -15
  313. endoreg_db/utils/ai/model_training/config.py +117 -0
  314. endoreg_db/utils/ai/model_training/dataset.py +74 -0
  315. endoreg_db/utils/ai/model_training/losses.py +68 -0
  316. endoreg_db/utils/ai/model_training/metrics.py +78 -0
  317. endoreg_db/utils/ai/model_training/model_backbones.py +155 -0
  318. endoreg_db/utils/ai/model_training/model_gastronet_resnet.py +118 -0
  319. endoreg_db/utils/ai/model_training/trainer_gastronet_multilabel.py +771 -0
  320. endoreg_db/utils/ai/multilabel_classification_net.py +21 -6
  321. endoreg_db/utils/ai/predict.py +4 -4
  322. endoreg_db/utils/ai/preprocess.py +19 -11
  323. endoreg_db/utils/calc_duration_seconds.py +4 -4
  324. endoreg_db/utils/case_generator/lab_sample_factory.py +3 -4
  325. endoreg_db/utils/check_video_files.py +74 -47
  326. endoreg_db/utils/cropping.py +10 -9
  327. endoreg_db/utils/dataloader.py +11 -3
  328. endoreg_db/utils/dates.py +3 -4
  329. endoreg_db/utils/defaults/set_default_center.py +7 -6
  330. endoreg_db/utils/env.py +6 -2
  331. endoreg_db/utils/extract_specific_frames.py +24 -9
  332. endoreg_db/utils/file_operations.py +30 -18
  333. endoreg_db/utils/fix_video_path_direct.py +57 -41
  334. endoreg_db/utils/frame_anonymization_utils.py +157 -157
  335. endoreg_db/utils/hashs.py +3 -18
  336. endoreg_db/utils/links/requirement_link.py +96 -52
  337. endoreg_db/utils/ocr.py +30 -25
  338. endoreg_db/utils/operation_log.py +61 -0
  339. endoreg_db/utils/parse_and_generate_yaml.py +12 -13
  340. endoreg_db/utils/paths.py +6 -6
  341. endoreg_db/utils/permissions.py +40 -24
  342. endoreg_db/utils/pipelines/process_video_dir.py +50 -26
  343. endoreg_db/utils/product/sum_emissions.py +5 -3
  344. endoreg_db/utils/product/sum_weights.py +4 -2
  345. endoreg_db/utils/pydantic_models/__init__.py +3 -4
  346. endoreg_db/utils/requirement_operator_logic/_old/lab_value_operators.py +207 -107
  347. endoreg_db/utils/requirement_operator_logic/_old/model_evaluators.py +252 -65
  348. endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +27 -10
  349. endoreg_db/utils/setup_config.py +21 -5
  350. endoreg_db/utils/storage.py +3 -1
  351. endoreg_db/utils/translation.py +19 -15
  352. endoreg_db/utils/uuid.py +1 -0
  353. endoreg_db/utils/validate_endo_roi.py +12 -4
  354. endoreg_db/utils/validate_subcategory_dict.py +26 -24
  355. endoreg_db/utils/validate_video_detailed.py +207 -149
  356. endoreg_db/utils/video/__init__.py +7 -3
  357. endoreg_db/utils/video/extract_frames.py +30 -18
  358. endoreg_db/utils/video/names.py +11 -6
  359. endoreg_db/utils/video/streaming_processor.py +175 -101
  360. endoreg_db/utils/video/video_splitter.py +30 -19
  361. endoreg_db/views/Frames_NICE_and_PARIS_classifications_views.py +59 -50
  362. endoreg_db/views/__init__.py +0 -20
  363. endoreg_db/views/anonymization/__init__.py +6 -2
  364. endoreg_db/views/anonymization/media_management.py +2 -6
  365. endoreg_db/views/anonymization/overview.py +34 -1
  366. endoreg_db/views/anonymization/validate.py +79 -18
  367. endoreg_db/views/auth/__init__.py +1 -1
  368. endoreg_db/views/auth/keycloak.py +16 -14
  369. endoreg_db/views/examination/__init__.py +12 -15
  370. endoreg_db/views/examination/examination.py +5 -5
  371. endoreg_db/views/examination/examination_manifest_cache.py +5 -5
  372. endoreg_db/views/examination/get_finding_classification_choices.py +8 -5
  373. endoreg_db/views/examination/get_finding_classifications.py +9 -7
  374. endoreg_db/views/examination/get_findings.py +8 -10
  375. endoreg_db/views/examination/get_instruments.py +3 -2
  376. endoreg_db/views/examination/get_interventions.py +1 -1
  377. endoreg_db/views/finding/__init__.py +2 -2
  378. endoreg_db/views/finding/finding.py +58 -54
  379. endoreg_db/views/finding/get_classifications.py +1 -1
  380. endoreg_db/views/finding/get_interventions.py +1 -1
  381. endoreg_db/views/finding_classification/__init__.py +5 -5
  382. endoreg_db/views/finding_classification/finding_classification.py +5 -6
  383. endoreg_db/views/finding_classification/get_classification_choices.py +3 -4
  384. endoreg_db/views/media/__init__.py +13 -13
  385. endoreg_db/views/media/pdf_media.py +9 -9
  386. endoreg_db/views/media/sensitive_metadata.py +10 -7
  387. endoreg_db/views/media/video_media.py +4 -4
  388. endoreg_db/views/meta/__init__.py +1 -1
  389. endoreg_db/views/meta/sensitive_meta_list.py +20 -22
  390. endoreg_db/views/meta/sensitive_meta_verification.py +14 -11
  391. endoreg_db/views/misc/__init__.py +6 -34
  392. endoreg_db/views/misc/center.py +2 -1
  393. endoreg_db/views/misc/csrf.py +2 -1
  394. endoreg_db/views/misc/gender.py +2 -1
  395. endoreg_db/views/misc/stats.py +141 -106
  396. endoreg_db/views/patient/__init__.py +1 -3
  397. endoreg_db/views/patient/patient.py +141 -99
  398. endoreg_db/views/patient_examination/__init__.py +5 -5
  399. endoreg_db/views/patient_examination/patient_examination.py +43 -42
  400. endoreg_db/views/patient_examination/patient_examination_create.py +10 -15
  401. endoreg_db/views/patient_examination/patient_examination_detail.py +12 -15
  402. endoreg_db/views/patient_examination/patient_examination_list.py +21 -17
  403. endoreg_db/views/patient_examination/video.py +114 -80
  404. endoreg_db/views/patient_finding/__init__.py +1 -1
  405. endoreg_db/views/patient_finding/patient_finding.py +17 -10
  406. endoreg_db/views/patient_finding/patient_finding_optimized.py +127 -95
  407. endoreg_db/views/patient_finding_classification/__init__.py +1 -1
  408. endoreg_db/views/patient_finding_classification/pfc_create.py +35 -27
  409. endoreg_db/views/report/reimport.py +1 -1
  410. endoreg_db/views/report/report_stream.py +5 -8
  411. endoreg_db/views/requirement/__init__.py +2 -1
  412. endoreg_db/views/requirement/evaluate.py +7 -9
  413. endoreg_db/views/requirement/lookup.py +2 -3
  414. endoreg_db/views/requirement/lookup_store.py +0 -1
  415. endoreg_db/views/requirement/requirement_utils.py +2 -4
  416. endoreg_db/views/stats/__init__.py +4 -4
  417. endoreg_db/views/stats/stats_views.py +152 -115
  418. endoreg_db/views/video/__init__.py +18 -27
  419. endoreg_db/views/{ai → video/ai}/__init__.py +2 -2
  420. endoreg_db/views/{ai → video/ai}/label.py +20 -16
  421. endoreg_db/views/video/correction.py +5 -6
  422. endoreg_db/views/video/reimport.py +134 -99
  423. endoreg_db/views/video/segments_crud.py +134 -44
  424. endoreg_db/views/video/video_apply_mask.py +13 -12
  425. endoreg_db/views/video/video_correction.py +2 -1
  426. endoreg_db/views/video/video_download_processed.py +15 -15
  427. endoreg_db/views/video/video_meta_stats.py +7 -6
  428. endoreg_db/views/video/video_processing_history.py +3 -2
  429. endoreg_db/views/video/video_remove_frames.py +13 -12
  430. endoreg_db/views/video/video_stream.py +110 -82
  431. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/METADATA +9 -3
  432. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/RECORD +434 -431
  433. endoreg_db/management/commands/import_fallback_video.py +0 -203
  434. endoreg_db/management/commands/import_video.py +0 -422
  435. endoreg_db/management/commands/import_video_with_classification.py +0 -367
  436. endoreg_db/models/media/processing_history/processing_history.py +0 -96
  437. endoreg_db/serializers/label/__init__.py +0 -7
  438. endoreg_db/serializers/label_video_segment/_lvs_create.py +0 -149
  439. endoreg_db/serializers/label_video_segment/_lvs_update.py +0 -138
  440. endoreg_db/serializers/label_video_segment/_lvs_validate.py +0 -149
  441. endoreg_db/serializers/label_video_segment/label_video_segment_annotation.py +0 -99
  442. endoreg_db/serializers/label_video_segment/label_video_segment_update.py +0 -163
  443. endoreg_db/services/__old/pdf_import.py +0 -1487
  444. endoreg_db/services/__old/video_import.py +0 -1306
  445. endoreg_db/tasks/upload_tasks.py +0 -216
  446. endoreg_db/tasks/video_ingest.py +0 -161
  447. endoreg_db/tasks/video_processing_tasks.py +0 -327
  448. endoreg_db/views/misc/translation.py +0 -182
  449. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/WHEEL +0 -0
  450. {endoreg_db-0.8.9.2.dist-info → endoreg_db-0.8.9.10.dist-info}/licenses/LICENSE +0 -0
@@ -31,7 +31,9 @@ class ModelMetaManager(models.Manager):
31
31
  Provides methods for retrieving ModelMeta instances using natural keys.
32
32
  """
33
33
 
34
- def get_by_natural_key(self, name: str, version: str, model_name: str) -> "ModelMeta":
34
+ def get_by_natural_key(
35
+ self, name: str, version: str, model_name: str
36
+ ) -> "ModelMeta":
35
37
  """
36
38
  Retrieves a ModelMeta instance using its natural key.
37
39
 
@@ -80,10 +82,16 @@ class ModelMeta(models.Model):
80
82
  related_name="model_metadata",
81
83
  help_text="The set of labels this model version predicts.",
82
84
  )
83
- activation = models.CharField(max_length=50, default="sigmoid", help_text="Output activation function (e.g., 'sigmoid', 'softmax', 'none').")
85
+ activation = models.CharField(
86
+ max_length=50,
87
+ default="sigmoid",
88
+ help_text="Output activation function (e.g., 'sigmoid', 'softmax', 'none').",
89
+ )
84
90
  weights = models.FileField(
85
91
  upload_to=WEIGHTS_DIR.name, # Use .name for relative path
86
- validators=[FileExtensionValidator(allowed_extensions=["safetensors", "pth", "pt"])],
92
+ validators=[
93
+ FileExtensionValidator(allowed_extensions=["safetensors", "pth", "pt"])
94
+ ],
87
95
  null=True,
88
96
  blank=True,
89
97
  help_text="Path to the model weights file (.safetensors), relative to MEDIA_ROOT.",
@@ -102,14 +110,24 @@ class ModelMeta(models.Model):
102
110
  )
103
111
  size_x = models.IntegerField(default=716, help_text="Expected input image width.")
104
112
  size_y = models.IntegerField(default=716, help_text="Expected input image height.")
105
- axes = models.CharField(max_length=10, default="2,0,1", help_text="Comma-separated target axis order (e.g., '2,0,1' for CHW).")
113
+ axes = models.CharField(
114
+ max_length=10,
115
+ default="2,0,1",
116
+ help_text="Comma-separated target axis order (e.g., '2,0,1' for CHW).",
117
+ )
106
118
 
107
119
  # --- Inference Parameters ---
108
- batchsize = models.IntegerField(default=16, help_text="Default batch size for inference.")
109
- num_workers = models.IntegerField(default=0, help_text="Default number of workers for data loading.")
120
+ batchsize = models.IntegerField(
121
+ default=16, help_text="Default batch size for inference."
122
+ )
123
+ num_workers = models.IntegerField(
124
+ default=0, help_text="Default number of workers for data loading."
125
+ )
110
126
 
111
127
  # --- Metadata ---
112
- description = models.TextField(blank=True, null=True, help_text="Optional description.")
128
+ description = models.TextField(
129
+ blank=True, null=True, help_text="Optional description."
130
+ )
113
131
  date_created = models.DateTimeField(auto_now_add=True)
114
132
 
115
133
  objects = ModelMetaManager()
@@ -117,7 +135,9 @@ class ModelMeta(models.Model):
117
135
  # --- Type Hinting for Related Fields ---
118
136
  if TYPE_CHECKING:
119
137
  labelset: models.ForeignKey["LabelSet"]
120
- model: models.ForeignKey["AiModel"] # Corrected from ai_model to match field name
138
+ model: models.ForeignKey[
139
+ "AiModel"
140
+ ] # Corrected from ai_model to match field name
121
141
  weights = cast(FieldFile, weights)
122
142
 
123
143
  class Meta:
@@ -175,7 +195,9 @@ class ModelMeta(models.Model):
175
195
  )
176
196
 
177
197
  @classmethod
178
- def get_latest_version_number(cls: Type["ModelMeta"], meta_name: str, model_name: str) -> int:
198
+ def get_latest_version_number(
199
+ cls: Type["ModelMeta"], meta_name: str, model_name: str
200
+ ) -> int:
179
201
  """
180
202
  Gets the latest version *number* using external logic.
181
203
  """
@@ -228,10 +250,16 @@ class ModelMeta(models.Model):
228
250
  Retrieves a ModelMeta instance by name, model name, and optionally version using external logic.
229
251
  """
230
252
  # Delegate to logic function
231
- return logic.get_model_meta_by_name_version_logic(cls, meta_name, model_name, version)
253
+ return logic.get_model_meta_by_name_version_logic(
254
+ cls, meta_name, model_name, version
255
+ )
232
256
 
233
257
  @classmethod
234
- def get_latest(cls: Type["ModelMeta"], meta_name: str, model_name: str) -> "ModelMeta":
258
+ def get_latest(
259
+ cls: Type["ModelMeta"], meta_name: str, model_name: str
260
+ ) -> "ModelMeta":
235
261
  """Alias for get_by_name_version(meta_name, model_name, version=None) using external logic."""
236
262
  # Delegate directly to the specific logic function
237
- return logic.get_model_meta_by_name_version_logic(cls, meta_name, model_name, version=None)
263
+ return logic.get_model_meta_by_name_version_logic(
264
+ cls, meta_name, model_name, version=None
265
+ )
@@ -25,14 +25,18 @@ def _get_model_meta_class():
25
25
  return ModelMeta
26
26
 
27
27
 
28
- def get_latest_version_number_logic(cls: Type["ModelMeta"], meta_name: str, model_name: str) -> int:
28
+ def get_latest_version_number_logic(
29
+ cls: Type["ModelMeta"], meta_name: str, model_name: str
30
+ ) -> int:
29
31
  """
30
32
  Finds the highest numerical version for a given meta_name and model_name.
31
33
  Iterates through all versions, attempts to parse them as integers,
32
34
  and returns the maximum integer found. If no numeric versions are found,
33
35
  returns 0.
34
36
  """
35
- versions_qs = cls.objects.filter(name=meta_name, model__name=model_name).values_list("version", flat=True)
37
+ versions_qs = cls.objects.filter(
38
+ name=meta_name, model__name=model_name
39
+ ).values_list("version", flat=True)
36
40
 
37
41
  max_v = 0
38
42
  found_numeric_version = False
@@ -88,7 +92,9 @@ def create_from_file_logic(
88
92
  try:
89
93
  label_set = labelset_qs.get()
90
94
  except LabelSet.DoesNotExist as exc:
91
- raise ValueError(f"LabelSet '{labelset_name}' with version '{labelset_version}' not found.") from exc
95
+ raise ValueError(
96
+ f"LabelSet '{labelset_name}' with version '{labelset_version}' not found."
97
+ ) from exc
92
98
  except LabelSet.MultipleObjectsReturned:
93
99
  # Prefer the highest version when duplicates remain and no explicit version requested
94
100
  label_set = labelset_qs.order_by("-version").first()
@@ -101,17 +107,23 @@ def create_from_file_logic(
101
107
 
102
108
  if requested_version:
103
109
  target_version = str(requested_version)
104
- existing = cls.objects.filter(name=meta_name, model=ai_model, version=target_version).first()
110
+ existing = cls.objects.filter(
111
+ name=meta_name, model=ai_model, version=target_version
112
+ ).first()
105
113
  if existing and not bump_if_exists:
106
114
  raise ValueError(
107
115
  f"ModelMeta '{meta_name}' version '{target_version}' for model '{model_name}' already exists. Use bump_if_exists=True to increment."
108
116
  )
109
117
  elif existing and bump_if_exists:
110
118
  target_version = str(latest_version_num + 1)
111
- logger.info(f"Bumping version for {meta_name}/{model_name} to {target_version}")
119
+ logger.info(
120
+ f"Bumping version for {meta_name}/{model_name} to {target_version}"
121
+ )
112
122
  else:
113
123
  target_version = str(latest_version_num + 1)
114
- logger.info(f"Setting next version for {meta_name}/{model_name} to {target_version}")
124
+ logger.info(
125
+ f"Setting next version for {meta_name}/{model_name} to {target_version}"
126
+ )
115
127
 
116
128
  # --- Prepare Weights File ---
117
129
  source_weights_path = Path(weights_file).resolve()
@@ -121,7 +133,10 @@ def create_from_file_logic(
121
133
  # Construct destination path within MEDIA_ROOT/WEIGHTS_DIR
122
134
  weights_filename = source_weights_path.name
123
135
  # Relative path for the FileField upload_to
124
- relative_dest_path = Path(WEIGHTS_DIR.relative_to(STORAGE_DIR)) / f"{meta_name}_v{target_version}_{weights_filename}"
136
+ relative_dest_path = (
137
+ Path(WEIGHTS_DIR.relative_to(STORAGE_DIR))
138
+ / f"{meta_name}_v{target_version}_{weights_filename}"
139
+ )
125
140
  # Full path for shutil.copy
126
141
  full_dest_path = STORAGE_DIR / relative_dest_path
127
142
 
@@ -216,7 +231,9 @@ def _parse_float_sequence(value: Any, *, fallback: Iterable[float]) -> list[floa
216
231
  try:
217
232
  parsed.append(float(token))
218
233
  except (TypeError, ValueError):
219
- logger.warning("Failed to parse normalisation value %r; using fallback", token)
234
+ logger.warning(
235
+ "Failed to parse normalisation value %r; using fallback", token
236
+ )
220
237
  return list(fallback)
221
238
 
222
239
  return parsed or list(fallback)
@@ -237,7 +254,9 @@ def _parse_axes(axes_value: str | Iterable[Any]) -> list[int]:
237
254
  if isinstance(axes_value, str):
238
255
  token_source = axes_value.strip()
239
256
  if "," in token_source:
240
- tokens = [token.strip() for token in token_source.split(",") if token.strip()]
257
+ tokens = [
258
+ token.strip() for token in token_source.split(",") if token.strip()
259
+ ]
241
260
  else:
242
261
  tokens = [char for char in token_source if char.strip()]
243
262
  else:
@@ -316,14 +335,22 @@ def get_model_meta_by_name_version_logic(
316
335
  try:
317
336
  return cls.objects.get(name=meta_name, model=ai_model, version=version)
318
337
  except Exception as exc:
319
- raise cls.DoesNotExist(f"ModelMeta '{meta_name}' version '{version}' for model '{model_name}' not found.") from exc
338
+ raise cls.DoesNotExist(
339
+ f"ModelMeta '{meta_name}' version '{version}' for model '{model_name}' not found."
340
+ ) from exc
320
341
  else:
321
342
  # Get latest version
322
- latest = cls.objects.filter(name=meta_name, model=ai_model).order_by("-date_created").first()
343
+ latest = (
344
+ cls.objects.filter(name=meta_name, model=ai_model)
345
+ .order_by("-date_created")
346
+ .first()
347
+ )
323
348
  if latest:
324
349
  return latest
325
350
  else:
326
- raise cls.DoesNotExist(f"No ModelMeta found for '{meta_name}' and model '{model_name}'.")
351
+ raise cls.DoesNotExist(
352
+ f"No ModelMeta found for '{meta_name}' and model '{model_name}'."
353
+ )
327
354
 
328
355
 
329
356
  import re
@@ -341,7 +368,9 @@ def infer_default_model_meta_from_hf(model_id: str) -> dict[str, Any]:
341
368
  """
342
369
 
343
370
  if not (info := model_info(model_id)):
344
- logger.info(f"Could not retrieve model info for {model_id}, using ColoReg segmentation defaults.")
371
+ logger.info(
372
+ f"Could not retrieve model info for {model_id}, using ColoReg segmentation defaults."
373
+ )
345
374
  return {
346
375
  "name": "wg-lux/colo_segmentation_RegNetX800MF_base",
347
376
  "activation": "sigmoid",
@@ -409,7 +438,9 @@ def setup_default_from_huggingface_logic(
409
438
  local_dir=WEIGHTS_DIR,
410
439
  )
411
440
  except Exception as exc: # pragma: no cover - network errors
412
- raise RuntimeError("Failed to download safetensor weights from Hugging Face; ensure the repository provides a .safetensors artifact.") from exc
441
+ raise RuntimeError(
442
+ "Failed to download safetensor weights from Hugging Face; ensure the repository provides a .safetensors artifact."
443
+ ) from exc
413
444
 
414
445
  ai_model, _ = AiModel.objects.get_or_create(name=meta["name"])
415
446
  if not labelset_name:
@@ -426,12 +457,16 @@ def setup_default_from_huggingface_logic(
426
457
  labelset_qs = labelset_qs.filter(version=version_value)
427
458
  labelset = labelset_qs.order_by("-version").first()
428
459
  if not labelset:
429
- raise ValueError(f"LabelSet '{labelset_name}' with version '{labelset_version}' not found.")
460
+ raise ValueError(
461
+ f"LabelSet '{labelset_name}' with version '{labelset_version}' not found."
462
+ )
430
463
 
431
464
  ModelMeta = _get_model_meta_class()
432
465
  model_meta = ModelMeta.objects.filter(name=meta["name"], model=ai_model).first()
433
466
  if model_meta:
434
- logger.info(f"ModelMeta {meta['name']} for model {ai_model.name} already exists. Skipping creation.")
467
+ logger.info(
468
+ f"ModelMeta {meta['name']} for model {ai_model.name} already exists. Skipping creation."
469
+ )
435
470
  return model_meta
436
471
 
437
472
  return create_from_file_logic(
@@ -2,11 +2,10 @@ import logging
2
2
 
3
3
  # Removed hash utils, datetime, random, os, timezone, sha256 imports
4
4
  # Removed icecream import (was used in old save logic)
5
- from typing import TYPE_CHECKING, Any, Dict, Self, Type, cast
5
+ from typing import TYPE_CHECKING, Any, Dict, Type, cast
6
6
 
7
7
  from django.db import models
8
8
 
9
- from ..administration.person.patient import PatientExternalID
10
9
 
11
10
  # Import models needed for type hints and FKs
12
11
  from ..state import SensitiveMetaState # Needed for post-save state check
@@ -34,8 +33,6 @@ class SensitiveMeta(models.Model):
34
33
  Stores potentially sensitive information extracted from media.
35
34
  Logic for creation, hashing, pseudo-anonymization, and saving is in sensitive_meta_logic.py.
36
35
  """
37
-
38
-
39
36
 
40
37
  # --- Examination and Patient Info ---
41
38
  examination_date = models.DateField(blank=True, null=True)
@@ -43,44 +40,72 @@ class SensitiveMeta(models.Model):
43
40
  casenumber = models.CharField(max_length=255, blank=True, null=True)
44
41
  file_path = models.CharField(max_length=1024, blank=True, null=True)
45
42
 
46
-
47
43
  # --- Core FKs ---
48
- pseudo_patient = models.ForeignKey("Patient", on_delete=models.CASCADE, blank=True, null=True, help_text="FK to the pseudo-anonymized Patient record.")
44
+ pseudo_patient = models.ForeignKey(
45
+ "Patient",
46
+ on_delete=models.CASCADE,
47
+ blank=True,
48
+ null=True,
49
+ help_text="FK to the pseudo-anonymized Patient record.",
50
+ )
49
51
  pseudo_examination = models.ForeignKey(
50
- "PatientExamination", on_delete=models.CASCADE, blank=True, null=True, help_text="FK to the pseudo-anonymized PatientExamination record."
52
+ "PatientExamination",
53
+ on_delete=models.CASCADE,
54
+ blank=True,
55
+ null=True,
56
+ help_text="FK to the pseudo-anonymized PatientExamination record.",
57
+ )
58
+ patient_gender = models.ForeignKey(
59
+ "Gender", on_delete=models.CASCADE, blank=True, null=True
60
+ )
61
+ examiners = models.ManyToManyField(
62
+ "Examiner", blank=True, help_text="Pseudo-anonymized examiner(s)"
63
+ )
64
+ center = models.ForeignKey(
65
+ "Center", on_delete=models.CASCADE, blank=True, null=True
51
66
  )
52
- patient_gender = models.ForeignKey("Gender", on_delete=models.CASCADE, blank=True, null=True)
53
- examiners = models.ManyToManyField("Examiner", blank=True, help_text="Pseudo-anonymized examiner(s)")
54
- center = models.ForeignKey("Center", on_delete=models.CASCADE, blank=True, null=True)
55
67
 
56
68
  # --- Names and DOB ---
57
69
  patient_first_name = models.CharField(max_length=255, blank=True, null=True)
58
70
  patient_last_name = models.CharField(max_length=255, blank=True, null=True)
59
- patient_dob = models.DateTimeField(blank=True, null=True, help_text="Date of birth (can be auto-generated).")
71
+ patient_dob = models.DateTimeField(
72
+ blank=True, null=True, help_text="Date of birth (can be auto-generated)."
73
+ )
60
74
 
61
- examiner_first_name = models.CharField(max_length=255, blank=True, null=True, editable=False)
62
- examiner_last_name = models.CharField(max_length=255, blank=True, null=True, editable=False)
75
+ examiner_first_name = models.CharField(
76
+ max_length=255, blank=True, null=True, editable=False
77
+ )
78
+ examiner_last_name = models.CharField(
79
+ max_length=255, blank=True, null=True, editable=False
80
+ )
63
81
 
64
82
  # --- Hashes ---
65
- patient_hash = models.CharField(max_length=64, blank=True, null=True, editable=False, db_index=True)
66
- examination_hash = models.CharField(max_length=64, blank=True, null=True, editable=False, db_index=True)
83
+ patient_hash = models.CharField(
84
+ max_length=64, blank=True, null=True, editable=False, db_index=True
85
+ )
86
+ examination_hash = models.CharField(
87
+ max_length=64, blank=True, null=True, editable=False, db_index=True
88
+ )
67
89
 
68
90
  # --- Endoscope Info ---
69
91
  endoscope_type = models.CharField(max_length=255, blank=True, null=True)
70
92
  endoscope_sn = models.CharField(max_length=255, blank=True, null=True)
71
93
 
72
94
  # --- External patient ID ---
73
- external_id = models.ForeignKey("PatientExternalID", on_delete=models.CASCADE, blank=True, null=True)
95
+ external_id = models.ForeignKey(
96
+ "PatientExternalID", on_delete=models.CASCADE, blank=True, null=True
97
+ )
74
98
 
75
99
  if TYPE_CHECKING:
76
100
  pseudo_patient: models.ForeignKey["Patient|None"]
77
101
 
78
102
  patient_gender: models.ForeignKey["Gender|None"]
79
103
  pseudo_examination: models.ForeignKey["PatientExamination|None"]
80
- state: models.ForeignKey["SensitiveMetaState|None"] # Assuming related_name='state' is defined on SensitiveMetaState.origin
104
+ state: models.ForeignKey[
105
+ "SensitiveMetaState|None"
106
+ ] # Assuming related_name='state' is defined on SensitiveMetaState.origin
81
107
  center: models.ForeignKey["Center|None"]
82
108
 
83
- examiners = cast(models.manager.RelatedManager["Examiner"], examiners)
84
109
 
85
110
  @property
86
111
  def external_id_origin(self) -> str | None:
@@ -92,7 +117,7 @@ class SensitiveMeta(models.Model):
92
117
  # --- Text Fields ---
93
118
  text = models.TextField(blank=True, null=True)
94
119
  anonymized_text = models.TextField(blank=True, null=True)
95
-
120
+
96
121
  # --- Anonymization helper method ---
97
122
  create_anonymized_record = logic._create_anonymized_record
98
123
 
@@ -142,14 +167,18 @@ class SensitiveMeta(models.Model):
142
167
  # Keep this method for basic representation, ensure fields are accessed safely
143
168
  center_name = self.center.name if self.center else "None"
144
169
  gender_str = str(self.patient_gender) if self.patient_gender else "None"
145
- dob_str = str(self.patient_dob.date()) if self.patient_dob else "None" # Show only date part
170
+ dob_str = (
171
+ str(self.patient_dob.date()) if self.patient_dob else "None"
172
+ ) # Show only date part
146
173
  exam_date_str = str(self.examination_date) if self.examination_date else "None"
147
174
 
148
175
  examiners_str = "[Not saved yet]"
149
176
  if self.pk:
150
177
  try:
151
178
  # Use prefetch_related in queries accessing this for efficiency
152
- examiners_str = ", ".join([str(e) for e in self.examiners.all()]) or "[None]"
179
+ examiners_str = (
180
+ ", ".join([str(e) for e in self.examiners.all()]) or "[None]"
181
+ )
153
182
  except Exception as e:
154
183
  examiners_str = f"[Error: {e}]"
155
184
 
@@ -173,7 +202,9 @@ class SensitiveMeta(models.Model):
173
202
  def state_safe(self) -> "SensitiveMetaState":
174
203
  state = self.state
175
204
  if not state:
176
- raise SensitiveMetaState.DoesNotExist("SensitiveMetaState does not exist for this SensitiveMeta instance.")
205
+ raise SensitiveMetaState.DoesNotExist(
206
+ "SensitiveMetaState does not exist for this SensitiveMeta instance."
207
+ )
177
208
  return state
178
209
 
179
210
  @property
@@ -217,13 +248,18 @@ class SensitiveMeta(models.Model):
217
248
  if self.pk:
218
249
  state, created = SensitiveMetaState.objects.get_or_create(origin=self)
219
250
  if created:
220
- logger.info("Created new SensitiveMetaState for SensitiveMeta %s (via get_or_create)", self.pk)
251
+ logger.info(
252
+ "Created new SensitiveMetaState for SensitiveMeta %s (via get_or_create)",
253
+ self.pk,
254
+ )
221
255
  # Link the state back to the instance in memory
222
256
  self.state = state
223
257
  return state
224
258
  else:
225
259
  # Cannot create state if the main instance has no PK
226
- raise ValueError("Cannot get or create state for an unsaved SensitiveMeta instance.")
260
+ raise ValueError(
261
+ "Cannot get or create state for an unsaved SensitiveMeta instance."
262
+ )
227
263
 
228
264
  def __repr__(self):
229
265
  return self.__str__()
@@ -274,7 +310,11 @@ class SensitiveMeta(models.Model):
274
310
  SensitiveMetaState.objects.create(origin=self)
275
311
 
276
312
  # 4. Handle ManyToMany linking (examiners) *after* the instance has a PK.
277
- if examiner_to_link and self.pk and not self.examiners.filter(pk=examiner_to_link.pk).exists():
313
+ if (
314
+ examiner_to_link
315
+ and self.pk
316
+ and not self.examiners.filter(pk=examiner_to_link.pk).exists()
317
+ ):
278
318
  self.examiners.add(examiner_to_link)
279
319
  # Adding to M2M handles its own DB interaction, no second super().save() needed.
280
320
 
@@ -303,6 +343,3 @@ class SensitiveMeta(models.Model):
303
343
  This method delegates the update operation to the external logic module responsible for managing name data.
304
344
  """
305
345
  logic.update_name_db(first_name, last_name)
306
-
307
-
308
-
@@ -1017,7 +1017,7 @@ def update_sensitive_meta_from_dict(
1017
1017
  logger.debug(
1018
1018
  "Parsed string patient_dob '%s' during update to aware datetime: %s",
1019
1019
  v,
1020
- aware_dob,
1020
+ aware_dob,
1021
1021
  )
1022
1022
  else:
1023
1023
  logger.warning(
@@ -1025,35 +1025,37 @@ def update_sensitive_meta_from_dict(
1025
1025
  v,
1026
1026
  )
1027
1027
  continue
1028
- elif k == "examination_date":
1029
- if isinstance(v, str):
1030
- parsed = parse_any_date(v)
1031
- if parsed:
1032
- value_to_set = parsed # field is DateField, so keep it as date
1033
- logger.debug(
1034
- "Parsed string examination_date '%s' during update to date: %s",
1035
- v,
1036
- value_to_set,
1037
- )
1038
- else:
1039
- logger.warning(
1040
- "Could not parse examination_date string '%s' during update, skipping",
1041
- v,
1042
- )
1043
- continue
1044
- elif isinstance(v, date):
1045
- value_to_set = v
1028
+ elif k == "examination_date":
1029
+ if isinstance(v, str):
1030
+ parsed = parse_any_date(v)
1031
+ if parsed:
1032
+ value_to_set = (
1033
+ parsed # field is DateField, so keep it as date
1034
+ )
1035
+ logger.debug(
1036
+ "Parsed string examination_date '%s' during update to date: %s",
1037
+ v,
1038
+ value_to_set,
1039
+ )
1040
+ else:
1041
+ logger.warning(
1042
+ "Could not parse examination_date string '%s' during update, skipping",
1043
+ v,
1044
+ )
1045
+ continue
1046
+ elif isinstance(v, date):
1047
+ value_to_set = v
1046
1048
 
1047
1049
  # --- End Conversion ---
1048
1050
 
1049
- # Check if patient name is changing
1050
- if (
1051
- k in ["patient_first_name", "patient_last_name"]
1052
- and getattr(instance, k) != value_to_set
1053
- ):
1054
- patient_name_changed = True
1051
+ # Check if patient name is changing
1052
+ if (
1053
+ k in ["patient_first_name", "patient_last_name"]
1054
+ and getattr(instance, k) != value_to_set
1055
+ ):
1056
+ patient_name_changed = True
1055
1057
 
1056
- setattr(instance, k, value_to_set) # Use value_to_set
1058
+ setattr(instance, k, value_to_set) # Use value_to_set
1057
1059
 
1058
1060
  except Exception as e:
1059
1061
  logger.error(