endoreg-db 0.8.4.4__py3-none-any.whl → 0.8.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of endoreg-db might be problematic. Click here for more details.
- 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_ai_model_data.py +2 -1
- 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 +14 -10
- 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 +249 -177
- 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_1.py +30 -33
- endoreg_db/models/media/video/pipe_2.py +8 -9
- endoreg_db/models/media/video/video_file.py +359 -204
- 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 +139 -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 +383 -43
- 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 +26 -57
- 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/__init__.py +1 -1
- endoreg_db/serializers/misc/file_overview.py +33 -91
- endoreg_db/serializers/misc/{vop_patient_data.py → sensitive_patient_data.py} +1 -1
- 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/serializers/video_examination.py +198 -0
- endoreg_db/services/anonymization.py +75 -73
- endoreg_db/services/lookup_service.py +256 -73
- endoreg_db/services/lookup_store.py +174 -30
- endoreg_db/services/pdf_import.py +711 -310
- endoreg_db/services/storage_aware_video_processor.py +140 -114
- endoreg_db/services/video_import.py +266 -117
- endoreg_db/urls/__init__.py +27 -27
- endoreg_db/urls/label_video_segments.py +2 -0
- endoreg_db/urls/media.py +108 -66
- 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 +5 -12
- 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 -150
- 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 +187 -260
- endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
- endoreg_db/views/patient/patient.py +5 -4
- endoreg_db/views/pdf/__init__.py +5 -8
- endoreg_db/views/pdf/pdf_stream.py +186 -0
- endoreg_db/views/pdf/pdf_stream_views.py +0 -127
- endoreg_db/views/pdf/reimport.py +86 -91
- endoreg_db/views/requirement/evaluate.py +188 -187
- endoreg_db/views/requirement/lookup.py +186 -288
- endoreg_db/views/requirement/requirement_utils.py +89 -0
- endoreg_db/views/video/__init__.py +0 -4
- endoreg_db/views/video/correction.py +2 -2
- endoreg_db/views/video/video_examination_viewset.py +202 -289
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +350 -255
- endoreg_db/models/administration/permissions/__init__.py +0 -44
- endoreg_db/models/media/video/refactor_plan.md +0 -0
- 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/pdf/pdf_media.py +0 -239
- 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/video/video_media.py +0 -158
- 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-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
from endoreg_db.models import CaseTemplate, CaseTemplateRule, CaseTemplateRuleType
|
|
2
|
-
from endoreg_db.case_generator.lab_sample_factory import LabSampleFactory
|
|
3
|
-
|
|
4
|
-
DEFAULT_CASE_TEMPLATE_NAME = "pre_default_screening_colonoscopy"
|
|
5
|
-
|
|
6
|
-
class CaseGenerator:
|
|
7
|
-
"""
|
|
8
|
-
Provides methods to generate cases based on a template.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
def __init__(self, template: CaseTemplate = None):
|
|
12
|
-
"""
|
|
13
|
-
Initializes the CaseGenerator with a template.
|
|
14
|
-
|
|
15
|
-
Args:
|
|
16
|
-
template (CaseTemplate, optional): The template to use for case generation. Defaults to the predefined template.
|
|
17
|
-
"""
|
|
18
|
-
self.template = template or CaseTemplate.objects.get(name=DEFAULT_CASE_TEMPLATE_NAME)
|
|
19
|
-
self.lab_sample_factory = LabSampleFactory()
|
|
20
|
-
|
|
21
|
-
# Define available rule types
|
|
22
|
-
rule_type_names = [
|
|
23
|
-
"create-object",
|
|
24
|
-
"set-field-default",
|
|
25
|
-
"set-field-by-distribution",
|
|
26
|
-
"set-field-by-value",
|
|
27
|
-
"set-field-single-choice",
|
|
28
|
-
"set-field-multiple-choice",
|
|
29
|
-
]
|
|
30
|
-
self.available_rule_types = CaseTemplateRuleType.objects.filter(name__in=rule_type_names)
|
|
31
|
-
|
|
32
|
-
def _validate_rule_type(self, rule_type: CaseTemplateRuleType):
|
|
33
|
-
"""
|
|
34
|
-
Validates if the rule type is supported.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
rule_type (CaseTemplateRuleType): The rule type to validate.
|
|
38
|
-
|
|
39
|
-
Raises:
|
|
40
|
-
ValueError: If the rule type is not supported.
|
|
41
|
-
"""
|
|
42
|
-
if rule_type not in self.available_rule_types:
|
|
43
|
-
raise ValueError(f"Rule type {rule_type} is not supported.")
|
|
44
|
-
|
|
45
|
-
def _apply_create_object(self, rule: CaseTemplateRule, parent=None):
|
|
46
|
-
"""
|
|
47
|
-
Applies a create-object rule to generate a model instance.
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
rule (CaseTemplateRule): The rule to apply.
|
|
51
|
-
parent (Optional[Model]): The parent object for the rule.
|
|
52
|
-
|
|
53
|
-
Returns:
|
|
54
|
-
Model: The created model instance.
|
|
55
|
-
"""
|
|
56
|
-
target_model = rule.get_target_model()
|
|
57
|
-
extra_params = rule.extra_parameters or {}
|
|
58
|
-
create_method_info = extra_params.get("create_method", {})
|
|
59
|
-
|
|
60
|
-
assert create_method_info, "Create method must be set for a create-object rule."
|
|
61
|
-
|
|
62
|
-
create_method = getattr(target_model, create_method_info["name"])
|
|
63
|
-
kwargs = create_method_info.get("kwargs", {})
|
|
64
|
-
|
|
65
|
-
if parent:
|
|
66
|
-
kwargs[rule.parent_field] = parent
|
|
67
|
-
|
|
68
|
-
target_instance = create_method(**kwargs)
|
|
69
|
-
target_instance.save()
|
|
70
|
-
|
|
71
|
-
for action in extra_params.get("actions", []):
|
|
72
|
-
action_method = getattr(target_instance, action["name"])
|
|
73
|
-
action_kwargs = action.get("kwargs", {})
|
|
74
|
-
action_method(**action_kwargs)
|
|
75
|
-
|
|
76
|
-
for chained_rule in rule.chained_rules.all():
|
|
77
|
-
self.apply_rule(chained_rule, parent=target_instance)
|
|
78
|
-
|
|
79
|
-
return target_instance
|
|
80
|
-
|
|
81
|
-
def _apply_set_field_by_distribution(self, rule: CaseTemplateRule, parent):
|
|
82
|
-
"""
|
|
83
|
-
Applies a set-field-by-distribution rule.
|
|
84
|
-
|
|
85
|
-
Args:
|
|
86
|
-
rule (CaseTemplateRule): The rule to apply.
|
|
87
|
-
parent (Model): The parent object for the rule.
|
|
88
|
-
|
|
89
|
-
Returns:
|
|
90
|
-
Model: The updated parent object.
|
|
91
|
-
"""
|
|
92
|
-
assert parent, "Parent must be provided for set-field-by-distribution rules."
|
|
93
|
-
assert rule.target_field, "Target field must be specified for the rule."
|
|
94
|
-
|
|
95
|
-
distribution = rule.get_distribution()
|
|
96
|
-
value = distribution.generate_value()
|
|
97
|
-
|
|
98
|
-
setattr(parent, rule.target_field, value)
|
|
99
|
-
parent.save()
|
|
100
|
-
return parent
|
|
101
|
-
|
|
102
|
-
def apply_rule(self, rule: CaseTemplateRule, parent=None):
|
|
103
|
-
"""
|
|
104
|
-
Applies a rule based on its type to generate a case.
|
|
105
|
-
|
|
106
|
-
Args:
|
|
107
|
-
rule (CaseTemplateRule): The rule to apply.
|
|
108
|
-
parent (Optional[Model]): The parent object for the rule.
|
|
109
|
-
|
|
110
|
-
Returns:
|
|
111
|
-
Model: The case by applying the rule.
|
|
112
|
-
"""
|
|
113
|
-
self._validate_rule_type(rule.rule_type)
|
|
114
|
-
|
|
115
|
-
if rule.rule_type.name == "create-object":
|
|
116
|
-
return self._apply_create_object(rule, parent)
|
|
117
|
-
|
|
118
|
-
if rule.rule_type.name == "set-field-by-distribution":
|
|
119
|
-
return self._apply_set_field_by_distribution(rule, parent)
|
|
120
|
-
|
|
121
|
-
raise ValueError(f"Unsupported rule type: {rule.rule_type.name}")
|
|
122
|
-
|
|
123
|
-
def generate_case(self, case_template: CaseTemplate = None):
|
|
124
|
-
"""
|
|
125
|
-
Generates a case based on the provided or default template.
|
|
126
|
-
|
|
127
|
-
Args:
|
|
128
|
-
case_template (CaseTemplate, optional): The template to use for case generation. Defaults to None.
|
|
129
|
-
|
|
130
|
-
Returns:
|
|
131
|
-
Tuple[Model, Model]: The generated patient and medication schedule.
|
|
132
|
-
"""
|
|
133
|
-
case_template = case_template or CaseTemplate.objects.get(name=DEFAULT_CASE_TEMPLATE_NAME)
|
|
134
|
-
|
|
135
|
-
create_patient_rule = case_template.get_create_patient_rule()
|
|
136
|
-
patient = self.apply_rule(create_patient_rule)
|
|
137
|
-
|
|
138
|
-
medication_schedule_rule = case_template.get_create_patient_medication_schedule_rule()
|
|
139
|
-
medication_schedule = self.apply_rule(medication_schedule_rule, parent=patient)
|
|
140
|
-
|
|
141
|
-
return patient, medication_schedule
|
|
142
|
-
|
|
143
|
-
# if not create_new_patient:
|
|
144
|
-
# raise NotImplementedError("Only new patients are supported at the moment.")
|
|
145
|
-
# else:
|
|
146
|
-
# # TODO Implement patient rules
|
|
147
|
-
# patient_rules = None # all rules of type "create_patient"
|
|
148
|
-
# patient = self.generate_patient(patient_rules)
|
|
149
|
-
|
|
150
|
-
# # Generate case based on template
|
|
151
|
-
# rules = self.template.get_rules()
|
|
152
|
-
# chained_rules = set()
|
|
153
|
-
|
|
154
|
-
# for rule in rules:
|
|
155
|
-
# rule_chain = rule.get_all_downward_chained_rules()
|
|
156
|
-
# chained_rules.add(rule)
|
|
157
|
-
# chained_rules.update(rule_chain)
|
|
158
|
-
|
|
159
|
-
# return chained_rules
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
from endoreg_db.models import CaseTemplate
|
|
2
|
-
from endoreg_db.case_generator.case_generator import CaseGenerator
|
|
3
|
-
|
|
4
|
-
# TEMPLATE_NAME = "pre_endo-anticoagulation-af-low_risk"
|
|
5
|
-
TEMPLATE_NAME = "pre_default_screening_colonoscopy"
|
|
6
|
-
|
|
7
|
-
def fetch_template(template_name: str = DEFAULT_TEMPLATE_NAME) -> CaseTemplate:
|
|
8
|
-
"""
|
|
9
|
-
Fetches a CaseTemplate by name.
|
|
10
|
-
|
|
11
|
-
Args:
|
|
12
|
-
template_name (str): The name of the template to fetch. Defaults to DEFAULT_TEMPLATE_NAME.
|
|
13
|
-
|
|
14
|
-
Returns:
|
|
15
|
-
CaseTemplate: The fetched CaseTemplate instance.
|
|
16
|
-
"""
|
|
17
|
-
return CaseTemplate.objects.get(name=template_name)
|
|
18
|
-
|
|
19
|
-
def initialize_case_generator(template_name: str = DEFAULT_TEMPLATE_NAME) -> CaseGenerator:
|
|
20
|
-
"""
|
|
21
|
-
Initializes a CaseGenerator with the specified template.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
template_name (str): The name of the template to use. Defaults to DEFAULT_TEMPLATE_NAME.
|
|
25
|
-
|
|
26
|
-
Returns:
|
|
27
|
-
CaseGenerator: An instance of CaseGenerator initialized with the template.
|
|
28
|
-
"""
|
|
29
|
-
template = fetch_template(template_name)
|
|
30
|
-
return CaseGenerator(template)
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
from django.http import FileResponse, Http404, StreamingHttpResponse
|
|
2
|
-
import mimetypes
|
|
3
|
-
import os
|
|
4
|
-
import logging
|
|
5
|
-
import re
|
|
6
|
-
from ...models import RawPdfFile
|
|
7
|
-
from ...serializers._old.raw_pdf_meta_validation import PDFFileForMetaSerializer, SensitiveMetaUpdateSerializer
|
|
8
|
-
from rest_framework.views import APIView
|
|
9
|
-
from rest_framework.response import Response
|
|
10
|
-
from rest_framework import status
|
|
11
|
-
from ...models import SensitiveMeta
|
|
12
|
-
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
|
13
|
-
from django.utils.decorators import method_decorator
|
|
14
|
-
from django.db import transaction
|
|
15
|
-
from django.urls import reverse
|
|
16
|
-
from django.utils.encoding import iri_to_uri
|
|
17
|
-
from endoreg_db.utils.paths import PDF_DIR, STORAGE_DIR
|
|
18
|
-
from .pdf_stream_views import ClosingFileWrapper
|
|
19
|
-
|
|
20
|
-
logger = logging.getLogger(__name__)
|
|
21
|
-
_RANGE_RE = re.compile(r"bytes=(\d+)-(\d*)")
|
|
22
|
-
|
|
23
|
-
class PDFMediaView(APIView):
|
|
24
|
-
"""
|
|
25
|
-
Unified API for PDFs to support frontend flows:
|
|
26
|
-
- Without `id`: returns next PDF metadata (including anonymized_text) and stream URLs
|
|
27
|
-
- With `id`: streams the PDF (original by default; `?variant=anonymized` for anonymized)
|
|
28
|
-
- Integrates with Media Management expectations (clean deletion after validation is handled elsewhere)
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
def get(self, request):
|
|
32
|
-
"""
|
|
33
|
-
Handles both:
|
|
34
|
-
- Fetching PDF metadata (if `id` is NOT provided)
|
|
35
|
-
- Streaming the actual PDF file (if `id` is provided)
|
|
36
|
-
"""
|
|
37
|
-
pdf_id = request.GET.get("id")
|
|
38
|
-
last_id = request.GET.get("last_id")
|
|
39
|
-
|
|
40
|
-
if pdf_id:
|
|
41
|
-
return self.serve_pdf_file(pdf_id)
|
|
42
|
-
else:
|
|
43
|
-
return self.fetch_pdf_metadata(last_id)
|
|
44
|
-
|
|
45
|
-
def fetch_pdf_metadata(self, last_id):
|
|
46
|
-
"""
|
|
47
|
-
Fetches the first or next available PDF metadata and provides stream URLs.
|
|
48
|
-
"""
|
|
49
|
-
pdf_entry = PDFFileForMetaSerializer.get_next_pdf(last_id)
|
|
50
|
-
if pdf_entry is None:
|
|
51
|
-
return Response({"error": "No more PDFs available."}, status=status.HTTP_404_NOT_FOUND)
|
|
52
|
-
|
|
53
|
-
serialized_pdf = PDFFileForMetaSerializer(pdf_entry, context={'request': self.request})
|
|
54
|
-
|
|
55
|
-
# Build stream URLs pointing to this unified endpoint
|
|
56
|
-
try:
|
|
57
|
-
media_url = reverse('pdf_media')
|
|
58
|
-
except Exception:
|
|
59
|
-
media_url = "/api/pdf/media/"
|
|
60
|
-
stream_url = f"{media_url}?id={pdf_entry.id}"
|
|
61
|
-
anon_stream_url = f"{media_url}?id={pdf_entry.id}&variant=anonymized"
|
|
62
|
-
|
|
63
|
-
data = dict(serialized_pdf.data)
|
|
64
|
-
data.update({
|
|
65
|
-
'stream_url': iri_to_uri(self.request.build_absolute_uri(stream_url)),
|
|
66
|
-
'anonymized_stream_url': iri_to_uri(self.request.build_absolute_uri(anon_stream_url)),
|
|
67
|
-
'pdf_id': pdf_entry.id,
|
|
68
|
-
'has_anonymized': bool(getattr(pdf_entry, 'anonymized_file', None) and getattr(pdf_entry.anonymized_file, 'name', None)),
|
|
69
|
-
})
|
|
70
|
-
return Response(data, status=status.HTTP_200_OK)
|
|
71
|
-
|
|
72
|
-
@method_decorator(xframe_options_sameorigin)
|
|
73
|
-
def serve_pdf_file(self, pdf_id):
|
|
74
|
-
"""
|
|
75
|
-
Streams the actual PDF file (original or anonymized) with Range support.
|
|
76
|
-
Query param `variant=anonymized` selects anonymized file; default is original.
|
|
77
|
-
"""
|
|
78
|
-
variant = (self.request.GET.get('variant') or 'original').lower()
|
|
79
|
-
range_header = self.request.headers.get("Range")
|
|
80
|
-
|
|
81
|
-
try:
|
|
82
|
-
pdf_entry = RawPdfFile.objects.get(id=pdf_id)
|
|
83
|
-
except RawPdfFile.DoesNotExist:
|
|
84
|
-
return Response({"error": "Invalid PDF ID."}, status=status.HTTP_400_BAD_REQUEST)
|
|
85
|
-
|
|
86
|
-
# Choose file according to variant
|
|
87
|
-
file_field = None
|
|
88
|
-
if variant == 'anonymized' and getattr(pdf_entry, 'anonymized_file', None):
|
|
89
|
-
file_field = pdf_entry.anonymized_file
|
|
90
|
-
else:
|
|
91
|
-
file_field = pdf_entry.file
|
|
92
|
-
|
|
93
|
-
if not file_field:
|
|
94
|
-
return Response({"error": "PDF file not found."}, status=status.HTTP_404_NOT_FOUND)
|
|
95
|
-
|
|
96
|
-
# Resolve path and attempt self-heal for originals
|
|
97
|
-
try:
|
|
98
|
-
file_path = file_field.path
|
|
99
|
-
except Exception:
|
|
100
|
-
file_path = None
|
|
101
|
-
|
|
102
|
-
if not file_path or not os.path.exists(file_path):
|
|
103
|
-
if variant != 'anonymized':
|
|
104
|
-
# Try to self-heal original reference to sensitive storage
|
|
105
|
-
sensitive_path = os.path.join(str(PDF_DIR), "sensitive", f"{pdf_entry.pdf_hash}.pdf")
|
|
106
|
-
if os.path.exists(sensitive_path):
|
|
107
|
-
try:
|
|
108
|
-
relative_name = os.path.relpath(sensitive_path, str(STORAGE_DIR))
|
|
109
|
-
if getattr(pdf_entry.file, 'name', None) != relative_name:
|
|
110
|
-
pdf_entry.file.name = relative_name
|
|
111
|
-
pdf_entry.save(update_fields=['file'])
|
|
112
|
-
logger.info("Self-healed PDF file reference for ID %s -> %s", pdf_entry.id, pdf_entry.file.path)
|
|
113
|
-
file_path = sensitive_path
|
|
114
|
-
except Exception as e:
|
|
115
|
-
logger.error("Failed to self-heal file path for PDF %s: %s", pdf_entry.id, e)
|
|
116
|
-
file_path = sensitive_path
|
|
117
|
-
# If still missing (or anonymized missing), fail
|
|
118
|
-
if not file_path or not os.path.exists(file_path):
|
|
119
|
-
raise Http404("PDF file not found on server.")
|
|
120
|
-
|
|
121
|
-
# Prepare headers
|
|
122
|
-
safe_filename = os.path.basename(getattr(file_field, 'name', None) or f"document_{pdf_id}.pdf")
|
|
123
|
-
if not safe_filename.endswith('.pdf'):
|
|
124
|
-
safe_filename += '.pdf'
|
|
125
|
-
|
|
126
|
-
file_size = os.path.getsize(file_path)
|
|
127
|
-
|
|
128
|
-
# Range support
|
|
129
|
-
if range_header:
|
|
130
|
-
match = _RANGE_RE.match(range_header)
|
|
131
|
-
if match:
|
|
132
|
-
start = int(match.group(1))
|
|
133
|
-
end = int(match.group(2) or file_size - 1)
|
|
134
|
-
|
|
135
|
-
if start < 0 or start >= file_size:
|
|
136
|
-
raise Http404("Invalid range")
|
|
137
|
-
if end >= file_size:
|
|
138
|
-
end = file_size - 1
|
|
139
|
-
|
|
140
|
-
chunk_size = end - start + 1
|
|
141
|
-
try:
|
|
142
|
-
fh = open(file_path, 'rb')
|
|
143
|
-
fh.seek(start)
|
|
144
|
-
resp = StreamingHttpResponse(
|
|
145
|
-
ClosingFileWrapper(fh, blksize=8192),
|
|
146
|
-
status=206,
|
|
147
|
-
content_type="application/pdf",
|
|
148
|
-
)
|
|
149
|
-
resp["Content-Length"] = str(chunk_size)
|
|
150
|
-
resp["Content-Range"] = f"bytes {start}-{end}/{file_size}"
|
|
151
|
-
resp["Accept-Ranges"] = "bytes"
|
|
152
|
-
resp["Content-Disposition"] = f'inline; filename="{safe_filename}"'
|
|
153
|
-
return resp
|
|
154
|
-
except (OSError, IOError) as e:
|
|
155
|
-
logger.error(f"Error opening PDF file for range request: {e}")
|
|
156
|
-
raise Http404("Error accessing PDF file")
|
|
157
|
-
|
|
158
|
-
# Fallback: serve full file
|
|
159
|
-
mime_type, _ = mimetypes.guess_type(file_path)
|
|
160
|
-
try:
|
|
161
|
-
fh = open(file_path, 'rb')
|
|
162
|
-
response = FileResponse(fh, content_type=mime_type or "application/pdf")
|
|
163
|
-
response["Content-Length"] = str(file_size)
|
|
164
|
-
response["Accept-Ranges"] = "bytes"
|
|
165
|
-
response["Content-Disposition"] = f'inline; filename="{safe_filename}"'
|
|
166
|
-
return response
|
|
167
|
-
except (OSError, IOError) as e:
|
|
168
|
-
logger.error(f"Error opening PDF file: {e}")
|
|
169
|
-
raise Http404("Error accessing PDF file")
|
|
170
|
-
|
|
171
|
-
class UpdateSensitiveMetaView(APIView):
|
|
172
|
-
"""
|
|
173
|
-
API endpoint to update patient details in the SensitiveMeta table.
|
|
174
|
-
Handles partial updates (only edited fields) and raw file deletion after validation acceptance.
|
|
175
|
-
"""
|
|
176
|
-
|
|
177
|
-
@transaction.atomic
|
|
178
|
-
def patch(self, request, *args, **kwargs):
|
|
179
|
-
"""
|
|
180
|
-
Updates the provided fields for a specific patient record.
|
|
181
|
-
Only updates fields that are sent in the request.
|
|
182
|
-
Automatically deletes raw PDF files when validation is accepted.
|
|
183
|
-
"""
|
|
184
|
-
sensitive_meta_id = request.data.get("sensitive_meta_id")
|
|
185
|
-
|
|
186
|
-
if not sensitive_meta_id:
|
|
187
|
-
return Response({"error": "sensitive_meta_id is required."}, status=status.HTTP_400_BAD_REQUEST)
|
|
188
|
-
|
|
189
|
-
try:
|
|
190
|
-
sensitive_meta = SensitiveMeta.objects.get(id=sensitive_meta_id)
|
|
191
|
-
except SensitiveMeta.DoesNotExist:
|
|
192
|
-
return Response({"error": "Patient record not found."}, status=status.HTTP_404_NOT_FOUND)
|
|
193
|
-
|
|
194
|
-
is_accepting_validation = request.data.get("is_verified", False)
|
|
195
|
-
delete_raw_files = request.data.get("delete_raw_files", False)
|
|
196
|
-
if is_accepting_validation:
|
|
197
|
-
delete_raw_files = True
|
|
198
|
-
logger.info(f"Validation accepted for PDF SensitiveMeta {sensitive_meta_id}, marking raw files for deletion")
|
|
199
|
-
|
|
200
|
-
serializer = SensitiveMetaUpdateSerializer(sensitive_meta, data=request.data, partial=True)
|
|
201
|
-
|
|
202
|
-
if serializer.is_valid():
|
|
203
|
-
updated_sm = serializer.save()
|
|
204
|
-
if delete_raw_files and updated_sm.is_verified:
|
|
205
|
-
try:
|
|
206
|
-
pdf_file = RawPdfFile.objects.filter(sensitive_meta=updated_sm).first()
|
|
207
|
-
if pdf_file:
|
|
208
|
-
self._schedule_raw_file_deletion(pdf_file)
|
|
209
|
-
logger.info(f"Scheduled raw file deletion for PDF {pdf_file.id}")
|
|
210
|
-
else:
|
|
211
|
-
logger.warning(f"No PDF file found for SensitiveMeta {sensitive_meta_id}")
|
|
212
|
-
except Exception as e:
|
|
213
|
-
logger.error(f"Error scheduling raw file deletion for PDF SensitiveMeta {sensitive_meta_id}: {e}")
|
|
214
|
-
return Response({"message": "Patient information updated successfully.", "updated_data": serializer.data}, status=status.HTTP_200_OK)
|
|
215
|
-
|
|
216
|
-
return Response({"error": "Invalid data.", "details": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
|
217
|
-
|
|
218
|
-
def _schedule_raw_file_deletion(self, pdf_file):
|
|
219
|
-
"""
|
|
220
|
-
Schedule deletion of raw PDF file after validation acceptance.
|
|
221
|
-
Deletes the original (sensitive) file but keeps anonymized_file for frontend.
|
|
222
|
-
"""
|
|
223
|
-
try:
|
|
224
|
-
def cleanup_raw_files():
|
|
225
|
-
try:
|
|
226
|
-
if pdf_file.file and getattr(pdf_file.file, 'path', None) and os.path.exists(pdf_file.file.path):
|
|
227
|
-
logger.info(f"Deleting original (sensitive) PDF file: {pdf_file.file.path}")
|
|
228
|
-
os.remove(pdf_file.file.path)
|
|
229
|
-
pdf_file.file = None
|
|
230
|
-
pdf_file.save(update_fields=['file'])
|
|
231
|
-
logger.info(f"Successfully deleted original file for PDF {pdf_file.id}")
|
|
232
|
-
else:
|
|
233
|
-
logger.info(f"Original file already deleted or not found for PDF {pdf_file.id}")
|
|
234
|
-
except Exception as e:
|
|
235
|
-
logger.error(f"Error during raw file cleanup for PDF {pdf_file.id}: {e}")
|
|
236
|
-
transaction.on_commit(cleanup_raw_files)
|
|
237
|
-
except Exception as e:
|
|
238
|
-
logger.error(f"Error scheduling raw file deletion for PDF {pdf_file.id}: {e}")
|
|
239
|
-
raise
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
from endoreg_db.models import RawPdfFile
|
|
2
|
-
from endoreg_db.serializers.report.report_list import ReportListSerializer
|
|
3
|
-
|
|
4
|
-
from django.core.paginator import Paginator
|
|
5
|
-
from django.db.models import Q
|
|
6
|
-
from rest_framework import status
|
|
7
|
-
from rest_framework.response import Response
|
|
8
|
-
from rest_framework.views import APIView
|
|
9
|
-
|
|
10
|
-
import logging
|
|
11
|
-
logger = logging.getLogger(__name__)
|
|
12
|
-
|
|
13
|
-
class ReportListView(APIView):
|
|
14
|
-
"""
|
|
15
|
-
API-Endpunkt für paginierte Report-Listen mit optionaler Filterung
|
|
16
|
-
GET /api/reports/
|
|
17
|
-
"""
|
|
18
|
-
|
|
19
|
-
def get(self, request):
|
|
20
|
-
try:
|
|
21
|
-
# Query-Parameter abrufen
|
|
22
|
-
page = int(request.GET.get('page', 1))
|
|
23
|
-
page_size = min(int(request.GET.get('page_size', 20)), 100) # Max 100 pro Seite
|
|
24
|
-
|
|
25
|
-
# Filter-Parameter
|
|
26
|
-
file_type_filter = request.GET.get('file_type')
|
|
27
|
-
patient_name_filter = request.GET.get('patient_name')
|
|
28
|
-
casenumber_filter = request.GET.get('casenumber')
|
|
29
|
-
date_from = request.GET.get('date_from')
|
|
30
|
-
date_to = request.GET.get('date_to')
|
|
31
|
-
|
|
32
|
-
# Base QuerySet mit related data
|
|
33
|
-
queryset = RawPdfFile.objects.select_related('sensitive_meta').all()
|
|
34
|
-
|
|
35
|
-
# Filter anwenden
|
|
36
|
-
if patient_name_filter:
|
|
37
|
-
queryset = queryset.filter(
|
|
38
|
-
Q(sensitive_meta__patient_first_name__icontains=patient_name_filter) |
|
|
39
|
-
Q(sensitive_meta__patient_last_name__icontains=patient_name_filter)
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
if casenumber_filter:
|
|
43
|
-
queryset = queryset.filter(
|
|
44
|
-
sensitive_meta__case_number__icontains=casenumber_filter
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
if file_type_filter:
|
|
48
|
-
# Filter basierend auf Dateiendung
|
|
49
|
-
queryset = queryset.filter(file__endswith=f'.{file_type_filter}')
|
|
50
|
-
|
|
51
|
-
if date_from:
|
|
52
|
-
queryset = queryset.filter(created_at__gte=date_from)
|
|
53
|
-
|
|
54
|
-
if date_to:
|
|
55
|
-
queryset = queryset.filter(created_at__lte=date_to)
|
|
56
|
-
|
|
57
|
-
# Sortierung (neueste zuerst)
|
|
58
|
-
queryset = queryset.order_by('-created_at')
|
|
59
|
-
|
|
60
|
-
# Paginierung
|
|
61
|
-
paginator = Paginator(queryset, page_size)
|
|
62
|
-
|
|
63
|
-
if page > paginator.num_pages:
|
|
64
|
-
return Response({
|
|
65
|
-
'count': paginator.count,
|
|
66
|
-
'next': None,
|
|
67
|
-
'previous': None,
|
|
68
|
-
'results': []
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
page_obj = paginator.get_page(page)
|
|
72
|
-
|
|
73
|
-
# Serialisierung
|
|
74
|
-
serializer = ReportListSerializer(
|
|
75
|
-
page_obj.object_list,
|
|
76
|
-
many=True,
|
|
77
|
-
context={'request': request}
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
# URLs für Paginierung
|
|
81
|
-
next_url = None
|
|
82
|
-
previous_url = None
|
|
83
|
-
|
|
84
|
-
if page_obj.has_next():
|
|
85
|
-
next_url = request.build_absolute_uri(
|
|
86
|
-
f"?page={page_obj.next_page_number()}&page_size={page_size}"
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
if page_obj.has_previous():
|
|
90
|
-
previous_url = request.build_absolute_uri(
|
|
91
|
-
f"?page={page_obj.previous_page_number()}&page_size={page_size}"
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
-
return Response({
|
|
95
|
-
'count': paginator.count,
|
|
96
|
-
'next': next_url,
|
|
97
|
-
'previous': previous_url,
|
|
98
|
-
'results': serializer.data
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
except ValueError as e:
|
|
102
|
-
logger.error("Ungültige Parameter in ReportListView: %s", str(e))
|
|
103
|
-
return Response(
|
|
104
|
-
{"error": "Ungültige Parameter"},
|
|
105
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
106
|
-
)
|
|
107
|
-
except (AttributeError, TypeError, OSError) as e:
|
|
108
|
-
logger.error("Fehler in ReportListView: %s", str(e))
|
|
109
|
-
return Response(
|
|
110
|
-
{"error": "Fehler beim Laden der Reports"},
|
|
111
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
112
|
-
)
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
from endoreg_db.models import RawPdfFile
|
|
2
|
-
from endoreg_db.serializers.report.report import ReportDataSerializer
|
|
3
|
-
|
|
4
|
-
from django.shortcuts import get_object_or_404
|
|
5
|
-
from rest_framework import status
|
|
6
|
-
from rest_framework.response import Response
|
|
7
|
-
from rest_framework.views import APIView
|
|
8
|
-
|
|
9
|
-
import logging
|
|
10
|
-
logger = logging.getLogger(__name__)
|
|
11
|
-
|
|
12
|
-
class ReportWithSecureUrlView(APIView):
|
|
13
|
-
"""
|
|
14
|
-
API-Endpunkt für Reports mit sicherer URL-Generierung
|
|
15
|
-
GET /api/reports/{report_id}/with-secure-url/
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
def get(self, request, report_id):
|
|
19
|
-
try:
|
|
20
|
-
report = get_object_or_404(RawPdfFile, id=report_id)
|
|
21
|
-
serializer = ReportDataSerializer(report, context={'request': request})
|
|
22
|
-
return Response(serializer.data)
|
|
23
|
-
except (ValueError, TypeError) as e:
|
|
24
|
-
logger.error("Fehler beim Laden des Reports %s: %s", report_id, str(e))
|
|
25
|
-
return Response(
|
|
26
|
-
{"error": "Report konnte nicht geladen werden"},
|
|
27
|
-
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
|
28
|
-
)
|