endoreg-db 0.8.6.1__py3-none-any.whl → 0.8.8.9__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 +2 -11
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +3 -3
- endoreg_db/data/event_classification/data.yaml +4 -0
- endoreg_db/data/event_classification_choice/data.yaml +9 -0
- endoreg_db/data/examination/examinations/data.yaml +114 -14
- endoreg_db/data/examination/time-type/data.yaml +0 -3
- endoreg_db/data/examination_indication/endoscopy.yaml +108 -173
- endoreg_db/data/examination_indication_classification/endoscopy.yaml +0 -70
- endoreg_db/data/examination_indication_classification_choice/endoscopy.yaml +33 -37
- endoreg_db/data/finding/00_generic.yaml +35 -0
- endoreg_db/data/finding/00_generic_complication.yaml +9 -0
- endoreg_db/data/finding/01_gastroscopy_baseline.yaml +88 -0
- endoreg_db/data/finding/01_gastroscopy_observation.yaml +113 -0
- endoreg_db/data/finding/02_colonoscopy_baseline.yaml +53 -0
- endoreg_db/data/finding/02_colonoscopy_hidden.yaml +119 -0
- endoreg_db/data/finding/02_colonoscopy_observation.yaml +152 -0
- endoreg_db/data/finding_classification/00_generic.yaml +44 -0
- endoreg_db/data/finding_classification/00_generic_histology.yaml +28 -0
- endoreg_db/data/finding_classification/00_generic_lesion.yaml +52 -0
- endoreg_db/data/finding_classification/02_colonoscopy_baseline.yaml +83 -0
- endoreg_db/data/finding_classification/02_colonoscopy_histology.yaml +13 -0
- endoreg_db/data/finding_classification/02_colonoscopy_other.yaml +12 -0
- endoreg_db/data/finding_classification/02_colonoscopy_polyp.yaml +101 -0
- endoreg_db/data/finding_classification_choice/{yes_no_na.yaml → 00_generic.yaml} +5 -1
- endoreg_db/data/finding_classification_choice/{examination_setting_generic_types.yaml → 00_generic_baseline.yaml} +10 -2
- endoreg_db/data/finding_classification_choice/{complication_generic_types.yaml → 00_generic_complication.yaml} +1 -1
- endoreg_db/data/finding_classification_choice/{histology.yaml → 00_generic_histology.yaml} +1 -4
- endoreg_db/data/finding_classification_choice/00_generic_lesion.yaml +158 -0
- endoreg_db/data/finding_classification_choice/{bowel_preparation.yaml → 02_colonoscopy_bowel_preparation.yaml} +1 -30
- endoreg_db/data/finding_classification_choice/{colonoscopy_not_complete_reason.yaml → 02_colonoscopy_generic.yaml} +1 -1
- endoreg_db/data/finding_classification_choice/{histology_polyp.yaml → 02_colonoscopy_histology.yaml} +1 -1
- endoreg_db/data/finding_classification_choice/{colonoscopy_location.yaml → 02_colonoscopy_location.yaml} +23 -4
- endoreg_db/data/finding_classification_choice/02_colonoscopy_other.yaml +34 -0
- endoreg_db/data/finding_classification_choice/02_colonoscopy_polyp_advanced_imaging.yaml +76 -0
- endoreg_db/data/finding_classification_choice/{colon_lesion_paris.yaml → 02_colonoscopy_polyp_morphology.yaml} +26 -8
- endoreg_db/data/finding_classification_choice/02_colonoscopy_size.yaml +27 -0
- endoreg_db/data/finding_classification_type/{colonoscopy_basic.yaml → 00_generic.yaml} +18 -13
- endoreg_db/data/finding_classification_type/02_colonoscopy.yaml +9 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy.yaml +59 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_ablation.yaml +44 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_bleeding.yaml +55 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_resection.yaml +85 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_stenosis.yaml +17 -0
- endoreg_db/data/finding_intervention/00_generic_endoscopy_stent.yaml +9 -0
- endoreg_db/data/finding_intervention/01_gastroscopy.yaml +19 -0
- endoreg_db/data/finding_intervention/04_eus.yaml +39 -0
- endoreg_db/data/finding_intervention/05_ercp.yaml +3 -0
- endoreg_db/data/finding_type/data.yaml +8 -12
- endoreg_db/data/requirement/01_patient_data.yaml +93 -0
- endoreg_db/data/requirement/old/colon_polyp_intervention.yaml +49 -0
- endoreg_db/data/requirement/old/coloreg_colon_polyp.yaml +49 -0
- endoreg_db/data/requirement_operator/new_operators.yaml +36 -0
- endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +29 -12
- endoreg_db/data/requirement_set/01_laboratory.yaml +13 -0
- endoreg_db/data/requirement_set/{endoscopy_bleeding_risk.yaml → 02_endoscopy_bleeding_risk.yaml} +0 -6
- endoreg_db/data/requirement_set/90_coloreg.yaml +190 -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 +4 -2
- endoreg_db/forms/examination_form.py +1 -1
- endoreg_db/helpers/data_loader.py +125 -53
- endoreg_db/helpers/default_objects.py +116 -81
- endoreg_db/import_files/__init__.py +27 -0
- endoreg_db/import_files/context/__init__.py +7 -0
- endoreg_db/import_files/context/default_sensitive_meta.py +81 -0
- endoreg_db/import_files/context/ensure_center.py +17 -0
- endoreg_db/import_files/context/file_lock.py +66 -0
- endoreg_db/import_files/context/import_context.py +43 -0
- endoreg_db/import_files/context/validate_directories.py +56 -0
- endoreg_db/import_files/file_storage/__init__.py +15 -0
- endoreg_db/import_files/file_storage/create_report_file.py +76 -0
- endoreg_db/import_files/file_storage/create_video_file.py +75 -0
- endoreg_db/import_files/file_storage/sensitive_meta_storage.py +39 -0
- endoreg_db/import_files/file_storage/state_management.py +400 -0
- endoreg_db/import_files/file_storage/storage.py +36 -0
- endoreg_db/import_files/import_service.md +26 -0
- endoreg_db/import_files/processing/__init__.py +11 -0
- endoreg_db/import_files/processing/report_processing/report_anonymization.py +94 -0
- endoreg_db/import_files/processing/sensitive_meta_adapter.py +51 -0
- endoreg_db/import_files/processing/video_processing/video_anonymization.py +107 -0
- endoreg_db/import_files/processing/video_processing/video_cleanup_on_error.py +119 -0
- endoreg_db/import_files/pseudonymization/fake.py +52 -0
- endoreg_db/import_files/pseudonymization/k_anonymity.py +182 -0
- endoreg_db/import_files/pseudonymization/k_pseudonymity.py +128 -0
- endoreg_db/import_files/report_import_service.py +141 -0
- endoreg_db/import_files/video_import_service.py +150 -0
- 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_report.py +130 -65
- endoreg_db/management/commands/import_video.py +9 -10
- endoreg_db/management/commands/import_video_with_classification.py +2 -2
- endoreg_db/management/commands/list_routes.py +18 -0
- endoreg_db/management/commands/load_ai_model_data.py +5 -5
- endoreg_db/management/commands/load_ai_model_label_data.py +9 -7
- endoreg_db/management/commands/load_base_db_data.py +5 -134
- endoreg_db/management/commands/load_center_data.py +12 -12
- endoreg_db/management/commands/load_contraindication_data.py +14 -16
- endoreg_db/management/commands/load_disease_classification_choices_data.py +15 -18
- endoreg_db/management/commands/load_disease_classification_data.py +15 -18
- endoreg_db/management/commands/load_disease_data.py +25 -28
- endoreg_db/management/commands/load_endoscope_data.py +20 -27
- endoreg_db/management/commands/load_event_data.py +14 -16
- endoreg_db/management/commands/load_examination_data.py +31 -44
- endoreg_db/management/commands/load_examination_indication_data.py +20 -21
- endoreg_db/management/commands/load_finding_data.py +52 -80
- endoreg_db/management/commands/load_information_source.py +21 -23
- endoreg_db/management/commands/load_lab_value_data.py +17 -26
- endoreg_db/management/commands/load_medication_data.py +13 -12
- endoreg_db/management/commands/load_organ_data.py +15 -19
- endoreg_db/management/commands/load_pdf_type_data.py +19 -18
- endoreg_db/management/commands/load_profession_data.py +14 -17
- endoreg_db/management/commands/load_qualification_data.py +20 -23
- endoreg_db/management/commands/load_report_reader_flag_data.py +17 -19
- endoreg_db/management/commands/load_requirement_data.py +62 -39
- endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
- endoreg_db/management/commands/load_risk_data.py +7 -6
- endoreg_db/management/commands/load_shift_data.py +20 -23
- endoreg_db/management/commands/load_tag_data.py +8 -11
- endoreg_db/management/commands/load_unit_data.py +17 -19
- endoreg_db/management/commands/setup_endoreg_db.py +3 -3
- endoreg_db/management/commands/start_filewatcher.py +46 -37
- endoreg_db/management/commands/storage_management.py +271 -203
- endoreg_db/management/commands/validate_video_files.py +1 -5
- endoreg_db/migrations/0001_initial.py +297 -250
- 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 +129 -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 +8 -9
- endoreg_db/models/label/annotation/image_classification.py +10 -9
- endoreg_db/models/label/annotation/video_segmentation_annotation.py +23 -28
- 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 +98 -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 +194 -194
- endoreg_db/models/media/pdf/report_file.py +25 -29
- endoreg_db/models/media/pdf/report_reader/report_reader_config.py +55 -47
- endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
- endoreg_db/models/media/processing_history/__init__.py +5 -0
- endoreg_db/models/media/processing_history/processing_history.py +96 -0
- endoreg_db/models/media/video/__init__.py +1 -0
- endoreg_db/models/media/video/create_from_file.py +139 -77
- endoreg_db/models/media/video/pipe_2.py +8 -9
- endoreg_db/models/media/video/video_file.py +174 -112
- 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 +113 -61
- 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/README.md +1 -0
- 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 +39 -20
- endoreg_db/models/medical/examination/examination_indication.py +30 -95
- endoreg_db/models/medical/examination/examination_time.py +23 -8
- 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 +32 -40
- endoreg_db/models/medical/finding/finding_classification.py +42 -72
- endoreg_db/models/medical/finding/finding_intervention.py +25 -22
- endoreg_db/models/medical/finding/finding_type.py +13 -12
- endoreg_db/models/medical/hardware/endoscope.py +26 -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 +6 -6
- 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 +31 -24
- endoreg_db/models/metadata/sensitive_meta.py +105 -85
- endoreg_db/models/metadata/sensitive_meta_logic.py +198 -103
- 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 +50 -29
- 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/report/report.py +6 -0
- endoreg_db/models/requirement/requirement.py +329 -361
- 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 +103 -112
- 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 +49 -51
- endoreg_db/models/state/label_video_segment.py +9 -0
- endoreg_db/models/state/raw_pdf.py +101 -68
- endoreg_db/models/state/sensitive_meta.py +6 -2
- endoreg_db/models/state/video.py +110 -90
- endoreg_db/models/upload_job.py +35 -34
- endoreg_db/models/utils.py +28 -25
- endoreg_db/queries/__init__.py +3 -1
- endoreg_db/root_urls.py +21 -2
- endoreg_db/schemas/examination_evaluation.py +1 -1
- endoreg_db/serializers/__init__.py +2 -10
- endoreg_db/serializers/anonymization.py +18 -10
- endoreg_db/serializers/label_video_segment/label_video_segment.py +2 -29
- endoreg_db/serializers/meta/__init__.py +1 -6
- endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
- endoreg_db/serializers/misc/file_overview.py +11 -99
- endoreg_db/serializers/misc/sensitive_patient_data.py +50 -26
- endoreg_db/serializers/patient_examination/patient_examination.py +3 -3
- endoreg_db/serializers/pdf/anony_text_validation.py +39 -23
- endoreg_db/serializers/requirements/requirement_sets.py +92 -22
- endoreg_db/serializers/video/segmentation.py +2 -1
- endoreg_db/serializers/video/video_file_list.py +65 -34
- endoreg_db/serializers/video/video_processing_history.py +20 -5
- endoreg_db/services/__old/pdf_import.py +1487 -0
- endoreg_db/services/__old/video_import.py +1306 -0
- endoreg_db/services/anonymization.py +128 -89
- endoreg_db/services/lookup_service.py +65 -52
- endoreg_db/services/lookup_store.py +2 -2
- endoreg_db/services/pdf_import.py +0 -1382
- endoreg_db/services/report_import.py +10 -0
- endoreg_db/services/video_import.py +6 -1255
- endoreg_db/tasks/upload_tasks.py +79 -70
- endoreg_db/tasks/video_ingest.py +8 -4
- endoreg_db/urls/__init__.py +5 -32
- endoreg_db/urls/ai.py +32 -0
- endoreg_db/urls/media.py +121 -83
- 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 +142 -40
- endoreg_db/utils/defaults/set_default_center.py +32 -0
- endoreg_db/utils/names.py +22 -16
- endoreg_db/utils/paths.py +110 -46
- endoreg_db/utils/permissions.py +2 -1
- endoreg_db/utils/pipelines/Readme.md +1 -1
- endoreg_db/utils/pipelines/process_video_dir.py +1 -1
- endoreg_db/utils/requirement_operator_logic/_old/model_evaluators.py +655 -0
- endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +97 -0
- 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 +85 -183
- endoreg_db/views/ai/__init__.py +8 -0
- endoreg_db/views/ai/label.py +155 -0
- endoreg_db/views/anonymization/media_management.py +202 -166
- endoreg_db/views/anonymization/overview.py +99 -67
- endoreg_db/views/anonymization/validate.py +182 -44
- endoreg_db/views/media/__init__.py +7 -20
- endoreg_db/views/media/pdf_media.py +197 -174
- endoreg_db/views/media/sensitive_metadata.py +193 -138
- endoreg_db/views/media/video_media.py +89 -82
- endoreg_db/views/meta/__init__.py +0 -8
- endoreg_db/views/misc/__init__.py +1 -7
- endoreg_db/views/misc/upload_views.py +94 -93
- endoreg_db/views/patient/patient.py +5 -4
- endoreg_db/views/report/__init__.py +5 -7
- endoreg_db/views/{pdf → report}/reimport.py +22 -22
- endoreg_db/views/{pdf/pdf_stream.py → report/report_stream.py} +46 -39
- endoreg_db/views/requirement/evaluate.py +188 -187
- endoreg_db/views/requirement/lookup.py +17 -3
- endoreg_db/views/requirement/lookup_store.py +22 -90
- endoreg_db/views/requirement/requirement_utils.py +89 -0
- endoreg_db/views/video/__init__.py +23 -24
- endoreg_db/views/video/correction.py +201 -172
- endoreg_db/views/video/reimport.py +1 -1
- endoreg_db/views/{media/video_segments.py → video/segments_crud.py} +77 -40
- endoreg_db/views/video/{video_meta.py → video_meta_stats.py} +2 -2
- endoreg_db/views/video/video_stream.py +7 -8
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.9.dist-info}/METADATA +7 -3
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.9.dist-info}/RECORD +391 -413
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.9.dist-info}/WHEEL +1 -1
- endoreg_db/data/finding/anatomy_colon.yaml +0 -128
- endoreg_db/data/finding/colonoscopy.yaml +0 -40
- endoreg_db/data/finding/colonoscopy_bowel_prep.yaml +0 -56
- endoreg_db/data/finding/complication.yaml +0 -16
- endoreg_db/data/finding/data.yaml +0 -105
- endoreg_db/data/finding/examination_setting.yaml +0 -16
- endoreg_db/data/finding/medication_related.yaml +0 -18
- endoreg_db/data/finding/outcome.yaml +0 -12
- endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +0 -95
- endoreg_db/data/finding_classification/colonoscopy_jnet.yaml +0 -22
- endoreg_db/data/finding_classification/colonoscopy_kudo.yaml +0 -25
- endoreg_db/data/finding_classification/colonoscopy_lesion_circularity.yaml +0 -20
- endoreg_db/data/finding_classification/colonoscopy_lesion_planarity.yaml +0 -24
- endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +0 -68
- endoreg_db/data/finding_classification/colonoscopy_lesion_surface.yaml +0 -20
- endoreg_db/data/finding_classification/colonoscopy_location.yaml +0 -80
- endoreg_db/data/finding_classification/colonoscopy_lst.yaml +0 -21
- endoreg_db/data/finding_classification/colonoscopy_nice.yaml +0 -20
- endoreg_db/data/finding_classification/colonoscopy_paris.yaml +0 -26
- endoreg_db/data/finding_classification/colonoscopy_sano.yaml +0 -22
- endoreg_db/data/finding_classification/colonoscopy_summary.yaml +0 -53
- endoreg_db/data/finding_classification/complication_generic.yaml +0 -25
- endoreg_db/data/finding_classification/examination_setting_generic.yaml +0 -40
- endoreg_db/data/finding_classification/histology_colo.yaml +0 -51
- endoreg_db/data/finding_classification/intervention_required.yaml +0 -26
- endoreg_db/data/finding_classification/medication_related.yaml +0 -23
- endoreg_db/data/finding_classification/visualized.yaml +0 -33
- endoreg_db/data/finding_classification_choice/colon_lesion_circularity_default.yaml +0 -32
- endoreg_db/data/finding_classification_choice/colon_lesion_jnet.yaml +0 -15
- endoreg_db/data/finding_classification_choice/colon_lesion_kudo.yaml +0 -23
- endoreg_db/data/finding_classification_choice/colon_lesion_lst.yaml +0 -15
- endoreg_db/data/finding_classification_choice/colon_lesion_nice.yaml +0 -17
- endoreg_db/data/finding_classification_choice/colon_lesion_planarity_default.yaml +0 -49
- endoreg_db/data/finding_classification_choice/colon_lesion_sano.yaml +0 -14
- endoreg_db/data/finding_classification_choice/colon_lesion_surface_intact_default.yaml +0 -36
- endoreg_db/data/finding_classification_choice/colonoscopy_size.yaml +0 -82
- endoreg_db/data/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +0 -15
- endoreg_db/data/finding_classification_choice/outcome.yaml +0 -19
- endoreg_db/data/finding_intervention/endoscopy.yaml +0 -43
- endoreg_db/data/finding_intervention/endoscopy_colonoscopy.yaml +0 -168
- endoreg_db/data/finding_intervention/endoscopy_egd.yaml +0 -128
- endoreg_db/data/finding_intervention/endoscopy_ercp.yaml +0 -32
- endoreg_db/data/finding_intervention/endoscopy_eus_lower.yaml +0 -9
- endoreg_db/data/finding_intervention/endoscopy_eus_upper.yaml +0 -36
- endoreg_db/data/finding_morphology_classification_type/colonoscopy.yaml +0 -79
- endoreg_db/data/requirement/age.yaml +0 -26
- endoreg_db/data/requirement/gender.yaml +0 -25
- endoreg_db/management/commands/init_default_ai_model.py +0 -112
- endoreg_db/management/commands/reset_celery_schedule.py +0 -9
- endoreg_db/management/commands/validate_video.py +0 -204
- endoreg_db/migrations/0002_add_video_correction_models.py +0 -52
- endoreg_db/migrations/0003_add_center_display_name.py +0 -30
- endoreg_db/models/administration/permissions/__init__.py +0 -44
- 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/renames.yml +0 -8
- endoreg_db/serializers/_old/raw_pdf_meta_validation.py +0 -223
- endoreg_db/serializers/_old/raw_video_meta_validation.py +0 -179
- endoreg_db/serializers/_old/video.py +0 -71
- endoreg_db/serializers/meta/pdf_file_meta_extraction.py +0 -115
- endoreg_db/serializers/meta/report_meta.py +0 -53
- endoreg_db/serializers/report/__init__.py +0 -9
- endoreg_db/serializers/report/mixins.py +0 -45
- endoreg_db/serializers/report/report.py +0 -105
- endoreg_db/serializers/report/report_list.py +0 -22
- endoreg_db/serializers/report/secure_file_url.py +0 -26
- endoreg_db/serializers/video/video_metadata.py +0 -105
- endoreg_db/services/requirements_object.py +0 -147
- endoreg_db/services/storage_aware_video_processor.py +0 -344
- endoreg_db/urls/files.py +0 -6
- endoreg_db/urls/label_video_segment_validate.py +0 -33
- endoreg_db/urls/label_video_segments.py +0 -46
- 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/utils/requirement_operator_logic/model_evaluators.py +0 -368
- endoreg_db/views/label/__init__.py +0 -5
- endoreg_db/views/label/label.py +0 -15
- endoreg_db/views/label_video_segment/__init__.py +0 -16
- endoreg_db/views/label_video_segment/create_lvs_from_annotation.py +0 -44
- endoreg_db/views/label_video_segment/get_lvs_by_name_and_video.py +0 -50
- endoreg_db/views/label_video_segment/label_video_segment.py +0 -77
- endoreg_db/views/label_video_segment/label_video_segment_by_label.py +0 -174
- endoreg_db/views/label_video_segment/label_video_segment_detail.py +0 -73
- endoreg_db/views/label_video_segment/update_lvs_from_annotation.py +0 -46
- endoreg_db/views/label_video_segment/validate.py +0 -226
- endoreg_db/views/media/segments.py +0 -71
- endoreg_db/views/meta/available_files_list.py +0 -146
- endoreg_db/views/meta/report_meta.py +0 -53
- endoreg_db/views/meta/sensitive_meta_detail.py +0 -148
- endoreg_db/views/misc/secure_file_serving_view.py +0 -80
- endoreg_db/views/misc/secure_file_url_view.py +0 -84
- endoreg_db/views/misc/secure_url_validate.py +0 -79
- endoreg_db/views/patient_examination/DEPRECATED_video_backup.py +0 -164
- endoreg_db/views/patient_finding_location/__init__.py +0 -5
- endoreg_db/views/patient_finding_location/pfl_create.py +0 -70
- endoreg_db/views/patient_finding_morphology/__init__.py +0 -5
- endoreg_db/views/patient_finding_morphology/pfm_create.py +0 -70
- endoreg_db/views/pdf/__init__.py +0 -8
- 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/segmentation.py +0 -274
- endoreg_db/views/video/task_status.py +0 -49
- endoreg_db/views/video/timeline.py +0 -46
- endoreg_db/views/video/video_analyze.py +0 -52
- endoreg_db/views.py +0 -0
- /endoreg_db/data/requirement/{colonoscopy_baseline_austria.yaml → old/colonoscopy_baseline_austria.yaml} +0 -0
- /endoreg_db/data/requirement/{disease_cardiovascular.yaml → old/disease_cardiovascular.yaml} +0 -0
- /endoreg_db/data/requirement/{disease_classification_choice_cardiovascular.yaml → old/disease_classification_choice_cardiovascular.yaml} +0 -0
- /endoreg_db/data/requirement/{disease_hepatology.yaml → old/disease_hepatology.yaml} +0 -0
- /endoreg_db/data/requirement/{disease_misc.yaml → old/disease_misc.yaml} +0 -0
- /endoreg_db/data/requirement/{disease_renal.yaml → old/disease_renal.yaml} +0 -0
- /endoreg_db/data/requirement/{endoscopy_bleeding_risk.yaml → old/endoscopy_bleeding_risk.yaml} +0 -0
- /endoreg_db/data/requirement/{event_cardiology.yaml → old/event_cardiology.yaml} +0 -0
- /endoreg_db/data/requirement/{event_requirements.yaml → old/event_requirements.yaml} +0 -0
- /endoreg_db/data/requirement/{finding_colon_polyp.yaml → old/finding_colon_polyp.yaml} +0 -0
- /endoreg_db/{migrations/__init__.py → data/requirement/old/gender.yaml} +0 -0
- /endoreg_db/data/requirement/{lab_value.yaml → old/lab_value.yaml} +0 -0
- /endoreg_db/data/requirement/{medication.yaml → old/medication.yaml} +0 -0
- /endoreg_db/data/requirement_operator/{age.yaml → _old/age.yaml} +0 -0
- /endoreg_db/data/requirement_operator/{lab_operators.yaml → _old/lab_operators.yaml} +0 -0
- /endoreg_db/data/requirement_operator/{model_operators.yaml → _old/model_operators.yaml} +0 -0
- /endoreg_db/{models/media/video/refactor_plan.md → import_files/pseudonymization/__init__.py} +0 -0
- /endoreg_db/{models/media/video/video_file_frames.py → import_files/pseudonymization/pseudonymize.py} +0 -0
- /endoreg_db/models/{metadata/frame_ocr_result.py → report/__init__.py} +0 -0
- /endoreg_db/{urls/sensitive_meta.py → models/report/images.py} +0 -0
- /endoreg_db/utils/requirement_operator_logic/{lab_value_operators.py → _old/lab_value_operators.py} +0 -0
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional, Any, Mapping, TYPE_CHECKING
|
|
3
|
+
if TYPE_CHECKING:
|
|
4
|
+
from endoreg_db.models.requirement.requirement import Requirement
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class RequirementEvaluationErrorContext:
|
|
9
|
+
"""
|
|
10
|
+
Extra context for evaluation errors.
|
|
11
|
+
|
|
12
|
+
- requirement: DB object (so we can use names, groups, translations, etc.)
|
|
13
|
+
- code: stable internal code, preferred over free-text matching
|
|
14
|
+
- user_message: optional pre-formatted text for UI
|
|
15
|
+
- meta: optional arbitrary payload for logging / debugging
|
|
16
|
+
"""
|
|
17
|
+
requirement: "Requirement"
|
|
18
|
+
code: str
|
|
19
|
+
technical_message: str
|
|
20
|
+
user_message: Optional[str] = None
|
|
21
|
+
description: Optional[str] = None
|
|
22
|
+
meta: Optional[Mapping[str, Any]] = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RequirementEvaluationError(Exception):
|
|
27
|
+
"""
|
|
28
|
+
Domain-level error that signals: this requirement could be evaluated.
|
|
29
|
+
|
|
30
|
+
It carries:
|
|
31
|
+
- a Requirement instance (from DB)
|
|
32
|
+
- a stable error code
|
|
33
|
+
- a technical message for logs
|
|
34
|
+
- an optional user-ready message
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
requirement: "Requirement",
|
|
40
|
+
code: str,
|
|
41
|
+
technical_message: str,
|
|
42
|
+
user_message: Optional[str] = None,
|
|
43
|
+
meta: Optional[Mapping[str, Any]] = None,
|
|
44
|
+
*args,
|
|
45
|
+
) -> None:
|
|
46
|
+
self.context = RequirementEvaluationErrorContext(
|
|
47
|
+
requirement=requirement,
|
|
48
|
+
code=code,
|
|
49
|
+
technical_message=technical_message,
|
|
50
|
+
user_message=user_message,
|
|
51
|
+
meta=meta,
|
|
52
|
+
)
|
|
53
|
+
# Base Exception message = technical message
|
|
54
|
+
super().__init__(technical_message, *args)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def requirement(self) -> "Requirement":
|
|
60
|
+
return self.context.requirement
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def requirement_name(self) -> str:
|
|
64
|
+
# This is the DB name, *not* the internal Python repr
|
|
65
|
+
return getattr(self.context.requirement, "name", "unknown")
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def requirement_description(self) -> str:
|
|
69
|
+
return getattr(self.context.requirement, "description")
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def code(self) -> str:
|
|
73
|
+
return self.context.code
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def technical_message(self) -> str:
|
|
77
|
+
return self.context.technical_message
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def user_message(self) -> Optional[str]:
|
|
81
|
+
return self.context.user_message
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def meta(self) -> Optional[Mapping[str, Any]]:
|
|
85
|
+
return self.context.meta
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
from collections import defaultdict, deque
|
|
2
|
+
from typing import Iterable, Dict, List, Literal, Tuple
|
|
3
|
+
from endoreg_db.models.requirement import Requirement, RequirementSet
|
|
4
|
+
from endoreg_db.models.requirement.requirement_error import RequirementEvaluationError
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
RequirementStatus = Literal["PASSED", "FAILED", "BLOCKED", "ERROR"]
|
|
10
|
+
|
|
11
|
+
def topologically_sort_requirement_sets(
|
|
12
|
+
sets: Iterable[RequirementSet],
|
|
13
|
+
) -> List[RequirementSet]:
|
|
14
|
+
"""
|
|
15
|
+
Topologically sort requirement sets by `depends_on`.
|
|
16
|
+
|
|
17
|
+
Raises ValueError if a cycle is detected.
|
|
18
|
+
"""
|
|
19
|
+
sets = list(sets)
|
|
20
|
+
id_map = {s.id: s for s in sets}
|
|
21
|
+
|
|
22
|
+
depends = {s.id: {d.id for d in s.depends_on.all()} for s in sets}
|
|
23
|
+
indegree = defaultdict(int)
|
|
24
|
+
for set_id, deps in depends.items():
|
|
25
|
+
for d_id in deps:
|
|
26
|
+
indegree[set_id] += 1
|
|
27
|
+
|
|
28
|
+
queue = deque([sid for sid in id_map if indegree[sid] == 0])
|
|
29
|
+
ordered_ids: List[int] = []
|
|
30
|
+
|
|
31
|
+
while queue:
|
|
32
|
+
sid = queue.popleft()
|
|
33
|
+
ordered_ids.append(sid)
|
|
34
|
+
for other_id, deps in depends.items():
|
|
35
|
+
if sid in deps:
|
|
36
|
+
deps.remove(sid)
|
|
37
|
+
indegree[other_id] -= 1
|
|
38
|
+
if indegree[other_id] == 0:
|
|
39
|
+
queue.append(other_id)
|
|
40
|
+
|
|
41
|
+
if len(ordered_ids) != len(sets):
|
|
42
|
+
raise ValueError("RequirementSet dependency cycle detected.")
|
|
43
|
+
|
|
44
|
+
return [id_map[sid] for sid in ordered_ids]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def evaluate_requirement_sets_with_dependencies(
|
|
50
|
+
sets: Iterable[RequirementSet],
|
|
51
|
+
*args,
|
|
52
|
+
mode: str = "strict",
|
|
53
|
+
**kwargs,
|
|
54
|
+
) -> Dict[int, Dict[int, Tuple[RequirementStatus, str]]]:
|
|
55
|
+
"""
|
|
56
|
+
Evaluates RequirementSets with set-level `depends_on`.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
{
|
|
60
|
+
set_id: {
|
|
61
|
+
requirement_id: (status, details)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
"""
|
|
65
|
+
ordered_sets = topologically_sort_requirement_sets(sets)
|
|
66
|
+
set_results: Dict[int, Dict[int, Tuple[RequirementStatus, str]]] = {}
|
|
67
|
+
set_statuses: Dict[int, RequirementStatus] = {}
|
|
68
|
+
|
|
69
|
+
for req_set in ordered_sets:
|
|
70
|
+
set_id = req_set.id
|
|
71
|
+
dep_set_ids = [s.id for s in req_set.depends_on.all()]
|
|
72
|
+
|
|
73
|
+
# Any dependency set FAILED or ERROR → this set is BLOCKED
|
|
74
|
+
blocking_deps = [
|
|
75
|
+
(dep_id, set_statuses[dep_id])
|
|
76
|
+
for dep_id in dep_set_ids
|
|
77
|
+
if dep_id in set_statuses and set_statuses[dep_id] in ("FAILED", "ERROR")
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
reqs = list(req_set.requirements.all())
|
|
81
|
+
set_results[set_id] = {}
|
|
82
|
+
|
|
83
|
+
if blocking_deps:
|
|
84
|
+
failed_names = ", ".join(
|
|
85
|
+
str(RequirementSet.objects.get(pk=dep_id).name)
|
|
86
|
+
for dep_id, _ in blocking_deps
|
|
87
|
+
)
|
|
88
|
+
msg = (
|
|
89
|
+
f"Die Voraussetzungengruppe '{req_set.name}' wurde nicht geprüft, "
|
|
90
|
+
f"weil die abhängige(n) Gruppe(n) {failed_names} "
|
|
91
|
+
"noch nicht erfüllt sind."
|
|
92
|
+
)
|
|
93
|
+
for req in reqs:
|
|
94
|
+
set_results[set_id][req.id] = ("BLOCKED", msg)
|
|
95
|
+
set_statuses[set_id] = "BLOCKED"
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
# Dependencies only BLOCKED → also BLOCKED (design choice)
|
|
99
|
+
blocked_only = [
|
|
100
|
+
(dep_id, set_statuses[dep_id])
|
|
101
|
+
for dep_id in dep_set_ids
|
|
102
|
+
if dep_id in set_statuses and set_statuses[dep_id] == "BLOCKED"
|
|
103
|
+
]
|
|
104
|
+
if blocked_only and not blocking_deps:
|
|
105
|
+
msg = (
|
|
106
|
+
f"Die Voraussetzungengruppe '{req_set.name}' wurde nicht geprüft, "
|
|
107
|
+
"weil abhängige Gruppen noch ausstehend sind."
|
|
108
|
+
)
|
|
109
|
+
for req in reqs:
|
|
110
|
+
set_results[set_id][req.id] = ("BLOCKED", msg)
|
|
111
|
+
set_statuses[set_id] = "BLOCKED"
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
# All deps PASSED or no deps → evaluate each requirement
|
|
115
|
+
had_failed = False
|
|
116
|
+
had_error = False
|
|
117
|
+
|
|
118
|
+
for req in reqs:
|
|
119
|
+
try:
|
|
120
|
+
met, details = req.evaluate_with_details(*args, mode=mode, **kwargs)
|
|
121
|
+
status: RequirementStatus = "PASSED" if met else "FAILED"
|
|
122
|
+
if status == "FAILED":
|
|
123
|
+
had_failed = True
|
|
124
|
+
set_results[set_id][req.id] = (status, details)
|
|
125
|
+
|
|
126
|
+
except RequirementEvaluationError as exc:
|
|
127
|
+
# domain-level failure → treat as FAILED but with nice message
|
|
128
|
+
had_failed = True
|
|
129
|
+
msg = (
|
|
130
|
+
f"Fehler bei der Auswertung der Voraussetzung '{req.name}': "
|
|
131
|
+
f"{exc.code}: {exc.technical_message}"
|
|
132
|
+
)
|
|
133
|
+
logger.warning(
|
|
134
|
+
"RequirementSet %s / Requirement %s domain error: %s",
|
|
135
|
+
req_set.id,
|
|
136
|
+
req.id,
|
|
137
|
+
exc,
|
|
138
|
+
)
|
|
139
|
+
set_results[set_id][req.id] = ("FAILED", msg)
|
|
140
|
+
|
|
141
|
+
except Exception as exc:
|
|
142
|
+
had_error = True
|
|
143
|
+
logger.exception(
|
|
144
|
+
"Unexpected error while evaluating RequirementSet %s / Requirement %s",
|
|
145
|
+
req_set.id,
|
|
146
|
+
req.id,
|
|
147
|
+
)
|
|
148
|
+
msg = (
|
|
149
|
+
f"Technischer Fehler bei der Auswertung von '{req.name}': {exc}"
|
|
150
|
+
)
|
|
151
|
+
set_results[set_id][req.id] = ("ERROR", msg)
|
|
152
|
+
|
|
153
|
+
# derive set status
|
|
154
|
+
if had_failed:
|
|
155
|
+
set_statuses[set_id] = "FAILED"
|
|
156
|
+
elif had_error:
|
|
157
|
+
set_statuses[set_id] = "ERROR"
|
|
158
|
+
else:
|
|
159
|
+
# if no requirements → call it PASSED (vacuous truth), or adjust as you like
|
|
160
|
+
set_statuses[set_id] = "PASSED"
|
|
161
|
+
|
|
162
|
+
return set_results
|
|
163
|
+
|
|
164
|
+
#TODO Remove when sure that no per requirement evaluation will happen. Needs the depends on attribute for the reatuirements.
|
|
165
|
+
|
|
166
|
+
def topologically_sort_requirements(
|
|
167
|
+
requirements: Iterable[Requirement],
|
|
168
|
+
) -> List[Requirement]:
|
|
169
|
+
"""
|
|
170
|
+
Topologically sort requirements by `depends_on`.
|
|
171
|
+
|
|
172
|
+
Raises ValueError if a cycle is detected.
|
|
173
|
+
"""
|
|
174
|
+
reqs = list(requirements)
|
|
175
|
+
id_map = {r.id: r for r in reqs}
|
|
176
|
+
|
|
177
|
+
# Build adjacency + indegree for Kahn's algorithm
|
|
178
|
+
depends = {r.id: {d.id for d in r.depends_on.all()} for r in reqs}
|
|
179
|
+
indegree = defaultdict(int)
|
|
180
|
+
for r_id, deps in depends.items():
|
|
181
|
+
for d_id in deps:
|
|
182
|
+
indegree[r_id] += 1
|
|
183
|
+
|
|
184
|
+
queue = deque([r_id for r_id in id_map if indegree[r_id] == 0])
|
|
185
|
+
ordered_ids: List[int] = []
|
|
186
|
+
|
|
187
|
+
while queue:
|
|
188
|
+
r_id = queue.popleft()
|
|
189
|
+
ordered_ids.append(r_id)
|
|
190
|
+
# remove this node as dep from others
|
|
191
|
+
for other_id, deps in depends.items():
|
|
192
|
+
if r_id in deps:
|
|
193
|
+
deps.remove(r_id)
|
|
194
|
+
indegree[other_id] -= 1
|
|
195
|
+
if indegree[other_id] == 0:
|
|
196
|
+
queue.append(other_id)
|
|
197
|
+
|
|
198
|
+
if len(ordered_ids) != len(reqs):
|
|
199
|
+
raise ValueError("Requirement dependency cycle detected.")
|
|
200
|
+
|
|
201
|
+
return [id_map[rid] for rid in ordered_ids]
|
|
202
|
+
|
|
203
|
+
def evaluate_requirements_with_dependencies(
|
|
204
|
+
requirements: Iterable[Requirement],
|
|
205
|
+
*args,
|
|
206
|
+
mode: str = "strict",
|
|
207
|
+
**kwargs,
|
|
208
|
+
) -> Dict[int, Tuple[RequirementStatus, str]]:
|
|
209
|
+
ordered = topologically_sort_requirements(requirements)
|
|
210
|
+
results: Dict[int, Tuple[RequirementStatus, str]] = {}
|
|
211
|
+
|
|
212
|
+
for req in ordered:
|
|
213
|
+
dep_ids = [d.id for d in req.depends_on.all()]
|
|
214
|
+
|
|
215
|
+
# If any dependency FAILED or ERROR → BLOCKED
|
|
216
|
+
blocking = [
|
|
217
|
+
(dep_id, results[dep_id])
|
|
218
|
+
for dep_id in dep_ids
|
|
219
|
+
if dep_id in results and results[dep_id][0] in ("FAILED", "ERROR")
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
if blocking:
|
|
223
|
+
failed_names = ", ".join(
|
|
224
|
+
str(Requirement.objects.get(pk=dep_id).name) for dep_id, _ in blocking
|
|
225
|
+
)
|
|
226
|
+
results[req.id] = (
|
|
227
|
+
"BLOCKED",
|
|
228
|
+
f"Die Voraussetzung '{req.name}' wurde nicht geprüft, "
|
|
229
|
+
f"weil die abhängige(n) Voraussetzung(en) {failed_names} "
|
|
230
|
+
"noch nicht erfüllt sind.",
|
|
231
|
+
)
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
# If dependencies exist but are only BLOCKED → still BLOCKED (design choice)
|
|
235
|
+
blocked_only = [
|
|
236
|
+
(dep_id, results[dep_id])
|
|
237
|
+
for dep_id in dep_ids
|
|
238
|
+
if dep_id in results and results[dep_id][0] == "BLOCKED"
|
|
239
|
+
]
|
|
240
|
+
if blocked_only and not blocking:
|
|
241
|
+
results[req.id] = (
|
|
242
|
+
"BLOCKED",
|
|
243
|
+
f"Die Voraussetzung '{req.name}' wurde nicht geprüft, "
|
|
244
|
+
"weil abhängige Voraussetzungen noch ausstehend sind.",
|
|
245
|
+
)
|
|
246
|
+
continue
|
|
247
|
+
|
|
248
|
+
# All deps passed (or none) → evaluate requirement itself
|
|
249
|
+
try:
|
|
250
|
+
met, details = req.evaluate_with_details(*args, mode=mode, **kwargs)
|
|
251
|
+
except RequirementEvaluationError as exc:
|
|
252
|
+
met = False
|
|
253
|
+
results[req.id] = (
|
|
254
|
+
"FAILED",
|
|
255
|
+
f"Nachtragebedarf bei der Auswertung von '{req.name}': {exc}"
|
|
256
|
+
)
|
|
257
|
+
except Exception as exc:
|
|
258
|
+
met = False
|
|
259
|
+
logger.exception("Unexpected error while evaluating requirement %s", req)
|
|
260
|
+
results[req.id] = (
|
|
261
|
+
"ERROR",
|
|
262
|
+
f"Technischer Fehler bei der Auswertung von '{req.name}': {exc}",
|
|
263
|
+
)
|
|
264
|
+
else:
|
|
265
|
+
status: RequirementStatus = "PASSED" if met else "FAILED"
|
|
266
|
+
results[req.id] = (status, details)
|
|
267
|
+
|
|
268
|
+
return results
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
from endoreg_db.models import
|
|
2
|
-
PatientExamination
|
|
3
|
-
)
|
|
1
|
+
from endoreg_db.models import PatientExamination
|
|
4
2
|
|
|
5
|
-
#TODO
|
|
6
|
-
#TODO All those models must have a "get_requirement_links" property
|
|
3
|
+
# TODO Still used anywhere?
|
|
7
4
|
operator_evaluation_models = [
|
|
8
5
|
PatientExamination,
|
|
9
|
-
]
|
|
6
|
+
]
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"""
|
|
2
|
+
Validated registry of requirement-type model bindings.
|
|
3
|
+
|
|
4
|
+
The registry ensures the string keys stay aligned with the Django models while
|
|
5
|
+
providing structured validation via Pydantic. Downstream code should consume
|
|
6
|
+
``data_model_dict``/``data_model_dict_reverse`` instead of re-declaring
|
|
7
|
+
unvalidated mappings.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Dict
|
|
11
|
+
|
|
12
|
+
from django.db import models
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
|
3
14
|
|
|
4
|
-
from typing import Dict, Union
|
|
5
15
|
from endoreg_db.models import (
|
|
6
16
|
Disease,
|
|
7
17
|
DiseaseClassification,
|
|
@@ -12,23 +22,24 @@ from endoreg_db.models import (
|
|
|
12
22
|
Examination,
|
|
13
23
|
ExaminationIndication,
|
|
14
24
|
Finding,
|
|
15
|
-
FindingIntervention,
|
|
16
25
|
FindingClassification,
|
|
17
26
|
FindingClassificationChoice,
|
|
27
|
+
FindingIntervention,
|
|
18
28
|
LabValue,
|
|
29
|
+
Patient,
|
|
19
30
|
PatientDisease,
|
|
20
31
|
PatientEvent,
|
|
21
32
|
PatientExamination,
|
|
22
33
|
PatientFinding,
|
|
23
34
|
PatientFindingClassification,
|
|
24
35
|
PatientFindingIntervention,
|
|
36
|
+
PatientLabSample,
|
|
25
37
|
PatientLabValue,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
PatientMedication, # Added PatientMedication
|
|
29
|
-
PatientMedicationSchedule, # Added PatientMedicationSchedule
|
|
38
|
+
PatientMedication,
|
|
39
|
+
PatientMedicationSchedule,
|
|
30
40
|
)
|
|
31
41
|
from endoreg_db.models.other.gender import Gender
|
|
42
|
+
|
|
32
43
|
# if TYPE_CHECKING:
|
|
33
44
|
# from endoreg_db.models import (
|
|
34
45
|
# RequirementOperator,
|
|
@@ -36,60 +47,75 @@ from endoreg_db.models.other.gender import Gender
|
|
|
36
47
|
# )
|
|
37
48
|
|
|
38
49
|
|
|
50
|
+
class DataModelEntry(BaseModel):
|
|
51
|
+
"""Validated binding between an identifier string and a Django model class."""
|
|
39
52
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
53
|
+
name: str = Field(min_length=1)
|
|
54
|
+
model: type[models.Model]
|
|
55
|
+
|
|
56
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
57
|
+
|
|
58
|
+
@field_validator("model")
|
|
59
|
+
@classmethod
|
|
60
|
+
def ensure_model_subclass(cls, value: type[models.Model]) -> type[models.Model]:
|
|
61
|
+
if not issubclass(value, models.Model): # Defensive: ensure provided class is a Django model
|
|
62
|
+
raise TypeError(f"{value!r} is not a Django model class")
|
|
63
|
+
return value
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class DataModelRegistry(BaseModel):
|
|
67
|
+
"""Collection of ``DataModelEntry`` items with duplicate safeguards."""
|
|
68
|
+
|
|
69
|
+
entries: tuple[DataModelEntry, ...]
|
|
70
|
+
|
|
71
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
72
|
+
|
|
73
|
+
@model_validator(mode="after")
|
|
74
|
+
def ensure_unique(self) -> "DataModelRegistry":
|
|
75
|
+
names = [entry.name for entry in self.entries]
|
|
76
|
+
models_set = [entry.model for entry in self.entries]
|
|
77
|
+
if len(names) != len(set(names)):
|
|
78
|
+
raise ValueError("Duplicate requirement type names detected")
|
|
79
|
+
if len(models_set) != len(set(models_set)):
|
|
80
|
+
raise ValueError("Duplicate Django model classes detected in registry")
|
|
81
|
+
return self
|
|
82
|
+
|
|
83
|
+
def as_mapping(self) -> Dict[str, type[models.Model]]:
|
|
84
|
+
return {entry.name: entry.model for entry in self.entries}
|
|
85
|
+
|
|
86
|
+
def as_reverse_mapping(self) -> Dict[type[models.Model], str]:
|
|
87
|
+
return {entry.model: entry.name for entry in self.entries}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
DATA_MODEL_REGISTRY = DataModelRegistry(
|
|
91
|
+
entries=(
|
|
92
|
+
DataModelEntry(name="disease", model=Disease),
|
|
93
|
+
DataModelEntry(name="disease_classification_choice", model=DiseaseClassificationChoice),
|
|
94
|
+
DataModelEntry(name="disease_classification", model=DiseaseClassification),
|
|
95
|
+
DataModelEntry(name="event", model=Event),
|
|
96
|
+
DataModelEntry(name="event_classification", model=EventClassification),
|
|
97
|
+
DataModelEntry(name="event_classification_choice", model=EventClassificationChoice),
|
|
98
|
+
DataModelEntry(name="examination", model=Examination),
|
|
99
|
+
DataModelEntry(name="examination_indication", model=ExaminationIndication),
|
|
100
|
+
DataModelEntry(name="finding", model=Finding),
|
|
101
|
+
DataModelEntry(name="finding_intervention", model=FindingIntervention),
|
|
102
|
+
DataModelEntry(name="finding_classification", model=FindingClassification),
|
|
103
|
+
DataModelEntry(name="finding_classification_choice", model=FindingClassificationChoice),
|
|
104
|
+
DataModelEntry(name="lab_value", model=LabValue),
|
|
105
|
+
DataModelEntry(name="patient_disease", model=PatientDisease),
|
|
106
|
+
DataModelEntry(name="patient_event", model=PatientEvent),
|
|
107
|
+
DataModelEntry(name="patient_examination", model=PatientExamination),
|
|
108
|
+
DataModelEntry(name="patient_finding", model=PatientFinding),
|
|
109
|
+
DataModelEntry(name="patient_finding_intervention", model=PatientFindingIntervention),
|
|
110
|
+
DataModelEntry(name="patient_finding_classification", model=PatientFindingClassification),
|
|
111
|
+
DataModelEntry(name="patient_lab_value", model=PatientLabValue),
|
|
112
|
+
DataModelEntry(name="patient_lab_sample", model=PatientLabSample),
|
|
113
|
+
DataModelEntry(name="patient", model=Patient),
|
|
114
|
+
DataModelEntry(name="patient_medication", model=PatientMedication),
|
|
115
|
+
DataModelEntry(name="patient_medication_schedule", model=PatientMedicationSchedule),
|
|
116
|
+
DataModelEntry(name="gender", model=Gender),
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
data_model_dict: Dict[str, type[models.Model]] = DATA_MODEL_REGISTRY.as_mapping()
|
|
121
|
+
data_model_dict_reverse: Dict[type[models.Model], str] = DATA_MODEL_REGISTRY.as_reverse_mapping()
|