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.
- endoreg_db/authz/auth.py +74 -0
- endoreg_db/authz/backends.py +168 -0
- endoreg_db/authz/management/commands/list_routes.py +18 -0
- endoreg_db/authz/middleware.py +83 -0
- endoreg_db/authz/permissions.py +127 -0
- endoreg_db/authz/policy.py +218 -0
- endoreg_db/authz/views_auth.py +66 -0
- endoreg_db/config/env.py +13 -8
- endoreg_db/data/__init__.py +8 -31
- endoreg_db/data/_examples/disease.yaml +55 -0
- endoreg_db/data/_examples/disease_classification.yaml +13 -0
- endoreg_db/data/_examples/disease_classification_choice.yaml +62 -0
- endoreg_db/data/_examples/event.yaml +64 -0
- endoreg_db/data/_examples/examination.yaml +72 -0
- endoreg_db/data/_examples/finding/anatomy_colon.yaml +128 -0
- endoreg_db/data/_examples/finding/colonoscopy.yaml +40 -0
- endoreg_db/data/_examples/finding/colonoscopy_bowel_prep.yaml +56 -0
- endoreg_db/data/_examples/finding/complication.yaml +16 -0
- endoreg_db/data/_examples/finding/data.yaml +105 -0
- endoreg_db/data/_examples/finding/examination_setting.yaml +16 -0
- endoreg_db/data/_examples/finding/medication_related.yaml +18 -0
- endoreg_db/data/_examples/finding/outcome.yaml +12 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_bowel_preparation.yaml +68 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_jnet.yaml +22 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_kudo.yaml +25 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_circularity.yaml +20 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_planarity.yaml +24 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_size.yaml +68 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_surface.yaml +20 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_location.yaml +80 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lst.yaml +21 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_nice.yaml +20 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_paris.yaml +26 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_sano.yaml +22 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_summary.yaml +53 -0
- endoreg_db/data/_examples/finding_classification/complication_generic.yaml +25 -0
- endoreg_db/data/_examples/finding_classification/examination_setting_generic.yaml +40 -0
- endoreg_db/data/_examples/finding_classification/histology_colo.yaml +51 -0
- endoreg_db/data/_examples/finding_classification/intervention_required.yaml +26 -0
- endoreg_db/data/_examples/finding_classification/medication_related.yaml +23 -0
- endoreg_db/data/_examples/finding_classification/visualized.yaml +33 -0
- endoreg_db/data/_examples/finding_classification_choice/bowel_preparation.yaml +78 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_circularity_default.yaml +32 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_jnet.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_kudo.yaml +23 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_lst.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_nice.yaml +17 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_paris.yaml +57 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_planarity_default.yaml +49 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_sano.yaml +14 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_surface_intact_default.yaml +36 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_location.yaml +229 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_not_complete_reason.yaml +19 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_size.yaml +82 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/complication_generic_types.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/examination_setting_generic_types.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/histology.yaml +24 -0
- endoreg_db/data/_examples/finding_classification_choice/histology_polyp.yaml +20 -0
- endoreg_db/data/_examples/finding_classification_choice/outcome.yaml +19 -0
- endoreg_db/data/_examples/finding_classification_choice/yes_no_na.yaml +11 -0
- endoreg_db/data/_examples/finding_classification_type/colonoscopy_basic.yaml +48 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy.yaml +43 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_colonoscopy.yaml +168 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_egd.yaml +128 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_ercp.yaml +32 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_eus_lower.yaml +9 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_eus_upper.yaml +36 -0
- endoreg_db/data/_examples/finding_intervention_type/endoscopy.yaml +15 -0
- endoreg_db/data/_examples/finding_type/data.yaml +43 -0
- endoreg_db/data/_examples/requirement/age.yaml +26 -0
- endoreg_db/data/_examples/requirement/colonoscopy_baseline_austria.yaml +45 -0
- endoreg_db/data/_examples/requirement/disease_cardiovascular.yaml +79 -0
- endoreg_db/data/_examples/requirement/disease_classification_choice_cardiovascular.yaml +41 -0
- endoreg_db/data/_examples/requirement/disease_hepatology.yaml +12 -0
- endoreg_db/data/_examples/requirement/disease_misc.yaml +12 -0
- endoreg_db/data/_examples/requirement/disease_renal.yaml +96 -0
- endoreg_db/data/_examples/requirement/endoscopy_bleeding_risk.yaml +59 -0
- endoreg_db/data/_examples/requirement/event_cardiology.yaml +251 -0
- endoreg_db/data/_examples/requirement/event_requirements.yaml +145 -0
- endoreg_db/data/_examples/requirement/finding_colon_polyp.yaml +50 -0
- endoreg_db/data/_examples/requirement/gender.yaml +25 -0
- endoreg_db/data/_examples/requirement/lab_value.yaml +441 -0
- endoreg_db/data/_examples/requirement/medication.yaml +93 -0
- endoreg_db/data/_examples/requirement_operator/age.yaml +13 -0
- endoreg_db/data/_examples/requirement_operator/lab_operators.yaml +129 -0
- endoreg_db/data/_examples/requirement_operator/model_operators.yaml +96 -0
- endoreg_db/data/_examples/requirement_set/01_endoscopy_generic.yaml +48 -0
- endoreg_db/data/_examples/requirement_set/colonoscopy_austria_screening.yaml +57 -0
- endoreg_db/data/_examples/yaml_examples.xlsx +0 -0
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +4 -3
- endoreg_db/data/event_classification/data.yaml +4 -0
- endoreg_db/data/event_classification_choice/data.yaml +9 -0
- endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +43 -70
- endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +22 -52
- endoreg_db/data/finding_classification/colonoscopy_location.yaml +31 -62
- endoreg_db/data/finding_classification/histology_colo.yaml +28 -36
- endoreg_db/data/requirement/colon_polyp_intervention.yaml +49 -0
- endoreg_db/data/requirement/coloreg_colon_polyp.yaml +49 -0
- endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +31 -12
- endoreg_db/data/requirement_set/01_laboratory.yaml +13 -0
- endoreg_db/data/requirement_set/02_endoscopy_bleeding_risk.yaml +46 -0
- endoreg_db/data/requirement_set/90_coloreg.yaml +178 -0
- endoreg_db/data/requirement_set/_old_ +109 -0
- endoreg_db/data/requirement_set_type/data.yaml +21 -0
- endoreg_db/data/setup_config.yaml +4 -4
- endoreg_db/data/tag/requirement_set_tags.yaml +21 -0
- endoreg_db/exceptions.py +5 -2
- endoreg_db/helpers/data_loader.py +1 -1
- endoreg_db/management/commands/create_model_meta_from_huggingface.py +21 -10
- endoreg_db/management/commands/create_multilabel_model_meta.py +299 -129
- endoreg_db/management/commands/import_video.py +9 -10
- endoreg_db/management/commands/import_video_with_classification.py +1 -1
- endoreg_db/management/commands/init_default_ai_model.py +1 -1
- endoreg_db/management/commands/list_routes.py +18 -0
- endoreg_db/management/commands/load_center_data.py +12 -12
- endoreg_db/management/commands/load_requirement_data.py +60 -31
- endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
- endoreg_db/management/commands/setup_endoreg_db.py +3 -3
- endoreg_db/management/commands/storage_management.py +271 -203
- endoreg_db/migrations/0001_initial.py +1799 -1300
- endoreg_db/migrations/0002_requirementset_depends_on.py +18 -0
- endoreg_db/migrations/_old/0001_initial.py +1857 -0
- endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +68 -0
- endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +77 -0
- endoreg_db/migrations/_old/0005_merge_20251111_1003.py +14 -0
- endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +68 -0
- endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +89 -0
- endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +27 -0
- endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +21 -0
- endoreg_db/models/__init__.py +78 -123
- endoreg_db/models/administration/__init__.py +21 -42
- endoreg_db/models/administration/ai/active_model.py +2 -2
- endoreg_db/models/administration/ai/ai_model.py +7 -6
- endoreg_db/models/administration/case/__init__.py +1 -15
- endoreg_db/models/administration/case/case.py +3 -3
- endoreg_db/models/administration/case/case_template/__init__.py +2 -14
- endoreg_db/models/administration/case/case_template/case_template.py +2 -124
- endoreg_db/models/administration/case/case_template/case_template_rule.py +2 -268
- endoreg_db/models/administration/case/case_template/case_template_rule_value.py +2 -85
- endoreg_db/models/administration/case/case_template/case_template_type.py +2 -25
- endoreg_db/models/administration/center/center.py +33 -19
- endoreg_db/models/administration/center/center_product.py +12 -9
- endoreg_db/models/administration/center/center_resource.py +25 -19
- endoreg_db/models/administration/center/center_shift.py +21 -17
- endoreg_db/models/administration/center/center_waste.py +16 -8
- endoreg_db/models/administration/person/__init__.py +2 -0
- endoreg_db/models/administration/person/employee/employee.py +10 -5
- endoreg_db/models/administration/person/employee/employee_qualification.py +9 -4
- endoreg_db/models/administration/person/employee/employee_type.py +12 -6
- endoreg_db/models/administration/person/examiner/examiner.py +13 -11
- endoreg_db/models/administration/person/patient/__init__.py +2 -0
- endoreg_db/models/administration/person/patient/patient.py +103 -100
- endoreg_db/models/administration/person/patient/patient_external_id.py +37 -0
- endoreg_db/models/administration/person/person.py +4 -0
- endoreg_db/models/administration/person/profession/__init__.py +8 -4
- endoreg_db/models/administration/person/user/portal_user_information.py +11 -7
- endoreg_db/models/administration/product/product.py +20 -15
- endoreg_db/models/administration/product/product_material.py +17 -18
- endoreg_db/models/administration/product/product_weight.py +12 -8
- endoreg_db/models/administration/product/reference_product.py +23 -55
- endoreg_db/models/administration/qualification/qualification.py +7 -3
- endoreg_db/models/administration/qualification/qualification_type.py +7 -3
- endoreg_db/models/administration/shift/scheduled_days.py +8 -5
- endoreg_db/models/administration/shift/shift.py +16 -12
- endoreg_db/models/administration/shift/shift_type.py +23 -31
- endoreg_db/models/label/__init__.py +7 -8
- endoreg_db/models/label/annotation/image_classification.py +10 -9
- endoreg_db/models/label/annotation/video_segmentation_annotation.py +8 -5
- endoreg_db/models/label/label.py +15 -15
- endoreg_db/models/label/label_set.py +19 -6
- endoreg_db/models/label/label_type.py +1 -1
- endoreg_db/models/label/label_video_segment/_create_from_video.py +5 -8
- endoreg_db/models/label/label_video_segment/label_video_segment.py +76 -102
- endoreg_db/models/label/video_segmentation_label.py +4 -0
- endoreg_db/models/label/video_segmentation_labelset.py +4 -3
- endoreg_db/models/media/frame/frame.py +22 -22
- endoreg_db/models/media/pdf/raw_pdf.py +110 -182
- endoreg_db/models/media/pdf/report_file.py +25 -29
- endoreg_db/models/media/pdf/report_reader/report_reader_config.py +30 -46
- endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
- endoreg_db/models/media/video/__init__.py +1 -0
- endoreg_db/models/media/video/create_from_file.py +48 -56
- endoreg_db/models/media/video/pipe_2.py +8 -9
- endoreg_db/models/media/video/video_file.py +150 -108
- endoreg_db/models/media/video/video_file_ai.py +288 -74
- endoreg_db/models/media/video/video_file_anonymize.py +38 -38
- endoreg_db/models/media/video/video_file_frames/__init__.py +3 -1
- endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +7 -9
- endoreg_db/models/media/video/video_file_frames/_delete_frames.py +9 -8
- endoreg_db/models/media/video/video_file_frames/_extract_frames.py +38 -45
- endoreg_db/models/media/video/video_file_frames/_get_frame.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +4 -18
- endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -3
- endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +7 -6
- endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_get_frames.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +15 -25
- endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +26 -23
- endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +23 -14
- endoreg_db/models/media/video/video_file_io.py +109 -62
- endoreg_db/models/media/video/video_file_meta/get_crop_template.py +3 -3
- endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +5 -3
- endoreg_db/models/media/video/video_file_meta/get_fps.py +37 -34
- endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +19 -25
- endoreg_db/models/media/video/video_file_meta/text_meta.py +41 -38
- endoreg_db/models/media/video/video_file_meta/video_meta.py +14 -7
- endoreg_db/models/media/video/video_file_segments.py +24 -17
- endoreg_db/models/media/video/video_metadata.py +19 -35
- endoreg_db/models/media/video/video_processing.py +96 -95
- endoreg_db/models/medical/contraindication/__init__.py +13 -3
- endoreg_db/models/medical/disease.py +22 -16
- endoreg_db/models/medical/event.py +31 -18
- endoreg_db/models/medical/examination/__init__.py +13 -6
- endoreg_db/models/medical/examination/examination.py +17 -18
- endoreg_db/models/medical/examination/examination_indication.py +26 -25
- endoreg_db/models/medical/examination/examination_time.py +16 -6
- endoreg_db/models/medical/examination/examination_time_type.py +9 -6
- endoreg_db/models/medical/examination/examination_type.py +3 -4
- endoreg_db/models/medical/finding/finding.py +38 -39
- endoreg_db/models/medical/finding/finding_classification.py +37 -48
- endoreg_db/models/medical/finding/finding_intervention.py +27 -22
- endoreg_db/models/medical/finding/finding_type.py +13 -12
- endoreg_db/models/medical/hardware/endoscope.py +20 -26
- endoreg_db/models/medical/hardware/endoscopy_processor.py +2 -2
- endoreg_db/models/medical/laboratory/lab_value.py +62 -91
- endoreg_db/models/medical/medication/medication.py +22 -10
- endoreg_db/models/medical/medication/medication_indication.py +29 -3
- endoreg_db/models/medical/medication/medication_indication_type.py +25 -14
- endoreg_db/models/medical/medication/medication_intake_time.py +31 -19
- endoreg_db/models/medical/medication/medication_schedule.py +27 -16
- endoreg_db/models/medical/organ/__init__.py +15 -12
- endoreg_db/models/medical/patient/medication_examples.py +1 -5
- endoreg_db/models/medical/patient/patient_disease.py +20 -23
- endoreg_db/models/medical/patient/patient_event.py +19 -22
- endoreg_db/models/medical/patient/patient_examination.py +48 -54
- endoreg_db/models/medical/patient/patient_examination_indication.py +16 -14
- endoreg_db/models/medical/patient/patient_finding.py +122 -139
- endoreg_db/models/medical/patient/patient_finding_classification.py +44 -49
- endoreg_db/models/medical/patient/patient_finding_intervention.py +8 -19
- endoreg_db/models/medical/patient/patient_lab_sample.py +28 -23
- endoreg_db/models/medical/patient/patient_lab_value.py +82 -89
- endoreg_db/models/medical/patient/patient_medication.py +27 -38
- endoreg_db/models/medical/patient/patient_medication_schedule.py +28 -36
- endoreg_db/models/medical/risk/risk.py +7 -6
- endoreg_db/models/medical/risk/risk_type.py +8 -5
- endoreg_db/models/metadata/model_meta.py +60 -29
- endoreg_db/models/metadata/model_meta_logic.py +125 -18
- endoreg_db/models/metadata/pdf_meta.py +19 -24
- endoreg_db/models/metadata/sensitive_meta.py +102 -85
- endoreg_db/models/metadata/sensitive_meta_logic.py +192 -173
- endoreg_db/models/metadata/video_meta.py +51 -31
- endoreg_db/models/metadata/video_prediction_logic.py +16 -23
- endoreg_db/models/metadata/video_prediction_meta.py +29 -33
- endoreg_db/models/other/distribution/date_value_distribution.py +89 -29
- endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +21 -5
- endoreg_db/models/other/distribution/numeric_value_distribution.py +114 -53
- endoreg_db/models/other/distribution/single_categorical_value_distribution.py +4 -3
- endoreg_db/models/other/emission/emission_factor.py +18 -8
- endoreg_db/models/other/gender.py +10 -5
- endoreg_db/models/other/information_source.py +25 -25
- endoreg_db/models/other/material.py +9 -5
- endoreg_db/models/other/resource.py +6 -4
- endoreg_db/models/other/tag.py +10 -5
- endoreg_db/models/other/transport_route.py +13 -8
- endoreg_db/models/other/unit.py +10 -6
- endoreg_db/models/other/waste.py +6 -5
- endoreg_db/models/requirement/requirement.py +580 -272
- endoreg_db/models/requirement/requirement_error.py +85 -0
- endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +268 -0
- endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +3 -6
- endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +90 -64
- endoreg_db/models/requirement/requirement_operator.py +36 -33
- endoreg_db/models/requirement/requirement_set.py +74 -57
- endoreg_db/models/state/__init__.py +4 -4
- endoreg_db/models/state/abstract.py +2 -2
- endoreg_db/models/state/anonymization.py +12 -0
- endoreg_db/models/state/audit_ledger.py +46 -47
- endoreg_db/models/state/label_video_segment.py +9 -0
- endoreg_db/models/state/raw_pdf.py +40 -46
- endoreg_db/models/state/sensitive_meta.py +6 -2
- endoreg_db/models/state/video.py +58 -53
- endoreg_db/models/upload_job.py +32 -55
- endoreg_db/models/utils.py +1 -2
- endoreg_db/root_urls.py +21 -2
- endoreg_db/serializers/__init__.py +0 -2
- endoreg_db/serializers/anonymization.py +18 -10
- endoreg_db/serializers/meta/report_meta.py +1 -1
- endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
- endoreg_db/serializers/misc/file_overview.py +11 -99
- endoreg_db/serializers/requirements/requirement_sets.py +92 -22
- endoreg_db/serializers/video/segmentation.py +2 -1
- endoreg_db/serializers/video/video_processing_history.py +20 -5
- endoreg_db/services/anonymization.py +75 -73
- endoreg_db/services/lookup_service.py +37 -24
- endoreg_db/services/pdf_import.py +166 -68
- endoreg_db/services/storage_aware_video_processor.py +140 -114
- endoreg_db/services/video_import.py +193 -283
- endoreg_db/urls/__init__.py +7 -20
- endoreg_db/urls/media.py +108 -67
- endoreg_db/urls/root_urls.py +29 -0
- endoreg_db/utils/__init__.py +15 -5
- endoreg_db/utils/ai/multilabel_classification_net.py +116 -20
- endoreg_db/utils/case_generator/__init__.py +3 -0
- endoreg_db/utils/dataloader.py +88 -16
- endoreg_db/utils/defaults/set_default_center.py +32 -0
- endoreg_db/utils/names.py +22 -16
- endoreg_db/utils/permissions.py +2 -1
- endoreg_db/utils/pipelines/process_video_dir.py +1 -1
- endoreg_db/utils/requirement_operator_logic/model_evaluators.py +414 -127
- endoreg_db/utils/setup_config.py +8 -5
- endoreg_db/utils/storage.py +115 -0
- endoreg_db/utils/validate_endo_roi.py +8 -2
- endoreg_db/utils/video/ffmpeg_wrapper.py +184 -188
- endoreg_db/views/__init__.py +0 -10
- endoreg_db/views/anonymization/media_management.py +198 -163
- endoreg_db/views/anonymization/overview.py +4 -1
- endoreg_db/views/anonymization/validate.py +174 -40
- endoreg_db/views/media/__init__.py +2 -0
- endoreg_db/views/media/pdf_media.py +131 -152
- endoreg_db/views/media/sensitive_metadata.py +46 -6
- endoreg_db/views/media/video_media.py +89 -82
- endoreg_db/views/media/video_segments.py +2 -3
- endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
- endoreg_db/views/patient/patient.py +5 -4
- endoreg_db/views/pdf/pdf_stream.py +20 -21
- endoreg_db/views/pdf/reimport.py +11 -32
- endoreg_db/views/requirement/evaluate.py +188 -187
- endoreg_db/views/requirement/lookup.py +17 -3
- endoreg_db/views/requirement/requirement_utils.py +89 -0
- endoreg_db/views/video/__init__.py +0 -2
- endoreg_db/views/video/correction.py +2 -2
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +341 -245
- endoreg_db/models/administration/permissions/__init__.py +0 -44
- endoreg_db/models/media/video/video_file_frames.py +0 -0
- endoreg_db/models/metadata/frame_ocr_result.py +0 -0
- endoreg_db/models/rule/__init__.py +0 -13
- endoreg_db/models/rule/rule.py +0 -27
- endoreg_db/models/rule/rule_applicator.py +0 -224
- endoreg_db/models/rule/rule_attribute_dtype.py +0 -17
- endoreg_db/models/rule/rule_type.py +0 -20
- endoreg_db/models/rule/ruleset.py +0 -17
- endoreg_db/serializers/video/video_metadata.py +0 -105
- endoreg_db/urls/report.py +0 -48
- endoreg_db/urls/video.py +0 -61
- endoreg_db/utils/case_generator/case_generator.py +0 -159
- endoreg_db/utils/case_generator/utils.py +0 -30
- endoreg_db/views/report/__init__.py +0 -9
- endoreg_db/views/report/report_list.py +0 -112
- endoreg_db/views/report/report_with_secure_url.py +0 -28
- endoreg_db/views/report/start_examination.py +0 -7
- endoreg_db/views.py +0 -0
- /endoreg_db/data/{requirement_set → _examples/requirement_set}/endoscopy_bleeding_risk.yaml +0 -0
- /endoreg_db/migrations/{0002_add_video_correction_models.py → _old/0002_add_video_correction_models.py} +0 -0
- /endoreg_db/migrations/{0003_add_center_display_name.py → _old/0003_add_center_display_name.py} +0 -0
- /endoreg_db/{models/media/video/refactor_plan.md → views/pdf/pdf_stream_views.py} +0 -0
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,33 +4,27 @@
|
|
|
4
4
|
# Class contains classmethod to create object from pdf file
|
|
5
5
|
# objects contains methods to extract text, extract metadata from text and anonymize text from pdf file uzing agl_report_reader.ReportReader class
|
|
6
6
|
# ------------------------------------------------------------------------------
|
|
7
|
-
import
|
|
8
|
-
from typing import TYPE_CHECKING, Optional, Union
|
|
7
|
+
from typing import TYPE_CHECKING, Optional, cast
|
|
9
8
|
|
|
10
9
|
from django.core.exceptions import ValidationError
|
|
11
10
|
from django.core.files import File
|
|
12
11
|
from django.core.validators import FileExtensionValidator
|
|
13
12
|
from django.db import models
|
|
14
|
-
from numpy import isin # Import Django File
|
|
15
13
|
|
|
16
14
|
from endoreg_db.utils.file_operations import get_uuid_filename
|
|
17
15
|
from endoreg_db.utils.hashs import get_pdf_hash
|
|
18
|
-
from endoreg_db.utils.paths import PDF_DIR
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
from endoreg_db.utils.paths import PDF_DIR
|
|
17
|
+
from endoreg_db.utils.storage import (
|
|
18
|
+
delete_field_file,
|
|
19
|
+
ensure_local_file,
|
|
20
|
+
file_exists,
|
|
21
|
+
save_local_file,
|
|
22
|
+
)
|
|
22
23
|
|
|
23
24
|
if TYPE_CHECKING:
|
|
24
|
-
from
|
|
25
|
-
Examiner,
|
|
26
|
-
Patient,
|
|
27
|
-
)
|
|
25
|
+
from django.db.models.fields.files import FieldFile
|
|
28
26
|
|
|
29
|
-
from
|
|
30
|
-
from ...medical.patient import PatientExamination
|
|
31
|
-
from ...metadata.pdf_meta import PdfType
|
|
32
|
-
from ...state import RawPdfState
|
|
33
|
-
from .report_file import AnonymExaminationReport
|
|
27
|
+
from endoreg_db.models.state import RawPdfState
|
|
34
28
|
|
|
35
29
|
# setup logging to pdf_import.log
|
|
36
30
|
import logging
|
|
@@ -42,6 +36,7 @@ logger = logging.getLogger("raw_pdf")
|
|
|
42
36
|
|
|
43
37
|
|
|
44
38
|
class RawPdfFile(models.Model):
|
|
39
|
+
objects = models.Manager()
|
|
45
40
|
# Fields from AbstractPdfFile
|
|
46
41
|
pdf_hash = models.CharField(max_length=255, unique=True)
|
|
47
42
|
pdf_type = models.ForeignKey(
|
|
@@ -55,50 +50,89 @@ class RawPdfFile(models.Model):
|
|
|
55
50
|
on_delete=models.SET_NULL,
|
|
56
51
|
blank=True,
|
|
57
52
|
null=True,
|
|
58
|
-
)
|
|
53
|
+
)
|
|
59
54
|
examination = models.ForeignKey(
|
|
60
55
|
"PatientExamination",
|
|
61
56
|
on_delete=models.SET_NULL,
|
|
62
57
|
blank=True,
|
|
63
58
|
null=True,
|
|
64
59
|
related_name="raw_pdf_files",
|
|
65
|
-
)
|
|
60
|
+
)
|
|
66
61
|
examiner = models.ForeignKey(
|
|
67
62
|
"Examiner",
|
|
68
63
|
on_delete=models.SET_NULL,
|
|
69
64
|
blank=True,
|
|
70
65
|
null=True,
|
|
71
|
-
)
|
|
66
|
+
)
|
|
72
67
|
text = models.TextField(blank=True, null=True)
|
|
73
68
|
date_created = models.DateTimeField(auto_now_add=True)
|
|
74
69
|
date_modified = models.DateTimeField(auto_now=True)
|
|
75
|
-
anonymized = models.BooleanField(
|
|
76
|
-
default=False, help_text="True if the PDF has been anonymized."
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
# Fields specific to RawPdfFile (keeping existing related_names)
|
|
70
|
+
anonymized = models.BooleanField(default=False, help_text="True if the PDF has been anonymized.")
|
|
80
71
|
file = models.FileField(
|
|
81
72
|
# Use the relative path from the specific PDF_DIR
|
|
82
73
|
upload_to=PDF_DIR.name,
|
|
83
74
|
validators=[FileExtensionValidator(allowed_extensions=["pdf"])],
|
|
84
|
-
)
|
|
85
|
-
|
|
75
|
+
)
|
|
86
76
|
anonymized_file = models.FileField(
|
|
87
77
|
upload_to=PDF_DIR.name,
|
|
88
78
|
validators=[FileExtensionValidator(allowed_extensions=["pdf"])],
|
|
89
79
|
null=True,
|
|
90
80
|
blank=True,
|
|
91
|
-
)
|
|
92
|
-
|
|
81
|
+
)
|
|
93
82
|
state = models.OneToOneField(
|
|
94
83
|
"RawPdfState",
|
|
95
84
|
on_delete=models.SET_NULL,
|
|
96
85
|
blank=True,
|
|
97
86
|
null=True,
|
|
98
87
|
related_name="raw_pdf_file",
|
|
99
|
-
)
|
|
88
|
+
)
|
|
89
|
+
patient = models.ForeignKey(
|
|
90
|
+
"Patient",
|
|
91
|
+
on_delete=models.SET_NULL,
|
|
92
|
+
blank=True,
|
|
93
|
+
null=True,
|
|
94
|
+
related_name="raw_pdf_files",
|
|
95
|
+
)
|
|
96
|
+
sensitive_meta = models.ForeignKey(
|
|
97
|
+
"SensitiveMeta",
|
|
98
|
+
on_delete=models.SET_NULL,
|
|
99
|
+
related_name="raw_pdf_files",
|
|
100
|
+
null=True,
|
|
101
|
+
blank=True,
|
|
102
|
+
)
|
|
103
|
+
state_report_processing_required = models.BooleanField(default=True)
|
|
104
|
+
state_report_processed = models.BooleanField(default=False)
|
|
105
|
+
raw_meta = models.JSONField(blank=True, null=True)
|
|
106
|
+
anonym_examination_report = models.OneToOneField(
|
|
107
|
+
"AnonymExaminationReport",
|
|
108
|
+
on_delete=models.SET_NULL,
|
|
109
|
+
blank=True,
|
|
110
|
+
null=True,
|
|
111
|
+
related_name="raw_pdf_file",
|
|
112
|
+
)
|
|
113
|
+
anonymized_text = models.TextField(blank=True, null=True)
|
|
100
114
|
|
|
101
|
-
|
|
115
|
+
# Type hinting is needed, improve and use correct django types
|
|
116
|
+
if TYPE_CHECKING:
|
|
117
|
+
from endoreg_db.models import (
|
|
118
|
+
AnonymExaminationReport,
|
|
119
|
+
Center,
|
|
120
|
+
Examiner,
|
|
121
|
+
Patient,
|
|
122
|
+
PatientExamination,
|
|
123
|
+
RawPdfState,
|
|
124
|
+
SensitiveMeta,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
center: models.ForeignKey["Center | None"]
|
|
128
|
+
examination: models.ForeignKey["PatientExamination | None"]
|
|
129
|
+
examiner: models.ForeignKey["Examiner | None"]
|
|
130
|
+
state: models.ForeignKey["RawPdfState | None"]
|
|
131
|
+
patient: models.ForeignKey["Patient | None"]
|
|
132
|
+
sensitive_meta: models.ForeignKey["SensitiveMeta | None"]
|
|
133
|
+
anonym_examination_report: models.OneToOneField["AnonymExaminationReport | None"]
|
|
134
|
+
file = cast(FieldFile, file)
|
|
135
|
+
anonymized_file = cast(FieldFile, anonymized_file)
|
|
102
136
|
|
|
103
137
|
@property
|
|
104
138
|
def uuid(self):
|
|
@@ -130,7 +164,10 @@ class RawPdfFile(models.Model):
|
|
|
130
164
|
"""
|
|
131
165
|
Sets the file path of the stored PDF file.
|
|
132
166
|
"""
|
|
133
|
-
|
|
167
|
+
if not file_path.exists():
|
|
168
|
+
raise FileNotFoundError(f"File path does not exist: {file_path}")
|
|
169
|
+
|
|
170
|
+
save_local_file(self.file, file_path, name=file_path.name, save=False)
|
|
134
171
|
self.save(update_fields=["file"])
|
|
135
172
|
|
|
136
173
|
@property
|
|
@@ -149,7 +186,10 @@ class RawPdfFile(models.Model):
|
|
|
149
186
|
"""
|
|
150
187
|
Sets the file path of the anonymized PDF file.
|
|
151
188
|
"""
|
|
152
|
-
|
|
189
|
+
if not file_path.exists():
|
|
190
|
+
raise FileNotFoundError(f"File path does not exist: {file_path}")
|
|
191
|
+
|
|
192
|
+
save_local_file(self.anonymized_file, file_path, name=file_path.name, save=False)
|
|
153
193
|
self.save(update_fields=["anonymized_file"])
|
|
154
194
|
|
|
155
195
|
def get_raw_file_path(self) -> Optional[Path]:
|
|
@@ -228,55 +268,10 @@ class RawPdfFile(models.Model):
|
|
|
228
268
|
Returns the URL of the stored PDF file if available; otherwise, returns None.
|
|
229
269
|
"""
|
|
230
270
|
try:
|
|
231
|
-
return
|
|
232
|
-
self.anonymized_file.url
|
|
233
|
-
if self.anonymized_file and self.anonymized_file.name
|
|
234
|
-
else None
|
|
235
|
-
)
|
|
271
|
+
return self.anonymized_file.url if self.anonymized_file and self.anonymized_file.name else None
|
|
236
272
|
except (ValueError, AttributeError):
|
|
237
273
|
return None
|
|
238
274
|
|
|
239
|
-
patient = models.ForeignKey(
|
|
240
|
-
"Patient",
|
|
241
|
-
on_delete=models.SET_NULL,
|
|
242
|
-
blank=True,
|
|
243
|
-
null=True,
|
|
244
|
-
related_name="raw_pdf_files",
|
|
245
|
-
) # type: ignore
|
|
246
|
-
sensitive_meta = models.ForeignKey(
|
|
247
|
-
"SensitiveMeta",
|
|
248
|
-
on_delete=models.SET_NULL,
|
|
249
|
-
related_name="raw_pdf_files",
|
|
250
|
-
null=True,
|
|
251
|
-
blank=True,
|
|
252
|
-
) # type: ignore
|
|
253
|
-
state_report_processing_required = models.BooleanField(default=True)
|
|
254
|
-
state_report_processed = models.BooleanField(default=False)
|
|
255
|
-
raw_meta = models.JSONField(blank=True, null=True)
|
|
256
|
-
anonym_examination_report = models.OneToOneField(
|
|
257
|
-
"AnonymExaminationReport",
|
|
258
|
-
on_delete=models.SET_NULL,
|
|
259
|
-
blank=True,
|
|
260
|
-
null=True,
|
|
261
|
-
related_name="raw_pdf_file",
|
|
262
|
-
) # type: ignore
|
|
263
|
-
anonymized_text = models.TextField(blank=True, null=True)
|
|
264
|
-
|
|
265
|
-
# Type hinting is needed, improve and use correct django types
|
|
266
|
-
if TYPE_CHECKING:
|
|
267
|
-
file: Optional[Union[models.FieldFile, models.FileField]]
|
|
268
|
-
anonymized_file: Optional[Union[models.FieldFile, models.FileField]]
|
|
269
|
-
pdf_type: Optional[models.ForeignKey]
|
|
270
|
-
examination: Optional[models.ForeignKey["PatientExamination"]]
|
|
271
|
-
examiner: Optional[models.ForeignKey["Examiner"]]
|
|
272
|
-
patient: Optional[models.ForeignKey["Patient"]]
|
|
273
|
-
center: Optional[models.ForeignKey["Center"]]
|
|
274
|
-
anonym_examination_report: Optional[
|
|
275
|
-
models.OneToOneField["AnonymExaminationReport"]
|
|
276
|
-
]
|
|
277
|
-
sensitive_meta: Optional[models.ForeignKey["SensitiveMeta"]]
|
|
278
|
-
state: Optional[models.ForeignKey["RawPdfState"]]
|
|
279
|
-
|
|
280
275
|
def __str__(self):
|
|
281
276
|
"""
|
|
282
277
|
Return a string representation of the RawPdfFile, including its PDF hash, type, and center.
|
|
@@ -290,35 +285,17 @@ class RawPdfFile(models.Model):
|
|
|
290
285
|
|
|
291
286
|
This method ensures that the physical PDF file is deleted from the file system after the database record is removed. Logs warnings or errors if the file cannot be found or deleted.
|
|
292
287
|
"""
|
|
293
|
-
|
|
294
|
-
if self.
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
logger.warning(
|
|
301
|
-
f"Could not get file path for {self.file.name} before deletion: {e}"
|
|
302
|
-
)
|
|
303
|
-
if self.anonymized_file:
|
|
304
|
-
try:
|
|
305
|
-
if self.anonymized_file_path:
|
|
306
|
-
os.remove(Path(self.anonymized_file_path))
|
|
307
|
-
logger.info(
|
|
308
|
-
"Anonymized file removed: %s", self.anonymized_file.name
|
|
309
|
-
)
|
|
310
|
-
except OSError as e:
|
|
311
|
-
logger.error(
|
|
312
|
-
"Error removing anonymized file %s: %s",
|
|
313
|
-
self.anonymized_file.name,
|
|
314
|
-
e,
|
|
315
|
-
)
|
|
288
|
+
primary_name = self.file.name if self.file and self.file.name else None
|
|
289
|
+
anonymized_name = self.anonymized_file.name if self.anonymized_file and self.anonymized_file.name else None
|
|
290
|
+
|
|
291
|
+
if delete_field_file(self.file, missing_ok=True, save=False):
|
|
292
|
+
logger.info("Original file removed from storage: %s", primary_name)
|
|
293
|
+
if delete_field_file(self.anonymized_file, missing_ok=True, save=False):
|
|
294
|
+
logger.info("Anonymized file removed from storage: %s", anonymized_name)
|
|
316
295
|
|
|
317
296
|
super().delete(*args, **kwargs)
|
|
318
297
|
|
|
319
|
-
def validate_metadata_annotation(
|
|
320
|
-
self, extracted_data_dict: Optional[dict] = None
|
|
321
|
-
) -> bool:
|
|
298
|
+
def validate_metadata_annotation(self, extracted_data_dict: Optional[dict] = None) -> bool:
|
|
322
299
|
"""
|
|
323
300
|
Validate the metadata of the RawPdf instance.
|
|
324
301
|
|
|
@@ -334,8 +311,10 @@ class RawPdfFile(models.Model):
|
|
|
334
311
|
logger.error("No extracted data provided for validation.")
|
|
335
312
|
return False
|
|
336
313
|
|
|
337
|
-
|
|
338
|
-
|
|
314
|
+
if extracted_data_dict:
|
|
315
|
+
self.sensitive_meta.update_from_dict(extracted_data_dict)
|
|
316
|
+
else:
|
|
317
|
+
return False
|
|
339
318
|
|
|
340
319
|
# Save the sensitive meta to ensure changes are persisted
|
|
341
320
|
self.sensitive_meta.save()
|
|
@@ -345,21 +324,12 @@ class RawPdfFile(models.Model):
|
|
|
345
324
|
|
|
346
325
|
logger.info(f"Metadata for PDF {self.pk} validated and updated successfully.")
|
|
347
326
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
except OSError as e:
|
|
352
|
-
logger.error(f"Error removing original file {self.file_path}: {e}")
|
|
353
|
-
|
|
354
|
-
if self.anonymized_file_path:
|
|
355
|
-
try:
|
|
356
|
-
os.unlink(self.anonymized_file_path)
|
|
357
|
-
except OSError as e:
|
|
358
|
-
logger.error(
|
|
359
|
-
f"Error removing anonymized file {self.anonymized_file_path}: {e}"
|
|
360
|
-
)
|
|
327
|
+
deleted_original = delete_field_file(self.file, missing_ok=True, save=False)
|
|
328
|
+
deleted_anonymized = delete_field_file(self.anonymized_file, missing_ok=True, save=False)
|
|
329
|
+
self.get_or_create_state().mark_anonymization_validated()
|
|
361
330
|
|
|
362
|
-
|
|
331
|
+
if deleted_original or deleted_anonymized:
|
|
332
|
+
self.save(update_fields=["file", "anonymized_file"]) # Persist cleared fields
|
|
363
333
|
|
|
364
334
|
logger.info(f"Files for PDF {self.pk} deleted successfully.")
|
|
365
335
|
return True
|
|
@@ -457,13 +427,9 @@ class RawPdfFile(models.Model):
|
|
|
457
427
|
)
|
|
458
428
|
# Re-save the file from the source to potentially fix it
|
|
459
429
|
with file_path.open("rb") as f:
|
|
460
|
-
django_file = File(
|
|
461
|
-
|
|
462
|
-
) #
|
|
463
|
-
existing_pdf_file.file = django_file # type: ignore
|
|
464
|
-
existing_pdf_file.save(
|
|
465
|
-
update_fields=["file"]
|
|
466
|
-
) # Only update file field
|
|
430
|
+
django_file = File(f, name=Path(_file.name).name) # Use existing name if possible
|
|
431
|
+
existing_pdf_file.file = django_file
|
|
432
|
+
existing_pdf_file.save(update_fields=["file"]) # Only update file field
|
|
467
433
|
else:
|
|
468
434
|
pass
|
|
469
435
|
# logger.debug("File for existing RawPdfFile %s already exists in storage.", pdf_hash)
|
|
@@ -519,9 +485,7 @@ class RawPdfFile(models.Model):
|
|
|
519
485
|
"File was not saved correctly to storage path %s after model save.",
|
|
520
486
|
_file.name,
|
|
521
487
|
)
|
|
522
|
-
raise IOError(
|
|
523
|
-
f"File not found at expected storage path after save: {_file.name}"
|
|
524
|
-
)
|
|
488
|
+
raise IOError(f"File not found at expected storage path after save: {_file.name}")
|
|
525
489
|
|
|
526
490
|
try:
|
|
527
491
|
logger.info("File saved to absolute path: %s", _file.path)
|
|
@@ -532,18 +496,14 @@ class RawPdfFile(models.Model):
|
|
|
532
496
|
)
|
|
533
497
|
|
|
534
498
|
except Exception as e:
|
|
535
|
-
logger.error(
|
|
536
|
-
"Error processing or saving file %s for new record: %s", file_path, e
|
|
537
|
-
)
|
|
499
|
+
logger.error("Error processing or saving file %s for new record: %s", file_path, e)
|
|
538
500
|
raise
|
|
539
501
|
|
|
540
502
|
# Delete source file *after* successful save and verification
|
|
541
503
|
if delete_source:
|
|
542
504
|
try:
|
|
543
505
|
file_path.unlink()
|
|
544
|
-
logger.info(
|
|
545
|
-
"Deleted source file %s after creating new record.", file_path
|
|
546
|
-
)
|
|
506
|
+
logger.info("Deleted source file %s after creating new record.", file_path)
|
|
547
507
|
except OSError as e:
|
|
548
508
|
logger.error("Error deleting source file %s: %s", file_path, e)
|
|
549
509
|
|
|
@@ -560,27 +520,15 @@ class RawPdfFile(models.Model):
|
|
|
560
520
|
"""
|
|
561
521
|
if not self.pk and not self.pdf_hash and self.file:
|
|
562
522
|
try:
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
self.file.open("rb") # Ensure file is open
|
|
568
|
-
self.file.seek(0) # Go to beginning
|
|
569
|
-
self.pdf_hash = get_pdf_hash(
|
|
570
|
-
file_path
|
|
571
|
-
) # Assuming get_pdf_hash can handle file obj
|
|
572
|
-
self.file.seek(0) # Reset position
|
|
573
|
-
self.file.close() # Close after reading
|
|
574
|
-
logger.info(f"Calculated hash during pre-save for {self.file.name}")
|
|
575
|
-
except Exception as e:
|
|
523
|
+
with ensure_local_file(self.file) as local_path:
|
|
524
|
+
self.pdf_hash = get_pdf_hash(local_path)
|
|
525
|
+
logger.info("Calculated hash during pre-save for %s", self.file.name)
|
|
526
|
+
except Exception as exc:
|
|
576
527
|
logger.warning(
|
|
577
528
|
"Could not calculate hash before initial save for %s: %s",
|
|
578
529
|
self.file.name,
|
|
579
|
-
|
|
530
|
+
exc,
|
|
580
531
|
)
|
|
581
|
-
# Ensure file is closed if opened
|
|
582
|
-
if hasattr(self.file, "closed") and not self.file.closed:
|
|
583
|
-
self.file.close()
|
|
584
532
|
|
|
585
533
|
if self.file and not self.file.name.endswith(".pdf"):
|
|
586
534
|
raise ValidationError("Only PDF files are allowed")
|
|
@@ -588,30 +536,16 @@ class RawPdfFile(models.Model):
|
|
|
588
536
|
# If hash is still missing after potential creation logic (e.g., direct instantiation)
|
|
589
537
|
# and the file exists in storage, try calculating it from storage path.
|
|
590
538
|
# This is less ideal as it requires the file to be saved first.
|
|
591
|
-
if (
|
|
592
|
-
not self.pdf_hash
|
|
593
|
-
and self.pk
|
|
594
|
-
and self.file
|
|
595
|
-
and self.file.storage.exists(self.file.name)
|
|
596
|
-
):
|
|
539
|
+
if not self.pdf_hash and self.pk and self.file and file_exists(self.file):
|
|
597
540
|
try:
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
f"Hash missing for saved file {self.file.name}. Recalculating."
|
|
603
|
-
)
|
|
604
|
-
with self.file.storage.open(self.file.name, "rb") as f:
|
|
605
|
-
self.pdf_hash = get_pdf_hash(
|
|
606
|
-
file_path
|
|
607
|
-
) # Assuming get_pdf_hash handles file obj
|
|
608
|
-
# No need to save again just for hash unless update_fields is used carefully
|
|
609
|
-
# Let the main super().save() handle saving the hash if it changed
|
|
610
|
-
except Exception as e:
|
|
541
|
+
with ensure_local_file(self.file) as local_path:
|
|
542
|
+
logger.warning("Hash missing for saved file %s. Recalculating.", self.file.name)
|
|
543
|
+
self.pdf_hash = get_pdf_hash(local_path)
|
|
544
|
+
except Exception as exc:
|
|
611
545
|
logger.error(
|
|
612
546
|
"Could not calculate hash during save for existing file %s: %s",
|
|
613
547
|
self.file.name,
|
|
614
|
-
|
|
548
|
+
exc,
|
|
615
549
|
)
|
|
616
550
|
|
|
617
551
|
# Derive related fields from sensitive_meta if available
|
|
@@ -663,18 +597,12 @@ class RawPdfFile(models.Model):
|
|
|
663
597
|
assert _file is not None
|
|
664
598
|
try:
|
|
665
599
|
if not _file.field.storage.exists(_file.name):
|
|
666
|
-
logger.warning(
|
|
667
|
-
f"File missing at storage path {_file.name}. Attempting copy from fallback {fallback_file}"
|
|
668
|
-
)
|
|
600
|
+
logger.warning(f"File missing at storage path {_file.name}. Attempting copy from fallback {fallback_file}")
|
|
669
601
|
if fallback_file.exists():
|
|
670
602
|
with fallback_file.open("rb") as f:
|
|
671
603
|
# Use save method which handles storage backend
|
|
672
|
-
_file.save(
|
|
673
|
-
|
|
674
|
-
) # Re-save the file content
|
|
675
|
-
logger.info(
|
|
676
|
-
f"Successfully restored file from fallback {fallback_file} to {_file.name}"
|
|
677
|
-
)
|
|
604
|
+
_file.save(Path(_file.name).name, File(f), save=True) # Re-save the file content
|
|
605
|
+
logger.info(f"Successfully restored file from fallback {fallback_file} to {_file.name}")
|
|
678
606
|
else:
|
|
679
607
|
logger.error(f"Fallback file {fallback_file} does not exist.")
|
|
680
608
|
except Exception as e:
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
from ...utils import DOCUMENT_DIR, STORAGE_DIR
|
|
2
|
-
from django.db import models
|
|
3
1
|
from typing import TYPE_CHECKING
|
|
4
2
|
|
|
3
|
+
from django.db import models
|
|
4
|
+
|
|
5
|
+
from ...utils import DOCUMENT_DIR, STORAGE_DIR
|
|
6
|
+
|
|
5
7
|
if TYPE_CHECKING:
|
|
6
8
|
from ...administration import (
|
|
7
9
|
Center,
|
|
@@ -12,17 +14,21 @@ if TYPE_CHECKING:
|
|
|
12
14
|
)
|
|
13
15
|
from ...metadata import SensitiveMeta
|
|
14
16
|
|
|
17
|
+
|
|
15
18
|
class DocumentTypeManager(models.Manager):
|
|
16
19
|
"""
|
|
17
20
|
Custom manager for DocumentType.
|
|
18
21
|
"""
|
|
22
|
+
|
|
19
23
|
def get_by_natural_key(self, name):
|
|
20
24
|
return self.get(name=name)
|
|
21
25
|
|
|
26
|
+
|
|
22
27
|
class DocumentType(models.Model):
|
|
23
28
|
"""
|
|
24
29
|
Represents the type of a document.
|
|
25
30
|
"""
|
|
31
|
+
|
|
26
32
|
name = models.CharField(max_length=255, unique=True)
|
|
27
33
|
description = models.TextField(blank=True, null=True)
|
|
28
34
|
|
|
@@ -33,21 +39,23 @@ class DocumentType(models.Model):
|
|
|
33
39
|
|
|
34
40
|
def __str__(self):
|
|
35
41
|
return str(self.name)
|
|
36
|
-
|
|
42
|
+
|
|
37
43
|
class Meta:
|
|
38
44
|
verbose_name = "Document Type"
|
|
39
45
|
verbose_name_plural = "Document Types"
|
|
40
46
|
|
|
47
|
+
|
|
41
48
|
class AbstractDocument(models.Model):
|
|
42
49
|
"""
|
|
43
50
|
Abstract base class for documents.
|
|
44
51
|
"""
|
|
52
|
+
|
|
45
53
|
meta = models.JSONField(blank=True, null=True)
|
|
46
54
|
text = models.TextField(blank=True, null=True)
|
|
47
55
|
date = models.DateField(blank=True, null=True)
|
|
48
56
|
time = models.TimeField(blank=True, null=True)
|
|
49
57
|
file = models.FileField(
|
|
50
|
-
upload_to=DOCUMENT_DIR.relative_to(STORAGE_DIR),
|
|
58
|
+
upload_to=DOCUMENT_DIR.relative_to(STORAGE_DIR).as_posix(),
|
|
51
59
|
blank=True,
|
|
52
60
|
null=True,
|
|
53
61
|
)
|
|
@@ -67,22 +75,19 @@ class AbstractDocument(models.Model):
|
|
|
67
75
|
)
|
|
68
76
|
|
|
69
77
|
if TYPE_CHECKING:
|
|
70
|
-
center: "Center"
|
|
71
|
-
type: "DocumentType"
|
|
78
|
+
center: models.ForeignKey["Center|None"]
|
|
79
|
+
type: models.ForeignKey["DocumentType|None"]
|
|
72
80
|
|
|
73
81
|
class Meta:
|
|
74
82
|
abstract = True
|
|
75
83
|
|
|
76
84
|
|
|
77
|
-
|
|
78
|
-
|
|
79
85
|
class AbstractExaminationReport(AbstractDocument):
|
|
80
86
|
"""
|
|
81
87
|
Abstract base class for examination reports.
|
|
82
88
|
"""
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
89
|
+
|
|
90
|
+
patient = models.ForeignKey("Patient", on_delete=models.DO_NOTHING, blank=True, null=True)
|
|
86
91
|
|
|
87
92
|
patient_examination = models.ForeignKey(
|
|
88
93
|
"PatientExamination",
|
|
@@ -96,25 +101,18 @@ class AbstractExaminationReport(AbstractDocument):
|
|
|
96
101
|
blank=True,
|
|
97
102
|
)
|
|
98
103
|
|
|
99
|
-
sensitive_meta = models.ForeignKey(
|
|
100
|
-
"SensitiveMeta",
|
|
101
|
-
on_delete=models.SET_NULL,
|
|
102
|
-
null=True,
|
|
103
|
-
blank=True
|
|
104
|
-
)
|
|
104
|
+
sensitive_meta = models.ForeignKey("SensitiveMeta", on_delete=models.SET_NULL, null=True, blank=True)
|
|
105
105
|
|
|
106
106
|
if TYPE_CHECKING:
|
|
107
|
-
center: "Center"
|
|
108
|
-
type: "DocumentType"
|
|
109
|
-
patient: "Patient"
|
|
110
|
-
patient_examination: "PatientExamination"
|
|
111
|
-
sensitive_meta: "SensitiveMeta"
|
|
112
|
-
|
|
107
|
+
center: models.ForeignKey["Center|None"]
|
|
108
|
+
type: models.ForeignKey["DocumentType|None"]
|
|
109
|
+
patient: models.ForeignKey["Patient|None"]
|
|
110
|
+
patient_examination: models.ForeignKey["PatientExamination|None"]
|
|
111
|
+
sensitive_meta: models.ForeignKey["SensitiveMeta|None"]
|
|
113
112
|
|
|
114
113
|
class Meta:
|
|
115
114
|
abstract = True
|
|
116
115
|
|
|
117
|
-
|
|
118
116
|
def get_or_create_examiner(self, examiner_first_name, examiner_last_name):
|
|
119
117
|
raise NotImplementedError("Subclasses must implement this method.")
|
|
120
118
|
|
|
@@ -122,10 +120,8 @@ class AbstractExaminationReport(AbstractDocument):
|
|
|
122
120
|
raise NotImplementedError("Subclasses must implement this method.")
|
|
123
121
|
|
|
124
122
|
|
|
125
|
-
|
|
126
123
|
class AnonymExaminationReport(AbstractExaminationReport):
|
|
127
|
-
|
|
128
|
-
def get_or_create_examiner(self, examiner_first_name:str, examiner_last_name:str):
|
|
124
|
+
def get_or_create_examiner(self, examiner_first_name: str, examiner_last_name: str):
|
|
129
125
|
from ...administration.person import Examiner
|
|
130
126
|
|
|
131
127
|
examiner_center = self.center
|
|
@@ -139,7 +135,7 @@ class AnonymExaminationReport(AbstractExaminationReport):
|
|
|
139
135
|
return examiner, created
|
|
140
136
|
|
|
141
137
|
def set_examination_date_and_time(self, report_meta=None):
|
|
142
|
-
#TODO
|
|
138
|
+
# TODO
|
|
143
139
|
if not report_meta:
|
|
144
140
|
report_meta = self.meta
|
|
145
141
|
# examination_date_str = report_meta["examination_date"]
|
|
@@ -152,11 +148,11 @@ class AnonymExaminationReport(AbstractExaminationReport):
|
|
|
152
148
|
# # TODO: get django TimeField compatible time from string (e.g. "12:00")
|
|
153
149
|
# self.time = time.fromisoformat(examination_time_str)
|
|
154
150
|
|
|
151
|
+
|
|
155
152
|
class AnonymHistologyReport(AbstractExaminationReport):
|
|
156
153
|
"""
|
|
157
154
|
Represents a histology report.
|
|
158
155
|
"""
|
|
159
156
|
|
|
160
|
-
|
|
161
157
|
def get_or_create_examiner(self, examiner_first_name, examiner_last_name):
|
|
162
158
|
raise NotImplementedError("Subclasses must implement this method.")
|