endoreg-db 0.8.6.1__py3-none-any.whl → 0.8.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of endoreg-db might be problematic. Click here for more details.
- endoreg_db/authz/auth.py +74 -0
- endoreg_db/authz/backends.py +168 -0
- endoreg_db/authz/management/commands/list_routes.py +18 -0
- endoreg_db/authz/middleware.py +83 -0
- endoreg_db/authz/permissions.py +127 -0
- endoreg_db/authz/policy.py +218 -0
- endoreg_db/authz/views_auth.py +66 -0
- endoreg_db/config/env.py +13 -8
- endoreg_db/data/__init__.py +8 -31
- endoreg_db/data/_examples/disease.yaml +55 -0
- endoreg_db/data/_examples/disease_classification.yaml +13 -0
- endoreg_db/data/_examples/disease_classification_choice.yaml +62 -0
- endoreg_db/data/_examples/event.yaml +64 -0
- endoreg_db/data/_examples/examination.yaml +72 -0
- endoreg_db/data/_examples/finding/anatomy_colon.yaml +128 -0
- endoreg_db/data/_examples/finding/colonoscopy.yaml +40 -0
- endoreg_db/data/_examples/finding/colonoscopy_bowel_prep.yaml +56 -0
- endoreg_db/data/_examples/finding/complication.yaml +16 -0
- endoreg_db/data/_examples/finding/data.yaml +105 -0
- endoreg_db/data/_examples/finding/examination_setting.yaml +16 -0
- endoreg_db/data/_examples/finding/medication_related.yaml +18 -0
- endoreg_db/data/_examples/finding/outcome.yaml +12 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_bowel_preparation.yaml +68 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_jnet.yaml +22 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_kudo.yaml +25 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_circularity.yaml +20 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_planarity.yaml +24 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_size.yaml +68 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lesion_surface.yaml +20 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_location.yaml +80 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_lst.yaml +21 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_nice.yaml +20 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_paris.yaml +26 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_sano.yaml +22 -0
- endoreg_db/data/_examples/finding_classification/colonoscopy_summary.yaml +53 -0
- endoreg_db/data/_examples/finding_classification/complication_generic.yaml +25 -0
- endoreg_db/data/_examples/finding_classification/examination_setting_generic.yaml +40 -0
- endoreg_db/data/_examples/finding_classification/histology_colo.yaml +51 -0
- endoreg_db/data/_examples/finding_classification/intervention_required.yaml +26 -0
- endoreg_db/data/_examples/finding_classification/medication_related.yaml +23 -0
- endoreg_db/data/_examples/finding_classification/visualized.yaml +33 -0
- endoreg_db/data/_examples/finding_classification_choice/bowel_preparation.yaml +78 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_circularity_default.yaml +32 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_jnet.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_kudo.yaml +23 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_lst.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_nice.yaml +17 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_paris.yaml +57 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_planarity_default.yaml +49 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_sano.yaml +14 -0
- endoreg_db/data/_examples/finding_classification_choice/colon_lesion_surface_intact_default.yaml +36 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_location.yaml +229 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_not_complete_reason.yaml +19 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_size.yaml +82 -0
- endoreg_db/data/_examples/finding_classification_choice/colonoscopy_summary_worst_finding.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/complication_generic_types.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/examination_setting_generic_types.yaml +15 -0
- endoreg_db/data/_examples/finding_classification_choice/histology.yaml +24 -0
- endoreg_db/data/_examples/finding_classification_choice/histology_polyp.yaml +20 -0
- endoreg_db/data/_examples/finding_classification_choice/outcome.yaml +19 -0
- endoreg_db/data/_examples/finding_classification_choice/yes_no_na.yaml +11 -0
- endoreg_db/data/_examples/finding_classification_type/colonoscopy_basic.yaml +48 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy.yaml +43 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_colonoscopy.yaml +168 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_egd.yaml +128 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_ercp.yaml +32 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_eus_lower.yaml +9 -0
- endoreg_db/data/_examples/finding_intervention/endoscopy_eus_upper.yaml +36 -0
- endoreg_db/data/_examples/finding_intervention_type/endoscopy.yaml +15 -0
- endoreg_db/data/_examples/finding_type/data.yaml +43 -0
- endoreg_db/data/_examples/requirement/age.yaml +26 -0
- endoreg_db/data/_examples/requirement/colonoscopy_baseline_austria.yaml +45 -0
- endoreg_db/data/_examples/requirement/disease_cardiovascular.yaml +79 -0
- endoreg_db/data/_examples/requirement/disease_classification_choice_cardiovascular.yaml +41 -0
- endoreg_db/data/_examples/requirement/disease_hepatology.yaml +12 -0
- endoreg_db/data/_examples/requirement/disease_misc.yaml +12 -0
- endoreg_db/data/_examples/requirement/disease_renal.yaml +96 -0
- endoreg_db/data/_examples/requirement/endoscopy_bleeding_risk.yaml +59 -0
- endoreg_db/data/_examples/requirement/event_cardiology.yaml +251 -0
- endoreg_db/data/_examples/requirement/event_requirements.yaml +145 -0
- endoreg_db/data/_examples/requirement/finding_colon_polyp.yaml +50 -0
- endoreg_db/data/_examples/requirement/gender.yaml +25 -0
- endoreg_db/data/_examples/requirement/lab_value.yaml +441 -0
- endoreg_db/data/_examples/requirement/medication.yaml +93 -0
- endoreg_db/data/_examples/requirement_operator/age.yaml +13 -0
- endoreg_db/data/_examples/requirement_operator/lab_operators.yaml +129 -0
- endoreg_db/data/_examples/requirement_operator/model_operators.yaml +96 -0
- endoreg_db/data/_examples/requirement_set/01_endoscopy_generic.yaml +48 -0
- endoreg_db/data/_examples/requirement_set/colonoscopy_austria_screening.yaml +57 -0
- endoreg_db/data/_examples/yaml_examples.xlsx +0 -0
- endoreg_db/data/ai_model_meta/default_multilabel_classification.yaml +4 -3
- endoreg_db/data/event_classification/data.yaml +4 -0
- endoreg_db/data/event_classification_choice/data.yaml +9 -0
- endoreg_db/data/finding_classification/colonoscopy_bowel_preparation.yaml +43 -70
- endoreg_db/data/finding_classification/colonoscopy_lesion_size.yaml +22 -52
- endoreg_db/data/finding_classification/colonoscopy_location.yaml +31 -62
- endoreg_db/data/finding_classification/histology_colo.yaml +28 -36
- endoreg_db/data/requirement/colon_polyp_intervention.yaml +49 -0
- endoreg_db/data/requirement/coloreg_colon_polyp.yaml +49 -0
- endoreg_db/data/requirement_set/01_endoscopy_generic.yaml +31 -12
- endoreg_db/data/requirement_set/01_laboratory.yaml +13 -0
- endoreg_db/data/requirement_set/02_endoscopy_bleeding_risk.yaml +46 -0
- endoreg_db/data/requirement_set/90_coloreg.yaml +178 -0
- endoreg_db/data/requirement_set/_old_ +109 -0
- endoreg_db/data/requirement_set_type/data.yaml +21 -0
- endoreg_db/data/setup_config.yaml +4 -4
- endoreg_db/data/tag/requirement_set_tags.yaml +21 -0
- endoreg_db/exceptions.py +5 -2
- endoreg_db/helpers/data_loader.py +1 -1
- endoreg_db/management/commands/create_model_meta_from_huggingface.py +21 -10
- endoreg_db/management/commands/create_multilabel_model_meta.py +299 -129
- endoreg_db/management/commands/import_video.py +9 -10
- endoreg_db/management/commands/import_video_with_classification.py +1 -1
- endoreg_db/management/commands/init_default_ai_model.py +1 -1
- endoreg_db/management/commands/list_routes.py +18 -0
- endoreg_db/management/commands/load_center_data.py +12 -12
- endoreg_db/management/commands/load_requirement_data.py +60 -31
- endoreg_db/management/commands/load_requirement_set_tags.py +95 -0
- endoreg_db/management/commands/setup_endoreg_db.py +3 -3
- endoreg_db/management/commands/storage_management.py +271 -203
- endoreg_db/migrations/0001_initial.py +1799 -1300
- endoreg_db/migrations/0002_requirementset_depends_on.py +18 -0
- endoreg_db/migrations/_old/0001_initial.py +1857 -0
- endoreg_db/migrations/_old/0004_employee_city_employee_post_code_employee_street_and_more.py +68 -0
- endoreg_db/migrations/_old/0004_remove_casetemplate_rules_and_more.py +77 -0
- endoreg_db/migrations/_old/0005_merge_20251111_1003.py +14 -0
- endoreg_db/migrations/_old/0006_sensitivemeta_anonymized_text_and_more.py +68 -0
- endoreg_db/migrations/_old/0007_remove_rule_attribute_dtype_remove_rule_rule_type_and_more.py +89 -0
- endoreg_db/migrations/_old/0008_remove_event_event_classification_and_more.py +27 -0
- endoreg_db/migrations/_old/0009_alter_modelmeta_options_and_more.py +21 -0
- endoreg_db/models/__init__.py +78 -123
- endoreg_db/models/administration/__init__.py +21 -42
- endoreg_db/models/administration/ai/active_model.py +2 -2
- endoreg_db/models/administration/ai/ai_model.py +7 -6
- endoreg_db/models/administration/case/__init__.py +1 -15
- endoreg_db/models/administration/case/case.py +3 -3
- endoreg_db/models/administration/case/case_template/__init__.py +2 -14
- endoreg_db/models/administration/case/case_template/case_template.py +2 -124
- endoreg_db/models/administration/case/case_template/case_template_rule.py +2 -268
- endoreg_db/models/administration/case/case_template/case_template_rule_value.py +2 -85
- endoreg_db/models/administration/case/case_template/case_template_type.py +2 -25
- endoreg_db/models/administration/center/center.py +33 -19
- endoreg_db/models/administration/center/center_product.py +12 -9
- endoreg_db/models/administration/center/center_resource.py +25 -19
- endoreg_db/models/administration/center/center_shift.py +21 -17
- endoreg_db/models/administration/center/center_waste.py +16 -8
- endoreg_db/models/administration/person/__init__.py +2 -0
- endoreg_db/models/administration/person/employee/employee.py +10 -5
- endoreg_db/models/administration/person/employee/employee_qualification.py +9 -4
- endoreg_db/models/administration/person/employee/employee_type.py +12 -6
- endoreg_db/models/administration/person/examiner/examiner.py +13 -11
- endoreg_db/models/administration/person/patient/__init__.py +2 -0
- endoreg_db/models/administration/person/patient/patient.py +103 -100
- endoreg_db/models/administration/person/patient/patient_external_id.py +37 -0
- endoreg_db/models/administration/person/person.py +4 -0
- endoreg_db/models/administration/person/profession/__init__.py +8 -4
- endoreg_db/models/administration/person/user/portal_user_information.py +11 -7
- endoreg_db/models/administration/product/product.py +20 -15
- endoreg_db/models/administration/product/product_material.py +17 -18
- endoreg_db/models/administration/product/product_weight.py +12 -8
- endoreg_db/models/administration/product/reference_product.py +23 -55
- endoreg_db/models/administration/qualification/qualification.py +7 -3
- endoreg_db/models/administration/qualification/qualification_type.py +7 -3
- endoreg_db/models/administration/shift/scheduled_days.py +8 -5
- endoreg_db/models/administration/shift/shift.py +16 -12
- endoreg_db/models/administration/shift/shift_type.py +23 -31
- endoreg_db/models/label/__init__.py +7 -8
- endoreg_db/models/label/annotation/image_classification.py +10 -9
- endoreg_db/models/label/annotation/video_segmentation_annotation.py +8 -5
- endoreg_db/models/label/label.py +15 -15
- endoreg_db/models/label/label_set.py +19 -6
- endoreg_db/models/label/label_type.py +1 -1
- endoreg_db/models/label/label_video_segment/_create_from_video.py +5 -8
- endoreg_db/models/label/label_video_segment/label_video_segment.py +76 -102
- endoreg_db/models/label/video_segmentation_label.py +4 -0
- endoreg_db/models/label/video_segmentation_labelset.py +4 -3
- endoreg_db/models/media/frame/frame.py +22 -22
- endoreg_db/models/media/pdf/raw_pdf.py +110 -182
- endoreg_db/models/media/pdf/report_file.py +25 -29
- endoreg_db/models/media/pdf/report_reader/report_reader_config.py +30 -46
- endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +23 -7
- endoreg_db/models/media/video/__init__.py +1 -0
- endoreg_db/models/media/video/create_from_file.py +48 -56
- endoreg_db/models/media/video/pipe_2.py +8 -9
- endoreg_db/models/media/video/video_file.py +150 -108
- endoreg_db/models/media/video/video_file_ai.py +288 -74
- endoreg_db/models/media/video/video_file_anonymize.py +38 -38
- endoreg_db/models/media/video/video_file_frames/__init__.py +3 -1
- endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +7 -9
- endoreg_db/models/media/video/video_file_frames/_delete_frames.py +9 -8
- endoreg_db/models/media/video/video_file_frames/_extract_frames.py +38 -45
- endoreg_db/models/media/video/video_file_frames/_get_frame.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_get_frame_number.py +4 -18
- endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -3
- endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +7 -6
- endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_get_frames.py +6 -8
- endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +15 -25
- endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +26 -23
- endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +23 -14
- endoreg_db/models/media/video/video_file_io.py +109 -62
- endoreg_db/models/media/video/video_file_meta/get_crop_template.py +3 -3
- endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +5 -3
- endoreg_db/models/media/video/video_file_meta/get_fps.py +37 -34
- endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +19 -25
- endoreg_db/models/media/video/video_file_meta/text_meta.py +41 -38
- endoreg_db/models/media/video/video_file_meta/video_meta.py +14 -7
- endoreg_db/models/media/video/video_file_segments.py +24 -17
- endoreg_db/models/media/video/video_metadata.py +19 -35
- endoreg_db/models/media/video/video_processing.py +96 -95
- endoreg_db/models/medical/contraindication/__init__.py +13 -3
- endoreg_db/models/medical/disease.py +22 -16
- endoreg_db/models/medical/event.py +31 -18
- endoreg_db/models/medical/examination/__init__.py +13 -6
- endoreg_db/models/medical/examination/examination.py +17 -18
- endoreg_db/models/medical/examination/examination_indication.py +26 -25
- endoreg_db/models/medical/examination/examination_time.py +16 -6
- endoreg_db/models/medical/examination/examination_time_type.py +9 -6
- endoreg_db/models/medical/examination/examination_type.py +3 -4
- endoreg_db/models/medical/finding/finding.py +38 -39
- endoreg_db/models/medical/finding/finding_classification.py +37 -48
- endoreg_db/models/medical/finding/finding_intervention.py +27 -22
- endoreg_db/models/medical/finding/finding_type.py +13 -12
- endoreg_db/models/medical/hardware/endoscope.py +20 -26
- endoreg_db/models/medical/hardware/endoscopy_processor.py +2 -2
- endoreg_db/models/medical/laboratory/lab_value.py +62 -91
- endoreg_db/models/medical/medication/medication.py +22 -10
- endoreg_db/models/medical/medication/medication_indication.py +29 -3
- endoreg_db/models/medical/medication/medication_indication_type.py +25 -14
- endoreg_db/models/medical/medication/medication_intake_time.py +31 -19
- endoreg_db/models/medical/medication/medication_schedule.py +27 -16
- endoreg_db/models/medical/organ/__init__.py +15 -12
- endoreg_db/models/medical/patient/medication_examples.py +1 -5
- endoreg_db/models/medical/patient/patient_disease.py +20 -23
- endoreg_db/models/medical/patient/patient_event.py +19 -22
- endoreg_db/models/medical/patient/patient_examination.py +48 -54
- endoreg_db/models/medical/patient/patient_examination_indication.py +16 -14
- endoreg_db/models/medical/patient/patient_finding.py +122 -139
- endoreg_db/models/medical/patient/patient_finding_classification.py +44 -49
- endoreg_db/models/medical/patient/patient_finding_intervention.py +8 -19
- endoreg_db/models/medical/patient/patient_lab_sample.py +28 -23
- endoreg_db/models/medical/patient/patient_lab_value.py +82 -89
- endoreg_db/models/medical/patient/patient_medication.py +27 -38
- endoreg_db/models/medical/patient/patient_medication_schedule.py +28 -36
- endoreg_db/models/medical/risk/risk.py +7 -6
- endoreg_db/models/medical/risk/risk_type.py +8 -5
- endoreg_db/models/metadata/model_meta.py +60 -29
- endoreg_db/models/metadata/model_meta_logic.py +125 -18
- endoreg_db/models/metadata/pdf_meta.py +19 -24
- endoreg_db/models/metadata/sensitive_meta.py +102 -85
- endoreg_db/models/metadata/sensitive_meta_logic.py +192 -173
- endoreg_db/models/metadata/video_meta.py +51 -31
- endoreg_db/models/metadata/video_prediction_logic.py +16 -23
- endoreg_db/models/metadata/video_prediction_meta.py +29 -33
- endoreg_db/models/other/distribution/date_value_distribution.py +89 -29
- endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +21 -5
- endoreg_db/models/other/distribution/numeric_value_distribution.py +114 -53
- endoreg_db/models/other/distribution/single_categorical_value_distribution.py +4 -3
- endoreg_db/models/other/emission/emission_factor.py +18 -8
- endoreg_db/models/other/gender.py +10 -5
- endoreg_db/models/other/information_source.py +25 -25
- endoreg_db/models/other/material.py +9 -5
- endoreg_db/models/other/resource.py +6 -4
- endoreg_db/models/other/tag.py +10 -5
- endoreg_db/models/other/transport_route.py +13 -8
- endoreg_db/models/other/unit.py +10 -6
- endoreg_db/models/other/waste.py +6 -5
- endoreg_db/models/requirement/requirement.py +580 -272
- endoreg_db/models/requirement/requirement_error.py +85 -0
- endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +268 -0
- endoreg_db/models/requirement/requirement_evaluation/operator_evaluation_models.py +3 -6
- endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +90 -64
- endoreg_db/models/requirement/requirement_operator.py +36 -33
- endoreg_db/models/requirement/requirement_set.py +74 -57
- endoreg_db/models/state/__init__.py +4 -4
- endoreg_db/models/state/abstract.py +2 -2
- endoreg_db/models/state/anonymization.py +12 -0
- endoreg_db/models/state/audit_ledger.py +46 -47
- endoreg_db/models/state/label_video_segment.py +9 -0
- endoreg_db/models/state/raw_pdf.py +40 -46
- endoreg_db/models/state/sensitive_meta.py +6 -2
- endoreg_db/models/state/video.py +58 -53
- endoreg_db/models/upload_job.py +32 -55
- endoreg_db/models/utils.py +1 -2
- endoreg_db/root_urls.py +21 -2
- endoreg_db/serializers/__init__.py +0 -2
- endoreg_db/serializers/anonymization.py +18 -10
- endoreg_db/serializers/meta/report_meta.py +1 -1
- endoreg_db/serializers/meta/sensitive_meta_detail.py +63 -118
- endoreg_db/serializers/misc/file_overview.py +11 -99
- endoreg_db/serializers/requirements/requirement_sets.py +92 -22
- endoreg_db/serializers/video/segmentation.py +2 -1
- endoreg_db/serializers/video/video_processing_history.py +20 -5
- endoreg_db/services/anonymization.py +75 -73
- endoreg_db/services/lookup_service.py +37 -24
- endoreg_db/services/pdf_import.py +166 -68
- endoreg_db/services/storage_aware_video_processor.py +140 -114
- endoreg_db/services/video_import.py +193 -283
- endoreg_db/urls/__init__.py +7 -20
- endoreg_db/urls/media.py +108 -67
- endoreg_db/urls/root_urls.py +29 -0
- endoreg_db/utils/__init__.py +15 -5
- endoreg_db/utils/ai/multilabel_classification_net.py +116 -20
- endoreg_db/utils/case_generator/__init__.py +3 -0
- endoreg_db/utils/dataloader.py +88 -16
- endoreg_db/utils/defaults/set_default_center.py +32 -0
- endoreg_db/utils/names.py +22 -16
- endoreg_db/utils/permissions.py +2 -1
- endoreg_db/utils/pipelines/process_video_dir.py +1 -1
- endoreg_db/utils/requirement_operator_logic/model_evaluators.py +414 -127
- endoreg_db/utils/setup_config.py +8 -5
- endoreg_db/utils/storage.py +115 -0
- endoreg_db/utils/validate_endo_roi.py +8 -2
- endoreg_db/utils/video/ffmpeg_wrapper.py +184 -188
- endoreg_db/views/__init__.py +0 -10
- endoreg_db/views/anonymization/media_management.py +198 -163
- endoreg_db/views/anonymization/overview.py +4 -1
- endoreg_db/views/anonymization/validate.py +174 -40
- endoreg_db/views/media/__init__.py +2 -0
- endoreg_db/views/media/pdf_media.py +131 -152
- endoreg_db/views/media/sensitive_metadata.py +46 -6
- endoreg_db/views/media/video_media.py +89 -82
- endoreg_db/views/media/video_segments.py +2 -3
- endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
- endoreg_db/views/patient/patient.py +5 -4
- endoreg_db/views/pdf/pdf_stream.py +20 -21
- endoreg_db/views/pdf/reimport.py +11 -32
- endoreg_db/views/requirement/evaluate.py +188 -187
- endoreg_db/views/requirement/lookup.py +17 -3
- endoreg_db/views/requirement/requirement_utils.py +89 -0
- endoreg_db/views/video/__init__.py +0 -2
- endoreg_db/views/video/correction.py +2 -2
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +341 -245
- endoreg_db/models/administration/permissions/__init__.py +0 -44
- endoreg_db/models/media/video/video_file_frames.py +0 -0
- endoreg_db/models/metadata/frame_ocr_result.py +0 -0
- endoreg_db/models/rule/__init__.py +0 -13
- endoreg_db/models/rule/rule.py +0 -27
- endoreg_db/models/rule/rule_applicator.py +0 -224
- endoreg_db/models/rule/rule_attribute_dtype.py +0 -17
- endoreg_db/models/rule/rule_type.py +0 -20
- endoreg_db/models/rule/ruleset.py +0 -17
- endoreg_db/serializers/video/video_metadata.py +0 -105
- endoreg_db/urls/report.py +0 -48
- endoreg_db/urls/video.py +0 -61
- endoreg_db/utils/case_generator/case_generator.py +0 -159
- endoreg_db/utils/case_generator/utils.py +0 -30
- endoreg_db/views/report/__init__.py +0 -9
- endoreg_db/views/report/report_list.py +0 -112
- endoreg_db/views/report/report_with_secure_url.py +0 -28
- endoreg_db/views/report/start_examination.py +0 -7
- endoreg_db/views.py +0 -0
- /endoreg_db/data/{requirement_set → _examples/requirement_set}/endoscopy_bleeding_risk.yaml +0 -0
- /endoreg_db/migrations/{0002_add_video_correction_models.py → _old/0002_add_video_correction_models.py} +0 -0
- /endoreg_db/migrations/{0003_add_center_display_name.py → _old/0003_add_center_display_name.py} +0 -0
- /endoreg_db/{models/media/video/refactor_plan.md → views/pdf/pdf_stream_views.py} +0 -0
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.6.1.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,14 +1,80 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import calendar
|
|
2
|
+
import datetime
|
|
3
|
+
from datetime import timedelta
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
if TYPE_CHECKING:
|
|
6
|
-
from endoreg_db.utils.links.requirement_link import RequirementLinks
|
|
7
7
|
from endoreg_db.models.requirement.requirement import Requirement
|
|
8
|
+
from endoreg_db.utils.links.requirement_link import RequirementLinks
|
|
8
9
|
# from endoreg_db.models import Unit # Potentially needed for dynamic unit handling
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
# Helper function to check if a date is within the timeframe specified by a Requirement
|
|
13
|
+
def _normalize_timeframe_unit(requirement: "Requirement") -> str | None:
|
|
14
|
+
"""Derives a canonical timeframe unit string from the requirement."""
|
|
15
|
+
if not requirement.unit:
|
|
16
|
+
return None
|
|
17
|
+
|
|
18
|
+
name = (requirement.unit.name or "").strip().lower()
|
|
19
|
+
abbreviation = (requirement.unit.abbreviation or "").strip().lower()
|
|
20
|
+
|
|
21
|
+
unit_aliases = {
|
|
22
|
+
"hour": "hours",
|
|
23
|
+
"hours": "hours",
|
|
24
|
+
"h": "hours",
|
|
25
|
+
"day": "days",
|
|
26
|
+
"days": "days",
|
|
27
|
+
"d": "days",
|
|
28
|
+
"week": "weeks",
|
|
29
|
+
"weeks": "weeks",
|
|
30
|
+
"w": "weeks",
|
|
31
|
+
"month": "months",
|
|
32
|
+
"months": "months",
|
|
33
|
+
"m": "months",
|
|
34
|
+
"year": "years",
|
|
35
|
+
"years": "years",
|
|
36
|
+
"y": "years",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if name in unit_aliases:
|
|
40
|
+
return unit_aliases[name]
|
|
41
|
+
if abbreviation in unit_aliases:
|
|
42
|
+
return unit_aliases[abbreviation]
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _add_months(base_date: datetime.date, months: int) -> datetime.date:
|
|
47
|
+
"""Shifts ``base_date`` by the given number of months, clamping the day when needed."""
|
|
48
|
+
if months == 0:
|
|
49
|
+
return base_date
|
|
50
|
+
|
|
51
|
+
total_months = base_date.month - 1 + months
|
|
52
|
+
year = base_date.year + total_months // 12
|
|
53
|
+
month = total_months % 12 + 1
|
|
54
|
+
day = min(base_date.day, calendar.monthrange(year, month)[1])
|
|
55
|
+
return base_date.replace(year=year, month=month, day=day)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _shift_date_by_unit(base_date: datetime.date, value: int, unit: str) -> datetime.date:
|
|
59
|
+
"""Returns ``base_date`` shifted by ``value`` units using the supported unit set.
|
|
60
|
+
|
|
61
|
+
Hour-level offsets are not supported because ``base_date`` is a ``date``
|
|
62
|
+
object without time information; callers must switch to datetime-aware
|
|
63
|
+
handling before requesting hourly shifts.
|
|
64
|
+
"""
|
|
65
|
+
if unit == "days":
|
|
66
|
+
return base_date + timedelta(days=value)
|
|
67
|
+
if unit == "hours":
|
|
68
|
+
raise NotImplementedError("Hour-level timeframe comparisons require datetime-aware inputs; _shift_date_by_unit only operates on date objects.")
|
|
69
|
+
if unit == "weeks":
|
|
70
|
+
return base_date + timedelta(weeks=value)
|
|
71
|
+
if unit == "months":
|
|
72
|
+
return _add_months(base_date, value)
|
|
73
|
+
if unit == "years":
|
|
74
|
+
return _add_months(base_date, value * 12)
|
|
75
|
+
raise NotImplementedError(f"Timeframe unit '{unit}' is not supported.")
|
|
76
|
+
|
|
77
|
+
|
|
12
78
|
def _is_date_in_timeframe(date_to_check: datetime.date | None, requirement: "Requirement") -> bool:
|
|
13
79
|
"""
|
|
14
80
|
Checks if a given date falls within the timeframe specified by a Requirement.
|
|
@@ -16,8 +82,9 @@ def _is_date_in_timeframe(date_to_check: datetime.date | None, requirement: "Req
|
|
|
16
82
|
The timeframe is defined by `numeric_value_min` and `numeric_value_max` on the
|
|
17
83
|
Requirement, interpreted relative to the current date.
|
|
18
84
|
|
|
19
|
-
Currently
|
|
20
|
-
|
|
85
|
+
Currently supports day/week/month/year windows. Hour-level units are
|
|
86
|
+
explicitly rejected because the timeframe calculations operate on dates
|
|
87
|
+
rather than datetimes.
|
|
21
88
|
|
|
22
89
|
Args:
|
|
23
90
|
date_to_check: The date to evaluate. If None, returns False.
|
|
@@ -30,44 +97,39 @@ def _is_date_in_timeframe(date_to_check: datetime.date | None, requirement: "Req
|
|
|
30
97
|
necessary timeframe information (unit, min/max values).
|
|
31
98
|
|
|
32
99
|
Raises:
|
|
33
|
-
NotImplementedError: If the requirement.unit
|
|
100
|
+
NotImplementedError: If the requirement.unit resolves to an unsupported
|
|
101
|
+
unit (e.g. hours) or cannot be normalised.
|
|
34
102
|
"""
|
|
35
103
|
if date_to_check is None:
|
|
36
104
|
return False
|
|
37
|
-
if
|
|
38
|
-
return False
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if
|
|
42
|
-
raise NotImplementedError(
|
|
43
|
-
f"Timeframe unit '{requirement.unit.name}' is not supported. "
|
|
44
|
-
"Currently, only 'days' is implemented for timeframe checks."
|
|
45
|
-
)
|
|
105
|
+
if requirement.numeric_value_min is None or requirement.numeric_value_max is None:
|
|
106
|
+
return False # Not enough information for timeframe evaluation
|
|
107
|
+
|
|
108
|
+
unit = _normalize_timeframe_unit(requirement)
|
|
109
|
+
if not unit:
|
|
110
|
+
raise NotImplementedError("Timeframe unit could not be resolved from requirement's Unit name/abbreviation.")
|
|
46
111
|
|
|
47
112
|
today = datetime.date.today()
|
|
48
|
-
# numeric_value_min is typically negative for "days ago" (e.g., -30 for 30 days ago)
|
|
49
|
-
# numeric_value_max is typically 0 for "today"
|
|
50
113
|
timeframe_start_delta = int(requirement.numeric_value_min)
|
|
51
114
|
timeframe_end_delta = int(requirement.numeric_value_max)
|
|
52
115
|
|
|
53
|
-
start_date_bound = today
|
|
54
|
-
end_date_bound = today
|
|
116
|
+
start_date_bound = _shift_date_by_unit(today, timeframe_start_delta, unit)
|
|
117
|
+
end_date_bound = _shift_date_by_unit(today, timeframe_end_delta, unit)
|
|
118
|
+
|
|
119
|
+
if start_date_bound > end_date_bound:
|
|
120
|
+
start_date_bound, end_date_bound = end_date_bound, start_date_bound
|
|
55
121
|
|
|
56
122
|
return start_date_bound <= date_to_check <= end_date_bound
|
|
57
123
|
|
|
58
124
|
|
|
59
|
-
def _evaluate_models_match_any(
|
|
60
|
-
requirement_links: "RequirementLinks",
|
|
61
|
-
input_links: "RequirementLinks",
|
|
62
|
-
**kwargs
|
|
63
|
-
) -> bool:
|
|
125
|
+
def _evaluate_models_match_any(requirement_links: "RequirementLinks", input_links: "RequirementLinks", **kwargs) -> bool:
|
|
64
126
|
"""
|
|
65
127
|
Checks if the requirement_links matches any of the input_links.
|
|
66
|
-
|
|
128
|
+
|
|
67
129
|
Args:
|
|
68
130
|
requirement_links: The reference set of requirement links to compare against.
|
|
69
131
|
input_links: The aggregated requirement links from the input objects.
|
|
70
|
-
|
|
132
|
+
|
|
71
133
|
Returns:
|
|
72
134
|
True if the input set of requirement links matches according to requirement_links.match_any; otherwise, False.
|
|
73
135
|
"""
|
|
@@ -77,8 +139,8 @@ def _evaluate_models_match_any(
|
|
|
77
139
|
def _evaluate_models_match_any_in_timeframe(
|
|
78
140
|
requirement_links: "RequirementLinks",
|
|
79
141
|
input_links: "RequirementLinks",
|
|
80
|
-
requirement: "Requirement",
|
|
81
|
-
**kwargs
|
|
142
|
+
requirement: "Requirement", # Explicitly pass Requirement
|
|
143
|
+
**kwargs, # Keep for consistency, though 'requirement' is the main one used here
|
|
82
144
|
) -> bool:
|
|
83
145
|
"""
|
|
84
146
|
Checks if any relevant model in input_links matches a model specified in
|
|
@@ -86,6 +148,9 @@ def _evaluate_models_match_any_in_timeframe(
|
|
|
86
148
|
|
|
87
149
|
Currently focuses on PatientEvent instances and their dates.
|
|
88
150
|
"""
|
|
151
|
+
if not _has_timeframe_configuration(requirement):
|
|
152
|
+
return False
|
|
153
|
+
|
|
89
154
|
active_req_links_dict = requirement_links.active()
|
|
90
155
|
if not active_req_links_dict:
|
|
91
156
|
# If the Requirement itself doesn't specify any models to match (e.g., requirement.events is empty),
|
|
@@ -95,16 +160,16 @@ def _evaluate_models_match_any_in_timeframe(
|
|
|
95
160
|
|
|
96
161
|
# --- Handle PatientEvents ---
|
|
97
162
|
# Check if the requirement is concerned with events
|
|
98
|
-
if requirement_links.events:
|
|
99
|
-
required_event_models = set(requirement_links.events)
|
|
100
|
-
|
|
163
|
+
if requirement_links.events: # This list contains Event model instances
|
|
164
|
+
required_event_models = set(requirement_links.events) # Target Event models from the Requirement
|
|
165
|
+
|
|
101
166
|
# input_links.patient_events contains PatientEvent instances provided as input
|
|
102
167
|
for patient_event_instance in input_links.patient_events:
|
|
103
168
|
# Check if the event of the current PatientEvent instance is one of the target events
|
|
104
169
|
if patient_event_instance.event in required_event_models:
|
|
105
170
|
# If it is, check if this PatientEvent's date is within the timeframe
|
|
106
171
|
if _is_date_in_timeframe(patient_event_instance.date, requirement):
|
|
107
|
-
return True
|
|
172
|
+
return True # Found a matching event within the timeframe
|
|
108
173
|
|
|
109
174
|
# --- Handle Other Model Types (Example: PatientLabValue) ---
|
|
110
175
|
# if requirement_links.lab_values:
|
|
@@ -119,17 +184,62 @@ def _evaluate_models_match_any_in_timeframe(
|
|
|
119
184
|
#
|
|
120
185
|
# if _is_date_in_timeframe(date_to_check, requirement):
|
|
121
186
|
# return True
|
|
122
|
-
|
|
187
|
+
|
|
123
188
|
# If the code reaches here, no matching model within the timeframe was found
|
|
124
189
|
# for any of the categories specified in requirement_links.
|
|
125
190
|
return False
|
|
126
191
|
|
|
127
192
|
|
|
128
|
-
def
|
|
129
|
-
requirement_links: "RequirementLinks",
|
|
130
|
-
|
|
131
|
-
**kwargs
|
|
193
|
+
def _evaluate_models_match_all_in_timeframe(
|
|
194
|
+
requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs
|
|
195
|
+
) -> bool:
|
|
196
|
+
if not _evaluate_models_match_all(requirement_links, input_links, **kwargs):
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
if not requirement_links.events:
|
|
200
|
+
return True
|
|
201
|
+
|
|
202
|
+
if not _has_timeframe_configuration(requirement):
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
required_events = set(requirement_links.events)
|
|
206
|
+
patient_events = getattr(input_links, "patient_events", [])
|
|
207
|
+
if not patient_events:
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
for event in required_events:
|
|
211
|
+
found_in_timeframe = False
|
|
212
|
+
for patient_event in patient_events:
|
|
213
|
+
if getattr(patient_event, "event", None) == event:
|
|
214
|
+
if _is_date_in_timeframe(getattr(patient_event, "date", None), requirement):
|
|
215
|
+
found_in_timeframe = True
|
|
216
|
+
break
|
|
217
|
+
if not found_in_timeframe:
|
|
218
|
+
return False
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
if requirement.unit is None:
|
|
222
|
+
return False
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _evaluate_models_match_none_in_timeframe(
|
|
226
|
+
requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs
|
|
132
227
|
) -> bool:
|
|
228
|
+
if not requirement_links.events:
|
|
229
|
+
return True
|
|
230
|
+
|
|
231
|
+
if not _has_timeframe_configuration(requirement):
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
required_events = set(requirement_links.events)
|
|
235
|
+
for patient_event in getattr(input_links, "patient_events", []):
|
|
236
|
+
if getattr(patient_event, "event", None) in required_events:
|
|
237
|
+
if _is_date_in_timeframe(getattr(patient_event, "date", None), requirement):
|
|
238
|
+
return False
|
|
239
|
+
return True
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _evaluate_models_match_all(requirement_links: "RequirementLinks", input_links: "RequirementLinks", **kwargs) -> bool:
|
|
133
243
|
"""
|
|
134
244
|
Evaluates if all active links in requirement_links are present in input_links.
|
|
135
245
|
|
|
@@ -146,62 +256,217 @@ def _evaluate_models_match_all(
|
|
|
146
256
|
True if all specified items in requirement_links are found in input_links,
|
|
147
257
|
False otherwise.
|
|
148
258
|
"""
|
|
149
|
-
active_req_links = requirement_links.active()
|
|
259
|
+
active_req_links = requirement_links.active() # Get dict of non-empty lists from requirement
|
|
150
260
|
|
|
151
|
-
if not active_req_links:
|
|
152
|
-
return True
|
|
261
|
+
if not active_req_links: # If the requirement specifies no actual items to link
|
|
262
|
+
return True # Vacuously true, as there are no conditions to fail
|
|
153
263
|
|
|
154
264
|
for link_category_name, req_items_list in active_req_links.items():
|
|
155
265
|
input_items_list = getattr(input_links, link_category_name, [])
|
|
156
|
-
|
|
266
|
+
|
|
157
267
|
try:
|
|
158
268
|
set_input_items = set(input_items_list)
|
|
159
269
|
set_req_items = set(req_items_list)
|
|
160
|
-
except TypeError:
|
|
270
|
+
except TypeError:
|
|
161
271
|
for req_item in req_items_list:
|
|
162
272
|
if req_item not in input_items_list:
|
|
163
|
-
return False
|
|
164
|
-
continue
|
|
273
|
+
return False
|
|
274
|
+
continue
|
|
165
275
|
|
|
166
276
|
if not set_req_items.issubset(set_input_items):
|
|
167
|
-
return False
|
|
168
|
-
|
|
277
|
+
return False
|
|
278
|
+
|
|
279
|
+
return True
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _evaluate_models_match_none(requirement_links: "RequirementLinks", input_links: "RequirementLinks", **kwargs) -> bool:
|
|
283
|
+
"""Returns True when no required models are present in the input links."""
|
|
284
|
+
active_req_links = requirement_links.active()
|
|
285
|
+
if not active_req_links:
|
|
286
|
+
return True
|
|
287
|
+
|
|
288
|
+
for link_category_name, req_items_list in active_req_links.items():
|
|
289
|
+
input_items_list = getattr(input_links, link_category_name, [])
|
|
290
|
+
if not input_items_list:
|
|
291
|
+
continue
|
|
292
|
+
try:
|
|
293
|
+
if set(req_items_list) & set(input_items_list):
|
|
294
|
+
return False
|
|
295
|
+
except TypeError:
|
|
296
|
+
for req_item in req_items_list:
|
|
297
|
+
if req_item in input_items_list:
|
|
298
|
+
return False
|
|
169
299
|
return True
|
|
170
300
|
|
|
171
|
-
|
|
301
|
+
|
|
302
|
+
def _count_matching_models(
|
|
303
|
+
requirement_links: "RequirementLinks",
|
|
304
|
+
input_links: "RequirementLinks",
|
|
305
|
+
) -> int:
|
|
306
|
+
"""Counts how many required models are present in the input links."""
|
|
307
|
+
active_req_links = requirement_links.active()
|
|
308
|
+
if not active_req_links:
|
|
309
|
+
return 0
|
|
310
|
+
|
|
311
|
+
match_count = 0
|
|
312
|
+
for link_category_name, req_items_list in active_req_links.items():
|
|
313
|
+
input_items_list = getattr(input_links, link_category_name, [])
|
|
314
|
+
if not input_items_list:
|
|
315
|
+
continue
|
|
316
|
+
try:
|
|
317
|
+
match_count += len(set(req_items_list) & set(input_items_list))
|
|
318
|
+
except TypeError:
|
|
319
|
+
for req_item in req_items_list:
|
|
320
|
+
if req_item in input_items_list:
|
|
321
|
+
match_count += 1
|
|
322
|
+
return match_count
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def _resolve_expected_count(requirement: "Requirement") -> int | None:
|
|
326
|
+
"""Extracts the expected count from numeric fields on the requirement."""
|
|
327
|
+
if requirement.numeric_value is not None:
|
|
328
|
+
return int(requirement.numeric_value)
|
|
329
|
+
if requirement.numeric_value_min is not None:
|
|
330
|
+
return int(requirement.numeric_value_min)
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def _has_timeframe_configuration(requirement: "Requirement") -> bool:
|
|
335
|
+
"""Checks whether timeframe boundaries and unit are configured."""
|
|
336
|
+
return (
|
|
337
|
+
requirement.unit is not None
|
|
338
|
+
and requirement.numeric_value_min is not None
|
|
339
|
+
and requirement.numeric_value_max is not None
|
|
340
|
+
and _normalize_timeframe_unit(requirement) is not None
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _count_matching_events_in_timeframe(
|
|
172
345
|
requirement_links: "RequirementLinks",
|
|
173
346
|
input_links: "RequirementLinks",
|
|
174
347
|
requirement: "Requirement",
|
|
175
|
-
|
|
348
|
+
) -> int:
|
|
349
|
+
"""Counts required events that have a matching patient event within the timeframe."""
|
|
350
|
+
required_events = set(requirement_links.events)
|
|
351
|
+
if not required_events:
|
|
352
|
+
return 0
|
|
353
|
+
|
|
354
|
+
patient_events = getattr(input_links, "patient_events", [])
|
|
355
|
+
if not patient_events:
|
|
356
|
+
return 0
|
|
357
|
+
|
|
358
|
+
matched_events = 0
|
|
359
|
+
for event in required_events:
|
|
360
|
+
for patient_event in patient_events:
|
|
361
|
+
if getattr(patient_event, "event", None) != event:
|
|
362
|
+
continue
|
|
363
|
+
if _is_date_in_timeframe(getattr(patient_event, "date", None), requirement):
|
|
364
|
+
matched_events += 1
|
|
365
|
+
break
|
|
366
|
+
return matched_events
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def _evaluate_models_match_n(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
|
|
370
|
+
expected = _resolve_expected_count(requirement)
|
|
371
|
+
if expected is None:
|
|
372
|
+
return False
|
|
373
|
+
return _count_matching_models(requirement_links, input_links) == expected
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _evaluate_models_match_n_or_more(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
|
|
377
|
+
threshold = _resolve_expected_count(requirement)
|
|
378
|
+
if threshold is None:
|
|
379
|
+
return False
|
|
380
|
+
return _count_matching_models(requirement_links, input_links) >= max(threshold, 0)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _evaluate_models_match_n_or_less(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
|
|
384
|
+
limit = _resolve_expected_count(requirement)
|
|
385
|
+
if limit is None:
|
|
386
|
+
return False
|
|
387
|
+
return _count_matching_models(requirement_links, input_links) <= limit
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _evaluate_models_match_count_in_range(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
|
|
391
|
+
if requirement.numeric_value_min is None or requirement.numeric_value_max is None:
|
|
392
|
+
return False
|
|
393
|
+
|
|
394
|
+
lower = int(requirement.numeric_value_min)
|
|
395
|
+
upper = int(requirement.numeric_value_max)
|
|
396
|
+
if lower > upper:
|
|
397
|
+
lower, upper = upper, lower
|
|
398
|
+
|
|
399
|
+
match_count = _count_matching_models(requirement_links, input_links)
|
|
400
|
+
return lower <= match_count <= upper
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def _evaluate_models_match_n_in_timeframe(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
|
|
404
|
+
if not _has_timeframe_configuration(requirement):
|
|
405
|
+
return False
|
|
406
|
+
|
|
407
|
+
expected = _resolve_expected_count(requirement)
|
|
408
|
+
if expected is None:
|
|
409
|
+
return False
|
|
410
|
+
|
|
411
|
+
return _count_matching_events_in_timeframe(requirement_links, input_links, requirement) == expected
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _evaluate_models_match_n_or_more_in_timeframe(
|
|
415
|
+
requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs
|
|
416
|
+
) -> bool:
|
|
417
|
+
if not _has_timeframe_configuration(requirement):
|
|
418
|
+
return False
|
|
419
|
+
|
|
420
|
+
threshold = _resolve_expected_count(requirement)
|
|
421
|
+
if threshold is None:
|
|
422
|
+
return False
|
|
423
|
+
|
|
424
|
+
return _count_matching_events_in_timeframe(requirement_links, input_links, requirement) >= max(threshold, 0)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _evaluate_models_match_n_or_less_in_timeframe(
|
|
428
|
+
requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs
|
|
176
429
|
) -> bool:
|
|
430
|
+
if not _has_timeframe_configuration(requirement):
|
|
431
|
+
return False
|
|
432
|
+
|
|
433
|
+
limit = _resolve_expected_count(requirement)
|
|
434
|
+
if limit is None:
|
|
435
|
+
return False
|
|
436
|
+
|
|
437
|
+
return _count_matching_events_in_timeframe(requirement_links, input_links, requirement) <= limit
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _evaluate_age_gte(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
|
|
177
441
|
"""
|
|
178
442
|
Checks if any patient in the input has an age greater than or equal to the requirement's numeric_value.
|
|
179
|
-
|
|
443
|
+
|
|
180
444
|
Args:
|
|
181
445
|
requirement_links: The RequirementLinks object from the Requirement model (not used for age checks).
|
|
182
446
|
input_links: The aggregated RequirementLinks object from the input arguments.
|
|
183
447
|
requirement: The Requirement instance containing the minimum age in numeric_value.
|
|
184
448
|
**kwargs: Additional keyword arguments, should contain 'original_input_args' with the original Patient instances.
|
|
185
|
-
|
|
449
|
+
|
|
186
450
|
Returns:
|
|
187
451
|
True if any patient in the input has an age >= requirement.numeric_value, False otherwise.
|
|
188
452
|
"""
|
|
189
|
-
from endoreg_db.models.administration.person.patient import Patient
|
|
190
453
|
import logging
|
|
191
|
-
|
|
454
|
+
|
|
455
|
+
from endoreg_db.models.administration.person.patient import Patient
|
|
456
|
+
|
|
192
457
|
logger = logging.getLogger(__name__)
|
|
193
|
-
|
|
458
|
+
|
|
194
459
|
if requirement.numeric_value is None:
|
|
195
460
|
logger.debug("age_gte: requirement.numeric_value is None, returning False")
|
|
196
461
|
return False # Cannot evaluate without a minimum age requirement
|
|
197
|
-
|
|
462
|
+
|
|
198
463
|
min_age = requirement.numeric_value
|
|
199
464
|
logger.debug(f"age_gte: Checking if any patient has age >= {min_age}")
|
|
200
|
-
|
|
465
|
+
|
|
201
466
|
# Check if we have Patient instances in the original_input_args
|
|
202
|
-
original_args = kwargs.get(
|
|
467
|
+
original_args = kwargs.get("original_input_args", [])
|
|
203
468
|
logger.debug(f"age_gte: Found {len(original_args)} original input arguments: {[type(arg).__name__ for arg in original_args]}")
|
|
204
|
-
|
|
469
|
+
|
|
205
470
|
for i, arg in enumerate(original_args):
|
|
206
471
|
logger.debug(f"age_gte: Checking argument {i}: {type(arg).__name__}")
|
|
207
472
|
if isinstance(arg, Patient):
|
|
@@ -213,7 +478,7 @@ def _evaluate_age_gte(
|
|
|
213
478
|
else:
|
|
214
479
|
logger.debug(f"age_gte: Patient age {patient_age} < {min_age} or is None")
|
|
215
480
|
# Handle QuerySets of patients
|
|
216
|
-
elif hasattr(arg,
|
|
481
|
+
elif hasattr(arg, "model") and issubclass(arg.model, Patient):
|
|
217
482
|
logger.debug(f"age_gte: Found Patient QuerySet with {arg.count()} patients")
|
|
218
483
|
for patient in arg:
|
|
219
484
|
patient_age = patient.age()
|
|
@@ -223,62 +488,52 @@ def _evaluate_age_gte(
|
|
|
223
488
|
return True
|
|
224
489
|
else:
|
|
225
490
|
logger.debug(f"age_gte: Argument {i} is not a Patient or Patient QuerySet: {type(arg)}")
|
|
226
|
-
|
|
491
|
+
|
|
227
492
|
logger.debug(f"age_gte: No patient found with age >= {min_age}, returning False")
|
|
228
493
|
return False
|
|
229
494
|
|
|
230
495
|
|
|
231
|
-
def _evaluate_age_lte(
|
|
232
|
-
requirement_links: "RequirementLinks",
|
|
233
|
-
input_links: "RequirementLinks",
|
|
234
|
-
requirement: "Requirement",
|
|
235
|
-
**kwargs
|
|
236
|
-
) -> bool:
|
|
496
|
+
def _evaluate_age_lte(requirement_links: "RequirementLinks", input_links: "RequirementLinks", requirement: "Requirement", **kwargs) -> bool:
|
|
237
497
|
"""
|
|
238
498
|
Checks if any patient in the input has an age less than or equal to the requirement's numeric_value.
|
|
239
|
-
|
|
499
|
+
|
|
240
500
|
Args:
|
|
241
501
|
requirement_links: The RequirementLinks object from the Requirement model (not used for age checks).
|
|
242
502
|
input_links: The aggregated RequirementLinks object from the input arguments.
|
|
243
503
|
requirement: The Requirement instance containing the maximum age in numeric_value.
|
|
244
504
|
**kwargs: Additional keyword arguments, should contain 'original_input_args' with the original Patient instances.
|
|
245
|
-
|
|
505
|
+
|
|
246
506
|
Returns:
|
|
247
507
|
True if any patient in the input has an age <= requirement.numeric_value, False otherwise.
|
|
248
508
|
"""
|
|
249
509
|
from endoreg_db.models.administration.person.patient import Patient
|
|
250
|
-
|
|
510
|
+
|
|
251
511
|
if requirement.numeric_value is None:
|
|
252
512
|
return False # Cannot evaluate without a maximum age requirement
|
|
253
|
-
|
|
513
|
+
|
|
254
514
|
max_age = requirement.numeric_value
|
|
255
|
-
|
|
515
|
+
|
|
256
516
|
# Check if we have Patient instances in the original_input_args
|
|
257
|
-
original_args = kwargs.get(
|
|
517
|
+
original_args = kwargs.get("original_input_args", [])
|
|
258
518
|
for arg in original_args:
|
|
259
519
|
if isinstance(arg, Patient):
|
|
260
520
|
patient_age = arg.age()
|
|
261
521
|
if patient_age is not None and patient_age <= max_age:
|
|
262
522
|
return True
|
|
263
523
|
# Handle QuerySets of patients
|
|
264
|
-
elif hasattr(arg,
|
|
524
|
+
elif hasattr(arg, "model") and issubclass(arg.model, Patient):
|
|
265
525
|
for patient in arg:
|
|
266
526
|
patient_age = patient.age()
|
|
267
527
|
if patient_age is not None and patient_age <= max_age:
|
|
268
528
|
return True
|
|
269
|
-
|
|
529
|
+
|
|
270
530
|
return False
|
|
271
531
|
|
|
272
532
|
|
|
273
|
-
def dispatch_operator_evaluation(
|
|
274
|
-
operator_name: str,
|
|
275
|
-
requirement_links: "RequirementLinks",
|
|
276
|
-
input_links: "RequirementLinks",
|
|
277
|
-
**kwargs
|
|
278
|
-
) -> bool:
|
|
533
|
+
def dispatch_operator_evaluation(operator_name: str, requirement_links: "RequirementLinks", input_links: "RequirementLinks", **kwargs) -> bool:
|
|
279
534
|
"""
|
|
280
535
|
Dispatches the evaluation to the appropriate function based on the operator name.
|
|
281
|
-
|
|
536
|
+
|
|
282
537
|
Args:
|
|
283
538
|
operator_name: The name of the operator to evaluate.
|
|
284
539
|
requirement_links: The RequirementLinks object from the Requirement model.
|
|
@@ -288,81 +543,113 @@ def dispatch_operator_evaluation(
|
|
|
288
543
|
|
|
289
544
|
Returns:
|
|
290
545
|
True if the condition defined by the operator is met, False otherwise.
|
|
291
|
-
|
|
546
|
+
|
|
292
547
|
Raises:
|
|
293
548
|
NotImplementedError: If the evaluation logic for the operator's name is not implemented.
|
|
294
549
|
"""
|
|
550
|
+
from endoreg_db.models.requirement.requirement import Requirement # Runtime import for isinstance
|
|
551
|
+
|
|
295
552
|
from .lab_value_operators import LAB_VALUE_OPERATOR_FUNCTIONS
|
|
296
|
-
from endoreg_db.models.requirement.requirement import Requirement # Runtime import for isinstance
|
|
297
553
|
|
|
298
554
|
eval_func = None
|
|
299
|
-
requirement = kwargs.get("requirement")
|
|
555
|
+
requirement = kwargs.get("requirement") # Get requirement for operators that need it
|
|
556
|
+
|
|
557
|
+
def _kwargs_without_requirement() -> dict:
|
|
558
|
+
return {k: v for k, v in kwargs.items() if k != "requirement"}
|
|
300
559
|
|
|
301
560
|
if operator_name == "models_match_any":
|
|
302
561
|
eval_func = _evaluate_models_match_any
|
|
303
|
-
return eval_func(
|
|
304
|
-
requirement_links=requirement_links,
|
|
305
|
-
input_links=input_links,
|
|
306
|
-
**kwargs
|
|
307
|
-
)
|
|
562
|
+
return eval_func(requirement_links=requirement_links, input_links=input_links, **kwargs)
|
|
308
563
|
elif operator_name == "models_match_all":
|
|
309
564
|
eval_func = _evaluate_models_match_all
|
|
310
|
-
return eval_func(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
)
|
|
565
|
+
return eval_func(requirement_links=requirement_links, input_links=input_links, **kwargs)
|
|
566
|
+
elif operator_name == "models_match_none":
|
|
567
|
+
eval_func = _evaluate_models_match_none
|
|
568
|
+
return eval_func(requirement_links=requirement_links, input_links=input_links, **kwargs)
|
|
315
569
|
elif operator_name == "models_match_any_in_timeframe":
|
|
316
570
|
# 'requirement' is already extracted from kwargs via requirement = kwargs.get("requirement")
|
|
317
|
-
if not isinstance(requirement, Requirement):
|
|
571
|
+
if not isinstance(requirement, Requirement): # Ensure requirement is present and correct type
|
|
318
572
|
raise ValueError("models_match_any_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
|
|
319
|
-
|
|
320
|
-
# Create a new kwargs dict for the call, excluding 'requirement' to avoid passing it twice,
|
|
321
|
-
# as it's already an explicit parameter for _evaluate_models_match_any_in_timeframe.
|
|
322
|
-
kwargs_for_eval = {k: v for k, v in kwargs.items() if k != 'requirement'}
|
|
323
|
-
|
|
573
|
+
kwargs_for_eval = _kwargs_without_requirement()
|
|
324
574
|
eval_func = _evaluate_models_match_any_in_timeframe
|
|
325
575
|
return eval_func(
|
|
326
576
|
requirement_links=requirement_links,
|
|
327
577
|
input_links=input_links,
|
|
328
|
-
requirement=requirement,
|
|
329
|
-
**kwargs_for_eval
|
|
578
|
+
requirement=requirement, # Pass the requirement instance explicitly
|
|
579
|
+
**kwargs_for_eval, # Pass the remaining kwargs
|
|
580
|
+
)
|
|
581
|
+
elif operator_name == "models_match_all_in_timeframe":
|
|
582
|
+
if not isinstance(requirement, Requirement):
|
|
583
|
+
raise ValueError("models_match_all_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
|
|
584
|
+
kwargs_for_eval = _kwargs_without_requirement()
|
|
585
|
+
return _evaluate_models_match_all_in_timeframe(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
|
|
586
|
+
elif operator_name == "models_match_none_in_timeframe":
|
|
587
|
+
if not isinstance(requirement, Requirement):
|
|
588
|
+
raise ValueError("models_match_none_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
|
|
589
|
+
kwargs_for_eval = _kwargs_without_requirement()
|
|
590
|
+
return _evaluate_models_match_none_in_timeframe(
|
|
591
|
+
requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval
|
|
592
|
+
)
|
|
593
|
+
elif operator_name == "models_match_n":
|
|
594
|
+
if not isinstance(requirement, Requirement):
|
|
595
|
+
raise ValueError("models_match_n operator requires a valid 'requirement' instance in kwargs.")
|
|
596
|
+
kwargs_for_eval = _kwargs_without_requirement()
|
|
597
|
+
return _evaluate_models_match_n(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
|
|
598
|
+
elif operator_name == "models_match_n_or_more":
|
|
599
|
+
if not isinstance(requirement, Requirement):
|
|
600
|
+
raise ValueError("models_match_n_or_more operator requires a valid 'requirement' instance in kwargs.")
|
|
601
|
+
kwargs_for_eval = _kwargs_without_requirement()
|
|
602
|
+
return _evaluate_models_match_n_or_more(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
|
|
603
|
+
elif operator_name == "models_match_n_or_less":
|
|
604
|
+
if not isinstance(requirement, Requirement):
|
|
605
|
+
raise ValueError("models_match_n_or_less operator requires a valid 'requirement' instance in kwargs.")
|
|
606
|
+
kwargs_for_eval = _kwargs_without_requirement()
|
|
607
|
+
return _evaluate_models_match_n_or_less(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
|
|
608
|
+
elif operator_name == "models_match_count_in_range":
|
|
609
|
+
if not isinstance(requirement, Requirement):
|
|
610
|
+
raise ValueError("models_match_count_in_range operator requires a valid 'requirement' instance in kwargs.")
|
|
611
|
+
kwargs_for_eval = _kwargs_without_requirement()
|
|
612
|
+
return _evaluate_models_match_count_in_range(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
|
|
613
|
+
elif operator_name == "models_match_n_in_timeframe":
|
|
614
|
+
if not isinstance(requirement, Requirement):
|
|
615
|
+
raise ValueError("models_match_n_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
|
|
616
|
+
kwargs_for_eval = _kwargs_without_requirement()
|
|
617
|
+
return _evaluate_models_match_n_in_timeframe(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
|
|
618
|
+
elif operator_name == "models_match_n_or_more_in_timeframe":
|
|
619
|
+
if not isinstance(requirement, Requirement):
|
|
620
|
+
raise ValueError("models_match_n_or_more_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
|
|
621
|
+
kwargs_for_eval = _kwargs_without_requirement()
|
|
622
|
+
return _evaluate_models_match_n_or_more_in_timeframe(
|
|
623
|
+
requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval
|
|
624
|
+
)
|
|
625
|
+
elif operator_name == "models_match_n_or_less_in_timeframe":
|
|
626
|
+
if not isinstance(requirement, Requirement):
|
|
627
|
+
raise ValueError("models_match_n_or_less_in_timeframe operator requires a valid 'requirement' instance in kwargs.")
|
|
628
|
+
kwargs_for_eval = _kwargs_without_requirement()
|
|
629
|
+
return _evaluate_models_match_n_or_less_in_timeframe(
|
|
630
|
+
requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval
|
|
330
631
|
)
|
|
331
632
|
elif operator_name in LAB_VALUE_OPERATOR_FUNCTIONS:
|
|
332
|
-
if not isinstance(requirement, Requirement):
|
|
333
|
-
raise ValueError(f"Lab value operator
|
|
334
|
-
|
|
633
|
+
if not isinstance(requirement, Requirement): # Ensure requirement is present and correct type
|
|
634
|
+
raise ValueError(f"Lab value operator '{operator_name}' requires a valid 'requirement' instance in kwargs.")
|
|
635
|
+
|
|
335
636
|
eval_func = LAB_VALUE_OPERATOR_FUNCTIONS[operator_name]
|
|
336
|
-
return eval_func(
|
|
337
|
-
input_links=input_links,
|
|
338
|
-
requirement=requirement,
|
|
339
|
-
operator_kwargs=kwargs
|
|
340
|
-
)
|
|
637
|
+
return eval_func(input_links=input_links, requirement=requirement, operator_kwargs=kwargs)
|
|
341
638
|
elif operator_name == "age_gte":
|
|
342
639
|
if not isinstance(requirement, Requirement):
|
|
343
640
|
raise ValueError("age_gte operator requires a valid 'requirement' instance in kwargs.")
|
|
344
|
-
|
|
641
|
+
|
|
345
642
|
# Create a new kwargs dict for the call, excluding 'requirement' to avoid passing it twice
|
|
346
|
-
kwargs_for_eval = {k: v for k, v in kwargs.items() if k !=
|
|
347
|
-
|
|
348
|
-
return _evaluate_age_gte(
|
|
349
|
-
requirement_links=requirement_links,
|
|
350
|
-
input_links=input_links,
|
|
351
|
-
requirement=requirement,
|
|
352
|
-
**kwargs_for_eval
|
|
353
|
-
)
|
|
643
|
+
kwargs_for_eval = {k: v for k, v in kwargs.items() if k != "requirement"}
|
|
644
|
+
|
|
645
|
+
return _evaluate_age_gte(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
|
|
354
646
|
elif operator_name == "age_lte":
|
|
355
647
|
if not isinstance(requirement, Requirement):
|
|
356
648
|
raise ValueError("age_lte operator requires a valid 'requirement' instance in kwargs.")
|
|
357
|
-
|
|
649
|
+
|
|
358
650
|
# Create a new kwargs dict for the call, excluding 'requirement' to avoid passing it twice
|
|
359
|
-
kwargs_for_eval = {k: v for k, v in kwargs.items() if k !=
|
|
360
|
-
|
|
361
|
-
return _evaluate_age_lte(
|
|
362
|
-
requirement_links=requirement_links,
|
|
363
|
-
input_links=input_links,
|
|
364
|
-
requirement=requirement,
|
|
365
|
-
**kwargs_for_eval
|
|
366
|
-
)
|
|
651
|
+
kwargs_for_eval = {k: v for k, v in kwargs.items() if k != "requirement"}
|
|
652
|
+
|
|
653
|
+
return _evaluate_age_lte(requirement_links=requirement_links, input_links=input_links, requirement=requirement, **kwargs_for_eval)
|
|
367
654
|
else:
|
|
368
655
|
raise NotImplementedError(f"Evaluation logic for operator '{operator_name}' is not implemented.")
|