endoreg-db 0.8.8.9__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 (453) 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 +146 -83
  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/migrations/__init__.py +0 -0
  82. endoreg_db/models/__init__.py +8 -0
  83. endoreg_db/models/administration/ai/active_model.py +5 -5
  84. endoreg_db/models/administration/ai/ai_model.py +41 -18
  85. endoreg_db/models/administration/ai/model_type.py +1 -0
  86. endoreg_db/models/administration/case/case.py +22 -22
  87. endoreg_db/models/administration/center/__init__.py +5 -5
  88. endoreg_db/models/administration/center/center.py +6 -2
  89. endoreg_db/models/administration/center/center_resource.py +18 -4
  90. endoreg_db/models/administration/center/center_shift.py +3 -1
  91. endoreg_db/models/administration/center/center_waste.py +6 -2
  92. endoreg_db/models/administration/person/__init__.py +1 -1
  93. endoreg_db/models/administration/person/employee/__init__.py +1 -1
  94. endoreg_db/models/administration/person/employee/employee_type.py +3 -1
  95. endoreg_db/models/administration/person/examiner/__init__.py +1 -1
  96. endoreg_db/models/administration/person/examiner/examiner.py +10 -2
  97. endoreg_db/models/administration/person/names/first_name.py +6 -4
  98. endoreg_db/models/administration/person/names/last_name.py +4 -3
  99. endoreg_db/models/administration/person/patient/__init__.py +1 -1
  100. endoreg_db/models/administration/person/patient/patient.py +0 -1
  101. endoreg_db/models/administration/person/patient/patient_external_id.py +0 -1
  102. endoreg_db/models/administration/person/person.py +1 -1
  103. endoreg_db/models/administration/product/__init__.py +7 -6
  104. endoreg_db/models/administration/product/product.py +6 -2
  105. endoreg_db/models/administration/product/product_group.py +9 -7
  106. endoreg_db/models/administration/product/product_material.py +9 -2
  107. endoreg_db/models/administration/product/reference_product.py +64 -15
  108. endoreg_db/models/administration/qualification/qualification.py +3 -1
  109. endoreg_db/models/administration/shift/shift.py +3 -1
  110. endoreg_db/models/administration/shift/shift_type.py +12 -4
  111. endoreg_db/models/aidataset/__init__.py +5 -0
  112. endoreg_db/models/aidataset/aidataset.py +193 -0
  113. endoreg_db/models/label/__init__.py +1 -1
  114. endoreg_db/models/label/label.py +10 -2
  115. endoreg_db/models/label/label_set.py +3 -1
  116. endoreg_db/models/label/label_video_segment/_create_from_video.py +6 -2
  117. endoreg_db/models/label/label_video_segment/label_video_segment.py +148 -44
  118. endoreg_db/models/media/__init__.py +12 -5
  119. endoreg_db/models/media/frame/__init__.py +1 -1
  120. endoreg_db/models/media/frame/frame.py +34 -8
  121. endoreg_db/models/media/pdf/__init__.py +2 -1
  122. endoreg_db/models/media/pdf/raw_pdf.py +11 -4
  123. endoreg_db/models/media/pdf/report_file.py +6 -2
  124. endoreg_db/models/media/pdf/report_reader/__init__.py +3 -3
  125. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +15 -5
  126. endoreg_db/models/media/video/create_from_file.py +20 -41
  127. endoreg_db/models/media/video/pipe_1.py +75 -30
  128. endoreg_db/models/media/video/pipe_2.py +37 -12
  129. endoreg_db/models/media/video/video_file.py +36 -24
  130. endoreg_db/models/media/video/video_file_ai.py +235 -70
  131. endoreg_db/models/media/video/video_file_anonymize.py +240 -65
  132. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -1
  133. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +3 -1
  134. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +30 -9
  135. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +95 -29
  136. endoreg_db/models/media/video/video_file_frames/_get_frame.py +13 -3
  137. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -1
  138. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +15 -3
  139. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +15 -3
  140. endoreg_db/models/media/video/video_file_frames/_get_frames.py +7 -2
  141. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +109 -23
  142. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +111 -27
  143. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +46 -13
  144. endoreg_db/models/media/video/video_file_io.py +85 -33
  145. endoreg_db/models/media/video/video_file_meta/__init__.py +6 -6
  146. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +17 -4
  147. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +28 -7
  148. endoreg_db/models/media/video/video_file_meta/get_fps.py +46 -13
  149. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +81 -20
  150. endoreg_db/models/media/video/video_file_meta/text_meta.py +61 -20
  151. endoreg_db/models/media/video/video_file_meta/video_meta.py +40 -12
  152. endoreg_db/models/media/video/video_file_segments.py +118 -27
  153. endoreg_db/models/media/video/video_metadata.py +25 -6
  154. endoreg_db/models/media/video/video_processing.py +54 -15
  155. endoreg_db/models/medical/__init__.py +3 -13
  156. endoreg_db/models/medical/contraindication/__init__.py +3 -1
  157. endoreg_db/models/medical/disease.py +18 -6
  158. endoreg_db/models/medical/event.py +6 -2
  159. endoreg_db/models/medical/examination/__init__.py +5 -1
  160. endoreg_db/models/medical/examination/examination.py +22 -6
  161. endoreg_db/models/medical/examination/examination_indication.py +23 -7
  162. endoreg_db/models/medical/examination/examination_time.py +6 -2
  163. endoreg_db/models/medical/finding/__init__.py +3 -1
  164. endoreg_db/models/medical/finding/finding.py +37 -12
  165. endoreg_db/models/medical/finding/finding_classification.py +27 -8
  166. endoreg_db/models/medical/finding/finding_intervention.py +19 -6
  167. endoreg_db/models/medical/finding/finding_type.py +3 -1
  168. endoreg_db/models/medical/hardware/__init__.py +1 -1
  169. endoreg_db/models/medical/hardware/endoscope.py +14 -2
  170. endoreg_db/models/medical/laboratory/__init__.py +1 -1
  171. endoreg_db/models/medical/laboratory/lab_value.py +139 -39
  172. endoreg_db/models/medical/medication/__init__.py +7 -3
  173. endoreg_db/models/medical/medication/medication.py +3 -1
  174. endoreg_db/models/medical/medication/medication_indication.py +3 -1
  175. endoreg_db/models/medical/medication/medication_indication_type.py +11 -3
  176. endoreg_db/models/medical/medication/medication_intake_time.py +3 -1
  177. endoreg_db/models/medical/medication/medication_schedule.py +3 -1
  178. endoreg_db/models/medical/patient/__init__.py +2 -10
  179. endoreg_db/models/medical/patient/medication_examples.py +3 -14
  180. endoreg_db/models/medical/patient/patient_disease.py +17 -5
  181. endoreg_db/models/medical/patient/patient_event.py +12 -4
  182. endoreg_db/models/medical/patient/patient_examination.py +52 -15
  183. endoreg_db/models/medical/patient/patient_examination_indication.py +15 -4
  184. endoreg_db/models/medical/patient/patient_finding.py +105 -29
  185. endoreg_db/models/medical/patient/patient_finding_classification.py +41 -12
  186. endoreg_db/models/medical/patient/patient_finding_intervention.py +11 -3
  187. endoreg_db/models/medical/patient/patient_lab_sample.py +6 -2
  188. endoreg_db/models/medical/patient/patient_lab_value.py +42 -10
  189. endoreg_db/models/medical/patient/patient_medication.py +25 -7
  190. endoreg_db/models/medical/patient/patient_medication_schedule.py +34 -10
  191. endoreg_db/models/metadata/model_meta.py +40 -12
  192. endoreg_db/models/metadata/model_meta_logic.py +51 -16
  193. endoreg_db/models/metadata/sensitive_meta.py +65 -28
  194. endoreg_db/models/metadata/sensitive_meta_logic.py +28 -26
  195. endoreg_db/models/metadata/video_meta.py +146 -39
  196. endoreg_db/models/metadata/video_prediction_logic.py +70 -21
  197. endoreg_db/models/metadata/video_prediction_meta.py +80 -27
  198. endoreg_db/models/operation_log.py +63 -0
  199. endoreg_db/models/other/__init__.py +10 -10
  200. endoreg_db/models/other/distribution/__init__.py +9 -7
  201. endoreg_db/models/other/distribution/base_value_distribution.py +3 -1
  202. endoreg_db/models/other/distribution/date_value_distribution.py +19 -5
  203. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +3 -1
  204. endoreg_db/models/other/distribution/numeric_value_distribution.py +34 -9
  205. endoreg_db/models/other/emission/__init__.py +1 -1
  206. endoreg_db/models/other/emission/emission_factor.py +9 -3
  207. endoreg_db/models/other/information_source.py +15 -5
  208. endoreg_db/models/other/material.py +3 -1
  209. endoreg_db/models/other/transport_route.py +3 -1
  210. endoreg_db/models/other/unit.py +6 -2
  211. endoreg_db/models/report/report.py +0 -1
  212. endoreg_db/models/requirement/requirement.py +84 -27
  213. endoreg_db/models/requirement/requirement_error.py +5 -6
  214. endoreg_db/models/requirement/requirement_evaluation/__init__.py +1 -1
  215. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +8 -8
  216. endoreg_db/models/requirement/requirement_evaluation/get_values.py +3 -3
  217. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +24 -8
  218. endoreg_db/models/requirement/requirement_operator.py +28 -8
  219. endoreg_db/models/requirement/requirement_set.py +34 -11
  220. endoreg_db/models/state/__init__.py +1 -0
  221. endoreg_db/models/state/audit_ledger.py +9 -2
  222. endoreg_db/models/{media → state}/processing_history/__init__.py +1 -3
  223. endoreg_db/models/state/processing_history/processing_history.py +136 -0
  224. endoreg_db/models/state/raw_pdf.py +0 -1
  225. endoreg_db/models/state/video.py +2 -3
  226. endoreg_db/models/utils.py +4 -2
  227. endoreg_db/queries/__init__.py +2 -6
  228. endoreg_db/queries/annotations/__init__.py +1 -3
  229. endoreg_db/queries/annotations/legacy.py +37 -26
  230. endoreg_db/root_urls.py +3 -4
  231. endoreg_db/schemas/examination_evaluation.py +3 -0
  232. endoreg_db/serializers/Frames_NICE_and_PARIS_classifications.py +249 -163
  233. endoreg_db/serializers/__init__.py +2 -8
  234. endoreg_db/serializers/administration/__init__.py +1 -2
  235. endoreg_db/serializers/administration/ai/__init__.py +0 -1
  236. endoreg_db/serializers/administration/ai/active_model.py +3 -1
  237. endoreg_db/serializers/administration/ai/ai_model.py +5 -3
  238. endoreg_db/serializers/administration/ai/model_type.py +3 -1
  239. endoreg_db/serializers/administration/center.py +7 -2
  240. endoreg_db/serializers/administration/gender.py +4 -2
  241. endoreg_db/serializers/anonymization.py +13 -13
  242. endoreg_db/serializers/evaluation/examination_evaluation.py +0 -1
  243. endoreg_db/serializers/examination/__init__.py +1 -1
  244. endoreg_db/serializers/examination/base.py +12 -13
  245. endoreg_db/serializers/examination/dropdown.py +6 -7
  246. endoreg_db/serializers/examination_serializer.py +3 -6
  247. endoreg_db/serializers/finding/__init__.py +1 -1
  248. endoreg_db/serializers/finding/finding.py +14 -7
  249. endoreg_db/serializers/finding_classification/__init__.py +3 -3
  250. endoreg_db/serializers/finding_classification/choice.py +3 -3
  251. endoreg_db/serializers/finding_classification/classification.py +2 -4
  252. endoreg_db/serializers/label_video_segment/__init__.py +5 -3
  253. endoreg_db/serializers/{label → label_video_segment}/image_classification_annotation.py +5 -5
  254. endoreg_db/serializers/label_video_segment/label/__init__.py +6 -0
  255. endoreg_db/serializers/{label → label_video_segment/label}/label.py +1 -1
  256. endoreg_db/serializers/label_video_segment/label_video_segment.py +338 -228
  257. endoreg_db/serializers/meta/__init__.py +1 -2
  258. endoreg_db/serializers/meta/sensitive_meta_detail.py +28 -13
  259. endoreg_db/serializers/meta/sensitive_meta_update.py +51 -46
  260. endoreg_db/serializers/meta/sensitive_meta_verification.py +19 -16
  261. endoreg_db/serializers/misc/__init__.py +2 -2
  262. endoreg_db/serializers/misc/file_overview.py +11 -7
  263. endoreg_db/serializers/misc/stats.py +10 -8
  264. endoreg_db/serializers/misc/translatable_field_mix_in.py +6 -6
  265. endoreg_db/serializers/misc/upload_job.py +32 -29
  266. endoreg_db/serializers/patient/__init__.py +2 -1
  267. endoreg_db/serializers/patient/patient.py +32 -15
  268. endoreg_db/serializers/patient/patient_dropdown.py +11 -3
  269. endoreg_db/serializers/patient_examination/__init__.py +1 -1
  270. endoreg_db/serializers/patient_examination/patient_examination.py +67 -40
  271. endoreg_db/serializers/patient_finding/__init__.py +1 -1
  272. endoreg_db/serializers/patient_finding/patient_finding.py +2 -1
  273. endoreg_db/serializers/patient_finding/patient_finding_classification.py +17 -9
  274. endoreg_db/serializers/patient_finding/patient_finding_detail.py +26 -17
  275. endoreg_db/serializers/patient_finding/patient_finding_intervention.py +7 -5
  276. endoreg_db/serializers/patient_finding/patient_finding_list.py +10 -11
  277. endoreg_db/serializers/patient_finding/patient_finding_write.py +36 -27
  278. endoreg_db/serializers/pdf/__init__.py +1 -3
  279. endoreg_db/serializers/requirements/requirement_schema.py +1 -6
  280. endoreg_db/serializers/sensitive_meta_serializer.py +100 -81
  281. endoreg_db/serializers/video/__init__.py +2 -2
  282. endoreg_db/serializers/video/{segmentation.py → video_file.py} +66 -47
  283. endoreg_db/serializers/video/video_file_brief.py +6 -2
  284. endoreg_db/serializers/video/video_file_detail.py +36 -23
  285. endoreg_db/serializers/video/video_file_list.py +4 -2
  286. endoreg_db/serializers/video/video_processing_history.py +54 -50
  287. endoreg_db/services/__init__.py +1 -1
  288. endoreg_db/services/anonymization.py +2 -2
  289. endoreg_db/services/examination_evaluation.py +40 -17
  290. endoreg_db/services/model_meta_from_hf.py +76 -0
  291. endoreg_db/services/polling_coordinator.py +101 -70
  292. endoreg_db/services/pseudonym_service.py +27 -22
  293. endoreg_db/services/report_import.py +6 -3
  294. endoreg_db/services/segment_sync.py +75 -59
  295. endoreg_db/services/video_import.py +6 -7
  296. endoreg_db/urls/__init__.py +2 -2
  297. endoreg_db/urls/ai.py +7 -25
  298. endoreg_db/urls/anonymization.py +61 -15
  299. endoreg_db/urls/auth.py +4 -4
  300. endoreg_db/urls/classification.py +4 -9
  301. endoreg_db/urls/examination.py +27 -18
  302. endoreg_db/urls/media.py +27 -34
  303. endoreg_db/urls/patient.py +11 -7
  304. endoreg_db/urls/requirements.py +3 -1
  305. endoreg_db/urls/root_urls.py +2 -3
  306. endoreg_db/urls/stats.py +24 -16
  307. endoreg_db/urls/upload.py +3 -11
  308. endoreg_db/utils/__init__.py +14 -15
  309. endoreg_db/utils/ai/__init__.py +1 -1
  310. endoreg_db/utils/ai/data_loader_for_model_input.py +262 -0
  311. endoreg_db/utils/ai/data_loader_for_model_training.py +262 -0
  312. endoreg_db/utils/ai/get.py +2 -1
  313. endoreg_db/utils/ai/inference_dataset.py +14 -15
  314. endoreg_db/utils/ai/model_training/config.py +117 -0
  315. endoreg_db/utils/ai/model_training/dataset.py +74 -0
  316. endoreg_db/utils/ai/model_training/losses.py +68 -0
  317. endoreg_db/utils/ai/model_training/metrics.py +78 -0
  318. endoreg_db/utils/ai/model_training/model_backbones.py +155 -0
  319. endoreg_db/utils/ai/model_training/model_gastronet_resnet.py +118 -0
  320. endoreg_db/utils/ai/model_training/trainer_gastronet_multilabel.py +771 -0
  321. endoreg_db/utils/ai/multilabel_classification_net.py +21 -6
  322. endoreg_db/utils/ai/predict.py +4 -4
  323. endoreg_db/utils/ai/preprocess.py +19 -11
  324. endoreg_db/utils/calc_duration_seconds.py +4 -4
  325. endoreg_db/utils/case_generator/lab_sample_factory.py +3 -4
  326. endoreg_db/utils/check_video_files.py +74 -47
  327. endoreg_db/utils/cropping.py +10 -9
  328. endoreg_db/utils/dataloader.py +11 -3
  329. endoreg_db/utils/dates.py +3 -4
  330. endoreg_db/utils/defaults/set_default_center.py +7 -6
  331. endoreg_db/utils/env.py +6 -2
  332. endoreg_db/utils/extract_specific_frames.py +24 -9
  333. endoreg_db/utils/file_operations.py +30 -18
  334. endoreg_db/utils/fix_video_path_direct.py +57 -41
  335. endoreg_db/utils/frame_anonymization_utils.py +157 -157
  336. endoreg_db/utils/hashs.py +3 -18
  337. endoreg_db/utils/links/requirement_link.py +96 -52
  338. endoreg_db/utils/ocr.py +30 -25
  339. endoreg_db/utils/operation_log.py +61 -0
  340. endoreg_db/utils/parse_and_generate_yaml.py +12 -13
  341. endoreg_db/utils/paths.py +6 -6
  342. endoreg_db/utils/permissions.py +40 -24
  343. endoreg_db/utils/pipelines/process_video_dir.py +50 -26
  344. endoreg_db/utils/product/sum_emissions.py +5 -3
  345. endoreg_db/utils/product/sum_weights.py +4 -2
  346. endoreg_db/utils/pydantic_models/__init__.py +3 -4
  347. endoreg_db/utils/requirement_operator_logic/_old/lab_value_operators.py +207 -107
  348. endoreg_db/utils/requirement_operator_logic/_old/model_evaluators.py +252 -65
  349. endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +27 -10
  350. endoreg_db/utils/setup_config.py +21 -5
  351. endoreg_db/utils/storage.py +3 -1
  352. endoreg_db/utils/translation.py +19 -15
  353. endoreg_db/utils/uuid.py +1 -0
  354. endoreg_db/utils/validate_endo_roi.py +12 -4
  355. endoreg_db/utils/validate_subcategory_dict.py +26 -24
  356. endoreg_db/utils/validate_video_detailed.py +207 -149
  357. endoreg_db/utils/video/__init__.py +7 -3
  358. endoreg_db/utils/video/extract_frames.py +30 -18
  359. endoreg_db/utils/video/ffmpeg_wrapper.py +217 -52
  360. endoreg_db/utils/video/names.py +11 -6
  361. endoreg_db/utils/video/streaming_processor.py +175 -101
  362. endoreg_db/utils/video/video_splitter.py +30 -19
  363. endoreg_db/views/Frames_NICE_and_PARIS_classifications_views.py +59 -50
  364. endoreg_db/views/__init__.py +0 -20
  365. endoreg_db/views/anonymization/__init__.py +6 -2
  366. endoreg_db/views/anonymization/media_management.py +2 -6
  367. endoreg_db/views/anonymization/overview.py +34 -1
  368. endoreg_db/views/anonymization/validate.py +79 -18
  369. endoreg_db/views/auth/__init__.py +1 -1
  370. endoreg_db/views/auth/keycloak.py +16 -14
  371. endoreg_db/views/examination/__init__.py +12 -15
  372. endoreg_db/views/examination/examination.py +5 -5
  373. endoreg_db/views/examination/examination_manifest_cache.py +5 -5
  374. endoreg_db/views/examination/get_finding_classification_choices.py +8 -5
  375. endoreg_db/views/examination/get_finding_classifications.py +9 -7
  376. endoreg_db/views/examination/get_findings.py +8 -10
  377. endoreg_db/views/examination/get_instruments.py +3 -2
  378. endoreg_db/views/examination/get_interventions.py +1 -1
  379. endoreg_db/views/finding/__init__.py +2 -2
  380. endoreg_db/views/finding/finding.py +58 -54
  381. endoreg_db/views/finding/get_classifications.py +1 -1
  382. endoreg_db/views/finding/get_interventions.py +1 -1
  383. endoreg_db/views/finding_classification/__init__.py +5 -5
  384. endoreg_db/views/finding_classification/finding_classification.py +5 -6
  385. endoreg_db/views/finding_classification/get_classification_choices.py +3 -4
  386. endoreg_db/views/media/__init__.py +13 -13
  387. endoreg_db/views/media/pdf_media.py +9 -9
  388. endoreg_db/views/media/sensitive_metadata.py +10 -7
  389. endoreg_db/views/media/video_media.py +4 -4
  390. endoreg_db/views/meta/__init__.py +1 -1
  391. endoreg_db/views/meta/sensitive_meta_list.py +20 -22
  392. endoreg_db/views/meta/sensitive_meta_verification.py +14 -11
  393. endoreg_db/views/misc/__init__.py +6 -34
  394. endoreg_db/views/misc/center.py +2 -1
  395. endoreg_db/views/misc/csrf.py +2 -1
  396. endoreg_db/views/misc/gender.py +2 -1
  397. endoreg_db/views/misc/stats.py +141 -106
  398. endoreg_db/views/patient/__init__.py +1 -3
  399. endoreg_db/views/patient/patient.py +141 -99
  400. endoreg_db/views/patient_examination/__init__.py +5 -5
  401. endoreg_db/views/patient_examination/patient_examination.py +43 -42
  402. endoreg_db/views/patient_examination/patient_examination_create.py +10 -15
  403. endoreg_db/views/patient_examination/patient_examination_detail.py +12 -15
  404. endoreg_db/views/patient_examination/patient_examination_list.py +21 -17
  405. endoreg_db/views/patient_examination/video.py +114 -80
  406. endoreg_db/views/patient_finding/__init__.py +1 -1
  407. endoreg_db/views/patient_finding/patient_finding.py +17 -10
  408. endoreg_db/views/patient_finding/patient_finding_optimized.py +127 -95
  409. endoreg_db/views/patient_finding_classification/__init__.py +1 -1
  410. endoreg_db/views/patient_finding_classification/pfc_create.py +35 -27
  411. endoreg_db/views/report/reimport.py +1 -1
  412. endoreg_db/views/report/report_stream.py +5 -8
  413. endoreg_db/views/requirement/__init__.py +2 -1
  414. endoreg_db/views/requirement/evaluate.py +7 -9
  415. endoreg_db/views/requirement/lookup.py +2 -3
  416. endoreg_db/views/requirement/lookup_store.py +0 -1
  417. endoreg_db/views/requirement/requirement_utils.py +2 -4
  418. endoreg_db/views/stats/__init__.py +4 -4
  419. endoreg_db/views/stats/stats_views.py +152 -115
  420. endoreg_db/views/video/__init__.py +18 -27
  421. endoreg_db/views/{ai → video/ai}/__init__.py +2 -2
  422. endoreg_db/views/{ai → video/ai}/label.py +20 -16
  423. endoreg_db/views/video/correction.py +5 -6
  424. endoreg_db/views/video/reimport.py +134 -99
  425. endoreg_db/views/video/segments_crud.py +134 -44
  426. endoreg_db/views/video/video_apply_mask.py +13 -12
  427. endoreg_db/views/video/video_correction.py +2 -1
  428. endoreg_db/views/video/video_download_processed.py +15 -15
  429. endoreg_db/views/video/video_meta_stats.py +7 -6
  430. endoreg_db/views/video/video_processing_history.py +3 -2
  431. endoreg_db/views/video/video_remove_frames.py +13 -12
  432. endoreg_db/views/video/video_stream.py +110 -82
  433. {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/METADATA +9 -3
  434. {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/RECORD +436 -433
  435. endoreg_db/import_files/processing/video_processing/video_cleanup_on_error.py +0 -119
  436. endoreg_db/management/commands/import_fallback_video.py +0 -203
  437. endoreg_db/management/commands/import_video.py +0 -422
  438. endoreg_db/management/commands/import_video_with_classification.py +0 -367
  439. endoreg_db/models/media/processing_history/processing_history.py +0 -96
  440. endoreg_db/serializers/label/__init__.py +0 -7
  441. endoreg_db/serializers/label_video_segment/_lvs_create.py +0 -149
  442. endoreg_db/serializers/label_video_segment/_lvs_update.py +0 -138
  443. endoreg_db/serializers/label_video_segment/_lvs_validate.py +0 -149
  444. endoreg_db/serializers/label_video_segment/label_video_segment_annotation.py +0 -99
  445. endoreg_db/serializers/label_video_segment/label_video_segment_update.py +0 -163
  446. endoreg_db/services/__old/pdf_import.py +0 -1487
  447. endoreg_db/services/__old/video_import.py +0 -1306
  448. endoreg_db/tasks/upload_tasks.py +0 -216
  449. endoreg_db/tasks/video_ingest.py +0 -161
  450. endoreg_db/tasks/video_processing_tasks.py +0 -327
  451. endoreg_db/views/misc/translation.py +0 -182
  452. {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/WHEEL +0 -0
  453. {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,17 @@
1
1
  # app/services/evaluation.py
2
2
  from __future__ import annotations
3
- from typing import Dict, List, Set, Tuple
4
- from django.db.models import Prefetch
3
+ from typing import List, Set, Tuple
5
4
 
6
- from endoreg_db.schemas.examination_evaluation import ExaminationEvalReport, RequirementSetEval, RequirementEval
5
+ from endoreg_db.schemas.examination_evaluation import (
6
+ ExaminationEvalReport,
7
+ RequirementSetEval,
8
+ RequirementEval,
9
+ )
7
10
  from endoreg_db.models.medical.patient.patient_examination import PatientExamination
8
11
  from endoreg_db.models.requirement.requirement_set import RequirementSet
9
12
  import endoreg_db.services.lookup_service
10
13
 
14
+
11
15
  def _get_requirement_sets_for_exam(exam: PatientExamination) -> List[RequirementSet]:
12
16
  """
13
17
  Decide how an examination maps to requirement sets.
@@ -25,7 +29,10 @@ def _get_requirement_sets_for_exam(exam: PatientExamination) -> List[Requirement
25
29
  sets.append(link.requirement_set)
26
30
  return sets
27
31
 
28
- def _eval_requirement(requirement, input_object, mode="loose") -> Tuple[bool, str | None]:
32
+
33
+ def _eval_requirement(
34
+ requirement, input_object, mode="loose"
35
+ ) -> Tuple[bool, str | None]:
29
36
  """
30
37
  Evaluate a single Requirement and return (bool, message).
31
38
  The `message` can be None or contain a short explanation.
@@ -36,12 +43,19 @@ def _eval_requirement(requirement, input_object, mode="loose") -> Tuple[bool, st
36
43
  # Example: msg = requirement.last_reason if hasattr(requirement, "last_reason") else None
37
44
  return ok, msg
38
45
 
46
+
39
47
  def _reduce_bools(bools: List[bool], set_type_name: str | None) -> bool:
40
- from endoreg_db.models.requirement.requirement_set import REQUIREMENT_SET_TYPE_FUNCTION_LOOKUP
48
+ from endoreg_db.models.requirement.requirement_set import (
49
+ REQUIREMENT_SET_TYPE_FUNCTION_LOOKUP,
50
+ )
51
+
41
52
  func = REQUIREMENT_SET_TYPE_FUNCTION_LOOKUP.get(set_type_name or "all", all)
42
53
  return bool(func(bools))
43
54
 
44
- def _eval_set_tree(root: RequirementSet, input_object, visited: Set[int]) -> RequirementSetEval:
55
+
56
+ def _eval_set_tree(
57
+ root: RequirementSet, input_object, visited: Set[int]
58
+ ) -> RequirementSetEval:
45
59
  """
46
60
  Recursively evaluate a RequirementSet node and linked children.
47
61
  Protect against cycles with visited set.
@@ -52,10 +66,12 @@ def _eval_set_tree(root: RequirementSet, input_object, visited: Set[int]) -> Req
52
66
  return RequirementSetEval(
53
67
  id=root.pk,
54
68
  name=root.name,
55
- type=(root.requirement_set_type.name if root.requirement_set_type else None),
69
+ type=(
70
+ root.requirement_set_type.name if root.requirement_set_type else None
71
+ ),
56
72
  is_satisfied=True,
57
73
  requirements=[],
58
- linked_sets=[]
74
+ linked_sets=[],
59
75
  )
60
76
 
61
77
  visited.add(root.pk)
@@ -81,7 +97,9 @@ def _eval_set_tree(root: RequirementSet, input_object, visited: Set[int]) -> Req
81
97
 
82
98
  # Combine booleans
83
99
  bools = [re.satisfied for re in req_evals] + [ce.is_satisfied for ce in child_evals]
84
- set_type_name = root.requirement_set_type.name if root.requirement_set_type else "all"
100
+ set_type_name = (
101
+ root.requirement_set_type.name if root.requirement_set_type else "all"
102
+ )
85
103
  satisfied = _reduce_bools(bools, set_type_name)
86
104
 
87
105
  return RequirementSetEval(
@@ -92,20 +110,19 @@ def _eval_set_tree(root: RequirementSet, input_object, visited: Set[int]) -> Req
92
110
  requirements=req_evals,
93
111
  linked_sets=child_evals,
94
112
  )
95
-
96
113
 
97
114
 
98
115
  def evaluate_examination(request: dict) -> ExaminationEvalReport:
99
116
  """
100
117
  Communicates with: components/RequirementGenerator
101
118
  Evaluates a PatientExamination by its Lookup. The frontend sends this structure:
102
-
103
-
119
+
120
+
104
121
  requirement_set_ids: plainRequirementSetIds,
105
122
  lookup_token: lookupToken,
106
123
  patient_examination_id: patientExaminationId
107
124
  };
108
-
125
+
109
126
  And expects a response to be processed like this:
110
127
 
111
128
  const response = await axiosInstance.post('/api/evaluate-requirements/', payload);
@@ -125,9 +142,9 @@ def evaluate_examination(request: dict) -> ExaminationEvalReport:
125
142
  examination_id=None,
126
143
  summary={"is_satisfied": True, "failed_count": 0, "total_sets": 0},
127
144
  sets=[],
128
- errors=["No patient_examination_id provided in request."]
145
+ errors=["No patient_examination_id provided in request."],
129
146
  )
130
-
147
+
131
148
  # Use the dedicated loader function from the lookup service.
132
149
  exam = endoreg_db.services.lookup_service.load_patient_exam_for_eval(pk=exam_id)
133
150
 
@@ -135,7 +152,9 @@ def evaluate_examination(request: dict) -> ExaminationEvalReport:
135
152
  sets = _get_requirement_sets_for_exam(exam)
136
153
 
137
154
  visited: Set[int] = set()
138
- set_evals: List[RequirementSetEval] = [_eval_set_tree(s, exam, visited) for s in sets]
155
+ set_evals: List[RequirementSetEval] = [
156
+ _eval_set_tree(s, exam, visited) for s in sets
157
+ ]
139
158
 
140
159
  # Aggregate summary
141
160
  overall = all(se.is_satisfied for se in set_evals) if set_evals else True
@@ -143,7 +162,11 @@ def evaluate_examination(request: dict) -> ExaminationEvalReport:
143
162
 
144
163
  return ExaminationEvalReport(
145
164
  examination_id=exam.pk,
146
- summary={"is_satisfied": overall, "failed_count": failed, "total_sets": len(set_evals)},
165
+ summary={
166
+ "is_satisfied": overall,
167
+ "failed_count": failed,
168
+ "total_sets": len(set_evals),
169
+ },
147
170
  sets=set_evals,
148
171
  errors=[], # fill with any global issues you detect
149
172
  )
@@ -0,0 +1,76 @@
1
+ # endoreg_db/services/model_meta_from_hf.py
2
+
3
+ from django.core.files.base import ContentFile
4
+ from huggingface_hub import hf_hub_download
5
+
6
+ from endoreg_db.models import AiModel, LabelSet, ModelMeta
7
+
8
+
9
+ def ensure_model_meta_from_hf(
10
+ *,
11
+ model_id: str,
12
+ model_name: str,
13
+ labelset_name: str,
14
+ meta_version: str = "1",
15
+ labelset_version: int | None = None,
16
+ ) -> ModelMeta:
17
+ """
18
+ Download weights from Hugging Face (if needed) and ensure a ModelMeta
19
+ exists for the given configuration. Returns the ModelMeta.
20
+ """
21
+ # Download the model weights
22
+ weights_path = hf_hub_download(
23
+ repo_id=model_id,
24
+ filename="colo_segmentation_RegNetX800MF_base.safetensors",
25
+ local_dir="/tmp",
26
+ )
27
+
28
+ # Get or create AI model
29
+ ai_model, _ = AiModel.objects.get_or_create(
30
+ name=model_name, defaults={"description": f"Model from {model_id}"}
31
+ )
32
+
33
+ # Get labelset
34
+ labelset_qs = LabelSet.objects.filter(name=labelset_name)
35
+ if labelset_version is not None:
36
+ labelset_qs = labelset_qs.filter(version=labelset_version)
37
+ labelset = labelset_qs.order_by("-version").first()
38
+ if labelset is None:
39
+ raise ValueError(
40
+ f"LabelSet '{labelset_name}'"
41
+ + (f" v{labelset_version}" if labelset_version is not None else "")
42
+ + " not found"
43
+ )
44
+
45
+ # Create or get ModelMeta
46
+ model_meta, _ = ModelMeta.objects.get_or_create(
47
+ name=model_name,
48
+ model=ai_model,
49
+ version=meta_version,
50
+ defaults={
51
+ "labelset": labelset,
52
+ "activation": "sigmoid",
53
+ "mean": "0.45211223,0.27139644,0.19264949",
54
+ "std": "0.31418097,0.21088019,0.16059452",
55
+ "size_x": 716,
56
+ "size_y": 716,
57
+ "axes": "2,0,1",
58
+ "batchsize": 16,
59
+ "num_workers": 0,
60
+ "description": f"Downloaded from {model_id}",
61
+ },
62
+ )
63
+
64
+ # If weights file not yet saved, save it
65
+ if not model_meta.weights:
66
+ with open(weights_path, "rb") as f:
67
+ model_meta.weights.save(
68
+ f"{model_name}_v{meta_version}.safetensors",
69
+ ContentFile(f.read()),
70
+ )
71
+
72
+ # Set as active meta
73
+ ai_model.active_meta = model_meta
74
+ ai_model.save(update_fields=["active_meta"])
75
+
76
+ return model_meta
@@ -9,50 +9,57 @@ from datetime import timedelta
9
9
 
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
+
12
13
  class PollingCoordinator:
13
14
  """
14
15
  Service to prevent duplicate polling operations on the same media items.
15
16
  Uses Django cache and thread-safe operations to coordinate polling requests.
16
17
  """
17
-
18
+
18
19
  # Class-level lock for thread safety
19
20
  _lock = threading.Lock()
20
-
21
+
21
22
  # Cache key prefixes
22
23
  PROCESSING_PREFIX = "polling_processing:"
23
24
  LAST_CHECK_PREFIX = "polling_last_check:"
24
-
25
+
25
26
  # Default timeouts
26
27
  PROCESSING_TIMEOUT = 300 # 5 minutes
27
- CHECK_COOLDOWN = 10 # 10 seconds minimum between checks
28
-
28
+ CHECK_COOLDOWN = 10 # 10 seconds minimum between checks
29
+
29
30
  @classmethod
30
- def acquire_processing_lock(cls, file_id: int, file_type: str = "video", timeout: Optional[int] = None) -> bool:
31
+ def acquire_processing_lock(
32
+ cls, file_id: int, file_type: str = "video", timeout: Optional[int] = None
33
+ ) -> bool:
31
34
  """
32
35
  Acquire a processing lock for a media file to prevent duplicate processing.
33
-
36
+
34
37
  Args:
35
38
  file_id: ID of the media file
36
39
  file_type: Type of media (video, pdf)
37
40
  timeout: Lock timeout in seconds (default: 5 minutes)
38
-
41
+
39
42
  Returns:
40
43
  True if lock acquired, False if already locked
41
44
  """
42
45
  if timeout is None:
43
46
  timeout = cls.PROCESSING_TIMEOUT
44
-
47
+
45
48
  cache_key = f"{cls.PROCESSING_PREFIX}{file_type}:{file_id}"
46
-
49
+
47
50
  with cls._lock:
48
51
  # Try to acquire lock atomically
49
- lock_acquired = cache.add(cache_key, {
50
- "locked_at": timezone.now().isoformat(),
51
- "file_id": file_id,
52
- "file_type": file_type,
53
- "thread_id": threading.get_ident()
54
- }, timeout)
55
-
52
+ lock_acquired = cache.add(
53
+ cache_key,
54
+ {
55
+ "locked_at": timezone.now().isoformat(),
56
+ "file_id": file_id,
57
+ "file_type": file_type,
58
+ "thread_id": threading.get_ident(),
59
+ },
60
+ timeout,
61
+ )
62
+
56
63
  if lock_acquired:
57
64
  logger.info(f"Processing lock acquired for {file_type}:{file_id}")
58
65
  return True
@@ -60,102 +67,116 @@ class PollingCoordinator:
60
67
  # Check if existing lock is stale
61
68
  existing_lock = cache.get(cache_key)
62
69
  if existing_lock:
63
- logger.warning(f"Processing lock already exists for {file_type}:{file_id}: {existing_lock}")
70
+ logger.warning(
71
+ f"Processing lock already exists for {file_type}:{file_id}: {existing_lock}"
72
+ )
64
73
  else:
65
- logger.warning(f"Failed to acquire processing lock for {file_type}:{file_id}")
74
+ logger.warning(
75
+ f"Failed to acquire processing lock for {file_type}:{file_id}"
76
+ )
66
77
  return False
67
-
78
+
68
79
  @classmethod
69
80
  def release_processing_lock(cls, file_id: int, file_type: str = "video") -> bool:
70
81
  """
71
82
  Release a processing lock for a media file.
72
-
83
+
73
84
  Args:
74
85
  file_id: ID of the media file
75
86
  file_type: Type of media (video, pdf)
76
-
87
+
77
88
  Returns:
78
89
  True if lock released, False if lock didn't exist
79
90
  """
80
91
  cache_key = f"{cls.PROCESSING_PREFIX}{file_type}:{file_id}"
81
-
92
+
82
93
  with cls._lock:
83
94
  if cache.delete(cache_key):
84
95
  logger.info(f"Processing lock released for {file_type}:{file_id}")
85
96
  return True
86
97
  else:
87
- logger.warning(f"No processing lock found to release for {file_type}:{file_id}")
98
+ logger.warning(
99
+ f"No processing lock found to release for {file_type}:{file_id}"
100
+ )
88
101
  return False
89
-
102
+
90
103
  @classmethod
91
104
  def is_processing_locked(cls, file_id: int, file_type: str = "video") -> bool:
92
105
  """
93
106
  Check if a media file is currently processing locked.
94
-
107
+
95
108
  Args:
96
109
  file_id: ID of the media file
97
110
  file_type: Type of media (video, pdf)
98
-
111
+
99
112
  Returns:
100
113
  True if locked, False otherwise
101
114
  """
102
115
  cache_key = f"{cls.PROCESSING_PREFIX}{file_type}:{file_id}"
103
116
  return cache.get(cache_key) is not None
104
-
117
+
105
118
  @classmethod
106
119
  def can_check_status(cls, file_id: int, file_type: str = "video") -> bool:
107
120
  """
108
121
  Check if enough time has passed since last status check to prevent spam.
109
-
122
+
110
123
  Args:
111
124
  file_id: ID of the media file
112
125
  file_type: Type of media (video, pdf)
113
-
126
+
114
127
  Returns:
115
128
  True if status check is allowed, False if still in cooldown
116
129
  """
117
130
  cache_key = f"{cls.LAST_CHECK_PREFIX}{file_type}:{file_id}"
118
131
  last_check = cache.get(cache_key)
119
-
132
+
120
133
  if last_check is None:
121
134
  # First check or cooldown expired - allowed
122
135
  cls._record_status_check(file_id, file_type)
123
136
  return True
124
-
137
+
125
138
  # Check if cooldown period has passed
126
- last_check_time = timezone.datetime.fromisoformat(last_check.replace('Z', '+00:00'))
139
+ last_check_time = timezone.datetime.fromisoformat(
140
+ last_check.replace("Z", "+00:00")
141
+ )
127
142
  cooldown_end = last_check_time + timedelta(seconds=cls.CHECK_COOLDOWN)
128
-
143
+
129
144
  if timezone.now() > cooldown_end:
130
145
  cls._record_status_check(file_id, file_type)
131
146
  return True
132
147
  else:
133
148
  remaining_cooldown = (cooldown_end - timezone.now()).total_seconds()
134
- logger.debug(f"Status check cooldown active for {file_type}:{file_id}, {remaining_cooldown:.1f}s remaining")
149
+ logger.debug(
150
+ f"Status check cooldown active for {file_type}:{file_id}, {remaining_cooldown:.1f}s remaining"
151
+ )
135
152
  return False
136
-
153
+
137
154
  @classmethod
138
- def get_remaining_cooldown_seconds(cls, file_id: int, file_type: str = "video") -> int:
155
+ def get_remaining_cooldown_seconds(
156
+ cls, file_id: int, file_type: str = "video"
157
+ ) -> int:
139
158
  """
140
159
  Get the remaining cooldown seconds for a status check.
141
-
160
+
142
161
  Args:
143
162
  file_id: ID of the media file
144
163
  file_type: Type of media (video, pdf)
145
-
164
+
146
165
  Returns:
147
166
  Remaining cooldown in seconds (0 if no cooldown active)
148
167
  """
149
168
  cache_key = f"{cls.LAST_CHECK_PREFIX}{file_type}:{file_id}"
150
169
  last_check = cache.get(cache_key)
151
-
170
+
152
171
  if last_check is None:
153
172
  return 0
154
-
173
+
155
174
  # Check if cooldown period has passed
156
- last_check_time = timezone.datetime.fromisoformat(last_check.replace('Z', '+00:00'))
175
+ last_check_time = timezone.datetime.fromisoformat(
176
+ last_check.replace("Z", "+00:00")
177
+ )
157
178
  cooldown_end = last_check_time + timedelta(seconds=cls.CHECK_COOLDOWN)
158
-
179
+
159
180
  if timezone.now() > cooldown_end:
160
181
  return 0
161
182
  else:
@@ -168,47 +189,49 @@ class PollingCoordinator:
168
189
  """Record the time of a status check"""
169
190
  cache_key = f"{cls.LAST_CHECK_PREFIX}{file_type}:{file_id}"
170
191
  cache.set(cache_key, timezone.now().isoformat(), cls.CHECK_COOLDOWN + 5)
171
-
192
+
172
193
  @classmethod
173
194
  def get_processing_locks_info(cls) -> Dict[str, any]:
174
195
  """
175
196
  Get information about all currently active processing locks.
176
197
  Useful for debugging and monitoring.
177
-
198
+
178
199
  Returns:
179
200
  Dictionary with lock information
180
201
  """
181
202
  # Note: This is a simplified version since Django cache doesn't support pattern scanning
182
203
  # In production, consider using Redis with SCAN command for better performance
183
-
204
+
184
205
  info = {
185
206
  "coordinator_status": "active",
186
207
  "config": {
187
208
  "processing_timeout": cls.PROCESSING_TIMEOUT,
188
- "check_cooldown": cls.CHECK_COOLDOWN
209
+ "check_cooldown": cls.CHECK_COOLDOWN,
189
210
  },
190
- "note": "Active locks info requires Redis backend for pattern scanning"
211
+ "note": "Active locks info requires Redis backend for pattern scanning",
191
212
  }
192
-
213
+
193
214
  return info
194
-
215
+
195
216
  @classmethod
196
217
  def clear_all_locks(cls, file_type: Optional[str] = None) -> int:
197
218
  """
198
219
  Emergency function to clear all processing locks.
199
220
  Use with caution - only for debugging/recovery scenarios.
200
-
221
+
201
222
  Args:
202
223
  file_type: Optionally clear locks only for specific file type
203
-
224
+
204
225
  Returns:
205
226
  Number of locks cleared (approximation)
206
227
  """
207
- logger.warning("clear_all_locks called - this should only be used for debugging/recovery")
208
-
228
+ logger.warning(
229
+ "clear_all_locks called - this should only be used for debugging/recovery"
230
+ )
231
+
209
232
  # This is a simplified implementation
210
233
  # In production with Redis, you'd use SCAN to find and delete matching keys
211
- if hasattr(cache, 'clear'):
234
+ if hasattr(cache, "clear"):
212
235
  cache.clear()
213
236
  return -1 # Unknown count
214
237
  else:
@@ -220,38 +243,44 @@ class PollingCoordinator:
220
243
  def processing_coordination(file_id_param: str = "file_id", file_type: str = "video"):
221
244
  """
222
245
  Decorator to add automatic processing coordination to views.
223
-
246
+
224
247
  Args:
225
248
  file_id_param: Name of the parameter containing file ID
226
249
  file_type: Type of media file
227
250
  """
251
+
228
252
  def decorator(view_func):
229
253
  def wrapper(request, *args, **kwargs):
230
254
  # Extract file_id from kwargs or request
231
255
  file_id = kwargs.get(file_id_param) or request.data.get(file_id_param)
232
-
256
+
233
257
  if file_id is None:
234
- logger.error(f"No {file_id_param} found in request for processing coordination")
258
+ logger.error(
259
+ f"No {file_id_param} found in request for processing coordination"
260
+ )
235
261
  from rest_framework.response import Response
236
262
  from rest_framework import status
263
+
237
264
  return Response(
238
- {"error": f"Missing {file_id_param} parameter"},
239
- status=status.HTTP_400_BAD_REQUEST
265
+ {"error": f"Missing {file_id_param} parameter"},
266
+ status=status.HTTP_400_BAD_REQUEST,
240
267
  )
241
-
268
+
242
269
  # Check if processing is already locked
243
270
  if PollingCoordinator.is_processing_locked(file_id, file_type):
244
271
  from rest_framework.response import Response
245
272
  from rest_framework import status
273
+
246
274
  return Response(
247
- {"detail": "File is currently being processed by another request"},
248
- status=status.HTTP_409_CONFLICT
275
+ {"detail": "File is currently being processed by another request"},
276
+ status=status.HTTP_409_CONFLICT,
249
277
  )
250
-
278
+
251
279
  # Proceed with the view
252
280
  return view_func(request, *args, **kwargs)
253
-
281
+
254
282
  return wrapper
283
+
255
284
  return decorator
256
285
 
257
286
 
@@ -259,7 +288,7 @@ def processing_coordination(file_id_param: str = "file_id", file_type: str = "vi
259
288
  class ProcessingLockContext:
260
289
  """
261
290
  Context manager for automatic processing lock acquisition and release.
262
-
291
+
263
292
  Usage:
264
293
  with ProcessingLockContext(file_id, "video") as lock:
265
294
  if lock.acquired:
@@ -269,19 +298,21 @@ class ProcessingLockContext:
269
298
  # Handle lock acquisition failure
270
299
  pass
271
300
  """
272
-
273
- def __init__(self, file_id: int, file_type: str = "video", timeout: Optional[int] = None):
301
+
302
+ def __init__(
303
+ self, file_id: int, file_type: str = "video", timeout: Optional[int] = None
304
+ ):
274
305
  self.file_id = file_id
275
306
  self.file_type = file_type
276
307
  self.timeout = timeout
277
308
  self.acquired = False
278
-
309
+
279
310
  def __enter__(self):
280
311
  self.acquired = PollingCoordinator.acquire_processing_lock(
281
312
  self.file_id, self.file_type, self.timeout
282
313
  )
283
314
  return self
284
-
315
+
285
316
  def __exit__(self, exc_type, exc_val, exc_tb):
286
317
  if self.acquired:
287
318
  PollingCoordinator.release_processing_lock(self.file_id, self.file_type)