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,6 +1,5 @@
1
1
  from rest_framework import serializers
2
2
  import logging
3
-
4
3
  from ...models import SensitiveMeta
5
4
 
6
5
  logger = logging.getLogger(__name__)
@@ -10,153 +9,99 @@ class SensitiveMetaDetailSerializer(serializers.ModelSerializer):
10
9
  Serializer for displaying SensitiveMeta details with verification state.
11
10
  Includes all relevant fields for annotation and verification.
12
11
  """
13
-
12
+
14
13
  # State verification fields
15
14
  is_verified = serializers.SerializerMethodField()
16
15
  dob_verified = serializers.SerializerMethodField()
17
16
  names_verified = serializers.SerializerMethodField()
18
-
19
- # Related fields for better display
17
+
18
+ # Related fields
20
19
  center_name = serializers.CharField(source="center.name", read_only=True)
21
20
  patient_gender_name = serializers.CharField(source="patient_gender.name", read_only=True)
22
-
23
- # Examiner information
24
21
  examiners_display = serializers.SerializerMethodField()
25
-
26
- # Formatted dates for display
22
+
23
+ # Formatted display fields
27
24
  patient_dob_display = serializers.SerializerMethodField()
28
25
  examination_date_display = serializers.SerializerMethodField()
29
-
30
- # Hash displays (last 8 characters for security)
26
+
27
+ # Hash displays (last 8 chars)
31
28
  patient_hash_display = serializers.SerializerMethodField()
32
29
  examination_hash_display = serializers.SerializerMethodField()
33
30
 
31
+ # Text fields
32
+ text = serializers.SerializerMethodField()
33
+ anonymized_text = serializers.SerializerMethodField()
34
+
34
35
  class Meta:
35
36
  model = SensitiveMeta
36
37
  fields = [
37
- 'id',
38
- 'patient_first_name',
39
- 'patient_last_name',
40
- 'patient_dob',
41
- 'patient_dob_display',
42
- 'examination_date',
43
- 'examination_date_display',
44
- 'center_name',
45
- 'patient_gender_name',
46
- 'endoscope_type',
47
- 'endoscope_sn',
48
- 'patient_hash_display',
49
- 'examination_hash_display',
50
- 'examiners_display',
51
- 'is_verified',
52
- 'dob_verified',
53
- 'names_verified',
38
+ "id",
39
+ "casenumber",
40
+ "patient_first_name",
41
+ "patient_last_name",
42
+ "patient_dob",
43
+ "patient_dob_display",
44
+ "examination_date",
45
+ "examination_date_display",
46
+ "examination_time",
47
+ "center_name",
48
+ "patient_gender_name",
49
+ "endoscope_type",
50
+ "endoscope_sn",
51
+ "patient_hash_display",
52
+ "examination_hash_display",
53
+ "examiners_display",
54
+ "is_verified",
55
+ "dob_verified",
56
+ "names_verified",
57
+ "text",
58
+ "anonymized_text",
59
+ "external_id",
60
+ "external_id_origin"
54
61
  ]
55
62
  read_only_fields = [
56
- 'id',
57
- 'patient_hash_display',
58
- 'examination_hash_display',
63
+ "id",
64
+ "patient_hash_display",
65
+ "examination_hash_display",
59
66
  ]
60
67
 
61
- def get_is_verified(self, obj):
62
- """
63
- Return the overall verification status of the given SensitiveMeta instance.
64
-
65
- Returns:
66
- bool: True if the instance is verified; False if the verification attribute is missing.
67
-
68
- Raises:
69
- Exception: Propagates unexpected exceptions after logging.
70
- """
71
- try:
72
- return obj.is_verified
73
- except AttributeError:
74
- return False
75
- except Exception as e:
76
- logger.exception(f"Unexpected error in get_is_verified for SensitiveMeta {getattr(obj, 'pk', None)}: {e}")
77
- raise
78
-
79
- def get_dob_verified(self, obj):
80
- """
81
- Return the date of birth verification status for the given object.
82
-
83
- Returns:
84
- bool: True if the date of birth is verified; otherwise, False if unavailable or on error.
85
- """
86
- try:
87
- return obj.state.dob_verified
88
- except Exception:
89
- return False
90
-
91
- def get_names_verified(self, obj):
92
- """
93
- Return whether the patient's names have been verified.
94
-
95
- Returns:
96
- bool: True if the names are verified; False if not verified or if an error occurs.
97
- """
98
- try:
99
- return obj.state.names_verified
100
- except Exception:
101
- return False
68
+ # --- Verification getters ---
69
+ def get_is_verified(self, obj): return getattr(obj, "is_verified", False)
70
+ def get_dob_verified(self, obj): return getattr(getattr(obj, "state", None), "dob_verified", False)
71
+ def get_names_verified(self, obj): return getattr(getattr(obj, "state", None), "names_verified", False)
102
72
 
73
+ # --- Examiner display ---
103
74
  def get_examiners_display(self, obj):
104
- """
105
- Return a list of examiner full names for the given SensitiveMeta instance.
106
-
107
- Returns:
108
- list[str]: List of examiner names in "First Last" format, or an empty list if unavailable.
109
- """
110
75
  try:
111
- if obj.pk:
112
- examiners = obj.examiners.all()
113
- return [f"{e.first_name} {e.last_name}" for e in examiners]
114
- return []
76
+ return [f"{e.first_name} {e.last_name}" for e in obj.examiners.all()] if obj.pk else []
115
77
  except Exception as e:
116
- logger.warning(f"Error getting examiners for SensitiveMeta {obj.pk}: {e}")
78
+ logger.warning(f"Error fetching examiners for SensitiveMeta {obj.pk}: {e}")
117
79
  return []
118
80
 
81
+ # --- Date formatters ---
119
82
  def get_patient_dob_display(self, obj):
120
- """
121
- Return the patient's date of birth formatted as "YYYY-MM-DD" for display.
122
-
123
- Returns:
124
- str or None: Formatted date string if available, otherwise None.
125
- """
126
- if obj.patient_dob:
127
- return obj.patient_dob.strftime("%Y-%m-%d")
128
- return None
83
+ return obj.patient_dob.strftime("%Y-%m-%d") if obj.patient_dob else None
129
84
 
130
85
  def get_examination_date_display(self, obj):
131
- """
132
- Return the examination date formatted as "YYYY-MM-DD" for display.
133
-
134
- Returns:
135
- str or None: Formatted examination date string, or None if not set.
136
- """
137
- if obj.examination_date:
138
- return obj.examination_date.strftime("%Y-%m-%d")
139
- return None
86
+ return obj.examination_date.strftime("%Y-%m-%d") if obj.examination_date else None
140
87
 
88
+ # --- Hash short forms ---
141
89
  def get_patient_hash_display(self, obj):
142
- """
143
- Return the last eight characters of the patient's hash, prefixed with ellipsis, or None if not available.
144
-
145
- Returns:
146
- str or None: Truncated patient hash for display, or None if the hash is not set.
147
- """
148
- if obj.patient_hash:
149
- return f"...{obj.patient_hash[-8:]}"
150
- return None
90
+ return f"...{obj.patient_hash[-8:]}" if obj.patient_hash else None
151
91
 
152
92
  def get_examination_hash_display(self, obj):
153
- """
154
- Return the last eight characters of the examination hash, prefixed with ellipsis, or None if not available.
155
-
156
- Returns:
157
- str or None: Truncated examination hash for display, or None if the hash is not set.
158
- """
159
- if obj.examination_hash:
160
- return f"...{obj.examination_hash[-8:]}"
161
- return None
93
+ return f"...{obj.examination_hash[-8:]}" if obj.examination_hash else None
94
+
95
+ # --- Text fields ---
96
+ def get_text(self, obj):
97
+ return obj.text if isinstance(obj.text, str) else None
98
+
99
+ def get_anonymized_text(self, obj):
100
+ return obj.anonymized_text if isinstance(obj.anonymized_text, str) else None
101
+
102
+ def get_external_id(self, obj) -> str | None:
103
+ return obj.external_id if isinstance(obj.external_id, str) else None
104
+
105
+ def get_external_id_origin(self, obj) -> str | None:
106
+ return obj.external_id_origin if isinstance(obj.external_id_origin, str) else None
162
107
 
@@ -1,5 +1,5 @@
1
1
  from .file_overview import FileOverviewSerializer
2
- from .vop_patient_data import VoPPatientDataSerializer
2
+ from .sensitive_patient_data import VoPPatientDataSerializer
3
3
  from .stats import StatsSerializer
4
4
  from .upload_job import UploadJobStatusSerializer, UploadCreateResponseSerializer
5
5
  from .translatable_field_mix_in import TranslatableFieldMixin
@@ -1,12 +1,15 @@
1
- from rest_framework import serializers
2
1
  from typing import TYPE_CHECKING
3
- from endoreg_db.models.media import VideoFile, RawPdfFile
4
- from endoreg_db.models.state.video import AnonymizationStatus as VideoAnonymizationStatus
5
- from endoreg_db.models.state.raw_pdf import AnonymizationStatus as PdfAnonymizationStatus
2
+
3
+ from rest_framework import serializers
4
+
5
+ from endoreg_db.models.media import RawPdfFile, VideoFile
6
+ from endoreg_db.models.state.anonymization import AnonymizationState as PdfAnonymizationState
7
+ from endoreg_db.models.state.anonymization import AnonymizationState as VideoAnonymizationState
6
8
 
7
9
  if TYPE_CHECKING:
8
10
  pass
9
11
 
12
+
10
13
  class FileOverviewSerializer(serializers.Serializer):
11
14
  """
12
15
  Polymorphic "union" serializer – we normalise both model types
@@ -21,121 +24,62 @@ class FileOverviewSerializer(serializers.Serializer):
21
24
  anonymizationStatus = serializers.CharField(read_only=True)
22
25
  annotationStatus = serializers.CharField(read_only=True)
23
26
  createdAt = serializers.DateTimeField(read_only=True)
24
- text = serializers.CharField(required=False, allow_blank=True, read_only=True)
25
- anonymizedText = serializers.CharField(required=False, allow_blank=True, read_only=True)
26
27
 
27
28
  # --- converting DB objects to that shape -----------------------
28
29
  def to_representation(self, instance):
29
30
  """
30
31
  Return a unified dictionary representation of either a VideoFile or RawPdfFile instance for front-end use.
31
-
32
+
32
33
  For VideoFile instances, extracts and structures metadata such as patient, examination, equipment, and examiner information, and generates an anonymized version of the text by replacing sensitive fields with placeholders. For RawPdfFile instances, extracts text and anonymized text directly and determines statuses based on available fields.
33
-
34
+
34
35
  Parameters:
35
36
  instance: A VideoFile or RawPdfFile object to be serialized.
36
-
37
+
37
38
  Returns:
38
39
  dict: A normalized dictionary containing id, filename, mediaType, anonymizationStatus, annotationStatus, createdAt, text, and anonymizedText fields.
39
-
40
+
40
41
  Raises:
41
42
  TypeError: If the instance is not a VideoFile or RawPdfFile.
42
43
  """
43
44
  text = ""
44
45
  anonym_text = ""
45
-
46
+
46
47
  if isinstance(instance, VideoFile):
47
48
  media_type = "video"
48
49
  created_at = instance.uploaded_at
49
50
  filename = instance.original_file_name or (
50
- instance.raw_file.name.split("/")[-1] if instance.raw_file else "unknown"
51
+ instance.raw_file.name.split("/")[-1]
52
+ if instance.raw_file
53
+ else "unknown"
51
54
  )
52
-
55
+
53
56
  # ------- anonymization status using VideoState model
54
- vs = instance.state
55
- anonym_status = vs.anonymization_status if vs else VideoAnonymizationStatus.NOT_STARTED
56
-
57
+ vs = instance.get_or_create_state()
58
+ anonym_status = (
59
+ vs.anonymization_status if vs else VideoAnonymizationState.NOT_STARTED
60
+ )
61
+
57
62
  # ------- annotation status (validated label segments)
58
63
  if instance.label_video_segments.filter(state__is_validated=True).exists():
59
- annot_status = "done"
64
+ annot_status = "validated"
60
65
  else:
61
66
  annot_status = "not_started"
62
-
63
- # ------- Extract text from sensitive_meta for videos
64
- if instance.sensitive_meta:
65
- sm = instance.sensitive_meta
66
- # Create a structured text representation from sensitive meta
67
- text_parts = []
68
-
69
- # Patient information
70
- if sm.patient_first_name or sm.patient_last_name:
71
- patient_name = f"{sm.patient_first_name or ''} {sm.patient_last_name or ''}".strip()
72
- text_parts.append(f"Patient: {patient_name}")
73
-
74
- if sm.patient_dob:
75
- text_parts.append(f"Date of Birth: {sm.patient_dob.date()}")
76
-
77
- if sm.patient_gender:
78
- text_parts.append(f"Gender: {sm.patient_gender}")
79
-
80
- # Examination information
81
- if sm.examination_date:
82
- text_parts.append(f"Examination Date: {sm.examination_date}")
83
-
84
- if sm.center:
85
- text_parts.append(f"Center: {sm.center.name}")
86
-
87
- # Equipment information
88
- if sm.endoscope_type:
89
- text_parts.append(f"Endoscope Type: {sm.endoscope_type}")
90
-
91
- if sm.endoscope_sn:
92
- text_parts.append(f"Endoscope SN: {sm.endoscope_sn}")
93
-
94
- # Examiner information
95
- if sm.pk: # Only if saved
96
- try:
97
- examiners = list(sm.examiners.all())
98
- if examiners:
99
- examiner_names = ", ".join(str(e) for e in examiners)
100
- text_parts.append(f"Examiners: {examiner_names}")
101
- except Exception:
102
- pass # Ignore examiner lookup errors
103
-
104
- text = "\n".join(text_parts)
105
-
106
- # Create anonymized version by replacing sensitive data
107
- anonym_text = text
108
- if sm.patient_first_name:
109
- anonym_text = anonym_text.replace(sm.patient_first_name, "[FIRST_NAME]")
110
- if sm.patient_last_name:
111
- anonym_text = anonym_text.replace(sm.patient_last_name, "[LAST_NAME]")
112
- if sm.patient_dob:
113
- anonym_text = anonym_text.replace(str(sm.patient_dob.date()), "[DOB]")
114
- if sm.endoscope_sn:
115
- anonym_text = anonym_text.replace(sm.endoscope_sn, "[ENDOSCOPE_SN]")
116
-
117
- # Replace examiner names if available
118
- if sm.pk:
119
- try:
120
- examiners = list(sm.examiners.all())
121
- for examiner in examiners:
122
- anonym_text = anonym_text.replace(str(examiner), "[EXAMINER]")
123
- except Exception:
124
- pass
125
67
 
126
68
  elif isinstance(instance, RawPdfFile):
127
- instance:RawPdfFile
128
69
  media_type = "pdf"
129
70
  created_at = instance.date_created
130
71
  filename = instance.file.name.split("/")[-1] if instance.file else "unknown"
131
- # Check anonymized_text field
132
- anonym_status = "done" if (instance.anonymized_text and instance.anonymized_text.strip()) else "not_started"
133
- # PDF annotation == "sensitive meta validated"
134
- annot_status = "done" if getattr(instance.sensitive_meta, "is_verified", False) else "not_started"
135
-
136
- # Extract text content from PDF
137
- text = instance.text or ""
138
- anonym_text = instance.anonymized_text or ""
72
+
73
+ # ------- anonymization status using RawPdfState model
74
+ rps = instance.get_or_create_state()
75
+ anonym_status = (
76
+ rps.anonymization_status if rps else PdfAnonymizationState.NOT_STARTED
77
+ )
78
+
79
+ # ------- annotation status (not applicable for PDFs)
80
+ annot_status = (
81
+ PdfAnonymizationState.VALIDATED if rps.anonymization_validated else PdfAnonymizationState.NOT_STARTED
82
+ )
139
83
 
140
84
  else: # shouldn't happen
141
85
  raise TypeError(f"Unsupported instance for overview: {type(instance)}")
@@ -147,6 +91,4 @@ class FileOverviewSerializer(serializers.Serializer):
147
91
  "anonymizationStatus": anonym_status,
148
92
  "annotationStatus": annot_status,
149
93
  "createdAt": created_at,
150
- "text": text,
151
- "anonymizedText": anonym_text,
152
94
  }
@@ -80,7 +80,7 @@ class VoPPatientDataSerializer(serializers.Serializer):
80
80
 
81
81
  elif isinstance(instance, RawPdfFile):
82
82
  # Generate PDF streaming URL using pdf_id (RawPdfFile.id)
83
- pdf_stream_url = f"/api/pdfstream/{instance.pk}/"
83
+ pdf_stream_url = f"/api/media/pdfs/{instance.pk}/stream/"
84
84
 
85
85
  return {
86
86
  "id": instance.pk,
@@ -1,29 +1,99 @@
1
- from ...models.requirement.requirement_set import RequirementSet
2
- from .requirement_schema import RequirementSetSchema
1
+ from rest_framework import serializers
2
+
3
+ from endoreg_db.models.requirement.requirement_set import RequirementSet
4
+
5
+
6
+ class RequirementSetSerializer(serializers.ModelSerializer):
7
+ """
8
+ Serializer for RequirementSet model with optional tag field.
9
+
10
+ This serializer provides a flexible representation of requirement sets,
11
+ including role-based tags for filtering (e.g., "Gastroenterologist", "Student").
12
+
13
+ Fields:
14
+ id: Primary key
15
+ name: Display name of the requirement set
16
+ description: Optional description text
17
+ requirement_set_type: Type classification
18
+ tags: List of tag names (optional, read-only)
19
+ """
20
+
21
+ tags = serializers.SlugRelatedField(
22
+ many=True, read_only=True, slug_field="name", required=False
23
+ )
24
+
25
+ requirement_set_type = serializers.CharField(
26
+ source="requirement_set_type.name", read_only=True, allow_null=True
27
+ )
28
+
29
+ class Meta:
30
+ model = RequirementSet
31
+ fields = ["id", "name", "description", "requirement_set_type", "tags"]
32
+
33
+ def to_representation(self, instance):
34
+ """
35
+ Customize representation to exclude tags field if empty or not prefetched.
36
+
37
+ This prevents N+1 query issues and keeps the response clean when tags
38
+ aren't needed or weren't prefetched in the queryset.
39
+ """
40
+ representation = super().to_representation(instance)
41
+
42
+ # Only include tags if they exist and were prefetched
43
+ if not representation.get("tags"):
44
+ representation.pop("tags", None)
45
+
46
+ return representation
47
+
3
48
 
4
49
  def requirement_set_to_dict(requirement_set: RequirementSet) -> dict:
5
50
  """
6
51
  Convert a RequirementSet instance to a dictionary representation.
7
-
52
+
53
+ This function performs additional queries to fetch related data including
54
+ requirements and linked sets. Use RequirementSetSerializer for simpler
55
+ representations without the overhead of additional queries.
56
+
8
57
  Args:
9
- requirement_set (RequirementSet): The RequirementSet instance to convert.
10
-
58
+ requirement_set: The RequirementSet instance to convert.
59
+
11
60
  Returns:
12
- dict: A dictionary representation of the RequirementSet.
61
+ Dictionary representation of the RequirementSet with full nested details:
62
+ - id, name, description: Basic metadata
63
+ - requirements: List of requirement dictionaries
64
+ - links: List of linked requirement set data
65
+ - tags: List of tag names (if any exist)
13
66
  """
14
-
15
- requirement_set = RequirementSet.objects.select_related(
16
- "created_by", "updated_by"
17
- ).prefetch_related(
18
- "requirements", "links"
19
- ).get(id=requirement_set.id)
20
-
21
- links = requirement_set.links_to_sets()
22
-
23
- return {
24
- "id": requirement_set.id,
25
- "name": requirement_set.name,
26
- "description": requirement_set.description,
27
- "requirements": [req.to_dict() for req in requirement_set.requirements.all()],
28
- "links": [link.model_dump() for link in links]
29
- }
67
+ # Fetch the requirement set with all related data to avoid N+1 queries
68
+ requirement_set_full = (
69
+ RequirementSet.objects.select_related("requirement_set_type")
70
+ .prefetch_related("requirements", "links_to_sets", "tags")
71
+ .get(pk=requirement_set.pk)
72
+ )
73
+
74
+ # Get linked requirement sets
75
+ linked_sets = requirement_set_full.links_to_sets.all()
76
+
77
+ result = {
78
+ "id": requirement_set_full.pk,
79
+ "name": requirement_set_full.name,
80
+ "description": requirement_set_full.description or "",
81
+ "requirement_set_type": requirement_set_full.requirement_set_type.name
82
+ if requirement_set_full.requirement_set_type
83
+ else None,
84
+ "requirements": [
85
+ {"id": req.pk, "name": req.name, "description": req.description or ""}
86
+ for req in requirement_set_full.requirements.all()
87
+ ],
88
+ "linked_sets": [
89
+ {"id": link.pk, "name": link.name, "description": link.description or ""}
90
+ for link in linked_sets
91
+ ],
92
+ }
93
+
94
+ # Add tags if they exist
95
+ tags = list(requirement_set_full.tags.values_list("name", flat=True))
96
+ if tags:
97
+ result["tags"] = tags
98
+
99
+ return result
@@ -10,7 +10,8 @@ from django.conf import settings
10
10
  from typing import TYPE_CHECKING
11
11
  from rest_framework.exceptions import ValidationError
12
12
  if TYPE_CHECKING:
13
- from endoreg_db.models import Video
13
+ from endoreg_db.models import VideoFile
14
+
14
15
  class VideoFileSerializer(serializers.ModelSerializer):
15
16
  """
16
17
  Serializer that dynamically handles video retrieval and streaming.
@@ -4,7 +4,10 @@ Video Processing History Serializer
4
4
  Serializes VideoProcessingHistory model for API responses.
5
5
  Created as part of Phase 1.1: Video Correction API Endpoints.
6
6
  """
7
+ from collections.abc import Mapping
8
+
7
9
  from rest_framework import serializers
10
+
8
11
  from endoreg_db.models import VideoProcessingHistory
9
12
 
10
13
 
@@ -16,8 +19,8 @@ class VideoProcessingHistorySerializer(serializers.ModelSerializer):
16
19
  with download URLs for processed files.
17
20
  """
18
21
  download_url = serializers.SerializerMethodField()
19
- operation_display = serializers.CharField(source='get_operation_display', read_only=True)
20
- status_display = serializers.CharField(source='get_status_display', read_only=True)
22
+ operation_display = serializers.SerializerMethodField()
23
+ status_display = serializers.SerializerMethodField()
21
24
  duration = serializers.ReadOnlyField()
22
25
  is_complete = serializers.ReadOnlyField()
23
26
 
@@ -57,13 +60,24 @@ class VideoProcessingHistorySerializer(serializers.ModelSerializer):
57
60
 
58
61
  # Build URL to download endpoint (to be implemented)
59
62
  # Format: /api/media/processed-videos/{video_id}/{history_id}/
60
- request = self.context.get('request')
63
+ context = self.context if isinstance(self.context, Mapping) else None
64
+ request = context.get('request') if context else None
61
65
  if request:
62
66
  return request.build_absolute_uri(
63
67
  f'/api/media/processed-videos/{obj.video.id}/{obj.id}/'
64
68
  )
65
69
 
66
70
  return f'/api/media/processed-videos/{obj.video.id}/{obj.id}/'
71
+
72
+ def get_operation_display(self, obj) -> str:
73
+ display = getattr(obj, "get_operation_display", None)
74
+ result = display() if callable(display) else obj.operation
75
+ return str(result)
76
+
77
+ def get_status_display(self, obj) -> str:
78
+ display = getattr(obj, "get_status_display", None)
79
+ result = display() if callable(display) else obj.status
80
+ return str(result)
67
81
 
68
82
  def validate_operation(self, value):
69
83
  """
@@ -120,8 +134,9 @@ class VideoProcessingHistorySerializer(serializers.ModelSerializer):
120
134
  """
121
135
  if not isinstance(value, dict):
122
136
  raise serializers.ValidationError("config must be a dictionary")
123
-
124
- operation = self.initial_data.get('operation')
137
+
138
+ initial = self.initial_data if isinstance(self.initial_data, Mapping) else {}
139
+ operation = initial.get('operation')
125
140
 
126
141
  # Validate masking config
127
142
  if operation == VideoProcessingHistory.OPERATION_MASKING: