endoreg-db 0.8.6.1__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 (360) 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_center_data.py +12 -12
  117. endoreg_db/management/commands/load_requirement_data.py +60 -31
  118. endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
  119. endoreg_db/management/commands/setup_endoreg_db.py +3 -3
  120. endoreg_db/management/commands/storage_management.py +271 -203
  121. endoreg_db/migrations/0001_initial.py +1799 -1300
  122. endoreg_db/migrations/0002_requirementset_depends_on.py +18 -0
  123. endoreg_db/migrations/_old/0001_initial.py +1857 -0
  124. endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +68 -0
  125. endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +77 -0
  126. endoreg_db/migrations/_old/0005_merge_20251111_1003.py +14 -0
  127. endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +68 -0
  128. endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +89 -0
  129. endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +27 -0
  130. endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +21 -0
  131. endoreg_db/models/__init__.py +78 -123
  132. endoreg_db/models/administration/__init__.py +21 -42
  133. endoreg_db/models/administration/ai/active_model.py +2 -2
  134. endoreg_db/models/administration/ai/ai_model.py +7 -6
  135. endoreg_db/models/administration/case/__init__.py +1 -15
  136. endoreg_db/models/administration/case/case.py +3 -3
  137. endoreg_db/models/administration/case/case_template/__init__.py +2 -14
  138. endoreg_db/models/administration/case/case_template/case_template.py +2 -124
  139. endoreg_db/models/administration/case/case_template/case_template_rule.py +2 -268
  140. endoreg_db/models/administration/case/case_template/case_template_rule_value.py +2 -85
  141. endoreg_db/models/administration/case/case_template/case_template_type.py +2 -25
  142. endoreg_db/models/administration/center/center.py +33 -19
  143. endoreg_db/models/administration/center/center_product.py +12 -9
  144. endoreg_db/models/administration/center/center_resource.py +25 -19
  145. endoreg_db/models/administration/center/center_shift.py +21 -17
  146. endoreg_db/models/administration/center/center_waste.py +16 -8
  147. endoreg_db/models/administration/person/__init__.py +2 -0
  148. endoreg_db/models/administration/person/employee/employee.py +10 -5
  149. endoreg_db/models/administration/person/employee/employee_qualification.py +9 -4
  150. endoreg_db/models/administration/person/employee/employee_type.py +12 -6
  151. endoreg_db/models/administration/person/examiner/examiner.py +13 -11
  152. endoreg_db/models/administration/person/patient/__init__.py +2 -0
  153. endoreg_db/models/administration/person/patient/patient.py +103 -100
  154. endoreg_db/models/administration/person/patient/patient_external_id.py +37 -0
  155. endoreg_db/models/administration/person/person.py +4 -0
  156. endoreg_db/models/administration/person/profession/__init__.py +8 -4
  157. endoreg_db/models/administration/person/user/portal_user_information.py +11 -7
  158. endoreg_db/models/administration/product/product.py +20 -15
  159. endoreg_db/models/administration/product/product_material.py +17 -18
  160. endoreg_db/models/administration/product/product_weight.py +12 -8
  161. endoreg_db/models/administration/product/reference_product.py +23 -55
  162. endoreg_db/models/administration/qualification/qualification.py +7 -3
  163. endoreg_db/models/administration/qualification/qualification_type.py +7 -3
  164. endoreg_db/models/administration/shift/scheduled_days.py +8 -5
  165. endoreg_db/models/administration/shift/shift.py +16 -12
  166. endoreg_db/models/administration/shift/shift_type.py +23 -31
  167. endoreg_db/models/label/__init__.py +7 -8
  168. endoreg_db/models/label/annotation/image_classification.py +10 -9
  169. endoreg_db/models/label/annotation/video_segmentation_annotation.py +8 -5
  170. endoreg_db/models/label/label.py +15 -15
  171. endoreg_db/models/label/label_set.py +19 -6
  172. endoreg_db/models/label/label_type.py +1 -1
  173. endoreg_db/models/label/label_video_segment/_create_from_video.py +5 -8
  174. endoreg_db/models/label/label_video_segment/label_video_segment.py +76 -102
  175. endoreg_db/models/label/video_segmentation_label.py +4 -0
  176. endoreg_db/models/label/video_segmentation_labelset.py +4 -3
  177. endoreg_db/models/media/frame/frame.py +22 -22
  178. endoreg_db/models/media/pdf/raw_pdf.py +110 -182
  179. endoreg_db/models/media/pdf/report_file.py +25 -29
  180. endoreg_db/models/media/pdf/report_reader/report_reader_config.py +30 -46
  181. endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
  182. endoreg_db/models/media/video/__init__.py +1 -0
  183. endoreg_db/models/media/video/create_from_file.py +48 -56
  184. endoreg_db/models/media/video/pipe_2.py +8 -9
  185. endoreg_db/models/media/video/video_file.py +150 -108
  186. endoreg_db/models/media/video/video_file_ai.py +288 -74
  187. endoreg_db/models/media/video/video_file_anonymize.py +38 -38
  188. endoreg_db/models/media/video/video_file_frames/__init__.py +3 -1
  189. endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -8
  190. endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +7 -9
  191. endoreg_db/models/media/video/video_file_frames/_delete_frames.py +9 -8
  192. endoreg_db/models/media/video/video_file_frames/_extract_frames.py +38 -45
  193. endoreg_db/models/media/video/video_file_frames/_get_frame.py +6 -8
  194. endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +4 -18
  195. endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -3
  196. endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +7 -6
  197. endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +6 -8
  198. endoreg_db/models/media/video/video_file_frames/_get_frames.py +6 -8
  199. endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +15 -25
  200. endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +26 -23
  201. endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +23 -14
  202. endoreg_db/models/media/video/video_file_io.py +109 -62
  203. endoreg_db/models/media/video/video_file_meta/get_crop_template.py +3 -3
  204. endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +5 -3
  205. endoreg_db/models/media/video/video_file_meta/get_fps.py +37 -34
  206. endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +19 -25
  207. endoreg_db/models/media/video/video_file_meta/text_meta.py +41 -38
  208. endoreg_db/models/media/video/video_file_meta/video_meta.py +14 -7
  209. endoreg_db/models/media/video/video_file_segments.py +24 -17
  210. endoreg_db/models/media/video/video_metadata.py +19 -35
  211. endoreg_db/models/media/video/video_processing.py +96 -95
  212. endoreg_db/models/medical/contraindication/__init__.py +13 -3
  213. endoreg_db/models/medical/disease.py +22 -16
  214. endoreg_db/models/medical/event.py +31 -18
  215. endoreg_db/models/medical/examination/__init__.py +13 -6
  216. endoreg_db/models/medical/examination/examination.py +17 -18
  217. endoreg_db/models/medical/examination/examination_indication.py +26 -25
  218. endoreg_db/models/medical/examination/examination_time.py +16 -6
  219. endoreg_db/models/medical/examination/examination_time_type.py +9 -6
  220. endoreg_db/models/medical/examination/examination_type.py +3 -4
  221. endoreg_db/models/medical/finding/finding.py +38 -39
  222. endoreg_db/models/medical/finding/finding_classification.py +37 -48
  223. endoreg_db/models/medical/finding/finding_intervention.py +27 -22
  224. endoreg_db/models/medical/finding/finding_type.py +13 -12
  225. endoreg_db/models/medical/hardware/endoscope.py +20 -26
  226. endoreg_db/models/medical/hardware/endoscopy_processor.py +2 -2
  227. endoreg_db/models/medical/laboratory/lab_value.py +62 -91
  228. endoreg_db/models/medical/medication/medication.py +22 -10
  229. endoreg_db/models/medical/medication/medication_indication.py +29 -3
  230. endoreg_db/models/medical/medication/medication_indication_type.py +25 -14
  231. endoreg_db/models/medical/medication/medication_intake_time.py +31 -19
  232. endoreg_db/models/medical/medication/medication_schedule.py +27 -16
  233. endoreg_db/models/medical/organ/__init__.py +15 -12
  234. endoreg_db/models/medical/patient/medication_examples.py +1 -5
  235. endoreg_db/models/medical/patient/patient_disease.py +20 -23
  236. endoreg_db/models/medical/patient/patient_event.py +19 -22
  237. endoreg_db/models/medical/patient/patient_examination.py +48 -54
  238. endoreg_db/models/medical/patient/patient_examination_indication.py +16 -14
  239. endoreg_db/models/medical/patient/patient_finding.py +122 -139
  240. endoreg_db/models/medical/patient/patient_finding_classification.py +44 -49
  241. endoreg_db/models/medical/patient/patient_finding_intervention.py +8 -19
  242. endoreg_db/models/medical/patient/patient_lab_sample.py +28 -23
  243. endoreg_db/models/medical/patient/patient_lab_value.py +82 -89
  244. endoreg_db/models/medical/patient/patient_medication.py +27 -38
  245. endoreg_db/models/medical/patient/patient_medication_schedule.py +28 -36
  246. endoreg_db/models/medical/risk/risk.py +7 -6
  247. endoreg_db/models/medical/risk/risk_type.py +8 -5
  248. endoreg_db/models/metadata/model_meta.py +60 -29
  249. endoreg_db/models/metadata/model_meta_logic.py +125 -18
  250. endoreg_db/models/metadata/pdf_meta.py +19 -24
  251. endoreg_db/models/metadata/sensitive_meta.py +102 -85
  252. endoreg_db/models/metadata/sensitive_meta_logic.py +192 -173
  253. endoreg_db/models/metadata/video_meta.py +51 -31
  254. endoreg_db/models/metadata/video_prediction_logic.py +16 -23
  255. endoreg_db/models/metadata/video_prediction_meta.py +29 -33
  256. endoreg_db/models/other/distribution/date_value_distribution.py +89 -29
  257. endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +21 -5
  258. endoreg_db/models/other/distribution/numeric_value_distribution.py +114 -53
  259. endoreg_db/models/other/distribution/single_categorical_value_distribution.py +4 -3
  260. endoreg_db/models/other/emission/emission_factor.py +18 -8
  261. endoreg_db/models/other/gender.py +10 -5
  262. endoreg_db/models/other/information_source.py +25 -25
  263. endoreg_db/models/other/material.py +9 -5
  264. endoreg_db/models/other/resource.py +6 -4
  265. endoreg_db/models/other/tag.py +10 -5
  266. endoreg_db/models/other/transport_route.py +13 -8
  267. endoreg_db/models/other/unit.py +10 -6
  268. endoreg_db/models/other/waste.py +6 -5
  269. endoreg_db/models/requirement/requirement.py +580 -272
  270. endoreg_db/models/requirement/requirement_error.py +85 -0
  271. endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +268 -0
  272. endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +3 -6
  273. endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +90 -64
  274. endoreg_db/models/requirement/requirement_operator.py +36 -33
  275. endoreg_db/models/requirement/requirement_set.py +74 -57
  276. endoreg_db/models/state/__init__.py +4 -4
  277. endoreg_db/models/state/abstract.py +2 -2
  278. endoreg_db/models/state/anonymization.py +12 -0
  279. endoreg_db/models/state/audit_ledger.py +46 -47
  280. endoreg_db/models/state/label_video_segment.py +9 -0
  281. endoreg_db/models/state/raw_pdf.py +40 -46
  282. endoreg_db/models/state/sensitive_meta.py +6 -2
  283. endoreg_db/models/state/video.py +58 -53
  284. endoreg_db/models/upload_job.py +32 -55
  285. endoreg_db/models/utils.py +1 -2
  286. endoreg_db/root_urls.py +21 -2
  287. endoreg_db/serializers/__init__.py +0 -2
  288. endoreg_db/serializers/anonymization.py +18 -10
  289. endoreg_db/serializers/meta/report_meta.py +1 -1
  290. endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
  291. endoreg_db/serializers/misc/file_overview.py +11 -99
  292. endoreg_db/serializers/requirements/requirement_sets.py +92 -22
  293. endoreg_db/serializers/video/segmentation.py +2 -1
  294. endoreg_db/serializers/video/video_processing_history.py +20 -5
  295. endoreg_db/services/anonymization.py +75 -73
  296. endoreg_db/services/lookup_service.py +37 -24
  297. endoreg_db/services/pdf_import.py +166 -68
  298. endoreg_db/services/storage_aware_video_processor.py +140 -114
  299. endoreg_db/services/video_import.py +193 -283
  300. endoreg_db/urls/__init__.py +7 -20
  301. endoreg_db/urls/media.py +108 -67
  302. endoreg_db/urls/root_urls.py +29 -0
  303. endoreg_db/utils/__init__.py +15 -5
  304. endoreg_db/utils/ai/multilabel_classification_net.py +116 -20
  305. endoreg_db/utils/case_generator/__init__.py +3 -0
  306. endoreg_db/utils/dataloader.py +88 -16
  307. endoreg_db/utils/defaults/set_default_center.py +32 -0
  308. endoreg_db/utils/names.py +22 -16
  309. endoreg_db/utils/permissions.py +2 -1
  310. endoreg_db/utils/pipelines/process_video_dir.py +1 -1
  311. endoreg_db/utils/requirement_operator_logic/model_evaluators.py +414 -127
  312. endoreg_db/utils/setup_config.py +8 -5
  313. endoreg_db/utils/storage.py +115 -0
  314. endoreg_db/utils/validate_endo_roi.py +8 -2
  315. endoreg_db/utils/video/ffmpeg_wrapper.py +184 -188
  316. endoreg_db/views/__init__.py +0 -10
  317. endoreg_db/views/anonymization/media_management.py +198 -163
  318. endoreg_db/views/anonymization/overview.py +4 -1
  319. endoreg_db/views/anonymization/validate.py +174 -40
  320. endoreg_db/views/media/__init__.py +2 -0
  321. endoreg_db/views/media/pdf_media.py +131 -152
  322. endoreg_db/views/media/sensitive_metadata.py +46 -6
  323. endoreg_db/views/media/video_media.py +89 -82
  324. endoreg_db/views/media/video_segments.py +2 -3
  325. endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
  326. endoreg_db/views/patient/patient.py +5 -4
  327. endoreg_db/views/pdf/pdf_stream.py +20 -21
  328. endoreg_db/views/pdf/reimport.py +11 -32
  329. endoreg_db/views/requirement/evaluate.py +188 -187
  330. endoreg_db/views/requirement/lookup.py +17 -3
  331. endoreg_db/views/requirement/requirement_utils.py +89 -0
  332. endoreg_db/views/video/__init__.py +0 -2
  333. endoreg_db/views/video/correction.py +2 -2
  334. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
  335. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +341 -245
  336. endoreg_db/models/administration/permissions/__init__.py +0 -44
  337. endoreg_db/models/media/video/video_file_frames.py +0 -0
  338. endoreg_db/models/metadata/frame_ocr_result.py +0 -0
  339. endoreg_db/models/rule/__init__.py +0 -13
  340. endoreg_db/models/rule/rule.py +0 -27
  341. endoreg_db/models/rule/rule_applicator.py +0 -224
  342. endoreg_db/models/rule/rule_attribute_dtype.py +0 -17
  343. endoreg_db/models/rule/rule_type.py +0 -20
  344. endoreg_db/models/rule/ruleset.py +0 -17
  345. endoreg_db/serializers/video/video_metadata.py +0 -105
  346. endoreg_db/urls/report.py +0 -48
  347. endoreg_db/urls/video.py +0 -61
  348. endoreg_db/utils/case_generator/case_generator.py +0 -159
  349. endoreg_db/utils/case_generator/utils.py +0 -30
  350. endoreg_db/views/report/__init__.py +0 -9
  351. endoreg_db/views/report/report_list.py +0 -112
  352. endoreg_db/views/report/report_with_secure_url.py +0 -28
  353. endoreg_db/views/report/start_examination.py +0 -7
  354. endoreg_db/views.py +0 -0
  355. /endoreg_db/data/{requirement_set → _examples/requirement_set}/endoscopy_bleeding_risk.yaml +0 -0
  356. /endoreg_db/migrations/{0002_add_video_correction_models.py → _old/0002_add_video_correction_models.py} +0 -0
  357. /endoreg_db/migrations/{0003_add_center_display_name.py → _old/0003_add_center_display_name.py} +0 -0
  358. /endoreg_db/{models/media/video/refactor_plan.md → views/pdf/pdf_stream_views.py} +0 -0
  359. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
  360. {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,11 +1,13 @@
1
- from ..person import Person
2
- from django.db import models
3
- from faker import Faker
1
+ import logging
4
2
  import random
5
- from datetime import datetime
3
+ from datetime import date, datetime
6
4
  from typing import TYPE_CHECKING, List, Optional # Added List
7
- import logging
5
+
6
+ from django.db import models
8
7
  from django.utils import timezone # Add this import
8
+ from faker import Faker
9
+
10
+ from ..person import Person
9
11
 
10
12
  # Import RequirementLinks and Disease for the links property
11
13
 
@@ -13,23 +15,22 @@ logger = logging.getLogger("patient")
13
15
 
14
16
  if TYPE_CHECKING:
15
17
  from endoreg_db.models import (
18
+ AnonymExaminationReport,
19
+ AnonymHistologyReport,
20
+ Center,
16
21
  ExaminationIndication,
17
- PatientEvent, PatientDisease,
18
22
  Gender,
23
+ PatientDisease,
24
+ PatientEvent,
19
25
  PatientExamination,
20
- Center,
21
- AnonymExaminationReport,
22
- AnonymHistologyReport, RawPdfFile,
23
- # Added for links property
24
- Medication,
26
+ PatientExternalID,
27
+ PatientLabValue,
25
28
  PatientMedication,
26
- MedicationIndication,
27
- MedicationIntakeTime,
28
- PatientLabValue, # Assuming self.lab_values are PatientLabValue instances
29
- LabValue # If RequirementLinks expects actual LabValue instances
29
+ RawPdfFile,
30
30
  )
31
31
  from endoreg_db.utils.links.requirement_link import RequirementLinks
32
32
 
33
+
33
34
  class Patient(Person):
34
35
  """
35
36
  A class representing a patient.
@@ -44,31 +45,47 @@ class Patient(Person):
44
45
 
45
46
  """
46
47
 
47
- # -----gc-08-dev--changings---
48
- first_name = models.CharField(max_length=100) # type: ignore[assignment]
49
- last_name = models.CharField(max_length=100) # type: ignore[assignment]
50
- dob = models.DateField(null=True, blank=True) # type: ignore[assignment]
51
- gender = models.ForeignKey( # type: ignore[assignment]
52
- "Gender", on_delete=models.SET_NULL, null=True, blank=True
53
- )
54
- center = models.ForeignKey( # type: ignore[assignment]
55
- "Center", on_delete=models.SET_NULL, null=True, blank=True
56
- )
48
+ first_name = models.CharField(max_length=100)
49
+ last_name = models.CharField(max_length=100)
50
+ dob = models.DateField(null=True, blank=True)
51
+ gender = models.ForeignKey("Gender", on_delete=models.SET_NULL, null=True, blank=True)
52
+ center = models.ForeignKey("Center", on_delete=models.SET_NULL, null=True, blank=True)
57
53
  patient_hash = models.CharField(max_length=255, blank=True, null=True)
58
-
54
+
59
55
  objects = models.Manager() # Default manager
60
56
 
61
57
  if TYPE_CHECKING:
62
- first_name: str
63
- last_name: str
64
- dob: datetime.date
65
- gender: "Gender"
66
- center: "Center"
67
- events: models.QuerySet["PatientEvent"]
68
- diseases: models.QuerySet["PatientDisease"]
69
- patient_examinations: models.QuerySet["PatientExamination"]
70
- anonymexaminationreport_set: models.QuerySet["AnonymExaminationReport"]
71
- anonymhistologyreport_set: models.QuerySet["AnonymHistologyReport"]
58
+ from django.db.models.manager import RelatedManager
59
+
60
+ first_name: models.CharField[str]
61
+ last_name: models.CharField[str]
62
+ dob: models.DateField[date | None]
63
+ gender: models.ForeignKey["Gender | None"]
64
+ center: models.ForeignKey["Center | None"]
65
+
66
+ @property
67
+ def events(self) -> RelatedManager[PatientEvent]: ...
68
+
69
+ @property
70
+ def diseases(self) -> RelatedManager[PatientDisease]: ...
71
+
72
+ @property
73
+ def patient_examinations(self) -> RelatedManager[PatientExamination]: ...
74
+
75
+ @property
76
+ def anonymexaminationreport_set(self) -> RelatedManager[AnonymExaminationReport]: ...
77
+
78
+ @property
79
+ def anonymhistologyreport_set(self) -> RelatedManager[AnonymHistologyReport]: ...
80
+
81
+ @property
82
+ def external_ids(self) -> RelatedManager[PatientExternalID]: ...
83
+
84
+ @property
85
+ def patientmedication_set(self) -> RelatedManager[PatientMedication]: ...
86
+
87
+ @property
88
+ def lab_values(self) -> RelatedManager[PatientLabValue]: ...
72
89
 
73
90
  def __str__(self):
74
91
  return f"{self.first_name} {self.last_name} ({self.dob})"
@@ -77,12 +94,13 @@ class Patient(Person):
77
94
  def get_or_create_pseudo_patient_by_hash(
78
95
  cls,
79
96
  patient_hash: str,
80
- center: "Center" = None,
81
- gender: "Gender | str" = None, # Allow string type hint
82
- birth_month: int = None,
83
- birth_year: int = None,
97
+ center: Optional["Center"] = None,
98
+ gender: Optional["Gender | str"] = None, # Allow string type hint
99
+ birth_month: Optional[int] = None,
100
+ birth_year: Optional[int] = None,
84
101
  ):
85
- from endoreg_db.utils import random_day_by_year, create_mock_patient_name
102
+ from endoreg_db.utils import create_mock_patient_name, random_day_by_year
103
+
86
104
  from ....other import Gender # Import Gender model
87
105
 
88
106
  created = False
@@ -96,9 +114,7 @@ class Patient(Person):
96
114
  # If no patient with the given hash exists, create a new pseudo patient
97
115
  assert center, "Center must be provided to create a new pseudo patient"
98
116
  assert gender, "Gender must be provided to create a new pseudo patient"
99
- assert birth_month, (
100
- "Birth month must be provided to create a new pseudo patient"
101
- )
117
+ assert birth_month, "Birth month must be provided to create a new pseudo patient"
102
118
  assert birth_year, "Birth year must be provided to create a new pseudo patient"
103
119
 
104
120
  # Ensure gender is a Gender object
@@ -130,9 +146,8 @@ class Patient(Person):
130
146
 
131
147
  return patient, created
132
148
 
133
- def get_dob(self) -> datetime.date:
134
- dob: datetime.date = self.dob
135
- return dob
149
+ def get_dob(self) -> date | None:
150
+ return self.dob
136
151
 
137
152
  def get_patient_examinations(self): # field: self.patient_examinations
138
153
  """Returns all patient examinations for this patient ordered by date (most recent is first)."""
@@ -146,10 +161,9 @@ class Patient(Person):
146
161
  save: bool = True,
147
162
  ) -> "PatientExamination":
148
163
  """Creates a patient examination for this patient."""
164
+ from ....medical import Examination, PatientExamination
149
165
 
150
166
  if examination_name_str:
151
- from ....medical import Examination, PatientExamination
152
-
153
167
  examination = Examination.objects.get(name=examination_name_str)
154
168
  patient_examination = PatientExamination(
155
169
  patient=self,
@@ -159,21 +173,17 @@ class Patient(Person):
159
173
  )
160
174
 
161
175
  else:
162
- patient_examination = PatientExamination(
163
- patient=self, date_start=date_start, date_end=date_end
164
- )
176
+ patient_examination = PatientExamination(patient=self, date_start=date_start, date_end=date_end)
165
177
 
166
178
  if save:
167
179
  patient_examination.save()
168
180
 
169
181
  return patient_examination
170
182
 
171
- def create_examination_by_indication(
172
- self, indication: "ExaminationIndication", date_start: datetime = None, date_end: datetime = None
173
- ):
183
+ def create_examination_by_indication(self, indication: "ExaminationIndication", date_start: Optional[datetime] = None, date_end: Optional[datetime] = None):
174
184
  from ....medical import (
175
- PatientExaminationIndication,
176
185
  PatientExamination,
186
+ PatientExaminationIndication,
177
187
  )
178
188
 
179
189
  examination = indication.get_examination()
@@ -187,9 +197,7 @@ class Patient(Person):
187
197
 
188
198
  patient_examination.save()
189
199
 
190
- patient_examination_indication = PatientExaminationIndication.objects.create(
191
- patient_examination=patient_examination, examination_indication=indication
192
- )
200
+ patient_examination_indication = PatientExaminationIndication.objects.create(patient_examination=patient_examination, examination_indication=indication)
193
201
  patient_examination_indication.save()
194
202
 
195
203
  return patient_examination, patient_examination_indication
@@ -197,13 +205,13 @@ class Patient(Person):
197
205
  def create_event(
198
206
  self,
199
207
  event_name_str: str,
200
- date_start: datetime = None,
201
- date_end: datetime = None,
202
- description: str = None,
208
+ date_start: Optional[datetime] = None,
209
+ date_end: Optional[datetime] = None,
210
+ description: Optional[str] = None,
203
211
  ):
204
212
  """
205
213
  Creates a patient event with the specified event name and start date.
206
-
214
+
207
215
  If no start date is provided, the current datetime is used. Returns the created PatientEvent instance.
208
216
  """
209
217
  from ....medical import Event, PatientEvent
@@ -224,16 +232,17 @@ class Patient(Person):
224
232
  def create_examination_by_pdf(self, pdf: "RawPdfFile"):
225
233
  """
226
234
  Creates a patient examination and associates it with the provided PDF report file.
227
-
235
+
228
236
  The examination is created for this patient, saved, and linked to the given RawPdfFile instance. The PDF's examination field is updated and saved. Returns the created examination instance.
229
-
237
+
230
238
  Args:
231
239
  pdf: The RawPdfFile to associate with the new examination.
232
-
240
+
233
241
  Returns:
234
242
  The created PatientExamination instance.
235
243
  """
236
244
  from ....medical import PatientExamination
245
+
237
246
  patient_examination = PatientExamination(patient=self)
238
247
  patient_examination.save()
239
248
  pdf.examination = patient_examination
@@ -264,9 +273,7 @@ class Patient(Person):
264
273
  return gender_obj
265
274
 
266
275
  @classmethod
267
- def get_random_age(
268
- cls, min_age=55, max_age=90, mean_age=65, std_age=10, distribution="normal"
269
- ):
276
+ def get_random_age(cls, min_age=55, max_age=90, mean_age=65, std_age=10, distribution="normal"):
270
277
  """
271
278
  Get a random age based on the given distribution.
272
279
 
@@ -346,11 +353,17 @@ class Patient(Person):
346
353
  last_name=last_name,
347
354
  dob=dob,
348
355
  gender=gender,
349
- center=center_obj, # Assign the center object
356
+ center=center_obj, # Assign the center object
350
357
  )
351
358
  # No need to call save() again after create()
352
359
  return patient
353
360
 
361
+ @property
362
+ def age_safe(self) -> int:
363
+ age = self.age()
364
+ assert age is not None, "Patient age is not set."
365
+ return age
366
+
354
367
  def age(self) -> int | None:
355
368
  """
356
369
  Get the age of the patient.
@@ -362,11 +375,7 @@ class Patient(Person):
362
375
  dob = self.dob
363
376
  # Ensure dob is not None before calculation
364
377
  if dob:
365
- age = (
366
- current_date.year
367
- - dob.year
368
- - ((current_date.month, current_date.day) < (dob.month, dob.day))
369
- )
378
+ age = current_date.year - dob.year - ((current_date.month, current_date.day) < (dob.month, dob.day))
370
379
  return age
371
380
  return None # Or handle the case where dob is None appropriately
372
381
 
@@ -390,17 +399,11 @@ class Patient(Person):
390
399
 
391
400
  if isinstance(sample_type, str):
392
401
  sample_type = PatientLabSampleType.objects.get(name=sample_type)
393
- assert sample_type is not None, (
394
- f"Sample type with name '{sample_type}' not found."
395
- )
402
+ assert sample_type is not None, f"Sample type with name '{sample_type}' not found."
396
403
  elif not isinstance(sample_type, PatientLabSampleType):
397
- raise ValueError(
398
- "Sample type must be either a string or a PatientLabSampleType object."
399
- )
404
+ raise ValueError("Sample type must be either a string or a PatientLabSampleType object.")
400
405
 
401
- patient_lab_sample = PatientLabSample.objects.create(
402
- patient=self, sample_type=sample_type, date=date
403
- )
406
+ patient_lab_sample = PatientLabSample.objects.create(patient=self, sample_type=sample_type, date=date)
404
407
 
405
408
  return patient_lab_sample
406
409
 
@@ -411,50 +414,50 @@ class Patient(Person):
411
414
  as a RequirementLinks object. For a Patient, this includes their diseases, associated classification choices,
412
415
  all their lab values, and medication information.
413
416
  """
414
- from endoreg_db.utils.links.requirement_link import RequirementLinks
415
417
  from endoreg_db.models.medical.disease import Disease, DiseaseClassificationChoice
416
-
418
+
417
419
  # Imports for medication related models
418
420
  from endoreg_db.models.medical.medication.medication import Medication
419
421
  from endoreg_db.models.medical.medication.medication_indication import MedicationIndication
420
422
  from endoreg_db.models.medical.medication.medication_intake_time import MedicationIntakeTime
423
+ from endoreg_db.utils.links.requirement_link import RequirementLinks
421
424
  # PatientMedication objects are retrieved via self.patientmedication_set
422
425
  # PatientLabValue objects are retrieved via self.lab_values
423
426
 
424
- patient_disease_instances = list(self.diseases.all()) # These are PatientDisease model instances
427
+ patient_disease_instances = list(self.diseases.all()) # These are PatientDisease model instances
425
428
  actual_diseases: List[Disease] = []
426
429
  all_classification_choices: List[DiseaseClassificationChoice] = []
427
430
 
428
431
  for pd_instance in patient_disease_instances:
429
- if pd_instance.disease: # pd_instance.disease is a Disease instance
432
+ if pd_instance.disease: # pd_instance.disease is a Disease instance
430
433
  actual_diseases.append(pd_instance.disease)
431
434
  all_classification_choices.extend(list(pd_instance.classification_choices.all()))
432
-
435
+
433
436
  # Assuming self.lab_values is a related manager for PatientLabValue instances
434
- patient_lab_value_instances = list(self.lab_values.all()) # These are PatientLabValue model instances
437
+ patient_lab_value_instances = list(self.lab_values.all()) # These are PatientLabValue model instances
435
438
 
436
439
  # Medication information
437
440
  # self.patientmedication_set gives a QuerySet of PatientMedication
438
- patient_medication_instances = list(self.patientmedication_set.all())
439
-
441
+ patient_medication_instances = list(self.patientmedication_set.all())
442
+
440
443
  actual_medications: List[Medication] = []
441
444
  med_indications: List[MedicationIndication] = []
442
445
  med_intake_times: List[MedicationIntakeTime] = []
443
446
 
444
447
  for pm_instance in patient_medication_instances:
445
- if pm_instance.medication: # pm_instance.medication is a Medication instance
448
+ if pm_instance.medication: # pm_instance.medication is a Medication instance
446
449
  actual_medications.append(pm_instance.medication)
447
- if pm_instance.medication_indication: # pm_instance.medication_indication is a MedicationIndication instance
450
+ if pm_instance.medication_indication: # pm_instance.medication_indication is a MedicationIndication instance
448
451
  med_indications.append(pm_instance.medication_indication)
449
- med_intake_times.extend(list(pm_instance.intake_times.all())) # pm_instance.intake_times is a ManyRelatedManager for MedicationIntakeTime
452
+ med_intake_times.extend(list(pm_instance.intake_times.all())) # pm_instance.intake_times is a ManyRelatedManager for MedicationIntakeTime
450
453
 
451
454
  return RequirementLinks(
452
- diseases=list(set(actual_diseases)),
453
- patient_diseases=patient_disease_instances,
454
- disease_classification_choices=list(set(all_classification_choices)),
455
+ diseases=list(set(actual_diseases)),
456
+ patient_diseases=patient_disease_instances,
457
+ disease_classification_choices=list(set(all_classification_choices)),
455
458
  patient_lab_values=patient_lab_value_instances,
456
- medications=list(set(actual_medications)),
459
+ medications=list(set(actual_medications)),
457
460
  patient_medications=patient_medication_instances,
458
461
  medication_indications=list(set(med_indications)),
459
- medication_intake_times=list(set(med_intake_times))
462
+ medication_intake_times=list(set(med_intake_times)),
460
463
  )
@@ -0,0 +1,37 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from django.db import models # Add this import
4
+
5
+ if TYPE_CHECKING:
6
+ from endoreg_db.models import (
7
+ Patient,
8
+ )
9
+
10
+
11
+ class PatientExternalID(models.Model):
12
+ """
13
+ A class representing the identifier of external datasources.
14
+ Attributes:
15
+ external_id (str): The external ID value.
16
+ """
17
+
18
+ external_id: models.CharField = models.CharField(max_length=255)
19
+ patient = models.ForeignKey(
20
+ "Patient",
21
+ on_delete=models.CASCADE,
22
+ related_name="external_ids",
23
+ )
24
+ origin = models.CharField(max_length=255)
25
+
26
+ class Meta:
27
+ constraints = [
28
+ models.UniqueConstraint(
29
+ fields=("origin", "external_id"),
30
+ name="uniq_patient_external_id_per_origin",
31
+ )
32
+ ]
33
+
34
+ if TYPE_CHECKING:
35
+ patient: models.ForeignKey["Patient"]
36
+ origin: models.CharField[str]
37
+
@@ -22,6 +22,10 @@ class Person(models.Model):
22
22
  phone = models.CharField(max_length=255, blank=True, null=True)
23
23
  is_real_person = models.BooleanField(default=True)
24
24
 
25
+ post_code = models.CharField(max_length=20, blank=True, null=True)
26
+ city = models.CharField(max_length=255, blank=True, null=True)
27
+ street = models.CharField(max_length=255, blank=True, null=True)
28
+
25
29
  @abstractmethod
26
30
  def __str__(self):
27
31
  pass
@@ -1,24 +1,28 @@
1
- from django.db import models
2
1
  from typing import TYPE_CHECKING
3
2
 
3
+ from django.db import models
4
+
4
5
  if TYPE_CHECKING:
5
6
  from endoreg_db.models import PortalUserInfo
6
7
 
8
+
7
9
  class ProfessionManager(models.Manager):
8
10
  def get_by_natural_key(self, name):
9
11
  return self.get(name=name)
10
12
 
13
+
11
14
  class Profession(models.Model):
12
15
  objects = ProfessionManager()
13
16
  name = models.CharField(max_length=100)
14
17
  description = models.TextField(blank=True, null=True)
15
18
 
16
-
17
19
  if TYPE_CHECKING:
18
- portal_user_infos: models.QuerySet["PortalUserInfo"]
20
+
21
+ @property
22
+ def portal_user_infos(self) -> models.QuerySet["PortalUserInfo"]: ...
19
23
 
20
24
  def __str__(self):
21
25
  """
22
26
  Return the profession's name as its string representation.
23
27
  """
24
- return str(self.name)
28
+ return str(self.name)
@@ -1,17 +1,21 @@
1
- from django.db import models
2
1
  from typing import TYPE_CHECKING
2
+
3
+ from django.db import models
4
+
3
5
  # models.py in your main app
4
6
 
5
7
  if TYPE_CHECKING:
6
- from ..profession import Profession
7
- from endoreg_db.models import Examiner
8
8
  from django.contrib.auth.models import User
9
9
 
10
+ from endoreg_db.models import Examiner
11
+
12
+ from ..profession import Profession
13
+
10
14
 
11
15
  class PortalUserInfo(models.Model):
12
16
  user = models.OneToOneField("auth.User", on_delete=models.CASCADE)
13
17
  profession = models.ForeignKey(
14
- 'endoreg_db.Profession',
18
+ "endoreg_db.Profession",
15
19
  on_delete=models.CASCADE,
16
20
  blank=True,
17
21
  null=True,
@@ -29,9 +33,9 @@ class PortalUserInfo(models.Model):
29
33
  )
30
34
 
31
35
  if TYPE_CHECKING:
32
- user: "User"
33
- profession: "Profession"
34
- examiner: "Examiner"
36
+ user: models.OneToOneField["User"]
37
+ profession: models.ForeignKey["Profession|None"]
38
+ examiner: models.OneToOneField["Examiner|None"]
35
39
 
36
40
  def __str__(self):
37
41
  return self.user.username
@@ -1,20 +1,23 @@
1
- from django.db import models
2
1
  from typing import TYPE_CHECKING
3
2
 
3
+ from django.db import models
4
+
4
5
  from endoreg_db.utils.product.sum_emissions import sum_emissions
5
6
  from endoreg_db.utils.product.sum_weights import sum_weights
6
7
 
7
8
  if TYPE_CHECKING:
8
9
  from ...other.transport_route import TransportRoute
9
10
  from .product_group import ProductGroup
10
- from .reference_product import ReferenceProduct
11
11
  from .product_material import ProductMaterial
12
+ from .reference_product import ReferenceProduct
12
13
  # from .product_weight import ProductWeight
13
14
 
15
+
14
16
  class ProductManager(models.Manager):
15
17
  def get_by_natural_key(self, name):
16
18
  return self.get(name=name)
17
-
19
+
20
+
18
21
  class Product(models.Model):
19
22
  objects = ProductManager()
20
23
 
@@ -29,15 +32,14 @@ class Product(models.Model):
29
32
  )
30
33
 
31
34
  if TYPE_CHECKING:
32
- transport_route: "TransportRoute"
33
- product_group: "ProductGroup"
35
+ transport_route: models.ForeignKey["TransportRoute|None"]
36
+ product_group: models.ForeignKey["ProductGroup|None"]
34
37
  reference_products: models.QuerySet["ReferenceProduct"]
35
38
  product_product_materials: models.QuerySet["ProductMaterial"]
36
39
 
37
-
38
40
  def natural_key(self):
39
41
  return (self.name,)
40
-
42
+
41
43
  def __str__(self):
42
44
  result = f"{self.name}"
43
45
  if self.product_group:
@@ -49,41 +51,44 @@ class Product(models.Model):
49
51
  result += f"{self.transport_route})"
50
52
  else:
51
53
  result += "no transport route)"
52
-
54
+
53
55
  return result
54
-
56
+
55
57
  def _calculate_material_metric(self, component: str, calculation_func):
56
58
  """Helper method to calculate weight or emission for materials of a specific component."""
57
- from .product_material import ProductMaterial # Import locally to avoid circular dependency issues at module level
59
+ from .product_material import ProductMaterial # Import locally to avoid circular dependency issues at module level
60
+
58
61
  materials = ProductMaterial.objects.filter(product=self, component=component)
59
62
  return calculation_func(materials)
60
63
 
61
64
  def get_product_weight(self):
62
65
  """Get the product weight, prioritizing material definitions."""
63
66
  from .product_material import ProductMaterial
67
+
64
68
  # Check if there are specific material definitions for the product component
65
69
  if ProductMaterial.objects.filter(product=self, component="product").exists():
66
70
  return self.get_product_material_weight()
67
-
71
+
68
72
  # Fallback: check if there is a direct product weight defined (Not implemented yet)
69
73
  # TODO: Implement logic for ProductWeight lookup
70
- return None # Or appropriate default/error
74
+ return None # Or appropriate default/error
71
75
 
72
76
  def get_package_weight(self):
73
77
  """Get the package weight, prioritizing material definitions."""
74
78
  from .product_material import ProductMaterial
79
+
75
80
  # Check if there are specific material definitions for the package component
76
81
  if ProductMaterial.objects.filter(product=self, component="package").exists():
77
82
  return self.get_package_material_weight()
78
-
83
+
79
84
  # Fallback: check if there is a direct package weight defined (Not implemented yet)
80
85
  # TODO: Implement logic for PackageWeight lookup (if different from ProductWeight)
81
- return None # Or appropriate default/error
86
+ return None # Or appropriate default/error
82
87
 
83
88
  def get_product_material_weight(self):
84
89
  """Calculate the total weight based on defined product materials."""
85
90
  return self._calculate_material_metric("product", sum_weights)
86
-
91
+
87
92
  def get_package_material_weight(self):
88
93
  """Calculate the total weight based on defined package materials."""
89
94
  return self._calculate_material_metric("package", sum_weights)
@@ -1,54 +1,53 @@
1
- from django.db import models
2
1
  from typing import TYPE_CHECKING
3
2
 
3
+ from django.db import models
4
+
4
5
  from endoreg_db.models.other.unit import Unit
5
6
 
6
7
  if TYPE_CHECKING:
7
- from ...other.emission import EmissionFactor
8
+ from ...other.material import Material
8
9
  from ...other.unit import Unit
9
10
  from .product import Product
10
- from ...other.material import Material
11
+
11
12
 
12
13
  class ProductMaterial(models.Model):
13
14
  component = models.CharField(max_length=255)
14
15
  material = models.ForeignKey(
15
16
  "Material",
16
17
  on_delete=models.CASCADE,
17
- related_name="material_product_materials", # Changed related_name
18
+ related_name="material_product_materials", # Changed related_name
18
19
  )
19
20
  product = models.ForeignKey(
20
21
  "Product",
21
22
  on_delete=models.CASCADE,
22
- related_name="product_product_materials" # Changed related_name
23
+ related_name="product_product_materials", # Changed related_name
23
24
  )
24
25
  unit = models.ForeignKey(
25
26
  "Unit",
26
27
  on_delete=models.CASCADE,
27
- related_name="unit_product_materials", # Changed related_name
28
+ related_name="unit_product_materials", # Changed related_name
28
29
  )
29
30
  quantity = models.FloatField()
30
31
 
31
32
  if TYPE_CHECKING:
32
- product: "Product"
33
- material: "Material"
34
- unit: "Unit"
35
- emission_factor: "EmissionFactor"
33
+ product: models.ForeignKey["Product"]
34
+ material: models.ForeignKey["Material"]
35
+ unit: models.ForeignKey["Unit"]
36
36
 
37
37
  def get_emission(self) -> tuple[float, Unit]:
38
38
  emission_factor = self.material.emission_factor
39
39
  if emission_factor is None:
40
40
  raise Exception("No emission factor for material " + self.material.name + " found.")
41
-
41
+
42
42
  # make sure product_material.unit is the same as emission_factor.unit
43
- if self.unit != emission_factor.unit:
44
- raise Exception("Unit mismatch: " + self.unit.name + " != " + emission_factor.unit.name)
45
-
43
+ if self.unit is not None and emission_factor.unit is not None:
44
+ if self.unit != emission_factor.unit:
45
+ raise Exception("Unit mismatch: " + self.unit.name + " != " + emission_factor.unit.name)
46
+
46
47
  emmision_value = emission_factor.value * self.quantity
48
+ assert isinstance(emission_factor.unit, Unit)
47
49
  emission_unit = emission_factor.unit
48
50
  return emmision_value, emission_unit
49
-
51
+
50
52
  def __str__(self) -> str:
51
53
  return f"{self.product.name} - {self.material.name} - {self.quantity} {self.unit.name}"
52
-
53
-
54
-