endoreg-db 0.8.8.9__py3-none-any.whl → 0.8.9.10__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/admin.py +10 -5
- endoreg_db/apps.py +4 -7
- endoreg_db/authz/auth.py +1 -0
- endoreg_db/authz/backends.py +1 -1
- endoreg_db/authz/management/commands/list_routes.py +2 -0
- endoreg_db/authz/middleware.py +8 -7
- endoreg_db/authz/permissions.py +21 -10
- endoreg_db/authz/policy.py +14 -19
- endoreg_db/authz/views_auth.py +14 -10
- endoreg_db/codemods/rename_datetime_fields.py +8 -1
- endoreg_db/exceptions.py +5 -2
- endoreg_db/forms/__init__.py +0 -1
- endoreg_db/forms/examination_form.py +4 -3
- endoreg_db/forms/patient_finding_intervention_form.py +30 -8
- endoreg_db/forms/patient_form.py +9 -13
- endoreg_db/forms/questionnaires/__init__.py +1 -1
- endoreg_db/forms/settings/__init__.py +4 -1
- endoreg_db/forms/unit.py +2 -1
- endoreg_db/helpers/count_db.py +17 -14
- endoreg_db/helpers/default_objects.py +2 -1
- endoreg_db/helpers/download_segmentation_model.py +4 -3
- endoreg_db/helpers/interact.py +0 -5
- endoreg_db/helpers/test_video_helper.py +33 -25
- endoreg_db/import_files/__init__.py +1 -1
- endoreg_db/import_files/context/__init__.py +1 -1
- endoreg_db/import_files/context/default_sensitive_meta.py +11 -9
- endoreg_db/import_files/context/ensure_center.py +4 -4
- endoreg_db/import_files/context/file_lock.py +3 -3
- endoreg_db/import_files/context/import_context.py +11 -12
- endoreg_db/import_files/context/validate_directories.py +1 -0
- endoreg_db/import_files/file_storage/create_report_file.py +57 -34
- endoreg_db/import_files/file_storage/create_video_file.py +64 -35
- endoreg_db/import_files/file_storage/sensitive_meta_storage.py +5 -2
- endoreg_db/import_files/file_storage/state_management.py +146 -83
- endoreg_db/import_files/file_storage/storage.py +5 -1
- endoreg_db/import_files/processing/report_processing/report_anonymization.py +24 -19
- endoreg_db/import_files/processing/sensitive_meta_adapter.py +3 -3
- endoreg_db/import_files/processing/video_processing/video_anonymization.py +18 -18
- endoreg_db/import_files/pseudonymization/k_anonymity.py +8 -9
- endoreg_db/import_files/pseudonymization/k_pseudonymity.py +16 -5
- endoreg_db/import_files/report_import_service.py +36 -30
- endoreg_db/import_files/video_import_service.py +27 -23
- endoreg_db/logger_conf.py +56 -40
- endoreg_db/management/__init__.py +1 -1
- endoreg_db/management/commands/__init__.py +1 -1
- endoreg_db/management/commands/check_auth.py +45 -38
- endoreg_db/management/commands/create_model_meta_from_huggingface.py +53 -2
- endoreg_db/management/commands/create_multilabel_model_meta.py +54 -19
- endoreg_db/management/commands/fix_missing_patient_data.py +105 -71
- endoreg_db/management/commands/fix_video_paths.py +75 -54
- endoreg_db/management/commands/import_report.py +1 -3
- endoreg_db/management/commands/list_routes.py +2 -0
- endoreg_db/management/commands/load_ai_model_data.py +8 -2
- endoreg_db/management/commands/load_ai_model_label_data.py +0 -1
- endoreg_db/management/commands/load_center_data.py +3 -3
- endoreg_db/management/commands/load_distribution_data.py +35 -38
- endoreg_db/management/commands/load_endoscope_data.py +0 -3
- endoreg_db/management/commands/load_examination_data.py +20 -4
- endoreg_db/management/commands/load_finding_data.py +18 -3
- endoreg_db/management/commands/load_gender_data.py +17 -24
- endoreg_db/management/commands/load_green_endoscopy_wuerzburg_data.py +95 -85
- endoreg_db/management/commands/load_information_source.py +0 -3
- endoreg_db/management/commands/load_lab_value_data.py +14 -3
- endoreg_db/management/commands/load_legacy_data.py +303 -0
- endoreg_db/management/commands/load_name_data.py +1 -2
- endoreg_db/management/commands/load_pdf_type_data.py +4 -8
- endoreg_db/management/commands/load_profession_data.py +0 -1
- endoreg_db/management/commands/load_report_reader_flag_data.py +0 -4
- endoreg_db/management/commands/load_requirement_data.py +6 -2
- endoreg_db/management/commands/load_unit_data.py +0 -4
- endoreg_db/management/commands/load_user_groups.py +5 -7
- endoreg_db/management/commands/model_input.py +169 -0
- endoreg_db/management/commands/register_ai_model.py +22 -16
- endoreg_db/management/commands/setup_endoreg_db.py +110 -32
- endoreg_db/management/commands/storage_management.py +14 -8
- endoreg_db/management/commands/summarize_db_content.py +154 -63
- endoreg_db/management/commands/train_image_multilabel_model.py +144 -0
- endoreg_db/management/commands/validate_video_files.py +82 -50
- endoreg_db/management/commands/video_validation.py +4 -6
- endoreg_db/migrations/0001_initial.py +112 -63
- endoreg_db/migrations/__init__.py +0 -0
- endoreg_db/models/__init__.py +8 -0
- endoreg_db/models/administration/ai/active_model.py +5 -5
- endoreg_db/models/administration/ai/ai_model.py +41 -18
- endoreg_db/models/administration/ai/model_type.py +1 -0
- endoreg_db/models/administration/case/case.py +22 -22
- endoreg_db/models/administration/center/__init__.py +5 -5
- endoreg_db/models/administration/center/center.py +6 -2
- endoreg_db/models/administration/center/center_resource.py +18 -4
- endoreg_db/models/administration/center/center_shift.py +3 -1
- endoreg_db/models/administration/center/center_waste.py +6 -2
- endoreg_db/models/administration/person/__init__.py +1 -1
- endoreg_db/models/administration/person/employee/__init__.py +1 -1
- endoreg_db/models/administration/person/employee/employee_type.py +3 -1
- endoreg_db/models/administration/person/examiner/__init__.py +1 -1
- endoreg_db/models/administration/person/examiner/examiner.py +10 -2
- endoreg_db/models/administration/person/names/first_name.py +6 -4
- endoreg_db/models/administration/person/names/last_name.py +4 -3
- endoreg_db/models/administration/person/patient/__init__.py +1 -1
- endoreg_db/models/administration/person/patient/patient.py +0 -1
- endoreg_db/models/administration/person/patient/patient_external_id.py +0 -1
- endoreg_db/models/administration/person/person.py +1 -1
- endoreg_db/models/administration/product/__init__.py +7 -6
- endoreg_db/models/administration/product/product.py +6 -2
- endoreg_db/models/administration/product/product_group.py +9 -7
- endoreg_db/models/administration/product/product_material.py +9 -2
- endoreg_db/models/administration/product/reference_product.py +64 -15
- endoreg_db/models/administration/qualification/qualification.py +3 -1
- endoreg_db/models/administration/shift/shift.py +3 -1
- endoreg_db/models/administration/shift/shift_type.py +12 -4
- endoreg_db/models/aidataset/__init__.py +5 -0
- endoreg_db/models/aidataset/aidataset.py +193 -0
- endoreg_db/models/label/__init__.py +1 -1
- endoreg_db/models/label/label.py +10 -2
- endoreg_db/models/label/label_set.py +3 -1
- endoreg_db/models/label/label_video_segment/_create_from_video.py +6 -2
- endoreg_db/models/label/label_video_segment/label_video_segment.py +148 -44
- endoreg_db/models/media/__init__.py +12 -5
- endoreg_db/models/media/frame/__init__.py +1 -1
- endoreg_db/models/media/frame/frame.py +34 -8
- endoreg_db/models/media/pdf/__init__.py +2 -1
- endoreg_db/models/media/pdf/raw_pdf.py +11 -4
- endoreg_db/models/media/pdf/report_file.py +6 -2
- endoreg_db/models/media/pdf/report_reader/__init__.py +3 -3
- endoreg_db/models/media/pdf/report_reader/report_reader_flag.py +15 -5
- endoreg_db/models/media/video/create_from_file.py +20 -41
- endoreg_db/models/media/video/pipe_1.py +75 -30
- endoreg_db/models/media/video/pipe_2.py +37 -12
- endoreg_db/models/media/video/video_file.py +36 -24
- endoreg_db/models/media/video/video_file_ai.py +235 -70
- endoreg_db/models/media/video/video_file_anonymize.py +240 -65
- endoreg_db/models/media/video/video_file_frames/_bulk_create_frames.py +6 -1
- endoreg_db/models/media/video/video_file_frames/_create_frame_object.py +3 -1
- endoreg_db/models/media/video/video_file_frames/_delete_frames.py +30 -9
- endoreg_db/models/media/video/video_file_frames/_extract_frames.py +95 -29
- endoreg_db/models/media/video/video_file_frames/_get_frame.py +13 -3
- endoreg_db/models/media/video/video_file_frames/_get_frame_path.py +4 -1
- endoreg_db/models/media/video/video_file_frames/_get_frame_paths.py +15 -3
- endoreg_db/models/media/video/video_file_frames/_get_frame_range.py +15 -3
- endoreg_db/models/media/video/video_file_frames/_get_frames.py +7 -2
- endoreg_db/models/media/video/video_file_frames/_initialize_frames.py +109 -23
- endoreg_db/models/media/video/video_file_frames/_manage_frame_range.py +111 -27
- endoreg_db/models/media/video/video_file_frames/_mark_frames_extracted_status.py +46 -13
- endoreg_db/models/media/video/video_file_io.py +85 -33
- endoreg_db/models/media/video/video_file_meta/__init__.py +6 -6
- endoreg_db/models/media/video/video_file_meta/get_crop_template.py +17 -4
- endoreg_db/models/media/video/video_file_meta/get_endo_roi.py +28 -7
- endoreg_db/models/media/video/video_file_meta/get_fps.py +46 -13
- endoreg_db/models/media/video/video_file_meta/initialize_video_specs.py +81 -20
- endoreg_db/models/media/video/video_file_meta/text_meta.py +61 -20
- endoreg_db/models/media/video/video_file_meta/video_meta.py +40 -12
- endoreg_db/models/media/video/video_file_segments.py +118 -27
- endoreg_db/models/media/video/video_metadata.py +25 -6
- endoreg_db/models/media/video/video_processing.py +54 -15
- endoreg_db/models/medical/__init__.py +3 -13
- endoreg_db/models/medical/contraindication/__init__.py +3 -1
- endoreg_db/models/medical/disease.py +18 -6
- endoreg_db/models/medical/event.py +6 -2
- endoreg_db/models/medical/examination/__init__.py +5 -1
- endoreg_db/models/medical/examination/examination.py +22 -6
- endoreg_db/models/medical/examination/examination_indication.py +23 -7
- endoreg_db/models/medical/examination/examination_time.py +6 -2
- endoreg_db/models/medical/finding/__init__.py +3 -1
- endoreg_db/models/medical/finding/finding.py +37 -12
- endoreg_db/models/medical/finding/finding_classification.py +27 -8
- endoreg_db/models/medical/finding/finding_intervention.py +19 -6
- endoreg_db/models/medical/finding/finding_type.py +3 -1
- endoreg_db/models/medical/hardware/__init__.py +1 -1
- endoreg_db/models/medical/hardware/endoscope.py +14 -2
- endoreg_db/models/medical/laboratory/__init__.py +1 -1
- endoreg_db/models/medical/laboratory/lab_value.py +139 -39
- endoreg_db/models/medical/medication/__init__.py +7 -3
- endoreg_db/models/medical/medication/medication.py +3 -1
- endoreg_db/models/medical/medication/medication_indication.py +3 -1
- endoreg_db/models/medical/medication/medication_indication_type.py +11 -3
- endoreg_db/models/medical/medication/medication_intake_time.py +3 -1
- endoreg_db/models/medical/medication/medication_schedule.py +3 -1
- endoreg_db/models/medical/patient/__init__.py +2 -10
- endoreg_db/models/medical/patient/medication_examples.py +3 -14
- endoreg_db/models/medical/patient/patient_disease.py +17 -5
- endoreg_db/models/medical/patient/patient_event.py +12 -4
- endoreg_db/models/medical/patient/patient_examination.py +52 -15
- endoreg_db/models/medical/patient/patient_examination_indication.py +15 -4
- endoreg_db/models/medical/patient/patient_finding.py +105 -29
- endoreg_db/models/medical/patient/patient_finding_classification.py +41 -12
- endoreg_db/models/medical/patient/patient_finding_intervention.py +11 -3
- endoreg_db/models/medical/patient/patient_lab_sample.py +6 -2
- endoreg_db/models/medical/patient/patient_lab_value.py +42 -10
- endoreg_db/models/medical/patient/patient_medication.py +25 -7
- endoreg_db/models/medical/patient/patient_medication_schedule.py +34 -10
- endoreg_db/models/metadata/model_meta.py +40 -12
- endoreg_db/models/metadata/model_meta_logic.py +51 -16
- endoreg_db/models/metadata/sensitive_meta.py +65 -28
- endoreg_db/models/metadata/sensitive_meta_logic.py +28 -26
- endoreg_db/models/metadata/video_meta.py +146 -39
- endoreg_db/models/metadata/video_prediction_logic.py +70 -21
- endoreg_db/models/metadata/video_prediction_meta.py +80 -27
- endoreg_db/models/operation_log.py +63 -0
- endoreg_db/models/other/__init__.py +10 -10
- endoreg_db/models/other/distribution/__init__.py +9 -7
- endoreg_db/models/other/distribution/base_value_distribution.py +3 -1
- endoreg_db/models/other/distribution/date_value_distribution.py +19 -5
- endoreg_db/models/other/distribution/multiple_categorical_value_distribution.py +3 -1
- endoreg_db/models/other/distribution/numeric_value_distribution.py +34 -9
- endoreg_db/models/other/emission/__init__.py +1 -1
- endoreg_db/models/other/emission/emission_factor.py +9 -3
- endoreg_db/models/other/information_source.py +15 -5
- endoreg_db/models/other/material.py +3 -1
- endoreg_db/models/other/transport_route.py +3 -1
- endoreg_db/models/other/unit.py +6 -2
- endoreg_db/models/report/report.py +0 -1
- endoreg_db/models/requirement/requirement.py +84 -27
- endoreg_db/models/requirement/requirement_error.py +5 -6
- endoreg_db/models/requirement/requirement_evaluation/__init__.py +1 -1
- endoreg_db/models/requirement/requirement_evaluation/evaluate_with_dependencies.py +8 -8
- endoreg_db/models/requirement/requirement_evaluation/get_values.py +3 -3
- endoreg_db/models/requirement/requirement_evaluation/requirement_type_parser.py +24 -8
- endoreg_db/models/requirement/requirement_operator.py +28 -8
- endoreg_db/models/requirement/requirement_set.py +34 -11
- endoreg_db/models/state/__init__.py +1 -0
- endoreg_db/models/state/audit_ledger.py +9 -2
- endoreg_db/models/{media → state}/processing_history/__init__.py +1 -3
- endoreg_db/models/state/processing_history/processing_history.py +136 -0
- endoreg_db/models/state/raw_pdf.py +0 -1
- endoreg_db/models/state/video.py +2 -3
- endoreg_db/models/utils.py +4 -2
- endoreg_db/queries/__init__.py +2 -6
- endoreg_db/queries/annotations/__init__.py +1 -3
- endoreg_db/queries/annotations/legacy.py +37 -26
- endoreg_db/root_urls.py +3 -4
- endoreg_db/schemas/examination_evaluation.py +3 -0
- endoreg_db/serializers/Frames_NICE_and_PARIS_classifications.py +249 -163
- endoreg_db/serializers/__init__.py +2 -8
- endoreg_db/serializers/administration/__init__.py +1 -2
- endoreg_db/serializers/administration/ai/__init__.py +0 -1
- endoreg_db/serializers/administration/ai/active_model.py +3 -1
- endoreg_db/serializers/administration/ai/ai_model.py +5 -3
- endoreg_db/serializers/administration/ai/model_type.py +3 -1
- endoreg_db/serializers/administration/center.py +7 -2
- endoreg_db/serializers/administration/gender.py +4 -2
- endoreg_db/serializers/anonymization.py +13 -13
- endoreg_db/serializers/evaluation/examination_evaluation.py +0 -1
- endoreg_db/serializers/examination/__init__.py +1 -1
- endoreg_db/serializers/examination/base.py +12 -13
- endoreg_db/serializers/examination/dropdown.py +6 -7
- endoreg_db/serializers/examination_serializer.py +3 -6
- endoreg_db/serializers/finding/__init__.py +1 -1
- endoreg_db/serializers/finding/finding.py +14 -7
- endoreg_db/serializers/finding_classification/__init__.py +3 -3
- endoreg_db/serializers/finding_classification/choice.py +3 -3
- endoreg_db/serializers/finding_classification/classification.py +2 -4
- endoreg_db/serializers/label_video_segment/__init__.py +5 -3
- endoreg_db/serializers/{label → label_video_segment}/image_classification_annotation.py +5 -5
- endoreg_db/serializers/label_video_segment/label/__init__.py +6 -0
- endoreg_db/serializers/{label → label_video_segment/label}/label.py +1 -1
- endoreg_db/serializers/label_video_segment/label_video_segment.py +338 -228
- endoreg_db/serializers/meta/__init__.py +1 -2
- endoreg_db/serializers/meta/sensitive_meta_detail.py +28 -13
- endoreg_db/serializers/meta/sensitive_meta_update.py +51 -46
- endoreg_db/serializers/meta/sensitive_meta_verification.py +19 -16
- endoreg_db/serializers/misc/__init__.py +2 -2
- endoreg_db/serializers/misc/file_overview.py +11 -7
- endoreg_db/serializers/misc/stats.py +10 -8
- endoreg_db/serializers/misc/translatable_field_mix_in.py +6 -6
- endoreg_db/serializers/misc/upload_job.py +32 -29
- endoreg_db/serializers/patient/__init__.py +2 -1
- endoreg_db/serializers/patient/patient.py +32 -15
- endoreg_db/serializers/patient/patient_dropdown.py +11 -3
- endoreg_db/serializers/patient_examination/__init__.py +1 -1
- endoreg_db/serializers/patient_examination/patient_examination.py +67 -40
- endoreg_db/serializers/patient_finding/__init__.py +1 -1
- endoreg_db/serializers/patient_finding/patient_finding.py +2 -1
- endoreg_db/serializers/patient_finding/patient_finding_classification.py +17 -9
- endoreg_db/serializers/patient_finding/patient_finding_detail.py +26 -17
- endoreg_db/serializers/patient_finding/patient_finding_intervention.py +7 -5
- endoreg_db/serializers/patient_finding/patient_finding_list.py +10 -11
- endoreg_db/serializers/patient_finding/patient_finding_write.py +36 -27
- endoreg_db/serializers/pdf/__init__.py +1 -3
- endoreg_db/serializers/requirements/requirement_schema.py +1 -6
- endoreg_db/serializers/sensitive_meta_serializer.py +100 -81
- endoreg_db/serializers/video/__init__.py +2 -2
- endoreg_db/serializers/video/{segmentation.py → video_file.py} +66 -47
- endoreg_db/serializers/video/video_file_brief.py +6 -2
- endoreg_db/serializers/video/video_file_detail.py +36 -23
- endoreg_db/serializers/video/video_file_list.py +4 -2
- endoreg_db/serializers/video/video_processing_history.py +54 -50
- endoreg_db/services/__init__.py +1 -1
- endoreg_db/services/anonymization.py +2 -2
- endoreg_db/services/examination_evaluation.py +40 -17
- endoreg_db/services/model_meta_from_hf.py +76 -0
- endoreg_db/services/polling_coordinator.py +101 -70
- endoreg_db/services/pseudonym_service.py +27 -22
- endoreg_db/services/report_import.py +6 -3
- endoreg_db/services/segment_sync.py +75 -59
- endoreg_db/services/video_import.py +6 -7
- endoreg_db/urls/__init__.py +2 -2
- endoreg_db/urls/ai.py +7 -25
- endoreg_db/urls/anonymization.py +61 -15
- endoreg_db/urls/auth.py +4 -4
- endoreg_db/urls/classification.py +4 -9
- endoreg_db/urls/examination.py +27 -18
- endoreg_db/urls/media.py +27 -34
- endoreg_db/urls/patient.py +11 -7
- endoreg_db/urls/requirements.py +3 -1
- endoreg_db/urls/root_urls.py +2 -3
- endoreg_db/urls/stats.py +24 -16
- endoreg_db/urls/upload.py +3 -11
- endoreg_db/utils/__init__.py +14 -15
- endoreg_db/utils/ai/__init__.py +1 -1
- endoreg_db/utils/ai/data_loader_for_model_input.py +262 -0
- endoreg_db/utils/ai/data_loader_for_model_training.py +262 -0
- endoreg_db/utils/ai/get.py +2 -1
- endoreg_db/utils/ai/inference_dataset.py +14 -15
- endoreg_db/utils/ai/model_training/config.py +117 -0
- endoreg_db/utils/ai/model_training/dataset.py +74 -0
- endoreg_db/utils/ai/model_training/losses.py +68 -0
- endoreg_db/utils/ai/model_training/metrics.py +78 -0
- endoreg_db/utils/ai/model_training/model_backbones.py +155 -0
- endoreg_db/utils/ai/model_training/model_gastronet_resnet.py +118 -0
- endoreg_db/utils/ai/model_training/trainer_gastronet_multilabel.py +771 -0
- endoreg_db/utils/ai/multilabel_classification_net.py +21 -6
- endoreg_db/utils/ai/predict.py +4 -4
- endoreg_db/utils/ai/preprocess.py +19 -11
- endoreg_db/utils/calc_duration_seconds.py +4 -4
- endoreg_db/utils/case_generator/lab_sample_factory.py +3 -4
- endoreg_db/utils/check_video_files.py +74 -47
- endoreg_db/utils/cropping.py +10 -9
- endoreg_db/utils/dataloader.py +11 -3
- endoreg_db/utils/dates.py +3 -4
- endoreg_db/utils/defaults/set_default_center.py +7 -6
- endoreg_db/utils/env.py +6 -2
- endoreg_db/utils/extract_specific_frames.py +24 -9
- endoreg_db/utils/file_operations.py +30 -18
- endoreg_db/utils/fix_video_path_direct.py +57 -41
- endoreg_db/utils/frame_anonymization_utils.py +157 -157
- endoreg_db/utils/hashs.py +3 -18
- endoreg_db/utils/links/requirement_link.py +96 -52
- endoreg_db/utils/ocr.py +30 -25
- endoreg_db/utils/operation_log.py +61 -0
- endoreg_db/utils/parse_and_generate_yaml.py +12 -13
- endoreg_db/utils/paths.py +6 -6
- endoreg_db/utils/permissions.py +40 -24
- endoreg_db/utils/pipelines/process_video_dir.py +50 -26
- endoreg_db/utils/product/sum_emissions.py +5 -3
- endoreg_db/utils/product/sum_weights.py +4 -2
- endoreg_db/utils/pydantic_models/__init__.py +3 -4
- endoreg_db/utils/requirement_operator_logic/_old/lab_value_operators.py +207 -107
- endoreg_db/utils/requirement_operator_logic/_old/model_evaluators.py +252 -65
- endoreg_db/utils/requirement_operator_logic/new_operator_logic.py +27 -10
- endoreg_db/utils/setup_config.py +21 -5
- endoreg_db/utils/storage.py +3 -1
- endoreg_db/utils/translation.py +19 -15
- endoreg_db/utils/uuid.py +1 -0
- endoreg_db/utils/validate_endo_roi.py +12 -4
- endoreg_db/utils/validate_subcategory_dict.py +26 -24
- endoreg_db/utils/validate_video_detailed.py +207 -149
- endoreg_db/utils/video/__init__.py +7 -3
- endoreg_db/utils/video/extract_frames.py +30 -18
- endoreg_db/utils/video/ffmpeg_wrapper.py +217 -52
- endoreg_db/utils/video/names.py +11 -6
- endoreg_db/utils/video/streaming_processor.py +175 -101
- endoreg_db/utils/video/video_splitter.py +30 -19
- endoreg_db/views/Frames_NICE_and_PARIS_classifications_views.py +59 -50
- endoreg_db/views/__init__.py +0 -20
- endoreg_db/views/anonymization/__init__.py +6 -2
- endoreg_db/views/anonymization/media_management.py +2 -6
- endoreg_db/views/anonymization/overview.py +34 -1
- endoreg_db/views/anonymization/validate.py +79 -18
- endoreg_db/views/auth/__init__.py +1 -1
- endoreg_db/views/auth/keycloak.py +16 -14
- endoreg_db/views/examination/__init__.py +12 -15
- endoreg_db/views/examination/examination.py +5 -5
- endoreg_db/views/examination/examination_manifest_cache.py +5 -5
- endoreg_db/views/examination/get_finding_classification_choices.py +8 -5
- endoreg_db/views/examination/get_finding_classifications.py +9 -7
- endoreg_db/views/examination/get_findings.py +8 -10
- endoreg_db/views/examination/get_instruments.py +3 -2
- endoreg_db/views/examination/get_interventions.py +1 -1
- endoreg_db/views/finding/__init__.py +2 -2
- endoreg_db/views/finding/finding.py +58 -54
- endoreg_db/views/finding/get_classifications.py +1 -1
- endoreg_db/views/finding/get_interventions.py +1 -1
- endoreg_db/views/finding_classification/__init__.py +5 -5
- endoreg_db/views/finding_classification/finding_classification.py +5 -6
- endoreg_db/views/finding_classification/get_classification_choices.py +3 -4
- endoreg_db/views/media/__init__.py +13 -13
- endoreg_db/views/media/pdf_media.py +9 -9
- endoreg_db/views/media/sensitive_metadata.py +10 -7
- endoreg_db/views/media/video_media.py +4 -4
- endoreg_db/views/meta/__init__.py +1 -1
- endoreg_db/views/meta/sensitive_meta_list.py +20 -22
- endoreg_db/views/meta/sensitive_meta_verification.py +14 -11
- endoreg_db/views/misc/__init__.py +6 -34
- endoreg_db/views/misc/center.py +2 -1
- endoreg_db/views/misc/csrf.py +2 -1
- endoreg_db/views/misc/gender.py +2 -1
- endoreg_db/views/misc/stats.py +141 -106
- endoreg_db/views/patient/__init__.py +1 -3
- endoreg_db/views/patient/patient.py +141 -99
- endoreg_db/views/patient_examination/__init__.py +5 -5
- endoreg_db/views/patient_examination/patient_examination.py +43 -42
- endoreg_db/views/patient_examination/patient_examination_create.py +10 -15
- endoreg_db/views/patient_examination/patient_examination_detail.py +12 -15
- endoreg_db/views/patient_examination/patient_examination_list.py +21 -17
- endoreg_db/views/patient_examination/video.py +114 -80
- endoreg_db/views/patient_finding/__init__.py +1 -1
- endoreg_db/views/patient_finding/patient_finding.py +17 -10
- endoreg_db/views/patient_finding/patient_finding_optimized.py +127 -95
- endoreg_db/views/patient_finding_classification/__init__.py +1 -1
- endoreg_db/views/patient_finding_classification/pfc_create.py +35 -27
- endoreg_db/views/report/reimport.py +1 -1
- endoreg_db/views/report/report_stream.py +5 -8
- endoreg_db/views/requirement/__init__.py +2 -1
- endoreg_db/views/requirement/evaluate.py +7 -9
- endoreg_db/views/requirement/lookup.py +2 -3
- endoreg_db/views/requirement/lookup_store.py +0 -1
- endoreg_db/views/requirement/requirement_utils.py +2 -4
- endoreg_db/views/stats/__init__.py +4 -4
- endoreg_db/views/stats/stats_views.py +152 -115
- endoreg_db/views/video/__init__.py +18 -27
- endoreg_db/views/{ai → video/ai}/__init__.py +2 -2
- endoreg_db/views/{ai → video/ai}/label.py +20 -16
- endoreg_db/views/video/correction.py +5 -6
- endoreg_db/views/video/reimport.py +134 -99
- endoreg_db/views/video/segments_crud.py +134 -44
- endoreg_db/views/video/video_apply_mask.py +13 -12
- endoreg_db/views/video/video_correction.py +2 -1
- endoreg_db/views/video/video_download_processed.py +15 -15
- endoreg_db/views/video/video_meta_stats.py +7 -6
- endoreg_db/views/video/video_processing_history.py +3 -2
- endoreg_db/views/video/video_remove_frames.py +13 -12
- endoreg_db/views/video/video_stream.py +110 -82
- {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/METADATA +9 -3
- {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/RECORD +436 -433
- endoreg_db/import_files/processing/video_processing/video_cleanup_on_error.py +0 -119
- endoreg_db/management/commands/import_fallback_video.py +0 -203
- endoreg_db/management/commands/import_video.py +0 -422
- endoreg_db/management/commands/import_video_with_classification.py +0 -367
- endoreg_db/models/media/processing_history/processing_history.py +0 -96
- endoreg_db/serializers/label/__init__.py +0 -7
- endoreg_db/serializers/label_video_segment/_lvs_create.py +0 -149
- endoreg_db/serializers/label_video_segment/_lvs_update.py +0 -138
- endoreg_db/serializers/label_video_segment/_lvs_validate.py +0 -149
- endoreg_db/serializers/label_video_segment/label_video_segment_annotation.py +0 -99
- endoreg_db/serializers/label_video_segment/label_video_segment_update.py +0 -163
- endoreg_db/services/__old/pdf_import.py +0 -1487
- endoreg_db/services/__old/video_import.py +0 -1306
- endoreg_db/tasks/upload_tasks.py +0 -216
- endoreg_db/tasks/video_ingest.py +0 -161
- endoreg_db/tasks/video_processing_tasks.py +0 -327
- endoreg_db/views/misc/translation.py +0 -182
- {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/WHEEL +0 -0
- {endoreg_db-0.8.8.9.dist-info → endoreg_db-0.8.9.10.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
# app/services/evaluation.py
|
|
2
2
|
from __future__ import annotations
|
|
3
|
-
from typing import
|
|
4
|
-
from django.db.models import Prefetch
|
|
3
|
+
from typing import List, Set, Tuple
|
|
5
4
|
|
|
6
|
-
from endoreg_db.schemas.examination_evaluation import
|
|
5
|
+
from endoreg_db.schemas.examination_evaluation import (
|
|
6
|
+
ExaminationEvalReport,
|
|
7
|
+
RequirementSetEval,
|
|
8
|
+
RequirementEval,
|
|
9
|
+
)
|
|
7
10
|
from endoreg_db.models.medical.patient.patient_examination import PatientExamination
|
|
8
11
|
from endoreg_db.models.requirement.requirement_set import RequirementSet
|
|
9
12
|
import endoreg_db.services.lookup_service
|
|
10
13
|
|
|
14
|
+
|
|
11
15
|
def _get_requirement_sets_for_exam(exam: PatientExamination) -> List[RequirementSet]:
|
|
12
16
|
"""
|
|
13
17
|
Decide how an examination maps to requirement sets.
|
|
@@ -25,7 +29,10 @@ def _get_requirement_sets_for_exam(exam: PatientExamination) -> List[Requirement
|
|
|
25
29
|
sets.append(link.requirement_set)
|
|
26
30
|
return sets
|
|
27
31
|
|
|
28
|
-
|
|
32
|
+
|
|
33
|
+
def _eval_requirement(
|
|
34
|
+
requirement, input_object, mode="loose"
|
|
35
|
+
) -> Tuple[bool, str | None]:
|
|
29
36
|
"""
|
|
30
37
|
Evaluate a single Requirement and return (bool, message).
|
|
31
38
|
The `message` can be None or contain a short explanation.
|
|
@@ -36,12 +43,19 @@ def _eval_requirement(requirement, input_object, mode="loose") -> Tuple[bool, st
|
|
|
36
43
|
# Example: msg = requirement.last_reason if hasattr(requirement, "last_reason") else None
|
|
37
44
|
return ok, msg
|
|
38
45
|
|
|
46
|
+
|
|
39
47
|
def _reduce_bools(bools: List[bool], set_type_name: str | None) -> bool:
|
|
40
|
-
from endoreg_db.models.requirement.requirement_set import
|
|
48
|
+
from endoreg_db.models.requirement.requirement_set import (
|
|
49
|
+
REQUIREMENT_SET_TYPE_FUNCTION_LOOKUP,
|
|
50
|
+
)
|
|
51
|
+
|
|
41
52
|
func = REQUIREMENT_SET_TYPE_FUNCTION_LOOKUP.get(set_type_name or "all", all)
|
|
42
53
|
return bool(func(bools))
|
|
43
54
|
|
|
44
|
-
|
|
55
|
+
|
|
56
|
+
def _eval_set_tree(
|
|
57
|
+
root: RequirementSet, input_object, visited: Set[int]
|
|
58
|
+
) -> RequirementSetEval:
|
|
45
59
|
"""
|
|
46
60
|
Recursively evaluate a RequirementSet node and linked children.
|
|
47
61
|
Protect against cycles with visited set.
|
|
@@ -52,10 +66,12 @@ def _eval_set_tree(root: RequirementSet, input_object, visited: Set[int]) -> Req
|
|
|
52
66
|
return RequirementSetEval(
|
|
53
67
|
id=root.pk,
|
|
54
68
|
name=root.name,
|
|
55
|
-
type=(
|
|
69
|
+
type=(
|
|
70
|
+
root.requirement_set_type.name if root.requirement_set_type else None
|
|
71
|
+
),
|
|
56
72
|
is_satisfied=True,
|
|
57
73
|
requirements=[],
|
|
58
|
-
linked_sets=[]
|
|
74
|
+
linked_sets=[],
|
|
59
75
|
)
|
|
60
76
|
|
|
61
77
|
visited.add(root.pk)
|
|
@@ -81,7 +97,9 @@ def _eval_set_tree(root: RequirementSet, input_object, visited: Set[int]) -> Req
|
|
|
81
97
|
|
|
82
98
|
# Combine booleans
|
|
83
99
|
bools = [re.satisfied for re in req_evals] + [ce.is_satisfied for ce in child_evals]
|
|
84
|
-
set_type_name =
|
|
100
|
+
set_type_name = (
|
|
101
|
+
root.requirement_set_type.name if root.requirement_set_type else "all"
|
|
102
|
+
)
|
|
85
103
|
satisfied = _reduce_bools(bools, set_type_name)
|
|
86
104
|
|
|
87
105
|
return RequirementSetEval(
|
|
@@ -92,20 +110,19 @@ def _eval_set_tree(root: RequirementSet, input_object, visited: Set[int]) -> Req
|
|
|
92
110
|
requirements=req_evals,
|
|
93
111
|
linked_sets=child_evals,
|
|
94
112
|
)
|
|
95
|
-
|
|
96
113
|
|
|
97
114
|
|
|
98
115
|
def evaluate_examination(request: dict) -> ExaminationEvalReport:
|
|
99
116
|
"""
|
|
100
117
|
Communicates with: components/RequirementGenerator
|
|
101
118
|
Evaluates a PatientExamination by its Lookup. The frontend sends this structure:
|
|
102
|
-
|
|
103
|
-
|
|
119
|
+
|
|
120
|
+
|
|
104
121
|
requirement_set_ids: plainRequirementSetIds,
|
|
105
122
|
lookup_token: lookupToken,
|
|
106
123
|
patient_examination_id: patientExaminationId
|
|
107
124
|
};
|
|
108
|
-
|
|
125
|
+
|
|
109
126
|
And expects a response to be processed like this:
|
|
110
127
|
|
|
111
128
|
const response = await axiosInstance.post('/api/evaluate-requirements/', payload);
|
|
@@ -125,9 +142,9 @@ def evaluate_examination(request: dict) -> ExaminationEvalReport:
|
|
|
125
142
|
examination_id=None,
|
|
126
143
|
summary={"is_satisfied": True, "failed_count": 0, "total_sets": 0},
|
|
127
144
|
sets=[],
|
|
128
|
-
errors=["No patient_examination_id provided in request."]
|
|
145
|
+
errors=["No patient_examination_id provided in request."],
|
|
129
146
|
)
|
|
130
|
-
|
|
147
|
+
|
|
131
148
|
# Use the dedicated loader function from the lookup service.
|
|
132
149
|
exam = endoreg_db.services.lookup_service.load_patient_exam_for_eval(pk=exam_id)
|
|
133
150
|
|
|
@@ -135,7 +152,9 @@ def evaluate_examination(request: dict) -> ExaminationEvalReport:
|
|
|
135
152
|
sets = _get_requirement_sets_for_exam(exam)
|
|
136
153
|
|
|
137
154
|
visited: Set[int] = set()
|
|
138
|
-
set_evals: List[RequirementSetEval] = [
|
|
155
|
+
set_evals: List[RequirementSetEval] = [
|
|
156
|
+
_eval_set_tree(s, exam, visited) for s in sets
|
|
157
|
+
]
|
|
139
158
|
|
|
140
159
|
# Aggregate summary
|
|
141
160
|
overall = all(se.is_satisfied for se in set_evals) if set_evals else True
|
|
@@ -143,7 +162,11 @@ def evaluate_examination(request: dict) -> ExaminationEvalReport:
|
|
|
143
162
|
|
|
144
163
|
return ExaminationEvalReport(
|
|
145
164
|
examination_id=exam.pk,
|
|
146
|
-
summary={
|
|
165
|
+
summary={
|
|
166
|
+
"is_satisfied": overall,
|
|
167
|
+
"failed_count": failed,
|
|
168
|
+
"total_sets": len(set_evals),
|
|
169
|
+
},
|
|
147
170
|
sets=set_evals,
|
|
148
171
|
errors=[], # fill with any global issues you detect
|
|
149
172
|
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# endoreg_db/services/model_meta_from_hf.py
|
|
2
|
+
|
|
3
|
+
from django.core.files.base import ContentFile
|
|
4
|
+
from huggingface_hub import hf_hub_download
|
|
5
|
+
|
|
6
|
+
from endoreg_db.models import AiModel, LabelSet, ModelMeta
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def ensure_model_meta_from_hf(
|
|
10
|
+
*,
|
|
11
|
+
model_id: str,
|
|
12
|
+
model_name: str,
|
|
13
|
+
labelset_name: str,
|
|
14
|
+
meta_version: str = "1",
|
|
15
|
+
labelset_version: int | None = None,
|
|
16
|
+
) -> ModelMeta:
|
|
17
|
+
"""
|
|
18
|
+
Download weights from Hugging Face (if needed) and ensure a ModelMeta
|
|
19
|
+
exists for the given configuration. Returns the ModelMeta.
|
|
20
|
+
"""
|
|
21
|
+
# Download the model weights
|
|
22
|
+
weights_path = hf_hub_download(
|
|
23
|
+
repo_id=model_id,
|
|
24
|
+
filename="colo_segmentation_RegNetX800MF_base.safetensors",
|
|
25
|
+
local_dir="/tmp",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Get or create AI model
|
|
29
|
+
ai_model, _ = AiModel.objects.get_or_create(
|
|
30
|
+
name=model_name, defaults={"description": f"Model from {model_id}"}
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Get labelset
|
|
34
|
+
labelset_qs = LabelSet.objects.filter(name=labelset_name)
|
|
35
|
+
if labelset_version is not None:
|
|
36
|
+
labelset_qs = labelset_qs.filter(version=labelset_version)
|
|
37
|
+
labelset = labelset_qs.order_by("-version").first()
|
|
38
|
+
if labelset is None:
|
|
39
|
+
raise ValueError(
|
|
40
|
+
f"LabelSet '{labelset_name}'"
|
|
41
|
+
+ (f" v{labelset_version}" if labelset_version is not None else "")
|
|
42
|
+
+ " not found"
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Create or get ModelMeta
|
|
46
|
+
model_meta, _ = ModelMeta.objects.get_or_create(
|
|
47
|
+
name=model_name,
|
|
48
|
+
model=ai_model,
|
|
49
|
+
version=meta_version,
|
|
50
|
+
defaults={
|
|
51
|
+
"labelset": labelset,
|
|
52
|
+
"activation": "sigmoid",
|
|
53
|
+
"mean": "0.45211223,0.27139644,0.19264949",
|
|
54
|
+
"std": "0.31418097,0.21088019,0.16059452",
|
|
55
|
+
"size_x": 716,
|
|
56
|
+
"size_y": 716,
|
|
57
|
+
"axes": "2,0,1",
|
|
58
|
+
"batchsize": 16,
|
|
59
|
+
"num_workers": 0,
|
|
60
|
+
"description": f"Downloaded from {model_id}",
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# If weights file not yet saved, save it
|
|
65
|
+
if not model_meta.weights:
|
|
66
|
+
with open(weights_path, "rb") as f:
|
|
67
|
+
model_meta.weights.save(
|
|
68
|
+
f"{model_name}_v{meta_version}.safetensors",
|
|
69
|
+
ContentFile(f.read()),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Set as active meta
|
|
73
|
+
ai_model.active_meta = model_meta
|
|
74
|
+
ai_model.save(update_fields=["active_meta"])
|
|
75
|
+
|
|
76
|
+
return model_meta
|
|
@@ -9,50 +9,57 @@ from datetime import timedelta
|
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger(__name__)
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
class PollingCoordinator:
|
|
13
14
|
"""
|
|
14
15
|
Service to prevent duplicate polling operations on the same media items.
|
|
15
16
|
Uses Django cache and thread-safe operations to coordinate polling requests.
|
|
16
17
|
"""
|
|
17
|
-
|
|
18
|
+
|
|
18
19
|
# Class-level lock for thread safety
|
|
19
20
|
_lock = threading.Lock()
|
|
20
|
-
|
|
21
|
+
|
|
21
22
|
# Cache key prefixes
|
|
22
23
|
PROCESSING_PREFIX = "polling_processing:"
|
|
23
24
|
LAST_CHECK_PREFIX = "polling_last_check:"
|
|
24
|
-
|
|
25
|
+
|
|
25
26
|
# Default timeouts
|
|
26
27
|
PROCESSING_TIMEOUT = 300 # 5 minutes
|
|
27
|
-
CHECK_COOLDOWN = 10
|
|
28
|
-
|
|
28
|
+
CHECK_COOLDOWN = 10 # 10 seconds minimum between checks
|
|
29
|
+
|
|
29
30
|
@classmethod
|
|
30
|
-
def acquire_processing_lock(
|
|
31
|
+
def acquire_processing_lock(
|
|
32
|
+
cls, file_id: int, file_type: str = "video", timeout: Optional[int] = None
|
|
33
|
+
) -> bool:
|
|
31
34
|
"""
|
|
32
35
|
Acquire a processing lock for a media file to prevent duplicate processing.
|
|
33
|
-
|
|
36
|
+
|
|
34
37
|
Args:
|
|
35
38
|
file_id: ID of the media file
|
|
36
39
|
file_type: Type of media (video, pdf)
|
|
37
40
|
timeout: Lock timeout in seconds (default: 5 minutes)
|
|
38
|
-
|
|
41
|
+
|
|
39
42
|
Returns:
|
|
40
43
|
True if lock acquired, False if already locked
|
|
41
44
|
"""
|
|
42
45
|
if timeout is None:
|
|
43
46
|
timeout = cls.PROCESSING_TIMEOUT
|
|
44
|
-
|
|
47
|
+
|
|
45
48
|
cache_key = f"{cls.PROCESSING_PREFIX}{file_type}:{file_id}"
|
|
46
|
-
|
|
49
|
+
|
|
47
50
|
with cls._lock:
|
|
48
51
|
# Try to acquire lock atomically
|
|
49
|
-
lock_acquired = cache.add(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
lock_acquired = cache.add(
|
|
53
|
+
cache_key,
|
|
54
|
+
{
|
|
55
|
+
"locked_at": timezone.now().isoformat(),
|
|
56
|
+
"file_id": file_id,
|
|
57
|
+
"file_type": file_type,
|
|
58
|
+
"thread_id": threading.get_ident(),
|
|
59
|
+
},
|
|
60
|
+
timeout,
|
|
61
|
+
)
|
|
62
|
+
|
|
56
63
|
if lock_acquired:
|
|
57
64
|
logger.info(f"Processing lock acquired for {file_type}:{file_id}")
|
|
58
65
|
return True
|
|
@@ -60,102 +67,116 @@ class PollingCoordinator:
|
|
|
60
67
|
# Check if existing lock is stale
|
|
61
68
|
existing_lock = cache.get(cache_key)
|
|
62
69
|
if existing_lock:
|
|
63
|
-
logger.warning(
|
|
70
|
+
logger.warning(
|
|
71
|
+
f"Processing lock already exists for {file_type}:{file_id}: {existing_lock}"
|
|
72
|
+
)
|
|
64
73
|
else:
|
|
65
|
-
logger.warning(
|
|
74
|
+
logger.warning(
|
|
75
|
+
f"Failed to acquire processing lock for {file_type}:{file_id}"
|
|
76
|
+
)
|
|
66
77
|
return False
|
|
67
|
-
|
|
78
|
+
|
|
68
79
|
@classmethod
|
|
69
80
|
def release_processing_lock(cls, file_id: int, file_type: str = "video") -> bool:
|
|
70
81
|
"""
|
|
71
82
|
Release a processing lock for a media file.
|
|
72
|
-
|
|
83
|
+
|
|
73
84
|
Args:
|
|
74
85
|
file_id: ID of the media file
|
|
75
86
|
file_type: Type of media (video, pdf)
|
|
76
|
-
|
|
87
|
+
|
|
77
88
|
Returns:
|
|
78
89
|
True if lock released, False if lock didn't exist
|
|
79
90
|
"""
|
|
80
91
|
cache_key = f"{cls.PROCESSING_PREFIX}{file_type}:{file_id}"
|
|
81
|
-
|
|
92
|
+
|
|
82
93
|
with cls._lock:
|
|
83
94
|
if cache.delete(cache_key):
|
|
84
95
|
logger.info(f"Processing lock released for {file_type}:{file_id}")
|
|
85
96
|
return True
|
|
86
97
|
else:
|
|
87
|
-
logger.warning(
|
|
98
|
+
logger.warning(
|
|
99
|
+
f"No processing lock found to release for {file_type}:{file_id}"
|
|
100
|
+
)
|
|
88
101
|
return False
|
|
89
|
-
|
|
102
|
+
|
|
90
103
|
@classmethod
|
|
91
104
|
def is_processing_locked(cls, file_id: int, file_type: str = "video") -> bool:
|
|
92
105
|
"""
|
|
93
106
|
Check if a media file is currently processing locked.
|
|
94
|
-
|
|
107
|
+
|
|
95
108
|
Args:
|
|
96
109
|
file_id: ID of the media file
|
|
97
110
|
file_type: Type of media (video, pdf)
|
|
98
|
-
|
|
111
|
+
|
|
99
112
|
Returns:
|
|
100
113
|
True if locked, False otherwise
|
|
101
114
|
"""
|
|
102
115
|
cache_key = f"{cls.PROCESSING_PREFIX}{file_type}:{file_id}"
|
|
103
116
|
return cache.get(cache_key) is not None
|
|
104
|
-
|
|
117
|
+
|
|
105
118
|
@classmethod
|
|
106
119
|
def can_check_status(cls, file_id: int, file_type: str = "video") -> bool:
|
|
107
120
|
"""
|
|
108
121
|
Check if enough time has passed since last status check to prevent spam.
|
|
109
|
-
|
|
122
|
+
|
|
110
123
|
Args:
|
|
111
124
|
file_id: ID of the media file
|
|
112
125
|
file_type: Type of media (video, pdf)
|
|
113
|
-
|
|
126
|
+
|
|
114
127
|
Returns:
|
|
115
128
|
True if status check is allowed, False if still in cooldown
|
|
116
129
|
"""
|
|
117
130
|
cache_key = f"{cls.LAST_CHECK_PREFIX}{file_type}:{file_id}"
|
|
118
131
|
last_check = cache.get(cache_key)
|
|
119
|
-
|
|
132
|
+
|
|
120
133
|
if last_check is None:
|
|
121
134
|
# First check or cooldown expired - allowed
|
|
122
135
|
cls._record_status_check(file_id, file_type)
|
|
123
136
|
return True
|
|
124
|
-
|
|
137
|
+
|
|
125
138
|
# Check if cooldown period has passed
|
|
126
|
-
last_check_time = timezone.datetime.fromisoformat(
|
|
139
|
+
last_check_time = timezone.datetime.fromisoformat(
|
|
140
|
+
last_check.replace("Z", "+00:00")
|
|
141
|
+
)
|
|
127
142
|
cooldown_end = last_check_time + timedelta(seconds=cls.CHECK_COOLDOWN)
|
|
128
|
-
|
|
143
|
+
|
|
129
144
|
if timezone.now() > cooldown_end:
|
|
130
145
|
cls._record_status_check(file_id, file_type)
|
|
131
146
|
return True
|
|
132
147
|
else:
|
|
133
148
|
remaining_cooldown = (cooldown_end - timezone.now()).total_seconds()
|
|
134
|
-
logger.debug(
|
|
149
|
+
logger.debug(
|
|
150
|
+
f"Status check cooldown active for {file_type}:{file_id}, {remaining_cooldown:.1f}s remaining"
|
|
151
|
+
)
|
|
135
152
|
return False
|
|
136
|
-
|
|
153
|
+
|
|
137
154
|
@classmethod
|
|
138
|
-
def get_remaining_cooldown_seconds(
|
|
155
|
+
def get_remaining_cooldown_seconds(
|
|
156
|
+
cls, file_id: int, file_type: str = "video"
|
|
157
|
+
) -> int:
|
|
139
158
|
"""
|
|
140
159
|
Get the remaining cooldown seconds for a status check.
|
|
141
|
-
|
|
160
|
+
|
|
142
161
|
Args:
|
|
143
162
|
file_id: ID of the media file
|
|
144
163
|
file_type: Type of media (video, pdf)
|
|
145
|
-
|
|
164
|
+
|
|
146
165
|
Returns:
|
|
147
166
|
Remaining cooldown in seconds (0 if no cooldown active)
|
|
148
167
|
"""
|
|
149
168
|
cache_key = f"{cls.LAST_CHECK_PREFIX}{file_type}:{file_id}"
|
|
150
169
|
last_check = cache.get(cache_key)
|
|
151
|
-
|
|
170
|
+
|
|
152
171
|
if last_check is None:
|
|
153
172
|
return 0
|
|
154
|
-
|
|
173
|
+
|
|
155
174
|
# Check if cooldown period has passed
|
|
156
|
-
last_check_time = timezone.datetime.fromisoformat(
|
|
175
|
+
last_check_time = timezone.datetime.fromisoformat(
|
|
176
|
+
last_check.replace("Z", "+00:00")
|
|
177
|
+
)
|
|
157
178
|
cooldown_end = last_check_time + timedelta(seconds=cls.CHECK_COOLDOWN)
|
|
158
|
-
|
|
179
|
+
|
|
159
180
|
if timezone.now() > cooldown_end:
|
|
160
181
|
return 0
|
|
161
182
|
else:
|
|
@@ -168,47 +189,49 @@ class PollingCoordinator:
|
|
|
168
189
|
"""Record the time of a status check"""
|
|
169
190
|
cache_key = f"{cls.LAST_CHECK_PREFIX}{file_type}:{file_id}"
|
|
170
191
|
cache.set(cache_key, timezone.now().isoformat(), cls.CHECK_COOLDOWN + 5)
|
|
171
|
-
|
|
192
|
+
|
|
172
193
|
@classmethod
|
|
173
194
|
def get_processing_locks_info(cls) -> Dict[str, any]:
|
|
174
195
|
"""
|
|
175
196
|
Get information about all currently active processing locks.
|
|
176
197
|
Useful for debugging and monitoring.
|
|
177
|
-
|
|
198
|
+
|
|
178
199
|
Returns:
|
|
179
200
|
Dictionary with lock information
|
|
180
201
|
"""
|
|
181
202
|
# Note: This is a simplified version since Django cache doesn't support pattern scanning
|
|
182
203
|
# In production, consider using Redis with SCAN command for better performance
|
|
183
|
-
|
|
204
|
+
|
|
184
205
|
info = {
|
|
185
206
|
"coordinator_status": "active",
|
|
186
207
|
"config": {
|
|
187
208
|
"processing_timeout": cls.PROCESSING_TIMEOUT,
|
|
188
|
-
"check_cooldown": cls.CHECK_COOLDOWN
|
|
209
|
+
"check_cooldown": cls.CHECK_COOLDOWN,
|
|
189
210
|
},
|
|
190
|
-
"note": "Active locks info requires Redis backend for pattern scanning"
|
|
211
|
+
"note": "Active locks info requires Redis backend for pattern scanning",
|
|
191
212
|
}
|
|
192
|
-
|
|
213
|
+
|
|
193
214
|
return info
|
|
194
|
-
|
|
215
|
+
|
|
195
216
|
@classmethod
|
|
196
217
|
def clear_all_locks(cls, file_type: Optional[str] = None) -> int:
|
|
197
218
|
"""
|
|
198
219
|
Emergency function to clear all processing locks.
|
|
199
220
|
Use with caution - only for debugging/recovery scenarios.
|
|
200
|
-
|
|
221
|
+
|
|
201
222
|
Args:
|
|
202
223
|
file_type: Optionally clear locks only for specific file type
|
|
203
|
-
|
|
224
|
+
|
|
204
225
|
Returns:
|
|
205
226
|
Number of locks cleared (approximation)
|
|
206
227
|
"""
|
|
207
|
-
logger.warning(
|
|
208
|
-
|
|
228
|
+
logger.warning(
|
|
229
|
+
"clear_all_locks called - this should only be used for debugging/recovery"
|
|
230
|
+
)
|
|
231
|
+
|
|
209
232
|
# This is a simplified implementation
|
|
210
233
|
# In production with Redis, you'd use SCAN to find and delete matching keys
|
|
211
|
-
if hasattr(cache,
|
|
234
|
+
if hasattr(cache, "clear"):
|
|
212
235
|
cache.clear()
|
|
213
236
|
return -1 # Unknown count
|
|
214
237
|
else:
|
|
@@ -220,38 +243,44 @@ class PollingCoordinator:
|
|
|
220
243
|
def processing_coordination(file_id_param: str = "file_id", file_type: str = "video"):
|
|
221
244
|
"""
|
|
222
245
|
Decorator to add automatic processing coordination to views.
|
|
223
|
-
|
|
246
|
+
|
|
224
247
|
Args:
|
|
225
248
|
file_id_param: Name of the parameter containing file ID
|
|
226
249
|
file_type: Type of media file
|
|
227
250
|
"""
|
|
251
|
+
|
|
228
252
|
def decorator(view_func):
|
|
229
253
|
def wrapper(request, *args, **kwargs):
|
|
230
254
|
# Extract file_id from kwargs or request
|
|
231
255
|
file_id = kwargs.get(file_id_param) or request.data.get(file_id_param)
|
|
232
|
-
|
|
256
|
+
|
|
233
257
|
if file_id is None:
|
|
234
|
-
logger.error(
|
|
258
|
+
logger.error(
|
|
259
|
+
f"No {file_id_param} found in request for processing coordination"
|
|
260
|
+
)
|
|
235
261
|
from rest_framework.response import Response
|
|
236
262
|
from rest_framework import status
|
|
263
|
+
|
|
237
264
|
return Response(
|
|
238
|
-
{"error": f"Missing {file_id_param} parameter"},
|
|
239
|
-
status=status.HTTP_400_BAD_REQUEST
|
|
265
|
+
{"error": f"Missing {file_id_param} parameter"},
|
|
266
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
240
267
|
)
|
|
241
|
-
|
|
268
|
+
|
|
242
269
|
# Check if processing is already locked
|
|
243
270
|
if PollingCoordinator.is_processing_locked(file_id, file_type):
|
|
244
271
|
from rest_framework.response import Response
|
|
245
272
|
from rest_framework import status
|
|
273
|
+
|
|
246
274
|
return Response(
|
|
247
|
-
{"detail": "File is currently being processed by another request"},
|
|
248
|
-
status=status.HTTP_409_CONFLICT
|
|
275
|
+
{"detail": "File is currently being processed by another request"},
|
|
276
|
+
status=status.HTTP_409_CONFLICT,
|
|
249
277
|
)
|
|
250
|
-
|
|
278
|
+
|
|
251
279
|
# Proceed with the view
|
|
252
280
|
return view_func(request, *args, **kwargs)
|
|
253
|
-
|
|
281
|
+
|
|
254
282
|
return wrapper
|
|
283
|
+
|
|
255
284
|
return decorator
|
|
256
285
|
|
|
257
286
|
|
|
@@ -259,7 +288,7 @@ def processing_coordination(file_id_param: str = "file_id", file_type: str = "vi
|
|
|
259
288
|
class ProcessingLockContext:
|
|
260
289
|
"""
|
|
261
290
|
Context manager for automatic processing lock acquisition and release.
|
|
262
|
-
|
|
291
|
+
|
|
263
292
|
Usage:
|
|
264
293
|
with ProcessingLockContext(file_id, "video") as lock:
|
|
265
294
|
if lock.acquired:
|
|
@@ -269,19 +298,21 @@ class ProcessingLockContext:
|
|
|
269
298
|
# Handle lock acquisition failure
|
|
270
299
|
pass
|
|
271
300
|
"""
|
|
272
|
-
|
|
273
|
-
def __init__(
|
|
301
|
+
|
|
302
|
+
def __init__(
|
|
303
|
+
self, file_id: int, file_type: str = "video", timeout: Optional[int] = None
|
|
304
|
+
):
|
|
274
305
|
self.file_id = file_id
|
|
275
306
|
self.file_type = file_type
|
|
276
307
|
self.timeout = timeout
|
|
277
308
|
self.acquired = False
|
|
278
|
-
|
|
309
|
+
|
|
279
310
|
def __enter__(self):
|
|
280
311
|
self.acquired = PollingCoordinator.acquire_processing_lock(
|
|
281
312
|
self.file_id, self.file_type, self.timeout
|
|
282
313
|
)
|
|
283
314
|
return self
|
|
284
|
-
|
|
315
|
+
|
|
285
316
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
286
317
|
if self.acquired:
|
|
287
318
|
PollingCoordinator.release_processing_lock(self.file_id, self.file_type)
|