endoreg-db 0.8.4.4__py3-none-any.whl → 0.8.8.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (372) hide show
  1. endoreg_db/authz/auth.py +74 -0
  2. endoreg_db/authz/backends.py +168 -0
  3. endoreg_db/authz/management/commands/list_routes.py +18 -0
  4. endoreg_db/authz/middleware.py +83 -0
  5. endoreg_db/authz/permissions.py +127 -0
  6. endoreg_db/authz/policy.py +218 -0
  7. endoreg_db/authz/views_auth.py +66 -0
  8. endoreg_db/config/env.py +13 -8
  9. endoreg_db/data/__init__.py +8 -31
  10. endoreg_db/data/_examples/disease.yaml +55 -0
  11. endoreg_db/data/_examples/disease_classification.yaml +13 -0
  12. endoreg_db/data/_examples/disease_classification_choice.yaml +62 -0
  13. endoreg_db/data/_examples/event.yaml +64 -0
  14. endoreg_db/data/_examples/examination.yaml +72 -0
  15. endoreg_db/data/_examples/finding/anatomy_colon.yaml +128 -0
  16. endoreg_db/data/_examples/finding/colonoscopy.yaml +40 -0
  17. endoreg_db/data/_examples/finding/colonoscopy_bowel_prep.yaml +56 -0
  18. endoreg_db/data/_examples/finding/complication.yaml +16 -0
  19. endoreg_db/data/_examples/finding/data.yaml +105 -0
  20. endoreg_db/data/_examples/finding/examination_setting.yaml +16 -0
  21. endoreg_db/data/_examples/finding/medication_related.yaml +18 -0
  22. endoreg_db/data/_examples/finding/outcome.yaml +12 -0
  23. endoreg_db/data/_examples/finding_classification/colonoscopy_bowel_preparation.yaml +68 -0
  24. endoreg_db/data/_examples/finding_classification/colonoscopy_jnet.yaml +22 -0
  25. endoreg_db/data/_examples/finding_classification/colonoscopy_kudo.yaml +25 -0
  26. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_circularity.yaml +20 -0
  27. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_planarity.yaml +24 -0
  28. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_size.yaml +68 -0
  29. endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_surface.yaml +20 -0
  30. endoreg_db/data/_examples/finding_classification/colonoscopy_location.yaml +80 -0
  31. endoreg_db/data/_examples/finding_classification/colonoscopy_lst.yaml +21 -0
  32. endoreg_db/data/_examples/finding_classification/colonoscopy_nice.yaml +20 -0
  33. endoreg_db/data/_examples/finding_classification/colonoscopy_paris.yaml +26 -0
  34. endoreg_db/data/_examples/finding_classification/colonoscopy_sano.yaml +22 -0
  35. endoreg_db/data/_examples/finding_classification/colonoscopy_summary.yaml +53 -0
  36. endoreg_db/data/_examples/finding_classification/complication_generic.yaml +25 -0
  37. endoreg_db/data/_examples/finding_classification/examination_setting_generic.yaml +40 -0
  38. endoreg_db/data/_examples/finding_classification/histology_colo.yaml +51 -0
  39. endoreg_db/data/_examples/finding_classification/intervention_required.yaml +26 -0
  40. endoreg_db/data/_examples/finding_classification/medication_related.yaml +23 -0
  41. endoreg_db/data/_examples/finding_classification/visualized.yaml +33 -0
  42. endoreg_db/data/_examples/finding_classification_choice/bowel_preparation.yaml +78 -0
  43. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_circularity_default.yaml +32 -0
  44. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_jnet.yaml +15 -0
  45. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_kudo.yaml +23 -0
  46. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_lst.yaml +15 -0
  47. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_nice.yaml +17 -0
  48. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_paris.yaml +57 -0
  49. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_planarity_default.yaml +49 -0
  50. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_sano.yaml +14 -0
  51. endoreg_db/data/_examples/finding_classification_choice/colon_lesion_surface_intact_default.yaml +36 -0
  52. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_location.yaml +229 -0
  53. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_not_complete_reason.yaml +19 -0
  54. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_size.yaml +82 -0
  55. endoreg_db/data/_examples/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +15 -0
  56. endoreg_db/data/_examples/finding_classification_choice/complication_generic_types.yaml +15 -0
  57. endoreg_db/data/_examples/finding_classification_choice/examination_setting_generic_types.yaml +15 -0
  58. endoreg_db/data/_examples/finding_classification_choice/histology.yaml +24 -0
  59. endoreg_db/data/_examples/finding_classification_choice/histology_polyp.yaml +20 -0
  60. endoreg_db/data/_examples/finding_classification_choice/outcome.yaml +19 -0
  61. endoreg_db/data/_examples/finding_classification_choice/yes_no_na.yaml +11 -0
  62. endoreg_db/data/_examples/finding_classification_type/colonoscopy_basic.yaml +48 -0
  63. endoreg_db/data/_examples/finding_intervention/endoscopy.yaml +43 -0
  64. endoreg_db/data/_examples/finding_intervention/endoscopy_colonoscopy.yaml +168 -0
  65. endoreg_db/data/_examples/finding_intervention/endoscopy_egd.yaml +128 -0
  66. endoreg_db/data/_examples/finding_intervention/endoscopy_ercp.yaml +32 -0
  67. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_lower.yaml +9 -0
  68. endoreg_db/data/_examples/finding_intervention/endoscopy_eus_upper.yaml +36 -0
  69. endoreg_db/data/_examples/finding_intervention_type/endoscopy.yaml +15 -0
  70. endoreg_db/data/_examples/finding_type/data.yaml +43 -0
  71. endoreg_db/data/_examples/requirement/age.yaml +26 -0
  72. endoreg_db/data/_examples/requirement/colonoscopy_baseline_austria.yaml +45 -0
  73. endoreg_db/data/_examples/requirement/disease_cardiovascular.yaml +79 -0
  74. endoreg_db/data/_examples/requirement/disease_classification_choice_cardiovascular.yaml +41 -0
  75. endoreg_db/data/_examples/requirement/disease_hepatology.yaml +12 -0
  76. endoreg_db/data/_examples/requirement/disease_misc.yaml +12 -0
  77. endoreg_db/data/_examples/requirement/disease_renal.yaml +96 -0
  78. endoreg_db/data/_examples/requirement/endoscopy_bleeding_risk.yaml +59 -0
  79. endoreg_db/data/_examples/requirement/event_cardiology.yaml +251 -0
  80. endoreg_db/data/_examples/requirement/event_requirements.yaml +145 -0
  81. endoreg_db/data/_examples/requirement/finding_colon_polyp.yaml +50 -0
  82. endoreg_db/data/_examples/requirement/gender.yaml +25 -0
  83. endoreg_db/data/_examples/requirement/lab_value.yaml +441 -0
  84. endoreg_db/data/_examples/requirement/medication.yaml +93 -0
  85. endoreg_db/data/_examples/requirement_operator/age.yaml +13 -0
  86. endoreg_db/data/_examples/requirement_operator/lab_operators.yaml +129 -0
  87. endoreg_db/data/_examples/requirement_operator/model_operators.yaml +96 -0
  88. endoreg_db/data/_examples/requirement_set/01_endoscopy_generic.yaml +48 -0
  89. endoreg_db/data/_examples/requirement_set/colonoscopy_austria_screening.yaml +57 -0
  90. endoreg_db/data/_examples/yaml_examples.xlsx +0 -0
  91. endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +4 -3
  92. endoreg_db/data/event_classification/data.yaml +4 -0
  93. endoreg_db/data/event_classification_choice/data.yaml +9 -0
  94. endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +43 -70
  95. endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +22 -52
  96. endoreg_db/data/finding_classification/colonoscopy_location.yaml +31 -62
  97. endoreg_db/data/finding_classification/histology_colo.yaml +28 -36
  98. endoreg_db/data/requirement/colon_polyp_intervention.yaml +49 -0
  99. endoreg_db/data/requirement/coloreg_colon_polyp.yaml +49 -0
  100. endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +31 -12
  101. endoreg_db/data/requirement_set/01_laboratory.yaml +13 -0
  102. endoreg_db/data/requirement_set/02_endoscopy_bleeding_risk.yaml +46 -0
  103. endoreg_db/data/requirement_set/90_coloreg.yaml +178 -0
  104. endoreg_db/data/requirement_set/_old_ +109 -0
  105. endoreg_db/data/requirement_set_type/data.yaml +21 -0
  106. endoreg_db/data/setup_config.yaml +4 -4
  107. endoreg_db/data/tag/requirement_set_tags.yaml +21 -0
  108. endoreg_db/exceptions.py +5 -2
  109. endoreg_db/helpers/data_loader.py +1 -1
  110. endoreg_db/management/commands/create_model_meta_from_huggingface.py +21 -10
  111. endoreg_db/management/commands/create_multilabel_model_meta.py +299 -129
  112. endoreg_db/management/commands/import_video.py +9 -10
  113. endoreg_db/management/commands/import_video_with_classification.py +1 -1
  114. endoreg_db/management/commands/init_default_ai_model.py +1 -1
  115. endoreg_db/management/commands/list_routes.py +18 -0
  116. endoreg_db/management/commands/load_ai_model_data.py +2 -1
  117. endoreg_db/management/commands/load_center_data.py +12 -12
  118. endoreg_db/management/commands/load_requirement_data.py +60 -31
  119. endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
  120. endoreg_db/management/commands/setup_endoreg_db.py +14 -10
  121. endoreg_db/management/commands/storage_management.py +271 -203
  122. endoreg_db/migrations/0001_initial.py +1799 -1300
  123. endoreg_db/migrations/0002_requirementset_depends_on.py +18 -0
  124. endoreg_db/migrations/_old/0001_initial.py +1857 -0
  125. endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +68 -0
  126. endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +77 -0
  127. endoreg_db/migrations/_old/0005_merge_20251111_1003.py +14 -0
  128. endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +68 -0
  129. endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +89 -0
  130. endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +27 -0
  131. endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +21 -0
  132. endoreg_db/models/__init__.py +78 -123
  133. endoreg_db/models/administration/__init__.py +21 -42
  134. endoreg_db/models/administration/ai/active_model.py +2 -2
  135. endoreg_db/models/administration/ai/ai_model.py +7 -6
  136. endoreg_db/models/administration/case/__init__.py +1 -15
  137. endoreg_db/models/administration/case/case.py +3 -3
  138. endoreg_db/models/administration/case/case_template/__init__.py +2 -14
  139. endoreg_db/models/administration/case/case_template/case_template.py +2 -124
  140. endoreg_db/models/administration/case/case_template/case_template_rule.py +2 -268
  141. endoreg_db/models/administration/case/case_template/case_template_rule_value.py +2 -85
  142. endoreg_db/models/administration/case/case_template/case_template_type.py +2 -25
  143. endoreg_db/models/administration/center/center.py +33 -19
  144. endoreg_db/models/administration/center/center_product.py +12 -9
  145. endoreg_db/models/administration/center/center_resource.py +25 -19
  146. endoreg_db/models/administration/center/center_shift.py +21 -17
  147. endoreg_db/models/administration/center/center_waste.py +16 -8
  148. endoreg_db/models/administration/person/__init__.py +2 -0
  149. endoreg_db/models/administration/person/employee/employee.py +10 -5
  150. endoreg_db/models/administration/person/employee/employee_qualification.py +9 -4
  151. endoreg_db/models/administration/person/employee/employee_type.py +12 -6
  152. endoreg_db/models/administration/person/examiner/examiner.py +13 -11
  153. endoreg_db/models/administration/person/patient/__init__.py +2 -0
  154. endoreg_db/models/administration/person/patient/patient.py +103 -100
  155. endoreg_db/models/administration/person/patient/patient_external_id.py +37 -0
  156. endoreg_db/models/administration/person/person.py +4 -0
  157. endoreg_db/models/administration/person/profession/__init__.py +8 -4
  158. endoreg_db/models/administration/person/user/portal_user_information.py +11 -7
  159. endoreg_db/models/administration/product/product.py +20 -15
  160. endoreg_db/models/administration/product/product_material.py +17 -18
  161. endoreg_db/models/administration/product/product_weight.py +12 -8
  162. endoreg_db/models/administration/product/reference_product.py +23 -55
  163. endoreg_db/models/administration/qualification/qualification.py +7 -3
  164. endoreg_db/models/administration/qualification/qualification_type.py +7 -3
  165. endoreg_db/models/administration/shift/scheduled_days.py +8 -5
  166. endoreg_db/models/administration/shift/shift.py +16 -12
  167. endoreg_db/models/administration/shift/shift_type.py +23 -31
  168. endoreg_db/models/label/__init__.py +7 -8
  169. endoreg_db/models/label/annotation/image_classification.py +10 -9
  170. endoreg_db/models/label/annotation/video_segmentation_annotation.py +8 -5
  171. endoreg_db/models/label/label.py +15 -15
  172. endoreg_db/models/label/label_set.py +19 -6
  173. endoreg_db/models/label/label_type.py +1 -1
  174. endoreg_db/models/label/label_video_segment/_create_from_video.py +5 -8
  175. endoreg_db/models/label/label_video_segment/label_video_segment.py +76 -102
  176. endoreg_db/models/label/video_segmentation_label.py +4 -0
  177. endoreg_db/models/label/video_segmentation_labelset.py +4 -3
  178. endoreg_db/models/media/frame/frame.py +22 -22
  179. endoreg_db/models/media/pdf/raw_pdf.py +249 -177
  180. endoreg_db/models/media/pdf/report_file.py +25 -29
  181. endoreg_db/models/media/pdf/report_reader/report_reader_config.py +30 -46
  182. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
  183. endoreg_db/models/media/video/__init__.py +1 -0
  184. endoreg_db/models/media/video/create_from_file.py +48 -56
  185. endoreg_db/models/media/video/pipe_1.py +30 -33
  186. endoreg_db/models/media/video/pipe_2.py +8 -9
  187. endoreg_db/models/media/video/video_file.py +359 -204
  188. endoreg_db/models/media/video/video_file_ai.py +288 -74
  189. endoreg_db/models/media/video/video_file_anonymize.py +38 -38
  190. endoreg_db/models/media/video/video_file_frames/__init__.py +3 -1
  191. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -8
  192. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +7 -9
  193. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +9 -8
  194. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +38 -45
  195. endoreg_db/models/media/video/video_file_frames/_get_frame.py +6 -8
  196. endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +4 -18
  197. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -3
  198. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +7 -6
  199. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +6 -8
  200. endoreg_db/models/media/video/video_file_frames/_get_frames.py +6 -8
  201. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +15 -25
  202. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +26 -23
  203. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +23 -14
  204. endoreg_db/models/media/video/video_file_io.py +109 -62
  205. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +3 -3
  206. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +5 -3
  207. endoreg_db/models/media/video/video_file_meta/get_fps.py +37 -34
  208. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +19 -25
  209. endoreg_db/models/media/video/video_file_meta/text_meta.py +41 -38
  210. endoreg_db/models/media/video/video_file_meta/video_meta.py +14 -7
  211. endoreg_db/models/media/video/video_file_segments.py +24 -17
  212. endoreg_db/models/media/video/video_metadata.py +19 -35
  213. endoreg_db/models/media/video/video_processing.py +96 -95
  214. endoreg_db/models/medical/contraindication/__init__.py +13 -3
  215. endoreg_db/models/medical/disease.py +22 -16
  216. endoreg_db/models/medical/event.py +31 -18
  217. endoreg_db/models/medical/examination/__init__.py +13 -6
  218. endoreg_db/models/medical/examination/examination.py +17 -18
  219. endoreg_db/models/medical/examination/examination_indication.py +26 -25
  220. endoreg_db/models/medical/examination/examination_time.py +16 -6
  221. endoreg_db/models/medical/examination/examination_time_type.py +9 -6
  222. endoreg_db/models/medical/examination/examination_type.py +3 -4
  223. endoreg_db/models/medical/finding/finding.py +38 -39
  224. endoreg_db/models/medical/finding/finding_classification.py +37 -48
  225. endoreg_db/models/medical/finding/finding_intervention.py +27 -22
  226. endoreg_db/models/medical/finding/finding_type.py +13 -12
  227. endoreg_db/models/medical/hardware/endoscope.py +20 -26
  228. endoreg_db/models/medical/hardware/endoscopy_processor.py +2 -2
  229. endoreg_db/models/medical/laboratory/lab_value.py +62 -91
  230. endoreg_db/models/medical/medication/medication.py +22 -10
  231. endoreg_db/models/medical/medication/medication_indication.py +29 -3
  232. endoreg_db/models/medical/medication/medication_indication_type.py +25 -14
  233. endoreg_db/models/medical/medication/medication_intake_time.py +31 -19
  234. endoreg_db/models/medical/medication/medication_schedule.py +27 -16
  235. endoreg_db/models/medical/organ/__init__.py +15 -12
  236. endoreg_db/models/medical/patient/medication_examples.py +1 -5
  237. endoreg_db/models/medical/patient/patient_disease.py +20 -23
  238. endoreg_db/models/medical/patient/patient_event.py +19 -22
  239. endoreg_db/models/medical/patient/patient_examination.py +48 -54
  240. endoreg_db/models/medical/patient/patient_examination_indication.py +16 -14
  241. endoreg_db/models/medical/patient/patient_finding.py +122 -139
  242. endoreg_db/models/medical/patient/patient_finding_classification.py +44 -49
  243. endoreg_db/models/medical/patient/patient_finding_intervention.py +8 -19
  244. endoreg_db/models/medical/patient/patient_lab_sample.py +28 -23
  245. endoreg_db/models/medical/patient/patient_lab_value.py +82 -89
  246. endoreg_db/models/medical/patient/patient_medication.py +27 -38
  247. endoreg_db/models/medical/patient/patient_medication_schedule.py +28 -36
  248. endoreg_db/models/medical/risk/risk.py +7 -6
  249. endoreg_db/models/medical/risk/risk_type.py +8 -5
  250. endoreg_db/models/metadata/model_meta.py +60 -29
  251. endoreg_db/models/metadata/model_meta_logic.py +139 -18
  252. endoreg_db/models/metadata/pdf_meta.py +19 -24
  253. endoreg_db/models/metadata/sensitive_meta.py +102 -85
  254. endoreg_db/models/metadata/sensitive_meta_logic.py +383 -43
  255. endoreg_db/models/metadata/video_meta.py +51 -31
  256. endoreg_db/models/metadata/video_prediction_logic.py +16 -23
  257. endoreg_db/models/metadata/video_prediction_meta.py +29 -33
  258. endoreg_db/models/other/distribution/date_value_distribution.py +89 -29
  259. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +21 -5
  260. endoreg_db/models/other/distribution/numeric_value_distribution.py +114 -53
  261. endoreg_db/models/other/distribution/single_categorical_value_distribution.py +4 -3
  262. endoreg_db/models/other/emission/emission_factor.py +18 -8
  263. endoreg_db/models/other/gender.py +10 -5
  264. endoreg_db/models/other/information_source.py +25 -25
  265. endoreg_db/models/other/material.py +9 -5
  266. endoreg_db/models/other/resource.py +6 -4
  267. endoreg_db/models/other/tag.py +10 -5
  268. endoreg_db/models/other/transport_route.py +13 -8
  269. endoreg_db/models/other/unit.py +10 -6
  270. endoreg_db/models/other/waste.py +6 -5
  271. endoreg_db/models/requirement/requirement.py +580 -272
  272. endoreg_db/models/requirement/requirement_error.py +85 -0
  273. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +268 -0
  274. endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +3 -6
  275. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +90 -64
  276. endoreg_db/models/requirement/requirement_operator.py +36 -33
  277. endoreg_db/models/requirement/requirement_set.py +74 -57
  278. endoreg_db/models/state/__init__.py +4 -4
  279. endoreg_db/models/state/abstract.py +2 -2
  280. endoreg_db/models/state/anonymization.py +12 -0
  281. endoreg_db/models/state/audit_ledger.py +46 -47
  282. endoreg_db/models/state/label_video_segment.py +9 -0
  283. endoreg_db/models/state/raw_pdf.py +40 -46
  284. endoreg_db/models/state/sensitive_meta.py +6 -2
  285. endoreg_db/models/state/video.py +58 -53
  286. endoreg_db/models/upload_job.py +32 -55
  287. endoreg_db/models/utils.py +1 -2
  288. endoreg_db/root_urls.py +21 -2
  289. endoreg_db/serializers/__init__.py +26 -57
  290. endoreg_db/serializers/anonymization.py +18 -10
  291. endoreg_db/serializers/meta/report_meta.py +1 -1
  292. endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
  293. endoreg_db/serializers/misc/__init__.py +1 -1
  294. endoreg_db/serializers/misc/file_overview.py +33 -91
  295. endoreg_db/serializers/misc/{vop_patient_data.py → sensitive_patient_data.py} +1 -1
  296. endoreg_db/serializers/requirements/requirement_sets.py +92 -22
  297. endoreg_db/serializers/video/segmentation.py +2 -1
  298. endoreg_db/serializers/video/video_processing_history.py +20 -5
  299. endoreg_db/serializers/video_examination.py +198 -0
  300. endoreg_db/services/anonymization.py +75 -73
  301. endoreg_db/services/lookup_service.py +256 -73
  302. endoreg_db/services/lookup_store.py +174 -30
  303. endoreg_db/services/pdf_import.py +711 -310
  304. endoreg_db/services/storage_aware_video_processor.py +140 -114
  305. endoreg_db/services/video_import.py +266 -117
  306. endoreg_db/urls/__init__.py +27 -27
  307. endoreg_db/urls/label_video_segments.py +2 -0
  308. endoreg_db/urls/media.py +108 -66
  309. endoreg_db/urls/root_urls.py +29 -0
  310. endoreg_db/utils/__init__.py +15 -5
  311. endoreg_db/utils/ai/multilabel_classification_net.py +116 -20
  312. endoreg_db/utils/case_generator/__init__.py +3 -0
  313. endoreg_db/utils/dataloader.py +88 -16
  314. endoreg_db/utils/defaults/set_default_center.py +32 -0
  315. endoreg_db/utils/names.py +22 -16
  316. endoreg_db/utils/permissions.py +2 -1
  317. endoreg_db/utils/pipelines/process_video_dir.py +1 -1
  318. endoreg_db/utils/requirement_operator_logic/model_evaluators.py +414 -127
  319. endoreg_db/utils/setup_config.py +8 -5
  320. endoreg_db/utils/storage.py +115 -0
  321. endoreg_db/utils/validate_endo_roi.py +8 -2
  322. endoreg_db/utils/video/ffmpeg_wrapper.py +184 -188
  323. endoreg_db/views/__init__.py +5 -12
  324. endoreg_db/views/anonymization/media_management.py +198 -163
  325. endoreg_db/views/anonymization/overview.py +4 -1
  326. endoreg_db/views/anonymization/validate.py +174 -40
  327. endoreg_db/views/media/__init__.py +2 -0
  328. endoreg_db/views/media/pdf_media.py +131 -150
  329. endoreg_db/views/media/sensitive_metadata.py +46 -6
  330. endoreg_db/views/media/video_media.py +89 -82
  331. endoreg_db/views/media/video_segments.py +187 -260
  332. endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
  333. endoreg_db/views/patient/patient.py +5 -4
  334. endoreg_db/views/pdf/__init__.py +5 -8
  335. endoreg_db/views/pdf/pdf_stream.py +186 -0
  336. endoreg_db/views/pdf/pdf_stream_views.py +0 -127
  337. endoreg_db/views/pdf/reimport.py +86 -91
  338. endoreg_db/views/requirement/evaluate.py +188 -187
  339. endoreg_db/views/requirement/lookup.py +186 -288
  340. endoreg_db/views/requirement/requirement_utils.py +89 -0
  341. endoreg_db/views/video/__init__.py +0 -4
  342. endoreg_db/views/video/correction.py +2 -2
  343. endoreg_db/views/video/video_examination_viewset.py +202 -289
  344. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
  345. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +350 -255
  346. endoreg_db/models/administration/permissions/__init__.py +0 -44
  347. endoreg_db/models/media/video/refactor_plan.md +0 -0
  348. endoreg_db/models/media/video/video_file_frames.py +0 -0
  349. endoreg_db/models/metadata/frame_ocr_result.py +0 -0
  350. endoreg_db/models/rule/__init__.py +0 -13
  351. endoreg_db/models/rule/rule.py +0 -27
  352. endoreg_db/models/rule/rule_applicator.py +0 -224
  353. endoreg_db/models/rule/rule_attribute_dtype.py +0 -17
  354. endoreg_db/models/rule/rule_type.py +0 -20
  355. endoreg_db/models/rule/ruleset.py +0 -17
  356. endoreg_db/serializers/video/video_metadata.py +0 -105
  357. endoreg_db/urls/report.py +0 -48
  358. endoreg_db/urls/video.py +0 -61
  359. endoreg_db/utils/case_generator/case_generator.py +0 -159
  360. endoreg_db/utils/case_generator/utils.py +0 -30
  361. endoreg_db/views/pdf/pdf_media.py +0 -239
  362. endoreg_db/views/report/__init__.py +0 -9
  363. endoreg_db/views/report/report_list.py +0 -112
  364. endoreg_db/views/report/report_with_secure_url.py +0 -28
  365. endoreg_db/views/report/start_examination.py +0 -7
  366. endoreg_db/views/video/video_media.py +0 -158
  367. endoreg_db/views.py +0 -0
  368. /endoreg_db/data/{requirement_set → _examples/requirement_set}/endoscopy_bleeding_risk.yaml +0 -0
  369. /endoreg_db/migrations/{0002_add_video_correction_models.py → _old/0002_add_video_correction_models.py} +0 -0
  370. /endoreg_db/migrations/{0003_add_center_display_name.py → _old/0003_add_center_display_name.py} +0 -0
  371. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
  372. {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,7 +1,9 @@
1
- from ...utils import DOCUMENT_DIR, STORAGE_DIR
2
- from django.db import models
3
1
  from typing import TYPE_CHECKING
4
2
 
3
+ from django.db import models
4
+
5
+ from ...utils import DOCUMENT_DIR, STORAGE_DIR
6
+
5
7
  if TYPE_CHECKING:
6
8
  from ...administration import (
7
9
  Center,
@@ -12,17 +14,21 @@ if TYPE_CHECKING:
12
14
  )
13
15
  from ...metadata import SensitiveMeta
14
16
 
17
+
15
18
  class DocumentTypeManager(models.Manager):
16
19
  """
17
20
  Custom manager for DocumentType.
18
21
  """
22
+
19
23
  def get_by_natural_key(self, name):
20
24
  return self.get(name=name)
21
25
 
26
+
22
27
  class DocumentType(models.Model):
23
28
  """
24
29
  Represents the type of a document.
25
30
  """
31
+
26
32
  name = models.CharField(max_length=255, unique=True)
27
33
  description = models.TextField(blank=True, null=True)
28
34
 
@@ -33,21 +39,23 @@ class DocumentType(models.Model):
33
39
 
34
40
  def __str__(self):
35
41
  return str(self.name)
36
-
42
+
37
43
  class Meta:
38
44
  verbose_name = "Document Type"
39
45
  verbose_name_plural = "Document Types"
40
46
 
47
+
41
48
  class AbstractDocument(models.Model):
42
49
  """
43
50
  Abstract base class for documents.
44
51
  """
52
+
45
53
  meta = models.JSONField(blank=True, null=True)
46
54
  text = models.TextField(blank=True, null=True)
47
55
  date = models.DateField(blank=True, null=True)
48
56
  time = models.TimeField(blank=True, null=True)
49
57
  file = models.FileField(
50
- upload_to=DOCUMENT_DIR.relative_to(STORAGE_DIR),
58
+ upload_to=DOCUMENT_DIR.relative_to(STORAGE_DIR).as_posix(),
51
59
  blank=True,
52
60
  null=True,
53
61
  )
@@ -67,22 +75,19 @@ class AbstractDocument(models.Model):
67
75
  )
68
76
 
69
77
  if TYPE_CHECKING:
70
- center: "Center"
71
- type: "DocumentType"
78
+ center: models.ForeignKey["Center|None"]
79
+ type: models.ForeignKey["DocumentType|None"]
72
80
 
73
81
  class Meta:
74
82
  abstract = True
75
83
 
76
84
 
77
-
78
-
79
85
  class AbstractExaminationReport(AbstractDocument):
80
86
  """
81
87
  Abstract base class for examination reports.
82
88
  """
83
- patient = models.ForeignKey(
84
- "Patient", on_delete=models.DO_NOTHING, blank=True, null=True
85
- )
89
+
90
+ patient = models.ForeignKey("Patient", on_delete=models.DO_NOTHING, blank=True, null=True)
86
91
 
87
92
  patient_examination = models.ForeignKey(
88
93
  "PatientExamination",
@@ -96,25 +101,18 @@ class AbstractExaminationReport(AbstractDocument):
96
101
  blank=True,
97
102
  )
98
103
 
99
- sensitive_meta = models.ForeignKey(
100
- "SensitiveMeta",
101
- on_delete=models.SET_NULL,
102
- null=True,
103
- blank=True
104
- )
104
+ sensitive_meta = models.ForeignKey("SensitiveMeta", on_delete=models.SET_NULL, null=True, blank=True)
105
105
 
106
106
  if TYPE_CHECKING:
107
- center: "Center"
108
- type: "DocumentType"
109
- patient: "Patient"
110
- patient_examination: "PatientExamination"
111
- sensitive_meta: "SensitiveMeta"
112
-
107
+ center: models.ForeignKey["Center|None"]
108
+ type: models.ForeignKey["DocumentType|None"]
109
+ patient: models.ForeignKey["Patient|None"]
110
+ patient_examination: models.ForeignKey["PatientExamination|None"]
111
+ sensitive_meta: models.ForeignKey["SensitiveMeta|None"]
113
112
 
114
113
  class Meta:
115
114
  abstract = True
116
115
 
117
-
118
116
  def get_or_create_examiner(self, examiner_first_name, examiner_last_name):
119
117
  raise NotImplementedError("Subclasses must implement this method.")
120
118
 
@@ -122,10 +120,8 @@ class AbstractExaminationReport(AbstractDocument):
122
120
  raise NotImplementedError("Subclasses must implement this method.")
123
121
 
124
122
 
125
-
126
123
  class AnonymExaminationReport(AbstractExaminationReport):
127
-
128
- def get_or_create_examiner(self, examiner_first_name:str, examiner_last_name:str):
124
+ def get_or_create_examiner(self, examiner_first_name: str, examiner_last_name: str):
129
125
  from ...administration.person import Examiner
130
126
 
131
127
  examiner_center = self.center
@@ -139,7 +135,7 @@ class AnonymExaminationReport(AbstractExaminationReport):
139
135
  return examiner, created
140
136
 
141
137
  def set_examination_date_and_time(self, report_meta=None):
142
- #TODO
138
+ # TODO
143
139
  if not report_meta:
144
140
  report_meta = self.meta
145
141
  # examination_date_str = report_meta["examination_date"]
@@ -152,11 +148,11 @@ class AnonymExaminationReport(AbstractExaminationReport):
152
148
  # # TODO: get django TimeField compatible time from string (e.g. "12:00")
153
149
  # self.time = time.fromisoformat(examination_time_str)
154
150
 
151
+
155
152
  class AnonymHistologyReport(AbstractExaminationReport):
156
153
  """
157
154
  Represents a histology report.
158
155
  """
159
156
 
160
-
161
157
  def get_or_create_examiner(self, examiner_first_name, examiner_last_name):
162
158
  raise NotImplementedError("Subclasses must implement this method.")
@@ -1,30 +1,14 @@
1
1
  # ReportReaderConfig Class
2
- # Description: This class is used to store the configuration of the ReportReader
3
-
4
- # PATIENT_INFO_LINE_FLAG = "Patient: "
5
- # ENDOSCOPE_INFO_LINE_FLAG = "Gerät: "
6
- # EXAMINER_INFO_LINE_FLAG = "1. Unters.:"
7
- # CUT_OFF_BELOW_LINE_FLAG = "________________"
8
-
9
-
10
- # CUT_OFF_ABOVE_LINE_FLAGS = [
11
- # ENDOSCOPE_INFO_LINE_FLAG,
12
- # EXAMINER_INFO_LINE_FLAG,
13
- # ]
14
-
15
- # CUT_OFF_BELOW_LINE_FLAGS = [
16
- # CUT_OFF_BELOW_LINE_FLAG
17
- # ]
2
+ from typing import TYPE_CHECKING, cast
18
3
 
19
4
  from django.db import models
20
5
 
21
-
22
- from typing import TYPE_CHECKING
23
6
  if TYPE_CHECKING:
24
- from .report_reader_flag import ReportReaderFlag
25
- from ....administration.person import FirstName, LastName
26
7
  from ....administration.center import Center
8
+ from ....administration.person import FirstName, LastName
27
9
  from ....metadata import PdfType
10
+ from .report_reader_flag import ReportReaderFlag
11
+
28
12
 
29
13
  class ReportReaderConfig(models.Model):
30
14
  """
@@ -33,45 +17,45 @@ class ReportReaderConfig(models.Model):
33
17
  Stores locale, name lists, date format, and flags used to identify key information lines
34
18
  and text sections to ignore.
35
19
  """
20
+
36
21
  locale = models.CharField(default="de_DE", max_length=10)
37
- first_names = models.ManyToManyField('FirstName', related_name='report_reader_configs')
38
- last_names = models.ManyToManyField('LastName', related_name='report_reader_configs')
39
- text_date_format = models.CharField(default = "%d.%m.%Y", max_length=10)
40
- patient_info_line_flag = models.ForeignKey("ReportReaderFlag", related_name='report_reader_configs_patient_info_line', on_delete=models.CASCADE)
41
- endoscope_info_line_flag = models.ForeignKey("ReportReaderFlag", related_name='report_reader_configs_endoscope_info_line', on_delete=models.CASCADE)
42
- examiner_info_line_flag = models.ForeignKey("ReportReaderFlag", related_name='report_reader_configs_examiner_info_line', on_delete=models.CASCADE)
43
- cut_off_below = models.ManyToManyField("ReportReaderFlag", related_name='report_reader_configs_cut_off_below')
44
- cut_off_above = models.ManyToManyField("ReportReaderFlag", related_name='report_reader_configs_cut_off_above')
45
-
22
+ first_names = models.ManyToManyField("FirstName", related_name="report_reader_configs")
23
+ last_names = models.ManyToManyField("LastName", related_name="report_reader_configs")
24
+ text_date_format = models.CharField(default="%d.%m.%Y", max_length=10)
25
+ patient_info_line_flag = models.ForeignKey("ReportReaderFlag", related_name="report_reader_configs_patient_info_line", on_delete=models.CASCADE)
26
+ endoscope_info_line_flag = models.ForeignKey("ReportReaderFlag", related_name="report_reader_configs_endoscope_info_line", on_delete=models.CASCADE)
27
+ examiner_info_line_flag = models.ForeignKey("ReportReaderFlag", related_name="report_reader_configs_examiner_info_line", on_delete=models.CASCADE)
28
+ cut_off_below = models.ManyToManyField("ReportReaderFlag", related_name="report_reader_configs_cut_off_below")
29
+ cut_off_above = models.ManyToManyField("ReportReaderFlag", related_name="report_reader_configs_cut_off_above")
30
+
46
31
  if TYPE_CHECKING:
47
- first_names: models.QuerySet["FirstName"]
48
- last_names: models.QuerySet["LastName"]
49
- patient_info_line_flag: "ReportReaderFlag"
50
- endoscope_info_line_flag: "ReportReaderFlag"
51
- examiner_info_line_flag: "ReportReaderFlag"
52
- cut_off_below: models.QuerySet["ReportReaderFlag"]
53
- cut_off_above: models.QuerySet["ReportReaderFlag"]
54
-
32
+ patient_info_line_flag = models.ForeignKey["ReportReaderFlag"]
33
+ endoscope_info_line_flag = models.ForeignKey["ReportReaderFlag"]
34
+ examiner_info_line_flag = models.ForeignKey["ReportReaderFlag"]
35
+
36
+ first_names = cast(models.manager.RelatedManager["FirstName"], first_names)
37
+ last_names = cast(models.manager.RelatedManager["LastName"], last_names)
38
+ cut_off_below = cast(models.manager.RelatedManager["ReportReaderFlag"], cut_off_below)
39
+ cut_off_above = cast(models.manager.RelatedManager["ReportReaderFlag"], cut_off_above)
55
40
 
56
41
  def __str__(self):
57
42
  """Returns a string representation including the locale and primary key."""
58
43
  _str = f"ReportReaderConfig: {self.locale} (id: {self.pk}\n"
59
44
  return _str
60
-
61
- def update_names_by_center(self, center:"Center", save = True):
45
+
46
+ def update_names_by_center(self, center: "Center", save=True):
62
47
  """Updates the first and last name lists based on the names associated with a Center."""
63
48
  self.first_names.set(center.first_names.all())
64
49
  self.last_names.set(center.last_names.all())
65
50
  if save:
66
51
  self.save()
67
52
 
68
- def update_flags_by_pdf_type(self, pdf_type:"PdfType", save = True):
53
+ def update_flags_by_pdf_type(self, pdf_type: "PdfType", save=True):
69
54
  """Updates the line identification flags based on a specific PdfType."""
70
- self.patient_info_line_flag = pdf_type.patient_info_line_flag
71
- self.endoscope_info_line_flag = pdf_type.endoscope_info_line_flag
72
- self.examiner_info_line_flag = pdf_type.examiner_info_line_flag
73
- self.cut_off_below.set(pdf_type.cut_off_below.all())
74
- self.cut_off_above.set(pdf_type.cut_off_above.all())
55
+ self.patient_info_line_flag = pdf_type.patient_info_line
56
+ self.endoscope_info_line_flag = pdf_type.endoscope_info_line
57
+ self.examiner_info_line_flag = pdf_type.examiner_info_line
58
+ self.cut_off_below.set(pdf_type.cut_off_below_lines.all())
59
+ self.cut_off_above.set(pdf_type.cut_off_above_lines.all())
75
60
  if save:
76
61
  self.save()
77
-
@@ -1,20 +1,36 @@
1
- # Django model for the report reader flag
2
- # have name and value
3
- # name is natural key
1
+ from typing import TYPE_CHECKING
4
2
 
5
3
  from django.db import models
6
4
 
5
+ if TYPE_CHECKING:
6
+ from endoreg_db.models import ReportReaderConfig
7
+
8
+
7
9
  class ReportReaderFlagManager(models.Manager):
8
10
  def get_by_natural_key(self, name):
9
11
  return self.get(name=name)
10
-
12
+
13
+
11
14
  class ReportReaderFlag(models.Model):
12
15
  objects = ReportReaderFlagManager()
13
16
  name = models.CharField(max_length=255, unique=True)
14
17
  value = models.CharField(max_length=255)
15
-
18
+
19
+ if TYPE_CHECKING:
20
+
21
+ @property
22
+ def report_reader_configs_patient_info_line(self) -> models.QuerySet["ReportReaderConfig"]: ...
23
+ @property
24
+ def report_reader_configs_endoscope_info_line(self) -> models.QuerySet["ReportReaderConfig"]: ...
25
+ @property
26
+ def report_reader_configs_examiner_info_line(self) -> models.QuerySet["ReportReaderConfig"]: ...
27
+ @property
28
+ def report_reader_configs_cut_off_below(self) -> models.QuerySet["ReportReaderConfig"]: ...
29
+ @property
30
+ def report_reader_configs_cut_off_above(self) -> models.QuerySet["ReportReaderConfig"]: ...
31
+
16
32
  def natural_key(self):
17
33
  return (self.name,)
18
-
34
+
19
35
  def __str__(self):
20
- return self.name
36
+ return self.name
@@ -1,6 +1,7 @@
1
1
  from .video_file import VideoFile
2
2
  from .video_metadata import VideoMetadata
3
3
  from .video_processing import VideoProcessingHistory
4
+
4
5
  __all__ = [
5
6
  "VideoFile",
6
7
  "VideoMetadata",
@@ -1,20 +1,21 @@
1
- import shutil
2
1
  import logging
2
+ import shutil
3
3
  import uuid
4
+ from importlib import import_module
4
5
  from pathlib import Path
5
6
  from typing import TYPE_CHECKING, Optional, Type
6
7
 
7
8
  # Import the new exceptions from the correct path
8
9
  from endoreg_db.exceptions import InsufficientStorageError, TranscodingError
9
- from ...utils import VIDEO_DIR, TMP_VIDEO_DIR
10
- from importlib import import_module
10
+
11
+ from ...utils import TMP_VIDEO_DIR, VIDEO_DIR
11
12
 
12
13
  if TYPE_CHECKING:
13
14
  from endoreg_db.models import VideoFile
14
15
 
15
- from ....utils.video.ffmpeg_wrapper import transcode_videofile_if_required
16
- from ....utils.hashs import get_video_hash
17
16
  from ....utils.file_operations import get_uuid_filename
17
+ from ....utils.hashs import get_video_hash
18
+ from ....utils.video.ffmpeg_wrapper import transcode_videofile_if_required
18
19
 
19
20
  logger = logging.getLogger(__name__)
20
21
 
@@ -22,33 +23,31 @@ logger = logging.getLogger(__name__)
22
23
  def check_storage_capacity(src_path: Path, dst_root: Path, safety_margin: float = 1.2) -> None:
23
24
  """
24
25
  Check if there's enough storage space before starting operations.
25
-
26
+
26
27
  Args:
27
28
  src_path: Source file path
28
29
  dst_root: Destination root directory
29
30
  safety_margin: Safety factor (1.2 = 20% extra space required)
30
-
31
+
31
32
  Raises:
32
33
  InsufficientStorageError: If insufficient storage space
33
34
  """
34
35
  try:
35
36
  src_size = src_path.stat().st_size
36
37
  required_space = int(src_size * safety_margin)
37
-
38
+
38
39
  # Check free space on destination
39
40
  free_space = shutil.disk_usage(dst_root).free
40
-
41
+
41
42
  if free_space < required_space:
42
43
  raise InsufficientStorageError(
43
- f"Insufficient storage space. Required: {required_space/1e9:.1f} GB, "
44
- f"Available: {free_space/1e9:.1f} GB on {dst_root}",
44
+ f"Insufficient storage space. Required: {required_space / 1e9:.1f} GB, Available: {free_space / 1e9:.1f} GB on {dst_root}",
45
45
  required_space=required_space,
46
- available_space=free_space
46
+ available_space=free_space,
47
47
  )
48
-
49
- logger.info(f"Storage check passed: {free_space/1e9:.1f} GB available, "
50
- f"{required_space/1e9:.1f} GB required")
51
-
48
+
49
+ logger.info(f"Storage check passed: {free_space / 1e9:.1f} GB available, {required_space / 1e9:.1f} GB required")
50
+
52
51
  except OSError as e:
53
52
  logger.warning(f"Could not check storage capacity: {e}")
54
53
  # Don't fail the operation, just log the warning
@@ -57,14 +56,14 @@ def check_storage_capacity(src_path: Path, dst_root: Path, safety_margin: float
57
56
  def atomic_copy_with_fallback(src_path: Path, dst_path: Path) -> bool:
58
57
  """
59
58
  Atomically copy file from src to dst, preserving the source file.
60
-
59
+
61
60
  Args:
62
61
  src_path: Source file path
63
62
  dst_path: Destination file path
64
-
63
+
65
64
  Returns:
66
65
  True if successful
67
-
66
+
68
67
  Raises:
69
68
  InsufficientStorageError: If not enough space for the operation
70
69
  OSError: For other file system errors
@@ -73,18 +72,17 @@ def atomic_copy_with_fallback(src_path: Path, dst_path: Path) -> bool:
73
72
  # Check space before copy
74
73
  src_size = src_path.stat().st_size
75
74
  free_space = shutil.disk_usage(dst_path.parent).free
76
-
75
+
77
76
  if free_space < src_size * 1.1: # 10% safety margin
78
77
  raise InsufficientStorageError(
79
- f"Insufficient space for copy operation. Required: {src_size/1e9:.1f} GB, "
80
- f"Available: {free_space/1e9:.1f} GB",
78
+ f"Insufficient space for copy operation. Required: {src_size / 1e9:.1f} GB, Available: {free_space / 1e9:.1f} GB",
81
79
  required_space=src_size,
82
- available_space=free_space
80
+ available_space=free_space,
83
81
  )
84
-
82
+
85
83
  # Use a temporary name during copy for atomicity
86
- temp_dst = dst_path.with_suffix(dst_path.suffix + '.tmp')
87
-
84
+ temp_dst = dst_path.with_suffix(dst_path.suffix + ".tmp")
85
+
88
86
  try:
89
87
  shutil.copy2(str(src_path), str(temp_dst))
90
88
  temp_dst.rename(dst_path)
@@ -95,7 +93,7 @@ def atomic_copy_with_fallback(src_path: Path, dst_path: Path) -> bool:
95
93
  if temp_dst.exists():
96
94
  temp_dst.unlink(missing_ok=True)
97
95
  raise
98
-
96
+
99
97
  except Exception as e:
100
98
  logger.error(f"Copy operation failed: {src_path} -> {dst_path}: {e}")
101
99
  raise
@@ -104,14 +102,14 @@ def atomic_copy_with_fallback(src_path: Path, dst_path: Path) -> bool:
104
102
  def atomic_move_with_fallback(src_path: Path, dst_path: Path) -> bool:
105
103
  """
106
104
  Atomically move file from src to dst, with fallback to copy+remove.
107
-
105
+
108
106
  Args:
109
107
  src_path: Source file path
110
108
  dst_path: Destination file path
111
-
109
+
112
110
  Returns:
113
111
  True if successful
114
-
112
+
115
113
  Raises:
116
114
  InsufficientStorageError: If not enough space for the operation
117
115
  OSError: For other file system errors
@@ -127,32 +125,31 @@ def atomic_move_with_fallback(src_path: Path, dst_path: Path) -> bool:
127
125
  logger.debug("Cross-device move detected, falling back to copy+remove")
128
126
  else:
129
127
  raise
130
-
128
+
131
129
  # Check space before cross-filesystem copy
132
130
  src_size = src_path.stat().st_size
133
131
  free_space = shutil.disk_usage(dst_path.parent).free
134
-
132
+
135
133
  if free_space < src_size * 1.1: # 10% safety margin
136
134
  raise InsufficientStorageError(
137
- f"Insufficient space for copy operation. Required: {src_size/1e9:.1f} GB, "
138
- f"Available: {free_space/1e9:.1f} GB",
135
+ f"Insufficient space for copy operation. Required: {src_size / 1e9:.1f} GB, Available: {free_space / 1e9:.1f} GB",
139
136
  required_space=src_size,
140
- available_space=free_space
137
+ available_space=free_space,
141
138
  )
142
-
139
+
143
140
  # Fallback to copy+remove for cross-filesystem moves
144
141
  logger.info(f"Copying file (cross-filesystem): {src_path} -> {dst_path}")
145
-
142
+
146
143
  # Use a temporary name during copy for atomicity
147
- temp_dst = dst_path.with_suffix(dst_path.suffix + '.tmp')
148
-
144
+ temp_dst = dst_path.with_suffix(dst_path.suffix + ".tmp")
145
+
149
146
  try:
150
147
  shutil.copy2(str(src_path), str(temp_dst))
151
148
  temp_dst.rename(dst_path)
152
149
  src_path.unlink() # Remove source only after successful copy
153
150
  logger.debug(f"Copy+remove successful: {src_path} -> {dst_path}")
154
151
  return True
155
-
152
+
156
153
  except OSError as e:
157
154
  # Clean up temp file on failure
158
155
  if temp_dst.exists():
@@ -160,12 +157,10 @@ def atomic_move_with_fallback(src_path: Path, dst_path: Path) -> bool:
160
157
  # Re-raise with better context
161
158
  if e.errno == 28: # No space left on device
162
159
  raise InsufficientStorageError(
163
- f"No space left on device during copy: {e}",
164
- required_space=src_path.stat().st_size,
165
- available_space=shutil.disk_usage(dst_path.parent).free
160
+ f"No space left on device during copy: {e}", required_space=src_path.stat().st_size, available_space=shutil.disk_usage(dst_path.parent).free
166
161
  )
167
162
  raise
168
-
163
+
169
164
  except Exception as e:
170
165
  logger.error(f"Failed to move {src_path} -> {dst_path}: {e}")
171
166
  raise
@@ -195,11 +190,11 @@ def _create_from_file(
195
190
  video_dir: Path = VIDEO_DIR,
196
191
  save: bool = True,
197
192
  delete_source: bool = False,
198
- **kwargs
193
+ **kwargs,
199
194
  ) -> "VideoFile":
200
195
  """
201
196
  Creates a VideoFile instance from a given video file path with improved error handling.
202
-
197
+
203
198
  Raises:
204
199
  InsufficientStorageError: When not enough disk space
205
200
  TranscodingError: When video transcoding fails
@@ -208,7 +203,7 @@ def _create_from_file(
208
203
  """
209
204
  from endoreg_db.models.administration.center.center import Center
210
205
  from endoreg_db.models.medical.hardware import EndoscopyProcessor
211
-
206
+
212
207
  original_file_name = file_path.name
213
208
  original_suffix = file_path.suffix
214
209
  final_storage_path = None
@@ -223,23 +218,20 @@ def _create_from_file(
223
218
  resolved_storage_root = _get_path(data_paths, "storage", storage_root_default)
224
219
  storage_root = Path(resolved_storage_root)
225
220
  storage_root.mkdir(parents=True, exist_ok=True)
226
-
221
+
227
222
  # Check storage capacity before starting any work
228
223
  check_storage_capacity(file_path, storage_root)
229
224
 
230
225
  # 1. Transcode if necessary
231
226
  logger.debug("Checking transcoding requirement for %s", file_path)
232
- temp_transcode_dir = TMP_VIDEO_DIR / 'transcoding'
227
+ temp_transcode_dir = TMP_VIDEO_DIR / "transcoding"
233
228
  temp_transcode_dir.mkdir(parents=True, exist_ok=True)
234
-
229
+
235
230
  # Use a unique name for the potential transcoded file
236
231
  temp_transcoded_output_path = temp_transcode_dir / f"{uuid.uuid4()}{original_suffix}"
237
232
 
238
233
  try:
239
- transcoded_file_path = transcode_videofile_if_required(
240
- input_path=file_path,
241
- output_path=temp_transcoded_output_path
242
- )
234
+ transcoded_file_path = transcode_videofile_if_required(input_path=file_path, output_path=temp_transcoded_output_path)
243
235
  if transcoded_file_path is None:
244
236
  raise TranscodingError(f"Transcoding check/process failed for {file_path}")
245
237
  except Exception as e:
@@ -257,7 +249,7 @@ def _create_from_file(
257
249
  if cls_model.check_hash_exists(video_hash=video_hash):
258
250
  existing_video = cls_model.objects.get(video_hash=video_hash)
259
251
  logger.warning("Video with hash %s already exists (UUID: %s)", video_hash, existing_video.uuid)
260
-
252
+
261
253
  # Check if the existing video has a valid file
262
254
  existing_raw_path = existing_video.get_raw_file_path()
263
255
  if existing_video.has_raw and existing_raw_path and existing_raw_path.exists():
@@ -355,4 +347,4 @@ def _create_from_file(
355
347
  if transcoded_file_path and transcoded_file_path != file_path and transcoded_file_path.exists():
356
348
  logger.warning("Cleaning up orphaned transcoded file: %s", transcoded_file_path)
357
349
  transcoded_file_path.unlink(missing_ok=True)
358
- raise RuntimeError(f"Video processing failed: {e}") from e
350
+ raise RuntimeError(f"Video processing failed: {e}") from e