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
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
from django.db import models
|
|
2
|
-
from typing import TYPE_CHECKING, Dict, List, Union
|
|
3
|
-
from endoreg_db.utils.links.requirement_link import RequirementLinks
|
|
4
1
|
import logging
|
|
5
2
|
from subprocess import run
|
|
3
|
+
from typing import TYPE_CHECKING, Dict, List, Tuple, Union, cast
|
|
4
|
+
|
|
5
|
+
from django.db import models
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from endoreg_db.utils.links.requirement_link import RequirementLinks
|
|
6
9
|
|
|
7
10
|
logger = logging.getLogger(__name__)
|
|
8
11
|
|
|
@@ -10,7 +13,7 @@ logger = logging.getLogger(__name__)
|
|
|
10
13
|
QuerySet = models.QuerySet
|
|
11
14
|
|
|
12
15
|
if TYPE_CHECKING:
|
|
13
|
-
from endoreg_db.models import (
|
|
16
|
+
from endoreg_db.models import ( # RequirementSet,
|
|
14
17
|
Disease,
|
|
15
18
|
DiseaseClassificationChoice,
|
|
16
19
|
Event,
|
|
@@ -19,27 +22,27 @@ if TYPE_CHECKING:
|
|
|
19
22
|
Examination,
|
|
20
23
|
ExaminationIndication,
|
|
21
24
|
Finding,
|
|
22
|
-
FindingIntervention,
|
|
23
25
|
FindingClassification,
|
|
24
26
|
FindingClassificationChoice,
|
|
25
27
|
FindingClassificationType,
|
|
28
|
+
FindingIntervention,
|
|
29
|
+
Gender,
|
|
26
30
|
LabValue,
|
|
27
31
|
Medication,
|
|
28
32
|
MedicationIndication,
|
|
29
|
-
MedicationIntakeTime,
|
|
33
|
+
MedicationIntakeTime, # Added MedicationIntakeTime
|
|
30
34
|
MedicationSchedule,
|
|
31
35
|
PatientDisease,
|
|
32
36
|
PatientEvent,
|
|
33
37
|
PatientExamination,
|
|
34
38
|
PatientFinding,
|
|
35
|
-
PatientFindingIntervention,
|
|
36
39
|
PatientFindingClassification,
|
|
40
|
+
PatientFindingIntervention,
|
|
37
41
|
PatientLabValue,
|
|
38
|
-
PatientMedicationSchedule,
|
|
42
|
+
PatientMedicationSchedule, # Added PatientMedicationSchedule
|
|
39
43
|
RequirementOperator,
|
|
40
|
-
RequirementSet,
|
|
41
|
-
Gender
|
|
42
44
|
)
|
|
45
|
+
|
|
43
46
|
# from endoreg_db.utils.links.requirement_link import RequirementLinks # Already imported above
|
|
44
47
|
|
|
45
48
|
|
|
@@ -114,48 +117,55 @@ class Requirement(models.Model):
|
|
|
114
117
|
|
|
115
118
|
name = models.CharField(max_length=100, unique=True)
|
|
116
119
|
description = models.TextField(blank=True, null=True)
|
|
117
|
-
|
|
120
|
+
|
|
121
|
+
operator_instructions = models.TextField(
|
|
122
|
+
help_text="semicolon-separated list of target attributes for the requirement",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def operator_instructions_parsed(self):
|
|
127
|
+
from endoreg_db.models.requirement.requirement_operator import RequirementOperator
|
|
128
|
+
|
|
129
|
+
instructions = RequirementOperator.parse_instructions(self.operator_instructions)
|
|
130
|
+
return instructions
|
|
131
|
+
|
|
118
132
|
numeric_value = models.FloatField(
|
|
119
133
|
blank=True,
|
|
120
134
|
null=True,
|
|
121
|
-
help_text="Numeric value for the requirement.
|
|
135
|
+
help_text="Numeric value for the requirement. ons.",
|
|
122
136
|
)
|
|
123
|
-
|
|
124
137
|
numeric_value_min = models.FloatField(
|
|
125
138
|
blank=True,
|
|
126
139
|
null=True,
|
|
127
|
-
help_text="Minimum numeric value for the requirement.
|
|
140
|
+
help_text="Minimum numeric value for the requirement. ons.",
|
|
128
141
|
)
|
|
129
142
|
numeric_value_max = models.FloatField(
|
|
130
143
|
blank=True,
|
|
131
144
|
null=True,
|
|
132
|
-
help_text="Maximum numeric value for the requirement.
|
|
145
|
+
help_text="Maximum numeric value for the requirement. ons.",
|
|
133
146
|
)
|
|
134
|
-
|
|
135
147
|
string_value = models.CharField(
|
|
136
148
|
max_length=100,
|
|
137
149
|
blank=True,
|
|
138
150
|
null=True,
|
|
139
|
-
help_text="String value for the requirement.
|
|
151
|
+
help_text="String value for the requirement. ons.",
|
|
140
152
|
)
|
|
141
|
-
|
|
142
153
|
string_values = models.TextField(
|
|
143
154
|
blank=True,
|
|
144
155
|
null=True,
|
|
145
|
-
help_text=" ','-separated list of string values for the requirement.
|
|
156
|
+
help_text=" ','-separated list of string values for the requirement.ons.",
|
|
146
157
|
)
|
|
147
|
-
|
|
148
158
|
objects = RequirementManager()
|
|
149
159
|
|
|
150
|
-
requirement_types = models.ManyToManyField(
|
|
160
|
+
requirement_types = models.ManyToManyField(
|
|
151
161
|
"RequirementType",
|
|
152
162
|
blank=True,
|
|
153
163
|
related_name="linked_requirements",
|
|
154
164
|
)
|
|
155
165
|
|
|
156
|
-
|
|
166
|
+
operator = models.ForeignKey(
|
|
157
167
|
"RequirementOperator",
|
|
158
|
-
|
|
168
|
+
on_delete=models.CASCADE,
|
|
159
169
|
related_name="required_in",
|
|
160
170
|
)
|
|
161
171
|
|
|
@@ -167,120 +177,138 @@ class Requirement(models.Model):
|
|
|
167
177
|
null=True,
|
|
168
178
|
)
|
|
169
179
|
|
|
170
|
-
examinations = models.ManyToManyField(
|
|
180
|
+
examinations = models.ManyToManyField(
|
|
171
181
|
"Examination",
|
|
172
182
|
blank=True,
|
|
173
183
|
related_name="required_in",
|
|
174
184
|
)
|
|
175
185
|
|
|
176
|
-
examination_indications = models.ManyToManyField(
|
|
186
|
+
examination_indications = models.ManyToManyField(
|
|
177
187
|
"ExaminationIndication",
|
|
178
188
|
blank=True,
|
|
179
189
|
related_name="required_in",
|
|
180
190
|
)
|
|
181
191
|
|
|
182
|
-
diseases = models.ManyToManyField(
|
|
192
|
+
diseases = models.ManyToManyField(
|
|
183
193
|
"Disease",
|
|
184
194
|
blank=True,
|
|
185
195
|
related_name="required_in",
|
|
186
196
|
)
|
|
187
197
|
|
|
188
|
-
disease_classification_choices = models.ManyToManyField(
|
|
198
|
+
disease_classification_choices = models.ManyToManyField(
|
|
189
199
|
"DiseaseClassificationChoice",
|
|
190
200
|
blank=True,
|
|
191
201
|
related_name="required_in",
|
|
192
202
|
)
|
|
193
203
|
|
|
194
|
-
events = models.ManyToManyField(
|
|
204
|
+
events = models.ManyToManyField(
|
|
195
205
|
"Event",
|
|
196
206
|
blank=True,
|
|
197
207
|
related_name="required_in",
|
|
198
208
|
)
|
|
199
209
|
|
|
200
|
-
lab_values = models.ManyToManyField(
|
|
210
|
+
lab_values = models.ManyToManyField(
|
|
201
211
|
"LabValue",
|
|
202
212
|
blank=True,
|
|
203
213
|
related_name="required_in",
|
|
204
214
|
)
|
|
205
215
|
|
|
206
|
-
findings = models.ManyToManyField(
|
|
216
|
+
findings = models.ManyToManyField(
|
|
207
217
|
"Finding",
|
|
208
218
|
blank=True,
|
|
209
219
|
related_name="required_in",
|
|
210
220
|
)
|
|
211
221
|
|
|
212
|
-
finding_classifications = models.ManyToManyField(
|
|
222
|
+
finding_classifications = models.ManyToManyField(
|
|
213
223
|
"FindingClassification",
|
|
214
224
|
blank=True,
|
|
215
225
|
related_name="required_in",
|
|
216
226
|
)
|
|
217
227
|
|
|
218
|
-
finding_classification_choices = models.ManyToManyField(
|
|
228
|
+
finding_classification_choices = models.ManyToManyField(
|
|
219
229
|
"FindingClassificationChoice",
|
|
220
230
|
blank=True,
|
|
221
231
|
related_name="required_in",
|
|
222
232
|
)
|
|
223
233
|
|
|
224
|
-
finding_interventions = models.ManyToManyField(
|
|
234
|
+
finding_interventions = models.ManyToManyField(
|
|
225
235
|
"FindingIntervention",
|
|
226
236
|
blank=True,
|
|
227
237
|
related_name="required_in",
|
|
228
238
|
)
|
|
229
239
|
|
|
230
|
-
medications = models.ManyToManyField(
|
|
240
|
+
medications = models.ManyToManyField(
|
|
231
241
|
"Medication",
|
|
232
242
|
blank=True,
|
|
233
243
|
related_name="required_in",
|
|
234
244
|
)
|
|
235
245
|
|
|
236
|
-
medication_indications = models.ManyToManyField(
|
|
246
|
+
medication_indications = models.ManyToManyField(
|
|
237
247
|
"MedicationIndication",
|
|
238
248
|
blank=True,
|
|
239
249
|
related_name="required_in",
|
|
240
250
|
)
|
|
241
251
|
|
|
242
|
-
medication_intake_times = models.ManyToManyField(
|
|
252
|
+
medication_intake_times = models.ManyToManyField(
|
|
243
253
|
"MedicationIntakeTime",
|
|
244
254
|
blank=True,
|
|
245
255
|
related_name="required_in",
|
|
246
256
|
)
|
|
247
257
|
|
|
248
|
-
medication_schedules = models.ManyToManyField(
|
|
258
|
+
medication_schedules = models.ManyToManyField(
|
|
249
259
|
"MedicationSchedule",
|
|
250
260
|
blank=True,
|
|
251
261
|
related_name="required_in",
|
|
252
262
|
)
|
|
253
263
|
|
|
254
|
-
genders = models.ManyToManyField(
|
|
264
|
+
genders = models.ManyToManyField(
|
|
255
265
|
"Gender",
|
|
256
266
|
blank=True,
|
|
257
267
|
related_name="required_in",
|
|
258
268
|
)
|
|
259
269
|
|
|
260
270
|
if TYPE_CHECKING:
|
|
261
|
-
requirement_types
|
|
262
|
-
|
|
263
|
-
requirement_sets
|
|
264
|
-
examinations
|
|
265
|
-
examination_indications
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
271
|
+
requirement_types = cast(models.manager.RelatedManager["RequirementType"], requirement_types)
|
|
272
|
+
operator = models.ForeignKey["RequirementOperator"]
|
|
273
|
+
# requirement_sets = cast(models.manager.RelatedManager["RequirementSet"], requirement_sets)
|
|
274
|
+
examinations = cast(models.manager.RelatedManager["Examination"], examinations)
|
|
275
|
+
examination_indications = cast(
|
|
276
|
+
models.manager.RelatedManager["ExaminationIndication"],
|
|
277
|
+
examination_indications,
|
|
278
|
+
)
|
|
279
|
+
lab_values = cast(models.manager.RelatedManager["LabValue"], lab_values)
|
|
280
|
+
diseases = cast(models.manager.RelatedManager["Disease"], diseases)
|
|
281
|
+
disease_classification_choices = cast(
|
|
282
|
+
models.manager.RelatedManager["DiseaseClassificationChoice"],
|
|
283
|
+
disease_classification_choices,
|
|
284
|
+
)
|
|
285
|
+
events = cast(models.manager.RelatedManager["Event"], events)
|
|
286
|
+
findings = cast(models.manager.RelatedManager["Finding"], findings)
|
|
287
|
+
finding_classifications = cast(
|
|
288
|
+
models.manager.RelatedManager["FindingClassification"],
|
|
289
|
+
finding_classifications,
|
|
290
|
+
)
|
|
291
|
+
finding_classification_choices = cast(
|
|
292
|
+
models.manager.RelatedManager["FindingClassificationChoice"],
|
|
293
|
+
finding_classification_choices,
|
|
294
|
+
)
|
|
295
|
+
finding_interventions = cast(models.manager.RelatedManager["FindingIntervention"], finding_interventions)
|
|
296
|
+
medications = cast(models.manager.RelatedManager["Medication"], medications)
|
|
297
|
+
medication_indications = cast(
|
|
298
|
+
models.manager.RelatedManager["MedicationIndication"],
|
|
299
|
+
medication_indications,
|
|
300
|
+
)
|
|
301
|
+
medication_intake_times = cast(
|
|
302
|
+
models.manager.RelatedManager["MedicationIntakeTime"],
|
|
303
|
+
medication_intake_times,
|
|
304
|
+
)
|
|
305
|
+
medication_schedules = cast(models.manager.RelatedManager["MedicationSchedule"], medication_schedules)
|
|
306
|
+
genders = cast(models.manager.RelatedManager["Gender"], genders)
|
|
279
307
|
|
|
280
308
|
def natural_key(self):
|
|
281
309
|
"""
|
|
282
310
|
Returns a tuple containing the instance's name as its natural key.
|
|
283
|
-
|
|
311
|
+
|
|
284
312
|
This tuple provides a unique identifier for serialization purposes.
|
|
285
313
|
"""
|
|
286
314
|
return (self.name,)
|
|
@@ -290,35 +318,39 @@ class Requirement(models.Model):
|
|
|
290
318
|
return str(self.name)
|
|
291
319
|
|
|
292
320
|
@property
|
|
293
|
-
def expected_models(
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
321
|
+
def expected_models(
|
|
322
|
+
self,
|
|
323
|
+
) -> List[
|
|
324
|
+
Union[
|
|
325
|
+
"Disease",
|
|
326
|
+
"DiseaseClassificationChoice",
|
|
327
|
+
"Event",
|
|
328
|
+
"EventClassification",
|
|
329
|
+
"EventClassificationChoice",
|
|
330
|
+
"Examination",
|
|
331
|
+
"ExaminationIndication",
|
|
332
|
+
"Finding",
|
|
333
|
+
"FindingIntervention",
|
|
334
|
+
"FindingClassification",
|
|
335
|
+
"FindingClassificationChoice",
|
|
336
|
+
"FindingClassificationType",
|
|
337
|
+
"LabValue",
|
|
338
|
+
"Medication",
|
|
339
|
+
"MedicationIndication",
|
|
340
|
+
"MedicationIntakeTime", # Added MedicationIntakeTime
|
|
341
|
+
"PatientDisease",
|
|
342
|
+
"PatientEvent",
|
|
343
|
+
"PatientExamination",
|
|
344
|
+
"PatientFinding",
|
|
345
|
+
"PatientFindingIntervention",
|
|
346
|
+
"PatientFindingClassification",
|
|
347
|
+
"PatientLabValue",
|
|
348
|
+
"PatientMedicationSchedule", # Added PatientMedicationSchedule
|
|
349
|
+
]
|
|
350
|
+
]:
|
|
319
351
|
"""
|
|
320
352
|
Return the list of model classes that are expected as input for evaluating this requirement.
|
|
321
|
-
|
|
353
|
+
|
|
322
354
|
The returned models correspond to the requirement types linked to this requirement, mapped via the internal data model dictionary.
|
|
323
355
|
"""
|
|
324
356
|
req_types = self.requirement_types.all()
|
|
@@ -332,7 +364,7 @@ class Requirement(models.Model):
|
|
|
332
364
|
def links(self) -> "RequirementLinks":
|
|
333
365
|
"""
|
|
334
366
|
Return a RequirementLinks object containing all non-null related model instances for this requirement.
|
|
335
|
-
|
|
367
|
+
|
|
336
368
|
The returned object provides structured access to all associated entities, such as examinations, diseases, findings, classifications, interventions, medications, and related choices, aggregated from the requirement's many-to-many fields.
|
|
337
369
|
"""
|
|
338
370
|
# requirement_sets is not part of RequirementLinks (avoids circular import); collect other related models
|
|
@@ -352,238 +384,103 @@ class Requirement(models.Model):
|
|
|
352
384
|
medication_intake_times=[_ for _ in self.medication_intake_times.all() if _ is not None],
|
|
353
385
|
)
|
|
354
386
|
return models_dict
|
|
355
|
-
|
|
387
|
+
|
|
356
388
|
@property
|
|
357
389
|
def data_model_dict(self) -> dict:
|
|
358
390
|
"""
|
|
359
391
|
Provides a mapping from requirement type names to their corresponding model classes.
|
|
360
|
-
|
|
392
|
+
|
|
361
393
|
Returns:
|
|
362
394
|
A dictionary where keys are requirement type names and values are model classes used for requirement evaluation.
|
|
363
395
|
"""
|
|
364
396
|
from .requirement_evaluation.requirement_type_parser import data_model_dict
|
|
397
|
+
|
|
365
398
|
return data_model_dict
|
|
366
|
-
|
|
399
|
+
|
|
367
400
|
@property
|
|
368
401
|
def active_links(self) -> Dict[str, List]:
|
|
369
402
|
"""Returns a dictionary of linked models containing only non-empty entries.
|
|
370
|
-
|
|
403
|
+
|
|
371
404
|
The returned dictionary includes only those related model lists that have at least one linked instance.
|
|
372
405
|
"""
|
|
373
406
|
return self.links.active()
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
def evaluate(self, *args, mode:str, **kwargs):
|
|
407
|
+
|
|
408
|
+
def evaluate(self, input_obj):
|
|
377
409
|
"""
|
|
378
410
|
Evaluates whether the requirement is satisfied for the given input models using linked operators and gender constraints.
|
|
379
|
-
|
|
411
|
+
|
|
380
412
|
Args:
|
|
381
413
|
*args: Instances or QuerySets of expected model classes to be evaluated. Each must have a `.links` property returning a `RequirementLinks` object.
|
|
382
414
|
mode: Evaluation mode; "strict" requires all operators to pass, "loose" requires any operator to pass.
|
|
383
415
|
**kwargs: Additional keyword arguments passed to operator evaluations.
|
|
384
|
-
|
|
416
|
+
|
|
385
417
|
Returns:
|
|
386
418
|
True if the requirement is satisfied according to the specified mode, linked operators, and gender restrictions; otherwise, False.
|
|
387
|
-
|
|
419
|
+
|
|
388
420
|
Raises:
|
|
389
421
|
ValueError: If an invalid mode is provided.
|
|
390
422
|
TypeError: If an input is not an instance or QuerySet of expected models, or lacks a valid `.links` attribute.
|
|
391
|
-
|
|
423
|
+
|
|
392
424
|
If the requirement specifies genders, only input containing a patient with a matching gender will be considered valid for evaluation.
|
|
393
425
|
"""
|
|
394
|
-
|
|
395
|
-
if mode not in ["strict", "loose"]:
|
|
396
|
-
raise ValueError(f"Invalid mode: {mode}. Use 'strict' or 'loose'.")
|
|
426
|
+
is_valid: bool = False
|
|
397
427
|
|
|
398
|
-
|
|
428
|
+
requirement_req_links = self.active_links
|
|
399
429
|
|
|
400
|
-
|
|
401
|
-
expected_models = self.expected_models
|
|
430
|
+
# expected_models = self.expected_models
|
|
402
431
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
for cls in expected_models:
|
|
406
|
-
if isinstance(cls, type):
|
|
407
|
-
try:
|
|
408
|
-
if isinstance(obj, cls):
|
|
409
|
-
return True
|
|
410
|
-
except Exception:
|
|
411
|
-
# cls might not be a runtime type
|
|
412
|
-
continue
|
|
413
|
-
return False
|
|
432
|
+
operator = self.operator
|
|
433
|
+
assert isinstance(operator, RequirementOperator)
|
|
414
434
|
|
|
415
|
-
|
|
416
|
-
if not isinstance(qs, models.QuerySet) or not hasattr(qs, 'model'):
|
|
417
|
-
return False
|
|
418
|
-
for cls in expected_models:
|
|
419
|
-
if isinstance(cls, type):
|
|
420
|
-
try:
|
|
421
|
-
if issubclass(qs.model, cls):
|
|
422
|
-
return True
|
|
423
|
-
except Exception:
|
|
424
|
-
continue
|
|
425
|
-
return False
|
|
435
|
+
operator_instructions = self.operator_instructions_parsed
|
|
426
436
|
|
|
427
|
-
|
|
428
|
-
aggregated_input_links_data = {}
|
|
429
|
-
processed_inputs_count = 0
|
|
430
|
-
|
|
431
|
-
for _input in args:
|
|
432
|
-
# Check if the input is an instance of any of the expected model types
|
|
433
|
-
if not _is_expected_instance(_input):
|
|
434
|
-
# Allow QuerySets of expected models
|
|
435
|
-
if _is_queryset_of_expected(_input):
|
|
436
|
-
# For QuerySets, evaluate each item individually and return True if any matches
|
|
437
|
-
if not _input.exists(): # Skip empty querysets
|
|
438
|
-
continue
|
|
439
|
-
|
|
440
|
-
queryset_results = []
|
|
441
|
-
for item in _input:
|
|
442
|
-
if not hasattr(item, 'links') or not isinstance(item.links, RequirementLinks):
|
|
443
|
-
raise TypeError(
|
|
444
|
-
f"Item {item} of type {type(item)} in QuerySet does not have a valid .links attribute of type RequirementLinks."
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
# Evaluate this single item against the requirement
|
|
448
|
-
item_input_links = RequirementLinks(**item.links.active())
|
|
449
|
-
|
|
450
|
-
# Evaluate all operators for this single item
|
|
451
|
-
item_operator_results = []
|
|
452
|
-
for operator in self.operators.all():
|
|
453
|
-
try:
|
|
454
|
-
operator_result = operator.evaluate(
|
|
455
|
-
requirement_links=requirement_req_links,
|
|
456
|
-
input_links=item_input_links,
|
|
457
|
-
requirement=self,
|
|
458
|
-
original_input_args=args,
|
|
459
|
-
**kwargs
|
|
460
|
-
)
|
|
461
|
-
item_operator_results.append(operator_result)
|
|
462
|
-
except Exception as e:
|
|
463
|
-
logger.debug(f"Operator {operator.name} evaluation failed for item {item}: {e}")
|
|
464
|
-
item_operator_results.append(False)
|
|
465
|
-
|
|
466
|
-
# Apply evaluation mode for this single item
|
|
467
|
-
item_result = evaluate_result_list_func(item_operator_results) if item_operator_results else True
|
|
468
|
-
queryset_results.append(item_result)
|
|
469
|
-
processed_inputs_count += 1
|
|
470
|
-
|
|
471
|
-
# If any item in the QuerySet matches, return True for the whole QuerySet evaluation
|
|
472
|
-
if any(queryset_results):
|
|
473
|
-
return True
|
|
474
|
-
continue # Move to the next arg after processing queryset
|
|
475
|
-
else:
|
|
476
|
-
raise TypeError(
|
|
477
|
-
f"Input type {type(_input)} is not among expected models: {self.expected_models} "
|
|
478
|
-
f"nor a QuerySet of expected models."
|
|
479
|
-
)
|
|
480
|
-
|
|
481
|
-
# Process single model instance
|
|
482
|
-
if not hasattr(_input, 'links') or not isinstance(_input.links, RequirementLinks):
|
|
483
|
-
raise TypeError(
|
|
484
|
-
f"Input {_input} of type {type(_input)} does not have a valid .links attribute of type RequirementLinks."
|
|
485
|
-
)
|
|
486
|
-
|
|
487
|
-
active_input_links = _input.links.active() # Get dict of non-empty lists
|
|
488
|
-
for link_key, link_list in active_input_links.items():
|
|
489
|
-
if link_key not in aggregated_input_links_data:
|
|
490
|
-
aggregated_input_links_data[link_key] = []
|
|
491
|
-
aggregated_input_links_data[link_key].extend(link_list)
|
|
492
|
-
processed_inputs_count += 1
|
|
493
|
-
|
|
494
|
-
if not processed_inputs_count and args: # If args were provided but none were processable (e.g. all empty querysets)
|
|
495
|
-
# This situation implies no relevant data was provided for evaluation against the requirement.
|
|
496
|
-
# Depending on operator logic (e.g., "requires at least one matching item"), this might lead to False.
|
|
497
|
-
# For "models_match_any", an empty input_links will likely result in False if requirement_req_links is not empty.
|
|
498
|
-
pass
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
# Deduplicate items within each list after aggregation
|
|
502
|
-
for key in aggregated_input_links_data:
|
|
503
|
-
try:
|
|
504
|
-
# Using dict.fromkeys to preserve order and remove duplicates for hashable items
|
|
505
|
-
aggregated_input_links_data[key] = list(dict.fromkeys(aggregated_input_links_data[key]))
|
|
506
|
-
except TypeError:
|
|
507
|
-
# Fallback for non-hashable items (though Django models are hashable)
|
|
508
|
-
temp_list = []
|
|
509
|
-
for item in aggregated_input_links_data[key]:
|
|
510
|
-
if item not in temp_list:
|
|
511
|
-
temp_list.append(item)
|
|
512
|
-
aggregated_input_links_data[key] = temp_list
|
|
513
|
-
|
|
514
|
-
final_input_links = RequirementLinks(**aggregated_input_links_data)
|
|
515
|
-
|
|
516
|
-
# Gender strict check: if this requirement has genders, only pass if patient.gender is in the set
|
|
517
|
-
genders_exist = self.genders.exists()
|
|
518
|
-
if genders_exist:
|
|
519
|
-
# Import here to avoid circular import
|
|
520
|
-
from endoreg_db.models.administration.person.patient import Patient
|
|
521
|
-
patient = None
|
|
522
|
-
for arg in args:
|
|
523
|
-
if isinstance(arg, Patient):
|
|
524
|
-
patient = arg
|
|
525
|
-
break
|
|
526
|
-
if patient is None or patient.gender is None:
|
|
527
|
-
return False
|
|
528
|
-
if not self.genders.filter(pk=patient.gender.pk).exists():
|
|
529
|
-
return False
|
|
530
|
-
|
|
531
|
-
operators = self.operators.all()
|
|
532
|
-
if not operators.exists(): # If a requirement has no operators, its evaluation is ambiguous.
|
|
533
|
-
# Consider if this should be True, False, or an error.
|
|
534
|
-
# For now, if no operators, and mode is strict, it's vacuously true. If loose, vacuously false.
|
|
535
|
-
# However, typically a requirement implies some condition.
|
|
536
|
-
# Let's assume if no operators, it cannot be satisfied unless it also has no specific links.
|
|
537
|
-
# This behavior might need further refinement based on business logic.
|
|
538
|
-
if not requirement_req_links.active(): # No conditions in requirement
|
|
539
|
-
return True # Vacuously true if requirement itself is empty
|
|
540
|
-
return False # Cannot be satisfied if requirement has conditions but no operators to check them
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
operator_results = []
|
|
544
|
-
for operator in operators:
|
|
545
|
-
# Prepare kwargs for the operator, including the current Requirement instance
|
|
546
|
-
op_kwargs = kwargs.copy() # Start with kwargs passed to Requirement.evaluate
|
|
547
|
-
op_kwargs['requirement'] = self # Add the Requirement instance itself
|
|
548
|
-
op_kwargs['original_input_args'] = args # Add the original input arguments for operators that need them (e.g., age operators)
|
|
549
|
-
operator_results.append(operator.evaluate(
|
|
550
|
-
requirement_links=requirement_req_links,
|
|
551
|
-
input_links=final_input_links,
|
|
552
|
-
**op_kwargs
|
|
553
|
-
))
|
|
554
|
-
|
|
555
|
-
is_valid = evaluate_result_list_func(operator_results)
|
|
437
|
+
is_valid = operator.evaluate(input_links)
|
|
556
438
|
|
|
557
439
|
return is_valid
|
|
558
440
|
|
|
559
|
-
def evaluate_with_details(self, *args, mode:str, **kwargs):
|
|
441
|
+
def evaluate_with_details(self, *args, mode: str, **kwargs) -> Tuple[bool, str]:
|
|
560
442
|
"""
|
|
561
443
|
Evaluates whether the requirement is satisfied for the given input models using linked operators and gender constraints.
|
|
562
|
-
|
|
444
|
+
|
|
563
445
|
Args:
|
|
564
446
|
*args: Instances or QuerySets of expected model classes to be evaluated. Each must have a `.links` property returning a `RequirementLinks` object.
|
|
565
447
|
mode: Evaluation mode; "strict" requires all operators to pass, "loose" requires any operator to pass.
|
|
566
448
|
**kwargs: Additional keyword arguments passed to operator evaluations.
|
|
567
|
-
|
|
449
|
+
|
|
568
450
|
Returns:
|
|
569
|
-
|
|
570
|
-
|
|
451
|
+
(met, details):
|
|
452
|
+
met: True/False, ob die Voraussetzung erfüllt ist
|
|
453
|
+
details: menschenlesbare Erklärung (für UI geeignet)
|
|
454
|
+
|
|
571
455
|
Raises:
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
If the requirement specifies genders, only input containing a patient with a matching gender will be considered valid for evaluation.
|
|
456
|
+
RequirementEvaluationError:
|
|
457
|
+
- bei ungültigem Modus
|
|
458
|
+
- bei komplett falschen Input-Typen / fehlender .links-Struktur
|
|
576
459
|
"""
|
|
577
|
-
|
|
460
|
+
from endoreg_db.models.requirement.requirement_error import RequirementEvaluationError
|
|
461
|
+
|
|
462
|
+
# --- Mode validieren -------------------------------------------------
|
|
578
463
|
if mode not in ["strict", "loose"]:
|
|
579
|
-
raise
|
|
464
|
+
raise RequirementEvaluationError(
|
|
465
|
+
requirement=self,
|
|
466
|
+
code="INVALID_MODE",
|
|
467
|
+
technical_message=f"Invalid mode: {mode}. Use 'strict' or 'loose'.",
|
|
468
|
+
user_message=(
|
|
469
|
+
"Diese Voraussetzung ist intern mit einem ungültigen Bewertungsmodus konfiguriert und kann aktuell nicht korrekt geprüft werden."
|
|
470
|
+
),
|
|
471
|
+
)
|
|
580
472
|
|
|
581
473
|
evaluate_result_list_func = all if mode == "strict" else any
|
|
582
474
|
|
|
583
475
|
requirement_req_links = self.links
|
|
584
476
|
expected_models = self.expected_models
|
|
585
477
|
|
|
586
|
-
|
|
478
|
+
operators = list(self.operators.all())
|
|
479
|
+
has_operators = bool(operators)
|
|
480
|
+
requirement_has_conditions = bool(requirement_req_links.active())
|
|
481
|
+
queryset_mode, queryset_min_count = self._resolve_queryset_config(kwargs)
|
|
482
|
+
|
|
483
|
+
# --- Helper für Typprüfung ------------------------------------------
|
|
587
484
|
def _is_expected_instance(obj) -> bool:
|
|
588
485
|
for cls in expected_models:
|
|
589
486
|
if isinstance(cls, type):
|
|
@@ -591,12 +488,12 @@ class Requirement(models.Model):
|
|
|
591
488
|
if isinstance(obj, cls):
|
|
592
489
|
return True
|
|
593
490
|
except Exception:
|
|
594
|
-
# cls might
|
|
491
|
+
# cls might nicht runtime-kompatibel sein
|
|
595
492
|
continue
|
|
596
493
|
return False
|
|
597
494
|
|
|
598
495
|
def _is_queryset_of_expected(qs) -> bool:
|
|
599
|
-
if not isinstance(qs, models.QuerySet) or not hasattr(qs,
|
|
496
|
+
if not isinstance(qs, models.QuerySet) or not hasattr(qs, "model"):
|
|
600
497
|
return False
|
|
601
498
|
for cls in expected_models:
|
|
602
499
|
if isinstance(cls, type):
|
|
@@ -607,146 +504,217 @@ class Requirement(models.Model):
|
|
|
607
504
|
continue
|
|
608
505
|
return False
|
|
609
506
|
|
|
610
|
-
#
|
|
611
|
-
aggregated_input_links_data = {}
|
|
507
|
+
# --- RequirementLinks aus allen Inputs aggregieren -------------------
|
|
508
|
+
aggregated_input_links_data: dict = {}
|
|
612
509
|
processed_inputs_count = 0
|
|
613
510
|
|
|
614
511
|
for _input in args:
|
|
615
|
-
# Check if the input is an instance of any of the expected model types
|
|
616
512
|
if not _is_expected_instance(_input):
|
|
617
|
-
#
|
|
513
|
+
# QuerySet von erwarteten Typen erlauben
|
|
618
514
|
if _is_queryset_of_expected(_input):
|
|
619
|
-
|
|
620
|
-
|
|
515
|
+
if not _input.exists():
|
|
516
|
+
# leeres QS -> je nach QS-Mode sofort nicht erfüllt
|
|
517
|
+
if queryset_mode == "all":
|
|
518
|
+
return (
|
|
519
|
+
False,
|
|
520
|
+
"Für diese Voraussetzung müssen alle passenden Einträge vorliegen, aber es wurden keine entsprechenden Datensätze gefunden.",
|
|
521
|
+
)
|
|
522
|
+
if queryset_mode == "min_count":
|
|
523
|
+
required = queryset_min_count if queryset_min_count is not None else 1
|
|
524
|
+
if required > 0:
|
|
525
|
+
return (
|
|
526
|
+
False,
|
|
527
|
+
f"Für diese Voraussetzung werden mindestens {required} passende Einträge benötigt, es wurden jedoch keine gefunden.",
|
|
528
|
+
)
|
|
529
|
+
# queryset_mode == "any" bei leerem QS -> neutral (keine zusätzliche Einschränkung)
|
|
621
530
|
continue
|
|
622
|
-
|
|
623
|
-
queryset_results = []
|
|
531
|
+
|
|
532
|
+
queryset_results: List[bool] = []
|
|
533
|
+
queryset_true_count = 0
|
|
534
|
+
queryset_item_count = 0
|
|
535
|
+
|
|
624
536
|
for item in _input:
|
|
625
|
-
if not hasattr(item,
|
|
626
|
-
raise
|
|
627
|
-
|
|
537
|
+
if not hasattr(item, "links") or not isinstance(item.links, RequirementLinks):
|
|
538
|
+
raise RequirementEvaluationError(
|
|
539
|
+
requirement=self,
|
|
540
|
+
code="MISSING_LINKS_ATTR",
|
|
541
|
+
technical_message=(
|
|
542
|
+
f"Item {item} of type {type(item)} in QuerySet does not have a valid .links attribute of type RequirementLinks."
|
|
543
|
+
),
|
|
544
|
+
user_message=(
|
|
545
|
+
"Für einen Datensatz fehlen die intern benötigten Verknüpfungen, "
|
|
546
|
+
"sodass diese Voraussetzung nicht korrekt geprüft werden kann."
|
|
547
|
+
),
|
|
548
|
+
meta={"item_type": str(type(item))},
|
|
628
549
|
)
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
item_input_links = RequirementLinks(**
|
|
632
|
-
|
|
633
|
-
#
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
550
|
+
|
|
551
|
+
item_active_links = item.links.active()
|
|
552
|
+
item_input_links = RequirementLinks(**item_active_links)
|
|
553
|
+
|
|
554
|
+
# Links sammeln
|
|
555
|
+
for link_key, link_list in item_active_links.items():
|
|
556
|
+
if link_key not in aggregated_input_links_data:
|
|
557
|
+
aggregated_input_links_data[link_key] = []
|
|
558
|
+
aggregated_input_links_data[link_key].extend(link_list)
|
|
559
|
+
|
|
560
|
+
per_item_args = tuple(item if arg is _input else arg for arg in args)
|
|
561
|
+
op_kwargs = kwargs.copy()
|
|
562
|
+
op_kwargs["requirement"] = self
|
|
563
|
+
op_kwargs["original_input_args"] = per_item_args
|
|
564
|
+
|
|
565
|
+
if has_operators:
|
|
566
|
+
item_operator_results: List[bool] = []
|
|
567
|
+
for operator in operators:
|
|
568
|
+
try:
|
|
569
|
+
operator_result = operator.evaluate(
|
|
570
|
+
requirement_links=requirement_req_links,
|
|
571
|
+
input_links=item_input_links,
|
|
572
|
+
**op_kwargs,
|
|
573
|
+
)
|
|
574
|
+
item_operator_results.append(operator_result)
|
|
575
|
+
except Exception as exc:
|
|
576
|
+
logger.debug(
|
|
577
|
+
"Operator %s evaluation failed for item %s: %s",
|
|
578
|
+
getattr(operator, "name", "unknown"),
|
|
579
|
+
item,
|
|
580
|
+
exc,
|
|
581
|
+
)
|
|
582
|
+
item_operator_results.append(False)
|
|
583
|
+
item_result = evaluate_result_list_func(item_operator_results) if item_operator_results else True
|
|
584
|
+
else:
|
|
585
|
+
# keine Operatoren -> Bedingung erfüllt, wenn Requirement selbst keine Bedingungen hat
|
|
586
|
+
item_result = not requirement_has_conditions
|
|
587
|
+
|
|
651
588
|
queryset_results.append(item_result)
|
|
589
|
+
if item_result:
|
|
590
|
+
queryset_true_count += 1
|
|
591
|
+
queryset_item_count += 1
|
|
652
592
|
processed_inputs_count += 1
|
|
653
|
-
|
|
654
|
-
#
|
|
655
|
-
if
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
593
|
+
|
|
594
|
+
# QS-Modus nach Auswertung anwenden
|
|
595
|
+
if queryset_mode == "all":
|
|
596
|
+
if queryset_item_count == 0 or not all(queryset_results):
|
|
597
|
+
return (
|
|
598
|
+
False,
|
|
599
|
+
"Für diese Voraussetzung müssen alle relevanten Einträge die Bedingung erfüllen.",
|
|
600
|
+
)
|
|
601
|
+
elif queryset_mode == "min_count":
|
|
602
|
+
required = queryset_min_count if queryset_min_count is not None else 1
|
|
603
|
+
if queryset_true_count < max(required, 0):
|
|
604
|
+
return (
|
|
605
|
+
False,
|
|
606
|
+
f"Für diese Voraussetzung werden mindestens {max(required, 0)} passende Einträge benötigt (gefunden: {queryset_true_count}).",
|
|
607
|
+
)
|
|
608
|
+
# queryset_mode == "any": keine zusätzliche Einschränkung
|
|
609
|
+
continue
|
|
610
|
+
|
|
611
|
+
# Weder Instanz noch QS eines erwarteten Modells -> Konfig-/Aufruf-Fehler
|
|
612
|
+
raise RequirementEvaluationError(
|
|
613
|
+
requirement=self,
|
|
614
|
+
code="INVALID_INPUT_TYPE",
|
|
615
|
+
technical_message=(f"Input type {type(_input)} is not among expected models: {self.expected_models} nor a QuerySet of expected models."),
|
|
616
|
+
user_message=("Diese Voraussetzung wurde mit einem nicht passenden Datentyp aufgerufen und kann aktuell nicht korrekt geprüft werden."),
|
|
617
|
+
meta={"input_type": str(type(_input))},
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
# Einzelinstanz erwarteten Typs
|
|
621
|
+
if not hasattr(_input, "links") or not isinstance(_input.links, RequirementLinks):
|
|
622
|
+
raise RequirementEvaluationError(
|
|
623
|
+
requirement=self,
|
|
624
|
+
code="MISSING_LINKS_ATTR",
|
|
625
|
+
technical_message=(f"Input {_input} of type {type(_input)} does not have a valid .links attribute of type RequirementLinks."),
|
|
626
|
+
user_message=("Für die Auswertung dieser Voraussetzung fehlen die intern benötigten Verknüpfungsinformationen."),
|
|
627
|
+
meta={"input_type": str(type(_input))},
|
|
668
628
|
)
|
|
669
|
-
|
|
670
|
-
active_input_links = _input.links.active()
|
|
629
|
+
|
|
630
|
+
active_input_links = _input.links.active()
|
|
671
631
|
for link_key, link_list in active_input_links.items():
|
|
672
632
|
if link_key not in aggregated_input_links_data:
|
|
673
633
|
aggregated_input_links_data[link_key] = []
|
|
674
634
|
aggregated_input_links_data[link_key].extend(link_list)
|
|
675
635
|
processed_inputs_count += 1
|
|
676
636
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
# Depending on operator logic (e.g., "requires at least one matching item"), this might lead to False.
|
|
680
|
-
# For "models_match_any", an empty input_links will likely result in False if requirement_req_links is not empty.
|
|
681
|
-
pass
|
|
682
|
-
|
|
637
|
+
# Wenn es zwar *args gibt, aber alles leer/irrelevant war, lassen wir das weiterlaufen.
|
|
638
|
+
# Operatoren sehen dann ggf. ein leeres final_input_links.
|
|
683
639
|
|
|
684
|
-
#
|
|
640
|
+
# Deduplizieren der aggregierten Links
|
|
685
641
|
for key in aggregated_input_links_data:
|
|
686
642
|
try:
|
|
687
|
-
# Using dict.fromkeys to preserve order and remove duplicates for hashable items
|
|
688
643
|
aggregated_input_links_data[key] = list(dict.fromkeys(aggregated_input_links_data[key]))
|
|
689
644
|
except TypeError:
|
|
690
|
-
# Fallback
|
|
691
|
-
|
|
645
|
+
# Fallback für nicht-hashbare Items
|
|
646
|
+
tmp: list = []
|
|
692
647
|
for item in aggregated_input_links_data[key]:
|
|
693
|
-
if item not in
|
|
694
|
-
|
|
695
|
-
aggregated_input_links_data[key] =
|
|
696
|
-
|
|
648
|
+
if item not in tmp:
|
|
649
|
+
tmp.append(item)
|
|
650
|
+
aggregated_input_links_data[key] = tmp
|
|
651
|
+
|
|
697
652
|
final_input_links = RequirementLinks(**aggregated_input_links_data)
|
|
698
|
-
|
|
699
|
-
# Gender
|
|
653
|
+
|
|
654
|
+
# --- Gender-Check ----------------------------------------------------
|
|
700
655
|
genders_exist = self.genders.exists()
|
|
701
656
|
if genders_exist:
|
|
702
|
-
# Import here to avoid circular import
|
|
703
657
|
from endoreg_db.models.administration.person.patient import Patient
|
|
658
|
+
|
|
704
659
|
patient = None
|
|
705
660
|
for arg in args:
|
|
706
661
|
if isinstance(arg, Patient):
|
|
707
662
|
patient = arg
|
|
708
663
|
break
|
|
664
|
+
|
|
709
665
|
if patient is None or patient.gender is None:
|
|
710
|
-
return
|
|
666
|
+
return (
|
|
667
|
+
False,
|
|
668
|
+
"Für diese Voraussetzung ist ein hinterlegtes Geschlecht des Patienten erforderlich.",
|
|
669
|
+
)
|
|
670
|
+
|
|
711
671
|
if not self.genders.filter(pk=patient.gender.pk).exists():
|
|
712
|
-
return
|
|
672
|
+
return (
|
|
673
|
+
False,
|
|
674
|
+
"Diese Voraussetzung gilt nur für bestimmte Geschlechter und ist für diesen Patienten nicht erfüllt.",
|
|
675
|
+
)
|
|
713
676
|
|
|
714
|
-
|
|
715
|
-
if not
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
return True # Vacuously true if requirement itself is empty
|
|
723
|
-
return False # Cannot be satisfied if requirement has conditions but no operators to check them
|
|
677
|
+
# --- Fall: keine Operatoren -----------------------------------------
|
|
678
|
+
if not has_operators:
|
|
679
|
+
if not requirement_has_conditions:
|
|
680
|
+
return True, "Keine Operatoren für die Bewertung erforderlich."
|
|
681
|
+
return (
|
|
682
|
+
False,
|
|
683
|
+
"Die Voraussetzung besitzt Bedingungen, aber keinen Operator zur Auswertung.",
|
|
684
|
+
)
|
|
724
685
|
|
|
686
|
+
# --- Operatoren anwenden --------------------------------------------
|
|
687
|
+
operator_results: List[bool] = []
|
|
688
|
+
operator_details: List[str] = []
|
|
725
689
|
|
|
726
|
-
operator_results = []
|
|
727
|
-
operator_details = []
|
|
728
690
|
for operator in operators:
|
|
729
|
-
|
|
730
|
-
op_kwargs =
|
|
731
|
-
op_kwargs[
|
|
732
|
-
|
|
691
|
+
op_kwargs = kwargs.copy()
|
|
692
|
+
op_kwargs["requirement"] = self
|
|
693
|
+
op_kwargs["original_input_args"] = args
|
|
694
|
+
|
|
733
695
|
try:
|
|
734
696
|
operator_result = operator.evaluate(
|
|
735
697
|
requirement_links=requirement_req_links,
|
|
736
698
|
input_links=final_input_links,
|
|
737
|
-
**op_kwargs
|
|
699
|
+
**op_kwargs,
|
|
738
700
|
)
|
|
739
701
|
operator_results.append(operator_result)
|
|
740
|
-
operator_details.append(f"{operator.name}: {'
|
|
702
|
+
operator_details.append(f"{operator.name}: {'erfüllt' if operator_result else 'nicht erfüllt'}")
|
|
741
703
|
except Exception as e:
|
|
742
704
|
operator_results.append(False)
|
|
743
|
-
operator_details.append(f"{operator.name}: {
|
|
705
|
+
operator_details.append(f"{operator.name}: technischer Fehler ({e})")
|
|
706
|
+
logger.debug(
|
|
707
|
+
"Operator %s evaluation failed for requirement %s: %s",
|
|
708
|
+
getattr(operator, "name", "unknown"),
|
|
709
|
+
getattr(self, "name", "unknown"),
|
|
710
|
+
e,
|
|
711
|
+
)
|
|
744
712
|
|
|
745
713
|
is_valid = evaluate_result_list_func(operator_results)
|
|
746
714
|
|
|
747
|
-
#
|
|
715
|
+
# --- Detailtext bauen -----------------------------------------------
|
|
748
716
|
if not operator_results:
|
|
749
|
-
details = "Keine Operatoren für die Bewertung verfügbar"
|
|
717
|
+
details = "Keine Operatoren für die Bewertung verfügbar."
|
|
750
718
|
elif len(operator_results) == 1:
|
|
751
719
|
details = operator_details[0]
|
|
752
720
|
else:
|
|
@@ -754,14 +722,14 @@ class Requirement(models.Model):
|
|
|
754
722
|
if failed_details:
|
|
755
723
|
details = "; ".join(failed_details)
|
|
756
724
|
else:
|
|
757
|
-
details = "Alle
|
|
725
|
+
details = "Alle verknüpften Bedingungen sind erfüllt."
|
|
758
726
|
|
|
759
|
-
#
|
|
727
|
+
# Arbeitsverzeichnis als Debug-Helfer anhängen (optional)
|
|
760
728
|
try:
|
|
761
729
|
cwd = run("pwd", capture_output=True, text=True).stdout.strip()
|
|
762
730
|
details = f"{details}\ncwd: {cwd}"
|
|
763
731
|
except Exception:
|
|
764
|
-
#
|
|
732
|
+
# nicht kritisch
|
|
765
733
|
pass
|
|
766
734
|
|
|
767
|
-
return is_valid, details
|
|
735
|
+
return bool(is_valid), details
|