endoreg-db 0.8.4.4__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_ai_model_data.py +2 -1
- 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 +14 -10
- 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 +249 -177
- 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_1.py +30 -33
- endoreg_db/models/media/video/pipe_2.py +8 -9
- endoreg_db/models/media/video/video_file.py +359 -204
- 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 +139 -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 +383 -43
- 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 +26 -57
- 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/__init__.py +1 -1
- endoreg_db/serializers/misc/file_overview.py +33 -91
- endoreg_db/serializers/misc/{vop_patient_data.py → sensitive_patient_data.py} +1 -1
- 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/serializers/video_examination.py +198 -0
- endoreg_db/services/anonymization.py +75 -73
- endoreg_db/services/lookup_service.py +256 -73
- endoreg_db/services/lookup_store.py +174 -30
- endoreg_db/services/pdf_import.py +711 -310
- endoreg_db/services/storage_aware_video_processor.py +140 -114
- endoreg_db/services/video_import.py +266 -117
- endoreg_db/urls/__init__.py +27 -27
- endoreg_db/urls/label_video_segments.py +2 -0
- endoreg_db/urls/media.py +108 -66
- 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 +5 -12
- 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 -150
- 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 +187 -260
- endoreg_db/views/meta/sensitive_meta_detail.py +0 -63
- endoreg_db/views/patient/patient.py +5 -4
- endoreg_db/views/pdf/__init__.py +5 -8
- endoreg_db/views/pdf/pdf_stream.py +186 -0
- endoreg_db/views/pdf/pdf_stream_views.py +0 -127
- endoreg_db/views/pdf/reimport.py +86 -91
- endoreg_db/views/requirement/evaluate.py +188 -187
- endoreg_db/views/requirement/lookup.py +186 -288
- endoreg_db/views/requirement/requirement_utils.py +89 -0
- endoreg_db/views/video/__init__.py +0 -4
- endoreg_db/views/video/correction.py +2 -2
- endoreg_db/views/video/video_examination_viewset.py +202 -289
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/METADATA +7 -3
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/RECORD +350 -255
- endoreg_db/models/administration/permissions/__init__.py +0 -44
- endoreg_db/models/media/video/refactor_plan.md +0 -0
- 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/pdf/pdf_media.py +0 -239
- 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/video/video_media.py +0 -158
- 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-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.4.4.dist-info → endoreg_db-0.8.8.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,483 +1,381 @@
|
|
|
1
1
|
# api/viewsets/lookup.py
|
|
2
|
+
import logging
|
|
3
|
+
from ast import literal_eval
|
|
4
|
+
from collections.abc import Mapping
|
|
2
5
|
|
|
3
|
-
from
|
|
4
|
-
|
|
6
|
+
from django.core.cache import cache
|
|
7
|
+
from rest_framework import status, viewsets
|
|
5
8
|
from rest_framework.decorators import action
|
|
6
|
-
|
|
9
|
+
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
|
|
7
10
|
from rest_framework.response import Response
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
from endoreg_db.services.lookup_store import LookupStore, DEFAULT_TTL_SECONDS
|
|
14
|
-
|
|
15
|
-
|
|
16
12
|
# Use module import so tests can monkeypatch functions on the module
|
|
17
|
-
|
|
18
|
-
|
|
19
13
|
from endoreg_db.services import lookup_service as ls
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
from endoreg_db.services.lookup_store import DEFAULT_TTL_SECONDS, LookupStore
|
|
22
15
|
from endoreg_db.utils.permissions import EnvironmentAwarePermission
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
from django.core.cache import cache
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
import logging
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
from ast import literal_eval
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
from collections.abc import Mapping
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
16
|
+
from endoreg_db.models.other.tag import Tag
|
|
39
17
|
|
|
40
18
|
ORIGIN_MAP_PREFIX = "lookup:origin:"
|
|
41
|
-
|
|
42
|
-
|
|
43
19
|
ISSUED_MAP_PREFIX = "lookup:issued_for_internal:"
|
|
44
20
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
21
|
logger = logging.getLogger(__name__)
|
|
50
22
|
|
|
51
23
|
|
|
52
|
-
|
|
53
24
|
class LookupViewSet(viewsets.ViewSet):
|
|
54
|
-
|
|
25
|
+
"""
|
|
26
|
+
Django REST Framework ViewSet for managing lookup sessions.
|
|
27
|
+
|
|
28
|
+
This ViewSet provides REST API endpoints for the lookup system, which
|
|
29
|
+
evaluates medical examination requirements against patient data. It uses
|
|
30
|
+
token-based sessions stored in Django cache to maintain state across
|
|
31
|
+
multiple client requests.
|
|
32
|
+
|
|
33
|
+
Key features:
|
|
34
|
+
- Session initialization with patient examination data
|
|
35
|
+
- Retrieval of lookup data by token
|
|
36
|
+
- Partial updates to session data with automatic recomputation
|
|
37
|
+
- Manual recomputation of derived data
|
|
38
|
+
- Automatic session recovery for expired tokens
|
|
39
|
+
|
|
40
|
+
The API supports both internal service tokens and public client tokens,
|
|
41
|
+
with origin mapping to enable session restart functionality.
|
|
42
|
+
|
|
43
|
+
Endpoints:
|
|
44
|
+
- POST /init: Initialize new lookup session
|
|
45
|
+
- GET /{token}/all: Retrieve complete session data
|
|
46
|
+
- GET/PATCH /{token}/parts: Get/update partial session data
|
|
47
|
+
- POST /{token}/recompute: Manually trigger recomputation
|
|
48
|
+
"""
|
|
55
49
|
|
|
56
50
|
permission_classes = [EnvironmentAwarePermission]
|
|
57
|
-
|
|
58
|
-
|
|
59
51
|
parser_classes = (JSONParser, FormParser, MultiPartParser)
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
INPUT_KEYS = {"patient_examination_id", "selectedRequirementSetIds", "selectedChoices"}
|
|
70
|
-
|
|
71
|
-
|
|
52
|
+
INPUT_KEYS = {
|
|
53
|
+
"patient_examination_id",
|
|
54
|
+
"selectedRequirementSetIds",
|
|
55
|
+
"selectedChoices",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
user_tags = Tag
|
|
72
59
|
|
|
73
60
|
@action(detail=False, methods=["post"])
|
|
74
|
-
|
|
75
61
|
def init(self, request):
|
|
62
|
+
"""
|
|
63
|
+
Initialize a new lookup session for a patient examination.
|
|
76
64
|
|
|
65
|
+
Creates a new token-based session containing initial lookup data
|
|
66
|
+
for the specified patient examination. Handles malformed payloads
|
|
67
|
+
and supports multiple initialization requests for the same examination
|
|
68
|
+
by issuing fresh public tokens while maintaining internal state.
|
|
77
69
|
|
|
70
|
+
Request body:
|
|
71
|
+
patient_examination_id: Integer ID of the patient examination
|
|
78
72
|
|
|
73
|
+
Returns:
|
|
74
|
+
JSON response with session token
|
|
79
75
|
|
|
76
|
+
Raises:
|
|
77
|
+
400: Invalid patient_examination_id or creation failure
|
|
78
|
+
"""
|
|
80
79
|
try:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
debug_data = getattr(request, 'data', None)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
raw_post = getattr(getattr(request, '_request', None), 'POST', None)
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
debug_data = getattr(request, "data", None)
|
|
81
|
+
raw_post = getattr(getattr(request, "_request", None), "POST", None)
|
|
89
82
|
body_preview = None
|
|
90
|
-
|
|
91
|
-
|
|
92
83
|
try:
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
body = getattr(getattr(request, '_request', None), 'body', b'')
|
|
96
|
-
|
|
97
|
-
|
|
84
|
+
body = getattr(getattr(request, "_request", None), "body", b"")
|
|
98
85
|
body_preview = body[:200]
|
|
99
86
|
|
|
100
|
-
|
|
101
87
|
except Exception:
|
|
102
|
-
|
|
103
|
-
|
|
104
88
|
body_preview = None
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
89
|
+
logger.debug(
|
|
90
|
+
"lookup.init incoming: data=%r POST=%r body[:200]=%r",
|
|
91
|
+
debug_data,
|
|
92
|
+
raw_post,
|
|
93
|
+
body_preview,
|
|
94
|
+
)
|
|
109
95
|
|
|
110
96
|
except Exception:
|
|
111
|
-
|
|
112
|
-
|
|
113
97
|
pass
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
98
|
# Prefer DRF data
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
99
|
+
raw_pe = (
|
|
100
|
+
request.data.get("patient_examination_id")
|
|
101
|
+
if hasattr(request, "data")
|
|
102
|
+
else None
|
|
103
|
+
)
|
|
128
104
|
# Fallback: parse malformed form payload where the entire dict was sent as a single key string
|
|
129
105
|
|
|
130
|
-
|
|
131
106
|
if raw_pe is None:
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
107
|
+
for candidate in (
|
|
108
|
+
getattr(getattr(request, "_request", None), "POST", None),
|
|
109
|
+
request.data if hasattr(request, "data") else None,
|
|
110
|
+
):
|
|
137
111
|
try:
|
|
138
|
-
|
|
139
|
-
|
|
140
112
|
if isinstance(candidate, Mapping) and len(candidate.keys()) == 1:
|
|
141
|
-
|
|
142
|
-
|
|
143
113
|
only_key = next(iter(candidate.keys()))
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
114
|
+
if (
|
|
115
|
+
isinstance(only_key, str)
|
|
116
|
+
and only_key.startswith("{")
|
|
117
|
+
and only_key.endswith("}")
|
|
118
|
+
):
|
|
149
119
|
try:
|
|
150
|
-
|
|
151
|
-
|
|
152
120
|
parsed = literal_eval(only_key)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
121
|
+
if (
|
|
122
|
+
isinstance(parsed, dict)
|
|
123
|
+
and "patient_examination_id" in parsed
|
|
124
|
+
):
|
|
125
|
+
raw_pe = parsed.get("patient_examination_id")
|
|
126
|
+
logger.debug(
|
|
127
|
+
"lookup.init recovered pe_id from malformed payload: %r",
|
|
128
|
+
raw_pe,
|
|
129
|
+
)
|
|
164
130
|
break
|
|
165
131
|
|
|
166
|
-
|
|
167
132
|
except Exception:
|
|
168
|
-
|
|
169
|
-
|
|
170
133
|
pass
|
|
171
134
|
|
|
172
|
-
|
|
173
135
|
except Exception:
|
|
174
|
-
|
|
175
|
-
|
|
176
136
|
pass
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
137
|
+
|
|
138
|
+
user_tags = request.data.get("user_tags", None)
|
|
139
|
+
if user_tags and not isinstance(user_tags, list):
|
|
140
|
+
user_tags = [user_tags]
|
|
182
141
|
# Fallback to query params
|
|
183
|
-
|
|
184
|
-
|
|
185
142
|
if raw_pe is None:
|
|
186
|
-
|
|
187
|
-
|
|
188
143
|
raw_pe = request.query_params.get("patient_examination_id")
|
|
189
144
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
145
|
logger.debug("lookup.init raw_pe=%r type=%s", raw_pe, type(raw_pe))
|
|
195
146
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
147
|
# Normalize potential list/tuple inputs (e.g., from form submissions)
|
|
201
|
-
|
|
202
|
-
|
|
203
148
|
if isinstance(raw_pe, (list, tuple)):
|
|
204
|
-
|
|
205
|
-
|
|
206
149
|
raw_pe = raw_pe[0] if raw_pe else None
|
|
207
150
|
|
|
208
|
-
|
|
209
151
|
if raw_pe in (None, ""):
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
152
|
+
return Response(
|
|
153
|
+
{"detail": "patient_examination_id must be an integer"},
|
|
154
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
155
|
+
)
|
|
217
156
|
|
|
218
157
|
# Coerce to int robustly
|
|
219
|
-
|
|
220
|
-
|
|
221
158
|
try:
|
|
222
|
-
|
|
223
|
-
|
|
224
159
|
pe_id = int(str(raw_pe))
|
|
225
160
|
|
|
226
|
-
|
|
227
161
|
except (TypeError, ValueError):
|
|
228
|
-
|
|
229
|
-
|
|
230
162
|
logger.warning("lookup.init failed to int() raw_pe=%r", raw_pe)
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
163
|
+
return Response(
|
|
164
|
+
{"detail": "patient_examination_id must be an integer"},
|
|
165
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
166
|
+
)
|
|
235
167
|
|
|
236
168
|
if pe_id <= 0:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
169
|
+
return Response(
|
|
170
|
+
{"detail": "patient_examination_id must be positive"},
|
|
171
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
172
|
+
)
|
|
244
173
|
|
|
245
174
|
try:
|
|
246
|
-
|
|
247
|
-
|
|
248
175
|
# Create internal session via service (may seed its own token/cache)
|
|
176
|
+
service_kwargs = {}
|
|
177
|
+
if user_tags:
|
|
178
|
+
service_kwargs["user_tags"] = user_tags
|
|
249
179
|
|
|
250
|
-
|
|
251
|
-
internal_token = ls.create_lookup_token_for_pe(pe_id)
|
|
252
|
-
|
|
253
|
-
|
|
180
|
+
internal_token = ls.create_lookup_token_for_pe(pe_id, **service_kwargs)
|
|
254
181
|
internal_data = LookupStore(token=internal_token).get_all()
|
|
255
182
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
183
|
issued_key = f"{ISSUED_MAP_PREFIX}{internal_token}"
|
|
261
184
|
|
|
262
|
-
|
|
263
185
|
issued_count = cache.get(issued_key, 0)
|
|
264
186
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
187
|
if issued_count == 0:
|
|
270
|
-
|
|
271
|
-
|
|
272
188
|
# First issuance: return the service token directly
|
|
273
189
|
|
|
274
|
-
|
|
275
190
|
token_to_return = internal_token
|
|
276
191
|
|
|
277
|
-
|
|
278
192
|
cache.set(issued_key, 1, DEFAULT_TTL_SECONDS)
|
|
279
193
|
|
|
280
|
-
|
|
281
194
|
else:
|
|
282
|
-
|
|
283
|
-
|
|
284
195
|
# Subsequent inits for same internal token: issue a fresh public token seeded with internal data
|
|
285
196
|
|
|
286
|
-
|
|
287
197
|
public_store = LookupStore()
|
|
288
198
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
199
|
+
token_to_return = public_store.init(
|
|
200
|
+
initial=internal_data, ttl=DEFAULT_TTL_SECONDS
|
|
201
|
+
)
|
|
292
202
|
|
|
293
203
|
cache.set(issued_key, issued_count + 1, DEFAULT_TTL_SECONDS)
|
|
294
204
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
205
|
# Persist origin mapping so we can restart expired sessions
|
|
300
206
|
|
|
301
|
-
|
|
302
|
-
|
|
207
|
+
cache.set(
|
|
208
|
+
f"{ORIGIN_MAP_PREFIX}{token_to_return}", pe_id, DEFAULT_TTL_SECONDS
|
|
209
|
+
)
|
|
303
210
|
|
|
304
211
|
except Exception as e:
|
|
305
|
-
|
|
306
212
|
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
|
307
213
|
|
|
308
|
-
|
|
309
214
|
return Response({"token": token_to_return}, status=status.HTTP_201_CREATED)
|
|
310
215
|
|
|
311
|
-
|
|
312
|
-
|
|
313
216
|
@action(detail=True, methods=["get"], url_path="all")
|
|
314
|
-
|
|
315
217
|
def get_all(self, request, pk=None):
|
|
218
|
+
"""
|
|
219
|
+
Retrieve complete lookup data for a session token.
|
|
316
220
|
|
|
221
|
+
Returns all stored data for the given token. If data is not found,
|
|
222
|
+
attempts automatic session recovery using persisted origin mapping.
|
|
317
223
|
|
|
318
|
-
|
|
224
|
+
Args:
|
|
225
|
+
pk: Session token
|
|
319
226
|
|
|
227
|
+
Returns:
|
|
228
|
+
Complete lookup data dictionary
|
|
320
229
|
|
|
321
|
-
|
|
230
|
+
Raises:
|
|
231
|
+
404: Token not found and recovery failed
|
|
232
|
+
"""
|
|
322
233
|
|
|
323
|
-
|
|
234
|
+
if not pk:
|
|
235
|
+
return Response(
|
|
236
|
+
{"detail": "Token required"}, status=status.HTTP_404_NOT_FOUND
|
|
237
|
+
)
|
|
324
238
|
|
|
239
|
+
store = LookupStore(token=pk)
|
|
325
240
|
|
|
326
241
|
try:
|
|
327
|
-
|
|
328
|
-
|
|
329
242
|
validated_data = store.validate_and_recover_data(pk)
|
|
330
243
|
|
|
331
|
-
|
|
332
244
|
except Exception:
|
|
333
|
-
|
|
334
|
-
|
|
335
245
|
validated_data = None
|
|
336
246
|
|
|
337
247
|
if validated_data is None:
|
|
338
|
-
|
|
339
|
-
|
|
340
248
|
# Try automatic restart once using persisted origin mapping
|
|
341
249
|
|
|
342
|
-
|
|
343
250
|
pe_id = cache.get(f"{ORIGIN_MAP_PREFIX}{pk}")
|
|
344
251
|
|
|
345
|
-
|
|
346
252
|
if pe_id:
|
|
347
|
-
|
|
348
|
-
|
|
349
253
|
try:
|
|
350
|
-
|
|
351
|
-
|
|
352
254
|
internal_token = ls.create_lookup_token_for_pe(int(pe_id))
|
|
353
255
|
|
|
354
|
-
|
|
355
256
|
new_data = LookupStore(token=internal_token).get_all()
|
|
356
257
|
|
|
357
|
-
|
|
358
258
|
if not new_data:
|
|
259
|
+
return Response(
|
|
260
|
+
{
|
|
261
|
+
"error": "Lookup data not available after restart",
|
|
262
|
+
"token": pk,
|
|
263
|
+
},
|
|
264
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
265
|
+
)
|
|
359
266
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
return Response(new_data, status=status.HTTP_200_OK)
|
|
267
|
+
# Hydrate the original token with recovered data and refresh origin TTL
|
|
268
|
+
store.set_many(new_data)
|
|
269
|
+
cache.set(f"{ORIGIN_MAP_PREFIX}{pk}", pe_id, DEFAULT_TTL_SECONDS)
|
|
270
|
+
return Response(store.get_all(), status=status.HTTP_200_OK)
|
|
365
271
|
|
|
366
272
|
|
|
367
273
|
except Exception:
|
|
368
|
-
|
|
369
|
-
|
|
370
274
|
pass
|
|
371
275
|
|
|
372
|
-
|
|
373
|
-
|
|
276
|
+
return Response(
|
|
277
|
+
{"error": "Lookup data not found or expired", "token": pk},
|
|
278
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
279
|
+
)
|
|
374
280
|
|
|
375
281
|
return Response(store.get_all())
|
|
376
282
|
|
|
377
|
-
|
|
378
|
-
|
|
379
283
|
@action(detail=True, methods=["get", "patch"], url_path="parts")
|
|
380
|
-
|
|
381
284
|
def parts(self, request, pk=None):
|
|
285
|
+
"""
|
|
286
|
+
Get or update partial lookup data for a session.
|
|
382
287
|
|
|
288
|
+
GET: Retrieve specific keys from the session data.
|
|
289
|
+
PATCH: Update session data and trigger recomputation if input keys changed.
|
|
383
290
|
|
|
384
|
-
|
|
291
|
+
GET query params:
|
|
292
|
+
keys: Comma-separated list of keys to retrieve
|
|
385
293
|
|
|
294
|
+
PATCH body:
|
|
295
|
+
updates: Dictionary of key-value pairs to update
|
|
386
296
|
|
|
387
|
-
|
|
297
|
+
Args:
|
|
298
|
+
pk: Session token
|
|
388
299
|
|
|
389
|
-
|
|
300
|
+
Returns:
|
|
301
|
+
GET: Dictionary with requested keys
|
|
302
|
+
PATCH: Success confirmation
|
|
390
303
|
|
|
304
|
+
Raises:
|
|
305
|
+
404: Token not found
|
|
306
|
+
400: Invalid request parameters
|
|
307
|
+
"""
|
|
391
308
|
|
|
309
|
+
if not pk:
|
|
310
|
+
return Response(
|
|
311
|
+
{"detail": "Token required"}, status=status.HTTP_404_NOT_FOUND
|
|
312
|
+
)
|
|
392
313
|
|
|
393
|
-
|
|
314
|
+
store = LookupStore(token=pk)
|
|
394
315
|
|
|
316
|
+
if request.method == "GET":
|
|
395
317
|
keys_param = request.query_params.get("keys", "")
|
|
396
318
|
|
|
397
319
|
keys = [k.strip() for k in keys_param.split(",") if k.strip()]
|
|
398
320
|
|
|
399
321
|
if not keys:
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
322
|
+
return Response(
|
|
323
|
+
{"detail": "Provide ?keys=key1,key2"},
|
|
324
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
325
|
+
)
|
|
403
326
|
|
|
404
327
|
try:
|
|
405
|
-
|
|
406
|
-
|
|
407
328
|
return Response(store.get_many(keys))
|
|
408
329
|
|
|
409
|
-
|
|
410
330
|
except Exception:
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
331
|
+
return Response(
|
|
332
|
+
{"detail": "Lookup data not found or expired"},
|
|
333
|
+
status=status.HTTP_404_NOT_FOUND,
|
|
334
|
+
)
|
|
416
335
|
|
|
417
336
|
# PATCH
|
|
418
337
|
|
|
419
338
|
updates = request.data.get("updates", {})
|
|
420
339
|
|
|
421
|
-
|
|
422
340
|
if not isinstance(updates, dict) or not updates:
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
341
|
+
return Response(
|
|
342
|
+
{"detail": "updates must be a non-empty object"},
|
|
343
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
344
|
+
)
|
|
428
345
|
|
|
429
346
|
store.set_many(updates)
|
|
430
347
|
|
|
431
|
-
|
|
432
|
-
|
|
433
348
|
if any(key in self.INPUT_KEYS for key in updates.keys()):
|
|
434
|
-
|
|
435
349
|
try:
|
|
436
|
-
|
|
437
|
-
|
|
438
350
|
ls.recompute_lookup(pk)
|
|
439
351
|
|
|
440
352
|
except Exception as e:
|
|
441
|
-
|
|
442
353
|
import logging
|
|
443
354
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
355
|
+
logging.getLogger(__name__).error(
|
|
356
|
+
"Failed to recompute after patch for token %s: %s", pk, e
|
|
357
|
+
)
|
|
448
358
|
|
|
449
359
|
return Response({"ok": True, "token": pk}, status=status.HTTP_200_OK)
|
|
450
360
|
|
|
451
|
-
|
|
452
|
-
|
|
453
361
|
@action(detail=True, methods=["post"], url_path="recompute")
|
|
454
|
-
|
|
455
362
|
def recompute(self, request, pk=None):
|
|
456
|
-
|
|
457
363
|
"""Recompute lookup data based on current PatientExamination and user selections"""
|
|
458
364
|
|
|
459
|
-
|
|
460
365
|
if not pk:
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
366
|
+
return Response(
|
|
367
|
+
{"detail": "Token required"}, status=status.HTTP_404_NOT_FOUND
|
|
368
|
+
)
|
|
464
369
|
|
|
465
370
|
try:
|
|
466
|
-
|
|
467
|
-
|
|
468
371
|
updates = ls.recompute_lookup(pk)
|
|
469
372
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
373
|
+
return Response(
|
|
374
|
+
{"ok": True, "token": pk, "updates": updates}, status=status.HTTP_200_OK
|
|
375
|
+
)
|
|
476
376
|
|
|
477
377
|
except ValueError as e:
|
|
478
|
-
|
|
479
378
|
return Response({"detail": str(e)}, status=status.HTTP_404_NOT_FOUND)
|
|
480
379
|
|
|
481
380
|
except Exception as e:
|
|
482
|
-
|
|
483
|
-
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
|
381
|
+
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|