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
|
@@ -18,14 +18,14 @@ from endoreg_db.utils.hashs import get_patient_examination_hash, get_patient_has
|
|
|
18
18
|
from ..administration import Center, Examiner, FirstName, LastName, Patient
|
|
19
19
|
from ..medical import PatientExamination
|
|
20
20
|
from ..other import Gender
|
|
21
|
-
from ..state import SensitiveMetaState
|
|
22
21
|
|
|
23
22
|
if TYPE_CHECKING:
|
|
24
23
|
from .sensitive_meta import SensitiveMeta # Import model for type hinting
|
|
25
24
|
|
|
26
25
|
logger = logging.getLogger(__name__)
|
|
27
26
|
SECRET_SALT = os.getenv("DJANGO_SALT", "default_salt")
|
|
28
|
-
|
|
27
|
+
DEFAULT_UNKNOWN = "unknown"
|
|
28
|
+
|
|
29
29
|
|
|
30
30
|
# Regex-Pattern für verschiedene Datumsformate
|
|
31
31
|
ISO_RX = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
|
@@ -80,9 +80,7 @@ def parse_any_date(s: str) -> Optional[date]:
|
|
|
80
80
|
# Try dateparser with German locale preference
|
|
81
81
|
import dateparser
|
|
82
82
|
|
|
83
|
-
dt = dateparser.parse(
|
|
84
|
-
s, settings={"DATE_ORDER": "DMY", "PREFER_DAY_OF_MONTH": "first"}
|
|
85
|
-
)
|
|
83
|
+
dt = dateparser.parse(s, settings={"DATE_ORDER": "DMY", "PREFER_DAY_OF_MONTH": "first"})
|
|
86
84
|
return dt.date() if dt else None
|
|
87
85
|
except Exception as e:
|
|
88
86
|
logger.debug(f"Dateparser fallback failed for '{s}': {e}")
|
|
@@ -175,9 +173,7 @@ def calculate_patient_hash(instance: "SensitiveMeta", salt: str = SECRET_SALT) -
|
|
|
175
173
|
return sha256(hash_str.encode()).hexdigest()
|
|
176
174
|
|
|
177
175
|
|
|
178
|
-
def calculate_examination_hash(
|
|
179
|
-
instance: "SensitiveMeta", salt: str = SECRET_SALT
|
|
180
|
-
) -> str:
|
|
176
|
+
def calculate_examination_hash(instance: "SensitiveMeta", salt: str = SECRET_SALT) -> str:
|
|
181
177
|
"""Calculates the examination hash for the instance."""
|
|
182
178
|
dob = instance.patient_dob
|
|
183
179
|
first_name = instance.patient_first_name
|
|
@@ -192,6 +188,11 @@ def calculate_examination_hash(
|
|
|
192
188
|
if not center:
|
|
193
189
|
raise ValueError("Center is required to calculate examination hash.")
|
|
194
190
|
|
|
191
|
+
if not first_name:
|
|
192
|
+
raise ValueError("First name is required to calculate examination hash.")
|
|
193
|
+
if not last_name:
|
|
194
|
+
raise ValueError("Last name is required to calculate examination hash.")
|
|
195
|
+
|
|
195
196
|
hash_str = get_patient_examination_hash(
|
|
196
197
|
first_name=first_name,
|
|
197
198
|
last_name=last_name,
|
|
@@ -210,30 +211,22 @@ def create_pseudo_examiner_logic(instance: "SensitiveMeta") -> "Examiner":
|
|
|
210
211
|
center = instance.center # Should be set before calling save
|
|
211
212
|
|
|
212
213
|
if not first_name or not last_name or not center:
|
|
213
|
-
logger.warning(
|
|
214
|
-
f"Incomplete examiner info for SensitiveMeta (pk={instance.pk or 'new'}). Using default examiner."
|
|
215
|
-
)
|
|
214
|
+
logger.warning(f"Incomplete examiner info for SensitiveMeta (pk={instance.pk or 'new'}). Using default examiner.")
|
|
216
215
|
# Ensure default center exists or handle appropriately
|
|
217
216
|
try:
|
|
218
217
|
default_center = Center.objects.get(name="endoreg_db_demo")
|
|
219
218
|
except Center.DoesNotExist:
|
|
220
|
-
logger.error(
|
|
221
|
-
"Default center 'endoreg_db_demo' not found. Cannot create default examiner."
|
|
222
|
-
)
|
|
219
|
+
logger.error("Default center 'endoreg_db_demo' not found. Cannot create default examiner.")
|
|
223
220
|
raise ValueError("Default center 'endoreg_db_demo' not found.")
|
|
224
221
|
|
|
225
|
-
examiner, _created = Examiner.custom_get_or_create(
|
|
226
|
-
first_name="Unknown", last_name="Unknown", center=default_center
|
|
227
|
-
)
|
|
222
|
+
examiner, _created = Examiner.custom_get_or_create(first_name="Unknown", last_name="Unknown", center=default_center)
|
|
228
223
|
else:
|
|
229
|
-
examiner, _created = Examiner.custom_get_or_create(
|
|
230
|
-
first_name=first_name, last_name=last_name, center=center
|
|
231
|
-
)
|
|
224
|
+
examiner, _created = Examiner.custom_get_or_create(first_name=first_name, last_name=last_name, center=center)
|
|
232
225
|
|
|
233
226
|
return examiner
|
|
234
227
|
|
|
235
228
|
|
|
236
|
-
def get_or_create_pseudo_patient_logic(instance: "SensitiveMeta")
|
|
229
|
+
def get_or_create_pseudo_patient_logic(instance: "SensitiveMeta"):
|
|
237
230
|
"""Gets or creates the pseudo patient based on instance data."""
|
|
238
231
|
# Ensure necessary fields are set
|
|
239
232
|
if not instance.patient_hash:
|
|
@@ -256,12 +249,12 @@ def get_or_create_pseudo_patient_logic(instance: "SensitiveMeta") -> "Patient":
|
|
|
256
249
|
birth_year=year,
|
|
257
250
|
birth_month=month,
|
|
258
251
|
)
|
|
259
|
-
return patient
|
|
252
|
+
return patient, _created
|
|
260
253
|
|
|
261
254
|
|
|
262
255
|
def get_or_create_pseudo_patient_examination_logic(
|
|
263
256
|
instance: "SensitiveMeta",
|
|
264
|
-
)
|
|
257
|
+
):
|
|
265
258
|
"""Gets or creates the pseudo patient examination based on instance data."""
|
|
266
259
|
# Ensure necessary fields are set
|
|
267
260
|
if not instance.patient_hash:
|
|
@@ -270,19 +263,17 @@ def get_or_create_pseudo_patient_examination_logic(
|
|
|
270
263
|
instance.examination_hash = calculate_examination_hash(instance)
|
|
271
264
|
|
|
272
265
|
# Ensure the pseudo patient exists first, as PatientExamination might depend on it
|
|
273
|
-
if not instance.
|
|
274
|
-
pseudo_patient = get_or_create_pseudo_patient_logic(instance)
|
|
275
|
-
instance.
|
|
276
|
-
|
|
277
|
-
patient_examination, _created = (
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
# pseudo_patient=instance.pseudo_patient
|
|
283
|
-
)
|
|
266
|
+
if not instance.pseudo_patient:
|
|
267
|
+
pseudo_patient, _created = get_or_create_pseudo_patient_logic(instance)
|
|
268
|
+
instance.pseudo_patient = pseudo_patient # Assign FK directly
|
|
269
|
+
|
|
270
|
+
patient_examination, _created = PatientExamination.get_or_create_pseudo_patient_examination_by_hash(
|
|
271
|
+
patient_hash=instance.patient_hash,
|
|
272
|
+
examination_hash=instance.examination_hash,
|
|
273
|
+
# Optionally pass pseudo_patient if the method requires it
|
|
274
|
+
# pseudo_patient=instance.pseudo_patient
|
|
284
275
|
)
|
|
285
|
-
return patient_examination
|
|
276
|
+
return patient_examination, _created
|
|
286
277
|
|
|
287
278
|
|
|
288
279
|
@transaction.atomic # Ensure all operations within save succeed or fail together
|
|
@@ -343,14 +334,10 @@ def perform_save_logic(instance: "SensitiveMeta") -> "Examiner":
|
|
|
343
334
|
|
|
344
335
|
# 1. Ensure DOB and Examination Date exist
|
|
345
336
|
if not instance.patient_dob:
|
|
346
|
-
logger.debug(
|
|
347
|
-
f"SensitiveMeta (pk={instance.pk or 'new'}): Patient DOB missing, generating random."
|
|
348
|
-
)
|
|
337
|
+
logger.debug(f"SensitiveMeta (pk={instance.pk or 'new'}): Patient DOB missing, generating random.")
|
|
349
338
|
instance.patient_dob = generate_random_dob()
|
|
350
339
|
if not instance.examination_date:
|
|
351
|
-
logger.debug(
|
|
352
|
-
f"SensitiveMeta (pk={instance.pk or 'new'}): Examination date missing, generating random."
|
|
353
|
-
)
|
|
340
|
+
logger.debug(f"SensitiveMeta (pk={instance.pk or 'new'}): Examination date missing, generating random.")
|
|
354
341
|
instance.examination_date = generate_random_examination_date()
|
|
355
342
|
|
|
356
343
|
# 2. Ensure Center exists (should be set before calling save)
|
|
@@ -381,19 +368,19 @@ def perform_save_logic(instance: "SensitiveMeta") -> "Examiner":
|
|
|
381
368
|
# Updated: patient_first_name = "Max" → hash = sha256("Max Mustermann...")
|
|
382
369
|
#
|
|
383
370
|
if not instance.patient_first_name:
|
|
384
|
-
instance.patient_first_name =
|
|
371
|
+
instance.patient_first_name = DEFAULT_UNKNOWN
|
|
385
372
|
logger.debug(
|
|
386
373
|
"SensitiveMeta (pk=%s): Patient first name missing, set to default '%s'.",
|
|
387
374
|
instance.pk or "new",
|
|
388
|
-
|
|
375
|
+
DEFAULT_UNKNOWN,
|
|
389
376
|
)
|
|
390
377
|
|
|
391
378
|
if not instance.patient_last_name:
|
|
392
|
-
instance.patient_last_name =
|
|
379
|
+
instance.patient_last_name = DEFAULT_UNKNOWN
|
|
393
380
|
logger.debug(
|
|
394
381
|
"SensitiveMeta (pk=%s): Patient last name missing, set to default '%s'.",
|
|
395
382
|
instance.pk or "new",
|
|
396
|
-
|
|
383
|
+
DEFAULT_UNKNOWN,
|
|
397
384
|
)
|
|
398
385
|
|
|
399
386
|
# 3. Ensure Gender exists (should be set before calling save, e.g., during creation/update)
|
|
@@ -402,9 +389,7 @@ def perform_save_logic(instance: "SensitiveMeta") -> "Examiner":
|
|
|
402
389
|
first_name = instance.patient_first_name
|
|
403
390
|
gender_str = guess_name_gender(first_name)
|
|
404
391
|
if not gender_str:
|
|
405
|
-
raise ValueError(
|
|
406
|
-
"Patient gender could not be determined and must be set before saving."
|
|
407
|
-
)
|
|
392
|
+
raise ValueError("Patient gender could not be determined and must be set before saving.")
|
|
408
393
|
# Convert string to Gender object
|
|
409
394
|
try:
|
|
410
395
|
gender_obj = Gender.objects.get(name=gender_str)
|
|
@@ -426,13 +411,13 @@ def perform_save_logic(instance: "SensitiveMeta") -> "Examiner":
|
|
|
426
411
|
|
|
427
412
|
# 5. Get or Create Pseudo Patient (depends on hash, center, gender, dob)
|
|
428
413
|
# Assign directly to the FK field to avoid premature saving issues
|
|
429
|
-
pseudo_patient = get_or_create_pseudo_patient_logic(instance)
|
|
430
|
-
instance.
|
|
414
|
+
pseudo_patient, _created = get_or_create_pseudo_patient_logic(instance)
|
|
415
|
+
instance.pseudo_patient = pseudo_patient
|
|
431
416
|
|
|
432
417
|
# 6. Get or Create Pseudo Examination (depends on hashes)
|
|
433
418
|
# Assign directly to the FK field
|
|
434
|
-
pseudo_examination = get_or_create_pseudo_patient_examination_logic(instance)
|
|
435
|
-
instance.
|
|
419
|
+
pseudo_examination, _created = get_or_create_pseudo_patient_examination_logic(instance)
|
|
420
|
+
instance.pseudo_examination = pseudo_examination
|
|
436
421
|
|
|
437
422
|
# 7. Get or Create Pseudo Examiner (depends on names, center)
|
|
438
423
|
# This needs to happen *after* the main instance has a PK for M2M linking.
|
|
@@ -445,9 +430,7 @@ def perform_save_logic(instance: "SensitiveMeta") -> "Examiner":
|
|
|
445
430
|
return examiner_instance
|
|
446
431
|
|
|
447
432
|
|
|
448
|
-
def create_sensitive_meta_from_dict(
|
|
449
|
-
cls: Type["SensitiveMeta"], data: Dict[str, Any]
|
|
450
|
-
) -> "SensitiveMeta":
|
|
433
|
+
def create_sensitive_meta_from_dict(cls: Type["SensitiveMeta"], data: Dict[str, Any]) -> "SensitiveMeta":
|
|
451
434
|
"""
|
|
452
435
|
Create a SensitiveMeta instance from a dictionary.
|
|
453
436
|
|
|
@@ -467,6 +450,8 @@ def create_sensitive_meta_from_dict(
|
|
|
467
450
|
"patient_dob": date(1990, 1, 1),
|
|
468
451
|
"examination_date": date.today(),
|
|
469
452
|
"center": center_obj, # ← Center object
|
|
453
|
+
"text": text #from extraction
|
|
454
|
+
|
|
470
455
|
}
|
|
471
456
|
sm = SensitiveMeta.create_from_dict(data)
|
|
472
457
|
|
|
@@ -477,6 +462,7 @@ def create_sensitive_meta_from_dict(
|
|
|
477
462
|
"patient_dob": date(1990, 1, 1),
|
|
478
463
|
"examination_date": date.today(),
|
|
479
464
|
"center_name": "university_hospital_wuerzburg", # ← String
|
|
465
|
+
"anonymized_text": "anonymized text"
|
|
480
466
|
}
|
|
481
467
|
sm = SensitiveMeta.create_from_dict(data)
|
|
482
468
|
```
|
|
@@ -493,11 +479,7 @@ def create_sensitive_meta_from_dict(
|
|
|
493
479
|
ValueError: If center_name does not match any Center in database
|
|
494
480
|
"""
|
|
495
481
|
|
|
496
|
-
field_names = {
|
|
497
|
-
f.name
|
|
498
|
-
for f in cls._meta.get_fields()
|
|
499
|
-
if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)
|
|
500
|
-
}
|
|
482
|
+
field_names = {f.name for f in cls._meta.get_fields() if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)}
|
|
501
483
|
selected_data = {k: v for k, v in data.items() if k in field_names}
|
|
502
484
|
|
|
503
485
|
# --- Convert patient_dob if it's a date object ---
|
|
@@ -524,13 +506,9 @@ def create_sensitive_meta_from_dict(
|
|
|
524
506
|
try:
|
|
525
507
|
import dateparser
|
|
526
508
|
|
|
527
|
-
parsed_dob = dateparser.parse(
|
|
528
|
-
dob, languages=["de"], settings={"DATE_ORDER": "DMY"}
|
|
529
|
-
)
|
|
509
|
+
parsed_dob = dateparser.parse(dob, languages=["de"], settings={"DATE_ORDER": "DMY"})
|
|
530
510
|
if parsed_dob:
|
|
531
|
-
aware_dob = timezone.make_aware(
|
|
532
|
-
parsed_dob.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
533
|
-
)
|
|
511
|
+
aware_dob = timezone.make_aware(parsed_dob.replace(hour=0, minute=0, second=0, microsecond=0))
|
|
534
512
|
selected_data["patient_dob"] = aware_dob
|
|
535
513
|
logger.debug(
|
|
536
514
|
"Parsed string patient_dob '%s' to aware datetime: %s",
|
|
@@ -584,9 +562,7 @@ def create_sensitive_meta_from_dict(
|
|
|
584
562
|
# Fall back to dateparser for complex formats
|
|
585
563
|
import dateparser
|
|
586
564
|
|
|
587
|
-
parsed_date = dateparser.parse(
|
|
588
|
-
exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"}
|
|
589
|
-
)
|
|
565
|
+
parsed_date = dateparser.parse(exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"})
|
|
590
566
|
if parsed_date:
|
|
591
567
|
selected_data["examination_date"] = parsed_date.date()
|
|
592
568
|
logger.debug(
|
|
@@ -604,9 +580,7 @@ def create_sensitive_meta_from_dict(
|
|
|
604
580
|
# Use dateparser for non-ISO formats
|
|
605
581
|
import dateparser
|
|
606
582
|
|
|
607
|
-
parsed_date = dateparser.parse(
|
|
608
|
-
exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"}
|
|
609
|
-
)
|
|
583
|
+
parsed_date = dateparser.parse(exam_date, languages=["de"], settings={"DATE_ORDER": "DMY"})
|
|
610
584
|
if parsed_date:
|
|
611
585
|
selected_data["examination_date"] = parsed_date.date()
|
|
612
586
|
logger.debug(
|
|
@@ -620,6 +594,7 @@ def create_sensitive_meta_from_dict(
|
|
|
620
594
|
exam_date,
|
|
621
595
|
)
|
|
622
596
|
selected_data.pop("examination_date", None)
|
|
597
|
+
|
|
623
598
|
except Exception as e:
|
|
624
599
|
logger.warning(
|
|
625
600
|
"Error parsing examination_date string '%s': %s, removing from data",
|
|
@@ -648,13 +623,11 @@ def create_sensitive_meta_from_dict(
|
|
|
648
623
|
raise ValueError(f"Center with name '{center_name}' does not exist.")
|
|
649
624
|
else:
|
|
650
625
|
# Neither center nor center_name provided
|
|
651
|
-
raise ValueError(
|
|
652
|
-
"Either 'center' (Center object) or 'center_name' (string) is required in data dictionary."
|
|
653
|
-
)
|
|
626
|
+
raise ValueError("Either 'center' (Center object) or 'center_name' (string) is required in data dictionary.")
|
|
654
627
|
|
|
655
628
|
# Handle Names and Gender
|
|
656
|
-
first_name = selected_data.get("patient_first_name") or
|
|
657
|
-
last_name = selected_data.get("patient_last_name") or
|
|
629
|
+
first_name = selected_data.get("patient_first_name") or DEFAULT_UNKNOWN
|
|
630
|
+
last_name = selected_data.get("patient_last_name") or DEFAULT_UNKNOWN
|
|
658
631
|
selected_data["patient_first_name"] = first_name # Ensure defaults are set
|
|
659
632
|
selected_data["patient_last_name"] = last_name
|
|
660
633
|
|
|
@@ -666,34 +639,80 @@ def create_sensitive_meta_from_dict(
|
|
|
666
639
|
elif isinstance(patient_gender_input, str):
|
|
667
640
|
# Input is a string (gender name)
|
|
668
641
|
try:
|
|
669
|
-
selected_data["patient_gender"] = Gender.objects.get(
|
|
670
|
-
name=patient_gender_input
|
|
671
|
-
)
|
|
642
|
+
selected_data["patient_gender"] = Gender.objects.get(name=patient_gender_input)
|
|
672
643
|
except Gender.DoesNotExist:
|
|
673
|
-
logger.warning(
|
|
674
|
-
f"Gender with name '{patient_gender_input}' provided but not found. Attempting to guess or use default."
|
|
675
|
-
)
|
|
644
|
+
logger.warning(f"Gender with name '{patient_gender_input}' provided but not found. Attempting to guess or use default.")
|
|
676
645
|
# Fall through to guessing logic if provided string name is invalid
|
|
677
|
-
|
|
646
|
+
normalized = (patient_gender_input or "").lower()
|
|
647
|
+
if normalized in {"male", "female", "unknown"}:
|
|
648
|
+
gender_obj, _ = Gender.objects.get_or_create(
|
|
649
|
+
name=normalized,
|
|
650
|
+
defaults={
|
|
651
|
+
"abbreviation": normalized[:1].upper() or None,
|
|
652
|
+
"description": "Auto-created default gender entry",
|
|
653
|
+
},
|
|
654
|
+
)
|
|
655
|
+
selected_data["patient_gender"] = gender_obj
|
|
656
|
+
else:
|
|
657
|
+
patient_gender_input = None # Reset to trigger guessing
|
|
678
658
|
|
|
679
|
-
if not isinstance(
|
|
680
|
-
selected_data.get("patient_gender"), Gender
|
|
681
|
-
): # If not already a Gender object (e.g. was None, or string lookup failed)
|
|
659
|
+
if not isinstance(selected_data.get("patient_gender"), Gender): # If not already a Gender object (e.g. was None, or string lookup failed)
|
|
682
660
|
gender_name_to_use = guess_name_gender(first_name)
|
|
683
661
|
if not gender_name_to_use:
|
|
684
|
-
logger.warning(
|
|
685
|
-
f"Could not guess gender for name '{first_name}'. Setting Gender to unknown."
|
|
686
|
-
)
|
|
662
|
+
logger.warning(f"Could not guess gender for name '{first_name}'. Setting Gender to unknown.")
|
|
687
663
|
gender_name_to_use = "unknown"
|
|
688
664
|
try:
|
|
689
|
-
selected_data["patient_gender"] = Gender.objects.get(
|
|
690
|
-
name=gender_name_to_use
|
|
691
|
-
)
|
|
665
|
+
selected_data["patient_gender"] = Gender.objects.get(name=gender_name_to_use)
|
|
692
666
|
except Gender.DoesNotExist:
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
667
|
+
gender_obj, _ = Gender.objects.get_or_create(
|
|
668
|
+
name=gender_name_to_use,
|
|
669
|
+
defaults={
|
|
670
|
+
"abbreviation": gender_name_to_use[:1].upper() or None,
|
|
671
|
+
"description": "Auto-created default gender entry",
|
|
672
|
+
},
|
|
696
673
|
)
|
|
674
|
+
selected_data["patient_gender"] = gender_obj
|
|
675
|
+
|
|
676
|
+
# Handle Text
|
|
677
|
+
selected_data["text"] = data.get("text") or DEFAULT_UNKNOWN
|
|
678
|
+
|
|
679
|
+
# --- Add missing optional fields safely ---
|
|
680
|
+
file_path = data.get("file_path")
|
|
681
|
+
if file_path:
|
|
682
|
+
selected_data["file_path"] = str(file_path)
|
|
683
|
+
logger.debug(f"Set file_path: {file_path}")
|
|
684
|
+
|
|
685
|
+
casenumber = data.get("casenumber")
|
|
686
|
+
if casenumber:
|
|
687
|
+
selected_data["casenumber"] = str(casenumber).strip()
|
|
688
|
+
logger.debug(f"Set casenumber: {casenumber}")
|
|
689
|
+
|
|
690
|
+
exam_time = data.get("examination_time")
|
|
691
|
+
if exam_time:
|
|
692
|
+
try:
|
|
693
|
+
from datetime import time as dt_time
|
|
694
|
+
|
|
695
|
+
# Accepts strings like "14:35" or full datetime
|
|
696
|
+
if isinstance(exam_time, str):
|
|
697
|
+
h, m = exam_time.strip().split(":")[:2]
|
|
698
|
+
selected_data["examination_time"] = dt_time(int(h), int(m))
|
|
699
|
+
elif isinstance(exam_time, datetime):
|
|
700
|
+
selected_data["examination_time"] = exam_time.time()
|
|
701
|
+
elif isinstance(exam_time, date):
|
|
702
|
+
# no time info — ignore
|
|
703
|
+
logger.debug(f"examination_time value {exam_time} has no time component; skipping")
|
|
704
|
+
else:
|
|
705
|
+
selected_data["examination_time"] = exam_time
|
|
706
|
+
except Exception as e:
|
|
707
|
+
logger.warning(f"Invalid examination_time '{exam_time}': {e}")
|
|
708
|
+
|
|
709
|
+
anonymized_text = data.get("anonymized_text") or data.get("anonym_text")
|
|
710
|
+
if anonymized_text:
|
|
711
|
+
if isinstance(anonymized_text, (str, bytes)):
|
|
712
|
+
selected_data["anonymized_text"] = anonymized_text.decode() if isinstance(anonymized_text, bytes) else anonymized_text
|
|
713
|
+
else:
|
|
714
|
+
selected_data["anonymized_text"] = str(anonymized_text)
|
|
715
|
+
logger.debug("Set anonymized_text (length=%d)", len(selected_data["anonymized_text"]))
|
|
697
716
|
|
|
698
717
|
# Update name DB
|
|
699
718
|
update_name_db(first_name, last_name)
|
|
@@ -707,9 +726,7 @@ def create_sensitive_meta_from_dict(
|
|
|
707
726
|
return sensitive_meta
|
|
708
727
|
|
|
709
728
|
|
|
710
|
-
def update_sensitive_meta_from_dict(
|
|
711
|
-
instance: "SensitiveMeta", data: Dict[str, Any]
|
|
712
|
-
) -> "SensitiveMeta":
|
|
729
|
+
def update_sensitive_meta_from_dict(instance: "SensitiveMeta", data: Dict[str, Any]) -> "SensitiveMeta":
|
|
713
730
|
"""
|
|
714
731
|
Updates a SensitiveMeta instance from a dictionary of new values.
|
|
715
732
|
|
|
@@ -754,16 +771,10 @@ def update_sensitive_meta_from_dict(
|
|
|
754
771
|
Raises:
|
|
755
772
|
Exception: If save fails or required conversions fail
|
|
756
773
|
"""
|
|
757
|
-
field_names = {
|
|
758
|
-
f.name
|
|
759
|
-
for f in instance._meta.get_fields()
|
|
760
|
-
if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)
|
|
761
|
-
}
|
|
774
|
+
field_names = {f.name for f in instance._meta.get_fields() if not f.is_relation or f.one_to_one or (f.many_to_one and f.related_model)}
|
|
762
775
|
# Exclude FKs that should not be updated directly from dict keys (handled separately or via save logic)
|
|
763
776
|
excluded_fields = {"pseudo_patient", "pseudo_examination"}
|
|
764
|
-
selected_data = {
|
|
765
|
-
k: v for k, v in data.items() if k in field_names and k not in excluded_fields
|
|
766
|
-
}
|
|
777
|
+
selected_data = {k: v for k, v in data.items() if k in field_names and k not in excluded_fields}
|
|
767
778
|
|
|
768
779
|
# Handle potential Center update - accept both center_name (string) and center (object)
|
|
769
780
|
from ..administration import Center
|
|
@@ -777,9 +788,7 @@ def update_sensitive_meta_from_dict(
|
|
|
777
788
|
instance.center = center
|
|
778
789
|
logger.debug(f"Updated center from Center object: {center.name}")
|
|
779
790
|
else:
|
|
780
|
-
logger.warning(
|
|
781
|
-
f"Invalid center type {type(center)}, expected Center instance. Ignoring."
|
|
782
|
-
)
|
|
791
|
+
logger.warning(f"Invalid center type {type(center)}, expected Center instance. Ignoring.")
|
|
783
792
|
# Remove from selected_data to prevent override
|
|
784
793
|
selected_data.pop("center", None)
|
|
785
794
|
elif center_name:
|
|
@@ -789,9 +798,7 @@ def update_sensitive_meta_from_dict(
|
|
|
789
798
|
instance.center = center_obj
|
|
790
799
|
logger.debug(f"Updated center from center_name string: {center_name}")
|
|
791
800
|
except Center.DoesNotExist:
|
|
792
|
-
logger.warning(
|
|
793
|
-
f"Center '{center_name}' not found during update. Keeping existing center."
|
|
794
|
-
)
|
|
801
|
+
logger.warning(f"Center '{center_name}' not found during update. Keeping existing center.")
|
|
795
802
|
else:
|
|
796
803
|
# Both are None/missing - remove 'center' from selected_data to preserve existing value
|
|
797
804
|
selected_data.pop("center", None)
|
|
@@ -814,14 +821,10 @@ def update_sensitive_meta_from_dict(
|
|
|
814
821
|
elif isinstance(patient_gender_input, str):
|
|
815
822
|
gender_input_clean = patient_gender_input.strip()
|
|
816
823
|
# Try direct case-insensitive DB lookup first
|
|
817
|
-
gender_obj = Gender.objects.filter(
|
|
818
|
-
name__iexact=gender_input_clean
|
|
819
|
-
).first()
|
|
824
|
+
gender_obj = Gender.objects.filter(name__iexact=gender_input_clean).first()
|
|
820
825
|
if gender_obj:
|
|
821
826
|
selected_data["patient_gender"] = gender_obj
|
|
822
|
-
logger.debug(
|
|
823
|
-
f"Successfully matched gender string '{patient_gender_input}' to Gender object via iexact lookup"
|
|
824
|
-
)
|
|
827
|
+
logger.debug(f"Successfully matched gender string '{patient_gender_input}' to Gender object via iexact lookup")
|
|
825
828
|
else:
|
|
826
829
|
# Use mapping helper for fallback
|
|
827
830
|
mapped = _map_gender_string_to_standard(gender_input_clean)
|
|
@@ -829,52 +832,52 @@ def update_sensitive_meta_from_dict(
|
|
|
829
832
|
gender_obj = Gender.objects.filter(name__iexact=mapped).first()
|
|
830
833
|
if gender_obj:
|
|
831
834
|
selected_data["patient_gender"] = gender_obj
|
|
832
|
-
logger.info(
|
|
833
|
-
f"Mapped gender '{patient_gender_input}' to '{mapped}' via fallback mapping"
|
|
834
|
-
)
|
|
835
|
+
logger.info(f"Mapped gender '{patient_gender_input}' to '{mapped}' via fallback mapping")
|
|
835
836
|
else:
|
|
836
|
-
logger.warning(
|
|
837
|
-
|
|
838
|
-
)
|
|
839
|
-
unknown_gender = Gender.objects.filter(
|
|
840
|
-
name__iexact="unknown"
|
|
841
|
-
).first()
|
|
837
|
+
logger.warning(f"Mapped gender '{patient_gender_input}' to '{mapped}', but no such Gender in DB. Trying 'unknown'.")
|
|
838
|
+
unknown_gender = Gender.objects.filter(name__iexact="unknown").first()
|
|
842
839
|
if unknown_gender:
|
|
843
840
|
selected_data["patient_gender"] = unknown_gender
|
|
844
|
-
logger.warning(
|
|
845
|
-
f"Using 'unknown' gender as fallback for '{patient_gender_input}'"
|
|
846
|
-
)
|
|
841
|
+
logger.warning(f"Using 'unknown' gender as fallback for '{patient_gender_input}'")
|
|
847
842
|
else:
|
|
848
|
-
logger.error(
|
|
849
|
-
f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update."
|
|
850
|
-
)
|
|
843
|
+
logger.error(f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update.")
|
|
851
844
|
selected_data.pop("patient_gender", None)
|
|
852
845
|
else:
|
|
853
846
|
# Last resort: try to get 'unknown' gender
|
|
854
|
-
unknown_gender = Gender.objects.filter(
|
|
855
|
-
name__iexact="unknown"
|
|
856
|
-
).first()
|
|
847
|
+
unknown_gender = Gender.objects.filter(name__iexact="unknown").first()
|
|
857
848
|
if unknown_gender:
|
|
858
849
|
selected_data["patient_gender"] = unknown_gender
|
|
859
|
-
logger.warning(
|
|
860
|
-
f"Using 'unknown' gender as fallback for '{patient_gender_input}' (no mapping)"
|
|
861
|
-
)
|
|
850
|
+
logger.warning(f"Using 'unknown' gender as fallback for '{patient_gender_input}' (no mapping)")
|
|
862
851
|
else:
|
|
863
|
-
logger.error(
|
|
864
|
-
f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update."
|
|
865
|
-
)
|
|
852
|
+
logger.error(f"No 'unknown' gender found in database. Cannot handle gender '{patient_gender_input}'. Skipping gender update.")
|
|
866
853
|
selected_data.pop("patient_gender", None)
|
|
867
854
|
else:
|
|
868
|
-
logger.warning(
|
|
869
|
-
f"Unexpected patient_gender type {type(patient_gender_input)}: {patient_gender_input}. Skipping gender update."
|
|
870
|
-
)
|
|
855
|
+
logger.warning(f"Unexpected patient_gender type {type(patient_gender_input)}: {patient_gender_input}. Skipping gender update.")
|
|
871
856
|
selected_data.pop("patient_gender", None)
|
|
872
857
|
except Exception as e:
|
|
873
|
-
logger.exception(
|
|
874
|
-
f"Error handling patient_gender '{patient_gender_input}': {e}. Skipping gender update."
|
|
875
|
-
)
|
|
858
|
+
logger.exception(f"Error handling patient_gender '{patient_gender_input}': {e}. Skipping gender update.")
|
|
876
859
|
selected_data.pop("patient_gender", None)
|
|
877
860
|
|
|
861
|
+
# TODO Review: Handle new optional fields on update
|
|
862
|
+
for key in ("file_path", "casenumber", "examination_time", "anonymized_text", "anonym_text"):
|
|
863
|
+
if key in data and data[key] is not None:
|
|
864
|
+
val = data[key]
|
|
865
|
+
if key in ("file_path", "casenumber"):
|
|
866
|
+
setattr(instance, key, str(val))
|
|
867
|
+
elif key in ("anonymized_text", "anonym_text"):
|
|
868
|
+
setattr(instance, "anonymized_text", val if isinstance(val, str) else str(val))
|
|
869
|
+
elif key == "examination_time":
|
|
870
|
+
try:
|
|
871
|
+
from datetime import time as dt_time
|
|
872
|
+
|
|
873
|
+
if isinstance(val, str) and ":" in val:
|
|
874
|
+
h, m = val.strip().split(":")[:2]
|
|
875
|
+
setattr(instance, "examination_time", dt_time(int(h), int(m)))
|
|
876
|
+
elif isinstance(val, datetime):
|
|
877
|
+
setattr(instance, "examination_time", val.time())
|
|
878
|
+
except Exception as e:
|
|
879
|
+
logger.warning(f"Skipping invalid examination_time '{val}': {e}")
|
|
880
|
+
|
|
878
881
|
# Update other attributes from selected_data
|
|
879
882
|
patient_name_changed = False
|
|
880
883
|
for k, v in selected_data.items():
|
|
@@ -894,9 +897,7 @@ def update_sensitive_meta_from_dict(
|
|
|
894
897
|
value_to_set = v
|
|
895
898
|
if k == "patient_dob":
|
|
896
899
|
if isinstance(v, date) and not isinstance(v, datetime):
|
|
897
|
-
aware_dob = timezone.make_aware(
|
|
898
|
-
datetime.combine(v, datetime.min.time())
|
|
899
|
-
)
|
|
900
|
+
aware_dob = timezone.make_aware(datetime.combine(v, datetime.min.time()))
|
|
900
901
|
value_to_set = aware_dob
|
|
901
902
|
logger.debug(
|
|
902
903
|
"Converted patient_dob from date to aware datetime during update: %s",
|
|
@@ -919,15 +920,9 @@ def update_sensitive_meta_from_dict(
|
|
|
919
920
|
try:
|
|
920
921
|
import dateparser
|
|
921
922
|
|
|
922
|
-
parsed_dob = dateparser.parse(
|
|
923
|
-
v, languages=["de"], settings={"DATE_ORDER": "DMY"}
|
|
924
|
-
)
|
|
923
|
+
parsed_dob = dateparser.parse(v, languages=["de"], settings={"DATE_ORDER": "DMY"})
|
|
925
924
|
if parsed_dob:
|
|
926
|
-
value_to_set = timezone.make_aware(
|
|
927
|
-
parsed_dob.replace(
|
|
928
|
-
hour=0, minute=0, second=0, microsecond=0
|
|
929
|
-
)
|
|
930
|
-
)
|
|
925
|
+
value_to_set = timezone.make_aware(parsed_dob.replace(hour=0, minute=0, second=0, microsecond=0))
|
|
931
926
|
logger.debug(
|
|
932
927
|
"Parsed string patient_dob '%s' during update to aware datetime: %s",
|
|
933
928
|
v,
|
|
@@ -962,9 +957,7 @@ def update_sensitive_meta_from_dict(
|
|
|
962
957
|
try:
|
|
963
958
|
import dateparser
|
|
964
959
|
|
|
965
|
-
parsed_date = dateparser.parse(
|
|
966
|
-
v, languages=["de"], settings={"DATE_ORDER": "DMY"}
|
|
967
|
-
)
|
|
960
|
+
parsed_date = dateparser.parse(v, languages=["de"], settings={"DATE_ORDER": "DMY"})
|
|
968
961
|
if parsed_date:
|
|
969
962
|
value_to_set = parsed_date.date()
|
|
970
963
|
logger.debug(
|
|
@@ -988,18 +981,13 @@ def update_sensitive_meta_from_dict(
|
|
|
988
981
|
# --- End Conversion ---
|
|
989
982
|
|
|
990
983
|
# Check if patient name is changing
|
|
991
|
-
if (
|
|
992
|
-
k in ["patient_first_name", "patient_last_name"]
|
|
993
|
-
and getattr(instance, k) != value_to_set
|
|
994
|
-
):
|
|
984
|
+
if k in ["patient_first_name", "patient_last_name"] and getattr(instance, k) != value_to_set:
|
|
995
985
|
patient_name_changed = True
|
|
996
986
|
|
|
997
987
|
setattr(instance, k, value_to_set) # Use value_to_set
|
|
998
988
|
|
|
999
989
|
except Exception as e:
|
|
1000
|
-
logger.error(
|
|
1001
|
-
f"Error setting attribute '{k}' to '{v}': {e}. Skipping this field."
|
|
1002
|
-
)
|
|
990
|
+
logger.error(f"Error setting attribute '{k}' to '{v}': {e}. Skipping this field.")
|
|
1003
991
|
continue
|
|
1004
992
|
|
|
1005
993
|
# Update name DB if patient names changed
|
|
@@ -1023,15 +1011,21 @@ def update_or_create_sensitive_meta_from_dict(
|
|
|
1023
1011
|
cls: Type["SensitiveMeta"],
|
|
1024
1012
|
data: Dict[str, Any],
|
|
1025
1013
|
instance: Optional["SensitiveMeta"] = None,
|
|
1026
|
-
)
|
|
1014
|
+
):
|
|
1027
1015
|
"""Logic to update or create a SensitiveMeta instance from a dictionary."""
|
|
1028
1016
|
# Check if the instance already exists based on unique fields
|
|
1017
|
+
sensitive_meta: "SensitiveMeta"
|
|
1018
|
+
_created: bool
|
|
1029
1019
|
if instance:
|
|
1030
1020
|
# Update the existing instance
|
|
1031
|
-
|
|
1021
|
+
sensitive_meta = update_sensitive_meta_from_dict(instance, data)
|
|
1022
|
+
_created = False
|
|
1023
|
+
|
|
1032
1024
|
else:
|
|
1033
1025
|
# Create a new instance
|
|
1034
|
-
|
|
1026
|
+
sensitive_meta = create_sensitive_meta_from_dict(cls, data)
|
|
1027
|
+
_created = True
|
|
1028
|
+
return sensitive_meta, _created
|
|
1035
1029
|
|
|
1036
1030
|
|
|
1037
1031
|
def _map_gender_string_to_standard(gender_str: str) -> Optional[str]:
|
|
@@ -1046,3 +1040,28 @@ def _map_gender_string_to_standard(gender_str: str) -> Optional[str]:
|
|
|
1046
1040
|
if gender_lower in variants:
|
|
1047
1041
|
return standard
|
|
1048
1042
|
return None
|
|
1043
|
+
|
|
1044
|
+
def _create_anonymized_record(instance: "SensitiveMeta", DEFAULT_ANONYMIZED=None, DEFAULT_ANONYMIZED_DATE=timezone.make_aware(datetime(1900, 1, 1))) -> None:
|
|
1045
|
+
"""
|
|
1046
|
+
Create a SensitiveMeta instance with all sensitive fields set to anonymized defaults.
|
|
1047
|
+
This is only called after anonymization and will delete all data that can identify a patient from the database.
|
|
1048
|
+
What is left will only be the patient hash.
|
|
1049
|
+
|
|
1050
|
+
Args:
|
|
1051
|
+
instance: The existing SensitiveMeta instance to anonymize
|
|
1052
|
+
DEFAULT_ANONYMIZED: Usually None, The default string to use for anonymized fields (e.g., "anonymized,")
|
|
1053
|
+
"""
|
|
1054
|
+
|
|
1055
|
+
instance.refresh_from_db()
|
|
1056
|
+
instance.get_patient_hash()
|
|
1057
|
+
instance.get_patient_examination_hash()
|
|
1058
|
+
|
|
1059
|
+
anonymized_data = {
|
|
1060
|
+
"patient_first_name": DEFAULT_ANONYMIZED,
|
|
1061
|
+
"patient_last_name": DEFAULT_ANONYMIZED,
|
|
1062
|
+
"patient_dob": DEFAULT_ANONYMIZED_DATE,
|
|
1063
|
+
"examination_date": DEFAULT_ANONYMIZED_DATE,
|
|
1064
|
+
}
|
|
1065
|
+
sensitive_meta = update_sensitive_meta_from_dict(instance, anonymized_data)
|
|
1066
|
+
|
|
1067
|
+
sensitive_meta.save()
|