endoreg-db 0.8.8.0__py3-none-any.whl → 0.8.9.2__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/data/__init__.py +22 -8
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +0 -1
- 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/{colonoscopy_bowel_preparation.yaml → 02_colonoscopy_baseline.yaml} +35 -20
- 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/{_examples/finding_classification_choice/colonoscopy_not_complete_reason.yaml → finding_classification_choice/02_colonoscopy_generic.yaml} +1 -1
- endoreg_db/data/finding_classification_choice/{histology_polyp.yaml → 02_colonoscopy_histology.yaml} +1 -1
- endoreg_db/data/{_examples/finding_classification_choice/colonoscopy_location.yaml → finding_classification_choice/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/{_examples/finding_classification_choice/colon_lesion_paris.yaml → finding_classification_choice/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_operator/new_operators.yaml +36 -0
- endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +0 -2
- endoreg_db/data/requirement_set/90_coloreg.yaml +20 -8
- endoreg_db/exceptions.py +0 -1
- endoreg_db/forms/examination_form.py +1 -1
- endoreg_db/helpers/data_loader.py +124 -52
- 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 +496 -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/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/pseudonymization/pseudonymize.py +0 -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/import_report.py +130 -65
- endoreg_db/management/commands/import_video_with_classification.py +1 -1
- 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_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 +14 -20
- 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/start_filewatcher.py +46 -37
- endoreg_db/management/commands/validate_video_files.py +1 -5
- endoreg_db/migrations/0001_initial.py +1360 -1812
- endoreg_db/models/administration/person/patient/patient.py +72 -46
- endoreg_db/models/label/__init__.py +2 -2
- endoreg_db/models/label/annotation/video_segmentation_annotation.py +18 -26
- endoreg_db/models/label/label_video_segment/label_video_segment.py +23 -1
- endoreg_db/models/media/pdf/raw_pdf.py +136 -64
- endoreg_db/models/media/pdf/report_reader/report_reader_config.py +34 -10
- 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/create_from_file.py +101 -31
- endoreg_db/models/media/video/video_file.py +125 -105
- endoreg_db/models/media/video/video_file_io.py +31 -26
- endoreg_db/models/medical/contraindication/README.md +1 -0
- endoreg_db/models/medical/examination/examination.py +28 -8
- endoreg_db/models/medical/examination/examination_indication.py +13 -79
- endoreg_db/models/medical/examination/examination_time.py +8 -3
- endoreg_db/models/medical/finding/finding.py +5 -12
- endoreg_db/models/medical/finding/finding_classification.py +18 -37
- endoreg_db/models/medical/finding/finding_intervention.py +7 -9
- endoreg_db/models/medical/hardware/endoscope.py +6 -0
- endoreg_db/models/medical/patient/medication_examples.py +5 -1
- endoreg_db/models/medical/patient/patient_finding.py +1 -1
- endoreg_db/models/metadata/pdf_meta.py +22 -10
- endoreg_db/models/metadata/sensitive_meta.py +3 -0
- endoreg_db/models/metadata/sensitive_meta_logic.py +200 -124
- endoreg_db/models/other/information_source.py +27 -6
- endoreg_db/models/report/__init__.py +0 -0
- endoreg_db/models/report/images.py +0 -0
- endoreg_db/models/report/report.py +6 -0
- endoreg_db/models/requirement/requirement.py +59 -399
- endoreg_db/models/requirement/requirement_operator.py +86 -98
- endoreg_db/models/state/audit_ledger.py +4 -5
- endoreg_db/models/state/raw_pdf.py +69 -30
- endoreg_db/models/state/video.py +65 -49
- endoreg_db/models/upload_job.py +33 -9
- endoreg_db/models/utils.py +27 -23
- endoreg_db/queries/__init__.py +3 -1
- endoreg_db/schemas/examination_evaluation.py +1 -1
- endoreg_db/serializers/__init__.py +2 -8
- endoreg_db/serializers/label_video_segment/label_video_segment.py +2 -29
- endoreg_db/serializers/meta/__init__.py +1 -6
- 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/video/video_file_list.py +65 -34
- endoreg_db/services/__old/pdf_import.py +1487 -0
- endoreg_db/services/__old/video_import.py +1306 -0
- endoreg_db/services/anonymization.py +63 -26
- endoreg_db/services/lookup_service.py +28 -28
- endoreg_db/services/lookup_store.py +2 -2
- endoreg_db/services/pdf_import.py +0 -1480
- endoreg_db/services/report_import.py +10 -0
- endoreg_db/services/video_import.py +6 -1165
- endoreg_db/tasks/upload_tasks.py +79 -70
- endoreg_db/tasks/video_ingest.py +8 -4
- endoreg_db/urls/__init__.py +0 -14
- endoreg_db/urls/ai.py +32 -0
- endoreg_db/urls/media.py +21 -24
- endoreg_db/utils/dataloader.py +87 -57
- endoreg_db/utils/paths.py +110 -46
- endoreg_db/utils/pipelines/Readme.md +1 -1
- endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +97 -0
- endoreg_db/utils/video/ffmpeg_wrapper.py +217 -52
- endoreg_db/views/__init__.py +85 -173
- endoreg_db/views/ai/__init__.py +8 -0
- endoreg_db/views/ai/label.py +155 -0
- endoreg_db/views/anonymization/media_management.py +8 -7
- endoreg_db/views/anonymization/overview.py +97 -68
- endoreg_db/views/anonymization/validate.py +25 -21
- endoreg_db/views/media/__init__.py +5 -20
- endoreg_db/views/media/pdf_media.py +109 -65
- endoreg_db/views/media/sensitive_metadata.py +163 -148
- 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/report/__init__.py +7 -0
- endoreg_db/views/{pdf → report}/reimport.py +45 -24
- endoreg_db/views/{pdf/pdf_stream.py → report/report_stream.py} +40 -32
- endoreg_db/views/requirement/lookup_store.py +22 -90
- endoreg_db/views/video/__init__.py +23 -22
- 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} +75 -37
- 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.8.0.dist-info → endoreg_db-0.8.9.2.dist-info}/METADATA +2 -2
- {endoreg_db-0.8.8.0.dist-info → endoreg_db-0.8.9.2.dist-info}/RECORD +217 -335
- {endoreg_db-0.8.8.0.dist-info → endoreg_db-0.8.9.2.dist-info}/WHEEL +1 -1
- endoreg_db/data/_examples/disease.yaml +0 -55
- endoreg_db/data/_examples/disease_classification.yaml +0 -13
- endoreg_db/data/_examples/disease_classification_choice.yaml +0 -62
- endoreg_db/data/_examples/event.yaml +0 -64
- endoreg_db/data/_examples/examination.yaml +0 -72
- endoreg_db/data/_examples/finding/anatomy_colon.yaml +0 -128
- endoreg_db/data/_examples/finding/colonoscopy.yaml +0 -40
- endoreg_db/data/_examples/finding/colonoscopy_bowel_prep.yaml +0 -56
- endoreg_db/data/_examples/finding/complication.yaml +0 -16
- endoreg_db/data/_examples/finding/data.yaml +0 -105
- endoreg_db/data/_examples/finding/examination_setting.yaml +0 -16
- endoreg_db/data/_examples/finding/medication_related.yaml +0 -18
- endoreg_db/data/_examples/finding/outcome.yaml +0 -12
- endoreg_db/data/_examples/finding_classification/colonoscopy_bowel_preparation.yaml +0 -68
- endoreg_db/data/_examples/finding_classification/colonoscopy_jnet.yaml +0 -22
- endoreg_db/data/_examples/finding_classification/colonoscopy_kudo.yaml +0 -25
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_circularity.yaml +0 -20
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_planarity.yaml +0 -24
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_size.yaml +0 -68
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_surface.yaml +0 -20
- endoreg_db/data/_examples/finding_classification/colonoscopy_location.yaml +0 -80
- endoreg_db/data/_examples/finding_classification/colonoscopy_lst.yaml +0 -21
- endoreg_db/data/_examples/finding_classification/colonoscopy_nice.yaml +0 -20
- endoreg_db/data/_examples/finding_classification/colonoscopy_paris.yaml +0 -26
- endoreg_db/data/_examples/finding_classification/colonoscopy_sano.yaml +0 -22
- endoreg_db/data/_examples/finding_classification/colonoscopy_summary.yaml +0 -53
- endoreg_db/data/_examples/finding_classification/complication_generic.yaml +0 -25
- endoreg_db/data/_examples/finding_classification/examination_setting_generic.yaml +0 -40
- endoreg_db/data/_examples/finding_classification/histology_colo.yaml +0 -51
- endoreg_db/data/_examples/finding_classification/intervention_required.yaml +0 -26
- endoreg_db/data/_examples/finding_classification/medication_related.yaml +0 -23
- endoreg_db/data/_examples/finding_classification/visualized.yaml +0 -33
- endoreg_db/data/_examples/finding_classification_choice/bowel_preparation.yaml +0 -78
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_circularity_default.yaml +0 -32
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_jnet.yaml +0 -15
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_kudo.yaml +0 -23
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_lst.yaml +0 -15
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_nice.yaml +0 -17
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_planarity_default.yaml +0 -49
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_sano.yaml +0 -14
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_surface_intact_default.yaml +0 -36
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_size.yaml +0 -82
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +0 -15
- endoreg_db/data/_examples/finding_classification_choice/complication_generic_types.yaml +0 -15
- endoreg_db/data/_examples/finding_classification_choice/examination_setting_generic_types.yaml +0 -15
- endoreg_db/data/_examples/finding_classification_choice/histology.yaml +0 -24
- endoreg_db/data/_examples/finding_classification_choice/histology_polyp.yaml +0 -20
- endoreg_db/data/_examples/finding_classification_choice/outcome.yaml +0 -19
- endoreg_db/data/_examples/finding_classification_choice/yes_no_na.yaml +0 -11
- endoreg_db/data/_examples/finding_classification_type/colonoscopy_basic.yaml +0 -48
- endoreg_db/data/_examples/finding_intervention/endoscopy.yaml +0 -43
- endoreg_db/data/_examples/finding_intervention/endoscopy_colonoscopy.yaml +0 -168
- endoreg_db/data/_examples/finding_intervention/endoscopy_egd.yaml +0 -128
- endoreg_db/data/_examples/finding_intervention/endoscopy_ercp.yaml +0 -32
- endoreg_db/data/_examples/finding_intervention/endoscopy_eus_lower.yaml +0 -9
- endoreg_db/data/_examples/finding_intervention/endoscopy_eus_upper.yaml +0 -36
- endoreg_db/data/_examples/finding_intervention_type/endoscopy.yaml +0 -15
- endoreg_db/data/_examples/finding_type/data.yaml +0 -43
- endoreg_db/data/_examples/requirement/age.yaml +0 -26
- endoreg_db/data/_examples/requirement/gender.yaml +0 -25
- endoreg_db/data/_examples/requirement_set/01_endoscopy_generic.yaml +0 -48
- endoreg_db/data/_examples/requirement_set/colonoscopy_austria_screening.yaml +0 -57
- endoreg_db/data/_examples/requirement_set/endoscopy_bleeding_risk.yaml +0 -52
- endoreg_db/data/_examples/yaml_examples.xlsx +0 -0
- 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_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 -38
- endoreg_db/data/finding_classification/colonoscopy_lesion_surface.yaml +0 -20
- endoreg_db/data/finding_classification/colonoscopy_location.yaml +0 -49
- 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 -43
- 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_paris.yaml +0 -57
- 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_location.yaml +0 -229
- endoreg_db/data/finding_classification_choice/colonoscopy_not_complete_reason.yaml +0 -19
- 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/colonoscopy_baseline_austria.yaml +0 -45
- endoreg_db/data/requirement/disease_cardiovascular.yaml +0 -79
- endoreg_db/data/requirement/disease_classification_choice_cardiovascular.yaml +0 -41
- endoreg_db/data/requirement/disease_hepatology.yaml +0 -12
- endoreg_db/data/requirement/disease_misc.yaml +0 -12
- endoreg_db/data/requirement/disease_renal.yaml +0 -96
- endoreg_db/data/requirement/endoscopy_bleeding_risk.yaml +0 -59
- endoreg_db/data/requirement/event_cardiology.yaml +0 -251
- endoreg_db/data/requirement/event_requirements.yaml +0 -145
- endoreg_db/data/requirement/finding_colon_polyp.yaml +0 -50
- endoreg_db/data/requirement/gender.yaml +0 -25
- endoreg_db/data/requirement/lab_value.yaml +0 -441
- endoreg_db/data/requirement/medication.yaml +0 -93
- endoreg_db/data/requirement_operator/age.yaml +0 -13
- endoreg_db/data/requirement_operator/lab_operators.yaml +0 -129
- endoreg_db/data/requirement_operator/model_operators.yaml +0 -96
- 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_requirementset_depends_on.py +0 -18
- endoreg_db/migrations/_old/0001_initial.py +0 -1857
- endoreg_db/migrations/_old/0002_add_video_correction_models.py +0 -52
- endoreg_db/migrations/_old/0003_add_center_display_name.py +0 -30
- endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +0 -68
- endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +0 -77
- endoreg_db/migrations/_old/0005_merge_20251111_1003.py +0 -14
- endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +0 -68
- endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +0 -89
- endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +0 -27
- endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +0 -21
- 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/services/requirements_object.py +0 -147
- endoreg_db/services/storage_aware_video_processor.py +0 -370
- 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/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 -85
- 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/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/data/requirement/{colon_polyp_intervention.yaml → old/colon_polyp_intervention.yaml} +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/colonoscopy_baseline_austria.yaml +0 -0
- /endoreg_db/data/requirement/{coloreg_colon_polyp.yaml → old/coloreg_colon_polyp.yaml} +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/disease_cardiovascular.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/disease_classification_choice_cardiovascular.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/disease_hepatology.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/disease_misc.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/disease_renal.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/endoscopy_bleeding_risk.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/event_cardiology.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/event_requirements.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/finding_colon_polyp.yaml +0 -0
- /endoreg_db/{urls/sensitive_meta.py → data/requirement/old/gender.yaml} +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/lab_value.yaml +0 -0
- /endoreg_db/data/{_examples/requirement → requirement/old}/medication.yaml +0 -0
- /endoreg_db/data/{_examples/requirement_operator → requirement_operator/_old}/age.yaml +0 -0
- /endoreg_db/data/{_examples/requirement_operator → requirement_operator/_old}/lab_operators.yaml +0 -0
- /endoreg_db/data/{_examples/requirement_operator → requirement_operator/_old}/model_operators.yaml +0 -0
- /endoreg_db/{views/pdf/pdf_stream_views.py → import_files/pseudonymization/__init__.py} +0 -0
- /endoreg_db/utils/requirement_operator_logic/{lab_value_operators.py → _old/lab_value_operators.py} +0 -0
- /endoreg_db/utils/requirement_operator_logic/{model_evaluators.py → _old/model_evaluators.py} +0 -0
- {endoreg_db-0.8.8.0.dist-info → endoreg_db-0.8.9.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,28 +1,15 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from subprocess import run
|
|
3
|
-
from typing import TYPE_CHECKING, Dict, List, Union, cast
|
|
3
|
+
from typing import TYPE_CHECKING, Dict, List, Tuple, Union, cast
|
|
4
4
|
|
|
5
5
|
from django.db import models
|
|
6
|
+
from pydantic import BaseModel
|
|
6
7
|
|
|
7
8
|
from endoreg_db.utils.links.requirement_link import RequirementLinks
|
|
8
9
|
|
|
9
|
-
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
def _validate_requirement_configuration(instance: "Requirement") -> bool:
|
|
14
|
-
"""Ensures requirement fixtures declare both requirement_types and operators."""
|
|
15
|
-
if not instance.requirement_types.exists():
|
|
16
|
-
raise ValueError(
|
|
17
|
-
f"Requirement '{instance.name}' must be associated with at least one RequirementType."
|
|
18
|
-
)
|
|
19
|
-
if not instance.operators.exists():
|
|
20
|
-
raise ValueError(
|
|
21
|
-
f"Requirement '{instance.name}' must be associated with at least one RequirementOperator."
|
|
22
|
-
)
|
|
23
|
-
return True
|
|
24
|
-
|
|
25
|
-
|
|
26
13
|
QuerySet = models.QuerySet
|
|
27
14
|
|
|
28
15
|
if TYPE_CHECKING:
|
|
@@ -130,33 +117,43 @@ class Requirement(models.Model):
|
|
|
130
117
|
|
|
131
118
|
name = models.CharField(max_length=100, unique=True)
|
|
132
119
|
description = models.TextField(blank=True, null=True)
|
|
133
|
-
|
|
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
|
|
134
131
|
|
|
135
132
|
numeric_value = models.FloatField(
|
|
136
133
|
blank=True,
|
|
137
134
|
null=True,
|
|
138
|
-
help_text="Numeric value for the requirement.
|
|
135
|
+
help_text="Numeric value for the requirement. ons.",
|
|
139
136
|
)
|
|
140
137
|
numeric_value_min = models.FloatField(
|
|
141
138
|
blank=True,
|
|
142
139
|
null=True,
|
|
143
|
-
help_text="Minimum numeric value for the requirement.
|
|
140
|
+
help_text="Minimum numeric value for the requirement. ons.",
|
|
144
141
|
)
|
|
145
142
|
numeric_value_max = models.FloatField(
|
|
146
143
|
blank=True,
|
|
147
144
|
null=True,
|
|
148
|
-
help_text="Maximum numeric value for the requirement.
|
|
145
|
+
help_text="Maximum numeric value for the requirement. ons.",
|
|
149
146
|
)
|
|
150
147
|
string_value = models.CharField(
|
|
151
148
|
max_length=100,
|
|
152
149
|
blank=True,
|
|
153
150
|
null=True,
|
|
154
|
-
help_text="String value for the requirement.
|
|
151
|
+
help_text="String value for the requirement. ons.",
|
|
155
152
|
)
|
|
156
153
|
string_values = models.TextField(
|
|
157
154
|
blank=True,
|
|
158
155
|
null=True,
|
|
159
|
-
help_text=" ','-separated list of string values for the requirement.
|
|
156
|
+
help_text=" ','-separated list of string values for the requirement.ons.",
|
|
160
157
|
)
|
|
161
158
|
objects = RequirementManager()
|
|
162
159
|
|
|
@@ -166,9 +163,9 @@ class Requirement(models.Model):
|
|
|
166
163
|
related_name="linked_requirements",
|
|
167
164
|
)
|
|
168
165
|
|
|
169
|
-
|
|
166
|
+
operator = models.ForeignKey(
|
|
170
167
|
"RequirementOperator",
|
|
171
|
-
|
|
168
|
+
on_delete=models.CASCADE,
|
|
172
169
|
related_name="required_in",
|
|
173
170
|
)
|
|
174
171
|
|
|
@@ -271,12 +268,8 @@ class Requirement(models.Model):
|
|
|
271
268
|
)
|
|
272
269
|
|
|
273
270
|
if TYPE_CHECKING:
|
|
274
|
-
requirement_types = cast(
|
|
275
|
-
|
|
276
|
-
)
|
|
277
|
-
operators = cast(
|
|
278
|
-
models.manager.RelatedManager["RequirementOperator"], operators
|
|
279
|
-
)
|
|
271
|
+
requirement_types = cast(models.manager.RelatedManager["RequirementType"], requirement_types)
|
|
272
|
+
operator = models.ForeignKey["RequirementOperator"]
|
|
280
273
|
# requirement_sets = cast(models.manager.RelatedManager["RequirementSet"], requirement_sets)
|
|
281
274
|
examinations = cast(models.manager.RelatedManager["Examination"], examinations)
|
|
282
275
|
examination_indications = cast(
|
|
@@ -299,9 +292,7 @@ class Requirement(models.Model):
|
|
|
299
292
|
models.manager.RelatedManager["FindingClassificationChoice"],
|
|
300
293
|
finding_classification_choices,
|
|
301
294
|
)
|
|
302
|
-
finding_interventions = cast(
|
|
303
|
-
models.manager.RelatedManager["FindingIntervention"], finding_interventions
|
|
304
|
-
)
|
|
295
|
+
finding_interventions = cast(models.manager.RelatedManager["FindingIntervention"], finding_interventions)
|
|
305
296
|
medications = cast(models.manager.RelatedManager["Medication"], medications)
|
|
306
297
|
medication_indications = cast(
|
|
307
298
|
models.manager.RelatedManager["MedicationIndication"],
|
|
@@ -311,9 +302,7 @@ class Requirement(models.Model):
|
|
|
311
302
|
models.manager.RelatedManager["MedicationIntakeTime"],
|
|
312
303
|
medication_intake_times,
|
|
313
304
|
)
|
|
314
|
-
medication_schedules = cast(
|
|
315
|
-
models.manager.RelatedManager["MedicationSchedule"], medication_schedules
|
|
316
|
-
)
|
|
305
|
+
medication_schedules = cast(models.manager.RelatedManager["MedicationSchedule"], medication_schedules)
|
|
317
306
|
genders = cast(models.manager.RelatedManager["Gender"], genders)
|
|
318
307
|
|
|
319
308
|
def natural_key(self):
|
|
@@ -328,13 +317,6 @@ class Requirement(models.Model):
|
|
|
328
317
|
"""Returns the name of the requirement as its string representation."""
|
|
329
318
|
return str(self.name)
|
|
330
319
|
|
|
331
|
-
# override save method to add a validation step; requirements need at least one operator and at least one requirement type
|
|
332
|
-
# def save(self, *args, **kwargs):
|
|
333
|
-
# _valid = _validate_requirement_configuration(
|
|
334
|
-
# self,
|
|
335
|
-
# )
|
|
336
|
-
# super().save(*args, **kwargs)
|
|
337
|
-
|
|
338
320
|
@property
|
|
339
321
|
def expected_models(
|
|
340
322
|
self,
|
|
@@ -388,32 +370,18 @@ class Requirement(models.Model):
|
|
|
388
370
|
# requirement_sets is not part of RequirementLinks (avoids circular import); collect other related models
|
|
389
371
|
models_dict = RequirementLinks(
|
|
390
372
|
examinations=[_ for _ in self.examinations.all() if _ is not None],
|
|
391
|
-
examination_indications=[
|
|
392
|
-
_ for _ in self.examination_indications.all() if _ is not None
|
|
393
|
-
],
|
|
373
|
+
examination_indications=[_ for _ in self.examination_indications.all() if _ is not None],
|
|
394
374
|
lab_values=[_ for _ in self.lab_values.all() if _ is not None],
|
|
395
375
|
diseases=[_ for _ in self.diseases.all() if _ is not None],
|
|
396
|
-
disease_classification_choices=[
|
|
397
|
-
_ for _ in self.disease_classification_choices.all() if _ is not None
|
|
398
|
-
],
|
|
376
|
+
disease_classification_choices=[_ for _ in self.disease_classification_choices.all() if _ is not None],
|
|
399
377
|
events=[_ for _ in self.events.all() if _ is not None],
|
|
400
378
|
findings=[_ for _ in self.findings.all() if _ is not None],
|
|
401
|
-
finding_classifications=[
|
|
402
|
-
|
|
403
|
-
],
|
|
404
|
-
finding_classification_choices=[
|
|
405
|
-
_ for _ in self.finding_classification_choices.all() if _ is not None
|
|
406
|
-
],
|
|
407
|
-
finding_interventions=[
|
|
408
|
-
_ for _ in self.finding_interventions.all() if _ is not None
|
|
409
|
-
],
|
|
379
|
+
finding_classifications=[_ for _ in self.finding_classifications.all() if _ is not None],
|
|
380
|
+
finding_classification_choices=[_ for _ in self.finding_classification_choices.all() if _ is not None],
|
|
381
|
+
finding_interventions=[_ for _ in self.finding_interventions.all() if _ is not None],
|
|
410
382
|
medications=[_ for _ in self.medications.all() if _ is not None],
|
|
411
|
-
medication_indications=[
|
|
412
|
-
|
|
413
|
-
],
|
|
414
|
-
medication_intake_times=[
|
|
415
|
-
_ for _ in self.medication_intake_times.all() if _ is not None
|
|
416
|
-
],
|
|
383
|
+
medication_indications=[_ for _ in self.medication_indications.all() if _ is not None],
|
|
384
|
+
medication_intake_times=[_ for _ in self.medication_intake_times.all() if _ is not None],
|
|
417
385
|
)
|
|
418
386
|
return models_dict
|
|
419
387
|
|
|
@@ -437,63 +405,7 @@ class Requirement(models.Model):
|
|
|
437
405
|
"""
|
|
438
406
|
return self.links.active()
|
|
439
407
|
|
|
440
|
-
def
|
|
441
|
-
"""Parses the optional ``string_values`` field into a dictionary.
|
|
442
|
-
|
|
443
|
-
Values follow a simple ``key=value`` syntax separated by commas. Entries
|
|
444
|
-
without an equals sign are treated as boolean flags (value ``"true"``).
|
|
445
|
-
"""
|
|
446
|
-
if not self.string_values:
|
|
447
|
-
return {}
|
|
448
|
-
|
|
449
|
-
parsed: Dict[str, str] = {}
|
|
450
|
-
for raw_entry in self.string_values.split(","):
|
|
451
|
-
entry = raw_entry.strip()
|
|
452
|
-
if not entry:
|
|
453
|
-
continue
|
|
454
|
-
if "=" in entry:
|
|
455
|
-
key, value = entry.split("=", 1)
|
|
456
|
-
parsed[key.strip()] = value.strip()
|
|
457
|
-
else:
|
|
458
|
-
parsed[entry] = "true"
|
|
459
|
-
return parsed
|
|
460
|
-
|
|
461
|
-
def _resolve_queryset_config(self, kwargs: Dict) -> tuple[str, int | None]:
|
|
462
|
-
"""Derives queryset evaluation settings from kwargs or ``string_values``.
|
|
463
|
-
|
|
464
|
-
Supported modes:
|
|
465
|
-
- ``any`` (default): at least one item may satisfy the requirement.
|
|
466
|
-
- ``all``: every item in the queryset must satisfy the requirement.
|
|
467
|
-
- ``min_count``/``at_least``/``min``: at least *n* items must satisfy.
|
|
468
|
-
"""
|
|
469
|
-
settings = self._parse_string_values()
|
|
470
|
-
|
|
471
|
-
mode_raw = kwargs.get("queryset_mode") or settings.get("qs_mode") or "any"
|
|
472
|
-
mode = str(mode_raw).strip().lower()
|
|
473
|
-
mode_aliases = {
|
|
474
|
-
"min": "min_count",
|
|
475
|
-
"at_least": "min_count",
|
|
476
|
-
"minimum": "min_count",
|
|
477
|
-
}
|
|
478
|
-
mode = mode_aliases.get(mode, mode)
|
|
479
|
-
if mode not in {"any", "all", "min_count"}:
|
|
480
|
-
mode = "any"
|
|
481
|
-
|
|
482
|
-
min_count_raw = kwargs.get("queryset_min_count")
|
|
483
|
-
if min_count_raw is None:
|
|
484
|
-
for candidate_key in ("qs_min_count", "qs_min", "qs_count", "qs_required"):
|
|
485
|
-
if candidate_key in settings:
|
|
486
|
-
min_count_raw = settings[candidate_key]
|
|
487
|
-
break
|
|
488
|
-
|
|
489
|
-
try:
|
|
490
|
-
min_count = int(min_count_raw) if min_count_raw is not None else None
|
|
491
|
-
except (TypeError, ValueError):
|
|
492
|
-
min_count = None
|
|
493
|
-
|
|
494
|
-
return mode, min_count
|
|
495
|
-
|
|
496
|
-
def evaluate(self, *args, mode: str, **kwargs):
|
|
408
|
+
def evaluate(self, input_obj):
|
|
497
409
|
"""
|
|
498
410
|
Evaluates whether the requirement is satisfied for the given input models using linked operators and gender constraints.
|
|
499
411
|
|
|
@@ -511,230 +423,21 @@ class Requirement(models.Model):
|
|
|
511
423
|
|
|
512
424
|
If the requirement specifies genders, only input containing a patient with a matching gender will be considered valid for evaluation.
|
|
513
425
|
"""
|
|
426
|
+
is_valid: bool = False
|
|
514
427
|
|
|
515
|
-
|
|
516
|
-
_validate_requirement_configuration(self)
|
|
517
|
-
except Exception as e:
|
|
518
|
-
logger.warning(str(e))
|
|
519
|
-
# TODO Review, Optimize or remove
|
|
520
|
-
if mode not in ["strict", "loose"]:
|
|
521
|
-
raise ValueError(f"Invalid mode: {mode}. Use 'strict' or 'loose'.")
|
|
428
|
+
requirement_req_links = self.active_links
|
|
522
429
|
|
|
523
|
-
|
|
430
|
+
# expected_models = self.expected_models
|
|
524
431
|
|
|
525
|
-
|
|
526
|
-
|
|
432
|
+
operator = self.operator
|
|
433
|
+
assert isinstance(operator, RequirementOperator)
|
|
527
434
|
|
|
528
|
-
|
|
529
|
-
has_operators = bool(operators)
|
|
530
|
-
requirement_has_conditions = bool(requirement_req_links.active())
|
|
531
|
-
queryset_mode, queryset_min_count = self._resolve_queryset_config(kwargs)
|
|
435
|
+
operator_instructions = self.operator_instructions_parsed
|
|
532
436
|
|
|
533
|
-
|
|
534
|
-
def _is_expected_instance(obj) -> bool:
|
|
535
|
-
for cls in expected_models:
|
|
536
|
-
if isinstance(cls, type):
|
|
537
|
-
try:
|
|
538
|
-
if isinstance(obj, cls):
|
|
539
|
-
return True
|
|
540
|
-
except Exception:
|
|
541
|
-
# cls might not be a runtime type
|
|
542
|
-
continue
|
|
543
|
-
return False
|
|
544
|
-
|
|
545
|
-
def _is_queryset_of_expected(qs) -> bool:
|
|
546
|
-
if not isinstance(qs, models.QuerySet) or not hasattr(qs, "model"):
|
|
547
|
-
return False
|
|
548
|
-
for cls in expected_models:
|
|
549
|
-
if isinstance(cls, type):
|
|
550
|
-
try:
|
|
551
|
-
if issubclass(qs.model, cls):
|
|
552
|
-
return True
|
|
553
|
-
except Exception:
|
|
554
|
-
continue
|
|
555
|
-
return False
|
|
556
|
-
|
|
557
|
-
# Aggregate RequirementLinks from all input arguments
|
|
558
|
-
aggregated_input_links_data = {}
|
|
559
|
-
processed_inputs_count = 0
|
|
560
|
-
|
|
561
|
-
for _input in args:
|
|
562
|
-
# Check if the input is an instance of any of the expected model types
|
|
563
|
-
if not _is_expected_instance(_input):
|
|
564
|
-
# Allow QuerySets of expected models
|
|
565
|
-
if _is_queryset_of_expected(_input):
|
|
566
|
-
if not _input.exists():
|
|
567
|
-
# Empty queryset: enforce stricter modes immediately
|
|
568
|
-
if queryset_mode == "all":
|
|
569
|
-
return False
|
|
570
|
-
if queryset_mode == "min_count":
|
|
571
|
-
required = (
|
|
572
|
-
queryset_min_count
|
|
573
|
-
if queryset_min_count is not None
|
|
574
|
-
else 1
|
|
575
|
-
)
|
|
576
|
-
if required > 0:
|
|
577
|
-
return False
|
|
578
|
-
continue
|
|
579
|
-
|
|
580
|
-
queryset_results: List[bool] = []
|
|
581
|
-
queryset_true_count = 0
|
|
582
|
-
queryset_item_count = 0
|
|
583
|
-
|
|
584
|
-
for item in _input:
|
|
585
|
-
if not hasattr(item, "links") or not isinstance(
|
|
586
|
-
item.links, RequirementLinks
|
|
587
|
-
):
|
|
588
|
-
raise TypeError(
|
|
589
|
-
f"Item {item} of type {type(item)} in QuerySet does not have a valid .links attribute of type RequirementLinks."
|
|
590
|
-
)
|
|
591
|
-
|
|
592
|
-
item_active_links = item.links.active()
|
|
593
|
-
item_input_links = RequirementLinks(**item_active_links)
|
|
594
|
-
|
|
595
|
-
for link_key, link_list in item_active_links.items():
|
|
596
|
-
if link_key not in aggregated_input_links_data:
|
|
597
|
-
aggregated_input_links_data[link_key] = []
|
|
598
|
-
aggregated_input_links_data[link_key].extend(link_list)
|
|
599
|
-
|
|
600
|
-
per_item_args = tuple(
|
|
601
|
-
item if arg is _input else arg for arg in args
|
|
602
|
-
)
|
|
603
|
-
op_kwargs = kwargs.copy()
|
|
604
|
-
op_kwargs["requirement"] = self
|
|
605
|
-
op_kwargs["original_input_args"] = per_item_args
|
|
606
|
-
|
|
607
|
-
if has_operators:
|
|
608
|
-
item_operator_results: List[bool] = []
|
|
609
|
-
for operator in operators:
|
|
610
|
-
try:
|
|
611
|
-
operator_result = operator.evaluate(
|
|
612
|
-
requirement_links=requirement_req_links,
|
|
613
|
-
input_links=item_input_links,
|
|
614
|
-
**op_kwargs,
|
|
615
|
-
)
|
|
616
|
-
item_operator_results.append(operator_result)
|
|
617
|
-
except Exception as exc:
|
|
618
|
-
logger.debug(
|
|
619
|
-
f"Operator {operator.name} evaluation failed for item {item}: {exc}"
|
|
620
|
-
)
|
|
621
|
-
item_operator_results.append(False)
|
|
622
|
-
item_result = (
|
|
623
|
-
evaluate_result_list_func(item_operator_results)
|
|
624
|
-
if item_operator_results
|
|
625
|
-
else True
|
|
626
|
-
)
|
|
627
|
-
else:
|
|
628
|
-
item_result = not requirement_has_conditions
|
|
629
|
-
|
|
630
|
-
queryset_results.append(item_result)
|
|
631
|
-
if item_result:
|
|
632
|
-
queryset_true_count += 1
|
|
633
|
-
queryset_item_count += 1
|
|
634
|
-
processed_inputs_count += 1
|
|
635
|
-
|
|
636
|
-
if queryset_mode == "all":
|
|
637
|
-
if queryset_item_count == 0 or not all(queryset_results):
|
|
638
|
-
return False
|
|
639
|
-
elif queryset_mode == "min_count":
|
|
640
|
-
required = (
|
|
641
|
-
queryset_min_count if queryset_min_count is not None else 1
|
|
642
|
-
)
|
|
643
|
-
if queryset_true_count < max(required, 0):
|
|
644
|
-
return False
|
|
645
|
-
# queryset_mode == "any" imposes no extra constraint here
|
|
646
|
-
continue # Move to the next arg after processing queryset
|
|
647
|
-
else:
|
|
648
|
-
raise TypeError(
|
|
649
|
-
f"Input type {type(_input)} is not among expected models: {self.expected_models} nor a QuerySet of expected models."
|
|
650
|
-
)
|
|
651
|
-
|
|
652
|
-
# Process single model instance
|
|
653
|
-
if not hasattr(_input, "links") or not isinstance(
|
|
654
|
-
_input.links, RequirementLinks
|
|
655
|
-
):
|
|
656
|
-
raise TypeError(
|
|
657
|
-
f"Input {_input} of type {type(_input)} does not have a valid .links attribute of type RequirementLinks."
|
|
658
|
-
)
|
|
659
|
-
|
|
660
|
-
active_input_links = _input.links.active() # Get dict of non-empty lists
|
|
661
|
-
for link_key, link_list in active_input_links.items():
|
|
662
|
-
if link_key not in aggregated_input_links_data:
|
|
663
|
-
aggregated_input_links_data[link_key] = []
|
|
664
|
-
aggregated_input_links_data[link_key].extend(link_list)
|
|
665
|
-
processed_inputs_count += 1
|
|
666
|
-
|
|
667
|
-
if (
|
|
668
|
-
not processed_inputs_count and args
|
|
669
|
-
): # If args were provided but none were processable (e.g. all empty querysets)
|
|
670
|
-
# This situation implies no relevant data was provided for evaluation against the requirement.
|
|
671
|
-
# Depending on operator logic (e.g., "requires at least one matching item"), this might lead to False.
|
|
672
|
-
# For "models_match_any", an empty input_links will likely result in False if requirement_req_links is not empty.
|
|
673
|
-
pass
|
|
674
|
-
|
|
675
|
-
# Deduplicate items within each list after aggregation
|
|
676
|
-
for key in aggregated_input_links_data:
|
|
677
|
-
try:
|
|
678
|
-
# Using dict.fromkeys to preserve order and remove duplicates for hashable items
|
|
679
|
-
aggregated_input_links_data[key] = list(
|
|
680
|
-
dict.fromkeys(aggregated_input_links_data[key])
|
|
681
|
-
)
|
|
682
|
-
except TypeError:
|
|
683
|
-
# Fallback for non-hashable items (though Django models are hashable)
|
|
684
|
-
temp_list = []
|
|
685
|
-
for item in aggregated_input_links_data[key]:
|
|
686
|
-
if item not in temp_list:
|
|
687
|
-
temp_list.append(item)
|
|
688
|
-
aggregated_input_links_data[key] = temp_list
|
|
689
|
-
|
|
690
|
-
final_input_links = RequirementLinks(**aggregated_input_links_data)
|
|
691
|
-
|
|
692
|
-
# Gender strict check: if this requirement has genders, only pass if patient.gender is in the set
|
|
693
|
-
genders_exist = self.genders.exists()
|
|
694
|
-
if genders_exist:
|
|
695
|
-
# Import here to avoid circular import
|
|
696
|
-
from endoreg_db.models.administration.person.patient import Patient
|
|
697
|
-
|
|
698
|
-
patient = None
|
|
699
|
-
for arg in args:
|
|
700
|
-
if isinstance(arg, Patient):
|
|
701
|
-
patient = arg
|
|
702
|
-
break
|
|
703
|
-
if patient is None or patient.gender is None:
|
|
704
|
-
return False
|
|
705
|
-
if not self.genders.filter(pk=patient.gender.pk).exists():
|
|
706
|
-
return False
|
|
707
|
-
|
|
708
|
-
if (
|
|
709
|
-
not has_operators
|
|
710
|
-
): # If a requirement has no operators, its evaluation is ambiguous.
|
|
711
|
-
if not requirement_has_conditions: # No conditions in requirement
|
|
712
|
-
return True # Vacuously true if requirement itself is empty
|
|
713
|
-
return False # Cannot be satisfied if requirement has conditions but no operators to check them
|
|
714
|
-
|
|
715
|
-
operator_results = []
|
|
716
|
-
for operator in operators:
|
|
717
|
-
# Prepare kwargs for the operator, including the current Requirement instance
|
|
718
|
-
op_kwargs = (
|
|
719
|
-
kwargs.copy()
|
|
720
|
-
) # Start with kwargs passed to Requirement.evaluate
|
|
721
|
-
op_kwargs["requirement"] = self # Add the Requirement instance itself
|
|
722
|
-
op_kwargs["original_input_args"] = (
|
|
723
|
-
args # Add the original input arguments for operators that need them (e.g., age operators)
|
|
724
|
-
)
|
|
725
|
-
operator_results.append(
|
|
726
|
-
operator.evaluate(
|
|
727
|
-
requirement_links=requirement_req_links,
|
|
728
|
-
input_links=final_input_links,
|
|
729
|
-
**op_kwargs,
|
|
730
|
-
)
|
|
731
|
-
)
|
|
732
|
-
|
|
733
|
-
is_valid = evaluate_result_list_func(operator_results)
|
|
437
|
+
is_valid = operator.evaluate(input_links)
|
|
734
438
|
|
|
735
439
|
return is_valid
|
|
736
440
|
|
|
737
|
-
|
|
738
441
|
def evaluate_with_details(self, *args, mode: str, **kwargs) -> Tuple[bool, str]:
|
|
739
442
|
"""
|
|
740
443
|
Evaluates whether the requirement is satisfied for the given input models using linked operators and gender constraints.
|
|
@@ -763,9 +466,7 @@ class Requirement(models.Model):
|
|
|
763
466
|
code="INVALID_MODE",
|
|
764
467
|
technical_message=f"Invalid mode: {mode}. Use 'strict' or 'loose'.",
|
|
765
468
|
user_message=(
|
|
766
|
-
"Diese Voraussetzung ist intern mit einem ungültigen "
|
|
767
|
-
"Bewertungsmodus konfiguriert und kann aktuell nicht "
|
|
768
|
-
"korrekt geprüft werden."
|
|
469
|
+
"Diese Voraussetzung ist intern mit einem ungültigen Bewertungsmodus konfiguriert und kann aktuell nicht korrekt geprüft werden."
|
|
769
470
|
),
|
|
770
471
|
)
|
|
771
472
|
|
|
@@ -816,20 +517,14 @@ class Requirement(models.Model):
|
|
|
816
517
|
if queryset_mode == "all":
|
|
817
518
|
return (
|
|
818
519
|
False,
|
|
819
|
-
"Für diese Voraussetzung müssen alle passenden Einträge vorliegen, "
|
|
820
|
-
"aber es wurden keine entsprechenden Datensätze gefunden.",
|
|
520
|
+
"Für diese Voraussetzung müssen alle passenden Einträge vorliegen, aber es wurden keine entsprechenden Datensätze gefunden.",
|
|
821
521
|
)
|
|
822
522
|
if queryset_mode == "min_count":
|
|
823
|
-
required =
|
|
824
|
-
queryset_min_count
|
|
825
|
-
if queryset_min_count is not None
|
|
826
|
-
else 1
|
|
827
|
-
)
|
|
523
|
+
required = queryset_min_count if queryset_min_count is not None else 1
|
|
828
524
|
if required > 0:
|
|
829
525
|
return (
|
|
830
526
|
False,
|
|
831
|
-
f"Für diese Voraussetzung werden mindestens {required} passende "
|
|
832
|
-
"Einträge benötigt, es wurden jedoch keine gefunden.",
|
|
527
|
+
f"Für diese Voraussetzung werden mindestens {required} passende Einträge benötigt, es wurden jedoch keine gefunden.",
|
|
833
528
|
)
|
|
834
529
|
# queryset_mode == "any" bei leerem QS -> neutral (keine zusätzliche Einschränkung)
|
|
835
530
|
continue
|
|
@@ -839,15 +534,12 @@ class Requirement(models.Model):
|
|
|
839
534
|
queryset_item_count = 0
|
|
840
535
|
|
|
841
536
|
for item in _input:
|
|
842
|
-
if not hasattr(item, "links") or not isinstance(
|
|
843
|
-
item.links, RequirementLinks
|
|
844
|
-
):
|
|
537
|
+
if not hasattr(item, "links") or not isinstance(item.links, RequirementLinks):
|
|
845
538
|
raise RequirementEvaluationError(
|
|
846
539
|
requirement=self,
|
|
847
540
|
code="MISSING_LINKS_ATTR",
|
|
848
541
|
technical_message=(
|
|
849
|
-
f"Item {item} of type {type(item)} in QuerySet does not "
|
|
850
|
-
f"have a valid .links attribute of type RequirementLinks."
|
|
542
|
+
f"Item {item} of type {type(item)} in QuerySet does not have a valid .links attribute of type RequirementLinks."
|
|
851
543
|
),
|
|
852
544
|
user_message=(
|
|
853
545
|
"Für einen Datensatz fehlen die intern benötigten Verknüpfungen, "
|
|
@@ -865,9 +557,7 @@ class Requirement(models.Model):
|
|
|
865
557
|
aggregated_input_links_data[link_key] = []
|
|
866
558
|
aggregated_input_links_data[link_key].extend(link_list)
|
|
867
559
|
|
|
868
|
-
per_item_args = tuple(
|
|
869
|
-
item if arg is _input else arg for arg in args
|
|
870
|
-
)
|
|
560
|
+
per_item_args = tuple(item if arg is _input else arg for arg in args)
|
|
871
561
|
op_kwargs = kwargs.copy()
|
|
872
562
|
op_kwargs["requirement"] = self
|
|
873
563
|
op_kwargs["original_input_args"] = per_item_args
|
|
@@ -890,11 +580,7 @@ class Requirement(models.Model):
|
|
|
890
580
|
exc,
|
|
891
581
|
)
|
|
892
582
|
item_operator_results.append(False)
|
|
893
|
-
item_result = (
|
|
894
|
-
evaluate_result_list_func(item_operator_results)
|
|
895
|
-
if item_operator_results
|
|
896
|
-
else True
|
|
897
|
-
)
|
|
583
|
+
item_result = evaluate_result_list_func(item_operator_results) if item_operator_results else True
|
|
898
584
|
else:
|
|
899
585
|
# keine Operatoren -> Bedingung erfüllt, wenn Requirement selbst keine Bedingungen hat
|
|
900
586
|
item_result = not requirement_has_conditions
|
|
@@ -913,14 +599,11 @@ class Requirement(models.Model):
|
|
|
913
599
|
"Für diese Voraussetzung müssen alle relevanten Einträge die Bedingung erfüllen.",
|
|
914
600
|
)
|
|
915
601
|
elif queryset_mode == "min_count":
|
|
916
|
-
required =
|
|
917
|
-
queryset_min_count if queryset_min_count is not None else 1
|
|
918
|
-
)
|
|
602
|
+
required = queryset_min_count if queryset_min_count is not None else 1
|
|
919
603
|
if queryset_true_count < max(required, 0):
|
|
920
604
|
return (
|
|
921
605
|
False,
|
|
922
|
-
f"Für diese Voraussetzung werden mindestens {max(required, 0)} "
|
|
923
|
-
f"passende Einträge benötigt (gefunden: {queryset_true_count}).",
|
|
606
|
+
f"Für diese Voraussetzung werden mindestens {max(required, 0)} passende Einträge benötigt (gefunden: {queryset_true_count}).",
|
|
924
607
|
)
|
|
925
608
|
# queryset_mode == "any": keine zusätzliche Einschränkung
|
|
926
609
|
continue
|
|
@@ -929,32 +612,18 @@ class Requirement(models.Model):
|
|
|
929
612
|
raise RequirementEvaluationError(
|
|
930
613
|
requirement=self,
|
|
931
614
|
code="INVALID_INPUT_TYPE",
|
|
932
|
-
technical_message=(
|
|
933
|
-
|
|
934
|
-
f"{self.expected_models} nor a QuerySet of expected models."
|
|
935
|
-
),
|
|
936
|
-
user_message=(
|
|
937
|
-
"Diese Voraussetzung wurde mit einem nicht passenden Datentyp "
|
|
938
|
-
"aufgerufen und kann aktuell nicht korrekt geprüft werden."
|
|
939
|
-
),
|
|
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."),
|
|
940
617
|
meta={"input_type": str(type(_input))},
|
|
941
618
|
)
|
|
942
619
|
|
|
943
620
|
# Einzelinstanz erwarteten Typs
|
|
944
|
-
if not hasattr(_input, "links") or not isinstance(
|
|
945
|
-
_input.links, RequirementLinks
|
|
946
|
-
):
|
|
621
|
+
if not hasattr(_input, "links") or not isinstance(_input.links, RequirementLinks):
|
|
947
622
|
raise RequirementEvaluationError(
|
|
948
623
|
requirement=self,
|
|
949
624
|
code="MISSING_LINKS_ATTR",
|
|
950
|
-
technical_message=(
|
|
951
|
-
|
|
952
|
-
f".links attribute of type RequirementLinks."
|
|
953
|
-
),
|
|
954
|
-
user_message=(
|
|
955
|
-
"Für die Auswertung dieser Voraussetzung fehlen die intern "
|
|
956
|
-
"benötigten Verknüpfungsinformationen."
|
|
957
|
-
),
|
|
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."),
|
|
958
627
|
meta={"input_type": str(type(_input))},
|
|
959
628
|
)
|
|
960
629
|
|
|
@@ -971,9 +640,7 @@ class Requirement(models.Model):
|
|
|
971
640
|
# Deduplizieren der aggregierten Links
|
|
972
641
|
for key in aggregated_input_links_data:
|
|
973
642
|
try:
|
|
974
|
-
aggregated_input_links_data[key] = list(
|
|
975
|
-
dict.fromkeys(aggregated_input_links_data[key])
|
|
976
|
-
)
|
|
643
|
+
aggregated_input_links_data[key] = list(dict.fromkeys(aggregated_input_links_data[key]))
|
|
977
644
|
except TypeError:
|
|
978
645
|
# Fallback für nicht-hashbare Items
|
|
979
646
|
tmp: list = []
|
|
@@ -1004,8 +671,7 @@ class Requirement(models.Model):
|
|
|
1004
671
|
if not self.genders.filter(pk=patient.gender.pk).exists():
|
|
1005
672
|
return (
|
|
1006
673
|
False,
|
|
1007
|
-
"Diese Voraussetzung gilt nur für bestimmte Geschlechter und ist "
|
|
1008
|
-
"für diesen Patienten nicht erfüllt.",
|
|
674
|
+
"Diese Voraussetzung gilt nur für bestimmte Geschlechter und ist für diesen Patienten nicht erfüllt.",
|
|
1009
675
|
)
|
|
1010
676
|
|
|
1011
677
|
# --- Fall: keine Operatoren -----------------------------------------
|
|
@@ -1033,9 +699,7 @@ class Requirement(models.Model):
|
|
|
1033
699
|
**op_kwargs,
|
|
1034
700
|
)
|
|
1035
701
|
operator_results.append(operator_result)
|
|
1036
|
-
operator_details.append(
|
|
1037
|
-
f"{operator.name}: {'erfüllt' if operator_result else 'nicht erfüllt'}"
|
|
1038
|
-
)
|
|
702
|
+
operator_details.append(f"{operator.name}: {'erfüllt' if operator_result else 'nicht erfüllt'}")
|
|
1039
703
|
except Exception as e:
|
|
1040
704
|
operator_results.append(False)
|
|
1041
705
|
operator_details.append(f"{operator.name}: technischer Fehler ({e})")
|
|
@@ -1054,11 +718,7 @@ class Requirement(models.Model):
|
|
|
1054
718
|
elif len(operator_results) == 1:
|
|
1055
719
|
details = operator_details[0]
|
|
1056
720
|
else:
|
|
1057
|
-
failed_details = [
|
|
1058
|
-
detail
|
|
1059
|
-
for detail, result in zip(operator_details, operator_results)
|
|
1060
|
-
if not result
|
|
1061
|
-
]
|
|
721
|
+
failed_details = [detail for detail, result in zip(operator_details, operator_results) if not result]
|
|
1062
722
|
if failed_details:
|
|
1063
723
|
details = "; ".join(failed_details)
|
|
1064
724
|
else:
|
|
@@ -1072,4 +732,4 @@ class Requirement(models.Model):
|
|
|
1072
732
|
# nicht kritisch
|
|
1073
733
|
pass
|
|
1074
734
|
|
|
1075
|
-
return bool(is_valid), details
|
|
735
|
+
return bool(is_valid), details
|