From c4e519894b71664f15f86adf0623cc4acd746f37 Mon Sep 17 00:00:00 2001 From: Katon Minhas Date: Tue, 5 Aug 2025 20:46:19 +0000 Subject: [PATCH] Merged in refactor/one-time-io (pull request #648) Refactor/one time io * Delete client work * Streamline imports * Fix list class * Update tests * Clear code funcs test * Fix unit tests * Single read code funcs * Single-read model * Single-load embeddings * Successful E2E test * update unit tests * Update importsg * Reload poetry.lock * remove old exhibit header function * Remove aarete_derived generic function * remove align and format tables * Remove strings to dict * references * Clear code * Move preprocessing_funcs * remove keywords * refactor postprocessing_funcs * Pass unit test - remove qa_qc directory * Black and isort * remove print Approved-by: Alex Galarce --- fieldExtraction/.gitignore | 4 +- .../{crosswalk => constants}/__init__.py | 0 fieldExtraction/constants/constants.py | 267 + fieldExtraction/constants/delimiters.py | 7 + fieldExtraction/constants/embedding.py | 22 + .../{src => }/constants/investment_columns.py | 7 +- .../constants/lists/compound_indicators.json | 26 + .../constants/lists/do_not_run.json | 4 + .../lists/exhibit_header_markers.json | 9 + .../constants/lists/stop_words.json | 31 + .../constants/lists/valid_fee_schedule.json | 8 + .../lists/valid_fee_schedule_version.json | 4 + .../constants/lists/valid_prov_type.json | 31 + .../lists/valid_reimb_methodology.json | 11 + .../lists/valid_unit_of_measure.json | 16 + .../admit_type_cd/admit_mapping.csv | 0 .../mapping_csvs/diag_cd/diag.csv | 0 .../grouper_cd/drg_mapping_apr-drg.csv | 0 .../grouper_cd/drg_mapping_ms-drg.csv | 0 .../mapping_csvs/proc_cd/cpt.csv | 0 .../mapping_csvs/proc_cd/cpt_level1.csv | 0 .../mapping_csvs/proc_cd/cpt_level2.csv | 0 .../mapping_csvs/proc_cd/cpt_level3.csv | 0 .../mapping_csvs/proc_cd/hcpcs.csv | 0 .../mapping_csvs/proc_cd/hcpcs_level1.csv | 0 .../mapping_csvs/proc_cd/hcpcs_level2.csv | 0 .../mapping_csvs/rev_cd/rev.csv | 0 .../mapping_csvs/rev_cd/rev_level1.csv | 0 .../taxonomy_cd/taxonomy_mapping.csv | 0 .../mappings/crosswalk_bill_type.json | 0 .../mappings/crosswalk_claim_type.json | 0 .../mappings/crosswalk_lob.json | 0 .../mappings/crosswalk_network.json | 0 .../mappings/crosswalk_place_of_service.json | 0 .../mappings/crosswalk_product_lob.json | 0 .../mappings/crosswalk_program.json | 0 .../mappings/crosswalk_program_lob.json | 0 .../crosswalk_provider_specialty.json | 0 .../constants/mappings/synonym_map.json | 9 + .../constants/mappings/valid_carveouts.json | 15 + .../constants/mappings/valid_special.json | 10 + fieldExtraction/constants/regex_patterns.py | 8 + .../constants/special_case_configs.py | 5 +- fieldExtraction/{src => }/constants/valid.py | 105 +- fieldExtraction/poetry.lock | 6199 +++++++++-------- .../scripts/create_code_embeddings.py | 2 +- fieldExtraction/src/client/___init__.py | 0 .../src/client/conditional_funcs.py | 105 - .../src/client/consolidate_output.py | 392 -- fieldExtraction/src/client/file_processing.py | 150 - fieldExtraction/src/client/main.py | 370 - fieldExtraction/src/client/one_to_n_funcs.py | 370 - fieldExtraction/src/client/postprocess.py | 110 - fieldExtraction/src/client/preprocess.py | 38 - .../src/client/smart_chunking_funcs.py | 640 -- fieldExtraction/src/codes/code_constants.py | 58 - fieldExtraction/src/codes/code_funcs.py | 361 +- fieldExtraction/src/codes/main.py | 62 +- fieldExtraction/src/config.py | 44 +- fieldExtraction/src/constants/__init__.py | 0 .../src/constants/investment_values.py | 179 - fieldExtraction/{ => src}/crosswalk/README.md | 0 .../crosswalk/crosswalk_builder.py} | 85 +- fieldExtraction/src/crosswalk/list_builder.py | 249 + fieldExtraction/src/enums/__init__.py | 0 fieldExtraction/src/enums/delimiters.py | 7 - .../src/investment/aarete_derived.py | 135 +- .../src/investment/consolidate_output.py | 389 -- .../src/investment/dynamic_funcs.py | 121 +- .../src/investment/file_processing.py | 194 +- fieldExtraction/src/investment/main.py | 18 +- .../src/investment/one_to_n_funcs.py | 245 +- .../src/investment/one_to_one_funcs.py | 218 +- fieldExtraction/src/investment/postprocess.py | 78 +- ...ssing_funcs.py => postprocessing_funcs.py} | 369 +- fieldExtraction/src/investment/preprocess.py | 19 +- .../{ => investment}/preprocessing_funcs.py | 179 +- fieldExtraction/src/investment/row_funcs.py | 66 +- .../src/investment/smart_chunking_funcs.py | 157 +- fieldExtraction/src/investment/table_funcs.py | 29 +- .../src/investment/tin_npi_funcs.py | 234 +- .../src/investment/vision_funcs.py | 41 +- fieldExtraction/src/keywords.py | 115 - fieldExtraction/src/postprocessing_funcs.py | 1241 ---- fieldExtraction/src/prompts/client_prompts.py | 516 -- .../src/prompts/investment_prompts.py | 236 +- .../src/prompts/postprocessing_prompts.py | 3 +- .../src/prompts/preprocessing_prompts.py | 53 - fieldExtraction/src/qa_qc/__init__.py | 0 fieldExtraction/src/regex/__init__.py | 0 fieldExtraction/src/regex/regex_patterns.py | 8 - fieldExtraction/src/regex/regex_utils.py | 293 - .../src/testbed/add_reimb_id_to_testbed.py | 8 +- .../src/testbed/one_to_n_comparison.py | 54 +- .../src/testbed/one_to_one_comparison.py | 59 +- fieldExtraction/src/testbed/test.py | 38 +- .../src/testbed/testbed_postprocess.py | 11 +- fieldExtraction/src/testbed/testbed_utils.py | 1095 +-- .../src/tracking/batch_tracking.py | 281 +- .../src/tracking/tracking_utils.py | 255 +- fieldExtraction/src/utils/crosswalk_utils.py | 68 + fieldExtraction/src/utils/embedding_utils.py | 35 +- fieldExtraction/src/utils/io_utils.py | 92 +- fieldExtraction/src/utils/llm_utils.py | 304 +- .../src/{qa_qc => utils}/qa_qc_utils.py | 458 +- fieldExtraction/src/utils/string_utils.py | 84 +- fieldExtraction/src/utils/table_utils.py | 41 +- .../tests/exhibit_chunk_mapping_test.py | 31 - fieldExtraction/tests/fieldset_test.py | 134 - .../tests/postprocessing_funcs_test.py | 625 -- .../tests/preprocessing_funcs_test.py | 112 - fieldExtraction/tests/qa_qc/blanks_test.py | 40 - fieldExtraction/tests/qa_qc/selftalk_test.py | 44 - fieldExtraction/tests/qa_qc/test_blanks.py | 48 + ...ate_format_test.py => test_date_format.py} | 43 +- .../tests/qa_qc/{npi_test.py => test_npi.py} | 37 +- ..._pagenum_test.py => test_pages_pagenum.py} | 9 +- .../qa_qc/{state_test.py => test_state.py} | 8 +- fieldExtraction/tests/qa_qc/tin_test.py | 39 +- fieldExtraction/tests/qa_qc/yn_test.py | 7 +- ...st.py => test_calculate_progress_stats.py} | 7 +- fieldExtraction/tests/test_chunking.py | 192 - fieldExtraction/tests/test_code_funcs.py | 542 +- ...og_funcs_test.py => test_costlog_funcs.py} | 2 +- .../test_crosswalk_utils.py | 52 +- .../{dynamic_test.py => test_dynamic.py} | 119 +- .../tests/test_exhibit_chunk_mapping.py | 43 + fieldExtraction/tests/test_fieldset.py | 141 + ...test.py => test_investment_postprocess.py} | 510 +- .../tests/test_investment_prompts.py | 29 +- .../{io_utils_test.py => test_io_utils.py} | 133 +- .../{llm_utils_test.py => test_llm_utils.py} | 27 +- .../{one_to_n_test.py => test_one_to_n.py} | 381 +- .../tests/test_preprocessing_funcs.py | 83 + ...t.py => test_preprocessing_integration.py} | 18 +- .../tests/test_smart_chunking_funcs.py | 49 +- ...ing_utils_test.py => test_string_utils.py} | 146 +- ...able_funcs_test.py => test_table_funcs.py} | 132 +- fieldExtraction/tests/test_tin_npi_funcs.py | 106 +- 139 files changed, 9354 insertions(+), 12395 deletions(-) rename fieldExtraction/{crosswalk => constants}/__init__.py (100%) create mode 100644 fieldExtraction/constants/constants.py create mode 100644 fieldExtraction/constants/delimiters.py create mode 100644 fieldExtraction/constants/embedding.py rename fieldExtraction/{src => }/constants/investment_columns.py (98%) create mode 100644 fieldExtraction/constants/lists/compound_indicators.json create mode 100644 fieldExtraction/constants/lists/do_not_run.json create mode 100644 fieldExtraction/constants/lists/exhibit_header_markers.json create mode 100644 fieldExtraction/constants/lists/stop_words.json create mode 100644 fieldExtraction/constants/lists/valid_fee_schedule.json create mode 100644 fieldExtraction/constants/lists/valid_fee_schedule_version.json create mode 100644 fieldExtraction/constants/lists/valid_prov_type.json create mode 100644 fieldExtraction/constants/lists/valid_reimb_methodology.json create mode 100644 fieldExtraction/constants/lists/valid_unit_of_measure.json rename fieldExtraction/{crosswalk => constants}/mapping_csvs/admit_type_cd/admit_mapping.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/diag_cd/diag.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/grouper_cd/drg_mapping_apr-drg.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/grouper_cd/drg_mapping_ms-drg.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/proc_cd/cpt.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/proc_cd/cpt_level1.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/proc_cd/cpt_level2.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/proc_cd/cpt_level3.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/proc_cd/hcpcs.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/proc_cd/hcpcs_level1.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/proc_cd/hcpcs_level2.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/rev_cd/rev.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/rev_cd/rev_level1.csv (100%) rename fieldExtraction/{crosswalk => constants}/mapping_csvs/taxonomy_cd/taxonomy_mapping.csv (100%) rename fieldExtraction/{crosswalk => constants}/mappings/crosswalk_bill_type.json (100%) rename fieldExtraction/{crosswalk => constants}/mappings/crosswalk_claim_type.json (100%) rename fieldExtraction/{crosswalk => constants}/mappings/crosswalk_lob.json (100%) rename fieldExtraction/{crosswalk => constants}/mappings/crosswalk_network.json (100%) rename fieldExtraction/{crosswalk => constants}/mappings/crosswalk_place_of_service.json (100%) rename fieldExtraction/{crosswalk => constants}/mappings/crosswalk_product_lob.json (100%) rename fieldExtraction/{crosswalk => constants}/mappings/crosswalk_program.json (100%) rename fieldExtraction/{crosswalk => constants}/mappings/crosswalk_program_lob.json (100%) rename fieldExtraction/{crosswalk => constants}/mappings/crosswalk_provider_specialty.json (100%) create mode 100644 fieldExtraction/constants/mappings/synonym_map.json create mode 100644 fieldExtraction/constants/mappings/valid_carveouts.json create mode 100644 fieldExtraction/constants/mappings/valid_special.json create mode 100644 fieldExtraction/constants/regex_patterns.py rename fieldExtraction/{src => }/constants/special_case_configs.py (98%) rename fieldExtraction/{src => }/constants/valid.py (89%) delete mode 100644 fieldExtraction/src/client/___init__.py delete mode 100644 fieldExtraction/src/client/conditional_funcs.py delete mode 100644 fieldExtraction/src/client/consolidate_output.py delete mode 100644 fieldExtraction/src/client/file_processing.py delete mode 100644 fieldExtraction/src/client/main.py delete mode 100644 fieldExtraction/src/client/one_to_n_funcs.py delete mode 100644 fieldExtraction/src/client/postprocess.py delete mode 100644 fieldExtraction/src/client/preprocess.py delete mode 100644 fieldExtraction/src/client/smart_chunking_funcs.py delete mode 100644 fieldExtraction/src/codes/code_constants.py delete mode 100644 fieldExtraction/src/constants/__init__.py delete mode 100644 fieldExtraction/src/constants/investment_values.py rename fieldExtraction/{ => src}/crosswalk/README.md (100%) rename fieldExtraction/{crosswalk/crosswalk_utils.py => src/crosswalk/crosswalk_builder.py} (67%) create mode 100644 fieldExtraction/src/crosswalk/list_builder.py delete mode 100644 fieldExtraction/src/enums/__init__.py delete mode 100644 fieldExtraction/src/enums/delimiters.py delete mode 100644 fieldExtraction/src/investment/consolidate_output.py rename fieldExtraction/src/investment/{investment_postprocessing_funcs.py => postprocessing_funcs.py} (70%) rename fieldExtraction/src/{ => investment}/preprocessing_funcs.py (81%) delete mode 100644 fieldExtraction/src/keywords.py delete mode 100644 fieldExtraction/src/postprocessing_funcs.py delete mode 100644 fieldExtraction/src/prompts/client_prompts.py delete mode 100644 fieldExtraction/src/qa_qc/__init__.py delete mode 100644 fieldExtraction/src/regex/__init__.py delete mode 100644 fieldExtraction/src/regex/regex_patterns.py delete mode 100644 fieldExtraction/src/regex/regex_utils.py create mode 100644 fieldExtraction/src/utils/crosswalk_utils.py rename fieldExtraction/src/{qa_qc => utils}/qa_qc_utils.py (60%) delete mode 100644 fieldExtraction/tests/exhibit_chunk_mapping_test.py delete mode 100644 fieldExtraction/tests/fieldset_test.py delete mode 100644 fieldExtraction/tests/postprocessing_funcs_test.py delete mode 100644 fieldExtraction/tests/preprocessing_funcs_test.py delete mode 100644 fieldExtraction/tests/qa_qc/blanks_test.py delete mode 100644 fieldExtraction/tests/qa_qc/selftalk_test.py create mode 100644 fieldExtraction/tests/qa_qc/test_blanks.py rename fieldExtraction/tests/qa_qc/{date_format_test.py => test_date_format.py} (76%) rename fieldExtraction/tests/qa_qc/{npi_test.py => test_npi.py} (72%) rename fieldExtraction/tests/qa_qc/{pages_pagenum_test.py => test_pages_pagenum.py} (92%) rename fieldExtraction/tests/qa_qc/{state_test.py => test_state.py} (90%) rename fieldExtraction/tests/{calculate_progress_stats_test.py => test_calculate_progress_stats.py} (98%) delete mode 100644 fieldExtraction/tests/test_chunking.py rename fieldExtraction/tests/{costlog_funcs_test.py => test_costlog_funcs.py} (79%) rename fieldExtraction/{crosswalk => tests}/test_crosswalk_utils.py (91%) rename fieldExtraction/tests/{dynamic_test.py => test_dynamic.py} (68%) create mode 100644 fieldExtraction/tests/test_exhibit_chunk_mapping.py create mode 100644 fieldExtraction/tests/test_fieldset.py rename fieldExtraction/tests/{investment_postprocess_test.py => test_investment_postprocess.py} (54%) rename fieldExtraction/tests/{io_utils_test.py => test_io_utils.py} (73%) rename fieldExtraction/tests/{llm_utils_test.py => test_llm_utils.py} (85%) rename fieldExtraction/tests/{one_to_n_test.py => test_one_to_n.py} (56%) create mode 100644 fieldExtraction/tests/test_preprocessing_funcs.py rename fieldExtraction/tests/{preprocessing_integration_test.py => test_preprocessing_integration.py} (86%) rename fieldExtraction/tests/{string_utils_test.py => test_string_utils.py} (73%) rename fieldExtraction/tests/{table_funcs_test.py => test_table_funcs.py} (94%) diff --git a/fieldExtraction/.gitignore b/fieldExtraction/.gitignore index 1ae7bf9..2321e80 100644 --- a/fieldExtraction/.gitignore +++ b/fieldExtraction/.gitignore @@ -67,9 +67,9 @@ streamlit/venv textfiles/ texts/ *.csv -!crosswalk/mapping_csvs/*/*.csv +!constants/mapping_csvs/*/*.csv *.json -!crosswalk/mappings/*.json +!constants/*/*.json !src/prompts/*.json myenv/ *.env diff --git a/fieldExtraction/crosswalk/__init__.py b/fieldExtraction/constants/__init__.py similarity index 100% rename from fieldExtraction/crosswalk/__init__.py rename to fieldExtraction/constants/__init__.py diff --git a/fieldExtraction/constants/constants.py b/fieldExtraction/constants/constants.py new file mode 100644 index 0000000..bb22ce3 --- /dev/null +++ b/fieldExtraction/constants/constants.py @@ -0,0 +1,267 @@ +from sentence_transformers import SentenceTransformer +from src.crosswalk.crosswalk_builder import CrosswalkBuilder +from src.crosswalk.list_builder import ListBuilder + +from constants.embedding import CodeEmbedding + + +class Constants: + """Class to manage all constants and mappings for a Doczy.ai execution""" + + def __init__(self): + + #################################### Dynamic and Exhibit-Level #################################### + + # Dynamic Primary + self.VALID_LOBS = list( + CrosswalkBuilder() + .from_json(path="constants/mappings/crosswalk_lob.json") + .mapping.keys() + ) + self.VALID_PROGRAMS = list( + CrosswalkBuilder() + .from_json(path="constants/mappings/crosswalk_program.json") + .mapping.keys() + ) + self.VALID_NETWORKS = list( + CrosswalkBuilder() + .from_json(path="constants/mappings/crosswalk_network.json") + .mapping.keys() + ) + self.VALID_PRODUCTS = list( + CrosswalkBuilder() + .from_json(path="constants/mappings/crosswalk_product_lob.json") + .mapping.keys() + ) + + # Dynamic Codes + self.VALID_PROV_SPECIALTY = list( + CrosswalkBuilder() + .from_json(path="constants/mappings/crosswalk_provider_specialty.json") + .mapping.keys() + ) + self.VALID_BILL_TYPE = list( + set( + CrosswalkBuilder() + .from_json(path="constants/mappings/crosswalk_bill_type.json") + .mapping.values() + ) + ) + self.VALID_PLACE_OF_SERVICE = list( + CrosswalkBuilder() + .from_json(path="constants/mappings/crosswalk_place_of_service.json") + .mapping.keys() + ) + self.VALID_CLAIM_TYPE = list( + set( + CrosswalkBuilder() + .from_json(path="constants/mappings/crosswalk_claim_type.json") + .mapping.keys() + ) + ) + + # Provider Type + self.VALID_PROV_TYPE = ( + ListBuilder().from_json("constants/lists/valid_prov_type.json").list + ) + + #################################### Code Breakout #################################### + self.SYNONYM_MAP = ( + CrosswalkBuilder() + .from_json(path="constants/mappings/synonym_map.json") + .mapping + ) + self.STOP_WORD_LIST = ( + ListBuilder().from_json("constants/lists/stop_words.json").list + ) + self.DO_NOT_RUN = ( + ListBuilder().from_json("constants/lists/do_not_run.json").list + ) + self.REMOVAL_LIST = self._load_removal_list() + + # Code Crosswalks + self.CPT_LEVEL1_MAPPING = ( + CrosswalkBuilder() + .from_excel( + "constants/mapping_csvs/proc_cd/cpt_level1.csv", "Code", "Description" + ) + .mapping + ) + self.CPT_LEVEL2_MAPPING = ( + CrosswalkBuilder() + .from_excel( + "constants/mapping_csvs/proc_cd/cpt_level2.csv", "Code", "Description" + ) + .mapping + ) + # self.CPT_LEVEL3_MAPPING = CrosswalkBuilder().from_excel("constants/mapping_csvs/proc_cd/cpt_level3.csv", "Code", "Description").mapping + + self.HCPCS_LEVEL1_MAPPING = ( + CrosswalkBuilder() + .from_excel( + "constants/mapping_csvs/proc_cd/hcpcs_level1.csv", "Code", "Description" + ) + .mapping + ) + self.HCPCS_LEVEL2_MAPPING = ( + CrosswalkBuilder() + .from_excel( + "constants/mapping_csvs/proc_cd/hcpcs_level2.csv", "Code", "Description" + ) + .mapping + ) + # self.HCPCS_MAPPING = CrosswalkBuilder().from_excel("constants/mapping_csvs/proc_cd/hcpcs.csv", "Code", "Description").mapping + + self.REV_LEVEL1_MAPPING = ( + CrosswalkBuilder() + .from_excel( + "constants/mapping_csvs/rev_cd/rev_level1.csv", "Code", "Description" + ) + .mapping + ) + self.REV_MAPPING = ( + CrosswalkBuilder() + .from_excel("constants/mapping_csvs/rev_cd/rev.csv", "Code", "Description") + .mapping + ) + + self.BILL_TYPE_MAPPING = ( + CrosswalkBuilder() + .from_json("constants/mappings/crosswalk_bill_type.json") + .mapping + ) + self.BILL_TYPE_REVERSE_MAPPING = ( + CrosswalkBuilder() + .from_json("constants/mappings/crosswalk_bill_type.json") + .create_reverse_mapping() + ) + + # Embedding Model + self.EMBEDDING_MODEL = SentenceTransformer("all-roberta-large-v1") + + # Embeddings + self.CPT_LEVEL1_EMBEDDING = CodeEmbedding("cpt_level1") + self.CPT_LEVEL2_EMBEDDING = CodeEmbedding("cpt_level2") + # self.CPT_LEVEL3_EMBEDDING = CodeEmbedding("cpt_level3") + + self.HCPCS_LEVEL1_EMBEDDING = CodeEmbedding("hcpcs_level1") + self.HCPCS_LEVEL2_EMBEDDING = CodeEmbedding("hcpcs_level2") + + self.REV_LEVEL1_EMBEDDING = CodeEmbedding("rev_level1") + self.REV_EMBEDDING = CodeEmbedding("rev") + + #################################### Methodology Breakout #################################### + self.VALID_AARETE_DERIVED_FEE_SCHEDULE = ( + ListBuilder().from_json(path="constants/lists/valid_fee_schedule.json").list + ) + self.VALID_AARETE_DERIVED_FEE_SCHEDULE_VERSION = ( + ListBuilder() + .from_json(path="constants/lists/valid_fee_schedule_version.json") + .list + ) + self.VALID_REIMB_METHODOLOGY = ( + ListBuilder() + .from_json(path="constants/lists/valid_reimb_methodology.json") + .list + ) + self.VALID_UNIT_OF_MEASURE = ( + ListBuilder() + .from_json(path="constants/lists/valid_unit_of_measure.json") + .list + ) + + self.VALID_CARVEOUTS = ( + CrosswalkBuilder() + .from_json(path="constants/mappings/valid_carveouts.json") + .mapping + ) + self.VALID_SPECIAL = ( + CrosswalkBuilder() + .from_json(path="constants/mappings/valid_special.json") + .mapping + ) + + #################################### Other #################################### + self.COMPOUND_INDICATORS = ( + ListBuilder() + .from_json(path="constants/lists/compound_indicators.json") + .list + ) + self.EXHIBIT_HEADER_MARKERS = ( + ListBuilder() + .from_json(path="constants/lists/exhibit_header_markers.json") + .list + ) + + def _load_removal_list(self): + return [ + v.upper() + for v in self.VALID_LOBS + + self.VALID_PRODUCTS + + list( + set( + CrosswalkBuilder() + .from_json(path="constants/mappings/crosswalk_network.json") + .mapping.values() + ) + ) + + self.VALID_CLAIM_TYPE + + list( + set( + CrosswalkBuilder() + .from_json(path="constants/mappings/crosswalk_program.json") + .mapping.values() + ) + ) + + ["INPATIENT", "OUTPATIENT", "IN-PATIENT", "OUT-PATIENT", "IP", "OP"] + + [ + "COVERED SERVICES", + "SERVICES", + "PROCEDURES", + "CONTRACTED", + "HEALTHCARE", + "HEALTH CARE", + "ALL OTHER", + "PER DIEM", + "ADMISSION", + "REIMBURSEMENT", + "ADDITIONAL PAYMENT", + "PAYMENT", + "RATE", + "FEE SCHEDULE", + "METHODOLOGY", + "OUTLIER", + "INTERMEDIATE", + "READMISSION", + "ACUTE", + "OPPS", + "FEE-FOR-SERVICE", + "PROGRAM", + "PRODUCT", + ] # Generic Terms not found in any of the proc code mappings + ] + + def get_embedding(self, level: str): + """ + Returns the CodeEmbedding attribute corresponding to the given level. + + Args: + level (str): The embedding level identifier (e.g., "rev", "cpt_level1"). + + Returns: + CodeEmbedding: The matching embedding object, or None if not found. + """ + level_map = { + # Revenue code embeddings + "rev": self.REV_EMBEDDING, + "rev_level1": self.REV_LEVEL1_EMBEDDING, + # CPT code embeddings + "cpt_level1": self.CPT_LEVEL1_EMBEDDING, + "cpt_level2": self.CPT_LEVEL2_EMBEDDING, + # "cpt_level3": self.CPT_LEVEL3_EMBEDDING, # Uncomment if needed + # HCPCS code embeddings + "hcpcs_level1": self.HCPCS_LEVEL1_EMBEDDING, + "hcpcs_level2": self.HCPCS_LEVEL2_EMBEDDING, + } + + return level_map.get(level) diff --git a/fieldExtraction/constants/delimiters.py b/fieldExtraction/constants/delimiters.py new file mode 100644 index 0000000..98cc389 --- /dev/null +++ b/fieldExtraction/constants/delimiters.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class Delimiter(Enum): + PIPE = "|" + BACKTICK = "`" + TRIPLE_BACKTICK = "```" diff --git a/fieldExtraction/constants/embedding.py b/fieldExtraction/constants/embedding.py new file mode 100644 index 0000000..d93e48e --- /dev/null +++ b/fieldExtraction/constants/embedding.py @@ -0,0 +1,22 @@ +import os +import pickle + +import faiss +import numpy as np + + +class CodeEmbedding: + """Class to manage Embeddings for a Doczy.ai execution""" + + def __init__( + self, + level, + index_name="faiss_index.bin", + embedding_name="embeddings.npy", + choices_name="choices.pkl", + ): + self.level = level + self.index = faiss.read_index(os.path.join("embeddings", level, index_name)) + self.embeddings = np.load(os.path.join("embeddings", level, embedding_name)) + with open(os.path.join("embeddings", level, choices_name), "rb") as f: + self.choices = pickle.load(f) diff --git a/fieldExtraction/src/constants/investment_columns.py b/fieldExtraction/constants/investment_columns.py similarity index 98% rename from fieldExtraction/src/constants/investment_columns.py rename to fieldExtraction/constants/investment_columns.py index b30d8d8..0da8cfc 100644 --- a/fieldExtraction/src/constants/investment_columns.py +++ b/fieldExtraction/constants/investment_columns.py @@ -1,4 +1,3 @@ - # This is the ONE AND ONLY source of investment column names and order. Do not reference anything else COLUMN_ORDER = [ "CONTRACT_SID", @@ -42,7 +41,7 @@ COLUMN_ORDER = [ "AARETE_DERIVED_LOB", "PROGRAM", "AARETE_DERIVED_PROGRAM", - "NETWORK", + "NETWORK", "AARETE_DERIVED_NETWORK", "SERVICE_AREA", "AARETE_DERIVED_SERVICE_AREA", @@ -132,5 +131,5 @@ COLUMN_ORDER = [ "RATE_ESCALATOR_IND", "RATE_ESCALATOR_DESC", "RATE_ESCALATOR_MAX_RATE_INC_PCT", - "RATE_ESCALATOR_RATE_CHANGE_TIMELINE" -] \ No newline at end of file + "RATE_ESCALATOR_RATE_CHANGE_TIMELINE", +] diff --git a/fieldExtraction/constants/lists/compound_indicators.json b/fieldExtraction/constants/lists/compound_indicators.json new file mode 100644 index 0000000..d4d396a --- /dev/null +++ b/fieldExtraction/constants/lists/compound_indicators.json @@ -0,0 +1,26 @@ +[ + "for services where there is no", + "where no rate exists", + "if no rate is available", + "for which there is no", + "when no payment rate", + "where there is no payment rate", + + "otherwise shall be", + "shall otherwise be", + "alternatively", + "in the alternative", + "or alternatively", + + "for certain services", + "for specific services", + "for other services", + "for all other", + "for any services", + + "except for", + "with the exception of", + "excluding", + "however, for", + "notwithstanding" +] \ No newline at end of file diff --git a/fieldExtraction/constants/lists/do_not_run.json b/fieldExtraction/constants/lists/do_not_run.json new file mode 100644 index 0000000..6ce6758 --- /dev/null +++ b/fieldExtraction/constants/lists/do_not_run.json @@ -0,0 +1,4 @@ + [ + "BY REPORT", + "STOP-LOSS" +] \ No newline at end of file diff --git a/fieldExtraction/constants/lists/exhibit_header_markers.json b/fieldExtraction/constants/lists/exhibit_header_markers.json new file mode 100644 index 0000000..7bf1614 --- /dev/null +++ b/fieldExtraction/constants/lists/exhibit_header_markers.json @@ -0,0 +1,9 @@ +[ + "EXHIBIT", + "ATTACHMENT", + "ARTICLE", + "AMENDMENT", + "SCHEDULE", + "ADDENDUM", + "APPENDIX" +] \ No newline at end of file diff --git a/fieldExtraction/constants/lists/stop_words.json b/fieldExtraction/constants/lists/stop_words.json new file mode 100644 index 0000000..3e93fc3 --- /dev/null +++ b/fieldExtraction/constants/lists/stop_words.json @@ -0,0 +1,31 @@ +[ + "THE", + "AND", + "OF", + "TO", + "A", + "IN", + "FOR", + "ON", + "WITH", + "AT", + "BY", + "AN", + "BE", + "THIS", + "THAT", + "FROM", + "OR", + "AS", + "IS", + "ARE", + "WAS", + "WERE", + "WHICH", + "WHO", + "ALL", + "OTHER", + "/", + ",", + "NOT" +] \ No newline at end of file diff --git a/fieldExtraction/constants/lists/valid_fee_schedule.json b/fieldExtraction/constants/lists/valid_fee_schedule.json new file mode 100644 index 0000000..0224117 --- /dev/null +++ b/fieldExtraction/constants/lists/valid_fee_schedule.json @@ -0,0 +1,8 @@ +[ + "CMS", + "State", + "Proprietary", + "AWP", + "WAC", + "ASP" +] \ No newline at end of file diff --git a/fieldExtraction/constants/lists/valid_fee_schedule_version.json b/fieldExtraction/constants/lists/valid_fee_schedule_version.json new file mode 100644 index 0000000..c8878a0 --- /dev/null +++ b/fieldExtraction/constants/lists/valid_fee_schedule_version.json @@ -0,0 +1,4 @@ +[ + "current", + "year prior" +] \ No newline at end of file diff --git a/fieldExtraction/constants/lists/valid_prov_type.json b/fieldExtraction/constants/lists/valid_prov_type.json new file mode 100644 index 0000000..51481a0 --- /dev/null +++ b/fieldExtraction/constants/lists/valid_prov_type.json @@ -0,0 +1,31 @@ +[ + "Physician", + "Primary Care Provider (PCP)", + "Dual Capacity Physician", + "Mid-Level Practicioner", + "Primary Care Mid-Level Practicioner", + "Pediatrician", + "Specialist", + "OB/GYN", + "Behavioral Health", + "Therapist", + "Surgery", + "Ambulance Services", + "Home Health", + "Durable Medical Equipment (DME)", + "Radiology", + "Lab", + "Hospice", + "Dialysis", + "Physical Therapy, Occupational Therapy, Speech Therapy (PT/OT/ST)", + "Urgent Care", + "Home Infusion", + "Ambulatory Surgery Center (ASC)", + "Skilled Nursing Facility (SNF)", + "Hospital", + "Clinic", + "Rural Health Clinic (RHC)", + "Federally Qualified Health Center (FQHC)", + "Long Term Care", + "Community Mental Health Center" +] \ No newline at end of file diff --git a/fieldExtraction/constants/lists/valid_reimb_methodology.json b/fieldExtraction/constants/lists/valid_reimb_methodology.json new file mode 100644 index 0000000..1b2e127 --- /dev/null +++ b/fieldExtraction/constants/lists/valid_reimb_methodology.json @@ -0,0 +1,11 @@ + [ + "Billed Charges", + "Fee Schedule", + "Flat Rate", + "Per Visit", + "Per Diem", + "Case Rate", + "Cost", + "Grouper", + "DOFR" +] \ No newline at end of file diff --git a/fieldExtraction/constants/lists/valid_unit_of_measure.json b/fieldExtraction/constants/lists/valid_unit_of_measure.json new file mode 100644 index 0000000..53f8505 --- /dev/null +++ b/fieldExtraction/constants/lists/valid_unit_of_measure.json @@ -0,0 +1,16 @@ +[ + "Per Unit", + "Per ASA Unit", + "Per Day", + "Per 15 Minutes", + "Per 30 Minutes", + "Per Hour", + "Per Month", + "Per Member Per Month (PMPM)", + "Per Year", + "Per Session", + "Per Training Session", + "Per Visit", + "Per Case", + "Per Procedure" +] \ No newline at end of file diff --git a/fieldExtraction/crosswalk/mapping_csvs/admit_type_cd/admit_mapping.csv b/fieldExtraction/constants/mapping_csvs/admit_type_cd/admit_mapping.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/admit_type_cd/admit_mapping.csv rename to fieldExtraction/constants/mapping_csvs/admit_type_cd/admit_mapping.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/diag_cd/diag.csv b/fieldExtraction/constants/mapping_csvs/diag_cd/diag.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/diag_cd/diag.csv rename to fieldExtraction/constants/mapping_csvs/diag_cd/diag.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/grouper_cd/drg_mapping_apr-drg.csv b/fieldExtraction/constants/mapping_csvs/grouper_cd/drg_mapping_apr-drg.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/grouper_cd/drg_mapping_apr-drg.csv rename to fieldExtraction/constants/mapping_csvs/grouper_cd/drg_mapping_apr-drg.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/grouper_cd/drg_mapping_ms-drg.csv b/fieldExtraction/constants/mapping_csvs/grouper_cd/drg_mapping_ms-drg.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/grouper_cd/drg_mapping_ms-drg.csv rename to fieldExtraction/constants/mapping_csvs/grouper_cd/drg_mapping_ms-drg.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/proc_cd/cpt.csv b/fieldExtraction/constants/mapping_csvs/proc_cd/cpt.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/proc_cd/cpt.csv rename to fieldExtraction/constants/mapping_csvs/proc_cd/cpt.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/proc_cd/cpt_level1.csv b/fieldExtraction/constants/mapping_csvs/proc_cd/cpt_level1.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/proc_cd/cpt_level1.csv rename to fieldExtraction/constants/mapping_csvs/proc_cd/cpt_level1.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/proc_cd/cpt_level2.csv b/fieldExtraction/constants/mapping_csvs/proc_cd/cpt_level2.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/proc_cd/cpt_level2.csv rename to fieldExtraction/constants/mapping_csvs/proc_cd/cpt_level2.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/proc_cd/cpt_level3.csv b/fieldExtraction/constants/mapping_csvs/proc_cd/cpt_level3.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/proc_cd/cpt_level3.csv rename to fieldExtraction/constants/mapping_csvs/proc_cd/cpt_level3.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/proc_cd/hcpcs.csv b/fieldExtraction/constants/mapping_csvs/proc_cd/hcpcs.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/proc_cd/hcpcs.csv rename to fieldExtraction/constants/mapping_csvs/proc_cd/hcpcs.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/proc_cd/hcpcs_level1.csv b/fieldExtraction/constants/mapping_csvs/proc_cd/hcpcs_level1.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/proc_cd/hcpcs_level1.csv rename to fieldExtraction/constants/mapping_csvs/proc_cd/hcpcs_level1.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/proc_cd/hcpcs_level2.csv b/fieldExtraction/constants/mapping_csvs/proc_cd/hcpcs_level2.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/proc_cd/hcpcs_level2.csv rename to fieldExtraction/constants/mapping_csvs/proc_cd/hcpcs_level2.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/rev_cd/rev.csv b/fieldExtraction/constants/mapping_csvs/rev_cd/rev.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/rev_cd/rev.csv rename to fieldExtraction/constants/mapping_csvs/rev_cd/rev.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/rev_cd/rev_level1.csv b/fieldExtraction/constants/mapping_csvs/rev_cd/rev_level1.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/rev_cd/rev_level1.csv rename to fieldExtraction/constants/mapping_csvs/rev_cd/rev_level1.csv diff --git a/fieldExtraction/crosswalk/mapping_csvs/taxonomy_cd/taxonomy_mapping.csv b/fieldExtraction/constants/mapping_csvs/taxonomy_cd/taxonomy_mapping.csv similarity index 100% rename from fieldExtraction/crosswalk/mapping_csvs/taxonomy_cd/taxonomy_mapping.csv rename to fieldExtraction/constants/mapping_csvs/taxonomy_cd/taxonomy_mapping.csv diff --git a/fieldExtraction/crosswalk/mappings/crosswalk_bill_type.json b/fieldExtraction/constants/mappings/crosswalk_bill_type.json similarity index 100% rename from fieldExtraction/crosswalk/mappings/crosswalk_bill_type.json rename to fieldExtraction/constants/mappings/crosswalk_bill_type.json diff --git a/fieldExtraction/crosswalk/mappings/crosswalk_claim_type.json b/fieldExtraction/constants/mappings/crosswalk_claim_type.json similarity index 100% rename from fieldExtraction/crosswalk/mappings/crosswalk_claim_type.json rename to fieldExtraction/constants/mappings/crosswalk_claim_type.json diff --git a/fieldExtraction/crosswalk/mappings/crosswalk_lob.json b/fieldExtraction/constants/mappings/crosswalk_lob.json similarity index 100% rename from fieldExtraction/crosswalk/mappings/crosswalk_lob.json rename to fieldExtraction/constants/mappings/crosswalk_lob.json diff --git a/fieldExtraction/crosswalk/mappings/crosswalk_network.json b/fieldExtraction/constants/mappings/crosswalk_network.json similarity index 100% rename from fieldExtraction/crosswalk/mappings/crosswalk_network.json rename to fieldExtraction/constants/mappings/crosswalk_network.json diff --git a/fieldExtraction/crosswalk/mappings/crosswalk_place_of_service.json b/fieldExtraction/constants/mappings/crosswalk_place_of_service.json similarity index 100% rename from fieldExtraction/crosswalk/mappings/crosswalk_place_of_service.json rename to fieldExtraction/constants/mappings/crosswalk_place_of_service.json diff --git a/fieldExtraction/crosswalk/mappings/crosswalk_product_lob.json b/fieldExtraction/constants/mappings/crosswalk_product_lob.json similarity index 100% rename from fieldExtraction/crosswalk/mappings/crosswalk_product_lob.json rename to fieldExtraction/constants/mappings/crosswalk_product_lob.json diff --git a/fieldExtraction/crosswalk/mappings/crosswalk_program.json b/fieldExtraction/constants/mappings/crosswalk_program.json similarity index 100% rename from fieldExtraction/crosswalk/mappings/crosswalk_program.json rename to fieldExtraction/constants/mappings/crosswalk_program.json diff --git a/fieldExtraction/crosswalk/mappings/crosswalk_program_lob.json b/fieldExtraction/constants/mappings/crosswalk_program_lob.json similarity index 100% rename from fieldExtraction/crosswalk/mappings/crosswalk_program_lob.json rename to fieldExtraction/constants/mappings/crosswalk_program_lob.json diff --git a/fieldExtraction/crosswalk/mappings/crosswalk_provider_specialty.json b/fieldExtraction/constants/mappings/crosswalk_provider_specialty.json similarity index 100% rename from fieldExtraction/crosswalk/mappings/crosswalk_provider_specialty.json rename to fieldExtraction/constants/mappings/crosswalk_provider_specialty.json diff --git a/fieldExtraction/constants/mappings/synonym_map.json b/fieldExtraction/constants/mappings/synonym_map.json new file mode 100644 index 0000000..bd13fea --- /dev/null +++ b/fieldExtraction/constants/mappings/synonym_map.json @@ -0,0 +1,9 @@ +{ + "DME" : "DURABLE MEDICAL EQUIPMENT", + "SNF" : "SKILLED NURSING FACILITY", + "ASC" : "AMBULATORY SURGERY CENTER", + "LABORATORY" : "LABORATORY AND PATHOLOGY", + "LAB" : "LABORATORY AND PATHOLOGY", + "STOP LOSS" : "STOP-LOSS", + "OB": "OBSTETRIC" +} \ No newline at end of file diff --git a/fieldExtraction/constants/mappings/valid_carveouts.json b/fieldExtraction/constants/mappings/valid_carveouts.json new file mode 100644 index 0000000..418612a --- /dev/null +++ b/fieldExtraction/constants/mappings/valid_carveouts.json @@ -0,0 +1,15 @@ + { + "BASE_COVERED_SERVICES" : "Describes generic payments for standard covered services. This refers to the payment made for a service that is not subject to any adjustments. Example of base methodology:\n- 'covered services'\n- 'Covered Services not included in the Service Categories in Table 1 shall be reimbursed at 100% of the Medicare fee schedule.'", + "PAYMENT_CARVEOUT" : "Describes payments for services or codes that are separately mentioned or carved out from the standard payment for all covered services. Examples include 'Radiology covered services', 'Anesthesia covered services', 'Pharmaceuticals' and 'HEMATOCRIT (CPT CODE: 85014)'. Any service or code that is specifically or separately mentioned falls under the 'payment_carveout' category.", + "UNLISTED_CODE" : "Describes payments for codes that are not listed on a fee schedule. This is similar to DEFAULT_TERM. Only populate with Y if the Service or Methodology specifically mentions Codes.", + "NEVER_EVENT" : "Indicates that Never Events (e.g. sepsis acquired while inpatient) will not be covered. This may be phrased as 'no liability' or 'no payment'.", + "EXPERIMENTAL_INVESTIGATIONAL" : "Describes payments for experimental and investigational procedures.", + "NOT_MEDICALLY_NECESSARY" : "Describes payments for procedures deemed not medically necessary (or medically unnecessary)", + "BUNDLED_CODES" : "Indicates that payment for bundles of codes shall be at a reduced rate.", + "MULTIPLE_PROCEDURE_REDUCTIONS" : "Describe cases where payment terms apply to multiple or subsequent procedures or surgeries. It contains language indicating reduced payment for second and subsequent procedures, such as: 'multiple procedure reductions', 'second and subsequent deliveries' or similar terms that imply payment adjustments for multiple procedures performed during the same encounter.", + "TECHNICAL_COMPONENT" : "If Modifier TC (technical component) is present.", + "PROFESSIONAL_COMPONENT" : "If Modifier 26 (professional component) is present.", + "DIAGNOSTIC_PROCEDURES" : "Describes payments for diagnostic procedures.", + "READMISSIONS" : "Describes payment for subsequent admission with the same diagnostic category of codes as the initial admission.", + "DEFAULT_TERM" : "Indicates whether this service/methodology serves as a default/fallback payment rate when no other payment methods mentioned anywhere in the contract apply. The default/fallback/'end-of-the-line' payment rate may be a lower rate, often based on billed charges. Do not classify as default if:\n- The methodology is the primary/base reimbursement approach, even if defined by exclusion\n- The methodology applies to specific listed services/procedures \n- No default/fallback language is present\n\nExample: 'For any services not specifically listed in Exhibits A-D, reimbursement shall be 50% of billed charges.'" +} \ No newline at end of file diff --git a/fieldExtraction/constants/mappings/valid_special.json b/fieldExtraction/constants/mappings/valid_special.json new file mode 100644 index 0000000..ed66d0b --- /dev/null +++ b/fieldExtraction/constants/mappings/valid_special.json @@ -0,0 +1,10 @@ +{ + "OUTLIER" : "Describes additional payments built into standard reimbursement methodologies that are triggered when specific claims exceed predetermined cost thresholds. Often labeled as 'Outlier' in contracts, though the threshold may be called a 'Stop-Loss threshold'. Look for automatic payment adjustments based on claim cost.", + "STOP_LOSS" : "Describes financial safety net provisions primarily designed to protect providers against extreme losses. May apply at individual claim or aggregate levels and typically has higher thresholds than standard outlier provisions. Look for provisions specifically labeled as 'Stop Loss' that function separately from the standard payment methodology.", + "SEQUESTRATION" : "Describes a government-mandated reduction in federal spending for a period of time. Look for the term 'Sequestration'.", + "DISCOUNT" : "Describes a discount to the payment. Look for the term 'Discount'.", + "PREMIUM" : "Describes a premium to the payment. Look for the term 'Premium'.", + "ADDITION" : "Describes a payment where an additional amount is added to the base reimbursement rate for individual claims. This can be a fixed dollar amount, a percentage of the base reimbursement, or a variable amount derived from the base rate that may change over time based on specified conditions, schedules, or specific services or procedures. Exclude cases where: \n- No specific amount or percentage tied to the base reimbursement of an individual claim is stated, \n- It is unclear whether the payment applies to individual claims, or \n- The payment is dependent on total compensation to Participating IPA Providers.", + "TRIGGER_CAP": "Describes the payments that trigger a complete shift to a new payment methodology after a defined quantitative threshold is reached. Unlike supplemental payments, this fundamentally changes how reimbursement is calculated beyond the threshold. Valid thresholds can be based on dollar amounts, number of days, visits, units, or other measurable metrics. However, the following scenarios are NOT considered trigger caps: (1) Payment variations based solely on the timing of an event, such as discharge or admission dates (e.g., different payment if discharge occurs before or after a certain day of the month); (2) Thresholds based on the number of births (e.g., 'If the number of deliveries exceeds 17 per 1,000 members, an additional payment of $2,193 per birth will apply'); (3) Administrative pricing mechanisms such as Charge Description Master (CDM) increases or decreases; (4) Reimbursement limits tied to Acquisition Cost validations (e.g., payments capped at cost plus a percentage); (5) Provisions labeled as 'Stop-Loss' in either the service description or reimbursement text — even if a threshold and rate change are present — as these are considered risk protection mechanisms rather than true shifts in payment methodology; 6) Conditional changes in payment based on service context (e.g., ER vs. admission, Observation vs. surgery) without a numeric threshold do not qualify or Conditional reimbursement rules based on service combinations. Look for language indicating a clear change in the payment methodology when a defined quantitative threshold is exceeded, such as 'when charges exceed X, pay Y', 'above X, reimburse at Y%', or 'over X threshold, payment becomes Y','when charges exceed $50,000, pay 80%' or 'after 3 days, payment changes to $1,200 per diem'.", + "RATE_ESCALATOR" : "Describes a payment arrangement where the reimbursement rate automatically increases by a specified percentage annually after an payment rate defined for the initial term. Exception: Does not include predetermined fixed payment amounts scheduled at specific dates (e.g., $860 in 2008, $925 in 2009, $994 in 2010)." +} \ No newline at end of file diff --git a/fieldExtraction/constants/regex_patterns.py b/fieldExtraction/constants/regex_patterns.py new file mode 100644 index 0000000..3e12cf9 --- /dev/null +++ b/fieldExtraction/constants/regex_patterns.py @@ -0,0 +1,8 @@ +# Delimiter Patterns +PIPE_PATTERN = r"\|([^|]+)\|" +BACKTICK_PATTERN = r"`([^`]+)`" +TRIPLE_BACKTICK_PATTERN = r"```([^`]+)```" + +# TIN and NPI Regex Patterns +TIN_PATTERN = r"\b\d{9}\b|\b\d{2}[-.]\d{7}\b|\b\d{3}[-.]\d{2}[-.]\d{4}\b" +NPI_PATTERN = r"\b\d{10}\b" diff --git a/fieldExtraction/src/constants/special_case_configs.py b/fieldExtraction/constants/special_case_configs.py similarity index 98% rename from fieldExtraction/src/constants/special_case_configs.py rename to fieldExtraction/constants/special_case_configs.py index 5a784d9..9e86c5a 100644 --- a/fieldExtraction/src/constants/special_case_configs.py +++ b/fieldExtraction/constants/special_case_configs.py @@ -274,7 +274,7 @@ SPECIAL_CASE_CONFIGS = { "term_key": "methodology", }, "RATE_ESCALATOR": { - "questions": METHODOLOGY_BREAKOUT_QUESTIONS, # No rate escalator questions are added here because rate escalator is handled with its own breakout separately + "questions": METHODOLOGY_BREAKOUT_QUESTIONS, # No rate escalator questions are added here because rate escalator is handled with its own breakout separately "fs_questions": FS_BREAKOUT_QUESTIONS, "methodology_prompt_template": investment_prompts.REIMB_TERM_BREAKOUT, "fee_schedule_prompt_template": investment_prompts.FEE_SCHEDULE_BREAKOUT, @@ -284,6 +284,7 @@ SPECIAL_CASE_CONFIGS = { }, } + def merge_special_case_configs(special_cases: list[str]) -> dict: merged_questions = METHODOLOGY_BREAKOUT_QUESTIONS term_key = "methodology" # default @@ -319,4 +320,4 @@ def merge_special_case_configs(special_cases: list[str]) -> dict: "carveout_ind": False, "default_ind": False, "term_key": term_key, - } \ No newline at end of file + } diff --git a/fieldExtraction/src/constants/valid.py b/fieldExtraction/constants/valid.py similarity index 89% rename from fieldExtraction/src/constants/valid.py rename to fieldExtraction/constants/valid.py index ec360c7..5a7f622 100644 --- a/fieldExtraction/src/constants/valid.py +++ b/fieldExtraction/constants/valid.py @@ -1,5 +1,4 @@ import pandas as pd - from src import config VALID_LOBS = [ @@ -594,15 +593,17 @@ METHODOLOGY_FILTER = [ "if there is no established payment", ] -keywords_to_detect_invalid_state_name = [ #This is for health plan state when the LLM talks to itself - "suggest", - "The Contract Text", - "refers", - "For ", - "Answer", - "Multiple", - "Statewide", -] +keywords_to_detect_invalid_state_name = ( + [ # This is for health plan state when the LLM talks to itself + "suggest", + "The Contract Text", + "refers", + "For ", + "Answer", + "Multiple", + "Statewide", + ] +) STATE_MAP = { "AL": "Alabama", @@ -669,7 +670,7 @@ HOTFIX_MAPPING = { "Parent Agreement Code": "Parent Agreement Code", "Pages": "Pages", "page_num": "Page_Num", - "Page Num" : "Page_Num", + "Page Num": "Page_Num", "EXHIBIT": "Attachment/Exhibit", "CONTRACT_LOB": "Line of Business", "PROV_TYPE": "Provider Type", @@ -677,22 +678,22 @@ HOTFIX_MAPPING = { "IP_OP": "IP/OP", "FULL_SERVICE": "Service Type", "CONTRACT_PROGRAM": "Plan Type", - 'Lesser of Logic Language, Included (Y/N)' : "Lesser of Logic language, included (Y/N)", + "Lesser of Logic Language, Included (Y/N)": "Lesser of Logic language, included (Y/N)", "LESSER": "Lesser of Logic language, included (Y/N)", "LESSER_RATE": "Lesser of Rate", "FULL_METHODOLOGY": "Reimb. Methodology", - "Reimb. Methodology Standard" : "Reimb. Methodology", + "Reimb. Methodology Standard": "Reimb. Methodology", "SHORT_METHODOLOGY": "Reimb. Methodology_Short", - 'Reimb. Methodology Short' : 'Reimb. Methodology_Short', - 'If rate is % of Payor or MCR [Standard]' : 'If rate is % of Payor or MCR [STANDARD]', - 'If Rate is % of Payor or MCR [Standard]' : "If rate is % of Payor or MCR [STANDARD]", - 'If rate is % of Payor or MCR [STANDARD]' : "If rate is % of Payor or MCR [STANDARD]", + "Reimb. Methodology Short": "Reimb. Methodology_Short", + "If rate is % of Payor or MCR [Standard]": "If rate is % of Payor or MCR [STANDARD]", + "If Rate is % of Payor or MCR [Standard]": "If rate is % of Payor or MCR [STANDARD]", + "If rate is % of Payor or MCR [STANDARD]": "If rate is % of Payor or MCR [STANDARD]", "RATE_STANDARD": "If rate is % of Payor or MCR [STANDARD]", - 'If rate is % of Payor or MCR [Standard] Short' : 'If rate is % of Payor or MCR [STANDARD]_Short', - 'If rate is % of Payor or MCR [STANDARD]_Short' : 'If rate is % of Payor or MCR [STANDARD]_Short', + "If rate is % of Payor or MCR [Standard] Short": "If rate is % of Payor or MCR [STANDARD]_Short", + "If rate is % of Payor or MCR [STANDARD]_Short": "If rate is % of Payor or MCR [STANDARD]_Short", "RATE_SHORT": "If rate is % of Payor or MCR [STANDARD]_Short", "FLAT_FEE_STANDARD": "FLAT FEE", - 'Flat Fee' : 'FLAT FEE', + "Flat Fee": "FLAT FEE", "DEFAULT_TERM": "Default Term", "DEFAULT_RATE": "Default Rate", "MEDICAL_NECESSITY_LANGUAGE": "Medical Necessity Language (Language)", @@ -700,7 +701,7 @@ HOTFIX_MAPPING = { 'Inclusion of essential RBRVS "Fee Source" Language (Y/N)': 'Inclusion of Essential RBRVS "Fee Source" Language (Y/N)', "CDM_IND": "CDM Neutralization Language, included (Y/N)", "CHARGEMASTER": "CONTRACT_CHARGEMASTER_PROTECTION_LANGUAGE", - 'Contract Chargemaster Protection Language' : "CONTRACT_CHARGEMASTER_PROTECTION_LANGUAGE", + "Contract Chargemaster Protection Language": "CONTRACT_CHARGEMASTER_PROTECTION_LANGUAGE", "ADD_ON_REIMBURSEMENT_LANGUAGE": "Add On Reimbursement (Language)", "ADD_ON_REIMBURSEMENT_IND": "Add On Reimbursement (Y/N)", "SINGLE_CODE_MULTIPLE_RATES_LANGUAGE": "Single Code Multiple Rates (Language)", @@ -712,9 +713,9 @@ HOTFIX_MAPPING = { "RATE_ESCALATOR_IND": "Escalator or COLA (Y/N)", "RATE_ESCALATOR_DT": "Escalator I, Eff. Date", "CONTRACT_TITLE": "Agreement_Name (Contract Title)", - 'Agreement Name (Contract Title)': "Agreement_Name (Contract Title)", + "Agreement Name (Contract Title)": "Agreement_Name (Contract Title)", "PAYER_NAME": "PAYER NAME", - "Payer Name" : "PAYER NAME", + "Payer Name": "PAYER NAME", "AFFILIATION_CLAUSE_IND": "Affiliate (Y/N)", "TERM_CLAUSE": "Term Clause", "CONTRACT_AUTO_RENEWAL_IND": "Contract Auto-Renewal Indicator", @@ -722,7 +723,7 @@ HOTFIX_MAPPING = { "TERMINATION_UPON_NOTICE": "Termination Upon Notice - Days", "TERMINATION_UPON_CAUSE": "Termination With Cause - Days", "AMEND_CONTRACT_NOTICE_IND": "Amend Contract Upon notice Flag (Y/N)", - 'Amend Contract Upon Notice (Y/N)' : "Amend Contract Upon Notice Flag (Y/N)", + "Amend Contract Upon Notice (Y/N)": "Amend Contract Upon Notice Flag (Y/N)", "TIME_TO_OBJECT": "Timeframe to Object - Days", "ASSIGNMENTS_CLAUSE_IND": "Assignments Clause (Y/N)", "CONTRACT_EFFECTIVE_DT": "Contract Effective Date", @@ -730,11 +731,11 @@ HOTFIX_MAPPING = { "PROV_GROUP_NAME": "IRS_Name", "PROV_GROUP_NPI": "NPI (10-digits)", "PROV_GROUP_TIN_SIGNATORY": "PROV_GROUP_TIN_SIGNATORY", - 'Prov Group TIN Signatory' : "PROV_GROUP_TIN_SIGNATORY", + "Prov Group TIN Signatory": "PROV_GROUP_TIN_SIGNATORY", "PROV_TIN_OTHER": "PROV_TIN_OTHER", - "Prov TIN Other" : "PROV_TIN_OTHER", + "Prov TIN Other": "PROV_TIN_OTHER", "PROV_NPI_OTHER": "PROV_NPI_OTHER", - 'Prov NPI Other' : "PROV_NPI_OTHER", + "Prov NPI Other": "PROV_NPI_OTHER", "NOTICE_PROVIDER_NAME": "Notice to Provider Name", "NOTICE_PROVIDER_ADDRESS": "Notice to Provider Address", "HEALTH_PLAN_STATE": "Health Plan State", @@ -804,30 +805,30 @@ HOTFIX_MAPPING = { } B_IND_LANG_COLS = { - "LESSER" : "LESSER_RATE", - "MEDICAL_NECESSITY_LANGUAGE" : "MEDICAL_NECESSITY_LANGUAGE_IND", - "ADD_ON_REIMBURSEMENT_LANGUAGE" : "ADD_ON_REIMBURSEMENT_IND", - "SINGLE_CODE_MULTIPLE_RATES_IND" : "SINGLE_CODE_MULTIPLE_RATES_LANGUAGE", - "RATE_ESCALATOR_IND" : "RATE_ESCALATOR_DT" - } + "LESSER": "LESSER_RATE", + "MEDICAL_NECESSITY_LANGUAGE": "MEDICAL_NECESSITY_LANGUAGE_IND", + "ADD_ON_REIMBURSEMENT_LANGUAGE": "ADD_ON_REIMBURSEMENT_IND", + "SINGLE_CODE_MULTIPLE_RATES_IND": "SINGLE_CODE_MULTIPLE_RATES_LANGUAGE", + "RATE_ESCALATOR_IND": "RATE_ESCALATOR_DT", +} AC_IND_LANG_COLS = { - "SEQUESTRATION_LANGUAGE_IND" : "SEQUESTRATION_LANGUAGE", - "COST_SETTLEMENT_LANGUAGE_IND" : "COST_SETTLEMENT_LANGUAGE", - "LATE_PAID_CLAIMS_LANGUAGE_IND" : "LATE_PAID_CLAIMS_LANGUAGE", - "EXCLUSIVITY_REQUIREMENT_LANGUAGE_IND": "EXCLUSIVITY_REQUIREMENT_LANGUAGE", - "INDEPENDENT_REVIEW_LANGUAGE_IND" : "INDEPENDENT_REVIEW_LANGUAGE", - "MEMBER_CONFINEMENT_DAYS_LANGUAGE_IND" : "MEMBER_CONFINEMENT_DAYS_LANGUAGE", - "NETWORK_ACCESS_FEES_LANGUAGE_IND" : "NETWORK_ACCESS_FEES_LANGUAGE", - "PAYMENT_IN_ADVANCE_OF_CLAIMS_SUBMISSION_LANGUAGE_IND" : "PAYMENT_IN_ADVANCE_OF_CLAIMS_SUBMISSION_LANGUAGE", - "CONFLICTS_BETWEEN_CERTAIN_DOCUMENTS_LANGUAGE_IND" : "CONFLICTS_BETWEEN_CERTAIN_DOCUMENTS_LANGUAGE", - "RELATIONSHIP_OF_PARTIES_LANGUAGE_IND" : "RELATIONSHIP_OF_PARTIES_LANGUAGE", - "NONSTANDARD_APPEALS_PROCESS_LANGUAGE_IND" : "NONSTANDARD_APPEALS_PROCESS_LANGUAGE", - "DISPARAGEMENT_PROHIBITION_LANGUAGE_IND" : "DISPARAGEMENT_PROHIBITION_LANGUAGE", - "CLAIMS_EDITING_LANGUAGE_IND" : "CLAIMS_EDITING_LANGUAGE", - "GUARANTEE_OF_PROVIDER_YIELD_LANGUAGE_IND" : "GUARANTEE_OF_PROVIDER_YIELD_LANGUAGE", - "ADD_ON_REIMBURSEMENT_IND" : "ADD_ON_REIMBURSEMENT_LANGUAGE", - "SINGLE_CODE_MULTIPLE_RATES_IND" : "SINGLE_CODE_MULTIPLE_RATES_LANGUAGE", - "INVOICE_PRICING_LANGUAGE_IND" : "INVOICE_PRICING_LANGUAGE", - "PROVIDER_BASED_BILLING_EXCLUSION_LANGUAGE_IND" : "PROVIDER_BASED_BILLING_EXCLUSION_LANGUAGE" - } + "SEQUESTRATION_LANGUAGE_IND": "SEQUESTRATION_LANGUAGE", + "COST_SETTLEMENT_LANGUAGE_IND": "COST_SETTLEMENT_LANGUAGE", + "LATE_PAID_CLAIMS_LANGUAGE_IND": "LATE_PAID_CLAIMS_LANGUAGE", + "EXCLUSIVITY_REQUIREMENT_LANGUAGE_IND": "EXCLUSIVITY_REQUIREMENT_LANGUAGE", + "INDEPENDENT_REVIEW_LANGUAGE_IND": "INDEPENDENT_REVIEW_LANGUAGE", + "MEMBER_CONFINEMENT_DAYS_LANGUAGE_IND": "MEMBER_CONFINEMENT_DAYS_LANGUAGE", + "NETWORK_ACCESS_FEES_LANGUAGE_IND": "NETWORK_ACCESS_FEES_LANGUAGE", + "PAYMENT_IN_ADVANCE_OF_CLAIMS_SUBMISSION_LANGUAGE_IND": "PAYMENT_IN_ADVANCE_OF_CLAIMS_SUBMISSION_LANGUAGE", + "CONFLICTS_BETWEEN_CERTAIN_DOCUMENTS_LANGUAGE_IND": "CONFLICTS_BETWEEN_CERTAIN_DOCUMENTS_LANGUAGE", + "RELATIONSHIP_OF_PARTIES_LANGUAGE_IND": "RELATIONSHIP_OF_PARTIES_LANGUAGE", + "NONSTANDARD_APPEALS_PROCESS_LANGUAGE_IND": "NONSTANDARD_APPEALS_PROCESS_LANGUAGE", + "DISPARAGEMENT_PROHIBITION_LANGUAGE_IND": "DISPARAGEMENT_PROHIBITION_LANGUAGE", + "CLAIMS_EDITING_LANGUAGE_IND": "CLAIMS_EDITING_LANGUAGE", + "GUARANTEE_OF_PROVIDER_YIELD_LANGUAGE_IND": "GUARANTEE_OF_PROVIDER_YIELD_LANGUAGE", + "ADD_ON_REIMBURSEMENT_IND": "ADD_ON_REIMBURSEMENT_LANGUAGE", + "SINGLE_CODE_MULTIPLE_RATES_IND": "SINGLE_CODE_MULTIPLE_RATES_LANGUAGE", + "INVOICE_PRICING_LANGUAGE_IND": "INVOICE_PRICING_LANGUAGE", + "PROVIDER_BASED_BILLING_EXCLUSION_LANGUAGE_IND": "PROVIDER_BASED_BILLING_EXCLUSION_LANGUAGE", +} diff --git a/fieldExtraction/poetry.lock b/fieldExtraction/poetry.lock index 13be7d3..a322e90 100644 --- a/fieldExtraction/poetry.lock +++ b/fieldExtraction/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -6,6 +6,7 @@ version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, @@ -13,97 +14,103 @@ files = [ [[package]] name = "aiohttp" -version = "3.11.16" +version = "3.12.15" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb46bb0f24813e6cede6cc07b1961d4b04f331f7112a23b5e21f567da4ee50aa"}, - {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:54eb3aead72a5c19fad07219acd882c1643a1027fbcdefac9b502c267242f955"}, - {file = "aiohttp-3.11.16-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:38bea84ee4fe24ebcc8edeb7b54bf20f06fd53ce4d2cc8b74344c5b9620597fd"}, - {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0666afbe984f6933fe72cd1f1c3560d8c55880a0bdd728ad774006eb4241ecd"}, - {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba92a2d9ace559a0a14b03d87f47e021e4fa7681dc6970ebbc7b447c7d4b7cd"}, - {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ad1d59fd7114e6a08c4814983bb498f391c699f3c78712770077518cae63ff7"}, - {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b88a2bf26965f2015a771381624dd4b0839034b70d406dc74fd8be4cc053e3"}, - {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:576f5ca28d1b3276026f7df3ec841ae460e0fc3aac2a47cbf72eabcfc0f102e1"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a2a450bcce4931b295fc0848f384834c3f9b00edfc2150baafb4488c27953de6"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:37dcee4906454ae377be5937ab2a66a9a88377b11dd7c072df7a7c142b63c37c"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4d0c970c0d602b1017e2067ff3b7dac41c98fef4f7472ec2ea26fd8a4e8c2149"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:004511d3413737700835e949433536a2fe95a7d0297edd911a1e9705c5b5ea43"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c15b2271c44da77ee9d822552201180779e5e942f3a71fb74e026bf6172ff287"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad9509ffb2396483ceacb1eee9134724443ee45b92141105a4645857244aecc8"}, - {file = "aiohttp-3.11.16-cp310-cp310-win32.whl", hash = "sha256:634d96869be6c4dc232fc503e03e40c42d32cfaa51712aee181e922e61d74814"}, - {file = "aiohttp-3.11.16-cp310-cp310-win_amd64.whl", hash = "sha256:938f756c2b9374bbcc262a37eea521d8a0e6458162f2a9c26329cc87fdf06534"}, - {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cb0688a8d81c63d716e867d59a9ccc389e97ac7037ebef904c2b89334407180"}, - {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ad1fb47da60ae1ddfb316f0ff16d1f3b8e844d1a1e154641928ea0583d486ed"}, - {file = "aiohttp-3.11.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df7db76400bf46ec6a0a73192b14c8295bdb9812053f4fe53f4e789f3ea66bbb"}, - {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc3a145479a76ad0ed646434d09216d33d08eef0d8c9a11f5ae5cdc37caa3540"}, - {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d007aa39a52d62373bd23428ba4a2546eed0e7643d7bf2e41ddcefd54519842c"}, - {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6ddd90d9fb4b501c97a4458f1c1720e42432c26cb76d28177c5b5ad4e332601"}, - {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a2f451849e6b39e5c226803dcacfa9c7133e9825dcefd2f4e837a2ec5a3bb98"}, - {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8df6612df74409080575dca38a5237282865408016e65636a76a2eb9348c2567"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78e6e23b954644737e385befa0deb20233e2dfddf95dd11e9db752bdd2a294d3"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:696ef00e8a1f0cec5e30640e64eca75d8e777933d1438f4facc9c0cdf288a810"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3538bc9fe1b902bef51372462e3d7c96fce2b566642512138a480b7adc9d508"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3ab3367bb7f61ad18793fea2ef71f2d181c528c87948638366bf1de26e239183"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:56a3443aca82abda0e07be2e1ecb76a050714faf2be84256dae291182ba59049"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:61c721764e41af907c9d16b6daa05a458f066015abd35923051be8705108ed17"}, - {file = "aiohttp-3.11.16-cp311-cp311-win32.whl", hash = "sha256:3e061b09f6fa42997cf627307f220315e313ece74907d35776ec4373ed718b86"}, - {file = "aiohttp-3.11.16-cp311-cp311-win_amd64.whl", hash = "sha256:745f1ed5e2c687baefc3c5e7b4304e91bf3e2f32834d07baaee243e349624b24"}, - {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:911a6e91d08bb2c72938bc17f0a2d97864c531536b7832abee6429d5296e5b27"}, - {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac13b71761e49d5f9e4d05d33683bbafef753e876e8e5a7ef26e937dd766713"}, - {file = "aiohttp-3.11.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd36c119c5d6551bce374fcb5c19269638f8d09862445f85a5a48596fd59f4bb"}, - {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d489d9778522fbd0f8d6a5c6e48e3514f11be81cb0a5954bdda06f7e1594b321"}, - {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69a2cbd61788d26f8f1e626e188044834f37f6ae3f937bd9f08b65fc9d7e514e"}, - {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd464ba806e27ee24a91362ba3621bfc39dbbb8b79f2e1340201615197370f7c"}, - {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce63ae04719513dd2651202352a2beb9f67f55cb8490c40f056cea3c5c355ce"}, - {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b00dd520d88eac9d1768439a59ab3d145065c91a8fab97f900d1b5f802895e"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f6428fee52d2bcf96a8aa7b62095b190ee341ab0e6b1bcf50c615d7966fd45b"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13ceac2c5cdcc3f64b9015710221ddf81c900c5febc505dbd8f810e770011540"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fadbb8f1d4140825069db3fedbbb843290fd5f5bc0a5dbd7eaf81d91bf1b003b"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6a792ce34b999fbe04a7a71a90c74f10c57ae4c51f65461a411faa70e154154e"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f4065145bf69de124accdd17ea5f4dc770da0a6a6e440c53f6e0a8c27b3e635c"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa73e8c2656a3653ae6c307b3f4e878a21f87859a9afab228280ddccd7369d71"}, - {file = "aiohttp-3.11.16-cp312-cp312-win32.whl", hash = "sha256:f244b8e541f414664889e2c87cac11a07b918cb4b540c36f7ada7bfa76571ea2"}, - {file = "aiohttp-3.11.16-cp312-cp312-win_amd64.whl", hash = "sha256:23a15727fbfccab973343b6d1b7181bfb0b4aa7ae280f36fd2f90f5476805682"}, - {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a3814760a1a700f3cfd2f977249f1032301d0a12c92aba74605cfa6ce9f78489"}, - {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b751a6306f330801665ae69270a8a3993654a85569b3469662efaad6cf5cc50"}, - {file = "aiohttp-3.11.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad497f38a0d6c329cb621774788583ee12321863cd4bd9feee1effd60f2ad133"}, - {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca37057625693d097543bd88076ceebeb248291df9d6ca8481349efc0b05dcd0"}, - {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5abcbba9f4b463a45c8ca8b7720891200658f6f46894f79517e6cd11f3405ca"}, - {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f420bfe862fb357a6d76f2065447ef6f484bc489292ac91e29bc65d2d7a2c84d"}, - {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58ede86453a6cf2d6ce40ef0ca15481677a66950e73b0a788917916f7e35a0bb"}, - {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fdec0213244c39973674ca2a7f5435bf74369e7d4e104d6c7473c81c9bcc8c4"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72b1b03fb4655c1960403c131740755ec19c5898c82abd3961c364c2afd59fe7"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:780df0d837276276226a1ff803f8d0fa5f8996c479aeef52eb040179f3156cbd"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ecdb8173e6c7aa09eee342ac62e193e6904923bd232e76b4157ac0bfa670609f"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6db7458ab89c7d80bc1f4e930cc9df6edee2200127cfa6f6e080cf619eddfbd"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2540ddc83cc724b13d1838026f6a5ad178510953302a49e6d647f6e1de82bc34"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b4e6db8dc4879015b9955778cfb9881897339c8fab7b3676f8433f849425913"}, - {file = "aiohttp-3.11.16-cp313-cp313-win32.whl", hash = "sha256:493910ceb2764f792db4dc6e8e4b375dae1b08f72e18e8f10f18b34ca17d0979"}, - {file = "aiohttp-3.11.16-cp313-cp313-win_amd64.whl", hash = "sha256:42864e70a248f5f6a49fdaf417d9bc62d6e4d8ee9695b24c5916cb4bb666c802"}, - {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bbcba75fe879ad6fd2e0d6a8d937f34a571f116a0e4db37df8079e738ea95c71"}, - {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:87a6e922b2b2401e0b0cf6b976b97f11ec7f136bfed445e16384fbf6fd5e8602"}, - {file = "aiohttp-3.11.16-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccf10f16ab498d20e28bc2b5c1306e9c1512f2840f7b6a67000a517a4b37d5ee"}, - {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb3d0cc5cdb926090748ea60172fa8a213cec728bd6c54eae18b96040fcd6227"}, - {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d07502cc14ecd64f52b2a74ebbc106893d9a9717120057ea9ea1fd6568a747e7"}, - {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:776c8e959a01e5e8321f1dec77964cb6101020a69d5a94cd3d34db6d555e01f7"}, - {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0902e887b0e1d50424112f200eb9ae3dfed6c0d0a19fc60f633ae5a57c809656"}, - {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87fd812899aa78252866ae03a048e77bd11b80fb4878ce27c23cade239b42b2"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0a950c2eb8ff17361abd8c85987fd6076d9f47d040ebffce67dce4993285e973"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:c10d85e81d0b9ef87970ecbdbfaeec14a361a7fa947118817fcea8e45335fa46"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7951decace76a9271a1ef181b04aa77d3cc309a02a51d73826039003210bdc86"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14461157d8426bcb40bd94deb0450a6fa16f05129f7da546090cebf8f3123b0f"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9756d9b9d4547e091f99d554fbba0d2a920aab98caa82a8fb3d3d9bee3c9ae85"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:87944bd16b7fe6160607f6a17808abd25f17f61ae1e26c47a491b970fb66d8cb"}, - {file = "aiohttp-3.11.16-cp39-cp39-win32.whl", hash = "sha256:92b7ee222e2b903e0a4b329a9943d432b3767f2d5029dbe4ca59fb75223bbe2e"}, - {file = "aiohttp-3.11.16-cp39-cp39-win_amd64.whl", hash = "sha256:17ae4664031aadfbcb34fd40ffd90976671fa0c0286e6c4113989f78bebab37a"}, - {file = "aiohttp-3.11.16.tar.gz", hash = "sha256:16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8"}, + {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b6fc902bff74d9b1879ad55f5404153e2b33a82e72a95c89cec5eb6cc9e92fbc"}, + {file = "aiohttp-3.12.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:098e92835b8119b54c693f2f88a1dec690e20798ca5f5fe5f0520245253ee0af"}, + {file = "aiohttp-3.12.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:40b3fee496a47c3b4a39a731954c06f0bd9bd3e8258c059a4beb76ac23f8e421"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ce13fcfb0bb2f259fb42106cdc63fa5515fb85b7e87177267d89a771a660b79"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3beb14f053222b391bf9cf92ae82e0171067cc9c8f52453a0f1ec7c37df12a77"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c39e87afe48aa3e814cac5f535bc6199180a53e38d3f51c5e2530f5aa4ec58c"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5f1b4ce5bc528a6ee38dbf5f39bbf11dd127048726323b72b8e85769319ffc4"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1004e67962efabbaf3f03b11b4c43b834081c9e3f9b32b16a7d97d4708a9abe6"}, + {file = "aiohttp-3.12.15-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8faa08fcc2e411f7ab91d1541d9d597d3a90e9004180edb2072238c085eac8c2"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fe086edf38b2222328cdf89af0dde2439ee173b8ad7cb659b4e4c6f385b2be3d"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:79b26fe467219add81d5e47b4a4ba0f2394e8b7c7c3198ed36609f9ba161aecb"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b761bac1192ef24e16706d761aefcb581438b34b13a2f069a6d343ec8fb693a5"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e153e8adacfe2af562861b72f8bc47f8a5c08e010ac94eebbe33dc21d677cd5b"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:fc49c4de44977aa8601a00edbf157e9a421f227aa7eb477d9e3df48343311065"}, + {file = "aiohttp-3.12.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2776c7ec89c54a47029940177e75c8c07c29c66f73464784971d6a81904ce9d1"}, + {file = "aiohttp-3.12.15-cp310-cp310-win32.whl", hash = "sha256:2c7d81a277fa78b2203ab626ced1487420e8c11a8e373707ab72d189fcdad20a"}, + {file = "aiohttp-3.12.15-cp310-cp310-win_amd64.whl", hash = "sha256:83603f881e11f0f710f8e2327817c82e79431ec976448839f3cd05d7afe8f830"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe"}, + {file = "aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b"}, + {file = "aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7"}, + {file = "aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685"}, + {file = "aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b"}, + {file = "aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444"}, + {file = "aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545"}, + {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea"}, + {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3"}, + {file = "aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1"}, + {file = "aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd"}, + {file = "aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d"}, + {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64"}, + {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51"}, + {file = "aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0"}, + {file = "aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84"}, + {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:691d203c2bdf4f4637792efbbcdcd157ae11e55eaeb5e9c360c1206fb03d4d98"}, + {file = "aiohttp-3.12.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e995e1abc4ed2a454c731385bf4082be06f875822adc4c6d9eaadf96e20d406"}, + {file = "aiohttp-3.12.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bd44d5936ab3193c617bfd6c9a7d8d1085a8dc8c3f44d5f1dcf554d17d04cf7d"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46749be6e89cd78d6068cdf7da51dbcfa4321147ab8e4116ee6678d9a056a0cf"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0c643f4d75adea39e92c0f01b3fb83d57abdec8c9279b3078b68a3a52b3933b6"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a23918fedc05806966a2438489dcffccbdf83e921a1170773b6178d04ade142"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74bdd8c864b36c3673741023343565d95bfbd778ffe1eb4d412c135a28a8dc89"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a146708808c9b7a988a4af3821379e379e0f0e5e466ca31a73dbdd0325b0263"}, + {file = "aiohttp-3.12.15-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7011a70b56facde58d6d26da4fec3280cc8e2a78c714c96b7a01a87930a9530"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:3bdd6e17e16e1dbd3db74d7f989e8af29c4d2e025f9828e6ef45fbdee158ec75"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57d16590a351dfc914670bd72530fd78344b885a00b250e992faea565b7fdc05"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:bc9a0f6569ff990e0bbd75506c8d8fe7214c8f6579cca32f0546e54372a3bb54"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:536ad7234747a37e50e7b6794ea868833d5220b49c92806ae2d7e8a9d6b5de02"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f0adb4177fa748072546fb650d9bd7398caaf0e15b370ed3317280b13f4083b0"}, + {file = "aiohttp-3.12.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:14954a2988feae3987f1eb49c706bff39947605f4b6fa4027c1d75743723eb09"}, + {file = "aiohttp-3.12.15-cp39-cp39-win32.whl", hash = "sha256:b784d6ed757f27574dca1c336f968f4e81130b27595e458e69457e6878251f5d"}, + {file = "aiohttp-3.12.15-cp39-cp39-win_amd64.whl", hash = "sha256:86ceded4e78a992f835209e236617bffae649371c4a50d5e5a3987f237db84b8"}, + {file = "aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2"}, ] [package.dependencies] -aiohappyeyeballs = ">=2.3.0" -aiosignal = ">=1.1.2" +aiohappyeyeballs = ">=2.5.0" +aiosignal = ">=1.4.0" attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" @@ -111,21 +118,23 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] +speedups = ["Brotli", "aiodns (>=3.3.0)", "brotlicffi"] [[package]] name = "aiosignal" -version = "1.3.2" +version = "1.4.0" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, - {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, + {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"}, + {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"}, ] [package.dependencies] frozenlist = ">=1.1.0" +typing-extensions = {version = ">=4.2", markers = "python_version < \"3.13\""} [[package]] name = "annotated-types" @@ -133,6 +142,7 @@ version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -144,6 +154,7 @@ version = "0.36.2" description = "The official Python library for the anthropic API" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "anthropic-0.36.2-py3-none-any.whl", hash = "sha256:308ddc6c538de03c081552e456bc0b387b6f7c7d1dea0c20122cc11c7cdbaf6a"}, {file = "anthropic-0.36.2.tar.gz", hash = "sha256:d5a3fa56d1c82a51944f9dc7b0dc72048deb89f8df5ebfd09e2d1b59c62eb8eb"}, @@ -165,13 +176,14 @@ vertex = ["google-auth (>=2,<3)"] [[package]] name = "anyio" -version = "4.9.0" -description = "High level compatibility layer for multiple asynchronous event loop implementations" +version = "4.10.0" +description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, - {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, + {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"}, + {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"}, ] [package.dependencies] @@ -180,8 +192,6 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -190,6 +200,8 @@ version = "0.1.4" description = "Disable App Nap on macOS >= 10.9" optional = false python-versions = ">=3.6" +groups = ["dev"] +markers = "platform_system == \"Darwin\"" files = [ {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, @@ -197,60 +209,60 @@ files = [ [[package]] name = "argon2-cffi" -version = "23.1.0" +version = "25.1.0" description = "Argon2 for Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, - {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, + {file = "argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741"}, + {file = "argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1"}, ] [package.dependencies] argon2-cffi-bindings = "*" -[package.extras] -dev = ["argon2-cffi[tests,typing]", "tox (>4)"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] -tests = ["hypothesis", "pytest"] -typing = ["mypy"] - [[package]] name = "argon2-cffi-bindings" -version = "21.2.0" +version = "25.1.0" description = "Low-level CFFI bindings for Argon2" optional = false -python-versions = ">=3.6" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, - {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, - {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, - {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, - {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:3d3f05610594151994ca9ccb3c771115bdb4daef161976a266f0dd8aa9996b8f"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8b8efee945193e667a396cbc7b4fb7d357297d6234d30a489905d96caabde56b"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3c6702abc36bf3ccba3f802b799505def420a1b7039862014a65db3205967f5a"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1c70058c6ab1e352304ac7e3b52554daadacd8d453c1752e547c76e9c99ac44"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e2fd3bfbff3c5d74fef31a722f729bf93500910db650c925c2d6ef879a7e51cb"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4f9665de60b1b0e99bcd6be4f17d90339698ce954cfd8d9cf4f91c995165a92"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ba92837e4a9aa6a508c8d2d7883ed5a8f6c308c89a4790e1e447a220deb79a85"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-win32.whl", hash = "sha256:84a461d4d84ae1295871329b346a97f68eade8c53b6ed9a7ca2d7467f3c8ff6f"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b55aec3565b65f56455eebc9b9f34130440404f27fe21c3b375bf1ea4d8fbae6"}, + {file = "argon2_cffi_bindings-25.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:87c33a52407e4c41f3b70a9c2d3f6056d88b10dad7695be708c5021673f55623"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98"}, + {file = "argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94"}, + {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6dca33a9859abf613e22733131fc9194091c1fa7cb3e131c143056b4856aa47e"}, + {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:21378b40e1b8d1655dd5310c84a40fc19a9aa5e6366e835ceb8576bf0fea716d"}, + {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d588dec224e2a83edbdc785a5e6f3c6cd736f46bfd4b441bbb5aa1f5085e584"}, + {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5acb4e41090d53f17ca1110c3427f0a130f944b896fc8c83973219c97f57b690"}, + {file = "argon2_cffi_bindings-25.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:da0c79c23a63723aa5d782250fbf51b768abca630285262fb5144ba5ae01e520"}, + {file = "argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d"}, ] [package.dependencies] -cffi = ">=1.0.1" - -[package.extras] -dev = ["cogapp", "pre-commit", "pytest", "wheel"] -tests = ["pytest"] +cffi = [ + {version = ">=1.0.1", markers = "python_version < \"3.14\""}, + {version = ">=2.0.0b1", markers = "python_version >= \"3.14\""}, +] [[package]] name = "arrow" @@ -258,6 +270,7 @@ version = "1.3.0" description = "Better dates & times for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "arrow-1.3.0-py3-none-any.whl", hash = "sha256:c728b120ebc00eb84e01882a6f5e7927a53960aa990ce7dd2b10f39005a67f80"}, {file = "arrow-1.3.0.tar.gz", hash = "sha256:d4540617648cb5f895730f1ad8c82a65f2dad0166f57b75f3ca54759c4d67a85"}, @@ -271,26 +284,13 @@ types-python-dateutil = ">=2.8.10" doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] -[[package]] -name = "asgiref" -version = "3.8.1" -description = "ASGI specs, helper code, and adapters" -optional = false -python-versions = ">=3.8" -files = [ - {file = "asgiref-3.8.1-py3-none-any.whl", hash = "sha256:3e1e3ecc849832fe52ccf2cb6686b7a55f82bb1d6aee72a58826471390335e47"}, - {file = "asgiref-3.8.1.tar.gz", hash = "sha256:c343bd80a0bec947a9860adb4c432ffa7db769836c64238fc34bdc3fec84d590"}, -] - -[package.extras] -tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] - [[package]] name = "asttokens" version = "3.0.0" description = "Annotate AST trees with source code positions" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, @@ -306,6 +306,7 @@ version = "2.0.5" description = "Simple LRU cache for asyncio" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "async_lru-2.0.5-py3-none-any.whl", hash = "sha256:ab95404d8d2605310d345932697371a5f40def0487c03d6d0ad9138de52c9943"}, {file = "async_lru-2.0.5.tar.gz", hash = "sha256:481d52ccdd27275f42c43a928b4a50c3bfb2d67af4e78b170e3e0bb39c66e5bb"}, @@ -317,6 +318,7 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, @@ -336,6 +338,7 @@ version = "2.17.0" description = "Internationalization utilities" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, @@ -350,6 +353,7 @@ version = "2.2.1" description = "Function decoration for backoff and retry" optional = false python-versions = ">=3.7,<4.0" +groups = ["main"] files = [ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"}, {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"}, @@ -361,6 +365,7 @@ version = "4.3.0" description = "Modern password hashing for your software and your servers" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281"}, {file = "bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb"}, @@ -421,13 +426,14 @@ typecheck = ["mypy"] [[package]] name = "beautifulsoup4" -version = "4.13.3" +version = "4.13.4" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" +groups = ["dev"] files = [ - {file = "beautifulsoup4-4.13.3-py3-none-any.whl", hash = "sha256:99045d7d3f08f91f0d656bc9b7efbae189426cd913d830294a15eefa0ea4df16"}, - {file = "beautifulsoup4-4.13.3.tar.gz", hash = "sha256:1bd32405dacc920b42b83ba01644747ed77456a65760e285fbc47633ceddaf8b"}, + {file = "beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b"}, + {file = "beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195"}, ] [package.dependencies] @@ -447,6 +453,7 @@ version = "24.10.0" description = "The uncompromising code formatter." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, @@ -491,6 +498,7 @@ version = "6.2.0" description = "An easy safelist-based HTML-sanitizing tool." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "bleach-6.2.0-py3-none-any.whl", hash = "sha256:117d9c6097a7c3d22fd578fcd8d35ff1e125df6736f554da4e432fdd63f31e5e"}, {file = "bleach-6.2.0.tar.gz", hash = "sha256:123e894118b8a599fd80d3ec1a6d4cc7ce4e5882b1317a7e1ba69b56e95f991f"}, @@ -505,32 +513,34 @@ css = ["tinycss2 (>=1.1.0,<1.5)"] [[package]] name = "boto3" -version = "1.37.29" +version = "1.40.2" description = "The AWS SDK for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "boto3-1.37.29-py3-none-any.whl", hash = "sha256:869979050e2cf6f5461503e0f1c8f226e47ec02802e88a2210f085ec22485945"}, - {file = "boto3-1.37.29.tar.gz", hash = "sha256:5702e38356b93c56ed2a27e17f7664d791f1fe2eafd58ae6ab3853b2804cadd2"}, + {file = "boto3-1.40.2-py3-none-any.whl", hash = "sha256:3d99325ee874190e8f3bfd38823987327c826cdfbab943420851bdb7684d727c"}, + {file = "boto3-1.40.2.tar.gz", hash = "sha256:2dfbc214fdbf94abfd61eec687ea39089d05af43bb00be792c76f3a6c1393f7b"}, ] [package.dependencies] -botocore = ">=1.37.29,<1.38.0" +botocore = ">=1.40.2,<1.41.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.11.0,<0.12.0" +s3transfer = ">=0.13.0,<0.14.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.37.29" +version = "1.40.2" description = "Low-level, data-driven core of boto 3." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "botocore-1.37.29-py3-none-any.whl", hash = "sha256:092c41e346df37a8d7cf60a799791f8225ad3a5ba7cda749047eb31d1440b9c5"}, - {file = "botocore-1.37.29.tar.gz", hash = "sha256:728c1ef3b66a0f79bc08008a59f6fd6bef2a0a0195e5b3b9e9bef255df519890"}, + {file = "botocore-1.40.2-py3-none-any.whl", hash = "sha256:a31e6269af05498f8dc1c7f2b3f34448a0f16c79a8601c0389ecddab51b2c2ab"}, + {file = "botocore-1.40.2.tar.gz", hash = "sha256:77c4710bf37b28e897833b5b1f47d6a83e45a29985cd01a560dfdb8b6ad524e5"}, ] [package.dependencies] @@ -543,13 +553,14 @@ crt = ["awscrt (==0.23.8)"] [[package]] name = "build" -version = "1.2.2.post1" +version = "1.3.0" description = "A simple, correct Python build frontend" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5"}, - {file = "build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7"}, + {file = "build-1.3.0-py3-none-any.whl", hash = "sha256:7145f0b5061ba90a1500d60bd1b13ca0a8a4cebdd0cc16ed8adf1c0e739f43b4"}, + {file = "build-1.3.0.tar.gz", hash = "sha256:698edd0ea270bde950f53aed21f3a0135672206f3911e0176261a31e0e07b397"}, ] [package.dependencies] @@ -558,11 +569,8 @@ packaging = ">=19.1" pyproject_hooks = "*" [package.extras] -docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] -test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"] -typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"] uv = ["uv (>=0.1.18)"] -virtualenv = ["virtualenv (>=20.0.35)"] +virtualenv = ["virtualenv (>=20.11)", "virtualenv (>=20.17)", "virtualenv (>=20.31)"] [[package]] name = "cachetools" @@ -570,6 +578,7 @@ version = "5.5.2" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a"}, {file = "cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4"}, @@ -577,13 +586,14 @@ files = [ [[package]] name = "certifi" -version = "2025.1.31" +version = "2025.8.3" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" +groups = ["main", "dev"] files = [ - {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, - {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, ] [[package]] @@ -592,6 +602,7 @@ version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, @@ -661,202 +672,269 @@ files = [ {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] +markers = {main = "python_version < \"3.14\" and platform_python_implementation == \"PyPy\"", dev = "python_version < \"3.14\""} [package.dependencies] pycparser = "*" +[[package]] +name = "cffi" +version = "2.0.0b1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "cffi-2.0.0b1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:4b69c24a89c30a7821ecd25bcaff99075d95dd0c85c8845768c340a7736d84cf"}, + {file = "cffi-2.0.0b1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ba9946f292f7ae3a6f1cc72af259c477c291eb10ad3ca74180862e39f46a521"}, + {file = "cffi-2.0.0b1-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1f4ca4ac8b9ee620ff5cb4307fae08691a0911bf0eeb488e8d6cf55bd77dfe43"}, + {file = "cffi-2.0.0b1-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0eb17b22e313c453c940931f5d063ba9e87e5db12d99473477ab1851e66fedb4"}, + {file = "cffi-2.0.0b1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a1faa47c7fbe0627f6b621dadebed9f532a789a1d3b519731304da1d3ec3d14"}, + {file = "cffi-2.0.0b1-cp310-cp310-manylinux_2_27_i686.manylinux_2_28_i686.whl", hash = "sha256:230a97779cdd6734b6af3bfda4be31406bab58a078f25327b169975be9225a46"}, + {file = "cffi-2.0.0b1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c177aa1cdae420519665da22760f4a4a159551733d4686a4467f579bf7b75470"}, + {file = "cffi-2.0.0b1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bdd3ce5e620ff6ee1e89fb7abb620756482fb3e337e5121e441cb0071c11cbd0"}, + {file = "cffi-2.0.0b1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0dbbe4a9bfcc058fccfee33ea5bebe50440767d219c2efa3a722a90ed59e8cfa"}, + {file = "cffi-2.0.0b1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5f304ce328ecfb7bc36034374c20d0b4ae70423253f8a81c5e0b5efd90e29cd4"}, + {file = "cffi-2.0.0b1-cp310-cp310-win32.whl", hash = "sha256:5acd1da34b96c8881b5df0e3d83cdbecc349b9ad5e9b8c0c589646c241448853"}, + {file = "cffi-2.0.0b1-cp310-cp310-win_amd64.whl", hash = "sha256:ebb116751a49977c0b130493d3af13c567c4613946d293d4f61601237fabcd5f"}, + {file = "cffi-2.0.0b1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:5f373f9bdc3569acd8aaebb6b521080eeb5a298533a58715537caf74e9e27f6b"}, + {file = "cffi-2.0.0b1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a898f76bac81f9a371df6c8664228a85cdea6b283a721f2493f0df6f80afd208"}, + {file = "cffi-2.0.0b1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:314afab228f7b45de7bae55059b4e706296e7d3984d53e643cc0389757216221"}, + {file = "cffi-2.0.0b1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6de033c73dc89f80139c5a7d135fbd6c1d7b28ebb0d2df98cd1f4ef76991b15c"}, + {file = "cffi-2.0.0b1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffbbeedd6bac26c0373b71831d3c73181a1c100dc6fc7aadbfcca54cace417db"}, + {file = "cffi-2.0.0b1-cp311-cp311-manylinux_2_27_i686.manylinux_2_28_i686.whl", hash = "sha256:c5713cac21b2351a53958c765d8e9eda45184bb757c3ccab139608e708788796"}, + {file = "cffi-2.0.0b1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71ab35c6cc375da1e2c06af65bf0b5049199ad9b264f9ed7c90c0fe9450900e3"}, + {file = "cffi-2.0.0b1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:53c780c2ec8ce0e5db9b74e9b0b55ff5d5f70071202740cef073a2771fa1d2ce"}, + {file = "cffi-2.0.0b1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:be957dd266facf8e4925643073159b05021a990b46620b06ca27eaf9d900dbc2"}, + {file = "cffi-2.0.0b1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:16dc303af3630f54186b86aadf1121badf3cba6de17dfeacb84c5091e059a690"}, + {file = "cffi-2.0.0b1-cp311-cp311-win32.whl", hash = "sha256:504d264944d0934d7b02164af5c62b175255ef0d39c5142d95968b710c58a8f6"}, + {file = "cffi-2.0.0b1-cp311-cp311-win_amd64.whl", hash = "sha256:e2920fa42cf0616c21ea6d3948ad207cf0e420d2d2ef449d86ccad6ef9c13393"}, + {file = "cffi-2.0.0b1-cp311-cp311-win_arm64.whl", hash = "sha256:142c9c0c75fbc95ce23836e538681bd89e483de37b7cdf251dbdf0975995f8ac"}, + {file = "cffi-2.0.0b1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9d04b5fc06ba0ce45d7e51dfd8a14dc20708ef301fcf5a215c507f4e084b00c8"}, + {file = "cffi-2.0.0b1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b17e92900eb61bce62ea07ea8dd0dc33aa476ee8f977918050e52f90f5b645c"}, + {file = "cffi-2.0.0b1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2155d2a0819c3fdcaa37832fb69e698d455627c23f83bc9c7adbef699fe4be19"}, + {file = "cffi-2.0.0b1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4210ddc2b41c20739c64dede1304fb81415220ea671885623063fab44066e376"}, + {file = "cffi-2.0.0b1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31b8e3204cdef043e59a296383e6a43461d17c5c3d73fa9cebf4716a561291b0"}, + {file = "cffi-2.0.0b1-cp312-cp312-manylinux_2_27_i686.manylinux_2_28_i686.whl", hash = "sha256:cbde39be02aa7d8fbcd6bf1a9241cb1d84f2e2f0614970c51a707a9a176b85c6"}, + {file = "cffi-2.0.0b1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ea57043b545f346b081877737cb0320960012107d0250fa5183a4306f9365d6"}, + {file = "cffi-2.0.0b1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d31ba9f54739dcf98edb87e4881e326fad79e4866137c24afb0da531c1a965ca"}, + {file = "cffi-2.0.0b1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:27309de8cebf48e056550db6607e2fb2c50109b54fc72c02b3b34811233483be"}, + {file = "cffi-2.0.0b1-cp312-cp312-win32.whl", hash = "sha256:f4b5acb4cddcaf0ebb82a226f9fa1d5063505e0c206031ee1f4d173750b592fd"}, + {file = "cffi-2.0.0b1-cp312-cp312-win_amd64.whl", hash = "sha256:cf1b2510f1a91c4d7e8f83df6a13404332421e6e4a067059174d455653ae5314"}, + {file = "cffi-2.0.0b1-cp312-cp312-win_arm64.whl", hash = "sha256:bd7ce5d8224fb5a57bd7f1d9843aa4ecb870ec3f4a2101e1ba8314e91177e184"}, + {file = "cffi-2.0.0b1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a160995771c54b12dc5a1ef44d6fd59aeea4909e2d58c10169156e9d9a7e2960"}, + {file = "cffi-2.0.0b1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c70c77ec47b96a593477386d7bf23243996c75f1cc7ce383ba35dcedca9bd14"}, + {file = "cffi-2.0.0b1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:47a91ab8d17ed7caed27e5b2eda3b3478f3d28cecb3939d708545804273e159b"}, + {file = "cffi-2.0.0b1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2fd8f55419576289d7cd8c9349ea46a222379936136754ab4c2b041294b0b48d"}, + {file = "cffi-2.0.0b1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:916141ca9ff05e9f67fe73c39a527d96a7101191673dee9985e71cd164b55915"}, + {file = "cffi-2.0.0b1-cp313-cp313-manylinux_2_27_i686.manylinux_2_28_i686.whl", hash = "sha256:91fc109a1412dd29657f442a61bb571baaa1d074628145008ceb54dc9bb13941"}, + {file = "cffi-2.0.0b1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b08dd1a826b678d39aa78f30edc1b7d9bd1e5b7e5adc2d47e8f56ab25ac7c13"}, + {file = "cffi-2.0.0b1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76a19efb88a495bb7377fc542c7f97c9816dfc1d6bb4ad147acb99599a83e248"}, + {file = "cffi-2.0.0b1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:87acb9e2221ed37c385c9cef866377fbaa13180de9ba1cdc4e6dc927b273c87f"}, + {file = "cffi-2.0.0b1-cp313-cp313-win32.whl", hash = "sha256:60c2c1d7adf558b932de9e4633f68e359063d1a748c92a4a3cba832085e9819b"}, + {file = "cffi-2.0.0b1-cp313-cp313-win_amd64.whl", hash = "sha256:6ff1ba153e0740c2ea47d74d015c1a03c3addab1681633be0838103c297b855c"}, + {file = "cffi-2.0.0b1-cp313-cp313-win_arm64.whl", hash = "sha256:adbed7d68bc8837eb2c73e01bc284b5af9898e82b6067a6cbffea4f1820626e4"}, + {file = "cffi-2.0.0b1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fe8cb43962af8e43facad740930fadc4cf8cdc1e073f59d0f13714711807979f"}, + {file = "cffi-2.0.0b1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a812e9ab7a0bfef3e89089c0359e631d8521d5efc8d21c7ede3f1568db689920"}, + {file = "cffi-2.0.0b1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bce5ce4790b8347c2d7937312218d0282af344f8a589db163520a02fe8e42281"}, + {file = "cffi-2.0.0b1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:39eedbed09879f6d1591ad155afcc162aa11ebf3271215339b4aef3df5631573"}, + {file = "cffi-2.0.0b1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7dfd6f8f57e812f3175aa0d4d36ed797b6ff35f7cdfefea05417569b543ddc94"}, + {file = "cffi-2.0.0b1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:782f60714ea2935e5391a0f69ad4705624cdc86243b18dcfafd08565c28e89bd"}, + {file = "cffi-2.0.0b1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f2ebc97ba03b26e9b6b048b6c3981165126905cb20564fbf6584f5e072a1c189"}, + {file = "cffi-2.0.0b1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:fba9546b80f3b275f04915ffbca7b75aa22a353c4f6410469fb1d8c340ec1c31"}, + {file = "cffi-2.0.0b1-cp314-cp314-win32.whl", hash = "sha256:339e853c75f69c726b1a85f2217db6880422f915770679c47150eea895e02b46"}, + {file = "cffi-2.0.0b1-cp314-cp314-win_amd64.whl", hash = "sha256:856eb353a42b04d02b0633c71123276710a5390e92a27fbd2446864ca7d27923"}, + {file = "cffi-2.0.0b1-cp314-cp314-win_arm64.whl", hash = "sha256:9e23ac717e8b3767c80198d483c743fe596b055a6e29ef34f9d8cdf61f941f2f"}, + {file = "cffi-2.0.0b1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e227627762046204df31c589d7406540778d05622e395d41fc68b7895d40c174"}, + {file = "cffi-2.0.0b1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2355cd38f375906da70a8bad548eb63f65bed43c1044ed075691fa36e8e8315a"}, + {file = "cffi-2.0.0b1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:14c0ade7949f088615450abf884064b4ef11e8c9917b99d53f12e06cdfd2cd36"}, + {file = "cffi-2.0.0b1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:765c82d4a73ded03bfea961364f4c57dd6cfe7b0d57b7a2d9b95e2e7bd5de6f7"}, + {file = "cffi-2.0.0b1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:265666e15da6974e6a74110873321e84c7c2288e379aca44a7df4713325b9be4"}, + {file = "cffi-2.0.0b1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d88f849d03c9aa2d7bbd710a0e20266f92bf524396c7fce881cd5a1971447812"}, + {file = "cffi-2.0.0b1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:853e90e942246f9e098f16baa45896f80675f86ab6447823c4030a67c3cc112d"}, + {file = "cffi-2.0.0b1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b8aee0176d80781a21855832c411cfd3126c34966650693ec1245f0b756498b"}, + {file = "cffi-2.0.0b1-cp314-cp314t-win32.whl", hash = "sha256:2da933859e1465a08f36d88e0452194da27b9ff0813e5ba49f02c544682d40e0"}, + {file = "cffi-2.0.0b1-cp314-cp314t-win_amd64.whl", hash = "sha256:53fbcfdb35760bc6fb68096632d29700bcf37fd0d71922dcc577eb6193fc6edc"}, + {file = "cffi-2.0.0b1-cp314-cp314t-win_arm64.whl", hash = "sha256:505bec438236c623d7cfd8cc740598611a1d4883a629a0e33eb9e3c2dcd81b04"}, + {file = "cffi-2.0.0b1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:d2ede96d5de012d74b174082dec44c58a35b42e0ea9f197063ddb5e504ee0c7e"}, + {file = "cffi-2.0.0b1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:14505e4a82aa84abddab6e493946d3ed6bf6d268b58e4c2f5bcf8ec2dee2ca2d"}, + {file = "cffi-2.0.0b1-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:762dd8db1bd710f7b828b3c6cbb7101b5e190e722eb5633eb79b1a6b751e349a"}, + {file = "cffi-2.0.0b1-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8af08fd246d2a544c8b68c25c171809d08eed9372f2026ae48dad17d26525578"}, + {file = "cffi-2.0.0b1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e342223ada6b1d34f3719d3612991924cb68fa7f8fb2ec22f5bda254882828ab"}, + {file = "cffi-2.0.0b1-cp39-cp39-manylinux_2_27_i686.manylinux_2_28_i686.whl", hash = "sha256:352e1949f7af33c37b060d2c2ea8a8fa1be6695ff94f8d5f7738bacacb9d6de4"}, + {file = "cffi-2.0.0b1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cc3245802b4950bc5459a2ef9a650d948972e44df120ecd2c6201814c8edb54"}, + {file = "cffi-2.0.0b1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ab4aea2f93ab6c408f0c6be8ddebe4d1086b4966148f542fe11cf82ca698dc07"}, + {file = "cffi-2.0.0b1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ecf72cb96106fbde29682db37569c7cee3ebf29ecf9ead46978679057c6df234"}, + {file = "cffi-2.0.0b1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:aaec3f41cd6f0ffda5e23365822710d747b8613d3b8f54e12b5d7dcde688300d"}, + {file = "cffi-2.0.0b1-cp39-cp39-win32.whl", hash = "sha256:601ddbaa51b1bd96a92a6a26e855060390023ab600377280a9bed7703ed2a088"}, + {file = "cffi-2.0.0b1-cp39-cp39-win_amd64.whl", hash = "sha256:cb351fade24f7ba9ca481bee53d4257053b9fa9da55da276fe1187a990a49dde"}, + {file = "cffi-2.0.0b1.tar.gz", hash = "sha256:4440de58d19c0bebe6a2f3b721253d67b27aabb34e00ab35756d8699876191ea"}, +] +markers = {main = "python_version >= \"3.14\" and platform_python_implementation == \"PyPy\"", dev = "python_version >= \"3.14\""} + +[package.dependencies] +pycparser = {version = "*", markers = "implementation_name != \"PyPy\""} + [[package]] name = "charset-normalizer" -version = "3.4.1" +version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ - {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win32.whl", hash = "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f"}, - {file = "charset_normalizer-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b"}, - {file = "charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35"}, - {file = "charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407"}, - {file = "charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win32.whl", hash = "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487"}, - {file = "charset_normalizer-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win32.whl", hash = "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e"}, - {file = "charset_normalizer-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win32.whl", hash = "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5"}, - {file = "charset_normalizer-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765"}, - {file = "charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85"}, - {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win32.whl", hash = "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a"}, + {file = "charset_normalizer-3.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a"}, + {file = "charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c"}, + {file = "charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7"}, + {file = "charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win32.whl", hash = "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58"}, + {file = "charset_normalizer-3.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win32.whl", hash = "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7"}, + {file = "charset_normalizer-3.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win32.whl", hash = "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471"}, + {file = "charset_normalizer-3.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e"}, + {file = "charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0"}, + {file = "charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63"}, ] -[[package]] -name = "chroma-hnswlib" -version = "0.7.6" -description = "Chromas fork of hnswlib" -optional = false -python-versions = "*" -files = [ - {file = "chroma_hnswlib-0.7.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f35192fbbeadc8c0633f0a69c3d3e9f1a4eab3a46b65458bbcbcabdd9e895c36"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f007b608c96362b8f0c8b6b2ac94f67f83fcbabd857c378ae82007ec92f4d82"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:456fd88fa0d14e6b385358515aef69fc89b3c2191706fd9aee62087b62aad09c"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dfaae825499c2beaa3b75a12d7ec713b64226df72a5c4097203e3ed532680da"}, - {file = "chroma_hnswlib-0.7.6-cp310-cp310-win_amd64.whl", hash = "sha256:2487201982241fb1581be26524145092c95902cb09fc2646ccfbc407de3328ec"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81181d54a2b1e4727369486a631f977ffc53c5533d26e3d366dda243fb0998ca"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4b4ab4e11f1083dd0a11ee4f0e0b183ca9f0f2ed63ededba1935b13ce2b3606f"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53db45cd9173d95b4b0bdccb4dbff4c54a42b51420599c32267f3abbeb795170"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c093f07a010b499c00a15bc9376036ee4800d335360570b14f7fe92badcdcf9"}, - {file = "chroma_hnswlib-0.7.6-cp311-cp311-win_amd64.whl", hash = "sha256:0540b0ac96e47d0aa39e88ea4714358ae05d64bbe6bf33c52f316c664190a6a3"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e87e9b616c281bfbe748d01705817c71211613c3b063021f7ed5e47173556cb7"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec5ca25bc7b66d2ecbf14502b5729cde25f70945d22f2aaf523c2d747ea68912"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:305ae491de9d5f3c51e8bd52d84fdf2545a4a2bc7af49765cda286b7bb30b1d4"}, - {file = "chroma_hnswlib-0.7.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:822ede968d25a2c88823ca078a58f92c9b5c4142e38c7c8b4c48178894a0a3c5"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2fe6ea949047beed19a94b33f41fe882a691e58b70c55fdaa90274ae78be046f"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feceff971e2a2728c9ddd862a9dd6eb9f638377ad98438876c9aeac96c9482f5"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb0633b60e00a2b92314d0bf5bbc0da3d3320be72c7e3f4a9b19f4609dc2b2ab"}, - {file = "chroma_hnswlib-0.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:a566abe32fab42291f766d667bdbfa234a7f457dcbd2ba19948b7a978c8ca624"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6be47853d9a58dedcfa90fc846af202b071f028bbafe1d8711bf64fe5a7f6111"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3a7af35bdd39a88bffa49f9bb4bf4f9040b684514a024435a1ef5cdff980579d"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a53b1f1551f2b5ad94eb610207bde1bb476245fc5097a2bec2b476c653c58bde"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3085402958dbdc9ff5626ae58d696948e715aef88c86d1e3f9285a88f1afd3bc"}, - {file = "chroma_hnswlib-0.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:77326f658a15adfb806a16543f7db7c45f06fd787d699e643642d6bde8ed49c4"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:93b056ab4e25adab861dfef21e1d2a2756b18be5bc9c292aa252fa12bb44e6ae"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fe91f018b30452c16c811fd6c8ede01f84e5a9f3c23e0758775e57f1c3778871"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6c0e627476f0f4d9e153420d36042dd9c6c3671cfd1fe511c0253e38c2a1039"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e9796a4536b7de6c6d76a792ba03e08f5aaa53e97e052709568e50b4d20c04f"}, - {file = "chroma_hnswlib-0.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:d30e2db08e7ffdcc415bd072883a322de5995eb6ec28a8f8c054103bbd3ec1e0"}, - {file = "chroma_hnswlib-0.7.6.tar.gz", hash = "sha256:4dce282543039681160259d29fcde6151cc9106c6461e0485f57cdccd83059b7"}, -] - -[package.dependencies] -numpy = "*" - [[package]] name = "chromadb" -version = "0.6.3" +version = "1.0.15" description = "Chroma." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "chromadb-0.6.3-py3-none-any.whl", hash = "sha256:4851258489a3612b558488d98d09ae0fe0a28d5cad6bd1ba64b96fdc419dc0e5"}, - {file = "chromadb-0.6.3.tar.gz", hash = "sha256:c8f34c0b704b9108b04491480a36d42e894a960429f87c6516027b5481d59ed3"}, + {file = "chromadb-1.0.15-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:51791553014297798b53df4e043e9c30f4e8bd157647971a6bb02b04bfa65f82"}, + {file = "chromadb-1.0.15-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:48015803c0631c3a817befc276436dc084bb628c37fd4214047212afb2056291"}, + {file = "chromadb-1.0.15-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b73cd6fb32fcdd91c577cca16ea6112b691d72b441bb3f2140426d1e79e453a"}, + {file = "chromadb-1.0.15-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479f1b401af9e7c20f50642ffb3376abbfd78e2b5b170429f7c79eff52e367db"}, + {file = "chromadb-1.0.15-cp39-abi3-win_amd64.whl", hash = "sha256:e0cb3b93fdc42b1786f151d413ef36299f30f783a30ce08bf0bfb12e552b4190"}, + {file = "chromadb-1.0.15.tar.gz", hash = "sha256:3e910da3f5414e2204f89c7beca1650847f2bf3bd71f11a2e40aad1eb31050aa"}, ] [package.dependencies] bcrypt = ">=4.0.1" build = ">=1.0.3" -chroma-hnswlib = "0.7.6" -fastapi = ">=0.95.2" grpcio = ">=1.58.0" httpx = ">=0.27.0" importlib-resources = "*" +jsonschema = ">=4.19.0" kubernetes = ">=28.1.0" mmh3 = ">=4.0.1" numpy = ">=1.22.5" onnxruntime = ">=1.14.1" opentelemetry-api = ">=1.2.0" opentelemetry-exporter-otlp-proto-grpc = ">=1.2.0" -opentelemetry-instrumentation-fastapi = ">=0.41b0" opentelemetry-sdk = ">=1.2.0" orjson = ">=3.9.12" overrides = ">=7.3.1" -posthog = ">=2.4.0" +posthog = ">=2.4.0,<6.0.0" +pybase64 = ">=1.4.1" pydantic = ">=1.9" pypika = ">=0.48.9" -PyYAML = ">=6.0.0" +pyyaml = ">=6.0.0" rich = ">=10.11.0" tenacity = ">=8.2.3" tokenizers = ">=0.13.2" tqdm = ">=4.65.0" typer = ">=0.9.0" -typing_extensions = ">=4.5.0" +typing-extensions = ">=4.5.0" uvicorn = {version = ">=0.18.3", extras = ["standard"]} +[package.extras] +dev = ["chroma-hnswlib (==0.7.6)", "fastapi (>=0.115.9)", "opentelemetry-instrumentation-fastapi (>=0.41b0)"] + [[package]] name = "click" -version = "8.1.8" +version = "8.2.1" description = "Composable command line interface toolkit" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" +groups = ["main", "dev"] files = [ - {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, - {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, + {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"}, + {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"}, ] [package.dependencies] @@ -868,10 +946,12 @@ version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +groups = ["main", "dev", "test"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\" or os_name == \"nt\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\"", test = "sys_platform == \"win32\""} [[package]] name = "coloredlogs" @@ -879,6 +959,7 @@ version = "15.0.1" description = "Colored terminal output for Python's logging module" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, @@ -892,91 +973,115 @@ cron = ["capturer (>=2.4)"] [[package]] name = "comm" -version = "0.2.2" +version = "0.2.3" description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, - {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, + {file = "comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417"}, + {file = "comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971"}, ] -[package.dependencies] -traitlets = ">=4" - [package.extras] test = ["pytest"] [[package]] name = "coverage" -version = "7.8.0" +version = "7.10.2" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.9" +groups = ["test"] files = [ - {file = "coverage-7.8.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2931f66991175369859b5fd58529cd4b73582461877ecfd859b6549869287ffe"}, - {file = "coverage-7.8.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52a523153c568d2c0ef8826f6cc23031dc86cffb8c6aeab92c4ff776e7951b28"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c8a5c139aae4c35cbd7cadca1df02ea8cf28a911534fc1b0456acb0b14234f3"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a26c0c795c3e0b63ec7da6efded5f0bc856d7c0b24b2ac84b4d1d7bc578d676"}, - {file = "coverage-7.8.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:821f7bcbaa84318287115d54becb1915eece6918136c6f91045bb84e2f88739d"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a321c61477ff8ee705b8a5fed370b5710c56b3a52d17b983d9215861e37b642a"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ed2144b8a78f9d94d9515963ed273d620e07846acd5d4b0a642d4849e8d91a0c"}, - {file = "coverage-7.8.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:042e7841a26498fff7a37d6fda770d17519982f5b7d8bf5278d140b67b61095f"}, - {file = "coverage-7.8.0-cp310-cp310-win32.whl", hash = "sha256:f9983d01d7705b2d1f7a95e10bbe4091fabc03a46881a256c2787637b087003f"}, - {file = "coverage-7.8.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a570cd9bd20b85d1a0d7b009aaf6c110b52b5755c17be6962f8ccd65d1dbd23"}, - {file = "coverage-7.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7ac22a0bb2c7c49f441f7a6d46c9c80d96e56f5a8bc6972529ed43c8b694e27"}, - {file = "coverage-7.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf13d564d310c156d1c8e53877baf2993fb3073b2fc9f69790ca6a732eb4bfea"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5761c70c017c1b0d21b0815a920ffb94a670c8d5d409d9b38857874c21f70d7"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5ff52d790c7e1628241ffbcaeb33e07d14b007b6eb00a19320c7b8a7024c040"}, - {file = "coverage-7.8.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d39fc4817fd67b3915256af5dda75fd4ee10621a3d484524487e33416c6f3543"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b44674870709017e4b4036e3d0d6c17f06a0e6d4436422e0ad29b882c40697d2"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f99eb72bf27cbb167b636eb1726f590c00e1ad375002230607a844d9e9a2318"}, - {file = "coverage-7.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b571bf5341ba8c6bc02e0baeaf3b061ab993bf372d982ae509807e7f112554e9"}, - {file = "coverage-7.8.0-cp311-cp311-win32.whl", hash = "sha256:e75a2ad7b647fd8046d58c3132d7eaf31b12d8a53c0e4b21fa9c4d23d6ee6d3c"}, - {file = "coverage-7.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:3043ba1c88b2139126fc72cb48574b90e2e0546d4c78b5299317f61b7f718b78"}, - {file = "coverage-7.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbb5cc845a0292e0c520656d19d7ce40e18d0e19b22cb3e0409135a575bf79fc"}, - {file = "coverage-7.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4dfd9a93db9e78666d178d4f08a5408aa3f2474ad4d0e0378ed5f2ef71640cb6"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f017a61399f13aa6d1039f75cd467be388d157cd81f1a119b9d9a68ba6f2830d"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0915742f4c82208ebf47a2b154a5334155ed9ef9fe6190674b8a46c2fb89cb05"}, - {file = "coverage-7.8.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a40fcf208e021eb14b0fac6bdb045c0e0cab53105f93ba0d03fd934c956143a"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a1f406a8e0995d654b2ad87c62caf6befa767885301f3b8f6f73e6f3c31ec3a6"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:77af0f6447a582fdc7de5e06fa3757a3ef87769fbb0fdbdeba78c23049140a47"}, - {file = "coverage-7.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f2d32f95922927186c6dbc8bc60df0d186b6edb828d299ab10898ef3f40052fe"}, - {file = "coverage-7.8.0-cp312-cp312-win32.whl", hash = "sha256:769773614e676f9d8e8a0980dd7740f09a6ea386d0f383db6821df07d0f08545"}, - {file = "coverage-7.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:e5d2b9be5b0693cf21eb4ce0ec8d211efb43966f6657807f6859aab3814f946b"}, - {file = "coverage-7.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ac46d0c2dd5820ce93943a501ac5f6548ea81594777ca585bf002aa8854cacd"}, - {file = "coverage-7.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:771eb7587a0563ca5bb6f622b9ed7f9d07bd08900f7589b4febff05f469bea00"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42421e04069fb2cbcbca5a696c4050b84a43b05392679d4068acbe65449b5c64"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:554fec1199d93ab30adaa751db68acec2b41c5602ac944bb19187cb9a41a8067"}, - {file = "coverage-7.8.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5aaeb00761f985007b38cf463b1d160a14a22c34eb3f6a39d9ad6fc27cb73008"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:581a40c7b94921fffd6457ffe532259813fc68eb2bdda60fa8cc343414ce3733"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f319bae0321bc838e205bf9e5bc28f0a3165f30c203b610f17ab5552cff90323"}, - {file = "coverage-7.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04bfec25a8ef1c5f41f5e7e5c842f6b615599ca8ba8391ec33a9290d9d2db3a3"}, - {file = "coverage-7.8.0-cp313-cp313-win32.whl", hash = "sha256:dd19608788b50eed889e13a5d71d832edc34fc9dfce606f66e8f9f917eef910d"}, - {file = "coverage-7.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:a9abbccd778d98e9c7e85038e35e91e67f5b520776781d9a1e2ee9d400869487"}, - {file = "coverage-7.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:18c5ae6d061ad5b3e7eef4363fb27a0576012a7447af48be6c75b88494c6cf25"}, - {file = "coverage-7.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:95aa6ae391a22bbbce1b77ddac846c98c5473de0372ba5c463480043a07bff42"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e013b07ba1c748dacc2a80e69a46286ff145935f260eb8c72df7185bf048f502"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d766a4f0e5aa1ba056ec3496243150698dc0481902e2b8559314368717be82b1"}, - {file = "coverage-7.8.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad80e6b4a0c3cb6f10f29ae4c60e991f424e6b14219d46f1e7d442b938ee68a4"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b87eb6fc9e1bb8f98892a2458781348fa37e6925f35bb6ceb9d4afd54ba36c73"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d1ba00ae33be84066cfbe7361d4e04dec78445b2b88bdb734d0d1cbab916025a"}, - {file = "coverage-7.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f3c38e4e5ccbdc9198aecc766cedbb134b2d89bf64533973678dfcf07effd883"}, - {file = "coverage-7.8.0-cp313-cp313t-win32.whl", hash = "sha256:379fe315e206b14e21db5240f89dc0774bdd3e25c3c58c2c733c99eca96f1ada"}, - {file = "coverage-7.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2e4b6b87bb0c846a9315e3ab4be2d52fac905100565f4b92f02c445c8799e257"}, - {file = "coverage-7.8.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa260de59dfb143af06dcf30c2be0b200bed2a73737a8a59248fcb9fa601ef0f"}, - {file = "coverage-7.8.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96121edfa4c2dfdda409877ea8608dd01de816a4dc4a0523356067b305e4e17a"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b8af63b9afa1031c0ef05b217faa598f3069148eeee6bb24b79da9012423b82"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89b1f4af0d4afe495cd4787a68e00f30f1d15939f550e869de90a86efa7e0814"}, - {file = "coverage-7.8.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94ec0be97723ae72d63d3aa41961a0b9a6f5a53ff599813c324548d18e3b9e8c"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8a1d96e780bdb2d0cbb297325711701f7c0b6f89199a57f2049e90064c29f6bd"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:f1d8a2a57b47142b10374902777e798784abf400a004b14f1b0b9eaf1e528ba4"}, - {file = "coverage-7.8.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cf60dd2696b457b710dd40bf17ad269d5f5457b96442f7f85722bdb16fa6c899"}, - {file = "coverage-7.8.0-cp39-cp39-win32.whl", hash = "sha256:be945402e03de47ba1872cd5236395e0f4ad635526185a930735f66710e1bd3f"}, - {file = "coverage-7.8.0-cp39-cp39-win_amd64.whl", hash = "sha256:90e7fbc6216ecaffa5a880cdc9c77b7418c1dcb166166b78dbc630d07f278cc3"}, - {file = "coverage-7.8.0-pp39.pp310.pp311-none-any.whl", hash = "sha256:b8194fb8e50d556d5849753de991d390c5a1edeeba50f68e3a9253fbd8bf8ccd"}, - {file = "coverage-7.8.0-py3-none-any.whl", hash = "sha256:dbf364b4c5e7bae9250528167dfe40219b62e2d573c854d74be213e1e52069f7"}, - {file = "coverage-7.8.0.tar.gz", hash = "sha256:7a3d62b3b03b4b6fd41a085f3574874cf946cb4604d2b4d3e8dca8cd570ca501"}, + {file = "coverage-7.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:79f0283ab5e6499fd5fe382ca3d62afa40fb50ff227676a3125d18af70eabf65"}, + {file = "coverage-7.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4545e906f595ee8ab8e03e21be20d899bfc06647925bc5b224ad7e8c40e08b8"}, + {file = "coverage-7.10.2-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ae385e1d58fbc6a9b1c315e5510ac52281e271478b45f92ca9b5ad42cf39643f"}, + {file = "coverage-7.10.2-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6f0cbe5f7dd19f3a32bac2251b95d51c3b89621ac88a2648096ce40f9a5aa1e7"}, + {file = "coverage-7.10.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd17f427f041f6b116dc90b4049c6f3e1230524407d00daa2d8c7915037b5947"}, + {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7f10ca4cde7b466405cce0a0e9971a13eb22e57a5ecc8b5f93a81090cc9c7eb9"}, + {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3b990df23dd51dccce26d18fb09fd85a77ebe46368f387b0ffba7a74e470b31b"}, + {file = "coverage-7.10.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc3902584d25c7eef57fb38f440aa849a26a3a9f761a029a72b69acfca4e31f8"}, + {file = "coverage-7.10.2-cp310-cp310-win32.whl", hash = "sha256:9dd37e9ac00d5eb72f38ed93e3cdf2280b1dbda3bb9b48c6941805f265ad8d87"}, + {file = "coverage-7.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:99d16f15cb5baf0729354c5bd3080ae53847a4072b9ba1e10957522fb290417f"}, + {file = "coverage-7.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c3b210d79925a476dfc8d74c7d53224888421edebf3a611f3adae923e212b27"}, + {file = "coverage-7.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf67d1787cd317c3f8b2e4c6ed1ae93497be7e30605a0d32237ac37a37a8a322"}, + {file = "coverage-7.10.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:069b779d03d458602bc0e27189876e7d8bdf6b24ac0f12900de22dd2154e6ad7"}, + {file = "coverage-7.10.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4c2de4cb80b9990e71c62c2d3e9f3ec71b804b1f9ca4784ec7e74127e0f42468"}, + {file = "coverage-7.10.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:75bf7ab2374a7eb107602f1e07310cda164016cd60968abf817b7a0b5703e288"}, + {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3f37516458ec1550815134937f73d6d15b434059cd10f64678a2068f65c62406"}, + {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:de3c6271c482c250d3303fb5c6bdb8ca025fff20a67245e1425df04dc990ece9"}, + {file = "coverage-7.10.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:98a838101321ac3089c9bb1d4bfa967e8afed58021fda72d7880dc1997f20ae1"}, + {file = "coverage-7.10.2-cp311-cp311-win32.whl", hash = "sha256:f2a79145a531a0e42df32d37be5af069b4a914845b6f686590739b786f2f7bce"}, + {file = "coverage-7.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:e4f5f1320f8ee0d7cfa421ceb257bef9d39fd614dd3ddcfcacd284d4824ed2c2"}, + {file = "coverage-7.10.2-cp311-cp311-win_arm64.whl", hash = "sha256:d8f2d83118f25328552c728b8e91babf93217db259ca5c2cd4dd4220b8926293"}, + {file = "coverage-7.10.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:890ad3a26da9ec7bf69255b9371800e2a8da9bc223ae5d86daeb940b42247c83"}, + {file = "coverage-7.10.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38fd1ccfca7838c031d7a7874d4353e2f1b98eb5d2a80a2fe5732d542ae25e9c"}, + {file = "coverage-7.10.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:76c1ffaaf4f6f0f6e8e9ca06f24bb6454a7a5d4ced97a1bc466f0d6baf4bd518"}, + {file = "coverage-7.10.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:86da8a3a84b79ead5c7d0e960c34f580bc3b231bb546627773a3f53c532c2f21"}, + {file = "coverage-7.10.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99cef9731c8a39801830a604cc53c93c9e57ea8b44953d26589499eded9576e0"}, + {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ea58b112f2966a8b91eb13f5d3b1f8bb43c180d624cd3283fb33b1cedcc2dd75"}, + {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:20f405188d28da9522b7232e51154e1b884fc18d0b3a10f382d54784715bbe01"}, + {file = "coverage-7.10.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:64586ce42bbe0da4d9f76f97235c545d1abb9b25985a8791857690f96e23dc3b"}, + {file = "coverage-7.10.2-cp312-cp312-win32.whl", hash = "sha256:bc2e69b795d97ee6d126e7e22e78a509438b46be6ff44f4dccbb5230f550d340"}, + {file = "coverage-7.10.2-cp312-cp312-win_amd64.whl", hash = "sha256:adda2268b8cf0d11f160fad3743b4dfe9813cd6ecf02c1d6397eceaa5b45b388"}, + {file = "coverage-7.10.2-cp312-cp312-win_arm64.whl", hash = "sha256:164429decd0d6b39a0582eaa30c67bf482612c0330572343042d0ed9e7f15c20"}, + {file = "coverage-7.10.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:aca7b5645afa688de6d4f8e89d30c577f62956fefb1bad021490d63173874186"}, + {file = "coverage-7.10.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:96e5921342574a14303dfdb73de0019e1ac041c863743c8fe1aa6c2b4a257226"}, + {file = "coverage-7.10.2-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11333094c1bff621aa811b67ed794865cbcaa99984dedea4bd9cf780ad64ecba"}, + {file = "coverage-7.10.2-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6eb586fa7d2aee8d65d5ae1dd71414020b2f447435c57ee8de8abea0a77d5074"}, + {file = "coverage-7.10.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d358f259d8019d4ef25d8c5b78aca4c7af25e28bd4231312911c22a0e824a57"}, + {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5250bda76e30382e0a2dcd68d961afcab92c3a7613606e6269855c6979a1b0bb"}, + {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a91e027d66eff214d88d9afbe528e21c9ef1ecdf4956c46e366c50f3094696d0"}, + {file = "coverage-7.10.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:228946da741558904e2c03ce870ba5efd9cd6e48cbc004d9a27abee08100a15a"}, + {file = "coverage-7.10.2-cp313-cp313-win32.whl", hash = "sha256:95e23987b52d02e7c413bf2d6dc6288bd5721beb518052109a13bfdc62c8033b"}, + {file = "coverage-7.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:f35481d42c6d146d48ec92d4e239c23f97b53a3f1fbd2302e7c64336f28641fe"}, + {file = "coverage-7.10.2-cp313-cp313-win_arm64.whl", hash = "sha256:65b451949cb789c346f9f9002441fc934d8ccedcc9ec09daabc2139ad13853f7"}, + {file = "coverage-7.10.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8415918856a3e7d57a4e0ad94651b761317de459eb74d34cc1bb51aad80f07e"}, + {file = "coverage-7.10.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f287a25a8ca53901c613498e4a40885b19361a2fe8fbfdbb7f8ef2cad2a23f03"}, + {file = "coverage-7.10.2-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:75cc1a3f8c88c69bf16a871dab1fe5a7303fdb1e9f285f204b60f1ee539b8fc0"}, + {file = "coverage-7.10.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ca07fa78cc9d26bc8c4740de1abd3489cf9c47cc06d9a8ab3d552ff5101af4c0"}, + {file = "coverage-7.10.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c2e117e64c26300032755d4520cd769f2623cde1a1d1c3515b05a3b8add0ade1"}, + {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:daaf98009977f577b71f8800208f4d40d4dcf5c2db53d4d822787cdc198d76e1"}, + {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ea8d8fe546c528535c761ba424410bbeb36ba8a0f24be653e94b70c93fd8a8ca"}, + {file = "coverage-7.10.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fe024d40ac31eb8d5aae70215b41dafa264676caa4404ae155f77d2fa95c37bb"}, + {file = "coverage-7.10.2-cp313-cp313t-win32.whl", hash = "sha256:8f34b09f68bdadec122ffad312154eda965ade433559cc1eadd96cca3de5c824"}, + {file = "coverage-7.10.2-cp313-cp313t-win_amd64.whl", hash = "sha256:71d40b3ac0f26fa9ffa6ee16219a714fed5c6ec197cdcd2018904ab5e75bcfa3"}, + {file = "coverage-7.10.2-cp313-cp313t-win_arm64.whl", hash = "sha256:abb57fdd38bf6f7dcc66b38dafb7af7c5fdc31ac6029ce373a6f7f5331d6f60f"}, + {file = "coverage-7.10.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a3e853cc04987c85ec410905667eed4bf08b1d84d80dfab2684bb250ac8da4f6"}, + {file = "coverage-7.10.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0100b19f230df72c90fdb36db59d3f39232391e8d89616a7de30f677da4f532b"}, + {file = "coverage-7.10.2-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9c1cd71483ea78331bdfadb8dcec4f4edfb73c7002c1206d8e0af6797853f5be"}, + {file = "coverage-7.10.2-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9f75dbf4899e29a37d74f48342f29279391668ef625fdac6d2f67363518056a1"}, + {file = "coverage-7.10.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7df481e7508de1c38b9b8043da48d94931aefa3e32b47dd20277e4978ed5b95"}, + {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:835f39e618099325e7612b3406f57af30ab0a0af350490eff6421e2e5f608e46"}, + {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:12e52b5aa00aa720097d6947d2eb9e404e7c1101ad775f9661ba165ed0a28303"}, + {file = "coverage-7.10.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:718044729bf1fe3e9eb9f31b52e44ddae07e434ec050c8c628bf5adc56fe4bdd"}, + {file = "coverage-7.10.2-cp314-cp314-win32.whl", hash = "sha256:f256173b48cc68486299d510a3e729a96e62c889703807482dbf56946befb5c8"}, + {file = "coverage-7.10.2-cp314-cp314-win_amd64.whl", hash = "sha256:2e980e4179f33d9b65ac4acb86c9c0dde904098853f27f289766657ed16e07b3"}, + {file = "coverage-7.10.2-cp314-cp314-win_arm64.whl", hash = "sha256:14fb5b6641ab5b3c4161572579f0f2ea8834f9d3af2f7dd8fbaecd58ef9175cc"}, + {file = "coverage-7.10.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:e96649ac34a3d0e6491e82a2af71098e43be2874b619547c3282fc11d3840a4b"}, + {file = "coverage-7.10.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1a2e934e9da26341d342d30bfe91422bbfdb3f1f069ec87f19b2909d10d8dcc4"}, + {file = "coverage-7.10.2-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:651015dcd5fd9b5a51ca79ece60d353cacc5beaf304db750407b29c89f72fe2b"}, + {file = "coverage-7.10.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:81bf6a32212f9f66da03d63ecb9cd9bd48e662050a937db7199dbf47d19831de"}, + {file = "coverage-7.10.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d800705f6951f75a905ea6feb03fff8f3ea3468b81e7563373ddc29aa3e5d1ca"}, + {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:248b5394718e10d067354448dc406d651709c6765669679311170da18e0e9af8"}, + {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5c61675a922b569137cf943770d7ad3edd0202d992ce53ac328c5ff68213ccf4"}, + {file = "coverage-7.10.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:52d708b5fd65589461381fa442d9905f5903d76c086c6a4108e8e9efdca7a7ed"}, + {file = "coverage-7.10.2-cp314-cp314t-win32.whl", hash = "sha256:916369b3b914186b2c5e5ad2f7264b02cff5df96cdd7cdad65dccd39aa5fd9f0"}, + {file = "coverage-7.10.2-cp314-cp314t-win_amd64.whl", hash = "sha256:5b9d538e8e04916a5df63052d698b30c74eb0174f2ca9cd942c981f274a18eaf"}, + {file = "coverage-7.10.2-cp314-cp314t-win_arm64.whl", hash = "sha256:04c74f9ef1f925456a9fd23a7eef1103126186d0500ef9a0acb0bd2514bdc7cc"}, + {file = "coverage-7.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:765b13b164685a2f8b2abef867ad07aebedc0e090c757958a186f64e39d63dbd"}, + {file = "coverage-7.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a219b70100500d0c7fd3ebb824a3302efb6b1a122baa9d4eb3f43df8f0b3d899"}, + {file = "coverage-7.10.2-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e33e79a219105aa315439ee051bd50b6caa705dc4164a5aba6932c8ac3ce2d98"}, + {file = "coverage-7.10.2-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc3945b7bad33957a9eca16e9e5eae4b17cb03173ef594fdaad228f4fc7da53b"}, + {file = "coverage-7.10.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bdff88e858ee608a924acfad32a180d2bf6e13e059d6a7174abbae075f30436"}, + {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:44329cbed24966c0b49acb386352c9722219af1f0c80db7f218af7793d251902"}, + {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:be127f292496d0fbe20d8025f73221b36117b3587f890346e80a13b310712982"}, + {file = "coverage-7.10.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6c031da749a05f7a01447dd7f47beedb498edd293e31e1878c0d52db18787df0"}, + {file = "coverage-7.10.2-cp39-cp39-win32.whl", hash = "sha256:22aca3e691c7709c5999ccf48b7a8ff5cf5a8bd6fe9b36efbd4993f5a36b2fcf"}, + {file = "coverage-7.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c7195444b932356055a8e287fa910bf9753a84a1bc33aeb3770e8fca521e032e"}, + {file = "coverage-7.10.2-py3-none-any.whl", hash = "sha256:95db3750dd2e6e93d99fa2498f3a1580581e49c494bddccc6f85c5c21604921f"}, + {file = "coverage-7.10.2.tar.gz", hash = "sha256:5d6e6d84e6dd31a8ded64759626627247d676a23c1b892e1326f7c55c8d61055"}, ] [package.extras] @@ -988,6 +1093,7 @@ version = "0.6.7" description = "Easily serialize dataclasses to and from JSON." optional = false python-versions = "<4.0,>=3.7" +groups = ["main"] files = [ {file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"}, {file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"}, @@ -999,37 +1105,38 @@ typing-inspect = ">=0.4.0,<1" [[package]] name = "debugpy" -version = "1.8.13" +version = "1.8.15" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "debugpy-1.8.13-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:06859f68e817966723ffe046b896b1bd75c665996a77313370336ee9e1de3e90"}, - {file = "debugpy-1.8.13-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c2db69fb8df3168bc857d7b7d2494fed295dfdbde9a45f27b4b152f37520"}, - {file = "debugpy-1.8.13-cp310-cp310-win32.whl", hash = "sha256:46abe0b821cad751fc1fb9f860fb2e68d75e2c5d360986d0136cd1db8cad4428"}, - {file = "debugpy-1.8.13-cp310-cp310-win_amd64.whl", hash = "sha256:dc7b77f5d32674686a5f06955e4b18c0e41fb5a605f5b33cf225790f114cfeec"}, - {file = "debugpy-1.8.13-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:eee02b2ed52a563126c97bf04194af48f2fe1f68bb522a312b05935798e922ff"}, - {file = "debugpy-1.8.13-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4caca674206e97c85c034c1efab4483f33971d4e02e73081265ecb612af65377"}, - {file = "debugpy-1.8.13-cp311-cp311-win32.whl", hash = "sha256:7d9a05efc6973b5aaf076d779cf3a6bbb1199e059a17738a2aa9d27a53bcc888"}, - {file = "debugpy-1.8.13-cp311-cp311-win_amd64.whl", hash = "sha256:62f9b4a861c256f37e163ada8cf5a81f4c8d5148fc17ee31fb46813bd658cdcc"}, - {file = "debugpy-1.8.13-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:2b8de94c5c78aa0d0ed79023eb27c7c56a64c68217d881bee2ffbcb13951d0c1"}, - {file = "debugpy-1.8.13-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887d54276cefbe7290a754424b077e41efa405a3e07122d8897de54709dbe522"}, - {file = "debugpy-1.8.13-cp312-cp312-win32.whl", hash = "sha256:3872ce5453b17837ef47fb9f3edc25085ff998ce63543f45ba7af41e7f7d370f"}, - {file = "debugpy-1.8.13-cp312-cp312-win_amd64.whl", hash = "sha256:63ca7670563c320503fea26ac688988d9d6b9c6a12abc8a8cf2e7dd8e5f6b6ea"}, - {file = "debugpy-1.8.13-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:31abc9618be4edad0b3e3a85277bc9ab51a2d9f708ead0d99ffb5bb750e18503"}, - {file = "debugpy-1.8.13-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0bd87557f97bced5513a74088af0b84982b6ccb2e254b9312e29e8a5c4270eb"}, - {file = "debugpy-1.8.13-cp313-cp313-win32.whl", hash = "sha256:5268ae7fdca75f526d04465931cb0bd24577477ff50e8bb03dab90983f4ebd02"}, - {file = "debugpy-1.8.13-cp313-cp313-win_amd64.whl", hash = "sha256:79ce4ed40966c4c1631d0131606b055a5a2f8e430e3f7bf8fd3744b09943e8e8"}, - {file = "debugpy-1.8.13-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:acf39a6e98630959763f9669feddee540745dfc45ad28dbc9bd1f9cd60639391"}, - {file = "debugpy-1.8.13-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:924464d87e7d905eb0d79fb70846558910e906d9ee309b60c4fe597a2e802590"}, - {file = "debugpy-1.8.13-cp38-cp38-win32.whl", hash = "sha256:3dae443739c6b604802da9f3e09b0f45ddf1cf23c99161f3a1a8039f61a8bb89"}, - {file = "debugpy-1.8.13-cp38-cp38-win_amd64.whl", hash = "sha256:ed93c3155fc1f888ab2b43626182174e457fc31b7781cd1845629303790b8ad1"}, - {file = "debugpy-1.8.13-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:6fab771639332bd8ceb769aacf454a30d14d7a964f2012bf9c4e04c60f16e85b"}, - {file = "debugpy-1.8.13-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32b6857f8263a969ce2ca098f228e5cc0604d277447ec05911a8c46cf3e7e307"}, - {file = "debugpy-1.8.13-cp39-cp39-win32.whl", hash = "sha256:f14d2c4efa1809da125ca62df41050d9c7cd9cb9e380a2685d1e453c4d450ccb"}, - {file = "debugpy-1.8.13-cp39-cp39-win_amd64.whl", hash = "sha256:ea869fe405880327497e6945c09365922c79d2a1eed4c3ae04d77ac7ae34b2b5"}, - {file = "debugpy-1.8.13-py2.py3-none-any.whl", hash = "sha256:d4ba115cdd0e3a70942bd562adba9ec8c651fe69ddde2298a1be296fc331906f"}, - {file = "debugpy-1.8.13.tar.gz", hash = "sha256:837e7bef95bdefba426ae38b9a94821ebdc5bea55627879cd48165c90b9e50ce"}, + {file = "debugpy-1.8.15-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:e9a8125c85172e3ec30985012e7a81ea5e70bbb836637f8a4104f454f9b06c97"}, + {file = "debugpy-1.8.15-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fd0b6b5eccaa745c214fd240ea82f46049d99ef74b185a3517dad3ea1ec55d9"}, + {file = "debugpy-1.8.15-cp310-cp310-win32.whl", hash = "sha256:8181cce4d344010f6bfe94a531c351a46a96b0f7987750932b2908e7a1e14a55"}, + {file = "debugpy-1.8.15-cp310-cp310-win_amd64.whl", hash = "sha256:af2dcae4e4cd6e8b35f982ccab29fe65f7e8766e10720a717bc80c464584ee21"}, + {file = "debugpy-1.8.15-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:babc4fb1962dd6a37e94d611280e3d0d11a1f5e6c72ac9b3d87a08212c4b6dd3"}, + {file = "debugpy-1.8.15-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f778e68f2986a58479d0ac4f643e0b8c82fdd97c2e200d4d61e7c2d13838eb53"}, + {file = "debugpy-1.8.15-cp311-cp311-win32.whl", hash = "sha256:f9d1b5abd75cd965e2deabb1a06b0e93a1546f31f9f621d2705e78104377c702"}, + {file = "debugpy-1.8.15-cp311-cp311-win_amd64.whl", hash = "sha256:62954fb904bec463e2b5a415777f6d1926c97febb08ef1694da0e5d1463c5c3b"}, + {file = "debugpy-1.8.15-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:3dcc7225cb317469721ab5136cda9ff9c8b6e6fb43e87c9e15d5b108b99d01ba"}, + {file = "debugpy-1.8.15-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:047a493ca93c85ccede1dbbaf4e66816794bdc214213dde41a9a61e42d27f8fc"}, + {file = "debugpy-1.8.15-cp312-cp312-win32.whl", hash = "sha256:b08e9b0bc260cf324c890626961dad4ffd973f7568fbf57feb3c3a65ab6b6327"}, + {file = "debugpy-1.8.15-cp312-cp312-win_amd64.whl", hash = "sha256:e2a4fe357c92334272eb2845fcfcdbec3ef9f22c16cf613c388ac0887aed15fa"}, + {file = "debugpy-1.8.15-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:f5e01291ad7d6649aed5773256c5bba7a1a556196300232de1474c3c372592bf"}, + {file = "debugpy-1.8.15-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94dc0f0d00e528d915e0ce1c78e771475b2335b376c49afcc7382ee0b146bab6"}, + {file = "debugpy-1.8.15-cp313-cp313-win32.whl", hash = "sha256:fcf0748d4f6e25f89dc5e013d1129ca6f26ad4da405e0723a4f704583896a709"}, + {file = "debugpy-1.8.15-cp313-cp313-win_amd64.whl", hash = "sha256:73c943776cb83e36baf95e8f7f8da765896fd94b05991e7bc162456d25500683"}, + {file = "debugpy-1.8.15-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:054cd4935bd2e4964dfe1aeee4d6bca89d0c833366776fc35387f8a2f517dd00"}, + {file = "debugpy-1.8.15-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21c4288e662997df3176c4b9d93ee1393913fbaf320732be332d538000c53208"}, + {file = "debugpy-1.8.15-cp38-cp38-win32.whl", hash = "sha256:aaa8ce6a37d764f93fe583d7c6ca58eb7550b36941387483db113125f122bb0d"}, + {file = "debugpy-1.8.15-cp38-cp38-win_amd64.whl", hash = "sha256:71cdf7f676af78e70f005c7fad2ef9da0edc2a24befbf3ab146a51f0d58048c2"}, + {file = "debugpy-1.8.15-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:085b6d0adb3eb457c2823ac497a0690b10a99eff8b01c01a041e84579f114b56"}, + {file = "debugpy-1.8.15-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd546a405381d17527814852642df0a74b7da8acc20ae5f3cfad0b7c86419511"}, + {file = "debugpy-1.8.15-cp39-cp39-win32.whl", hash = "sha256:ae0d445fe11ff4351428e6c2389e904e1cdcb4a47785da5a5ec4af6c5b95fce5"}, + {file = "debugpy-1.8.15-cp39-cp39-win_amd64.whl", hash = "sha256:de7db80189ca97ab4b10a87e4039cfe4dd7ddfccc8f33b5ae40fcd33792fc67a"}, + {file = "debugpy-1.8.15-py2.py3-none-any.whl", hash = "sha256:bce2e6c5ff4f2e00b98d45e7e01a49c7b489ff6df5f12d881c67d2f1ac635f3d"}, + {file = "debugpy-1.8.15.tar.gz", hash = "sha256:58d7a20b7773ab5ee6bdfb2e6cf622fdf1e40c9d5aef2857d85391526719ac00"}, ] [[package]] @@ -1038,6 +1145,7 @@ version = "5.2.1" description = "Decorators for Humans" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, @@ -1049,34 +1157,19 @@ version = "0.7.1" description = "XML bomb protection for Python stdlib modules" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["dev"] files = [ {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, ] -[[package]] -name = "deprecated" -version = "1.2.18" -description = "Python @deprecated decorator to deprecate old python classes, functions or methods." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" -files = [ - {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, - {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, -] - -[package.dependencies] -wrapt = ">=1.10,<2" - -[package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"] - [[package]] name = "distro" version = "1.9.0" description = "Distro - an OS platform information API" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, @@ -1084,13 +1177,14 @@ files = [ [[package]] name = "durationpy" -version = "0.9" +version = "0.10" description = "Module for converting between datetime.timedelta and Go's Duration strings." optional = false python-versions = "*" +groups = ["main"] files = [ - {file = "durationpy-0.9-py3-none-any.whl", hash = "sha256:e65359a7af5cedad07fb77a2dd3f390f8eb0b74cb845589fa6c057086834dd38"}, - {file = "durationpy-0.9.tar.gz", hash = "sha256:fd3feb0a69a0057d582ef643c355c40d2fa1c942191f914d12203b1a01ac722a"}, + {file = "durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286"}, + {file = "durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba"}, ] [[package]] @@ -1099,6 +1193,7 @@ version = "2.0.0" description = "An implementation of lxml.xmlfile for the standard library" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa"}, {file = "et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54"}, @@ -1110,6 +1205,7 @@ version = "2.2.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, @@ -1120,69 +1216,64 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth [[package]] name = "faiss-cpu" -version = "1.10.0" +version = "1.11.0.post1" description = "A library for efficient similarity search and clustering of dense vectors." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "faiss_cpu-1.10.0-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:6693474be296a7142ade1051ea18e7d85cedbfdee4b7eac9c52f83fed0467855"}, - {file = "faiss_cpu-1.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:70ebe60a560414dc8dd6cfe8fed105c8f002c0d11f765f5adfe8d63d42c0467f"}, - {file = "faiss_cpu-1.10.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:74c5712d4890f15c661ab7b1b75867812e9596e1469759956fad900999bedbb5"}, - {file = "faiss_cpu-1.10.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:473d158fbd638d6ad5fb64469ba79a9f09d3494b5f4e8dfb4f40ce2fc335dca4"}, - {file = "faiss_cpu-1.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:dcd0cb2ec84698cbe3df9ed247d2392f09bda041ad34b92d38fa916cd019ad4b"}, - {file = "faiss_cpu-1.10.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:8ff6924b0f00df278afe70940ae86302066466580724c2f3238860039e9946f1"}, - {file = "faiss_cpu-1.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb80b530a9ded44a7d4031a7355a237aaa0ff1f150c1176df050e0254ea5f6f6"}, - {file = "faiss_cpu-1.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:7a9fef4039ed877d40e41d5563417b154c7f8cd57621487dad13c4eb4f32515f"}, - {file = "faiss_cpu-1.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:49b6647aa9e159a2c4603cbff2e1b313becd98ad6e851737ab325c74fe8e0278"}, - {file = "faiss_cpu-1.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:6f8c0ef8b615c12c7bf612bd1fc51cffa49c1ddaa6207c6981f01ab6782e6b3b"}, - {file = "faiss_cpu-1.10.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:2aca486fe2d680ea64a18d356206c91ff85db99fd34c19a757298c67c23262b1"}, - {file = "faiss_cpu-1.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c1108a4059c66c37c403183e566ca1ed0974a6af7557c92d49207639aab661bc"}, - {file = "faiss_cpu-1.10.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:449f3eb778d6d937e01a16a3170de4bb8aabfe87c7cb479b458fb790276310c5"}, - {file = "faiss_cpu-1.10.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9899c340f92bd94071d6faf4bef0ccb5362843daea42144d4ba857a2a1f67511"}, - {file = "faiss_cpu-1.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:345a52dbfa980d24b93c94410eadf82d1eef359c6a42e5e0768cca96539f1c3c"}, - {file = "faiss_cpu-1.10.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:cb8473d69c3964c1bf3f8eb3e04287bb3275f536e6d9635ef32242b5f506b45d"}, - {file = "faiss_cpu-1.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82ca5098de694e7b8495c1a8770e2c08df6e834922546dad0ae1284ff519ced6"}, - {file = "faiss_cpu-1.10.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:035e4d797e2db7fc0d0c90531d4a655d089ad5d1382b7a49358c1f2307b3a309"}, - {file = "faiss_cpu-1.10.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e02af3696a6b9e1f9072e502f48095a305de2163c42ceb1f6f6b1db9e7ffe574"}, - {file = "faiss_cpu-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:e71f7e24d5b02d3a51df47b77bd10f394a1b48a8331d5c817e71e9e27a8a75ac"}, - {file = "faiss_cpu-1.10.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:3118b5d7680b0e0a3cd64b3d29389d8384de4298739504fc661b658109540b4b"}, - {file = "faiss_cpu-1.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f71c5860c860df2320299f9e4f2ca1725beb559c04acb1cf961ed24e6218277a"}, - {file = "faiss_cpu-1.10.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:2f15b7957d474391fc63f02bfb8011b95317a580e4d9bd70c276f4bc179a17b3"}, - {file = "faiss_cpu-1.10.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:dadbbb834ddc34ca7e21411811833cebaae4c5a86198dd7c2a349dbe4e7e0398"}, - {file = "faiss_cpu-1.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:cb77a6a5f304890c23ffb4c566bc819c0e0cf34370b20ddff02477f2bbbaf7a3"}, - {file = "faiss_cpu-1.10.0.tar.gz", hash = "sha256:5bdca555f24bc036f4d67f8a5a4d6cc91b8d2126d4e78de496ca23ccd46e479d"}, + {file = "faiss_cpu-1.11.0.post1-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:e079d44ea22919f6477fea553b05854c68838ab553e1c6b1237437a8becdf89d"}, + {file = "faiss_cpu-1.11.0.post1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:4ded0c91cb67f462ae00a4d339718ea2fbb23eedbf260c3a07de77c32c23205a"}, + {file = "faiss_cpu-1.11.0.post1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78812f4d7ff9d3773f50009efcf294f3da787cd8c835c1fc41d997a58100f7b5"}, + {file = "faiss_cpu-1.11.0.post1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:76b133d746ddb3e6d39e6de62ff717cf4d45110d4af101a62d6a4fed4cd1d4d1"}, + {file = "faiss_cpu-1.11.0.post1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9443bc89447f9988f2288477584d2f1c59424a5e9f9a202e4ada8708df816db1"}, + {file = "faiss_cpu-1.11.0.post1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6acc20021b69bd30d3cb5cadb4f8dc1c338aec887cd5411b0982e8a3e48b3d7f"}, + {file = "faiss_cpu-1.11.0.post1-cp310-cp310-win_amd64.whl", hash = "sha256:9dccf67d4087f9b0f937d4dccd1183929ebb6fe7622b75cba51b53e4f0055a0c"}, + {file = "faiss_cpu-1.11.0.post1-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:2c8c384e65cc1b118d2903d9f3a27cd35f6c45337696fc0437f71e05f732dbc0"}, + {file = "faiss_cpu-1.11.0.post1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:36af46945274ed14751b788673125a8a4900408e4837a92371b0cad5708619ea"}, + {file = "faiss_cpu-1.11.0.post1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b15412b22a05865433aecfdebf7664b9565bd49b600d23a0a27c74a5526893e"}, + {file = "faiss_cpu-1.11.0.post1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81c169ea74213b2c055b8240befe7e9b42a1f3d97cda5238b3b401035ce1a18b"}, + {file = "faiss_cpu-1.11.0.post1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0794eb035c6075e931996cf2b2703fbb3f47c8c34bc2d727819ddc3e5e486a31"}, + {file = "faiss_cpu-1.11.0.post1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:18d2221014813dc9a4236e47f9c4097a71273fbf17c3fe66243e724e2018a67a"}, + {file = "faiss_cpu-1.11.0.post1-cp311-cp311-win_amd64.whl", hash = "sha256:3ce8a8984a7dcc689fd192c69a476ecd0b2611c61f96fe0799ff432aa73ff79c"}, + {file = "faiss_cpu-1.11.0.post1-cp311-cp311-win_arm64.whl", hash = "sha256:8384e05afb7c7968e93b81566759f862e744c0667b175086efb3d8b20949b39f"}, + {file = "faiss_cpu-1.11.0.post1-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:68f6ce2d9c510a5765af2f5711bd76c2c37bd598af747f3300224bdccf45378c"}, + {file = "faiss_cpu-1.11.0.post1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b940c530a8236cc0b9fd9d6e87b3d70b9c6c216bc2baf2649356c908902e52c9"}, + {file = "faiss_cpu-1.11.0.post1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fafae1dcbcba3856a0bb82ffb0c3cae5922bdd6566fdd3b7feb2425cf4fca247"}, + {file = "faiss_cpu-1.11.0.post1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d1262702c19aba2d23144b73f4b5730ca988c1f4e43ecec87edf25171cafe3d"}, + {file = "faiss_cpu-1.11.0.post1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:925feb69c06bfcc7f28869c99ab172f123e4b9d97a7e1353316fcc2748696f5b"}, + {file = "faiss_cpu-1.11.0.post1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:00a837581b675f099c80c8c46908648dcf944a8992dd21e3887c61c6b110fe5f"}, + {file = "faiss_cpu-1.11.0.post1-cp312-cp312-win_amd64.whl", hash = "sha256:8bbaef5b56d1b0c01357ee6449d464ea4e52732fdb53a40bb5b9d77923af905f"}, + {file = "faiss_cpu-1.11.0.post1-cp312-cp312-win_arm64.whl", hash = "sha256:57f85dbefe590f8399a95c07e839ee64373cfcc6db5dd35232a41137e3deefeb"}, + {file = "faiss_cpu-1.11.0.post1-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:caedaddfbfe365e3f1a57d5151cf94ea7b73c0e4789caf68eae05e0e10ca9fbf"}, + {file = "faiss_cpu-1.11.0.post1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:202d11f1d973224ca0bde13e7ee8b862b6de74287e626f9f8820b360e6253d12"}, + {file = "faiss_cpu-1.11.0.post1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6086e25ef680301350d6db72db7315e3531582cf896a7ee3f26295b1da73c44"}, + {file = "faiss_cpu-1.11.0.post1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b93131842996efbbf76f07dba1775d3a5f355f74b9ba34334f1149aef046b37f"}, + {file = "faiss_cpu-1.11.0.post1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f26e3e93f537b2e1633212a1b0a7dab74d77825366ed575ca434dac2fa14cea6"}, + {file = "faiss_cpu-1.11.0.post1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7f4b0e03cd758d03012d88aa4a70e673d10b66f31f7c122adc0c8c323cad2e33"}, + {file = "faiss_cpu-1.11.0.post1-cp313-cp313-win_amd64.whl", hash = "sha256:bc53fe59b546dbab63144dc19dcee534ad7a213db617b37aa4d0e33c26f9bbaf"}, + {file = "faiss_cpu-1.11.0.post1-cp313-cp313-win_arm64.whl", hash = "sha256:9cebb720cd57afdbe9dd7ed8a689c65dc5cf1bad475c5aa6fa0d0daea890beb6"}, + {file = "faiss_cpu-1.11.0.post1-cp39-cp39-macosx_13_0_x86_64.whl", hash = "sha256:3663059682589a42e3c4da0f3915492c466c886954cf9280273f92257bcfa0b4"}, + {file = "faiss_cpu-1.11.0.post1-cp39-cp39-macosx_14_0_arm64.whl", hash = "sha256:0348794ae91fb1454f2cddf7a9c7de23510f2a63e60c0fba0ae73bc7bf23a060"}, + {file = "faiss_cpu-1.11.0.post1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8baf46be73b4fce99f4620d99a52cdb01f7823a849f00064f02802f554d8b59f"}, + {file = "faiss_cpu-1.11.0.post1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:388a590ab2847e421ba2702ff2774835287f137fb77e24e679f0063c1c10a96f"}, + {file = "faiss_cpu-1.11.0.post1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dc12b3f89cf48be3f2a20b37f310c3f1a7a5708fdf705f88d639339a24bb590b"}, + {file = "faiss_cpu-1.11.0.post1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:773fa45aa98a210ab4e2c17c1b5fb45f6d7e9acb4979c9a0b320b678984428ac"}, + {file = "faiss_cpu-1.11.0.post1-cp39-cp39-win_amd64.whl", hash = "sha256:6240c4b1551eedc07e76813c2e14a1583a1db6c319a92a3934bf212d0e4c7791"}, + {file = "faiss_cpu-1.11.0.post1.tar.gz", hash = "sha256:06b1ea9ddec9e4d9a41c8ef7478d493b08d770e9a89475056e963081eed757d1"}, ] [package.dependencies] numpy = ">=1.25.0,<3.0" packaging = "*" -[[package]] -name = "fastapi" -version = "0.115.12" -description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" -optional = false -python-versions = ">=3.8" -files = [ - {file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"}, - {file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"}, -] - -[package.dependencies] -pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.40.0,<0.47.0" -typing-extensions = ">=4.8.0" - -[package.extras] -all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] - [[package]] name = "fastjsonschema" version = "2.21.1" description = "Fastest Python implementation of JSON schema" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "fastjsonschema-2.21.1-py3-none-any.whl", hash = "sha256:c9e5b7e908310918cf494a434eeb31384dd84a98b57a30bcb1f535015b554667"}, {file = "fastjsonschema-2.21.1.tar.gz", hash = "sha256:794d4f0a58f848961ba16af7b9c85a3e88cd360df008c59aac6fc5ae9323b5d4"}, @@ -1197,6 +1288,7 @@ version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, @@ -1213,6 +1305,7 @@ version = "25.2.10" description = "The FlatBuffers serialization format for Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "flatbuffers-25.2.10-py2.py3-none-any.whl", hash = "sha256:ebba5f4d5ea615af3f7fd70fc310636fbb2bbd1f566ac0a23d98dd412de50051"}, {file = "flatbuffers-25.2.10.tar.gz", hash = "sha256:97e451377a41262f8d9bd4295cc836133415cc03d8cb966410a4af92eb00d26e"}, @@ -1224,6 +1317,7 @@ version = "1.5.1" description = "Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsers" optional = false python-versions = ">=2.7, !=3.0, !=3.1, !=3.2, !=3.3, !=3.4, <4" +groups = ["dev"] files = [ {file = "fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014"}, {file = "fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f"}, @@ -1231,114 +1325,128 @@ files = [ [[package]] name = "frozenlist" -version = "1.5.0" +version = "1.7.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, - {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, - {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, - {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, - {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, - {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, - {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, - {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, - {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, - {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, - {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, - {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, - {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, - {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, - {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cc4df77d638aa2ed703b878dd093725b72a824c3c546c076e8fdf276f78ee84a"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:716a9973a2cc963160394f701964fe25012600f3d311f60c790400b00e568b61"}, + {file = "frozenlist-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0fd1bad056a3600047fb9462cff4c5322cebc59ebf5d0a3725e0ee78955001d"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3789ebc19cb811163e70fe2bd354cea097254ce6e707ae42e56f45e31e96cb8e"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af369aa35ee34f132fcfad5be45fbfcde0e3a5f6a1ec0712857f286b7d20cca9"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac64b6478722eeb7a3313d494f8342ef3478dff539d17002f849101b212ef97c"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f89f65d85774f1797239693cef07ad4c97fdd0639544bad9ac4b869782eb1981"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1073557c941395fdfcfac13eb2456cb8aad89f9de27bae29fabca8e563b12615"}, + {file = "frozenlist-1.7.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed8d2fa095aae4bdc7fdd80351009a48d286635edffee66bf865e37a9125c50"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:24c34bea555fe42d9f928ba0a740c553088500377448febecaa82cc3e88aa1fa"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:69cac419ac6a6baad202c85aaf467b65ac860ac2e7f2ac1686dc40dbb52f6577"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:960d67d0611f4c87da7e2ae2eacf7ea81a5be967861e0c63cf205215afbfac59"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:41be2964bd4b15bf575e5daee5a5ce7ed3115320fb3c2b71fca05582ffa4dc9e"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:46d84d49e00c9429238a7ce02dc0be8f6d7cd0cd405abd1bebdc991bf27c15bd"}, + {file = "frozenlist-1.7.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15900082e886edb37480335d9d518cec978afc69ccbc30bd18610b7c1b22a718"}, + {file = "frozenlist-1.7.0-cp310-cp310-win32.whl", hash = "sha256:400ddd24ab4e55014bba442d917203c73b2846391dd42ca5e38ff52bb18c3c5e"}, + {file = "frozenlist-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:6eb93efb8101ef39d32d50bce242c84bcbddb4f7e9febfa7b524532a239b4464"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750"}, + {file = "frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86"}, + {file = "frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898"}, + {file = "frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56"}, + {file = "frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7"}, + {file = "frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"}, + {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"}, + {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"}, + {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"}, + {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"}, + {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"}, + {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"}, + {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"}, + {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"}, + {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"}, + {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"}, + {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"}, + {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"}, + {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"}, + {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cea3dbd15aea1341ea2de490574a4a37ca080b2ae24e4b4f4b51b9057b4c3630"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7d536ee086b23fecc36c2073c371572374ff50ef4db515e4e503925361c24f71"}, + {file = "frozenlist-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dfcebf56f703cb2e346315431699f00db126d158455e513bd14089d992101e44"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:974c5336e61d6e7eb1ea5b929cb645e882aadab0095c5a6974a111e6479f8878"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c70db4a0ab5ab20878432c40563573229a7ed9241506181bba12f6b7d0dc41cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1137b78384eebaf70560a36b7b229f752fb64d463d38d1304939984d5cb887b6"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e793a9f01b3e8b5c0bc646fb59140ce0efcc580d22a3468d70766091beb81b35"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74739ba8e4e38221d2c5c03d90a7e542cb8ad681915f4ca8f68d04f810ee0a87"}, + {file = "frozenlist-1.7.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e63344c4e929b1a01e29bc184bbb5fd82954869033765bfe8d65d09e336a677"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2ea2a7369eb76de2217a842f22087913cdf75f63cf1307b9024ab82dfb525938"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:836b42f472a0e006e02499cef9352ce8097f33df43baaba3e0a28a964c26c7d2"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e22b9a99741294b2571667c07d9f8cceec07cb92aae5ccda39ea1b6052ed4319"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:9a19e85cc503d958abe5218953df722748d87172f71b73cf3c9257a91b999890"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f22dac33bb3ee8fe3e013aa7b91dc12f60d61d05b7fe32191ffa84c3aafe77bd"}, + {file = "frozenlist-1.7.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ccec739a99e4ccf664ea0775149f2749b8a6418eb5b8384b4dc0a7d15d304cb"}, + {file = "frozenlist-1.7.0-cp39-cp39-win32.whl", hash = "sha256:b3950f11058310008a87757f3eee16a8e1ca97979833239439586857bc25482e"}, + {file = "frozenlist-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:43a82fce6769c70f2f5a06248b614a7d268080a9d20f7457ef10ecee5af82b63"}, + {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"}, + {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"}, ] [[package]] name = "fsspec" -version = "2025.3.2" +version = "2025.7.0" description = "File-system specification" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "fsspec-2025.3.2-py3-none-any.whl", hash = "sha256:2daf8dc3d1dfa65b6aa37748d112773a7a08416f6c70d96b264c96476ecaf711"}, - {file = "fsspec-2025.3.2.tar.gz", hash = "sha256:e52c77ef398680bbd6a98c0e628fbc469491282981209907bbc8aea76a04fdc6"}, + {file = "fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21"}, + {file = "fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58"}, ] [package.extras] @@ -1346,7 +1454,7 @@ abfs = ["adlfs"] adl = ["adlfs"] arrow = ["pyarrow (>=1)"] dask = ["dask", "distributed"] -dev = ["pre-commit", "ruff"] +dev = ["pre-commit", "ruff (>=0.5)"] doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] dropbox = ["dropbox", "dropboxdrivefs", "requests"] full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] @@ -1371,13 +1479,14 @@ tqdm = ["tqdm"] [[package]] name = "google-auth" -version = "2.38.0" +version = "2.40.3" description = "Google Authentication Library" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a"}, - {file = "google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4"}, + {file = "google_auth-2.40.3-py2.py3-none-any.whl", hash = "sha256:1370d4593e86213563547f97a92752fc658456fe4514c809544f330fed45a7ca"}, + {file = "google_auth-2.40.3.tar.gz", hash = "sha256:500c3a29adedeb36ea9cf24b8d10858e152f2412e3ca37829b3fa18e33d63b77"}, ] [package.dependencies] @@ -1386,22 +1495,25 @@ pyasn1-modules = ">=0.2.1" rsa = ">=3.1.4,<5" [package.extras] -aiohttp = ["aiohttp (>=3.6.2,<4.0.0.dev0)", "requests (>=2.20.0,<3.0.0.dev0)"] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] enterprise-cert = ["cryptography", "pyopenssl"] -pyjwt = ["cryptography (>=38.0.3)", "pyjwt (>=2.0)"] -pyopenssl = ["cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] +pyjwt = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] +pyopenssl = ["cryptography (<39.0.0)", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] reauth = ["pyu2f (>=0.1.5)"] -requests = ["requests (>=2.20.0,<3.0.0.dev0)"] +requests = ["requests (>=2.20.0,<3.0.0)"] +testing = ["aiohttp (<3.10.0)", "aiohttp (>=3.6.2,<4.0.0)", "aioresponses", "cryptography (<39.0.0)", "cryptography (>=38.0.3)", "flask", "freezegun", "grpcio", "mock", "oauth2client", "packaging", "pyjwt (>=2.0)", "pyopenssl (<24.3.0)", "pyopenssl (>=20.0.0)", "pytest", "pytest-asyncio", "pytest-cov", "pytest-localserver", "pyu2f (>=0.1.5)", "requests (>=2.20.0,<3.0.0)", "responses", "urllib3"] +urllib3 = ["packaging", "urllib3"] [[package]] name = "googleapis-common-protos" -version = "1.69.2" +version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "googleapis_common_protos-1.69.2-py3-none-any.whl", hash = "sha256:0b30452ff9c7a27d80bfc5718954063e8ab53dd3697093d3bc99581f5fd24212"}, - {file = "googleapis_common_protos-1.69.2.tar.gz", hash = "sha256:3e1b904a27a33c821b4b749fd31d334c0c9c30e6113023d495e48979a3dc9c5f"}, + {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, + {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, ] [package.dependencies] @@ -1412,84 +1524,67 @@ grpc = ["grpcio (>=1.44.0,<2.0.0)"] [[package]] name = "greenlet" -version = "3.1.1" +version = "3.2.3" description = "Lightweight in-process concurrent programming" optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version < \"3.14\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")" files = [ - {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, - {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, - {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, - {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, - {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, - {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, - {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, - {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, - {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, - {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, - {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, - {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, - {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, - {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, - {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, - {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, - {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, - {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, - {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, - {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, - {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, - {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, - {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, - {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, - {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, - {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, - {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, - {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, - {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, - {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, - {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, - {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, - {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, + {file = "greenlet-3.2.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:1afd685acd5597349ee6d7a88a8bec83ce13c106ac78c196ee9dde7c04fe87be"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:761917cac215c61e9dc7324b2606107b3b292a8349bdebb31503ab4de3f559ac"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a433dbc54e4a37e4fff90ef34f25a8c00aed99b06856f0119dcf09fbafa16392"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:72e77ed69312bab0434d7292316d5afd6896192ac4327d44f3d613ecb85b037c"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:68671180e3849b963649254a882cd544a3c75bfcd2c527346ad8bb53494444db"}, + {file = "greenlet-3.2.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49c8cfb18fb419b3d08e011228ef8a25882397f3a859b9fe1436946140b6756b"}, + {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:efc6dc8a792243c31f2f5674b670b3a95d46fa1c6a912b8e310d6f542e7b0712"}, + {file = "greenlet-3.2.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:731e154aba8e757aedd0781d4b240f1225b075b4409f1bb83b05ff410582cf00"}, + {file = "greenlet-3.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:96c20252c2f792defe9a115d3287e14811036d51e78b3aaddbee23b69b216302"}, + {file = "greenlet-3.2.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:784ae58bba89fa1fa5733d170d42486580cab9decda3484779f4759345b29822"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0921ac4ea42a5315d3446120ad48f90c3a6b9bb93dd9b3cf4e4d84a66e42de83"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d2971d93bb99e05f8c2c0c2f4aa9484a18d98c4c3bd3c62b65b7e6ae33dfcfaf"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c667c0bf9d406b77a15c924ef3285e1e05250948001220368e039b6aa5b5034b"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:592c12fb1165be74592f5de0d70f82bc5ba552ac44800d632214b76089945147"}, + {file = "greenlet-3.2.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29e184536ba333003540790ba29829ac14bb645514fbd7e32af331e8202a62a5"}, + {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:93c0bb79844a367782ec4f429d07589417052e621aa39a5ac1fb99c5aa308edc"}, + {file = "greenlet-3.2.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:751261fc5ad7b6705f5f76726567375bb2104a059454e0226e1eef6c756748ba"}, + {file = "greenlet-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:83a8761c75312361aa2b5b903b79da97f13f556164a7dd2d5448655425bd4c34"}, + {file = "greenlet-3.2.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:25ad29caed5783d4bd7a85c9251c651696164622494c00802a139c00d639242d"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88cd97bf37fe24a6710ec6a3a7799f3f81d9cd33317dcf565ff9950c83f55e0b"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:baeedccca94880d2f5666b4fa16fc20ef50ba1ee353ee2d7092b383a243b0b0d"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:be52af4b6292baecfa0f397f3edb3c6092ce071b499dd6fe292c9ac9f2c8f264"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0cc73378150b8b78b0c9fe2ce56e166695e67478550769536a6742dca3651688"}, + {file = "greenlet-3.2.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:706d016a03e78df129f68c4c9b4c4f963f7d73534e48a24f5f5a7101ed13dbbb"}, + {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:419e60f80709510c343c57b4bb5a339d8767bf9aef9b8ce43f4f143240f88b7c"}, + {file = "greenlet-3.2.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:93d48533fade144203816783373f27a97e4193177ebaaf0fc396db19e5d61163"}, + {file = "greenlet-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:7454d37c740bb27bdeddfc3f358f26956a07d5220818ceb467a483197d84f849"}, + {file = "greenlet-3.2.3-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:500b8689aa9dd1ab26872a34084503aeddefcb438e2e7317b89b11eaea1901ad"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a07d3472c2a93117af3b0136f246b2833fdc0b542d4a9799ae5f41c28323faef"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8704b3768d2f51150626962f4b9a9e4a17d2e37c8a8d9867bbd9fa4eb938d3b3"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5035d77a27b7c62db6cf41cf786cfe2242644a7a337a0e155c80960598baab95"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2d8aa5423cd4a396792f6d4580f88bdc6efcb9205891c9d40d20f6e670992efb"}, + {file = "greenlet-3.2.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c724620a101f8170065d7dded3f962a2aea7a7dae133a009cada42847e04a7b"}, + {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:873abe55f134c48e1f2a6f53f7d1419192a3d1a4e873bace00499a4e45ea6af0"}, + {file = "greenlet-3.2.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:024571bbce5f2c1cfff08bf3fbaa43bbc7444f580ae13b0099e95d0e6e67ed36"}, + {file = "greenlet-3.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:5195fb1e75e592dd04ce79881c8a22becdfa3e6f500e7feb059b1e6fdd54d3e3"}, + {file = "greenlet-3.2.3-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:3d04332dddb10b4a211b68111dabaee2e1a073663d117dc10247b5b1642bac86"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8186162dffde068a465deab08fc72c767196895c39db26ab1c17c0b77a6d8b97"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f4bfbaa6096b1b7a200024784217defedf46a07c2eee1a498e94a1b5f8ec5728"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:ed6cfa9200484d234d8394c70f5492f144b20d4533f69262d530a1a082f6ee9a"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:02b0df6f63cd15012bed5401b47829cfd2e97052dc89da3cfaf2c779124eb892"}, + {file = "greenlet-3.2.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:86c2d68e87107c1792e2e8d5399acec2487a4e993ab76c792408e59394d52141"}, + {file = "greenlet-3.2.3-cp314-cp314-win_amd64.whl", hash = "sha256:8c47aae8fbbfcf82cc13327ae802ba13c9c36753b67e760023fd116bc124a62a"}, + {file = "greenlet-3.2.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:42efc522c0bd75ffa11a71e09cd8a399d83fafe36db250a87cf1dacfaa15dc64"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d760f9bdfe79bff803bad32b4d8ffb2c1d2ce906313fc10a83976ffb73d64ca7"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:8324319cbd7b35b97990090808fdc99c27fe5338f87db50514959f8059999805"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:8c37ef5b3787567d322331d5250e44e42b58c8c713859b8a04c6065f27efbf72"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ce539fb52fb774d0802175d37fcff5c723e2c7d249c65916257f0a940cee8904"}, + {file = "greenlet-3.2.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:003c930e0e074db83559edc8705f3a2d066d4aa8c2f198aff1e454946efd0f26"}, + {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7e70ea4384b81ef9e84192e8a77fb87573138aa5d4feee541d8014e452b434da"}, + {file = "greenlet-3.2.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22eb5ba839c4b2156f18f76768233fe44b23a31decd9cc0d4cc8141c211fd1b4"}, + {file = "greenlet-3.2.3-cp39-cp39-win32.whl", hash = "sha256:4532f0d25df67f896d137431b13f4cdce89f7e3d4a96387a41290910df4d3a57"}, + {file = "greenlet-3.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:aaa7aae1e7f75eaa3ae400ad98f8644bb81e1dc6ba47ce8a93d3f17274e08322"}, + {file = "greenlet-3.2.3.tar.gz", hash = "sha256:8b0dd8ae4c0d6f5e54ee55ba935eeb3d735a9b58a8a1e5b5cbab64e01a39f365"}, ] [package.extras] @@ -1498,92 +1593,117 @@ test = ["objgraph", "psutil"] [[package]] name = "grpcio" -version = "1.71.0" +version = "1.74.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "grpcio-1.71.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:c200cb6f2393468142eb50ab19613229dcc7829b5ccee8b658a36005f6669fdd"}, - {file = "grpcio-1.71.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:b2266862c5ad664a380fbbcdbdb8289d71464c42a8c29053820ee78ba0119e5d"}, - {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:0ab8b2864396663a5b0b0d6d79495657ae85fa37dcb6498a2669d067c65c11ea"}, - {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c30f393f9d5ff00a71bb56de4aa75b8fe91b161aeb61d39528db6b768d7eac69"}, - {file = "grpcio-1.71.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f250ff44843d9a0615e350c77f890082102a0318d66a99540f54769c8766ab73"}, - {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e6d8de076528f7c43a2f576bc311799f89d795aa6c9b637377cc2b1616473804"}, - {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9b91879d6da1605811ebc60d21ab6a7e4bae6c35f6b63a061d61eb818c8168f6"}, - {file = "grpcio-1.71.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f71574afdf944e6652203cd1badcda195b2a27d9c83e6d88dc1ce3cfb73b31a5"}, - {file = "grpcio-1.71.0-cp310-cp310-win32.whl", hash = "sha256:8997d6785e93308f277884ee6899ba63baafa0dfb4729748200fcc537858a509"}, - {file = "grpcio-1.71.0-cp310-cp310-win_amd64.whl", hash = "sha256:7d6ac9481d9d0d129224f6d5934d5832c4b1cddb96b59e7eba8416868909786a"}, - {file = "grpcio-1.71.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:d6aa986318c36508dc1d5001a3ff169a15b99b9f96ef5e98e13522c506b37eef"}, - {file = "grpcio-1.71.0-cp311-cp311-macosx_10_14_universal2.whl", hash = "sha256:d2c170247315f2d7e5798a22358e982ad6eeb68fa20cf7a820bb74c11f0736e7"}, - {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:e6f83a583ed0a5b08c5bc7a3fe860bb3c2eac1f03f1f63e0bc2091325605d2b7"}, - {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4be74ddeeb92cc87190e0e376dbc8fc7736dbb6d3d454f2fa1f5be1dee26b9d7"}, - {file = "grpcio-1.71.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4dd0dfbe4d5eb1fcfec9490ca13f82b089a309dc3678e2edabc144051270a66e"}, - {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a2242d6950dc892afdf9e951ed7ff89473aaf744b7d5727ad56bdaace363722b"}, - {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:0fa05ee31a20456b13ae49ad2e5d585265f71dd19fbd9ef983c28f926d45d0a7"}, - {file = "grpcio-1.71.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3d081e859fb1ebe176de33fc3adb26c7d46b8812f906042705346b314bde32c3"}, - {file = "grpcio-1.71.0-cp311-cp311-win32.whl", hash = "sha256:d6de81c9c00c8a23047136b11794b3584cdc1460ed7cbc10eada50614baa1444"}, - {file = "grpcio-1.71.0-cp311-cp311-win_amd64.whl", hash = "sha256:24e867651fc67717b6f896d5f0cac0ec863a8b5fb7d6441c2ab428f52c651c6b"}, - {file = "grpcio-1.71.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:0ff35c8d807c1c7531d3002be03221ff9ae15712b53ab46e2a0b4bb271f38537"}, - {file = "grpcio-1.71.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:b78a99cd1ece4be92ab7c07765a0b038194ded2e0a26fd654591ee136088d8d7"}, - {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc1a1231ed23caac1de9f943d031f1bc38d0f69d2a3b243ea0d664fc1fbd7fec"}, - {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6beeea5566092c5e3c4896c6d1d307fb46b1d4bdf3e70c8340b190a69198594"}, - {file = "grpcio-1.71.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5170929109450a2c031cfe87d6716f2fae39695ad5335d9106ae88cc32dc84c"}, - {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5b08d03ace7aca7b2fadd4baf291139b4a5f058805a8327bfe9aece7253b6d67"}, - {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f903017db76bf9cc2b2d8bdd37bf04b505bbccad6be8a81e1542206875d0e9db"}, - {file = "grpcio-1.71.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:469f42a0b410883185eab4689060a20488a1a0a00f8bbb3cbc1061197b4c5a79"}, - {file = "grpcio-1.71.0-cp312-cp312-win32.whl", hash = "sha256:ad9f30838550695b5eb302add33f21f7301b882937460dd24f24b3cc5a95067a"}, - {file = "grpcio-1.71.0-cp312-cp312-win_amd64.whl", hash = "sha256:652350609332de6dac4ece254e5d7e1ff834e203d6afb769601f286886f6f3a8"}, - {file = "grpcio-1.71.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:cebc1b34ba40a312ab480ccdb396ff3c529377a2fce72c45a741f7215bfe8379"}, - {file = "grpcio-1.71.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:85da336e3649a3d2171e82f696b5cad2c6231fdd5bad52616476235681bee5b3"}, - {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f9a412f55bb6e8f3bb000e020dbc1e709627dcb3a56f6431fa7076b4c1aab0db"}, - {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47be9584729534660416f6d2a3108aaeac1122f6b5bdbf9fd823e11fe6fbaa29"}, - {file = "grpcio-1.71.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9c80ac6091c916db81131d50926a93ab162a7e97e4428ffc186b6e80d6dda4"}, - {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:789d5e2a3a15419374b7b45cd680b1e83bbc1e52b9086e49308e2c0b5bbae6e3"}, - {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1be857615e26a86d7363e8a163fade914595c81fec962b3d514a4b1e8760467b"}, - {file = "grpcio-1.71.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a76d39b5fafd79ed604c4be0a869ec3581a172a707e2a8d7a4858cb05a5a7637"}, - {file = "grpcio-1.71.0-cp313-cp313-win32.whl", hash = "sha256:74258dce215cb1995083daa17b379a1a5a87d275387b7ffe137f1d5131e2cfbb"}, - {file = "grpcio-1.71.0-cp313-cp313-win_amd64.whl", hash = "sha256:22c3bc8d488c039a199f7a003a38cb7635db6656fa96437a8accde8322ce2366"}, - {file = "grpcio-1.71.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:c6a0a28450c16809f94e0b5bfe52cabff63e7e4b97b44123ebf77f448534d07d"}, - {file = "grpcio-1.71.0-cp39-cp39-macosx_10_14_universal2.whl", hash = "sha256:a371e6b6a5379d3692cc4ea1cb92754d2a47bdddeee755d3203d1f84ae08e03e"}, - {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:39983a9245d37394fd59de71e88c4b295eb510a3555e0a847d9965088cdbd033"}, - {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9182e0063112e55e74ee7584769ec5a0b4f18252c35787f48738627e23a62b97"}, - {file = "grpcio-1.71.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693bc706c031aeb848849b9d1c6b63ae6bcc64057984bb91a542332b75aa4c3d"}, - {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:20e8f653abd5ec606be69540f57289274c9ca503ed38388481e98fa396ed0b41"}, - {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8700a2a57771cc43ea295296330daaddc0d93c088f0a35cc969292b6db959bf3"}, - {file = "grpcio-1.71.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d35a95f05a8a2cbe8e02be137740138b3b2ea5f80bd004444e4f9a1ffc511e32"}, - {file = "grpcio-1.71.0-cp39-cp39-win32.whl", hash = "sha256:f9c30c464cb2ddfbc2ddf9400287701270fdc0f14be5f08a1e3939f1e749b455"}, - {file = "grpcio-1.71.0-cp39-cp39-win_amd64.whl", hash = "sha256:63e41b91032f298b3e973b3fa4093cbbc620c875e2da7b93e249d4728b54559a"}, - {file = "grpcio-1.71.0.tar.gz", hash = "sha256:2b85f7820475ad3edec209d3d89a7909ada16caab05d3f2e08a7e8ae3200a55c"}, + {file = "grpcio-1.74.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:85bd5cdf4ed7b2d6438871adf6afff9af7096486fcf51818a81b77ef4dd30907"}, + {file = "grpcio-1.74.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:68c8ebcca945efff9d86d8d6d7bfb0841cf0071024417e2d7f45c5e46b5b08eb"}, + {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:e154d230dc1bbbd78ad2fdc3039fa50ad7ffcf438e4eb2fa30bce223a70c7486"}, + {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8978003816c7b9eabe217f88c78bc26adc8f9304bf6a594b02e5a49b2ef9c11"}, + {file = "grpcio-1.74.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3d7bd6e3929fd2ea7fbc3f562e4987229ead70c9ae5f01501a46701e08f1ad9"}, + {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:136b53c91ac1d02c8c24201bfdeb56f8b3ac3278668cbb8e0ba49c88069e1bdc"}, + {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fe0f540750a13fd8e5da4b3eaba91a785eea8dca5ccd2bc2ffe978caa403090e"}, + {file = "grpcio-1.74.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4e4181bfc24413d1e3a37a0b7889bea68d973d4b45dd2bc68bb766c140718f82"}, + {file = "grpcio-1.74.0-cp310-cp310-win32.whl", hash = "sha256:1733969040989f7acc3d94c22f55b4a9501a30f6aaacdbccfaba0a3ffb255ab7"}, + {file = "grpcio-1.74.0-cp310-cp310-win_amd64.whl", hash = "sha256:9e912d3c993a29df6c627459af58975b2e5c897d93287939b9d5065f000249b5"}, + {file = "grpcio-1.74.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:69e1a8180868a2576f02356565f16635b99088da7df3d45aaa7e24e73a054e31"}, + {file = "grpcio-1.74.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8efe72fde5500f47aca1ef59495cb59c885afe04ac89dd11d810f2de87d935d4"}, + {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a8f0302f9ac4e9923f98d8e243939a6fb627cd048f5cd38595c97e38020dffce"}, + {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f609a39f62a6f6f05c7512746798282546358a37ea93c1fcbadf8b2fed162e3"}, + {file = "grpcio-1.74.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98e0b7434a7fa4e3e63f250456eaef52499fba5ae661c58cc5b5477d11e7182"}, + {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:662456c4513e298db6d7bd9c3b8df6f75f8752f0ba01fb653e252ed4a59b5a5d"}, + {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3d14e3c4d65e19d8430a4e28ceb71ace4728776fd6c3ce34016947474479683f"}, + {file = "grpcio-1.74.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bf949792cee20d2078323a9b02bacbbae002b9e3b9e2433f2741c15bdeba1c4"}, + {file = "grpcio-1.74.0-cp311-cp311-win32.whl", hash = "sha256:55b453812fa7c7ce2f5c88be3018fb4a490519b6ce80788d5913f3f9d7da8c7b"}, + {file = "grpcio-1.74.0-cp311-cp311-win_amd64.whl", hash = "sha256:86ad489db097141a907c559988c29718719aa3e13370d40e20506f11b4de0d11"}, + {file = "grpcio-1.74.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8533e6e9c5bd630ca98062e3a1326249e6ada07d05acf191a77bc33f8948f3d8"}, + {file = "grpcio-1.74.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2918948864fec2a11721d91568effffbe0a02b23ecd57f281391d986847982f6"}, + {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:60d2d48b0580e70d2e1954d0d19fa3c2e60dd7cbed826aca104fff518310d1c5"}, + {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3601274bc0523f6dc07666c0e01682c94472402ac2fd1226fd96e079863bfa49"}, + {file = "grpcio-1.74.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:176d60a5168d7948539def20b2a3adcce67d72454d9ae05969a2e73f3a0feee7"}, + {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e759f9e8bc908aaae0412642afe5416c9f983a80499448fcc7fab8692ae044c3"}, + {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e7c4389771855a92934b2846bd807fc25a3dfa820fd912fe6bd8136026b2707"}, + {file = "grpcio-1.74.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cce634b10aeab37010449124814b05a62fb5f18928ca878f1bf4750d1f0c815b"}, + {file = "grpcio-1.74.0-cp312-cp312-win32.whl", hash = "sha256:885912559974df35d92219e2dc98f51a16a48395f37b92865ad45186f294096c"}, + {file = "grpcio-1.74.0-cp312-cp312-win_amd64.whl", hash = "sha256:42f8fee287427b94be63d916c90399ed310ed10aadbf9e2e5538b3e497d269bc"}, + {file = "grpcio-1.74.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2bc2d7d8d184e2362b53905cb1708c84cb16354771c04b490485fa07ce3a1d89"}, + {file = "grpcio-1.74.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c14e803037e572c177ba54a3e090d6eb12efd795d49327c5ee2b3bddb836bf01"}, + {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f6ec94f0e50eb8fa1744a731088b966427575e40c2944a980049798b127a687e"}, + {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:566b9395b90cc3d0d0c6404bc8572c7c18786ede549cdb540ae27b58afe0fb91"}, + {file = "grpcio-1.74.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1ea6176d7dfd5b941ea01c2ec34de9531ba494d541fe2057c904e601879f249"}, + {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:64229c1e9cea079420527fa8ac45d80fc1e8d3f94deaa35643c381fa8d98f362"}, + {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:0f87bddd6e27fc776aacf7ebfec367b6d49cad0455123951e4488ea99d9b9b8f"}, + {file = "grpcio-1.74.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3b03d8f2a07f0fea8c8f74deb59f8352b770e3900d143b3d1475effcb08eec20"}, + {file = "grpcio-1.74.0-cp313-cp313-win32.whl", hash = "sha256:b6a73b2ba83e663b2480a90b82fdae6a7aa6427f62bf43b29912c0cfd1aa2bfa"}, + {file = "grpcio-1.74.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd3c71aeee838299c5887230b8a1822795325ddfea635edd82954c1eaa831e24"}, + {file = "grpcio-1.74.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:4bc5fca10aaf74779081e16c2bcc3d5ec643ffd528d9e7b1c9039000ead73bae"}, + {file = "grpcio-1.74.0-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:6bab67d15ad617aff094c382c882e0177637da73cbc5532d52c07b4ee887a87b"}, + {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:655726919b75ab3c34cdad39da5c530ac6fa32696fb23119e36b64adcfca174a"}, + {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a2b06afe2e50ebfd46247ac3ba60cac523f54ec7792ae9ba6073c12daf26f0a"}, + {file = "grpcio-1.74.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f251c355167b2360537cf17bea2cf0197995e551ab9da6a0a59b3da5e8704f9"}, + {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8f7b5882fb50632ab1e48cb3122d6df55b9afabc265582808036b6e51b9fd6b7"}, + {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:834988b6c34515545b3edd13e902c1acdd9f2465d386ea5143fb558f153a7176"}, + {file = "grpcio-1.74.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:22b834cef33429ca6cc28303c9c327ba9a3fafecbf62fae17e9a7b7163cc43ac"}, + {file = "grpcio-1.74.0-cp39-cp39-win32.whl", hash = "sha256:7d95d71ff35291bab3f1c52f52f474c632db26ea12700c2ff0ea0532cb0b5854"}, + {file = "grpcio-1.74.0-cp39-cp39-win_amd64.whl", hash = "sha256:ecde9ab49f58433abe02f9ed076c7b5be839cf0153883a6d23995937a82392fa"}, + {file = "grpcio-1.74.0.tar.gz", hash = "sha256:80d1f4fbb35b0742d3e3d3bb654b7381cd5f015f8497279a1e9c21ba623e01b1"}, ] [package.extras] -protobuf = ["grpcio-tools (>=1.71.0)"] +protobuf = ["grpcio-tools (>=1.74.0)"] [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, ] +[[package]] +name = "hf-xet" +version = "1.1.5" +description = "Fast transfer of large files with the Hugging Face Hub." +optional = false +python-versions = ">=3.8" +groups = ["main"] +markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"" +files = [ + {file = "hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23"}, + {file = "hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8"}, + {file = "hf_xet-1.1.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc874b5c843e642f45fd85cda1ce599e123308ad2901ead23d3510a47ff506d1"}, + {file = "hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dbba1660e5d810bd0ea77c511a99e9242d920790d0e63c0e4673ed36c4022d18"}, + {file = "hf_xet-1.1.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ab34c4c3104133c495785d5d8bba3b1efc99de52c02e759cf711a91fd39d3a14"}, + {file = "hf_xet-1.1.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:83088ecea236d5113de478acb2339f92c95b4fb0462acaa30621fac02f5a534a"}, + {file = "hf_xet-1.1.5-cp37-abi3-win_amd64.whl", hash = "sha256:73e167d9807d166596b4b2f0b585c6d5bd84a26dea32843665a8b58f6edba245"}, + {file = "hf_xet-1.1.5.tar.gz", hash = "sha256:69ebbcfd9ec44fdc2af73441619eeb06b94ee34511bbcf57cd423820090f5694"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.9" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, + {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"}, + {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"}, ] [package.dependencies] certifi = "*" -h11 = ">=0.13,<0.15" +h11 = ">=0.16" [package.extras] asyncio = ["anyio (>=4.0,<5.0)"] @@ -1597,6 +1717,7 @@ version = "0.6.4" description = "A collection of framework independent HTTP protocol utils." optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"}, {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"}, @@ -1652,6 +1773,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -1672,29 +1794,32 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "httpx-sse" -version = "0.4.0" +version = "0.4.1" description = "Consume Server-Sent Event (SSE) messages with HTTPX." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721"}, - {file = "httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f"}, + {file = "httpx_sse-0.4.1-py3-none-any.whl", hash = "sha256:cba42174344c3a5b06f255ce65b350880f962d99ead85e776f23c6618a377a37"}, + {file = "httpx_sse-0.4.1.tar.gz", hash = "sha256:8f44d34414bc7b21bf3602713005c5df4917884f76072479b21f68befa4ea26e"}, ] [[package]] name = "huggingface-hub" -version = "0.30.2" +version = "0.34.3" description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ - {file = "huggingface_hub-0.30.2-py3-none-any.whl", hash = "sha256:68ff05969927058cfa41df4f2155d4bb48f5f54f719dd0390103eefa9b191e28"}, - {file = "huggingface_hub-0.30.2.tar.gz", hash = "sha256:9a7897c5b6fd9dad3168a794a8998d6378210f5b9688d0dfc180b1a228dc2466"}, + {file = "huggingface_hub-0.34.3-py3-none-any.whl", hash = "sha256:5444550099e2d86e68b2898b09e85878fbd788fc2957b506c6a79ce060e39492"}, + {file = "huggingface_hub-0.34.3.tar.gz", hash = "sha256:d58130fd5aa7408480681475491c0abd7e835442082fbc3ef4d45b6c39f83853"}, ] [package.dependencies] filelock = "*" fsspec = ">=2023.5.0" +hf-xet = {version = ">=1.1.3,<2.0.0", markers = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""} packaging = ">=20.9" pyyaml = ">=5.1" requests = "*" @@ -1702,17 +1827,19 @@ tqdm = ">=4.42.1" typing-extensions = ">=3.7.4.3" [package.extras] -all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] cli = ["InquirerPy (==0.3.4)"] -dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] hf-transfer = ["hf-transfer (>=0.1.4)"] -hf-xet = ["hf-xet (>=0.1.4)"] +hf-xet = ["hf-xet (>=1.1.2,<2.0.0)"] inference = ["aiohttp"] -quality = ["libcst (==1.4.0)", "mypy (==1.5.1)", "ruff (>=0.9.0)"] +mcp = ["aiohttp", "mcp (>=1.8.0)", "typer"] +oauth = ["authlib (>=1.3.2)", "fastapi", "httpx", "itsdangerous"] +quality = ["libcst (>=1.4.0)", "mypy (==1.15.0)", "mypy (>=1.14.1,<1.15.0)", "ruff (>=0.9.0)"] tensorflow = ["graphviz", "pydot", "tensorflow"] tensorflow-testing = ["keras (<3.0)", "tensorflow"] -testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "authlib (>=1.3.2)", "fastapi", "gradio (>=4.0.0)", "httpx", "itsdangerous", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] torch = ["safetensors[torch]", "torch"] typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] @@ -1722,6 +1849,7 @@ version = "10.0" description = "Human friendly output for text interfaces using Python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["main"] files = [ {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, @@ -1736,6 +1864,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1746,13 +1875,14 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "importlib-metadata" -version = "8.6.1" +version = "8.7.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, - {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, + {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"}, + {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"}, ] [package.dependencies] @@ -1773,6 +1903,7 @@ version = "6.5.2" description = "Read resources from Python packages" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec"}, {file = "importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c"}, @@ -1792,6 +1923,7 @@ version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, @@ -1799,46 +1931,48 @@ files = [ [[package]] name = "ipykernel" -version = "6.29.5" +version = "6.30.1" description = "IPython Kernel for Jupyter" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, - {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, + {file = "ipykernel-6.30.1-py3-none-any.whl", hash = "sha256:aa6b9fb93dca949069d8b85b6c79b2518e32ac583ae9c7d37c51d119e18b3fb4"}, + {file = "ipykernel-6.30.1.tar.gz", hash = "sha256:6abb270161896402e76b91394fcdce5d1be5d45f456671e5080572f8505be39b"}, ] [package.dependencies] -appnope = {version = "*", markers = "platform_system == \"Darwin\""} +appnope = {version = ">=0.1.2", markers = "platform_system == \"Darwin\""} comm = ">=0.1.1" debugpy = ">=1.6.5" ipython = ">=7.23.1" -jupyter-client = ">=6.1.12" +jupyter-client = ">=8.0.0" jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" matplotlib-inline = ">=0.1" -nest-asyncio = "*" -packaging = "*" -psutil = "*" -pyzmq = ">=24" -tornado = ">=6.1" +nest-asyncio = ">=1.4" +packaging = ">=22" +psutil = ">=5.7" +pyzmq = ">=25" +tornado = ">=6.2" traitlets = ">=5.4.0" [package.extras] -cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +cov = ["coverage[toml]", "matplotlib", "pytest-cov", "trio"] +docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] pyqt5 = ["pyqt5"] pyside6 = ["pyside6"] -test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0,<9)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] [[package]] name = "ipython" -version = "9.1.0" +version = "9.4.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.11" +groups = ["dev"] files = [ - {file = "ipython-9.1.0-py3-none-any.whl", hash = "sha256:2df07257ec2f84a6b346b8d83100bcf8fa501c6e01ab75cd3799b0bb253b3d2a"}, - {file = "ipython-9.1.0.tar.gz", hash = "sha256:a47e13a5e05e02f3b8e1e7a0f9db372199fe8c3763532fe7a1e0379e4e135f16"}, + {file = "ipython-9.4.0-py3-none-any.whl", hash = "sha256:25850f025a446d9b359e8d296ba175a36aedd32e83ca9b5060430fe16801f066"}, + {file = "ipython-9.4.0.tar.gz", hash = "sha256:c033c6d4e7914c3d9768aabe76bbe87ba1dc66a92a05db6bfa1125d81f2ee270"}, ] [package.dependencies] @@ -1867,6 +2001,7 @@ version = "1.1.1" description = "Defines a variety of Pygments lexers for highlighting IPython code." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c"}, {file = "ipython_pygments_lexers-1.1.1.tar.gz", hash = "sha256:09c0138009e56b6854f9535736f4171d855c8c08a563a0dcd8022f78355c7e81"}, @@ -1877,21 +2012,22 @@ pygments = "*" [[package]] name = "ipywidgets" -version = "8.1.5" +version = "8.1.7" description = "Jupyter interactive widgets" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "ipywidgets-8.1.5-py3-none-any.whl", hash = "sha256:3290f526f87ae6e77655555baba4f36681c555b8bdbbff430b70e52c34c86245"}, - {file = "ipywidgets-8.1.5.tar.gz", hash = "sha256:870e43b1a35656a80c18c9503bbf2d16802db1cb487eec6fab27d683381dde17"}, + {file = "ipywidgets-8.1.7-py3-none-any.whl", hash = "sha256:764f2602d25471c213919b8a1997df04bef869251db4ca8efba1b76b1bd9f7bb"}, + {file = "ipywidgets-8.1.7.tar.gz", hash = "sha256:15f1ac050b9ccbefd45dccfbb2ef6bed0029d8278682d569d71b8dd96bee0376"}, ] [package.dependencies] comm = ">=0.1.3" ipython = ">=6.1.0" -jupyterlab-widgets = ">=3.0.12,<3.1.0" +jupyterlab_widgets = ">=3.0.15,<3.1.0" traitlets = ">=4.3.1" -widgetsnbextension = ">=4.0.12,<4.1.0" +widgetsnbextension = ">=4.0.14,<4.1.0" [package.extras] test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] @@ -1902,6 +2038,7 @@ version = "20.11.0" description = "Operations with ISO 8601 durations" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042"}, {file = "isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9"}, @@ -1916,6 +2053,7 @@ version = "5.13.2" description = "A Python utility / library to sort Python imports." optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, @@ -1930,6 +2068,7 @@ version = "0.19.2" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, @@ -1949,6 +2088,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1962,87 +2102,89 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jiter" -version = "0.9.0" +version = "0.10.0" description = "Fast iterable JSON parser." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad"}, - {file = "jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea"}, - {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1339f839b91ae30b37c409bf16ccd3dc453e8b8c3ed4bd1d6a567193651a4a51"}, - {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ffba79584b3b670fefae66ceb3a28822365d25b7bf811e030609a3d5b876f538"}, - {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cfc7d0a8e899089d11f065e289cb5b2daf3d82fbe028f49b20d7b809193958d"}, - {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e00a1a2bbfaaf237e13c3d1592356eab3e9015d7efd59359ac8b51eb56390a12"}, - {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1d9870561eb26b11448854dce0ff27a9a27cb616b632468cafc938de25e9e51"}, - {file = "jiter-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9872aeff3f21e437651df378cb75aeb7043e5297261222b6441a620218b58708"}, - {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fd19112d1049bdd47f17bfbb44a2c0001061312dcf0e72765bfa8abd4aa30e5"}, - {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ef5da104664e526836070e4a23b5f68dec1cc673b60bf1edb1bfbe8a55d0678"}, - {file = "jiter-0.9.0-cp310-cp310-win32.whl", hash = "sha256:cb12e6d65ebbefe5518de819f3eda53b73187b7089040b2d17f5b39001ff31c4"}, - {file = "jiter-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:c43ca669493626d8672be3b645dbb406ef25af3f4b6384cfd306da7eb2e70322"}, - {file = "jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af"}, - {file = "jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58"}, - {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b"}, - {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b"}, - {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5"}, - {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572"}, - {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15"}, - {file = "jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419"}, - {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043"}, - {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965"}, - {file = "jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2"}, - {file = "jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd"}, - {file = "jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11"}, - {file = "jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e"}, - {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2"}, - {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75"}, - {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d"}, - {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42"}, - {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc"}, - {file = "jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc"}, - {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e"}, - {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d"}, - {file = "jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06"}, - {file = "jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0"}, - {file = "jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7"}, - {file = "jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b"}, - {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69"}, - {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103"}, - {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635"}, - {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4"}, - {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d"}, - {file = "jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3"}, - {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5"}, - {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d"}, - {file = "jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53"}, - {file = "jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7"}, - {file = "jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001"}, - {file = "jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a"}, - {file = "jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf"}, - {file = "jiter-0.9.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4a2d16360d0642cd68236f931b85fe50288834c383492e4279d9f1792e309571"}, - {file = "jiter-0.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e84ed1c9c9ec10bbb8c37f450077cbe3c0d4e8c2b19f0a49a60ac7ace73c7452"}, - {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f3c848209ccd1bfa344a1240763975ca917de753c7875c77ec3034f4151d06c"}, - {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7825f46e50646bee937e0f849d14ef3a417910966136f59cd1eb848b8b5bb3e4"}, - {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d82a811928b26d1a6311a886b2566f68ccf2b23cf3bfed042e18686f1f22c2d7"}, - {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c058ecb51763a67f019ae423b1cbe3fa90f7ee6280c31a1baa6ccc0c0e2d06e"}, - {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9897115ad716c48f0120c1f0c4efae348ec47037319a6c63b2d7838bb53aaef4"}, - {file = "jiter-0.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:351f4c90a24c4fb8c87c6a73af2944c440494ed2bea2094feecacb75c50398ae"}, - {file = "jiter-0.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d45807b0f236c485e1e525e2ce3a854807dfe28ccf0d013dd4a563395e28008a"}, - {file = "jiter-0.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1537a890724ba00fdba21787010ac6f24dad47f763410e9e1093277913592784"}, - {file = "jiter-0.9.0-cp38-cp38-win32.whl", hash = "sha256:e3630ec20cbeaddd4b65513fa3857e1b7c4190d4481ef07fb63d0fad59033321"}, - {file = "jiter-0.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:2685f44bf80e95f8910553bf2d33b9c87bf25fceae6e9f0c1355f75d2922b0ee"}, - {file = "jiter-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:9ef340fae98065071ccd5805fe81c99c8f80484e820e40043689cf97fb66b3e2"}, - {file = "jiter-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:efb767d92c63b2cd9ec9f24feeb48f49574a713870ec87e9ba0c2c6e9329c3e2"}, - {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:113f30f87fb1f412510c6d7ed13e91422cfd329436364a690c34c8b8bd880c42"}, - {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8793b6df019b988526f5a633fdc7456ea75e4a79bd8396a3373c371fc59f5c9b"}, - {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a9aaa5102dba4e079bb728076fadd5a2dca94c05c04ce68004cfd96f128ea34"}, - {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d838650f6ebaf4ccadfb04522463e74a4c378d7e667e0eb1865cfe3990bfac49"}, - {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0194f813efdf4b8865ad5f5c5f50f8566df7d770a82c51ef593d09e0b347020"}, - {file = "jiter-0.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7954a401d0a8a0b8bc669199db78af435aae1e3569187c2939c477c53cb6a0a"}, - {file = "jiter-0.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4feafe787eb8a8d98168ab15637ca2577f6ddf77ac6c8c66242c2d028aa5420e"}, - {file = "jiter-0.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:27cd1f2e8bb377f31d3190b34e4328d280325ad7ef55c6ac9abde72f79e84d2e"}, - {file = "jiter-0.9.0-cp39-cp39-win32.whl", hash = "sha256:161d461dcbe658cf0bd0aa375b30a968b087cdddc624fc585f3867c63c6eca95"}, - {file = "jiter-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e8b36d8a16a61993be33e75126ad3d8aa29cf450b09576f3c427d27647fcb4aa"}, - {file = "jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893"}, + {file = "jiter-0.10.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cd2fb72b02478f06a900a5782de2ef47e0396b3e1f7d5aba30daeb1fce66f303"}, + {file = "jiter-0.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32bb468e3af278f095d3fa5b90314728a6916d89ba3d0ffb726dd9bf7367285e"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8b3e0068c26ddedc7abc6fac37da2d0af16b921e288a5a613f4b86f050354f"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:286299b74cc49e25cd42eea19b72aa82c515d2f2ee12d11392c56d8701f52224"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ed5649ceeaeffc28d87fb012d25a4cd356dcd53eff5acff1f0466b831dda2a7"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2ab0051160cb758a70716448908ef14ad476c3774bd03ddce075f3c1f90a3d6"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03997d2f37f6b67d2f5c475da4412be584e1cec273c1cfc03d642c46db43f8cf"}, + {file = "jiter-0.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c404a99352d839fed80d6afd6c1d66071f3bacaaa5c4268983fc10f769112e90"}, + {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:66e989410b6666d3ddb27a74c7e50d0829704ede652fd4c858e91f8d64b403d0"}, + {file = "jiter-0.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b532d3af9ef4f6374609a3bcb5e05a1951d3bf6190dc6b176fdb277c9bbf15ee"}, + {file = "jiter-0.10.0-cp310-cp310-win32.whl", hash = "sha256:da9be20b333970e28b72edc4dff63d4fec3398e05770fb3205f7fb460eb48dd4"}, + {file = "jiter-0.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:f59e533afed0c5b0ac3eba20d2548c4a550336d8282ee69eb07b37ea526ee4e5"}, + {file = "jiter-0.10.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:3bebe0c558e19902c96e99217e0b8e8b17d570906e72ed8a87170bc290b1e978"}, + {file = "jiter-0.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:558cc7e44fd8e507a236bee6a02fa17199ba752874400a0ca6cd6e2196cdb7dc"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d613e4b379a07d7c8453c5712ce7014e86c6ac93d990a0b8e7377e18505e98d"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f62cf8ba0618eda841b9bf61797f21c5ebd15a7a1e19daab76e4e4b498d515b2"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:919d139cdfa8ae8945112398511cb7fca58a77382617d279556b344867a37e61"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13ddbc6ae311175a3b03bd8994881bc4635c923754932918e18da841632349db"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c440ea003ad10927a30521a9062ce10b5479592e8a70da27f21eeb457b4a9c5"}, + {file = "jiter-0.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dc347c87944983481e138dea467c0551080c86b9d21de6ea9306efb12ca8f606"}, + {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:13252b58c1f4d8c5b63ab103c03d909e8e1e7842d302473f482915d95fefd605"}, + {file = "jiter-0.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7d1bbf3c465de4a24ab12fb7766a0003f6f9bce48b8b6a886158c4d569452dc5"}, + {file = "jiter-0.10.0-cp311-cp311-win32.whl", hash = "sha256:db16e4848b7e826edca4ccdd5b145939758dadf0dc06e7007ad0e9cfb5928ae7"}, + {file = "jiter-0.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:9c9c1d5f10e18909e993f9641f12fe1c77b3e9b533ee94ffa970acc14ded3812"}, + {file = "jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b"}, + {file = "jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a"}, + {file = "jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95"}, + {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea"}, + {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b"}, + {file = "jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01"}, + {file = "jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49"}, + {file = "jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644"}, + {file = "jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041"}, + {file = "jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca"}, + {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4"}, + {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e"}, + {file = "jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d"}, + {file = "jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4"}, + {file = "jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca"}, + {file = "jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070"}, + {file = "jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca"}, + {file = "jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522"}, + {file = "jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9"}, + {file = "jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a"}, + {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853"}, + {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86"}, + {file = "jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357"}, + {file = "jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00"}, + {file = "jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5"}, + {file = "jiter-0.10.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:bd6292a43c0fc09ce7c154ec0fa646a536b877d1e8f2f96c19707f65355b5a4d"}, + {file = "jiter-0.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:39de429dcaeb6808d75ffe9effefe96a4903c6a4b376b2f6d08d77c1aaee2f18"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52ce124f13a7a616fad3bb723f2bfb537d78239d1f7f219566dc52b6f2a9e48d"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:166f3606f11920f9a1746b2eea84fa2c0a5d50fd313c38bdea4edc072000b0af"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:28dcecbb4ba402916034fc14eba7709f250c4d24b0c43fc94d187ee0580af181"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86c5aa6910f9bebcc7bc4f8bc461aff68504388b43bfe5e5c0bd21efa33b52f4"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ceeb52d242b315d7f1f74b441b6a167f78cea801ad7c11c36da77ff2d42e8a28"}, + {file = "jiter-0.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ff76d8887c8c8ee1e772274fcf8cc1071c2c58590d13e33bd12d02dc9a560397"}, + {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a9be4d0fa2b79f7222a88aa488bd89e2ae0a0a5b189462a12def6ece2faa45f1"}, + {file = "jiter-0.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab7fd8738094139b6c1ab1822d6f2000ebe41515c537235fd45dabe13ec9324"}, + {file = "jiter-0.10.0-cp39-cp39-win32.whl", hash = "sha256:5f51e048540dd27f204ff4a87f5d79294ea0aa3aa552aca34934588cf27023cf"}, + {file = "jiter-0.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b28302349dc65703a9e4ead16f163b1c339efffbe1049c30a44b001a2a4fff9"}, + {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"}, ] [[package]] @@ -2051,6 +2193,7 @@ version = "1.0.1" description = "JSON Matching Expressions" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, @@ -2058,13 +2201,14 @@ files = [ [[package]] name = "joblib" -version = "1.4.2" +version = "1.5.1" description = "Lightweight pipelining with Python functions" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, - {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, + {file = "joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a"}, + {file = "joblib-1.5.1.tar.gz", hash = "sha256:f4f86e351f39fe3d0d32a9f2c3d8af1ee4cec285aafcb27003dda5205576b444"}, ] [[package]] @@ -2073,6 +2217,7 @@ version = "0.12.0" description = "A Python implementation of the JSON5 data format." optional = false python-versions = ">=3.8.0" +groups = ["dev"] files = [ {file = "json5-0.12.0-py3-none-any.whl", hash = "sha256:6d37aa6c08b0609f16e1ec5ff94697e2cbbfbad5ac112afa05794da9ab7810db"}, {file = "json5-0.12.0.tar.gz", hash = "sha256:0b4b6ff56801a1c7dc817b0241bca4ce474a0e6a163bfef3fc594d3fd263ff3a"}, @@ -2087,6 +2232,7 @@ version = "1.33" description = "Apply JSON-Patches (RFC 6902)" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" +groups = ["main"] files = [ {file = "jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade"}, {file = "jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c"}, @@ -2101,6 +2247,7 @@ version = "3.0.0" description = "Identify specific nodes in a JSON document (RFC 6901)" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942"}, {file = "jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef"}, @@ -2108,13 +2255,14 @@ files = [ [[package]] name = "jsonschema" -version = "4.23.0" +version = "4.25.0" description = "An implementation of JSON Schema validation for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, - {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, + {file = "jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716"}, + {file = "jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f"}, ] [package.dependencies] @@ -2127,23 +2275,25 @@ jsonschema-specifications = ">=2023.03.6" referencing = ">=0.28.4" rfc3339-validator = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} rfc3986-validator = {version = ">0.1.0", optional = true, markers = "extra == \"format-nongpl\""} +rfc3987-syntax = {version = ">=1.1.0", optional = true, markers = "extra == \"format-nongpl\""} rpds-py = ">=0.7.1" uri-template = {version = "*", optional = true, markers = "extra == \"format-nongpl\""} webcolors = {version = ">=24.6.0", optional = true, markers = "extra == \"format-nongpl\""} [package.extras] format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] -format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "rfc3987-syntax (>=1.1.0)", "uri-template", "webcolors (>=24.6.0)"] [[package]] name = "jsonschema-specifications" -version = "2024.10.1" +version = "2025.4.1" description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf"}, - {file = "jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272"}, + {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, + {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, ] [package.dependencies] @@ -2155,6 +2305,7 @@ version = "1.1.1" description = "Jupyter metapackage. Install all the Jupyter components in one go." optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83"}, {file = "jupyter-1.1.1.tar.gz", hash = "sha256:d55467bceabdea49d7e3624af7e33d59c37fff53ed3a350e1ac957bed731de7a"}, @@ -2174,6 +2325,7 @@ version = "8.6.3" description = "Jupyter protocol implementation and client libraries" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "jupyter_client-8.6.3-py3-none-any.whl", hash = "sha256:e8a19cc986cc45905ac3362915f410f3af85424b4c0905e94fa5f2cb08e8f23f"}, {file = "jupyter_client-8.6.3.tar.gz", hash = "sha256:35b3a0947c4a6e9d589eb97d7d4cd5e90f910ee73101611f01283732bd6d9419"}, @@ -2196,6 +2348,7 @@ version = "6.6.3" description = "Jupyter terminal console" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485"}, {file = "jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539"}, @@ -2216,13 +2369,14 @@ test = ["flaky", "pexpect", "pytest"] [[package]] name = "jupyter-core" -version = "5.7.2" +version = "5.8.1" description = "Jupyter core package. A base package on which Jupyter projects rely." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, - {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, + {file = "jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0"}, + {file = "jupyter_core-5.8.1.tar.gz", hash = "sha256:0a5f9706f70e64786b75acba995988915ebd4601c8a52e534a40b51c95f59941"}, ] [package.dependencies] @@ -2231,8 +2385,8 @@ pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_ traitlets = ">=5.3" [package.extras] -docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] -test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] +docs = ["intersphinx-registry", "myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<9)", "pytest-cov", "pytest-timeout"] [[package]] name = "jupyter-events" @@ -2240,6 +2394,7 @@ version = "0.12.0" description = "Jupyter Event System library" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb"}, {file = "jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b"}, @@ -2262,27 +2417,29 @@ test = ["click", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.19.0)", "p [[package]] name = "jupyter-lsp" -version = "2.2.5" +version = "2.2.6" description = "Multi-Language Server WebSocket proxy for Jupyter Notebook/Lab server" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "jupyter-lsp-2.2.5.tar.gz", hash = "sha256:793147a05ad446f809fd53ef1cd19a9f5256fd0a2d6b7ce943a982cb4f545001"}, - {file = "jupyter_lsp-2.2.5-py3-none-any.whl", hash = "sha256:45fbddbd505f3fbfb0b6cb2f1bc5e15e83ab7c79cd6e89416b248cb3c00c11da"}, + {file = "jupyter_lsp-2.2.6-py3-none-any.whl", hash = "sha256:283783752bf0b459ee7fa88effa72104d87dd343b82d5c06cf113ef755b15b6d"}, + {file = "jupyter_lsp-2.2.6.tar.gz", hash = "sha256:0566bd9bb04fd9e6774a937ed01522b555ba78be37bebef787c8ab22de4c0361"}, ] [package.dependencies] -jupyter-server = ">=1.1.2" +jupyter_server = ">=1.1.2" [[package]] name = "jupyter-server" -version = "2.15.0" +version = "2.16.0" description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "jupyter_server-2.15.0-py3-none-any.whl", hash = "sha256:872d989becf83517012ee669f09604aa4a28097c0bd90b2f424310156c2cdae3"}, - {file = "jupyter_server-2.15.0.tar.gz", hash = "sha256:9d446b8697b4f7337a1b7cdcac40778babdd93ba614b6d68ab1c0c918f1c4084"}, + {file = "jupyter_server-2.16.0-py3-none-any.whl", hash = "sha256:3d8db5be3bc64403b1c65b400a1d7f4647a5ce743f3b20dbdefe8ddb7b55af9e"}, + {file = "jupyter_server-2.16.0.tar.gz", hash = "sha256:65d4b44fdf2dcbbdfe0aa1ace4a842d4aaf746a2b7b168134d5aaed35621b7f6"}, ] [package.dependencies] @@ -2316,6 +2473,7 @@ version = "0.5.3" description = "A Jupyter Server Extension Providing Terminals." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa"}, {file = "jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269"}, @@ -2331,13 +2489,14 @@ test = ["jupyter-server (>=2.0.0)", "pytest (>=7.0)", "pytest-jupyter[server] (> [[package]] name = "jupyterlab" -version = "4.3.6" +version = "4.4.5" description = "JupyterLab computational environment" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "jupyterlab-4.3.6-py3-none-any.whl", hash = "sha256:fc9eb0455562a56a9bd6d2977cf090842f321fa1a298fcee9bf8c19de353d5fd"}, - {file = "jupyterlab-4.3.6.tar.gz", hash = "sha256:2900ffdbfca9ed37c4ad7fdda3eb76582fd945d46962af3ac64741ae2d6b2ff4"}, + {file = "jupyterlab-4.4.5-py3-none-any.whl", hash = "sha256:e76244cceb2d1fb4a99341f3edc866f2a13a9e14c50368d730d75d8017be0863"}, + {file = "jupyterlab-4.4.5.tar.gz", hash = "sha256:0bd6c18e6a3c3d91388af6540afa3d0bb0b2e76287a7b88ddf20ab41b336e595"}, ] [package.dependencies] @@ -2351,14 +2510,14 @@ jupyter-server = ">=2.4.0,<3" jupyterlab-server = ">=2.27.1,<3" notebook-shim = ">=0.2" packaging = "*" -setuptools = ">=40.8.0" +setuptools = ">=41.1.0" tornado = ">=6.2.0" traitlets = "*" [package.extras] -dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.6.9)"] -docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-jupyter", "sphinx (>=1.8,<8.1.0)", "sphinx-copybutton"] -docs-screenshots = ["altair (==5.4.1)", "ipython (==8.16.1)", "ipywidgets (==8.1.5)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.2.post3)", "matplotlib (==3.9.2)", "nbconvert (>=7.0.0)", "pandas (==2.2.3)", "scipy (==1.14.1)", "vega-datasets (==0.9.0)"] +dev = ["build", "bump2version", "coverage", "hatch", "pre-commit", "pytest-cov", "ruff (==0.11.4)"] +docs = ["jsx-lexer", "myst-parser", "pydata-sphinx-theme (>=0.13.0)", "pytest", "pytest-check-links", "pytest-jupyter", "sphinx (>=1.8,<8.2.0)", "sphinx-copybutton"] +docs-screenshots = ["altair (==5.5.0)", "ipython (==8.16.1)", "ipywidgets (==8.1.5)", "jupyterlab-geojson (==3.4.0)", "jupyterlab-language-pack-zh-cn (==4.3.post1)", "matplotlib (==3.10.0)", "nbconvert (>=7.0.0)", "pandas (==2.2.3)", "scipy (==1.15.1)", "vega-datasets (==0.9.0)"] test = ["coverage", "pytest (>=7.0)", "pytest-check-links (>=0.7)", "pytest-console-scripts", "pytest-cov", "pytest-jupyter (>=0.5.3)", "pytest-timeout", "pytest-tornasync", "requests", "requests-cache", "virtualenv"] upgrade-extension = ["copier (>=9,<10)", "jinja2-time (<0.3)", "pydantic (<3.0)", "pyyaml-include (<3.0)", "tomli-w (<2.0)"] @@ -2368,6 +2527,7 @@ version = "0.3.0" description = "Pygments theme using JupyterLab CSS variables" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780"}, {file = "jupyterlab_pygments-0.3.0.tar.gz", hash = "sha256:721aca4d9029252b11cfa9d185e5b5af4d54772bb8072f9b7036f4170054d35d"}, @@ -2379,6 +2539,7 @@ version = "2.27.3" description = "A set of server components for JupyterLab and JupyterLab like applications." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "jupyterlab_server-2.27.3-py3-none-any.whl", hash = "sha256:e697488f66c3db49df675158a77b3b017520d772c6e1548c7d9bcc5df7944ee4"}, {file = "jupyterlab_server-2.27.3.tar.gz", hash = "sha256:eb36caca59e74471988f0ae25c77945610b887f777255aa21f8065def9e51ed4"}, @@ -2400,24 +2561,26 @@ test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-v [[package]] name = "jupyterlab-widgets" -version = "3.0.13" +version = "3.0.15" description = "Jupyter interactive widgets for JupyterLab" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "jupyterlab_widgets-3.0.13-py3-none-any.whl", hash = "sha256:e3cda2c233ce144192f1e29914ad522b2f4c40e77214b0cc97377ca3d323db54"}, - {file = "jupyterlab_widgets-3.0.13.tar.gz", hash = "sha256:a2966d385328c1942b683a8cd96b89b8dd82c8b8f81dda902bb2bc06d46f5bed"}, + {file = "jupyterlab_widgets-3.0.15-py3-none-any.whl", hash = "sha256:d59023d7d7ef71400d51e6fee9a88867f6e65e10a4201605d2d7f3e8f012a31c"}, + {file = "jupyterlab_widgets-3.0.15.tar.gz", hash = "sha256:2920888a0c2922351a9202817957a68c07d99673504d6cd37345299e971bb08b"}, ] [[package]] name = "kubernetes" -version = "32.0.1" +version = "33.1.0" description = "Kubernetes python client" optional = false python-versions = ">=3.6" +groups = ["main"] files = [ - {file = "kubernetes-32.0.1-py2.py3-none-any.whl", hash = "sha256:35282ab8493b938b08ab5526c7ce66588232df00ef5e1dbe88a419107dc10998"}, - {file = "kubernetes-32.0.1.tar.gz", hash = "sha256:42f43d49abd437ada79a79a16bd48a604d3471a117a8347e87db693f2ba0ba28"}, + {file = "kubernetes-33.1.0-py2.py3-none-any.whl", hash = "sha256:544de42b24b64287f7e0aa9513c93cb503f7f40eea39b20f66810011a86eabc5"}, + {file = "kubernetes-33.1.0.tar.gz", hash = "sha256:f64d829843a54c251061a8e7a14523b521f2dc5c896cf6d65ccf348648a88993"}, ] [package.dependencies] @@ -2438,19 +2601,20 @@ adal = ["adal (>=1.0.2)"] [[package]] name = "langchain" -version = "0.3.23" +version = "0.3.27" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.9" +groups = ["main"] files = [ - {file = "langchain-0.3.23-py3-none-any.whl", hash = "sha256:084f05ee7e80b7c3f378ebadd7309f2a37868ce2906fa0ae64365a67843ade3d"}, - {file = "langchain-0.3.23.tar.gz", hash = "sha256:d95004afe8abebb52d51d6026270248da3f4b53d93e9bf699f76005e0c83ad34"}, + {file = "langchain-0.3.27-py3-none-any.whl", hash = "sha256:7b20c4f338826acb148d885b20a73a16e410ede9ee4f19bb02011852d5f98798"}, + {file = "langchain-0.3.27.tar.gz", hash = "sha256:aa6f1e6274ff055d0fd36254176770f356ed0a8994297d1df47df341953cec62"}, ] [package.dependencies] -langchain-core = ">=0.3.51,<1.0.0" -langchain-text-splitters = ">=0.3.8,<1.0.0" -langsmith = ">=0.1.17,<0.4" +langchain-core = ">=0.3.72,<1.0.0" +langchain-text-splitters = ">=0.3.9,<1.0.0" +langsmith = ">=0.1.17" pydantic = ">=2.7.4,<3.0.0" PyYAML = ">=5.3" requests = ">=2,<3" @@ -2477,56 +2641,68 @@ xai = ["langchain-xai"] [[package]] name = "langchain-aws" -version = "0.2.18" +version = "0.2.30" description = "An integration package connecting AWS and LangChain" optional = false -python-versions = "<4.0,>=3.9" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "langchain_aws-0.2.18-py3-none-any.whl", hash = "sha256:9407322a0a94cce79e009bdadb64a83219ced87f6149bdc3bbba844768f2f223"}, - {file = "langchain_aws-0.2.18.tar.gz", hash = "sha256:5aa98c90eda244419a27a433739ff4cb22ade749efff3dc8f120d76d420102fb"}, + {file = "langchain_aws-0.2.30-py3-none-any.whl", hash = "sha256:947fe4ece30bde0a37ea721b87049d7642607bd2ba9d2d93c9890736972b4274"}, + {file = "langchain_aws-0.2.30.tar.gz", hash = "sha256:67c31f0784045a4a73ef78a2c18f392e14c1e9f7b55870e62f18039cadfb925c"}, ] [package.dependencies] -boto3 = ">=1.37.0" -langchain-core = ">=0.3.49,<0.4.0" +boto3 = ">=1.39.7" +langchain-core = ">=0.3.72,<0.4.0" numpy = {version = ">=1.26.0,<3", markers = "python_version >= \"3.12\""} pydantic = ">=2.10.0,<3" +[package.extras] +tools = ["beautifulsoup4 (>=4.13.4)", "bedrock-agentcore (>=0.1.0)", "playwright (>=1.53.0)"] + [[package]] name = "langchain-chroma" -version = "0.2.2" -description = "An integration package connecting Chroma and LangChain" +version = "0.2.5" +description = "An integration package connecting Chroma and LangChain." optional = false -python-versions = "<4,>=3.9" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "langchain_chroma-0.2.2-py3-none-any.whl", hash = "sha256:7766335f16975c2059bb6e8ea75a59a4082c52e6c9d66827681d1bce2c2756a2"}, - {file = "langchain_chroma-0.2.2.tar.gz", hash = "sha256:11225ca6077b2bf919b84d74e4d343121e077c0fa3274db1929a270fef9d1002"}, + {file = "langchain_chroma-0.2.5-py3-none-any.whl", hash = "sha256:dcb80330cd8ac9a87695a5ba237a15affeb805751bdb42dee213b12e0d379825"}, + {file = "langchain_chroma-0.2.5.tar.gz", hash = "sha256:b63e9df8c3fbb316313c232214b7bc8b696a505039a6f7adf29b992a74cc4b2e"}, ] [package.dependencies] -chromadb = ">=0.4.0,<0.5.4 || >0.5.4,<0.5.5 || >0.5.5,<0.5.7 || >0.5.7,<0.5.9 || >0.5.9,<0.5.10 || >0.5.10,<0.5.11 || >0.5.11,<0.5.12 || >0.5.12,<0.7.0" -langchain-core = ">=0.2.43,<0.3.0 || >0.3.0,<0.3.1 || >0.3.1,<0.3.2 || >0.3.2,<0.3.3 || >0.3.3,<0.3.4 || >0.3.4,<0.3.5 || >0.3.5,<0.3.6 || >0.3.6,<0.3.7 || >0.3.7,<0.3.8 || >0.3.8,<0.3.9 || >0.3.9,<0.3.10 || >0.3.10,<0.3.11 || >0.3.11,<0.3.12 || >0.3.12,<0.3.13 || >0.3.13,<0.3.14 || >0.3.14,<0.4.0" -numpy = {version = ">=1.26.2,<2.0.0", markers = "python_version >= \"3.12\""} +chromadb = ">=1.0.9" +langchain-core = ">=0.3.70" +numpy = [ + {version = ">=1.26.0", markers = "python_version < \"3.13\""}, + {version = ">=2.1.0", markers = "python_version >= \"3.13\""}, +] [[package]] name = "langchain-community" -version = "0.3.21" +version = "0.3.27" description = "Community contributed LangChain integrations." optional = false -python-versions = "<4.0,>=3.9" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "langchain_community-0.3.21-py3-none-any.whl", hash = "sha256:8cb9bbb7ef15e5eea776193528dd0e0e1299047146d0c78b6c696ae2dc62e81f"}, - {file = "langchain_community-0.3.21.tar.gz", hash = "sha256:b87b9992cbeea7553ed93e3d39faf9893a8690318485f7dc861751c7878729f7"}, + {file = "langchain_community-0.3.27-py3-none-any.whl", hash = "sha256:581f97b795f9633da738ea95da9cb78f8879b538090c9b7a68c0aed49c828f0d"}, + {file = "langchain_community-0.3.27.tar.gz", hash = "sha256:e1037c3b9da0c6d10bf06e838b034eb741e016515c79ef8f3f16e53ead33d882"}, ] [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" dataclasses-json = ">=0.5.7,<0.7" httpx-sse = ">=0.4.0,<1.0.0" -langchain = ">=0.3.23,<1.0.0" -langchain-core = ">=0.3.51,<1.0.0" -langsmith = ">=0.1.125,<0.4" -numpy = ">=1.26.2,<3" +langchain = ">=0.3.26,<1.0.0" +langchain-core = ">=0.3.66,<1.0.0" +langsmith = ">=0.1.125" +numpy = [ + {version = ">=1.26.2", markers = "python_version < \"3.13\""}, + {version = ">=2.1.0", markers = "python_version >= \"3.13\""}, +] pydantic-settings = ">=2.4.0,<3.0.0" PyYAML = ">=5.3" requests = ">=2,<3" @@ -2535,40 +2711,39 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10" [[package]] name = "langchain-core" -version = "0.3.51" +version = "0.3.72" description = "Building applications with LLMs through composability" optional = false -python-versions = "<4.0,>=3.9" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "langchain_core-0.3.51-py3-none-any.whl", hash = "sha256:4bd71e8acd45362aa428953f2a91d8162318014544a2216e4b769463caf68e13"}, - {file = "langchain_core-0.3.51.tar.gz", hash = "sha256:db76b9cc331411602cb40ba0469a161febe7a0663fbcaddbc9056046ac2d22f4"}, + {file = "langchain_core-0.3.72-py3-none-any.whl", hash = "sha256:9fa15d390600eb6b6544397a7aa84be9564939b6adf7a2b091179ea30405b240"}, + {file = "langchain_core-0.3.72.tar.gz", hash = "sha256:4de3828909b3d7910c313242ab07b241294650f5cb6eac17738dd3638b1cd7de"}, ] [package.dependencies] jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.1.125,<0.4" -packaging = ">=23.2,<25" -pydantic = [ - {version = ">=2.5.2,<3.0.0", markers = "python_full_version < \"3.12.4\""}, - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, -] +langsmith = ">=0.3.45" +packaging = ">=23.2" +pydantic = ">=2.7.4" PyYAML = ">=5.3" tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" typing-extensions = ">=4.7" [[package]] name = "langchain-text-splitters" -version = "0.3.8" +version = "0.3.9" description = "LangChain text splitting utilities" optional = false -python-versions = "<4.0,>=3.9" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "langchain_text_splitters-0.3.8-py3-none-any.whl", hash = "sha256:e75cc0f4ae58dcf07d9f18776400cf8ade27fadd4ff6d264df6278bb302f6f02"}, - {file = "langchain_text_splitters-0.3.8.tar.gz", hash = "sha256:116d4b9f2a22dda357d0b79e30acf005c5518177971c66a9f1ab0edfdb0f912e"}, + {file = "langchain_text_splitters-0.3.9-py3-none-any.whl", hash = "sha256:cee0bb816211584ea79cc79927317c358543f40404bcfdd69e69ba3ccde54401"}, + {file = "langchain_text_splitters-0.3.9.tar.gz", hash = "sha256:7cd1e5a3aaf609979583eeca2eb34177622570b8fa8f586a605c6b1c34e7ebdb"}, ] [package.dependencies] -langchain-core = ">=0.3.51,<1.0.0" +langchain-core = ">=0.3.72,<1.0.0" [[package]] name = "langchainhub" @@ -2576,6 +2751,7 @@ version = "0.1.21" description = "The LangChain Hub API client" optional = false python-versions = "<4.0,>=3.8.1" +groups = ["main"] files = [ {file = "langchainhub-0.1.21-py3-none-any.whl", hash = "sha256:1cc002dc31e0d132a776afd044361e2b698743df5202618cf2bad399246b895f"}, {file = "langchainhub-0.1.21.tar.gz", hash = "sha256:723383b3964a47dbaea6ad5d0ef728accefbc9d2c07480e800bdec43510a8c10"}, @@ -2588,32 +2764,49 @@ types-requests = ">=2.31.0.2,<3.0.0.0" [[package]] name = "langsmith" -version = "0.3.27" +version = "0.4.11" description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform." optional = false -python-versions = "<4.0,>=3.9" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "langsmith-0.3.27-py3-none-any.whl", hash = "sha256:060956aaed5f391a85829daa0c220b5e07b2e7dd5d33be4b92f280672be984f7"}, - {file = "langsmith-0.3.27.tar.gz", hash = "sha256:0bdeda73cf723cbcde1cab0f3459f7e5d5748db28a33bf9f6bdc0e2f4fe0ee1e"}, + {file = "langsmith-0.4.11-py3-none-any.whl", hash = "sha256:ce3a52809d37854bcb129affd8cd883025985891b82a46f2ae597c511283360d"}, + {file = "langsmith-0.4.11.tar.gz", hash = "sha256:2cb39a8af795ff4014e60ca07a73a9bcd89b67837193c9f6be22efb5c9c4c846"}, ] [package.dependencies] httpx = ">=0.23.0,<1" -orjson = {version = ">=3.9.14,<4.0.0", markers = "platform_python_implementation != \"PyPy\""} +orjson = {version = ">=3.9.14", markers = "platform_python_implementation != \"PyPy\""} packaging = ">=23.2" -pydantic = [ - {version = ">=1,<3", markers = "python_full_version < \"3.12.4\""}, - {version = ">=2.7.4,<3.0.0", markers = "python_full_version >= \"3.12.4\""}, -] -requests = ">=2,<3" -requests-toolbelt = ">=1.0.0,<2.0.0" -zstandard = ">=0.23.0,<0.24.0" +pydantic = ">=1,<3" +requests = ">=2.0.0" +requests-toolbelt = ">=1.0.0" +zstandard = ">=0.23.0" [package.extras] -langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] -openai-agents = ["openai-agents (>=0.0.3,<0.1)"] -otel = ["opentelemetry-api (>=1.30.0,<2.0.0)", "opentelemetry-exporter-otlp-proto-http (>=1.30.0,<2.0.0)", "opentelemetry-sdk (>=1.30.0,<2.0.0)"] -pytest = ["pytest (>=7.0.0)", "rich (>=13.9.4,<14.0.0)"] +langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2)"] +openai-agents = ["openai-agents (>=0.0.3)"] +otel = ["opentelemetry-api (>=1.30.0)", "opentelemetry-exporter-otlp-proto-http (>=1.30.0)", "opentelemetry-sdk (>=1.30.0)"] +pytest = ["pytest (>=7.0.0)", "rich (>=13.9.4)", "vcrpy (>=7.0.0)"] +vcr = ["vcrpy (>=7.0.0)"] + +[[package]] +name = "lark" +version = "1.2.2" +description = "a modern parsing library" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "lark-1.2.2-py3-none-any.whl", hash = "sha256:c2276486b02f0f1b90be155f2c8ba4a8e194d42775786db622faccd652d8e80c"}, + {file = "lark-1.2.2.tar.gz", hash = "sha256:ca807d0162cd16cef15a8feecb862d7319e7a09bdb13aef927968e45040fed80"}, +] + +[package.extras] +atomic-cache = ["atomicwrites"] +interegular = ["interegular (>=0.3.1,<0.4.0)"] +nearley = ["js2py"] +regex = ["regex"] [[package]] name = "markdown-it-py" @@ -2621,6 +2814,7 @@ version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -2645,6 +2839,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -2715,6 +2910,7 @@ version = "3.26.1" description = "A lightweight library for converting complex datatypes to and from native Python datatypes." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c"}, {file = "marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6"}, @@ -2734,6 +2930,7 @@ version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, @@ -2748,6 +2945,7 @@ version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, @@ -2759,6 +2957,7 @@ version = "3.1.3" description = "A sane and fast Markdown parser with useful plugins and renderers" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "mistune-3.1.3-py3-none-any.whl", hash = "sha256:1a32314113cff28aa6432e99e522677c8587fd83e3d51c29b82a52409c842bd9"}, {file = "mistune-3.1.3.tar.gz", hash = "sha256:a7035c21782b2becb6be62f8f25d3df81ccb4d6fa477a6525b15af06539f02a0"}, @@ -2766,112 +2965,142 @@ files = [ [[package]] name = "mmh3" -version = "5.1.0" +version = "5.2.0" description = "Python extension for MurmurHash (MurmurHash3), a set of fast and robust hash functions." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "mmh3-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:eaf4ac5c6ee18ca9232238364d7f2a213278ae5ca97897cafaa123fcc7bb8bec"}, - {file = "mmh3-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:48f9aa8ccb9ad1d577a16104834ac44ff640d8de8c0caed09a2300df7ce8460a"}, - {file = "mmh3-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d4ba8cac21e1f2d4e436ce03a82a7f87cda80378691f760e9ea55045ec480a3d"}, - {file = "mmh3-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69281c281cb01994f054d862a6bb02a2e7acfe64917795c58934b0872b9ece4"}, - {file = "mmh3-5.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4d05ed3962312fbda2a1589b97359d2467f677166952f6bd410d8c916a55febf"}, - {file = "mmh3-5.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78ae6a03f4cff4aa92ddd690611168856f8c33a141bd3e5a1e0a85521dc21ea0"}, - {file = "mmh3-5.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95f983535b39795d9fb7336438faae117424c6798f763d67c6624f6caf2c4c01"}, - {file = "mmh3-5.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d46fdd80d4c7ecadd9faa6181e92ccc6fe91c50991c9af0e371fdf8b8a7a6150"}, - {file = "mmh3-5.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0f16e976af7365ea3b5c425124b2a7f0147eed97fdbb36d99857f173c8d8e096"}, - {file = "mmh3-5.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6fa97f7d1e1f74ad1565127229d510f3fd65d931fdedd707c1e15100bc9e5ebb"}, - {file = "mmh3-5.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4052fa4a8561bd62648e9eb993c8f3af3bdedadf3d9687aa4770d10e3709a80c"}, - {file = "mmh3-5.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:3f0e8ae9f961037f812afe3cce7da57abf734285961fffbeff9a4c011b737732"}, - {file = "mmh3-5.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:99297f207db967814f1f02135bb7fe7628b9eacb046134a34e1015b26b06edce"}, - {file = "mmh3-5.1.0-cp310-cp310-win32.whl", hash = "sha256:2e6c8dc3631a5e22007fbdb55e993b2dbce7985c14b25b572dd78403c2e79182"}, - {file = "mmh3-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:e4e8c7ad5a4dddcfde35fd28ef96744c1ee0f9d9570108aa5f7e77cf9cfdf0bf"}, - {file = "mmh3-5.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:45da549269883208912868a07d0364e1418d8292c4259ca11699ba1b2475bd26"}, - {file = "mmh3-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b529dcda3f951ff363a51d5866bc6d63cf57f1e73e8961f864ae5010647079d"}, - {file = "mmh3-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db1079b3ace965e562cdfc95847312f9273eb2ad3ebea983435c8423e06acd7"}, - {file = "mmh3-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:22d31e3a0ff89b8eb3b826d6fc8e19532998b2aa6b9143698043a1268da413e1"}, - {file = "mmh3-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2139bfbd354cd6cb0afed51c4b504f29bcd687a3b1460b7e89498329cc28a894"}, - {file = "mmh3-5.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c8105c6a435bc2cd6ea2ef59558ab1a2976fd4a4437026f562856d08996673a"}, - {file = "mmh3-5.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57730067174a7f36fcd6ce012fe359bd5510fdaa5fe067bc94ed03e65dafb769"}, - {file = "mmh3-5.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde80eb196d7fdc765a318604ded74a4378f02c5b46c17aa48a27d742edaded2"}, - {file = "mmh3-5.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9c8eddcb441abddeb419c16c56fd74b3e2df9e57f7aa2903221996718435c7a"}, - {file = "mmh3-5.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:99e07e4acafbccc7a28c076a847fb060ffc1406036bc2005acb1b2af620e53c3"}, - {file = "mmh3-5.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e25ba5b530e9a7d65f41a08d48f4b3fedc1e89c26486361166a5544aa4cad33"}, - {file = "mmh3-5.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bb9bf7475b4d99156ce2f0cf277c061a17560c8c10199c910a680869a278ddc7"}, - {file = "mmh3-5.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a1b0878dd281ea3003368ab53ff6f568e175f1b39f281df1da319e58a19c23a"}, - {file = "mmh3-5.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:25f565093ac8b8aefe0f61f8f95c9a9d11dd69e6a9e9832ff0d293511bc36258"}, - {file = "mmh3-5.1.0-cp311-cp311-win32.whl", hash = "sha256:1e3554d8792387eac73c99c6eaea0b3f884e7130eb67986e11c403e4f9b6d372"}, - {file = "mmh3-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ad777a48197882492af50bf3098085424993ce850bdda406a358b6ab74be759"}, - {file = "mmh3-5.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f29dc4efd99bdd29fe85ed6c81915b17b2ef2cf853abf7213a48ac6fb3eaabe1"}, - {file = "mmh3-5.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45712987367cb9235026e3cbf4334670522a97751abfd00b5bc8bfa022c3311d"}, - {file = "mmh3-5.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b1020735eb35086ab24affbea59bb9082f7f6a0ad517cb89f0fc14f16cea4dae"}, - {file = "mmh3-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:babf2a78ce5513d120c358722a2e3aa7762d6071cd10cede026f8b32452be322"}, - {file = "mmh3-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4f47f58cd5cbef968c84a7c1ddc192fef0a36b48b0b8a3cb67354531aa33b00"}, - {file = "mmh3-5.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2044a601c113c981f2c1e14fa33adc9b826c9017034fe193e9eb49a6882dbb06"}, - {file = "mmh3-5.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94d999c9f2eb2da44d7c2826d3fbffdbbbbcde8488d353fee7c848ecc42b968"}, - {file = "mmh3-5.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a015dcb24fa0c7a78f88e9419ac74f5001c1ed6a92e70fd1803f74afb26a4c83"}, - {file = "mmh3-5.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:457da019c491a2d20e2022c7d4ce723675e4c081d9efc3b4d8b9f28a5ea789bd"}, - {file = "mmh3-5.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71408579a570193a4ac9c77344d68ddefa440b00468a0b566dcc2ba282a9c559"}, - {file = "mmh3-5.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8b3a04bc214a6e16c81f02f855e285c6df274a2084787eeafaa45f2fbdef1b63"}, - {file = "mmh3-5.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:832dae26a35514f6d3c1e267fa48e8de3c7b978afdafa0529c808ad72e13ada3"}, - {file = "mmh3-5.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bf658a61fc92ef8a48945ebb1076ef4ad74269e353fffcb642dfa0890b13673b"}, - {file = "mmh3-5.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3313577453582b03383731b66447cdcdd28a68f78df28f10d275d7d19010c1df"}, - {file = "mmh3-5.1.0-cp312-cp312-win32.whl", hash = "sha256:1d6508504c531ab86c4424b5a5ff07c1132d063863339cf92f6657ff7a580f76"}, - {file = "mmh3-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:aa75981fcdf3f21759d94f2c81b6a6e04a49dfbcdad88b152ba49b8e20544776"}, - {file = "mmh3-5.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4c1a76808dfea47f7407a0b07aaff9087447ef6280716fd0783409b3088bb3c"}, - {file = "mmh3-5.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a523899ca29cfb8a5239618474a435f3d892b22004b91779fcb83504c0d5b8c"}, - {file = "mmh3-5.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:17cef2c3a6ca2391ca7171a35ed574b5dab8398163129a3e3a4c05ab85a4ff40"}, - {file = "mmh3-5.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52e12895b30110f3d89dae59a888683cc886ed0472dd2eca77497edef6161997"}, - {file = "mmh3-5.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d6719045cda75c3f40397fc24ab67b18e0cb8f69d3429ab4c39763c4c608dd"}, - {file = "mmh3-5.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d19fa07d303a91f8858982c37e6939834cb11893cb3ff20e6ee6fa2a7563826a"}, - {file = "mmh3-5.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31b47a620d622fbde8ca1ca0435c5d25de0ac57ab507209245e918128e38e676"}, - {file = "mmh3-5.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f810647c22c179b6821079f7aa306d51953ac893587ee09cf1afb35adf87cb"}, - {file = "mmh3-5.1.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6128b610b577eed1e89ac7177ab0c33d06ade2aba93f5c89306032306b5f1c6"}, - {file = "mmh3-5.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1e550a45d2ff87a1c11b42015107f1778c93f4c6f8e731bf1b8fa770321b8cc4"}, - {file = "mmh3-5.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:785ae09276342f79fd8092633e2d52c0f7c44d56e8cfda8274ccc9b76612dba2"}, - {file = "mmh3-5.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0f4be3703a867ef976434afd3661a33884abe73ceb4ee436cac49d3b4c2aaa7b"}, - {file = "mmh3-5.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e513983830c4ff1f205ab97152a0050cf7164f1b4783d702256d39c637b9d107"}, - {file = "mmh3-5.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9135c300535c828c0bae311b659f33a31c941572eae278568d1a953c4a57b59"}, - {file = "mmh3-5.1.0-cp313-cp313-win32.whl", hash = "sha256:c65dbd12885a5598b70140d24de5839551af5a99b29f9804bb2484b29ef07692"}, - {file = "mmh3-5.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:10db7765201fc65003fa998faa067417ef6283eb5f9bba8f323c48fd9c33e91f"}, - {file = "mmh3-5.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:b22fe2e54be81f6c07dcb36b96fa250fb72effe08aa52fbb83eade6e1e2d5fd7"}, - {file = "mmh3-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:166b67749a1d8c93b06f5e90576f1ba838a65c8e79f28ffd9dfafba7c7d0a084"}, - {file = "mmh3-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:adba83c7ba5cc8ea201ee1e235f8413a68e7f7b8a657d582cc6c6c9d73f2830e"}, - {file = "mmh3-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a61f434736106804eb0b1612d503c4e6eb22ba31b16e6a2f987473de4226fa55"}, - {file = "mmh3-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba9ce59816b30866093f048b3312c2204ff59806d3a02adee71ff7bd22b87554"}, - {file = "mmh3-5.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd51597bef1e503363b05cb579db09269e6e6c39d419486626b255048daf545b"}, - {file = "mmh3-5.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d51a1ed642d3fb37b8f4cab966811c52eb246c3e1740985f701ef5ad4cdd2145"}, - {file = "mmh3-5.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:709bfe81c53bf8a3609efcbd65c72305ade60944f66138f697eefc1a86b6e356"}, - {file = "mmh3-5.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e01a9b0092b6f82e861137c8e9bb9899375125b24012eb5219e61708be320032"}, - {file = "mmh3-5.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:27e46a2c13c9a805e03c9ec7de0ca8e096794688ab2125bdce4229daf60c4a56"}, - {file = "mmh3-5.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5766299c1d26f6bfd0a638e070bd17dbd98d4ccb067d64db3745bf178e700ef0"}, - {file = "mmh3-5.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7785205e3e4443fdcbb73766798c7647f94c2f538b90f666688f3e757546069e"}, - {file = "mmh3-5.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:8e574fbd39afb433b3ab95683b1b4bf18313dc46456fc9daaddc2693c19ca565"}, - {file = "mmh3-5.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1b6727a5a20e32cbf605743749f3862abe5f5e097cbf2afc7be5aafd32a549ae"}, - {file = "mmh3-5.1.0-cp39-cp39-win32.whl", hash = "sha256:d6eaa711d4b9220fe5252032a44bf68e5dcfb7b21745a96efc9e769b0dd57ec2"}, - {file = "mmh3-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:49d444913f6c02980e5241a53fe9af2338f2043d6ce5b6f5ea7d302c52c604ac"}, - {file = "mmh3-5.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:0daaeaedd78773b70378f2413c7d6b10239a75d955d30d54f460fb25d599942d"}, - {file = "mmh3-5.1.0.tar.gz", hash = "sha256:136e1e670500f177f49ec106a4ebf0adf20d18d96990cc36ea492c651d2b406c"}, + {file = "mmh3-5.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:81c504ad11c588c8629536b032940f2a359dda3b6cbfd4ad8f74cb24dcd1b0bc"}, + {file = "mmh3-5.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0b898cecff57442724a0f52bf42c2de42de63083a91008fb452887e372f9c328"}, + {file = "mmh3-5.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be1374df449465c9f2500e62eee73a39db62152a8bdfbe12ec5b5c1cd451344d"}, + {file = "mmh3-5.2.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b0d753ad566c721faa33db7e2e0eddd74b224cdd3eaf8481d76c926603c7a00e"}, + {file = "mmh3-5.2.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dfbead5575f6470c17e955b94f92d62a03dfc3d07f2e6f817d9b93dc211a1515"}, + {file = "mmh3-5.2.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7434a27754049144539d2099a6d2da5d88b8bdeedf935180bf42ad59b3607aa3"}, + {file = "mmh3-5.2.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cadc16e8ea64b5d9a47363013e2bea469e121e6e7cb416a7593aeb24f2ad122e"}, + {file = "mmh3-5.2.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d765058da196f68dc721116cab335e696e87e76720e6ef8ee5a24801af65e63d"}, + {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8b0c53fe0994beade1ad7c0f13bd6fec980a0664bfbe5a6a7d64500b9ab76772"}, + {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:49037d417419863b222ae47ee562b2de9c3416add0a45c8d7f4e864be8dc4f89"}, + {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:6ecb4e750d712abde046858ee6992b65c93f1f71b397fce7975c3860c07365d2"}, + {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:382a6bb3f8c6532ea084e7acc5be6ae0c6effa529240836d59352398f002e3fc"}, + {file = "mmh3-5.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7733ec52296fc1ba22e9b90a245c821adbb943e98c91d8a330a2254612726106"}, + {file = "mmh3-5.2.0-cp310-cp310-win32.whl", hash = "sha256:127c95336f2a98c51e7682341ab7cb0be3adb9df0819ab8505a726ed1801876d"}, + {file = "mmh3-5.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:419005f84ba1cab47a77465a2a843562dadadd6671b8758bf179d82a15ca63eb"}, + {file = "mmh3-5.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:d22c9dcafed659fadc605538946c041722b6d1104fe619dbf5cc73b3c8a0ded8"}, + {file = "mmh3-5.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7901c893e704ee3c65f92d39b951f8f34ccf8e8566768c58103fb10e55afb8c1"}, + {file = "mmh3-5.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5f5536b1cbfa72318ab3bfc8a8188b949260baed186b75f0abc75b95d8c051"}, + {file = "mmh3-5.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cedac4f4054b8f7859e5aed41aaa31ad03fce6851901a7fdc2af0275ac533c10"}, + {file = "mmh3-5.2.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eb756caf8975882630ce4e9fbbeb9d3401242a72528230422c9ab3a0d278e60c"}, + {file = "mmh3-5.2.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:097e13c8b8a66c5753c6968b7640faefe85d8e38992703c1f666eda6ef4c3762"}, + {file = "mmh3-5.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7c0c7845566b9686480e6a7e9044db4afb60038d5fabd19227443f0104eeee4"}, + {file = "mmh3-5.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:61ac226af521a572700f863d6ecddc6ece97220ce7174e311948ff8c8919a363"}, + {file = "mmh3-5.2.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:582f9dbeefe15c32a5fa528b79b088b599a1dfe290a4436351c6090f90ddebb8"}, + {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ebfc46b39168ab1cd44670a32ea5489bcbc74a25795c61b6d888c5c2cf654ed"}, + {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1556e31e4bd0ac0c17eaf220be17a09c171d7396919c3794274cb3415a9d3646"}, + {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81df0dae22cd0da87f1c978602750f33d17fb3d21fb0f326c89dc89834fea79b"}, + {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:eba01ec3bd4a49b9ac5ca2bc6a73ff5f3af53374b8556fcc2966dd2af9eb7779"}, + {file = "mmh3-5.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9a011469b47b752e7d20de296bb34591cdfcbe76c99c2e863ceaa2aa61113d2"}, + {file = "mmh3-5.2.0-cp311-cp311-win32.whl", hash = "sha256:bc44fc2b886243d7c0d8daeb37864e16f232e5b56aaec27cc781d848264cfd28"}, + {file = "mmh3-5.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ebf241072cf2777a492d0e09252f8cc2b3edd07dfdb9404b9757bffeb4f2cee"}, + {file = "mmh3-5.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5f317a727bba0e633a12e71228bc6a4acb4f471a98b1c003163b917311ea9a9"}, + {file = "mmh3-5.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:384eda9361a7bf83a85e09447e1feafe081034af9dd428893701b959230d84be"}, + {file = "mmh3-5.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c9da0d568569cc87315cb063486d761e38458b8ad513fedd3dc9263e1b81bcd"}, + {file = "mmh3-5.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86d1be5d63232e6eb93c50881aea55ff06eb86d8e08f9b5417c8c9b10db9db96"}, + {file = "mmh3-5.2.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf7bee43e17e81671c447e9c83499f53d99bf440bc6d9dc26a841e21acfbe094"}, + {file = "mmh3-5.2.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7aa18cdb58983ee660c9c400b46272e14fa253c675ed963d3812487f8ca42037"}, + {file = "mmh3-5.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9d032488fcec32d22be6542d1a836f00247f40f320844dbb361393b5b22773"}, + {file = "mmh3-5.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1861fb6b1d0453ed7293200139c0a9011eeb1376632e048e3766945b13313c5"}, + {file = "mmh3-5.2.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:99bb6a4d809aa4e528ddfe2c85dd5239b78b9dd14be62cca0329db78505e7b50"}, + {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1f8d8b627799f4e2fcc7c034fed8f5f24dc7724ff52f69838a3d6d15f1ad4765"}, + {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5995088dd7023d2d9f310a0c67de5a2b2e06a570ecfd00f9ff4ab94a67cde43"}, + {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1a5f4d2e59d6bba8ef01b013c472741835ad961e7c28f50c82b27c57748744a4"}, + {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fd6e6c3d90660d085f7e73710eab6f5545d4854b81b0135a3526e797009dbda3"}, + {file = "mmh3-5.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c4a2f3d83879e3de2eb8cbf562e71563a8ed15ee9b9c2e77ca5d9f73072ac15c"}, + {file = "mmh3-5.2.0-cp312-cp312-win32.whl", hash = "sha256:2421b9d665a0b1ad724ec7332fb5a98d075f50bc51a6ff854f3a1882bd650d49"}, + {file = "mmh3-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d80005b7634a3a2220f81fbeb94775ebd12794623bb2e1451701ea732b4aa3"}, + {file = "mmh3-5.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:3d6bfd9662a20c054bc216f861fa330c2dac7c81e7fb8307b5e32ab5b9b4d2e0"}, + {file = "mmh3-5.2.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:e79c00eba78f7258e5b354eccd4d7907d60317ced924ea4a5f2e9d83f5453065"}, + {file = "mmh3-5.2.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:956127e663d05edbeec54df38885d943dfa27406594c411139690485128525de"}, + {file = "mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:c3dca4cb5b946ee91b3d6bb700d137b1cd85c20827f89fdf9c16258253489044"}, + {file = "mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e651e17bfde5840e9e4174b01e9e080ce49277b70d424308b36a7969d0d1af73"}, + {file = "mmh3-5.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:9f64bf06f4bf623325fda3a6d02d36cd69199b9ace99b04bb2d7fd9f89688504"}, + {file = "mmh3-5.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ddc63328889bcaee77b743309e5c7d2d52cee0d7d577837c91b6e7cc9e755e0b"}, + {file = "mmh3-5.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bb0fdc451fb6d86d81ab8f23d881b8d6e37fc373a2deae1c02d27002d2ad7a05"}, + {file = "mmh3-5.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b29044e1ffdb84fe164d0a7ea05c7316afea93c00f8ed9449cf357c36fc4f814"}, + {file = "mmh3-5.2.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:58981d6ea9646dbbf9e59a30890cbf9f610df0e4a57dbfe09215116fd90b0093"}, + {file = "mmh3-5.2.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e5634565367b6d98dc4aa2983703526ef556b3688ba3065edb4b9b90ede1c54"}, + {file = "mmh3-5.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0271ac12415afd3171ab9a3c7cbfc71dee2c68760a7dc9d05bf8ed6ddfa3a7a"}, + {file = "mmh3-5.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:45b590e31bc552c6f8e2150ff1ad0c28dd151e9f87589e7eaf508fbdd8e8e908"}, + {file = "mmh3-5.2.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bdde97310d59604f2a9119322f61b31546748499a21b44f6715e8ced9308a6c5"}, + {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc9c5f280438cf1c1a8f9abb87dc8ce9630a964120cfb5dd50d1e7ce79690c7a"}, + {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c903e71fd8debb35ad2a4184c1316b3cb22f64ce517b4e6747f25b0a34e41266"}, + {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:eed4bba7ff8a0d37106ba931ab03bdd3915fbb025bcf4e1f0aa02bc8114960c5"}, + {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1fdb36b940e9261aff0b5177c5b74a36936b902f473180f6c15bde26143681a9"}, + {file = "mmh3-5.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7303aab41e97adcf010a09efd8f1403e719e59b7705d5e3cfed3dd7571589290"}, + {file = "mmh3-5.2.0-cp313-cp313-win32.whl", hash = "sha256:03e08c6ebaf666ec1e3d6ea657a2d363bb01effd1a9acfe41f9197decaef0051"}, + {file = "mmh3-5.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:7fddccd4113e7b736706e17a239a696332360cbaddf25ae75b57ba1acce65081"}, + {file = "mmh3-5.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa0c966ee727aad5406d516375593c5f058c766b21236ab8985693934bb5085b"}, + {file = "mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e5015f0bb6eb50008bed2d4b1ce0f2a294698a926111e4bb202c0987b4f89078"}, + {file = "mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e0f3ed828d709f5b82d8bfe14f8856120718ec4bd44a5b26102c3030a1e12501"}, + {file = "mmh3-5.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:f35727c5118aba95f0397e18a1a5b8405425581bfe53e821f0fb444cbdc2bc9b"}, + {file = "mmh3-5.2.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bc244802ccab5220008cb712ca1508cb6a12f0eb64ad62997156410579a1770"}, + {file = "mmh3-5.2.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ff3d50dc3fe8a98059f99b445dfb62792b5d006c5e0b8f03c6de2813b8376110"}, + {file = "mmh3-5.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:37a358cc881fe796e099c1db6ce07ff757f088827b4e8467ac52b7a7ffdca647"}, + {file = "mmh3-5.2.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b9a87025121d1c448f24f27ff53a5fe7b6ef980574b4a4f11acaabe702420d63"}, + {file = "mmh3-5.2.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ba55d6ca32eeef8b2625e1e4bfc3b3db52bc63014bd7e5df8cc11bf2b036b12"}, + {file = "mmh3-5.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9ff37ba9f15637e424c2ab57a1a590c52897c845b768e4e0a4958084ec87f22"}, + {file = "mmh3-5.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a094319ec0db52a04af9fdc391b4d39a1bc72bc8424b47c4411afb05413a44b5"}, + {file = "mmh3-5.2.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5584061fd3da584659b13587f26c6cad25a096246a481636d64375d0c1f6c07"}, + {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecbfc0437ddfdced5e7822d1ce4855c9c64f46819d0fdc4482c53f56c707b935"}, + {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7b986d506a8e8ea345791897ba5d8ba0d9d8820cd4fc3e52dbe6de19388de2e7"}, + {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:38d899a156549da8ef6a9f1d6f7ef231228d29f8f69bce2ee12f5fba6d6fd7c5"}, + {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d86651fa45799530885ba4dab3d21144486ed15285e8784181a0ab37a4552384"}, + {file = "mmh3-5.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c463d7c1c4cfc9d751efeaadd936bbba07b5b0ed81a012b3a9f5a12f0872bd6e"}, + {file = "mmh3-5.2.0-cp314-cp314-win32.whl", hash = "sha256:bb4fe46bdc6104fbc28db7a6bacb115ee6368ff993366bbd8a2a7f0076e6f0c0"}, + {file = "mmh3-5.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c7f0b342fd06044bedd0b6e72177ddc0076f54fd89ee239447f8b271d919d9b"}, + {file = "mmh3-5.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:3193752fc05ea72366c2b63ff24b9a190f422e32d75fdeae71087c08fff26115"}, + {file = "mmh3-5.2.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:69fc339d7202bea69ef9bd7c39bfdf9fdabc8e6822a01eba62fb43233c1b3932"}, + {file = "mmh3-5.2.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:12da42c0a55c9d86ab566395324213c319c73ecb0c239fad4726324212b9441c"}, + {file = "mmh3-5.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7f9034c7cf05ddfaac8d7a2e63a3c97a840d4615d0a0e65ba8bdf6f8576e3be"}, + {file = "mmh3-5.2.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11730eeb16dfcf9674fdea9bb6b8e6dd9b40813b7eb839bc35113649eef38aeb"}, + {file = "mmh3-5.2.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:932a6eec1d2e2c3c9e630d10f7128d80e70e2d47fe6b8c7ea5e1afbd98733e65"}, + {file = "mmh3-5.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ca975c51c5028947bbcfc24966517aac06a01d6c921e30f7c5383c195f87991"}, + {file = "mmh3-5.2.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5b0b58215befe0f0e120b828f7645e97719bbba9f23b69e268ed0ac7adde8645"}, + {file = "mmh3-5.2.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29c2b9ce61886809d0492a274a5a53047742dea0f703f9c4d5d223c3ea6377d3"}, + {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a367d4741ac0103f8198c82f429bccb9359f543ca542b06a51f4f0332e8de279"}, + {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5a5dba98e514fb26241868f6eb90a7f7ca0e039aed779342965ce24ea32ba513"}, + {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:941603bfd75a46023807511c1ac2f1b0f39cccc393c15039969806063b27e6db"}, + {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:132dd943451a7c7546978863d2f5a64977928410782e1a87d583cb60eb89e667"}, + {file = "mmh3-5.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f698733a8a494466432d611a8f0d1e026f5286dee051beea4b3c3146817e35d5"}, + {file = "mmh3-5.2.0-cp314-cp314t-win32.whl", hash = "sha256:6d541038b3fc360ec538fc116de87462627944765a6750308118f8b509a8eec7"}, + {file = "mmh3-5.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e912b19cf2378f2967d0c08e86ff4c6c360129887f678e27e4dde970d21b3f4d"}, + {file = "mmh3-5.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:e7884931fe5e788163e7b3c511614130c2c59feffdc21112290a194487efb2e9"}, + {file = "mmh3-5.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3c6041fd9d5fb5fcac57d5c80f521a36b74aea06b8566431c63e4ffc49aced51"}, + {file = "mmh3-5.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:58477cf9ef16664d1ce2b038f87d2dc96d70fe50733a34a7f07da6c9a5e3538c"}, + {file = "mmh3-5.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:be7d3dca9358e01dab1bad881fb2b4e8730cec58d36dd44482bc068bfcd3bc65"}, + {file = "mmh3-5.2.0-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:931d47e08c9c8a67bf75d82f0ada8399eac18b03388818b62bfa42882d571d72"}, + {file = "mmh3-5.2.0-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dd966df3489ec13848d6c6303429bbace94a153f43d1ae2a55115fd36fd5ca5d"}, + {file = "mmh3-5.2.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c677d78887244bf3095020b73c42b505b700f801c690f8eaa90ad12d3179612f"}, + {file = "mmh3-5.2.0-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63830f846797187c5d3e2dae50f0848fdc86032f5bfdc58ae352f02f857e9025"}, + {file = "mmh3-5.2.0-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c3f563e8901960e2eaa64c8e8821895818acabeb41c96f2efbb936f65dbe486c"}, + {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:96f1e1ac44cbb42bcc406e509f70c9af42c594e72ccc7b1257f97554204445f0"}, + {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7bbb0df897944b5ec830f3ad883e32c5a7375370a521565f5fe24443bfb2c4f7"}, + {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:1fae471339ae1b9c641f19cf46dfe6ffd7f64b1fba7c4333b99fa3dd7f21ae0a"}, + {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:aa6e5d31fdc5ed9e3e95f9873508615a778fe9b523d52c17fc770a3eb39ab6e4"}, + {file = "mmh3-5.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:746a5ee71c6d1103d9b560fa147881b5e68fd35da56e54e03d5acefad0e7c055"}, + {file = "mmh3-5.2.0-cp39-cp39-win32.whl", hash = "sha256:10983c10f5c77683bd845751905ba535ec47409874acc759d5ce3ff7ef34398a"}, + {file = "mmh3-5.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fdfd3fb739f4e22746e13ad7ba0c6eedf5f454b18d11249724a388868e308ee4"}, + {file = "mmh3-5.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:33576136c06b46a7046b6d83a3d75fbca7d25f84cec743f1ae156362608dc6d2"}, + {file = "mmh3-5.2.0.tar.gz", hash = "sha256:1efc8fec8478e9243a78bb993422cf79f8ff85cb4cf6b79647480a31e0d950a8"}, ] [package.extras] -benchmark = ["pymmh3 (==0.0.5)", "pyperf (==2.8.1)", "xxhash (==3.5.0)"] -docs = ["myst-parser (==4.0.0)", "shibuya (==2024.12.21)", "sphinx (==8.1.3)", "sphinx-copybutton (==0.5.2)"] -lint = ["black (==24.10.0)", "clang-format (==19.1.7)", "isort (==5.13.2)", "pylint (==3.3.3)"] -plot = ["matplotlib (==3.10.0)", "pandas (==2.2.3)"] -test = ["pytest (==8.3.4)", "pytest-sugar (==1.0.0)"] -type = ["mypy (==1.14.1)"] - -[[package]] -name = "monotonic" -version = "1.6" -description = "An implementation of time.monotonic() for Python 2 & < 3.3" -optional = false -python-versions = "*" -files = [ - {file = "monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c"}, - {file = "monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7"}, -] +benchmark = ["pymmh3 (==0.0.5)", "pyperf (==2.9.0)", "xxhash (==3.5.0)"] +docs = ["myst-parser (==4.0.1)", "shibuya (==2025.7.24)", "sphinx (==8.2.3)", "sphinx-copybutton (==0.5.2)"] +lint = ["black (==25.1.0)", "clang-format (==20.1.8)", "isort (==6.0.1)", "pylint (==3.3.7)"] +plot = ["matplotlib (==3.10.3)", "pandas (==2.3.1)"] +test = ["pytest (==8.4.1)", "pytest-sugar (==1.0.0)"] +type = ["mypy (==1.17.0)"] [[package]] name = "mpmath" @@ -2879,6 +3108,7 @@ version = "1.3.0" description = "Python library for arbitrary-precision floating-point arithmetic" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, @@ -2892,148 +3122,175 @@ tests = ["pytest (>=4.6)"] [[package]] name = "multidict" -version = "6.2.0" +version = "6.6.3" description = "multidict implementation" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "multidict-6.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b9f6392d98c0bd70676ae41474e2eecf4c7150cb419237a41f8f96043fcb81d1"}, - {file = "multidict-6.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3501621d5e86f1a88521ea65d5cad0a0834c77b26f193747615b7c911e5422d2"}, - {file = "multidict-6.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32ed748ff9ac682eae7859790d3044b50e3076c7d80e17a44239683769ff485e"}, - {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc826b9a8176e686b67aa60fd6c6a7047b0461cae5591ea1dc73d28f72332a8a"}, - {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:214207dcc7a6221d9942f23797fe89144128a71c03632bf713d918db99bd36de"}, - {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05fefbc3cddc4e36da209a5e49f1094bbece9a581faa7f3589201fd95df40e5d"}, - {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e851e6363d0dbe515d8de81fd544a2c956fdec6f8a049739562286727d4a00c3"}, - {file = "multidict-6.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32c9b4878f48be3e75808ea7e499d6223b1eea6d54c487a66bc10a1871e3dc6a"}, - {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7243c5a6523c5cfeca76e063efa5f6a656d1d74c8b1fc64b2cd1e84e507f7e2a"}, - {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0e5a644e50ef9fb87878d4d57907f03a12410d2aa3b93b3acdf90a741df52c49"}, - {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0dc25a3293c50744796e87048de5e68996104d86d940bb24bc3ec31df281b191"}, - {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a49994481b99cd7dedde07f2e7e93b1d86c01c0fca1c32aded18f10695ae17eb"}, - {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:641cf2e3447c9ecff2f7aa6e9eee9eaa286ea65d57b014543a4911ff2799d08a"}, - {file = "multidict-6.2.0-cp310-cp310-win32.whl", hash = "sha256:0c383d28857f66f5aebe3e91d6cf498da73af75fbd51cedbe1adfb85e90c0460"}, - {file = "multidict-6.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:a33273a541f1e1a8219b2a4ed2de355848ecc0254264915b9290c8d2de1c74e1"}, - {file = "multidict-6.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e87a7d75fa36839a3a432286d719975362d230c70ebfa0948549cc38bd5b46"}, - {file = "multidict-6.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8de4d42dffd5ced9117af2ce66ba8722402541a3aa98ffdf78dde92badb68932"}, - {file = "multidict-6.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d91a230c7f8af86c904a5a992b8c064b66330544693fd6759c3d6162382ecf"}, - {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f6cad071960ba1914fa231677d21b1b4a3acdcce463cee41ea30bc82e6040cf"}, - {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f74f2fc51555f4b037ef278efc29a870d327053aba5cb7d86ae572426c7cccc"}, - {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14ed9ed1bfedd72a877807c71113deac292bf485159a29025dfdc524c326f3e1"}, - {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac3fcf9a2d369bd075b2c2965544036a27ccd277fc3c04f708338cc57533081"}, - {file = "multidict-6.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fc6af8e39f7496047c7876314f4317736eac82bf85b54c7c76cf1a6f8e35d98"}, - {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f8cb1329f42fadfb40d6211e5ff568d71ab49be36e759345f91c69d1033d633"}, - {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5389445f0173c197f4a3613713b5fb3f3879df1ded2a1a2e4bc4b5b9c5441b7e"}, - {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:94a7bb972178a8bfc4055db80c51efd24baefaced5e51c59b0d598a004e8305d"}, - {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da51d8928ad8b4244926fe862ba1795f0b6e68ed8c42cd2f822d435db9c2a8f4"}, - {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:063be88bd684782a0715641de853e1e58a2f25b76388538bd62d974777ce9bc2"}, - {file = "multidict-6.2.0-cp311-cp311-win32.whl", hash = "sha256:52b05e21ff05729fbea9bc20b3a791c3c11da61649ff64cce8257c82a020466d"}, - {file = "multidict-6.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1e2a2193d3aa5cbf5758f6d5680a52aa848e0cf611da324f71e5e48a9695cc86"}, - {file = "multidict-6.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:437c33561edb6eb504b5a30203daf81d4a9b727e167e78b0854d9a4e18e8950b"}, - {file = "multidict-6.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9f49585f4abadd2283034fc605961f40c638635bc60f5162276fec075f2e37a4"}, - {file = "multidict-6.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5dd7106d064d05896ce28c97da3f46caa442fe5a43bc26dfb258e90853b39b44"}, - {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b11a0417475f093d0f0809a149aff3943c2c56da50fdf2c3c88d57fe3dfbd"}, - {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac380cacdd3b183338ba63a144a34e9044520a6fb30c58aa14077157a033c13e"}, - {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61d5541f27533f803a941d3a3f8a3d10ed48c12cf918f557efcbf3cd04ef265c"}, - {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:facaf11f21f3a4c51b62931feb13310e6fe3475f85e20d9c9fdce0d2ea561b87"}, - {file = "multidict-6.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:095a2eabe8c43041d3e6c2cb8287a257b5f1801c2d6ebd1dd877424f1e89cf29"}, - {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0cc398350ef31167e03f3ca7c19313d4e40a662adcb98a88755e4e861170bdd"}, - {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7c611345bbe7cb44aabb877cb94b63e86f2d0db03e382667dbd037866d44b4f8"}, - {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8cd1a0644ccaf27e9d2f6d9c9474faabee21f0578fe85225cc5af9a61e1653df"}, - {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:89b3857652183b8206a891168af47bac10b970d275bba1f6ee46565a758c078d"}, - {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:125dd82b40f8c06d08d87b3510beaccb88afac94e9ed4a6f6c71362dc7dbb04b"}, - {file = "multidict-6.2.0-cp312-cp312-win32.whl", hash = "sha256:76b34c12b013d813e6cb325e6bd4f9c984db27758b16085926bbe7ceeaace626"}, - {file = "multidict-6.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:0b183a959fb88ad1be201de2c4bdf52fa8e46e6c185d76201286a97b6f5ee65c"}, - {file = "multidict-6.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5c5e7d2e300d5cb3b2693b6d60d3e8c8e7dd4ebe27cd17c9cb57020cac0acb80"}, - {file = "multidict-6.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:256d431fe4583c5f1e0f2e9c4d9c22f3a04ae96009b8cfa096da3a8723db0a16"}, - {file = "multidict-6.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a3c0ff89fe40a152e77b191b83282c9664357dce3004032d42e68c514ceff27e"}, - {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef7d48207926edbf8b16b336f779c557dd8f5a33035a85db9c4b0febb0706817"}, - {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c099d3899b14e1ce52262eb82a5f5cb92157bb5106bf627b618c090a0eadc"}, - {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e16e7297f29a544f49340012d6fc08cf14de0ab361c9eb7529f6a57a30cbfda1"}, - {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:042028348dc5a1f2be6c666437042a98a5d24cee50380f4c0902215e5ec41844"}, - {file = "multidict-6.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08549895e6a799bd551cf276f6e59820aa084f0f90665c0f03dd3a50db5d3c48"}, - {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ccfd74957ef53fa7380aaa1c961f523d582cd5e85a620880ffabd407f8202c0"}, - {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83b78c680d4b15d33042d330c2fa31813ca3974197bddb3836a5c635a5fd013f"}, - {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b4c153863dd6569f6511845922c53e39c8d61f6e81f228ad5443e690fca403de"}, - {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:98aa8325c7f47183b45588af9c434533196e241be0a4e4ae2190b06d17675c02"}, - {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e658d1373c424457ddf6d55ec1db93c280b8579276bebd1f72f113072df8a5d"}, - {file = "multidict-6.2.0-cp313-cp313-win32.whl", hash = "sha256:3157126b028c074951839233647bd0e30df77ef1fedd801b48bdcad242a60f4e"}, - {file = "multidict-6.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:2e87f1926e91855ae61769ba3e3f7315120788c099677e0842e697b0bfb659f2"}, - {file = "multidict-6.2.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2529ddbdaa424b2c6c2eb668ea684dd6b75b839d0ad4b21aad60c168269478d7"}, - {file = "multidict-6.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:13551d0e2d7201f0959725a6a769b6f7b9019a168ed96006479c9ac33fe4096b"}, - {file = "multidict-6.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d1996ee1330e245cd3aeda0887b4409e3930524c27642b046e4fae88ffa66c5e"}, - {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c537da54ce4ff7c15e78ab1292e5799d0d43a2108e006578a57f531866f64025"}, - {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f249badb360b0b4d694307ad40f811f83df4da8cef7b68e429e4eea939e49dd"}, - {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48d39b1824b8d6ea7de878ef6226efbe0773f9c64333e1125e0efcfdd18a24c7"}, - {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b99aac6bb2c37db336fa03a39b40ed4ef2818bf2dfb9441458165ebe88b793af"}, - {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bfa8bc649783e703263f783f73e27fef8cd37baaad4389816cf6a133141331"}, - {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2c00ad31fbc2cbac85d7d0fcf90853b2ca2e69d825a2d3f3edb842ef1544a2c"}, - {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d57a01a2a9fa00234aace434d8c131f0ac6e0ac6ef131eda5962d7e79edfb5b"}, - {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:abf5b17bc0cf626a8a497d89ac691308dbd825d2ac372aa990b1ca114e470151"}, - {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:f7716f7e7138252d88607228ce40be22660d6608d20fd365d596e7ca0738e019"}, - {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d5a36953389f35f0a4e88dc796048829a2f467c9197265504593f0e420571547"}, - {file = "multidict-6.2.0-cp313-cp313t-win32.whl", hash = "sha256:e653d36b1bf48fa78c7fcebb5fa679342e025121ace8c87ab05c1cefd33b34fc"}, - {file = "multidict-6.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ca23db5fb195b5ef4fd1f77ce26cadefdf13dba71dab14dadd29b34d457d7c44"}, - {file = "multidict-6.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b4f3d66dd0354b79761481fc15bdafaba0b9d9076f1f42cc9ce10d7fcbda205a"}, - {file = "multidict-6.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e2a2d6749e1ff2c9c76a72c6530d5baa601205b14e441e6d98011000f47a7ac"}, - {file = "multidict-6.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cca83a629f77402cfadd58352e394d79a61c8015f1694b83ab72237ec3941f88"}, - {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:781b5dd1db18c9e9eacc419027b0acb5073bdec9de1675c0be25ceb10e2ad133"}, - {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf8d370b2fea27fb300825ec3984334f7dd54a581bde6456799ba3776915a656"}, - {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25bb96338512e2f46f615a2bb7c6012fe92a4a5ebd353e5020836a7e33120349"}, - {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e2819b0b468174de25c0ceed766606a07cedeab132383f1e83b9a4e96ccb4f"}, - {file = "multidict-6.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aed763b6a1b28c46c055692836879328f0b334a6d61572ee4113a5d0c859872"}, - {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a1133414b771619aa3c3000701c11b2e4624a7f492f12f256aedde97c28331a2"}, - {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:639556758c36093b35e2e368ca485dada6afc2bd6a1b1207d85ea6dfc3deab27"}, - {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:163f4604e76639f728d127293d24c3e208b445b463168af3d031b92b0998bb90"}, - {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2325105e16d434749e1be8022f942876a936f9bece4ec41ae244e3d7fae42aaf"}, - {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e4371591e621579cb6da8401e4ea405b33ff25a755874a3567c4075ca63d56e2"}, - {file = "multidict-6.2.0-cp39-cp39-win32.whl", hash = "sha256:d1175b0e0d6037fab207f05774a176d71210ebd40b1c51f480a04b65ec5c786d"}, - {file = "multidict-6.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad81012b24b88aad4c70b2cbc2dad84018783221b7f923e926f4690ff8569da3"}, - {file = "multidict-6.2.0-py3-none-any.whl", hash = "sha256:5d26547423e5e71dcc562c4acdc134b900640a39abd9066d7326a7cc2324c530"}, - {file = "multidict-6.2.0.tar.gz", hash = "sha256:0085b0afb2446e57050140240a8595846ed64d1cbd26cef936bfab3192c673b8"}, + {file = "multidict-6.6.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a2be5b7b35271f7fff1397204ba6708365e3d773579fe2a30625e16c4b4ce817"}, + {file = "multidict-6.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:12f4581d2930840295c461764b9a65732ec01250b46c6b2c510d7ee68872b140"}, + {file = "multidict-6.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dd7793bab517e706c9ed9d7310b06c8672fd0aeee5781bfad612f56b8e0f7d14"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:72d8815f2cd3cf3df0f83cac3f3ef801d908b2d90409ae28102e0553af85545a"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:531e331a2ee53543ab32b16334e2deb26f4e6b9b28e41f8e0c87e99a6c8e2d69"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:42ca5aa9329a63be8dc49040f63817d1ac980e02eeddba763a9ae5b4027b9c9c"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:208b9b9757060b9faa6f11ab4bc52846e4f3c2fb8b14d5680c8aac80af3dc751"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:acf6b97bd0884891af6a8b43d0f586ab2fcf8e717cbd47ab4bdddc09e20652d8"}, + {file = "multidict-6.6.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:68e9e12ed00e2089725669bdc88602b0b6f8d23c0c95e52b95f0bc69f7fe9b55"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:05db2f66c9addb10cfa226e1acb363450fab2ff8a6df73c622fefe2f5af6d4e7"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0db58da8eafb514db832a1b44f8fa7906fdd102f7d982025f816a93ba45e3dcb"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:14117a41c8fdb3ee19c743b1c027da0736fdb79584d61a766da53d399b71176c"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:877443eaaabcd0b74ff32ebeed6f6176c71850feb7d6a1d2db65945256ea535c"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:70b72e749a4f6e7ed8fb334fa8d8496384840319512746a5f42fa0aec79f4d61"}, + {file = "multidict-6.6.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:43571f785b86afd02b3855c5ac8e86ec921b760298d6f82ff2a61daf5a35330b"}, + {file = "multidict-6.6.3-cp310-cp310-win32.whl", hash = "sha256:20c5a0c3c13a15fd5ea86c42311859f970070e4e24de5a550e99d7c271d76318"}, + {file = "multidict-6.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab0a34a007704c625e25a9116c6770b4d3617a071c8a7c30cd338dfbadfe6485"}, + {file = "multidict-6.6.3-cp310-cp310-win_arm64.whl", hash = "sha256:769841d70ca8bdd140a715746199fc6473414bd02efd678d75681d2d6a8986c5"}, + {file = "multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c"}, + {file = "multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df"}, + {file = "multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9"}, + {file = "multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56"}, + {file = "multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183"}, + {file = "multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5"}, + {file = "multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2"}, + {file = "multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb"}, + {file = "multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6"}, + {file = "multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f"}, + {file = "multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a"}, + {file = "multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75"}, + {file = "multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10"}, + {file = "multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5"}, + {file = "multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17"}, + {file = "multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b"}, + {file = "multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55"}, + {file = "multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b"}, + {file = "multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca"}, + {file = "multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1"}, + {file = "multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6"}, + {file = "multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e"}, + {file = "multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9"}, + {file = "multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600"}, + {file = "multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134"}, + {file = "multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37"}, + {file = "multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0"}, + {file = "multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d"}, + {file = "multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c"}, + {file = "multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e"}, + {file = "multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d"}, + {file = "multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb"}, + {file = "multidict-6.6.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c8161b5a7778d3137ea2ee7ae8a08cce0010de3b00ac671c5ebddeaa17cefd22"}, + {file = "multidict-6.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1328201ee930f069961ae707d59c6627ac92e351ed5b92397cf534d1336ce557"}, + {file = "multidict-6.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b1db4d2093d6b235de76932febf9d50766cf49a5692277b2c28a501c9637f616"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53becb01dd8ebd19d1724bebe369cfa87e4e7f29abbbe5c14c98ce4c383e16cd"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41bb9d1d4c303886e2d85bade86e59885112a7f4277af5ad47ab919a2251f306"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:775b464d31dac90f23192af9c291dc9f423101857e33e9ebf0020a10bfcf4144"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d04d01f0a913202205a598246cf77826fe3baa5a63e9f6ccf1ab0601cf56eca0"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d25594d3b38a2e6cabfdcafef339f754ca6e81fbbdb6650ad773ea9775af35ab"}, + {file = "multidict-6.6.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:35712f1748d409e0707b165bf49f9f17f9e28ae85470c41615778f8d4f7d9609"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1c8082e5814b662de8589d6a06c17e77940d5539080cbab9fe6794b5241b76d9"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:61af8a4b771f1d4d000b3168c12c3120ccf7284502a94aa58c68a81f5afac090"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:448e4a9afccbf297577f2eaa586f07067441e7b63c8362a3540ba5a38dc0f14a"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:233ad16999afc2bbd3e534ad8dbe685ef8ee49a37dbc2cdc9514e57b6d589ced"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:bb933c891cd4da6bdcc9733d048e994e22e1883287ff7540c2a0f3b117605092"}, + {file = "multidict-6.6.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:37b09ca60998e87734699e88c2363abfd457ed18cfbf88e4009a4e83788e63ed"}, + {file = "multidict-6.6.3-cp39-cp39-win32.whl", hash = "sha256:f54cb79d26d0cd420637d184af38f0668558f3c4bbe22ab7ad830e67249f2e0b"}, + {file = "multidict-6.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:295adc9c0551e5d5214b45cf29ca23dbc28c2d197a9c30d51aed9e037cb7c578"}, + {file = "multidict-6.6.3-cp39-cp39-win_arm64.whl", hash = "sha256:15332783596f227db50fb261c2c251a58ac3873c457f3a550a95d5c0aa3c770d"}, + {file = "multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a"}, + {file = "multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc"}, ] [[package]] name = "mypy" -version = "1.15.0" +version = "1.17.1" description = "Optional static typing for Python" optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, - {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be68172e9fd9ad8fb876c6389f16d1c1b5f100ffa779f77b1fb2176fcc9ab95b"}, - {file = "mypy-1.15.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7be1e46525adfa0d97681432ee9fcd61a3964c2446795714699a998d193f1a3"}, - {file = "mypy-1.15.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2e2c2e6d3593f6451b18588848e66260ff62ccca522dd231cd4dd59b0160668b"}, - {file = "mypy-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:6983aae8b2f653e098edb77f893f7b6aca69f6cffb19b2cc7443f23cce5f4828"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2922d42e16d6de288022e5ca321cd0618b238cfc5570e0263e5ba0a77dbef56f"}, - {file = "mypy-1.15.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2ee2d57e01a7c35de00f4634ba1bbf015185b219e4dc5909e281016df43f5ee5"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:973500e0774b85d9689715feeffcc980193086551110fd678ebe1f4342fb7c5e"}, - {file = "mypy-1.15.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a95fb17c13e29d2d5195869262f8125dfdb5c134dc8d9a9d0aecf7525b10c2c"}, - {file = "mypy-1.15.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1905f494bfd7d85a23a88c5d97840888a7bd516545fc5aaedff0267e0bb54e2f"}, - {file = "mypy-1.15.0-cp311-cp311-win_amd64.whl", hash = "sha256:c9817fa23833ff189db061e6d2eff49b2f3b6ed9856b4a0a73046e41932d744f"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:aea39e0583d05124836ea645f412e88a5c7d0fd77a6d694b60d9b6b2d9f184fd"}, - {file = "mypy-1.15.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f2147ab812b75e5b5499b01ade1f4a81489a147c01585cda36019102538615f"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce436f4c6d218a070048ed6a44c0bbb10cd2cc5e272b29e7845f6a2f57ee4464"}, - {file = "mypy-1.15.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8023ff13985661b50a5928fc7a5ca15f3d1affb41e5f0a9952cb68ef090b31ee"}, - {file = "mypy-1.15.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1124a18bc11a6a62887e3e137f37f53fbae476dc36c185d549d4f837a2a6a14e"}, - {file = "mypy-1.15.0-cp312-cp312-win_amd64.whl", hash = "sha256:171a9ca9a40cd1843abeca0e405bc1940cd9b305eaeea2dda769ba096932bb22"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93faf3fdb04768d44bf28693293f3904bbb555d076b781ad2530214ee53e3445"}, - {file = "mypy-1.15.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:811aeccadfb730024c5d3e326b2fbe9249bb7413553f15499a4050f7c30e801d"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:98b7b9b9aedb65fe628c62a6dc57f6d5088ef2dfca37903a7d9ee374d03acca5"}, - {file = "mypy-1.15.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c43a7682e24b4f576d93072216bf56eeff70d9140241f9edec0c104d0c515036"}, - {file = "mypy-1.15.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:baefc32840a9f00babd83251560e0ae1573e2f9d1b067719479bfb0e987c6357"}, - {file = "mypy-1.15.0-cp313-cp313-win_amd64.whl", hash = "sha256:b9378e2c00146c44793c98b8d5a61039a048e31f429fb0eb546d93f4b000bedf"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e601a7fa172c2131bff456bb3ee08a88360760d0d2f8cbd7a75a65497e2df078"}, - {file = "mypy-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:712e962a6357634fef20412699a3655c610110e01cdaa6180acec7fc9f8513ba"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95579473af29ab73a10bada2f9722856792a36ec5af5399b653aa28360290a5"}, - {file = "mypy-1.15.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f8722560a14cde92fdb1e31597760dc35f9f5524cce17836c0d22841830fd5b"}, - {file = "mypy-1.15.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1fbb8da62dc352133d7d7ca90ed2fb0e9d42bb1a32724c287d3c76c58cbaa9c2"}, - {file = "mypy-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:d10d994b41fb3497719bbf866f227b3489048ea4bbbb5015357db306249f7980"}, - {file = "mypy-1.15.0-py3-none-any.whl", hash = "sha256:5469affef548bd1895d86d3bf10ce2b44e33d86923c29e4d675b3e323437ea3e"}, - {file = "mypy-1.15.0.tar.gz", hash = "sha256:404534629d51d3efea5c800ee7c42b72a6554d6c400e6a79eafe15d11341fd43"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3fbe6d5555bf608c47203baa3e72dbc6ec9965b3d7c318aa9a4ca76f465bd972"}, + {file = "mypy-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80ef5c058b7bce08c83cac668158cb7edea692e458d21098c7d3bce35a5d43e7"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4a580f8a70c69e4a75587bd925d298434057fe2a428faaf927ffe6e4b9a98df"}, + {file = "mypy-1.17.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd86bb649299f09d987a2eebb4d52d10603224500792e1bee18303bbcc1ce390"}, + {file = "mypy-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a76906f26bd8d51ea9504966a9c25419f2e668f012e0bdf3da4ea1526c534d94"}, + {file = "mypy-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:e79311f2d904ccb59787477b7bd5d26f3347789c06fcd7656fa500875290264b"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58"}, + {file = "mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd"}, + {file = "mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b"}, + {file = "mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5"}, + {file = "mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"}, + {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"}, + {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"}, + {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"}, + {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"}, + {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"}, + {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"}, + {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"}, + {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"}, + {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"}, + {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"}, + {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"}, + {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5d1092694f166a7e56c805caaf794e0585cabdbf1df36911c414e4e9abb62ae9"}, + {file = "mypy-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:79d44f9bfb004941ebb0abe8eff6504223a9c1ac51ef967d1263c6572bbebc99"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b01586eed696ec905e61bd2568f48740f7ac4a45b3a468e6423a03d3788a51a8"}, + {file = "mypy-1.17.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43808d9476c36b927fbcd0b0255ce75efe1b68a080154a38ae68a7e62de8f0f8"}, + {file = "mypy-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:feb8cc32d319edd5859da2cc084493b3e2ce5e49a946377663cc90f6c15fb259"}, + {file = "mypy-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d7598cf74c3e16539d4e2f0b8d8c318e00041553d83d4861f87c7a72e95ac24d"}, + {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"}, + {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"}, ] [package.dependencies] mypy_extensions = ">=1.0.0" +pathspec = ">=0.9.0" typing_extensions = ">=4.6.0" [package.extras] @@ -3045,13 +3302,14 @@ reports = ["lxml"] [[package]] name = "mypy-extensions" -version = "1.0.0" +version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] @@ -3060,6 +3318,7 @@ version = "0.10.2" description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." optional = false python-versions = ">=3.9.0" +groups = ["dev"] files = [ {file = "nbclient-0.10.2-py3-none-any.whl", hash = "sha256:4ffee11e788b4a27fabeb7955547e4318a5298f34342a4bfd01f2e1faaeadc3d"}, {file = "nbclient-0.10.2.tar.gz", hash = "sha256:90b7fc6b810630db87a6d0c2250b1f0ab4cf4d3c27a299b0cde78a4ed3fd9193"}, @@ -3082,6 +3341,7 @@ version = "7.16.6" description = "Converting Jupyter Notebooks (.ipynb files) to other formats. Output formats include asciidoc, html, latex, markdown, pdf, py, rst, script. nbconvert can be used both as a Python library (`import nbconvert`) or as a command line tool (invoked as `jupyter nbconvert ...`)." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "nbconvert-7.16.6-py3-none-any.whl", hash = "sha256:1375a7b67e0c2883678c48e506dc320febb57685e5ee67faa51b18a90f3a712b"}, {file = "nbconvert-7.16.6.tar.gz", hash = "sha256:576a7e37c6480da7b8465eefa66c17844243816ce1ccc372633c6b71c3c0f582"}, @@ -3118,6 +3378,7 @@ version = "5.10.4" description = "The Jupyter Notebook format" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "nbformat-5.10.4-py3-none-any.whl", hash = "sha256:3b48d6c8fbca4b299bf3982ea7db1af21580e4fec269ad087b9e81588891200b"}, {file = "nbformat-5.10.4.tar.gz", hash = "sha256:322168b14f937a5d11362988ecac2a4952d3d8e3a2cbeb2319584631226d5b3a"}, @@ -3139,6 +3400,7 @@ version = "1.6.0" description = "Patch asyncio to allow nested event loops" optional = false python-versions = ">=3.5" +groups = ["dev"] files = [ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, @@ -3146,22 +3408,24 @@ files = [ [[package]] name = "networkx" -version = "3.4.2" +version = "3.5" description = "Python package for creating and manipulating graphs and networks" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" +groups = ["main"] files = [ - {file = "networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f"}, - {file = "networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1"}, + {file = "networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec"}, + {file = "networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037"}, ] [package.extras] -default = ["matplotlib (>=3.7)", "numpy (>=1.24)", "pandas (>=2.0)", "scipy (>=1.10,!=1.11.0,!=1.11.1)"] -developer = ["changelist (==0.5)", "mypy (>=1.1)", "pre-commit (>=3.2)", "rtoml"] -doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=9.4)", "pydata-sphinx-theme (>=0.15)", "sphinx (>=7.3)", "sphinx-gallery (>=0.16)", "texext (>=0.6.7)"] -example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=1.9)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] +default = ["matplotlib (>=3.8)", "numpy (>=1.25)", "pandas (>=2.0)", "scipy (>=1.11.2)"] +developer = ["mypy (>=1.15)", "pre-commit (>=4.1)"] +doc = ["intersphinx-registry", "myst-nb (>=1.1)", "numpydoc (>=1.8.0)", "pillow (>=10)", "pydata-sphinx-theme (>=0.16)", "sphinx (>=8.0)", "sphinx-gallery (>=0.18)", "texext (>=0.6.7)"] +example = ["cairocffi (>=1.7)", "contextily (>=1.6)", "igraph (>=0.11)", "momepy (>=0.7.2)", "osmnx (>=2.0.0)", "scikit-learn (>=1.5)", "seaborn (>=0.13)"] extra = ["lxml (>=4.6)", "pydot (>=3.0.1)", "pygraphviz (>=1.14)", "sympy (>=1.10)"] -test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] +test = ["pytest (>=7.2)", "pytest-cov (>=4.0)", "pytest-xdist (>=3.0)"] +test-extras = ["pytest-mpl", "pytest-randomly"] [[package]] name = "nltk" @@ -3169,6 +3433,7 @@ version = "3.9.1" description = "Natural Language Toolkit" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "nltk-3.9.1-py3-none-any.whl", hash = "sha256:4fa26829c5b00715afe3061398a8989dc643b92ce7dd93fb4585a70930d168a1"}, {file = "nltk-3.9.1.tar.gz", hash = "sha256:87d127bd3de4bd89a4f81265e5fa59cb1b199b27440175370f7417d2bc7ae868"}, @@ -3190,18 +3455,19 @@ twitter = ["twython"] [[package]] name = "notebook" -version = "7.3.3" +version = "7.4.5" description = "Jupyter Notebook - A web-based notebook environment for interactive computing" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "notebook-7.3.3-py3-none-any.whl", hash = "sha256:b193df0878956562d5171c8e25c9252b8e86c9fcc16163b8ee3fe6c5e3f422f7"}, - {file = "notebook-7.3.3.tar.gz", hash = "sha256:707a313fb882d35f921989eb3d204de942ed5132a44e4aa1fe0e8f24bb9dc25d"}, + {file = "notebook-7.4.5-py3-none-any.whl", hash = "sha256:351635461aca9dad08cf8946a4216f963e2760cc1bf7b1aaaecb23afc33ec046"}, + {file = "notebook-7.4.5.tar.gz", hash = "sha256:7c2c4ea245913c3ad8ab3e5d36b34a842c06e524556f5c2e1f5d7d08c986615e"}, ] [package.dependencies] jupyter-server = ">=2.4.0,<3" -jupyterlab = ">=4.3.6,<4.4" +jupyterlab = ">=4.4.5,<4.5" jupyterlab-server = ">=2.27.1,<3" notebook-shim = ">=0.2,<0.3" tornado = ">=6.2.0" @@ -3217,6 +3483,7 @@ version = "0.2.4" description = "A shim layer for notebook traits and config" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "notebook_shim-0.2.4-py3-none-any.whl", hash = "sha256:411a5be4e9dc882a074ccbcae671eda64cceb068767e9a3419096986560e1cef"}, {file = "notebook_shim-0.2.4.tar.gz", hash = "sha256:b4b2cfa1b65d98307ca24361f5b30fe785b53c3fd07b7a47e89acb5e6ac638cb"}, @@ -3230,106 +3497,160 @@ test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync" [[package]] name = "numpy" -version = "1.26.4" +version = "2.3.2" description = "Fundamental package for array computing in Python" optional = false -python-versions = ">=3.9" +python-versions = ">=3.11" +groups = ["main"] files = [ - {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, - {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, - {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, - {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, - {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, - {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, - {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, - {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, - {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, - {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, - {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, - {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, - {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, - {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, - {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, - {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, - {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, - {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, - {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, - {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, - {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, - {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, - {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b"}, + {file = "numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8"}, + {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d"}, + {file = "numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3"}, + {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f"}, + {file = "numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097"}, + {file = "numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220"}, + {file = "numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170"}, + {file = "numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0"}, + {file = "numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b"}, + {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370"}, + {file = "numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73"}, + {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc"}, + {file = "numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be"}, + {file = "numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036"}, + {file = "numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f"}, + {file = "numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6"}, + {file = "numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089"}, + {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2"}, + {file = "numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f"}, + {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee"}, + {file = "numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6"}, + {file = "numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b"}, + {file = "numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56"}, + {file = "numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a"}, + {file = "numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286"}, + {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8"}, + {file = "numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a"}, + {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91"}, + {file = "numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5"}, + {file = "numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5"}, + {file = "numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450"}, + {file = "numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125"}, + {file = "numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19"}, + {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f"}, + {file = "numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5"}, + {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58"}, + {file = "numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0"}, + {file = "numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2"}, + {file = "numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b"}, + {file = "numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b"}, + {file = "numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2"}, + {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0"}, + {file = "numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0"}, + {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2"}, + {file = "numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf"}, + {file = "numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1"}, + {file = "numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b"}, + {file = "numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981"}, + {file = "numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619"}, + {file = "numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48"}, ] [[package]] name = "nvidia-cublas-cu12" -version = "12.4.5.8" +version = "12.6.4.1" description = "CUBLAS native runtime libraries" optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3"}, - {file = "nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b"}, - {file = "nvidia_cublas_cu12-12.4.5.8-py3-none-win_amd64.whl", hash = "sha256:5a796786da89203a0657eda402bcdcec6180254a8ac22d72213abc42069522dc"}, + {file = "nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08ed2686e9875d01b58e3cb379c6896df8e76c75e0d4a7f7dace3d7b6d9ef8eb"}, + {file = "nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:235f728d6e2a409eddf1df58d5b0921cf80cfa9e72b9f2775ccb7b4a87984668"}, + {file = "nvidia_cublas_cu12-12.6.4.1-py3-none-win_amd64.whl", hash = "sha256:9e4fa264f4d8a4eb0cdbd34beadc029f453b3bafae02401e999cf3d5a5af75f8"}, ] [[package]] name = "nvidia-cuda-cupti-cu12" -version = "12.4.127" +version = "12.6.80" description = "CUDA profiling tools runtime libs." optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:79279b35cf6f91da114182a5ce1864997fd52294a87a16179ce275773799458a"}, - {file = "nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb"}, - {file = "nvidia_cuda_cupti_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:5688d203301ab051449a2b1cb6690fbe90d2b372f411521c86018b950f3d7922"}, + {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:166ee35a3ff1587f2490364f90eeeb8da06cd867bd5b701bf7f9a02b78bc63fc"}, + {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_aarch64.whl", hash = "sha256:358b4a1d35370353d52e12f0a7d1769fc01ff74a191689d3870b2123156184c4"}, + {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6768bad6cab4f19e8292125e5f1ac8aa7d1718704012a0e3272a6f61c4bce132"}, + {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a3eff6cdfcc6a4c35db968a06fcadb061cbc7d6dde548609a941ff8701b98b73"}, + {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-win_amd64.whl", hash = "sha256:bbe6ae76e83ce5251b56e8c8e61a964f757175682bbad058b170b136266ab00a"}, ] [[package]] name = "nvidia-cuda-nvrtc-cu12" -version = "12.4.127" +version = "12.6.77" description = "NVRTC native runtime libraries" optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0eedf14185e04b76aa05b1fea04133e59f465b6f960c0cbf4e37c3cb6b0ea198"}, - {file = "nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338"}, - {file = "nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:a961b2f1d5f17b14867c619ceb99ef6fcec12e46612711bcec78eb05068a60ec"}, + {file = "nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5847f1d6e5b757f1d2b3991a01082a44aad6f10ab3c5c0213fa3e25bddc25a13"}, + {file = "nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:35b0cc6ee3a9636d5409133e79273ce1f3fd087abb0532d2d2e8fff1fe9efc53"}, + {file = "nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:f7007dbd914c56bd80ea31bc43e8e149da38f68158f423ba845fc3292684e45a"}, ] [[package]] name = "nvidia-cuda-runtime-cu12" -version = "12.4.127" +version = "12.6.77" description = "CUDA Runtime native Libraries" optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:961fe0e2e716a2a1d967aab7caee97512f71767f852f67432d572e36cb3a11f3"}, - {file = "nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5"}, - {file = "nvidia_cuda_runtime_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:09c2e35f48359752dfa822c09918211844a3d93c100a715d79b59591130c5e1e"}, + {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6116fad3e049e04791c0256a9778c16237837c08b27ed8c8401e2e45de8d60cd"}, + {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d461264ecb429c84c8879a7153499ddc7b19b5f8d84c204307491989a365588e"}, + {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba3b56a4f896141e25e19ab287cd71e52a6a0f4b29d0d31609f60e3b4d5219b7"}, + {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a84d15d5e1da416dd4774cb42edf5e954a3e60cc945698dc1d5be02321c44dc8"}, + {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:86c58044c824bf3c173c49a2dbc7a6c8b53cb4e4dca50068be0bf64e9dab3f7f"}, ] [[package]] name = "nvidia-cudnn-cu12" -version = "9.1.0.70" +version = "9.5.1.17" description = "cuDNN runtime libraries" optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f"}, - {file = "nvidia_cudnn_cu12-9.1.0.70-py3-none-win_amd64.whl", hash = "sha256:6278562929433d68365a07a4a1546c237ba2849852c0d4b2262a486e805b977a"}, + {file = "nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9fd4584468533c61873e5fda8ca41bac3a38bcb2d12350830c69b0a96a7e4def"}, + {file = "nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:30ac3869f6db17d170e0e556dd6cc5eee02647abc31ca856634d5a40f82c15b2"}, + {file = "nvidia_cudnn_cu12-9.5.1.17-py3-none-win_amd64.whl", hash = "sha256:d7af0f8a4f3b4b9dbb3122f2ef553b45694ed9c384d5a75bab197b8eefb79ab8"}, ] [package.dependencies] @@ -3337,41 +3658,66 @@ nvidia-cublas-cu12 = "*" [[package]] name = "nvidia-cufft-cu12" -version = "11.2.1.3" +version = "11.3.0.4" description = "CUFFT native runtime libraries" optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399"}, - {file = "nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9"}, - {file = "nvidia_cufft_cu12-11.2.1.3-py3-none-win_amd64.whl", hash = "sha256:d802f4954291101186078ccbe22fc285a902136f974d369540fd4a5333d1440b"}, + {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d16079550df460376455cba121db6564089176d9bac9e4f360493ca4741b22a6"}, + {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8510990de9f96c803a051822618d42bf6cb8f069ff3f48d93a8486efdacb48fb"}, + {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ccba62eb9cef5559abd5e0d54ceed2d9934030f51163df018532142a8ec533e5"}, + {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:768160ac89f6f7b459bee747e8d175dbf53619cfe74b2a5636264163138013ca"}, + {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-win_amd64.whl", hash = "sha256:6048ebddfb90d09d2707efb1fd78d4e3a77cb3ae4dc60e19aab6be0ece2ae464"}, ] [package.dependencies] nvidia-nvjitlink-cu12 = "*" +[[package]] +name = "nvidia-cufile-cu12" +version = "1.11.1.6" +description = "cuFile GPUDirect libraries" +optional = false +python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" +files = [ + {file = "nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc23469d1c7e52ce6c1d55253273d32c565dd22068647f3aa59b3c6b005bf159"}, + {file = "nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:8f57a0051dcf2543f6dc2b98a98cb2719c37d3cee1baba8965d57f3bbc90d4db"}, +] + [[package]] name = "nvidia-curand-cu12" -version = "10.3.5.147" +version = "10.3.7.77" description = "CURAND native runtime libraries" optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1f173f09e3e3c76ab084aba0de819c49e56614feae5c12f69883f4ae9bb5fad9"}, - {file = "nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b"}, - {file = "nvidia_curand_cu12-10.3.5.147-py3-none-win_amd64.whl", hash = "sha256:f307cc191f96efe9e8f05a87096abc20d08845a841889ef78cb06924437f6771"}, + {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:6e82df077060ea28e37f48a3ec442a8f47690c7499bff392a5938614b56c98d8"}, + {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a42cd1344297f70b9e39a1e4f467a4e1c10f1da54ff7a85c12197f6c652c8bdf"}, + {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:99f1a32f1ac2bd134897fc7a203f779303261268a65762a623bf30cc9fe79117"}, + {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:7b2ed8e95595c3591d984ea3603dd66fe6ce6812b886d59049988a712ed06b6e"}, + {file = "nvidia_curand_cu12-10.3.7.77-py3-none-win_amd64.whl", hash = "sha256:6d6d935ffba0f3d439b7cd968192ff068fafd9018dbf1b85b37261b13cfc9905"}, ] [[package]] name = "nvidia-cusolver-cu12" -version = "11.6.1.9" +version = "11.7.1.2" description = "CUDA solver native runtime libraries" optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e"}, - {file = "nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260"}, - {file = "nvidia_cusolver_cu12-11.6.1.9-py3-none-win_amd64.whl", hash = "sha256:e77314c9d7b694fcebc84f58989f3aa4fb4cb442f12ca1a9bde50f5e8f6d1b9c"}, + {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0ce237ef60acde1efc457335a2ddadfd7610b892d94efee7b776c64bb1cac9e0"}, + {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9e49843a7707e42022babb9bcfa33c29857a93b88020c4e4434656a655b698c"}, + {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6cf28f17f64107a0c4d7802be5ff5537b2130bfc112f25d5a30df227058ca0e6"}, + {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:dbbe4fc38ec1289c7e5230e16248365e375c3673c9c8bac5796e2e20db07f56e"}, + {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-win_amd64.whl", hash = "sha256:6813f9d8073f555444a8705f3ab0296d3e1cb37a16d694c5fc8b862a0d8706d7"}, ] [package.dependencies] @@ -3381,14 +3727,18 @@ nvidia-nvjitlink-cu12 = "*" [[package]] name = "nvidia-cusparse-cu12" -version = "12.3.1.170" +version = "12.5.4.2" description = "CUSPARSE native runtime libraries" optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3"}, - {file = "nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1"}, - {file = "nvidia_cusparse_cu12-12.3.1.170-py3-none-win_amd64.whl", hash = "sha256:9bc90fb087bc7b4c15641521f31c0371e9a612fc2ba12c338d3ae032e6b6797f"}, + {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d25b62fb18751758fe3c93a4a08eff08effedfe4edf1c6bb5afd0890fe88f887"}, + {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7aa32fa5470cf754f72d1116c7cbc300b4e638d3ae5304cfa4a638a5b87161b1"}, + {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7556d9eca156e18184b94947ade0fba5bb47d69cec46bf8660fd2c71a4b48b73"}, + {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:23749a6571191a215cb74d1cdbff4a86e7b19f1200c071b3fcf844a5bea23a2f"}, + {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-win_amd64.whl", hash = "sha256:4acb8c08855a26d737398cba8fb6f8f5045d93f82612b4cfd84645a2332ccf20"}, ] [package.dependencies] @@ -3396,59 +3746,71 @@ nvidia-nvjitlink-cu12 = "*" [[package]] name = "nvidia-cusparselt-cu12" -version = "0.6.2" +version = "0.6.3" description = "NVIDIA cuSPARSELt" optional = false python-versions = "*" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_cusparselt_cu12-0.6.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:067a7f6d03ea0d4841c85f0c6f1991c5dda98211f6302cb83a4ab234ee95bef8"}, - {file = "nvidia_cusparselt_cu12-0.6.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:df2c24502fd76ebafe7457dbc4716b2fec071aabaed4fb7691a201cde03704d9"}, - {file = "nvidia_cusparselt_cu12-0.6.2-py3-none-win_amd64.whl", hash = "sha256:0057c91d230703924c0422feabe4ce768841f9b4b44d28586b6f6d2eb86fbe70"}, + {file = "nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8371549623ba601a06322af2133c4a44350575f5a3108fb75f3ef20b822ad5f1"}, + {file = "nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5c8a26c36445dd2e6812f1177978a24e2d37cacce7e090f297a688d1ec44f46"}, + {file = "nvidia_cusparselt_cu12-0.6.3-py3-none-win_amd64.whl", hash = "sha256:3b325bcbd9b754ba43df5a311488fca11a6b5dc3d11df4d190c000cf1a0765c7"}, ] [[package]] name = "nvidia-nccl-cu12" -version = "2.21.5" +version = "2.26.2" description = "NVIDIA Collective Communication Library (NCCL) Runtime" optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0"}, + {file = "nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c196e95e832ad30fbbb50381eb3cbd1fadd5675e587a548563993609af19522"}, + {file = "nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:694cf3879a206553cc9d7dbda76b13efaf610fdb70a50cba303de1b0d1530ac6"}, ] [[package]] name = "nvidia-nvjitlink-cu12" -version = "12.4.127" +version = "12.6.85" description = "Nvidia JIT LTO Library" optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4abe7fef64914ccfa909bc2ba39739670ecc9e820c83ccc7a6ed414122599b83"}, - {file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57"}, - {file = "nvidia_nvjitlink_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:fd9020c501d27d135f983c6d3e244b197a7ccad769e34df53a42e276b0e25fa1"}, + {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:eedc36df9e88b682efe4309aa16b5b4e78c2407eac59e8c10a6a47535164369a"}, + {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cf4eaa7d4b6b543ffd69d6abfb11efdeb2db48270d94dfd3a452c24150829e41"}, + {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-win_amd64.whl", hash = "sha256:e61120e52ed675747825cdd16febc6a0730537451d867ee58bee3853b1b13d1c"}, ] [[package]] name = "nvidia-nvtx-cu12" -version = "12.4.127" +version = "12.6.77" description = "NVIDIA Tools Extension" optional = false python-versions = ">=3" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3"}, - {file = "nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a"}, - {file = "nvidia_nvtx_cu12-12.4.127-py3-none-win_amd64.whl", hash = "sha256:641dccaaa1139f3ffb0d3164b4b84f9d253397e38246a4f2f36728b48566d485"}, + {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f44f8d86bb7d5629988d61c8d3ae61dddb2015dee142740536bc7481b022fe4b"}, + {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:adcaabb9d436c9761fca2b13959a2d237c5f9fd406c8e4b723c695409ff88059"}, + {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b90bed3df379fa79afbd21be8e04a0314336b8ae16768b58f2d34cb1d04cd7d2"}, + {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6574241a3ec5fdc9334353ab8c479fe75841dbe8f4532a8fc97ce63503330ba1"}, + {file = "nvidia_nvtx_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:2fb11a4af04a5e6c84073e6404d26588a34afd35379f0855a99797897efa75c0"}, ] [[package]] name = "oauthlib" -version = "3.2.2" +version = "3.3.1" description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, - {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, + {file = "oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1"}, + {file = "oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9"}, ] [package.extras] @@ -3458,29 +3820,30 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "onnxruntime" -version = "1.21.0" +version = "1.22.1" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "onnxruntime-1.21.0-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:95513c9302bc8dd013d84148dcf3168e782a80cdbf1654eddc948a23147ccd3d"}, - {file = "onnxruntime-1.21.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:635d4ab13ae0f150dd4c6ff8206fd58f1c6600636ecc796f6f0c42e4c918585b"}, - {file = "onnxruntime-1.21.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d06bfa0dd5512bd164f25a2bf594b2e7c9eabda6fc064b684924f3e81bdab1b"}, - {file = "onnxruntime-1.21.0-cp310-cp310-win_amd64.whl", hash = "sha256:b0fc22d219791e0284ee1d9c26724b8ee3fbdea28128ef25d9507ad3b9621f23"}, - {file = "onnxruntime-1.21.0-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:8e16f8a79df03919810852fb46ffcc916dc87a9e9c6540a58f20c914c575678c"}, - {file = "onnxruntime-1.21.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f9156cf6f8ee133d07a751e6518cf6f84ed37fbf8243156bd4a2c4ee6e073c8"}, - {file = "onnxruntime-1.21.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a5d09815a9e209fa0cb20c2985b34ab4daeba7aea94d0f96b8751eb10403201"}, - {file = "onnxruntime-1.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:1d970dff1e2fa4d9c53f2787b3b7d0005596866e6a31997b41169017d1362dd0"}, - {file = "onnxruntime-1.21.0-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:893d67c68ca9e7a58202fa8d96061ed86a5815b0925b5a97aef27b8ba246a20b"}, - {file = "onnxruntime-1.21.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:37b7445c920a96271a8dfa16855e258dc5599235b41c7bbde0d262d55bcc105f"}, - {file = "onnxruntime-1.21.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9a04aafb802c1e5573ba4552f8babcb5021b041eb4cfa802c9b7644ca3510eca"}, - {file = "onnxruntime-1.21.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f801318476cd7003d636a5b392f7a37c08b6c8d2f829773f3c3887029e03f32"}, - {file = "onnxruntime-1.21.0-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:85718cbde1c2912d3a03e3b3dc181b1480258a229c32378408cace7c450f7f23"}, - {file = "onnxruntime-1.21.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94dff3a61538f3b7b0ea9a06bc99e1410e90509c76e3a746f039e417802a12ae"}, - {file = "onnxruntime-1.21.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1e704b0eda5f2bbbe84182437315eaec89a450b08854b5a7762c85d04a28a0a"}, - {file = "onnxruntime-1.21.0-cp313-cp313-win_amd64.whl", hash = "sha256:19b630c6a8956ef97fb7c94948b17691167aa1aaf07b5f214fa66c3e4136c108"}, - {file = "onnxruntime-1.21.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3995c4a2d81719623c58697b9510f8de9fa42a1da6b4474052797b0d712324fe"}, - {file = "onnxruntime-1.21.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:36b18b8f39c0f84e783902112a0dd3c102466897f96d73bb83f6a6bff283a423"}, + {file = "onnxruntime-1.22.1-cp310-cp310-macosx_13_0_universal2.whl", hash = "sha256:80e7f51da1f5201c1379b8d6ef6170505cd800e40da216290f5e06be01aadf95"}, + {file = "onnxruntime-1.22.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89ddfdbbdaf7e3a59515dee657f6515601d55cb21a0f0f48c81aefc54ff1b73"}, + {file = "onnxruntime-1.22.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bddc75868bcf6f9ed76858a632f65f7b1846bdcefc6d637b1e359c2c68609964"}, + {file = "onnxruntime-1.22.1-cp310-cp310-win_amd64.whl", hash = "sha256:01e2f21b2793eb0c8642d2be3cee34cc7d96b85f45f6615e4e220424158877ce"}, + {file = "onnxruntime-1.22.1-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:f4581bccb786da68725d8eac7c63a8f31a89116b8761ff8b4989dc58b61d49a0"}, + {file = "onnxruntime-1.22.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7ae7526cf10f93454beb0f751e78e5cb7619e3b92f9fc3bd51aa6f3b7a8977e5"}, + {file = "onnxruntime-1.22.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6effa1299ac549a05c784d50292e3378dbbf010346ded67400193b09ddc2f04"}, + {file = "onnxruntime-1.22.1-cp311-cp311-win_amd64.whl", hash = "sha256:f28a42bb322b4ca6d255531bb334a2b3e21f172e37c1741bd5e66bc4b7b61f03"}, + {file = "onnxruntime-1.22.1-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:a938d11c0dc811badf78e435daa3899d9af38abee950d87f3ab7430eb5b3cf5a"}, + {file = "onnxruntime-1.22.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:984cea2a02fcc5dfea44ade9aca9fe0f7a8a2cd6f77c258fc4388238618f3928"}, + {file = "onnxruntime-1.22.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d39a530aff1ec8d02e365f35e503193991417788641b184f5b1e8c9a6d5ce8d"}, + {file = "onnxruntime-1.22.1-cp312-cp312-win_amd64.whl", hash = "sha256:6a64291d57ea966a245f749eb970f4fa05a64d26672e05a83fdb5db6b7d62f87"}, + {file = "onnxruntime-1.22.1-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:d29c7d87b6cbed8fecfd09dca471832384d12a69e1ab873e5effbb94adc3e966"}, + {file = "onnxruntime-1.22.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:460487d83b7056ba98f1f7bac80287224c31d8149b15712b0d6f5078fcc33d0f"}, + {file = "onnxruntime-1.22.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b0c37070268ba4e02a1a9d28560cd00cd1e94f0d4f275cbef283854f861a65fa"}, + {file = "onnxruntime-1.22.1-cp313-cp313-win_amd64.whl", hash = "sha256:70980d729145a36a05f74b573435531f55ef9503bcda81fc6c3d6b9306199982"}, + {file = "onnxruntime-1.22.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33a7980bbc4b7f446bac26c3785652fe8730ed02617d765399e89ac7d44e0f7d"}, + {file = "onnxruntime-1.22.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7e823624b015ea879d976cbef8bfaed2f7e2cc233d7506860a76dd37f8f381"}, ] [package.dependencies] @@ -3497,6 +3860,7 @@ version = "3.1.5" description = "A Python library to read/write Excel 2010 xlsx/xlsm files" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2"}, {file = "openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050"}, @@ -3507,246 +3871,198 @@ et-xmlfile = "*" [[package]] name = "opentelemetry-api" -version = "1.31.1" +version = "1.36.0" description = "OpenTelemetry Python API" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_api-1.31.1-py3-none-any.whl", hash = "sha256:1511a3f470c9c8a32eeea68d4ea37835880c0eed09dd1a0187acc8b1301da0a1"}, - {file = "opentelemetry_api-1.31.1.tar.gz", hash = "sha256:137ad4b64215f02b3000a0292e077641c8611aab636414632a9b9068593b7e91"}, + {file = "opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c"}, + {file = "opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0"}, ] [package.dependencies] -deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<8.7.0" +importlib-metadata = ">=6.0,<8.8.0" +typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.31.1" +version = "1.36.0" description = "OpenTelemetry Protobuf encoding" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.31.1-py3-none-any.whl", hash = "sha256:7cadf89dbab12e217a33c5d757e67c76dd20ce173f8203e7370c4996f2e9efd8"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.31.1.tar.gz", hash = "sha256:c748e224c01f13073a2205397ba0e415dcd3be9a0f95101ba4aace5fc730e0da"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf"}, ] [package.dependencies] -opentelemetry-proto = "1.31.1" +opentelemetry-proto = "1.36.0" [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.31.1" +version = "1.36.0" description = "OpenTelemetry Collector Protobuf over gRPC Exporter" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_grpc-1.31.1-py3-none-any.whl", hash = "sha256:f4055ad2c9a2ea3ae00cbb927d6253233478b3b87888e197d34d095a62305fae"}, - {file = "opentelemetry_exporter_otlp_proto_grpc-1.31.1.tar.gz", hash = "sha256:c7f66b4b333c52248dc89a6583506222c896c74824d5d2060b818ae55510939a"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.36.0-py3-none-any.whl", hash = "sha256:734e841fc6a5d6f30e7be4d8053adb703c70ca80c562ae24e8083a28fadef211"}, + {file = "opentelemetry_exporter_otlp_proto_grpc-1.36.0.tar.gz", hash = "sha256:b281afbf7036b325b3588b5b6c8bb175069e3978d1bd24071f4a59d04c1e5bbf"}, ] [package.dependencies] -deprecated = ">=1.2.6" -googleapis-common-protos = ">=1.52,<2.0" +googleapis-common-protos = ">=1.57,<2.0" grpcio = [ {version = ">=1.63.2,<2.0.0", markers = "python_version < \"3.13\""}, {version = ">=1.66.2,<2.0.0", markers = "python_version >= \"3.13\""}, ] opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.31.1" -opentelemetry-proto = "1.31.1" -opentelemetry-sdk = ">=1.31.1,<1.32.0" - -[[package]] -name = "opentelemetry-instrumentation" -version = "0.52b1" -description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" -optional = false -python-versions = ">=3.8" -files = [ - {file = "opentelemetry_instrumentation-0.52b1-py3-none-any.whl", hash = "sha256:8c0059c4379d77bbd8015c8d8476020efe873c123047ec069bb335e4b8717477"}, - {file = "opentelemetry_instrumentation-0.52b1.tar.gz", hash = "sha256:739f3bfadbbeec04dd59297479e15660a53df93c131d907bb61052e3d3c1406f"}, -] - -[package.dependencies] -opentelemetry-api = ">=1.4,<2.0" -opentelemetry-semantic-conventions = "0.52b1" -packaging = ">=18.0" -wrapt = ">=1.0.0,<2.0.0" - -[[package]] -name = "opentelemetry-instrumentation-asgi" -version = "0.52b1" -description = "ASGI instrumentation for OpenTelemetry" -optional = false -python-versions = ">=3.8" -files = [ - {file = "opentelemetry_instrumentation_asgi-0.52b1-py3-none-any.whl", hash = "sha256:f7179f477ed665ba21871972f979f21e8534edb971232e11920c8a22f4759236"}, - {file = "opentelemetry_instrumentation_asgi-0.52b1.tar.gz", hash = "sha256:a6dbce9cb5b2c2f45ce4817ad21f44c67fd328358ad3ab911eb46f0be67f82ec"}, -] - -[package.dependencies] -asgiref = ">=3.0,<4.0" -opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.52b1" -opentelemetry-semantic-conventions = "0.52b1" -opentelemetry-util-http = "0.52b1" - -[package.extras] -instruments = ["asgiref (>=3.0,<4.0)"] - -[[package]] -name = "opentelemetry-instrumentation-fastapi" -version = "0.52b1" -description = "OpenTelemetry FastAPI Instrumentation" -optional = false -python-versions = ">=3.8" -files = [ - {file = "opentelemetry_instrumentation_fastapi-0.52b1-py3-none-any.whl", hash = "sha256:73c8804f053c5eb2fd2c948218bff9561f1ef65e89db326a6ab0b5bf829969f4"}, - {file = "opentelemetry_instrumentation_fastapi-0.52b1.tar.gz", hash = "sha256:d26ab15dc49e041301d5c2571605b8f5c3a6ee4a85b60940338f56c120221e98"}, -] - -[package.dependencies] -opentelemetry-api = ">=1.12,<2.0" -opentelemetry-instrumentation = "0.52b1" -opentelemetry-instrumentation-asgi = "0.52b1" -opentelemetry-semantic-conventions = "0.52b1" -opentelemetry-util-http = "0.52b1" - -[package.extras] -instruments = ["fastapi (>=0.58,<1.0)"] +opentelemetry-exporter-otlp-proto-common = "1.36.0" +opentelemetry-proto = "1.36.0" +opentelemetry-sdk = ">=1.36.0,<1.37.0" +typing-extensions = ">=4.6.0" [[package]] name = "opentelemetry-proto" -version = "1.31.1" +version = "1.36.0" description = "OpenTelemetry Python Proto" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_proto-1.31.1-py3-none-any.whl", hash = "sha256:1398ffc6d850c2f1549ce355744e574c8cd7c1dba3eea900d630d52c41d07178"}, - {file = "opentelemetry_proto-1.31.1.tar.gz", hash = "sha256:d93e9c2b444e63d1064fb50ae035bcb09e5822274f1683886970d2734208e790"}, + {file = "opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e"}, + {file = "opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f"}, ] [package.dependencies] -protobuf = ">=5.0,<6.0" +protobuf = ">=5.0,<7.0" [[package]] name = "opentelemetry-sdk" -version = "1.31.1" +version = "1.36.0" description = "OpenTelemetry Python SDK" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_sdk-1.31.1-py3-none-any.whl", hash = "sha256:882d021321f223e37afaca7b4e06c1d8bbc013f9e17ff48a7aa017460a8e7dae"}, - {file = "opentelemetry_sdk-1.31.1.tar.gz", hash = "sha256:c95f61e74b60769f8ff01ec6ffd3d29684743404603df34b20aa16a49dc8d903"}, + {file = "opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb"}, + {file = "opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581"}, ] [package.dependencies] -opentelemetry-api = "1.31.1" -opentelemetry-semantic-conventions = "0.52b1" -typing-extensions = ">=3.7.4" +opentelemetry-api = "1.36.0" +opentelemetry-semantic-conventions = "0.57b0" +typing-extensions = ">=4.5.0" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.52b1" +version = "0.57b0" description = "OpenTelemetry Semantic Conventions" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "opentelemetry_semantic_conventions-0.52b1-py3-none-any.whl", hash = "sha256:72b42db327e29ca8bb1b91e8082514ddf3bbf33f32ec088feb09526ade4bc77e"}, - {file = "opentelemetry_semantic_conventions-0.52b1.tar.gz", hash = "sha256:7b3d226ecf7523c27499758a58b542b48a0ac8d12be03c0488ff8ec60c5bae5d"}, + {file = "opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78"}, + {file = "opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32"}, ] [package.dependencies] -deprecated = ">=1.2.6" -opentelemetry-api = "1.31.1" - -[[package]] -name = "opentelemetry-util-http" -version = "0.52b1" -description = "Web util for OpenTelemetry" -optional = false -python-versions = ">=3.8" -files = [ - {file = "opentelemetry_util_http-0.52b1-py3-none-any.whl", hash = "sha256:6a6ab6bfa23fef96f4995233e874f67602adf9d224895981b4ab9d4dde23de78"}, - {file = "opentelemetry_util_http-0.52b1.tar.gz", hash = "sha256:c03c8c23f1b75fadf548faece7ead3aecd50761c5593a2b2831b48730eee5b31"}, -] +opentelemetry-api = "1.36.0" +typing-extensions = ">=4.5.0" [[package]] name = "orjson" -version = "3.10.16" +version = "3.11.1" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "orjson-3.10.16-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:4cb473b8e79154fa778fb56d2d73763d977be3dcc140587e07dbc545bbfc38f8"}, - {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:622a8e85eeec1948690409a19ca1c7d9fd8ff116f4861d261e6ae2094fe59a00"}, - {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c682d852d0ce77613993dc967e90e151899fe2d8e71c20e9be164080f468e370"}, - {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c520ae736acd2e32df193bcff73491e64c936f3e44a2916b548da048a48b46b"}, - {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:134f87c76bfae00f2094d85cfab261b289b76d78c6da8a7a3b3c09d362fd1e06"}, - {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b59afde79563e2cf37cfe62ee3b71c063fd5546c8e662d7fcfc2a3d5031a5c4c"}, - {file = "orjson-3.10.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:113602f8241daaff05d6fad25bd481d54c42d8d72ef4c831bb3ab682a54d9e15"}, - {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4fc0077d101f8fab4031e6554fc17b4c2ad8fdbc56ee64a727f3c95b379e31da"}, - {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:9c6bf6ff180cd69e93f3f50380224218cfab79953a868ea3908430bcfaf9cb5e"}, - {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5673eadfa952f95a7cd76418ff189df11b0a9c34b1995dff43a6fdbce5d63bf4"}, - {file = "orjson-3.10.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5fe638a423d852b0ae1e1a79895851696cb0d9fa0946fdbfd5da5072d9bb9551"}, - {file = "orjson-3.10.16-cp310-cp310-win32.whl", hash = "sha256:33af58f479b3c6435ab8f8b57999874b4b40c804c7a36b5cc6b54d8f28e1d3dd"}, - {file = "orjson-3.10.16-cp310-cp310-win_amd64.whl", hash = "sha256:0338356b3f56d71293c583350af26f053017071836b07e064e92819ecf1aa055"}, - {file = "orjson-3.10.16-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44fcbe1a1884f8bc9e2e863168b0f84230c3d634afe41c678637d2728ea8e739"}, - {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78177bf0a9d0192e0b34c3d78bcff7fe21d1b5d84aeb5ebdfe0dbe637b885225"}, - {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12824073a010a754bb27330cad21d6e9b98374f497f391b8707752b96f72e741"}, - {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ddd41007e56284e9867864aa2f29f3136bb1dd19a49ca43c0b4eda22a579cf53"}, - {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0877c4d35de639645de83666458ca1f12560d9fa7aa9b25d8bb8f52f61627d14"}, - {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a09a539e9cc3beead3e7107093b4ac176d015bec64f811afb5965fce077a03c"}, - {file = "orjson-3.10.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31b98bc9b40610fec971d9a4d67bb2ed02eec0a8ae35f8ccd2086320c28526ca"}, - {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0ce243f5a8739f3a18830bc62dc2e05b69a7545bafd3e3249f86668b2bcd8e50"}, - {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:64792c0025bae049b3074c6abe0cf06f23c8e9f5a445f4bab31dc5ca23dbf9e1"}, - {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea53f7e68eec718b8e17e942f7ca56c6bd43562eb19db3f22d90d75e13f0431d"}, - {file = "orjson-3.10.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a741ba1a9488c92227711bde8c8c2b63d7d3816883268c808fbeada00400c164"}, - {file = "orjson-3.10.16-cp311-cp311-win32.whl", hash = "sha256:c7ed2c61bb8226384c3fdf1fb01c51b47b03e3f4536c985078cccc2fd19f1619"}, - {file = "orjson-3.10.16-cp311-cp311-win_amd64.whl", hash = "sha256:cd67d8b3e0e56222a2e7b7f7da9031e30ecd1fe251c023340b9f12caca85ab60"}, - {file = "orjson-3.10.16-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6d3444abbfa71ba21bb042caa4b062535b122248259fdb9deea567969140abca"}, - {file = "orjson-3.10.16-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:30245c08d818fdcaa48b7d5b81499b8cae09acabb216fe61ca619876b128e184"}, - {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0ba1d0baa71bf7579a4ccdcf503e6f3098ef9542106a0eca82395898c8a500a"}, - {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb0beefa5ef3af8845f3a69ff2a4aa62529b5acec1cfe5f8a6b4141033fd46ef"}, - {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6daa0e1c9bf2e030e93c98394de94506f2a4d12e1e9dadd7c53d5e44d0f9628e"}, - {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9da9019afb21e02410ef600e56666652b73eb3e4d213a0ec919ff391a7dd52aa"}, - {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:daeb3a1ee17b69981d3aae30c3b4e786b0f8c9e6c71f2b48f1aef934f63f38f4"}, - {file = "orjson-3.10.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fed80eaf0e20a31942ae5d0728849862446512769692474be5e6b73123a23b"}, - {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73390ed838f03764540a7bdc4071fe0123914c2cc02fb6abf35182d5fd1b7a42"}, - {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:a22bba012a0c94ec02a7768953020ab0d3e2b884760f859176343a36c01adf87"}, - {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5385bbfdbc90ff5b2635b7e6bebf259652db00a92b5e3c45b616df75b9058e88"}, - {file = "orjson-3.10.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:02c6279016346e774dd92625d46c6c40db687b8a0d685aadb91e26e46cc33e1e"}, - {file = "orjson-3.10.16-cp312-cp312-win32.whl", hash = "sha256:7ca55097a11426db80f79378e873a8c51f4dde9ffc22de44850f9696b7eb0e8c"}, - {file = "orjson-3.10.16-cp312-cp312-win_amd64.whl", hash = "sha256:86d127efdd3f9bf5f04809b70faca1e6836556ea3cc46e662b44dab3fe71f3d6"}, - {file = "orjson-3.10.16-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:148a97f7de811ba14bc6dbc4a433e0341ffd2cc285065199fb5f6a98013744bd"}, - {file = "orjson-3.10.16-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1d960c1bf0e734ea36d0adc880076de3846aaec45ffad29b78c7f1b7962516b8"}, - {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a318cd184d1269f68634464b12871386808dc8b7c27de8565234d25975a7a137"}, - {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df23f8df3ef9223d1d6748bea63fca55aae7da30a875700809c500a05975522b"}, - {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b94dda8dd6d1378f1037d7f3f6b21db769ef911c4567cbaa962bb6dc5021cf90"}, - {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f12970a26666a8775346003fd94347d03ccb98ab8aa063036818381acf5f523e"}, - {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15a1431a245d856bd56e4d29ea0023eb4d2c8f71efe914beb3dee8ab3f0cd7fb"}, - {file = "orjson-3.10.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c83655cfc247f399a222567d146524674a7b217af7ef8289c0ff53cfe8db09f0"}, - {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fa59ae64cb6ddde8f09bdbf7baf933c4cd05734ad84dcf4e43b887eb24e37652"}, - {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ca5426e5aacc2e9507d341bc169d8af9c3cbe88f4cd4c1cf2f87e8564730eb56"}, - {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6fd5da4edf98a400946cd3a195680de56f1e7575109b9acb9493331047157430"}, - {file = "orjson-3.10.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:980ecc7a53e567169282a5e0ff078393bac78320d44238da4e246d71a4e0e8f5"}, - {file = "orjson-3.10.16-cp313-cp313-win32.whl", hash = "sha256:28f79944dd006ac540a6465ebd5f8f45dfdf0948ff998eac7a908275b4c1add6"}, - {file = "orjson-3.10.16-cp313-cp313-win_amd64.whl", hash = "sha256:fe0a145e96d51971407cb8ba947e63ead2aa915db59d6631a355f5f2150b56b7"}, - {file = "orjson-3.10.16-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:c35b5c1fb5a5d6d2fea825dec5d3d16bea3c06ac744708a8e1ff41d4ba10cdf1"}, - {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9aac7ecc86218b4b3048c768f227a9452287001d7548500150bb75ee21bf55d"}, - {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6e19f5102fff36f923b6dfdb3236ec710b649da975ed57c29833cb910c5a73ab"}, - {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17210490408eb62755a334a6f20ed17c39f27b4f45d89a38cd144cd458eba80b"}, - {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbbe04451db85916e52a9f720bd89bf41f803cf63b038595674691680cbebd1b"}, - {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a966eba501a3a1f309f5a6af32ed9eb8f316fa19d9947bac3e6350dc63a6f0a"}, - {file = "orjson-3.10.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01e0d22f06c81e6c435723343e1eefc710e0510a35d897856766d475f2a15687"}, - {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:7c1e602d028ee285dbd300fb9820b342b937df64d5a3336e1618b354e95a2569"}, - {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d230e5020666a6725629df81e210dc11c3eae7d52fe909a7157b3875238484f3"}, - {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0f8baac07d4555f57d44746a7d80fbe6b2c4fe2ed68136b4abb51cfec512a5e9"}, - {file = "orjson-3.10.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:524e48420b90fc66953e91b660b3d05faaf921277d6707e328fde1c218b31250"}, - {file = "orjson-3.10.16-cp39-cp39-win32.whl", hash = "sha256:a9f614e31423d7292dbca966a53b2d775c64528c7d91424ab2747d8ab8ce5c72"}, - {file = "orjson-3.10.16-cp39-cp39-win_amd64.whl", hash = "sha256:c338dc2296d1ed0d5c5c27dfb22d00b330555cb706c2e0be1e1c3940a0895905"}, - {file = "orjson-3.10.16.tar.gz", hash = "sha256:d2aaa5c495e11d17b9b93205f5fa196737ee3202f000aaebf028dc9a73750f10"}, + {file = "orjson-3.11.1-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:92d771c492b64119456afb50f2dff3e03a2db8b5af0eba32c5932d306f970532"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0085ef83a4141c2ed23bfec5fecbfdb1e95dd42fc8e8c76057bdeeec1608ea65"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5caf7f13f2e1b4e137060aed892d4541d07dabc3f29e6d891e2383c7ed483440"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f716bcc166524eddfcf9f13f8209ac19a7f27b05cf591e883419079d98c8c99d"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:507d6012fab05465d8bf21f5d7f4635ba4b6d60132874e349beff12fb51af7fe"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1545083b0931f754c80fd2422a73d83bea7a6d1b6de104a5f2c8dd3d64c291e"}, + {file = "orjson-3.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e217ce3bad76351e1eb29ebe5ca630326f45cd2141f62620107a229909501a3"}, + {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06ef26e009304bda4df42e4afe518994cde6f89b4b04c0ff24021064f83f4fbb"}, + {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:ba49683b87bea3ae1489a88e766e767d4f423a669a61270b6d6a7ead1c33bd65"}, + {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5072488fcc5cbcda2ece966d248e43ea1d222e19dd4c56d3f82747777f24d864"}, + {file = "orjson-3.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f58ae2bcd119226fe4aa934b5880fe57b8e97b69e51d5d91c88a89477a307016"}, + {file = "orjson-3.11.1-cp310-cp310-win32.whl", hash = "sha256:6723be919c07906781b9c63cc52dc7d2fb101336c99dd7e85d3531d73fb493f7"}, + {file = "orjson-3.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:5fd44d69ddfdfb4e8d0d83f09d27a4db34930fba153fbf79f8d4ae8b47914e04"}, + {file = "orjson-3.11.1-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:15e2a57ce3b57c1a36acffcc02e823afefceee0a532180c2568c62213c98e3ef"}, + {file = "orjson-3.11.1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:17040a83ecaa130474af05bbb59a13cfeb2157d76385556041f945da936b1afd"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a68f23f09e5626cc0867a96cf618f68b91acb4753d33a80bf16111fd7f9928c"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47e07528bb6ccbd6e32a55e330979048b59bfc5518b47c89bc7ab9e3de15174a"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3807cce72bf40a9d251d689cbec28d2efd27e0f6673709f948f971afd52cb09"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b2dc7e88da4ca201c940f5e6127998d9e89aa64264292334dad62854bc7fc27"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3091dad33ac9e67c0a550cfff8ad5be156e2614d6f5d2a9247df0627751a1495"}, + {file = "orjson-3.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ed0fce2307843b79a0c83de49f65b86197f1e2310de07af9db2a1a77a61ce4c"}, + {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a31e84782a18c30abd56774c0cfa7b9884589f4d37d9acabfa0504dad59bb9d"}, + {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26b6c821abf1ae515fbb8e140a2406c9f9004f3e52acb780b3dee9bfffddbd84"}, + {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f857b3d134b36a8436f1e24dcb525b6b945108b30746c1b0b556200b5cb76d39"}, + {file = "orjson-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df146f2a14116ce80f7da669785fcb411406d8e80136558b0ecda4c924b9ac55"}, + {file = "orjson-3.11.1-cp311-cp311-win32.whl", hash = "sha256:d777c57c1f86855fe5492b973f1012be776e0398571f7cc3970e9a58ecf4dc17"}, + {file = "orjson-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:e9a5fd589951f02ec2fcb8d69339258bbf74b41b104c556e6d4420ea5e059313"}, + {file = "orjson-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:4cddbe41ee04fddad35d75b9cf3e3736ad0b80588280766156b94783167777af"}, + {file = "orjson-3.11.1-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2b7c8be96db3a977367250c6367793a3c5851a6ca4263f92f0b48d00702f9910"}, + {file = "orjson-3.11.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:72e18088f567bd4a45db5e3196677d9ed1605e356e500c8e32dd6e303167a13d"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d346e2ae1ce17888f7040b65a5a4a0c9734cb20ffbd228728661e020b4c8b3a5"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4bda5426ebb02ceb806a7d7ec9ba9ee5e0c93fca62375151a7b1c00bc634d06b"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10506cebe908542c4f024861102673db534fd2e03eb9b95b30d94438fa220abf"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45202ee3f5494644e064c41abd1320497fb92fd31fc73af708708af664ac3b56"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5adaf01b92e0402a9ac5c3ebe04effe2bbb115f0914a0a53d34ea239a746289"}, + {file = "orjson-3.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6162a1a757a1f1f4a94bc6ffac834a3602e04ad5db022dd8395a54ed9dd51c81"}, + {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:78404206977c9f946613d3f916727c189d43193e708d760ea5d4b2087d6b0968"}, + {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:db48f8e81072e26df6cdb0e9fff808c28597c6ac20a13d595756cf9ba1fed48a"}, + {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c1e394e67ced6bb16fea7054d99fbdd99a539cf4d446d40378d4c06e0a8548d"}, + {file = "orjson-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e7a840752c93d4eecd1378e9bb465c3703e127b58f675cd5c620f361b6cf57a4"}, + {file = "orjson-3.11.1-cp312-cp312-win32.whl", hash = "sha256:4537b0e09f45d2b74cb69c7f39ca1e62c24c0488d6bf01cd24673c74cd9596bf"}, + {file = "orjson-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:dbee6b050062540ae404530cacec1bf25e56e8d87d8d9b610b935afeb6725cae"}, + {file = "orjson-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:f55e557d4248322d87c4673e085c7634039ff04b47bfc823b87149ae12bef60d"}, + {file = "orjson-3.11.1-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53cfefe4af059e65aabe9683f76b9c88bf34b4341a77d329227c2424e0e59b0e"}, + {file = "orjson-3.11.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:93d5abed5a6f9e1b6f9b5bf6ed4423c11932b5447c2f7281d3b64e0f26c6d064"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf06642f3db2966df504944cdd0eb68ca2717f0353bb20b20acd78109374a6"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dddf4e78747fa7f2188273f84562017a3c4f0824485b78372513c1681ea7a894"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa3fe8653c9f57f0e16f008e43626485b6723b84b2f741f54d1258095b655912"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6334d2382aff975a61f6f4d1c3daf39368b887c7de08f7c16c58f485dcf7adb2"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3d0855b643f259ee0cb76fe3df4c04483354409a520a902b067c674842eb6b8"}, + {file = "orjson-3.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eacdfeefd0a79987926476eb16e0245546bedeb8febbbbcf4b653e79257a8e4"}, + {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ed07faf9e4873518c60480325dcbc16d17c59a165532cccfb409b4cdbaeff24"}, + {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d308dd578ae3658f62bb9eba54801533225823cd3248c902be1ebc79b5e014"}, + {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c4aa13ca959ba6b15c0a98d3d204b850f9dc36c08c9ce422ffb024eb30d6e058"}, + {file = "orjson-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:be3d0653322abc9b68e5bcdaee6cfd58fcbe9973740ab222b87f4d687232ab1f"}, + {file = "orjson-3.11.1-cp313-cp313-win32.whl", hash = "sha256:4dd34e7e2518de8d7834268846f8cab7204364f427c56fb2251e098da86f5092"}, + {file = "orjson-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6895d32032b6362540e6d0694b19130bb4f2ad04694002dce7d8af588ca5f77"}, + {file = "orjson-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:bb7c36d5d3570fcbb01d24fa447a21a7fe5a41141fd88e78f7994053cc4e28f4"}, + {file = "orjson-3.11.1-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7b71ef394327b3d0b39f6ea7ade2ecda2731a56c6a7cbf0d6a7301203b92a89b"}, + {file = "orjson-3.11.1-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:77c0fe28ed659b62273995244ae2aa430e432c71f86e4573ab16caa2f2e3ca5e"}, + {file = "orjson-3.11.1-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:1495692f1f1ba2467df429343388a0ed259382835922e124c0cfdd56b3d1f727"}, + {file = "orjson-3.11.1-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:08c6a762fca63ca4dc04f66c48ea5d2428db55839fec996890e1bfaf057b658c"}, + {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e26794fe3976810b2c01fda29bd9ac7c91a3c1284b29cc9a383989f7b614037"}, + {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4b4b4f8f0b1d3ef8dc73e55363a0ffe012a42f4e2f1a140bf559698dca39b3fa"}, + {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:848be553ea35aa89bfefbed2e27c8a41244c862956ab8ba00dc0b27e84fd58de"}, + {file = "orjson-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c964c29711a4b1df52f8d9966f015402a6cf87753a406c1c4405c407dd66fd45"}, + {file = "orjson-3.11.1-cp314-cp314-win32.whl", hash = "sha256:33aada2e6b6bc9c540d396528b91e666cedb383740fee6e6a917f561b390ecb1"}, + {file = "orjson-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:68e10fd804e44e36188b9952543e3fa22f5aa8394da1b5283ca2b423735c06e8"}, + {file = "orjson-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:f3cf6c07f8b32127d836be8e1c55d4f34843f7df346536da768e9f73f22078a1"}, + {file = "orjson-3.11.1-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3d593a9e0bccf2c7401ae53625b519a7ad7aa555b1c82c0042b322762dc8af4e"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0baad413c498fc1eef568504f11ea46bc71f94b845c075e437da1e2b85b4fb86"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:22cf17ae1dae3f9b5f37bfcdba002ed22c98bbdb70306e42dc18d8cc9b50399a"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e855c1e97208133ce88b3ef6663c9a82ddf1d09390cd0856a1638deee0390c3c"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5861c5f7acff10599132854c70ab10abf72aebf7c627ae13575e5f20b1ab8fe"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1e6415c5b5ff3a616a6dafad7b6ec303a9fc625e9313c8e1268fb1370a63dcb"}, + {file = "orjson-3.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:912579642f5d7a4a84d93c5eed8daf0aa34e1f2d3f4dc6571a8e418703f5701e"}, + {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2092e1d3b33f64e129ff8271642afddc43763c81f2c30823b4a4a4a5f2ea5b55"}, + {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:b8ac64caba1add2c04e9cd4782d4d0c4d6c554b7a3369bdec1eed7854c98db7b"}, + {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:23196b826ebc85c43f8e27bee0ab33c5fb13a29ea47fb4fcd6ebb1e660eb0252"}, + {file = "orjson-3.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f2d3364cfad43003f1e3d564a069c8866237cca30f9c914b26ed2740b596ed00"}, + {file = "orjson-3.11.1-cp39-cp39-win32.whl", hash = "sha256:20b0dca94ea4ebe4628330de50975b35817a3f52954c1efb6d5d0498a3bbe581"}, + {file = "orjson-3.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:200c3ad7ed8b5d31d49143265dfebd33420c4b61934ead16833b5cd2c3d241be"}, + {file = "orjson-3.11.1.tar.gz", hash = "sha256:48d82770a5fd88778063604c566f9c7c71820270c9cc9338d25147cbf34afd96"}, ] [[package]] @@ -3755,6 +4071,7 @@ version = "7.7.0" description = "A decorator to automatically detect mismatch when overriding a method." optional = false python-versions = ">=3.6" +groups = ["main", "dev"] files = [ {file = "overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49"}, {file = "overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a"}, @@ -3766,6 +4083,7 @@ version = "24.2" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" +groups = ["main", "dev", "test"] files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -3773,53 +4091,54 @@ files = [ [[package]] name = "pandas" -version = "2.2.3" +version = "2.3.1" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, - {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, - {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, - {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, - {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, - {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, - {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, - {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, - {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, - {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, - {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, - {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, - {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, - {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, - {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, - {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, - {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, - {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, - {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, - {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, - {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, - {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, - {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, - {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, - {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, + {file = "pandas-2.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:22c2e866f7209ebc3a8f08d75766566aae02bcc91d196935a1d9e59c7b990ac9"}, + {file = "pandas-2.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3583d348546201aff730c8c47e49bc159833f971c2899d6097bce68b9112a4f1"}, + {file = "pandas-2.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f951fbb702dacd390561e0ea45cdd8ecfa7fb56935eb3dd78e306c19104b9b0"}, + {file = "pandas-2.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd05b72ec02ebfb993569b4931b2e16fbb4d6ad6ce80224a3ee838387d83a191"}, + {file = "pandas-2.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1b916a627919a247d865aed068eb65eb91a344b13f5b57ab9f610b7716c92de1"}, + {file = "pandas-2.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fe67dc676818c186d5a3d5425250e40f179c2a89145df477dd82945eaea89e97"}, + {file = "pandas-2.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:2eb789ae0274672acbd3c575b0598d213345660120a257b47b5dafdc618aec83"}, + {file = "pandas-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2b0540963d83431f5ce8870ea02a7430adca100cec8a050f0811f8e31035541b"}, + {file = "pandas-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fe7317f578c6a153912bd2292f02e40c1d8f253e93c599e82620c7f69755c74f"}, + {file = "pandas-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6723a27ad7b244c0c79d8e7007092d7c8f0f11305770e2f4cd778b3ad5f9f85"}, + {file = "pandas-2.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3462c3735fe19f2638f2c3a40bd94ec2dc5ba13abbb032dd2fa1f540a075509d"}, + {file = "pandas-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:98bcc8b5bf7afed22cc753a28bc4d9e26e078e777066bc53fac7904ddef9a678"}, + {file = "pandas-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d544806b485ddf29e52d75b1f559142514e60ef58a832f74fb38e48d757b299"}, + {file = "pandas-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b3cd4273d3cb3707b6fffd217204c52ed92859533e31dc03b7c5008aa933aaab"}, + {file = "pandas-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3"}, + {file = "pandas-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232"}, + {file = "pandas-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e"}, + {file = "pandas-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4"}, + {file = "pandas-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8"}, + {file = "pandas-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679"}, + {file = "pandas-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8"}, + {file = "pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22"}, + {file = "pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a"}, + {file = "pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928"}, + {file = "pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9"}, + {file = "pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12"}, + {file = "pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb"}, + {file = "pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956"}, + {file = "pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a"}, + {file = "pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9"}, + {file = "pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275"}, + {file = "pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab"}, + {file = "pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96"}, + {file = "pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444"}, + {file = "pandas-2.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4645f770f98d656f11c69e81aeb21c6fca076a44bed3dcbb9396a4311bc7f6d8"}, + {file = "pandas-2.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:342e59589cc454aaff7484d75b816a433350b3d7964d7847327edda4d532a2e3"}, + {file = "pandas-2.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d12f618d80379fde6af007f65f0c25bd3e40251dbd1636480dfffce2cf1e6da"}, + {file = "pandas-2.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd71c47a911da120d72ef173aeac0bf5241423f9bfea57320110a978457e069e"}, + {file = "pandas-2.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:09e3b1587f0f3b0913e21e8b32c3119174551deb4a4eba4a89bc7377947977e7"}, + {file = "pandas-2.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2323294c73ed50f612f67e2bf3ae45aea04dce5690778e08a09391897f35ff88"}, + {file = "pandas-2.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4b0de34dc8499c2db34000ef8baad684cfa4cbd836ecee05f323ebfba348c7d"}, + {file = "pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2"}, ] [package.dependencies] @@ -3859,6 +4178,7 @@ version = "1.5.1" description = "Utilities for writing pandoc filters in python" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["dev"] files = [ {file = "pandocfilters-1.5.1-py2.py3-none-any.whl", hash = "sha256:93be382804a9cdb0a7267585f157e5d1731bbe5545a85b268d6f5fe6232de2bc"}, {file = "pandocfilters-1.5.1.tar.gz", hash = "sha256:002b4a555ee4ebc03f8b66307e287fa492e4a77b4ea14d3f934328297bb4939e"}, @@ -3870,6 +4190,7 @@ version = "0.8.4" description = "A Python Parser" optional = false python-versions = ">=3.6" +groups = ["dev"] files = [ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, @@ -3885,6 +4206,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -3896,6 +4218,8 @@ version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, @@ -3906,112 +4230,139 @@ ptyprocess = ">=0.5" [[package]] name = "pillow" -version = "11.2.1" +version = "11.3.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pillow-11.2.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:d57a75d53922fc20c165016a20d9c44f73305e67c351bbc60d1adaf662e74047"}, - {file = "pillow-11.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:127bf6ac4a5b58b3d32fc8289656f77f80567d65660bc46f72c0d77e6600cc95"}, - {file = "pillow-11.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4ba4be812c7a40280629e55ae0b14a0aafa150dd6451297562e1764808bbe61"}, - {file = "pillow-11.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bd62331e5032bc396a93609982a9ab6b411c05078a52f5fe3cc59234a3abd1"}, - {file = "pillow-11.2.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:562d11134c97a62fe3af29581f083033179f7ff435f78392565a1ad2d1c2c45c"}, - {file = "pillow-11.2.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:c97209e85b5be259994eb5b69ff50c5d20cca0f458ef9abd835e262d9d88b39d"}, - {file = "pillow-11.2.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0c3e6d0f59171dfa2e25d7116217543310908dfa2770aa64b8f87605f8cacc97"}, - {file = "pillow-11.2.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc1c3bc53befb6096b84165956e886b1729634a799e9d6329a0c512ab651e579"}, - {file = "pillow-11.2.1-cp310-cp310-win32.whl", hash = "sha256:312c77b7f07ab2139924d2639860e084ec2a13e72af54d4f08ac843a5fc9c79d"}, - {file = "pillow-11.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:9bc7ae48b8057a611e5fe9f853baa88093b9a76303937449397899385da06fad"}, - {file = "pillow-11.2.1-cp310-cp310-win_arm64.whl", hash = "sha256:2728567e249cdd939f6cc3d1f049595c66e4187f3c34078cbc0a7d21c47482d2"}, - {file = "pillow-11.2.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35ca289f712ccfc699508c4658a1d14652e8033e9b69839edf83cbdd0ba39e70"}, - {file = "pillow-11.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0409af9f829f87a2dfb7e259f78f317a5351f2045158be321fd135973fff7bf"}, - {file = "pillow-11.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4e5c5edee874dce4f653dbe59db7c73a600119fbea8d31f53423586ee2aafd7"}, - {file = "pillow-11.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b93a07e76d13bff9444f1a029e0af2964e654bfc2e2c2d46bfd080df5ad5f3d8"}, - {file = "pillow-11.2.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:e6def7eed9e7fa90fde255afaf08060dc4b343bbe524a8f69bdd2a2f0018f600"}, - {file = "pillow-11.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8f4f3724c068be008c08257207210c138d5f3731af6c155a81c2b09a9eb3a788"}, - {file = "pillow-11.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a0a6709b47019dff32e678bc12c63008311b82b9327613f534e496dacaefb71e"}, - {file = "pillow-11.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f6b0c664ccb879109ee3ca702a9272d877f4fcd21e5eb63c26422fd6e415365e"}, - {file = "pillow-11.2.1-cp311-cp311-win32.whl", hash = "sha256:cc5d875d56e49f112b6def6813c4e3d3036d269c008bf8aef72cd08d20ca6df6"}, - {file = "pillow-11.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:0f5c7eda47bf8e3c8a283762cab94e496ba977a420868cb819159980b6709193"}, - {file = "pillow-11.2.1-cp311-cp311-win_arm64.whl", hash = "sha256:4d375eb838755f2528ac8cbc926c3e31cc49ca4ad0cf79cff48b20e30634a4a7"}, - {file = "pillow-11.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:78afba22027b4accef10dbd5eed84425930ba41b3ea0a86fa8d20baaf19d807f"}, - {file = "pillow-11.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78092232a4ab376a35d68c4e6d5e00dfd73454bd12b230420025fbe178ee3b0b"}, - {file = "pillow-11.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a5f306095c6780c52e6bbb6109624b95c5b18e40aab1c3041da3e9e0cd3e2d"}, - {file = "pillow-11.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c7b29dbd4281923a2bfe562acb734cee96bbb129e96e6972d315ed9f232bef4"}, - {file = "pillow-11.2.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:3e645b020f3209a0181a418bffe7b4a93171eef6c4ef6cc20980b30bebf17b7d"}, - {file = "pillow-11.2.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b2dbea1012ccb784a65349f57bbc93730b96e85b42e9bf7b01ef40443db720b4"}, - {file = "pillow-11.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:da3104c57bbd72948d75f6a9389e6727d2ab6333c3617f0a89d72d4940aa0443"}, - {file = "pillow-11.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:598174aef4589af795f66f9caab87ba4ff860ce08cd5bb447c6fc553ffee603c"}, - {file = "pillow-11.2.1-cp312-cp312-win32.whl", hash = "sha256:1d535df14716e7f8776b9e7fee118576d65572b4aad3ed639be9e4fa88a1cad3"}, - {file = "pillow-11.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:14e33b28bf17c7a38eede290f77db7c664e4eb01f7869e37fa98a5aa95978941"}, - {file = "pillow-11.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:21e1470ac9e5739ff880c211fc3af01e3ae505859392bf65458c224d0bf283eb"}, - {file = "pillow-11.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fdec757fea0b793056419bca3e9932eb2b0ceec90ef4813ea4c1e072c389eb28"}, - {file = "pillow-11.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0e130705d568e2f43a17bcbe74d90958e8a16263868a12c3e0d9c8162690830"}, - {file = "pillow-11.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bdb5e09068332578214cadd9c05e3d64d99e0e87591be22a324bdbc18925be0"}, - {file = "pillow-11.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d189ba1bebfbc0c0e529159631ec72bb9e9bc041f01ec6d3233d6d82eb823bc1"}, - {file = "pillow-11.2.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:191955c55d8a712fab8934a42bfefbf99dd0b5875078240943f913bb66d46d9f"}, - {file = "pillow-11.2.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:ad275964d52e2243430472fc5d2c2334b4fc3ff9c16cb0a19254e25efa03a155"}, - {file = "pillow-11.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:750f96efe0597382660d8b53e90dd1dd44568a8edb51cb7f9d5d918b80d4de14"}, - {file = "pillow-11.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fe15238d3798788d00716637b3d4e7bb6bde18b26e5d08335a96e88564a36b6b"}, - {file = "pillow-11.2.1-cp313-cp313-win32.whl", hash = "sha256:3fe735ced9a607fee4f481423a9c36701a39719252a9bb251679635f99d0f7d2"}, - {file = "pillow-11.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:74ee3d7ecb3f3c05459ba95eed5efa28d6092d751ce9bf20e3e253a4e497e691"}, - {file = "pillow-11.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:5119225c622403afb4b44bad4c1ca6c1f98eed79db8d3bc6e4e160fc6339d66c"}, - {file = "pillow-11.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8ce2e8411c7aaef53e6bb29fe98f28cd4fbd9a1d9be2eeea434331aac0536b22"}, - {file = "pillow-11.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ee66787e095127116d91dea2143db65c7bb1e232f617aa5957c0d9d2a3f23a7"}, - {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9622e3b6c1d8b551b6e6f21873bdcc55762b4b2126633014cea1803368a9aa16"}, - {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63b5dff3a68f371ea06025a1a6966c9a1e1ee452fc8020c2cd0ea41b83e9037b"}, - {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:31df6e2d3d8fc99f993fd253e97fae451a8db2e7207acf97859732273e108406"}, - {file = "pillow-11.2.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:062b7a42d672c45a70fa1f8b43d1d38ff76b63421cbbe7f88146b39e8a558d91"}, - {file = "pillow-11.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4eb92eca2711ef8be42fd3f67533765d9fd043b8c80db204f16c8ea62ee1a751"}, - {file = "pillow-11.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f91ebf30830a48c825590aede79376cb40f110b387c17ee9bd59932c961044f9"}, - {file = "pillow-11.2.1-cp313-cp313t-win32.whl", hash = "sha256:e0b55f27f584ed623221cfe995c912c61606be8513bfa0e07d2c674b4516d9dd"}, - {file = "pillow-11.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:36d6b82164c39ce5482f649b437382c0fb2395eabc1e2b1702a6deb8ad647d6e"}, - {file = "pillow-11.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:225c832a13326e34f212d2072982bb1adb210e0cc0b153e688743018c94a2681"}, - {file = "pillow-11.2.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:7491cf8a79b8eb867d419648fff2f83cb0b3891c8b36da92cc7f1931d46108c8"}, - {file = "pillow-11.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b02d8f9cb83c52578a0b4beadba92e37d83a4ef11570a8688bbf43f4ca50909"}, - {file = "pillow-11.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:014ca0050c85003620526b0ac1ac53f56fc93af128f7546623cc8e31875ab928"}, - {file = "pillow-11.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3692b68c87096ac6308296d96354eddd25f98740c9d2ab54e1549d6c8aea9d79"}, - {file = "pillow-11.2.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:f781dcb0bc9929adc77bad571b8621ecb1e4cdef86e940fe2e5b5ee24fd33b35"}, - {file = "pillow-11.2.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:2b490402c96f907a166615e9a5afacf2519e28295f157ec3a2bb9bd57de638cb"}, - {file = "pillow-11.2.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dd6b20b93b3ccc9c1b597999209e4bc5cf2853f9ee66e3fc9a400a78733ffc9a"}, - {file = "pillow-11.2.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4b835d89c08a6c2ee7781b8dd0a30209a8012b5f09c0a665b65b0eb3560b6f36"}, - {file = "pillow-11.2.1-cp39-cp39-win32.whl", hash = "sha256:b10428b3416d4f9c61f94b494681280be7686bda15898a3a9e08eb66a6d92d67"}, - {file = "pillow-11.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:6ebce70c3f486acf7591a3d73431fa504a4e18a9b97ff27f5f47b7368e4b9dd1"}, - {file = "pillow-11.2.1-cp39-cp39-win_arm64.whl", hash = "sha256:c27476257b2fdcd7872d54cfd119b3a9ce4610fb85c8e32b70b42e3680a29a1e"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9b7b0d4fd2635f54ad82785d56bc0d94f147096493a79985d0ab57aedd563156"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:aa442755e31c64037aa7c1cb186e0b369f8416c567381852c63444dd666fb772"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d3348c95b766f54b76116d53d4cb171b52992a1027e7ca50c81b43b9d9e363"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85d27ea4c889342f7e35f6d56e7e1cb345632ad592e8c51b693d7b7556043ce0"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bf2c33d6791c598142f00c9c4c7d47f6476731c31081331664eb26d6ab583e01"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e616e7154c37669fc1dfc14584f11e284e05d1c650e1c0f972f281c4ccc53193"}, - {file = "pillow-11.2.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:39ad2e0f424394e3aebc40168845fee52df1394a4673a6ee512d840d14ab3013"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80f1df8dbe9572b4b7abdfa17eb5d78dd620b1d55d9e25f834efdbee872d3aed"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ea926cfbc3957090becbcbbb65ad177161a2ff2ad578b5a6ec9bb1e1cd78753c"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:738db0e0941ca0376804d4de6a782c005245264edaa253ffce24e5a15cbdc7bd"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9db98ab6565c69082ec9b0d4e40dd9f6181dab0dd236d26f7a50b8b9bfbd5076"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:036e53f4170e270ddb8797d4c590e6dd14d28e15c7da375c18978045f7e6c37b"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:14f73f7c291279bd65fda51ee87affd7c1e097709f7fdd0188957a16c264601f"}, - {file = "pillow-11.2.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:208653868d5c9ecc2b327f9b9ef34e0e42a4cdd172c2988fd81d62d2bc9bc044"}, - {file = "pillow-11.2.1.tar.gz", hash = "sha256:a64dd61998416367b7ef979b73d3a85853ba9bec4c2925f74e588879a58716b6"}, + {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, + {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7107195ddc914f656c7fc8e4a5e1c25f32e9236ea3ea860f257b0436011fddd0"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc3e831b563b3114baac7ec2ee86819eb03caa1a2cef0b481a5675b59c4fe23b"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f1f182ebd2303acf8c380a54f615ec883322593320a9b00438eb842c1f37ae50"}, + {file = "pillow-11.3.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4445fa62e15936a028672fd48c4c11a66d641d2c05726c7ec1f8ba6a572036ae"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:71f511f6b3b91dd543282477be45a033e4845a40278fa8dcdbfdb07109bf18f9"}, + {file = "pillow-11.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:040a5b691b0713e1f6cbe222e0f4f74cd233421e105850ae3b3c0ceda520f42e"}, + {file = "pillow-11.3.0-cp310-cp310-win32.whl", hash = "sha256:89bd777bc6624fe4115e9fac3352c79ed60f3bb18651420635f26e643e3dd1f6"}, + {file = "pillow-11.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:19d2ff547c75b8e3ff46f4d9ef969a06c30ab2d4263a9e287733aa8b2429ce8f"}, + {file = "pillow-11.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:819931d25e57b513242859ce1876c58c59dc31587847bf74cfe06b2e0cb22d2f"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722"}, + {file = "pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58"}, + {file = "pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e"}, + {file = "pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94"}, + {file = "pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0"}, + {file = "pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac"}, + {file = "pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"}, + {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"}, + {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"}, + {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"}, + {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"}, + {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"}, + {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"}, + {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"}, + {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"}, + {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"}, + {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"}, + {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"}, + {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"}, + {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"}, + {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"}, + {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"}, + {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"}, + {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"}, + {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"}, + {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"}, + {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"}, + {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"}, + {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"}, + {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"}, + {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"}, + {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"}, + {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"}, + {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"}, + {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"}, + {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"}, + {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"}, + {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:48d254f8a4c776de343051023eb61ffe818299eeac478da55227d96e241de53f"}, + {file = "pillow-11.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7aee118e30a4cf54fdd873bd3a29de51e29105ab11f9aad8c32123f58c8f8081"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:23cff760a9049c502721bdb743a7cb3e03365fafcdfc2ef9784610714166e5a4"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6359a3bc43f57d5b375d1ad54a0074318a0844d11b76abccf478c37c986d3cfc"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:092c80c76635f5ecb10f3f83d76716165c96f5229addbd1ec2bdbbda7d496e06"}, + {file = "pillow-11.3.0-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cadc9e0ea0a2431124cde7e1697106471fc4c1da01530e679b2391c37d3fbb3a"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:6a418691000f2a418c9135a7cf0d797c1bb7d9a485e61fe8e7722845b95ef978"}, + {file = "pillow-11.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:97afb3a00b65cc0804d1c7abddbf090a81eaac02768af58cbdcaaa0a931e0b6d"}, + {file = "pillow-11.3.0-cp39-cp39-win32.whl", hash = "sha256:ea944117a7974ae78059fcc1800e5d3295172bb97035c0c1d9345fca1419da71"}, + {file = "pillow-11.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:e5c5858ad8ec655450a7c7df532e9842cf8df7cc349df7225c60d5d348c8aada"}, + {file = "pillow-11.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:6abdbfd3aea42be05702a8dd98832329c167ee84400a1d1f61ab11437f1717eb"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:3cee80663f29e3843b68199b9d6f4f54bd1d4a6b59bdd91bceefc51238bcb967"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b5f56c3f344f2ccaf0dd875d3e180f631dc60a51b314295a3e681fe8cf851fbe"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e67d793d180c9df62f1f40aee3accca4829d3794c95098887edc18af4b8b780c"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d000f46e2917c705e9fb93a3606ee4a819d1e3aa7a9b442f6444f07e77cf5e25"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:527b37216b6ac3a12d7838dc3bd75208ec57c1c6d11ef01902266a5a0c14fc27"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be5463ac478b623b9dd3937afd7fb7ab3d79dd290a28e2b6df292dc75063eb8a"}, + {file = "pillow-11.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:8dc70ca24c110503e16918a658b869019126ecfe03109b754c402daff12b3d9f"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7"}, + {file = "pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8"}, + {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=8.2)", "sphinx-autobuild", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] test-arrow = ["pyarrow"] -tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"] +tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] typing = ["typing-extensions"] xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.3.7" +version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, - {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, + {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, + {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, ] [package.extras] @@ -4021,53 +4372,54 @@ type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" -version = "1.5.0" +version = "1.6.0" description = "plugin and hook calling mechanisms for python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "test"] files = [ - {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, - {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, + {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"}, + {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"}, ] [package.extras] dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] +testing = ["coverage", "pytest", "pytest-benchmark"] [[package]] name = "posthog" -version = "3.23.0" +version = "5.4.0" description = "Integrate PostHog into any python application." optional = false -python-versions = "*" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "posthog-3.23.0-py2.py3-none-any.whl", hash = "sha256:2b07d06670170ac2e21465dffa8d356722834cc877ab34e583da6e525c1037df"}, - {file = "posthog-3.23.0.tar.gz", hash = "sha256:1ac0305ab6c54a80c4a82c137231f17616bef007bbf474d1a529cda032d808eb"}, + {file = "posthog-5.4.0-py3-none-any.whl", hash = "sha256:284dfa302f64353484420b52d4ad81ff5c2c2d1d607c4e2db602ac72761831bd"}, + {file = "posthog-5.4.0.tar.gz", hash = "sha256:701669261b8d07cdde0276e5bc096b87f9e200e3b9589c5ebff14df658c5893c"}, ] [package.dependencies] backoff = ">=1.10.0" distro = ">=1.5.0" -monotonic = ">=1.5" -python-dateutil = ">2.1" +python-dateutil = ">=2.2" requests = ">=2.7,<3.0" six = ">=1.5" [package.extras] -dev = ["black", "django-stubs", "flake8", "flake8-print", "isort", "lxml", "mypy", "mypy-baseline", "pre-commit", "pydantic", "types-mock", "types-python-dateutil", "types-requests", "types-setuptools", "types-six"] +dev = ["django-stubs", "lxml", "mypy", "mypy-baseline", "packaging", "pre-commit", "pydantic", "ruff", "setuptools", "tomli", "tomli_w", "twine", "types-mock", "types-python-dateutil", "types-requests", "types-setuptools", "types-six", "wheel"] langchain = ["langchain (>=0.2.0)"] -sentry = ["django", "sentry-sdk"] -test = ["anthropic", "coverage", "django", "flake8", "freezegun (==1.5.1)", "langchain-anthropic (>=0.2.0)", "langchain-community (>=0.2.0)", "langchain-openai (>=0.2.0)", "langgraph", "mock (>=2.0.0)", "openai", "parameterized (>=0.8.1)", "pydantic", "pylint", "pytest", "pytest-asyncio", "pytest-timeout"] +test = ["anthropic", "coverage", "django", "freezegun (==1.5.1)", "google-genai", "langchain-anthropic (>=0.3.15)", "langchain-community (>=0.3.25)", "langchain-core (>=0.3.65)", "langchain-openai (>=0.3.22)", "langgraph (>=0.4.8)", "mock (>=2.0.0)", "openai", "parameterized (>=0.8.1)", "pydantic", "pytest", "pytest-asyncio", "pytest-timeout"] [[package]] name = "prometheus-client" -version = "0.21.1" +version = "0.22.1" description = "Python client for the Prometheus monitoring system." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "prometheus_client-0.21.1-py3-none-any.whl", hash = "sha256:594b45c410d6f4f8888940fe80b5cc2521b305a1fafe1c58609ef715a001f301"}, - {file = "prometheus_client-0.21.1.tar.gz", hash = "sha256:252505a722ac04b0456be05c05f75f45d760c2911ffc45f2a06bcaed9f3ae3fb"}, + {file = "prometheus_client-0.22.1-py3-none-any.whl", hash = "sha256:cca895342e308174341b2cbf99a56bef291fbc0ef7b9e5412a0f26d653ba7094"}, + {file = "prometheus_client-0.22.1.tar.gz", hash = "sha256:190f1331e783cf21eb60bca559354e0a4d4378facecf78f5428c39b675d20d28"}, ] [package.extras] @@ -4075,13 +4427,14 @@ twisted = ["twisted"] [[package]] name = "prompt-toolkit" -version = "3.0.50" +version = "3.0.51" description = "Library for building powerful interactive command lines in Python" optional = false -python-versions = ">=3.8.0" +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198"}, - {file = "prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab"}, + {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, + {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, ] [package.dependencies] @@ -4089,129 +4442,129 @@ wcwidth = "*" [[package]] name = "propcache" -version = "0.3.1" +version = "0.3.2" description = "Accelerated property cache" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f27785888d2fdd918bc36de8b8739f2d6c791399552333721b58193f68ea3e98"}, - {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4e89cde74154c7b5957f87a355bb9c8ec929c167b59c83d90654ea36aeb6180"}, - {file = "propcache-0.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:730178f476ef03d3d4d255f0c9fa186cb1d13fd33ffe89d39f2cda4da90ceb71"}, - {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967a8eec513dbe08330f10137eacb427b2ca52118769e82ebcfcab0fba92a649"}, - {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b9145c35cc87313b5fd480144f8078716007656093d23059e8993d3a8fa730f"}, - {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e64e948ab41411958670f1093c0a57acfdc3bee5cf5b935671bbd5313bcf229"}, - {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:319fa8765bfd6a265e5fa661547556da381e53274bc05094fc9ea50da51bfd46"}, - {file = "propcache-0.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66d8ccbc902ad548312b96ed8d5d266d0d2c6d006fd0f66323e9d8f2dd49be7"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2d219b0dbabe75e15e581fc1ae796109b07c8ba7d25b9ae8d650da582bed01b0"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd6a55f65241c551eb53f8cf4d2f4af33512c39da5d9777694e9d9c60872f519"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9979643ffc69b799d50d3a7b72b5164a2e97e117009d7af6dfdd2ab906cb72cd"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4cf9e93a81979f1424f1a3d155213dc928f1069d697e4353edb8a5eba67c6259"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2fce1df66915909ff6c824bbb5eb403d2d15f98f1518e583074671a30fe0c21e"}, - {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4d0dfdd9a2ebc77b869a0b04423591ea8823f791293b527dc1bb896c1d6f1136"}, - {file = "propcache-0.3.1-cp310-cp310-win32.whl", hash = "sha256:1f6cc0ad7b4560e5637eb2c994e97b4fa41ba8226069c9277eb5ea7101845b42"}, - {file = "propcache-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:47ef24aa6511e388e9894ec16f0fbf3313a53ee68402bc428744a367ec55b833"}, - {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5"}, - {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371"}, - {file = "propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da"}, - {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744"}, - {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0"}, - {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5"}, - {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256"}, - {file = "propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a"}, - {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9"}, - {file = "propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005"}, - {file = "propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7"}, - {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723"}, - {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976"}, - {file = "propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b"}, - {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f"}, - {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70"}, - {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7"}, - {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25"}, - {file = "propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5"}, - {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7"}, - {file = "propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b"}, - {file = "propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3"}, - {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8"}, - {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f"}, - {file = "propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111"}, - {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5"}, - {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb"}, - {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7"}, - {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120"}, - {file = "propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7"}, - {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef"}, - {file = "propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24"}, - {file = "propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037"}, - {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f"}, - {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c"}, - {file = "propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc"}, - {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de"}, - {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6"}, - {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7"}, - {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458"}, - {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18"}, - {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a"}, - {file = "propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d"}, - {file = "propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e"}, - {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ed5f6d2edbf349bd8d630e81f474d33d6ae5d07760c44d33cd808e2f5c8f4ae6"}, - {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:668ddddc9f3075af019f784456267eb504cb77c2c4bd46cc8402d723b4d200bf"}, - {file = "propcache-0.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c86e7ceea56376216eba345aa1fc6a8a6b27ac236181f840d1d7e6a1ea9ba5c"}, - {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83be47aa4e35b87c106fc0c84c0fc069d3f9b9b06d3c494cd404ec6747544894"}, - {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27c6ac6aa9fc7bc662f594ef380707494cb42c22786a558d95fcdedb9aa5d035"}, - {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a956dff37080b352c1c40b2966b09defb014347043e740d420ca1eb7c9b908"}, - {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82de5da8c8893056603ac2d6a89eb8b4df49abf1a7c19d536984c8dd63f481d5"}, - {file = "propcache-0.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3c3a203c375b08fd06a20da3cf7aac293b834b6f4f4db71190e8422750cca5"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b303b194c2e6f171cfddf8b8ba30baefccf03d36a4d9cab7fd0bb68ba476a3d7"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:916cd229b0150129d645ec51614d38129ee74c03293a9f3f17537be0029a9641"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a461959ead5b38e2581998700b26346b78cd98540b5524796c175722f18b0294"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:069e7212890b0bcf9b2be0a03afb0c2d5161d91e1bf51569a64f629acc7defbf"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef2e4e91fb3945769e14ce82ed53007195e616a63aa43b40fb7ebaaf907c8d4c"}, - {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8638f99dca15b9dff328fb6273e09f03d1c50d9b6512f3b65a4154588a7595fe"}, - {file = "propcache-0.3.1-cp39-cp39-win32.whl", hash = "sha256:6f173bbfe976105aaa890b712d1759de339d8a7cef2fc0a1714cc1a1e1c47f64"}, - {file = "propcache-0.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:603f1fe4144420374f1a69b907494c3acbc867a581c2d49d4175b0de7cc64566"}, - {file = "propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40"}, - {file = "propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:22d9962a358aedbb7a2e36187ff273adeaab9743373a272976d2e348d08c7770"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0d0fda578d1dc3f77b6b5a5dce3b9ad69a8250a891760a548df850a5e8da87f3"}, + {file = "propcache-0.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3def3da3ac3ce41562d85db655d18ebac740cb3fa4367f11a52b3da9d03a5cc3"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bec58347a5a6cebf239daba9bda37dffec5b8d2ce004d9fe4edef3d2815137e"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55ffda449a507e9fbd4aca1a7d9aa6753b07d6166140e5a18d2ac9bc49eac220"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a67fb39229a8a8491dd42f864e5e263155e729c2e7ff723d6e25f596b1e8cb"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da1cf97b92b51253d5b68cf5a2b9e0dafca095e36b7f2da335e27dc6172a614"}, + {file = "propcache-0.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5f559e127134b07425134b4065be45b166183fdcb433cb6c24c8e4149056ad50"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:aff2e4e06435d61f11a428360a932138d0ec288b0a31dd9bd78d200bd4a2b339"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4927842833830942a5d0a56e6f4839bc484785b8e1ce8d287359794818633ba0"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6107ddd08b02654a30fb8ad7a132021759d750a82578b94cd55ee2772b6ebea2"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:70bd8b9cd6b519e12859c99f3fc9a93f375ebd22a50296c3a295028bea73b9e7"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2183111651d710d3097338dd1893fcf09c9f54e27ff1a8795495a16a469cc90b"}, + {file = "propcache-0.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb075ad271405dcad8e2a7ffc9a750a3bf70e533bd86e89f0603e607b93aa64c"}, + {file = "propcache-0.3.2-cp310-cp310-win32.whl", hash = "sha256:404d70768080d3d3bdb41d0771037da19d8340d50b08e104ca0e7f9ce55fce70"}, + {file = "propcache-0.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:7435d766f978b4ede777002e6b3b6641dd229cd1da8d3d3106a45770365f9ad9"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f"}, + {file = "propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df"}, + {file = "propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf"}, + {file = "propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e"}, + {file = "propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897"}, + {file = "propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"}, + {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"}, + {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"}, + {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"}, + {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"}, + {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"}, + {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"}, + {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"}, + {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"}, + {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"}, + {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"}, + {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"}, + {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"}, + {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"}, + {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"}, + {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a7fad897f14d92086d6b03fdd2eb844777b0c4d7ec5e3bac0fbae2ab0602bbe5"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1f43837d4ca000243fd7fd6301947d7cb93360d03cd08369969450cc6b2ce3b4"}, + {file = "propcache-0.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:261df2e9474a5949c46e962065d88eb9b96ce0f2bd30e9d3136bcde84befd8f2"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e514326b79e51f0a177daab1052bc164d9d9e54133797a3a58d24c9c87a3fe6d"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d4a996adb6904f85894570301939afeee65f072b4fd265ed7e569e8d9058e4ec"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76cace5d6b2a54e55b137669b30f31aa15977eeed390c7cbfb1dafa8dfe9a701"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31248e44b81d59d6addbb182c4720f90b44e1efdc19f58112a3c3a1615fb47ef"}, + {file = "propcache-0.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb7fa19dbf88d3857363e0493b999b8011eea856b846305d8c0512dfdf8fbb1"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d81ac3ae39d38588ad0549e321e6f773a4e7cc68e7751524a22885d5bbadf886"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:cc2782eb0f7a16462285b6f8394bbbd0e1ee5f928034e941ffc444012224171b"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:db429c19a6c7e8a1c320e6a13c99799450f411b02251fb1b75e6217cf4a14fcb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:21d8759141a9e00a681d35a1f160892a36fb6caa715ba0b832f7747da48fb6ea"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2ca6d378f09adb13837614ad2754fa8afaee330254f404299611bce41a8438cb"}, + {file = "propcache-0.3.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:34a624af06c048946709f4278b4176470073deda88d91342665d95f7c6270fbe"}, + {file = "propcache-0.3.2-cp39-cp39-win32.whl", hash = "sha256:4ba3fef1c30f306b1c274ce0b8baaa2c3cdd91f645c48f06394068f37d3837a1"}, + {file = "propcache-0.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:7a2368eed65fc69a7a7a40b27f22e85e7627b74216f0846b04ba5c116e191ec9"}, + {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"}, + {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"}, ] [[package]] name = "protobuf" -version = "5.29.4" +version = "6.31.1" description = "" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "protobuf-5.29.4-cp310-abi3-win32.whl", hash = "sha256:13eb236f8eb9ec34e63fc8b1d6efd2777d062fa6aaa68268fb67cf77f6839ad7"}, - {file = "protobuf-5.29.4-cp310-abi3-win_amd64.whl", hash = "sha256:bcefcdf3976233f8a502d265eb65ea740c989bacc6c30a58290ed0e519eb4b8d"}, - {file = "protobuf-5.29.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:307ecba1d852ec237e9ba668e087326a67564ef83e45a0189a772ede9e854dd0"}, - {file = "protobuf-5.29.4-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:aec4962f9ea93c431d5714ed1be1c93f13e1a8618e70035ba2b0564d9e633f2e"}, - {file = "protobuf-5.29.4-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:d7d3f7d1d5a66ed4942d4fefb12ac4b14a29028b209d4bfb25c68ae172059922"}, - {file = "protobuf-5.29.4-cp38-cp38-win32.whl", hash = "sha256:1832f0515b62d12d8e6ffc078d7e9eb06969aa6dc13c13e1036e39d73bebc2de"}, - {file = "protobuf-5.29.4-cp38-cp38-win_amd64.whl", hash = "sha256:476cb7b14914c780605a8cf62e38c2a85f8caff2e28a6a0bad827ec7d6c85d68"}, - {file = "protobuf-5.29.4-cp39-cp39-win32.whl", hash = "sha256:fd32223020cb25a2cc100366f1dedc904e2d71d9322403224cdde5fdced0dabe"}, - {file = "protobuf-5.29.4-cp39-cp39-win_amd64.whl", hash = "sha256:678974e1e3a9b975b8bc2447fca458db5f93a2fb6b0c8db46b6675b5b5346812"}, - {file = "protobuf-5.29.4-py3-none-any.whl", hash = "sha256:3fde11b505e1597f71b875ef2fc52062b6a9740e5f7c8997ce878b6009145862"}, - {file = "protobuf-5.29.4.tar.gz", hash = "sha256:4f1dfcd7997b31ef8f53ec82781ff434a28bf71d9102ddde14d076adcfc78c99"}, + {file = "protobuf-6.31.1-cp310-abi3-win32.whl", hash = "sha256:7fa17d5a29c2e04b7d90e5e32388b8bfd0e7107cd8e616feef7ed3fa6bdab5c9"}, + {file = "protobuf-6.31.1-cp310-abi3-win_amd64.whl", hash = "sha256:426f59d2964864a1a366254fa703b8632dcec0790d8862d30034d8245e1cd447"}, + {file = "protobuf-6.31.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:6f1227473dc43d44ed644425268eb7c2e488ae245d51c6866d19fe158e207402"}, + {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:a40fc12b84c154884d7d4c4ebd675d5b3b5283e155f324049ae396b95ddebc39"}, + {file = "protobuf-6.31.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:4ee898bf66f7a8b0bd21bce523814e6fbd8c6add948045ce958b73af7e8878c6"}, + {file = "protobuf-6.31.1-cp39-cp39-win32.whl", hash = "sha256:0414e3aa5a5f3ff423828e1e6a6e907d6c65c1d5b7e6e975793d5590bdeecc16"}, + {file = "protobuf-6.31.1-cp39-cp39-win_amd64.whl", hash = "sha256:8764cf4587791e7564051b35524b72844f845ad0bb011704c3736cce762d8fe9"}, + {file = "protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e"}, + {file = "protobuf-6.31.1.tar.gz", hash = "sha256:d8cac4c982f0b957a4dc73a80e2ea24fab08e679c0de9deb835f4a12d69aca9a"}, ] [[package]] @@ -4220,6 +4573,7 @@ version = "6.1.1" description = "Cross-platform lib for process and system monitoring in Python." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"}, {file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"}, @@ -4250,6 +4604,8 @@ version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" +groups = ["dev"] +markers = "os_name != \"nt\" or sys_platform != \"win32\" and sys_platform != \"emscripten\"" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, @@ -4261,6 +4617,7 @@ version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, @@ -4275,6 +4632,7 @@ version = "0.6.1" description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, @@ -4286,6 +4644,7 @@ version = "0.4.2" description = "A collection of ASN.1-based protocols modules" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a"}, {file = "pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6"}, @@ -4294,31 +4653,252 @@ files = [ [package.dependencies] pyasn1 = ">=0.6.1,<0.7.0" +[[package]] +name = "pybase64" +version = "1.4.2" +description = "Fast Base64 encoding/decoding" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pybase64-1.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82b4593b480773b17698fef33c68bae0e1c474ba07663fad74249370c46b46c9"}, + {file = "pybase64-1.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a126f29d29cb4a498db179135dbf955442a0de5b00f374523f5dcceb9074ff58"}, + {file = "pybase64-1.4.2-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1eef93c29cc5567480d168f9cc1ebd3fc3107c65787aed2019a8ea68575a33e0"}, + {file = "pybase64-1.4.2-cp310-cp310-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:17b871a34aaeb0644145cb6bf28feb163f593abea11aec3dbcc34a006edfc828"}, + {file = "pybase64-1.4.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1f734e16293637a35d282ce594eb05a7a90ea3ae2bc84a3496a5df9e6b890725"}, + {file = "pybase64-1.4.2-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:22bd38db2d990d5545dde83511edeec366630d00679dbd945472315c09041dc6"}, + {file = "pybase64-1.4.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:dc65cee686dda72007b7541b2014f33ee282459c781b9b61305bd8b9cfadc8e1"}, + {file = "pybase64-1.4.2-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1e79641c420a22e49c67c046895efad05bf5f8b1dbe0dd78b4af3ab3f2923fe2"}, + {file = "pybase64-1.4.2-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:12f5e7db522ef780a8b333dab5f7d750d270b23a1684bc2235ba50756c7ba428"}, + {file = "pybase64-1.4.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a618b1e1a63e75dd40c2a397d875935ed0835464dc55cb1b91e8f880113d0444"}, + {file = "pybase64-1.4.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:89b0a51702c7746fa914e75e680ad697b979cdead6b418603f56a6fc9de2f50f"}, + {file = "pybase64-1.4.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c5161b8b82f8ba5dbbc3f76e0270622a2c2fdb9ffaf092d8f774ad7ec468c027"}, + {file = "pybase64-1.4.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:2168de920c9b1e57850e9ff681852923a953601f73cc96a0742a42236695c316"}, + {file = "pybase64-1.4.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7a1e3dc977562abe40ab43483223013be71b215a5d5f3c78a666e70a5076eeec"}, + {file = "pybase64-1.4.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4cf1e8a57449e48137ef4de00a005e24c3f1cffc0aafc488e36ceb5bb2cbb1da"}, + {file = "pybase64-1.4.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d8e1a381ba124f26a93d5925efbf6e6c36287fc2c93d74958e8b677c30a53fc0"}, + {file = "pybase64-1.4.2-cp310-cp310-win32.whl", hash = "sha256:8fdd9c5b60ec9a1db854f5f96bba46b80a9520069282dc1d37ff433eb8248b1f"}, + {file = "pybase64-1.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:37a6c73f14c6539c0ad1aebf0cce92138af25c99a6e7aee637d9f9fc634c8a40"}, + {file = "pybase64-1.4.2-cp310-cp310-win_arm64.whl", hash = "sha256:b3280d03b7b361622c469d005cc270d763d9e29d0a490c26addb4f82dfe71a79"}, + {file = "pybase64-1.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:26284ef64f142067293347bcc9d501d2b5d44b92eab9d941cb10a085fb01c666"}, + {file = "pybase64-1.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:52dd32fe5cbfd8af8f3f034a4a65ee61948c72e5c358bf69d59543fc0dbcf950"}, + {file = "pybase64-1.4.2-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:37f133e8c96427995480bb6d396d9d49e949a3e829591845bb6a5a7f215ca177"}, + {file = "pybase64-1.4.2-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6ee3874b0abbdd4c903d3989682a3f016fd84188622879f6f95a5dc5718d7e5"}, + {file = "pybase64-1.4.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c69f177b1e404b22b05802127d6979acf4cb57f953c7de9472410f9c3fdece7"}, + {file = "pybase64-1.4.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:80c817e88ef2ca3cc9a285fde267690a1cb821ce0da4848c921c16f0fec56fda"}, + {file = "pybase64-1.4.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7a4bb6e7e45bfdaea0f2aaf022fc9a013abe6e46ccea31914a77e10f44098688"}, + {file = "pybase64-1.4.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2710a80d41a2b41293cb0e5b84b5464f54aa3f28f7c43de88784d2d9702b8a1c"}, + {file = "pybase64-1.4.2-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:aa6122c8a81f6597e1c1116511f03ed42cf377c2100fe7debaae7ca62521095a"}, + {file = "pybase64-1.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7e22b02505d64db308e9feeb6cb52f1d554ede5983de0befa59ac2d2ffb6a5f"}, + {file = "pybase64-1.4.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:edfe4a3c8c4007f09591f49b46a89d287ef5e8cd6630339536fe98ff077263c2"}, + {file = "pybase64-1.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b79b4a53dd117ffbd03e96953f2e6bd2827bfe11afeb717ea16d9b0893603077"}, + {file = "pybase64-1.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fd9afa7a61d89d170607faf22287290045757e782089f0357b8f801d228d52c3"}, + {file = "pybase64-1.4.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5c17b092e4da677a595178d2db17a5d2fafe5c8e418d46c0c4e4cde5adb8cff3"}, + {file = "pybase64-1.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:120799274cf55f3f5bb8489eaa85142f26170564baafa7cf3e85541c46b6ab13"}, + {file = "pybase64-1.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:522e4e712686acec2d25de9759dda0b0618cb9f6588523528bc74715c0245c7b"}, + {file = "pybase64-1.4.2-cp311-cp311-win32.whl", hash = "sha256:bfd828792982db8d787515535948c1e340f1819407c8832f94384c0ebeaf9d74"}, + {file = "pybase64-1.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7a9e89d40dbf833af481d1d5f1a44d173c9c4b56a7c8dba98e39a78ee87cfc52"}, + {file = "pybase64-1.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:ce5809fa90619b03eab1cd63fec142e6cf1d361731a9b9feacf27df76c833343"}, + {file = "pybase64-1.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db2c75d1388855b5a1015b65096d7dbcc708e7de3245dcbedeb872ec05a09326"}, + {file = "pybase64-1.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b621a972a01841368fdb9dedc55fd3c6e0c7217d0505ba3b1ebe95e7ef1b493"}, + {file = "pybase64-1.4.2-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f48c32ac6a16cbf57a5a96a073fef6ff7e3526f623cd49faa112b7f9980bafba"}, + {file = "pybase64-1.4.2-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ace8b23093a6bb862477080d9059b784096ab2f97541e8bfc40d42f062875149"}, + {file = "pybase64-1.4.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1772c7532a7fb6301baea3dd3e010148dbf70cd1136a83c2f5f91bdc94822145"}, + {file = "pybase64-1.4.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:f86f7faddcba5cbfea475f8ab96567834c28bf09ca6c7c3d66ee445adac80d8f"}, + {file = "pybase64-1.4.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:0b8c8e275b5294089f314814b4a50174ab90af79d6a4850f6ae11261ff6a7372"}, + {file = "pybase64-1.4.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:864d85a0470c615807ae8b97d724d068b940a2d10ac13a5f1b9e75a3ce441758"}, + {file = "pybase64-1.4.2-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:47254d97ed2d8351e30ecfdb9e2414547f66ba73f8a09f932c9378ff75cd10c5"}, + {file = "pybase64-1.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:264b65ecc4f0ee73f3298ab83bbd8008f7f9578361b8df5b448f985d8c63e02a"}, + {file = "pybase64-1.4.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbcc2b30cd740c16c9699f596f22c7a9e643591311ae72b1e776f2d539e9dd9d"}, + {file = "pybase64-1.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cda9f79c22d51ee4508f5a43b673565f1d26af4330c99f114e37e3186fdd3607"}, + {file = "pybase64-1.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0c91c6d2a7232e2a1cd10b3b75a8bb657defacd4295a1e5e80455df2dfc84d4f"}, + {file = "pybase64-1.4.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:a370dea7b1cee2a36a4d5445d4e09cc243816c5bc8def61f602db5a6f5438e52"}, + {file = "pybase64-1.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9aa4de83f02e462a6f4e066811c71d6af31b52d7484de635582d0e3ec3d6cc3e"}, + {file = "pybase64-1.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83a1c2f9ed00fee8f064d548c8654a480741131f280e5750bb32475b7ec8ee38"}, + {file = "pybase64-1.4.2-cp312-cp312-win32.whl", hash = "sha256:a6e5688b18d558e8c6b8701cc8560836c4bbeba61d33c836b4dba56b19423716"}, + {file = "pybase64-1.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:c995d21b8bd08aa179cd7dd4db0695c185486ecc72da1e8f6c37ec86cadb8182"}, + {file = "pybase64-1.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:e254b9258c40509c2ea063a7784f6994988f3f26099d6e08704e3c15dfed9a55"}, + {file = "pybase64-1.4.2-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:0f331aa59549de21f690b6ccc79360ffed1155c3cfbc852eb5c097c0b8565a2b"}, + {file = "pybase64-1.4.2-cp313-cp313-android_21_x86_64.whl", hash = "sha256:9dad20bf1f3ed9e6fe566c4c9d07d9a6c04f5a280daebd2082ffb8620b0a880d"}, + {file = "pybase64-1.4.2-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:69d3f0445b0faeef7bb7f93bf8c18d850785e2a77f12835f49e524cc54af04e7"}, + {file = "pybase64-1.4.2-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2372b257b1f4dd512f317fb27e77d313afd137334de64c87de8374027aacd88a"}, + {file = "pybase64-1.4.2-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:fb794502b4b1ec91c4ca5d283ae71aef65e3de7721057bd9e2b3ec79f7a62d7d"}, + {file = "pybase64-1.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d5c532b03fd14a5040d6cf6571299a05616f925369c72ddf6fe2fb643eb36fed"}, + {file = "pybase64-1.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f699514dc1d5689ca9cf378139e0214051922732f9adec9404bc680a8bef7c0"}, + {file = "pybase64-1.4.2-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:cd3e8713cbd32c8c6aa935feaf15c7670e2b7e8bfe51c24dc556811ebd293a29"}, + {file = "pybase64-1.4.2-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d377d48acf53abf4b926c2a7a24a19deb092f366a04ffd856bf4b3aa330b025d"}, + {file = "pybase64-1.4.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d83c076e78d619b9e1dd674e2bf5fb9001aeb3e0b494b80a6c8f6d4120e38cd9"}, + {file = "pybase64-1.4.2-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:ab9cdb6a8176a5cb967f53e6ad60e40c83caaa1ae31c5e1b29e5c8f507f17538"}, + {file = "pybase64-1.4.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:adf0c103ad559dbfb9fe69edfd26a15c65d9c991a5ab0a25b04770f9eb0b9484"}, + {file = "pybase64-1.4.2-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:0d03ef2f253d97ce0685d3624bf5e552d716b86cacb8a6c971333ba4b827e1fc"}, + {file = "pybase64-1.4.2-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:e565abf906efee76ae4be1aef5df4aed0fda1639bc0d7732a3dafef76cb6fc35"}, + {file = "pybase64-1.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3c6a5f15fd03f232fc6f295cce3684f7bb08da6c6d5b12cc771f81c9f125cc6"}, + {file = "pybase64-1.4.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:bad9e3db16f448728138737bbd1af9dc2398efd593a8bdd73748cc02cd33f9c6"}, + {file = "pybase64-1.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2683ef271328365c31afee0ed8fa29356fb8fb7c10606794656aa9ffb95e92be"}, + {file = "pybase64-1.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:265b20089cd470079114c09bb74b101b3bfc3c94ad6b4231706cf9eff877d570"}, + {file = "pybase64-1.4.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e53173badead10ef8b839aa5506eecf0067c7b75ad16d9bf39bc7144631f8e67"}, + {file = "pybase64-1.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5823b8dcf74da7da0f761ed60c961e8928a6524e520411ad05fe7f9f47d55b40"}, + {file = "pybase64-1.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1237f66c54357d325390da60aa5e21c6918fbcd1bf527acb9c1f4188c62cb7d5"}, + {file = "pybase64-1.4.2-cp313-cp313-win32.whl", hash = "sha256:b0b851eb4f801d16040047f6889cca5e9dfa102b3e33f68934d12511245cef86"}, + {file = "pybase64-1.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:19541c6e26d17d9522c02680fe242206ae05df659c82a657aabadf209cd4c6c7"}, + {file = "pybase64-1.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:77a191863d576c0a5dd81f8a568a5ca15597cc980ae809dce62c717c8d42d8aa"}, + {file = "pybase64-1.4.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2e194bbabe3fdf9e47ba9f3e157394efe0849eb226df76432126239b3f44992c"}, + {file = "pybase64-1.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:39aef1dadf4a004f11dd09e703abaf6528a87c8dbd39c448bb8aebdc0a08c1be"}, + {file = "pybase64-1.4.2-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:91cb920c7143e36ec8217031282c8651da3b2206d70343f068fac0e7f073b7f9"}, + {file = "pybase64-1.4.2-cp313-cp313t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6958631143fb9e71f9842000da042ec2f6686506b6706e2dfda29e97925f6aa0"}, + {file = "pybase64-1.4.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:dc35f14141ef3f1ac70d963950a278a2593af66fe5a1c7a208e185ca6278fa25"}, + {file = "pybase64-1.4.2-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:5d949d2d677859c3a8507e1b21432a039d2b995e0bd3fe307052b6ded80f207a"}, + {file = "pybase64-1.4.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:09caacdd3e15fe7253a67781edd10a6a918befab0052a2a3c215fe5d1f150269"}, + {file = "pybase64-1.4.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:e44b0e793b23f28ea0f15a9754bd0c960102a2ac4bccb8fafdedbd4cc4d235c0"}, + {file = "pybase64-1.4.2-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:849f274d0bcb90fc6f642c39274082724d108e41b15f3a17864282bd41fc71d5"}, + {file = "pybase64-1.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:528dba7ef1357bd7ce1aea143084501f47f5dd0fff7937d3906a68565aa59cfe"}, + {file = "pybase64-1.4.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:1da54be743d9a68671700cfe56c3ab8c26e8f2f5cc34eface905c55bc3a9af94"}, + {file = "pybase64-1.4.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9b07c0406c3eaa7014499b0aacafb21a6d1146cfaa85d56f0aa02e6d542ee8f3"}, + {file = "pybase64-1.4.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:312f2aa4cf5d199a97fbcaee75d2e59ebbaafcd091993eb373b43683498cdacb"}, + {file = "pybase64-1.4.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad59362fc267bf15498a318c9e076686e4beeb0dfe09b457fabbc2b32468b97a"}, + {file = "pybase64-1.4.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:01593bd064e7dcd6c86d04e94e44acfe364049500c20ac68ca1e708fbb2ca970"}, + {file = "pybase64-1.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5b81547ad8ea271c79fdf10da89a1e9313cb15edcba2a17adf8871735e9c02a0"}, + {file = "pybase64-1.4.2-cp313-cp313t-win32.whl", hash = "sha256:7edbe70b5654545a37e6e6b02de738303b1bbdfcde67f6cfec374cfb5cc4099e"}, + {file = "pybase64-1.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:385690addf87c25d6366fab5d8ff512eed8a7ecb18da9e8152af1c789162f208"}, + {file = "pybase64-1.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c2070d0aa88580f57fe15ca88b09f162e604d19282915a95a3795b5d3c1c05b5"}, + {file = "pybase64-1.4.2-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:4157ad277a32cf4f02a975dffc62a3c67d73dfa4609b2c1978ef47e722b18b8e"}, + {file = "pybase64-1.4.2-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e113267dc349cf624eb4f4fbf53fd77835e1aa048ac6877399af426aab435757"}, + {file = "pybase64-1.4.2-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:cea5aaf218fd9c5c23afacfe86fd4464dfedc1a0316dd3b5b4075b068cc67df0"}, + {file = "pybase64-1.4.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:41213497abbd770435c7a9c8123fb02b93709ac4cf60155cd5aefc5f3042b600"}, + {file = "pybase64-1.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c8b522df7ee00f2ac1993ccd5e1f6608ae7482de3907668c2ff96a83ef213925"}, + {file = "pybase64-1.4.2-cp314-cp314-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:06725022e540c5b098b978a0418ca979773e2cbdbb76f10bd97536f2ad1c5b49"}, + {file = "pybase64-1.4.2-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a3e54dcf0d0305ec88473c9d0009f698cabf86f88a8a10090efeff2879c421bb"}, + {file = "pybase64-1.4.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:67675cee727a60dc91173d2790206f01aa3c7b3fbccfa84fd5c1e3d883fe6caa"}, + {file = "pybase64-1.4.2-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:753da25d4fd20be7bda2746f545935773beea12d5cb5ec56ec2d2960796477b1"}, + {file = "pybase64-1.4.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:a78c768ce4ca550885246d14babdb8923e0f4a848dfaaeb63c38fc99e7ea4052"}, + {file = "pybase64-1.4.2-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:51b17f36d890c92f0618fb1c8db2ccc25e6ed07afa505bab616396fc9b0b0492"}, + {file = "pybase64-1.4.2-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f92218d667049ab4f65d54fa043a88ffdb2f07fff1f868789ef705a5221de7ec"}, + {file = "pybase64-1.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3547b3d1499919a06491b3f879a19fbe206af2bd1a424ecbb4e601eb2bd11fea"}, + {file = "pybase64-1.4.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:958af7b0e09ddeb13e8c2330767c47b556b1ade19c35370f6451d139cde9f2a9"}, + {file = "pybase64-1.4.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4facc57f6671e2229a385a97a618273e7be36a9ea0a9d1c1b9347f14d19ceba8"}, + {file = "pybase64-1.4.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:a32fc57d05d73a7c9b0ca95e9e265e21cf734195dc6873829a890058c35f5cfd"}, + {file = "pybase64-1.4.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:3dc853243c81ce89cc7318e6946f860df28ddb7cd2a0648b981652d9ad09ee5a"}, + {file = "pybase64-1.4.2-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:0e6d863a86b3e7bc6ac9bd659bebda4501b9da842521111b0b0e54eb51295df5"}, + {file = "pybase64-1.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6579475140ff2067903725d8aca47f5747bcb211597a1edd60b58f6d90ada2bd"}, + {file = "pybase64-1.4.2-cp314-cp314-win32.whl", hash = "sha256:373897f728d7b4f241a1f803ac732c27b6945d26d86b2741ad9b75c802e4e378"}, + {file = "pybase64-1.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:1afe3361344617d298c1d08bc657ef56d0f702d6b72cb65d968b2771017935aa"}, + {file = "pybase64-1.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:f131c9360babe522f3d90f34da3f827cba80318125cf18d66f2ee27e3730e8c4"}, + {file = "pybase64-1.4.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2583ac304131c1bd6e3120b0179333610f18816000db77c0a2dd6da1364722a8"}, + {file = "pybase64-1.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:75a8116be4ea4cdd30a5c4f1a6f3b038e0d457eb03c8a2685d8ce2aa00ef8f92"}, + {file = "pybase64-1.4.2-cp314-cp314t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:217ea776a098d7c08668e5526b9764f5048bbfd28cac86834217ddfe76a4e3c4"}, + {file = "pybase64-1.4.2-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ec14683e343c95b14248cdfdfa78c052582be7a3865fd570aa7cffa5ab5cf37"}, + {file = "pybase64-1.4.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:480ecf21e1e956c5a10d3cf7b3b7e75bce3f9328cf08c101e4aab1925d879f34"}, + {file = "pybase64-1.4.2-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:1fe1ebdc55e9447142e2f6658944aadfb5a4fbf03dbd509be34182585515ecc1"}, + {file = "pybase64-1.4.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c793a2b06753accdaf5e1a8bbe5d800aab2406919e5008174f989a1ca0081411"}, + {file = "pybase64-1.4.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6acae6e1d1f7ebe40165f08076c7a73692b2bf9046fefe673f350536e007f556"}, + {file = "pybase64-1.4.2-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:88b91cd0949358aadcea75f8de5afbcf3c8c5fb9ec82325bd24285b7119cf56e"}, + {file = "pybase64-1.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:53316587e1b1f47a11a5ff068d3cbd4a3911c291f2aec14882734973684871b2"}, + {file = "pybase64-1.4.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:caa7f20f43d00602cf9043b5ba758d54f5c41707d3709b2a5fac17361579c53c"}, + {file = "pybase64-1.4.2-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:2d93817e24fdd79c534ed97705df855af6f1d2535ceb8dfa80da9de75482a8d7"}, + {file = "pybase64-1.4.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:63cd769b51474d8d08f7f2ce73b30380d9b4078ec92ea6b348ea20ed1e1af88a"}, + {file = "pybase64-1.4.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cd07e6a9993c392ec8eb03912a43c6a6b21b2deb79ee0d606700fe276e9a576f"}, + {file = "pybase64-1.4.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:6a8944e8194adff4668350504bc6b7dbde2dab9244c88d99c491657d145b5af5"}, + {file = "pybase64-1.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:04ab398ec4b6a212af57f6a21a6336d5a1d754ff4ccb215951366ab9080481b2"}, + {file = "pybase64-1.4.2-cp314-cp314t-win32.whl", hash = "sha256:3b9201ecdcb1c3e23be4caebd6393a4e6615bd0722528f5413b58e22e3792dd3"}, + {file = "pybase64-1.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:36e9b0cad8197136d73904ef5a71d843381d063fd528c5ab203fc4990264f682"}, + {file = "pybase64-1.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:f25140496b02db0e7401567cd869fb13b4c8118bf5c2428592ec339987146d8b"}, + {file = "pybase64-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d176c83a9cd45a8b27786372b9b5815803bdf812b7e65be86df75660df3d9443"}, + {file = "pybase64-1.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8aea9abde684d282def3697839163ad5167f9381d5adde6b9d05bf39b1decda"}, + {file = "pybase64-1.4.2-cp38-cp38-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:39120d4a650d7c66689c226131e2942142a5b1b27ccf190f441b1a602bc1e6a5"}, + {file = "pybase64-1.4.2-cp38-cp38-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e67579d2081344b2e43a78fe1604a9637056eed2bfb61bf4a1f847e81525cb3"}, + {file = "pybase64-1.4.2-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4142c58d6a7a57eb094725bec40f2cd46488d8f204e956750a6565cd506322d"}, + {file = "pybase64-1.4.2-cp38-cp38-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:4a6a417a94c2934faa8f84e8279c57092a54045340e26305a07a6691d2890766"}, + {file = "pybase64-1.4.2-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:66071c72417f5cb4640d3291644afc95eba06297cca5dbcacbea5c7181f3a05e"}, + {file = "pybase64-1.4.2-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5257751ff60f9acb2971baf70063dff549fe154ce6be1e7a1808e140d79598d9"}, + {file = "pybase64-1.4.2-cp38-cp38-manylinux_2_31_riscv64.whl", hash = "sha256:86d3294a07c37c8ce8f3eb24c62a5157699ddeb75f4ae7b4922e8765b8fbe3fb"}, + {file = "pybase64-1.4.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:bb9e8eba5461acaf5fd69c66e170d9174e3aaae67d42dbc9590e0883e099fd47"}, + {file = "pybase64-1.4.2-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:845c2fa4f0ec45ca48c60c9ed6714c5266f62850c767c86fb0e137b3f5f7585b"}, + {file = "pybase64-1.4.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:3bed71e32075895e06b2ca9faf136ee805db2ade4715b4732b119ef0e5ffcb52"}, + {file = "pybase64-1.4.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:88bbcab0f58ffc9fd79ab8aa047b64e1e04514194d8e7c9f450451682e7555bf"}, + {file = "pybase64-1.4.2-cp38-cp38-musllinux_1_2_riscv64.whl", hash = "sha256:b5a1d81b4a10a4b724fa7bc7cbd2d527b21030089940d6acc50bf5ad29849e5e"}, + {file = "pybase64-1.4.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5b5694af6f4632633372fcb678c7fe56b953c33961f39d57086abb08ef5dcbf4"}, + {file = "pybase64-1.4.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:58f0e40d8128c55dee2309d41e027e0cf22f4931b43aa590ee785ea4eff88f8d"}, + {file = "pybase64-1.4.2-cp38-cp38-win32.whl", hash = "sha256:d93691f52e1396abfe93a75bc5da4c029649c004d8eefd08f20340b17db51429"}, + {file = "pybase64-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b9d4a8e6fce1c2943dce37db9b66f7cf88082ef0ef68025183c48fb3b0d8068a"}, + {file = "pybase64-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5f47f00221f6892c6f8532f7c2e449b491e0fd86de73e9306cfe88768570eff1"}, + {file = "pybase64-1.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:514ad5d72b1990453c895015392729521757eca1a984327c0f9e44af6854385d"}, + {file = "pybase64-1.4.2-cp39-cp39-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2089a72b04a62f63e0eac202ecff4440fb52fd05cd5f4ab9fe7e07839fedb9e9"}, + {file = "pybase64-1.4.2-cp39-cp39-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bad101c24dcd23ed6fd6ea24c4a1b36ac7abc5eb07447dd7fa98b33859aed871"}, + {file = "pybase64-1.4.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:28592c88a9cf6fd27c9f191fb41688c1c27f57493d874cbc50e72e1cc2a3b854"}, + {file = "pybase64-1.4.2-cp39-cp39-manylinux2014_armv7l.manylinux_2_17_armv7l.whl", hash = "sha256:0b5639fa2ceb3095393bd56dca8c16079717c361dd3a75439c9a8b8d679f4cf0"}, + {file = "pybase64-1.4.2-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:49630338d4c321336d0dfc4c2c23162a87d9ebc8bb8879348ae019ac8a4366de"}, + {file = "pybase64-1.4.2-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c3d9f9881d7315e1d04d72aa7b3f40e2059bdbfdcec51939016409417725c952"}, + {file = "pybase64-1.4.2-cp39-cp39-manylinux_2_31_riscv64.whl", hash = "sha256:8e1226939eac9ce1f080d1b0a8afafee3140e277a4c40ccb306d82de396a41a8"}, + {file = "pybase64-1.4.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:69f424a227ec503742bac69b89e232c474dc199cd98c3e58e91020c1c4bad0ad"}, + {file = "pybase64-1.4.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:171ae85837de14d3691d5c4f29f5bb551209930c063a2cab6f5feb270aec66db"}, + {file = "pybase64-1.4.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a55a13493fd165c3a619080149eda6f31c05c04c0577da9c9ef63d23f3abf374"}, + {file = "pybase64-1.4.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:06801fdc7fa83eac5cb7d1c7051bb623a25af8cb40e088671fa51a393d1053ad"}, + {file = "pybase64-1.4.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:7f2fbd6870228e9c8c3e2e2622ed7615a8d0159125b85e9d6c2d8e9ead74cdf0"}, + {file = "pybase64-1.4.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1726017f04da880d10a57f078d117fe62532b5ed7bd58bd3318f3364b9767d91"}, + {file = "pybase64-1.4.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1264f7fa417de7183732761f37c8ceb4652662a84f04538a28dadd5d84bf9a4a"}, + {file = "pybase64-1.4.2-cp39-cp39-win32.whl", hash = "sha256:8ad0c411898280a924eb41e07389666c89cfe1389cb4c24e3853cb1949872893"}, + {file = "pybase64-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:11c5698b696f681fe04c6ccf11c346d438d05f1a542dbb5e5cdf6c27c348431d"}, + {file = "pybase64-1.4.2-cp39-cp39-win_arm64.whl", hash = "sha256:e64721ae9252a62caf06f2df5d22065d02f28cd2768b610be84c37856ac4a3a8"}, + {file = "pybase64-1.4.2-graalpy311-graalpy242_311_native-macosx_10_9_x86_64.whl", hash = "sha256:b4eed40a5f1627ee65613a6ac834a33f8ba24066656f569c852f98eb16f6ab5d"}, + {file = "pybase64-1.4.2-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:57885fa521e9add235af4db13e9e048d3a2934cd27d7c5efac1925e1b4d6538d"}, + {file = "pybase64-1.4.2-graalpy311-graalpy242_311_native-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:eef9255d926c64e2fca021d3aee98023bacb98e1518e5986d6aab04102411b04"}, + {file = "pybase64-1.4.2-graalpy311-graalpy242_311_native-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:89614ea2d2329b6708746c540e0f14d692125df99fb1203ff0de948d9e68dfc9"}, + {file = "pybase64-1.4.2-graalpy311-graalpy242_311_native-win_amd64.whl", hash = "sha256:e401cecd2d7ddcd558768b2140fd4430746be4d17fb14c99eec9e40789df136d"}, + {file = "pybase64-1.4.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4b29c93414ba965777643a9d98443f08f76ac04519ad717aa859113695372a07"}, + {file = "pybase64-1.4.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5e0c3353c0bf099c5c3f8f750202c486abee8f23a566b49e9e7b1222fbf5f259"}, + {file = "pybase64-1.4.2-pp310-pypy310_pp73-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:4f98c5c6152d3c01d933fcde04322cd9ddcf65b5346034aac69a04c1a7cbb012"}, + {file = "pybase64-1.4.2-pp310-pypy310_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9096a4977b7aff7ef250f759fb6a4b6b7b6199d99c84070c7fc862dd3b208b34"}, + {file = "pybase64-1.4.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49d8597e2872966399410502310b1e2a5b7e8d8ba96766ee1fe242e00bd80775"}, + {file = "pybase64-1.4.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ef16366565389a287df82659e055e88bdb6c36e46a3394950903e0a9cb2e5bf"}, + {file = "pybase64-1.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0a5393be20b0705870f5a8969749af84d734c077de80dd7e9f5424a247afa85e"}, + {file = "pybase64-1.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:448f0259a2f1a17eb086f70fe2ad9b556edba1fc5bc4e62ce6966179368ee9f8"}, + {file = "pybase64-1.4.2-pp311-pypy311_pp73-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:1159e70cba8e76c3d8f334bd1f8fd52a1bb7384f4c3533831b23ab2df84a6ef3"}, + {file = "pybase64-1.4.2-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7d943bc5dad8388971494554b97f22ae06a46cc7779ad0de3d4bfdf7d0bbea30"}, + {file = "pybase64-1.4.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:10b99182c561d86422c5de4265fd1f8f172fb38efaed9d72c71fb31e279a7f94"}, + {file = "pybase64-1.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bb082c1114f046e59fcbc4f2be13edc93b36d7b54b58605820605be948f8fdf6"}, + {file = "pybase64-1.4.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:49ff078c0afd2c6ba355a5b999c321b8554e3673eff5a413d83b40e9cfb53b96"}, + {file = "pybase64-1.4.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad9c5ac606cb232dfd6679519c86333d4d665732b6fcaab4653ae531990da8b6"}, + {file = "pybase64-1.4.2-pp38-pypy38_pp73-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b30e66969a5bee39d31ede36f5866be59991cdcbb597fe734b02753ca0e18e04"}, + {file = "pybase64-1.4.2-pp38-pypy38_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4eef95fe6adfa5763a79874be77944edde2d16f765eca8841f1cc9f2310eb3b2"}, + {file = "pybase64-1.4.2-pp38-pypy38_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5b315f0d01eb25ec7a6c7e9ea0c69b82165f4653ff4bc17790fdadf7650eb0e1"}, + {file = "pybase64-1.4.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:ba8781dad971d657be171c66abd4f45deb6aa982fa8d8bfd552ea48bbd8d2a09"}, + {file = "pybase64-1.4.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4dc4e353ff54ea480cf78aa629df927f7280920d35015f402a541fbfcbf2ba6b"}, + {file = "pybase64-1.4.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:4e8acd1e02aa4b80dd834dd703ef040d5c1127f39e4052011bf5d3f4bc917c41"}, + {file = "pybase64-1.4.2-pp39-pypy39_pp73-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:45f078139d76194024e59b4bcfa64d42e5a5f8a5a4ea55ca4d27df46989c5e32"}, + {file = "pybase64-1.4.2-pp39-pypy39_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:06305e602f128b289b98490a2d07d9d78e7e781e32e7b0252c2e71084fd19edf"}, + {file = "pybase64-1.4.2-pp39-pypy39_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d58eb4cb50b6466cef2e25761a5c915a8d57feda53165cced537a7ce0421b928"}, + {file = "pybase64-1.4.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21e72a662a62eba34a91e9424b21db99b8fc5cce99932ce736167496965fa154"}, + {file = "pybase64-1.4.2.tar.gz", hash = "sha256:46cdefd283ed9643315d952fe44de80dc9b9a811ce6e3ec97fd1827af97692d0"}, +] + [[package]] name = "pycparser" version = "2.22" description = "C parser in Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, ] +markers = {main = "platform_python_implementation == \"PyPy\" and (python_version < \"3.14\" or implementation_name != \"PyPy\")", dev = "python_version < \"3.14\" or implementation_name != \"PyPy\""} [[package]] name = "pydantic" -version = "2.11.3" +version = "2.11.7" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f"}, - {file = "pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3"}, + {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"}, + {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.33.1" +pydantic-core = "2.33.2" typing-extensions = ">=4.12.2" typing-inspection = ">=0.4.0" @@ -4328,110 +4908,111 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.33.1" +version = "2.33.2" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26"}, - {file = "pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927"}, - {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5183e4f6a2d468787243ebcd70cf4098c247e60d73fb7d68d5bc1e1beaa0c4db"}, - {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:398a38d323f37714023be1e0285765f0a27243a8b1506b7b7de87b647b517e48"}, - {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:87d3776f0001b43acebfa86f8c64019c043b55cc5a6a2e313d728b5c95b46969"}, - {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c566dd9c5f63d22226409553531f89de0cac55397f2ab8d97d6f06cfce6d947e"}, - {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0d5f3acc81452c56895e90643a625302bd6be351e7010664151cc55b7b97f89"}, - {file = "pydantic_core-2.33.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3a07fadec2a13274a8d861d3d37c61e97a816beae717efccaa4b36dfcaadcde"}, - {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f99aeda58dce827f76963ee87a0ebe75e648c72ff9ba1174a253f6744f518f65"}, - {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:902dbc832141aa0ec374f4310f1e4e7febeebc3256f00dc359a9ac3f264a45dc"}, - {file = "pydantic_core-2.33.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fe44d56aa0b00d66640aa84a3cbe80b7a3ccdc6f0b1ca71090696a6d4777c091"}, - {file = "pydantic_core-2.33.1-cp310-cp310-win32.whl", hash = "sha256:ed3eb16d51257c763539bde21e011092f127a2202692afaeaccb50db55a31383"}, - {file = "pydantic_core-2.33.1-cp310-cp310-win_amd64.whl", hash = "sha256:694ad99a7f6718c1a498dc170ca430687a39894a60327f548e02a9c7ee4b6504"}, - {file = "pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24"}, - {file = "pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30"}, - {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595"}, - {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e"}, - {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a"}, - {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505"}, - {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f"}, - {file = "pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77"}, - {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961"}, - {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1"}, - {file = "pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c"}, - {file = "pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896"}, - {file = "pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83"}, - {file = "pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89"}, - {file = "pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8"}, - {file = "pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498"}, - {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939"}, - {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d"}, - {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e"}, - {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3"}, - {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d"}, - {file = "pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b"}, - {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39"}, - {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a"}, - {file = "pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db"}, - {file = "pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda"}, - {file = "pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4"}, - {file = "pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea"}, - {file = "pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a"}, - {file = "pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266"}, - {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3"}, - {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a"}, - {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516"}, - {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764"}, - {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d"}, - {file = "pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4"}, - {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde"}, - {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e"}, - {file = "pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd"}, - {file = "pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f"}, - {file = "pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40"}, - {file = "pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523"}, - {file = "pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d"}, - {file = "pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c"}, - {file = "pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18"}, - {file = "pydantic_core-2.33.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:5ab77f45d33d264de66e1884fca158bc920cb5e27fd0764a72f72f5756ae8bdb"}, - {file = "pydantic_core-2.33.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e7aaba1b4b03aaea7bb59e1b5856d734be011d3e6d98f5bcaa98cb30f375f2ad"}, - {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fb66263e9ba8fea2aa85e1e5578980d127fb37d7f2e292773e7bc3a38fb0c7b"}, - {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f2648b9262607a7fb41d782cc263b48032ff7a03a835581abbf7a3bec62bcf5"}, - {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:723c5630c4259400818b4ad096735a829074601805d07f8cafc366d95786d331"}, - {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d100e3ae783d2167782391e0c1c7a20a31f55f8015f3293647544df3f9c67824"}, - {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177d50460bc976a0369920b6c744d927b0ecb8606fb56858ff542560251b19e5"}, - {file = "pydantic_core-2.33.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3edde68d1a1f9af1273b2fe798997b33f90308fb6d44d8550c89fc6a3647cf6"}, - {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a62c3c3ef6a7e2c45f7853b10b5bc4ddefd6ee3cd31024754a1a5842da7d598d"}, - {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:c91dbb0ab683fa0cd64a6e81907c8ff41d6497c346890e26b23de7ee55353f96"}, - {file = "pydantic_core-2.33.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f466e8bf0a62dc43e068c12166281c2eca72121dd2adc1040f3aa1e21ef8599"}, - {file = "pydantic_core-2.33.1-cp39-cp39-win32.whl", hash = "sha256:ab0277cedb698749caada82e5d099dc9fed3f906a30d4c382d1a21725777a1e5"}, - {file = "pydantic_core-2.33.1-cp39-cp39-win_amd64.whl", hash = "sha256:5773da0ee2d17136b1f1c6fbde543398d452a6ad2a7b54ea1033e2daa739b8d2"}, - {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c834f54f8f4640fd7e4b193f80eb25a0602bba9e19b3cd2fc7ffe8199f5ae02"}, - {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:049e0de24cf23766f12cc5cc71d8abc07d4a9deb9061b334b62093dedc7cb068"}, - {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a28239037b3d6f16916a4c831a5a0eadf856bdd6d2e92c10a0da3a59eadcf3e"}, - {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d3da303ab5f378a268fa7d45f37d7d85c3ec19769f28d2cc0c61826a8de21fe"}, - {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:25626fb37b3c543818c14821afe0fd3830bc327a43953bc88db924b68c5723f1"}, - {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3ab2d36e20fbfcce8f02d73c33a8a7362980cff717926bbae030b93ae46b56c7"}, - {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:2f9284e11c751b003fd4215ad92d325d92c9cb19ee6729ebd87e3250072cdcde"}, - {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:048c01eee07d37cbd066fc512b9d8b5ea88ceeb4e629ab94b3e56965ad655add"}, - {file = "pydantic_core-2.33.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5ccd429694cf26af7997595d627dd2637e7932214486f55b8a357edaac9dae8c"}, - {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a"}, - {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc"}, - {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b"}, - {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe"}, - {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5"}, - {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761"}, - {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850"}, - {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544"}, - {file = "pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5"}, - {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7edbc454a29fc6aeae1e1eecba4f07b63b8d76e76a748532233c4c167b4cb9ea"}, - {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ad05b683963f69a1d5d2c2bdab1274a31221ca737dbbceaa32bcb67359453cdd"}, - {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df6a94bf9452c6da9b5d76ed229a5683d0306ccb91cca8e1eea883189780d568"}, - {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7965c13b3967909a09ecc91f21d09cfc4576bf78140b988904e94f130f188396"}, - {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3f1fdb790440a34f6ecf7679e1863b825cb5ffde858a9197f851168ed08371e5"}, - {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:5277aec8d879f8d05168fdd17ae811dd313b8ff894aeeaf7cd34ad28b4d77e33"}, - {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:8ab581d3530611897d863d1a649fb0644b860286b4718db919bfd51ece41f10b"}, - {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0483847fa9ad5e3412265c1bd72aad35235512d9ce9d27d81a56d935ef489672"}, - {file = "pydantic_core-2.33.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:de9e06abe3cc5ec6a2d5f75bc99b0bdca4f5c719a5b34026f8c57efbdecd2ee3"}, - {file = "pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8"}, + {file = "pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2"}, + {file = "pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a"}, + {file = "pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22"}, + {file = "pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7"}, + {file = "pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef"}, + {file = "pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30"}, + {file = "pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab"}, + {file = "pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"}, + {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"}, + {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"}, + {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"}, + {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"}, + {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"}, + {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"}, + {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"}, + {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a2b911a5b90e0374d03813674bf0a5fbbb7741570dcd4b4e85a2e48d17def29d"}, + {file = "pydantic_core-2.33.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6fa6dfc3e4d1f734a34710f391ae822e0a8eb8559a85c6979e14e65ee6ba2954"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c54c939ee22dc8e2d545da79fc5381f1c020d6d3141d3bd747eab59164dc89fb"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:53a57d2ed685940a504248187d5685e49eb5eef0f696853647bf37c418c538f7"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09fb9dd6571aacd023fe6aaca316bd01cf60ab27240d7eb39ebd66a3a15293b4"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0e6116757f7959a712db11f3e9c0a99ade00a5bbedae83cb801985aa154f071b"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d55ab81c57b8ff8548c3e4947f119551253f4e3787a7bbc0b6b3ca47498a9d3"}, + {file = "pydantic_core-2.33.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c20c462aa4434b33a2661701b861604913f912254e441ab8d78d30485736115a"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:44857c3227d3fb5e753d5fe4a3420d6376fa594b07b621e220cd93703fe21782"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:eb9b459ca4df0e5c87deb59d37377461a538852765293f9e6ee834f0435a93b9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9fcd347d2cc5c23b06de6d3b7b8275be558a0c90549495c699e379a80bf8379e"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win32.whl", hash = "sha256:83aa99b1285bc8f038941ddf598501a86f1536789740991d7d8756e34f1e74d9"}, + {file = "pydantic_core-2.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:f481959862f57f29601ccced557cc2e817bce7533ab8e01a797a48b49c9692b3"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c"}, + {file = "pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb"}, + {file = "pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:87acbfcf8e90ca885206e98359d7dca4bcbb35abdc0ff66672a293e1d7a19101"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f92c15cd1e97d4b12acd1cc9004fa092578acfa57b67ad5e43a197175d01a64"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3f26877a748dc4251cfcfda9dfb5f13fcb034f5308388066bcfe9031b63ae7d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac89aea9af8cd672fa7b510e7b8c33b0bba9a43186680550ccf23020f32d535"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:970919794d126ba8645f3837ab6046fb4e72bbc057b3709144066204c19a455d"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3eb3fe62804e8f859c49ed20a8451342de53ed764150cb14ca71357c765dc2a6"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:3abcd9392a36025e3bd55f9bd38d908bd17962cc49bc6da8e7e96285336e2bca"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:3a1c81334778f9e3af2f8aeb7a960736e5cab1dfebfb26aabca09afd2906c039"}, + {file = "pydantic_core-2.33.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2807668ba86cb38c6817ad9bc66215ab8584d1d304030ce4f0887336f28a5e27"}, + {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"}, ] [package.dependencies] @@ -4439,33 +5020,38 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[package]] name = "pydantic-settings" -version = "2.8.1" +version = "2.10.1" description = "Settings management using Pydantic" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c"}, - {file = "pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585"}, + {file = "pydantic_settings-2.10.1-py3-none-any.whl", hash = "sha256:a60952460b99cf661dc25c29c0ef171721f98bfcb52ef8d9ea4c943d7c8cc796"}, + {file = "pydantic_settings-2.10.1.tar.gz", hash = "sha256:06f0062169818d0f5524420a360d632d5857b83cffd4d42fe29597807a1614ee"}, ] [package.dependencies] pydantic = ">=2.7.0" python-dotenv = ">=0.21.0" +typing-inspection = ">=0.4.0" [package.extras] +aws-secrets-manager = ["boto3 (>=1.35.0)", "boto3-stubs[secretsmanager]"] azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0)"] +gcp-secret-manager = ["google-cloud-secret-manager (>=2.23.1)"] toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" +groups = ["main", "dev", "test"] files = [ - {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, - {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, ] [package.extras] @@ -4473,19 +5059,20 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymupdf" -version = "1.25.5" +version = "1.26.3" description = "A high performance Python library for data extraction, analysis, conversion & manipulation of PDF (and other) documents." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "pymupdf-1.25.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:cde4e1c9cfb09c0e1e9c2b7f4b787dd6bb34a32cfe141a4675e24af7c0c25dd3"}, - {file = "pymupdf-1.25.5-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5a35e2725fae0ab57f058dff77615c15eb5961eac50ba04f41ebc792cd8facad"}, - {file = "pymupdf-1.25.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d94b800e9501929c42283d39bc241001dd87fdeea297b5cb40d5b5714534452f"}, - {file = "pymupdf-1.25.5-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee22155d3a634642d76553204867d862ae1bdd9f7cf70c0797d8127ebee6bed5"}, - {file = "pymupdf-1.25.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6ed7fc25271004d6d3279c20a80cb2bb4cda3efa9f9088dcc07cd790eca0bc63"}, - {file = "pymupdf-1.25.5-cp39-abi3-win32.whl", hash = "sha256:65e18ddb37fe8ec4edcdbebe9be3a8486b6a2f42609d0a142677e42f3a0614f8"}, - {file = "pymupdf-1.25.5-cp39-abi3-win_amd64.whl", hash = "sha256:7f44bc3d03ea45b2f68c96464f96105e8c7908896f2fb5e8c04f1fb8dae7981e"}, - {file = "pymupdf-1.25.5.tar.gz", hash = "sha256:5f96311cacd13254c905f6654a004a0a2025b71cabc04fda667f5472f72c15a0"}, + {file = "pymupdf-1.26.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ded891963944e5f13b03b88f6d9e982e816a4ec8689fe360876eef000c161f2b"}, + {file = "pymupdf-1.26.3-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:436a33c738bb10eadf00395d18a6992b801ffb26521ee1f361ae786dd283327a"}, + {file = "pymupdf-1.26.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:a2d7a3cd442f12f05103cb3bb1415111517f0a97162547a3720f3bbbc5e0b51c"}, + {file = "pymupdf-1.26.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:454f38c8cf07eb333eb4646dca10517b6e90f57ce2daa2265a78064109d85555"}, + {file = "pymupdf-1.26.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:759b75d2f710ff4edf8d097d2e98f60e9ecef47632cead6f949b3412facdb9f0"}, + {file = "pymupdf-1.26.3-cp39-abi3-win32.whl", hash = "sha256:a839ed44742faa1cd4956bb18068fe5aae435d67ce915e901318646c4e7bbea6"}, + {file = "pymupdf-1.26.3-cp39-abi3-win_amd64.whl", hash = "sha256:b4cd5124d05737944636cf45fc37ce5824f10e707b0342efe109c7b6bd37a9cc"}, + {file = "pymupdf-1.26.3.tar.gz", hash = "sha256:b7d2c3ffa9870e1e4416d18862f5ccd356af5fe337b4511093bbbce2ca73b7e5"}, ] [[package]] @@ -4494,6 +5081,7 @@ version = "0.48.9" description = "A SQL query builder API for Python" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "PyPika-0.48.9.tar.gz", hash = "sha256:838836a61747e7c8380cd1b7ff638694b7a7335345d0f559b04b2cd832ad5378"}, ] @@ -4504,6 +5092,7 @@ version = "1.2.0" description = "Wrappers to call pyproject.toml-based build backend hooks." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913"}, {file = "pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8"}, @@ -4515,6 +5104,8 @@ version = "3.5.4" description = "A python implementation of GNU readline." optional = false python-versions = ">=3.8" +groups = ["main"] +markers = "sys_platform == \"win32\"" files = [ {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, @@ -4525,51 +5116,56 @@ dev = ["build", "flake8", "mypy", "pytest", "twine"] [[package]] name = "pytest" -version = "8.3.5" +version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "test"] files = [ - {file = "pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820"}, - {file = "pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845"}, + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, ] [package.dependencies] -colorama = {version = "*", markers = "sys_platform == \"win32\""} -iniconfig = "*" -packaging = "*" +colorama = {version = ">=0.4", markers = "sys_platform == \"win32\""} +iniconfig = ">=1" +packaging = ">=20" pluggy = ">=1.5,<2" +pygments = ">=2.7.2" [package.extras] -dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" -version = "6.1.1" +version = "6.2.1" description = "Pytest plugin for measuring coverage." optional = false python-versions = ">=3.9" +groups = ["test"] files = [ - {file = "pytest_cov-6.1.1-py3-none-any.whl", hash = "sha256:bddf29ed2d0ab6f4df17b4c55b0a657287db8684af9c42ea546b21b1041b3dde"}, - {file = "pytest_cov-6.1.1.tar.gz", hash = "sha256:46935f7aaefba760e716c2ebfbe1c216240b9592966e7da99ea8292d4d3e2a0a"}, + {file = "pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5"}, + {file = "pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2"}, ] [package.dependencies] coverage = {version = ">=7.5", extras = ["toml"]} -pytest = ">=4.6" +pluggy = ">=1.2" +pytest = ">=6.2.5" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] [[package]] name = "pytest-mock" -version = "3.14.0" +version = "3.14.1" description = "Thin-wrapper around the mock package for easier use with pytest" optional = false python-versions = ">=3.8" +groups = ["main", "test"] files = [ - {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, - {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, + {file = "pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0"}, + {file = "pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e"}, ] [package.dependencies] @@ -4584,6 +5180,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -4594,13 +5191,14 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "1.1.0" +version = "1.1.1" description = "Read key-value pairs from a .env file and set them as environment variables" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, - {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, + {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"}, + {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"}, ] [package.extras] @@ -4612,6 +5210,7 @@ version = "3.3.0" description = "JSON Log Formatter for the Python Logging Package" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "python_json_logger-3.3.0-py3-none-any.whl", hash = "sha256:dd980fae8cffb24c13caf6e158d3d61c0d6d22342f932cb6e9deedab3d35eec7"}, {file = "python_json_logger-3.3.0.tar.gz", hash = "sha256:12b7e74b17775e7d565129296105bbe3910842d9d0eb083fc83a6a617aa8df84"}, @@ -4626,6 +5225,7 @@ version = "2025.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, @@ -4633,27 +5233,33 @@ files = [ [[package]] name = "pywin32" -version = "310" +version = "311" description = "Python for Window Extensions" optional = false python-versions = "*" +groups = ["dev"] +markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\"" files = [ - {file = "pywin32-310-cp310-cp310-win32.whl", hash = "sha256:6dd97011efc8bf51d6793a82292419eba2c71cf8e7250cfac03bba284454abc1"}, - {file = "pywin32-310-cp310-cp310-win_amd64.whl", hash = "sha256:c3e78706e4229b915a0821941a84e7ef420bf2b77e08c9dae3c76fd03fd2ae3d"}, - {file = "pywin32-310-cp310-cp310-win_arm64.whl", hash = "sha256:33babed0cf0c92a6f94cc6cc13546ab24ee13e3e800e61ed87609ab91e4c8213"}, - {file = "pywin32-310-cp311-cp311-win32.whl", hash = "sha256:1e765f9564e83011a63321bb9d27ec456a0ed90d3732c4b2e312b855365ed8bd"}, - {file = "pywin32-310-cp311-cp311-win_amd64.whl", hash = "sha256:126298077a9d7c95c53823934f000599f66ec9296b09167810eb24875f32689c"}, - {file = "pywin32-310-cp311-cp311-win_arm64.whl", hash = "sha256:19ec5fc9b1d51c4350be7bb00760ffce46e6c95eaf2f0b2f1150657b1a43c582"}, - {file = "pywin32-310-cp312-cp312-win32.whl", hash = "sha256:8a75a5cc3893e83a108c05d82198880704c44bbaee4d06e442e471d3c9ea4f3d"}, - {file = "pywin32-310-cp312-cp312-win_amd64.whl", hash = "sha256:bf5c397c9a9a19a6f62f3fb821fbf36cac08f03770056711f765ec1503972060"}, - {file = "pywin32-310-cp312-cp312-win_arm64.whl", hash = "sha256:2349cc906eae872d0663d4d6290d13b90621eaf78964bb1578632ff20e152966"}, - {file = "pywin32-310-cp313-cp313-win32.whl", hash = "sha256:5d241a659c496ada3253cd01cfaa779b048e90ce4b2b38cd44168ad555ce74ab"}, - {file = "pywin32-310-cp313-cp313-win_amd64.whl", hash = "sha256:667827eb3a90208ddbdcc9e860c81bde63a135710e21e4cb3348968e4bd5249e"}, - {file = "pywin32-310-cp313-cp313-win_arm64.whl", hash = "sha256:e308f831de771482b7cf692a1f308f8fca701b2d8f9dde6cc440c7da17e47b33"}, - {file = "pywin32-310-cp38-cp38-win32.whl", hash = "sha256:0867beb8addefa2e3979d4084352e4ac6e991ca45373390775f7084cc0209b9c"}, - {file = "pywin32-310-cp38-cp38-win_amd64.whl", hash = "sha256:30f0a9b3138fb5e07eb4973b7077e1883f558e40c578c6925acc7a94c34eaa36"}, - {file = "pywin32-310-cp39-cp39-win32.whl", hash = "sha256:851c8d927af0d879221e616ae1f66145253537bbdd321a77e8ef701b443a9a1a"}, - {file = "pywin32-310-cp39-cp39-win_amd64.whl", hash = "sha256:96867217335559ac619f00ad70e513c0fcf84b8a3af9fc2bba3b59b97da70475"}, + {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, + {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, + {file = "pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b"}, + {file = "pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151"}, + {file = "pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503"}, + {file = "pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2"}, + {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"}, + {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"}, + {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"}, + {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"}, + {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"}, + {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"}, + {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"}, + {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"}, + {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"}, + {file = "pywin32-311-cp38-cp38-win32.whl", hash = "sha256:6c6f2969607b5023b0d9ce2541f8d2cbb01c4f46bc87456017cf63b73f1e2d8c"}, + {file = "pywin32-311-cp38-cp38-win_amd64.whl", hash = "sha256:c8015b09fb9a5e188f83b7b04de91ddca4658cee2ae6f3bc483f0b21a77ef6cd"}, + {file = "pywin32-311-cp39-cp39-win32.whl", hash = "sha256:aba8f82d551a942cb20d4a83413ccbac30790b50efb89a75e4f586ac0bb8056b"}, + {file = "pywin32-311-cp39-cp39-win_amd64.whl", hash = "sha256:e0c4cfb0621281fe40387df582097fd796e80430597cb9944f0ae70447bacd91"}, + {file = "pywin32-311-cp39-cp39-win_arm64.whl", hash = "sha256:62ea666235135fee79bb154e695f3ff67370afefd71bd7fea7512fc70ef31e3d"}, ] [[package]] @@ -4662,6 +5268,8 @@ version = "2.0.15" description = "Pseudo terminal support for Windows from Python." optional = false python-versions = ">=3.9" +groups = ["dev"] +markers = "os_name == \"nt\"" files = [ {file = "pywinpty-2.0.15-cp310-cp310-win_amd64.whl", hash = "sha256:8e7f5de756a615a38b96cd86fa3cd65f901ce54ce147a3179c45907fa11b4c4e"}, {file = "pywinpty-2.0.15-cp311-cp311-win_amd64.whl", hash = "sha256:9a6bcec2df2707aaa9d08b86071970ee32c5026e10bcc3cc5f6f391d85baf7ca"}, @@ -4678,6 +5286,7 @@ version = "1.0.10" description = "Excel 2007-2010 Binary Workbook (xlsb) parser" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "pyxlsb-1.0.10-py2.py3-none-any.whl", hash = "sha256:87c122a9a622e35ca5e741d2e541201d28af00fb46bec492cfa9586890b120b4"}, {file = "pyxlsb-1.0.10.tar.gz", hash = "sha256:8062d1ea8626d3f1980e8b1cfe91a4483747449242ecb61013bc2df85435f685"}, @@ -4689,6 +5298,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -4747,104 +5357,104 @@ files = [ [[package]] name = "pyzmq" -version = "26.4.0" +version = "27.0.1" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "pyzmq-26.4.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:0329bdf83e170ac133f44a233fc651f6ed66ef8e66693b5af7d54f45d1ef5918"}, - {file = "pyzmq-26.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:398a825d2dea96227cf6460ce0a174cf7657d6f6827807d4d1ae9d0f9ae64315"}, - {file = "pyzmq-26.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d52d62edc96787f5c1dfa6c6ccff9b581cfae5a70d94ec4c8da157656c73b5b"}, - {file = "pyzmq-26.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1410c3a3705db68d11eb2424d75894d41cff2f64d948ffe245dd97a9debfebf4"}, - {file = "pyzmq-26.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:7dacb06a9c83b007cc01e8e5277f94c95c453c5851aac5e83efe93e72226353f"}, - {file = "pyzmq-26.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6bab961c8c9b3a4dc94d26e9b2cdf84de9918931d01d6ff38c721a83ab3c0ef5"}, - {file = "pyzmq-26.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7a5c09413b924d96af2aa8b57e76b9b0058284d60e2fc3730ce0f979031d162a"}, - {file = "pyzmq-26.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7d489ac234d38e57f458fdbd12a996bfe990ac028feaf6f3c1e81ff766513d3b"}, - {file = "pyzmq-26.4.0-cp310-cp310-win32.whl", hash = "sha256:dea1c8db78fb1b4b7dc9f8e213d0af3fc8ecd2c51a1d5a3ca1cde1bda034a980"}, - {file = "pyzmq-26.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:fa59e1f5a224b5e04dc6c101d7186058efa68288c2d714aa12d27603ae93318b"}, - {file = "pyzmq-26.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:a651fe2f447672f4a815e22e74630b6b1ec3a1ab670c95e5e5e28dcd4e69bbb5"}, - {file = "pyzmq-26.4.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:bfcf82644c9b45ddd7cd2a041f3ff8dce4a0904429b74d73a439e8cab1bd9e54"}, - {file = "pyzmq-26.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9bcae3979b2654d5289d3490742378b2f3ce804b0b5fd42036074e2bf35b030"}, - {file = "pyzmq-26.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccdff8ac4246b6fb60dcf3982dfaeeff5dd04f36051fe0632748fc0aa0679c01"}, - {file = "pyzmq-26.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4550af385b442dc2d55ab7717837812799d3674cb12f9a3aa897611839c18e9e"}, - {file = "pyzmq-26.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:2f9f7ffe9db1187a253fca95191854b3fda24696f086e8789d1d449308a34b88"}, - {file = "pyzmq-26.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3709c9ff7ba61589b7372923fd82b99a81932b592a5c7f1a24147c91da9a68d6"}, - {file = "pyzmq-26.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f8f3c30fb2d26ae5ce36b59768ba60fb72507ea9efc72f8f69fa088450cff1df"}, - {file = "pyzmq-26.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:382a4a48c8080e273427fc692037e3f7d2851959ffe40864f2db32646eeb3cef"}, - {file = "pyzmq-26.4.0-cp311-cp311-win32.whl", hash = "sha256:d56aad0517d4c09e3b4f15adebba8f6372c5102c27742a5bdbfc74a7dceb8fca"}, - {file = "pyzmq-26.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:963977ac8baed7058c1e126014f3fe58b3773f45c78cce7af5c26c09b6823896"}, - {file = "pyzmq-26.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0c8e8cadc81e44cc5088fcd53b9b3b4ce9344815f6c4a03aec653509296fae3"}, - {file = "pyzmq-26.4.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:5227cb8da4b6f68acfd48d20c588197fd67745c278827d5238c707daf579227b"}, - {file = "pyzmq-26.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1c07a7fa7f7ba86554a2b1bef198c9fed570c08ee062fd2fd6a4dcacd45f905"}, - {file = "pyzmq-26.4.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae775fa83f52f52de73183f7ef5395186f7105d5ed65b1ae65ba27cb1260de2b"}, - {file = "pyzmq-26.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c760d0226ebd52f1e6b644a9e839b5db1e107a23f2fcd46ec0569a4fdd4e63"}, - {file = "pyzmq-26.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ef8c6ecc1d520debc147173eaa3765d53f06cd8dbe7bd377064cdbc53ab456f5"}, - {file = "pyzmq-26.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3150ef4084e163dec29ae667b10d96aad309b668fac6810c9e8c27cf543d6e0b"}, - {file = "pyzmq-26.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4448c9e55bf8329fa1dcedd32f661bf611214fa70c8e02fee4347bc589d39a84"}, - {file = "pyzmq-26.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e07dde3647afb084d985310d067a3efa6efad0621ee10826f2cb2f9a31b89d2f"}, - {file = "pyzmq-26.4.0-cp312-cp312-win32.whl", hash = "sha256:ba034a32ecf9af72adfa5ee383ad0fd4f4e38cdb62b13624278ef768fe5b5b44"}, - {file = "pyzmq-26.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:056a97aab4064f526ecb32f4343917a4022a5d9efb6b9df990ff72e1879e40be"}, - {file = "pyzmq-26.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:2f23c750e485ce1eb639dbd576d27d168595908aa2d60b149e2d9e34c9df40e0"}, - {file = "pyzmq-26.4.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:c43fac689880f5174d6fc864857d1247fe5cfa22b09ed058a344ca92bf5301e3"}, - {file = "pyzmq-26.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:902aca7eba477657c5fb81c808318460328758e8367ecdd1964b6330c73cae43"}, - {file = "pyzmq-26.4.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e5e48a830bfd152fe17fbdeaf99ac5271aa4122521bf0d275b6b24e52ef35eb6"}, - {file = "pyzmq-26.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31be2b6de98c824c06f5574331f805707c667dc8f60cb18580b7de078479891e"}, - {file = "pyzmq-26.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:6332452034be001bbf3206ac59c0d2a7713de5f25bb38b06519fc6967b7cf771"}, - {file = "pyzmq-26.4.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:da8c0f5dd352136853e6a09b1b986ee5278dfddfebd30515e16eae425c872b30"}, - {file = "pyzmq-26.4.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f4ccc1a0a2c9806dda2a2dd118a3b7b681e448f3bb354056cad44a65169f6d86"}, - {file = "pyzmq-26.4.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:1c0b5fceadbab461578daf8d1dcc918ebe7ddd2952f748cf30c7cf2de5d51101"}, - {file = "pyzmq-26.4.0-cp313-cp313-win32.whl", hash = "sha256:28e2b0ff5ba4b3dd11062d905682bad33385cfa3cc03e81abd7f0822263e6637"}, - {file = "pyzmq-26.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:23ecc9d241004c10e8b4f49d12ac064cd7000e1643343944a10df98e57bc544b"}, - {file = "pyzmq-26.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:1edb0385c7f025045d6e0f759d4d3afe43c17a3d898914ec6582e6f464203c08"}, - {file = "pyzmq-26.4.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:93a29e882b2ba1db86ba5dd5e88e18e0ac6b627026c5cfbec9983422011b82d4"}, - {file = "pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45684f276f57110bb89e4300c00f1233ca631f08f5f42528a5c408a79efc4a"}, - {file = "pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f72073e75260cb301aad4258ad6150fa7f57c719b3f498cb91e31df16784d89b"}, - {file = "pyzmq-26.4.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be37e24b13026cfedd233bcbbccd8c0bcd2fdd186216094d095f60076201538d"}, - {file = "pyzmq-26.4.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:237b283044934d26f1eeff4075f751b05d2f3ed42a257fc44386d00df6a270cf"}, - {file = "pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:b30f862f6768b17040929a68432c8a8be77780317f45a353cb17e423127d250c"}, - {file = "pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:c80fcd3504232f13617c6ab501124d373e4895424e65de8b72042333316f64a8"}, - {file = "pyzmq-26.4.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:26a2a7451606b87f67cdeca2c2789d86f605da08b4bd616b1a9981605ca3a364"}, - {file = "pyzmq-26.4.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:831cc53bf6068d46d942af52fa8b0b9d128fb39bcf1f80d468dc9a3ae1da5bfb"}, - {file = "pyzmq-26.4.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:51d18be6193c25bd229524cfac21e39887c8d5e0217b1857998dfbef57c070a4"}, - {file = "pyzmq-26.4.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:445c97854204119ae2232503585ebb4fa7517142f71092cb129e5ee547957a1f"}, - {file = "pyzmq-26.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:807b8f4ad3e6084412c0f3df0613269f552110fa6fb91743e3e306223dbf11a6"}, - {file = "pyzmq-26.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c01d109dd675ac47fa15c0a79d256878d898f90bc10589f808b62d021d2e653c"}, - {file = "pyzmq-26.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0a294026e28679a8dd64c922e59411cb586dad307661b4d8a5c49e7bbca37621"}, - {file = "pyzmq-26.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:22c8dd677274af8dfb1efd05006d6f68fb2f054b17066e308ae20cb3f61028cf"}, - {file = "pyzmq-26.4.0-cp38-cp38-win32.whl", hash = "sha256:14fc678b696bc42c14e2d7f86ac4e97889d5e6b94d366ebcb637a768d2ad01af"}, - {file = "pyzmq-26.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1ef0a536662bbbdc8525f7e2ef19e74123ec9c4578e0582ecd41aedc414a169"}, - {file = "pyzmq-26.4.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:a88643de8abd000ce99ca72056a1a2ae15881ee365ecb24dd1d9111e43d57842"}, - {file = "pyzmq-26.4.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0a744ce209ecb557406fb928f3c8c55ce79b16c3eeb682da38ef5059a9af0848"}, - {file = "pyzmq-26.4.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9434540f333332224ecb02ee6278b6c6f11ea1266b48526e73c903119b2f420f"}, - {file = "pyzmq-26.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6c6f0a23e55cd38d27d4c89add963294ea091ebcb104d7fdab0f093bc5abb1c"}, - {file = "pyzmq-26.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6145df55dc2309f6ef72d70576dcd5aabb0fd373311613fe85a5e547c722b780"}, - {file = "pyzmq-26.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2ea81823840ef8c56e5d2f9918e4d571236294fea4d1842b302aebffb9e40997"}, - {file = "pyzmq-26.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:cc2abc385dc37835445abe206524fbc0c9e3fce87631dfaa90918a1ba8f425eb"}, - {file = "pyzmq-26.4.0-cp39-cp39-win32.whl", hash = "sha256:41a2508fe7bed4c76b4cf55aacfb8733926f59d440d9ae2b81ee8220633b4d12"}, - {file = "pyzmq-26.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:d4000e8255d6cbce38982e5622ebb90823f3409b7ffe8aeae4337ef7d6d2612a"}, - {file = "pyzmq-26.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:b4f6919d9c120488246bdc2a2f96662fa80d67b35bd6d66218f457e722b3ff64"}, - {file = "pyzmq-26.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:98d948288ce893a2edc5ec3c438fe8de2daa5bbbd6e2e865ec5f966e237084ba"}, - {file = "pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9f34f5c9e0203ece706a1003f1492a56c06c0632d86cb77bcfe77b56aacf27b"}, - {file = "pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80c9b48aef586ff8b698359ce22f9508937c799cc1d2c9c2f7c95996f2300c94"}, - {file = "pyzmq-26.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3f2a5b74009fd50b53b26f65daff23e9853e79aa86e0aa08a53a7628d92d44a"}, - {file = "pyzmq-26.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:61c5f93d7622d84cb3092d7f6398ffc77654c346545313a3737e266fc11a3beb"}, - {file = "pyzmq-26.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4478b14cb54a805088299c25a79f27eaf530564a7a4f72bf432a040042b554eb"}, - {file = "pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a28ac29c60e4ba84b5f58605ace8ad495414a724fe7aceb7cf06cd0598d04e1"}, - {file = "pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43b03c1ceea27c6520124f4fb2ba9c647409b9abdf9a62388117148a90419494"}, - {file = "pyzmq-26.4.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7731abd23a782851426d4e37deb2057bf9410848a4459b5ede4fe89342e687a9"}, - {file = "pyzmq-26.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a222ad02fbe80166b0526c038776e8042cd4e5f0dec1489a006a1df47e9040e0"}, - {file = "pyzmq-26.4.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:91c3ffaea475ec8bb1a32d77ebc441dcdd13cd3c4c284a6672b92a0f5ade1917"}, - {file = "pyzmq-26.4.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d9a78a52668bf5c9e7b0da36aa5760a9fc3680144e1445d68e98df78a25082ed"}, - {file = "pyzmq-26.4.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b70cab356ff8c860118b89dc86cd910c73ce2127eb986dada4fbac399ef644cf"}, - {file = "pyzmq-26.4.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acae207d4387780838192326b32d373bb286da0b299e733860e96f80728eb0af"}, - {file = "pyzmq-26.4.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f928eafd15794aa4be75463d537348b35503c1e014c5b663f206504ec1a90fe4"}, - {file = "pyzmq-26.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:552b0d2e39987733e1e9e948a0ced6ff75e0ea39ab1a1db2fc36eb60fd8760db"}, - {file = "pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd670a8aa843f2ee637039bbd412e0d7294a5e588e1ecc9ad98b0cdc050259a4"}, - {file = "pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d367b7b775a0e1e54a59a2ba3ed4d5e0a31566af97cc9154e34262777dab95ed"}, - {file = "pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112af16c406e4a93df2caef49f884f4c2bb2b558b0b5577ef0b2465d15c1abc"}, - {file = "pyzmq-26.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c76c298683f82669cab0b6da59071f55238c039738297c69f187a542c6d40099"}, - {file = "pyzmq-26.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:49b6ca2e625b46f499fb081aaf7819a177f41eeb555acb05758aa97f4f95d147"}, - {file = "pyzmq-26.4.0.tar.gz", hash = "sha256:4bd13f85f80962f91a651a7356fe0472791a5f7a92f227822b5acf44795c626d"}, + {file = "pyzmq-27.0.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:90a4da42aa322de8a3522461e3b5fe999935763b27f69a02fced40f4e3cf9682"}, + {file = "pyzmq-27.0.1-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:e648dca28178fc879c814cf285048dd22fd1f03e1104101106505ec0eea50a4d"}, + {file = "pyzmq-27.0.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bca8abc31799a6f3652d13f47e0b0e1cab76f9125f2283d085a3754f669b607"}, + {file = "pyzmq-27.0.1-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:092f4011b26d6b0201002f439bd74b38f23f3aefcb358621bdc3b230afc9b2d5"}, + {file = "pyzmq-27.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6f02f30a4a6b3efe665ab13a3dd47109d80326c8fd286311d1ba9f397dc5f247"}, + {file = "pyzmq-27.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:f293a1419266e3bf3557d1f8778f9e1ffe7e6b2c8df5c9dca191caf60831eb74"}, + {file = "pyzmq-27.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ce181dd1a7c6c012d0efa8ab603c34b5ee9d86e570c03415bbb1b8772eeb381c"}, + {file = "pyzmq-27.0.1-cp310-cp310-win32.whl", hash = "sha256:f65741cc06630652e82aa68ddef4986a3ab9073dd46d59f94ce5f005fa72037c"}, + {file = "pyzmq-27.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:44909aa3ed2234d69fe81e1dade7be336bcfeab106e16bdaa3318dcde4262b93"}, + {file = "pyzmq-27.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:4401649bfa0a38f0f8777f8faba7cd7eb7b5b8ae2abc7542b830dd09ad4aed0d"}, + {file = "pyzmq-27.0.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:9729190bd770314f5fbba42476abf6abe79a746eeda11d1d68fd56dd70e5c296"}, + {file = "pyzmq-27.0.1-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:696900ef6bc20bef6a242973943574f96c3f97d2183c1bd3da5eea4f559631b1"}, + {file = "pyzmq-27.0.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96a63aecec22d3f7fdea3c6c98df9e42973f5856bb6812c3d8d78c262fee808"}, + {file = "pyzmq-27.0.1-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c512824360ea7490390566ce00bee880e19b526b312b25cc0bc30a0fe95cb67f"}, + {file = "pyzmq-27.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dfb2bb5e0f7198eaacfb6796fb0330afd28f36d985a770745fba554a5903595a"}, + {file = "pyzmq-27.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4f6886c59ba93ffde09b957d3e857e7950c8fe818bd5494d9b4287bc6d5bc7f1"}, + {file = "pyzmq-27.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b99ea9d330e86ce1ff7f2456b33f1bf81c43862a5590faf4ef4ed3a63504bdab"}, + {file = "pyzmq-27.0.1-cp311-cp311-win32.whl", hash = "sha256:571f762aed89025ba8cdcbe355fea56889715ec06d0264fd8b6a3f3fa38154ed"}, + {file = "pyzmq-27.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee16906c8025fa464bea1e48128c048d02359fb40bebe5333103228528506530"}, + {file = "pyzmq-27.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:ba068f28028849da725ff9185c24f832ccf9207a40f9b28ac46ab7c04994bd41"}, + {file = "pyzmq-27.0.1-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:af7ebce2a1e7caf30c0bb64a845f63a69e76a2fadbc1cac47178f7bb6e657bdd"}, + {file = "pyzmq-27.0.1-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8f617f60a8b609a13099b313e7e525e67f84ef4524b6acad396d9ff153f6e4cd"}, + {file = "pyzmq-27.0.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1d59dad4173dc2a111f03e59315c7bd6e73da1a9d20a84a25cf08325b0582b1a"}, + {file = "pyzmq-27.0.1-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f5b6133c8d313bde8bd0d123c169d22525300ff164c2189f849de495e1344577"}, + {file = "pyzmq-27.0.1-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:58cca552567423f04d06a075f4b473e78ab5bdb906febe56bf4797633f54aa4e"}, + {file = "pyzmq-27.0.1-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:4b9d8e26fb600d0d69cc9933e20af08552e97cc868a183d38a5c0d661e40dfbb"}, + {file = "pyzmq-27.0.1-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2329f0c87f0466dce45bba32b63f47018dda5ca40a0085cc5c8558fea7d9fc55"}, + {file = "pyzmq-27.0.1-cp312-abi3-win32.whl", hash = "sha256:57bb92abdb48467b89c2d21da1ab01a07d0745e536d62afd2e30d5acbd0092eb"}, + {file = "pyzmq-27.0.1-cp312-abi3-win_amd64.whl", hash = "sha256:ff3f8757570e45da7a5bedaa140489846510014f7a9d5ee9301c61f3f1b8a686"}, + {file = "pyzmq-27.0.1-cp312-abi3-win_arm64.whl", hash = "sha256:df2c55c958d3766bdb3e9d858b911288acec09a9aab15883f384fc7180df5bed"}, + {file = "pyzmq-27.0.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:497bd8af534ae55dc4ef67eebd1c149ff2a0b0f1e146db73c8b5a53d83c1a5f5"}, + {file = "pyzmq-27.0.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:a066ea6ad6218b4c233906adf0ae67830f451ed238419c0db609310dd781fbe7"}, + {file = "pyzmq-27.0.1-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:72d235d6365ca73d8ce92f7425065d70f5c1e19baa458eb3f0d570e425b73a96"}, + {file = "pyzmq-27.0.1-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:313a7b374e3dc64848644ca348a51004b41726f768b02e17e689f1322366a4d9"}, + {file = "pyzmq-27.0.1-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:119ce8590409702394f959c159d048002cbed2f3c0645ec9d6a88087fc70f0f1"}, + {file = "pyzmq-27.0.1-cp313-cp313t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45c3e00ce16896ace2cd770ab9057a7cf97d4613ea5f2a13f815141d8b6894b9"}, + {file = "pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:678e50ec112bdc6df5a83ac259a55a4ba97a8b314c325ab26b3b5b071151bc61"}, + {file = "pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d0b96c30be9f9387b18b18b6133c75a7b1b0065da64e150fe1feb5ebf31ece1c"}, + {file = "pyzmq-27.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88dc92d9eb5ea4968123e74db146d770b0c8d48f0e2bfb1dbc6c50a8edb12d64"}, + {file = "pyzmq-27.0.1-cp313-cp313t-win32.whl", hash = "sha256:6dcbcb34f5c9b0cefdfc71ff745459241b7d3cda5b27c7ad69d45afc0821d1e1"}, + {file = "pyzmq-27.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9fd0fda730461f510cfd9a40fafa5355d65f5e3dbdd8d6dfa342b5b3f5d1949"}, + {file = "pyzmq-27.0.1-cp313-cp313t-win_arm64.whl", hash = "sha256:56a3b1853f3954ec1f0e91085f1350cc57d18f11205e4ab6e83e4b7c414120e0"}, + {file = "pyzmq-27.0.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f98f6b7787bd2beb1f0dde03f23a0621a0c978edf673b7d8f5e7bc039cbe1b60"}, + {file = "pyzmq-27.0.1-cp314-cp314t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:351bf5d8ca0788ca85327fda45843b6927593ff4c807faee368cc5aaf9f809c2"}, + {file = "pyzmq-27.0.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5268a5a9177afff53dc6d70dffe63114ba2a6e7b20d9411cc3adeba09eeda403"}, + {file = "pyzmq-27.0.1-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4aca06ba295aa78bec9b33ec028d1ca08744c36294338c41432b7171060c808"}, + {file = "pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1c363c6dc66352331d5ad64bb838765c6692766334a6a02fdb05e76bd408ae18"}, + {file = "pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:87aebf4acd7249bdff8d3df03aed4f09e67078e6762cfe0aecf8d0748ff94cde"}, + {file = "pyzmq-27.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e4f22d67756518d71901edf73b38dc0eb4765cce22c8fe122cc81748d425262b"}, + {file = "pyzmq-27.0.1-cp314-cp314t-win32.whl", hash = "sha256:8c62297bc7aea2147b472ca5ca2b4389377ad82898c87cabab2a94aedd75e337"}, + {file = "pyzmq-27.0.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bee5248d5ec9223545f8cc4f368c2d571477ae828c99409125c3911511d98245"}, + {file = "pyzmq-27.0.1-cp314-cp314t-win_arm64.whl", hash = "sha256:0fc24bf45e4a454e55ef99d7f5c8b8712539200ce98533af25a5bfa954b6b390"}, + {file = "pyzmq-27.0.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:9d16fdfd7d70a6b0ca45d36eb19f7702fa77ef6256652f17594fc9ce534c9da6"}, + {file = "pyzmq-27.0.1-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d0356a21e58c3e99248930ff73cc05b1d302ff50f41a8a47371aefb04327378a"}, + {file = "pyzmq-27.0.1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a27fa11ebaccc099cac4309c799aa33919671a7660e29b3e465b7893bc64ec81"}, + {file = "pyzmq-27.0.1-cp38-cp38-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b25e72e115399a4441aad322258fa8267b873850dc7c276e3f874042728c2b45"}, + {file = "pyzmq-27.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f8c3b74f1cd577a5a9253eae7ed363f88cbb345a990ca3027e9038301d47c7f4"}, + {file = "pyzmq-27.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:19dce6c93656f9c469540350d29b128cd8ba55b80b332b431b9a1e9ff74cfd01"}, + {file = "pyzmq-27.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:da81512b83032ed6cdf85ca62e020b4c23dda87f1b6c26b932131222ccfdbd27"}, + {file = "pyzmq-27.0.1-cp38-cp38-win32.whl", hash = "sha256:7418fb5736d0d39b3ecc6bec4ff549777988feb260f5381636d8bd321b653038"}, + {file = "pyzmq-27.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:af2ee67b3688b067e20fea3fe36b823a362609a1966e7e7a21883ae6da248804"}, + {file = "pyzmq-27.0.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:05a94233fdde585eb70924a6e4929202a747eea6ed308a6171c4f1c715bbe39e"}, + {file = "pyzmq-27.0.1-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:c96702e1082eab62ae583d64c4e19c9b848359196697e536a0c57ae9bd165bd5"}, + {file = "pyzmq-27.0.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c9180d1f5b4b73e28b64e63cc6c4c097690f102aa14935a62d5dd7426a4e5b5a"}, + {file = "pyzmq-27.0.1-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e971d8680003d0af6020713e52f92109b46fedb463916e988814e04c8133578a"}, + {file = "pyzmq-27.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe632fa4501154d58dfbe1764a0495734d55f84eaf1feda4549a1f1ca76659e9"}, + {file = "pyzmq-27.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4c3874344fd5fa6d58bb51919708048ac4cab21099f40a227173cddb76b4c20b"}, + {file = "pyzmq-27.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ec09073ed67ae236785d543df3b322282acc0bdf6d1b748c3e81f3043b21cb5"}, + {file = "pyzmq-27.0.1-cp39-cp39-win32.whl", hash = "sha256:f44e7ea288d022d4bf93b9e79dafcb4a7aea45a3cbeae2116792904931cefccf"}, + {file = "pyzmq-27.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ffe6b809a97ac6dea524b3b837d5b28743d8c2f121141056d168ff0ba8f614ef"}, + {file = "pyzmq-27.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:fde26267416c8478c95432c81489b53f57b0b5d24cd5c8bfaebf5bbaac4dc90c"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:544b995a6a1976fad5d7ff01409b4588f7608ccc41be72147700af91fd44875d"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0f772eea55cccce7f45d6ecdd1d5049c12a77ec22404f6b892fae687faa87bee"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9d63d66059114a6756d09169c9209ffceabacb65b9cb0f66e6fc344b20b73e6"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1da8e645c655d86f0305fb4c65a0d848f461cd90ee07d21f254667287b5dbe50"}, + {file = "pyzmq-27.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1843fd0daebcf843fe6d4da53b8bdd3fc906ad3e97d25f51c3fed44436d82a49"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7fb0ee35845bef1e8c4a152d766242164e138c239e3182f558ae15cb4a891f94"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f379f11e138dfd56c3f24a04164f871a08281194dd9ddf656a278d7d080c8ad0"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b978c0678cffbe8860ec9edc91200e895c29ae1ac8a7085f947f8e8864c489fb"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ebccf0d760bc92a4a7c751aeb2fef6626144aace76ee8f5a63abeb100cae87f"}, + {file = "pyzmq-27.0.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:77fed80e30fa65708546c4119840a46691290efc231f6bfb2ac2a39b52e15811"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9d7b6b90da7285642f480b48c9efd1d25302fd628237d8f6f6ee39ba6b2d2d34"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d2976b7079f09f48d59dc123293ed6282fca6ef96a270f4ea0364e4e54c8e855"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2852f67371918705cc18b321695f75c5d653d5d8c4a9b946c1eec4dab2bd6fdf"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:be45a895f98877271e8a0b6cf40925e0369121ce423421c20fa6d7958dc753c2"}, + {file = "pyzmq-27.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64ca3c7c614aefcdd5e358ecdd41d1237c35fe1417d01ec0160e7cdb0a380edc"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:d97b59cbd8a6c8b23524a8ce237ff9504d987dc07156258aa68ae06d2dd5f34d"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:27a78bdd384dbbe7b357af95f72efe8c494306b5ec0a03c31e2d53d6763e5307"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b007e5dcba684e888fbc90554cb12a2f4e492927c8c2761a80b7590209821743"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:95594b2ceeaa94934e3e94dd7bf5f3c3659cf1a26b1fb3edcf6e42dad7e0eaf2"}, + {file = "pyzmq-27.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:70b719a130b81dd130a57ac0ff636dc2c0127c5b35ca5467d1b67057e3c7a4d2"}, + {file = "pyzmq-27.0.1.tar.gz", hash = "sha256:45c549204bc20e7484ffd2555f6cf02e572440ecf2f3bdd60d4404b20fddf64b"}, ] [package.dependencies] @@ -4856,6 +5466,7 @@ version = "0.2.2" description = "Various BM25 algorithms for document ranking" optional = false python-versions = "*" +groups = ["main"] files = [ {file = "rank_bm25-0.2.2-py3-none-any.whl", hash = "sha256:7bd4a95571adadfc271746fa146a4bcfd89c0cf731e49c3d1ad863290adbe8ae"}, {file = "rank_bm25-0.2.2.tar.gz", hash = "sha256:096ccef76f8188563419aaf384a02f0ea459503fdf77901378d4fd9d87e5e51d"}, @@ -4873,6 +5484,7 @@ version = "3.13.0" description = "rapid fuzzy string matching" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:aafc42a1dc5e1beeba52cd83baa41372228d6d8266f6d803c16dbabbcc156255"}, {file = "rapidfuzz-3.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85c9a131a44a95f9cac2eb6e65531db014e09d89c4f18c7b1fa54979cb9ff1f3"}, @@ -4979,6 +5591,7 @@ version = "0.36.2" description = "JSON Referencing + Python" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, @@ -4991,121 +5604,116 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "regex" -version = "2024.11.6" +version = "2025.7.34" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, - {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, - {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, - {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, - {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, - {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, - {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, - {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, - {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, - {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, - {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, - {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, - {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, - {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, - {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, - {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, - {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, - {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, - {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, - {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, - {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, - {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, - {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, - {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, - {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, - {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, - {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, - {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, - {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, - {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, - {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, - {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, + {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d856164d25e2b3b07b779bfed813eb4b6b6ce73c2fd818d46f47c1eb5cd79bd6"}, + {file = "regex-2025.7.34-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2d15a9da5fad793e35fb7be74eec450d968e05d2e294f3e0e77ab03fa7234a83"}, + {file = "regex-2025.7.34-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:95b4639c77d414efa93c8de14ce3f7965a94d007e068a94f9d4997bb9bd9c81f"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d7de1ceed5a5f84f342ba4a9f4ae589524adf9744b2ee61b5da884b5b659834"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02e5860a250cd350c4933cf376c3bc9cb28948e2c96a8bc042aee7b985cfa26f"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0a5966220b9a1a88691282b7e4350e9599cf65780ca60d914a798cb791aa1177"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48fb045bbd4aab2418dc1ba2088a5e32de4bfe64e1457b948bb328a8dc2f1c2e"}, + {file = "regex-2025.7.34-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:20ff8433fa45e131f7316594efe24d4679c5449c0ca69d91c2f9d21846fdf064"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c436fd1e95c04c19039668cfb548450a37c13f051e8659f40aed426e36b3765f"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0b85241d3cfb9f8a13cefdfbd58a2843f208f2ed2c88181bf84e22e0c7fc066d"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:075641c94126b064c65ab86e7e71fc3d63e7ff1bea1fb794f0773c97cdad3a03"}, + {file = "regex-2025.7.34-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:70645cad3407d103d1dbcb4841839d2946f7d36cf38acbd40120fee1682151e5"}, + {file = "regex-2025.7.34-cp310-cp310-win32.whl", hash = "sha256:3b836eb4a95526b263c2a3359308600bd95ce7848ebd3c29af0c37c4f9627cd3"}, + {file = "regex-2025.7.34-cp310-cp310-win_amd64.whl", hash = "sha256:cbfaa401d77334613cf434f723c7e8ba585df162be76474bccc53ae4e5520b3a"}, + {file = "regex-2025.7.34-cp310-cp310-win_arm64.whl", hash = "sha256:bca11d3c38a47c621769433c47f364b44e8043e0de8e482c5968b20ab90a3986"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da304313761b8500b8e175eb2040c4394a875837d5635f6256d6fa0377ad32c8"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:35e43ebf5b18cd751ea81455b19acfdec402e82fe0dc6143edfae4c5c4b3909a"}, + {file = "regex-2025.7.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96bbae4c616726f4661fe7bcad5952e10d25d3c51ddc388189d8864fbc1b3c68"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9feab78a1ffa4f2b1e27b1bcdaad36f48c2fed4870264ce32f52a393db093c78"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f14b36e6d4d07f1a5060f28ef3b3561c5d95eb0651741474ce4c0a4c56ba8719"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85c3a958ef8b3d5079c763477e1f09e89d13ad22198a37e9d7b26b4b17438b33"}, + {file = "regex-2025.7.34-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37555e4ae0b93358fa7c2d240a4291d4a4227cc7c607d8f85596cdb08ec0a083"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee38926f31f1aa61b0232a3a11b83461f7807661c062df9eb88769d86e6195c3"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a664291c31cae9c4a30589bd8bc2ebb56ef880c9c6264cb7643633831e606a4d"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f3e5c1e0925e77ec46ddc736b756a6da50d4df4ee3f69536ffb2373460e2dafd"}, + {file = "regex-2025.7.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d428fc7731dcbb4e2ffe43aeb8f90775ad155e7db4347a639768bc6cd2df881a"}, + {file = "regex-2025.7.34-cp311-cp311-win32.whl", hash = "sha256:e154a7ee7fa18333ad90b20e16ef84daaeac61877c8ef942ec8dfa50dc38b7a1"}, + {file = "regex-2025.7.34-cp311-cp311-win_amd64.whl", hash = "sha256:24257953d5c1d6d3c129ab03414c07fc1a47833c9165d49b954190b2b7f21a1a"}, + {file = "regex-2025.7.34-cp311-cp311-win_arm64.whl", hash = "sha256:3157aa512b9e606586900888cd469a444f9b898ecb7f8931996cb715f77477f0"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7f7211a746aced993bef487de69307a38c5ddd79257d7be83f7b202cb59ddb50"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fb31080f2bd0681484b275461b202b5ad182f52c9ec606052020fe13eb13a72f"}, + {file = "regex-2025.7.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0200a5150c4cf61e407038f4b4d5cdad13e86345dac29ff9dab3d75d905cf130"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:739a74970e736df0773788377969c9fea3876c2fc13d0563f98e5503e5185f46"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4fef81b2f7ea6a2029161ed6dea9ae13834c28eb5a95b8771828194a026621e4"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea74cf81fe61a7e9d77989050d0089a927ab758c29dac4e8e1b6c06fccf3ebf0"}, + {file = "regex-2025.7.34-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4636a7f3b65a5f340ed9ddf53585c42e3ff37101d383ed321bfe5660481744b"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cef962d7834437fe8d3da6f9bfc6f93f20f218266dcefec0560ed7765f5fe01"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cbe1698e5b80298dbce8df4d8d1182279fbdaf1044e864cbc9d53c20e4a2be77"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:32b9f9bcf0f605eb094b08e8da72e44badabb63dde6b83bd530580b488d1c6da"}, + {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:524c868ba527eab4e8744a9287809579f54ae8c62fbf07d62aacd89f6026b282"}, + {file = "regex-2025.7.34-cp312-cp312-win32.whl", hash = "sha256:d600e58ee6d036081c89696d2bdd55d507498a7180df2e19945c6642fac59588"}, + {file = "regex-2025.7.34-cp312-cp312-win_amd64.whl", hash = "sha256:9a9ab52a466a9b4b91564437b36417b76033e8778e5af8f36be835d8cb370d62"}, + {file = "regex-2025.7.34-cp312-cp312-win_arm64.whl", hash = "sha256:c83aec91af9c6fbf7c743274fd952272403ad9a9db05fe9bfc9df8d12b45f176"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3c9740a77aeef3f5e3aaab92403946a8d34437db930a0280e7e81ddcada61f5"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69ed3bc611540f2ea70a4080f853741ec698be556b1df404599f8724690edbcd"}, + {file = "regex-2025.7.34-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d03c6f9dcd562c56527c42b8530aad93193e0b3254a588be1f2ed378cdfdea1b"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6164b1d99dee1dfad33f301f174d8139d4368a9fb50bf0a3603b2eaf579963ad"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1e4f4f62599b8142362f164ce776f19d79bdd21273e86920a7b604a4275b4f59"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72a26dcc6a59c057b292f39d41465d8233a10fd69121fa24f8f43ec6294e5415"}, + {file = "regex-2025.7.34-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5273fddf7a3e602695c92716c420c377599ed3c853ea669c1fe26218867002f"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1844be23cd40135b3a5a4dd298e1e0c0cb36757364dd6cdc6025770363e06c1"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dde35e2afbbe2272f8abee3b9fe6772d9b5a07d82607b5788e8508974059925c"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f6e8e7af516a7549412ce57613e859c3be27d55341a894aacaa11703a4c31a"}, + {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:469142fb94a869beb25b5f18ea87646d21def10fbacb0bcb749224f3509476f0"}, + {file = "regex-2025.7.34-cp313-cp313-win32.whl", hash = "sha256:da7507d083ee33ccea1310447410c27ca11fb9ef18c95899ca57ff60a7e4d8f1"}, + {file = "regex-2025.7.34-cp313-cp313-win_amd64.whl", hash = "sha256:9d644de5520441e5f7e2db63aec2748948cc39ed4d7a87fd5db578ea4043d997"}, + {file = "regex-2025.7.34-cp313-cp313-win_arm64.whl", hash = "sha256:7bf1c5503a9f2cbd2f52d7e260acb3131b07b6273c470abb78568174fe6bde3f"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:8283afe7042d8270cecf27cca558873168e771183d4d593e3c5fe5f12402212a"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6c053f9647e3421dd2f5dff8172eb7b4eec129df9d1d2f7133a4386319b47435"}, + {file = "regex-2025.7.34-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a16dd56bbcb7d10e62861c3cd000290ddff28ea142ffb5eb3470f183628011ac"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69c593ff5a24c0d5c1112b0df9b09eae42b33c014bdca7022d6523b210b69f72"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98d0ce170fcde1a03b5df19c5650db22ab58af375aaa6ff07978a85c9f250f0e"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d72765a4bff8c43711d5b0f5b452991a9947853dfa471972169b3cc0ba1d0751"}, + {file = "regex-2025.7.34-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4494f8fd95a77eb434039ad8460e64d57baa0434f1395b7da44015bef650d0e4"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4f42b522259c66e918a0121a12429b2abcf696c6f967fa37bdc7b72e61469f98"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:aaef1f056d96a0a5d53ad47d019d5b4c66fe4be2da87016e0d43b7242599ffc7"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:656433e5b7dccc9bc0da6312da8eb897b81f5e560321ec413500e5367fcd5d47"}, + {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e91eb2c62c39705e17b4d42d4b86c4e86c884c0d15d9c5a47d0835f8387add8e"}, + {file = "regex-2025.7.34-cp314-cp314-win32.whl", hash = "sha256:f978ddfb6216028c8f1d6b0f7ef779949498b64117fc35a939022f67f810bdcb"}, + {file = "regex-2025.7.34-cp314-cp314-win_amd64.whl", hash = "sha256:4b7dc33b9b48fb37ead12ffc7bdb846ac72f99a80373c4da48f64b373a7abeae"}, + {file = "regex-2025.7.34-cp314-cp314-win_arm64.whl", hash = "sha256:4b8c4d39f451e64809912c82392933d80fe2e4a87eeef8859fcc5380d0173c64"}, + {file = "regex-2025.7.34-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fd5edc3f453de727af267c7909d083e19f6426fc9dd149e332b6034f2a5611e6"}, + {file = "regex-2025.7.34-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fa1cdfb8db96ef20137de5587954c812821966c3e8b48ffc871e22d7ec0a4938"}, + {file = "regex-2025.7.34-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:89c9504fc96268e8e74b0283e548f53a80c421182a2007e3365805b74ceef936"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33be70d75fa05a904ee0dc43b650844e067d14c849df7e82ad673541cd465b5f"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:57d25b6732ea93eeb1d090e8399b6235ca84a651b52d52d272ed37d3d2efa0f1"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:baf2fe122a3db1c0b9f161aa44463d8f7e33eeeda47bb0309923deb743a18276"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a764a83128af9c1a54be81485b34dca488cbcacefe1e1d543ef11fbace191e1"}, + {file = "regex-2025.7.34-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7f663ccc4093877f55b51477522abd7299a14c5bb7626c5238599db6a0cb95d"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4913f52fbc7a744aaebf53acd8d3dc1b519e46ba481d4d7596de3c862e011ada"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:efac4db9e044d47fd3b6b0d40b6708f4dfa2d8131a5ac1d604064147c0f552fd"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7373afae7cfb716e3b8e15d0184510d518f9d21471f2d62918dbece85f2c588f"}, + {file = "regex-2025.7.34-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9960d162f3fecf6af252534a1ae337e9c2e20d74469fed782903b24e2cc9d3d7"}, + {file = "regex-2025.7.34-cp39-cp39-win32.whl", hash = "sha256:95d538b10eb4621350a54bf14600cc80b514211d91a019dc74b8e23d2159ace5"}, + {file = "regex-2025.7.34-cp39-cp39-win_amd64.whl", hash = "sha256:f7f3071b5faa605b0ea51ec4bb3ea7257277446b053f4fd3ad02b1dcb4e64353"}, + {file = "regex-2025.7.34-cp39-cp39-win_arm64.whl", hash = "sha256:716a47515ba1d03f8e8a61c5013041c8c90f2e21f055203498105d7571b44531"}, + {file = "regex-2025.7.34.tar.gz", hash = "sha256:9ead9765217afd04a86822dfcd4ed2747dfe426e887da413b15ff0ac2457e21a"}, ] [[package]] name = "requests" -version = "2.32.3" +version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, - {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, ] [package.dependencies] certifi = ">=2017.4.17" -charset-normalizer = ">=2,<4" +charset_normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" @@ -5119,6 +5727,7 @@ version = "2.0.0" description = "OAuthlib authentication support for Requests." optional = false python-versions = ">=3.4" +groups = ["main"] files = [ {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, @@ -5137,6 +5746,7 @@ version = "1.0.0" description = "A utility belt for advanced users of python-requests" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +groups = ["main"] files = [ {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, @@ -5151,6 +5761,7 @@ version = "0.1.4" description = "A pure python RFC3339 validator" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["dev"] files = [ {file = "rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa"}, {file = "rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b"}, @@ -5165,20 +5776,40 @@ version = "0.1.1" description = "Pure python rfc3986 validator" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +groups = ["dev"] files = [ {file = "rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9"}, {file = "rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055"}, ] +[[package]] +name = "rfc3987-syntax" +version = "1.1.0" +description = "Helper functions to syntactically validate strings according to RFC 3987." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f"}, + {file = "rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d"}, +] + +[package.dependencies] +lark = ">=1.2.2" + +[package.extras] +testing = ["pytest (>=8.3.5)"] + [[package]] name = "rich" -version = "14.0.0" +version = "14.1.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" +groups = ["main"] files = [ - {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, - {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, + {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"}, + {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"}, ] [package.dependencies] @@ -5190,136 +5821,168 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rpds-py" -version = "0.24.0" +version = "0.26.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724"}, - {file = "rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8acd55bd5b071156bae57b555f5d33697998752673b9de554dd82f5b5352727"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e80d375134ddb04231a53800503752093dbb65dad8dabacce2c84cccc78e964"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60748789e028d2a46fc1c70750454f83c6bdd0d05db50f5ae83e2db500b34da5"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e1daf5bf6c2be39654beae83ee6b9a12347cb5aced9a29eecf12a2d25fff664"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b221c2457d92a1fb3c97bee9095c874144d196f47c038462ae6e4a14436f7bc"}, - {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:66420986c9afff67ef0c5d1e4cdc2d0e5262f53ad11e4f90e5e22448df485bf0"}, - {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:43dba99f00f1d37b2a0265a259592d05fcc8e7c19d140fe51c6e6f16faabeb1f"}, - {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a88c0d17d039333a41d9bf4616bd062f0bd7aa0edeb6cafe00a2fc2a804e944f"}, - {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc31e13ce212e14a539d430428cd365e74f8b2d534f8bc22dd4c9c55b277b875"}, - {file = "rpds_py-0.24.0-cp310-cp310-win32.whl", hash = "sha256:fc2c1e1b00f88317d9de6b2c2b39b012ebbfe35fe5e7bef980fd2a91f6100a07"}, - {file = "rpds_py-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0145295ca415668420ad142ee42189f78d27af806fcf1f32a18e51d47dd2052"}, - {file = "rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef"}, - {file = "rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae"}, - {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc"}, - {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c"}, - {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c"}, - {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718"}, - {file = "rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a"}, - {file = "rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6"}, - {file = "rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205"}, - {file = "rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029"}, - {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9"}, - {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7"}, - {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91"}, - {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56"}, - {file = "rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30"}, - {file = "rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034"}, - {file = "rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c"}, - {file = "rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d"}, - {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7"}, - {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad"}, - {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120"}, - {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9"}, - {file = "rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143"}, - {file = "rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a"}, - {file = "rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114"}, - {file = "rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d"}, - {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7"}, - {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d"}, - {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797"}, - {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c"}, - {file = "rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba"}, - {file = "rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350"}, - {file = "rpds_py-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a36b452abbf29f68527cf52e181fced56685731c86b52e852053e38d8b60bc8d"}, - {file = "rpds_py-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b3b397eefecec8e8e39fa65c630ef70a24b09141a6f9fc17b3c3a50bed6b50e"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdabcd3beb2a6dca7027007473d8ef1c3b053347c76f685f5f060a00327b8b65"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5db385bacd0c43f24be92b60c857cf760b7f10d8234f4bd4be67b5b20a7c0b6b"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8097b3422d020ff1c44effc40ae58e67d93e60d540a65649d2cdaf9466030791"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493fe54318bed7d124ce272fc36adbf59d46729659b2c792e87c3b95649cdee9"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8aa362811ccdc1f8dadcc916c6d47e554169ab79559319ae9fae7d7752d0d60c"}, - {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8f9a6e7fd5434817526815f09ea27f2746c4a51ee11bb3439065f5fc754db58"}, - {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8205ee14463248d3349131bb8099efe15cd3ce83b8ef3ace63c7e976998e7124"}, - {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:921ae54f9ecba3b6325df425cf72c074cd469dea843fb5743a26ca7fb2ccb149"}, - {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32bab0a56eac685828e00cc2f5d1200c548f8bc11f2e44abf311d6b548ce2e45"}, - {file = "rpds_py-0.24.0-cp39-cp39-win32.whl", hash = "sha256:f5c0ed12926dec1dfe7d645333ea59cf93f4d07750986a586f511c0bc61fe103"}, - {file = "rpds_py-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:afc6e35f344490faa8276b5f2f7cbf71f88bc2cda4328e00553bd451728c571f"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:619ca56a5468f933d940e1bf431c6f4e13bef8e688698b067ae68eb4f9b30e3a"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4b28e5122829181de1898c2c97f81c0b3246d49f585f22743a1246420bb8d399"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e5ab32cf9eb3647450bc74eb201b27c185d3857276162c101c0f8c6374e098"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:208b3a70a98cf3710e97cabdc308a51cd4f28aa6e7bb11de3d56cd8b74bab98d"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbc4362e06f950c62cad3d4abf1191021b2ffaf0b31ac230fbf0526453eee75e"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebea2821cdb5f9fef44933617be76185b80150632736f3d76e54829ab4a3b4d1"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4df06c35465ef4d81799999bba810c68d29972bf1c31db61bfdb81dd9d5bb"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3aa13bdf38630da298f2e0d77aca967b200b8cc1473ea05248f6c5e9c9bdb44"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:041f00419e1da7a03c46042453598479f45be3d787eb837af382bfc169c0db33"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8754d872a5dfc3c5bf9c0e059e8107451364a30d9fd50f1f1a85c4fb9481164"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:896c41007931217a343eff197c34513c154267636c8056fb409eafd494c3dcdc"}, - {file = "rpds_py-0.24.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:92558d37d872e808944c3c96d0423b8604879a3d1c86fdad508d7ed91ea547d5"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25"}, - {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e0f3ef95795efcd3b2ec3fe0a5bcfb5dadf5e3996ea2117427e524d4fbf309c6"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2c13777ecdbbba2077670285dd1fe50828c8742f6a4119dbef6f83ea13ad10fb"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e8d804c2ccd618417e96720ad5cd076a86fa3f8cb310ea386a3e6229bae7d1"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd822f019ccccd75c832deb7aa040bb02d70a92eb15a2f16c7987b7ad4ee8d83"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0047638c3aa0dbcd0ab99ed1e549bbf0e142c9ecc173b6492868432d8989a046"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5b66d1b201cc71bc3081bc2f1fc36b0c1f268b773e03bbc39066651b9e18391"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbcbb6db5582ea33ce46a5d20a5793134b5365110d84df4e30b9d37c6fd40ad3"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63981feca3f110ed132fd217bf7768ee8ed738a55549883628ee3da75bb9cb78"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3a55fc10fdcbf1a4bd3c018eea422c52cf08700cf99c28b5cb10fe97ab77a0d3"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:c30ff468163a48535ee7e9bf21bd14c7a81147c0e58a36c1078289a8ca7af0bd"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:369d9c6d4c714e36d4a03957b4783217a3ccd1e222cdd67d464a3a479fc17796"}, - {file = "rpds_py-0.24.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:24795c099453e3721fda5d8ddd45f5dfcc8e5a547ce7b8e9da06fecc3832e26f"}, - {file = "rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e"}, + {file = "rpds_py-0.26.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:4c70c70f9169692b36307a95f3d8c0a9fcd79f7b4a383aad5eaa0e9718b79b37"}, + {file = "rpds_py-0.26.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:777c62479d12395bfb932944e61e915741e364c843afc3196b694db3d669fcd0"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec671691e72dff75817386aa02d81e708b5a7ec0dec6669ec05213ff6b77e1bd"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6a1cb5d6ce81379401bbb7f6dbe3d56de537fb8235979843f0d53bc2e9815a79"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f789e32fa1fb6a7bf890e0124e7b42d1e60d28ebff57fe806719abb75f0e9a3"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c55b0a669976cf258afd718de3d9ad1b7d1fe0a91cd1ab36f38b03d4d4aeaaf"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70d9ec912802ecfd6cd390dadb34a9578b04f9bcb8e863d0a7598ba5e9e7ccc"}, + {file = "rpds_py-0.26.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3021933c2cb7def39d927b9862292e0f4c75a13d7de70eb0ab06efed4c508c19"}, + {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a7898b6ca3b7d6659e55cdac825a2e58c638cbf335cde41f4619e290dd0ad11"}, + {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:12bff2ad9447188377f1b2794772f91fe68bb4bbfa5a39d7941fbebdbf8c500f"}, + {file = "rpds_py-0.26.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:191aa858f7d4902e975d4cf2f2d9243816c91e9605070aeb09c0a800d187e323"}, + {file = "rpds_py-0.26.0-cp310-cp310-win32.whl", hash = "sha256:b37a04d9f52cb76b6b78f35109b513f6519efb481d8ca4c321f6a3b9580b3f45"}, + {file = "rpds_py-0.26.0-cp310-cp310-win_amd64.whl", hash = "sha256:38721d4c9edd3eb6670437d8d5e2070063f305bfa2d5aa4278c51cedcd508a84"}, + {file = "rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed"}, + {file = "rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d"}, + {file = "rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3"}, + {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107"}, + {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a"}, + {file = "rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318"}, + {file = "rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a"}, + {file = "rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03"}, + {file = "rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41"}, + {file = "rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d"}, + {file = "rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a"}, + {file = "rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323"}, + {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158"}, + {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3"}, + {file = "rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2"}, + {file = "rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44"}, + {file = "rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c"}, + {file = "rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8"}, + {file = "rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d"}, + {file = "rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04"}, + {file = "rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1"}, + {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9"}, + {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9"}, + {file = "rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba"}, + {file = "rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b"}, + {file = "rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5"}, + {file = "rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256"}, + {file = "rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618"}, + {file = "rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f"}, + {file = "rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed"}, + {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632"}, + {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c"}, + {file = "rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0"}, + {file = "rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9"}, + {file = "rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9"}, + {file = "rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a"}, + {file = "rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246"}, + {file = "rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387"}, + {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af"}, + {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33"}, + {file = "rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953"}, + {file = "rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9"}, + {file = "rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37"}, + {file = "rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867"}, + {file = "rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da"}, + {file = "rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8"}, + {file = "rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b"}, + {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a"}, + {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170"}, + {file = "rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e"}, + {file = "rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f"}, + {file = "rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7"}, + {file = "rpds_py-0.26.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:7a48af25d9b3c15684059d0d1fc0bc30e8eee5ca521030e2bffddcab5be40226"}, + {file = "rpds_py-0.26.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c71c2f6bf36e61ee5c47b2b9b5d47e4d1baad6426bfed9eea3e858fc6ee8806"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d815d48b1804ed7867b539236b6dd62997850ca1c91cad187f2ddb1b7bbef19"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:84cfbd4d4d2cdeb2be61a057a258d26b22877266dd905809e94172dff01a42ae"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fbaa70553ca116c77717f513e08815aec458e6b69a028d4028d403b3bc84ff37"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39bfea47c375f379d8e87ab4bb9eb2c836e4f2069f0f65731d85e55d74666387"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1533b7eb683fb5f38c1d68a3c78f5fdd8f1412fa6b9bf03b40f450785a0ab915"}, + {file = "rpds_py-0.26.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c5ab0ee51f560d179b057555b4f601b7df909ed31312d301b99f8b9fc6028284"}, + {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e5162afc9e0d1f9cae3b577d9c29ddbab3505ab39012cb794d94a005825bde21"}, + {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:43f10b007033f359bc3fa9cd5e6c1e76723f056ffa9a6b5c117cc35720a80292"}, + {file = "rpds_py-0.26.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e3730a48e5622e598293eee0762b09cff34dd3f271530f47b0894891281f051d"}, + {file = "rpds_py-0.26.0-cp39-cp39-win32.whl", hash = "sha256:4b1f66eb81eab2e0ff5775a3a312e5e2e16bf758f7b06be82fb0d04078c7ac51"}, + {file = "rpds_py-0.26.0-cp39-cp39-win_amd64.whl", hash = "sha256:519067e29f67b5c90e64fb1a6b6e9d2ec0ba28705c51956637bac23a2f4ddae1"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3c0909c5234543ada2515c05dc08595b08d621ba919629e94427e8e03539c958"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:c1fb0cda2abcc0ac62f64e2ea4b4e64c57dfd6b885e693095460c61bde7bb18e"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84d142d2d6cf9b31c12aa4878d82ed3b2324226270b89b676ac62ccd7df52d08"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a547e21c5610b7e9093d870be50682a6a6cf180d6da0f42c47c306073bfdbbf6"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35e9a70a0f335371275cdcd08bc5b8051ac494dd58bff3bbfb421038220dc871"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dfa6115c6def37905344d56fb54c03afc49104e2ca473d5dedec0f6606913b4"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:313cfcd6af1a55a286a3c9a25f64af6d0e46cf60bc5798f1db152d97a216ff6f"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f7bf2496fa563c046d05e4d232d7b7fd61346e2402052064b773e5c378bf6f73"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:aa81873e2c8c5aa616ab8e017a481a96742fdf9313c40f14338ca7dbf50cb55f"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:68ffcf982715f5b5b7686bdd349ff75d422e8f22551000c24b30eaa1b7f7ae84"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6188de70e190847bb6db3dc3981cbadff87d27d6fe9b4f0e18726d55795cee9b"}, + {file = "rpds_py-0.26.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:1c962145c7473723df9722ba4c058de12eb5ebedcb4e27e7d902920aa3831ee8"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0"}, + {file = "rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:a90a13408a7a856b87be8a9f008fff53c5080eea4e4180f6c2e546e4a972fb5d"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:3ac51b65e8dc76cf4949419c54c5528adb24fc721df722fd452e5fbc236f5c40"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b2093224a18c6508d95cfdeba8db9cbfd6f3494e94793b58972933fcee4c6d"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4f01a5d6444a3258b00dc07b6ea4733e26f8072b788bef750baa37b370266137"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6e2c12160c72aeda9d1283e612f68804621f448145a210f1bf1d79151c47090"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cb28c1f569f8d33b2b5dcd05d0e6ef7005d8639c54c2f0be824f05aedf715255"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1766b5724c3f779317d5321664a343c07773c8c5fd1532e4039e6cc7d1a815be"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b6d9e5a2ed9c4988c8f9b28b3bc0e3e5b1aaa10c28d210a594ff3a8c02742daf"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:b5f7a446ddaf6ca0fad9a5535b56fbfc29998bf0e0b450d174bbec0d600e1d72"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:eed5ac260dd545fbc20da5f4f15e7efe36a55e0e7cf706e4ec005b491a9546a0"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:582462833ba7cee52e968b0341b85e392ae53d44c0f9af6a5927c80e539a8b67"}, + {file = "rpds_py-0.26.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:69a607203441e07e9a8a529cff1d5b73f6a160f22db1097211e6212a68567d11"}, + {file = "rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0"}, ] [[package]] name = "rsa" -version = "4.9" +version = "4.9.1" description = "Pure-Python RSA implementation" optional = false -python-versions = ">=3.6,<4" +python-versions = "<4,>=3.6" +groups = ["main"] files = [ - {file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"}, - {file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"}, + {file = "rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762"}, + {file = "rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75"}, ] [package.dependencies] @@ -5327,13 +5990,14 @@ pyasn1 = ">=0.1.3" [[package]] name = "s3transfer" -version = "0.11.4" +version = "0.13.1" description = "An Amazon S3 Transfer Manager" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "s3transfer-0.11.4-py3-none-any.whl", hash = "sha256:ac265fa68318763a03bf2dc4f39d5cbd6a9e178d81cc9483ad27da33637e320d"}, - {file = "s3transfer-0.11.4.tar.gz", hash = "sha256:559f161658e1cf0a911f45940552c696735f5c74e64362e515f333ebed87d679"}, + {file = "s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724"}, + {file = "s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf"}, ] [package.dependencies] @@ -5348,6 +6012,7 @@ version = "0.5.3" description = "" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073"}, {file = "safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7"}, @@ -5381,120 +6046,127 @@ torch = ["safetensors[numpy]", "torch (>=1.10)"] [[package]] name = "scikit-learn" -version = "1.6.1" +version = "1.7.1" description = "A set of python modules for machine learning and data mining" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" +groups = ["main"] files = [ - {file = "scikit_learn-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d056391530ccd1e501056160e3c9673b4da4805eb67eb2bdf4e983e1f9c9204e"}, - {file = "scikit_learn-1.6.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:0c8d036eb937dbb568c6242fa598d551d88fb4399c0344d95c001980ec1c7d36"}, - {file = "scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8634c4bd21a2a813e0a7e3900464e6d593162a29dd35d25bdf0103b3fce60ed5"}, - {file = "scikit_learn-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:775da975a471c4f6f467725dff0ced5c7ac7bda5e9316b260225b48475279a1b"}, - {file = "scikit_learn-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:8a600c31592bd7dab31e1c61b9bbd6dea1b3433e67d264d17ce1017dbdce8002"}, - {file = "scikit_learn-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:72abc587c75234935e97d09aa4913a82f7b03ee0b74111dcc2881cba3c5a7b33"}, - {file = "scikit_learn-1.6.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:b3b00cdc8f1317b5f33191df1386c0befd16625f49d979fe77a8d44cae82410d"}, - {file = "scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc4765af3386811c3ca21638f63b9cf5ecf66261cc4815c1db3f1e7dc7b79db2"}, - {file = "scikit_learn-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25fc636bdaf1cc2f4a124a116312d837148b5e10872147bdaf4887926b8c03d8"}, - {file = "scikit_learn-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:fa909b1a36e000a03c382aade0bd2063fd5680ff8b8e501660c0f59f021a6415"}, - {file = "scikit_learn-1.6.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:926f207c804104677af4857b2c609940b743d04c4c35ce0ddc8ff4f053cddc1b"}, - {file = "scikit_learn-1.6.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2c2cae262064e6a9b77eee1c8e768fc46aa0b8338c6a8297b9b6759720ec0ff2"}, - {file = "scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1061b7c028a8663fb9a1a1baf9317b64a257fcb036dae5c8752b2abef31d136f"}, - {file = "scikit_learn-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e69fab4ebfc9c9b580a7a80111b43d214ab06250f8a7ef590a4edf72464dd86"}, - {file = "scikit_learn-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:70b1d7e85b1c96383f872a519b3375f92f14731e279a7b4c6cfd650cf5dffc52"}, - {file = "scikit_learn-1.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ffa1e9e25b3d93990e74a4be2c2fc61ee5af85811562f1288d5d055880c4322"}, - {file = "scikit_learn-1.6.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dc5cf3d68c5a20ad6d571584c0750ec641cc46aeef1c1507be51300e6003a7e1"}, - {file = "scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c06beb2e839ecc641366000ca84f3cf6fa9faa1777e29cf0c04be6e4d096a348"}, - {file = "scikit_learn-1.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8ca8cb270fee8f1f76fa9bfd5c3507d60c6438bbee5687f81042e2bb98e5a97"}, - {file = "scikit_learn-1.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:7a1c43c8ec9fde528d664d947dc4c0789be4077a3647f232869f41d9bf50e0fb"}, - {file = "scikit_learn-1.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a17c1dea1d56dcda2fac315712f3651a1fea86565b64b48fa1bc090249cbf236"}, - {file = "scikit_learn-1.6.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a7aa5f9908f0f28f4edaa6963c0a6183f1911e63a69aa03782f0d924c830a35"}, - {file = "scikit_learn-1.6.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0650e730afb87402baa88afbf31c07b84c98272622aaba002559b614600ca691"}, - {file = "scikit_learn-1.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:3f59fe08dc03ea158605170eb52b22a105f238a5d512c4470ddeca71feae8e5f"}, - {file = "scikit_learn-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6849dd3234e87f55dce1db34c89a810b489ead832aaf4d4550b7ea85628be6c1"}, - {file = "scikit_learn-1.6.1-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:e7be3fa5d2eb9be7d77c3734ff1d599151bb523674be9b834e8da6abe132f44e"}, - {file = "scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44a17798172df1d3c1065e8fcf9019183f06c87609b49a124ebdf57ae6cb0107"}, - {file = "scikit_learn-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8b7a3b86e411e4bce21186e1c180d792f3d99223dcfa3b4f597ecc92fa1a422"}, - {file = "scikit_learn-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7a73d457070e3318e32bdb3aa79a8d990474f19035464dfd8bede2883ab5dc3b"}, - {file = "scikit_learn-1.6.1.tar.gz", hash = "sha256:b4fc2525eca2c69a59260f583c56a7557c6ccdf8deafdba6e060f94c1c59738e"}, + {file = "scikit_learn-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:406204dd4004f0517f0b23cf4b28c6245cbd51ab1b6b78153bc784def214946d"}, + {file = "scikit_learn-1.7.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:16af2e44164f05d04337fd1fc3ae7c4ea61fd9b0d527e22665346336920fe0e1"}, + {file = "scikit_learn-1.7.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2f2e78e56a40c7587dea9a28dc4a49500fa2ead366869418c66f0fd75b80885c"}, + {file = "scikit_learn-1.7.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b62b76ad408a821475b43b7bb90a9b1c9a4d8d125d505c2df0539f06d6e631b1"}, + {file = "scikit_learn-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:9963b065677a4ce295e8ccdee80a1dd62b37249e667095039adcd5bce6e90deb"}, + {file = "scikit_learn-1.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:90c8494ea23e24c0fb371afc474618c1019dc152ce4a10e4607e62196113851b"}, + {file = "scikit_learn-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:bb870c0daf3bf3be145ec51df8ac84720d9972170786601039f024bf6d61a518"}, + {file = "scikit_learn-1.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:40daccd1b5623f39e8943ab39735cadf0bdce80e67cdca2adcb5426e987320a8"}, + {file = "scikit_learn-1.7.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:30d1f413cfc0aa5a99132a554f1d80517563c34a9d3e7c118fde2d273c6fe0f7"}, + {file = "scikit_learn-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c711d652829a1805a95d7fe96654604a8f16eab5a9e9ad87b3e60173415cb650"}, + {file = "scikit_learn-1.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3cee419b49b5bbae8796ecd690f97aa412ef1674410c23fc3257c6b8b85b8087"}, + {file = "scikit_learn-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2fd8b8d35817b0d9ebf0b576f7d5ffbbabdb55536b0655a8aaae629d7ffd2e1f"}, + {file = "scikit_learn-1.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:588410fa19a96a69763202f1d6b7b91d5d7a5d73be36e189bc6396bfb355bd87"}, + {file = "scikit_learn-1.7.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e3142f0abe1ad1d1c31a2ae987621e41f6b578144a911ff4ac94781a583adad7"}, + {file = "scikit_learn-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3ddd9092c1bd469acab337d87930067c87eac6bd544f8d5027430983f1e1ae88"}, + {file = "scikit_learn-1.7.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b7839687fa46d02e01035ad775982f2470be2668e13ddd151f0f55a5bf123bae"}, + {file = "scikit_learn-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a10f276639195a96c86aa572ee0698ad64ee939a7b042060b98bd1930c261d10"}, + {file = "scikit_learn-1.7.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:13679981fdaebc10cc4c13c43344416a86fcbc61449cb3e6517e1df9d12c8309"}, + {file = "scikit_learn-1.7.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f1262883c6a63f067a980a8cdd2d2e7f2513dddcef6a9eaada6416a7a7cbe43"}, + {file = "scikit_learn-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca6d31fb10e04d50bfd2b50d66744729dbb512d4efd0223b864e2fdbfc4cee11"}, + {file = "scikit_learn-1.7.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:781674d096303cfe3d351ae6963ff7c958db61cde3421cd490e3a5a58f2a94ae"}, + {file = "scikit_learn-1.7.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:10679f7f125fe7ecd5fad37dd1aa2daae7e3ad8df7f3eefa08901b8254b3e12c"}, + {file = "scikit_learn-1.7.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1f812729e38c8cb37f760dce71a9b83ccfb04f59b3dca7c6079dcdc60544fa9e"}, + {file = "scikit_learn-1.7.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:88e1a20131cf741b84b89567e1717f27a2ced228e0f29103426102bc2e3b8ef7"}, + {file = "scikit_learn-1.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b1bd1d919210b6a10b7554b717c9000b5485aa95a1d0f177ae0d7ee8ec750da5"}, + {file = "scikit_learn-1.7.1.tar.gz", hash = "sha256:24b3f1e976a4665aa74ee0fcaac2b8fccc6ae77c8e07ab25da3ba6d3292b9802"}, ] [package.dependencies] joblib = ">=1.2.0" -numpy = ">=1.19.5" -scipy = ">=1.6.0" +numpy = ">=1.22.0" +scipy = ">=1.8.0" threadpoolctl = ">=3.1.0" [package.extras] -benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] -build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] -docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.17.1)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)", "towncrier (>=24.8.0)"] -examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] -install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] -maintenance = ["conda-lock (==2.5.6)"] -tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.5.1)", "scikit-image (>=0.17.2)"] +benchmark = ["matplotlib (>=3.5.0)", "memory_profiler (>=0.57.0)", "pandas (>=1.4.0)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.17.1)", "numpy (>=1.22.0)", "scipy (>=1.8.0)"] +docs = ["Pillow (>=8.4.0)", "matplotlib (>=3.5.0)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.4.0)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.19.0)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.17.1)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)", "towncrier (>=24.8.0)"] +examples = ["matplotlib (>=3.5.0)", "pandas (>=1.4.0)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.19.0)", "seaborn (>=0.9.0)"] +install = ["joblib (>=1.2.0)", "numpy (>=1.22.0)", "scipy (>=1.8.0)", "threadpoolctl (>=3.1.0)"] +maintenance = ["conda-lock (==3.0.1)"] +tests = ["matplotlib (>=3.5.0)", "mypy (>=1.15)", "numpydoc (>=1.2.0)", "pandas (>=1.4.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.2.1)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.11.7)", "scikit-image (>=0.19.0)"] [[package]] name = "scipy" -version = "1.15.2" +version = "1.16.1" description = "Fundamental algorithms for scientific computing in Python" optional = false -python-versions = ">=3.10" +python-versions = ">=3.11" +groups = ["main"] files = [ - {file = "scipy-1.15.2-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a2ec871edaa863e8213ea5df811cd600734f6400b4af272e1c011e69401218e9"}, - {file = "scipy-1.15.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:6f223753c6ea76983af380787611ae1291e3ceb23917393079dcc746ba60cfb5"}, - {file = "scipy-1.15.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:ecf797d2d798cf7c838c6d98321061eb3e72a74710e6c40540f0e8087e3b499e"}, - {file = "scipy-1.15.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:9b18aa747da280664642997e65aab1dd19d0c3d17068a04b3fe34e2559196cb9"}, - {file = "scipy-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87994da02e73549dfecaed9e09a4f9d58a045a053865679aeb8d6d43747d4df3"}, - {file = "scipy-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69ea6e56d00977f355c0f84eba69877b6df084516c602d93a33812aa04d90a3d"}, - {file = "scipy-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:888307125ea0c4466287191e5606a2c910963405ce9671448ff9c81c53f85f58"}, - {file = "scipy-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9412f5e408b397ff5641080ed1e798623dbe1ec0d78e72c9eca8992976fa65aa"}, - {file = "scipy-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:b5e025e903b4f166ea03b109bb241355b9c42c279ea694d8864d033727205e65"}, - {file = "scipy-1.15.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:92233b2df6938147be6fa8824b8136f29a18f016ecde986666be5f4d686a91a4"}, - {file = "scipy-1.15.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:62ca1ff3eb513e09ed17a5736929429189adf16d2d740f44e53270cc800ecff1"}, - {file = "scipy-1.15.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:4c6676490ad76d1c2894d77f976144b41bd1a4052107902238047fb6a473e971"}, - {file = "scipy-1.15.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:a8bf5cb4a25046ac61d38f8d3c3426ec11ebc350246a4642f2f315fe95bda655"}, - {file = "scipy-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a8e34cf4c188b6dd004654f88586d78f95639e48a25dfae9c5e34a6dc34547e"}, - {file = "scipy-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28a0d2c2075946346e4408b211240764759e0fabaeb08d871639b5f3b1aca8a0"}, - {file = "scipy-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42dabaaa798e987c425ed76062794e93a243be8f0f20fff6e7a89f4d61cb3d40"}, - {file = "scipy-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6f5e296ec63c5da6ba6fa0343ea73fd51b8b3e1a300b0a8cae3ed4b1122c7462"}, - {file = "scipy-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:597a0c7008b21c035831c39927406c6181bcf8f60a73f36219b69d010aa04737"}, - {file = "scipy-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c4697a10da8f8765bb7c83e24a470da5797e37041edfd77fd95ba3811a47c4fd"}, - {file = "scipy-1.15.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:869269b767d5ee7ea6991ed7e22b3ca1f22de73ab9a49c44bad338b725603301"}, - {file = "scipy-1.15.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bad78d580270a4d32470563ea86c6590b465cb98f83d760ff5b0990cb5518a93"}, - {file = "scipy-1.15.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b09ae80010f52efddb15551025f9016c910296cf70adbf03ce2a8704f3a5ad20"}, - {file = "scipy-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a6fd6eac1ce74a9f77a7fc724080d507c5812d61e72bd5e4c489b042455865e"}, - {file = "scipy-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b871df1fe1a3ba85d90e22742b93584f8d2b8e6124f8372ab15c71b73e428b8"}, - {file = "scipy-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:03205d57a28e18dfd39f0377d5002725bf1f19a46f444108c29bdb246b6c8a11"}, - {file = "scipy-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:601881dfb761311045b03114c5fe718a12634e5608c3b403737ae463c9885d53"}, - {file = "scipy-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:e7c68b6a43259ba0aab737237876e5c2c549a031ddb7abc28c7b47f22e202ded"}, - {file = "scipy-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01edfac9f0798ad6b46d9c4c9ca0e0ad23dbf0b1eb70e96adb9fa7f525eff0bf"}, - {file = "scipy-1.15.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:08b57a9336b8e79b305a143c3655cc5bdbe6d5ece3378578888d2afbb51c4e37"}, - {file = "scipy-1.15.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:54c462098484e7466362a9f1672d20888f724911a74c22ae35b61f9c5919183d"}, - {file = "scipy-1.15.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:cf72ff559a53a6a6d77bd8eefd12a17995ffa44ad86c77a5df96f533d4e6c6bb"}, - {file = "scipy-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9de9d1416b3d9e7df9923ab23cd2fe714244af10b763975bea9e4f2e81cebd27"}, - {file = "scipy-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb530e4794fc8ea76a4a21ccb67dea33e5e0e60f07fc38a49e821e1eae3b71a0"}, - {file = "scipy-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5ea7ed46d437fc52350b028b1d44e002646e28f3e8ddc714011aaf87330f2f32"}, - {file = "scipy-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11e7ad32cf184b74380f43d3c0a706f49358b904fa7d5345f16ddf993609184d"}, - {file = "scipy-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:a5080a79dfb9b78b768cebf3c9dcbc7b665c5875793569f48bf0e2b1d7f68f6f"}, - {file = "scipy-1.15.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:447ce30cee6a9d5d1379087c9e474628dab3db4a67484be1b7dc3196bfb2fac9"}, - {file = "scipy-1.15.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:c90ebe8aaa4397eaefa8455a8182b164a6cc1d59ad53f79943f266d99f68687f"}, - {file = "scipy-1.15.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:def751dd08243934c884a3221156d63e15234a3155cf25978b0a668409d45eb6"}, - {file = "scipy-1.15.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:302093e7dfb120e55515936cb55618ee0b895f8bcaf18ff81eca086c17bd80af"}, - {file = "scipy-1.15.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7cd5b77413e1855351cdde594eca99c1f4a588c2d63711388b6a1f1c01f62274"}, - {file = "scipy-1.15.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d0194c37037707b2afa7a2f2a924cf7bac3dc292d51b6a925e5fcb89bc5c776"}, - {file = "scipy-1.15.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:bae43364d600fdc3ac327db99659dcb79e6e7ecd279a75fe1266669d9a652828"}, - {file = "scipy-1.15.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f031846580d9acccd0044efd1a90e6f4df3a6e12b4b6bd694a7bc03a89892b28"}, - {file = "scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db"}, - {file = "scipy-1.15.2.tar.gz", hash = "sha256:cd58a314d92838f7e6f755c8a2167ead4f27e1fd5c1251fd54289569ef3495ec"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77"}, + {file = "scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe"}, + {file = "scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b"}, + {file = "scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7"}, + {file = "scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958"}, + {file = "scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39"}, + {file = "scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919"}, + {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921"}, + {file = "scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725"}, + {file = "scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618"}, + {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d"}, + {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119"}, + {file = "scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c"}, + {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608"}, + {file = "scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f"}, + {file = "scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b"}, + {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45"}, + {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65"}, + {file = "scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7"}, + {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6"}, + {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4"}, + {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3"}, + {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7"}, + {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc"}, + {file = "scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8"}, + {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e"}, + {file = "scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0"}, + {file = "scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b"}, + {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731"}, + {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3"}, + {file = "scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d"}, + {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695"}, + {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86"}, + {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff"}, + {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4"}, + {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3"}, + {file = "scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998"}, + {file = "scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3"}, ] [package.dependencies] -numpy = ">=1.23.5,<2.5" +numpy = ">=1.25.2,<2.6" [package.extras] dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] -doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.16.5)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] -test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] +doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "linkify-it-py", "matplotlib (>=3.5)", "myst-nb (>=1.2.0)", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.2.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] +test = ["Cython", "array-api-strict (>=2.3.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] [[package]] name = "send2trash" @@ -5502,6 +6174,7 @@ version = "1.8.3" description = "Send file to trash natively under Mac OS X, Windows and Linux" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +groups = ["dev"] files = [ {file = "Send2Trash-1.8.3-py3-none-any.whl", hash = "sha256:0c31227e0bd08961c7665474a3d1ef7193929fedda4233843689baa056be46c9"}, {file = "Send2Trash-1.8.3.tar.gz", hash = "sha256:b18e7a3966d99871aefeb00cfbcfdced55ce4871194810fc71f4aa484b953abf"}, @@ -5518,6 +6191,7 @@ version = "3.4.1" description = "State-of-the-Art Text Embeddings" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "sentence_transformers-3.4.1-py3-none-any.whl", hash = "sha256:e026dc6d56801fd83f74ad29a30263f401b4b522165c19386d8bc10dcca805da"}, {file = "sentence_transformers-3.4.1.tar.gz", hash = "sha256:68daa57504ff548340e54ff117bd86c1d2f784b21e0fb2689cf3272b8937b24b"}, @@ -5541,13 +6215,14 @@ train = ["accelerate (>=0.20.3)", "datasets"] [[package]] name = "setuptools" -version = "78.1.0" +version = "80.9.0" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8"}, - {file = "setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54"}, + {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"}, + {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"}, ] [package.extras] @@ -5565,6 +6240,7 @@ version = "1.5.4" description = "Tool to Detect Surrounding Shell" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, @@ -5576,6 +6252,7 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main", "dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -5587,6 +6264,7 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -5594,79 +6272,81 @@ files = [ [[package]] name = "soupsieve" -version = "2.6" +version = "2.7" description = "A modern CSS selector implementation for Beautiful Soup." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "soupsieve-2.6-py3-none-any.whl", hash = "sha256:e72c4ff06e4fb6e4b5a9f0f55fe6e81514581fca1515028625d0f299c602ccc9"}, - {file = "soupsieve-2.6.tar.gz", hash = "sha256:e2e68417777af359ec65daac1057404a3c8a5455bb8abc36f1a9866ab1a51abb"}, + {file = "soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4"}, + {file = "soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a"}, ] [[package]] name = "sqlalchemy" -version = "2.0.40" +version = "2.0.42" description = "Database Abstraction Library" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "SQLAlchemy-2.0.40-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ae9597cab738e7cc823f04a704fb754a9249f0b6695a6aeb63b74055cd417a96"}, - {file = "SQLAlchemy-2.0.40-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37a5c21ab099a83d669ebb251fddf8f5cee4d75ea40a5a1653d9c43d60e20867"}, - {file = "SQLAlchemy-2.0.40-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bece9527f5a98466d67fb5d34dc560c4da964240d8b09024bb21c1246545e04e"}, - {file = "SQLAlchemy-2.0.40-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:8bb131ffd2165fae48162c7bbd0d97c84ab961deea9b8bab16366543deeab625"}, - {file = "SQLAlchemy-2.0.40-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:9408fd453d5f8990405cc9def9af46bfbe3183e6110401b407c2d073c3388f47"}, - {file = "SQLAlchemy-2.0.40-cp37-cp37m-win32.whl", hash = "sha256:00a494ea6f42a44c326477b5bee4e0fc75f6a80c01570a32b57e89cf0fbef85a"}, - {file = "SQLAlchemy-2.0.40-cp37-cp37m-win_amd64.whl", hash = "sha256:c7b927155112ac858357ccf9d255dd8c044fd9ad2dc6ce4c4149527c901fa4c3"}, - {file = "sqlalchemy-2.0.40-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f1ea21bef99c703f44444ad29c2c1b6bd55d202750b6de8e06a955380f4725d7"}, - {file = "sqlalchemy-2.0.40-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:afe63b208153f3a7a2d1a5b9df452b0673082588933e54e7c8aac457cf35e758"}, - {file = "sqlalchemy-2.0.40-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8aae085ea549a1eddbc9298b113cffb75e514eadbb542133dd2b99b5fb3b6af"}, - {file = "sqlalchemy-2.0.40-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ea9181284754d37db15156eb7be09c86e16e50fbe77610e9e7bee09291771a1"}, - {file = "sqlalchemy-2.0.40-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5434223b795be5c5ef8244e5ac98056e290d3a99bdcc539b916e282b160dda00"}, - {file = "sqlalchemy-2.0.40-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15d08d5ef1b779af6a0909b97be6c1fd4298057504eb6461be88bd1696cb438e"}, - {file = "sqlalchemy-2.0.40-cp310-cp310-win32.whl", hash = "sha256:cd2f75598ae70bcfca9117d9e51a3b06fe29edd972fdd7fd57cc97b4dbf3b08a"}, - {file = "sqlalchemy-2.0.40-cp310-cp310-win_amd64.whl", hash = "sha256:2cbafc8d39ff1abdfdda96435f38fab141892dc759a2165947d1a8fffa7ef596"}, - {file = "sqlalchemy-2.0.40-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f6bacab7514de6146a1976bc56e1545bee247242fab030b89e5f70336fc0003e"}, - {file = "sqlalchemy-2.0.40-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5654d1ac34e922b6c5711631f2da497d3a7bffd6f9f87ac23b35feea56098011"}, - {file = "sqlalchemy-2.0.40-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35904d63412db21088739510216e9349e335f142ce4a04b69e2528020ee19ed4"}, - {file = "sqlalchemy-2.0.40-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7a80ed86d6aaacb8160a1caef6680d4ddd03c944d985aecee940d168c411d1"}, - {file = "sqlalchemy-2.0.40-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:519624685a51525ddaa7d8ba8265a1540442a2ec71476f0e75241eb8263d6f51"}, - {file = "sqlalchemy-2.0.40-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2ee5f9999a5b0e9689bed96e60ee53c3384f1a05c2dd8068cc2e8361b0df5b7a"}, - {file = "sqlalchemy-2.0.40-cp311-cp311-win32.whl", hash = "sha256:c0cae71e20e3c02c52f6b9e9722bca70e4a90a466d59477822739dc31ac18b4b"}, - {file = "sqlalchemy-2.0.40-cp311-cp311-win_amd64.whl", hash = "sha256:574aea2c54d8f1dd1699449f332c7d9b71c339e04ae50163a3eb5ce4c4325ee4"}, - {file = "sqlalchemy-2.0.40-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9d3b31d0a1c44b74d3ae27a3de422dfccd2b8f0b75e51ecb2faa2bf65ab1ba0d"}, - {file = "sqlalchemy-2.0.40-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:37f7a0f506cf78c80450ed1e816978643d3969f99c4ac6b01104a6fe95c5490a"}, - {file = "sqlalchemy-2.0.40-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bb933a650323e476a2e4fbef8997a10d0003d4da996aad3fd7873e962fdde4d"}, - {file = "sqlalchemy-2.0.40-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6959738971b4745eea16f818a2cd086fb35081383b078272c35ece2b07012716"}, - {file = "sqlalchemy-2.0.40-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:110179728e442dae85dd39591beb74072ae4ad55a44eda2acc6ec98ead80d5f2"}, - {file = "sqlalchemy-2.0.40-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8040680eaacdce4d635f12c55c714f3d4c7f57da2bc47a01229d115bd319191"}, - {file = "sqlalchemy-2.0.40-cp312-cp312-win32.whl", hash = "sha256:650490653b110905c10adac69408380688cefc1f536a137d0d69aca1069dc1d1"}, - {file = "sqlalchemy-2.0.40-cp312-cp312-win_amd64.whl", hash = "sha256:2be94d75ee06548d2fc591a3513422b873490efb124048f50556369a834853b0"}, - {file = "sqlalchemy-2.0.40-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:915866fd50dd868fdcc18d61d8258db1bf9ed7fbd6dfec960ba43365952f3b01"}, - {file = "sqlalchemy-2.0.40-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a4c5a2905a9ccdc67a8963e24abd2f7afcd4348829412483695c59e0af9a705"}, - {file = "sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55028d7a3ebdf7ace492fab9895cbc5270153f75442a0472d8516e03159ab364"}, - {file = "sqlalchemy-2.0.40-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6cfedff6878b0e0d1d0a50666a817ecd85051d12d56b43d9d425455e608b5ba0"}, - {file = "sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bb19e30fdae77d357ce92192a3504579abe48a66877f476880238a962e5b96db"}, - {file = "sqlalchemy-2.0.40-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:16d325ea898f74b26ffcd1cf8c593b0beed8714f0317df2bed0d8d1de05a8f26"}, - {file = "sqlalchemy-2.0.40-cp313-cp313-win32.whl", hash = "sha256:a669cbe5be3c63f75bcbee0b266779706f1a54bcb1000f302685b87d1b8c1500"}, - {file = "sqlalchemy-2.0.40-cp313-cp313-win_amd64.whl", hash = "sha256:641ee2e0834812d657862f3a7de95e0048bdcb6c55496f39c6fa3d435f6ac6ad"}, - {file = "sqlalchemy-2.0.40-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:50f5885bbed261fc97e2e66c5156244f9704083a674b8d17f24c72217d29baf5"}, - {file = "sqlalchemy-2.0.40-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cf0e99cdb600eabcd1d65cdba0d3c91418fee21c4aa1d28db47d095b1064a7d8"}, - {file = "sqlalchemy-2.0.40-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe147fcd85aaed53ce90645c91ed5fca0cc88a797314c70dfd9d35925bd5d106"}, - {file = "sqlalchemy-2.0.40-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf7cee56bd552385c1ee39af360772fbfc2f43be005c78d1140204ad6148438"}, - {file = "sqlalchemy-2.0.40-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4aeb939bcac234b88e2d25d5381655e8353fe06b4e50b1c55ecffe56951d18c2"}, - {file = "sqlalchemy-2.0.40-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c268b5100cfeaa222c40f55e169d484efa1384b44bf9ca415eae6d556f02cb08"}, - {file = "sqlalchemy-2.0.40-cp38-cp38-win32.whl", hash = "sha256:46628ebcec4f23a1584fb52f2abe12ddb00f3bb3b7b337618b80fc1b51177aff"}, - {file = "sqlalchemy-2.0.40-cp38-cp38-win_amd64.whl", hash = "sha256:7e0505719939e52a7b0c65d20e84a6044eb3712bb6f239c6b1db77ba8e173a37"}, - {file = "sqlalchemy-2.0.40-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c884de19528e0fcd9dc34ee94c810581dd6e74aef75437ff17e696c2bfefae3e"}, - {file = "sqlalchemy-2.0.40-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1abb387710283fc5983d8a1209d9696a4eae9db8d7ac94b402981fe2fe2e39ad"}, - {file = "sqlalchemy-2.0.40-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cfa124eda500ba4b0d3afc3e91ea27ed4754e727c7f025f293a22f512bcd4c9"}, - {file = "sqlalchemy-2.0.40-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b6b28d303b9d57c17a5164eb1fd2d5119bb6ff4413d5894e74873280483eeb5"}, - {file = "sqlalchemy-2.0.40-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b5a5bbe29c10c5bfd63893747a1bf6f8049df607638c786252cb9243b86b6706"}, - {file = "sqlalchemy-2.0.40-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:f0fda83e113bb0fb27dc003685f32a5dcb99c9c4f41f4fa0838ac35265c23b5c"}, - {file = "sqlalchemy-2.0.40-cp39-cp39-win32.whl", hash = "sha256:957f8d85d5e834397ef78a6109550aeb0d27a53b5032f7a57f2451e1adc37e98"}, - {file = "sqlalchemy-2.0.40-cp39-cp39-win_amd64.whl", hash = "sha256:1ffdf9c91428e59744f8e6f98190516f8e1d05eec90e936eb08b257332c5e870"}, - {file = "sqlalchemy-2.0.40-py3-none-any.whl", hash = "sha256:32587e2e1e359276957e6fe5dad089758bc042a971a8a09ae8ecf7a8fe23d07a"}, - {file = "sqlalchemy-2.0.40.tar.gz", hash = "sha256:d827099289c64589418ebbcaead0145cd19f4e3e8a93919a0100247af245fa00"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7ee065898359fdee83961aed5cf1fb4cfa913ba71b58b41e036001d90bebbf7a"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56bc76d86216443daa2e27e6b04a9b96423f0b69b5d0c40c7f4b9a4cdf7d8d90"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89143290fb94c50a8dec73b06109ccd245efd8011d24fc0ddafe89dc55b36651"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:4efbdc9754c7145a954911bfeef815fb0843e8edab0e9cecfa3417a5cbd316af"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:88f8a8007a658dfd82c16a20bd9673ae6b33576c003b5166d42697d49e496e61"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-win32.whl", hash = "sha256:c5dd245e6502990ccf612d51f220a7b04cbea3f00f6030691ffe27def76ca79b"}, + {file = "SQLAlchemy-2.0.42-cp37-cp37m-win_amd64.whl", hash = "sha256:5651eb19cacbeb2fe7431e4019312ed00a0b3fbd2d701423e0e2ceaadb5bcd9f"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:172b244753e034d91a826f80a9a70f4cbac690641207f2217f8404c261473efe"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:be28f88abd74af8519a4542185ee80ca914933ca65cdfa99504d82af0e4210df"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98b344859d282fde388047f1710860bb23f4098f705491e06b8ab52a48aafea9"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97978d223b11f1d161390a96f28c49a13ce48fdd2fed7683167c39bdb1b8aa09"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e35b9b000c59fcac2867ab3a79fc368a6caca8706741beab3b799d47005b3407"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:bc7347ad7a7b1c78b94177f2d57263113bb950e62c59b96ed839b131ea4234e1"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-win32.whl", hash = "sha256:739e58879b20a179156b63aa21f05ccacfd3e28e08e9c2b630ff55cd7177c4f1"}, + {file = "sqlalchemy-2.0.42-cp310-cp310-win_amd64.whl", hash = "sha256:1aef304ada61b81f1955196f584b9e72b798ed525a7c0b46e09e98397393297b"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c34100c0b7ea31fbc113c124bcf93a53094f8951c7bf39c45f39d327bad6d1e7"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad59dbe4d1252448c19d171dfba14c74e7950b46dc49d015722a4a06bfdab2b0"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f9187498c2149919753a7fd51766ea9c8eecdec7da47c1b955fa8090bc642eaa"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f092cf83ebcafba23a247f5e03f99f5436e3ef026d01c8213b5eca48ad6efa9"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc6afee7e66fdba4f5a68610b487c1f754fccdc53894a9567785932dbb6a265e"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:260ca1d2e5910f1f1ad3fe0113f8fab28657cee2542cb48c2f342ed90046e8ec"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-win32.whl", hash = "sha256:2eb539fd83185a85e5fcd6b19214e1c734ab0351d81505b0f987705ba0a1e231"}, + {file = "sqlalchemy-2.0.42-cp311-cp311-win_amd64.whl", hash = "sha256:9193fa484bf00dcc1804aecbb4f528f1123c04bad6a08d7710c909750fa76aeb"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:09637a0872689d3eb71c41e249c6f422e3e18bbd05b4cd258193cfc7a9a50da2"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3cb3ec67cc08bea54e06b569398ae21623534a7b1b23c258883a7c696ae10df"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e87e6a5ef6f9d8daeb2ce5918bf5fddecc11cae6a7d7a671fcc4616c47635e01"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b718011a9d66c0d2f78e1997755cd965f3414563b31867475e9bc6efdc2281d"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:16d9b544873fe6486dddbb859501a07d89f77c61d29060bb87d0faf7519b6a4d"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21bfdf57abf72fa89b97dd74d3187caa3172a78c125f2144764a73970810c4ee"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-win32.whl", hash = "sha256:78b46555b730a24901ceb4cb901c6b45c9407f8875209ed3c5d6bcd0390a6ed1"}, + {file = "sqlalchemy-2.0.42-cp312-cp312-win_amd64.whl", hash = "sha256:4c94447a016f36c4da80072e6c6964713b0af3c8019e9c4daadf21f61b81ab53"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:941804f55c7d507334da38133268e3f6e5b0340d584ba0f277dd884197f4ae8c"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:95d3d06a968a760ce2aa6a5889fefcbdd53ca935735e0768e1db046ec08cbf01"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4cf10396a8a700a0f38ccd220d940be529c8f64435c5d5b29375acab9267a6c9"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9cae6c2b05326d7c2c7c0519f323f90e0fb9e8afa783c6a05bb9ee92a90d0f04"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f50f7b20677b23cfb35b6afcd8372b2feb348a38e3033f6447ee0704540be894"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d88a1c0d66d24e229e3938e1ef16ebdbd2bf4ced93af6eff55225f7465cf350"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-win32.whl", hash = "sha256:45c842c94c9ad546c72225a0c0d1ae8ef3f7c212484be3d429715a062970e87f"}, + {file = "sqlalchemy-2.0.42-cp313-cp313-win_amd64.whl", hash = "sha256:eb9905f7f1e49fd57a7ed6269bc567fcbbdac9feadff20ad6bd7707266a91577"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed5a6959b1668d97a32e3fd848b485f65ee3c05a759dee06d90e4545a3c77f1e"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2ddbaafe32f0dd12d64284b1c3189104b784c9f3dba8cc1ba7e642e2b14b906f"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37f4f42568b6c656ee177b3e111d354b5dda75eafe9fe63492535f91dfa35829"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb57923d852d38671a17abda9a65cc59e3e5eab51fb8307b09de46ed775bcbb8"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:437c2a8b0c780ff8168a470beb22cb4a25e1c63ea6a7aec87ffeb07aa4b76641"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:480f7df62f0b3ad6aa011eefa096049dc1770208bb71f234959ee2864206eefe"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-win32.whl", hash = "sha256:d119c80c614d62d32e236ae68e21dd28a2eaf070876b2f28a6075d5bae54ef3f"}, + {file = "sqlalchemy-2.0.42-cp38-cp38-win_amd64.whl", hash = "sha256:be3a02f963c8d66e28bb4183bebab66dc4379701d92e660f461c65fecd6ff399"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:78548fd65cd76d4c5a2e6b5f245d7734023ee4de33ee7bb298f1ac25a9935e0d"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cf4bf5a174d8a679a713b7a896470ffc6baab78e80a79e7ec5668387ffeccc8b"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8c7ff7ba08b375f8a8fa0511e595c9bdabb5494ec68f1cf69bb24e54c0d90f2"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b3c117f65d64e806ce5ce9ce578f06224dc36845e25ebd2554b3e86960e1aed"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:27e4a7b3a7a61ff919c2e7caafd612f8626114e6e5ebbe339de3b5b1df9bc27e"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:b01e0dd39f96aefda5ab002d8402db4895db871eb0145836246ce0661635ce55"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-win32.whl", hash = "sha256:49362193b1f43aa158deebf438062d7b5495daa9177c6c5d0f02ceeb64b544ea"}, + {file = "sqlalchemy-2.0.42-cp39-cp39-win_amd64.whl", hash = "sha256:636ec3dc83b2422a7ff548d0f8abf9c23742ca50e2a5cdc492a151eac7a0248b"}, + {file = "sqlalchemy-2.0.42-py3-none-any.whl", hash = "sha256:defcdff7e661f0043daa381832af65d616e060ddb54d3fe4476f51df7eaa1835"}, + {file = "sqlalchemy-2.0.42.tar.gz", hash = "sha256:160bedd8a5c28765bd5be4dec2d881e109e33b34922e50a3b881a7681773ac5f"}, ] [package.dependencies] @@ -5704,6 +6384,7 @@ version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, @@ -5717,32 +6398,16 @@ pure-eval = "*" [package.extras] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] -[[package]] -name = "starlette" -version = "0.46.1" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.9" -files = [ - {file = "starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227"}, - {file = "starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230"}, -] - -[package.dependencies] -anyio = ">=3.6.2,<5" - -[package.extras] -full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] - [[package]] name = "sympy" -version = "1.13.1" +version = "1.14.0" description = "Computer algebra system (CAS) in Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8"}, - {file = "sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f"}, + {file = "sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5"}, + {file = "sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517"}, ] [package.dependencies] @@ -5757,6 +6422,7 @@ version = "9.1.2" description = "Retry code until it succeeds" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -5772,6 +6438,7 @@ version = "0.18.1" description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0"}, {file = "terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e"}, @@ -5793,6 +6460,7 @@ version = "3.6.0" description = "threadpoolctl" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "threadpoolctl-3.6.0-py3-none-any.whl", hash = "sha256:43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb"}, {file = "threadpoolctl-3.6.0.tar.gz", hash = "sha256:8ab8b4aa3491d812b623328249fab5302a68d2d71745c8a4c719a2fcaba9f44e"}, @@ -5804,6 +6472,7 @@ version = "1.4.0" description = "A tiny CSS parser" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "tinycss2-1.4.0-py3-none-any.whl", hash = "sha256:3a49cf47b7675da0b15d0c6e1df8df4ebd96e9394bb905a5775adb0d884c5289"}, {file = "tinycss2-1.4.0.tar.gz", hash = "sha256:10c0972f6fc0fbee87c3edb76549357415e94548c1ae10ebccdea16fb404a9b7"}, @@ -5818,26 +6487,27 @@ test = ["pytest", "ruff"] [[package]] name = "tokenizers" -version = "0.21.1" +version = "0.21.4" description = "" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41"}, - {file = "tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3"}, - {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f"}, - {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf"}, - {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8"}, - {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0"}, - {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c"}, - {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a"}, - {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf"}, - {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6"}, - {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d"}, - {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f"}, - {file = "tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3"}, - {file = "tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382"}, - {file = "tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab"}, + {file = "tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133"}, + {file = "tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60"}, + {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5"}, + {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2107ad649e2cda4488d41dfd031469e9da3fcbfd6183e74e4958fa729ffbf9c6"}, + {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c73012da95afafdf235ba80047699df4384fdc481527448a078ffd00e45a7d9"}, + {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f23186c40395fc390d27f519679a58023f368a0aad234af145e0f39ad1212732"}, + {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc88bb34e23a54cc42713d6d98af5f1bf79c07653d24fe984d2d695ba2c922a2"}, + {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff"}, + {file = "tokenizers-0.21.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:714b05b2e1af1288bd1bc56ce496c4cebb64a20d158ee802887757791191e6e2"}, + {file = "tokenizers-0.21.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1340ff877ceedfa937544b7d79f5b7becf33a4cfb58f89b3b49927004ef66f78"}, + {file = "tokenizers-0.21.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3c1f4317576e465ac9ef0d165b247825a2a4078bcd01cba6b54b867bdf9fdd8b"}, + {file = "tokenizers-0.21.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c212aa4e45ec0bb5274b16b6f31dd3f1c41944025c2358faaa5782c754e84c24"}, + {file = "tokenizers-0.21.4-cp39-abi3-win32.whl", hash = "sha256:6c42a930bc5f4c47f4ea775c91de47d27910881902b0f20e4990ebe045a415d0"}, + {file = "tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597"}, + {file = "tokenizers-0.21.4.tar.gz", hash = "sha256:fa23f85fbc9a02ec5c6978da172cdcbac23498c3ca9f3645c5c68740ac007880"}, ] [package.dependencies] @@ -5850,31 +6520,36 @@ testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests", "ruff"] [[package]] name = "torch" -version = "2.6.0" +version = "2.7.1" description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" optional = false python-versions = ">=3.9.0" +groups = ["main"] files = [ - {file = "torch-2.6.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:6860df13d9911ac158f4c44031609700e1eba07916fff62e21e6ffa0a9e01961"}, - {file = "torch-2.6.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c4f103a49830ce4c7561ef4434cc7926e5a5fe4e5eb100c19ab36ea1e2b634ab"}, - {file = "torch-2.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:56eeaf2ecac90da5d9e35f7f35eb286da82673ec3c582e310a8d1631a1c02341"}, - {file = "torch-2.6.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:09e06f9949e1a0518c5b09fe95295bc9661f219d9ecb6f9893e5123e10696628"}, - {file = "torch-2.6.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:7979834102cd5b7a43cc64e87f2f3b14bd0e1458f06e9f88ffa386d07c7446e1"}, - {file = "torch-2.6.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ccbd0320411fe1a3b3fec7b4d3185aa7d0c52adac94480ab024b5c8f74a0bf1d"}, - {file = "torch-2.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:46763dcb051180ce1ed23d1891d9b1598e07d051ce4c9d14307029809c4d64f7"}, - {file = "torch-2.6.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:94fc63b3b4bedd327af588696559f68c264440e2503cc9e6954019473d74ae21"}, - {file = "torch-2.6.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:2bb8987f3bb1ef2675897034402373ddfc8f5ef0e156e2d8cfc47cacafdda4a9"}, - {file = "torch-2.6.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b789069020c5588c70d5c2158ac0aa23fd24a028f34a8b4fcb8fcb4d7efcf5fb"}, - {file = "torch-2.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e1448426d0ba3620408218b50aa6ada88aeae34f7a239ba5431f6c8774b1239"}, - {file = "torch-2.6.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:9a610afe216a85a8b9bc9f8365ed561535c93e804c2a317ef7fabcc5deda0989"}, - {file = "torch-2.6.0-cp313-cp313-manylinux1_x86_64.whl", hash = "sha256:4874a73507a300a5d089ceaff616a569e7bb7c613c56f37f63ec3ffac65259cf"}, - {file = "torch-2.6.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:a0d5e1b9874c1a6c25556840ab8920569a7a4137afa8a63a32cee0bc7d89bd4b"}, - {file = "torch-2.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:510c73251bee9ba02ae1cb6c9d4ee0907b3ce6020e62784e2d7598e0cfa4d6cc"}, - {file = "torch-2.6.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:ff96f4038f8af9f7ec4231710ed4549da1bdebad95923953a25045dcf6fd87e2"}, - {file = "torch-2.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:9ea955317cfcd3852b1402b62af258ce735c2edeee42ca9419b6bc889e5ae053"}, - {file = "torch-2.6.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:bb2c6c3e65049f081940f5ab15c9136c7de40d3f01192541c920a07c7c585b7e"}, - {file = "torch-2.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:683410f97984103148e31b38a8631acf31c3034c020c0f4d26171e7626d8317a"}, - {file = "torch-2.6.0-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:265f70de5fd45b864d924b64be1797f86e76c8e48a02c2a3a6fc7ec247d2226c"}, + {file = "torch-2.7.1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a103b5d782af5bd119b81dbcc7ffc6fa09904c423ff8db397a1e6ea8fd71508f"}, + {file = "torch-2.7.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:fe955951bdf32d182ee8ead6c3186ad54781492bf03d547d31771a01b3d6fb7d"}, + {file = "torch-2.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:885453d6fba67d9991132143bf7fa06b79b24352f4506fd4d10b309f53454162"}, + {file = "torch-2.7.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:d72acfdb86cee2a32c0ce0101606f3758f0d8bb5f8f31e7920dc2809e963aa7c"}, + {file = "torch-2.7.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:236f501f2e383f1cb861337bdf057712182f910f10aeaf509065d54d339e49b2"}, + {file = "torch-2.7.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:06eea61f859436622e78dd0cdd51dbc8f8c6d76917a9cf0555a333f9eac31ec1"}, + {file = "torch-2.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:8273145a2e0a3c6f9fd2ac36762d6ee89c26d430e612b95a99885df083b04e52"}, + {file = "torch-2.7.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:aea4fc1bf433d12843eb2c6b2204861f43d8364597697074c8d38ae2507f8730"}, + {file = "torch-2.7.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ea1e518df4c9de73af7e8a720770f3628e7f667280bce2be7a16292697e3fa"}, + {file = "torch-2.7.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c33360cfc2edd976c2633b3b66c769bdcbbf0e0b6550606d188431c81e7dd1fc"}, + {file = "torch-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:d8bf6e1856ddd1807e79dc57e54d3335f2b62e6f316ed13ed3ecfe1fc1df3d8b"}, + {file = "torch-2.7.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:787687087412c4bd68d315e39bc1223f08aae1d16a9e9771d95eabbb04ae98fb"}, + {file = "torch-2.7.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:03563603d931e70722dce0e11999d53aa80a375a3d78e6b39b9f6805ea0a8d28"}, + {file = "torch-2.7.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:d632f5417b6980f61404a125b999ca6ebd0b8b4bbdbb5fbbba44374ab619a412"}, + {file = "torch-2.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:23660443e13995ee93e3d844786701ea4ca69f337027b05182f5ba053ce43b38"}, + {file = "torch-2.7.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:0da4f4dba9f65d0d203794e619fe7ca3247a55ffdcbd17ae8fb83c8b2dc9b585"}, + {file = "torch-2.7.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e08d7e6f21a617fe38eeb46dd2213ded43f27c072e9165dc27300c9ef9570934"}, + {file = "torch-2.7.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:30207f672328a42df4f2174b8f426f354b2baa0b7cca3a0adb3d6ab5daf00dc8"}, + {file = "torch-2.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:79042feca1c634aaf6603fe6feea8c6b30dfa140a6bbc0b973e2260c7e79a22e"}, + {file = "torch-2.7.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:988b0cbc4333618a1056d2ebad9eb10089637b659eb645434d0809d8d937b946"}, + {file = "torch-2.7.1-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:e0d81e9a12764b6f3879a866607c8ae93113cbcad57ce01ebde63eb48a576369"}, + {file = "torch-2.7.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:8394833c44484547ed4a47162318337b88c97acdb3273d85ea06e03ffff44998"}, + {file = "torch-2.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:df41989d9300e6e3c19ec9f56f856187a6ef060c3662fe54f4b6baf1fc90bd19"}, + {file = "torch-2.7.1-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:a737b5edd1c44a5c1ece2e9f3d00df9d1b3fb9541138bee56d83d38293fb6c9d"}, ] [package.dependencies] @@ -5882,22 +6557,23 @@ filelock = "*" fsspec = "*" jinja2 = "*" networkx = "*" -nvidia-cublas-cu12 = {version = "12.4.5.8", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-cupti-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-nvrtc-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cuda-runtime-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cudnn-cu12 = {version = "9.1.0.70", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cufft-cu12 = {version = "11.2.1.3", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-curand-cu12 = {version = "10.3.5.147", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cusolver-cu12 = {version = "11.6.1.9", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cusparse-cu12 = {version = "12.3.1.170", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-cusparselt-cu12 = {version = "0.6.2", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-nccl-cu12 = {version = "2.21.5", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-nvjitlink-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} -nvidia-nvtx-cu12 = {version = "12.4.127", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cublas-cu12 = {version = "12.6.4.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-cupti-cu12 = {version = "12.6.80", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-nvrtc-cu12 = {version = "12.6.77", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-runtime-cu12 = {version = "12.6.77", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cudnn-cu12 = {version = "9.5.1.17", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cufft-cu12 = {version = "11.3.0.4", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cufile-cu12 = {version = "1.11.1.6", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-curand-cu12 = {version = "10.3.7.77", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusolver-cu12 = {version = "11.7.1.2", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparse-cu12 = {version = "12.5.4.2", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparselt-cu12 = {version = "0.6.3", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nccl-cu12 = {version = "2.26.2", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvjitlink-cu12 = {version = "12.6.85", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvtx-cu12 = {version = "12.6.77", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} setuptools = {version = "*", markers = "python_version >= \"3.12\""} -sympy = {version = "1.13.1", markers = "python_version >= \"3.9\""} -triton = {version = "3.2.0", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +sympy = ">=1.13.3" +triton = {version = "3.3.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} typing-extensions = ">=4.10.0" [package.extras] @@ -5906,22 +6582,24 @@ optree = ["optree (>=0.13.0)"] [[package]] name = "tornado" -version = "6.4.2" +version = "6.5.1" description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1"}, - {file = "tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803"}, - {file = "tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec"}, - {file = "tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946"}, - {file = "tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf"}, - {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634"}, - {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73"}, - {file = "tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c"}, - {file = "tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482"}, - {file = "tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38"}, - {file = "tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b"}, + {file = "tornado-6.5.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d50065ba7fd11d3bd41bcad0825227cc9a95154bad83239357094c36708001f7"}, + {file = "tornado-6.5.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9e9ca370f717997cb85606d074b0e5b247282cf5e2e1611568b8821afe0342d6"}, + {file = "tornado-6.5.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b77e9dfa7ed69754a54c89d82ef746398be82f749df69c4d3abe75c4d1ff4888"}, + {file = "tornado-6.5.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:253b76040ee3bab8bcf7ba9feb136436a3787208717a1fb9f2c16b744fba7331"}, + {file = "tornado-6.5.1-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:308473f4cc5a76227157cdf904de33ac268af770b2c5f05ca6c1161d82fdd95e"}, + {file = "tornado-6.5.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:caec6314ce8a81cf69bd89909f4b633b9f523834dc1a352021775d45e51d9401"}, + {file = "tornado-6.5.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:13ce6e3396c24e2808774741331638ee6c2f50b114b97a55c5b442df65fd9692"}, + {file = "tornado-6.5.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5cae6145f4cdf5ab24744526cc0f55a17d76f02c98f4cff9daa08ae9a217448a"}, + {file = "tornado-6.5.1-cp39-abi3-win32.whl", hash = "sha256:e0a36e1bc684dca10b1aa75a31df8bdfed656831489bc1e6a6ebed05dc1ec365"}, + {file = "tornado-6.5.1-cp39-abi3-win_amd64.whl", hash = "sha256:908e7d64567cecd4c2b458075589a775063453aeb1d2a1853eedb806922f568b"}, + {file = "tornado-6.5.1-cp39-abi3-win_arm64.whl", hash = "sha256:02420a0eb7bf617257b9935e2b754d1b63897525d8a289c9d65690d580b4dcf7"}, + {file = "tornado-6.5.1.tar.gz", hash = "sha256:84ceece391e8eb9b2b95578db65e920d2a61070260594819589609ba9bc6308c"}, ] [[package]] @@ -5930,6 +6608,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" +groups = ["main"] files = [ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, @@ -5951,6 +6630,7 @@ version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" +groups = ["dev"] files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, @@ -5962,18 +6642,19 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, [[package]] name = "transformers" -version = "4.51.1" +version = "4.55.0" description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" optional = false python-versions = ">=3.9.0" +groups = ["main"] files = [ - {file = "transformers-4.51.1-py3-none-any.whl", hash = "sha256:c7038e216afb2a3e9b00dd12d87ad5e3af4c30895f70b28e92f65459eded0161"}, - {file = "transformers-4.51.1.tar.gz", hash = "sha256:206ea0b75dfde142ed7495b911da76579dce6ea249cc3695fdd29a544a9e007b"}, + {file = "transformers-4.55.0-py3-none-any.whl", hash = "sha256:29d9b8800e32a4a831bb16efb5f762f6a9742fef9fce5d693ed018d19b106490"}, + {file = "transformers-4.55.0.tar.gz", hash = "sha256:15aa138a05d07a15b30d191ea2c45e23061ebf9fcc928a1318e03fe2234f3ae1"}, ] [package.dependencies] filelock = "*" -huggingface-hub = ">=0.30.0,<1.0" +huggingface-hub = ">=0.34.0,<1.0" numpy = ">=1.17" packaging = ">=20.0" pyyaml = ">=5.1" @@ -5985,81 +6666,89 @@ tqdm = ">=4.27" [package.extras] accelerate = ["accelerate (>=0.26.0)"] -agents = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "datasets (!=2.5.0)", "diffusers", "opencv-python", "sentencepiece (>=0.1.91,!=0.1.92)", "torch (>=2.0)"] -all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "av", "codecarbon (>=2.8.1)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "keras-nlp (>=0.3.1,<0.14.0)", "kernels (>=0.3.2,<0.4)", "librosa", "num2words", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch (>=2.0)", "torchaudio", "torchvision"] -audio = ["librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +all = ["Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "av", "codecarbon (>=2.8.1)", "flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "kernels (>=0.6.1,<=0.9)", "librosa", "mistral-common[opencv] (>=1.6.3)", "num2words", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "phonemizer", "protobuf", "pyctcdecode (>=0.4.0)", "ray[tune] (>=2.7.0)", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timm (!=1.0.18,<=1.0.19)", "tokenizers (>=0.21,<0.22)", "torch (>=2.1)", "torchaudio", "torchvision"] +audio = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] benchmark = ["optimum-benchmark (>=0.3.0)"] codecarbon = ["codecarbon (>=2.8.1)"] deepspeed = ["accelerate (>=0.26.0)", "deepspeed (>=0.9.3)"] -deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.26.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk (<=3.8.1)", "optuna", "parameterized", "protobuf", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] -dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "av", "beautifulsoup4", "codecarbon (>=2.8.1)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "keras-nlp (>=0.3.1,<0.14.0)", "kernels (>=0.3.2,<0.4)", "libcst", "librosa", "nltk (<=3.8.1)", "num2words", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch (>=2.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] -dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "isort (>=5.5.4)", "keras-nlp (>=0.3.1,<0.14.0)", "libcst", "librosa", "nltk (<=3.8.1)", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.21,<0.22)", "urllib3 (<2.0.0)"] -dev-torch = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "beautifulsoup4", "codecarbon (>=2.8.1)", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "isort (>=5.5.4)", "kernels (>=0.3.2,<0.4)", "libcst", "librosa", "nltk (<=3.8.1)", "num2words", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "parameterized", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm (<=1.0.11)", "tokenizers (>=0.21,<0.22)", "torch (>=2.0)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +deepspeed-testing = ["GitPython (<3.1.19)", "accelerate (>=0.26.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (>=2.15.0)", "deepspeed (>=0.9.3)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "libcst", "mistral-common[opencv] (>=1.6.3)", "nltk (<=3.8.1)", "optuna", "parameterized (>=0.9)", "protobuf", "psutil", "pydantic (>=2)", "pytest (>=7.2.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +dev = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "av", "beautifulsoup4", "codecarbon (>=2.8.1)", "cookiecutter (==1.7.3)", "datasets (>=2.15.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "flax (>=0.4.1,<=0.7.0)", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "kernels (>=0.6.1,<=0.9)", "libcst", "librosa", "mistral-common[opencv] (>=1.6.3)", "nltk (<=3.8.1)", "num2words", "onnxconverter-common", "optax (>=0.0.8,<=0.1.4)", "optuna", "pandas (<2.3.0)", "parameterized (>=0.9)", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic (>=2)", "pytest (>=7.2.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "scipy (<1.13.0)", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "timm (!=1.0.18,<=1.0.19)", "tokenizers (>=0.21,<0.22)", "torch (>=2.1)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] +dev-tensorflow = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (>=2.15.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "kenlm", "keras-nlp (>=0.3.1,<0.14.0)", "libcst", "librosa", "mistral-common[opencv] (>=1.6.3)", "nltk (<=3.8.1)", "onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "pandas (<2.3.0)", "parameterized (>=0.9)", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic (>=2)", "pytest (>=7.2.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx", "timeout-decorator", "tokenizers (>=0.21,<0.22)", "urllib3 (<2.0.0)"] +dev-torch = ["GitPython (<3.1.19)", "Pillow (>=10.0.1,<=15.0)", "accelerate (>=0.26.0)", "beautifulsoup4", "codecarbon (>=2.8.1)", "cookiecutter (==1.7.3)", "datasets (>=2.15.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "kenlm", "kernels (>=0.6.1,<=0.9)", "libcst", "librosa", "mistral-common[opencv] (>=1.6.3)", "nltk (<=3.8.1)", "num2words", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "optuna", "pandas (<2.3.0)", "parameterized (>=0.9)", "phonemizer", "protobuf", "psutil", "pyctcdecode (>=0.4.0)", "pydantic (>=2)", "pytest (>=7.2.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "ray[tune] (>=2.7.0)", "rhoknp (>=1.1.0,<1.3.1)", "rich", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "scikit-learn", "sentencepiece (>=0.1.91,!=0.1.92)", "sigopt", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "tensorboard", "timeout-decorator", "timm (!=1.0.18,<=1.0.19)", "tokenizers (>=0.21,<0.22)", "torch (>=2.1)", "torchaudio", "torchvision", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)", "urllib3 (<2.0.0)"] flax = ["flax (>=0.4.1,<=0.7.0)", "jax (>=0.4.1,<=0.4.13)", "jaxlib (>=0.4.1,<=0.4.13)", "optax (>=0.0.8,<=0.1.4)", "scipy (<1.13.0)"] -flax-speech = ["librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +flax-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] ftfy = ["ftfy"] hf-xet = ["hf-xet"] -hub-kernels = ["kernels (>=0.3.2,<0.4)"] -integrations = ["kernels (>=0.3.2,<0.4)", "optuna", "ray[tune] (>=2.7.0)", "sigopt"] +hub-kernels = ["kernels (>=0.6.1,<=0.9)"] +integrations = ["kernels (>=0.6.1,<=0.9)", "optuna", "ray[tune] (>=2.7.0)", "sigopt"] ja = ["fugashi (>=1.0)", "ipadic (>=1.0.0,<2.0)", "rhoknp (>=1.1.0,<1.3.1)", "sudachidict-core (>=20220729)", "sudachipy (>=0.6.6)", "unidic (>=1.0.2)", "unidic-lite (>=1.0.7)"] +mistral-common = ["mistral-common[opencv] (>=1.6.3)"] modelcreation = ["cookiecutter (==1.7.3)"] natten = ["natten (>=0.14.6,<0.15.0)"] num2words = ["num2words"] onnx = ["onnxconverter-common", "onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)", "tf2onnx"] onnxruntime = ["onnxruntime (>=1.4.0)", "onnxruntime-tools (>=1.4.2)"] +open-telemetry = ["opentelemetry-api", "opentelemetry-exporter-otlp", "opentelemetry-sdk"] optuna = ["optuna"] -quality = ["GitPython (<3.1.19)", "datasets (!=2.5.0)", "isort (>=5.5.4)", "libcst", "rich", "ruff (==0.11.2)", "urllib3 (<2.0.0)"] +quality = ["GitPython (<3.1.19)", "datasets (>=2.15.0)", "libcst", "pandas (<2.3.0)", "rich", "ruff (==0.11.2)", "urllib3 (<2.0.0)"] ray = ["ray[tune] (>=2.7.0)"] -retrieval = ["datasets (!=2.5.0)", "faiss-cpu"] +retrieval = ["datasets (>=2.15.0)", "faiss-cpu"] ruff = ["ruff (==0.11.2)"] sagemaker = ["sagemaker (>=2.31.0)"] sentencepiece = ["protobuf", "sentencepiece (>=0.1.91,!=0.1.92)"] -serving = ["fastapi", "pydantic", "starlette", "uvicorn"] +serving = ["accelerate (>=0.26.0)", "fastapi", "openai (>=1.98.0)", "pydantic (>=2)", "starlette", "torch (>=2.1)", "uvicorn"] sigopt = ["sigopt"] sklearn = ["scikit-learn"] -speech = ["librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] -testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (!=2.5.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "nltk (<=3.8.1)", "parameterized", "psutil", "pydantic", "pytest (>=7.2.0,<8.0.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] +speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +testing = ["GitPython (<3.1.19)", "beautifulsoup4", "cookiecutter (==1.7.3)", "datasets (>=2.15.0)", "dill (<0.3.5)", "evaluate (>=0.2.0)", "faiss-cpu", "libcst", "mistral-common[opencv] (>=1.6.3)", "nltk (<=3.8.1)", "parameterized (>=0.9)", "psutil", "pydantic (>=2)", "pytest (>=7.2.0)", "pytest-asyncio", "pytest-order", "pytest-rerunfailures", "pytest-rich", "pytest-timeout", "pytest-xdist", "rjieba", "rouge-score (!=0.0.7,!=0.0.8,!=0.1,!=0.1.1)", "ruff (==0.11.2)", "sacrebleu (>=1.4.12,<2.0.0)", "sacremoses", "sentencepiece (>=0.1.91,!=0.1.92)", "tensorboard", "timeout-decorator"] tf = ["keras-nlp (>=0.3.1,<0.14.0)", "onnxconverter-common", "tensorflow (>2.9,<2.16)", "tensorflow-text (<2.16)", "tf2onnx"] tf-cpu = ["keras (>2.9,<2.16)", "keras-nlp (>=0.3.1,<0.14.0)", "onnxconverter-common", "tensorflow-cpu (>2.9,<2.16)", "tensorflow-probability (<0.24)", "tensorflow-text (<2.16)", "tf2onnx"] -tf-speech = ["librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] +tf-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)"] tiktoken = ["blobfile", "tiktoken"] -timm = ["timm (<=1.0.11)"] +timm = ["timm (!=1.0.18,<=1.0.19)"] tokenizers = ["tokenizers (>=0.21,<0.22)"] -torch = ["accelerate (>=0.26.0)", "torch (>=2.0)"] -torch-speech = ["librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] +torch = ["accelerate (>=0.26.0)", "torch (>=2.1)"] +torch-speech = ["kenlm", "librosa", "phonemizer", "pyctcdecode (>=0.4.0)", "torchaudio"] torch-vision = ["Pillow (>=10.0.1,<=15.0)", "torchvision"] -torchhub = ["filelock", "huggingface-hub (>=0.30.0,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.21,<0.22)", "torch (>=2.0)", "tqdm (>=4.27)"] +torchhub = ["filelock", "huggingface-hub (>=0.34.0,<1.0)", "importlib-metadata", "numpy (>=1.17)", "packaging (>=20.0)", "protobuf", "regex (!=2019.12.17)", "requests", "sentencepiece (>=0.1.91,!=0.1.92)", "tokenizers (>=0.21,<0.22)", "torch (>=2.1)", "tqdm (>=4.27)"] video = ["av"] vision = ["Pillow (>=10.0.1,<=15.0)"] [[package]] name = "triton" -version = "3.2.0" +version = "3.3.1" description = "A language and compiler for custom Deep Learning operations" optional = false python-versions = "*" +groups = ["main"] +markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\"" files = [ - {file = "triton-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3e54983cd51875855da7c68ec05c05cf8bb08df361b1d5b69e05e40b0c9bd62"}, - {file = "triton-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8009a1fb093ee8546495e96731336a33fb8856a38e45bb4ab6affd6dbc3ba220"}, - {file = "triton-3.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d9b215efc1c26fa7eefb9a157915c92d52e000d2bf83e5f69704047e63f125c"}, - {file = "triton-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5dfa23ba84541d7c0a531dfce76d8bcd19159d50a4a8b14ad01e91734a5c1b0"}, - {file = "triton-3.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30ceed0eff2c4a73b14eb63e052992f44bbdf175f3fad21e1ac8097a772de7ee"}, + {file = "triton-3.3.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b74db445b1c562844d3cfad6e9679c72e93fdfb1a90a24052b03bb5c49d1242e"}, + {file = "triton-3.3.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b31e3aa26f8cb3cc5bf4e187bf737cbacf17311e1112b781d4a059353dfd731b"}, + {file = "triton-3.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9999e83aba21e1a78c1f36f21bce621b77bcaa530277a50484a7cb4a822f6e43"}, + {file = "triton-3.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b89d846b5a4198317fec27a5d3a609ea96b6d557ff44b56c23176546023c4240"}, + {file = "triton-3.3.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3198adb9d78b77818a5388bff89fa72ff36f9da0bc689db2f0a651a67ce6a42"}, + {file = "triton-3.3.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6139aeb04a146b0b8e0fbbd89ad1e65861c57cfed881f21d62d3cb94a36bab7"}, ] +[package.dependencies] +setuptools = ">=40.8.0" + [package.extras] build = ["cmake (>=3.20)", "lit"] -tests = ["autopep8", "flake8", "isort", "llnl-hatchet", "numpy", "pytest", "scipy (>=1.7.1)"] +tests = ["autopep8", "isort", "llnl-hatchet", "numpy", "pytest", "pytest-forked", "pytest-xdist", "scipy (>=1.7.1)"] tutorials = ["matplotlib", "pandas", "tabulate"] [[package]] name = "typer" -version = "0.15.2" +version = "0.16.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." optional = false python-versions = ">=3.7" +groups = ["main"] files = [ - {file = "typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc"}, - {file = "typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5"}, + {file = "typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855"}, + {file = "typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b"}, ] [package.dependencies] @@ -6070,24 +6759,26 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "types-python-dateutil" -version = "2.9.0.20241206" +version = "2.9.0.20250708" description = "Typing stubs for python-dateutil" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["dev"] files = [ - {file = "types_python_dateutil-2.9.0.20241206-py3-none-any.whl", hash = "sha256:e248a4bc70a486d3e3ec84d0dc30eec3a5f979d6e7ee4123ae043eedbb987f53"}, - {file = "types_python_dateutil-2.9.0.20241206.tar.gz", hash = "sha256:18f493414c26ffba692a72369fea7a154c502646301ebfe3d56a04b3767284cb"}, + {file = "types_python_dateutil-2.9.0.20250708-py3-none-any.whl", hash = "sha256:4d6d0cc1cc4d24a2dc3816024e502564094497b713f7befda4d5bc7a8e3fd21f"}, + {file = "types_python_dateutil-2.9.0.20250708.tar.gz", hash = "sha256:ccdbd75dab2d6c9696c350579f34cffe2c281e4c5f27a585b2a2438dd1d5c8ab"}, ] [[package]] name = "types-requests" -version = "2.32.0.20250328" +version = "2.32.4.20250611" description = "Typing stubs for requests" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "types_requests-2.32.0.20250328-py3-none-any.whl", hash = "sha256:72ff80f84b15eb3aa7a8e2625fffb6a93f2ad5a0c20215fc1dcfa61117bcb2a2"}, - {file = "types_requests-2.32.0.20250328.tar.gz", hash = "sha256:c9e67228ea103bd811c96984fac36ed2ae8da87a36a633964a21f199d60baf32"}, + {file = "types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072"}, + {file = "types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826"}, ] [package.dependencies] @@ -6095,13 +6786,14 @@ urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.13.1" -description = "Backported and Experimental Type Hints for Python 3.8+" +version = "4.14.1" +description = "Backported and Experimental Type Hints for Python 3.9+" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.13.1-py3-none-any.whl", hash = "sha256:4b6cf02909eb5495cfbc3f6e8fd49217e6cc7944e145cdda8caa3734777f9e69"}, - {file = "typing_extensions-4.13.1.tar.gz", hash = "sha256:98795af00fb9640edec5b8e31fc647597b4691f099ad75f469a2616be1a76dff"}, + {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, + {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, ] [[package]] @@ -6110,6 +6802,7 @@ version = "0.9.0" description = "Runtime inspection utilities for typing module." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, @@ -6121,13 +6814,14 @@ typing-extensions = ">=3.7.4" [[package]] name = "typing-inspection" -version = "0.4.0" +version = "0.4.1" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"}, - {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"}, + {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"}, + {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"}, ] [package.dependencies] @@ -6139,6 +6833,7 @@ version = "2025.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" +groups = ["main"] files = [ {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, @@ -6150,6 +6845,7 @@ version = "1.3.0" description = "RFC 6570 URI Template Processor" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ {file = "uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7"}, {file = "uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363"}, @@ -6160,13 +6856,14 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake [[package]] name = "urllib3" -version = "2.3.0" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] @@ -6177,13 +6874,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvicorn" -version = "0.34.0" +version = "0.35.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "uvicorn-0.34.0-py3-none-any.whl", hash = "sha256:023dc038422502fa28a09c7a30bf2b6991512da7dcdb8fd35fe57cfc154126f4"}, - {file = "uvicorn-0.34.0.tar.gz", hash = "sha256:404051050cd7e905de2c9a7e61790943440b3416f49cb409f965d9dcd0fa73e9"}, + {file = "uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a"}, + {file = "uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01"}, ] [package.dependencies] @@ -6193,12 +6891,12 @@ h11 = ">=0.8" httptools = {version = ">=0.6.3", optional = true, markers = "extra == \"standard\""} python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} -uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} +uvloop = {version = ">=0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""} watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "uvloop" @@ -6206,6 +6904,8 @@ version = "0.21.0" description = "Fast implementation of asyncio event loop on top of libuv" optional = false python-versions = ">=3.8.0" +groups = ["main"] +markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\"" files = [ {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"}, {file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"}, @@ -6253,82 +6953,118 @@ test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", [[package]] name = "watchfiles" -version = "1.0.5" +version = "1.1.0" description = "Simple, modern and high performance file watching and code reload in python." optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "watchfiles-1.0.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5c40fe7dd9e5f81e0847b1ea64e1f5dd79dd61afbedb57759df06767ac719b40"}, - {file = "watchfiles-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c0db396e6003d99bb2d7232c957b5f0b5634bbd1b24e381a5afcc880f7373fb"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b551d4fb482fc57d852b4541f911ba28957d051c8776e79c3b4a51eb5e2a1b11"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:830aa432ba5c491d52a15b51526c29e4a4b92bf4f92253787f9726fe01519487"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a16512051a822a416b0d477d5f8c0e67b67c1a20d9acecb0aafa3aa4d6e7d256"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe0cbc787770e52a96c6fda6726ace75be7f840cb327e1b08d7d54eadc3bc85"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d363152c5e16b29d66cbde8fa614f9e313e6f94a8204eaab268db52231fe5358"}, - {file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee32c9a9bee4d0b7bd7cbeb53cb185cf0b622ac761efaa2eba84006c3b3a614"}, - {file = "watchfiles-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29c7fd632ccaf5517c16a5188e36f6612d6472ccf55382db6c7fe3fcccb7f59f"}, - {file = "watchfiles-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e637810586e6fe380c8bc1b3910accd7f1d3a9a7262c8a78d4c8fb3ba6a2b3d"}, - {file = "watchfiles-1.0.5-cp310-cp310-win32.whl", hash = "sha256:cd47d063fbeabd4c6cae1d4bcaa38f0902f8dc5ed168072874ea11d0c7afc1ff"}, - {file = "watchfiles-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:86c0df05b47a79d80351cd179893f2f9c1b1cae49d96e8b3290c7f4bd0ca0a92"}, - {file = "watchfiles-1.0.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:237f9be419e977a0f8f6b2e7b0475ababe78ff1ab06822df95d914a945eac827"}, - {file = "watchfiles-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0da39ff917af8b27a4bdc5a97ac577552a38aac0d260a859c1517ea3dc1a7c4"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfcb3952350e95603f232a7a15f6c5f86c5375e46f0bd4ae70d43e3e063c13d"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b2dddba7a4e6151384e252a5632efcaa9bc5d1c4b567f3cb621306b2ca9f63"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cf944fcfc394c5f9de794ce581914900f82ff1f855326f25ebcf24d5397418"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf6cd9f83d7c023b1aba15d13f705ca7b7d38675c121f3cc4a6e25bd0857ee9"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:852de68acd6212cd6d33edf21e6f9e56e5d98c6add46f48244bd479d97c967c6"}, - {file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5730f3aa35e646103b53389d5bc77edfbf578ab6dab2e005142b5b80a35ef25"}, - {file = "watchfiles-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:18b3bd29954bc4abeeb4e9d9cf0b30227f0f206c86657674f544cb032296acd5"}, - {file = "watchfiles-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba5552a1b07c8edbf197055bc9d518b8f0d98a1c6a73a293bc0726dce068ed01"}, - {file = "watchfiles-1.0.5-cp311-cp311-win32.whl", hash = "sha256:2f1fefb2e90e89959447bc0420fddd1e76f625784340d64a2f7d5983ef9ad246"}, - {file = "watchfiles-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b6e76ceb1dd18c8e29c73f47d41866972e891fc4cc7ba014f487def72c1cf096"}, - {file = "watchfiles-1.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:266710eb6fddc1f5e51843c70e3bebfb0f5e77cf4f27129278c70554104d19ed"}, - {file = "watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2"}, - {file = "watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6"}, - {file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234"}, - {file = "watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2"}, - {file = "watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663"}, - {file = "watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249"}, - {file = "watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705"}, - {file = "watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417"}, - {file = "watchfiles-1.0.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0b289572c33a0deae62daa57e44a25b99b783e5f7aed81b314232b3d3c81a11d"}, - {file = "watchfiles-1.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a056c2f692d65bf1e99c41045e3bdcaea3cb9e6b5a53dcaf60a5f3bd95fc9763"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9dca99744991fc9850d18015c4f0438865414e50069670f5f7eee08340d8b40"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:894342d61d355446d02cd3988a7326af344143eb33a2fd5d38482a92072d9563"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab44e1580924d1ffd7b3938e02716d5ad190441965138b4aa1d1f31ea0877f04"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6f9367b132078b2ceb8d066ff6c93a970a18c3029cea37bfd7b2d3dd2e5db8f"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2e55a9b162e06e3f862fb61e399fe9f05d908d019d87bf5b496a04ef18a970a"}, - {file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0125f91f70e0732a9f8ee01e49515c35d38ba48db507a50c5bdcad9503af5827"}, - {file = "watchfiles-1.0.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:13bb21f8ba3248386337c9fa51c528868e6c34a707f729ab041c846d52a0c69a"}, - {file = "watchfiles-1.0.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:839ebd0df4a18c5b3c1b890145b5a3f5f64063c2a0d02b13c76d78fe5de34936"}, - {file = "watchfiles-1.0.5-cp313-cp313-win32.whl", hash = "sha256:4a8ec1e4e16e2d5bafc9ba82f7aaecfeec990ca7cd27e84fb6f191804ed2fcfc"}, - {file = "watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11"}, - {file = "watchfiles-1.0.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2cfb371be97d4db374cba381b9f911dd35bb5f4c58faa7b8b7106c8853e5d225"}, - {file = "watchfiles-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a3904d88955fda461ea2531fcf6ef73584ca921415d5cfa44457a225f4a42bc1"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b7a21715fb12274a71d335cff6c71fe7f676b293d322722fe708a9ec81d91f5"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dfd6ae1c385ab481766b3c61c44aca2b3cd775f6f7c0fa93d979ddec853d29d5"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b659576b950865fdad31fa491d31d37cf78b27113a7671d39f919828587b429b"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1909e0a9cd95251b15bff4261de5dd7550885bd172e3536824bf1cf6b121e200"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:832ccc221927c860e7286c55c9b6ebcc0265d5e072f49c7f6456c7798d2b39aa"}, - {file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85fbb6102b3296926d0c62cfc9347f6237fb9400aecd0ba6bbda94cae15f2b3b"}, - {file = "watchfiles-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:15ac96dd567ad6c71c71f7b2c658cb22b7734901546cd50a475128ab557593ca"}, - {file = "watchfiles-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b6227351e11c57ae997d222e13f5b6f1f0700d84b8c52304e8675d33a808382"}, - {file = "watchfiles-1.0.5-cp39-cp39-win32.whl", hash = "sha256:974866e0db748ebf1eccab17862bc0f0303807ed9cda465d1324625b81293a18"}, - {file = "watchfiles-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:9848b21ae152fe79c10dd0197304ada8f7b586d3ebc3f27f43c506e5a52a863c"}, - {file = "watchfiles-1.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f59b870db1f1ae5a9ac28245707d955c8721dd6565e7f411024fa374b5362d1d"}, - {file = "watchfiles-1.0.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9475b0093767e1475095f2aeb1d219fb9664081d403d1dff81342df8cd707034"}, - {file = "watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc533aa50664ebd6c628b2f30591956519462f5d27f951ed03d6c82b2dfd9965"}, - {file = "watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed1cd825158dcaae36acce7b2db33dcbfd12b30c34317a88b8ed80f0541cc57"}, - {file = "watchfiles-1.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:554389562c29c2c182e3908b149095051f81d28c2fec79ad6c8997d7d63e0009"}, - {file = "watchfiles-1.0.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a74add8d7727e6404d5dc4dcd7fac65d4d82f95928bbee0cf5414c900e86773e"}, - {file = "watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb1489f25b051a89fae574505cc26360c8e95e227a9500182a7fe0afcc500ce0"}, - {file = "watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0901429650652d3f0da90bad42bdafc1f9143ff3605633c455c999a2d786cac"}, - {file = "watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9"}, + {file = "watchfiles-1.1.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:27f30e14aa1c1e91cb653f03a63445739919aef84c8d2517997a83155e7a2fcc"}, + {file = "watchfiles-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3366f56c272232860ab45c77c3ca7b74ee819c8e1f6f35a7125556b198bbc6df"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8412eacef34cae2836d891836a7fff7b754d6bcac61f6c12ba5ca9bc7e427b68"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df670918eb7dd719642e05979fc84704af913d563fd17ed636f7c4783003fdcc"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7642b9bc4827b5518ebdb3b82698ada8c14c7661ddec5fe719f3e56ccd13c97"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:199207b2d3eeaeb80ef4411875a6243d9ad8bc35b07fc42daa6b801cc39cc41c"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a479466da6db5c1e8754caee6c262cd373e6e6c363172d74394f4bff3d84d7b5"}, + {file = "watchfiles-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:935f9edd022ec13e447e5723a7d14456c8af254544cefbc533f6dd276c9aa0d9"}, + {file = "watchfiles-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8076a5769d6bdf5f673a19d51da05fc79e2bbf25e9fe755c47595785c06a8c72"}, + {file = "watchfiles-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86b1e28d4c37e89220e924305cd9f82866bb0ace666943a6e4196c5df4d58dcc"}, + {file = "watchfiles-1.1.0-cp310-cp310-win32.whl", hash = "sha256:d1caf40c1c657b27858f9774d5c0e232089bca9cb8ee17ce7478c6e9264d2587"}, + {file = "watchfiles-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:a89c75a5b9bc329131115a409d0acc16e8da8dfd5867ba59f1dd66ae7ea8fa82"}, + {file = "watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2"}, + {file = "watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8"}, + {file = "watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f"}, + {file = "watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4"}, + {file = "watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d"}, + {file = "watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2"}, + {file = "watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12"}, + {file = "watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a"}, + {file = "watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179"}, + {file = "watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd"}, + {file = "watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f"}, + {file = "watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4"}, + {file = "watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f"}, + {file = "watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd"}, + {file = "watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47"}, + {file = "watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6"}, + {file = "watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30"}, + {file = "watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b"}, + {file = "watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c"}, + {file = "watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b"}, + {file = "watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb"}, + {file = "watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9"}, + {file = "watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7"}, + {file = "watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5"}, + {file = "watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1"}, + {file = "watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4"}, + {file = "watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20"}, + {file = "watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef"}, + {file = "watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb"}, + {file = "watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297"}, + {file = "watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92"}, + {file = "watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e"}, + {file = "watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b"}, + {file = "watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259"}, + {file = "watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f"}, + {file = "watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb"}, + {file = "watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147"}, + {file = "watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8"}, + {file = "watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db"}, + {file = "watchfiles-1.1.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:865c8e95713744cf5ae261f3067861e9da5f1370ba91fc536431e29b418676fa"}, + {file = "watchfiles-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:42f92befc848bb7a19658f21f3e7bae80d7d005d13891c62c2cd4d4d0abb3433"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa0cc8365ab29487eb4f9979fd41b22549853389e22d5de3f134a6796e1b05a4"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:90ebb429e933645f3da534c89b29b665e285048973b4d2b6946526888c3eb2c7"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c588c45da9b08ab3da81d08d7987dae6d2a3badd63acdb3e206a42dbfa7cb76f"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7c55b0f9f68590115c25272b06e63f0824f03d4fc7d6deed43d8ad5660cabdbf"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd17a1e489f02ce9117b0de3c0b1fab1c3e2eedc82311b299ee6b6faf6c23a29"}, + {file = "watchfiles-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da71945c9ace018d8634822f16cbc2a78323ef6c876b1d34bbf5d5222fd6a72e"}, + {file = "watchfiles-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:51556d5004887045dba3acdd1fdf61dddea2be0a7e18048b5e853dcd37149b86"}, + {file = "watchfiles-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04e4ed5d1cd3eae68c89bcc1a485a109f39f2fd8de05f705e98af6b5f1861f1f"}, + {file = "watchfiles-1.1.0-cp39-cp39-win32.whl", hash = "sha256:c600e85f2ffd9f1035222b1a312aff85fd11ea39baff1d705b9b047aad2ce267"}, + {file = "watchfiles-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3aba215958d88182e8d2acba0fdaf687745180974946609119953c0e112397dc"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a6fd40bbb50d24976eb275ccb55cd1951dfb63dbc27cae3066a6ca5f4beabd5"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9f811079d2f9795b5d48b55a37aa7773680a5659afe34b54cc1d86590a51507d"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2726d7bfd9f76158c84c10a409b77a320426540df8c35be172444394b17f7ea"}, + {file = "watchfiles-1.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df32d59cb9780f66d165a9a7a26f19df2c7d24e3bd58713108b41d0ff4f929c6"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432"}, + {file = "watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7b3443f4ec3ba5aa00b0e9fa90cf31d98321cbff8b925a7c7b84161619870bc9"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:7049e52167fc75fc3cc418fc13d39a8e520cbb60ca08b47f6cedb85e181d2f2a"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54062ef956807ba806559b3c3d52105ae1827a0d4ab47b621b31132b6b7e2866"}, + {file = "watchfiles-1.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a7bd57a1bb02f9d5c398c0c1675384e7ab1dd39da0ca50b7f09af45fa435277"}, + {file = "watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575"}, ] [package.dependencies] @@ -6340,6 +7076,7 @@ version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, @@ -6351,6 +7088,7 @@ version = "24.11.1" description = "A library for working with the color formats defined by HTML and CSS." optional = false python-versions = ">=3.9" +groups = ["dev"] files = [ {file = "webcolors-24.11.1-py3-none-any.whl", hash = "sha256:515291393b4cdf0eb19c155749a096f779f7d909f7cceea072791cb9095b92e9"}, {file = "webcolors-24.11.1.tar.gz", hash = "sha256:ecb3d768f32202af770477b8b65f318fa4f566c22948673a977b00d589dd80f6"}, @@ -6362,6 +7100,7 @@ version = "0.5.1" description = "Character encoding aliases for legacy web content" optional = false python-versions = "*" +groups = ["dev"] files = [ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, @@ -6373,6 +7112,7 @@ version = "1.8.0" description = "WebSocket client for Python with low level API options" optional = false python-versions = ">=3.8" +groups = ["main", "dev"] files = [ {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, @@ -6389,6 +7129,7 @@ version = "15.0.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"}, {file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"}, @@ -6463,13 +7204,14 @@ files = [ [[package]] name = "widgetsnbextension" -version = "4.0.13" +version = "4.0.14" description = "Jupyter interactive widgets for Jupyter Notebook" optional = false python-versions = ">=3.7" +groups = ["dev"] files = [ - {file = "widgetsnbextension-4.0.13-py3-none-any.whl", hash = "sha256:74b2692e8500525cc38c2b877236ba51d34541e6385eeed5aec15a70f88a6c71"}, - {file = "widgetsnbextension-4.0.13.tar.gz", hash = "sha256:ffcb67bc9febd10234a362795f643927f4e0c05d9342c727b65d2384f8feacb6"}, + {file = "widgetsnbextension-4.0.14-py3-none-any.whl", hash = "sha256:4875a9eaf72fbf5079dc372a51a9f268fc38d46f767cbf85c43a36da5cb9b575"}, + {file = "widgetsnbextension-4.0.14.tar.gz", hash = "sha256:a3629b04e3edb893212df862038c7232f62973373869db5084aed739b437b5af"}, ] [[package]] @@ -6478,192 +7220,123 @@ version = "1.1" description = "Convert number words eg. three hundred and forty two to numbers (342)." optional = false python-versions = "*" +groups = ["main"] files = [ {file = "word2number-1.1.zip", hash = "sha256:70e27a5d387f67b04c71fbb7621c05930b19bfd26efd6851e6e0f9969dcde7d0"}, ] -[[package]] -name = "wrapt" -version = "1.17.2" -description = "Module for decorators, wrappers and monkey patching." -optional = false -python-versions = ">=3.8" -files = [ - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, - {file = "wrapt-1.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:80dd7db6a7cb57ffbc279c4394246414ec99537ae81ffd702443335a61dbf3a7"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a6e821770cf99cc586d33833b2ff32faebdbe886bd6322395606cf55153246c"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b60fb58b90c6d63779cb0c0c54eeb38941bae3ecf7a73c764c52c88c2dcb9d72"}, - {file = "wrapt-1.17.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b870b5df5b71d8c3359d21be8f0d6c485fa0ebdb6477dda51a1ea54a9b558061"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4011d137b9955791f9084749cba9a367c68d50ab8d11d64c50ba1688c9b457f2"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1473400e5b2733e58b396a04eb7f35f541e1fb976d0c0724d0223dd607e0f74c"}, - {file = "wrapt-1.17.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3cedbfa9c940fdad3e6e941db7138e26ce8aad38ab5fe9dcfadfed9db7a54e62"}, - {file = "wrapt-1.17.2-cp310-cp310-win32.whl", hash = "sha256:582530701bff1dec6779efa00c516496968edd851fba224fbd86e46cc6b73563"}, - {file = "wrapt-1.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:58705da316756681ad3c9c73fd15499aa4d8c69f9fd38dc8a35e06c12468582f"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ff04ef6eec3eee8a5efef2401495967a916feaa353643defcc03fc74fe213b58"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db983e7bca53819efdbd64590ee96c9213894272c776966ca6306b73e4affda"}, - {file = "wrapt-1.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9abc77a4ce4c6f2a3168ff34b1da9b0f311a8f1cfd694ec96b0603dff1c79438"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b929ac182f5ace000d459c59c2c9c33047e20e935f8e39371fa6e3b85d56f4a"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f09b286faeff3c750a879d336fb6d8713206fc97af3adc14def0cdd349df6000"}, - {file = "wrapt-1.17.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7ed2d9d039bd41e889f6fb9364554052ca21ce823580f6a07c4ec245c1f5d6"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:129a150f5c445165ff941fc02ee27df65940fcb8a22a61828b1853c98763a64b"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1fb5699e4464afe5c7e65fa51d4f99e0b2eadcc176e4aa33600a3df7801d6662"}, - {file = "wrapt-1.17.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9a2bce789a5ea90e51a02dfcc39e31b7f1e662bc3317979aa7e5538e3a034f72"}, - {file = "wrapt-1.17.2-cp311-cp311-win32.whl", hash = "sha256:4afd5814270fdf6380616b321fd31435a462019d834f83c8611a0ce7484c7317"}, - {file = "wrapt-1.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:acc130bc0375999da18e3d19e5a86403667ac0c4042a094fefb7eec8ebac7cf3"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392"}, - {file = "wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b"}, - {file = "wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae"}, - {file = "wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9"}, - {file = "wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9"}, - {file = "wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6ed6ffac43aecfe6d86ec5b74b06a5be33d5bb9243d055141e8cabb12aa08125"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:35621ae4c00e056adb0009f8e86e28eb4a41a4bfa8f9bfa9fca7d343fe94f998"}, - {file = "wrapt-1.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a604bf7a053f8362d27eb9fefd2097f82600b856d5abe996d623babd067b1ab5"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cbabee4f083b6b4cd282f5b817a867cf0b1028c54d445b7ec7cfe6505057cf8"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49703ce2ddc220df165bd2962f8e03b84c89fee2d65e1c24a7defff6f988f4d6"}, - {file = "wrapt-1.17.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8112e52c5822fc4253f3901b676c55ddf288614dc7011634e2719718eaa187dc"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9fee687dce376205d9a494e9c121e27183b2a3df18037f89d69bd7b35bcf59e2"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:18983c537e04d11cf027fbb60a1e8dfd5190e2b60cc27bc0808e653e7b218d1b"}, - {file = "wrapt-1.17.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:703919b1633412ab54bcf920ab388735832fdcb9f9a00ae49387f0fe67dad504"}, - {file = "wrapt-1.17.2-cp313-cp313-win32.whl", hash = "sha256:abbb9e76177c35d4e8568e58650aa6926040d6a9f6f03435b7a522bf1c487f9a"}, - {file = "wrapt-1.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:69606d7bb691b50a4240ce6b22ebb319c1cfb164e5f6569835058196e0f3a845"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:4a721d3c943dae44f8e243b380cb645a709ba5bd35d3ad27bc2ed947e9c68192"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:766d8bbefcb9e00c3ac3b000d9acc51f1b399513f44d77dfe0eb026ad7c9a19b"}, - {file = "wrapt-1.17.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e496a8ce2c256da1eb98bd15803a79bee00fc351f5dfb9ea82594a3f058309e0"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d615e4fe22f4ad3528448c193b218e077656ca9ccb22ce2cb20db730f8d306"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a5aaeff38654462bc4b09023918b7f21790efb807f54c000a39d41d69cf552cb"}, - {file = "wrapt-1.17.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a7d15bbd2bc99e92e39f49a04653062ee6085c0e18b3b7512a4f2fe91f2d681"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:e3890b508a23299083e065f435a492b5435eba6e304a7114d2f919d400888cc6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:8c8b293cd65ad716d13d8dd3624e42e5a19cc2a2f1acc74b30c2c13f15cb61a6"}, - {file = "wrapt-1.17.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c82b8785d98cdd9fed4cac84d765d234ed3251bd6afe34cb7ac523cb93e8b4f"}, - {file = "wrapt-1.17.2-cp313-cp313t-win32.whl", hash = "sha256:13e6afb7fe71fe7485a4550a8844cc9ffbe263c0f1a1eea569bc7091d4898555"}, - {file = "wrapt-1.17.2-cp313-cp313t-win_amd64.whl", hash = "sha256:eaf675418ed6b3b31c7a989fd007fa7c3be66ce14e5c3b27336383604c9da85c"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5c803c401ea1c1c18de70a06a6f79fcc9c5acfc79133e9869e730ad7f8ad8ef9"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f917c1180fdb8623c2b75a99192f4025e412597c50b2ac870f156de8fb101119"}, - {file = "wrapt-1.17.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ecc840861360ba9d176d413a5489b9a0aff6d6303d7e733e2c4623cfa26904a6"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb87745b2e6dc56361bfde481d5a378dc314b252a98d7dd19a651a3fa58f24a9"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58455b79ec2661c3600e65c0a716955adc2410f7383755d537584b0de41b1d8a"}, - {file = "wrapt-1.17.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4e42a40a5e164cbfdb7b386c966a588b1047558a990981ace551ed7e12ca9c2"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:91bd7d1773e64019f9288b7a5101f3ae50d3d8e6b1de7edee9c2ccc1d32f0c0a"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bb90fb8bda722a1b9d48ac1e6c38f923ea757b3baf8ebd0c82e09c5c1a0e7a04"}, - {file = "wrapt-1.17.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:08e7ce672e35efa54c5024936e559469436f8b8096253404faeb54d2a878416f"}, - {file = "wrapt-1.17.2-cp38-cp38-win32.whl", hash = "sha256:410a92fefd2e0e10d26210e1dfb4a876ddaf8439ef60d6434f21ef8d87efc5b7"}, - {file = "wrapt-1.17.2-cp38-cp38-win_amd64.whl", hash = "sha256:95c658736ec15602da0ed73f312d410117723914a5c91a14ee4cdd72f1d790b3"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99039fa9e6306880572915728d7f6c24a86ec57b0a83f6b2491e1d8ab0235b9a"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2696993ee1eebd20b8e4ee4356483c4cb696066ddc24bd70bcbb80fa56ff9061"}, - {file = "wrapt-1.17.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:612dff5db80beef9e649c6d803a8d50c409082f1fedc9dbcdfde2983b2025b82"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62c2caa1585c82b3f7a7ab56afef7b3602021d6da34fbc1cf234ff139fed3cd9"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c958bcfd59bacc2d0249dcfe575e71da54f9dcf4a8bdf89c4cb9a68a1170d73f"}, - {file = "wrapt-1.17.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc78a84e2dfbc27afe4b2bd7c80c8db9bca75cc5b85df52bfe634596a1da846b"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba0f0eb61ef00ea10e00eb53a9129501f52385c44853dbd6c4ad3f403603083f"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1e1fe0e6ab7775fd842bc39e86f6dcfc4507ab0ffe206093e76d61cde37225c8"}, - {file = "wrapt-1.17.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c86563182421896d73858e08e1db93afdd2b947a70064b813d515d66549e15f9"}, - {file = "wrapt-1.17.2-cp39-cp39-win32.whl", hash = "sha256:f393cda562f79828f38a819f4788641ac7c4085f30f1ce1a68672baa686482bb"}, - {file = "wrapt-1.17.2-cp39-cp39-win_amd64.whl", hash = "sha256:36ccae62f64235cf8ddb682073a60519426fdd4725524ae38874adf72b5f2aeb"}, - {file = "wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8"}, - {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, -] - [[package]] name = "yarl" -version = "1.19.0" +version = "1.20.1" description = "Yet another URL library" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "yarl-1.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0bae32f8ebd35c04d6528cedb4a26b8bf25339d3616b04613b97347f919b76d3"}, - {file = "yarl-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8015a076daf77823e7ebdcba474156587391dab4e70c732822960368c01251e6"}, - {file = "yarl-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9973ac95327f5d699eb620286c39365990b240031672b5c436a4cd00539596c5"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd4b5fbd7b9dde785cfeb486b8cca211a0b138d4f3a7da27db89a25b3c482e5c"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75460740005de5a912b19f657848aef419387426a40f581b1dc9fac0eb9addb5"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57abd66ca913f2cfbb51eb3dbbbac3648f1f6983f614a4446e0802e241441d2a"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46ade37911b7c99ce28a959147cb28bffbd14cea9e7dd91021e06a8d2359a5aa"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8346ec72ada749a6b5d82bff7be72578eab056ad7ec38c04f668a685abde6af0"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e4cb14a6ee5b6649ccf1c6d648b4da9220e8277d4d4380593c03cc08d8fe937"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:66fc1c2926a73a2fb46e4b92e3a6c03904d9bc3a0b65e01cb7d2b84146a8bd3b"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:5a70201dd1e0a4304849b6445a9891d7210604c27e67da59091d5412bc19e51c"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4807aab1bdeab6ae6f296be46337a260ae4b1f3a8c2fcd373e236b4b2b46efd"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ae584afe81a1de4c1bb06672481050f0d001cad13163e3c019477409f638f9b7"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30eaf4459df6e91f21b2999d1ee18f891bcd51e3cbe1de301b4858c84385895b"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0e617d45d03c8dec0dfce6f51f3e1b8a31aa81aaf4a4d1442fdb232bcf0c6d8c"}, - {file = "yarl-1.19.0-cp310-cp310-win32.whl", hash = "sha256:32ba32d0fa23893fd8ea8d05bdb05de6eb19d7f2106787024fd969f4ba5466cb"}, - {file = "yarl-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:545575ecfcd465891b51546c2bcafdde0acd2c62c2097d8d71902050b20e4922"}, - {file = "yarl-1.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:163ff326680de5f6d4966954cf9e3fe1bf980f5fee2255e46e89b8cf0f3418b5"}, - {file = "yarl-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a626c4d9cca298d1be8625cff4b17004a9066330ac82d132bbda64a4c17c18d3"}, - {file = "yarl-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:961c3e401ea7f13d02b8bb7cb0c709152a632a6e14cdc8119e9c6ee5596cd45d"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a39d7b807ab58e633ed760f80195cbd145b58ba265436af35f9080f1810dfe64"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4228978fb59c6b10f60124ba8e311c26151e176df364e996f3f8ff8b93971b5"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ba536b17ecf3c74a94239ec1137a3ad3caea8c0e4deb8c8d2ffe847d870a8c5"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a251e00e445d2e9df7b827c9843c0b87f58a3254aaa3f162fb610747491fe00f"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9b92431d8b4d4ca5ccbfdbac95b05a3a6cd70cd73aa62f32f9627acfde7549c"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec2f56edaf476f70b5831bbd59700b53d9dd011b1f77cd4846b5ab5c5eafdb3f"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:acf9b92c4245ac8b59bc7ec66a38d3dcb8d1f97fac934672529562bb824ecadb"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:57711f1465c06fee8825b95c0b83e82991e6d9425f9a042c3c19070a70ac92bf"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:528e86f5b1de0ad8dd758ddef4e0ed24f5d946d4a1cef80ffb2d4fca4e10f122"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3b77173663e075d9e5a57e09d711e9da2f3266be729ecca0b8ae78190990d260"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d8717924cf0a825b62b1a96fc7d28aab7f55a81bf5338b8ef41d7a76ab9223e9"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0df9f0221a78d858793f40cbea3915c29f969c11366646a92ca47e080a14f881"}, - {file = "yarl-1.19.0-cp311-cp311-win32.whl", hash = "sha256:8b3ade62678ee2c7c10dcd6be19045135e9badad53108f7d2ed14896ee396045"}, - {file = "yarl-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:0626ee31edb23ac36bdffe607231de2cca055ad3a5e2dc5da587ef8bc6a321bc"}, - {file = "yarl-1.19.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b687c334da3ff8eab848c9620c47a253d005e78335e9ce0d6868ed7e8fd170b"}, - {file = "yarl-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b0fe766febcf523a2930b819c87bb92407ae1368662c1bc267234e79b20ff894"}, - {file = "yarl-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:742ceffd3c7beeb2b20d47cdb92c513eef83c9ef88c46829f88d5b06be6734ee"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2af682a1e97437382ee0791eacbf540318bd487a942e068e7e0a6c571fadbbd3"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:63702f1a098d0eaaea755e9c9d63172be1acb9e2d4aeb28b187092bcc9ca2d17"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3560dcba3c71ae7382975dc1e912ee76e50b4cd7c34b454ed620d55464f11876"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68972df6a0cc47c8abaf77525a76ee5c5f6ea9bbdb79b9565b3234ded3c5e675"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5684e7ff93ea74e47542232bd132f608df4d449f8968fde6b05aaf9e08a140f9"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8182ad422bfacdebd4759ce3adc6055c0c79d4740aea1104e05652a81cd868c6"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aee5b90a5a9b71ac57400a7bdd0feaa27c51e8f961decc8d412e720a004a1791"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8c0b2371858d5a814b08542d5d548adb03ff2d7ab32f23160e54e92250961a72"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cd430c2b7df4ae92498da09e9b12cad5bdbb140d22d138f9e507de1aa3edfea3"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a93208282c0ccdf73065fd76c6c129bd428dba5ff65d338ae7d2ab27169861a0"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:b8179280cdeb4c36eb18d6534a328f9d40da60d2b96ac4a295c5f93e2799e9d9"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eda3c2b42dc0c389b7cfda2c4df81c12eeb552019e0de28bde8f913fc3d1fcf3"}, - {file = "yarl-1.19.0-cp312-cp312-win32.whl", hash = "sha256:57f3fed859af367b9ca316ecc05ce79ce327d6466342734305aa5cc380e4d8be"}, - {file = "yarl-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:5507c1f7dd3d41251b67eecba331c8b2157cfd324849879bebf74676ce76aff7"}, - {file = "yarl-1.19.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:59281b9ed27bc410e0793833bcbe7fc149739d56ffa071d1e0fe70536a4f7b61"}, - {file = "yarl-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d27a6482ad5e05e8bafd47bf42866f8a1c0c3345abcb48d4511b3c29ecc197dc"}, - {file = "yarl-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7a8e19fd5a6fdf19a91f2409665c7a089ffe7b9b5394ab33c0eec04cbecdd01f"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cda34ab19099c3a1685ad48fe45172536610c312b993310b5f1ca3eb83453b36"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7908a25d33f94852b479910f9cae6cdb9e2a509894e8d5f416c8342c0253c397"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e66c14d162bac94973e767b24de5d7e6c5153f7305a64ff4fcba701210bcd638"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c03607bf932aa4cfae371e2dc9ca8b76faf031f106dac6a6ff1458418140c165"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9931343d1c1f4e77421687b6b94bbebd8a15a64ab8279adf6fbb047eff47e536"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:262087a8a0d73e1d169d45c2baf968126f93c97cf403e1af23a7d5455d52721f"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70f384921c24e703d249a6ccdabeb57dd6312b568b504c69e428a8dd3e8e68ca"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:756b9ea5292a2c180d1fe782a377bc4159b3cfefaca7e41b5b0a00328ef62fa9"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cbeb9c145d534c240a63b6ecc8a8dd451faeb67b3dc61d729ec197bb93e29497"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:087ae8f8319848c18e0d114d0f56131a9c017f29200ab1413b0137ad7c83e2ae"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362f5480ba527b6c26ff58cff1f229afe8b7fdd54ee5ffac2ab827c1a75fc71c"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f408d4b4315e814e5c3668094e33d885f13c7809cbe831cbdc5b1bb8c7a448f4"}, - {file = "yarl-1.19.0-cp313-cp313-win32.whl", hash = "sha256:24e4c367ad69988a2283dd45ea88172561ca24b2326b9781e164eb46eea68345"}, - {file = "yarl-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:0110f91c57ab43d1538dfa92d61c45e33b84df9257bd08fcfcda90cce931cbc9"}, - {file = "yarl-1.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85ac908cd5a97bbd3048cca9f1bf37b932ea26c3885099444f34b0bf5d5e9fa6"}, - {file = "yarl-1.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6ba0931b559f1345df48a78521c31cfe356585670e8be22af84a33a39f7b9221"}, - {file = "yarl-1.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5bc503e1c1fee1b86bcb58db67c032957a52cae39fe8ddd95441f414ffbab83e"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d995122dcaf180fd4830a9aa425abddab7c0246107c21ecca2fa085611fa7ce9"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:217f69e60a14da4eed454a030ea8283f8fbd01a7d6d81e57efb865856822489b"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad67c8f13a4b79990082f72ef09c078a77de2b39899aabf3960a48069704973"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dff065a1a8ed051d7e641369ba1ad030d5a707afac54cf4ede7069b959898835"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada882e26b16ee651ab6544ce956f2f4beaed38261238f67c2a96db748e17741"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67a56b1acc7093451ea2de0687aa3bd4e58d6b4ef6cbeeaad137b45203deaade"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e97d2f0a06b39e231e59ebab0e6eec45c7683b339e8262299ac952707bdf7688"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a5288adb7c59d0f54e4ad58d86fb06d4b26e08a59ed06d00a1aac978c0e32884"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1efbf4d03e6eddf5da27752e0b67a8e70599053436e9344d0969532baa99df53"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f228f42f29cc87db67020f7d71624102b2c837686e55317b16e1d3ef2747a993"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c515f7dd60ca724e4c62b34aeaa603188964abed2eb66bb8e220f7f104d5a187"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4815ec6d3d68a96557fa71bd36661b45ac773fb50e5cfa31a7e843edb098f060"}, - {file = "yarl-1.19.0-cp39-cp39-win32.whl", hash = "sha256:9fac2dd1c5ecb921359d9546bc23a6dcc18c6acd50c6d96f118188d68010f497"}, - {file = "yarl-1.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:5864f539ce86b935053bfa18205fa08ce38e9a40ea4d51b19ce923345f0ed5db"}, - {file = "yarl-1.19.0-py3-none-any.whl", hash = "sha256:a727101eb27f66727576630d02985d8a065d09cd0b5fcbe38a5793f71b2a97ef"}, - {file = "yarl-1.19.0.tar.gz", hash = "sha256:01e02bb80ae0dbed44273c304095295106e1d9470460e773268a27d11e594892"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6032e6da6abd41e4acda34d75a816012717000fa6839f37124a47fcefc49bec4"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2c7b34d804b8cf9b214f05015c4fee2ebe7ed05cf581e7192c06555c71f4446a"}, + {file = "yarl-1.20.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c869f2651cc77465f6cd01d938d91a11d9ea5d798738c1dc077f3de0b5e5fed"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62915e6688eb4d180d93840cda4110995ad50c459bf931b8b3775b37c264af1e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:41ebd28167bc6af8abb97fec1a399f412eec5fd61a3ccbe2305a18b84fb4ca73"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21242b4288a6d56f04ea193adde174b7e347ac46ce6bc84989ff7c1b1ecea84e"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bea21cdae6c7eb02ba02a475f37463abfe0a01f5d7200121b03e605d6a0439f8"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f8a891e4a22a89f5dde7862994485e19db246b70bb288d3ce73a34422e55b23"}, + {file = "yarl-1.20.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dd803820d44c8853a109a34e3660e5a61beae12970da479cf44aa2954019bf70"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b982fa7f74c80d5c0c7b5b38f908971e513380a10fecea528091405f519b9ebb"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:33f29ecfe0330c570d997bcf1afd304377f2e48f61447f37e846a6058a4d33b2"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:835ab2cfc74d5eb4a6a528c57f05688099da41cf4957cf08cad38647e4a83b30"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:46b5e0ccf1943a9a6e766b2c2b8c732c55b34e28be57d8daa2b3c1d1d4009309"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:df47c55f7d74127d1b11251fe6397d84afdde0d53b90bedb46a23c0e534f9d24"}, + {file = "yarl-1.20.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76d12524d05841276b0e22573f28d5fbcb67589836772ae9244d90dd7d66aa13"}, + {file = "yarl-1.20.1-cp310-cp310-win32.whl", hash = "sha256:6c4fbf6b02d70e512d7ade4b1f998f237137f1417ab07ec06358ea04f69134f8"}, + {file = "yarl-1.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:aef6c4d69554d44b7f9d923245f8ad9a707d971e6209d51279196d8e8fe1ae16"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b"}, + {file = "yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8"}, + {file = "yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1"}, + {file = "yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e"}, + {file = "yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773"}, + {file = "yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"}, + {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"}, + {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"}, + {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"}, + {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"}, + {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"}, + {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"}, + {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"}, + {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"}, + {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"}, + {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"}, + {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"}, + {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"}, + {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"}, + {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"}, + {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e42ba79e2efb6845ebab49c7bf20306c4edf74a0b20fc6b2ccdd1a219d12fad3"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:41493b9b7c312ac448b7f0a42a089dffe1d6e6e981a2d76205801a023ed26a2b"}, + {file = "yarl-1.20.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f5a5928ff5eb13408c62a968ac90d43f8322fd56d87008b8f9dabf3c0f6ee983"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30c41ad5d717b3961b2dd785593b67d386b73feca30522048d37298fee981805"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:59febc3969b0781682b469d4aca1a5cab7505a4f7b85acf6db01fa500fa3f6ba"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d2b6fb3622b7e5bf7a6e5b679a69326b4279e805ed1699d749739a61d242449e"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:749d73611db8d26a6281086f859ea7ec08f9c4c56cec864e52028c8b328db723"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9427925776096e664c39e131447aa20ec738bdd77c049c48ea5200db2237e000"}, + {file = "yarl-1.20.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff70f32aa316393eaf8222d518ce9118148eddb8a53073c2403863b41033eed5"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c7ddf7a09f38667aea38801da8b8d6bfe81df767d9dfc8c88eb45827b195cd1c"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:57edc88517d7fc62b174fcfb2e939fbc486a68315d648d7e74d07fac42cec240"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:dab096ce479d5894d62c26ff4f699ec9072269d514b4edd630a393223f45a0ee"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14a85f3bd2d7bb255be7183e5d7d6e70add151a98edf56a770d6140f5d5f4010"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2c89b5c792685dd9cd3fa9761c1b9f46fc240c2a3265483acc1565769996a3f8"}, + {file = "yarl-1.20.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:69e9b141de5511021942a6866990aea6d111c9042235de90e08f94cf972ca03d"}, + {file = "yarl-1.20.1-cp39-cp39-win32.whl", hash = "sha256:b5f307337819cdfdbb40193cad84978a029f847b0a357fbe49f712063cfc4f06"}, + {file = "yarl-1.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:eae7bfe2069f9c1c5b05fc7fe5d612e5bbc089a39309904ee8b829e322dcad00"}, + {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"}, + {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"}, ] [package.dependencies] @@ -6673,13 +7346,14 @@ propcache = ">=0.2.1" [[package]] name = "zipp" -version = "3.21.0" +version = "3.23.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" +groups = ["main"] files = [ - {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, - {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, + {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"}, + {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"}, ] [package.extras] @@ -6687,7 +7361,7 @@ check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more_itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [[package]] @@ -6696,6 +7370,7 @@ version = "0.23.0" description = "Zstandard bindings for Python" optional = false python-versions = ">=3.8" +groups = ["main"] files = [ {file = "zstandard-0.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bf0a05b6059c0528477fba9054d09179beb63744355cab9f38059548fedd46a9"}, {file = "zstandard-0.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fc9ca1c9718cb3b06634c7c8dec57d24e9438b2aa9a0f02b8bb36bf478538880"}, @@ -6803,6 +7478,6 @@ cffi = {version = ">=1.11", markers = "platform_python_implementation == \"PyPy\ cffi = ["cffi (>=1.11)"] [metadata] -lock-version = "2.0" +lock-version = "2.1" python-versions = "^3.12" content-hash = "8d7212070e6a14f472e3fb95bac71fc6380405a383d8f8d9602109c67b4d3705" diff --git a/fieldExtraction/scripts/create_code_embeddings.py b/fieldExtraction/scripts/create_code_embeddings.py index ada71de..843a3be 100644 --- a/fieldExtraction/scripts/create_code_embeddings.py +++ b/fieldExtraction/scripts/create_code_embeddings.py @@ -1,6 +1,6 @@ import src.utils.embedding_utils as embedding_utils -from crosswalk.crosswalk_utils import CrosswalkBuilder +from src.utils.crosswalk_utils import CrosswalkBuilder import src.codes.code_funcs as code_funcs import os diff --git a/fieldExtraction/src/client/___init__.py b/fieldExtraction/src/client/___init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/fieldExtraction/src/client/conditional_funcs.py b/fieldExtraction/src/client/conditional_funcs.py deleted file mode 100644 index dc98606..0000000 --- a/fieldExtraction/src/client/conditional_funcs.py +++ /dev/null @@ -1,105 +0,0 @@ -from src.prompts import client_prompts -import src.utils.llm_utils as llm_utils -from src import config - - -def run_conditional(combined_results, text_dict, filename): - - dsh_dict, rbrvs_dict, sl_dict = {}, {}, {} - - for d in combined_results: - if d is not None: - page_num = d["page_num"] - - # LOB/Program Check - if "," in d["CONTRACT_LOB"]: - prompt = client_prompts.CONDITIONAL_LOB_CHECK(d, text_dict[page_num]) - lob_answer = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, max_tokens=128 - ) - d["CONTRACT_LOB"] = lob_answer - - # Prov Type Level 2 - prompt = client_prompts.CONDITIONAL_PROV_2(d, text_dict[page_num]) - prov2_answer = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, max_tokens=128 - ) - d["PROV_TYPE_LEVEL_2"] = prov2_answer.strip() - - # # Inclusion of essential RBRVS "Fee Source" Language (Y/N) - Top Down approach to be used for non-NY - if "NY" not in config.STATE: - prompt = client_prompts.CONDITIONAL_RBRVS(text_dict[page_num]) - rbrvs_answer = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE2, filename, max_tokens=16 - ) - - d['Inclusion of essential RBRVS "Fee Source" Language (Y/N)'] = ( - rbrvs_answer.strip() - ) - rbrvs_dict[page_num] = rbrvs_answer - else: - if "RBRVS" in d["FULL_METHODOLOGY"]: - d['Inclusion of essential RBRVS "Fee Source" Language (Y/N)'] = "Y" - else: - d['Inclusion of essential RBRVS "Fee Source" Language (Y/N)'] = "N" - - # IP_OP - if "FACILITY" in d["PROV_TYPE"].upper(): - prompt = client_prompts.CONDITIONAL_IP_OP_SMALL( - d["FULL_SERVICE"], d["FULL_METHODOLOGY"] - ) - ip_op_answer = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, max_tokens=16 - ) - if ip_op_answer.strip() in ["IP", "OP"]: - d["IP_OP"] = ip_op_answer.strip() - else: - d["IP_OP"] = "N/A" - - if "IP" in ip_op_answer: - # IP - DSH/IME/UC, included (Y/N) - if page_num in dsh_dict.keys(): - d["IP - DSH/IME/UC, included (Y/N)"] = dsh_dict[page_num] - else: - prompt = client_prompts.CONDITIONAL_DSH_IME_UC(text_dict[page_num]) - dsh_answer = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE2, filename, max_tokens=16 - ) - d["IP - DSH/IME/UC, included (Y/N)"] = dsh_answer.strip() - dsh_dict[page_num] = dsh_answer - - # Stop-Loss Catastrophic Threshold - if page_num in sl_dict.keys(): - d["IP - Stoploss Catastrophic Threshold"] = sl_dict[page_num] - else: - if any( - [ - i.upper() in text_dict[page_num].upper() - for i in ["stop loss", "stop-loss", "stoploss"] - ] - ): - prompt = client_prompts.CONDITIONAL_STOP_LOSS(text_dict[page_num]) - sl_answer = llm_utils.invoke_claude( - prompt, - config.MODEL_ID_CLAUDE3_HAIKU, - filename, - max_tokens=256, - ) - else: - sl_answer = "N/A" - d["IP - Stoploss Catastrophic Threshold"] = sl_answer - sl_dict[page_num] = sl_answer - else: - d["IP - Stoploss Catastrophic Threshold"] = "N/A" - d["IP - DSH/IME/UC, included (Y/N)"] = "N/A" - else: - d["IP - Stoploss Catastrophic Threshold"] = "N/A" - d["IP - DSH/IME/UC, included (Y/N)"] = "N/A" - d["IP_OP"] = "N/A" - else: - d["IP_OP"] = "N/A" - d["IP - DSH/IME/UC, included (Y/N)"] = "N/A" - d['Inclusion of essential RBRVS "Fee Source" Language (Y/N)'] = "N/A" - d["IP - Stoploss Catastrophic Threshold"] = "N/A" - - return combined_results diff --git a/fieldExtraction/src/client/consolidate_output.py b/fieldExtraction/src/client/consolidate_output.py deleted file mode 100644 index b383e84..0000000 --- a/fieldExtraction/src/client/consolidate_output.py +++ /dev/null @@ -1,392 +0,0 @@ -import os -from pathlib import Path -import pandas as pd - -from src import config -from src.tracking import costlog_utils, tracking_utils -from src.constants import valid -from src.qa_qc.qa_qc_utils import perform_qc_qa - - -def upload_tracking_files(run_timestamp): - """Upload tracking files and aggregated cost logs to S3 during batch processing.""" - if not config.WRITE_TO_S3: - return - - BUCKET_NAME = config.S3_OUTPUT_BUCKET - tracking_files = { - 'cost_log': config.COST_LOG_NAME, - 'file_log': config.FILE_LOG_NAME - } - - try: - # Create aggregated costlog files - costlog_caller_fn = "costlog_agg_by_caller.csv" - costlog_filename_fn = "costlog_agg_by_filename.csv" - - # Read and aggregate costlog - if os.path.exists(config.COST_LOG_NAME): - df_costlog = pd.read_csv(config.COST_LOG_NAME) - df_caller, df_filename = costlog_utils.aggregate_costlog(df_costlog) - - # Write aggregated costlogs - df_caller.to_csv(costlog_caller_fn, index=False) - df_filename.to_csv(costlog_filename_fn, index=False) - - # Add aggregated files to tracking - tracking_files['costlog_by_caller'] = costlog_caller_fn - tracking_files['costlog_by_filename'] = costlog_filename_fn - - except Exception as e: - print(f"Costlog aggregation failed: {str(e)}") - - # Upload all tracking files - for file_type, file_name in tracking_files.items(): - if os.path.exists(file_name): - try: - s3_key = f"{config.BATCH_ID}/{run_timestamp}/tracking/{file_name}" - config.S3_CLIENT.upload_file(file_name, BUCKET_NAME, s3_key) - print(f"Uploaded {file_type} to {s3_key}") - except Exception as e: - print(f"Error uploading {file_name}: {str(e)}") - raise - -def get_all_run_folders(batch_id): - """Get all run folders for a given batch ID from S3""" - prefix = f"{batch_id}/" - runs = set() - - paginator = config.S3_CLIENT.get_paginator('list_objects_v2') - for page in paginator.paginate(Bucket=config.S3_OUTPUT_BUCKET, Prefix=prefix): - for obj in page.get('Contents', []): - parts = obj['Key'].split('/') - if len(parts) > 1 and parts[1].startswith('run_'): - runs.add(parts[1]) - - return sorted(list(runs)) - -def consolidate_batch_outputs(batch_id): - """Consolidate outputs across all runs for a batch ID""" - print(f"Starting batch-level consolidation for {batch_id}") - - ac_dfs = [] - b_dfs = [] - - run_folders = get_all_run_folders(batch_id) - - for run_timestamp in run_folders: - ac_key = f"{batch_id}/{run_timestamp}/consolidated/{batch_id}-AC.csv" - b_key = f"{batch_id}/{run_timestamp}/consolidated/{batch_id}-B.csv" - - try: - response = config.S3_CLIENT.get_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=ac_key - ) - ac_df = pd.read_csv(response['Body']) - ac_df['run_timestamp'] = run_timestamp - ac_dfs.append(ac_df) - except Exception as e: - print(f"Could not read AC file from run {run_timestamp}: {str(e)}") - - try: - response = config.S3_CLIENT.get_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=b_key - ) - b_df = pd.read_csv(response['Body']) - b_df['run_timestamp'] = run_timestamp - b_dfs.append(b_df) - except Exception as e: - print(f"Could not read B file from run {run_timestamp}: {str(e)}") - - batch_ac_df = None - batch_b_df = None - - if ac_dfs: - batch_ac_df = pd.concat(ac_dfs, ignore_index=True) - batch_ac_df = batch_ac_df.sort_values('run_timestamp').drop_duplicates( - subset=['Contract Name'], - keep='last' - ).drop('run_timestamp', axis=1) - - ac_output_path = f"{batch_id}/consolidated/{batch_id}-AC.csv" - csv_buffer = batch_ac_df.to_csv(index=False).encode() - config.S3_CLIENT.put_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=ac_output_path, - Body=csv_buffer - ) - print(f"Saved batch AC file: {ac_output_path}") - - if b_dfs: - batch_b_df = pd.concat(b_dfs, ignore_index=True) - batch_b_df = batch_b_df.sort_values('run_timestamp').drop_duplicates( - subset=['Contract Name'], - keep='last' - ).drop('run_timestamp', axis=1) - - b_output_path = f"{batch_id}/consolidated/{batch_id}-B.csv" - csv_buffer = batch_b_df.to_csv(index=False).encode() - config.S3_CLIENT.put_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=b_output_path, - Body=csv_buffer - ) - print(f"Saved batch B file: {b_output_path}") - - # Create batch ABC if both exist - if batch_ac_df is not None and batch_b_df is not None: - try: - batch_b_df_copy = batch_b_df.copy() - batch_b_df_copy.drop(["Pages", "Parent Agreement Code"], axis=1, inplace=True) - - batch_abc_df = pd.merge( - batch_b_df_copy, - batch_ac_df, - on="Contract Name", - how="right" - ) - - batch_abc_df = batch_abc_df[ - [col for col in valid.ABC_COLUMNS if col in batch_abc_df] - ] - - abc_output_path = f"{batch_id}/consolidated/{batch_id}-ABC.csv" - csv_buffer = batch_abc_df.to_csv(index=False).encode() - config.S3_CLIENT.put_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=abc_output_path, - Body=csv_buffer - ) - print(f"Saved batch ABC file: {abc_output_path}") - - # Perform QAQC on batch consolidated files - perform_qc_qa(batch_ac_df, batch_b_df, batch_abc_df, f"{batch_id}_batch") - - except Exception as e: - print(f"Error creating batch ABC file: {str(e)}") - - print("Batch-level consolidation complete") - return batch_ac_df, batch_b_df, batch_abc_df - - -def upload_individual_outputs(run_timestamp): - """Upload individual AC and B output files to their respective S3 folders""" - BUCKET_NAME = config.S3_OUTPUT_BUCKET - - for subdir, dirs, files in os.walk(config.OUTPUT_DIRECTORY): - # Skip the consolidated directory - if subdir == config.CONSOLIDATED_OUTPUT_DIRECTORY: - continue - - basename = os.path.basename(subdir) - if basename: # Skip root directory - filename = basename + ".txt" - - # If already fully processed and uploaded, skip - if tracking_utils.check_file_processed(config.BATCH_ID, filename, 'AC') and \ - tracking_utils.check_file_processed(config.BATCH_ID, filename, 'B'): - print(f"Skipping {filename} - already processed") - continue - - individual_folder = f"{config.BATCH_ID}/{run_timestamp}/individual/{basename}" - - # Check for AC results - ac_path = os.path.join(subdir, config.AC_RESULTS_NAME) - if os.path.exists(ac_path): - s3_key = f"{individual_folder}/{config.AC_RESULTS_NAME}" - try: - config.S3_CLIENT.upload_file(ac_path, BUCKET_NAME, s3_key) - tracking_utils.log_file_processing(filename, 'AC', s3_key, 'Completed') - tracking_utils.update_master_batch_tracking( - config.BATCH_ID, - run_timestamp, - [filename], - 'AC', - s3_path=s3_key, - upload_status='Uploaded' - ) - except Exception as e: - error_msg = str(e) - print(f"Error uploading AC file {basename}: {error_msg}") - tracking_utils.log_file_processing(filename, 'AC', s3_key, 'Failed', error_msg) - - # Check for B results - b_path = os.path.join(subdir, config.B_RESULTS_NAME) - if os.path.exists(b_path): - s3_key = f"{individual_folder}/{config.B_RESULTS_NAME}" - try: - config.S3_CLIENT.upload_file(b_path, BUCKET_NAME, s3_key) - tracking_utils.log_file_processing(filename, 'B', s3_key, 'Completed') - tracking_utils.update_master_batch_tracking( - config.BATCH_ID, - run_timestamp, - [filename], - 'B', - s3_path=s3_key, - upload_status='Uploaded' - ) - except Exception as e: - error_msg = str(e) - print(f"Error uploading B file {basename}: {error_msg}") - tracking_utils.log_file_processing(filename, 'B', s3_key, 'Failed', error_msg) - -def consolidate_output(run_timestamp, ac_df = None, b_df = None): - """Consolidate and upload outputs""" - Path(config.CONSOLIDATED_OUTPUT_DIRECTORY).mkdir(parents=True, exist_ok=True) - processed_ac_files = [] - processed_b_files = [] - - ac_final_df = None if ac_df is None else ac_df - b_final_df = None if b_df is None else b_df - abc_final_df = None - - # First upload individual outputs - if config.WRITE_TO_S3: - print(f"Uploading individual outputs...") - upload_individual_outputs(run_timestamp) - - # If AC only - if "a" in config.FIELDS and "c" in config.FIELDS and ac_final_df is None: - ac_dfs = [] - for subdir, dirs, files in os.walk(config.OUTPUT_DIRECTORY): - ac_path = os.path.join(subdir, config.AC_RESULTS_NAME) - if os.path.exists(ac_path): - try: - ac_df = pd.read_csv(ac_path) - ac_dfs.append(ac_df) - processed_ac_files.append(os.path.basename(subdir) + ".txt") - except Exception as e: - print(f"Error reading AC file {ac_path}: {str(e)}") - - if ac_dfs: - ac_final_df = pd.concat(ac_dfs, ignore_index=True) - output_path = os.path.join( - config.CONSOLIDATED_OUTPUT_DIRECTORY, - f"{config.BATCH_ID}-AC.csv" - ) - ac_final_df.to_csv(output_path) - - if config.WRITE_TO_S3: - s3_key = f"{config.BATCH_ID}/{run_timestamp}/consolidated/{config.BATCH_ID}-AC.csv" - try: - config.S3_CLIENT.upload_file(output_path, config.S3_OUTPUT_BUCKET, s3_key) - tracking_utils.log_file_processing(f"{config.BATCH_ID}-AC.csv", 'consolidated', s3_key, 'Completed') - tracking_utils.update_master_batch_tracking( - config.BATCH_ID, - run_timestamp, - [f"{config.BATCH_ID}-AC.csv"], - 'consolidated', - s3_path=s3_key, - upload_status='Uploaded' - ) - except Exception as e: - print(f"Error uploading consolidated AC file: {str(e)}") - tracking_utils.log_file_processing(f"{config.BATCH_ID}-AC.csv", 'consolidated', s3_key, 'Failed', str(e)) - - # If B only - if "b" in config.FIELDS and b_final_df is None: - b_dfs = [] - for subdir, dirs, files in os.walk(config.OUTPUT_DIRECTORY): - b_path = os.path.join(subdir, config.B_RESULTS_NAME) - if os.path.exists(b_path): - try: - b_df = pd.read_csv(b_path) - b_dfs.append(b_df) - processed_b_files.append(os.path.basename(subdir) + ".txt") - except Exception as e: - print(f"Error reading B file {b_path}: {str(e)}") - - if b_dfs: - b_final_df = pd.concat(b_dfs, ignore_index=True) - output_path = os.path.join( - config.CONSOLIDATED_OUTPUT_DIRECTORY, - f"{config.BATCH_ID}-B.csv" - ) - b_final_df.to_csv(output_path) - - if config.WRITE_TO_S3: - s3_key = f"{config.BATCH_ID}/{run_timestamp}/consolidated/{config.BATCH_ID}-B.csv" - try: - config.S3_CLIENT.upload_file(output_path, config.S3_OUTPUT_BUCKET, s3_key) - tracking_utils.log_file_processing(f"{config.BATCH_ID}-B.csv", 'consolidated', s3_key, 'Completed') - tracking_utils.update_master_batch_tracking( - config.BATCH_ID, - run_timestamp, - [f"{config.BATCH_ID}-B.csv"], - 'consolidated', - s3_path=s3_key, - upload_status='Uploaded' - ) - except Exception as e: - print(f"Error uploading consolidated B file: {str(e)}") - tracking_utils.log_file_processing(f"{config.BATCH_ID}-B.csv", 'consolidated', s3_key, 'Failed', str(e)) - - # If ABC - if "a" in config.FIELDS and "c" in config.FIELDS and "b" in config.FIELDS: - try: - b_final_df.drop( - ["Pages", "Parent Agreement Code"], axis=1, inplace=True - ) - abc_path = os.path.join( - config.CONSOLIDATED_OUTPUT_DIRECTORY, f"{config.BATCH_ID}-ABC.csv" - ) - abc_final_df = pd.merge( - b_final_df, ac_final_df, on="Contract Name", how="right" - ) - abc_final_df = abc_final_df[ - [col for col in valid.ABC_COLUMNS if col in abc_final_df] - ] - - print( - "Columns not found: ", - [f for f in valid.ABC_COLUMNS if f not in abc_final_df.columns], - ) - abc_final_df.to_csv(abc_path) - print(f"Consolidation complete. Results written to {abc_path}") - - if config.WRITE_TO_S3: - s3_key = f"{config.BATCH_ID}/{run_timestamp}/consolidated/{config.BATCH_ID}-ABC.csv" - try: - config.S3_CLIENT.upload_file(abc_path, config.S3_OUTPUT_BUCKET, s3_key) - tracking_utils.log_file_processing(f"{config.BATCH_ID}-ABC.csv", 'consolidated', s3_key, 'Completed') - except Exception as e: - error_msg = str(e) - print(f"Error uploading ABC file: {error_msg}") - tracking_utils.log_file_processing(f"{config.BATCH_ID}-ABC.csv", 'consolidated', s3_key, 'Failed', error_msg) - except Exception as e: - print(f"Error creating ABC consolidated file: {str(e)}") - - # Upload tracking files - if config.WRITE_TO_S3: - try: - upload_tracking_files(run_timestamp) - print(f"Upload completed to s3://{config.S3_OUTPUT_BUCKET}/{config.BATCH_ID}/{run_timestamp}") - except Exception as e: - print(f"Error uploading tracking files: {str(e)}") - - - return ac_final_df, b_final_df, abc_final_df - -def consolidate_adhoc(ac_df, b_df): - - # If ABC - - b_df.drop([col for col in ["Pages", "Parent Agreement Code"] if col in b_df.columns], axis=1, inplace=True) - - abc_final_df = pd.merge( - b_df, ac_df, on=[config.FILE_NAME_COLUMN], how="right" - ) - abc_final_df = abc_final_df[ - [col for col in valid.ABC_COLUMNS if col in abc_final_df] - ] - - print( - "Columns not found: ", - [f for f in valid.ABC_COLUMNS if f not in abc_final_df.columns], - ) - - return ac_df, b_df, abc_final_df - - diff --git a/fieldExtraction/src/client/file_processing.py b/fieldExtraction/src/client/file_processing.py deleted file mode 100644 index 2024535..0000000 --- a/fieldExtraction/src/client/file_processing.py +++ /dev/null @@ -1,150 +0,0 @@ -import csv -import os - -import pandas as pd - -import src.client.conditional_funcs as conditional_funcs -import src.client.one_to_n_funcs as one_to_n_funcs -import src.client.postprocess as postprocess -import src.client.preprocess as preprocess -import src.client.smart_chunking_funcs as smart_chunking_funcs -from src import config -from src.regex.regex_utils import get_tin_fields, get_npi_fields - - -def run_ac_prompts(file_object): - - ac_answers_dict = {} - log_data = [] - chunk_data = [] - - ################## INITIATE PROCESSING ################## - filename, contract_text = file_object - print(f"Processing AC for {filename}...") - - ################## PREPROCESS - SMART CHUNK ################## - - # Top Sheet Addition - text_dict, top_sheet_dict, exhibit_pages, num_pages, ac_chunks = ( - preprocess.preprocess(contract_text, filename, fields="ac") - ) - print(f"AC Preprocessing Complete - {filename}") - - ################## RUN REGEX FUNCTIONALITY ################## - # no prompting for these fields - - irs_answers = get_tin_fields(filename, text_dict, top_sheet_dict) - ac_answers_dict.update(irs_answers) - - npi_answers = get_npi_fields(text_dict, top_sheet_dict) - ac_answers_dict.update(npi_answers) - - ################## RUN SMART CHUNKED PROMPTS ################## - ac_answers_dict, no_keyword_matches = ( - smart_chunking_funcs.run_smart_chunk_prompts( - ac_answers_dict, ac_chunks, contract_text, filename, text_dict - ) - ) - - ################## GET FULL CONTEXT FIELDS ################## - # Process 'full_context' fields together - full_context_fields = smart_chunking_funcs.get_full_context_fields( - ac_answers_dict, no_keyword_matches - ) - - ################## RUN FULL CONTEXT PROMPTS ################## - ac_answers_dict = smart_chunking_funcs.run_full_context_prompts( - ac_answers_dict, full_context_fields, contract_text, filename - ) - - ################## AC CONDITIONAL PROMPTS ################## - ac_answers_dict = smart_chunking_funcs.run_ac_conditional_prompts( - ac_answers_dict, contract_text, filename - ) - - ################## CREATE OUTPUT DIRECTORIES ################## - base_filename = os.path.splitext(filename)[0].strip() - output_dir = os.path.join(config.OUTPUT_DIRECTORY, base_filename) - os.makedirs(output_dir, exist_ok=True) - - ################## POSTPROCESS ################## - - ac_df = pd.DataFrame([ac_answers_dict]) - - # print(f"Before ac_postprocess Unique values: {ac_df['CONTRACT_EFFECTIVE_DT'].unique()}") - - ac_df = postprocess.ac_postprocess(ac_df, text_dict, filename, num_pages) - - # print(f"After ac_postprocess Unique values: {ac_df['Contract Effective Date'].unique()}") - - ################## WRITE TO OUTPUT ################## - ac_df.to_csv(os.path.join(output_dir, config.AC_RESULTS_NAME), index=False) - - chunk_file_path = "chunk_log.csv" - with open(chunk_file_path, "a", newline="", encoding="utf-8") as chunk_file: - fieldnames = [ - "Contract name", - "Field group", - "Field list", - "Methodology", - "Case sensitivity", - "Page list", - "Chunk size", - "% Reduction", - ] - writer = csv.DictWriter(chunk_file, fieldnames=fieldnames) - writer.writeheader() - writer.writerows(chunk_data) - - print(f"Chunk log written to {chunk_file_path}") - - return ac_answers_dict - - -def run_b_prompts(file_object): - ################## INITIATE PROCESSING ################## - filename, contract_text = file_object - print(f"Processing B for {filename}...") - - ################## PREPROCESS ################## - text_dict, top_sheet_dict, exhibit_pages, num_pages, ac_chunks = ( - preprocess.preprocess(contract_text, filename, fields="b") - ) - print(f"B Preprocessing Complete - {filename}") - - ################## RUN BOTTOM UP PROMPTS ################## - bu_results = one_to_n_funcs.run_bottom_up( - filename, text_dict - ) # Returns list of dictionaries - print(f"B Bottom Up Complete - {filename}") - - ################## RUN TOP DOWN PROMPTS ################## - combined_results = one_to_n_funcs.run_top_down( - filename, text_dict, bu_results, exhibit_pages - ) # Returns list of dictionaries - print(f"B Top Down Complete - {filename}") - - ################## RUN CONDITIONAL PROMPTS ################## - conditional_results = conditional_funcs.run_conditional( - combined_results, text_dict, filename - ) # List of dictionaries - print(f"B Conditional Prompts Complete - {filename}") - - ################## CREATE OUTPUT DIRECTORIES ################## - base_filename = os.path.splitext(filename)[0].strip() - output_dir = os.path.join(config.OUTPUT_DIRECTORY, base_filename) - os.makedirs(output_dir, exist_ok=True) - - ################## WRITE UNPROCESSED OUTPUT ################## - combined_df = pd.DataFrame(conditional_results) - combined_df.to_csv( - os.path.join(output_dir, config.UNPROCESSED_RESULTS_NAME), index=False - ) - - ################## RUN POSTPROCESSING ################## - final_df = postprocess.b_postprocess(filename, combined_df, num_pages) - print(f"B Postprocessing Complete - {filename} ") - - ################## WRITE FINAL ################## - final_df.to_csv(os.path.join(output_dir, config.B_RESULTS_NAME), index=False) - print(f"B Output Complete - {filename} ") diff --git a/fieldExtraction/src/client/main.py b/fieldExtraction/src/client/main.py deleted file mode 100644 index 5505d39..0000000 --- a/fieldExtraction/src/client/main.py +++ /dev/null @@ -1,370 +0,0 @@ - -import concurrent.futures -import os -import random -import traceback -from datetime import datetime - -random.seed(42) - -import src.client.file_processing as file_processing -import src.qa_qc.qa_qc_utils as qa_qc_utils -import src.tracking.tracking_utils as tracking_utils -import src.utils.io_utils as io_utils -import src.utils.string_utils as string_utils -import src.client.consolidate_output as consolidate_output -from src import config -from src.tracking.batch_tracking import BatchTracker - - -def process_b(item): - filename, contract_text = item - start_time = datetime.now() - - if string_utils.contains_reimbursement(contract_text, 0): - try: - file_processing.run_b_prompts(item) - end_time = datetime.now() - - output_dir = os.path.join(config.OUTPUT_DIRECTORY, os.path.splitext(filename)[0]) - output_file = os.path.join(output_dir, config.B_RESULTS_NAME) - - if os.path.exists(output_file): - batch_tracker.update_file_progress(filename, 'B', start_time, end_time) - tracking_utils.log_file_processing( - filename, - 'B', - f"{config.BATCH_ID}/processing/{filename}", - 'Completed' - ) - return ('B', item) - else: - error_msg = 'B output file not created' - batch_tracker.update_file_progress( - filename, 'B', start_time, datetime.now(), - status='Failed', error=error_msg - ) - tracking_utils.log_file_processing( - filename, - 'B', - f"{config.BATCH_ID}/processing/{filename}", - 'Failed', - error_msg - ) - return None - - except Exception as e: - error_msg = str(e) - print(f"Error processing item {filename}: {error_msg}") - traceback.print_exc() - batch_tracker.update_file_progress( - filename, 'B', start_time, datetime.now(), - status='Failed', error=error_msg - ) - tracking_utils.log_file_processing( - filename, - 'B', - f"{config.BATCH_ID}/processing/{filename}", - 'Failed', - error_msg - ) - return None - else: - print(f"No reimbursement information found in {filename}. Skipping processing.") - batch_tracker.update_file_progress( - filename, 'B', datetime.now(), datetime.now(), - status='Skipped', error='No reimbursement information found' - ) - tracking_utils.log_file_processing( - filename, - 'B', - f"{config.BATCH_ID}/processing/{filename}", - 'Skipped', - 'No reimbursement information found' - ) - return None - -def process_ac(item): - filename, contract_text = item - start_time = datetime.now() - - try: - file_processing.run_ac_prompts(item) - end_time = datetime.now() - - output_dir = os.path.join(config.OUTPUT_DIRECTORY, os.path.splitext(filename)[0]) - output_file = os.path.join(output_dir, config.AC_RESULTS_NAME) - - if os.path.exists(output_file): - batch_tracker.update_file_progress(filename, 'AC', start_time, end_time) - tracking_utils.log_file_processing( - filename, - 'AC', - f"{config.BATCH_ID}/processing/{filename}", - 'Completed' - ) - return ('AC', item) - else: - error_msg = 'AC output file not created' - batch_tracker.update_file_progress( - filename, 'AC', start_time, datetime.now(), - status='Failed', error=error_msg - ) - tracking_utils.log_file_processing( - filename, - 'AC', - f"{config.BATCH_ID}/processing/{filename}", - 'Failed', - error_msg - ) - return None - except Exception as e: - error_msg = str(e) - print(f"Error processing item {filename}: {error_msg}") - traceback.print_exc() - batch_tracker.update_file_progress( - filename, 'AC', start_time, datetime.now(), - status='Failed', error=error_msg - ) - tracking_utils.log_file_processing( - filename, - 'AC', - f"{config.BATCH_ID}/processing/{filename}", - 'Failed', - error_msg - ) - return None - -def process_results(result, filename, input_dict_b, stats): - """Process individual results and update tracking stats""" - if not result or not result[1]: - return stats - - proc_type, item = result - filename = item[0] - - output_dir = os.path.join(config.OUTPUT_DIRECTORY, os.path.splitext(filename)[0]) - output_file = os.path.join(output_dir, - config.B_RESULTS_NAME if proc_type == 'B' else config.AC_RESULTS_NAME) - - if os.path.exists(output_file): - if filename not in stats['processed_files'].get(proc_type, set()): - if proc_type == 'B': - stats['completed_b'] += 1 - else: - stats['completed_ac'] += 1 - stats['processed_files'].setdefault(proc_type, set()).add(filename) - - tracking_utils.log_file_processing( - filename, - proc_type, - f"{config.BATCH_ID}/processing/{filename}", - 'Completed' - ) - - stats['processed_count'] += 1 - - return stats - - -def process_remaining_files(input_dict, input_dict_b, run_timestamp, stats): - """Process any remaining files in self-repair mode""" - print("\nEntering self-repair mode...") - - - completed_ac, completed_b = tracking_utils.get_processed_files(config.BATCH_ID) - - - stats['processed_files']['AC'] = completed_ac - stats['processed_files']['B'] = completed_b - stats['completed_ac'] = len(completed_ac) - stats['completed_b'] = len(completed_b) - - # Find truly remaining files - remaining_ac = {k: v for k, v in input_dict.items() - if k not in completed_ac} - remaining_b = {k: v for k, v in input_dict_b.items() - if k not in completed_b} - - if not remaining_ac and not remaining_b: - print("No files need repair processing.") - return stats - - print(f"Found {len(remaining_ac)} AC files and {len(remaining_b)} B files requiring processing") - - with concurrent.futures.ThreadPoolExecutor(max_workers=config.MAX_WORKERS) as executor: - futures = [] - if remaining_b: - print(f"Processing {len(remaining_b)} remaining B files...") - futures.extend([executor.submit(process_b, item) for item in remaining_b.items()]) - if remaining_ac: - print(f"Processing {len(remaining_ac)} remaining AC files...") - futures.extend([executor.submit(process_ac, item) for item in remaining_ac.items()]) - - for future in concurrent.futures.as_completed(futures): - try: - result = future.result() - if result: - stats = process_results(result, result[1][0], input_dict_b, stats) - print(f"Repair processing completed for: {result[1][0]}") - except Exception as e: - print(f"Error in repair processing: {e}") - traceback.print_exc() - - print("Self-repair processing complete") - print("Reconsolidating outputs after repair...") - consolidate_output.consolidate_output(run_timestamp) - - return stats - -def main(): - global batch_tracker - batch_tracker = BatchTracker() - - try: - run_timestamp = datetime.now().strftime(f"run_%Y%m%d_%H:%M_{config.BATCH_ID}") - - # Read and process input - input_dict = io_utils.read_input() - print(f"Total Input Files : {len(input_dict)}") - - # Filter B - files with reimbursement - input_dict_b = { - k: v for k, v in input_dict.items() - if string_utils.contains_reimbursement(str(v)) - } - - batch_tracker.set_input_file_counts( - total_files=len(input_dict), - reimbursement_files=len(input_dict_b) - ) - batch_tracker.add_batch_run() - - if config.FILTER_ALREADY_PROCESSED: - # Get processed files using new tracking_utils function - processed_ac, processed_b = tracking_utils.get_processed_files(config.BATCH_ID) - - # Filter AC files - input_dict_ac = { - k: v for k, v in input_dict.items() - if k not in processed_ac - } - - # Filter B files - input_dict_b = { - k: v for k, v in input_dict_b.items() - if k not in processed_b - } - print(f"Filtered out {len(processed_ac) + len(processed_b)} already processed files") - else: - input_dict_ac = input_dict - - # Calculate totals - total_ac = len(input_dict_ac) if "a" in config.FIELDS.lower() and "c" in config.FIELDS.lower() else 0 - total_b = len(input_dict_b) if "b" in config.FIELDS.lower() else 0 - - # Initialize tracking stats - stats = { - 'completed_ac': 0, - 'completed_b': 0, - 'processed_count': 0, - 'processed_files': {'AC': set(), 'B': set()} - } - - # Setup processing types - files_processing_types = {} - if "b" in config.FIELDS.lower(): - for filename in input_dict_b: - files_processing_types[filename] = files_processing_types.get(filename, []) + ['B'] - - if "a" in config.FIELDS.lower() and "c" in config.FIELDS.lower(): - for filename in input_dict_ac: - files_processing_types[filename] = files_processing_types.get(filename, []) + ['AC'] - - total_processing_events = sum(len(types) for types in files_processing_types.values()) - print(f"Total unique files: {len(files_processing_types)}") - print(f"Total AC files to process: {total_ac}") - print(f"Total B files to process: {total_b}") - - last_upload_count = 0 - UPLOAD_FREQUENCY = 10 - - # Process files - with concurrent.futures.ThreadPoolExecutor(max_workers=config.MAX_WORKERS) as executor: - futures = [] - - if "b" in config.FIELDS.lower() and len(input_dict_b) > 0: - futures.extend([executor.submit(process_b, item) for item in input_dict_b.items()]) - - if "a" in config.FIELDS.lower() and "c" in config.FIELDS.lower() and len(input_dict_ac) > 0: - futures.extend([executor.submit(process_ac, item) for item in input_dict_ac.items()]) - - if not futures: - raise ValueError("no input files were staged for processing") - - for future in concurrent.futures.as_completed(futures): - try: - result = future.result() - if result: - filename = result[1][0] - stats = process_results(result, filename, input_dict_b, stats) - - # Calculate and display progress - ac_progress, b_progress = tracking_utils.calculate_progress_stats( - stats['completed_ac'], total_ac, - stats['completed_b'], total_b - ) - print(f"\nProgress:") - print(f"AC: {ac_progress:.2f}% ({stats['completed_ac']}/{total_ac})") - print(f"B: {b_progress:.2f}% ({stats['completed_b']}/{total_b})") - print(f"Overall: {(stats['processed_count'] / total_processing_events * 100):.2f}%") - - if stats['processed_count'] - last_upload_count >= UPLOAD_FREQUENCY: - try: - consolidate_output.consolidate_output(run_timestamp) - last_upload_count = stats['processed_count'] - except Exception as e: - print(f"Warning: Could not update tracking data: {str(e)}") - - except Exception as e: - print(f"Error processing future: {e}") - traceback.print_exc() - - print("\nInitial Processing Complete") - - print("Starting output consolidation and QA/QC...") - try: - ac_final_df, b_final_df, abc_final_df = consolidate_output.consolidate_output(run_timestamp) - wb = qa_qc_utils.perform_qc_qa(ac_final_df, b_final_df, abc_final_df, input_dict) - qa_qc_utils.save_qcqa_wb(wb, run_timestamp) - print("Consolidation and QA/QC complete") - except Exception as e: - print(f"Error during consolidation: {str(e)}") - - # Process remaining files and get updated stats - stats = process_remaining_files(input_dict, input_dict_b, run_timestamp, stats) - - batch_tracker.update_batch_run(end_time=datetime.now()) - - print(f"\nFinal Statistics:") - print(f"Total input files: {batch_tracker.total_input_files}") - print(f"Files with reimbursement: {batch_tracker.reimbursement_files}") - print(f"AC files processed: {stats['completed_ac']}/{total_ac}") - print(f"B files processed: {stats['completed_b']}/{total_b}") - print(f"Total processing events completed: {stats['processed_count']}/{total_processing_events}") - - if stats['processed_count'] > 0 and total_processing_events > 0: - success_rate = (stats['processed_count'] / total_processing_events) * 100 - print(f"Overall success rate: {success_rate:.2f}%") - - except Exception as e: - print(f"Error in main processing: {e}") - traceback.print_exc() - try: - batch_tracker.update_batch_run(end_time=datetime.now()) - except Exception as tracking_error: - print(f"Error updating batch tracking: {tracking_error}") - raise e - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/fieldExtraction/src/client/one_to_n_funcs.py b/fieldExtraction/src/client/one_to_n_funcs.py deleted file mode 100644 index 6124850..0000000 --- a/fieldExtraction/src/client/one_to_n_funcs.py +++ /dev/null @@ -1,370 +0,0 @@ - - -import re - -import src.constants.valid as valid -from src.prompts import client_prompts -import src.utils.llm_utils as llm_utils -import src.utils.string_utils as string_utils -from src import config, postprocessing_funcs - - -def run_bottom_up_primary( - text_dict: dict, tokens: int, filename: str -) -> dict[str, str]: - """Executes the primary Bottom Up processing for pages that contain reimbursement terms. - - This function scans through a dictionary of page texts, identifies pages containing reimbursement terms, - and processes those pages using Claude to extract relevant information. - - Args: - text_dict (dict): A dictionary containing text content of documents keyed by page numbers. - tokens (int): The maximum number of tokens to use in the language model invocation for generating the response. Deprecated. - filename (str): The filename of the contract in question. Used for logging purposes. - - Returns: - dict[str, str]: A dictionary where each key is a page number (string) and the value is the response from the language model. - """ - - # chunk_dict = preprocess.chunk_text(text_dict) - answer_strings = {} - # for page_number in chunk_dict.keys(): - # if '%' in chunk_dict[page_number] or '$' in chunk_dict[page_number]: - # prompt = prompts.BOTTOM_UP_PRIMARY(chunk_dict[page_number], config.CLIENT_NAME) - for page_number in text_dict.keys(): - if string_utils.contains_reimbursement(text_dict, page_number): - # Run Primary - prompt = client_prompts.BOTTOM_UP_PRIMARY( - text_dict[page_number], config.CLIENT_NAME - ) - answer = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE35_SONNET, filename - ) - answer_strings[page_number] = answer - - answer_dicts = string_utils.primary_string_to_dict(answer_strings, filename) - - answer_dicts = postprocessing_funcs.consolidate_subheader(answer_dicts) - answer_dicts = postprocessing_funcs.filter_service_column( - answer_dicts - ) # Filter on entire Service + Subheader - answer_dicts = postprocessing_funcs.filter_methodology_column(answer_dicts) - - return answer_dicts - - -def get_short_methodology(s): - pattern = r"\d+\.*\d*%" - short_string = re.sub(pattern, "%", s) - return short_string - - -def run_bottom_up_secondary( - answer_dicts: list[dict], text_dict: dict, tokens: int, filename: str -) -> list[dict]: - """Executes the secondary Bottom Up processing phase on the results obtained from the primary Bottom Up analysis. - - This function enhances the primary results with additional analyses based on configured conditions. Invokes - Claude 3 with tailored prompts to generate structured information that complements the initial results. - - Each piece of data processed possibly undergoes several rounds of checks and transformations, ensuring detailed and - comprehensive output. - - Args: - answer_dicts (list[dict]): Initial processed data from the primary Bottom Up analysis. - text_dict (dict): Dictionary containing the original document text keyed by page numbers. - tokens (int): Token limit for language model invocations. - filename (str): The filename of the contract in question. Used for logging purposes. - - Returns: - list[dict]: A list of dictionaries containing enriched and finalized structured data from both the primary and - secondary analyses. - """ - # Add additional fields to each row - final_dicts = [] - for d in answer_dicts: - if d is not None: - page_num = d["page_num"] - - # if '%' in d['FULL_METHODOLOGY'] or '$' in d['FULL_METHODOLOGY']: - prompt = client_prompts.BOTTOM_UP_METHODOLOGY_BREAKOUT(d) - answer = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, max_tokens=512 - ) - answer_dict = string_utils.secondary_string_to_dict(answer, filename) - d.update(answer_dict) - - # Ensure all bottom up methodology keys are present - for key in [ - "LESSER", - "RATE_STANDARD", - "RATE_SHORT", - "FLAT_FEE_STANDARD", - "LESSER_RATE", - "NOT_TO_EXCEED", - ]: - if key not in d.keys(): - d[key] = "" - - # SHORT_METHODOLOGY - if "%" in d["RATE_STANDARD"]: - d["SHORT_METHODOLOGY"] = get_short_methodology(d["RATE_STANDARD"]) - elif "$" in d["FLAT_FEE_STANDARD"]: - d["SHORT_METHODOLOGY"] = "Flat Fee" - else: - d["SHORT_METHODOLOGY"] = "N/A" - - # Escalator - prompt = client_prompts.BOTTOM_UP_ESCALATOR(d, text_dict[page_num]) - escalator_answer = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, max_tokens=1024 - ) - escalator_dict = string_utils.secondary_string_to_dict( - escalator_answer, filename - ) - d.update(escalator_dict) - - final_dicts.append(d) - - return final_dicts - - -def run_bottom_up(filename: str, text_dict: dict) -> list[dict]: - """Processes the text of a document using a two-tiered Bottom Up approach to extract key financial and operational information. - - This function first runs BOTTOM_UP_PRIMARY and BOTTOM_UP_SECONDARY prompts, then processes these initial results to further - refine and structure them into a dictionary form. - - Args: - filename (str): The name of the file being processed, used to tag output data. - text_dict (dict): A dictionary of text keyed by page numbers that contains the content to be analyzed. - - Returns: - list[dict]: A list of dictionaries with each dictionary containing refined and structured information - from both primary and secondary Bottom Up analyses, all tagged with the filename. - """ - - # Bottom Up Primary - FULL_SERVICE, FULL_METHODOLOGY - bu_primary_results = run_bottom_up_primary(text_dict, 8000, filename) - - # Bottom Up Secondary - bu_secondary_results = run_bottom_up_secondary( - bu_primary_results, text_dict, 8000, filename - ) - - for d in bu_secondary_results: - d["Filename"] = filename - return bu_secondary_results # List of dictionaries - - -def get_exhibit_range(text_dict, exhibit_pages, bu_page): - lower_bound = max( - [int(page) for page in exhibit_pages if int(page) <= int(bu_page)], default=None - ) - upper_bound = min( - [int(page) for page in exhibit_pages if int(page) > int(bu_page)], default=None - ) - if lower_bound is not None and upper_bound is not None: - pages_between = [ - page - for page in text_dict.keys() - if int(lower_bound) <= int(page) <= int(upper_bound) - ] - elif lower_bound is not None: # No upper_bound found - pages_between = [ - page for page in text_dict.keys() if int(lower_bound) <= int(page) - ] - elif upper_bound is not None: # No lower_bound found - pages_between = [ - page for page in text_dict.keys() if int(page) <= int(upper_bound) - ] - else: - pages_between = [] - return pages_between - - -def run_top_down_primary(text_dict, td_pages, filename): - page_text = "\n".join([text_dict[page_num] for page_num in td_pages]) - - prompt = client_prompts.TOP_DOWN_PRIMARY(page_text) - answer = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, max_tokens=4096 - ) - answer_dict = string_utils.secondary_string_to_dict(answer, filename) - for field in client_prompts.TD_PRIMARY_FIELDS: - if field not in answer_dict: - answer_dict[field] = "N/A" - - # Add indicators - if "ADD_ON_REIMBURSEMENT_LANGUAGE" in answer_dict: - if "N/A" in answer_dict["ADD_ON_REIMBURSEMENT_LANGUAGE"]: - answer_dict["ADD_ON_REIMBURSEMENT_IND"] = "N" - else: - answer_dict["ADD_ON_REIMBURSEMENT_IND"] = "Y" - else: - answer_dict["ADD_ON_REIMBURSEMENT_IND"] = "N" - answer_dict["ADD_ON_REIMBURSEMENT_LANGUAGE"] = "N/A" - - if config.PROV_TYPE: - answer_dict["PROV_TYPE"] = config.PROV_TYPE - return answer_dict - - -def get_td_dict(text_dict, filename, PROMPT_FUNC, VALID_VALUES): - td_dict = {} - for page_num, page_text in text_dict.items(): - if ( - any(i.upper() in page_text.upper() for i in VALID_VALUES) - and page_num.isdigit() - ): - prompt = PROMPT_FUNC(page_text) - answer = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, 1024 - ) - td_dict[page_num] = answer - return td_dict - - -def add_medically_necessary(merged_dicts, medically_necessary_dict): - if not medically_necessary_dict: - for d in merged_dicts: - d["MEDICAL_NECESSITY_LANGUAGE"] = "N/A" - d["MEDICAL_NECESSITY_LANGUAGE_IND"] = "N" - return merged_dicts - - sorted_keys = sorted(int(k) for k in medically_necessary_dict.keys()) - - # Find the closest page number that is less than or equal to the given page number - def find_closest_page(page_num): - closest_page = sorted_keys[0] - for key in sorted_keys: - if key <= page_num: - closest_page = key - else: - break - return closest_page - - for d in merged_dicts: - try: - current_page_num = int(d["page_num"]) - # Find the closest preceding medically necessary page - closest_page = find_closest_page(current_page_num) - d["MEDICAL_NECESSITY_LANGUAGE"] = medically_necessary_dict[ - str(closest_page) - ] - if not string_utils.is_empty(d["MEDICAL_NECESSITY_LANGUAGE"]): - d["MEDICAL_NECESSITY_LANGUAGE_IND"] = "Y" - else: - d["MEDICAL_NECESSITY_LANGUAGE_IND"] = "N" - except: - d["MEDICAL_NECESSITY_LANGUAGE"] = "N/A" - d["MEDICAL_NECESSITY_LANGUAGE_IND"] = "N" - - return merged_dicts - - -def add_exclusions(merged_dicts, exclusions_dict): - if not exclusions_dict: - for d in merged_dicts: - d["EXCLUSIONS"] = "N/A" - return merged_dicts - - # Prepare by sorting exclusion keys and initializing an empty dictionary to track exclusions by EXHIBIT - sorted_keys = sorted(int(k) for k in exclusions_dict.keys()) - exhibit_exclusions = {} - - # First pass: assign exclusions based on page number - for d in merged_dicts: - page_num = str(d["page_num"]) - if page_num.isdigit(): - for exc_page in sorted_keys: - if exc_page >= int(page_num) and exc_page <= int(page_num) + 2: - new_exclusion = exclusions_dict[str(exc_page)] - # Handle EXCLUSIONS as a list to avoid duplicates - if "EXCLUSIONS" not in d: - d["EXCLUSIONS"] = [] - if new_exclusion not in d["EXCLUSIONS"]: - try: - d["EXCLUSIONS"].append(new_exclusion) - except: - d["EXCLUSIONS"] = [new_exclusion] - - # Map this exclusion to the EXHIBIT value - if "EXHIBIT" in d: - if d["EXHIBIT"] not in exhibit_exclusions: - exhibit_exclusions[d["EXHIBIT"]] = [] - if new_exclusion not in exhibit_exclusions[d["EXHIBIT"]]: - exhibit_exclusions[d["EXHIBIT"]].append(new_exclusion) - break - else: - # If no matching exclusion found, check if 'EXHIBIT' maps to any existing exclusion - if "EXHIBIT" in d and d["EXHIBIT"] in exhibit_exclusions: - for exclusion in exhibit_exclusions[d["EXHIBIT"]]: - if "EXCLUSIONS" not in d: - d["EXCLUSIONS"] = [] - if exclusion not in d["EXCLUSIONS"]: - d["EXCLUSIONS"].append(exclusion) - else: - d["EXCLUSIONS"] = ["N/A"] - else: - d["EXCLUSIONS"] = ["N/A"] - - # Convert lists to semicolon-separated strings - for d in merged_dicts: - if isinstance(d["EXCLUSIONS"], list): - d["EXCLUSIONS"] = "; ".join(d["EXCLUSIONS"]) - - return merged_dicts - - -def run_top_down(filename, text_dict, bu_results, exhibit_pages): - bu_pages = {bu_dict["page_num"] for bu_dict in bu_results} - - td_page_dict = {} - td_answer_dict = {} - - for bu_page in bu_pages: - td_pages = get_exhibit_range(text_dict, exhibit_pages, bu_page) - if td_pages not in td_page_dict.values(): - answer_dict = run_top_down_primary(text_dict, td_pages, filename) - td_page_dict[bu_page] = td_pages - td_answer_dict[bu_page] = answer_dict - else: - td_page_dict[bu_page] = td_pages - for key, value in td_page_dict.items(): - if value == td_pages: - answer_dict = td_answer_dict[key] - td_answer_dict[bu_page] = answer_dict - break - - merged_dicts = [] - for bu_dict in bu_results: - answer_dict = td_answer_dict[bu_dict["page_num"]] - bu_dict.update(answer_dict) - merged_dicts.append(bu_dict) - - # Run and Merge Exclusions - exclusion_dict = get_td_dict( - text_dict, filename, client_prompts.TOP_DOWN_EXCLUSIONS, valid.EXCLUSION_TERMS - ) - final_dicts = add_exclusions(merged_dicts, exclusion_dict) - - # Run and Merge Medically Necessary - medically_necessary_dict = get_td_dict( - text_dict, - filename, - client_prompts.TOP_DOWN_MEDICALLY_NECESSARY, - valid.VALID_MEDICALLY_NECESSARY, - ) - medically_necessary_dict = postprocessing_funcs.filter_dict( - medically_necessary_dict, - pattern=re.compile( - "|".join( - re.escape(keyword) for keyword in valid.INVALID_MEDICALLY_NECESSARY - ), - re.IGNORECASE, - ), - ) - final_dicts = add_medically_necessary(final_dicts, medically_necessary_dict) - - return final_dicts diff --git a/fieldExtraction/src/client/postprocess.py b/fieldExtraction/src/client/postprocess.py deleted file mode 100644 index ca72571..0000000 --- a/fieldExtraction/src/client/postprocess.py +++ /dev/null @@ -1,110 +0,0 @@ -import src.constants.valid as valid -from src import postprocessing_funcs - - -def b_postprocess(filename, df, pages): - - if df.shape[0] > 0: - # Metadata fields - df.drop("Filename", axis=1, inplace=True) - - df["Contract Name"] = "Filename: " + filename - df["Parent Agreement Code"] = postprocessing_funcs.get_parent_agreement_code( - filename - ) - df["Pages"] = pages - - # Add Single Code, Multiple Rate - df = postprocessing_funcs.add_scmr(df) - - # Filter Add Ons - df = postprocessing_funcs.filter_add_ons(df) - - # Clean MSR - df = postprocessing_funcs.clean_msr_lesser(df) - - # Clean Default - df = postprocessing_funcs.clean_default_term(df) - - # Clean Prov 2 - df = postprocessing_funcs.clean_prov_2(df) - - # Clean Lesser Of Rate - df = postprocessing_funcs.clean_lesser_rate(df) - - # Clean LOB - df = postprocessing_funcs.clean_lob(df, filename) - - df = postprocessing_funcs.methodology_short_fix(df) - - df = postprocessing_funcs.yn_fixes(df) - - # Rename and reorder - df.rename(columns=valid.B_MAPPING, inplace=True) - - column_order = [col for col in valid.B_MAPPING.values() if col in df.columns] - final_df = df[column_order] - - return final_df - else: - return df - - -def ac_postprocess(df, text_dict, filename, pages): - - if df.shape[0] > 0: - - df["Contract Name"] = "Filename: " + filename - - df["Pages"] = pages - - df["Parent Agreement Code"] = postprocessing_funcs.get_parent_agreement_code( - filename - ) - - df = df.fillna("") - - df = postprocessing_funcs.clean_term_clause(df) - - df = postprocessing_funcs.clean_auto_renewal_ind(df) - - df = postprocessing_funcs.get_tin_from_filename(df) - - df = postprocessing_funcs.clean_tin(df) - - df = postprocessing_funcs.clean_npi(df) - - df = postprocessing_funcs.clean_network_access_fees(df) - - df = postprocessing_funcs.clean_health_plan_state(df) - - df = postprocessing_funcs.clean_health_plan_state_from_hotfix(df,filename,text_dict) - - df = postprocessing_funcs.clean_notice_provider_name_and_address(df) - - df = postprocessing_funcs.clean_policies_and_procedures(df) - - df = postprocessing_funcs.clean_tin_npi_other(df) - - df = postprocessing_funcs.icm_number_fix(df) - - df = postprocessing_funcs.yn_fixes(df) - - df = df.apply(lambda x: x.map(postprocessing_funcs.replace_null_terms)) - - df = df.apply(lambda x: x.map(postprocessing_funcs.replace_quotes)) - - # Derive Indicators - df = df.apply(postprocessing_funcs.derive_indicators, axis=1) - - # Rename and Reorder - df = df.rename(columns=valid.AC_MAPPING) - column_order = [col for col in valid.ABC_COLUMNS if col in df.columns] + [ - col for col in df.columns if col not in valid.ABC_COLUMNS - ] - - final_df = df[column_order] - - return final_df - else: - return df diff --git a/fieldExtraction/src/client/preprocess.py b/fieldExtraction/src/client/preprocess.py deleted file mode 100644 index acff4d3..0000000 --- a/fieldExtraction/src/client/preprocess.py +++ /dev/null @@ -1,38 +0,0 @@ -import csv - -import src.utils.table_utils as table_utils -from src import config, keywords, preprocessing_funcs -import src.client.smart_chunking_funcs as smart_chunking_funcs - - -def preprocess(contract_text, filename, fields=config.FIELDS): - - contract_text = preprocessing_funcs.remove_page_indicators(contract_text) - contract_text = preprocessing_funcs.clean_law_symbols(contract_text) - - text_dict = preprocessing_funcs.split_text( - contract_text - ) # return a dictionary with keys - page_num (str), values as the page_text - - # text_dict = {k: v for k, v in text_dict.items() if k.isdigit() and 42 <= int(k) <= 42} - num_pages = len(text_dict.keys()) - text_dict, top_sheet_dict = preprocessing_funcs.filter_quick_review(text_dict) - - if fields == "b": - exhibit_pages = preprocessing_funcs.get_exhibit_pages( - text_dict, filename - ) # All pages with exhibit headers - text_dict = table_utils.align_and_format_tables(text_dict, filename) - text_dict = preprocessing_funcs.chunk_consecutive(text_dict, exhibit_pages) - # write text_dict to a csv file: - with open("text_dict.csv", "w") as f: - writer = csv.writer(f) - for key, value in text_dict.items(): - writer.writerow([key, value]) - - ac_chunks, full_context = "", "" - if fields == "ac": - exhibit_pages = [] - ac_chunks = smart_chunking_funcs.smart_chunk_ac(text_dict, contract_text, keywords.GROUPED_KEYWORD_MAPPINGS) - - return text_dict, top_sheet_dict, exhibit_pages, num_pages, ac_chunks diff --git a/fieldExtraction/src/client/smart_chunking_funcs.py b/fieldExtraction/src/client/smart_chunking_funcs.py deleted file mode 100644 index 48970fb..0000000 --- a/fieldExtraction/src/client/smart_chunking_funcs.py +++ /dev/null @@ -1,640 +0,0 @@ -import re - -from src.prompts import client_prompts -import src.utils.llm_utils as llm_utils -import src.utils.string_utils as string_utils -from src import config, keywords, postprocessing_funcs -from src.enums.delimiters import Delimiter - -from collections import defaultdict -from itertools import chain, count, groupby - -""" -Date pattern regex: -Matches for "day of", -any full month name (e.g., "January", "February", ...) -any month abbreviation (e.g., "Jan", "Feb", ...) -date formats: -MM/DD/YYYY (e.g., 12/25/2023) -MM.DD.YYYY (e.g., 12.25.2023) -MM-DD-YYYY (e.g., 12-25-2023) -YYYY/MM/DD (e.g., 2024/12/09) -YYYY.MM.DD (e.g., 2024.12.09) -YYYY-MM-DD (e.g., 2024-12-09) -YYYYMMDD (e.g., 20241209) -MMYY or YYMM format (e.g., 1224 for December 2024 or 2405 for May 2024) -YYYY.MM.DD (e.g., 2024.12.09) - -Case insensitive -""" - -DATE_PATTERN = r"(?i)\bday of\b|\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\b|\b(?:\d{1,2}[\/\.-]\d{1,2}[\/\.-](?:\d{2}|\d{4})|\d{4}[\/\.-]\d{1,2}[\/\.-]\d{1,2}|\d{8}|\d{2}\d{2}|\d{4}[\/\.-]\d{1,2}[\/\.-]\d{1,2}|\d{4}[\.]\d{1,2}[\.]\d{1,2})\b" - - -def run_smart_chunk_prompts( - ac_answers_dict: dict[str, str], - ac_chunks: dict, - contract_text: str, - filename: str, - text_dict: dict[str, str], -): - - ## Check `ac_funcs.create_prompt()` vs. `prompts.AC_*_template()` - all_fields = set(client_prompts.AC_DICT.keys()) - field_groups = [ - field_group for field_group in keywords.GROUPED_KEYWORD_MAPPINGS.keys() - ] - - # list to append the fields with no matching keywords - no_keyword_matches = [] - - for field_group in field_groups: - keyword_dict = keywords.GROUPED_KEYWORD_MAPPINGS[field_group].get( - "keywords", [] - ) - fields = keywords.GROUPED_KEYWORD_MAPPINGS[field_group].get("fields", []) - fields = [field for field in fields] - fields.sort() - questions = {} - for field in fields: - questions[field] = client_prompts.AC_DICT.get(field, "") - - # Use chunk if available and different from full context, otherwise use full context - ############# Important Note ############# - # the above logic is changed - # New Logic - chunk if available and different from full context, otherwise add the field to full context list - if ( - field_group in ac_chunks - and ac_chunks[field_group].strip() - and ac_chunks[field_group] != contract_text - ): - - context = ac_chunks[field_group] - context = context[0 : min(100000, len(context)) - 1] - context_type = "Smart Chunking" - - if len(fields) == 1: - question = questions[ - fields[0] - ] # Take the one question for the single field included - if fields[0] == "CONTRACT_EFFECTIVE_DT": - prompt = client_prompts.get_effective_date_prompt(context) - else: - prompt = client_prompts.AC_SINGLE_FIELD_TEMPLATE(context, question) - elif len(fields) > 1: - prompt = client_prompts.AC_MULTI_FIELD_TEMPLATE(context, questions) - else: - raise ValueError("Field group has no defined fields") - - try: - if field_group == "AGREEMENT_NAME": - - # context, prompt and answer based on first 5 pages - context_agreement = "\n".join( - [ - text_dict[str(page_num)] - for page_num in text_dict.keys() - if str(page_num) in ["1", "2", "3", "4", "5"] - ] - ) - prompt_agreement = client_prompts.AC_SINGLE_FIELD_TEMPLATE( - context_agreement, question - ) - field_group_answer_raw = llm_utils.invoke_claude( - prompt_agreement, - config.MODEL_ID_CLAUDE35_SONNET, - filename, - 8192, - ) - - # if answer is N/A use the smart-chunked context - if field_group_answer_raw == "N/A": - field_group_answer_raw = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, 8192 - ) - else: - field_group_answer_raw = llm_utils.invoke_claude( - prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, 8192 - ) - - if len(fields) == 1: - if fields[0] == "CONTRACT_EFFECTIVE_DT": - # print(f'Effective Date Answer obtained from LLM: {field_group_answer_raw}') - field_group_answer_raw = string_utils.extract_text_from_delimiters( - field_group_answer_raw, Delimiter.PIPE - ) - # print(f'Parsed Effective Date Answer (extract_text_from_delimiters): {field_group_answer_raw}') - try: - field_group_answer_raw = postprocessing_funcs.convert_to_us_date_format( - field_group_answer_raw - ) # Fix date formatting - # print(f'Effective Date Answer (convert_to_us_date_format): {field_group_answer_raw}') - except postprocessing_funcs.InvalidDateException as e: - field_group_answer_raw = "N/A" - # print(f'Got Invalid Date Exception, using contract effective date as N/A for contract {filename}') - - field_group_answer_dict = {fields[0]: field_group_answer_raw} - - elif len(fields) > 1: - field_group_answer_raw = field_group_answer_raw.replace( - "_DATE", "_DT" - ) - field_group_answer_dict = string_utils.json_parsing_search( - field_group_answer_raw, fields - ) - - # TODO: Validate field names coming out of Claude, check against fields in group, make sure they're all there, make sure there aren't any extras - for field in fields: - field_answer = field_group_answer_dict[field] - ac_answers_dict[field] = field_answer - - except Exception as e: - print(f"Error processing field {field}: {type(e)}, {str(e)}") - ac_answers_dict[field] = f"Error: {str(e)}" - - else: # If no chunk available - if ( - field_group == "CONTRACT_EFFECTIVE_DT" - ): # Special case for effective date - ac_answers_dict["CONTRACT_EFFECTIVE_DT"] = "N/A" - # print('Got no chunk for effective date, so setting it to N/A', ac_answers_dict) - else: # Add to no_keyword_matches list to run using full context - no_keyword_matches.extend(fields) - - return ac_answers_dict, no_keyword_matches - - -def get_full_context_fields( - ac_answers_dict: dict[str, str], no_keyword_matches: list -) -> list: - - smartly_chunked = [] - - for field_groups in keywords.GROUPED_KEYWORD_MAPPINGS.keys(): - for fields in keywords.GROUPED_KEYWORD_MAPPINGS[field_groups]["fields"]: - smartly_chunked.append(fields) - - # Instantiate full context fields from those that weren't smart chunked - full_context_fields = [ - field for field in client_prompts.AC_DICT if field not in smartly_chunked - ] - - # Add fields with no keyword matches to full context list - if len(no_keyword_matches) > 0: - full_context_fields.extend(no_keyword_matches) - - # Filter out fields that already have answers - # e.g., some have already been filled out by regex chunking - full_context_fields = [ - field for field in full_context_fields if field not in ac_answers_dict.keys() - ] - return full_context_fields - - -def run_full_context_prompts( - ac_answers_dict: dict[str, str], - full_context_fields: list, - contract_text: str, - filename: str, -) -> dict[str, str]: - full_context_questions = { - field: client_prompts.AC_DICT.get(field) for field in full_context_fields - } - - if full_context_questions: - full_context_prompt = client_prompts.AC_MULTI_FIELD_TEMPLATE( - context=contract_text[0 : min(100000, len(contract_text) - 1)], - questions=full_context_questions, - ) - - try: - full_context_answers = llm_utils.get_ac_answer( - full_context_prompt, - filename, - fields=list(full_context_questions.keys()), - ) - - ac_answers_dict.update(full_context_answers) - - except Exception as e: - print(f"Error processing high accuracy fields: {str(e)}") - for field in full_context_questions.keys(): - ac_answers_dict[field] = f"Error: {str(e)}" - - # Ensure all AC fields have a key, populated with N/A if not from prompt - for key in client_prompts.AC_DICT.keys(): - if key not in ac_answers_dict: - ac_answers_dict[key] = "N/A" - - return ac_answers_dict - - -def run_ac_conditional_prompts( - ac_answers_dict: dict[str, str], contract_text: str, filename: str -) -> dict[str, str]: - - # Non-Renewal - Days - if ac_answers_dict["NON_RENEWAL_LANGUAGE"] != "N/A": - nrd_prompt = client_prompts.AC_SINGLE_FIELD_TEMPLATE( - ac_answers_dict["NON_RENEWAL_LANGUAGE"], client_prompts.AC_DICT["NON_RENEWAL_DAYS"] - ) - nrd_answer = llm_utils.invoke_claude( - nrd_prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, 8192 - ) - ac_answers_dict["NON_RENEWAL_DAYS"] = nrd_answer - else: - ac_answers_dict["NON_RENEWAL_DAYS"] = "N/A" - - # Clean Up Contract Effective Date - if ( - string_utils.is_empty(ac_answers_dict["CONTRACT_EFFECTIVE_DT"]) - and "meridian" in ac_answers_dict["PAYER_NAME"].lower() - ): - date_prompt = client_prompts.AC_EFFECTIVE_DATE_CLEANUP( - contract_text[0 : min(100000, len(contract_text)) - 1] - ) - date_answer = llm_utils.invoke_claude( - date_prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, 124 - ) - ac_answers_dict["CONTRACT_EFFECTIVE_DT"] = date_answer - - return ac_answers_dict - - -def chunk_hierarchical( - text_dict: dict[str, str], keywords: list[str], case_sensitive: bool = False -) -> list[str]: - """Look for the keywords, one-by-one, and take the first that satisfies the keyword. - We pad responsive pages with a one-page buffer (before and after) when possible. - - Args: - text_dict (dict[str, str]): A dictionary keyed by string page number and valued - by string page contents - keywords (list[str]): A list of string keywords - case_sensitive(bool): A flag for converting all keywords and text to lower case, - in order to ignore case altogether. - - Returns: - list[str]: The sorted list of string page numbers responsive to the first - keyword found - """ - if not case_sensitive: - keywords = [keyword.lower() for keyword in keywords] - text_dict = {key: value.lower() for key, value in text_dict.items()} - - page_list = [] - for keyword in keywords: - if len(page_list) == 0: - for page in text_dict.keys(): - if keyword in text_dict[page]: - if str(int(page) - 1) in text_dict.keys(): - page_list.append(str(int(page) - 1)) - - page_list.append(page) - - if str(int(page) + 1) in text_dict.keys(): - page_list.append(str(int(page) + 1)) - - page_list_sorted = sorted([int(page_str) for page_str in set(page_list)]) - page_list_sorted_str = list(map(str, page_list_sorted)) - return page_list_sorted_str - - -def chunk_or( - text_dict: dict[str, str], keywords: list[str], case_sensitive: bool = False -) -> list[str]: - """Look for any of the keywords and add any pages with any keywords to the chunk. - We pad responsive pages with a one-page buffer (before and after) when possible. - - Args: - text_dict (dict[str, str]): A dictionary keyed by string page number and valued - by string page contents - keywords (list[str]): A list of string keywords - case_sensitive(bool): A flag for converting all keywords and text to lower case, - in order to ignore case altogether. - - Returns: - list[str]: The sorted list of string page numbers responsive to the first - keyword found - """ - if not case_sensitive: - keywords = [keyword.lower() for keyword in keywords] - text_dict = {key: value.lower() for key, value in text_dict.items()} - - page_list = [] - for page in text_dict.keys(): - for keyword in keywords: - if keyword in text_dict[page]: - # Add buffer of previous and next page, if we're not at the beginning nor end - # If we are at the beginning (or the end) just take the next (or previous) page - if str(int(page) - 1) in text_dict.keys(): - page_list.append(str(int(page) - 1)) - - page_list.append(page) - - if str(int(page) + 1) in text_dict.keys(): - page_list.append(str(int(page) + 1)) - - page_list_sorted = sorted([int(page_str) for page_str in set(page_list)]) - page_list_sorted_str = list(map(str, page_list_sorted)) - return page_list_sorted_str - - -# And: run each keyword and include a page if all are present -def chunk_and( - text_dict: dict[str, str], keywords: list[str], case_sensitive: bool = False -) -> list[str]: - """Look for all of the keywords and add any pages with all keywords to the chunk. - We pad responsive pages with a one-page buffer (before and after) when possible. - - Args: - text_dict (dict[str, str]): A dictionary keyed by string page number and valued - by string page contents - keywords (list[str]): A list of string keywords - case_sensitive(bool): A flag for converting all keywords and text to lower case, - in order to ignore case altogether. - - Returns: - list[str]: The sorted list of string page numbers responsive to the first - keyword found - """ - if not case_sensitive: - keywords = [keyword.lower() for keyword in keywords] - text_dict = {key: value.lower() for key, value in text_dict.items()} - - page_list = [] - for page in text_dict.keys(): - if all([keyword in text_dict[page] for keyword in keywords]): - # Add buffer of previous and next page, if we're not at the beginning nor end - # If we are at the beginning (or the end) just take the next (or previous) page - if str(int(page) - 1) in text_dict.keys(): - page_list.append(str(int(page) - 1)) - - page_list.append(page) - - if str(int(page) + 1) in text_dict.keys(): - page_list.append(str(int(page) + 1)) - - page_list_sorted = sorted([int(page_str) for page_str in set(page_list)]) - page_list_sorted_str = list(map(str, page_list_sorted)) - return page_list_sorted_str - - -# Regex: Check for a regex and include a page if the regex matches within the page -def chunk_regex(text_dict: dict[str, str], regex: str) -> list[str]: - """Look for regex match and add any pages with regex match to the chunk. - We pad responsive pages with a one-page buffer (before and after) when possible. - - Args: - text_dict (dict[str, str]): A dictionary keyed by string page number and valued - by string page contents - regex (str): A regex expression - - Returns: - list[str]: The sorted list of string page numbers responsive to the first - keyword found - """ - - page_list = [] - for page in text_dict.keys(): - if re.search(regex, text_dict[page]): - # Add buffer of previous and next page, if we're not at the beginning nor end - # If we are at the beginning (or the end) just take the next (or previous) page - if str(int(page) - 1) in text_dict.keys(): - page_list.append(str(int(page) - 1)) - - page_list.append(page) - - if str(int(page) + 1) in text_dict.keys(): - page_list.append(str(int(page) + 1)) - - page_list_sorted = sorted([int(page_str) for page_str in set(page_list)]) - page_list_sorted_str = list(map(str, page_list_sorted)) - return page_list_sorted_str - - -def keyword_search( - text_dict: dict[str, str], - keywords: list[str], - case_sensitive: bool = False, - regex: bool = False, -) -> dict[str, list]: - """Look for keyword/regex match and add any pages with keyword/regex match to the chunk. - - Args: - text_dict (dict[str, str]): A dictionary keyed by string page number and valued - by string page contents - keywords (list[str]): A list of string keywords/regex - case_sensitive(bool): A flag for converting all keywords and text to lower case, - in order to ignore case altogether. - regex (bool): A flag to identify if the passed keyword list is regex list or string list - - Returns: - list[str]: The dictionary containing keyword as keys and sorted list of string page numbers responsive to the - keyword found - """ - if not case_sensitive and not regex: - keywords = [keyword.lower() for keyword in keywords] - text_dict = {key: value.lower() for key, value in text_dict.items()} - - page_dict = {} - for keyword in keywords: - page_dict[keyword] = [] - - for page in text_dict.keys(): - if (not regex and keyword in text_dict[page]) or ( - regex and re.search(keyword, text_dict[page]) - ): - # if str(int(page) - 1) in text_dict.keys(): - # page_dict[keyword].append(str(int(page) - 1)) - page_dict[keyword].append(page) - # if str(int(page) + 1) in text_dict.keys(): - # page_dict[keyword].append(str(int(page) + 1)) - - page_dict_sorted = {} - for keyword in page_dict: - page_dict_sorted[keyword] = sorted( - [int(page_str) for page_str in set(page_dict[keyword])] - ) - - return page_dict_sorted - - -def chunk_with_include_exclude_keywords( - text_dict: dict[str, str], - include_keywords: list[str], - exclude_keywords: list[str], - case_sensitive: bool = False, - apply_date_filtering: bool = True - ) -> list[str]: - - """Look for pages that include any of the include_keywords and exclude any pages that contain any of the exclude_keywords. - - Args: - text_dict (dict[str, str]): A dictionary keyed by string page number and valued - by string page contents - include_keywords (list[str]): A list of string keywords to include - exclude_keywords (list[str]): A list of string keywords to exclude - case_sensitive (bool): A flag for converting all keywords and text to lower case, - in order to ignore case altogether. - apply_date_filtering (bool): A flag to filter out the pages not containing a date. - - Returns: - list[str]: The sorted list of string page numbers that include all include_keywords and exclude any exclude_keywords - """ - - if not case_sensitive: - include_keywords = [keyword.lower() for keyword in include_keywords] - exclude_keywords = [keyword.lower() for keyword in exclude_keywords] - text_dict = {key: value.lower() for key, value in text_dict.items()} - - if apply_date_filtering: - pages_containing_date = set() - - for page_num, page_content in text_dict.items(): - dates_list = re.findall(DATE_PATTERN, page_content) - - if len(dates_list) > 0: - pages_containing_date.add(page_num) - pages_containing_date = sorted(pages_containing_date,key=int) - - # debug lines for inclusion and exclusion keywords found on each page - - # print(f"Pages containing date: {pages_containing_date}") - - # for page_num, page_content in text_dict.items(): - # if page_num in pages_containing_date: - - # inclusion_keywords = set() - # exclusion_keywords = set() - - # for keyword in include_keywords: - # if keyword in page_content: - # inclusion_keywords.add(keyword) - - # for keyword in exclude_keywords: - # if keyword in page_content: - # exclusion_keywords.add(keyword) - - # print(f"Page #: {page_num} contains inclusion keywords: {inclusion_keywords} and contain exclusion keywords: {exclusion_keywords}") - - page_list = [] - - for page in text_dict.keys(): - if not apply_date_filtering or (apply_date_filtering and page in pages_containing_date): - if any(keyword in text_dict[page] for keyword in include_keywords) and not any( - keyword in text_dict[page] for keyword in exclude_keywords - ): - # if str(int(page) - 1) in text_dict.keys(): - # page_list.append(str(int(page) - 1)) - # print(f"Taking page #: {str(int(page) - 1)} (as buffer for following page)") - - page_list.append(page) - # print(f"Taking page #: {page}") - - # if str(int(page) + 1) in text_dict.keys(): - # page_list.append(str(int(page) + 1)) - # # print(f"Taking page #: {str(int(page) + 1)} (as buffer for previous page)") - - page_list_sorted = sorted([int(page_str) for page_str in set(page_list)]) - page_list_sorted_str = list(map(str, page_list_sorted)) - - # print(", ".join([page for page in page_list_sorted_str])) - - # print(f"Obtained total pages: {len(page_list_sorted_str)}") - - return page_list_sorted_str - - -def smart_chunk_ac( - text_dict: dict, - contract_text: str, - keyword_mappings: dict = keywords.GROUPED_KEYWORD_MAPPINGS, -) -> dict: - """Performs a keyword search on textract outputs to retrieve relevant chunks for a given field. - - Args: - text_dict (dict): Dictionary keyed by string page-number and valued by actual textract output page - contract_text(str): Contract Text - - Raises: - ValueError: When the `key_dict` from keyword_mappings has a methodology that is not implemented. - Current options are `or` and `hierarchy`. - - Returns: - dict: A dictionary keyed by field (defined by the keys in keyword_mappings) valued by - corresponding chunks of text (input strings delimited by newlines). - """ - - chunks = defaultdict(str) - - full_context = "\n".join( - [f"Page {page}:\n{content}" for page, content in text_dict.items()] - ) - - for field_group, key_dict in keyword_mappings.items(): - if key_dict["methodology"] == "or": - page_list = chunk_or( - text_dict, key_dict["keywords"], key_dict["case_sensitive"] - ) - keyword_page_dict = keyword_search( - text_dict, key_dict["keywords"], key_dict["case_sensitive"] - ) - elif key_dict["methodology"] == "hierarchy": - page_list = chunk_hierarchical( - text_dict, key_dict["keywords"], key_dict["case_sensitive"] - ) - keyword_page_dict = keyword_search( - text_dict, key_dict["keywords"], key_dict["case_sensitive"] - ) - elif key_dict["methodology"] == "and": - page_list = chunk_and( - text_dict, key_dict["keywords"], key_dict["case_sensitive"] - ) - keyword_page_dict = keyword_search( - text_dict, key_dict["keywords"], key_dict["case_sensitive"] - ) - elif key_dict["methodology"] == "regex": - page_list = chunk_regex(text_dict, key_dict["regex"]) - keyword_page_dict = keyword_search( - text_dict, key_dict["keywords"], key_dict["case_sensitive"], True - ) - elif key_dict["methodology"] == "include_exclude": - # Chunk pages based on included and excluded keywords - page_list = chunk_with_include_exclude_keywords( - text_dict, key_dict["included_keywords"], key_dict["excluded_keywords"] - ) - - # Create a copy of keywords list by combining included and excluded keywords - this is required for keyword search - included_keywords = key_dict["included_keywords"] - excluded_keywords = key_dict["excluded_keywords"] - - keywords = list(chain(included_keywords,excluded_keywords)) - - # Perform keyword search on the text dictionary - this returns a dictionary keyed by keyword and values are list of pages containing that keyword - # This is not currently used but is available for debugging purposes (or future use cases) - keyword_page_dict = keyword_search( - text_dict, keywords, key_dict["case_sensitive"] - ) - else: - page_list = list(text_dict.keys()) - keyword_page_dict = {"All pages": list(text_dict.keys())} - raise ValueError( - "methodology must be one of 'hierarchy', 'or', 'and', 'regex', 'include_exclude' - using entire contract as chunk" - ) - if field_group == "initial_page_group": - keyword_page_dict["initial_pages"] = [1, 2] - - # chunks[field_group] = concatenated page texts - if len(page_list) == len(text_dict.keys()): - chunks[field_group] = contract_text - else: - chunks[field_group] = "\n".join( - [text_dict[str(page_num)] for page_num in page_list] - ) - - chunks[field_group + "_pages"] = keyword_page_dict - chunks[field_group + "_methodology"] = key_dict["methodology"] - chunks[field_group + "_case"] = key_dict["case_sensitive"] - return chunks - diff --git a/fieldExtraction/src/codes/code_constants.py b/fieldExtraction/src/codes/code_constants.py deleted file mode 100644 index 1df3751..0000000 --- a/fieldExtraction/src/codes/code_constants.py +++ /dev/null @@ -1,58 +0,0 @@ - -from crosswalk.crosswalk_utils import CrosswalkBuilder -import src.codes.code_funcs as code_funcs - -INPUT_FILE_PATH = "Codes-Test-Doc.xlsx" -OUTPUT_FILE_PATH = "Doczy-Codes-Test-9.csv" -SERVICE_COLUMN = "SERVICE_TERM" - - -SYNONYM_MAP = {"DME" : "DURABLE MEDICAL EQUIPMENT", - "SNF" : "SKILLED NURSING FACILITY", - "ASC" : "AMBULATORY SURGERY CENTER", - "LABORATORY" : "LABORATORY AND PATHOLOGY", - "LAB" : "LABORATORY AND PATHOLOGY", - "STOP LOSS" : "STOP-LOSS", - "OB": "OBSTETRIC" -} - -REMOVAL_LIST = [v.upper() for v in - list(set(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_lob.json").mapping.keys())) + # LOB - list(set(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_product_lob.json").mapping.keys())) + # Product - list(set(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_network.json").mapping.values())) + # Network - list(set(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_claim_type.json").mapping.keys())) + # Claim Type - list(set(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_program.json").mapping.values())) + # Program - ["INPATIENT", "OUTPATIENT", "IN-PATIENT", "OUT-PATIENT", "IP", "OP"] + - ["COVERED SERVICES", "SERVICES", "PROCEDURES", "CONTRACTED", "HEALTHCARE", "HEALTH CARE", "ALL OTHER", - "PER DIEM", "ADMISSION", "REIMBURSEMENT", "ADDITIONAL PAYMENT", "PAYMENT", "RATE", "FEE SCHEDULE", "METHODOLOGY", "OUTLIER", - "INTERMEDIATE", "READMISSION", "ACUTE", "OPPS", "FEE-FOR-SERVICE", "PROGRAM", "PRODUCT", - ] # Generic Terms not found in any of the proc code mappings -] - -STOP_WORD_LIST = [ - "THE", "AND", "OF", "TO", "A", "IN", "FOR", "ON", "WITH", "AT", "BY", - "AN", "BE", "THIS", "THAT", "FROM", "OR", "AS", "IS", "ARE", "WAS", - "WERE", "WHICH", "WHO", "ALL", "OTHER", "/", ",", "NOT" -] - - -DO_NOT_RUN = ["BY REPORT", "STOP-LOSS"] - - -from sentence_transformers import SentenceTransformer -model = SentenceTransformer("all-roberta-large-v1") - -# Code Crosswalks -cpt_level1_mapping = CrosswalkBuilder().from_excel("crosswalk/mapping_csvs/proc_cd/cpt_level1.csv", "Code", "Description").mapping -cpt_level2_mapping = CrosswalkBuilder().from_excel("crosswalk/mapping_csvs/proc_cd/cpt_level2.csv", "Code", "Description").mapping -cpt_level3_mapping = CrosswalkBuilder().from_excel("crosswalk/mapping_csvs/proc_cd/cpt_level3.csv", "Code", "Description").mapping - -hcpcs_level1_mapping = CrosswalkBuilder().from_excel("crosswalk/mapping_csvs/proc_cd/hcpcs_level1.csv", "Code", "Description").mapping -hcpcs_level2_mapping = CrosswalkBuilder().from_excel("crosswalk/mapping_csvs/proc_cd/hcpcs_level2.csv", "Code", "Description").mapping -hcpcs_mapping = CrosswalkBuilder().from_excel("crosswalk/mapping_csvs/proc_cd/hcpcs.csv", "Code", "Description").mapping - -rev_level1_mapping = CrosswalkBuilder().from_excel("crosswalk/mapping_csvs/rev_cd/rev_level1.csv", "Code", "Description").mapping -rev_mapping = CrosswalkBuilder().from_excel("crosswalk/mapping_csvs/rev_cd/rev.csv", "Code", "Description").mapping - -bill_type_mapping = CrosswalkBuilder().from_json("crosswalk/mappings/crosswalk_bill_type.json").mapping -bill_type_reverse_mapping = CrosswalkBuilder().from_json("crosswalk/mappings/crosswalk_bill_type.json").create_reverse_mapping() \ No newline at end of file diff --git a/fieldExtraction/src/codes/code_funcs.py b/fieldExtraction/src/codes/code_funcs.py index fbc093f..0982fa3 100644 --- a/fieldExtraction/src/codes/code_funcs.py +++ b/fieldExtraction/src/codes/code_funcs.py @@ -1,18 +1,19 @@ - -from src.prompts.investment_prompts import FieldSet -import src.config as config -import src.utils.string_utils as string_utils -import src.utils.llm_utils as llm_utils -import src.prompts.investment_prompts as investment_prompts -import src.utils.embedding_utils as embedding_utils -import src.codes.code_constants as code_constants -from src.enums.delimiters import Delimiter -import pandas as pd - import os import re -def clean_service(service) -> str: +import pandas as pd + +import src.config as config +import src.prompts.investment_prompts as investment_prompts +import src.utils.embedding_utils as embedding_utils +import src.utils.llm_utils as llm_utils +import src.utils.string_utils as string_utils +from constants.constants import Constants +from constants.delimiters import Delimiter +from src.prompts.investment_prompts import FieldSet + + +def clean_service(service, constants: Constants) -> str: """ Cleans the service string by removing unnecessary terms and formatting it for further processing. @@ -21,37 +22,37 @@ def clean_service(service) -> str: Returns: str: The cleaned service string. If the service is empty or contains only stop words, it returns an empty string. """ - + if string_utils.is_empty(service): return "" - + service = service.strip().upper() # Map common values - for acronym, full_form in code_constants.SYNONYM_MAP.items(): - service = re.sub(rf'\b{re.escape(acronym)}\b', full_form, service) - + for acronym, full_form in constants.SYNONYM_MAP.items(): + service = re.sub(rf"\b{re.escape(acronym)}\b", full_form, service) + # Remove other values - for term in code_constants.REMOVAL_LIST: - service = re.sub(rf'\b{re.escape(term)}\b', '', service).strip() - service = re.sub(r'\s+', ' ', service) + for term in constants.REMOVAL_LIST: + service = re.sub(rf"\b{re.escape(term)}\b", "", service).strip() + service = re.sub(r"\s+", " ", service) service = service.replace(" - ", "-") - if any([v in service for v in ['UNLISTED', 'UNCATEGORIZED', "NON-LISTED"]]): + if any([v in service for v in ["UNLISTED", "UNCATEGORIZED", "NON-LISTED"]]): return "UNLISTED" - + # Check if all remaining words are stop words remaining_words = service.split() - if all(word in code_constants.STOP_WORD_LIST for word in remaining_words): + if all(word in constants.STOP_WORD_LIST for word in remaining_words): return "" return service - + def get_regex_answers(service, code_answer_dict): """ **UNUSED** Extracts CPT and Revenue codes from the service string using regex patterns. - This function may be used in the future to save tokens for simple code extractions. It can be used if there is only one code match. + This function may be used in the future to save tokens for simple code extractions. It can be used if there is only one code match. If there are multiple matches, using this function risks losing information if we do not have a regex string for EVERY explicit code we need. Args: @@ -60,51 +61,56 @@ def get_regex_answers(service, code_answer_dict): Returns: dict: A dictionary containing the extracted codes. If no codes are found, returns an empty dictionary. """ - + # Define the regex pattern for CPT codes and ranges # Only accept single code or single code ranges - multiple codes may have complexities that need to be prompted for - cpt_pattern = r'\b(?:[A-Z]\d{4}|\d{5})(?:\s*(?:-|through)\s*(?:[A-Z]\d{4}|\d{5}))?\b' + cpt_pattern = ( + r"\b(?:[A-Z]\d{4}|\d{5})(?:\s*(?:-|through)\s*(?:[A-Z]\d{4}|\d{5}))?\b" + ) cpt_matches = re.findall(cpt_pattern, service) if len(cpt_matches) == 1: code_answer_dict["PROCEDURE_CD"] = cpt_matches[0].replace("through", "-") - + if "rev" in service.lower(): - rev_pattern = r'\b(?:\d{3}|\d{2}X)(?:\s*(?:-|through)\s*(?:\d{3}|\d{2}X))?\b' + rev_pattern = r"\b(?:\d{3}|\d{2}X)(?:\s*(?:-|through)\s*(?:\d{3}|\d{2}X))?\b" rev_matches = re.findall(rev_pattern, service) if len(rev_matches) > 0: code_answer_dict["REVENUE_CD"] = "|".join(rev_matches) return code_answer_dict + def code_explicit(service, filename): """ - Processes a service string and invokes a language model to generate a response - based on the provided primary questions. The response is then parsed into a + Processes a service string and invokes a language model to generate a response + based on the provided primary questions. The response is then parsed into a dictionary format. Args: - service (str): The service name or identifier. If empty, an empty dictionary + service (str): The service name or identifier. If empty, an empty dictionary is returned. - code_primary_questions (str): The primary questions or prompts to be used + code_primary_questions (str): The primary questions or prompts to be used for generating the response. - filename (str): The name of the file associated with the operation, used + filename (str): The name of the file associated with the operation, used for logging or tracking purposes. Returns: - dict: A dictionary containing the parsed response from the language model. + dict: A dictionary containing the parsed response from the language model. Returns an empty dictionary if the service string is empty. """ # Prompt - code_primary_questions = FieldSet(file_path=config.FIELD_JSON_PATH, field_type="code_primary_breakout").get_prompt_dict() + code_primary_questions = FieldSet( + file_path=config.FIELD_JSON_PATH, field_type="code_primary_breakout" + ).get_prompt_dict() claude_answer_raw = llm_utils.invoke_claude( - investment_prompts.CODE_EXPLICIT(service, code_primary_questions), - "sonnet_latest", - filename + investment_prompts.CODE_EXPLICIT(service, code_primary_questions), + "sonnet_latest", + filename, ) code_answer_dict = string_utils.universal_json_load(claude_answer_raw) return code_answer_dict -def code_category(service, proc_category, filename): +def code_category(service, proc_category, hcpcs_level2_mapping, filename): """ Determines if the service explicitly names a code category, such as "A-Codes" or "J-Codes", and returns the corresponding codes. Args: @@ -120,20 +126,28 @@ def code_category(service, proc_category, filename): matching_values = [] for category in code_category_list: - matching_values += [value for key, value in code_constants.hcpcs_level2_mapping.items() if key.startswith(category)] + matching_values += [ + value + for key, value in hcpcs_level2_mapping.items() + if key.startswith(category) + ] claude_answer_raw = llm_utils.invoke_claude( - investment_prompts.CODE_CATEGORY(service, matching_values), - "sonnet_latest", - filename - ) - claude_answer_final = string_utils.universal_json_load(claude_answer_raw) # Returns list + investment_prompts.CODE_CATEGORY(service, matching_values), + "sonnet_latest", + filename, + ) + claude_answer_final = string_utils.universal_json_load( + claude_answer_raw + ) # Returns list - code_answer_dict = {"PROCEDURE_CD" : [], "PROCEDURE_CD_DESC" : []} + code_answer_dict = {"PROCEDURE_CD": [], "PROCEDURE_CD_DESC": []} for answer in claude_answer_final: if "0000" in answer: code_answer_dict["PROCEDURE_CD"].append(answer) - elif answer in code_constants.hcpcs_level2_mapping.values(): - code = list(code_constants.hcpcs_level2_mapping.keys())[list(code_constants.hcpcs_level2_mapping.values()).index(answer)] + elif answer in hcpcs_level2_mapping.values(): + code = list(hcpcs_level2_mapping.keys())[ + list(hcpcs_level2_mapping.values()).index(answer) + ] code_answer_dict["PROCEDURE_CD"].append(code) code_answer_dict["PROCEDURE_CD_DESC"].append(answer) @@ -153,30 +167,30 @@ def code_implicit_special(service, filename): """ claude_answer_raw = llm_utils.invoke_claude( - investment_prompts.CODE_IMPLICIT_SPECIAL(service), - "sonnet_latest", - filename - ) - claude_answer_final = string_utils.extract_text_from_delimiters(claude_answer_raw, Delimiter.PIPE) + investment_prompts.CODE_IMPLICIT_SPECIAL(service), "sonnet_latest", filename + ) + claude_answer_final = string_utils.extract_text_from_delimiters( + claude_answer_raw, Delimiter.PIPE + ) special_case_mapping = { - "Drugs" : "J0000-J9999", - "Vaccines" : "J0000-J9999|90471‑90474|90620‑90621|90633|90647‑90648|90651|90670|90672|90680‑90681|90686|90696|90698|90700|90707|90710|90713|90714", - "Surgery" : "10004-69990", - "PT/OT/ST" : "92507-92508|92526|97014|97110|97112|97116|97150|97161-97168|97530|97535", - "PT" : "PT Codes TBD", - "OT" : "OT Codes TBD", - "ST" : "ST Codes TBD" + "Drugs": "J0000-J9999", + "Vaccines": "J0000-J9999|90471‑90474|90620‑90621|90633|90647‑90648|90651|90670|90672|90680‑90681|90686|90696|90698|90700|90707|90710|90713|90714", + "Surgery": "10004-69990", + "PT/OT/ST": "92507-92508|92526|97014|97110|97112|97116|97150|97161-97168|97530|97535", + "PT": "PT Codes TBD", + "OT": "OT Codes TBD", + "ST": "ST Codes TBD", } code_answer_dict = {} if not string_utils.is_empty(claude_answer_final): code_answer_dict["PROCEDURE_CD"] = special_case_mapping[claude_answer_final] code_answer_dict["PROCEDURE_CD_DESC"] = claude_answer_final - + return code_answer_dict -def get_embedding_levels(level_suffix, implicit_run_dict): +def get_embedding_levels(level_suffix, implicit_run_dict, constants: Constants): """ Retrieves the embedding levels and their corresponding mappings for a given level suffix. Args: @@ -190,29 +204,36 @@ def get_embedding_levels(level_suffix, implicit_run_dict): - rev_mapping (dict): Mapping for Revenue codes. """ if level_suffix == 1: - cpt_mapping = code_constants.cpt_level1_mapping - hcpcs_mapping = code_constants.hcpcs_level1_mapping - rev_mapping = code_constants.rev_level1_mapping + cpt_mapping = constants.CPT_LEVEL1_MAPPING + hcpcs_mapping = constants.HCPCS_LEVEL1_MAPPING + rev_mapping = constants.REV_LEVEL1_MAPPING embedding_levels = [] if implicit_run_dict["PROCEDURE_CD"]: - embedding_levels += [f"cpt_level{level_suffix}", f"hcpcs_level{level_suffix}",] + embedding_levels += [ + f"cpt_level{level_suffix}", + f"hcpcs_level{level_suffix}", + ] if implicit_run_dict["REVENUE_CD"]: - embedding_levels += [ f"rev_level{level_suffix}"] - + embedding_levels += [f"rev_level{level_suffix}"] + elif level_suffix == 2: - cpt_mapping = code_constants.cpt_level2_mapping - hcpcs_mapping = code_constants.hcpcs_level2_mapping - rev_mapping = code_constants.rev_mapping - + cpt_mapping = constants.CPT_LEVEL2_MAPPING + hcpcs_mapping = constants.HCPCS_LEVEL2_MAPPING + rev_mapping = constants.REV_MAPPING + embedding_levels = [] if implicit_run_dict["PROCEDURE_CD"]: - embedding_levels += [f"cpt_level{level_suffix}", f"hcpcs_level{level_suffix}",] + embedding_levels += [ + f"cpt_level{level_suffix}", + f"hcpcs_level{level_suffix}", + ] if implicit_run_dict["REVENUE_CD"]: - embedding_levels += [ f"rev"] + embedding_levels += [f"rev"] return embedding_levels, cpt_mapping, hcpcs_mapping, rev_mapping -def get_match_list(service, embedding_levels, top_k): + +def get_match_list(service, embedding_levels, constants: Constants, top_k: int): """ Retrieves the best matches for a given service from multiple embedding levels using FAISS index search. Args: @@ -224,23 +245,25 @@ def get_match_list(service, embedding_levels, top_k): - match_list (list): List of matched service descriptions. - highest_similarity (float): The highest similarity score among the matches. """ - target_vec = code_constants.model.encode([service], normalize_embeddings=True).astype("float32") + target_vec = constants.EMBEDDING_MODEL.encode( + [service], normalize_embeddings=True + ).astype("float32") match_list = [] highest_similarity = 0 for level in embedding_levels: - index, _, choices = embedding_utils.load_faiss_index(index_path=os.path.join("embeddings", level, "faiss_index.bin"), - embedding_path=os.path.join("embeddings", level, "embeddings.npy"), - choices_path=os.path.join("embeddings", level, "choices.pkl"),) - similarity_scores, match_indices = index.search(target_vec, top_k) + embedding = constants.get_embedding(level) + + similarity_scores, match_indices = embedding.index.search(target_vec, top_k) if max(similarity_scores[0]) > highest_similarity: highest_similarity = max(similarity_scores[0]) - + for idx, score in zip(match_indices[0], similarity_scores[0]): - match_list.append(choices[idx]) - + match_list.append(embedding.choices[idx]) + return match_list, highest_similarity -def code_implicit_rag(service, implicit_run_dict, filename): + +def code_implicit_rag(service, implicit_run_dict, filename, constants): """ Processes a service string to find implicit codes using RAG (Retrieval-Augmented Generation) methodology. Args: @@ -255,36 +278,50 @@ def code_implicit_rag(service, implicit_run_dict, filename): level_dicts = [] for level_suffix in [1, 2]: # Get Mappings and Embeddings - embedding_levels, cpt_mapping, hcpcs_mapping, rev_mapping = get_embedding_levels(level_suffix, implicit_run_dict) + embedding_levels, cpt_mapping, hcpcs_mapping, rev_mapping = ( + get_embedding_levels(level_suffix, implicit_run_dict, constants) + ) if not embedding_levels: return {} # Get Best Matches - match_list, highest_similarity = get_match_list(service, embedding_levels, top_k=5) - level_dicts.append({"level_suffix" : level_suffix, "match_list" : match_list, "highest_similarity" : highest_similarity}) + match_list, highest_similarity = get_match_list( + service, embedding_levels, constants, top_k=5 + ) + level_dicts.append( + { + "level_suffix": level_suffix, + "match_list": match_list, + "highest_similarity": highest_similarity, + } + ) # Sort level_answers by highest_similarity in descending order level_dicts.sort(key=lambda x: x["highest_similarity"], reverse=True) # Run the Implicit prompt for each level, starting from the highest similarity for level_dict in level_dicts: - embedding_levels, cpt_mapping, hcpcs_mapping, rev_mapping = get_embedding_levels(level_dict["level_suffix"], implicit_run_dict) + embedding_levels, cpt_mapping, hcpcs_mapping, rev_mapping = ( + get_embedding_levels( + level_dict["level_suffix"], implicit_run_dict, constants + ) + ) # Run Prompt claude_answer_raw = llm_utils.invoke_claude( - investment_prompts.CODE_IMPLICIT(service, level_dict["match_list"]), - "sonnet_latest", - filename - ) + investment_prompts.CODE_IMPLICIT(service, level_dict["match_list"]), + "sonnet_latest", + filename, + ) try: claude_answer_final = string_utils.universal_json_load(claude_answer_raw) except Exception as e: - return {"CODE_METHODOLOGY" : e} + return {"CODE_METHODOLOGY": e} if not claude_answer_final: continue - + # Populate answers, if any code_answer_dict = {} proc_codes, rev_codes = [], [] @@ -295,36 +332,47 @@ def code_implicit_rag(service, implicit_run_dict, filename): continue if description in cpt_mapping.values(): - matching_codes = [key for key, val in cpt_mapping.items() if val == description] + matching_codes = [ + key for key, val in cpt_mapping.items() if val == description + ] if matching_codes: proc_codes.append("|".join(matching_codes)) proc_descs.append(description) if description in hcpcs_mapping.values(): - matching_codes = [key for key, val in hcpcs_mapping.items() if val == description] + matching_codes = [ + key for key, val in hcpcs_mapping.items() if val == description + ] if matching_codes: proc_codes.append("|".join(matching_codes)) proc_descs.append(description) if description in rev_mapping.values(): - matching_codes = [key for key, val in rev_mapping.items() if val == description] + matching_codes = [ + key for key, val in rev_mapping.items() if val == description + ] if matching_codes: rev_codes.append("|".join(matching_codes)) rev_descs.append(description) - + # Combine answers if proc_codes: code_answer_dict["PROCEDURE_CD"] = "|".join(proc_codes) code_answer_dict["PROCEDURE_CD_DESC"] = "|".join(proc_descs) - code_answer_dict["CODE_METHODOLOGY"] = f"Implicit - Level {level_dict["level_suffix"]}" + code_answer_dict["CODE_METHODOLOGY"] = ( + f"Implicit - Level {level_dict["level_suffix"]}" + ) if rev_codes: code_answer_dict["REVENUE_CD"] = "|".join(rev_codes) code_answer_dict["REVENUE_CD_DESC"] = "|".join(rev_descs) - code_answer_dict["CODE_METHODOLOGY"] = f"Implicit - Level {level_dict["level_suffix"]}" + code_answer_dict["CODE_METHODOLOGY"] = ( + f"Implicit - Level {level_dict["level_suffix"]}" + ) if code_answer_dict: return code_answer_dict - + return {} + def code_last_check(service, filename): """ For Services that did not match any codes, this function categorizes the reason as "Specific" or "Generic". @@ -338,15 +386,15 @@ def code_last_check(service, filename): """ claude_answer_raw = llm_utils.invoke_claude( - investment_prompts.CODE_LAST_CHECK(service), - "sonnet_latest", - filename - ) - claude_answer_final = string_utils.extract_text_from_delimiters(claude_answer_raw, Delimiter.PIPE) + investment_prompts.CODE_LAST_CHECK(service), "sonnet_latest", filename + ) + claude_answer_final = string_utils.extract_text_from_delimiters( + claude_answer_raw, Delimiter.PIPE + ) return claude_answer_final -def fill_bill_type(service, answer_dict): +def fill_bill_type(service, answer_dict, BILL_TYPE_MAPPING, BILL_TYPE_REVERSE_MAPPING): """ Fills the BILL_TYPE_CD and BILL_TYPE_CD_DESC fields in the answer dictionary. @@ -358,29 +406,28 @@ def fill_bill_type(service, answer_dict): dict: The updated answer dictionary. """ - valid_bill_type = sorted(list(set(code_constants.bill_type_mapping.values()))) + valid_bill_type = sorted(list(set(BILL_TYPE_MAPPING.values()))) claude_answer_raw = llm_utils.invoke_claude( - investment_prompts.FILL_BILL_TYPE(service, valid_bill_type), - "sonnet_latest", - "" - ) + investment_prompts.FILL_BILL_TYPE(service, valid_bill_type), "sonnet_latest", "" + ) claude_answer_final = string_utils.universal_json_load(claude_answer_raw) if not claude_answer_final: return answer_dict - + bill_codes, bill_descs = [], [] for description in claude_answer_final: - if description in code_constants.bill_type_reverse_mapping: - bill_codes.append(code_constants.bill_type_reverse_mapping[description]) + if description in BILL_TYPE_REVERSE_MAPPING: + bill_codes.append(BILL_TYPE_REVERSE_MAPPING[description]) bill_descs.append(description) - + if bill_codes: answer_dict["BILL_TYPE_CD"] = "|".join(bill_codes) answer_dict["BILL_TYPE_CD_DESC"] = "|".join(bill_descs) return answer_dict + def get_implicit_runs(answer_dict): """ Determines which code runs should be executed implicitly based on the Claim Type and Bill Type @@ -392,7 +439,7 @@ def get_implicit_runs(answer_dict): """ run_dict = {} - + claim_type = answer_dict.get("AARETE_DERIVED_CLAIM_TYPE_CD") bill_type = answer_dict.get("BILL_TYPE_CD_DESC") @@ -414,15 +461,16 @@ def get_implicit_runs(answer_dict): return run_dict -def extract_codes_from_service(answer_dict): + +def extract_codes_from_service(answer_dict, constants: Constants): """ Extracts codes from the service term in the answer dictionary. - + Args: answer_dict (dict): Dictionary containing, at minimum, the SERVICE_TERM field. Returns: dict: Dictionary containing extracted codes and their descriptions. - + Codes Extracted: Explicit Codes: - PROCEDURE_CD: CPT, HCPCS @@ -438,32 +486,45 @@ def extract_codes_from_service(answer_dict): - REVENUE_CD: Revenue codes """ - service, bill_type = answer_dict.get("SERVICE_TERM"), answer_dict.get("BILL_TYPE_CD_DESC") + service, bill_type = answer_dict.get("SERVICE_TERM"), answer_dict.get( + "BILL_TYPE_CD_DESC" + ) # Fill Bill Type if not filled if string_utils.is_empty(bill_type): - answer_dict = fill_bill_type(service, answer_dict) + answer_dict = fill_bill_type( + service, + answer_dict, + constants.BILL_TYPE_MAPPING, + constants.BILL_TYPE_REVERSE_MAPPING, + ) # Get list of codes we want implicit - based on claim type implicit_run_dict = get_implicit_runs(answer_dict) - + # Preprocessing - service_clean = clean_service(service) + service_clean = clean_service(service, constants) # Exit point for unlisted if service_clean == "UNLISTED": answer_dict["PROCEDURE_CD_DESC"] = "UNLISTED" answer_dict["CODE_METHODOLOGY"] = "UNLISTED" return answer_dict - + # Exit point if N/A or in DO_NOT_RUN list - if string_utils.is_empty(service_clean) or any([v in service_clean for v in code_constants.DO_NOT_RUN]): + if string_utils.is_empty(service_clean) or any( + [v in service_clean for v in constants.DO_NOT_RUN] + ): answer_dict["CODE_METHODOLOGY"] = "Generic - Before Prompts" return answer_dict # Explicit Codes (run always) code_answer_dict = code_explicit(service_clean, "") - if any(not string_utils.is_empty(value) for value in code_answer_dict.values()) and all(["Category" not in v for v in code_answer_dict.get("PROCEDURE_CD", "")]): # if any code value is not empty, return + if any( + not string_utils.is_empty(value) for value in code_answer_dict.values() + ) and all( + ["Category" not in v for v in code_answer_dict.get("PROCEDURE_CD", "")] + ): # if any code value is not empty, return if "NOT_ESTABLISHED" in code_answer_dict.get("PROCEDURE_CD", ""): code_answer_dict["CODE_METHODOLOGY"] = "Implicit - Not Established" code_answer_dict["PROCEDURE_CD"] = "[]" @@ -474,8 +535,15 @@ def extract_codes_from_service(answer_dict): # Implicit Codes: Code Categories if any(["Category:" in v for v in code_answer_dict.get("PROCEDURE_CD", "")]): - code_answer_dict = code_category(service_clean, code_answer_dict.get("PROCEDURE_CD", ""), "") - if any(not string_utils.is_empty(value) for value in code_answer_dict.values()): # if any code value is not empty, return + code_answer_dict = code_category( + service_clean, + code_answer_dict.get("PROCEDURE_CD", ""), + constants.HCPCS_LEVEL2_MAPPING, + "", + ) + if any( + not string_utils.is_empty(value) for value in code_answer_dict.values() + ): # if any code value is not empty, return code_answer_dict["CODE_METHODOLOGY"] = "Implicit - Letter Category" answer_dict.update(code_answer_dict) return answer_dict @@ -488,11 +556,15 @@ def extract_codes_from_service(answer_dict): return answer_dict # Implicit Codes: Levels 1 and 2 - code_answer_dict = code_implicit_rag(service_clean, implicit_run_dict, "") - if any(not string_utils.is_empty(value) for value in code_answer_dict.values()): # if any code value is not empty, return + code_answer_dict = code_implicit_rag( + service_clean, implicit_run_dict, "", constants + ) + if any( + not string_utils.is_empty(value) for value in code_answer_dict.values() + ): # if any code value is not empty, return answer_dict.update(code_answer_dict) return answer_dict - + # No Match - Why? Generic or Specific last_check_answer = code_last_check(service_clean, "") if last_check_answer == "Generic": @@ -512,10 +584,14 @@ def fill_claim_type(answer_dicts): Returns: list[dict]: List of dictionaries with AARETE_DERIVED_CLAIM_TYPE """ - claim_types = [d.get("AARETE_DERIVED_CLAIM_TYPE_CD") for d in answer_dicts if d.get("AARETE_DERIVED_CLAIM_TYPE_CD")] + claim_types = [ + d.get("AARETE_DERIVED_CLAIM_TYPE_CD") + for d in answer_dicts + if d.get("AARETE_DERIVED_CLAIM_TYPE_CD") + ] if not claim_types: return answer_dicts - + mode_claim_type = max(set(claim_types), key=claim_types.count) for answer_dict in answer_dicts: if not answer_dict.get("AARETE_DERIVED_CLAIM_TYPE_CD"): @@ -523,8 +599,7 @@ def fill_claim_type(answer_dicts): return answer_dicts - -def code_breakout(merged_results: pd.DataFrame): +def code_breakout(merged_results: pd.DataFrame, constants: Constants): """ Processes a list of answer dictionaries to extract and fill in code-related information. Wrapper function to be run from main Doczy @@ -534,19 +609,17 @@ def code_breakout(merged_results: pd.DataFrame): Returns: pd.DataFrame: DataFrame with updated code fields. """ - + # Fill empty AARETE_DERIVED_CLAIM_TYPE_CD with mode - answer_dicts = fill_claim_type(merged_results.to_dict(orient='records')) + answer_dicts = fill_claim_type(merged_results.to_dict(orient="records")) final_answer_dicts = [] for answer_dict in answer_dicts: - code_answer_dict = extract_codes_from_service(answer_dict) + code_answer_dict = extract_codes_from_service(answer_dict, constants) answer_dict.update(code_answer_dict) final_answer_dicts.append(answer_dict) - + # Convert back to DataFrame final_answer_df = pd.DataFrame(final_answer_dicts) - + return final_answer_df - - diff --git a/fieldExtraction/src/codes/main.py b/fieldExtraction/src/codes/main.py index 63d9a4a..67a5f9b 100644 --- a/fieldExtraction/src/codes/main.py +++ b/fieldExtraction/src/codes/main.py @@ -1,38 +1,52 @@ - -import src.utils.io_utils as io_utils -import src.codes.code_funcs as code_funcs - -import src.codes.code_constants as code_constants -import src.config as config import concurrent.futures +from sentence_transformers import SentenceTransformer + +import src.codes.code_funcs as code_funcs +import src.config as config +import src.utils.io_utils as io_utils +from constants.constants import Constants + + def main(): - df = io_utils.read_local(code_constants.INPUT_FILE_PATH) - + # Constants + INPUT_FILE_PATH = "Codes-Test-Doc.xlsx" + OUTPUT_FILE_PATH = "Doczy-Codes-Test-9.csv" + + df = io_utils.read_local(INPUT_FILE_PATH) + + # Read Constants + constants = Constants() + model = SentenceTransformer("all-roberta-large-v1") + # Process entire rows instead of just services row_indices = list(range(len(df))) - + # Define a function to process a single row def process_row(idx): try: # Create answer_dict from the row row = df.iloc[idx] answer_dict = { - "SERVICE_TERM": str(row[code_constants.SERVICE_COLUMN]), - "AARETE_DERIVED_CLAIM_TYPE_CD": str(row.get("AARETE_DERIVED_CLAIM_TYPE_CD", "")), - "BILL_TYPE_CD_DESC": str(row.get("BILL_TYPE_CD_DESC", "")) + "SERVICE_TERM": str(row["SERVICE_TERM"]), + "AARETE_DERIVED_CLAIM_TYPE_CD": str( + row.get("AARETE_DERIVED_CLAIM_TYPE_CD", "") + ), + "BILL_TYPE_CD_DESC": str(row.get("BILL_TYPE_CD_DESC", "")), } - + # Process the row result_dict = code_funcs.extract_codes_from_service(answer_dict) return idx, result_dict except Exception as e: print(f"Error processing row {idx}: {e}") - return idx, {"SERVICE_TERM": str(row[code_constants.SERVICE_COLUMN]), "ERROR": str(e)} + return idx, {"SERVICE_TERM": str(row["SERVICE_TERM"]), "ERROR": str(e)} # Use ThreadPoolExecutor to process rows in parallel results = {} - with concurrent.futures.ThreadPoolExecutor(max_workers=config.MAX_WORKERS) as executor: + with concurrent.futures.ThreadPoolExecutor( + max_workers=config.MAX_WORKERS + ) as executor: futures = {executor.submit(process_row, idx): idx for idx in row_indices} for future in concurrent.futures.as_completed(futures): try: @@ -42,18 +56,21 @@ def main(): idx = futures[future] print(f"Error retrieving result for row {idx}: {e}") row = df.iloc[idx] - results[idx] = {"SERVICE_TERM": str(row[code_constants.SERVICE_COLUMN]), "ERROR": str(e)} + results[idx] = { + "SERVICE_TERM": str(row["SERVICE_TERM"]), + "ERROR": str(e), + } # Update the DataFrame with the results for idx, result_dict in results.items(): for key, value in result_dict.items(): if key not in df.columns: df[key] = None - + # Ensure value is scalar if isinstance(value, (list, dict)): value = str(value) # Convert lists/dicts to strings - + try: # Update the specific cell df.at[idx, key] = value @@ -62,9 +79,9 @@ def main(): # Save the updated DataFrame try: - if code_constants.OUTPUT_FILE_PATH: - df.to_csv(code_constants.OUTPUT_FILE_PATH, index=False) - print(f"Results saved to {code_constants.OUTPUT_FILE_PATH}") + if OUTPUT_FILE_PATH: + df.to_csv(OUTPUT_FILE_PATH, index=False) + print(f"Results saved to {OUTPUT_FILE_PATH}") else: print("No output path specified. Results not saved.") except Exception as e: @@ -82,6 +99,3 @@ def main(): if __name__ == "__main__": main() - - - diff --git a/fieldExtraction/src/config.py b/fieldExtraction/src/config.py index 7a8215f..221a110 100644 --- a/fieldExtraction/src/config.py +++ b/fieldExtraction/src/config.py @@ -54,6 +54,7 @@ def check_s3_bucket_exists(s3_client, bucket_name: str): raise return True + ######################################## GENERAL SETTINGS ######################################## TEST = False # True to run test prompt - just for testing model connection TODAY = datetime.now().strftime("%Y%m%d") @@ -143,7 +144,7 @@ MAX_WORKERS = int(get_arg_value("max_workers", 2)) # Valid: any integer # default false for the regex based IRS chunking IRS_REGEX_CHECK = get_arg_value("irs_regex_check", False) == "True" -#QCQA args - only need to be set when running qa_qc.py INDEPENDENTLY, not needed for at-scale runs +# QCQA args - only need to be set when running qa_qc.py INDEPENDENTLY, not needed for at-scale runs AC_DF = get_arg_value("ac_df", None) B_DF = get_arg_value("b_df", None) DF_READ_MODE = get_arg_value("df_read_mode", "s3") @@ -152,27 +153,26 @@ DF_READ_MODE = get_arg_value("df_read_mode", "s3") ######################################## FILE LOG ######################################## FILE_LOG_FIELDS = [ - 'filename', - 'process_type', - 'start_time', - 'end_time', - 'processing_time_seconds', - 'delta_time_hours', - 'status', - 'error', - 'memory_usage_mb', - 'cpu_percent' + "filename", + "process_type", + "start_time", + "end_time", + "processing_time_seconds", + "delta_time_hours", + "status", + "error", + "memory_usage_mb", + "cpu_percent", ] -FILE_LOG_NAME = f'{BATCH_ID}_filelog.csv' +FILE_LOG_NAME = f"{BATCH_ID}_filelog.csv" if not os.path.exists(FILE_LOG_NAME): - with open(FILE_LOG_NAME, 'w', newline='', encoding='utf-8') as f: + with open(FILE_LOG_NAME, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=FILE_LOG_FIELDS) writer.writeheader() - ######################################## CLIENT SETTINGS ######################################## CLIENT_NAME = get_arg_value("client_name", "Test-Client") PROV_TYPE = None @@ -220,7 +220,9 @@ if RUN_MODE == "local": elif RUN_MODE == "ec2": # Bedrock - config = Config(read_timeout=4000, max_pool_connections=60) # Doubled read_timeout to 4000 + config = Config( + read_timeout=4000, max_pool_connections=60 + ) # Doubled read_timeout to 4000 BEDROCK_RUNTIME = boto3.client( service_name="bedrock-runtime", region_name="us-east-1", config=config ) @@ -247,6 +249,7 @@ MODEL_ALIASES = { "legacy_sonnet": MODEL_ID_CLAUDE35_SONNET, } + def resolve_model_id(model_identifier: str) -> str: """ Resolve a model identifier to its actual model ID. @@ -254,13 +257,14 @@ def resolve_model_id(model_identifier: str) -> str: """ return MODEL_ALIASES.get(model_identifier, model_identifier) + ######################################## CLAUDE AND PROMPT TRACKING ######################################## ENABLE_ANSWER_TRACKING = False ANSWER_TRACKING_DICT = {} ######################################## POSTPROCESSING SETTINGS ######################################## FUZZY_MATCH_THRESHOLD = 0.8 -FILE_NAME_COLUMN = 'Contract Name' +FILE_NAME_COLUMN = "Contract Name" BLANKS_THRESHOLD = 0.5 ######################################## COMPLEX FLAG SETTINGS ######################################## @@ -321,9 +325,9 @@ dev_role_name = "doczy-dev-bedrock-lambda-role" prod_account_id = "211125720533" prod_role_name = "doczy-prod-bedrock-lambda-role" -############## S3 BUCKETS FOR PDF FILES ############## -DOCZY_PDF_FILES_BUCKET_NAME = "doczy-pdf-files" # sample bucket name added. we can either use this or create a new one -DOCZY_PDF_FILES_BUCKET_S3_URL = f"https://{DOCZY_PDF_FILES_BUCKET_NAME}.s3.us-east-2.amazonaws.com/" # sample s3 url added. we can either use this or create a new one +############## S3 BUCKETS FOR PDF FILES ############## +DOCZY_PDF_FILES_BUCKET_NAME = "doczy-pdf-files" # sample bucket name added. we can either use this or create a new one +DOCZY_PDF_FILES_BUCKET_S3_URL = f"https://{DOCZY_PDF_FILES_BUCKET_NAME}.s3.us-east-2.amazonaws.com/" # sample s3 url added. we can either use this or create a new one ############## VISION API SETTINGS ############## -ENABLE_VISION = get_arg_value("enable_vision", "False") == "True" # False by default \ No newline at end of file +ENABLE_VISION = get_arg_value("enable_vision", "False") == "True" # False by default diff --git a/fieldExtraction/src/constants/__init__.py b/fieldExtraction/src/constants/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/fieldExtraction/src/constants/investment_values.py b/fieldExtraction/src/constants/investment_values.py deleted file mode 100644 index 86e939e..0000000 --- a/fieldExtraction/src/constants/investment_values.py +++ /dev/null @@ -1,179 +0,0 @@ - -from crosswalk.crosswalk_utils import CrosswalkBuilder - -EXHIBIT_HEADER_MARKERS = [ - "EXHIBIT", - "ATTACHMENT", - "ARTICLE", - "AMENDMENT", - "SCHEDULE", - "ADDENDUM", - "APPENDIX", -] - -# Dynamic Primary -VALID_LOBS = list(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_lob.json").mapping.keys()) -VALID_PROGRAMS = list(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_program.json").mapping.keys()) -VALID_NETWORKS = list(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_network.json").mapping.keys()) -VALID_PRODUCTS = list(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_product_lob.json").mapping.keys()) - -# Dynamic Codes -VALID_PROV_SPECIALTY = list(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_provider_specialty.json").mapping.keys()) -VALID_BILL_TYPE = list(set(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_bill_type.json").mapping.values())) -VALID_PLACE_OF_SERVICE = list(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_place_of_service.json").mapping.keys()) -VALID_CLAIM_TYPE = list(set(CrosswalkBuilder().from_json(path="crosswalk/mappings/crosswalk_claim_type.json").mapping.keys())) - -# Provider Type -VALID_PROV_TYPE = [ - # Professional - "Physician", - "Primary Care Provider (PCP)", - "Dual Capacity Physician", - "Mid-Level Practicioner", - "Primary Care Mid-Level Practicioner", - "Pediatrician", - "Specialist", - "OB/GYN", - "Behavioral Health", - "Therapist", - "Surgery", - "Ambulance Services", - "Home Health", - "Durable Medical Equipment (DME)", - "Radiology", - "Lab", - "Hospice", - "Dialysis", - "Physical Therapy, Occupational Therapy, Speech Therapy (PT/OT/ST)", - "Urgent Care", - "Home Infusion", - # Facility - "Ambulatory Surgery Center (ASC)", - "Skilled Nursing Facility (SNF)", - "Hospital", - "Clinic", - "Rural Health Clinic (RHC)", - "Federally Qualified Health Center (FQHC)", - "Long Term Care", - "Community Mental Health Center", -] - - -VALID_AARETE_DERIVED_FEE_SCHEDULE = [ - "CMS", - "State", - "Proprietary", - "AWP", - "WAC", - "ASP" -] - -VALID_AARETE_DERIVED_FEE_SCHEDULE_VERSION = [ - "current", - "year prior" -] - -# AARETE_DERIVED_REIMB_METHOD -VALID_REIMB_TERM = [ - "Billed Charges", - "Fee Schedule", - "Flat Rate", # Per line - "Per Visit", # multiple lines - "Per Diem", # claim level - "Case Rate", # multiple claim level - # "MedRx", - "Cost", - # "Pass Through", - "Grouper", - # "Outlier", - # "Stop-Loss", - # "Reductions" - "DOFR", -] - -VALID_UNIT_OF_MEASURE = [ - "Per Unit", - "Per ASA Unit", - "Per Day", - "Per 15 Minutes", - "Per 30 Minutes", - "Per Hour", - "Per Month", - "Per Member Per Month (PMPM)", # this is invalid and will be removed in postprocessing - "Per Year", - "Per Session", - "Per Training Session", - "Per Visit", - "Per Case", - "Per Procedure", -] - -VALID_CARVEOUTS = { - "BASE_COVERED_SERVICES" : "Describes generic payments for standard covered services. This refers to the payment made for a service that is not subject to any adjustments. Example of base methodology:\n- 'covered services'\n- 'Covered Services not included in the Service Categories in Table 1 shall be reimbursed at 100% of the Medicare fee schedule.'", - "PAYMENT_CARVEOUT" : "Describes payments for services or codes that are separately mentioned or carved out from the standard payment for all covered services. Examples include 'Radiology covered services', 'Anesthesia covered services', 'Pharmaceuticals' and 'HEMATOCRIT (CPT CODE: 85014)'. Any service or code that is specifically or separately mentioned falls under the 'payment_carveout' category.", - "UNLISTED_CODE" : "Describes payments for codes that are not listed on a fee schedule. This is similar to DEFAULT_TERM. Only populate with Y if the Service or Methodology specifically mentions Codes.", - "NEVER_EVENT" : "Indicates that Never Events (e.g. sepsis acquired while inpatient) will not be covered. This may be phrased as 'no liability' or 'no payment'.", - "EXPERIMENTAL_INVESTIGATIONAL" : "Describes payments for experimental and investigational procedures.", - "NOT_MEDICALLY_NECESSARY" : "Describes payments for procedures deemed not medically necessary (or medically unnecessary)", - "GLOBAL_SERVICES" : None, - "BUNDLED_CODES" : "Indicates that payment for bundles of codes shall be at a reduced rate.", - "MULTIPLE_PROCEDURE_REDUCTIONS" : "Describe cases where payment terms apply to multiple or subsequent procedures or surgeries. It contains language indicating reduced payment for second and subsequent procedures, such as: 'multiple procedure reductions', 'second and subsequent deliveries' or similar terms that imply payment adjustments for multiple procedures performed during the same encounter.", - #"BILATERAL" : "", - "MIDLEVELS" : None, - "TECHNICAL_COMPONENT" : "If Modifier TC (technical component) is present.", - "PROFESSIONAL_COMPONENT" : "If Modifier 26 (professional component) is present.", - "DIAGNOSTIC_PROCEDURES" : "Describes payments for diagnostic procedures.", - "PREADMISSION" : None, - "POST_DISCHARGE" : None, - "READMISSIONS" : "Describes payment for subsequent admission with the same diagnostic category of codes as the initial admission.", - "DEFAULT_TERM" : "Indicates whether this service/methodology serves as a default/fallback payment rate when no other payment methods mentioned anywhere in the contract apply. The default/fallback/'end-of-the-line' payment rate may be a lower rate, often based on billed charges. Do not classify as default if:\n- The methodology is the primary/base reimbursement approach, even if defined by exclusion\n- The methodology applies to specific listed services/procedures \n- No default/fallback language is present\n\nExample: 'For any services not specifically listed in Exhibits A-D, reimbursement shall be 50% of billed charges.'", - # + Any Reimbursement Carveouts (Codes, Services, etc) -} - -VALID_SPECIAL = { - "OUTLIER" : "Describes additional payments built into standard reimbursement methodologies that are triggered when specific claims exceed predetermined cost thresholds. Often labeled as 'Outlier' in contracts, though the threshold may be called a 'Stop-Loss threshold'. Look for automatic payment adjustments based on claim cost.", - "STOP_LOSS" : "Describes financial safety net provisions primarily designed to protect providers against extreme losses. May apply at individual claim or aggregate levels and typically has higher thresholds than standard outlier provisions. Look for provisions specifically labeled as 'Stop Loss' that function separately from the standard payment methodology.", - "SEQUESTRATION" : "Describes a government-mandated reduction in federal spending for a period of time. Look for the term 'Sequestration'.", - "DISCOUNT" : "Describes a discount to the payment. Look for the term 'Discount'.", # placeholder prompt - "PREMIUM" : "Describes a premium to the payment. Look for the term 'Premium'.", # placeholder prompt - "ADDITION" : "Describes a payment where an additional amount is added to the base reimbursement rate for individual claims. This can be a fixed dollar amount, a percentage of the base reimbursement, or a variable amount derived from the base rate that may change over time based on specified conditions, schedules, or specific services or procedures. Exclude cases where: \n- No specific amount or percentage tied to the base reimbursement of an individual claim is stated, \n- It is unclear whether the payment applies to individual claims, or \n- The payment is dependent on total compensation to Participating IPA Providers.", - "TRIGGER_CAP": "Describes the payments that trigger a complete shift to a new payment methodology after a defined quantitative threshold is reached. Unlike supplemental payments, this fundamentally changes how reimbursement is calculated beyond the threshold. Valid thresholds can be based on dollar amounts, number of days, visits, units, or other measurable metrics. However, the following scenarios are NOT considered trigger caps: (1) Payment variations based solely on the timing of an event, such as discharge or admission dates (e.g., different payment if discharge occurs before or after a certain day of the month); (2) Thresholds based on the number of births (e.g., 'If the number of deliveries exceeds 17 per 1,000 members, an additional payment of $2,193 per birth will apply'); (3) Administrative pricing mechanisms such as Charge Description Master (CDM) increases or decreases; (4) Reimbursement limits tied to Acquisition Cost validations (e.g., payments capped at cost plus a percentage); (5) Provisions labeled as 'Stop-Loss' in either the service description or reimbursement text — even if a threshold and rate change are present — as these are considered risk protection mechanisms rather than true shifts in payment methodology; 6) Conditional changes in payment based on service context (e.g., ER vs. admission, Observation vs. surgery) without a numeric threshold do not qualify or Conditional reimbursement rules based on service combinations. Look for language indicating a clear change in the payment methodology when a defined quantitative threshold is exceeded, such as 'when charges exceed X, pay Y', 'above X, reimburse at Y%', or 'over X threshold, payment becomes Y','when charges exceed $50,000, pay 80%' or 'after 3 days, payment changes to $1,200 per diem'.", - "RATE_ESCALATOR" : "Describes a payment arrangement where the reimbursement rate automatically increases by a specified percentage annually after an payment rate defined for the initial term. Exception: Does not include predetermined fixed payment amounts scheduled at specific dates (e.g., $860 in 2008, $925 in 2009, $994 in 2010)." - #"CHARGEMASTER_INCREASE" : "" -} - -proc_levels = ["cpt", "hcpcs", "cpt_level3", "cpt_level2", "hcpcs_level2", "cpt_level1", "hcpcs_level1"] -diag_levels = ["diag"] - -### Reimbursement splitting keywords -# Patterns that indicate compound reimbursements -compound_indicators = [ - # Exception/fallback patterns - "for services where there is no", - "where no rate exists", - "if no rate is available", - "for which there is no", - "when no payment rate", - "where there is no payment rate", - - # Alternative methodology patterns - "otherwise shall be", - "shall otherwise be", - "alternatively", - "in the alternative", - "or alternatively", - - # Conditional patterns - "for certain services", - "for specific services", - "for other services", - "for all other", - "for any services", - - # Exception clauses - "except for", - "with the exception of", - "excluding", - "however, for", - "notwithstanding" - ] \ No newline at end of file diff --git a/fieldExtraction/crosswalk/README.md b/fieldExtraction/src/crosswalk/README.md similarity index 100% rename from fieldExtraction/crosswalk/README.md rename to fieldExtraction/src/crosswalk/README.md diff --git a/fieldExtraction/crosswalk/crosswalk_utils.py b/fieldExtraction/src/crosswalk/crosswalk_builder.py similarity index 67% rename from fieldExtraction/crosswalk/crosswalk_utils.py rename to fieldExtraction/src/crosswalk/crosswalk_builder.py index c09cebd..8cedb1c 100644 --- a/fieldExtraction/crosswalk/crosswalk_utils.py +++ b/fieldExtraction/src/crosswalk/crosswalk_builder.py @@ -1,11 +1,10 @@ import csv import json -import pandas as pd -import ast -from src.config import STATE -from src.config import CLIENT -import src.utils.string_utils as string_utils +import pandas as pd + +from src.config import CLIENT, STATE + class CrosswalkBuilder: def __init__(self): @@ -26,6 +25,8 @@ class CrosswalkBuilder: Returns: CrosswalkBuilder: the builder instance for method chaining """ + path = str(path) + if ".xlsx" in path: if sheet_name is None: # If sheet_name is not provided, read the first sheet @@ -35,7 +36,9 @@ class CrosswalkBuilder: elif ".csv" in path: df = pd.read_csv(path) else: - raise ValueError(f"Extension '{path.split(".")[-1]}' not a valid file extension.") + raise ValueError( + f"Extension '{path.split(".")[-1]}' not a valid file extension." + ) if from_col not in df.columns: raise ValueError(f"Column '{from_col}' not found in the excel file") @@ -73,10 +76,7 @@ class CrosswalkBuilder: return self def from_df( - self, - df: pd.DataFrame, - from_col: str, - to_col: str + self, df: pd.DataFrame, from_col: str, to_col: str ) -> "CrosswalkBuilder": """Load mapping from a DataFrame @@ -95,7 +95,6 @@ class CrosswalkBuilder: self.mapping = dict(zip(df[from_col], df[to_col])) return self - def from_json(self, path: str) -> "CrosswalkBuilder": """Load mapping from a JSON file @@ -169,67 +168,3 @@ class CrosswalkBuilder: # Then join the lists with commas return {target: "|".join(sources) for target, sources in reverse_dict.items()} - -def apply_crosswalk( - val: str, mapping: dict[str, str], default: str = "" -) -> str: - """Apply a crosswalk to a value - - Args: - val (str): Input value - mapping (dict[str, str]): Mapping dictionary - default (str | None, optional): Default value; if None and val is not found in the mapping, val is returned. Defaults to "". - - Returns: - str: Crosswalked value - """ - # Handle None or blank values defensively - if val is None: - return default - - # Store original for fallback - original_val = str(val).strip() - - # IF empty after stripping, return default - if string_utils.is_empty(original_val): - return default if default else original_val - - # Ensure val is a string - val = original_val - - try: - if "[" in val and "]" in val: - # Try to parse as literal, but handle any structure returned - parsed_val = ast.literal_eval(val) - values = string_utils.flatten_to_strings(parsed_val) - elif "," in val: - values = [v.strip() for v in val.split(",")] # Split by comma if present - elif "|" in val: - values = [v.strip() for v in val.split("|")] # Split by pipe if present - else: - values = [val.strip()] - except (ValueError, SyntaxError): - # If parsing fails, treat val as a regular string - values = [val.strip()] - - # Apply mapping with defensive string handling - mapped_values = set() - - for v in values: - v_str = str(v).strip() - if v_str in mapping: - mapped_result = mapping[v_str] - if mapped_result and not string_utils.is_empty(mapped_result): - mapped_values.add(mapped_result) - elif v_str in mapping.values(): - mapped_values.add(v_str) # If the value is already a mapped value, add it directly - - # Convert to list and filter out empty values - final_mapped = [v for v in mapped_values if v and not string_utils.is_empty(v)] - - if len(final_mapped) == 1: - return final_mapped[0] - elif len(final_mapped) > 1: - return "|".join(final_mapped) - else: - return default if default else original_val diff --git a/fieldExtraction/src/crosswalk/list_builder.py b/fieldExtraction/src/crosswalk/list_builder.py new file mode 100644 index 0000000..d86b5ca --- /dev/null +++ b/fieldExtraction/src/crosswalk/list_builder.py @@ -0,0 +1,249 @@ +import csv +import json +from typing import Any, Callable, List, Optional + +import pandas as pd + + +class ListBuilder: + def __init__(self): + self.list: list[str] = [] + self.metadata: dict[str, Any] = {} + + def from_excel( + self, path: str, column: str, sheet_name: Optional[str] = None + ) -> "ListBuilder": + """Load list items from an excel file column + + Args: + path (str): Location of the excel file + column (str): Column name to extract values from + sheet_name (str|None, optional): Sheet name in the excel file. Defaults to None. + + Returns: + ListBuilder: The builder instance for method chaining + """ + if ".xlsx" in path: + if sheet_name is None: + # If sheet_name is not provided, read the first sheet + df = pd.read_excel(path, sheet_name=0) + else: + df = pd.read_excel(path, sheet_name=sheet_name) + elif ".csv" in path: + df = pd.read_csv(path) + else: + raise ValueError( + f"Extension '{path.split('.')[-1]}' not a valid file extension." + ) + + if column not in df.columns: + raise ValueError(f"Column '{column}' not found in the file") + + self.metadata = { + "source_column": column, + "source_file": path, + } + + # Convert column to list and filter out None/NaN values + self.list = df[column].dropna().astype(str).tolist() + + return self + + def from_list( + self, items: list[str], source_info: Optional[str] = None + ) -> "ListBuilder": + """Load items from an existing list + + Args: + items (list[str]): List of items to store + source_info (str|None, optional): Information about the source. Defaults to None. + + Returns: + ListBuilder: The builder instance for method chaining + """ + self.metadata = { + "source_info": source_info, + } + self.list = [str(item) for item in items if item is not None] + return self + + def from_df(self, df: pd.DataFrame, column: str) -> "ListBuilder": + """Load list items from a DataFrame column + + Args: + df (pd.DataFrame): DataFrame containing the list items + column (str): Column name to extract values from + + Returns: + ListBuilder: The builder instance for method chaining + """ + if column not in df.columns: + raise ValueError(f"Column '{column}' not found in the DataFrame") + + self.metadata = { + "source_column": column, + } + + # Convert column to list and filter out None/NaN values + self.list = df[column].dropna().astype(str).tolist() + + return self + + def from_json(self, path: str, key: Optional[str] = None) -> "ListBuilder": + """Load list from a JSON file + + Args: + path (str): Path to the JSON file + key (str|None, optional): Key to extract the list from. If None, assumes JSON is a list. + + Returns: + ListBuilder: The builder instance for method chaining + """ + with open(path, "r") as f: + data = json.load(f) + + if key is not None: + if key not in data: + raise ValueError(f"Key '{key}' not found in the JSON file") + items = data[key] + + # Store additional metadata if available + if isinstance(data, dict): + self.metadata = {k: v for k, v in data.items() if k != key} + else: + items = data + self.metadata = {"source_file": path} + + # Ensure we have a list and all items are strings + if not isinstance(items, list): + raise ValueError(f"Data under key '{key}' is not a list") + + self.list = [str(item) for item in items if item is not None] + return self + + def from_dict_keys( + self, dictionary: dict, source_info: Optional[str] = None + ) -> "ListBuilder": + """Extract keys from a dictionary to form a list + + Args: + dictionary (dict): Dictionary to extract keys from + source_info (str|None, optional): Information about the source. Defaults to None. + + Returns: + ListBuilder: The builder instance for method chaining + """ + self.metadata = {"source_info": source_info, "source_type": "dict_keys"} + self.list = [str(key) for key in dictionary.keys()] + return self + + def from_dict_values( + self, dictionary: dict, source_info: Optional[str] = None + ) -> "ListBuilder": + """Extract values from a dictionary to form a list + + Args: + dictionary (dict): Dictionary to extract values from + source_info (str|None, optional): Information about the source. Defaults to None. + + Returns: + ListBuilder: The builder instance for method chaining + """ + self.metadata = {"source_info": source_info, "source_type": "dict_values"} + self.list = [str(value) for value in dictionary.values() if value is not None] + return self + + def save(self, output_path: str) -> None: + """Save the list to JSON. + + Args: + output_path (str): Path to save the list + """ + output_dict = {"metadata": self.metadata, "items": self.list} + with open(output_path, "w") as f: + json.dump(output_dict, f, indent=4) + + def to_csv(self, output_path: str, column_name: str = "item") -> None: + """Save the list to CSV. + + Args: + output_path (str): Path to save the list + column_name (str, optional): Name for the column. Defaults to "item". + """ + df = pd.DataFrame(self.list, columns=[column_name]) + df.to_csv(output_path, index=False, quoting=csv.QUOTE_ALL, encoding="utf-8-sig") + + def filter(self, condition: Callable) -> "ListBuilder": + """Filter the list based on a condition + + Args: + condition (callable): Function that returns True for items to keep + + Returns: + ListBuilder: The builder instance for method chaining + """ + self.list = [item for item in self.list if condition(item)] + return self + + def map(self, transform: Callable) -> "ListBuilder": + """Apply a transformation to each item in the list + + Args: + transform (callable): Function to apply to each item + + Returns: + ListBuilder: The builder instance for method chaining + """ + self.list = [transform(item) for item in self.list] + return self + + def deduplicate(self) -> "ListBuilder": + """Remove duplicate items from the list + + Returns: + ListBuilder: The builder instance for method chaining + """ + self.list = list(dict.fromkeys(self.list)) # Preserves order + return self + + def sort(self, reverse: bool = False) -> "ListBuilder": + """Sort the list + + Args: + reverse (bool, optional): Whether to sort in descending order. Defaults to False. + + Returns: + ListBuilder: The builder instance for method chaining + """ + self.list.sort(reverse=reverse) + return self + + def combine(self, other_builder: "ListBuilder") -> "ListBuilder": + """Combine with another ListBuilder + + Args: + other_builder (ListBuilder): Another ListBuilder to combine with + + Returns: + ListBuilder: The builder instance for method chaining + """ + self.list.extend(other_builder.list) + return self + + def to_upper(self) -> "ListBuilder": + """Convert all items to uppercase + + Returns: + ListBuilder: The builder instance for method chaining + """ + self.list = [item.upper() for item in self.list] + return self + + def to_lower(self) -> "ListBuilder": + """Convert all items to lowercase + + Returns: + ListBuilder: The builder instance for method chaining + """ + self.list = [item.lower() for item in self.list] + return self diff --git a/fieldExtraction/src/enums/__init__.py b/fieldExtraction/src/enums/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/fieldExtraction/src/enums/delimiters.py b/fieldExtraction/src/enums/delimiters.py deleted file mode 100644 index 6b73149..0000000 --- a/fieldExtraction/src/enums/delimiters.py +++ /dev/null @@ -1,7 +0,0 @@ -from enum import Enum - - -class Delimiter(Enum): - PIPE = '|' - BACKTICK = '`' - TRIPLE_BACKTICK = '```' diff --git a/fieldExtraction/src/investment/aarete_derived.py b/fieldExtraction/src/investment/aarete_derived.py index 28d3473..08259a9 100644 --- a/fieldExtraction/src/investment/aarete_derived.py +++ b/fieldExtraction/src/investment/aarete_derived.py @@ -1,15 +1,18 @@ -import src.config as config -from src.prompts.investment_prompts import Field, FieldSet, AARETE_DERIVED_PROMPT_TEMPLATE -import src.prompts.investment_prompts as prompts -from src.constants import investment_values -from src.enums.delimiters import Delimiter -import src.utils.llm_utils as llm_utils -import src.utils.string_utils as string_utils -import pandas as pd -from crosswalk.crosswalk_utils import CrosswalkBuilder, apply_crosswalk import os from collections import defaultdict +import pandas as pd + +import src.config as config +import src.utils.llm_utils as llm_utils +import src.utils.string_utils as string_utils +from constants.constants import Constants +from constants.delimiters import Delimiter +from src.crosswalk.crosswalk_builder import CrosswalkBuilder +from src.prompts.investment_prompts import AARETE_DERIVED_PROMPT_TEMPLATE, FieldSet +from src.utils.crosswalk_utils import apply_crosswalk + + def fill_na_from_field(results_dict, to_field, from_field, crosswalk_path): from_str = results_dict.get(from_field) if string_utils.is_empty(from_str): @@ -18,24 +21,35 @@ def fill_na_from_field(results_dict, to_field, from_field, crosswalk_path): to_value = apply_crosswalk(from_str, MAPPING_DICT) return to_value if to_value else results_dict.get(to_field) + def fill_na_mapping(answer_dicts): """ Fill in missing values in fields using mappings from other fields. - + Args: results_df (pd.DataFrame): DataFrame containing the results data - + Returns: pd.DataFrame: DataFrame with filled missing values """ for answer_dict in answer_dicts: # Fill AARETE_DERIVED_LOB from AARETE_DERIVED_PROGRAM if string_utils.is_empty(answer_dict.get("AARETE_DERIVED_LOB")): - answer_dict["AARETE_DERIVED_LOB"] = fill_na_from_field(answer_dict, "AARETE_DERIVED_LOB", "AARETE_DERIVED_PROGRAM", "crosswalk/mappings/crosswalk_program_lob.json") - + answer_dict["AARETE_DERIVED_LOB"] = fill_na_from_field( + answer_dict, + "AARETE_DERIVED_LOB", + "AARETE_DERIVED_PROGRAM", + "crosswalk/mappings/crosswalk_program_lob.json", + ) + # Fill AARETE_DERIVED_LOB from PRODUCT if string_utils.is_empty(answer_dict.get("AARETE_DERIVED_LOB")): - answer_dict["AARETE_DERIVED_LOB"] = fill_na_from_field(answer_dict, "AARETE_DERIVED_LOB", "PRODUCT", "crosswalk/mappings/crosswalk_product_lob.json") + answer_dict["AARETE_DERIVED_LOB"] = fill_na_from_field( + answer_dict, + "AARETE_DERIVED_LOB", + "PRODUCT", + "crosswalk/mappings/crosswalk_product_lob.json", + ) return answer_dicts @@ -47,10 +61,12 @@ def get_crosswalk_fields(answer_dicts): from_field = to_field.base_field if from_field in just_mapped: continue - - # Generate Crosswalk + + # Generate Crosswalk: TODO: change to referencing constants so we're not re-uploading crosswalk = CrosswalkBuilder() - crosswalk = crosswalk.from_json(path=os.path.join("crosswalk/mappings", to_field.crosswalk)) + crosswalk = crosswalk.from_json( + path=os.path.join("constants/mappings", to_field.crosswalk) + ) crosswalk_mapping = crosswalk.mapping crosswalk_mapping_reverse = crosswalk.create_reverse_mapping() @@ -59,86 +75,13 @@ def get_crosswalk_fields(answer_dicts): if not string_utils.is_empty(from_field_value): to_field_value = apply_crosswalk(from_field_value, crosswalk_mapping) - + if not to_field_value or to_field_value == from_field_value: - to_field_value = apply_crosswalk(from_field_value, crosswalk_mapping_reverse) - + to_field_value = apply_crosswalk( + from_field_value, crosswalk_mapping_reverse + ) + answer_dict[to_field.field_name] = to_field_value just_mapped.append(to_field.field_name) - + return answer_dicts - -def get_aarete_derived(aarete_derived_fields: FieldSet, - contract_answers_df: pd.DataFrame, - filename: str) -> pd.DataFrame: - """ - Process AARETE derived fields for a DataFrame - - Parameters: - ----------- - aarete_derived_fields : FieldSet - Configuration for AARETE derived fields - contract_answers_df : pd.DataFrame - Input DataFrame containing contract answers - filename : str - Filename for logging/reference purposes - - Returns: - -------- - pd.DataFrame - Updated DataFrame with new AARETE derived columns - """ - - # Create a copy of the DataFrame to avoid modifying the original - result_df = contract_answers_df.copy() - - # Cache to store LLM responses for each base field value - # Structure: {field_name: {base_field_value: derived_value}} - cache = defaultdict(dict) - - # Process each derived field - for field in aarete_derived_fields.fields: - derived_field_name, base_field = field.field_name, field.base_field - - # Check if base field exists in the DataFrame - if base_field not in result_df.columns: - print(f"Base field '{base_field}' not found in DataFrame.") - continue - - # Dynamically fetch valid list - valid_list = getattr(investment_values, field.valid_values, None) - if valid_list is None: - print(f"Variable '{field.valid_values}' is not defined in investment_values.py.") - continue - - # Get unique values from the base field - unique_base_values = result_df[base_field].unique() - - # Process each unique value only once - for unique_value in unique_base_values: - # Skip if we've already processed this value - if unique_value in cache[base_field]: - continue - - # Generate prompt for unique value - field_prompt = AARETE_DERIVED_PROMPT_TEMPLATE(unique_value, valid_list) - - # Invoke Claude and extract answer - field_answer_raw = llm_utils.invoke_claude( - field_prompt, - "sonnet_latest", - filename, - 8192 - ) - field_answer_extracted = string_utils.extract_text_from_delimiters( - field_answer_raw, - Delimiter.PIPE - ) - - # Store in cache - cache[base_field][unique_value] = field_answer_extracted - - # Map cached values back to DataFrame using vectorized operations - result_df[derived_field_name] = result_df[base_field].map(cache[base_field]) - - return result_df diff --git a/fieldExtraction/src/investment/consolidate_output.py b/fieldExtraction/src/investment/consolidate_output.py deleted file mode 100644 index 2fcc1cd..0000000 --- a/fieldExtraction/src/investment/consolidate_output.py +++ /dev/null @@ -1,389 +0,0 @@ -import os -from pathlib import Path -import pandas as pd - -from src import config -from src.tracking import costlog_utils, tracking_utils -from src.constants import valid -from src.qa_qc.qa_qc_utils import perform_qc_qa - - -def upload_tracking_files(run_timestamp): - """Upload tracking files and aggregated cost logs to S3 during batch processing.""" - if not config.WRITE_TO_S3: - return - - BUCKET_NAME = config.S3_OUTPUT_BUCKET - tracking_files = { - 'cost_log': config.COST_LOG_NAME, - 'file_log': config.FILE_LOG_NAME - } - - try: - # Create aggregated costlog files - costlog_caller_fn = "costlog_agg_by_caller.csv" - costlog_filename_fn = "costlog_agg_by_filename.csv" - - # Read and aggregate costlog - if os.path.exists(config.COST_LOG_NAME): - df_costlog = pd.read_csv(config.COST_LOG_NAME) - df_caller, df_filename = costlog_utils.aggregate_costlog(df_costlog) - - # Write aggregated costlogs - df_caller.to_csv(costlog_caller_fn, index=False) - df_filename.to_csv(costlog_filename_fn, index=False) - - # Add aggregated files to tracking - tracking_files['costlog_by_caller'] = costlog_caller_fn - tracking_files['costlog_by_filename'] = costlog_filename_fn - - except Exception as e: - print(f"Costlog aggregation failed: {str(e)}") - - # Upload all tracking files - for file_type, file_name in tracking_files.items(): - if os.path.exists(file_name): - try: - s3_key = f"{config.BATCH_ID}/{run_timestamp}/tracking/{file_name}" - config.S3_CLIENT.upload_file(file_name, BUCKET_NAME, s3_key) - print(f"Uploaded {file_type} to {s3_key}") - except Exception as e: - print(f"Error uploading {file_name}: {str(e)}") - raise - -def get_all_run_folders(batch_id): - """Get all run folders for a given batch ID from S3""" - prefix = f"{batch_id}/" - runs = set() - - paginator = config.S3_CLIENT.get_paginator('list_objects_v2') - for page in paginator.paginate(Bucket=config.S3_OUTPUT_BUCKET, Prefix=prefix): - for obj in page.get('Contents', []): - parts = obj['Key'].split('/') - if len(parts) > 1 and parts[1].startswith('run_'): - runs.add(parts[1]) - - return sorted(list(runs)) - -def consolidate_batch_outputs(batch_id): - """Consolidate outputs across all runs for a batch ID""" - print(f"Starting batch-level consolidation for {batch_id}") - - ac_dfs = [] - b_dfs = [] - - run_folders = get_all_run_folders(batch_id) - - for run_timestamp in run_folders: - ac_key = f"{batch_id}/{run_timestamp}/consolidated/{batch_id}-AC.csv" - b_key = f"{batch_id}/{run_timestamp}/consolidated/{batch_id}-B.csv" - - try: - response = config.S3_CLIENT.get_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=ac_key - ) - ac_df = pd.read_csv(response['Body']) - ac_df['run_timestamp'] = run_timestamp - ac_dfs.append(ac_df) - except Exception as e: - print(f"Could not read AC file from run {run_timestamp}: {str(e)}") - - try: - response = config.S3_CLIENT.get_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=b_key - ) - b_df = pd.read_csv(response['Body']) - b_df['run_timestamp'] = run_timestamp - b_dfs.append(b_df) - except Exception as e: - print(f"Could not read B file from run {run_timestamp}: {str(e)}") - - batch_ac_df = None - batch_b_df = None - - if ac_dfs: - batch_ac_df = pd.concat(ac_dfs, ignore_index=True) - batch_ac_df = batch_ac_df.sort_values('run_timestamp').drop_duplicates( - subset=['Contract Name'], - keep='last' - ).drop('run_timestamp', axis=1) - - ac_output_path = f"{batch_id}/consolidated/{batch_id}-AC.csv" - csv_buffer = batch_ac_df.to_csv(index=False).encode() - config.S3_CLIENT.put_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=ac_output_path, - Body=csv_buffer - ) - print(f"Saved batch AC file: {ac_output_path}") - - if b_dfs: - batch_b_df = pd.concat(b_dfs, ignore_index=True) - batch_b_df = batch_b_df.sort_values('run_timestamp').drop_duplicates( - subset=['Contract Name'], - keep='last' - ).drop('run_timestamp', axis=1) - - b_output_path = f"{batch_id}/consolidated/{batch_id}-B.csv" - csv_buffer = batch_b_df.to_csv(index=False).encode() - config.S3_CLIENT.put_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=b_output_path, - Body=csv_buffer - ) - print(f"Saved batch B file: {b_output_path}") - - # Create batch ABC if both exist - if batch_ac_df is not None and batch_b_df is not None: - try: - batch_b_df_copy = batch_b_df.copy() - batch_b_df_copy.drop(["Pages", "Parent Agreement Code"], axis=1, inplace=True) - - batch_abc_df = pd.merge( - batch_b_df_copy, - batch_ac_df, - on="Contract Name", - how="right" - ) - - batch_abc_df = batch_abc_df[ - [col for col in valid.ABC_COLUMNS if col in batch_abc_df] - ] - - abc_output_path = f"{batch_id}/consolidated/{batch_id}-ABC.csv" - csv_buffer = batch_abc_df.to_csv(index=False).encode() - config.S3_CLIENT.put_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=abc_output_path, - Body=csv_buffer - ) - print(f"Saved batch ABC file: {abc_output_path}") - - # Perform QAQC on batch consolidated files - perform_qc_qa(batch_ac_df, batch_b_df, batch_abc_df, f"{batch_id}_batch") - - except Exception as e: - print(f"Error creating batch ABC file: {str(e)}") - - print("Batch-level consolidation complete") - return batch_ac_df, batch_b_df, batch_abc_df - - -def upload_individual_outputs(run_timestamp): - """Upload individual AC and B output files to their respective S3 folders""" - BUCKET_NAME = config.S3_OUTPUT_BUCKET - - for subdir, dirs, files in os.walk(config.OUTPUT_DIRECTORY): - # Skip the consolidated directory - if subdir == config.CONSOLIDATED_OUTPUT_DIRECTORY: - continue - - basename = os.path.basename(subdir) - if basename: # Skip root directory - filename = basename + ".txt" - - # If already fully processed and uploaded, skip - if tracking_utils.check_file_processed(config.BATCH_ID, filename, 'AC') and \ - tracking_utils.check_file_processed(config.BATCH_ID, filename, 'B'): - print(f"Skipping {filename} - already processed") - continue - - individual_folder = f"{config.BATCH_ID}/{run_timestamp}/individual/{basename}" - - # Check for AC results - ac_path = os.path.join(subdir, config.AC_RESULTS_NAME) - if os.path.exists(ac_path): - s3_key = f"{individual_folder}/{config.AC_RESULTS_NAME}" - try: - config.S3_CLIENT.upload_file(ac_path, BUCKET_NAME, s3_key) - tracking_utils.log_file_processing(filename, 'AC', s3_key, 'Completed') - tracking_utils.update_master_batch_tracking( - config.BATCH_ID, - run_timestamp, - [filename], - 'AC', - s3_path=s3_key, - upload_status='Uploaded' - ) - except Exception as e: - error_msg = str(e) - print(f"Error uploading AC file {basename}: {error_msg}") - tracking_utils.log_file_processing(filename, 'AC', s3_key, 'Failed', error_msg) - - # Check for B results - b_path = os.path.join(subdir, config.B_RESULTS_NAME) - if os.path.exists(b_path): - s3_key = f"{individual_folder}/{config.B_RESULTS_NAME}" - try: - config.S3_CLIENT.upload_file(b_path, BUCKET_NAME, s3_key) - tracking_utils.log_file_processing(filename, 'B', s3_key, 'Completed') - tracking_utils.update_master_batch_tracking( - config.BATCH_ID, - run_timestamp, - [filename], - 'B', - s3_path=s3_key, - upload_status='Uploaded' - ) - except Exception as e: - error_msg = str(e) - print(f"Error uploading B file {basename}: {error_msg}") - tracking_utils.log_file_processing(filename, 'B', s3_key, 'Failed', error_msg) - -def consolidate_output(run_timestamp, ac_df = None, b_df = None): - """Consolidate and upload outputs""" - Path(config.CONSOLIDATED_OUTPUT_DIRECTORY).mkdir(parents=True, exist_ok=True) - processed_ac_files = [] - processed_b_files = [] - - ac_final_df = None if ac_df is None else ac_df - b_final_df = None if b_df is None else b_df - abc_final_df = None - - # First upload individual outputs - if config.WRITE_TO_S3: - print(f"Uploading individual outputs...") - upload_individual_outputs(run_timestamp) - - # If AC only - if "a" in config.FIELDS and "c" in config.FIELDS and ac_final_df is None: - ac_dfs = [] - for subdir, dirs, files in os.walk(config.OUTPUT_DIRECTORY): - ac_path = os.path.join(subdir, config.AC_RESULTS_NAME) - if os.path.exists(ac_path): - try: - ac_df = pd.read_csv(ac_path) - ac_dfs.append(ac_df) - processed_ac_files.append(os.path.basename(subdir) + ".txt") - except Exception as e: - print(f"Error reading AC file {ac_path}: {str(e)}") - - if ac_dfs: - ac_final_df = pd.concat(ac_dfs, ignore_index=True) - output_path = os.path.join( - config.CONSOLIDATED_OUTPUT_DIRECTORY, - f"{config.BATCH_ID}-AC.csv" - ) - ac_final_df.to_csv(output_path) - - if config.WRITE_TO_S3: - s3_key = f"{config.BATCH_ID}/{run_timestamp}/consolidated/{config.BATCH_ID}-AC.csv" - try: - config.S3_CLIENT.upload_file(output_path, config.S3_OUTPUT_BUCKET, s3_key) - tracking_utils.log_file_processing(f"{config.BATCH_ID}-AC.csv", 'consolidated', s3_key, 'Completed') - tracking_utils.update_master_batch_tracking( - config.BATCH_ID, - run_timestamp, - [f"{config.BATCH_ID}-AC.csv"], - 'consolidated', - s3_path=s3_key, - upload_status='Uploaded' - ) - except Exception as e: - print(f"Error uploading consolidated AC file: {str(e)}") - tracking_utils.log_file_processing(f"{config.BATCH_ID}-AC.csv", 'consolidated', s3_key, 'Failed', str(e)) - - # If B only - if "b" in config.FIELDS and b_final_df is None: - b_dfs = [] - for subdir, dirs, files in os.walk(config.OUTPUT_DIRECTORY): - b_path = os.path.join(subdir, config.B_RESULTS_NAME) - if os.path.exists(b_path): - try: - b_df = pd.read_csv(b_path) - b_dfs.append(b_df) - processed_b_files.append(os.path.basename(subdir) + ".txt") - except Exception as e: - print(f"Error reading B file {b_path}: {str(e)}") - - if b_dfs: - b_final_df = pd.concat(b_dfs, ignore_index=True) - output_path = os.path.join( - config.CONSOLIDATED_OUTPUT_DIRECTORY, - f"{config.BATCH_ID}-B.csv" - ) - b_final_df.to_csv(output_path) - - if config.WRITE_TO_S3: - s3_key = f"{config.BATCH_ID}/{run_timestamp}/consolidated/{config.BATCH_ID}-B.csv" - try: - config.S3_CLIENT.upload_file(output_path, config.S3_OUTPUT_BUCKET, s3_key) - tracking_utils.log_file_processing(f"{config.BATCH_ID}-B.csv", 'consolidated', s3_key, 'Completed') - tracking_utils.update_master_batch_tracking( - config.BATCH_ID, - run_timestamp, - [f"{config.BATCH_ID}-B.csv"], - 'consolidated', - s3_path=s3_key, - upload_status='Uploaded' - ) - except Exception as e: - print(f"Error uploading consolidated B file: {str(e)}") - tracking_utils.log_file_processing(f"{config.BATCH_ID}-B.csv", 'consolidated', s3_key, 'Failed', str(e)) - - # If ABC - if "a" in config.FIELDS and "c" in config.FIELDS and "b" in config.FIELDS: - try: - abc_path = os.path.join( - config.CONSOLIDATED_OUTPUT_DIRECTORY, f"{config.BATCH_ID}-ABC.csv" - ) - abc_final_df = pd.merge( - b_final_df, ac_final_df, on="Contract Name", how="right" - ) - abc_final_df = abc_final_df[ - [col for col in valid.ABC_COLUMNS if col in abc_final_df] - ] - - print( - "Columns not found: ", - [f for f in valid.ABC_COLUMNS if f not in abc_final_df.columns], - ) - abc_final_df.to_csv(abc_path) - print(f"Consolidation complete. Results written to {abc_path}") - - if config.WRITE_TO_S3: - s3_key = f"{config.BATCH_ID}/{run_timestamp}/consolidated/{config.BATCH_ID}-ABC.csv" - try: - config.S3_CLIENT.upload_file(abc_path, config.S3_OUTPUT_BUCKET, s3_key) - tracking_utils.log_file_processing(f"{config.BATCH_ID}-ABC.csv", 'consolidated', s3_key, 'Completed') - except Exception as e: - error_msg = str(e) - print(f"Error uploading ABC file: {error_msg}") - tracking_utils.log_file_processing(f"{config.BATCH_ID}-ABC.csv", 'consolidated', s3_key, 'Failed', error_msg) - except Exception as e: - print(f"Error creating ABC consolidated file: {str(e)}") - - # Upload tracking files - if config.WRITE_TO_S3: - try: - upload_tracking_files(run_timestamp) - print(f"Upload completed to s3://{config.S3_OUTPUT_BUCKET}/{config.BATCH_ID}/{run_timestamp}") - except Exception as e: - print(f"Error uploading tracking files: {str(e)}") - - - return ac_final_df, b_final_df, abc_final_df - -def consolidate_adhoc(ac_df, b_df): - - # If ABC - - b_df.drop([col for col in ["Pages", "Parent Agreement Code"] if col in b_df.columns], axis=1, inplace=True) - - abc_final_df = pd.merge( - b_df, ac_df, on=[config.FILE_NAME_COLUMN], how="right" - ) - abc_final_df = abc_final_df[ - [col for col in valid.ABC_COLUMNS if col in abc_final_df] - ] - - print( - "Columns not found: ", - [f for f in valid.ABC_COLUMNS if f not in abc_final_df.columns], - ) - - return ac_df, b_df, abc_final_df - - diff --git a/fieldExtraction/src/investment/dynamic_funcs.py b/fieldExtraction/src/investment/dynamic_funcs.py index ed16329..4d3b403 100644 --- a/fieldExtraction/src/investment/dynamic_funcs.py +++ b/fieldExtraction/src/investment/dynamic_funcs.py @@ -1,10 +1,8 @@ - -from src.prompts import investment_prompts -from src.prompts.investment_prompts import Field, FieldSet import src.utils.llm_utils as llm_utils import src.utils.string_utils as string_utils from src import config -import src.investment.one_to_n_funcs as one_to_n_funcs +from src.prompts import investment_prompts +from src.prompts.investment_prompts import Field, FieldSet def prompt_dynamic(text, field_prompts, filename): @@ -20,51 +18,86 @@ def prompt_dynamic(text, field_prompts, filename): dict: A dictionary containing field names as keys and extracted answers as values. """ prompt = investment_prompts.EXHIBIT_LEVEL(text, field_prompts) - llm_answer_raw = llm_utils.invoke_claude(prompt, "sonnet_latest", filename) # Returns dictionary of lists + llm_answer_raw = llm_utils.invoke_claude( + prompt, "sonnet_latest", filename + ) # Returns dictionary of lists llm_answer_final = string_utils.universal_json_load(llm_answer_raw) return llm_answer_final + def get_dynamic_one_to_one_fields(answer_dicts): """ Returns a FieldSet object of dynamic fields that have are empty for at least one dict in answer_dicts """ - fieldset = FieldSet(config.FIELD_JSON_PATH, relationship="one_to_n", base_field=True) - rate_escalator_fields = FieldSet(config.FIELD_JSON_PATH, relationship="one_to_n", field_type="rate_escalator",field_name="RATE_ESCALATOR_DESC") + fieldset = FieldSet( + config.FIELD_JSON_PATH, relationship="one_to_n", base_field=True + ) + rate_escalator_fields = FieldSet( + config.FIELD_JSON_PATH, + relationship="one_to_n", + field_type="rate_escalator", + field_name="RATE_ESCALATOR_DESC", + ) fieldset.combine(rate_escalator_fields, inplace=True) - + one_to_one_fields = FieldSet() for field in fieldset.fields: field_name = field.field_name - if "REIMB" not in field_name: # Don't do this for the REIMB_ fields + if "REIMB" not in field_name: # Don't do this for the REIMB_ fields for answer_dict in answer_dicts: - if string_utils.is_empty(answer_dict.get(field_name)) or field_name not in answer_dict: + if ( + string_utils.is_empty(answer_dict.get(field_name)) + or field_name not in answer_dict + ): if field_name == "RATE_ESCALATOR_DESC": # If the field is RATE_ESCALATOR_DESC, we need to add RATE_ESCALATOR_STATEMENT field and change its field type and relationship - field_to_add = Field.load_from_file(file_path=config.FIELD_JSON_PATH, field_name="RATE_ESCALATOR_STATEMENT") + field_to_add = Field.load_from_file( + file_path=config.FIELD_JSON_PATH, + field_name="RATE_ESCALATOR_STATEMENT", + ) else: - field_to_add = Field.load_from_file(file_path=config.FIELD_JSON_PATH, field_name=field.base_field) - + field_to_add = Field.load_from_file( + file_path=config.FIELD_JSON_PATH, + field_name=field.base_field, + ) + field_to_add.field_type = "full_context" field_to_add.relationship = "one_to_one" - + if field_to_add.allow_na: - field_to_add.prompt = field_to_add.prompt + " Only answer if the answer applies to the ENTIRE contract. Otherwise, answer N/A." + field_to_add.prompt = ( + field_to_add.prompt + + " Only answer if the answer applies to the ENTIRE contract. Otherwise, answer N/A." + ) else: - field_to_add.prompt = field_to_add.prompt + " Do NOT leave this field N/A. Choose the most appropriate answer based on the context." + field_to_add.prompt = ( + field_to_add.prompt + + " Do NOT leave this field N/A. Choose the most appropriate answer based on the context." + ) one_to_one_fields.add_field(field_to_add) continue return one_to_one_fields + def prompt_dynamic_primary(exhibit_text, field, filename): # Prompt Exhibit Header - prompt = investment_prompts.DYNAMIC_PRIMARY(exhibit_text, field.field_name, field.get_prompt()) + prompt = investment_prompts.DYNAMIC_PRIMARY( + exhibit_text, field.field_name, field.get_prompt() + ) claude_answer_raw = llm_utils.invoke_claude(prompt, "sonnet_latest", filename) - claude_answer_final = string_utils.extract_text_from_delimiters(claude_answer_raw, string_utils.Delimiter.PIPE) + claude_answer_final = string_utils.extract_text_from_delimiters( + claude_answer_raw, string_utils.Delimiter.PIPE + ) if "," in claude_answer_final: - claude_answer_final = "|".join([item.strip() for item in claude_answer_final.split(",")]) + claude_answer_final = "|".join( + [item.strip() for item in claude_answer_final.split(",")] + ) return claude_answer_final -def get_dynamic_primary(exhibit_chunk : str, exhibit_header: str, filename: str, dynamic_fields : FieldSet): + +def get_dynamic_primary( + exhibit_chunk: str, exhibit_header: str, filename: str, dynamic_fields: FieldSet +): """ Processes dynamic primary fields one-by-one from exhibit header and chunk text. @@ -81,18 +114,27 @@ def get_dynamic_primary(exhibit_chunk : str, exhibit_header: str, filename: str, """ if not dynamic_fields.contains_fields(): return {}, FieldSet() - + reimbursement_level_fields = FieldSet() exhibit_level_answer_dict = {} for field in dynamic_fields.fields: exhibit_header_answer = prompt_dynamic_primary(exhibit_header, field, filename) # If there is exactly ONE Non-N/A answer in the header - if not string_utils.is_empty(exhibit_header_answer) and "|" not in exhibit_header_answer: + if ( + not string_utils.is_empty(exhibit_header_answer) + and "|" not in exhibit_header_answer + ): exhibit_level_answer_dict[field.field_name] = exhibit_header_answer # If there are multiple Non-N/A answers in the header - elif not string_utils.is_empty(exhibit_header_answer) and "|" in exhibit_header_answer: + elif ( + not string_utils.is_empty(exhibit_header_answer) + and "|" in exhibit_header_answer + ): field.update_valid_values(exhibit_header_answer) - field.prompt = field.prompt + ". Ensure ALL values that apply to the specific reimbursement term are included." + field.prompt = ( + field.prompt + + ". Ensure ALL values that apply to the specific reimbursement term are included." + ) reimbursement_level_fields.add_field(field) dynamic_fields.remove_field(field.field_name) # If there are ZERO Non-N/A answers in the header @@ -106,11 +148,13 @@ def get_dynamic_primary(exhibit_chunk : str, exhibit_header: str, filename: str, # If the field is not found, add to Exhibit-Level answers (the answer will be N/A or similar) else: exhibit_level_answer_dict[field.field_name] = exhibit_text_answer - + return exhibit_level_answer_dict, reimbursement_level_fields -def get_dynamic_answers(exhibit_chunk : str, exhibit_header: str, filename: str, dynamic_fields : FieldSet): +def get_dynamic_answers( + exhibit_chunk: str, exhibit_header: str, filename: str, dynamic_fields: FieldSet +): """ Processes dynamic fields from exhibit header and chunk text. @@ -132,24 +176,31 @@ def get_dynamic_answers(exhibit_chunk : str, exhibit_header: str, filename: str, exhibit_level_answer_dict = {} # Check Exhibit Header - exhibit_header_results = prompt_dynamic(text=exhibit_header, - field_prompts=dynamic_fields.print_prompt_dict(), - filename=filename) - + exhibit_header_results = prompt_dynamic( + text=exhibit_header, + field_prompts=dynamic_fields.print_prompt_dict(), + filename=filename, + ) + # Process Exhibit Header Results - update dynamic fields and add to reimbursement level fields for field_name, answer in exhibit_header_results.items(): if not string_utils.is_empty(answer): field = dynamic_fields.get_field(field_name) field.update_valid_values(answer) - field.prompt = field.prompt + ". Ensure ALL values that apply to the specific reimbursement term are included." + field.prompt = ( + field.prompt + + ". Ensure ALL values that apply to the specific reimbursement term are included." + ) reimbursement_level_fields.add_field(field) dynamic_fields.remove_field(field_name) # Check Exhibit Text if dynamic_fields.contains_fields(): - exhibit_chunk_results = prompt_dynamic(text=exhibit_chunk, - field_prompts=dynamic_fields.print_prompt_dict(), - filename=filename) + exhibit_chunk_results = prompt_dynamic( + text=exhibit_chunk, + field_prompts=dynamic_fields.print_prompt_dict(), + filename=filename, + ) # Process Exhibit Chunk Results - update dynamic fields and add to reimbursement level fields for field_name, answer in exhibit_chunk_results.items(): if not string_utils.is_empty(answer): @@ -162,5 +213,3 @@ def get_dynamic_answers(exhibit_chunk : str, exhibit_header: str, filename: str, exhibit_level_answer_dict[field_name] = answer return exhibit_level_answer_dict, reimbursement_level_fields - - diff --git a/fieldExtraction/src/investment/file_processing.py b/fieldExtraction/src/investment/file_processing.py index 9aaf9aa..f29613d 100644 --- a/fieldExtraction/src/investment/file_processing.py +++ b/fieldExtraction/src/investment/file_processing.py @@ -1,64 +1,72 @@ -import csv -import json -import logging -import os - import pandas as pd + +import src.codes.code_funcs as code_funcs import src.investment.aarete_derived as aarete_derived import src.investment.dynamic_funcs as dynamic_funcs -import src.investment.investment_postprocessing_funcs as investment_postprocessing_funcs import src.investment.one_to_n_funcs as one_to_n_funcs import src.investment.one_to_one_funcs as one_to_one_funcs -import src.investment.tin_npi_funcs as tin_npi_funcs import src.investment.postprocess as postprocess +import src.investment.postprocessing_funcs as postprocessing_funcs import src.investment.preprocess as preprocess import src.investment.row_funcs as row_funcs -import src.codes.code_funcs as code_funcs +import src.investment.tin_npi_funcs as tin_npi_funcs import src.utils.io_utils as io_utils import src.utils.string_utils as string_utils +from constants.constants import Constants from src import config from src.config import FIELD_JSON_PATH -from src.investment.one_to_n_funcs import (combine_one_to_n_answers, - reimbursement_level) +from src.investment.one_to_n_funcs import combine_one_to_n_answers, reimbursement_level from src.prompts.investment_prompts import FieldSet from src.utils.string_utils import datetime_str -def process_file(file_object, run_timestamp): +def process_file(file_object, constants: Constants, run_timestamp): filename, contract_text = file_object print(f"{datetime_str()} Processing {filename}...") ################## PREPROCESS ################## contract_text = preprocess.clean_text(contract_text) text_dict, top_sheet_dict = preprocess.split_text(contract_text) - text_dict = preprocess.clean_tables(text_dict, filename) - exhibit_dict, all_exhibit_headers = preprocess.one_to_n_exhibit_chunking(text_dict, filename) + text_dict = preprocess.clean_tables( + text_dict, constants.EXHIBIT_HEADER_MARKERS, filename + ) + exhibit_dict, all_exhibit_headers = preprocess.one_to_n_exhibit_chunking( + text_dict, constants.EXHIBIT_HEADER_MARKERS, filename + ) print(f"{datetime_str()} Preprocessing Complete - {filename}") ################## ONE TO N ################## if string_utils.contains_reimbursement(contract_text): - one_to_n_results, dynamic_one_to_one_fields = run_one_to_n_prompts(filename, exhibit_dict, all_exhibit_headers) # Return df - one_to_n_results['FILE_NAME'] = filename - one_to_n_results = investment_postprocessing_funcs.generate_reimb_ids(one_to_n_results) # Add reimb_id + one_to_n_results, dynamic_one_to_one_fields = run_one_to_n_prompts( + filename, exhibit_dict, all_exhibit_headers, constants + ) # Return df + one_to_n_results["FILE_NAME"] = filename + one_to_n_results = postprocessing_funcs.generate_reimb_ids( + one_to_n_results + ) # Add reimb_id print(f"{datetime_str()} One to N Complete - {filename}") else: - one_to_n_results = pd.DataFrame([{'FILE_NAME' : filename}]) + one_to_n_results = pd.DataFrame([{"FILE_NAME": filename}]) dynamic_one_to_one_fields = FieldSet() print(f"{datetime_str()} No One to N Found, Skipping - {filename}") - + ################## ONE TO ONE ################## - one_to_one_results = run_one_to_one_prompts(filename, contract_text, text_dict, top_sheet_dict, dynamic_one_to_one_fields) # Return dict - one_to_one_results['FILE_NAME'] = filename + one_to_one_results = run_one_to_one_prompts( + filename, contract_text, text_dict, top_sheet_dict, dynamic_one_to_one_fields + ) # Return dict + one_to_one_results["FILE_NAME"] = filename print(f"{datetime_str()} One to One Complete - {filename}") ################## MERGE ################## - merged_results = row_funcs.merge_one_to_one_into_one_to_n(one_to_n_results, one_to_one_results) + merged_results = row_funcs.merge_one_to_one_into_one_to_n( + one_to_n_results, one_to_one_results + ) ################## CODES ################## - final_results = code_funcs.code_breakout(merged_results) + final_results = code_funcs.code_breakout(merged_results, constants) ################## POSTPROCESS ################## - final_df = postprocess.postprocess(final_results) + final_df = postprocess.postprocess(final_results, constants) print(f"{datetime_str()} Postprocessing Complete - {filename}") ################## WRITE INDIVIDUAL ################## @@ -71,17 +79,23 @@ def process_file(file_object, run_timestamp): return final_df -def run_one_to_one_prompts(filename, contract_text, text_dict, top_sheet_dict, dynamic_one_to_one_fields): +def run_one_to_one_prompts( + filename, contract_text, text_dict, top_sheet_dict, dynamic_one_to_one_fields +): ################## INITIALIZE FIELDS ################## - one_to_one_fields = FieldSet(relationship="one_to_one", file_path=config.FIELD_JSON_PATH).combine(dynamic_one_to_one_fields) + one_to_one_fields = FieldSet( + relationship="one_to_one", file_path=config.FIELD_JSON_PATH + ).combine(dynamic_one_to_one_fields) ################## RUN PROVIDER INFO ################## - one_to_one_results, one_to_one_fields = tin_npi_funcs.run_provider_info_fields(contract_text, one_to_one_fields, text_dict, filename) - + one_to_one_results, one_to_one_fields = tin_npi_funcs.run_provider_info_fields( + contract_text, one_to_one_fields, text_dict, filename + ) + ################## RUN SMART CHUNKED PROMPTS ################## smart_chunked_answers_dict = one_to_one_funcs.run_smart_chunked_fields( - one_to_one_fields, contract_text, filename,text_dict + one_to_one_fields, contract_text, filename, text_dict ) ################## RUN FULL CONTEXT PROMPTS ################## @@ -93,7 +107,7 @@ def run_one_to_one_prompts(filename, contract_text, text_dict, top_sheet_dict, d one_to_one_results.update(smart_chunked_answers_dict) one_to_one_results.update(full_context_answers_dict) - ################## Crosswalk Fields ################## + ################## Crosswalk Fields ################## one_to_one_results = aarete_derived.get_crosswalk_fields([one_to_one_results]) ################## Fill NA Mapping ################## @@ -102,81 +116,133 @@ def run_one_to_one_prompts(filename, contract_text, text_dict, top_sheet_dict, d return one_to_one_results[0] -def run_one_to_n_prompts(filename, exhibit_dict, all_exhibit_headers): - +def run_one_to_n_prompts(filename, exhibit_dict, all_exhibit_headers, constants): + ################## RUN PROMPTS ################## one_to_n_results = [] - seen_pairs = set() # Accumulator for tracking duplicate pairs of (`SERVICE_TERM`, `REIMB_TERM`) + seen_pairs = ( + set() + ) # Accumulator for tracking duplicate pairs of (`SERVICE_TERM`, `REIMB_TERM`) for exhibit_page in exhibit_dict.keys(): - + exhibit_text = exhibit_dict[exhibit_page] ################# GET EXHIBIT-LEVEL ANSWERS ################## if exhibit_page in all_exhibit_headers.keys(): exhibit_header = all_exhibit_headers[exhibit_page] - elif ".".join(exhibit_page.split(".")[:-1]) in all_exhibit_headers.keys(): # Take the parent header if it exists + elif ( + ".".join(exhibit_page.split(".")[:-1]) in all_exhibit_headers.keys() + ): # Take the parent header if it exists exhibit_header = all_exhibit_headers[".".join(exhibit_page.split(".")[:-1])] else: exhibit_header = "No Header Found" - exhibit_level_answers = one_to_n_funcs.get_exhibit_level_answers(exhibit_text, filename) - exhibit_level_answers['EXHIBIT_PAGE'] = exhibit_page - exhibit_level_answers['EXHIBIT_TITLE'] = exhibit_header + exhibit_level_answers = one_to_n_funcs.get_exhibit_level_answers( + exhibit_text, filename + ) + exhibit_level_answers["EXHIBIT_PAGE"] = exhibit_page + exhibit_level_answers["EXHIBIT_TITLE"] = exhibit_header # Extract exhibit lesser-of statement for passing to reimbursement_level - exhibit_lesser_of = exhibit_level_answers.get("EXHIBIT_LESSER_OF_STATEMENT", "N/A") + exhibit_lesser_of = exhibit_level_answers.get( + "EXHIBIT_LESSER_OF_STATEMENT", "N/A" + ) ################## INITIALIZE FIELDS ################## - reimbursement_level_fields = FieldSet(relationship="one_to_n", field_type="reimbursement_level", file_path=FIELD_JSON_PATH) + reimbursement_level_fields = FieldSet( + relationship="one_to_n", + field_type="reimbursement_level", + file_path=FIELD_JSON_PATH, + ) ################## GET DYNAMIC-PRIMARY ANSWERS ################## - dynamic_to_exhibit_level_answers, dynamic_to_reimbursement_level_fields = dynamic_funcs.get_dynamic_primary(exhibit_text, - exhibit_header, - filename, - FieldSet(relationship="one_to_n", field_type="dynamic_primary", file_path=config.FIELD_JSON_PATH)) + dynamic_to_exhibit_level_answers, dynamic_to_reimbursement_level_fields = ( + dynamic_funcs.get_dynamic_primary( + exhibit_text, + exhibit_header, + filename, + FieldSet( + relationship="one_to_n", + field_type="dynamic_primary", + file_path=config.FIELD_JSON_PATH, + ), + ) + ) exhibit_level_answers.update(dynamic_to_exhibit_level_answers) - reimbursement_level_fields.combine(dynamic_to_reimbursement_level_fields, inplace=True) + reimbursement_level_fields.combine( + dynamic_to_reimbursement_level_fields, inplace=True + ) ################## GET DYNAMIC-REIMB-INFO ANSWERS ################## - reimb_info_to_exhibit_level_answers, reimb_info_to_reimbursement_level_fields = dynamic_funcs.get_dynamic_answers(exhibit_text, - exhibit_header, - filename, - FieldSet(relationship="one_to_n", field_type="dynamic_reimb_info", file_path=config.FIELD_JSON_PATH)) + ( + reimb_info_to_exhibit_level_answers, + reimb_info_to_reimbursement_level_fields, + ) = dynamic_funcs.get_dynamic_answers( + exhibit_text, + exhibit_header, + filename, + FieldSet( + relationship="one_to_n", + field_type="dynamic_reimb_info", + file_path=config.FIELD_JSON_PATH, + ), + ) exhibit_level_answers.update(reimb_info_to_exhibit_level_answers) - reimbursement_level_fields.combine(reimb_info_to_reimbursement_level_fields, inplace=True) + reimbursement_level_fields.combine( + reimb_info_to_reimbursement_level_fields, inplace=True + ) ################## GET DYNAMIC-CODE ANSWERS ################## - code_to_exhibit_level_answers, code_to_reimbursement_level_fields = dynamic_funcs.get_dynamic_answers(exhibit_text, - exhibit_header, - filename, - FieldSet(relationship="one_to_n", field_type="dynamic_code", file_path=config.FIELD_JSON_PATH)) + code_to_exhibit_level_answers, code_to_reimbursement_level_fields = ( + dynamic_funcs.get_dynamic_answers( + exhibit_text, + exhibit_header, + filename, + FieldSet( + relationship="one_to_n", + field_type="dynamic_code", + file_path=config.FIELD_JSON_PATH, + ), + ) + ) exhibit_level_answers.update(code_to_exhibit_level_answers) - reimbursement_level_fields.combine(code_to_reimbursement_level_fields, inplace=True) + reimbursement_level_fields.combine( + code_to_reimbursement_level_fields, inplace=True + ) ################## GET REIMBURSEMENT-LEVEL ANSWERS (INCLUDING DYNAMIC) ################## - reimbursement_level_answers = reimbursement_level(exhibit_text, filename, reimbursement_level_fields, exhibit_page, seen_pairs, exhibit_lesser_of=exhibit_lesser_of) # Return list of dictionaries + reimbursement_level_answers = reimbursement_level( + exhibit_text, + filename, + reimbursement_level_fields, + exhibit_page, + seen_pairs, + exhibit_lesser_of=exhibit_lesser_of, + constants=constants, + ) # Return list of dictionaries ################# COMBINE ANSWERS ################## - full_answer_dict = combine_one_to_n_answers(exhibit_level_answers, reimbursement_level_answers, tin_npi_answers={}) + full_answer_dict = combine_one_to_n_answers( + exhibit_level_answers, reimbursement_level_answers, tin_npi_answers={} + ) one_to_n_results += full_answer_dict - - ################## Crosswalk Fields ################## + + ################## Crosswalk Fields ################## one_to_n_results = aarete_derived.get_crosswalk_fields(one_to_n_results) ################## Fill NA Mapping ################## one_to_n_results = aarete_derived.fill_na_mapping(one_to_n_results) ################## Update LOB for Duals ################## - one_to_n_results = investment_postprocessing_funcs.update_lob_for_duals(one_to_n_results) + one_to_n_results = postprocessing_funcs.update_lob_for_duals(one_to_n_results) ################## Add N/A Dynamic or Exhibit to One-to-One ################## - dynamic_one_to_one_fields = dynamic_funcs.get_dynamic_one_to_one_fields(one_to_n_results) + dynamic_one_to_one_fields = dynamic_funcs.get_dynamic_one_to_one_fields( + one_to_n_results + ) ################## CONVERT TO DF ################## one_to_n_df = pd.DataFrame(one_to_n_results) - + return one_to_n_df, dynamic_one_to_one_fields - - - diff --git a/fieldExtraction/src/investment/main.py b/fieldExtraction/src/investment/main.py index 72b8c10..92a8e51 100644 --- a/fieldExtraction/src/investment/main.py +++ b/fieldExtraction/src/investment/main.py @@ -11,12 +11,15 @@ random.seed(42) import traceback +from sentence_transformers import SentenceTransformer + import src.investment.file_processing as file_processing import src.utils.io_utils as io_utils +from constants.constants import Constants from src import config -def safe_process_file(item, run_timestamp): +def safe_process_file(item, constants, run_timestamp): """call process_file and catch any exceptions that occur Args: @@ -31,7 +34,7 @@ def safe_process_file(item, run_timestamp): item[0] if isinstance(item, (list, tuple)) and len(item) > 0 else item ) # unpack file_id from item (passed in as a tuple below) try: - return file_processing.process_file(item, run_timestamp) + return file_processing.process_file(item, constants, run_timestamp) except ( Exception ) as e: # When there's an issue with the processing inside the future @@ -63,6 +66,9 @@ def safe_process_file(item, run_timestamp): def main(testing=False, test_params={}): run_timestamp = datetime.now().strftime(f"run_%Y%m%d_%H:%M_{config.BATCH_ID}") + # Read Constants + constants = Constants() + # Read and process input if not testing: input_dict = io_utils.read_input() @@ -77,15 +83,17 @@ def main(testing=False, test_params={}): print(f"Total Input Files : {len(input_dict)}") - # Process files with run_timestamp successful_results = [] error_results = [] with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: # use executor to process files concurrently; use submit() instead of map() # so that we can catch exceptions and continue processing other files - futures = [executor.submit(safe_process_file, item, run_timestamp) for item in input_dict.items()] - + futures = [ + executor.submit(safe_process_file, item, constants, run_timestamp) + for item in input_dict.items() + ] + # collect results as they complete for future in concurrent.futures.as_completed(futures): try: diff --git a/fieldExtraction/src/investment/one_to_n_funcs.py b/fieldExtraction/src/investment/one_to_n_funcs.py index ba6ed36..beeb1b6 100644 --- a/fieldExtraction/src/investment/one_to_n_funcs.py +++ b/fieldExtraction/src/investment/one_to_n_funcs.py @@ -1,18 +1,20 @@ -import hashlib import logging import re from typing import Callable -import src.constants.investment_values as investment_values -import src.codes.code_funcs as code_funcs import src.prompts.investment_prompts as investment_prompts import src.utils.llm_utils as llm_utils import src.utils.string_utils as string_utils +from constants.constants import Constants +from constants.delimiters import Delimiter +from constants.special_case_configs import ( + CARVEOUT_CONFIGS, + SPECIAL_CASE_CONFIGS, + merge_special_case_configs, +) from src import config from src.config import FIELD_JSON_PATH -from src.constants.special_case_configs import SPECIAL_CASE_CONFIGS, CARVEOUT_CONFIGS, merge_special_case_configs -from src.enums.delimiters import Delimiter -from src.prompts.investment_prompts import EXHIBIT_LEVEL, Field, FieldSet +from src.prompts.investment_prompts import EXHIBIT_LEVEL, FieldSet METHODOLOGY_BREAKOUT_QUESTIONS = FieldSet( file_path=config.FIELD_JSON_PATH, field_type="methodology_breakout" @@ -23,8 +25,12 @@ FS_BREAKOUT_QUESTIONS = FieldSet( def get_exhibit_level_answers(exhibit_chunk, filename): - exhibit_level_fields = FieldSet(relationship="one_to_n", field_type="exhibit_level", file_path=FIELD_JSON_PATH) - rate_escalator_fields = FieldSet(config.FIELD_JSON_PATH, relationship="one_to_n", field_type="rate_escalator") + exhibit_level_fields = FieldSet( + relationship="one_to_n", field_type="exhibit_level", file_path=FIELD_JSON_PATH + ) + rate_escalator_fields = FieldSet( + config.FIELD_JSON_PATH, relationship="one_to_n", field_type="rate_escalator" + ) logging.debug(f"Running exhibit Level Fields for {filename}:") logging.debug(exhibit_level_fields.print_prompt_dict()) @@ -38,16 +44,20 @@ def get_exhibit_level_answers(exhibit_chunk, filename): ) logging.debug(f"LLM raw output for {filename}: {llm_answer_raw}") llm_answer_final = string_utils.universal_json_load(llm_answer_raw) - + # Handle Rate Escalators rate_escalator_statement = llm_answer_final.get("RATE_ESCALATOR_STATEMENT", "N/A") - exhibit_level_rate_escalator_answers = rate_escalator_breakout(rate_escalator_statement, filename, rate_escalator_fields) + exhibit_level_rate_escalator_answers = rate_escalator_breakout( + rate_escalator_statement, filename, rate_escalator_fields + ) llm_answer_final.update(exhibit_level_rate_escalator_answers) return llm_answer_final -def get_reimbursement_primary(reimbursement_level_fields, exhibit_text, filename) -> list[dict]: +def get_reimbursement_primary( + reimbursement_level_fields, exhibit_text, filename +) -> list[dict]: field_prompts = reimbursement_level_fields.print_prompt_dict() prompt = investment_prompts.REIMBURSEMENT_LEVEL_PRIMARY(exhibit_text, field_prompts) logging.debug( @@ -76,7 +86,10 @@ def get_reimbursement_primary(reimbursement_level_fields, exhibit_text, filename def get_special_cases( - reimbursement_primary_answers: list[dict], filename: str + reimbursement_primary_answers: list[dict], + VALID_CARVEOUTS: dict[str, str], + VALID_SPECIAL: dict[str, str], + filename: str, ) -> list[dict]: """ Determines carveout indicators for reimbursement answers using LLM-based classification. @@ -98,7 +111,7 @@ def get_special_cases( if contract_service_cd and contract_reimbursement_method: llm_answer_raw = llm_utils.invoke_claude( investment_prompts.SPECIAL_CASE_CHECK( - contract_service_cd, contract_reimbursement_method + contract_service_cd, contract_reimbursement_method, VALID_SPECIAL ), "sonnet_latest", filename, @@ -110,12 +123,16 @@ def get_special_cases( llm_answer_raw, Delimiter.PIPE ) # Ensure it's always a list - if isinstance(special_case_answer, str) and not string_utils.is_empty(special_case_answer): - special_case_answer = [item.strip().upper() for item in special_case_answer.split(",")] + if isinstance(special_case_answer, str) and not string_utils.is_empty( + special_case_answer + ): + special_case_answer = [ + item.strip().upper() for item in special_case_answer.split(",") + ] llm_answer_raw = llm_utils.invoke_claude( investment_prompts.CARVEOUT_CHECK( - contract_service_cd, contract_reimbursement_method + contract_service_cd, contract_reimbursement_method, VALID_CARVEOUTS ), "sonnet_latest", filename, @@ -136,7 +153,7 @@ def get_special_cases( elif any( [ carveout in carveout_answer - for carveout in list(investment_values.VALID_CARVEOUTS.keys()) + for carveout in list(VALID_CARVEOUTS.keys()) # + list(investment_values.VALID_SPECIAL.keys()) ] ): @@ -219,7 +236,10 @@ def process_carveout_or_special_case( def get_carveout_and_special_fields( - answer_dict: dict, carveout_answer: str, special_case_answer: list[str], filename: str + answer_dict: dict, + carveout_answer: str, + special_case_answer: list[str], + filename: str, ) -> list[dict]: """ Updates the answer dictionary with carveout-related (and special case) fields based on the given carveout answer. @@ -235,7 +255,9 @@ def get_carveout_and_special_fields( carveout_fields = [] # List to hold the results - if (carveout_answer in CARVEOUT_CONFIGS) or (special_case_answer in SPECIAL_CASE_CONFIGS): + if (carveout_answer in CARVEOUT_CONFIGS) or ( + special_case_answer in SPECIAL_CASE_CONFIGS + ): # Get the configuration for the carveout carveout_config = CARVEOUT_CONFIGS[carveout_answer] @@ -262,9 +284,7 @@ def get_carveout_and_special_fields( fs_breakout_questions=final_config["fs_questions"], filename=filename, methodology_prompt_template=final_config["methodology_prompt_template"], - fee_schedule_prompt_template=final_config[ - "fee_schedule_prompt_template" - ], + fee_schedule_prompt_template=final_config["fee_schedule_prompt_template"], term_key=final_config["term_key"], ) @@ -358,20 +378,32 @@ def methodology_breakout_single_row( ) # Addition propagation logic # Check if any row has ADDITION_DESC populated - has_addition = any(not string_utils.is_empty(row.get("ADDITION_DESC")) for row in results) + has_addition = any( + not string_utils.is_empty(row.get("ADDITION_DESC")) for row in results + ) if has_addition: - logging.debug(f"Propagating addition data to all {len(results)} rows in {filename}'s reimbursement primary answers for:\n{service} - {contract_reimbursement_method}") + logging.debug( + f"Propagating addition data to all {len(results)} rows in {filename}'s reimbursement primary answers for:\n{service} - {contract_reimbursement_method}" + ) # Find the row with addition data addition_data = {} for row in results: if not string_utils.is_empty(row.get("ADDITION_DESC")): addition_data = { "ADDITION_DESC": row.get("ADDITION_DESC", ""), - "ADDITION_MAX_FEE_RATE_INC": row.get("ADDITION_MAX_FEE_RATE_INC"), - "ADDITION_MAX_PCT_RATE_INC": row.get("ADDITION_MAX_PCT_RATE_INC"), - "ADDITION_RATE_CHANGE_TIMELINE": row.get("ADDITION_RATE_CHANGE_TIMELINE"), - "AARETE_DERIVED_ADDITION_RATE_CHANGE_TIMELINE": row.get("AARETE_DERIVED_ADDITION_RATE_CHANGE_TIMELINE"), + "ADDITION_MAX_FEE_RATE_INC": row.get( + "ADDITION_MAX_FEE_RATE_INC" + ), + "ADDITION_MAX_PCT_RATE_INC": row.get( + "ADDITION_MAX_PCT_RATE_INC" + ), + "ADDITION_RATE_CHANGE_TIMELINE": row.get( + "ADDITION_RATE_CHANGE_TIMELINE" + ), + "AARETE_DERIVED_ADDITION_RATE_CHANGE_TIMELINE": row.get( + "AARETE_DERIVED_ADDITION_RATE_CHANGE_TIMELINE" + ), } break # Apply addition data to all rows @@ -444,6 +476,7 @@ def _process_carveout_or_breakout( ) return True + def combine_one_to_n_answers( exhibit_level_answers: dict, reimbursement_level_answers: list[dict], @@ -477,7 +510,8 @@ def reimbursement_level( reimbursement_level_fields, exhibit_page: str, seen_pairs: set, - exhibit_lesser_of: str + exhibit_lesser_of: str, + constants: Constants, ): """ Processes reimbursement-level fields. @@ -503,12 +537,14 @@ def reimbursement_level( if reimbursement_primary_answers: # If any reimbursements found # Step 1: Split compound reimbursements FIRST - split_answers = split_compound_reimbursements(reimbursement_primary_answers, filename) + split_answers = split_compound_reimbursements( + reimbursement_primary_answers, constants.COMPOUND_INDICATORS, filename + ) # Step 2: Filter out any non-reimbursement content from split answers filtered_answers = filter_services_without_reimbursements( - split_answers, filename - ) + split_answers, filename + ) # Step 3: Deduplicate against previously seen (SERVICE_TERM, REIMB_TERM) pairs deduplicated_answers = deduplicate_with_accumulator( @@ -521,12 +557,18 @@ def reimbursement_level( deduplicated_answers, exhibit_lesser_of, filename ) logging.debug( - f"Applied exhibit lesser-of statement to {len(deduplicated_answers)} reimbursement pairs") + f"Applied exhibit lesser-of statement to {len(deduplicated_answers)} reimbursement pairs" + ) # Step 5: Continue with special cases (which includes methodology breakout) if deduplicated_answers: # Only process if we have valid reimbursement pairs # This will also run the methodology breakout prompt for non-carveout rows - carveout_answers = get_special_cases(deduplicated_answers, filename) + carveout_answers = get_special_cases( + deduplicated_answers, + constants.VALID_CARVEOUTS, + constants.VALID_SPECIAL, + filename, + ) return carveout_answers else: return [] # All pairs were duplicates, return empty list @@ -605,19 +647,19 @@ def filter_services_without_reimbursements( """ valid_reimbursements = [] - + for answer_dict in answers: if validate_reimbursements_for_llm(answer_dict, filename): valid_reimbursements.append(answer_dict) else: logging.debug(f"LLM filtered out: {answer_dict}") - + filtered_count = len(answers) - len(valid_reimbursements) if filtered_count > 0: logging.info( f"Filtered {filtered_count} services without clear reimbursement methodologies in {filename}." ) - + return valid_reimbursements @@ -634,9 +676,7 @@ def validate_reimbursements_for_llm(answer_dict: dict, filename: str) -> bool: service_term = str(answer_dict.get("SERVICE_TERM", "")).strip() reimb_term = str(answer_dict.get("REIMB_TERM", "")).strip() - if (string_utils.is_empty(service_term) - or string_utils.is_empty(reimb_term) - ): + if string_utils.is_empty(service_term) or string_utils.is_empty(reimb_term): logging.warning( f"Missing SERVICE_TERM or REIMB_TERM in {filename}: {answer_dict}" ) @@ -645,16 +685,23 @@ def validate_reimbursements_for_llm(answer_dict: dict, filename: str) -> bool: prompt = investment_prompts.VALIDATE_REIMBURSEMENTS_PROMPT(service_term, reimb_term) llm_response = llm_utils.invoke_claude( - prompt, "sonnet_latest", filename # Use sonnet for now but if we can simplify our VALIDATE_REIMBURSEMENTS_PROMPT, we can switch back to haiku_latest + prompt, + "sonnet_latest", + filename, # Use sonnet for now but if we can simplify our VALIDATE_REIMBURSEMENTS_PROMPT, we can switch back to haiku_latest ) - final_answer = string_utils.extract_text_from_delimiters( - llm_response, Delimiter.PIPE - ).strip().upper() + final_answer = ( + string_utils.extract_text_from_delimiters(llm_response, Delimiter.PIPE) + .strip() + .upper() + ) return final_answer == "YES" -def split_compound_reimbursements(answers: list[dict], filename: str) -> list[dict]: + +def split_compound_reimbursements( + answers: list[dict], COMPOUND_INDICATORS, filename: str +) -> list[dict]: """ Splits reimbursement terms that contain multiple distinct methodologies. @@ -666,37 +713,38 @@ def split_compound_reimbursements(answers: list[dict], filename: str) -> list[di list[dict]: List of dictionaries with split reimbursement terms. """ - split_answers = [] for answer_dict in answers: reimb_term = answer_dict.get("REIMB_TERM", "").lower().strip() # Check if this looks like a compound reimbursement - has_compound = any( - indicator in reimb_term for indicator in investment_values.compound_indicators - ) + has_compound = any(indicator in reimb_term for indicator in COMPOUND_INDICATORS) if has_compound: # Use LLM to split the compound reimbursement split_result = split_compound_reimbursement_llm( - answer_dict, filename) - + answer_dict, COMPOUND_INDICATORS, filename + ) + if split_result and len(split_result) > 1: - logging.info(f"Split compound reimbursement in {filename}: " - f"{answer_dict['SERVICE_TERM']} - {answer_dict['REIMB_TERM']}" - f" into {len(split_result)} parts.") - + logging.info( + f"Split compound reimbursement in {filename}: " + f"{answer_dict['SERVICE_TERM']} - {answer_dict['REIMB_TERM']}" + f" into {len(split_result)} parts." + ) + logging.info(f"Split results: {split_result}") - + split_answers.extend(split_result) - else: # If LLM didn't split, keep the original + else: # If LLM didn't split, keep the original split_answers.append(answer_dict) - else: # If not compound, keep the original + else: # If not compound, keep the original split_answers.append(answer_dict) - + return split_answers + def split_compound_reimbursement_llm(answer_dict: dict, filename: str) -> list[dict]: """ Uses LLM to split a compound reimbursement term into distinct methodologies. @@ -711,8 +759,7 @@ def split_compound_reimbursement_llm(answer_dict: dict, filename: str) -> list[d service_term = answer_dict.get("SERVICE_TERM", "").strip() reimb_term = answer_dict.get("REIMB_TERM", "").strip() - prompt = investment_prompts.SPLIT_REIMBURSEMENTS_PROMPT( - service_term, reimb_term) + prompt = investment_prompts.SPLIT_REIMBURSEMENTS_PROMPT(service_term, reimb_term) try: llm_response = llm_utils.invoke_claude( prompt, "haiku_latest", filename, max_tokens=4096 @@ -722,7 +769,7 @@ def split_compound_reimbursement_llm(answer_dict: dict, filename: str) -> list[d if not split_data or len(split_data) == 1: return [answer_dict] # No split needed, return original - + # Create new answer dictionaries for each split split_answers = [] for i, split_item in enumerate(split_data): @@ -732,19 +779,21 @@ def split_compound_reimbursement_llm(answer_dict: dict, filename: str) -> list[d split_service = split_item.get("SERVICE_TERM", "").strip() split_methodology = split_item.get("REIMB_TERM", "").strip() - new_answer['SERVICE_TERM'] = split_service - new_answer['REIMB_TERM'] = split_methodology + new_answer["SERVICE_TERM"] = split_service + new_answer["REIMB_TERM"] = split_methodology # Add a flag to indicate that this was split new_answer["COMPOUND_SPLIT_IND"] = "Y" - new_answer["COMPOUND_SPLIT_SEQUENCE"] = str(i+1) + new_answer["COMPOUND_SPLIT_SEQUENCE"] = str(i + 1) split_answers.append(new_answer) return split_answers except Exception as e: - logging.error(f"Error occurred while splitting reimbursement terms in {filename}: {e}") + logging.error( + f"Error occurred while splitting reimbursement terms in {filename}: {e}" + ) return [answer_dict] # Return original if error occurs - + def has_explicit_lesser_of_carveout(reimb_term: str) -> bool: """Check for explicit 'lesser of' carve-out language in the reimbursement term. @@ -758,15 +807,15 @@ def has_explicit_lesser_of_carveout(reimb_term: str) -> bool: bool: True if 'lesser of' carve-out language is found, False otherwise. """ - carveout_patterns = [ - "not subject to.*lesser", - "not bound by.*lesser" - ] + carveout_patterns = ["not subject to.*lesser", "not bound by.*lesser"] reimb_lower = reimb_term.lower() return any(re.search(pattern, reimb_lower) for pattern in carveout_patterns) -def create_exhibit_lesser_of_row(original_row: dict, exhibit_lesser_of: str, filename: str) -> dict: + +def create_exhibit_lesser_of_row( + original_row: dict, exhibit_lesser_of: str, filename: str +) -> dict: """Create enhanced row with exhibit lesser-of constraint applied, avoiding redundancy with substring checks. Args: @@ -785,25 +834,34 @@ def create_exhibit_lesser_of_row(original_row: dict, exhibit_lesser_of: str, fil # Skip if exhibit_lesser_of is empty or N/A if not exhibit_lesser_of or string_utils.is_empty(exhibit_lesser_of): return enhanced_row - + # FIRST-ORDER DEDUPLICATION: Check if the exhibit constraint is already present (case-insensitive) if exhibit_lesser_of.lower() in original_reimb.lower(): - logging.debug(f"Exhibit constraint '{exhibit_lesser_of}' already present in REIMB_TERM: {original_reimb}") + logging.debug( + f"Exhibit constraint '{exhibit_lesser_of}' already present in REIMB_TERM: {original_reimb}" + ) return enhanced_row # No need to modify if already present - + # SECOND-ORDER DEDUPLICATION: Use LLM to check if the exhibit constraint is redundant if is_exhibit_lesser_of_redundant(original_reimb, exhibit_lesser_of, filename): - logging.debug(f"LLM determined that exhibit constraint '{exhibit_lesser_of}' is semantically redundant with REIMB_TERM: {original_reimb}") - return enhanced_row # No need to modify if redundant + logging.debug( + f"LLM determined that exhibit constraint '{exhibit_lesser_of}' is semantically redundant with REIMB_TERM: {original_reimb}" + ) + return enhanced_row # No need to modify if redundant # Apply the exhibit lesser-of constraint enhanced_reimb = f"lesser of ({original_reimb}) or ({exhibit_lesser_of})" enhanced_row["REIMB_TERM"] = enhanced_reimb - logging.debug(f"Enhanced REIMB_TERM with exhibit lesser-of: {enhanced_row['REIMB_TERM']}") + logging.debug( + f"Enhanced REIMB_TERM with exhibit lesser-of: {enhanced_row['REIMB_TERM']}" + ) return enhanced_row -def apply_exhibit_lesser_of(rows: list[dict], exhibit_lesser_of: str, filename: str) -> list[dict]: + +def apply_exhibit_lesser_of( + rows: list[dict], exhibit_lesser_of: str, filename: str +) -> list[dict]: """Apply exhibit lesser-of constraint to rows that don't explicitly have a lesser-of carve-out. Args: @@ -832,7 +890,10 @@ def apply_exhibit_lesser_of(rows: list[dict], exhibit_lesser_of: str, filename: return enhanced_rows -def is_exhibit_lesser_of_redundant(reimb_term: str, exhibit_lesser_of: str, filename: str) -> bool: + +def is_exhibit_lesser_of_redundant( + reimb_term: str, exhibit_lesser_of: str, filename: str +) -> bool: """Use LLM to determine if exhibit-level lesser-of statement is redundant with the reimbursement term. This is a second step of filtering, after checking for exact substring matches. @@ -853,9 +914,17 @@ def is_exhibit_lesser_of_redundant(reimb_term: str, exhibit_lesser_of: str, file response = llm_utils.invoke_claude(prompt, "haiku_latest", filename=filename) logging.debug(f"LLM response for redundancy check in {filename}: {response}") - return string_utils.extract_text_from_delimiters(response, Delimiter.PIPE).strip().upper() == "YES" + return ( + string_utils.extract_text_from_delimiters(response, Delimiter.PIPE) + .strip() + .upper() + == "YES" + ) -def rate_escalator_breakout(rate_escalator_statement: str, filename: str, rate_escalator_fields: FieldSet) -> dict: + +def rate_escalator_breakout( + rate_escalator_statement: str, filename: str, rate_escalator_fields: FieldSet +) -> dict: """ Extracts rate escalator fields from the rate escalator statement using LLM. @@ -868,11 +937,17 @@ def rate_escalator_breakout(rate_escalator_statement: str, filename: str, rate_e list[dict]: List of dictionaries containing rate escalator information. """ if string_utils.is_empty(rate_escalator_statement): - logging.debug(f"No rate escalator statement found in {filename}. Skipping extraction.") + logging.debug( + f"No rate escalator statement found in {filename}. Skipping extraction." + ) return {} - prompt = investment_prompts.RATE_ESCALATOR_BREAKOUT_PROMPT(rate_escalator_statement, rate_escalator_fields.print_prompt_dict()) + prompt = investment_prompts.RATE_ESCALATOR_BREAKOUT_PROMPT( + rate_escalator_statement, rate_escalator_fields.print_prompt_dict() + ) logging.debug(f"Rate Escalator Prompt for {filename}: {prompt}") llm_response = llm_utils.invoke_claude(prompt, "sonnet_latest", filename) logging.debug(f"LLM Response for {filename}: {llm_response}") - return string_utils.universal_json_load(llm_response) or {} # Return empty dict if no response \ No newline at end of file + return ( + string_utils.universal_json_load(llm_response) or {} + ) # Return empty dict if no response diff --git a/fieldExtraction/src/investment/one_to_one_funcs.py b/fieldExtraction/src/investment/one_to_one_funcs.py index 4f65c61..92ca358 100644 --- a/fieldExtraction/src/investment/one_to_one_funcs.py +++ b/fieldExtraction/src/investment/one_to_one_funcs.py @@ -2,23 +2,31 @@ import logging import os import pandas as pd +from rapidfuzz import fuzz + +import src.investment.postprocessing_funcs as postprocessing_funcs import src.prompts.investment_prompts as prompts import src.utils.llm_utils as llm_utils import src.utils.string_utils as string_utils - -from rapidfuzz import fuzz +from constants.delimiters import Delimiter from src import config -from src import postprocessing_funcs as generic_postprocessing_funcs -from src.enums.delimiters import Delimiter -from src.investment import investment_postprocessing_funcs -from src.investment.one_to_n_funcs import METHODOLOGY_BREAKOUT_QUESTIONS, rate_escalator_breakout -from src.investment.smart_chunking_funcs import (field_context, group_fields, stitch_chunks) +from src.investment import postprocessing_funcs +from src.investment.one_to_n_funcs import ( + METHODOLOGY_BREAKOUT_QUESTIONS, + rate_escalator_breakout, +) +from src.investment.smart_chunking_funcs import ( + field_context, + group_fields, + stitch_chunks, +) from src.investment.vision_funcs import get_image_array_based_answer from src.prompts.investment_prompts import REIMB_TERM_BREAKOUT, Field, FieldSet MAX_CONTEXT_LENGTH = 100000 -def extract_global_lesser_of(contract_text: str, filename:str) -> dict: + +def extract_global_lesser_of(contract_text: str, filename: str) -> dict: """Extract global "lesser of" statements that apply contract-wide. Args: @@ -29,21 +37,33 @@ def extract_global_lesser_of(contract_text: str, filename:str) -> dict: dict: A dictionary containing the extracted "lesser of" statement. """ - global_lesser_of_field = Field.load_from_file(file_path=config.FIELD_JSON_PATH, field_name="GLOBAL_LESSER_OF_STATEMENT") + global_lesser_of_field = Field.load_from_file( + file_path=config.FIELD_JSON_PATH, field_name="GLOBAL_LESSER_OF_STATEMENT" + ) # Process using existing full context logic prompt = prompts.ONE_TO_ONE_SINGLE_FIELD_TEMPLATE( context=contract_text[0 : min(MAX_CONTEXT_LENGTH, len(contract_text) - 1)], question=global_lesser_of_field.prompt, ) - logging.debug(f"Extracting global lesser of statement for {filename} with prompt: {prompt}") + logging.debug( + f"Extracting global lesser of statement for {filename} with prompt: {prompt}" + ) response = llm_utils.invoke_claude(prompt, "sonnet_latest", filename) logging.debug(f"Response for global lesser of statement extraction: {response}") - return {"GLOBAL_LESSER_OF_STATEMENT": string_utils.extract_text_from_delimiters(response, Delimiter.PIPE) or "N/A"} + return { + "GLOBAL_LESSER_OF_STATEMENT": string_utils.extract_text_from_delimiters( + response, Delimiter.PIPE + ) + or "N/A" + } -def filter_payer_from_providers(providers: list[dict], payer_name: str, threshold: int = 80) -> list[dict]: + +def filter_payer_from_providers( + providers: list[dict], payer_name: str, threshold: int = 80 +) -> list[dict]: """Filters out providers whose names are too similar to the payer name. Sometimes the LLM pulls the payer as one of the providers, and we want to drop those. Args: @@ -55,12 +75,16 @@ def filter_payer_from_providers(providers: list[dict], payer_name: str, threshol list[dict]: Filtered list of providers """ if not payer_name or string_utils.is_empty(payer_name) or payer_name == "UNKNOWN": - return providers # Skip filtering if there's not a valid payer name - + return providers # Skip filtering if there's not a valid payer name + filtered_providers = [] for provider in providers: provider_name = provider.get("NAME", "") - if not provider_name or string_utils.is_empty(provider_name) or provider_name == "UNKNOWN": + if ( + not provider_name + or string_utils.is_empty(provider_name) + or provider_name == "UNKNOWN" + ): filtered_providers.append(provider) continue # Calculate similarity score using fuzzy matching @@ -70,14 +94,16 @@ def filter_payer_from_providers(providers: list[dict], payer_name: str, threshol if similarity_score < threshold: filtered_providers.append(provider) else: - print(f"Filtered out provider that matched payer: '{provider_name}' similar to '{payer_name}' with score {similarity_score}") - + print( + f"Filtered out provider that matched payer: '{provider_name}' similar to '{payer_name}' with score {similarity_score}" + ) + return filtered_providers -def run_smart_chunked_fields(one_to_one_fields: FieldSet, - contract_text: str, - filename: str, - text_dict: dict)-> dict[str, str]: + +def run_smart_chunked_fields( + one_to_one_fields: FieldSet, contract_text: str, filename: str, text_dict: dict +) -> dict[str, str]: """Process fields that require smart chunking and return the extracted answers. Args: @@ -91,33 +117,42 @@ def run_smart_chunked_fields(one_to_one_fields: FieldSet, dict[str, str]: Dictionary mapping field names to extracted answers. """ - one_to_one_fields, splits = field_context(one_to_one_fields, contract_text, filename) + one_to_one_fields, splits = field_context( + one_to_one_fields, contract_text, filename + ) - smart_chunked_fields = one_to_one_fields.filter(field_type="smart_chunked") # Only get the ones that are still smart_chunked after getting context + smart_chunked_fields = one_to_one_fields.filter( + field_type="smart_chunked" + ) # Only get the ones that are still smart_chunked after getting context groups = group_fields(smart_chunked_fields) - # create new field groups and corresponding chunks + # create new field groups and corresponding chunks new_field_groups = {} ac_chunks = {} for idx, group in enumerate(groups, 1): # convert dictionary of fields to list of fields group = list(group) new_field_groups[f"Group {idx}"] = {"fields": group} - + # combine chunks of fields of one group into one list retrieved_text_list = [field.retrieved_text for field in group] # Flatten list of lists into one list - retrieved_text_list = [item for internal_list in retrieved_text_list for item in internal_list] - retrieved_text_list = sorted(set(item for sublist in retrieved_text_list for item in (sublist if isinstance(sublist, (list, set)) else [sublist]))) + retrieved_text_list = [ + item for internal_list in retrieved_text_list for item in internal_list + ] + retrieved_text_list = sorted( + set( + item + for sublist in retrieved_text_list + for item in (sublist if isinstance(sublist, (list, set)) else [sublist]) + ) + ) # join relevant chunks of all fields of the group into one text ac_chunks[f"Group {idx}"] = stitch_chunks(retrieved_text_list) - - - field_groups = [ - field_group for field_group in new_field_groups.keys() - ] + + field_groups = [field_group for field_group in new_field_groups.keys()] answers_dict = {} for field_group in field_groups: @@ -145,8 +180,12 @@ def run_smart_chunked_fields(one_to_one_fields: FieldSet, prompt, "sonnet_latest", filename, 8192 ) if len(fields) == 1: - field_group_answer_parsed = string_utils.extract_text_from_delimiters(field_group_answer_raw, Delimiter.PIPE) - field_group_answer_dict = {fields[0].field_name: field_group_answer_parsed} + field_group_answer_parsed = string_utils.extract_text_from_delimiters( + field_group_answer_raw, Delimiter.PIPE + ) + field_group_answer_dict = { + fields[0].field_name: field_group_answer_parsed + } elif len(fields) > 1: field_group_answer_dict = string_utils.json_parsing_search( field_group_answer_raw, [field.field_name for field in fields] @@ -157,10 +196,12 @@ def run_smart_chunked_fields(one_to_one_fields: FieldSet, if expected_fields != received_fields: missing = expected_fields - received_fields extra = received_fields - expected_fields - raise ValueError(f"Field mismatch - Missing: {missing}, Extra: {extra}") + raise ValueError( + f"Field mismatch - Missing: {missing}, Extra: {extra}" + ) - # Load up answers dict - for field in fields: + # Load up answers dict + for field in fields: field_answer = field_group_answer_dict[field.field_name] answers_dict[field.field_name] = field_answer @@ -171,13 +212,18 @@ def run_smart_chunked_fields(one_to_one_fields: FieldSet, answers_dict = check_and_update_effective_date(answers_dict, text_dict, filename) # even after running the vision api, if the effective date is still N/A, we need to run the prompt again with full context - if "AARETE_DERIVED_EFFECTIVE_DT" in answers_dict and string_utils.is_empty(answers_dict.get("AARETE_DERIVED_EFFECTIVE_DT")): - # if the effective date is N/A, we need to run the prompt again with full context - smart_chunked_fields.update_field_attr( field_name = 'AARETE_DERIVED_EFFECTIVE_DT', - attr = 'field_type', - value = "full_context") + if "AARETE_DERIVED_EFFECTIVE_DT" in answers_dict and string_utils.is_empty( + answers_dict.get("AARETE_DERIVED_EFFECTIVE_DT") + ): + # if the effective date is N/A, we need to run the prompt again with full context + smart_chunked_fields.update_field_attr( + field_name="AARETE_DERIVED_EFFECTIVE_DT", + attr="field_type", + value="full_context", + ) return answers_dict + def check_and_update_effective_date(answers_dict, text_dict, filename): """ Checks and updates the effective date in the answers dictionary. If the effective date is empty, @@ -193,28 +239,44 @@ def check_and_update_effective_date(answers_dict, text_dict, filename): """ # Postprocess the effective date - answers_dict["AARETE_DERIVED_EFFECTIVE_DT"] = generic_postprocessing_funcs.date_postprocessing( - answers_dict.get("AARETE_DERIVED_EFFECTIVE_DT", "N/A") + answers_dict["AARETE_DERIVED_EFFECTIVE_DT"] = ( + postprocessing_funcs.date_postprocessing( + answers_dict.get("AARETE_DERIVED_EFFECTIVE_DT", "N/A") + ) ) - answers_dict["AARETE_DERIVED_EFFECTIVE_DT"] = investment_postprocessing_funcs.validate_and_reformat_date( - answers_dict["AARETE_DERIVED_EFFECTIVE_DT"] + answers_dict["AARETE_DERIVED_EFFECTIVE_DT"] = ( + postprocessing_funcs.validate_and_reformat_date( + answers_dict["AARETE_DERIVED_EFFECTIVE_DT"] + ) ) # If the effective date is empty, Get the effective date using the vision-based LLM - if config.ENABLE_VISION and string_utils.is_empty(answers_dict.get("AARETE_DERIVED_EFFECTIVE_DT")): - # if answers_dict.get("AARETE_DERIVED_EFFECTIVE_DT") in ["N/A", "UNKNOWN"]: - effective_date_page_numbers = list(string_utils.extract_effective_date_pages(text_dict, filename).keys()) + if config.ENABLE_VISION and string_utils.is_empty( + answers_dict.get("AARETE_DERIVED_EFFECTIVE_DT") + ): + # if answers_dict.get("AARETE_DERIVED_EFFECTIVE_DT") in ["N/A", "UNKNOWN"]: + effective_date_page_numbers = list( + string_utils.extract_effective_date_pages(text_dict, filename).keys() + ) - pdf_base_path = "/root/doczy.ai/fieldExtraction/src/investment/pdf_files" # local path + pdf_base_path = ( + "/root/doczy.ai/fieldExtraction/src/investment/pdf_files" # local path + ) pdf_path = os.path.join(pdf_base_path, filename.replace("txt", "pdf")) # pdf_path = os.path.join(DOCZY_PDF_FILES_BUCKET_S3_URL, filename.replace("txt", "pdf")) # s3 path image_answer_dict = get_image_array_based_answer( - pdf_path, effective_date_page_numbers, filename, fields=["AARETE_DERIVED_EFFECTIVE_DT"] + pdf_path, + effective_date_page_numbers, + filename, + fields=["AARETE_DERIVED_EFFECTIVE_DT"], + ) + answers_dict["AARETE_DERIVED_EFFECTIVE_DT"] = image_answer_dict.get( + "AARETE_DERIVED_EFFECTIVE_DT", "N/A" ) - answers_dict["AARETE_DERIVED_EFFECTIVE_DT"] = image_answer_dict.get("AARETE_DERIVED_EFFECTIVE_DT", "N/A") return answers_dict + def run_full_context_fields( one_to_one_fields: FieldSet, contract_text: str, @@ -227,7 +289,9 @@ def run_full_context_fields( questions=full_context_fields.print_prompt_dict(), ) try: - claude_answer_raw = llm_utils.invoke_claude(full_context_prompt, "sonnet_latest", filename, 8192) + claude_answer_raw = llm_utils.invoke_claude( + full_context_prompt, "sonnet_latest", filename, 8192 + ) full_context_answers_dict = string_utils.universal_json_load(claude_answer_raw) except Exception as e: print(f"Error processing high accuracy fields: {str(e)}") @@ -237,10 +301,12 @@ def run_full_context_fields( # Normalize PROVIDER_STATE to standardized two-letter abbreviation if "PROVIDER_STATE" in full_context_answers_dict: - full_context_answers_dict["PROVIDER_STATE"] = string_utils.normalize_state_to_abbreviation( - full_context_answers_dict["PROVIDER_STATE"] + full_context_answers_dict["PROVIDER_STATE"] = ( + string_utils.normalize_state_to_abbreviation( + full_context_answers_dict["PROVIDER_STATE"] + ) ) - + # Handle separate one-to-one fields that require individual processing separate_fields = one_to_one_fields.filter(field_type="full_context_separate") @@ -249,21 +315,27 @@ def run_full_context_fields( # Add global lesser of extraction global_lesser_of_results = extract_global_lesser_of(contract_text, filename) full_context_answers_dict.update(global_lesser_of_results) - + if "RATE_ESCALATOR_STATEMENT" in full_context_answers_dict: # If RATE_ESCALATOR_STATEMENT is present, we need to run the rate escalator breakout rate_escalator_statement = full_context_answers_dict["RATE_ESCALATOR_STATEMENT"] - rate_escalator_fields = FieldSet(config.FIELD_JSON_PATH, relationship="one_to_n", field_type="rate_escalator") - rate_escalator_answers = rate_escalator_breakout(rate_escalator_statement, filename, rate_escalator_fields) + rate_escalator_fields = FieldSet( + config.FIELD_JSON_PATH, relationship="one_to_n", field_type="rate_escalator" + ) + rate_escalator_answers = rate_escalator_breakout( + rate_escalator_statement, filename, rate_escalator_fields + ) full_context_answers_dict.update(rate_escalator_answers) return full_context_answers_dict -def create_global_lesser_of_row(original_row: pd.Series, global_methodology_breakout: dict[str, str]) -> pd.Series: +def create_global_lesser_of_row( + original_row: pd.Series, global_methodology_breakout: dict[str, str] +) -> pd.Series: """Create a new row with global lesser of constraint applied to original reimbursement term. - Takes an original row that lacks lesser of logic and creates a duplicate with the global lesser of + Takes an original row that lacks lesser of logic and creates a duplicate with the global lesser of methodology breakout results applied. Preserves the original REIMB_ID and SERVICE_TERM while updating reimbursement methodology fields. @@ -282,14 +354,16 @@ def create_global_lesser_of_row(original_row: pd.Series, global_methodology_brea new_row[field] = value # Ensure lesser of indicator is set - new_row['LESSER_OF_IND'] = "Y" + new_row["LESSER_OF_IND"] = "Y" # Add flag to track that this is a global lesser of row - new_row['GLOBAL_LESSER_OF_APPLIED'] = "Y" + new_row["GLOBAL_LESSER_OF_APPLIED"] = "Y" return new_row -def run_global_lesser_of_breakout(global_lesser_of_stmt: str, filename: str) -> dict[str, str]: +def run_global_lesser_of_breakout( + global_lesser_of_stmt: str, filename: str +) -> dict[str, str]: """Run methodology breakout on global lesser of statement to extract structured reimbursement details. Uses the same REIMB_TERM_BREAKOUT prompt as the one used for individual service terms @@ -299,7 +373,7 @@ def run_global_lesser_of_breakout(global_lesser_of_stmt: str, filename: str) -> global_lesser_of_stmt (str): Global lesser of statement extracted from contract (e.g., "lesser of billed or allowable"). filename (str): Name of the file being processed, used for logging and debugging. Returns: - dict[str, str]: Dictionary of methodology breakout fields (REIMB_PCT_RATE, + dict[str, str]: Dictionary of methodology breakout fields (REIMB_PCT_RATE, REIMB_FEE_RATE, etc.) extracted from the global lesser of statement. Returns an empty dict if parsing fails. """ @@ -307,8 +381,12 @@ def run_global_lesser_of_breakout(global_lesser_of_stmt: str, filename: str) -> # Create a mock service term for the breakout mock_service = "Global Lesser Of (all services)" - prompt = REIMB_TERM_BREAKOUT(mock_service, global_lesser_of_stmt, METHODOLOGY_BREAKOUT_QUESTIONS) - logging.debug(f"Running global lesser of breakout for {filename} with prompt: {prompt}") + prompt = REIMB_TERM_BREAKOUT( + mock_service, global_lesser_of_stmt, METHODOLOGY_BREAKOUT_QUESTIONS + ) + logging.debug( + f"Running global lesser of breakout for {filename} with prompt: {prompt}" + ) llm_response = llm_utils.invoke_claude(prompt, "sonnet_latest", filename) @@ -317,9 +395,11 @@ def run_global_lesser_of_breakout(global_lesser_of_stmt: str, filename: str) -> result = breakout_results[0] if breakout_results else {} # Add the global lesser of statement as the REIMB_TERM - result['REIMB_TERM'] = f"[GLOBAL LESSER OF] {global_lesser_of_stmt}" + result["REIMB_TERM"] = f"[GLOBAL LESSER OF] {global_lesser_of_stmt}" return result except: - logging.error(f"Failed to parse global lesser of breakout response for {filename}. Response: {llm_response}") + logging.error( + f"Failed to parse global lesser of breakout response for {filename}. Response: {llm_response}" + ) return {"REIMB_TERM": f"[GLOBAL LESSER OF] {global_lesser_of_stmt}"} diff --git a/fieldExtraction/src/investment/postprocess.py b/fieldExtraction/src/investment/postprocess.py index 1131c93..e2f8a00 100644 --- a/fieldExtraction/src/investment/postprocess.py +++ b/fieldExtraction/src/investment/postprocess.py @@ -1,82 +1,92 @@ import src.config as config -from src.constants.investment_columns import COLUMN_ORDER -from src.investment import investment_postprocessing_funcs +from constants.constants import Constants +from constants.investment_columns import COLUMN_ORDER +from src.investment import postprocessing_funcs + # I don't know why this import is necessary but if it's not present, unit tests fail # for `test_date_postprocess` from src.prompts.investment_prompts import FieldSet, invoke_derived_term_date -def postprocess(df): +def postprocess(df, constants: Constants): if df.shape[0] > 0: - - df['CLIENT_NAME'] = config.CLIENT_NAME + + df["CLIENT_NAME"] = config.CLIENT_NAME # Rename columns - df = investment_postprocessing_funcs.rename_columns(df) + df = postprocessing_funcs.rename_columns(df) # Sort to ensure consistent ordering - sort_columns = ['FILE_NAME'] - if 'EXHIBIT_PAGE' in df.columns: - sort_columns.append('EXHIBIT_PAGE') - if 'REIMB_LESSER_OF_ID' in df.columns: - sort_columns.append('REIMB_LESSER_OF_ID') + sort_columns = ["FILE_NAME"] + if "EXHIBIT_PAGE" in df.columns: + sort_columns.append("EXHIBIT_PAGE") + if "REIMB_LESSER_OF_ID" in df.columns: + sort_columns.append("REIMB_LESSER_OF_ID") df = df.sort_values(by=sort_columns).reset_index(drop=True) # Postprocess date fields - df = investment_postprocessing_funcs.date_postprocess(df, config.FIELD_JSON_PATH) + df = postprocessing_funcs.date_postprocess(df, config.FIELD_JSON_PATH) # Format rate fields if "REIMB_FEE_RATE" in df.columns: - df['REIMB_FEE_RATE'] = df['REIMB_FEE_RATE'].apply(investment_postprocessing_funcs.format_rate_fields_with_commas) + df["REIMB_FEE_RATE"] = df["REIMB_FEE_RATE"].apply( + postprocessing_funcs.format_rate_fields_with_commas + ) if "REIMB_PCT_RATE" in df.columns: - df['REIMB_PCT_RATE'] = df['REIMB_PCT_RATE'].apply(investment_postprocessing_funcs.format_rate_fields_with_commas) - + df["REIMB_PCT_RATE"] = df["REIMB_PCT_RATE"].apply( + postprocessing_funcs.format_rate_fields_with_commas + ) + for col in df.columns: if "_IND" in col: - df[col] = df[col].apply(investment_postprocessing_funcs.normalize_indicator_field) + df[col] = df[col].apply(postprocessing_funcs.normalize_indicator_field) # Normalize _IND fields and clean up TIN/NPI fields if "TIN" in col or "NPI" in col: - df[col] = df[col].apply(investment_postprocessing_funcs.remove_hyphens) + df[col] = df[col].apply(postprocessing_funcs.remove_hyphens) # Apply the flatten_singleton_string_list function to the 'CPT' column if "_CD" in col and "CPT" not in col: - df[col] = df[col].apply(investment_postprocessing_funcs.flatten_singleton_string_list) + df[col] = df[col].apply( + postprocessing_funcs.flatten_singleton_string_list + ) # Convert DATE to YYYY/MM/DD format if "_DT" in col or "DATE" in col: - df[col] = df[col].apply(investment_postprocessing_funcs.validate_and_reformat_date) + df[col] = df[col].apply(postprocessing_funcs.validate_and_reformat_date) if "CPT" in col: - df[col] = df[col].apply(investment_postprocessing_funcs.normalize_cpt_fields) + df[col] = df[col].apply(postprocessing_funcs.normalize_cpt_fields) # Deduplicate provider fields - remove GROUP values from OTHER fields and deduplicate OTHER lists - df = investment_postprocessing_funcs.deduplicate_provider_columns(df) + df = postprocessing_funcs.deduplicate_provider_columns(df) # Normalize the 'AUTO_RENEWAL_TERM' column - df = investment_postprocessing_funcs.auto_renewal(df) + df = postprocessing_funcs.auto_renewal(df) # Check conditions for TERMINATION_DT, AUTO_RENEWAL_IND, and AARETE_DERIVED_TERMINATION_DT - df = investment_postprocessing_funcs.update_termination_date_for_conditions(df) - + df = postprocessing_funcs.update_termination_date_for_conditions(df) + # Standardize the 'AARETE_DERIVED_REIMB_METHOD' column and updates the 'AARETE_DERIVED_FEE_SCHEDULE' column based on specific values. - df = investment_postprocessing_funcs.standardize_reimb_method_and_fee_schedule(df) + df = postprocessing_funcs.standardize_reimb_method_and_fee_schedule( + df, constants.VALID_UNIT_OF_MEASURE + ) # update reimb_pct_rate - df = investment_postprocessing_funcs.fill_empty_reimb_pct_rate(df) - - df = investment_postprocessing_funcs.remove_redundant_reimb_info(df) + df = postprocessing_funcs.fill_empty_reimb_pct_rate(df) - df = investment_postprocessing_funcs.add_aarete_derived_amendment_num(df) + df = postprocessing_funcs.remove_redundant_reimb_info(df) + + df = postprocessing_funcs.add_aarete_derived_amendment_num(df) # Process PATIENT_AGE_RANGE into PATIENT_AGE_MIN and PATIENT_AGE_MAX - df = investment_postprocessing_funcs.process_patient_age_range(df) + df = postprocessing_funcs.process_patient_age_range(df) # Add AARETE_DERIVED_PRODUCT - df = investment_postprocessing_funcs.add_aarete_derived_product(df) + df = postprocessing_funcs.add_aarete_derived_product(df) # Add GREATER_OF_IND df["GREATER_OF_IND"] = "N" # Standardize output column order - this should ALWAYS be the final postprocessing step - df = investment_postprocessing_funcs.reorder_columns(df, COLUMN_ORDER) - + df = postprocessing_funcs.reorder_columns(df, COLUMN_ORDER) + return df else: - return df \ No newline at end of file + return df diff --git a/fieldExtraction/src/investment/investment_postprocessing_funcs.py b/fieldExtraction/src/investment/postprocessing_funcs.py similarity index 70% rename from fieldExtraction/src/investment/investment_postprocessing_funcs.py rename to fieldExtraction/src/investment/postprocessing_funcs.py index 0ec42da..aa5af54 100644 --- a/fieldExtraction/src/investment/investment_postprocessing_funcs.py +++ b/fieldExtraction/src/investment/postprocessing_funcs.py @@ -3,21 +3,20 @@ import hashlib import os import re from datetime import datetime +from functools import cache import pandas as pd -from src import postprocessing_funcs as generic_postprocessing_funcs -from src.constants import investment_values -from src.prompts.investment_prompts import FieldSet, invoke_derived_term_date -from src.utils import string_utils + import src.prompts.investment_prompts as investment_prompts import src.utils.llm_utils as llm_utils -from src.enums.delimiters import Delimiter -import src.config as config +from constants.delimiters import Delimiter +from src.prompts.investment_prompts import FieldSet, invoke_derived_term_date +from src.utils import string_utils # Determine the base directory for the project BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # Path to the mappings folder -MAPPINGS_DIR = os.path.join(BASE_DIR, 'crosswalk', 'mappings') +MAPPINGS_DIR = os.path.join(BASE_DIR, "crosswalk", "mappings") def rename_columns(df): @@ -32,10 +31,12 @@ def rename_columns(df): # Check if the DataFrame is empty, return it as is if it is if df.empty: return df - + # If not empty, proceed with column renaming - rename_map = {"PROCEDURE_CD" : "CPT4_PROC_CD", - "PROCEDURE_CD_DESC" : "CPT4_PROC_CD_DESC"} + rename_map = { + "PROCEDURE_CD": "CPT4_PROC_CD", + "PROCEDURE_CD_DESC": "CPT4_PROC_CD_DESC", + } # Rename columns using the mapping df = df.rename(columns=rename_map) return df @@ -44,7 +45,7 @@ def rename_columns(df): def normalize_indicator_field(value: str) -> str: """ Normalize the indicator field value to 'Y' or 'N'. - + Args: value (str): The input value from an _IND field. Returns: @@ -52,13 +53,13 @@ def normalize_indicator_field(value: str) -> str: """ if isinstance(value, str) and value.strip().upper() == "Y": return "Y" - return 'N' + return "N" def format_rate_fields_with_commas(value: str) -> str: """ Format the rate with commas and two decimal places. - + Args: value (str): A string representing a numeric value. Returns: @@ -66,14 +67,14 @@ def format_rate_fields_with_commas(value: str) -> str: """ if string_utils.is_empty(value): return "" - + try: numeric_value = float(value) return f"{numeric_value:,.2f}" except ValueError: return "" - + def remove_hyphens(value: str) -> str: """ Remove hyphens from the input string. @@ -114,13 +115,14 @@ def flatten_singleton_string_list(list_str: str) -> str: except (ValueError, SyntaxError): return list_str + def validate_and_reformat_date(date_str: str) -> str: """ Validate if a string is in YYYY/MM/DD format or reformat it to YYYY/MM/DD if possible. - + Args: date_str (str): The input date string. - + Returns: str: The reformatted date string if valid or reformatted, otherwise the original string. """ @@ -133,9 +135,7 @@ def validate_and_reformat_date(date_str: str) -> str: return date_str except ValueError: # Attempt to reformat the date from known formats - known_formats = [ - "%Y-%m-%d", "%m/%d/%Y", "%d-%b-%Y", "%d/%m/%Y" - ] + known_formats = ["%Y-%m-%d", "%m/%d/%Y", "%d-%b-%Y", "%d/%m/%Y"] for fmt in known_formats: try: return datetime.strptime(date_str, fmt).strftime("%Y/%m/%d") @@ -143,6 +143,25 @@ def validate_and_reformat_date(date_str: str) -> str: continue return date_str + +@cache # memoize repeated calls to this function +def date_postprocessing(date: str) -> str: + """Processes dates using `investment_prompts.date_fix_prompt`. Following the call to the LLM, the answer is extracted from the response and returned. + + Args: + date (str): Input date string to be processed. + + Returns: + str: Processed date; either a date in YYYY/MM/DD format, a duration (e.g. "3 years"), or "N/A". + """ + if string_utils.is_empty(date): + return "N/A" + + prompt = investment_prompts.DATE_FIX_PROMPT(date) + response = llm_utils.invoke_claude(prompt, "sonnet_latest", "date_fix") + return string_utils.extract_text_from_delimiters(response, Delimiter.PIPE) + + def date_postprocess(df, field_json_path): """ Postprocess the date fields in the DataFrame. @@ -151,32 +170,39 @@ def date_postprocess(df, field_json_path): date_fields = FieldSet(file_path=field_json_path).filter(format="date") for field in date_fields.fields: if field.field_name in df.columns: - df[field.field_name] = list(map(generic_postprocessing_funcs.date_postprocessing, df[field.field_name])) + df[field.field_name] = list(map(date_postprocessing, df[field.field_name])) # Derived termination date - df['AARETE_DERIVED_TERMINATION_DT'] = list(map(invoke_derived_term_date, df['AARETE_DERIVED_EFFECTIVE_DT'], df['TERMINATION_DT'])) - + df["AARETE_DERIVED_TERMINATION_DT"] = list( + map( + invoke_derived_term_date, + df["AARETE_DERIVED_EFFECTIVE_DT"], + df["TERMINATION_DT"], + ) + ) + return df + def normalize_auto_renewal_term(text: str) -> str: """ Normalize the auto-renewal term to a standard format. - + Args: text (str): The input auto-renewal term. - + Returns: str: The normalized auto-renewal term. """ if not text or not isinstance(text, str): return "" - + # Convert to lowercase and strip whitespace text = text.lower().strip() - + # Remove numbers enclosed in parentheses (e.g., "(1) ", "(12) ") text = re.sub(r"\(\d+\) ", "", text) - + # Define replacements replacements = { r"\bone\b": "1", @@ -195,35 +221,38 @@ def normalize_auto_renewal_term(text: str) -> str: # Apply replacements for pattern, replacement in replacements.items(): text = re.sub(pattern, replacement, text) - + # Remove any remaining parentheses or extra spaces text = text.strip().replace("(", "").replace(")", "") - - text = text.replace("-", " ") + + text = text.replace("-", " ") if text == "year": text = "1 year" - + return text + def normalize_cpt_fields(value): """ Normalize code values to a list format. - + Args: value (Any): Input value which could be a list, string, or other types. - + Returns: list: A normalized list of string codes. """ if not value or (isinstance(value, float) and pd.isna(value)): # Handle NaN values - return "" # Empty string + return "" # Empty string # First normalize to a list if isinstance(value, (list, tuple)): # If already a list or tuple result = [str(v).strip() for v in value] elif isinstance(value, str): # If it's a string value = value.strip() - if value.startswith("[") and value.endswith("]"): # String representation of a list + if value.startswith("[") and value.endswith( + "]" + ): # String representation of a list try: # Parse it first parsed_list = ast.literal_eval(value) @@ -238,7 +267,7 @@ def normalize_cpt_fields(value): result.append(item_str) except (ValueError, SyntaxError): result = [value] # If parsing fails, treat it as a single value - elif "," in value: # If it's a comma-separated string + elif "," in value: # If it's a comma-separated string # Split by comma and create a list result = [str(v).strip() for v in value.split(",")] elif "-" in value: @@ -250,11 +279,12 @@ def normalize_cpt_fields(value): result = [str(int(value)).strip()] else: result = [str(value).strip()] # Default case for other types - + return str(result) # Return the list directly + def generate_reimb_ids(df: pd.DataFrame) -> pd.DataFrame: - """Generates REIMB_ID and REIMB_LESSER_OF_ID for each row in the DataFrame based on + """Generates REIMB_ID and REIMB_LESSER_OF_ID for each row in the DataFrame based on the FILE_NAME and other fields. The REIMB_ID is constructed using the following format: {FILE_NAME}_exh_pg_{EXHIBIT_PAGE}_{index}_{hash_value} @@ -277,79 +307,105 @@ def generate_reimb_ids(df: pd.DataFrame) -> pd.DataFrame: pd.DataFrame: DataFrame with generated REIMB_IDs and REIMB_LESSER_OF_IDs for each row. """ # First sort to ensure consistent ordering - df = df.sort_values(by=['FILE_NAME', 'EXHIBIT_PAGE']) if "EXHIBIT_PAGE" in df.columns else df.sort_values(by=['FILE_NAME']) + df = ( + df.sort_values(by=["FILE_NAME", "EXHIBIT_PAGE"]) + if "EXHIBIT_PAGE" in df.columns + else df.sort_values(by=["FILE_NAME"]) + ) # Create a temporary copy to avoid SettingWithCopyWarning df_temp = df.copy() # Process each file+exhibit_page group to reset counter for each exhibit page - group_cols = ['FILE_NAME', 'EXHIBIT_PAGE'] if "EXHIBIT_PAGE" in df.columns else ['FILE_NAME'] + group_cols = ( + ["FILE_NAME", "EXHIBIT_PAGE"] if "EXHIBIT_PAGE" in df.columns else ["FILE_NAME"] + ) for group_key, indices in df.groupby(group_cols).groups.items(): if isinstance(group_key, tuple): filename, exhibit_page = group_key exhibit_page = str(exhibit_page).zfill(3) else: filename = group_key - exhibit_page = '000' + exhibit_page = "000" clean_filename = str(filename).split("/")[-1].replace(".", "_") for i, idx in enumerate(indices): row = df.loc[idx] # Get fields to include in hash - service_term = str(row.get('SERVICE_TERM', '')) - reimb_term = str(row.get('REIMB_TERM', '')) - program = str(row.get('PROGRAM', '')) - product = str(row.get('PRODUCT', '')) - network = str(row.get('NETWORK', '')) - lob = str(row.get('LOB', '')) + service_term = str(row.get("SERVICE_TERM", "")) + reimb_term = str(row.get("REIMB_TERM", "")) + program = str(row.get("PROGRAM", "")) + product = str(row.get("PRODUCT", "")) + network = str(row.get("NETWORK", "")) + lob = str(row.get("LOB", "")) # Create hash string hash_string = f"{clean_filename}_{service_term}_{reimb_term}_{program}_{product}_{network}_{lob}" hash_value = hashlib.md5(hash_string.encode()).hexdigest()[:8] # Generate REIMB_ID and REIMB_LESSER_OF_ID - df_temp.at[idx, 'REIMB_ID'] = f"{clean_filename}_exh_pg_{exhibit_page}_{i+1:03d}_{hash_value}" - df_temp.at[idx, 'REIMB_LESSER_OF_ID'] = hash_value + df_temp.at[idx, "REIMB_ID"] = ( + f"{clean_filename}_exh_pg_{exhibit_page}_{i+1:03d}_{hash_value}" + ) + df_temp.at[idx, "REIMB_LESSER_OF_ID"] = hash_value return df_temp + def reorder_columns(df: pd.DataFrame, column_order: list[str]) -> pd.DataFrame: """ Reorders the columns of the DataFrame based on the given column order. Steps: - 1. Adds any missing columns from `column_order` to the DataFrame, filled with empty strings. + 1. Adds any missing columns from `column_order` to the DataFrame at once, filled with empty strings. 2. Reorders the columns of the DataFrame to match `column_order`. 3. Appends any columns in the DataFrame that are not in `column_order` to the end. + Args: df (pd.DataFrame): The input DataFrame. column_order (list[str]): The desired column order. + Returns: pd.DataFrame: The reordered DataFrame. """ - # Add missing columns from column_order to the DataFrame, filled with empty strings - for col in column_order: - if col not in df.columns: - df[col] = "" + # Create a copy to avoid fragmentation + df_copy = df.copy() + + # Find missing columns from column_order + missing_columns = [col for col in column_order if col not in df_copy.columns] + + # Add all missing columns at once using a dictionary + if missing_columns: + # Create a dictionary of empty columns + empty_cols = {col: [""] * len(df_copy) for col in missing_columns} + # Add all missing columns at once with pd.concat + df_copy = pd.concat( + [df_copy, pd.DataFrame(empty_cols, index=df_copy.index)], axis=1 + ) + # Reorder columns to match column_order - ordered_columns = [col for col in column_order if col in df.columns] + ordered_columns = [col for col in column_order if col in df_copy.columns] # Add columns in df that are not in column_order to the end - remaining_columns = [col for col in df.columns if col not in column_order] + remaining_columns = [col for col in df_copy.columns if col not in column_order] # Combine the ordered columns and remaining columns final_column_order = ordered_columns + remaining_columns + # Return the DataFrame with reordered columns - return df[final_column_order] + return df_copy[final_column_order] + def auto_renewal(df: pd.DataFrame) -> pd.DataFrame: # Normalize the 'AUTO_RENEWAL_TERM' column if "AUTO_RENEWAL_TERM" in df.columns: - df["AUTO_RENEWAL_TERM"] = df["AUTO_RENEWAL_TERM"].apply(normalize_auto_renewal_term) + df["AUTO_RENEWAL_TERM"] = df["AUTO_RENEWAL_TERM"].apply( + normalize_auto_renewal_term + ) return df def update_termination_date_for_conditions(df: pd.DataFrame) -> pd.DataFrame: """ - Updates the 'AARETE_DERIVED_TERMINATION_DT' column based on specific conditions + Updates the 'AARETE_DERIVED_TERMINATION_DT' column based on specific conditions involving 'TERMINATION_DT', 'AUTO_RENEWAL_IND', and 'AARETE_DERIVED_TERMINATION_DT'. Args: @@ -359,26 +415,32 @@ def update_termination_date_for_conditions(df: pd.DataFrame) -> pd.DataFrame: pd.DataFrame: The updated DataFrame with modified 'AARETE_DERIVED_TERMINATION_DT' values. """ - if "TERMINATION_DT" in df.columns and "AUTO_RENEWAL_IND" in df.columns and "AARETE_DERIVED_TERMINATION_DT" in df.columns: + if ( + "TERMINATION_DT" in df.columns + and "AUTO_RENEWAL_IND" in df.columns + and "AARETE_DERIVED_TERMINATION_DT" in df.columns + ): # Replace Y with 9999/12/31 - df.loc[ - df["AUTO_RENEWAL_IND"] == "Y", - "AARETE_DERIVED_TERMINATION_DT" - ] = "9999/12/31" + df.loc[df["AUTO_RENEWAL_IND"] == "Y", "AARETE_DERIVED_TERMINATION_DT"] = ( + "9999/12/31" + ) # Replace "N" and "year to year" with 9999/12/31 df.loc[ - (df["TERMINATION_DT"] == "year to year") & - (df["AUTO_RENEWAL_IND"] == "N") & - (df["AARETE_DERIVED_TERMINATION_DT"] == "N/A"), - "AARETE_DERIVED_TERMINATION_DT" + (df["TERMINATION_DT"] == "year to year") + & (df["AUTO_RENEWAL_IND"] == "N") + & (df["AARETE_DERIVED_TERMINATION_DT"] == "N/A"), + "AARETE_DERIVED_TERMINATION_DT", ] = "9999/12/31" - + return df -def standardize_reimb_method_and_fee_schedule(df: pd.DataFrame) -> pd.DataFrame: + +def standardize_reimb_method_and_fee_schedule( + df: pd.DataFrame, VALID_UNIT_OF_MEASURE: list +) -> pd.DataFrame: """ - Standardizes the 'AARETE_DERIVED_REIMB_METHOD' column and updates the + Standardizes the 'AARETE_DERIVED_REIMB_METHOD' column and updates the 'AARETE_DERIVED_FEE_SCHEDULE' column based on specific values. Args: @@ -387,73 +449,92 @@ def standardize_reimb_method_and_fee_schedule(df: pd.DataFrame) -> pd.DataFrame: Returns: pd.DataFrame: The updated DataFrame with standardized columns. """ - if "AARETE_DERIVED_REIMB_METHOD" in df.columns and 'AARETE_DERIVED_FEE_SCHEDULE' in df.columns: + if ( + "AARETE_DERIVED_REIMB_METHOD" in df.columns + and "AARETE_DERIVED_FEE_SCHEDULE" in df.columns + ): # Standardize 'AARETE_DERIVED_REIMB_METHOD' values # Copy AWP, ASP, WAC to AARETE_DERIVED_FEE_SCHEDULE column for the corresponding row - medrx_fee_schedule_values = ['AWP', 'ASP', 'WAC'] - df.loc[df['AARETE_DERIVED_REIMB_METHOD'].isin(medrx_fee_schedule_values), 'AARETE_DERIVED_FEE_SCHEDULE'] = df['AARETE_DERIVED_REIMB_METHOD'] + medrx_fee_schedule_values = ["AWP", "ASP", "WAC"] + df.loc[ + df["AARETE_DERIVED_REIMB_METHOD"].isin(medrx_fee_schedule_values), + "AARETE_DERIVED_FEE_SCHEDULE", + ] = df["AARETE_DERIVED_REIMB_METHOD"] # Convert AARETE_DERIVED_REIMB_METHOD values [AWP, ASP, WAC] to 'MedRx' - df['AARETE_DERIVED_REIMB_METHOD'] = df['AARETE_DERIVED_REIMB_METHOD'].replace({'AWP': 'MedRx', 'ASP': 'MedRx', 'WAC': 'MedRx'}) + df["AARETE_DERIVED_REIMB_METHOD"] = df["AARETE_DERIVED_REIMB_METHOD"].replace( + {"AWP": "MedRx", "ASP": "MedRx", "WAC": "MedRx"} + ) - if 'AARETE_DERIVED_REIMB_METHOD' in df.columns: + if "AARETE_DERIVED_REIMB_METHOD" in df.columns: # Remove rows where 'AARETE_DERIVED_REIMB_METHOD' is 'DOFR' but 'REIMB_TERM' does not contain 'DOFR' - mask = (df['AARETE_DERIVED_REIMB_METHOD'] == 'DOFR') & \ - (~df['REIMB_TERM'].str.contains('DOFR', na=False)) + mask = (df["AARETE_DERIVED_REIMB_METHOD"] == "DOFR") & ( + ~df["REIMB_TERM"].str.contains("DOFR", na=False) + ) df = df[~mask] # standardize the 'UNIT_OF_MEASURE' column if "UNIT_OF_MEASURE" in df.columns: - df["UNIT_OF_MEASURE"] = df["UNIT_OF_MEASURE"].str.replace("Per Encounter", "Per Visit") + df["UNIT_OF_MEASURE"] = df["UNIT_OF_MEASURE"].str.replace( + "Per Encounter", "Per Visit" + ) # remove cases of Per Member Per Month (PMPM) - df = df[~df['UNIT_OF_MEASURE'].isin(['Per Member Per Month (PMPM)'])] + df = df[~df["UNIT_OF_MEASURE"].isin(["Per Member Per Month (PMPM)"])] # Set UNIT_OF_MEASURE as 'Per Unit' for Flat Rate reimbursement method with empty UNIT_OF_MEASURE - df.loc[(df['AARETE_DERIVED_REIMB_METHOD'] == 'Flat Rate') & \ - (df['UNIT_OF_MEASURE'].apply(string_utils.is_empty)), 'UNIT_OF_MEASURE'] = 'Per Unit' + df.loc[ + (df["AARETE_DERIVED_REIMB_METHOD"] == "Flat Rate") + & (df["UNIT_OF_MEASURE"].apply(string_utils.is_empty)), + "UNIT_OF_MEASURE", + ] = "Per Unit" # Ensure UNIT_OF_MEASURE is one of the valid values - df['UNIT_OF_MEASURE'] = df['UNIT_OF_MEASURE'].apply(lambda x: x if x in investment_values.VALID_UNIT_OF_MEASURE else "") + df["UNIT_OF_MEASURE"] = df["UNIT_OF_MEASURE"].apply( + lambda x: x if x in VALID_UNIT_OF_MEASURE else "" + ) return df def process_patient_age_range(df): """ - Process the 'PATIENT_AGE_RANGE' column into 'PATIENT_AGE_MIN' and 'PATIENT_AGE_MAX'. + Process the 'PATIENT_AGE_RANGE' column into 'PATIENT_AGE_MIN' and 'PATIENT_AGE_MAX'. If a single number is provided, both min and max are set to that number. Remove PATIENT_AGE_RANGE column. - + Args: df (pd.DataFrame): The input DataFrame. - + Returns: pd.DataFrame: The updated DataFrame with 'PATIENT_AGE_MIN' and 'PATIENT_AGE_MAX' columns. """ if "PATIENT_AGE_RANGE" in df.columns: try: # Create temporary columns for processing - df['PATIENT_AGE_MIN'] = df['PATIENT_AGE_RANGE'] - df['PATIENT_AGE_MAX'] = df['PATIENT_AGE_RANGE'] - + df["PATIENT_AGE_MIN"] = df["PATIENT_AGE_RANGE"] + df["PATIENT_AGE_MAX"] = df["PATIENT_AGE_RANGE"] + # Handle hyphenated ranges - mask = df['PATIENT_AGE_RANGE'].str.contains('-', na=False) + mask = df["PATIENT_AGE_RANGE"].str.contains("-", na=False) if mask.any(): - temp_split = df.loc[mask, 'PATIENT_AGE_RANGE'].str.split("-", expand=True) - df.loc[mask, 'PATIENT_AGE_MIN'] = temp_split[0] - df.loc[mask, 'PATIENT_AGE_MAX'] = temp_split[1] - + temp_split = df.loc[mask, "PATIENT_AGE_RANGE"].str.split( + "-", expand=True + ) + df.loc[mask, "PATIENT_AGE_MIN"] = temp_split[0] + df.loc[mask, "PATIENT_AGE_MAX"] = temp_split[1] + except Exception as e: # If any error occurs, keep the original values pass - + # Remove the original PATIENT_AGE_RANGE column - df.drop(columns=['PATIENT_AGE_RANGE'], inplace=True, errors='ignore') - + df.drop(columns=["PATIENT_AGE_RANGE"], inplace=True, errors="ignore") + return df + def add_aarete_derived_amendment_num(df: pd.DataFrame) -> pd.DataFrame: """ Add the 'AARETE_DERIVED_AMENDMENT_NUM' column to the DataFrame. @@ -466,14 +547,20 @@ def add_aarete_derived_amendment_num(df: pd.DataFrame) -> pd.DataFrame: """ if "CONTRACT_AMENDMENT_NUM" in df.columns: - df["AARETE_DERIVED_AMENDMENT_NUM"] = df["CONTRACT_AMENDMENT_NUM"].str.split('-').str[1].fillna(df['CONTRACT_AMENDMENT_NUM']) + df["AARETE_DERIVED_AMENDMENT_NUM"] = ( + df["CONTRACT_AMENDMENT_NUM"] + .str.split("-") + .str[1] + .fillna(df["CONTRACT_AMENDMENT_NUM"]) + ) return df + def add_aarete_derived_product(df): """ Add AARETE_DERIVED_PRODUCT column to df, based on PRODUCT column. - + The new column should be Title Case of the PRODUCT column value. """ if "PRODUCT" in df.columns: @@ -486,16 +573,21 @@ def update_lob_for_duals(answer_dicts): # Iterate through each row in the dataframe for answer_dict in answer_dicts: # Get SERVICE_TERM value - service_term = answer_dict.get('SERVICE_TERM', "").lower() + service_term = answer_dict.get("SERVICE_TERM", "").lower() # Check if the SERVICE_TERM contains "Medicare" and "Medicaid" - case insensitive - if 'medicare' in service_term and 'medicaid' in service_term: + if "medicare" in service_term and "medicaid" in service_term: prompt = investment_prompts.DUAL_LOB_CHECK(service_term) - claude_answer_raw = llm_utils.invoke_claude(prompt, model_id="sonnet_latest", filename="", max_tokens=200) - claude_answer_extracted = string_utils.extract_text_from_delimiters(claude_answer_raw, Delimiter.PIPE) + claude_answer_raw = llm_utils.invoke_claude( + prompt, model_id="sonnet_latest", filename="", max_tokens=200 + ) + claude_answer_extracted = string_utils.extract_text_from_delimiters( + claude_answer_raw, Delimiter.PIPE + ) answer_dict["AARETE_DERIVED_LOB"] = claude_answer_extracted final_answer_dicts.append(answer_dict) return final_answer_dicts + def fill_empty_reimb_pct_rate(df): """ Fill 'REIMB_PCT_RATE' based on reimbursement method: @@ -504,25 +596,31 @@ def fill_empty_reimb_pct_rate(df): - Set 'REIMB_PCT_RATE' to 'N/A' for rows where: - 'AARETE_DERIVED_REIMB_METHOD' contains specific grouper. """ - required_cols = ["REIMB_PCT_RATE", "REIMB_FEE_RATE", "REIMB_CONVERSION_FACTOR", "AARETE_DERIVED_REIMB_METHOD"] - + required_cols = [ + "REIMB_PCT_RATE", + "REIMB_FEE_RATE", + "REIMB_CONVERSION_FACTOR", + "AARETE_DERIVED_REIMB_METHOD", + ] + if all(col in df.columns for col in required_cols): # Fill with '100' when all three rate fields are empty pct_empty = string_utils.is_empty(df["REIMB_PCT_RATE"]) fee_empty = string_utils.is_empty(df["REIMB_FEE_RATE"]) conversion_empty = string_utils.is_empty(df["REIMB_CONVERSION_FACTOR"]) - + fill_mask = pct_empty & fee_empty & conversion_empty df.loc[fill_mask, "REIMB_PCT_RATE"] = "100" - + # Set to N/A for specific reimbursement methods na_methods = ["Grouper", "Per Diem", "Flat Rate", "Per Visit", "Case Rate"] - + na_mask = df["AARETE_DERIVED_REIMB_METHOD"].isin(na_methods) df.loc[na_mask, "REIMB_PCT_RATE"] = "N/A" - + return df + def remove_redundant_reimb_info(df): """ Checks if "REIMB_" fields are the same as their corresponding 1:1 fields. @@ -530,14 +628,14 @@ def remove_redundant_reimb_info(df): Args: df (pd.DataFrame): The input DataFrame. - + Returns: pd.DataFrame: The updated DataFrame with redundant reimbursement field values removed. """ COLUMN_PAIRS = [ ("REIMB_EFFECTIVE_DT", "AARETE_DERIVED_EFFECTIVE_DT"), ("REIMB_TERMINATION_DT", "AARETE_DERIVED_TERMINATION_DT"), - ("REIMB_PROV_NAME", "PROV_GROUP_NAME_FULL") + ("REIMB_PROV_NAME", "PROV_GROUP_NAME_FULL"), ] for column_tuple in COLUMN_PAIRS: reimb_col = column_tuple[0] @@ -548,7 +646,6 @@ def remove_redundant_reimb_info(df): return df - def deduplicate_provider_columns(df: pd.DataFrame) -> pd.DataFrame: """Deduplicate provider fields in the DataFrame. @@ -566,12 +663,14 @@ def deduplicate_provider_columns(df: pd.DataFrame) -> pd.DataFrame: pd.DataFrame: The updated DataFrame with deduplicated provider fields. """ provider_columns = [ - "PROV_GROUP_TIN", "PROV_GROUP_NPI", "PROV_GROUP_NAME_FULL", - "PROV_OTHER_TIN", "PROV_OTHER_NPI", "PROV_OTHER_NAME_FULL" - ] - other_columns = [ - "PROV_OTHER_TIN", "PROV_OTHER_NPI", "PROV_OTHER_NAME_FULL" + "PROV_GROUP_TIN", + "PROV_GROUP_NPI", + "PROV_GROUP_NAME_FULL", + "PROV_OTHER_TIN", + "PROV_OTHER_NPI", + "PROV_OTHER_NAME_FULL", ] + other_columns = ["PROV_OTHER_TIN", "PROV_OTHER_NPI", "PROV_OTHER_NAME_FULL"] # Check if all provider columns exist in the DataFrame if all(col in df.columns for col in provider_columns): @@ -586,29 +685,45 @@ def deduplicate_provider_columns(df: pd.DataFrame) -> pd.DataFrame: column_mappings = { "PROV_OTHER_TIN": group_tin, "PROV_OTHER_NPI": group_npi, - "PROV_OTHER_NAME_FULL": group_name + "PROV_OTHER_NAME_FULL": group_name, } # Process each OTHER column for other_col, group_val in column_mappings.items(): other_value = str(row.get(other_col, "")).strip() - - if other_value and not string_utils.is_empty(other_value) and other_value != "UNKNOWN": + + if ( + other_value + and not string_utils.is_empty(other_value) + and other_value != "UNKNOWN" + ): # Split by pipe and strip whitespace - other_list = [v.strip() for v in other_value.split("|") if v.strip()] + other_list = [ + v.strip() for v in other_value.split("|") if v.strip() + ] # Remove corresponding GROUP value if present - if group_val and not string_utils.is_empty(group_val) and group_val != "UNKNOWN": + if ( + group_val + and not string_utils.is_empty(group_val) + and group_val != "UNKNOWN" + ): other_list = [v for v in other_list if v != group_val] # Remove duplicates while preserving order seen = set() deduped_list = [] for item in other_list: - if item not in seen and not string_utils.is_empty(item) and item != "UNKNOWN": + if ( + item not in seen + and not string_utils.is_empty(item) + and item != "UNKNOWN" + ): seen.add(item) deduped_list.append(item) - + # Update the DataFrame - df.at[index, other_col] = " | ".join(deduped_list) if deduped_list else "" - return df \ No newline at end of file + df.at[index, other_col] = ( + " | ".join(deduped_list) if deduped_list else "" + ) + return df diff --git a/fieldExtraction/src/investment/preprocess.py b/fieldExtraction/src/investment/preprocess.py index c274187..2b16a94 100644 --- a/fieldExtraction/src/investment/preprocess.py +++ b/fieldExtraction/src/investment/preprocess.py @@ -2,10 +2,8 @@ import logging import src.investment.smart_chunking_funcs as smart_chunking_funcs import src.investment.table_funcs as table_funcs -import src.prompts.investment_prompts as investment_prompts -from src import config, keywords, preprocessing_funcs -from src.enums.delimiters import Delimiter -from src.utils import llm_utils, string_utils +from src import config +from src.investment import preprocessing_funcs def clean_text(contract_text): @@ -46,7 +44,9 @@ def split_text(contract_text): return text_dict, top_sheet_dict -def one_to_n_exhibit_chunking(text_dict, filename) -> tuple[dict, dict]: +def one_to_n_exhibit_chunking( + text_dict, EXHIBIT_HEADER_MARKERS, filename +) -> tuple[dict, dict]: """ Process the text dictionary to identify and chunk exhibits. This function extracts exhibit pages, maps them to their respective chunks, @@ -61,7 +61,7 @@ def one_to_n_exhibit_chunking(text_dict, filename) -> tuple[dict, dict]: - dict: A dictionary where keys are exhibit page numbers and values are the exhibit headers. """ exhibit_pages, all_exhibit_headers = preprocessing_funcs.get_exhibit_pages( - text_dict, filename + text_dict, EXHIBIT_HEADER_MARKERS, filename ) exhibit_pages, all_exhibit_headers = preprocessing_funcs.link_exhibit_pages( all_exhibit_headers, text_dict, filename @@ -93,7 +93,9 @@ def one_to_one_smart_chunking(text_dict, contract_text, one_to_one_fields): return ac_chunks -def clean_tables(text_dict: dict[str, str], filename: str) -> dict[str, str]: +def clean_tables( + text_dict: dict[str, str], EXHIBIT_HEADER_MARKERS: list, filename: str +) -> dict[str, str]: """ Process tables with simplified logic: combine continuations, then split by row count. @@ -107,7 +109,7 @@ def clean_tables(text_dict: dict[str, str], filename: str) -> dict[str, str]: logging.info(f"Starting simple table processing for {filename}") # 1. Extract table info - table_dict = table_funcs.get_table_info(text_dict) + table_dict = table_funcs.get_table_info(text_dict, EXHIBIT_HEADER_MARKERS) logging.info(f"Found tables on pages: {list(table_dict.keys())}") # 2. Combine continuous tables (simple version) @@ -120,6 +122,7 @@ def clean_tables(text_dict: dict[str, str], filename: str) -> dict[str, str]: # 3. Split tables by row count only text_dict_final = table_funcs.split_tables_by_rows( text_dict=text_dict_combined, + EXHIBIT_HEADER_MARKERS=EXHIBIT_HEADER_MARKERS, row_limit=config.TABLE_ROW_LIMIT, ) diff --git a/fieldExtraction/src/preprocessing_funcs.py b/fieldExtraction/src/investment/preprocessing_funcs.py similarity index 81% rename from fieldExtraction/src/preprocessing_funcs.py rename to fieldExtraction/src/investment/preprocessing_funcs.py index a86d1d2..4dc9786 100644 --- a/fieldExtraction/src/preprocessing_funcs.py +++ b/fieldExtraction/src/investment/preprocessing_funcs.py @@ -1,17 +1,14 @@ -import re import logging +import re -from src.prompts import investment_prompts import src.utils.llm_utils as llm_utils import src.utils.string_utils as string_utils -from src import config, keywords -from src.regex.regex_patterns import PIPE_PATTERN +from constants.delimiters import Delimiter +from constants.regex_patterns import PIPE_PATTERN from src.investment import table_funcs +from src.prompts import investment_prompts -from src import config, keywords -from src.enums.delimiters import Delimiter - def remove_page_indicators(contract_text: str) -> str: """Clean textract output by removing page number indicators in the form of "Page X of Y" @@ -23,7 +20,7 @@ def remove_page_indicators(contract_text: str) -> str: Returns: str: cleaned text with newlines and page number indicators removed """ - + if contract_text: cleaned_text = re.sub(r"Page [0-9]+ of [0-9]+\n\n", " ", contract_text) else: @@ -47,7 +44,9 @@ def split_text(text: str) -> dict[str, str]: text_dict = {} for i in range(len(text_list)): if temp_list[i]: - text_dict[temp_list[i].split()[0]] = text_list[i] # splits on whitespace characters, which includes spaces, tabs, and newline characters + text_dict[temp_list[i].split()[0]] = text_list[ + i + ] # splits on whitespace characters, which includes spaces, tabs, and newline characters return {k: v for k, v in text_dict.items() if k != "Document"} @@ -58,8 +57,12 @@ def clean_law_symbols(contract_text): # First correction: Replace '$$' with '$' contract_text = contract_text.replace("$$", "$") - contract_text = re.sub(r"(U\.?S\.?C\.?) \$", r"\1§", contract_text) # replaces $ with § when it follows abbreviations like "U.S.C." (United States Code). - contract_text = re.sub(r"(C\.?F\.?R\.?) \$", r"\1§", contract_text) # replaces $ with § when it follows abbreviations like "C.F.R." (Code of Federal Regulations). + contract_text = re.sub( + r"(U\.?S\.?C\.?) \$", r"\1§", contract_text + ) # replaces $ with § when it follows abbreviations like "U.S.C." (United States Code). + contract_text = re.sub( + r"(C\.?F\.?R\.?) \$", r"\1§", contract_text + ) # replaces $ with § when it follows abbreviations like "C.F.R." (Code of Federal Regulations). # Second correction: Replace '$' with '§' when followed by a number with three decimal places contract_text = re.sub(r"\$(?=\d+\.\d{3})", "§", contract_text) @@ -71,7 +74,11 @@ def clean_law_symbols(contract_text): def chunk_consecutive_og(text_dict, exhibit_pages): # If needed - extend page 1 to page 1 and 2, then cut chunking off after (edge case: compensation terms not found on first page of exhibit) - reimbursement_pages = [page_num for page_num in text_dict.keys() if string_utils.contains_reimbursement(text_dict, page_num)] + reimbursement_pages = [ + page_num + for page_num in text_dict.keys() + if string_utils.contains_reimbursement(text_dict, page_num) + ] page_dict = {} current_exhibit = None @@ -99,7 +106,7 @@ def chunk_consecutive_og(text_dict, exhibit_pages): current_exhibit = None page_dict[page_num] = [page_num] - final_dict = {page_num : '' for page_num in page_dict.keys()} + final_dict = {page_num: "" for page_num in page_dict.keys()} for page_num in page_dict.keys(): for p in page_dict[page_num]: final_dict[page_num] += text_dict[p] @@ -212,7 +219,10 @@ def filter_quick_review(text_dict): or "COVER SHEET" in page_text.upper() } -def get_exhibit_pages(text_dict: dict[str, str], filename: str) -> tuple[list[str], dict[str, str]]: + +def get_exhibit_pages( + text_dict: dict[str, str], EXHIBIT_HEADER_MARKERS: list, filename: str +) -> tuple[list[str], dict[str, str]]: """ Identify exhibit pages from the text dictionary. This function processes the text dictionary to identify pages that are considered exhibits. @@ -242,7 +252,9 @@ def get_exhibit_pages(text_dict: dict[str, str], filename: str) -> tuple[list[st first_page = False else: if "." not in page_num or ".0" in page_num: - prompt = investment_prompts.EXHIBIT_HEADER(page_content[0:400]) + prompt = investment_prompts.EXHIBIT_HEADER( + page_content[0:400], EXHIBIT_HEADER_MARKERS + ) claude_answer_raw = llm_utils.invoke_claude( prompt, "sonnet_latest", filename, max_tokens=300 ) @@ -255,17 +267,19 @@ def get_exhibit_pages(text_dict: dict[str, str], filename: str) -> tuple[list[st exhibit_pages.append(page_num) exhibit_header_dict[page_num] = claude_answer_extracted is_exhibit = True - elif is_exhibit==True: + elif is_exhibit == True: exhibit_pages.append(page_num) return exhibit_pages, exhibit_header_dict -def link_exhibit_pages(all_exhibit_headers: dict[str, str], text_dict: dict[str, str], filename: str): +def link_exhibit_pages( + all_exhibit_headers: dict[str, str], text_dict: dict[str, str], filename: str +): """ Filters exhibit pages to include only those with meaningfully different headers. - This function processes exhibit headers sequentially and uses literal comparison and + This function processes exhibit headers sequentially and uses literal comparison and LLM-based semantic comparison to determine when exhibits represent continuation pages - vs. distinct exhibits. Continuation pages are filtered out of the headers list and + vs. distinct exhibits. Continuation pages are filtered out of the headers list and mapping, and when used downstream are included in their parent exhibit's text chunk. Links exhibit pages based on their headers. If the header of a page is identical (either literally or semantically) to the previous page's header, it is not added to the final list of exhibit pages. This function will link contiguous header pages together, so that the final list of exhibit pages will only contain pages that are meaningfully different from the previous page. E.g. "1" with header {"Exhibit 1"} and "2" with header {"Continued exhibit 1"} will be linked together, but "3" with header {"Exhibit 2"} will not be linked to either of them. @@ -302,27 +316,38 @@ def link_exhibit_pages(all_exhibit_headers: dict[str, str], text_dict: dict[str, has_table = has_substantial_table(text_dict.get(page_num, "")) # If literally identical, skip prompt (unless it has a table) - if page_header.upper().strip() == previous_header.upper().strip() and not has_table: + if ( + page_header.upper().strip() == previous_header.upper().strip() + and not has_table + ): exhibit_is_different = "N" elif string_utils.is_empty(previous_header): exhibit_is_different = "Y" previous_header = page_header elif has_table: # Always treat pages with substantial tables as different exhibits - logging.debug(f"Page {page_num} has substantial table, treating as separate exhibit") + logging.debug( + f"Page {page_num} has substantial table, treating as separate exhibit" + ) exhibit_is_different = "Y" previous_header = page_header else: - prompt = investment_prompts.EXHIBIT_LINKAGE(page_header, previous_header) - claude_answer_raw = llm_utils.invoke_claude(prompt, "sonnet_latest", filename) - exhibit_is_different = string_utils.extract_text_from_delimiters(claude_answer_raw, Delimiter.PIPE) + prompt = investment_prompts.EXHIBIT_LINKAGE( + page_header, previous_header + ) + claude_answer_raw = llm_utils.invoke_claude( + prompt, "sonnet_latest", filename + ) + exhibit_is_different = string_utils.extract_text_from_delimiters( + claude_answer_raw, Delimiter.PIPE + ) previous_header = page_header - + # If the prompt has identified that the current exhibit is meaningfully different than the previous if "Y" in exhibit_is_different: final_exhibit_pages.append(page_num) final_exhibit_headers[page_num] = page_header - + return final_exhibit_pages, final_exhibit_headers @@ -337,31 +362,31 @@ def has_substantial_table(page_text: str) -> bool: """ if not page_text or table_funcs.START_MARKER not in page_text: return False - + # Count table rows to determine if it's substantial table_start = page_text.find(table_funcs.START_MARKER) table_end = page_text.find(table_funcs.END_MARKER) if table_start == -1 or table_end == -1: return False - + table_content = page_text[table_start:table_end] # Count rows (each row is a list in the table representation) - row_count = table_content.count("'], ['") # Rough count of table rows + row_count = table_content.count("'], ['") # Rough count of table rows - return row_count >= 5 # Right now, using "5 rows" as "substantial". Alternative: use config.TABLE_ROW_LIMIT + return ( + row_count >= 5 + ) # Right now, using "5 rows" as "substantial". Alternative: use config.TABLE_ROW_LIMIT -def chunk_by_exhibit(text_dict: dict, - exhibit_pages: list - ) -> dict: +def chunk_by_exhibit(text_dict: dict, exhibit_pages: list) -> dict: """ Organizes pages into groups based on their association with specific exhibits. This function assigns each page number from the `text_dict` dictionary to an exhibit - based on the `exhibit_pages` list. Pages are grouped under the nearest preceding - page number in `exhibit_pages`. If a page number in `text_dict` is itself in + based on the `exhibit_pages` list. Pages are grouped under the nearest preceding + page number in `exhibit_pages`. If a page number in `text_dict` is itself in `exhibit_pages`, it starts a new exhibit group. Parameters: @@ -376,8 +401,8 @@ def chunk_by_exhibit(text_dict: dict, the first `exhibit_page`, they are grouped under the exhibit identifier "0". """ if len(exhibit_pages) == 0: - return {key : key for key in text_dict.keys()} - + return {key: key for key in text_dict.keys()} + exhibit_chunk_mapping = {} current_exhibit = "0" for page_num in text_dict.keys(): @@ -389,29 +414,27 @@ def chunk_by_exhibit(text_dict: dict, return exhibit_chunk_mapping -def get_exhibit_dict(text_dict: dict, - exhibit_chunk_mapping: dict - ) -> dict: +def get_exhibit_dict(text_dict: dict, exhibit_chunk_mapping: dict) -> dict: """ Creates a dictionary of exhibit texts, concatenating pages with the same exhibit number, but separating decimal pages into their own chunks. - + Args: text_dict (dict): Dictionary mapping page numbers to page text. exhibit_chunk_mapping (dict): Dictionary mapping page numbers to exhibit numbers. Returns: - dict: Dictionary mapping exhibit chunk keys to combined text. Format for keys: + dict: Dictionary mapping exhibit chunk keys to combined text. Format for keys: "{exhibit}.{index}" NOTE: If exhibit identifiers contain decimals (e.g., "23.0") output keys will follow the same pattern (resulting in "23.0.0", "23.0.1", etc.) - Guidelines: + Guidelines: - Every exhibit chunk contains at most 1 table page in it (i.e. one page with a decimal) - Tables are inserted in chronological order within the document flow. - Multiple decimal pages are separated into individual chunks - Each chunk gets a new key: "{exhibit}.{index}" (e.g. "2.0", "2.1", "2.2", etc.) - + Algorithm: For each exhibit: 1. Identify base pages (no decimals) and table pages (with decimals). @@ -435,28 +458,28 @@ def get_exhibit_dict(text_dict: dict, - '2.1': text from '2' + text from '3.1' + text from '4' + text from '5' - '2.2': text from '2' + text from '3.2' + text from '4' + text from '5' - '2.3': text from '2' + text from '4' + text from '5' - + Note: Tables 3.0, 3.1, and 3.2 appear between pages 2 and 4 in document flow. - + Other cases: - No decimal pages: creates single chunk for the exhibit. - Single decimal page exhibit: uses original page as the chunk key. """ exhibit_dict = {} - + # Group pages by exhibit exhibit_pages = {} for page, exhibit in exhibit_chunk_mapping.items(): if exhibit not in exhibit_pages: exhibit_pages[exhibit] = [] exhibit_pages[exhibit].append(page) - + # Process each exhibit for exhibit in exhibit_pages: pages = sorted(exhibit_pages[exhibit], key=string_utils.page_key_sort) - base_pages = [p for p in pages if '.' not in p] - decimal_pages = [p for p in pages if '.' in p] + base_pages = [p for p in pages if "." not in p] + decimal_pages = [p for p in pages if "." in p] # Special case: single decimal page exhibit if len(decimal_pages) == 1 and not base_pages: @@ -467,23 +490,23 @@ def get_exhibit_dict(text_dict: dict, if not decimal_pages: exhibit_dict[f"{exhibit}.0"] = "\n".join(text_dict[p] for p in pages) continue - + # Case: With decimal pages, create chunks for each decimal page chunk_index = 0 for decimal_page in decimal_pages: chunk_key = f"{exhibit}.{chunk_index}" # Get decimal page base number for chronological positioning - decimal_base = int(decimal_page.split('.')[0]) + decimal_base = int(decimal_page.split(".")[0]) # Build chunk with pages in chronological order chunk_parts = [] - + # Add base pages before decimal page for base_page in base_pages: if int(base_page) <= decimal_base: chunk_parts.append(text_dict[base_page]) - + # Add the decimal page itself chunk_parts.append(text_dict[decimal_page]) @@ -498,53 +521,13 @@ def get_exhibit_dict(text_dict: dict, # Add final chunk with just base pages (if there are base pages after all decimal pages) if base_pages and decimal_pages: # Check if there are base pages that come after all decimal pages - max_decimal_base = max(int(dp.split('.')[0]) for dp in decimal_pages) - remaining_base_pages = [bp for bp in base_pages if int(bp) > max_decimal_base] + max_decimal_base = max(int(dp.split(".")[0]) for dp in decimal_pages) + remaining_base_pages = [ + bp for bp in base_pages if int(bp) > max_decimal_base + ] if remaining_base_pages: chunk_key = f"{exhibit}.{chunk_index}" # Include ALL base pages in the final chunk exhibit_dict[chunk_key] = "\n".join(text_dict[bp] for bp in base_pages) return exhibit_dict - -def get_exhibit_headers(exhibit_dict: dict[str, str], filename: str) -> dict[str, str]: - """ - Extracts each exhibit header using an LLM prompt. - - This function processes exhibit chunks to extract their headers, optimizing LLM calls - by reusing headers for chunks that share the same base page number (e.g., "5.0", "5.1", "5.2" - all share base page "5"). - - Args: - exhibit_dict (dict[str, str]): A dictionary where keys are exhibit page numbers - (e.g., "1.0", "5.1") and values are the concatenated - text of those exhibit chunks. - filename (str): The name of the file being processed (used for LLM tracking). - - Returns: - dict[str, str]: A dictionary where keys are exhibit page numbers and values are - the extracted exhibit headers. - - Notes: - - Uses only the first 400 characters of each chunk for header extraction - - Caches headers by base page number to avoid redundant LLM calls - - Base page number is extracted by splitting on '.' and taking the first part - """ - exhibit_header_dict = {} - base_pages = {} - for page_num, exhibit_chunk in exhibit_dict.items(): - base_page_num = page_num.split('.')[0] - if base_page_num in base_pages.keys(): - exhibit_header_dict[page_num] = base_pages[base_page_num] - else: - prompt = investment_prompts.EXHIBIT_HEADER(exhibit_chunk[0:400]) - llm_answer_raw = llm_utils.invoke_claude( - prompt, "sonnet_latest", filename - ) - llm_answer_final = string_utils.extract_text_from_delimiters( - llm_answer_raw, Delimiter.PIPE - ) - exhibit_header_dict[page_num] = llm_answer_final - base_pages[base_page_num] = llm_answer_final - - return exhibit_header_dict \ No newline at end of file diff --git a/fieldExtraction/src/investment/row_funcs.py b/fieldExtraction/src/investment/row_funcs.py index 1c51085..1e111f7 100644 --- a/fieldExtraction/src/investment/row_funcs.py +++ b/fieldExtraction/src/investment/row_funcs.py @@ -1,9 +1,10 @@ - import logging + import pandas as pd -from src import config + +from src.investment import one_to_one_funcs from src.utils import string_utils -from src.investment import one_to_one_funcs, one_to_n_funcs + def merge_one_to_one_into_one_to_n(one_to_n_results, one_to_one_results): """ @@ -26,25 +27,36 @@ def merge_one_to_one_into_one_to_n(one_to_n_results, one_to_one_results): one_to_n_results[k] = one_to_n_results[k].apply( lambda row_val: v if string_utils.is_empty(row_val) else row_val ) - + # Handle global lesser of injection global_lesser_of = one_to_one_results.get("GLOBAL_LESSER_OF_STATEMENT") if global_lesser_of and global_lesser_of not in ["N/A", "UNKNOWN"]: - logging.debug(f"[{one_to_one_results.get('FILE_NAME', 'UNKNOWN')}] Injecting global lesser of logic: {global_lesser_of}") - one_to_n_results = inject_global_lesser_of_rows(one_to_n_results, global_lesser_of, one_to_one_results.get("FILE_NAME", "UNKNOWN")) + logging.debug( + f"[{one_to_one_results.get('FILE_NAME', 'UNKNOWN')}] Injecting global lesser of logic: {global_lesser_of}" + ) + one_to_n_results = inject_global_lesser_of_rows( + one_to_n_results, + global_lesser_of, + one_to_one_results.get("FILE_NAME", "UNKNOWN"), + ) else: - logging.debug(f"[{one_to_one_results.get('FILE_NAME', 'UNKNOWN')}] No global lesser of logic found or applicable.") + logging.debug( + f"[{one_to_one_results.get('FILE_NAME', 'UNKNOWN')}] No global lesser of logic found or applicable." + ) # Initialize the tracking flag when no global lesser of logic is found - one_to_n_results['GLOBAL_LESSER_OF_APPLIED'] = "N" + one_to_n_results["GLOBAL_LESSER_OF_APPLIED"] = "N" return one_to_n_results -def inject_global_lesser_of_rows(one_to_n_results: pd.DataFrame, global_lesser_of_stmt: str, filename: str) -> pd.DataFrame: + +def inject_global_lesser_of_rows( + one_to_n_results: pd.DataFrame, global_lesser_of_stmt: str, filename: str +) -> pd.DataFrame: """Create new rows with global lesser of constraint for REIMB_IDs that lack lesser of logic. - + For each row where LESSER_OF_IND="N", creates a duplicate row with the global lesser of statement applied via methodology breakout. The original row is preserved with LESSER_OF_IND updated to "Y" (since it's now subject to lesser of) and GLOBAL_LESSER_OF_APPLIED="N". - The new row has LESSER_OF_IND="Y" and GLOBAL_LESSER_OF_APPLIED="Y". Both rows + The new row has LESSER_OF_IND="Y" and GLOBAL_LESSER_OF_APPLIED="Y". Both rows share the same REIMB_ID. Args: @@ -56,36 +68,46 @@ def inject_global_lesser_of_rows(one_to_n_results: pd.DataFrame, global_lesser_o pd.DataFrame: Updated one_to_n_results with new rows injected for global lesser of logic. """ # Find rows that need global lesser of injection - rows_needing_injection = one_to_n_results[one_to_n_results['LESSER_OF_IND'] == "N"].copy() + rows_needing_injection = one_to_n_results[ + one_to_n_results["LESSER_OF_IND"] == "N" + ].copy() if rows_needing_injection.empty: logging.debug(f"No rows needing global lesser of injection for {filename}.") # Still need to set tracking flag for all existing rows - one_to_n_results['GLOBAL_LESSER_OF_APPLIED'] = "N" + one_to_n_results["GLOBAL_LESSER_OF_APPLIED"] = "N" return one_to_n_results - + # Update original rows that lacked lesser of: they're now subject to lesser of due to # global constraint - one_to_n_results.loc[one_to_n_results['LESSER_OF_IND'] == "N", 'LESSER_OF_IND'] = "Y" + one_to_n_results.loc[one_to_n_results["LESSER_OF_IND"] == "N", "LESSER_OF_IND"] = ( + "Y" + ) # Set tracking flag for all existing rows (both original injected rows and rows that already had lesser of logic) to "N" # New rows will get "Y" for this flag in create_global_lesser_of_row - one_to_n_results['GLOBAL_LESSER_OF_APPLIED'] = "N" + one_to_n_results["GLOBAL_LESSER_OF_APPLIED"] = "N" # Run methodology breakout on global statement (once) - global_methodology_breakout = one_to_one_funcs.run_global_lesser_of_breakout(global_lesser_of_stmt, filename) + global_methodology_breakout = one_to_one_funcs.run_global_lesser_of_breakout( + global_lesser_of_stmt, filename + ) # Create new rows for each REIMB_ID that was lacking lesser of logic new_rows = [] for _, original_row in rows_needing_injection.iterrows(): - new_row = one_to_one_funcs.create_global_lesser_of_row(original_row, global_methodology_breakout) + new_row = one_to_one_funcs.create_global_lesser_of_row( + original_row, global_methodology_breakout + ) new_rows.append(new_row) - + # Append new rows to results if new_rows: new_rows_df = pd.DataFrame(new_rows) one_to_n_results = pd.concat([one_to_n_results, new_rows_df], ignore_index=True) logging.debug(f"Injected {len(new_rows)} global lesser of rows for {filename}.") else: - logging.debug(f"No new rows created for global lesser of injection for {filename}.") - - return one_to_n_results \ No newline at end of file + logging.debug( + f"No new rows created for global lesser of injection for {filename}." + ) + + return one_to_n_results diff --git a/fieldExtraction/src/investment/smart_chunking_funcs.py b/fieldExtraction/src/investment/smart_chunking_funcs.py index 98b26e4..670e1e7 100644 --- a/fieldExtraction/src/investment/smart_chunking_funcs.py +++ b/fieldExtraction/src/investment/smart_chunking_funcs.py @@ -1,31 +1,28 @@ +import json +import os import re -# from src.prompts import client_prompts, investment_prompts -import importlib -import src.utils.llm_utils as llm_utils -import src.utils.string_utils as string_utils -from src import config, keywords, postprocessing_funcs -from src.enums.delimiters import Delimiter -import src.prompts.investment_prompts as prompts -from src.prompts.investment_prompts import Field, FieldSet -from src.config import FIELD_JSON_PATH -import boto3 -from langchain_chroma import Chroma -from langchain_aws.embeddings.bedrock import BedrockEmbeddings -from langchain_text_splitters import RecursiveCharacterTextSplitter -from langchain_community.retrievers import BM25Retriever -from langchain.retrievers import EnsembleRetriever -import json import nltk -import os +from langchain_aws.embeddings.bedrock import BedrockEmbeddings +from langchain_chroma import Chroma +from langchain_community.retrievers import BM25Retriever +from langchain_text_splitters import RecursiveCharacterTextSplitter + +from src import config +from src.prompts.investment_prompts import FieldSet + # Append path of NLTK data directory (fieldExtraction/nltk_data) -nltk.data.path.append(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "nltk_data")) +nltk.data.path.append( + os.path.join( + os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "nltk_data" + ) +) + +import shutil +from collections import defaultdict +from itertools import chain, combinations, count, groupby from nltk.tokenize import word_tokenize -from itertools import combinations -from collections import defaultdict -from itertools import chain, count, groupby -import shutil # Setup bedrock embeddings_model = BedrockEmbeddings( @@ -39,6 +36,7 @@ CHUNK_OVERLAP = 500 CHUNK_SIZE = 2000 TOP_K = 8 + def parse_chunk(chunk: str) -> tuple[int, str]: """Extract chunk number and content from a chunk string @@ -51,12 +49,13 @@ def parse_chunk(chunk: str) -> tuple[int, str]: if not chunk.startswith("chunk ") or len(chunk) < CHUNK_PREFIX_LENGTH: raise ValueError("Invalid chunk format") try: - chunk_number = int(chunk[6:9]) # Extract chunk number from "chunk NNN: " + chunk_number = int(chunk[6:9]) # Extract chunk number from "chunk NNN: " except ValueError: raise ValueError("Invalid chunk number format") content = chunk[CHUNK_PREFIX_LENGTH:] return chunk_number, content + def find_overlap(prev_chunk, next_chunk, min_overlap=3): """ Finds the maximum overlap between the end of prev_chunk and the start of next_chunk. @@ -68,22 +67,23 @@ def find_overlap(prev_chunk, next_chunk, min_overlap=3): return i return 0 # No overlap found + def stitch_chunks(chunks: list[str], overlap_size: int = CHUNK_OVERLAP) -> str: """Stitch together a list of chunks, with an overlap of `overlap_size` characters Args: chunks (list[str]): List of chunk strings, each prefixed with "chunk NNN: " overlap_size (int, optional): Overlap size between chunks. Defaults to CHUNK_OVERLAP. - + Returns: str: Combined text with properly handled overlaps """ if not chunks: return "" - + if overlap_size < 0: raise ValueError("Overlap size must be non-negative") - + # Parse and sort chunks parsed_chunks = [parse_chunk(chunk) for chunk in chunks] sorted_chunks = sorted(parsed_chunks, key=lambda x: x[0]) @@ -95,20 +95,23 @@ def stitch_chunks(chunks: list[str], overlap_size: int = CHUNK_OVERLAP) -> str: for i in range(1, len(sorted_chunks)): current_chunk = sorted_chunks[i][1] # if chunks are contiguous - if sorted_chunks[i][0] == sorted_chunks[i-1][0] + 1: + if sorted_chunks[i][0] == sorted_chunks[i - 1][0] + 1: # Check for overlap overlap = find_overlap(result, current_chunk) - if overlap > 0 and len(result) >= overlap and len(current_chunk) >= overlap: # if overlap is enabled and there is enough text to overlap + if ( + overlap > 0 and len(result) >= overlap and len(current_chunk) >= overlap + ): # if overlap is enabled and there is enough text to overlap result = result[:-overlap] + current_chunk else: result += "\n" + current_chunk - else: # if chunks are not contiguous, just append them + else: # if chunks are not contiguous, just append them result += "\n" + current_chunk - + return result -def field_context(one_to_one_fields: FieldSet, contract_text: str, filename: str): - """this function creates chunks from whole contract text and + +def field_context(one_to_one_fields: FieldSet, contract_text: str, filename: str): + """this function creates chunks from whole contract text and returns relevant chunks for each field based on sementic search and keyword search Args: @@ -123,17 +126,25 @@ def field_context(one_to_one_fields: FieldSet, contract_text: str, filename: str # Convert contract_text to lowercase for case-insensitive processing contract_text = contract_text.lower() - text_splitter = RecursiveCharacterTextSplitter(chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP) + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=CHUNK_SIZE, chunk_overlap=CHUNK_OVERLAP + ) splits = text_splitter.split_text(contract_text) # add chunk number strating from 1 - splits = ["chunk " + str(idx+1).zfill(3) + ": " + str(split) for idx,split in enumerate(splits)] + splits = [ + "chunk " + str(idx + 1).zfill(3) + ": " + str(split) + for idx, split in enumerate(splits) + ] # create vector retriever for vector/semantic search and keyword retriever for keyword search - vectorstore = Chroma.from_texts(texts=splits, embedding=embeddings_model, persist_directory=chroma_filename - ) + vectorstore = Chroma.from_texts( + texts=splits, embedding=embeddings_model, persist_directory=chroma_filename + ) vectorstore_retriever = vectorstore.as_retriever(search_kwargs={"k": TOP_K}) - keyword_retriever = BM25Retriever.from_texts(splits, k=TOP_K, preprocess_func=word_tokenize) + keyword_retriever = BM25Retriever.from_texts( + splits, k=TOP_K, preprocess_func=word_tokenize + ) # keyword_retriever.k = 3 # ensemble_retriever = EnsembleRetriever(retrievers=[vectorstore_retriever, keyword_retriever], weights=[0.5, 0.5]) @@ -148,17 +159,25 @@ def field_context(one_to_one_fields: FieldSet, contract_text: str, filename: str # get relevant chunks based on semantic search and keyword search and merge them semantic_relevant_docs = vectorstore_retriever.invoke(question) - keyword_relevant_docs = keyword_retriever.invoke(' ;'.join(field.keywords)) - semantic_relevant_chunks = [doc.page_content for doc in semantic_relevant_docs] - keyword_relevant_chunks = [doc.page_content for doc in keyword_relevant_docs] + keyword_relevant_docs = keyword_retriever.invoke(" ;".join(field.keywords)) + semantic_relevant_chunks = [ + doc.page_content for doc in semantic_relevant_docs + ] + keyword_relevant_chunks = [ + doc.page_content for doc in keyword_relevant_docs + ] if field.field_name == "CONTRACT_TITLE": keyword_relevant_chunks = keyword_relevant_chunks + splits[:5] - field.retrieved_text = sorted(list(set(semantic_relevant_chunks + keyword_relevant_chunks))) - + field.retrieved_text = sorted( + list(set(semantic_relevant_chunks + keyword_relevant_chunks)) + ) + # Check if no context or full_context, if so, switch attribute - if len(field.retrieved_text) == 0 or len(field.retrieved_text) == len(splits): + if len(field.retrieved_text) == 0 or len(field.retrieved_text) == len( + splits + ): field.field_type = "full_context" - + vectorstore.delete_collection() try: shutil.rmtree(chroma_filename) @@ -167,6 +186,7 @@ def field_context(one_to_one_fields: FieldSet, contract_text: str, filename: str return one_to_one_fields, splits + # Function to group fields based on overlap threshold def group_fields(field_set: FieldSet, threshold=3): """ @@ -180,35 +200,37 @@ def group_fields(field_set: FieldSet, threshold=3): overlap_matrix = { (f1, f2): len(set(f1.retrieved_text) & set(f2.retrieved_text)) for f1, f2 in combinations(field_set.fields, 2) - if (overlap := len(set(f1.retrieved_text) & set(f2.retrieved_text))) >= threshold + if (overlap := len(set(f1.retrieved_text) & set(f2.retrieved_text))) + >= threshold } - + groups = [] - visited = set() # store field objects, not names - + visited = set() # store field objects, not names + for field in field_set.fields: - if field not in visited: # check if field is already in a group + if field not in visited: # check if field is already in a group group = {field} stack = [field] while stack: curr = stack.pop() - visited.add(curr) # add field to visited set + visited.add(curr) # add field to visited set for (f1, f2), overlap in overlap_matrix.items(): - if (f1 == curr and f2 not in visited and overlap >= threshold): + if f1 == curr and f2 not in visited and overlap >= threshold: group.add(f2) stack.append(f2) - elif (f2 == curr and f1 not in visited and overlap >= threshold): + elif f2 == curr and f1 not in visited and overlap >= threshold: group.add(f1) stack.append(f1) - if group: # only add non-empty groups + if group: # only add non-empty groups groups.append(group) return groups - - def chunk_hierarchical( - text_dict: dict[str, str], keywords: list[str], regex: str = None, case_sensitive: bool = False + text_dict: dict[str, str], + keywords: list[str], + regex: str = None, + case_sensitive: bool = False, ) -> list[str]: """Look for the keywords, one-by-one, and take the first that satisfies the keyword. We pad responsive pages with a one-page buffer (before and after) when possible. @@ -247,7 +269,10 @@ def chunk_hierarchical( def chunk_or( - text_dict: dict[str, str], keywords: list[str], regex: str = None, case_sensitive: bool = False + text_dict: dict[str, str], + keywords: list[str], + regex: str = None, + case_sensitive: bool = False, ) -> list[str]: """Look for any of the keywords and add any pages with any keywords to the chunk. We pad responsive pages with a one-page buffer (before and after) when possible. @@ -288,7 +313,10 @@ def chunk_or( # And: run each keyword and include a page if all are present def chunk_and( - text_dict: dict[str, str], keywords: list[str], regex: str = None, case_sensitive: bool = False + text_dict: dict[str, str], + keywords: list[str], + regex: str = None, + case_sensitive: bool = False, ) -> list[str]: """Look for all of the keywords and add any pages with all keywords to the chunk. We pad responsive pages with a one-page buffer (before and after) when possible. @@ -327,7 +355,9 @@ def chunk_and( # Regex: Check for a regex and include a page if the regex matches within the page -def chunk_regex(text_dict: dict[str, str], keywords: list[str], regex: str, case_sensitive: bool) -> list[str]: +def chunk_regex( + text_dict: dict[str, str], keywords: list[str], regex: str, case_sensitive: bool +) -> list[str]: """Look for regex match and add any pages with regex match to the chunk. We pad responsive pages with a one-page buffer (before and after) when possible. @@ -403,9 +433,7 @@ def keyword_search( def smart_chunk_one_to_one( - text_dict: dict, - contract_text: str, - one_to_one_fields: FieldSet + text_dict: dict, contract_text: str, one_to_one_fields: FieldSet ) -> dict: """Performs a keyword search on textract outputs to retrieve relevant chunks for a given field. @@ -421,7 +449,7 @@ def smart_chunk_one_to_one( dict: A dictionary keyed by field (defined by the keys in keyword_mappings) valued by corresponding chunks of text (input strings delimited by newlines). """ - + smart_chunk_fields = one_to_one_fields.filter(field_type="smart_chunked") chunks = defaultdict(str) @@ -438,7 +466,7 @@ def smart_chunk_one_to_one( chunk_func = chunk_regex else: chunk_func = None - + if chunk_func: page_list = chunk_func( text_dict, field.keywords, field.regex_pattern, field.case_sensitive @@ -464,4 +492,3 @@ def smart_chunk_one_to_one( chunks[field.field_name + "_methodology"] = field.methodology chunks[field.field_name + "_case"] = field.case_sensitive return chunks - diff --git a/fieldExtraction/src/investment/table_funcs.py b/fieldExtraction/src/investment/table_funcs.py index 0780fec..bc0ca0b 100644 --- a/fieldExtraction/src/investment/table_funcs.py +++ b/fieldExtraction/src/investment/table_funcs.py @@ -1,18 +1,13 @@ import ast -import copy import logging -import math import re -from collections import defaultdict -from typing import Any, Dict, List, Optional, Pattern, Tuple, Union +from typing import Any, Dict, List, Pattern, Union import pandas as pd -import src.utils.llm_utils as llm_utils import src.utils.string_utils as string_utils +from constants.constants import Constants from src import config -from src.prompts import preprocessing_prompts -from src.constants.investment_values import EXHIBIT_HEADER_MARKERS # Table regex patterns START_PAGE_PATTERN: Pattern[str] = re.compile( @@ -376,7 +371,9 @@ class Table: return tables -def get_table_info(text_dict: Dict[str, str]) -> Dict[str, List[Table]]: +def get_table_info( + text_dict: Dict[str, str], EXHIBIT_HEADER_MARKERS: list +) -> Dict[str, List[Table]]: """ Extracts all of the Table objects from the text_dict. @@ -400,14 +397,14 @@ def get_table_info(text_dict: Dict[str, str]) -> Dict[str, List[Table]]: end_index = page_text.find(END_MARKER, start_index) if end_index == -1: break - + # Don't consider tables that don't have relevant info table_text = page_text[start_index:end_index] - + if "%" not in table_text and "$" not in table_text: start_index = end_index + len(END_MARKER) continue - + metadata = page_text[:first_table_start_index].strip() header_start = start_index + len(START_MARKER) @@ -423,7 +420,9 @@ def get_table_info(text_dict: Dict[str, str]) -> Dict[str, List[Table]]: # Check if this page starts a new exhibit from the text exhibit_pattern = "|".join(EXHIBIT_HEADER_MARKERS) - starts_new_exhibit = re.search(rf'\b({exhibit_pattern})\b', page_text[:100], re.IGNORECASE) + starts_new_exhibit = re.search( + rf"\b({exhibit_pattern})\b", page_text[:100], re.IGNORECASE + ) is_continuation = ( header == "" # No header text @@ -599,7 +598,9 @@ def combine_continuous_tables( return updated_text_dict -def split_tables_by_rows(text_dict: Dict[str, str], row_limit: int) -> Dict[str, str]: +def split_tables_by_rows( + text_dict: Dict[str, str], EXHIBIT_HEADER_MARKERS: list, row_limit: int +) -> Dict[str, str]: """Split tables that exceed the row limit, add .0 suffix to pages with tables Args: @@ -613,7 +614,7 @@ def split_tables_by_rows(text_dict: Dict[str, str], row_limit: int) -> Dict[str, for page_num, page_text in text_dict.items(): # get tables on this page - page_table_dict = get_table_info({page_num: page_text}) + page_table_dict = get_table_info({page_num: page_text}, EXHIBIT_HEADER_MARKERS) if page_num in page_table_dict and page_table_dict[page_num]: # This page has tables diff --git a/fieldExtraction/src/investment/tin_npi_funcs.py b/fieldExtraction/src/investment/tin_npi_funcs.py index 35e3b88..b6c4c14 100644 --- a/fieldExtraction/src/investment/tin_npi_funcs.py +++ b/fieldExtraction/src/investment/tin_npi_funcs.py @@ -1,17 +1,16 @@ - import json import re from typing import Optional +import constants.regex_patterns as regex_patterns import src.config as config import src.prompts.investment_prompts as investment_prompts - -import src.regex.regex_patterns as regex_patterns import src.utils.llm_utils as llm_utils import src.utils.string_utils as string_utils -from src.enums.delimiters import Delimiter +from constants.delimiters import Delimiter from src.prompts.investment_prompts import Field, FieldSet + def get_all_matches(text, pattern, filename): """ Extracts all unique matches of a given pattern from the provided text. @@ -24,13 +23,13 @@ def get_all_matches(text, pattern, filename): Returns: list: A list of unique matches found in the text. If the pattern is None or empty, returns an empty list. """ - if string_utils.is_empty(pattern): # check if pattern is None or empty + if string_utils.is_empty(pattern): # check if pattern is None or empty return [] matches = re.findall(pattern, text) return list(set(matches)) # Remove duplicates -def chunk_on_matches(matches: list[str], - text_dict: dict) -> str: + +def chunk_on_matches(matches: list[str], text_dict: dict) -> str: """ Extracts and concatenates text from a dictionary of pages where any of the specified matches are found. @@ -39,7 +38,7 @@ def chunk_on_matches(matches: list[str], text_dict (dict): A dictionary where keys are identifiers (e.g., page numbers) and values are strings of text. Returns: - str: A single string containing the concatenated text from all pages where at least one match is found, + str: A single string containing the concatenated text from all pages where at least one match is found, separated by newline characters. """ valid_pages = [] @@ -62,7 +61,7 @@ def clean_provider_info(provider_info: list[dict]) -> list[dict]: cleaned_providers = [] for provider in provider_info: # Create a new provider dict to hold the cleaned data - cleaned_provider = {"IS_GROUP" : provider["IS_GROUP"]} + cleaned_provider = {"IS_GROUP": provider["IS_GROUP"]} # Clean TIN - remove hyphens and dots, ensure it's 9 digits if "TIN" in provider and provider["TIN"]: @@ -72,7 +71,7 @@ def clean_provider_info(provider_info: list[dict]) -> list[dict]: cleaned_provider["TIN"] = "UNKNOWN" else: cleaned_provider["TIN"] = "UNKNOWN" - + # Clean NPI - remove hyphens and dots, ensure it's 10 digits if "NPI" in provider and provider["NPI"]: cleaned_provider["NPI"] = provider["NPI"].replace("-", "").replace(".", "") @@ -94,6 +93,7 @@ def clean_provider_info(provider_info: list[dict]) -> list[dict]: cleaned_providers.append(cleaned_provider) return cleaned_providers + def merge_provider_info(one_to_one_results: dict, provider_info: list[dict]) -> dict: """ Merges provider_info into one_to_one_results. @@ -106,7 +106,7 @@ def merge_provider_info(one_to_one_results: dict, provider_info: list[dict]) -> Returns: dict: Updated one_to_one_results with merged values from provider_info. """ - + # Extract group and non-group providers group_tins = set() group_npis = set() @@ -130,7 +130,7 @@ def merge_provider_info(one_to_one_results: dict, provider_info: list[dict]) -> other_npis.append(provider["NPI"]) if provider.get("NAME"): other_names.append(provider["NAME"]) - + # De-Unknown GROUP fields group_tins = [v for v in group_tins if v != "UNKNOWN"] group_npis = [v for v in group_npis if v != "UNKNOWN"] @@ -139,18 +139,27 @@ def merge_provider_info(one_to_one_results: dict, provider_info: list[dict]) -> # Merge group provider information into one_to_one_results one_to_one_results["PROV_GROUP_TIN"] = "|".join(group_tins) if group_tins else "" one_to_one_results["PROV_GROUP_NPI"] = "|".join(group_npis) if group_npis else "" - one_to_one_results["PROV_GROUP_NAME_FULL"] = "|".join(group_names) if group_names else "" + one_to_one_results["PROV_GROUP_NAME_FULL"] = ( + "|".join(group_names) if group_names else "" + ) # Merge other provider information into one_to_one_results one_to_one_results["PROV_OTHER_TIN"] = "|".join(other_tins) if other_tins else "" one_to_one_results["PROV_OTHER_NPI"] = "|".join(other_npis) if other_npis else "" - one_to_one_results["PROV_OTHER_NAME_FULL"] = "|".join(other_names) if other_names else "" + one_to_one_results["PROV_OTHER_NAME_FULL"] = ( + "|".join(other_names) if other_names else "" + ) return one_to_one_results -def reimbursement_tin_npi(exhibit_dict: dict[str, str], exhibit_page: str, reimbursement_level_fields: FieldSet, filename: str) -> tuple[dict, FieldSet]: - + +def reimbursement_tin_npi( + exhibit_dict: dict[str, str], + exhibit_page: str, + reimbursement_level_fields: FieldSet, + filename: str, +) -> tuple[dict, FieldSet]: """ - Extracts Reimbursement-Level TIN and NPI values from the given exhibit chunk using an LLM. - + Extracts Reimbursement-Level TIN and NPI values from the given exhibit chunk using an LLM. + Args: exhibit_dict (dict[str, str]): Dictionary containing the exhibit text with page numbers as keys. exhibit_page (str): The page number containing reimbursement details. @@ -158,24 +167,31 @@ def reimbursement_tin_npi(exhibit_dict: dict[str, str], exhibit_page: str, reimb filename (str): The name of the file being processed. Returns: - tuple[dict, FieldSet]: A dictionary containing extracted TIN and NPI values, and an + tuple[dict, FieldSet]: A dictionary containing extracted TIN and NPI values, and an updated FieldSet with additional fields if multiple values are found. Fields Added: REIMB_PROV_NAME, REIMB_PROV_TIN, REIMB_PROV_NPI """ + def get_list(field_name, filtered_providers): # Get all values and filter out invalid ones - all_values = [provider.get(field_name, "N/A") for provider in filtered_providers] - valid_values = [val for val in all_values if not string_utils.is_empty(val) and val != "UNKNOWN"] + all_values = [ + provider.get(field_name, "N/A") for provider in filtered_providers + ] + valid_values = [ + val + for val in all_values + if not string_utils.is_empty(val) and val != "UNKNOWN" + ] return list(set(valid_values)) # Remove duplicates answer_dict = {} # Extract all providers from the exhibit chunk - providers = get_provider_info(text_dict=exhibit_dict, - page_num=exhibit_page, - filename=filename) + providers = get_provider_info( + text_dict=exhibit_dict, page_num=exhibit_page, filename=filename + ) # Clean, standardize, and deduplicate extracted providers filtered_providers = deduplicate_providers(clean_provider_info(providers)) @@ -183,9 +199,9 @@ def reimbursement_tin_npi(exhibit_dict: dict[str, str], exhibit_page: str, reimb field_configs = [ ("REIMB_PROV_TIN", "TIN"), ("REIMB_PROV_NPI", "NPI"), - ("REIMB_PROV_NAME", "NAME") + ("REIMB_PROV_NAME", "NAME"), ] - + # Always add providers to reimbursement_level fields for proper term attribution for field_name, provider_key in field_configs: if len(filtered_providers) > 0: @@ -200,11 +216,13 @@ def reimbursement_tin_npi(exhibit_dict: dict[str, str], exhibit_page: str, reimb else: # If no providers are found, set default values answer_dict[field_name] = "N/A" - + return answer_dict, reimbursement_level_fields -def identify_group_provider(text_dict: dict, providers: list[dict], filename: str) -> Optional[dict]: +def identify_group_provider( + text_dict: dict, providers: list[dict], filename: str +) -> Optional[dict]: """Identifies the main contracting group provider from a list of providers. Focuses on first/last parts of the contract, where group identification is typically found @@ -214,7 +232,7 @@ def identify_group_provider(text_dict: dict, providers: list[dict], filename: st filename (str): The name of the file being processed Returns: - Optional[dict]: The provider that should be marked as IS_GROUP=true, + Optional[dict]: The provider that should be marked as IS_GROUP=true, or None if no group provider is found """ # Extract first 3 pages and signature pages @@ -225,11 +243,17 @@ def identify_group_provider(text_dict: dict, providers: list[dict], filename: st # I want to get the first 3 pages and the signature pages. But if they overlap, I want to avoid duplicates first_pages = pages[:3] - signature_pages = string_utils.extract_signature_page(text_dict, filename).keys() # Use textract "This page has X signature" as a marker + signature_pages = string_utils.extract_signature_page( + text_dict, filename + ).keys() # Use textract "This page has X signature" as a marker # Extract text from the first 3 pages and the signature pages - first_section = "\n".join([text_dict[page] for page in first_pages if page not in signature_pages]) # Avoid duplicates - signature_section = "\n".join([text_dict[page] for page in signature_pages]) # Use textract "This page has X signature" as a marker + first_section = "\n".join( + [text_dict[page] for page in first_pages if page not in signature_pages] + ) # Avoid duplicates + signature_section = "\n".join( + [text_dict[page] for page in signature_pages] + ) # Use textract "This page has X signature" as a marker # Create focused sections for group identification key_sections = first_section + "\n\n" + signature_section @@ -238,35 +262,47 @@ def identify_group_provider(text_dict: dict, providers: list[dict], filename: st provider_list = json.dumps(providers, indent=2) # Construct prompt - prompt = investment_prompts.GROUP_TIN_NPI_TEMPLATE(key_sections=key_sections, provider_list=provider_list) + prompt = investment_prompts.GROUP_TIN_NPI_TEMPLATE( + key_sections=key_sections, provider_list=provider_list + ) # Get response and parse response = llm_utils.invoke_claude(prompt, "sonnet_latest", filename) try: - group_info = json.loads(string_utils.extract_text_from_delimiters(response, Delimiter.PIPE)) + group_info = json.loads( + string_utils.extract_text_from_delimiters(response, Delimiter.PIPE) + ) # Match with an existing provider for provider in providers: - if provider.get("TIN") == group_info.get("TIN") and provider.get("NPI") == group_info.get("NPI"): + if provider.get("TIN") == group_info.get("TIN") and provider.get( + "NPI" + ) == group_info.get("NPI"): # Always use the group-provided name when available # This is a fallback in case the group provider name is not in the original provider list - if group_info.get("NAME") is not None and group_info.get("NAME") not in ["", "null", "None", "N/A", "UNKNOWN"]: + if group_info.get("NAME") is not None and group_info.get( + "NAME" + ) not in ["", "null", "None", "N/A", "UNKNOWN"]: provider["NAME"] = group_info.get("NAME") # Mark as group provider["IS_GROUP"] = True return provider - + # If we got here, no match was found, but we have group info from the LLM # Add the group provider to the list if it has valid information - if ((group_info.get("TIN", "") not in ["", "null", "None", "N/A", "UNKNOWN"]) or - (group_info.get("NPI", "") not in ["", "null", "None", "N/A", "UNKNOWN"]) or - (group_info.get("NAME", "") not in ["", "null", "None", "N/A", "UNKNOWN"])): + if ( + (group_info.get("TIN", "") not in ["", "null", "None", "N/A", "UNKNOWN"]) + or (group_info.get("NPI", "") not in ["", "null", "None", "N/A", "UNKNOWN"]) + or ( + group_info.get("NAME", "") not in ["", "null", "None", "N/A", "UNKNOWN"] + ) + ): # Create a new provider with the group info new_group_provider = { "TIN": group_info.get("TIN", "UNKNOWN"), "NPI": group_info.get("NPI", "UNKNOWN"), "NAME": group_info.get("NAME", "UNKNOWN"), - "IS_GROUP": True + "IS_GROUP": True, } # Add to the provider list providers.append(new_group_provider) @@ -280,7 +316,9 @@ def identify_group_provider(text_dict: dict, providers: list[dict], filename: st return None -def deduplicate_providers(provider_info: list[dict]) -> list[dict]: # TODO: add unit test +def deduplicate_providers( + provider_info: list[dict], +) -> list[dict]: # TODO: add unit test """Deduplicates provider information based on TIN, NPI, and NAME fields. This function checks for duplicates in the provider information list by comparing the TIN, NPI, and NAME fields. If all three fields match, only one instance of the @@ -300,9 +338,18 @@ def deduplicate_providers(provider_info: list[dict]) -> list[dict]: # TODO: add for provider in provider_info: # Skip providers where all critical identification fields are unknown if ( - (string_utils.is_empty(provider.get("TIN", "")) or provider.get("TIN", "") == "UNKNOWN") and - (string_utils.is_empty(provider.get("NPI", "")) or provider.get("NPI", "") == "UNKNOWN") and - (string_utils.is_empty(provider.get("NAME", "")) or provider.get("NAME", "") == "UNKNOWN") + ( + string_utils.is_empty(provider.get("TIN", "")) + or provider.get("TIN", "") == "UNKNOWN" + ) + and ( + string_utils.is_empty(provider.get("NPI", "")) + or provider.get("NPI", "") == "UNKNOWN" + ) + and ( + string_utils.is_empty(provider.get("NAME", "")) + or provider.get("NAME", "") == "UNKNOWN" + ) ): continue key = ( @@ -316,7 +363,7 @@ def deduplicate_providers(provider_info: list[dict]) -> list[dict]: # TODO: add return valid_providers -def get_provider_info(text_dict: dict, page_num : str, filename: str) -> list: +def get_provider_info(text_dict: dict, page_num: str, filename: str) -> list: """ Extracts provider information from a specified page in a document using a language model. @@ -326,17 +373,23 @@ def get_provider_info(text_dict: dict, page_num : str, filename: str) -> list: filename (str): The name of the file being processed, used for logging or tracking purposes. Returns: - list: A list of extracted provider information dictionaries, each containing TIN, NPI, + list: A list of extracted provider information dictionaries, each containing TIN, NPI, NAME, and flags for presence on signature or intro pages. Raises: ValueError: If the response from the language model cannot be parsed as valid JSON. """ chunk = text_dict[page_num] - - provider_fields = FieldSet(file_path=config.FIELD_JSON_PATH, field_type="provider_info") - prompt = investment_prompts.TIN_NPI_TEMPLATE(chunk, provider_fields.print_prompt_dict()) - claude_answer_raw = llm_utils.invoke_claude(prompt, "sonnet_latest", filename, max_tokens=8192) # Sometimes rosters can have 50+ provider + + provider_fields = FieldSet( + file_path=config.FIELD_JSON_PATH, field_type="provider_info" + ) + prompt = investment_prompts.TIN_NPI_TEMPLATE( + chunk, provider_fields.print_prompt_dict() + ) + claude_answer_raw = llm_utils.invoke_claude( + prompt, "sonnet_latest", filename, max_tokens=8192 + ) # Sometimes rosters can have 50+ provider # Defensive programming for any json parsing errors try: @@ -347,8 +400,17 @@ def get_provider_info(text_dict: dict, page_num : str, filename: str) -> list: providers = [providers] elif not isinstance(providers, list): # If not a list or dict, create a default list with error info - print(f"Warning: Unexpected format in provider info response: {type(providers)}") - providers = [{"TIN": "UNKNOWN", "NPI": "UNKNOWN", "NAME": "PARSING_ERROR", "ON_SIGNATURE_PAGE": "N"}] + print( + f"Warning: Unexpected format in provider info response: {type(providers)}" + ) + providers = [ + { + "TIN": "UNKNOWN", + "NPI": "UNKNOWN", + "NAME": "PARSING_ERROR", + "ON_SIGNATURE_PAGE": "N", + } + ] # Add IS_GROUP flag based on ON_SIGNATURE_PAGE and page_num for provider in providers: @@ -358,21 +420,29 @@ def get_provider_info(text_dict: dict, page_num : str, filename: str) -> list: provider["IS_GROUP"] = "N" if "ON_SIGNATURE_PAGE" in provider: del provider["ON_SIGNATURE_PAGE"] - + return providers - except ValueError as e: # Handle JSON parsing error + except ValueError as e: # Handle JSON parsing error print(f"Error parsing JSON response from LLM: {str(e)}") print(f"Raw response: {claude_answer_raw}") # Return a default value - return [{"TIN": "UNKNOWN", "NPI": "UNKNOWN", "NAME": "PARSING_ERROR", "IS_GROUP": "N"}] + return [ + { + "TIN": "UNKNOWN", + "NPI": "UNKNOWN", + "NAME": "PARSING_ERROR", + "IS_GROUP": "N", + } + ] -def run_provider_info_fields(contract_text: str, - one_to_one_fields: FieldSet, - text_dict: dict, - filename: str, - # payer_name: str # This is not being used right now, but it might be useful for filtering providers later - ): +def run_provider_info_fields( + contract_text: str, + one_to_one_fields: FieldSet, + text_dict: dict, + filename: str, + # payer_name: str # This is not being used right now, but it might be useful for filtering providers later +): """ Extracts provider information from a contract text using regex-based identifier extraction, processes the relevant text chunks, and cleans the results. @@ -391,15 +461,27 @@ def run_provider_info_fields(contract_text: str, # If there are no identifiers, return early with default values if not all_tins and not all_npis: - deduplicated_provider_info = [{"TIN": "UNKNOWN", "NPI": "UNKNOWN", "NAME": "NO_IDENTIFIERS_FOUND", "IS_GROUP" : "N"}] + deduplicated_provider_info = [ + { + "TIN": "UNKNOWN", + "NPI": "UNKNOWN", + "NAME": "NO_IDENTIFIERS_FOUND", + "IS_GROUP": "N", + } + ] else: # Find pages with TINs or NPIs - relevant_pages = [page_num for page_num, page_text in text_dict.items() if any(tin in page_text for tin in all_tins) or any(npi in page_text for npi in all_npis)] + relevant_pages = [ + page_num + for page_num, page_text in text_dict.items() + if any(tin in page_text for tin in all_tins) + or any(npi in page_text for npi in all_npis) + ] # Stopgap for when the TIN is written in the header or footer of every page (or nearly every page) if len(relevant_pages) >= len(text_dict) - 1 and len(all_tins) == 1: relevant_pages = [relevant_pages[0]] - + # Process each page separately and combine results combined_provider_info = [] for page_num in relevant_pages: @@ -414,21 +496,25 @@ def run_provider_info_fields(contract_text: str, # Create columns one_to_one_results = {} - one_to_one_results['PROV_INFO_JSON'] = json.dumps(deduplicated_provider_info) - one_to_one_results['PROV_INFO_JSON_FORMATTED'] = '\n'.join([json.dumps(provider) for provider in deduplicated_provider_info]) - one_to_one_results = merge_provider_info(one_to_one_results, deduplicated_provider_info) # Add Group TIN - + one_to_one_results["PROV_INFO_JSON"] = json.dumps(deduplicated_provider_info) + one_to_one_results["PROV_INFO_JSON_FORMATTED"] = "\n".join( + [json.dumps(provider) for provider in deduplicated_provider_info] + ) + one_to_one_results = merge_provider_info( + one_to_one_results, deduplicated_provider_info + ) # Add Group TIN + # What if the TIN/NPI regexes don't find anything? Add PROV_GROUP_NAME_FULL to full context - if one_to_one_results.get("PROV_OTHER_NAME_FULL") == "NO_IDENTIFIERS_FOUND": # This is how we know the regex didn't find anything + if ( + one_to_one_results.get("PROV_OTHER_NAME_FULL") == "NO_IDENTIFIERS_FOUND" + ): # This is how we know the regex didn't find anything other_provider_name_field = Field.from_values( field_name="PROV_GROUP_NAME_FULL", relationship="one_to_one", field_type="full_context", - prompt="What is the name of the group provider associated with this contract? Return the full name as it appears in the document." + prompt="What is the name of the group provider associated with this contract? Return the full name as it appears in the document.", ) one_to_one_fields.add_field(other_provider_name_field) one_to_one_results["PROV_OTHER_NAME_FULL"] = "UNKNOWN" - + return one_to_one_results, one_to_one_fields - - diff --git a/fieldExtraction/src/investment/vision_funcs.py b/fieldExtraction/src/investment/vision_funcs.py index c44484c..72f7094 100644 --- a/fieldExtraction/src/investment/vision_funcs.py +++ b/fieldExtraction/src/investment/vision_funcs.py @@ -1,22 +1,24 @@ -import fitz # PyMuPDF import base64 -from typing import List from io import BytesIO +from typing import List + +import fitz # PyMuPDF from PIL import Image # Pillow library -from src.config import DOCZY_PDF_FILES_BUCKET_S3_URL -from src.utils import io_utils + import src.prompts.investment_prompts as prompts import src.utils.llm_utils as llm_utils -import src.utils.string_utils as string_utils +from src.config import DOCZY_PDF_FILES_BUCKET_S3_URL +from src.utils import io_utils + def get_image_array_based_answer( - pdf_path: str, - effective_date_page_numbers: List, - filename: str, - fields: List[str] = ["AARETE_DERIVED_EFFECTIVE_DT"] + pdf_path: str, + effective_date_page_numbers: List, + filename: str, + fields: List[str] = ["AARETE_DERIVED_EFFECTIVE_DT"], ) -> dict: """ - Processes specific pages of a PDF, converts them to a base64-encoded image, + Processes specific pages of a PDF, converts them to a base64-encoded image, and retrieves answers using the Claude 3.5 Sonnet model. Args: @@ -30,17 +32,23 @@ def get_image_array_based_answer( {"AARETE_DERIVED_EFFECTIVE_DT": "YYYY/MM/DD"} """ # Convert specified PDF pages to a base64-encoded image - base64_images = process_pdf_pages_to_base64_images(pdf_path, filename, effective_date_page_numbers) + base64_images = process_pdf_pages_to_base64_images( + pdf_path, filename, effective_date_page_numbers + ) # Prepare the prompt for the LLM extended_prompt = prompts.effective_date_fix_prompt() # Invoke the LLM to get the response - response_dict = llm_utils.get_image_array_answer(extended_prompt, filename, base64_images) + response_dict = llm_utils.get_image_array_answer( + extended_prompt, filename, base64_images + ) return response_dict -def process_pdf_pages_to_base64_images(pdf_path: str, filename: str, page_numbers: List) -> List[str]: +def process_pdf_pages_to_base64_images( + pdf_path: str, filename: str, page_numbers: List +) -> List[str]: """ Convert multiple specific pages of a PDF into a list of base64-encoded JPEG images. @@ -57,8 +65,9 @@ def process_pdf_pages_to_base64_images(pdf_path: str, filename: str, page_number - Returns the list of base64-encoded images. """ - - page_numbers = sorted({int(float(x)) for x in page_numbers}) # Ensure unique and sorted page numbers + page_numbers = sorted( + {int(float(x)) for x in page_numbers} + ) # Ensure unique and sorted page numbers if not page_numbers: raise ValueError("No valid page numbers provided") try: @@ -107,4 +116,4 @@ def process_pdf_pages_to_base64_images(pdf_path: str, filename: str, page_number except (ValueError, fitz.FileDataError) as e: print(f"Error processing PDF pages: {str(e)}") - raise \ No newline at end of file + raise diff --git a/fieldExtraction/src/keywords.py b/fieldExtraction/src/keywords.py deleted file mode 100644 index 9111aea..0000000 --- a/fieldExtraction/src/keywords.py +++ /dev/null @@ -1,115 +0,0 @@ -# Methodology -# regex = make 'keyword' arg a pattern (NPI, TIN, Dates, etc) -# hierarchical and/or = Run and condition first, if nothing, then run or -# hierarchy = run each keyword independently in order of listing - case sensitive -# and = run each keyword if all are present, case insensitive -# or = run each keyword if any are present, case insensitive - - -# Example: {'blue_group' : {'fields' : ['TermClause', ContractAuto', ...], 'methodology' : 'or', 'keywords' : ['renew', 'term']} -# "irs_group / "Gray Group":" -# 'Tax ID,TIN,IRS,Provider Name,National Provider Identifier,NPI,Other NPIs,To Provider At:,To Provider at:' - -from src import config - -GROUPED_KEYWORD_MAPPINGS = { - "term_group": { - "fields": [ - "TERM_CLAUSE", - "CONTRACT_AUTO_RENEWAL_IND", - "CONTRACT_TERMINATION_DT", - "TERMINATION_UPON_NOTICE", - "TERMINATION_UPON_CAUSE", - ], - "methodology": "or", - "keywords": ["TERM AND TERMINATION", "Term and Termination", "Term and termination", "term and termination", "Term" - , "Renew", "renew", "Auto renew", "auto renew", "automatically renew", "evergreen", "Evergreen" - , "upon notice", "Notice", "NOTICE", "with notice", "without cause", "With Cause", "with cause" - , "Termination", "Terminate"], - "case_sensitive": True, - }, - "AGREEMENT_NAME": { - "fields": [ - "CONTRACT_TITLE" - ], - "methodology": "hierarchy", - "keywords": ["AGREEMENT", "AMENDMENT", "ADDENDUM", "PROVIDER", "CONTRACT", "MEMORANDUM", "LETTER", "REQUEST", "EFFECTIVE"], - "case_sensitive": False, - }, - "notice_provider_group": { - "fields": ["NOTICE_PROVIDER_NAME", "NOTICE_PROVIDER_ADDRESS"], - "methodology": "or", - "keywords": ["Attn", "Inc.", "Suite", "Ste", "P.O Box", "To Provider at:"], - "case_sensitive": False, - }, - "LATE_PAID_CLAIMS_LANGUAGE": { - "fields": ["LATE_PAID_CLAIMS_LANGUAGE"], - "methodology": "hierarchy", - "keywords": [ - "late paid", - "late payment", - "interest", - "late fee", - "interest shall be paid", - "interest payment", - ], - "case_sensitive": False, - }, - "NON_RENEWAL_LANGUAGE": { - "fields": ["NON_RENEWAL_LANGUAGE"], - "methodology": "or", - "keywords": ["not to renew", "non-renewal", "non renewal", "nonrenewal"], - "case_sensitive": False, - }, - "HCBS_SERVICES": { - "fields": ["HCBS_SERVICES"], - "methodology": "hierarchy", - "keywords": ["%", "Plan shall pay", "hcbs"], - "case_sensitive": False, - }, - "POLICIES_AND_PROCEDURES": { - "fields": ["POLICIES_AND_PROCEDURES"], - "methodology": "or", - "keywords": ["2.4", "policies", "procedures"], - "case_sensitive": False, - }, - "REGULATORY_REQUIREMENTS": { - "fields": ["REGULATORY_REQUIREMENTS"], - "methodology": "or", - "keywords": ["8.3", "6.4", "regulatory requirements", "regulatory"], - "case_sensitive": False, - }, - "RECOVERY_RIGHTS": { - "fields": ["RECOVERY_RIGHTS"], - "methodology": "or", - "keywords": ["3.5", "recovery rights"], - "case_sensitive": False, - }, - "RELATIONSHIP_OF_PARTIES_LANGUAGE": { - "fields": ["RELATIONSHIP_OF_PARTIES_LANGUAGE"], - "methodology": "or", - "keywords": ["8.1", "relationship of parties"], - "case_sensitive": False, - }, - "EXCLUSIVITY_REQUIREMENT_LANGUAGE": { - "fields": ["EXCLUSIVITY_REQUIREMENT_LANGUAGE"], - "methodology": "or", - "keywords": ["27", "exclusiv"], - "case_sensitive": False, - }, - "CONTRACT_EFFECTIVE_DT": { - "fields": ["CONTRACT_EFFECTIVE_DT"], - "methodology": "include_exclude", - "included_keywords" : ["Effective Date", "entered into", "shall be effective", "will become effective", "dated this", "made and entered into", "made this", "dated and effective"] \ - if any([cand in config.BATCH_ID.lower() for cand in ["cnc-4","cnc-4a","cnc-4b"]]) \ - else ["Effective Date", "entered into", "shall be effective", "will become effective"], - "excluded_keywords" : ["Permit","Registration","Department of Public Health"], - "case_sensitive": False, - }, - "TERMINATION_UPON_NOTICE": { - "fields": ["TERMINATION_UPON_NOTICE"], - "methodology": "or", - "keywords": ["notice", "upon notice", "termination"], - "case_sensitive": False, - } -} diff --git a/fieldExtraction/src/postprocessing_funcs.py b/fieldExtraction/src/postprocessing_funcs.py deleted file mode 100644 index 4aba4a5..0000000 --- a/fieldExtraction/src/postprocessing_funcs.py +++ /dev/null @@ -1,1241 +0,0 @@ -import logging -import re - -import hashlib -import numpy as np -import pandas as pd - -import src.constants.valid as valid -import src.constants.investment_values as investment_values -from src.prompts import postprocessing_prompts -from src.prompts import investment_prompts -from functools import cache -import src.utils.llm_utils as llm_utils -from src import config -from src.constants.valid import ( - DERIVED_INDICATOR_FIELDS, - STATE_MAP, - keywords_to_detect_invalid_state_name, -) -from src.utils.string_utils import is_empty, extract_text_from_delimiters -from src.enums.delimiters import Delimiter -from typing import Any - -logging.basicConfig( - level=logging.ERROR, format="%(asctime)s - %(levelname)s - %(message)s" -) - -def filter_service_column(answer_dicts: list[dict[str, str]]) -> list[dict[str, str]]: - """ - Filters the answer_dicts based on the presence of specific keywords in the 'FULL_SERVICE' field. - - Args: - answer_dicts (list[dict[str, str]]): A list of dictionaries containing service information. - - Returns: - list[dict[str, str]]: A filtered list of dictionaries where the 'FULL_SERVICE' field does not contain any of the specified keywords. - """ - filtered_list = [] - for d in answer_dicts: - clean_dict = True - for keyword in valid.SERVICE_FILTER: - if "FULL_SERVICE" in d and ( - keyword.upper() in d["FULL_SERVICE"].upper() - or d["FULL_SERVICE"].upper() in keyword.upper() - ): - clean_dict = False - if clean_dict: - filtered_list.append(d) - return filtered_list - -def filter_methodology_column(answer_dicts: list[dict[str, str]]) -> list[dict[str, str]]: - """ - Filters the answer_dicts based on the presence of specific keywords in the 'FULL_METHODOLOGY' field. - - Args: - answer_dicts (list[dict[str, str]]): A list of dictionaries containing methodology information. - - Returns: - list[dict[str, str]]: A filtered list of dictionaries where the 'FULL_METHODOLOGY' field does not contain any of the specified keywords. - """ - filtered_list = [] - for d in answer_dicts: - clean_dict = True - for keyword in valid.METHODOLOGY_FILTER: - if "FULL_METHODOLOGY" in d and ( - keyword.upper() in d["FULL_METHODOLOGY"].upper() - or d["FULL_METHODOLOGY"].upper() in keyword.upper() - ): - clean_dict = False - if clean_dict: - filtered_list.append(d) - return filtered_list - -def clean_td(td: list[dict[str, Any]]) -> list[dict[str,Any]]: - """ - Cleans the input list of dictionaries by transforming certain fields into lists and handling specific cases. - - Args: - td (list[dict[str, any]]): A list of dictionaries containing various fields. - - Returns: - list[dict[str, any]]: A cleaned list of dictionaries with transformed fields. - """ - td_clean = [] - for d in td: - new_d = {} - for k, v in d.items(): - if "DATE" in k: - new_d[k] = v if isinstance(v, list) else [v] - elif k not in ["page_num", "Filename"]: - if isinstance(v, str) and "," in v: - new_d[k] = [item.strip() for item in v.split(",")] - elif v == "N/A": - new_d[k] = [] - else: - new_d[k] = [v] if isinstance(v, str) else v - else: - new_d[k] = v - td_clean.append(new_d) - return td_clean - - -def get_parent_agreement_code(filename): - try: - filename = filename.split(".txt")[0] - - filename = re.sub(r"\([^)]*\)", "", filename) - - match = re.search(r"([^\sa-zA-Z]+)(?=\.\w+$|$)", filename) - end = [ - i for i in match.group(1).split("_") if i - ] # an error is happening here. This function needs a docstring. - return end[0] - except Exception as e: - print(f"Error in `get_parent_agreement_code`: {e}; returning `N/A`") - return "N/A" - - -def consolidate_subheader(dict_list: list[dict[str, str]]) -> list[dict[str, str]]: - """ - Consolidates the 'SUBHEADER' field into the 'FULL_SERVICE' field for each dictionary in the list. - - Args: - dict_list (list[dict[str, str]]): A list of dictionaries containing service information. - - Returns: - list[dict[str, str]]: A modified list of dictionaries where the 'SUBHEADER' field is concatenated to the 'FULL_SERVICE' field and then removed. - """ - modified_list = [] - for d in dict_list: - if "FULL_SERVICE" in d and "SUBHEADER" in d: - if d["SUBHEADER"] != "N/A": - d["FULL_SERVICE"] = d["SUBHEADER"] + " - " + d["FULL_SERVICE"] - # Remove the 'SUBHEADER' key - del d["SUBHEADER"] - modified_list.append(d) - return modified_list - -def clean_msr_lesser(df: pd.DataFrame, ls: list[str] = valid.VALID_MSR) -> pd.DataFrame: - """ - Cleans the 'LESSER' and 'LESSER_RATE' columns in the given DataFrame based on the 'FULL_SERVICE' and 'EXHIBIT' columns. - - This function performs the following operations: - 1. Identifies rows where the 'FULL_SERVICE' column contains any of the specified keywords. - 2. For each identified row, checks other rows with the same 'EXHIBIT' value. - 3. If any of these rows have 'LESSER' set to 'Y', updates the 'LESSER' value of the original row to 'Y'. - 4. Moves the first 'LESSER_RATE' value where 'LESSER' is 'Y' to the original row. - - Args: - df (pd.DataFrame): The input DataFrame containing the columns 'FULL_SERVICE', 'EXHIBIT', 'LESSER', and 'LESSER_RATE'. - ls (list[str], optional): A list of keywords to search for in the 'FULL_SERVICE' column. Defaults to valid.VALID_MSR. - - Returns: - pd.DataFrame: The modified DataFrame with updated 'LESSER' and 'LESSER_RATE' values. - """ - pattern = "|".join(re.escape(item) for item in ls) - mask = df["FULL_SERVICE"].str.contains(pattern, case=False, na=False) - target_rows = df[mask] - - # Iterate over these rows - for index, row in target_rows.iterrows(): - # Get all rows with the same 'EXHIBIT' value - exhibit_rows = df[df["EXHIBIT"] == row["EXHIBIT"]] - - # Check the 'LESSER' values of these rows - if (exhibit_rows["LESSER"] == "Y").any(): - # If any row has 'LESSER' == 'Y', set the 'LESSER' value of the original row to 'Y' - df.at[index, "LESSER"] = "Y" - - # Move first LESSER_RATE where LESSER==Y to MSR row - lesser_rate = exhibit_rows[exhibit_rows["LESSER"] == "Y"]["LESSER_RATE"].iloc[0] - df.at[index, "LESSER_RATE"] = lesser_rate - return df - -def clean_default_term(df: pd.DataFrame) -> pd.DataFrame: - """ - Cleans the 'DEFAULT_TERM' and 'DEFAULT_RATE' columns in the given DataFrame. - - This function performs the following operations: - 1. Removes any invalid default terms based on a predefined pattern. - 2. Replaces the 'DEFAULT_RATE' values with acronyms as specified in the valid module. - 3. Ensures that 'DEFAULT_TERM' values match the 'IP_OP' column values appropriately. - - Args: - df (pd.DataFrame): The input DataFrame containing the columns 'DEFAULT_TERM', 'DEFAULT_RATE', and 'IP_OP'. - - Returns: - pd.DataFrame: The cleaned DataFrame with updated 'DEFAULT_TERM' and 'DEFAULT_RATE' values. - """ - # Remove any invalid Defaults - pattern = "|".join(re.escape(term) for term in valid.INVALID_DEFAULT) - df["DEFAULT_TERM"] = df["DEFAULT_TERM"].astype(str) - mask = df["DEFAULT_TERM"].str.contains(pattern, case=False, na=False) - df.loc[mask, "DEFAULT_TERM"] = "N/A" - df.loc[mask, "DEFAULT_RATE"] = "N/A" - - # Replace RATE with acronyms (AC, BC, etc) - df["DEFAULT_RATE"] = df["DEFAULT_RATE"].replace(valid.RATE_REPLACEMENTS, regex=True) - - # Ensure Outpatient, Inpatient, etc match up - def replace_terms(row): - if "outpatient" in row["DEFAULT_TERM"].lower() and row["IP_OP"] != "OP": - row["DEFAULT_TERM"] = "N/A" - row["DEFAULT_RATE"] = "N/A" - elif "inpatient" in row["DEFAULT_TERM"].lower() and row["IP_OP"] != "IP": - row["DEFAULT_TERM"] = "N/A" - row["DEFAULT_RATE"] = "N/A" - return row - - df = df.apply(replace_terms, axis=1) - - return df - - -def clean_lesser_rate(df: pd.DataFrame) -> pd.DataFrame: - """ - Cleans the 'LESSER_RATE' column in the given DataFrame by updating it with the 'RATE_STANDARD' value if certain conditions are met. - - This function iterates over each row in the DataFrame and checks if the 'LESSER' column contains 'Y', - the 'LESSER_RATE' column is empty, and the 'RATE_STANDARD' column is not empty. If these conditions are met, it updates the 'LESSER_RATE' column with the value from the 'RATE_STANDARD' column and sets the 'RATE_STANDARD' column to 'N/A'. - - Args: - df (pd.DataFrame): The input DataFrame containing the columns 'LESSER', 'LESSER_RATE', and 'RATE_STANDARD'. - - Returns: - pd.DataFrame: The modified DataFrame with updated 'LESSER_RATE' and 'RATE_STANDARD' values. - """ - for index, row in df.iterrows(): - lesser = str(row["LESSER"]) if not pd.isna(row["LESSER"]) else "" - lesser_rate = row["LESSER_RATE"] - rate_standard = row["RATE_STANDARD"] - - if "Y" in lesser and is_empty(lesser_rate) and not is_empty(rate_standard): - df.at[index, "LESSER_RATE"] = row["RATE_STANDARD"] - df.at[index, "RATE_STANDARD"] = "N/A" - return df - -def clean_prov_2(df: pd.DataFrame) -> pd.DataFrame: - """ - Cleans the 'PROV_TYPE_LEVEL_2' column in the given DataFrame by finding and assigning valid values based on other columns. - - The function performs the following steps: - 1. Selects valid prov 2 values using 'PROV_TYPE' column. - 2. Identifies rows where 'PROV_TYPE_LEVEL_2' is empty. - 3. Attempts to find exact matches for 'PROV_TYPE_LEVEL_2' from 'FULL_SERVICE' and 'EXHIBIT' columns. - 4. If no match is found, it checks other rows with the same 'EXHIBIT' value for potential matches. - 5. Logs the matching process and any invalid assignments. - - Args: - df (pd.DataFrame): The input DataFrame containing columns 'PROV_TYPE', 'PROV_TYPE_LEVEL_2', 'FULL_SERVICE', and 'EXHIBIT'. - - Returns: - pd.DataFrame: The DataFrame with cleaned 'PROV_TYPE_LEVEL_2' values. - """ - valid_types = valid.select_valid_prov_2(df["PROV_TYPE"]) - target_rows = df[df["PROV_TYPE_LEVEL_2"].apply(is_empty)] - - def find_exact_match(text): - if pd.isna(text) or text == "": - return None - - words = re.findall(r"\b[\w/]+(?:[-\s][\w/]+)*\b", text) - for i in range(len(words)): - for j in range(i + 1, len(words) + 1): - phrase = " ".join(words[i:j]) - if phrase in valid_types: # Case-sensitive matching - return phrase - return None - - for index, row in target_rows.iterrows(): - match = None - - if not is_empty(row["FULL_SERVICE"]): - match = find_exact_match(str(row["FULL_SERVICE"])) - if match: - logging.info(f"Row {index}: Matched in FULL_SERVICE: {match}") - df.at[index, "PROV_TYPE_LEVEL_2"] = match - continue - - if not is_empty(row["EXHIBIT"]): - match = find_exact_match(str(row["EXHIBIT"])) - if match: - logging.info(f"Row {index}: Matched in EXHIBIT: {match}") - df.at[index, "PROV_TYPE_LEVEL_2"] = match - continue - - exhibit_rows = df[df["EXHIBIT"] == row["EXHIBIT"]] - if not exhibit_rows.empty: - for _, exhibit_row in exhibit_rows.iterrows(): - if not is_empty(exhibit_row["PROV_TYPE_LEVEL_2"]): - match = find_exact_match(str(exhibit_row["PROV_TYPE_LEVEL_2"])) - if match: - logging.info( - f"Row {index}: Matched in other row's PROV_TYPE_LEVEL_2: {match}" - ) - df.at[index, "PROV_TYPE_LEVEL_2"] = match - break - elif not is_empty(exhibit_row["FULL_SERVICE"]): - match = find_exact_match(str(exhibit_row["FULL_SERVICE"])) - if match: - logging.info( - f"Row {index}: Matched in other row's FULL_SERVICE: {match}" - ) - df.at[index, "PROV_TYPE_LEVEL_2"] = match - break - - if match is None: - logging.warning(f"Row {index}: No valid match found") - - # Final check to ensure no invalid values were assigned - invalid_assignments = df[ - (~df["PROV_TYPE_LEVEL_2"].isin(valid_types)) - & (~df["PROV_TYPE_LEVEL_2"].apply(is_empty)) - ] - if not invalid_assignments.empty: - logging.warning("Invalid assignments found:") - for idx, row in invalid_assignments.iterrows(): - logging.warning( - f"Row {idx}: Invalid PROV_TYPE_LEVEL_2: {row['PROV_TYPE_LEVEL_2']}" - ) - logging.warning(f" FULL_SERVICE: {row['FULL_SERVICE']}") - logging.warning(f" EXHIBIT: {row['EXHIBIT']}") - - # df.loc[invalid_assignments.index, "PROV_TYPE_LEVEL_2"] = "" - - return df - -def clean_term_clause(final_df: pd.DataFrame) -> pd.DataFrame: - """ - Cleans the 'TERM_CLAUSE' column in the given DataFrame by setting specific patterns to 'N/A'. - - This function identifies rows where the 'TERM_CLAUSE' column starts with or contains certain phrases - and sets the value to 'N/A' for those rows. - - Args: - final_df (pd.DataFrame): The input DataFrame containing the 'TERM_CLAUSE' column. - - Returns: - pd.DataFrame: The cleaned DataFrame with updated 'TERM_CLAUSE' values. - """ - if "TERM_CLAUSE" in final_df: - final_df.loc[ - final_df["TERM_CLAUSE"].str.startswith("IL-4 Termination"), "TERM_CLAUSE" - ] = "N/A" - final_df.loc[ - final_df["TERM_CLAUSE"].str.startswith("bonus payment shall be effective"), - "TERM_CLAUSE", - ] = "N/A" - final_df.loc[ - final_df["TERM_CLAUSE"].str.contains("does not contain"), "TERM_CLAUSE" - ] = "N/A" - final_df.loc[ - final_df["TERM_CLAUSE"].str.contains("No term or termination"), - "TERM_CLAUSE", - ] = "N/A" - return final_df - - -def clean_auto_renewal_ind(final_df): - """this function is to ensure the following relation between term, auto-renew and termination date is correct: - - automatically renew|coterminous cases are marked as autorenewal Yes - - null term clause should have autorenewal as N and termination date as blank - - autorenewal Y should have termination date blank - - If autorenewal is not Y it should be N. there should not be any blank value. - """ - if "TERM_CLAUSE" in final_df and "CONTRACT_AUTO_RENEWAL_IND" in final_df: - final_df.loc[ - final_df["TERM_CLAUSE"].str.contains("automatically renew|coterminous"), - "CONTRACT_AUTO_RENEWAL_IND", - ] = "Y" - final_df.loc[ - (final_df["TERM_CLAUSE"].isin(["N/A", "", " "])) - | (final_df["TERM_CLAUSE"].isna()), - "CONTRACT_AUTO_RENEWAL_IND", - ] = "N" - if "TERM_CLAUSE" in final_df and "CONTRACT_TERMINATION_DT" in final_df: - final_df.loc[ - (final_df["TERM_CLAUSE"].isin(["N/A", "", " "])) - | (final_df["TERM_CLAUSE"].isna()), - "CONTRACT_TERMINATION_DT", - ] = np.nan - if ( - "CONTRACT_AUTO_RENEWAL_IND" in final_df - and "CONTRACT_TERMINATION_DT" in final_df - ): - final_df.loc[ - final_df["CONTRACT_AUTO_RENEWAL_IND"].isin(["Yes", "Y"]), - "CONTRACT_TERMINATION_DT", - ] = np.nan - if "CONTRACT_AUTO_RENEWAL_IND" in final_df: - final_df.loc[ - ~(final_df["CONTRACT_AUTO_RENEWAL_IND"].isin(["Yes", "Y"])), - "CONTRACT_AUTO_RENEWAL_IND", - ] = "N" - else: - final_df["CONTRACT_AUTO_RENEWAL_IND"] = "N" - return final_df - - -def clean_npi(final_df: pd.DataFrame) -> pd.DataFrame: - """ - Cleans the 'PROV_GROUP_NPI' column in the given DataFrame by replacing strings with a digit count not equal to 10 with "N/A". - - Args: - final_df (pd.DataFrame): The input DataFrame containing the 'PROV_GROUP_NPI' column. - - Returns: - pd.DataFrame: The modified DataFrame with cleaned 'PROV_GROUP_NPI' values. - """ - if "PROV_GROUP_NPI" in final_df: - final_df["PROV_GROUP_NPI"] = final_df["PROV_GROUP_NPI"].apply( - lambda x: ( - f"N/A - (model detected: {x})" - if (len("".join(filter(str.isdigit, x))) != 10 and x != "N/A") - else x - ) - ) - return final_df - -def clean_network_access_fees(final_df: pd.DataFrame) -> pd.DataFrame: - """ - Cleans the 'NETWORK_ACCESS_FEES_IND' column in the given DataFrame. - - This function sets the 'NETWORK_ACCESS_FEES_IND' to "Yes" for rows where the value is not empty. - - Args: - final_df (pd.DataFrame): The input DataFrame containing the 'NETWORK_ACCESS_FEES_IND' column. - - Returns: - pd.DataFrame: The cleaned DataFrame with updated 'NETWORK_ACCESS_FEES_IND' values. - """ - if "NETWORK_ACCESS_FEES_IND" in final_df: - final_df.loc[~final_df["NETWORK_ACCESS_FEES_IND"].apply(is_empty), "NETWORK_ACCESS_FEES_IND"] = "Yes" - return final_df - - -def clean_health_plan_state(final_df): - - if "PAYER_NAME" in final_df and "HEALTH_PLAN_STATE" in final_df: - final_df.loc[ - final_df["PAYER_NAME"].str.startswith("Illini"), "HEALTH_PLAN_STATE" - ] = "Illinois" - - if "HEALTH_PLAN_STATE" in final_df: - final_df["HEALTH_PLAN_STATE"] = final_df["HEALTH_PLAN_STATE"].str.upper() - - final_df["HEALTH_PLAN_STATE"] = ( - final_df["HEALTH_PLAN_STATE"] - .map(valid.STATE_MAP) - .fillna(final_df["HEALTH_PLAN_STATE"]) - ) - - final_df["HEALTH_PLAN_STATE"] = final_df["HEALTH_PLAN_STATE"].str.title() - return final_df - - -def get_health_plan_state(text_dict: dict[str, str]) -> str: - """Go through a contract text and extract any mention of any state - - Args: - text_dict (dict[str, str]): A string-keyed dictionary valued by contract text - - Returns: - str: A single state extracted from the text, `multiple`, or `N/A` - """ - state_abbreviations = list(STATE_MAP.keys()) - state_names = list(STATE_MAP.values()) - - state_names_and_abbreviations = state_abbreviations + state_names - - pattern = ( - r"\b(?:" - + "|".join(re.escape(state) for state in state_names_and_abbreviations) - + r")\b" - ) - - states_set = set() - - for page_num, page_content in text_dict.items(): - state_matches = re.findall(pattern, page_content, flags=re.IGNORECASE) - state_matches = [state.strip().title() if len(state) > 2 else state.strip().upper() for state in state_matches] - states_set.update(state_matches) - - # Expand any abbreviations. If the state is already its full name, we're just gonna - # default to that. E.g., "CT" -> "Connecticut", "Connecticut" -> "Connecticut" - states_set = set([STATE_MAP.get(state, state) for state in states_set]) - - states_list = list(states_set) - - if len(states_list) > 1: - state = "Multiple" - elif len(states_list) == 1: - state = states_list[0] - else: - state = "N/A" - - return state - - -def state_check(states: str | list[str], text_dict: dict[str, str]) -> str: - if not isinstance(states, str) and not isinstance(states, list): - states = "N/A" - - if ( - isinstance(states, list) and all(isinstance(state, str) for state in states) - ) or isinstance( - states, str - ): # If it's a list of strings or a string... - - if isinstance( - states, str - ): # If it's a string, turn it to a list (even a singleton) - if "," in states: - states = states.replace("[", "").replace("]", "") - states = states.split(",") - else: - states = [states] - - result_states = [] - for state in states: - state = state.strip().replace(".", "") # Remove "." in cases like "N.Y." - - # fix 1 - if state == "Hlorida": - state = "Florida" - - # fix 2 - LLM self-talking - if any( - kw.lower() in state.lower() - for kw in keywords_to_detect_invalid_state_name - ): - state = "N/A" - - if ( - state.upper() in STATE_MAP.keys() - ): # Check if `state` is a state abbreviation. - result_states.append(STATE_MAP[state.upper()].title()) - - elif ( - state.strip().title() in STATE_MAP.values() - ): # Check if `state` is already an expanded name state from the validation set. - result_states.append(state.strip().title()) - - else: - result_states.append("N/A") - - result_states = set(result_states) - result_states.discard( - "N/A" - ) # Discard any N/As (so if `result_states` is ['Illinois', 'N/A'], it returns 'Illinois' instead of 'Multiple') - - print("Processed states", result_states) - - if len(result_states) == 1: - return list(result_states)[0] - - elif len(result_states) > 1: - return "Multiple" - - elif len(result_states) == 0: - state = get_health_plan_state( - text_dict - ) # search for state(s) in contract. If it's not found, `N/A` will be returned - return state - - raise TypeError( - f"Expected a string or list of strings, got {type(states).__name__}" - ) - - -def clean_health_plan_state_from_hotfix( - df: pd.DataFrame, - contract_name: str, - text_dict: dict[str, str], -) -> pd.DataFrame: - - """ - Cleans the 'HEALTH_PLAN_STATE' column in the given DataFrame using a hotfix. - Args: - df (pd.DataFrame): The DataFrame containing the data to be cleaned. - contract_name (str): The name of the contract. - text_dict (dict[str, str]): A dictionary containing text mappings for state checking. - Returns: - pd.DataFrame: The cleaned DataFrame. - Raises: - TypeError: If the input 'df' is not a pandas DataFrame. - ValueError: If the 'contract_name' is None. - KeyError: If the 'HEALTH_PLAN_STATE' column is not found in the DataFrame. - """ - - if not isinstance(df, pd.DataFrame): - # raise TypeError(f"Expected a dataframe, got {type(df).__name__}") - return df - - if contract_name is None: - # raise ValueError( - # f"Expected 'contract_name' parameter, got {contract_name}" - # ) - return df - - if "HEALTH_PLAN_STATE" not in df.columns: - # raise KeyError( - # f"Column 'Health Plan State' not found in DataFrame. Available columns are: {list(df.columns)}" - # ) - return df - - health_plan_state = df["HEALTH_PLAN_STATE"].unique().tolist()[0] - - print("Sending to state_check", health_plan_state) - answer = state_check(health_plan_state, text_dict) - - df["HEALTH_PLAN_STATE"] = answer - - return df - -def clean_notice_provider_name_and_address(final_df: pd.DataFrame) -> pd.DataFrame: - """ - Cleans the 'NOTICE_PROVIDER_NAME' and 'NOTICE_PROVIDER_ADDRESS' columns in the given DataFrame. - - This function sets the 'NOTICE_PROVIDER_NAME' and 'NOTICE_PROVIDER_ADDRESS' to NaN - for rows where the 'NOTICE_PROVIDER_NAME' contains "Superior HealthPlan". - - Args: - final_df (pd.DataFrame): The input DataFrame containing the columns to be cleaned. - - Returns: - pd.DataFrame: The cleaned DataFrame with updated 'NOTICE_PROVIDER_NAME' and 'NOTICE_PROVIDER_ADDRESS' values. - """ - if "NOTICE_PROVIDER_NAME" in final_df and "NOTICE_PROVIDER_ADDRESS" in final_df: - # Create a mask to identify rows where "Superior HealthPlan" is found - mask = final_df["NOTICE_PROVIDER_NAME"].str.contains("Superior HealthPlan", na=False) - - # Set "NOTICE_PROVIDER_NAME" to NaN where the condition is true - final_df.loc[mask, "NOTICE_PROVIDER_NAME"] = np.nan - - # Set "NOTICE_PROVIDER_ADDRESS" to NaN where the condition is true - final_df.loc[mask, "NOTICE_PROVIDER_ADDRESS"] = np.nan - - return final_df - -def get_tin_from_filename(final_df: pd.DataFrame) -> pd.DataFrame: - """ - Extracts TIN (Taxpayer Identification Number) from the filename or contract name - and updates the 'PROV_GROUP_TIN' column in the given DataFrame. - - The function checks if the DataFrame contains the 'Contract Name' or 'Filename' column. - If found, it extracts the first 10 characters from the respective column, removes any hyphens, - and assigns this value to a temporary column 'temp_filename'. If 'PROV_GROUP_TIN' is empty or - contains only spaces, and 'temp_filename' is numeric, it updates 'PROV_GROUP_TIN' with the value from 'temp_filename'. Finally, it drops the 'temp_filename' column. - - Args: - final_df (pd.DataFrame): The input DataFrame containing the columns 'Contract Name' or 'Filename' and 'PROV_GROUP_TIN'. - - Returns: - pd.DataFrame: The updated DataFrame with 'PROV_GROUP_TIN' potentially modified based on the filename or contract name. - """ - if "Contract Name" in final_df.columns: - final_df["temp_filename"] = ( - final_df["Contract Name"].str[:10].str.replace("-", "") - ) - if "PROV_GROUP_TIN" in final_df and "Contract Name" in final_df: - final_df.loc[ - (final_df["PROV_GROUP_TIN"].isin([" ", ""])) - & (final_df["temp_filename"].str.isnumeric()), - "PROV_GROUP_TIN", - ] = final_df["temp_filename"] - final_df.drop(columns=["temp_filename"], inplace=True) - - elif "Filename" in final_df.columns: - final_df["temp_filename"] = final_df["Filename"].str[:10].str.replace("-", "") - if "PROV_GROUP_TIN" in final_df and "Filename" in final_df: - final_df.loc[ - (final_df["PROV_GROUP_TIN"].isin([" ", ""])) - & (final_df["temp_filename"].str.isnumeric()), - "PROV_GROUP_TIN", - ] = final_df["temp_filename"] - final_df.drop(columns=["temp_filename"], inplace=True) - - return final_df - - -def clean_policies_and_procedures(final_df): - if "POLICIES_AND_PROCEDURES" in final_df: - final_df.loc[ - ~final_df["POLICIES_AND_PROCEDURES"].str.contains( - "polic", flags=re.IGNORECASE - ) - & ~final_df["POLICIES_AND_PROCEDURES"].str.contains( - "procedure", flags=re.IGNORECASE - ), - "POLICIES_AND_PROCEDURES", - ] = "N/A" - return final_df - -# Function to format and validate TIN numbers -def format_tin(x: str) -> str: - """ - Formats a given string as a Taxpayer Identification Number (TIN). - - The function extracts all digits from the input string and formats them as '##-#######' - if there are exactly 9 digits. If the input does not contain exactly 9 digits, it returns - 'N/A' or 'N/A - (model detected: {x})' depending on the input value. - - Args: - x (str): The input string to be formatted. - - Returns: - str: The formatted TIN or an appropriate 'N/A' message. - """ - digits = "".join(filter(str.isdigit, x)) - if len(digits) == 9: - # Format as '##-#######' - return f"{digits[:2]}-{digits[2:]}" - else: - # Retain prior handling for invalid entries - if x != "N/A": - return f"N/A - (model detected: {x})" - else: - return "N/A" - -def clean_tin(final_df: pd.DataFrame) -> pd.DataFrame: - """ - Cleans the 'PROV_GROUP_TIN' column in the given DataFrame by replacing - SSN-like strings with "N/A" and applying a formatting function. - - Args: - final_df (pd.DataFrame): The DataFrame containing the 'PROV_GROUP_TIN' column. - - Returns: - pd.DataFrame: The modified DataFrame with cleaned 'PROV_GROUP_TIN' values. - """ - if "PROV_GROUP_TIN" in final_df: - # Replace SSN-like strings with "N/A" - final_df["PROV_GROUP_TIN"] = final_df["PROV_GROUP_TIN"].replace( - r"\b\d{3}-\d{2}-\d{4}\b", "N/A", regex=True - ) - - # Apply the formatting function to the 'PROV_GROUP_TIN' column - final_df["PROV_GROUP_TIN"] = final_df["PROV_GROUP_TIN"].apply(format_tin) - - return final_df - - -def clean_tin_npi_other(final_df: pd.DataFrame) -> pd.DataFrame: - """ - Cleans the 'PROV_TIN_OTHER' and 'PROV_NPI_OTHER' columns in the given DataFrame by removing - TIN and NPI present in PROV_GROUP_TIN and PROV_GROUP_NPI respectively and - square brackets from the string values. - - Args: - final_df (pd.DataFrame): The DataFrame containing the columns to be cleaned. - - Returns: - pd.DataFrame: The cleaned DataFrame with square brackets removed from the specified columns. - """ - if "PROV_TIN_OTHER" in final_df and "PROV_GROUP_TIN" in final_df: - # if PROV_GROUP_TIN is still N/A, pick first value from tin_other - final_df["PROV_GROUP_TIN"] = final_df.apply(lambda row: row.PROV_TIN_OTHER[:9] if row.PROV_GROUP_TIN == 'N/A' and len(row.PROV_TIN_OTHER) >= 9 else row.PROV_GROUP_TIN, axis=1) - final_df["PROV_TIN_OTHER"] = final_df.apply(lambda row: row.PROV_TIN_OTHER.replace(row.PROV_GROUP_TIN+', ','').replace(', '+row.PROV_GROUP_TIN,'').replace(row.PROV_GROUP_TIN,''), axis=1) - if "PROV_NPI_OTHER" in final_df and "PROV_GROUP_NPI" in final_df: - # if PROV_GROUP_NPI is still N/A, pick first value from npi_other - final_df["PROV_GROUP_NPI"] = final_df.apply(lambda row: row.PROV_NPI_OTHER[:10] if row.PROV_GROUP_NPI == 'N/A' and len(row.PROV_NPI_OTHER) >= 10 else row.PROV_GROUP_NPI, axis=1) - final_df["PROV_NPI_OTHER"] = final_df.apply(lambda row: row.PROV_NPI_OTHER.replace(row.PROV_GROUP_NPI+', ','').replace(', '+row.PROV_GROUP_NPI,'').replace(row.PROV_GROUP_NPI,''), axis=1) - - if "PROV_TIN_OTHER" in final_df: - final_df["PROV_TIN_OTHER"] = ( - final_df["PROV_TIN_OTHER"] - .str.replace("[", "", regex=False) - .str.replace("]", "", regex=False) - ) - if "PROV_NPI_OTHER" in final_df: - final_df["PROV_NPI_OTHER"] = ( - final_df["PROV_NPI_OTHER"] - .str.replace("[", "", regex=False) - .str.replace("]", "", regex=False) - ) - - return final_df - -def replace_quotes(value: Any) -> str: - """ - Replaces escaped double quotes in a string with actual double quotes. - - Args: - value (Any): The input value which will be converted to a string and processed. - - Returns: - str: The processed string with escaped double quotes replaced by actual double quotes. - """ - try: - return str(value).replace(r"\"", '"') - except: - return value - -def replace_null_terms(value: Any) -> str: - """ - Replace null terms in the given value with "N/A". - - This function converts the input value to a string and checks if any term from - the NULL_ANSWER_TERMS list (defined in the valid module) is present in the value. - If a match is found, it returns "N/A". Otherwise, it returns the original value. - - Args: - value (Any): The input value to be checked and potentially replaced. - - Returns: - str: "N/A" if a null term is found in the value, otherwise the original value. - """ - # Convert value to string and check if any term from NULL_ANSWER_TERMS is in the value - if any(term.lower() in str(value).lower() for term in valid.NULL_ANSWER_TERMS): - return "N/A" - return value - -def filter_dict(d: dict, pattern: 're.Pattern') -> dict: - """ - Filters a dictionary by removing entries where the value matches a given pattern. - - Args: - d (dict): The dictionary to filter, where keys are page numbers and values are answers. - pattern (re.Pattern): The compiled regular expression pattern to search for in the answers. - - Returns: - dict: A new dictionary with entries that do not match the pattern. - """ - final_dict = {} - for page_num, answer in d.items(): - if not pattern.search(answer): - final_dict[page_num] = answer - return final_dict - -def check_add_on(group: pd.DataFrame) -> pd.DataFrame: - """ - Checks if any 'FULL_SERVICE' contains 'Add-on' or 'Add On' and updates the group accordingly. - - If any row in the group has 'FULL_SERVICE' containing 'Add-on' or 'Add On', this function sets - the 'ADD_ON_REIMBURSEMENT_LANGUAGE' to 'N/A' and 'ADD_ON_REIMBURSEMENT_IND' to 'N' for the entire group. - - Args: - group (pd.DataFrame): The input DataFrame group to be checked and updated. - - Returns: - pd.DataFrame: The updated DataFrame group with modified 'ADD_ON_REIMBURSEMENT_LANGUAGE' and 'ADD_ON_REIMBURSEMENT_IND' values. - """ - if ( - group["FULL_SERVICE"] - .str.contains("Add-on|Add On", regex=True, case=False, na=False) - .any() - ): - group["ADD_ON_REIMBURSEMENT_LANGUAGE"] = "N/A" - group["ADD_ON_REIMBURSEMENT_IND"] = "N" - return group - -def filter_add_ons(df: pd.DataFrame) -> pd.DataFrame: - """ - Filters and processes add-on reimbursement language in the given DataFrame. - - This function performs the following operations: - 1. Identifies rows where the 'ADD_ON_REIMBURSEMENT_LANGUAGE' column matches a specific pattern and sets the value to 'N/A' and 'ADD_ON_REIMBURSEMENT_IND' to 'N'. - 2. Groups the DataFrame by 'Contract Name' and 'EXHIBIT' or 'Filename' and 'EXHIBIT', and checks if any 'FULL_SERVICE' contains 'Add-on' or 'Add On'. If found, sets the 'ADD_ON_REIMBURSEMENT_LANGUAGE' to 'N/A' and 'ADD_ON_REIMBURSEMENT_IND' to 'N' for the whole group. - - Args: - df (pd.DataFrame): The input DataFrame containing the columns 'ADD_ON_REIMBURSEMENT_LANGUAGE', 'ADD_ON_REIMBURSEMENT_IND', 'FULL_SERVICE', and either 'Contract Name' or 'Filename', and 'EXHIBIT'. - - Returns: - pd.DataFrame: The processed DataFrame with updated 'ADD_ON_REIMBURSEMENT_LANGUAGE' and 'ADD_ON_REIMBURSEMENT_IND' values. - """ - pattern = r"(in no event).+(includ.?)|(forward).+(payments)" - - mask = df["ADD_ON_REIMBURSEMENT_LANGUAGE"].str.contains( - pattern, case=False, na=False, regex=True - ) - df.loc[mask, "ADD_ON_REIMBURSEMENT_LANGUAGE"] = "N/A" - df.loc[mask, "ADD_ON_REIMBURSEMENT_IND"] = "N" - - - # Group by 'Contract Name' and 'EXHIBIT', then apply the check_add_on function - if "Contract Name" in df.columns: - df = df.groupby(["Contract Name", "EXHIBIT"]).apply(check_add_on) - elif "Filename" in df.columns: - df = df.groupby(["Filename", "EXHIBIT"]).apply(check_add_on) - - # TODO : If original add on value is actually an Exclusion, and the Exclusion value for the row is invalid, then move the Add On value to the Exclusions column - return df - -def count_dollar_values(s: str) -> int: - """ - Count the number of dollar signs ('$') and percentage signs ('%') in a given string. - - Args: - s (str): The input string to be analyzed. - - Returns: - int: The total count of dollar signs and percentage signs in the input string. - """ - return str(s).count("$") + str(s).count("%") - -def add_scmr(df: pd.DataFrame) -> pd.DataFrame: - """ - Adds columns to the DataFrame to indicate if a row has multiple rates for a single code. - This function checks for the presence of specific columns ("FLAT_FEE_STANDARD", "FLAT_FEE", "FLAT FEE") - in the DataFrame and uses the first one it finds. It then counts the number of dollar values in the - selected column and adds two new columns to the DataFrame: - - "Single Code Multiple Rates (Language)": Contains the value from the selected column if there are - multiple dollar values, otherwise "N/A". - - "Single Code Multiple Rates (Y/N)": Contains "Y" if there are multiple dollar values, otherwise "N". - Parameters: - df (pd.DataFrame): The input DataFrame containing the fee columns. - Returns: - pd.DataFrame: The modified DataFrame with the new columns added. - """ - - if "FLAT_FEE_STANDARD" in df.columns: - col = "FLAT_FEE_STANDARD" - elif "FLAT_FEE" in df.columns: - col = "FLAT_FEE" - elif "FLAT FEE" in df.columns: - col = "FLAT FEE" - - df["dollar_count"] = df[col].apply(count_dollar_values) - - df["Single Code Multiple Rates (Language)"] = df.apply( - lambda row: row[col] if row["dollar_count"] > 1 else "N/A", axis=1 - ) - - df["Single Code Multiple Rates (Y/N)"] = df["dollar_count"].apply( - lambda x: "Y" if x > 1 else "N" - ) - df.drop("dollar_count", axis=1, inplace=True) - - return df - - -def clean_lob(df, filename): - def update_contract_lob(row): - try: - # Check if more than one valid lob is present in the 'FULL_SERVICE' column - if ( - sum( - val.lower() in row["FULL_SERVICE"].lower() - for val in valid.VALID_LOBS - ) - > 1 - ): - prompt = postprocessing_prompts.LOB_SWEEPER(row["EXHIBIT"]) - answer = llm_utils.invoke_claude( - prompt, "sonnet_latest", filename, 256 - ) - return answer # Return the value from 'EXHIBIT' if condition is met - return row["CONTRACT_LOB"] - except: - return row["CONTRACT_LOB"] - - df["CONTRACT_LOB"] = df.apply(update_contract_lob, axis=1) - return df - - -def derive_indicators(results): - for field in DERIVED_INDICATOR_FIELDS: - indicator_field = field + "_IND" - if field in results and results[field] and not is_empty(results[field]): - results[indicator_field] = "Y" - else: - results[indicator_field] = "N" - return results - - -def icm_number_fix(df: pd.DataFrame) -> pd.DataFrame: - - def clean_icm(payer_name, parent_agreement_code): - - pattern = r"\d{4,6}" - - if ( - ( - re.search(pattern, payer_name.split("ICMProviderAgreementAmendment")[0]) - is not None - ) - and (is_empty(parent_agreement_code)) - and ("ICMProviderAgreementAmendment" in payer_name) - ): - - parent_agreement_code = re.search( - pattern, payer_name.split("ICMProviderAgreementAmendment")[0] - ).group(0) - - return parent_agreement_code - - else: - - return parent_agreement_code - - df["Parent Agreement Code"] = df.apply( - lambda row: clean_icm(row["PAYER_NAME"], row["Parent Agreement Code"]), axis=1 - ) - - return df - - -def methodology_short_fix(df: pd.DataFrame) -> pd.DataFrame: - - df["RATE_SHORT"] = df["RATE_SHORT"].apply( - lambda x: re.sub(r"^\$?\d*\.?\d*", "", x) if not is_empty(x) else x - ) - - return df - -def yn_fixes(df: pd.DataFrame) -> pd.DataFrame: - """ - Fixes columns with (Y/N) values in the DataFrame by converting 'Yes' to 'Y' and 'No' to 'N'. - Also ensures that any other values are set to 'N'. Additionally, clears language columns - associated with 'Y/N' columns if the value is not 'Y'. - - Args: - df (pd.DataFrame): The input DataFrame containing columns with (Y/N) values. - - Returns: - pd.DataFrame: The modified DataFrame with fixed (Y/N) values and cleared language columns. - """ - for col in df.columns: - if "(Y/N)" in col: - df.loc[df[col] == "Yes", col] = "Y" - df.loc[df[col] == "No", col] = "N" - df.loc[(df[col] != "Y") & (df[col] != "N"), col] = "N" - - for yn, lang in valid.AC_IND_LANG_COLS.items(): - if yn in df.columns and lang in df.columns: - df.loc[df[yn] != "Y", lang] = "" - - for yn, lang in valid.B_IND_LANG_COLS.items(): - if yn in df.columns and lang in df.columns: - df.loc[df[yn] != "Y", lang] = "" - - return df - - -def add_hyphen_if_needed(input_str): # postprocessing_funcs.py - """Checks for specific patterns in the input string and modifies it by adding - a '0' at the start and a hyphen at the 3rd index, or just adds a hyphen at - the 3rd index if it's a 9-digit number. - - If the input string is exactly 8 characters long, it adds a '0' at the start - and inserts a hyphen at the 3rd position. If the string matches the pattern - of a 9-digit number (exactly 9 digits), it inserts a hyphen at the 3rd index. - If the input string matches the pattern 'x-xxxxxxx', it adds '0' at the start. - - All conditions will return a string in the format 'xx-xxxxxxx'. - - Args: - - input_str (str): The input string (IRS). - - Returns: - - str: The modified string in the format 'xx-xxxxxxx'. - - Example: - - '1-2345678' → '01-2345678' - - '12345678' → '01-2345678' - - '123456789' → '12-3456789' - """ - # Check if the string matches the pattern 'x-xxxxxxx' (1 character, hyphen, 7 characters) - if re.fullmatch(r"\d-\d{7}", input_str): - # Add '0' at the start - input_str = "0" + input_str - - # Check if the string is exactly 'xxxxxxxx' (8 characters) - elif bool(re.fullmatch(r"\b\d{8}\b", input_str)): - # Add '0' at the start and insert a hyphen at the 3rd index - input_str = "0" + input_str - input_str = input_str[:2] + "-" + input_str[2:] - - # If the string matches the pattern of a 9-digit number, add a hyphen at the 3rd index - elif bool(re.fullmatch(r"\b\d{9}\b", input_str)): - input_str = input_str[:2] + "-" + input_str[2:] - - # Ensure the output is in the format 'xx-xxxxxxx' - if not bool(re.fullmatch(r"\b\d{2}-\d{7}\b", input_str)): - input_str = None - - return input_str - - -class InvalidDateException(Exception): - pass - - -def convert_to_us_date_format(date_str: str) -> str: # postprocessing_funcs.py - """ - Input date string is expected to be in the format YYYY-MM-DD - - Ensure the date is in MM/DD/YYYY format. If the month is greater than 12, swap the month and day. - If the input is `N/A` or some variant (defined by is_empty), InvalidDateException will be raised. - Args: - date_str (str): The date string to be formatted. We expect it to come in as YYYY-MM-DD format (note the hyphens as well) - - Returns: - str: The date string formatted as MM/DD/YYYY. - - Raises: - InvalidDateException: If the date string is not in the expected format. - """ - - if not isinstance(date_str, str): - raise TypeError(f"Date must be a string, got: {type(date_str)}") - - if is_empty(date_str) or re.fullmatch(r"\d{4}-\d{1,2}-\d{1,2}", date_str) is None: - raise InvalidDateException(f"Invalid date format: {date_str}") - - # Split the date string into year, month, and day - year, month, day = date_str.split("-") - - year_int = int(year) - month_int = int(month) - day_int = int(day) - - if year_int == 0 or month_int == 0 or day_int == 0: - raise InvalidDateException(f"Invalid date format: {date_str}") - - if month_int > 12 and 0 < day_int <= 31: - # Swap the month and day if the month is greater than 12 - month_int, day_int = day_int, month_int - - if 0 < month_int <= 12 and 0 < day_int <= 31: - # Return the formatted date in MM/DD/YYYY format - year, month, day = ( - str(year_int).zfill(4), - str(month_int).zfill(2), - str(day_int).zfill(2), - ) - return "/".join([month, day, year]) - - else: - # Raise an exception if the month or day is out of range - raise InvalidDateException(f"Invalid date format: {date_str}") - -def remove_unnamed_columns(df): # postprocessing_funcs.py - return df[[col for col in df.columns if "Unnamed" not in col]] - - -def reorder_columns(df: pd.DataFrame, column_order: list[str]) -> pd.DataFrame: - """ - Reorders the columns of the DataFrame based on the given column order. - Steps: - 1. Adds any missing columns from `column_order` to the DataFrame, filled with empty strings. - 2. Reorders the columns of the DataFrame to match `column_order`. - 3. Appends any columns in the DataFrame that are not in `column_order` to the end. - Args: - df (pd.DataFrame): The input DataFrame. - column_order (list[str]): The desired column order. - Returns: - pd.DataFrame: The reordered DataFrame. - """ - # Add missing columns from column_order to the DataFrame, filled with empty strings - for col in column_order: - if col not in df.columns: - df[col] = "" - # Reorder columns to match column_order - ordered_columns = [col for col in column_order if col in df.columns] - # Add columns in df that are not in column_order to the end - remaining_columns = [col for col in df.columns if col not in column_order] - # Combine the ordered columns and remaining columns - final_column_order = ordered_columns + remaining_columns - # Return the DataFrame with reordered columns - return df[final_column_order] - - -@cache # memoize repeated calls to this function -def date_postprocessing(date: str) -> str: - """Processes dates using `investment_prompts.date_fix_prompt`. Following the call to the LLM, the answer is extracted from the response and returned. - - Args: - date (str): Input date string to be processed. - - Returns: - str: Processed date; either a date in YYYY/MM/DD format, a duration (e.g. "3 years"), or "N/A". - """ - if is_empty(date): - return "N/A" - - prompt = investment_prompts.DATE_FIX_PROMPT(date) - response = llm_utils.invoke_claude(prompt, "sonnet_latest", "date_fix") - return extract_text_from_delimiters(response, Delimiter.PIPE) - -def generate_reimb_ids(df: pd.DataFrame) -> pd.DataFrame: - """Generates REIMB_ID and REIMB_LESSER_OF_ID for each row in the DataFrame based on - the FILE_NAME and other fields. - The REIMB_ID is constructed using the following format: - {FILE_NAME}_exh_pg_{EXHIBIT_PAGE}_{index}_{hash_value} - - the REIMB_LESSER_OF_ID is just the {hash_value} itself. - - Where: - - FILE_NAME is the name of the file (without extension). - - EXHIBIT_PAGE is the page number of the exhibit. - - index is the index of the row in the group. - - hash_value is the first 8 characters of the MD5 hash of a concatenated string of relevant fields. - The relevant fields are SERVICE_TERM, REIMB_TERM, PROGRAM, PRODUCT, NETWORK, and LOB. - - Args: - df (pd.DataFrame): Input DataFrame containing the columns FILE_NAME, EXHIBIT_PAGE, SERVICE_TERM, REIMB_TERM, PROGRAM, PRODUCT, NETWORK, and LOB. - The FILE_NAME column should contain the name of the file (with or without extension). - The EXHIBIT_PAGE column should contain the page number of the exhibit. - - Returns: - pd.DataFrame: DataFrame with generated REIMB_IDs and REIMB_LESSER_OF_IDs for each row. - """ - # First sort to ensure consistent ordering - df = df.sort_values(by=['FILE_NAME', 'EXHIBIT_PAGE']) if "EXHIBIT_PAGE" in df.columns else df.sort_values(by=['FILE_NAME']) - - # Create a temporary copy to avoid SettingWithCopyWarning - df_temp = df.copy() - - # Process each file+exhibit_page group to reset counter for each exhibit page - group_cols = ['FILE_NAME', 'EXHIBIT_PAGE'] if "EXHIBIT_PAGE" in df.columns else ['FILE_NAME'] - for group_key, indices in df.groupby(group_cols).groups.items(): - if isinstance(group_key, tuple): - filename, exhibit_page = group_key - exhibit_page = str(exhibit_page).zfill(3) - else: - filename = group_key - exhibit_page = '000' - clean_filename = str(filename).split("/")[-1].replace(".", "_") - - for i, idx in enumerate(indices): - row = df.loc[idx] - - # Get fields to include in hash - service_term = str(row.get('SERVICE_TERM', '')) - reimb_term = str(row.get('REIMB_TERM', '')) - program = str(row.get('PROGRAM', '')) - product = str(row.get('PRODUCT', '')) - network = str(row.get('NETWORK', '')) - lob = str(row.get('LOB', '')) - - # Create hash string - hash_string = f"{clean_filename}_{service_term}_{reimb_term}_{program}_{product}_{network}_{lob}" - hash_value = hashlib.md5(hash_string.encode()).hexdigest()[:8] - - # Generate REIMB_ID and REIMB_LESSER_OF_ID - df_temp.at[idx, 'REIMB_ID'] = f"{clean_filename}_exh_pg_{exhibit_page}_{i+1:03d}_{hash_value}" - df_temp.at[idx, 'REIMB_LESSER_OF_ID'] = hash_value - - return df_temp \ No newline at end of file diff --git a/fieldExtraction/src/prompts/client_prompts.py b/fieldExtraction/src/prompts/client_prompts.py deleted file mode 100644 index 923ca3b..0000000 --- a/fieldExtraction/src/prompts/client_prompts.py +++ /dev/null @@ -1,516 +0,0 @@ -import src.constants.valid as valid - -##################################################################################### -##################################### BOTTOM UP ##################################### -##################################################################################### - -def BOTTOM_UP_PRIMARY(page, payer): - return f""" -### PAGE START ### {page} ### PAGE END - -The preceding text is one page of a contract between a Payer {payer} and a provider in their network. Your job is to extract attributes related to the reimbursement of different services and specialties. Ensure every compensation term (with a % or $ value) is captured. - -Some values are found in sections explicitly identified as examples - these must be omitted from output. Other values might be related to liability - these must also be excluded. - -Return at least one json object for each combination of attributes seen. It is possible that a page will not have any attributes. When this is the case, return an empty list. - -Here are the attributes to be included in each dictionary, and instructions on how to correctly answer: -'FULL_SERVICE' : What is the Service that is being reimbursed? Include the full Service, including any distinguishing context. Include any codes that are part of the service, including revenue codes and procedure codes. Use only exact text from the document. Do not leave this field N/A. -'SUBHEADER' : What subheader or subsection name does the FULL_SERVICE belong to? This may be include crucial additional detail about the Service. Note that this is not a primary header like that of a compensation schedule or exhibit. If there is no subheader, write 'N/A'. -'FULL_METHODOLOGY' : Write the full sentence or paragraph in the text describing the reimbursement methodology. -'PROV_TYPE' : Write the Provider Type of the service. Choose ONLY from the following: {valid.VALID_PROV_TYPES}. Note that Facility may also be referred to as Hospital, Clinic, Institutional or similar. Professional may also be referred to as Physician, Physician Services, Practicioner, Provider or similar. Make sure to break out/differentiate between Professional, Facility, and Ancillary. Do not mix the two. Do NOT write 'N/A' for this field. - -Ensure any relevant detail is included, including rates in tables if applicable. Include any 'Lesser of' statement that applies to the reimbursement. The 'Lesser of' statement might not be found in immediate proximity to the reimbursement term and may instead be found in a paragraph above. If the methodology is presented in a table, concatenate any relevant lesser of statement that applies to the table with the portion of the methodology found in the table. - -Here are some examples of language with additional context to be included in the FULL_SERVICE: -Text: 'Covered Services that are Medicare Covered Services and are not Medicaid Covered Services', -Text: 'Covered Services rendered to Covered Person who are eligible for Medicare and enrolled in a MMP Plan that may include coverage for both Medicare and Medicaid Covered Services' - -Here are some examples of SUBHEADER values: -Text: 'Where Payor is the Payor for both Medicare Covered Services and Medicaid Covered Services:' -Text: 'Outpatient Services' - -Here is an example of text with a Lesser of statement and a table that should be included as a FULL_METHODOLOGY answer: -Text: 'Professional Covered Services is the lesser of: (i) Allowable Charges; or (ii) the "Contracted Rate" percentage found in Table 1. Table 1 - OB and Anesthesia Services : 100% of the Payor's Oregon Health Plan DMAP fee schedule. Radiology Services : 110% of Medicare Fee Schedule' -Answer: "[{{'FULL_SERVICE' : 'OB and Anesthesia Services', 'SUBHEADER' : 'N/A', 'FULL_METHODOLOGY' : 'lesser of (i) Allowable Charges; or (ii) 100% of the Payor's Oregon Health Plan DMAP fee schedule', 'PROV_TYPE' :'Professional'}}, -{{'FULL_SERVICE' : 'Radiology Services', 'SUBHEADER' : 'N/A', 'FULL_METHODOLOGY' : 'lesser of (i) Allowable Charges; or (ii) 110% of Medicare Fee Schedule', 'PROV_TYPE' :'Professional'}}]" - -If there is no clear FULL_SERVICE, then fill in 'Covered Services' for the FULL_SERVICE value. - -Ensure that you do not forget to extract reimbursement items. MAKE SURE THAT YOU DON'T MISS ANY REIMBURSEMENT ITEMS AND RETURN ALL NECESSARY AND RELEVANT JSON OBJECTS. -Only return the list, with no other commentary or explanation. Ensure you abide by proper JSON formatting. -""" - - -def BOTTOM_UP_METHODOLOGY_BREAKOUT(d): - return f"""Analyze the full methodology text listed below: -{d['FULL_METHODOLOGY']} - -Populate a dictionary with the following values: - -LESSER : If the methodology contains the words 'lesser of' or similar, write 'Y'. Otherwise, write 'N'. -RATE_STANDARD : If there is only one rate in the methodology, write that rate here. Use the format 'X% of Y'. Do not include Flat Fees here. If there is no rate in the methodology, write 'N/A'. This value MUST be different than the LESSER_RATE and NOT_TO_EXCEED values. -RATE_SHORT : Return only the percentage from RATE_STANDARD in decimal form, with no other text. For example, 100% -> 1. -FLAT_FEE_STANDARD : If any part of the methodology is a flat fee (e.g. $X), write that value here. Do not include percentages here. If there is a 'Per' statement like 'Per Diem', 'Per Unit', etc, inclue that here as well. If there is no dollar value in the methodology, leave this field N/A. This value MUST be different than the LESSER_RATE and NOT_TO_EXCEED values. -LESSER_RATE : If the methodology contains the words 'lesser of' or similar, what is the rate listed that is NOT the RATE_STANDARD or the FLAT_FEE_STANDARD? Answer in the format 'X% of Y', where X is a numeric value. If there is no 'lesser of' in the text, write 'N/A'. -NOT_TO_EXCEED : Only if the methodology contains the specific phrase "Not to exceed", write the amount that the payment is not to exceed using the template "X% of Y" where X is a numeric value, if the value is a %. If the value is a dollar value, use the format "$X", where X is the numeric value. If "Not to exceed" is not found, write 'N/A'. - -For the format 'X% of Y', if Y is in the below list, then apply the indicated abbreviation. If Y is not in the list, then write the full Y value: -Medicare --> MCR -Medicare Advantage --> MCR -Medicaid --> MCD -Medicare Advantage --> MA -Allowable Charges --> AC -Allowed Amount --> AA -Billed Charges --> BC -Average Sales Price --> ASP -Average Wholesale Price --> AWP -DMAP --> DMAP -Amount Payable by MCR --> MCR -Amount Payable by MCD --> MCD - -Here are some examples of methodology text, and the correct response: -Example 1 - -Methodology: "The maximum compensation for Covered Services rendered to a Covered Person shall be the "Allowed Amount." The Allowed Amount for Covered Services is the lesser of: (i) Allowable Charges; or (ii) as applicable, (a) 100% of the Payor's Medicaid fee schedule in effect on the date of service, or (b) for those Covered Services included in the Service Categories in Table 1 below, the applicable "Contracted Rate" set forth in Table 1 below. Example Service | 80% of the Medicare Rate in effect on the date of service." -Correct Response: -{{'LESSER' : 'Y', 'RATE_STANDARD' : '80% of MCR', 'RATE_SHORT' : '0.8', 'FLAT_FEE_STANDARD' : 'N/A', 'LESSER_RATE' : '100% of AC', 'NOT_TO_EXCEED' : 'N/A'.}} - -Example 2 - -Methodology: "The maximum compensation for Covered Services rendered to a Covered Person shall be the "Allowed Amount." The Allowed Amount for Covered Services is the lesser of: (i) Allowable Charges; (ii) 100% of the Payor's HEDIS target value in effect on the date of service, or (iii) for those Covered Services included in the Service Categories in Table 1 below, the applicable "Contracted Rate" set forth in Table 1 below. Example Service | $900.00 Per Unit" -Correct Response: {{'LESSER' : 'Y', 'RATE_STANDARD' : '100% of Payor's HEDIS target value', 'RATE_SHORT' : '1', 'FLAT_FEE_STANDARD' : '$900.00 Per Unit', 'LESSER_RATE' : '100% of AC', 'NOT_TO_EXCEED' : 'N/A'.}} - -Example 3 - -Methodology: "The maximum compensation for Covered Services rendered to a Covered Person shall be the "Allowed Amount." The Allowed Amount for Covered Services is the lesser of: (i) Allowable Charges or (ii) Medicaid Allowed Amount" -Correct Response: {{'LESSER' : 'Y', 'RATE_STANDARD' : '100% of MCD', 'RATE_SHORT' : '1', 'FLAT_FEE_STANDARD' : 'N/A', 'LESSER_RATE' : '100% of AC', 'NOT_TO_EXCEED' : 'N/A'.}} - -Example 4 - -Methodology: "The maximum compensation for Covered Services rendered to a Covered Person shall be the "Allowed Amount." The Allowed Amount for Covered Services is the lesser of: (i) Provider's Billed Charges or (ii) The amount payable by Medicare" -Correct Response: {{'LESSER' : 'Y', 'RATE_STANDARD' : '100% of MCR', 'RATE_SHORT' : '1', 'FLAT_FEE_STANDARD' : 'N/A', 'LESSER_RATE' : '100% of BC', 'NOT_TO_EXCEED' : 'N/A'.}} - -Example 5 - -Methodology: "Multiple procedures performed during the same day will be reimbursed at 100% for the primary procedure, 50% for the second procedure, and 50% for the third procedure, subsequent procedures shall not be eligible for reimbursement." -Correct Response: {{'LESSER' : 'N', 'RATE_STANDARD' : '100% of AC / 50% of AC / 50% of AC / 0% of AC', 'RATE_SHORT' : '1 / 0.5 / 0.5 / 0', 'FLAT_FEE_STANDARD' : 'N/A', 'LESSER_RATE' : 'N/A', 'NOT_TO_EXCEED' : 'N/A'.}} - -Example 6 - -Methodology: "Provider shall be entitled to the lesser of: (1) Anciliary Provider's billed charges; or (2) the amount payable by Medicare, not including Medicare coinsurance and deductibles, plus the amount payable by Medicaid as a secondary coverage based on the Medicaid fee schedule in effect on the date of service." - -Correct Response: {{'LESSER' : 'Y', 'RATE_STANDARD' : '100% of MCR', 'RATE_SHORT' : '1', 'FLAT_FEE_STANDARD' : 'N/A', 'LESSER_RATE' : '100% of BC', 'NOT_TO_EXCEED' : 'N/A'.}} - -Example 7 - -Methodology: "The Compensation Schedule for the Medicare Product at any given time is the lesser of (i) the Allowable Charges for the particular Covered Service, or (ii) the appropriate amount for such Covered Service under the Company's fee schedule in effect on the date of service for the Medicare Product." -Correct Response: {{'LESSER' : 'Y', 'RATE_STANDARD' : '100% of MCR', 'RATE_SHORT' : '1', 'FLAT_FEE_STANDARD' : 'N/A', 'LESSER_RATE' : '100% of AC', 'NOT_TO_EXCEED' : 'N/A'.}} - -Only return the dictionary, with no other commentary or explanation. Ensure you abide by proper JSON formatting. -""" - - -def BOTTOM_UP_ESCALATOR(d, page): - return f"""### PAGE START ### {page} ### PAGE END - -The above text contains a number of reimbursement terms. For the rest of this request, focus only on the specific term listed below: -Service: {d['FULL_SERVICE']} -Methodology: {d['FULL_METHODOLOGY']} - -Identify if the above reimbursement term is subject to any escalators, or increases in rate over time. Also identify if there are any exclusions. Fill out the following fields: -'RATE_ESCALATOR_IND' : If there is language on the page stating that the given rate will increase over time, return 'Y'. Otherwise return 'N'. -'RATE_ESCALATOR_DT' : If there is a rate escalator, on what date or time period does it kick in? Write the date or time period only. If 'N', return 'N/A' for this field. - -For the escalator fields, ensure that only updates and increases to the RATE itself (the percentage) are counted. Updates to Codes and other features do not apply here. - -Only return the dictionary, with no other commentary or explanation. Ensure you abide by proper JSON formatting. -""" - - -##################################################################################### -##################################### TOP DOWN ###################################### -##################################################################################### - - -def TOP_DOWN_PRIMARY(page): - return f"""### PAGE START ### {page} ### PAGE END - -The preceding text is one page of a contract between a Payer and a provider in their network. Extract the names of certain entities listed in the contract and return a single dictionary with the values below: - -Here are the attributes to be included in each dictionary, and instructions on how to correctly answer: -'EXHIBIT': CRITICAL - Capture the complete hierarchy of document identifiers found on the page: -- MUST include ALL of the following when present: - * Full Attachment names and numbers (e.g., "Attachment C: Commercial-Exchange - EPO") - * Complete Exhibit numbers/letters with full titles - * All relevant subtitles and section names - * Provider/Entity names when listed with exhibit information -- Format: List each component on a new line in the exact order found on page -- Example format for multi-level exhibits: - "Attachment C: Commercial-Exchange - EPO - EXHIBIT 1 - COMPENSATION SCHEDULE - PHYSICIAN SERVICES - LAILA HIRJEE MD PA" -- Rules for inclusion: - * Do NOT abbreviate or summarize any part of the exhibit names - * Do NOT include page numbers - * Do NOT cherry-pick only certain parts - capture the complete exhibit hierarchy - * Include ALL text that appears to be part of the exhibit/attachment header - * Keep exact capitalization and formatting as shown in document -- Write 'N/A' only if NO exhibit, attachment, or amendment identifiers are found - -Example 1: -Document text shows: -"Attachment C: Commercial-Exchange - EPO -EXHIBIT 1 -COMPENSATION SCHEDULE -PHYSICIAN SERVICES -LAILA HIRJEE MD PA" -Correct output: "Attachment C: Commercial-Exchange - EPO -EXHIBIT 1 -COMPENSATION SCHEDULE -PHYSICIAN SERVICES -LAILA HIRJEE MD PA" - -Example 2: -Document text shows: -"ATTACHMENT A - PPO -EXHIBIT 3 -MEDICARE ADVANTAGE" -Correct output: "ATTACHMENT A - PPO -EXHIBIT 3 -MEDICARE ADVANTAGE" - -Final check before returning result: -1. Did you capture the COMPLETE attachment/exhibit name including all descriptive text? -2. Did you include ALL levels of the exhibit hierarchy? -3. Did you maintain the exact formatting and line breaks as shown in the document? -4. Did you include any provider/entity names that were part of the exhibit header? - -'CONTRACT_LOB' : List the Line of Business mentioned on the page. Choose ONLY from the following: {valid.VALID_LOBS}. If there are multiple on the page, write them in a comma-separated list. If no LOB is found, write 'N/A'. -'CONTRACT_PROGRAM' : List any Programs or Plans mentioned on the page. {valid.VALID_PROGRAMS}. If no program is found, write 'N/A'. - -'DEFAULT_TERM' : If there is a section labelled 'Default' or similar, write that entire section here. It may also be a term that describes the reimbursement instructions if there is no established payment amount. This language often starts with a phrase like "if there is no published fee schedule..." If there is no such term, write 'N/A'. -'DEFAULT_RATE' : Write the rate from the Default Term in the form 'X% of Y'. It may be that the rate is not expressed in the form of a percentage; in this case, write '100% of Y,' Y being the payment reference. For example, if the default term specifies 'Any service not mentioned above shall be paid according to the plan's fee schedule,' write '100% of Plan's Fee Schedule.' If there is no Default Term, or no rate in the Default Term, write 'N/A'. -'CDM_IND' : Is there language on the page regarding CDM Neutralization? Write 'Y' if yes, 'N' if no. -'CHARGEMASTER' : Is there language on the page related to Chargemaster Protections? If so, return the sentence or sentences. If not, return 'N/A'. -'ADD_ON_REIMBURSEMENT_LANGUAGE' : Extract the Add On Reimbursement language present in the contract. If it is not present, answer N/A. - -If there are multiple answers for a given attribute, return the answers in a comma-separated list. - -For all attributes, only write an answer if it is written on the page. Do not make up new phrases or words or try to deduce answers that are not seen. Simply write 'N/A' if none of the listed options are seen. - -Only return the dictionary, with no other commentary or explanation. Ensure you abide by proper JSON formatting. -""" - - -TD_PRIMARY_FIELDS = [ - "EXHIBIT", - "CONTRACT_LOB", - "CONTRACT_PROGRAM", - "DEFAULT_TERM", - "DEFAULT_RATE", - "CDM_IND", - "CHARGEMASTER", - "ADD_ON_REIMBURSEMENT_LANGUAGE", -] - - -def TOP_DOWN_EXCLUSIONS(page): - return f"""### PAGE START ### {page} ### PAGE END - -The above text contains a page with at least one term related to exclusions or inclusions - -If it is indicated that any type of service or other situation should be EXCLUDED (or NOT INCLUDED), write just the exclusion clause here. - -Include language that specifically has words like 'exclusion', 'excluded', 'not to include'. -If there is no exclusion clause, write 'N/A'. -The exclusion clause may be a single sentence, an entire paragraph, or multiple paragraphs. - -Only write the EXACT text from the page, with no other commentary or explanation. Do not write ANY words in your response that are not found on the page. -""" - - -def TOP_DOWN_MEDICALLY_NECESSARY(page): - return f"""### PAGE START ### {page} ### PAGE END - -The above text contains a page with at least one Medical Necessity term. - -Extract the Medical Necessity paragraph from the page. - -Only write the EXACT text from the page, with no other commentary or explanation. Do not write ANY words in your response that are not found on the page. -""" - - -##################################################################################### -#################################### CONDITIONAL #################################### -##################################################################################### - - -def CONDITIONAL_IP_OP_SMALL(s, m): - return f"""Service: {s} -Methodology: {m} - -Is the term above related to Inpatient or Outpatient services? - -Write only 'IP' for Inpatient, 'OP' for Outpatient, or 'N/A' if the answer is unclear. - -Only write IP or OP if 'Inpatient' or 'Outpatient' is clearly and explicitly written in the Service or Methodology. Otherwise, write 'N/A'. Do not attempt to deduce the answer based on your prior knowledge. - -Return only the answer, without using complete sentences. -""" - - -def CONDITIONAL_IP_OP_BIG(d, page): - return f"""### PAGE START ### {page} ### PAGE END - -The above text contains a number of reimbursement terms. For the rest of this request, focus only on the specific term listed below: -Service: {d['FULL_SERVICE']} -Methodology: {d['FULL_METHODOLOGY']} - -Based on the context given, is the term above related to Inpatient or Outpatient services. - -Write only 'IP' for Inpatient, 'OP' for Outpatient. Only write IP or OP if it is EXPLICITLY written on the page and applies to the Service and Methodology. - -If neither, simply write 'N/A'. - -Return only the answer, without using complete sentences. -""" - - -def CONDITIONAL_PROV_2(d, page): - if "PROFESSIONAL" in d["PROV_TYPE"].upper(): - valid_list = valid.VALID_PROF - elif "ANCILLARY" in d["PROV_TYPE"].upper(): - valid_list = valid.VALID_ANC - elif "FACILITY" in d["PROV_TYPE"].upper(): - valid_list = valid.VALID_FAC - else: - - valid_list = valid.VALID_PROF + valid.VALID_ANC + valid.VALID_FAC - return f"""### PAGE START ### {page} ### PAGE END - -The above text contains a number of reimbursement terms. For the rest of this request, focus only on the specific term listed below: -Service: {d['FULL_SERVICE']} -Methodology: {d['FULL_METHODOLOGY']} -Exhibit Name: {d['EXHIBIT']} - -Based on the context given, what is the Provider Type applicable to the term above? - -Choose ONLY from the following: -{valid_list} -Do not attempt to deduce the Provider Type from context clues. It will be clearly and explicitly written in the text itself. If there is not clear Provider Type from the list, write 'N/A'. - -Return only the answer, without using complete sentences. -""" - - -def CONDITIONAL_DSH_IME_UC(page): - return f"""### PAGE START ### {page} ### PAGE END - -Is there language on the page saying that DSH, GME, IME, or UC is included? Write 'Y' or 'N'. - -Return only the answer, without using complete sentences. -""" - - -def CONDITIONAL_RBRVS(page): - return f"""### PAGE START ### {page} ### PAGE END - -Is RBRVS language found anywhere on the page? Write 'Y' or 'N'. - -Return only the answer 'Y' or 'N', without using complete sentences. -""" - - -def CONDITIONAL_STOP_LOSS(page): - return f"""### PAGE START ### {page} ### PAGE END - -Does the page include a sentence describing stop-loss thresholds? If yes, write the threshold dollar value only. If not, write 'N/A'. - -Return only the answer, without using complete sentences. -""" - - -def CONDITIONAL_LOB_CHECK(d, page): - return f"""### PAGE START ### {page} ### PAGE END - -Above is a page of a contract, with several Services listed. For the rest of this task, focus only on the Service and methodology listed below: -Service: {d['FULL_SERVICE']} -Methodology: {d['FULL_METHODOLOGY']} - -Below are the Line of Business values previously identified as being present on this page. -CONTRACT_LOB : {d['CONTRACT_LOB']} - -First, identify the section of the page that the Service and Methodology belong to. This may be a specific subsection or paragraph. Do not write this answer out, only use it to inform your search for the LOB value. - -Next, identify which single value of CONTRACT_LOB the given Service falls under. It can ONLY be ONE of values in the list. Do NOT return the original list. -The correct answer will likely be found in the same subsection or paragraph that the Service and Methodology are in. It might be listed somewhere close above the Service and Methodology text. - -Again, there is only ONE correct answer in the list. Choose carefully. - -Return only the Line of Business answer, without using complete sentences. -""" - - -##################################################################################### -###################################### AC Fields #################################### -##################################################################################### - - -def AC_SINGLE_FIELD_TEMPLATE(context, question): - return f"""## START CONTRACT TEXT ## -{context} -## END CONTRACT TEXT ## - -Answer the following question, using the above contract text given as context: {question}. - -Do NOT add any additional commentary explaining the answer. Return ONLY the exact text from the contract, or N/A. -""" - - -def AC_MULTI_FIELD_TEMPLATE(context, questions): - return f"""## START CONTRACT TEXT ## -{context} -## END CONTRACT TEXT ## - -Answer the following questions, using the above contract text given as context. -For each question, provide ONLY the answer, with no additional commentary or explanation. Answer in JSON dictionary format, with every key included and no missing answers. -If for any question, no correct answer is found, return 'N/A'. -{questions}. -""" - - -def get_effective_date_prompt(context: str) -> str: - """ - Generate a prompt template to extract the effective date from a contract. - - Args: - context (str): The contract text to analyze. - - Returns: - str: The prompt template for extracting the effective date. - """ - prompt_template = f""" - Please analyze the following contract and extract ONLY the effective date of the contract or amendment. Follow these precise rules in order: - - 1. Look for dates that appear after phrases matching these exact patterns: - - "Effective Date:" followed by a date - - "FOR HEALTH PLAN USE ONLY" section containing "Effective Date:" - - "To be completed by Health Plan only:" section containing "Effective Date:" - - "is entered into as of" followed by a date - - 2. If multiple matches are found: - - Prioritize dates within "FOR HEALTH PLAN USE ONLY" or "To be completed by Health Plan only" sections - - Then prioritize dates that directly follow "Effective Date:" - - 3. Do not use the date found in the "Signature Date" section. - - If there is nothing next to "Effective Date:", return N/A. - - If there is only one date in the document, don't extract this date unless it is explicitly labeled "effective date". It is okay to return N/A. - - 4. If partial dates are found, return N/A. - - 5. Return the date in YYYY-MM-DD format or N/A if date not found. Enclose just your final answer in |pipes|. - - Here is the contract to analyze: - - ## START CONTRACT TEXT ## - - {context} - - ## END CONTRACT TEXT ## - - Ensure that you do not use the date found in the "Signature Date" section. - - Before returning the output, VERIFY the date format is YYYY-MM-DD. If the date is not found, return N/A. - - Finally state "Answer: |YYYY-MM-DD| or |N/A|" - """ - - return prompt_template - - -AC_DICT = { - "CONTRACT_TITLE": "What is the complete title of the contract or the document? It is generally written in CAPITAL LETTERS and is generally found at the beginning of the document before a paragraph containing provider and payer name. It typically contains the words AGREEMENT, AMENDMENT, CONTRACT, ADDENDUM, MEMORANDUM, LETTER, PROVIDER or REQUEST. In case of agreement, typical structure is the provider name, followed by the phrase with agreement, followed potentially by a number signifying a version of the contract. In case of amendment, typical structure is word amendment followed by number, followed by the phrase with agreement. Extract the complete title of the contract or document as accurately as possible. Ensure: All leading characters are included for each word (e.g. 'NETWORK' instead of 'ETWORK'). Spaces between words are preserved in phrases (e.g., 'PROVIDER COLLABORATION' instead of 'PROVIDERCOLLABORATION'). If text appears incomplete or lacks spacing, use logical patterns like capitalization or common formatting rules to infer and correct the output. Replace new lines and line breaks with space.", - "PAYER_NAME": "What is the name of the payer that is a party to the contract as stated in the Preamble?", - "PROV_GROUP_NAME": "What is the name of the Group provider that is a party to the contract? This is generally found near keyword provider. Only return the name of the Group provider along with dba name.", - # "PROV_GROUP_NPI": "What is the group provider's national provider identifier number mentioned in the contract? This may be found on the signature page or on the roster. It consists of 10 digits. Only return these 10 digits.", - # "PROV_GROUP_TIN_SIGNATORY": "What is the Group provider's taxpayer identification number stated near signature in the contract? This is a 9 digit long number typically with a hyphen after the first 2 digits. Return only this 9 digit number with a hyphen after the first 2 digits.", - # "PROV_GROUP_TIN": "What is the Group provider's taxpayer identification number stated in the contract? This is a 9 digit long number typically with a hyphen after the first 2 digits in it. Return only this 9 digit number with a hyphen after the first 2 digits. As an example, it has the format of XX-XXXXXXX Do not elaborate or add context. Do not pull any dates, do not pull any numbers that deviate from earlier described pattern. It must always be 9 digits. Strictly follow this instructions. Do not return any other information.", - # "PROV_NPI_OTHER": 'What are all the other provider national provider identifier numbers (NPI) associated with this agreement? They consist of 10 digits and do not contain "-". Provide the list of all the other provider identifier numbers including those that are present in the table or attachment.', - # "PROV_TIN_OTHER": "What are the provider taxpayer identification numbers (TIN) for each of the other providers associated with this agreement? They consist of 9 digits and may contain a hyphen after the first 2 digits. This is usually listed in the preample of the contract or on a roster. Provide only the 9 digit TINs.", - "TERMINATION_UPON_NOTICE": 'Extract the number of days can the contract be terminated by either party by giving notice. The answer may be found near the words "written notice of such termination" or "prior to the expiration". Only extract a value if one is present. Only return the number of days and nothing else.', - "TERMINATION_UPON_CAUSE": 'After how many days can the contract be terminated by either party upon breach of material term? The answer is usually found in section 7.2.2 or 10.2 but might be present in other sections as well. The answer is present after the Term language section. The answer may be found near the words "is in breach of any material term or condition". Here is an example of possible input text and the correct response. EXAMPLE INPUT TEXT: "By either party upon ninety (90) days prior written notice if the other party is in material breach of this Agreement, except that such termination shall not take place if the breach is cured within sixty (60) days following the written notice". EXAMPLE RESPONSE: 90.', - "TIME_TO_OBJECT": "How many days does the provider have to object to the amendment? The answer may be present in the Amendment section.", - "ASSIGNMENTS_CLAUSE_IND": 'Is assignment clause present in contract language? The answer is usually found in section 8.3 but might not always be present there. It can be found in the section with title containing "Assignment". Answer ONLY with Y or N. Do NOT write N/A.', - "NOTICE_PROVIDER_NAME": 'What is the notice provider name present usually near keywords like "Attn:". Return the full name, do not return any context. If it only says President/CEO and not the name itself then return N/A. Ensure that you do not pull the Payer\'s (EG. Superior Health Plan) info instead. Only pull the provider name.', - "NOTICE_PROVIDER_ADDRESS": "What is the notice provider address mentioned after the name of the provider. Only return the address of the provider, do not return any context. New Info --> Ensure Payer address (Eg. Superior Health Plan) isn't pulled. The payer address usually starts with 2100 South... Ensure you do not pull this. Always pull the provider address only. Ensure that you pull the full address with the city and state and zipcode. MAKE SURE YOU ALWAYS PULL THE CITY AND STATE AND ZIPCODE AT THE END. If it is not present in the text file, then do your best to at least return the state.", - "NPI_NAME": "What is the group provider's name whose national provider identifier number is mentioned in the contract? This may be found on the signature page or on the roster.", - "HEALTH_PLAN_STATE": "Which state is this health plan for?", - "SEQUESTRATION_LANGUAGE": "Is sequestration language present in the contract? It may be after reimbursement or compensation section. It is only present in case of medicare and medicare advantage. If Yes, extract the complete paragraph.", - # "SEQUESTRATION_REDUCTIONS_IND":"Are sequestration and reduction percentage present in the contract? Answer Yes if both are present otherwise answer No.", - "CONTRACT_AUTO_RENEWAL_IND": "If this contract automatically renews, answer Y. If this contract will remain in effect unless terminated, answer Y. If it will terminate after the mentioned termination date, answer N.", - "AMEND_CONTRACT_NOTICE_IND": 'Is amendment clause present in contract language? The answer is usually found in section 8.7 but might not always be present there. It may be found in the section with title containing "Amendment". Answer may be found near keywords "Agreement may be amended". Answer ONLY with Y or N. Do NOT write N/A.. Here is an example of possible input text and the correct response. EXAMPLE INPUT TEXT: "may amend this Agreement by giving Provider written notice". EXAMPLE RESPONSE: "Y". If the agreement does not explicitly indicate that it can be amended by giving written notice, the answer will be "N".', - "AFFILIATION_CLAUSE_IND": 'Is affiliation clause present in contract language? Answer may be found near keywords like "affiliate means a person or entity directly". Answer ONLY with Y or N. Do NOT write N/A.', - # "TERM_CLAUSE": """Extract the subsection or paragraph(s) labeled Term. It may also be labeled Termination. In instances there may be 2 bodies of text that you will need to pull. For example, first paragraph may be 'MCO must follow the procedures outlines in Section...' and the second one may be 'Not later than thirty (30) days following receipt of the termination notice...'. In this case, ensure that YOU ALWAYS PULL the whole text of the two paragraphs. If there are no such sections, extract the paragraph that most closely resembles this label. If there is still no correct answer, simply return 'N/A'.""", - "TERM_CLAUSE": """Extract the term or termination sections (or subsections or paragraphs), which are always present in the contract. They contain information related to term and termination of the contract. The biggest hint of a term or termination section will be the heading 'Term and Termination' or 'Term.'. Ensure that YOU ALWAYS PULL the whole text of all relevant paragraphs.""", - # "TIMELY_FILING": "Extract all of the clauses of text and section paragraphs along with their section number that are pertaining to the Timely Filing clause. This clause usually details the conditions and days for when a provider must submit a claim by. Look for section heading titled /'Payment of Clean Claims/'. Look for language such as /'all provider claims shall be processed within 30 days from the date of claim reciept by the MCO/'. We want to extract all following langauage such as MCO shall not pay any claim submitted by provider..., MCO must adjudicate all appealed claims, till the whole section that starts with 'MCO may deny a claim for failure to file'.", - "CREDENTIALING_APP_IND": "Is credentialing defined in section 2.4 or any other subsection of the contract? Answer ONLY with Y or N. Do NOT write N/A.", - "CONTRACT_TERMINATION_DT": "What is the contract termination date? This might be listed within the Terms or Terms of Agreement section of the contract, or can be calculated by adding contract duration to the contract effective date. Return the date in YYYY-MM-DD format.", - "TIME_TO_OBJECT": "How many days does the provider have to object to the amendment? The answer may be present in the Amendment section. Only return the number of days, as an integer.", - "NON_RENEWAL_LANGUAGE": "Extract the section of the contract that describes non-renewal obligations, especially the number of days notice that must be given. Write the full relevant sentence or paragraph. Ensure that the exact term 'non-renewal' is present in the answer. Do NOT return an answer that does not have 'non-renewal'.", - "NON_RENEWAL_DAYS": "How many days of notice does the section indicate must be given to terminate the agreement? Examples are: 30, 60, 90, 180. Only return the number of days.", - "ACCESS_TO_MEDICAL_RECORDS": "Access clause contains information regarding access to medical records. It may be found in section 4.2 of the contract. Extract the Access clause present in the contract.", - # "ADD_ON_REIMBURSEMENT_IND":"Add On Reimbursement is usually present in Compensation section of the contract. Is Add On Reimbursement present in the Compensation section of the contract? Answer with a Yes or No, do not provide any other context.", - # "ADD_ON_REIMBURSEMENT_LANGUAGE":"Add On Reimbursement language is usually present in Compensation section of the contract. Extract the Add On Reimbursement language present in the contract. If it is not present, answer N/A.", - "CARVEOUT_VENDORS": "Carve-Out Vendors clause may be found in section 2.9 of the contract. Extract the Carve-Out Vendors clause present in the contract.", - "CLAIMS_EDITING_LANGUAGE": "Claims Editing Language is typically found in the claims section in the beginning parts of the contract. Extract Claims Editing Language present in the contract.", - # "CLAIMS_EDITING_LANGUAGE_IND":"Claims Editing Language is typically found in the claims section in the beginning parts of the contract. Is Claims Editing Language present in the contract? Answer with a Yes or No, do not provide any other context.", - "CLEAN_CLAIM": 'Extract the "Clean Claim" subsection present in definition section of the contract.', - # "CONFLICTS_BETWEEN_CERTAIN_DOCUMENTS_IND":"Is Conflicts Between Certain Documents clause present in the contract? Answer with a Yes or No, do not provide any other context.", - "CONFLICTS_BETWEEN_CERTAIN_DOCUMENTS_LANGUAGE": "Extract the Conflicts Between Certain Documents clause present in the contract.", - # "COST_SETTLEMENT_IND":"Is cost settlement language present in the contract? Answer with a Yes or No, do not provide any other context.", - "COST_SETTLEMENT_LANGUAGE": "Cost settlement language may be present in disputes and arbitration subsection of the contract. It usually contains keywords like settlementor cost settlement. Extract the cost settlement language present in the contract.", - "DEEMER_AMENDMENT": "Deemer Amendment clause may be found in section 8.7.2 of the contract. Extract the Deemer Amendment clause present in the contract.", - # "DELEGATED_FUNCTION_IND":"Is Delegated Function clause present in the contract? Answer with a Yes or No, do not provide any other context.", - "DELEGATED_TERMS": "Extract Delegated Terms present in the contract.", - "ECM": 'What is the "ECM" number mentioned in the contract? Only Answer the ECM number, do not provide any context. Do not provide ICM number.', - # "EXCLUSIVITY_REQUIREMENT_IND":"Is Exclusivity requirement clause present in the contract? Answer with a Yes or No, do not provide any other context.", - "EXCLUSIVITY_REQUIREMENT_LANGUAGE": "Extract Exclusivity requirement clause present in the contract.", - # "GUARANTEE_OF_PROVIDER_YIELD_IND":"Is Guarantee of Provider Yield Language present in the contract? Answer with a Yes or No, do not provide any other context.", - "GUARANTEE_OF_PROVIDER_YIELD_LANGUAGE": "Extract Guarantee of Provider Yield Language present in the contract.", - "HCBS_SERVICES": 'Extract HCBS services clause present either in the compensation or exhibit section of the contract. It may contain keywords like "for covered HCBS services, plan shall pay". Do not include definitions of HCBS.', - "INDEMNIFICATION": "Indemnification clause may be found in section 5.2 of the contract. Extract the Indemnification clause present in the contract. ", - # "INDEPENDENT_REVIEW_IND":"Is Independent Review language present in the contract? Answer with a Yes or No, do not provide any other context.", - "INDEPENDENT_REVIEW_LANGUAGE": "Extract Independent Review language present in the contract.", - # "INVOICE_PRICING_IND":"Is table with heading \"INVOICED SERVICES\" present in compensation schedule of the contract?", - "INVOICE_PRICING_LANGUAGE": 'Extract table with heading "INVOICED SERVICES" if it is present in compensation schedule of the contract.', - # "LATE_PAID_CLAIMS_IND":"Is Late Paid Claims language present in the contract? Answer with a Yes or No, do not provide any other context.", - "LATE_PAID_CLAIMS_LANGUAGE": "Extract Late Paid Claims language present in the contract. This will be a sentence or paragraph describing late paid claims, interest payments or similar. Here is an example of a correct response: 'Any Clean Claim, as defined in 42 C.F.R. § 422.500, shall be paid within thirty (30) days of receipt by Plan at such address as may be designated by Plan, and Plan shall pay interest on any Clean Claim not paid within thirty (30) days of such receipt by Plan at the rate of interest required by law, or as otherwise set forth in the Provider Manual.'. Here is an example of an incorrect response: 'Any Clean Claim, as defined in 42 C.F.R. 422.500, shall be paid within thirty (30) days of receipt by Health Plan, Payor or (if Provider contracts with Downstream Entities) Provider, as applicable, as designated by Provider or such Downstream Entity, as applicable.'.", - # "MEDICAL_NECESSITY_LANGUAGE":"Extract the \"Medically Necessary\" or \"Medical Necessity\" care (or services) verbiage mentioned in the compensation schedule or section of the contract. Do not mention what \"Medically Necessary\" means. If it is not present in compensation schedule or section, answer N/A.", - # "MEDICAL_NECESSITY_LANGUAGE_IND":"Is \"Medically Necessary\" or \"Medical Necessity\" care (or services) verbiage mentioned in the compensation schedule or section of the contract? Answer with a Yes or No, do not provide any other context.", - "MEMBER_CONFINEMENT_DAYS_LANGUAGE": "Extract Member Confinement Days Language present in the contract.", - # "MEMBER_CONFINEMENT_DAYS_LANGUAGE_IND":"Is Member Confinement Days Language present in the contract? Answer with a Yes or No, do not provide any other context.", - "NATIONAL_AGREEMENT_IND": "Are more than one states covered in the contract? Answer ONLY with Y or N. Do NOT write N/A.", - # "NETWORK_ACCESS_FEES_IND":"Is \"Network Access Fee\" present in the contract? If yes what is the fee mentioned?", - "NETWORK_ACCESS_FEES_LANGUAGE": 'Extract "Network Access Fee" verbiage present in the contract.', - # "NONSTANDARD_APPEALS_PROCESS_IND":"Is Nonstandard Appeals Process present in the contract? Answer with a Yes or No, do not provide any other context.", - "NONSTANDARD_APPEALS_PROCESS_LANGUAGE": "Extract Nonstandard Appeals Process language present in the contract.", - "PAYMENT_IN_ADVANCE_OF_CLAIMS_SUBMISSION_LANGUAGE": "Extract Payment in Advance of Claims Submission language present in the contract.", - # "PAYMENT_IN_ADVANCE_OF_CLAIMS_SUBMISSION_LANGUAGE_IND":"Is Payment in Advance of Claims Submission language present in the contract? Answer with a Yes or No, do not provide any other context.", - "PAYOR": '"Payor" is usually found in section 1.12 of the contract. Extract the "Payor" subsection present in definition section of the contract.', - "PMPM": "What is the PMPM rate mentioned in the contract? Only return the rate. Do not provide any context.", - "PREAUTHORIZATION": "Preauthorization clause may be found in section 2.7 of the contract. Extract the Preauthorization clause present in the contract.", - "PRODUCT_REMOVAL": "Product removal may be found in the products attachment, stating a product is being removed. Extract product removal language present in the contract.", - # "PROVIDER_BASED_BILLING_EXCLUSION_IND":"Provider-based Billing Exclusion is often listed under the ‘Additional Provisions’ section. Is Provider-based Billing Exclusion mentioned the contract? Answer with a Yes or No, do not provide any other context.", - "PROVIDER_BASED_BILLING_EXCLUSION_LANGUAGE": "Provider-based Billing Exclusion is often listed under the ‘Additional Provisions’ section. Extract the Provider-based Billing Exclusion language mentioned in the contract.", - "RECOVERY_RIGHTS": "Recovery Rights clause may be found in section 3.5 of the contract. Extract the entire Recovery Rights sentence or paragraph present in the contract.", - # "SINGLE_CODE_MULTIPLE_RATES_IND":"Is Single Code Multiple Rates present in the compensation or exhibit section of the contract? Answer with a Yes or No, do not provide any other context.", - # "SINGLE_CODE_MULTIPLE_RATES_LANGUAGE":"Extract Single Code Multiple Rates present in the compensation or exhibit section of the contract. If it is not present, answer N/A.", - "TEMPLATE": "Is this contract in standard Centene template containing definition, terms clauses and other standard sections. Answer with 'Y' or 'N'.", - "ARBITRATION_AND_DISPUTES": "Dispute Resolution clause is usually found in section 6.1 of the contract. Arbitration clause is usually found in section 6.2 of the contract. Extract both the Dispute Resolution and the arbitration clause present in the contract. In case one of them is present, extract that one.", - # "DISPARAGEMENT_PROHIBITION_IND":"Is Disparagement Prohibition clause present in the contract? It may be found in section 2.1 of the contract. Answer with a Yes or No, do not provide any other context.", - "DISPARAGEMENT_PROHIBITION_LANGUAGE": "Disparagement Prohibition clause may be found in section 2.1 of the contract. Extract the Disparagement Prohibition clause present in the contract.", - "ELIGIBILITY_VERIFICATION": "Eligibility Verification or Determination clause may be found in section 2.6 of the contract. Extract the Eligibility Verification or Determination clause present in the contract.", - "INSURANCE_REQUIREMENT": "Extract Insurance requirement mentioned in the contract.", - "PARTICIPATION_IN_PRODUCTS": "Participation in Products clause may be found in section 2.2.2 of the contract. Extract the Participation in Products clause present in the contract.", - "POLICIES_AND_PROCEDURES": "Extract the entire Policies and Procedures sentence or paragraph present in the contract. Do not extract language about conflicts and construction. Do not extract language about PP (Preferred Provider) nor PPG (Preferred Provider Group).", - "REGULATORY_REQUIREMENTS": 'Extract the entire Regulatory Requirements paragraph present in the contract. It usually contains the keyword "Regulatory Requirements".', - # "RELATIONSHIP_OF_PARTIES_IND":"Is Relationship of Parties clause present in the contract? Answer with a Yes or No, do not provide any other context.", - "RELATIONSHIP_OF_PARTIES_LANGUAGE": "Extract the entire Relationship of Parties passage present in the contract.", -} - diff --git a/fieldExtraction/src/prompts/investment_prompts.py b/fieldExtraction/src/prompts/investment_prompts.py index 3b6afb0..1905aa0 100644 --- a/fieldExtraction/src/prompts/investment_prompts.py +++ b/fieldExtraction/src/prompts/investment_prompts.py @@ -1,43 +1,45 @@ -import importlib import json from functools import cache import pandas as pd -import src.constants.valid as valid + import src.utils.llm_utils as llm_utils -import src.utils.string_utils as string_utils -from src.constants.investment_values import ( - EXHIBIT_HEADER_MARKERS, - VALID_AARETE_DERIVED_FEE_SCHEDULE, - VALID_AARETE_DERIVED_FEE_SCHEDULE_VERSION, VALID_BILL_TYPE, - VALID_CARVEOUTS, VALID_CLAIM_TYPE, VALID_LOBS, VALID_NETWORKS, - VALID_PLACE_OF_SERVICE, VALID_PROGRAMS, VALID_PRODUCTS, VALID_PROV_SPECIALTY, - VALID_PROV_TYPE, VALID_REIMB_TERM, VALID_SPECIAL, VALID_UNIT_OF_MEASURE) -from src.enums.delimiters import Delimiter +from constants.delimiters import Delimiter from src.utils.string_utils import extract_text_from_delimiters PIPE_FORMAT_INSTRUCTIONS = "Feel free to justify your answer, but enclose your final answer in |pipes|. If the requested information doesn't apply to this context, return |N/A|. If the information should exist but cannot be clearly identified, return |UNKNOWN|." + class Field: def __init__(self, field_dict): # Base Attributes - self.field_name = field_dict['field_name'] - self.relationship = field_dict['relationship'] - self.field_type = field_dict['field_type'] - self.prompt = field_dict['prompt'] if "prompt" in field_dict else None - self.valid_values = field_dict['valid_values'] if 'valid_values' in field_dict else None + self.field_name = field_dict["field_name"] + self.relationship = field_dict["relationship"] + self.field_type = field_dict["field_type"] + self.prompt = field_dict["prompt"] if "prompt" in field_dict else None + self.valid_values = ( + field_dict["valid_values"] if "valid_values" in field_dict else None + ) # AArete-Derived Attributes - self.base_field = field_dict['base_field'] if 'base_field' in field_dict else None - self.crosswalk = field_dict['crosswalk'] if 'crosswalk' in field_dict else None + self.base_field = ( + field_dict["base_field"] if "base_field" in field_dict else None + ) + self.crosswalk = field_dict["crosswalk"] if "crosswalk" in field_dict else None # Smart Chunking Attributes - self.keywords = field_dict['keywords'] if 'keywords' in field_dict else None - self.methodology = field_dict['methodology'] if 'methodology' in field_dict else None - self.case_sensitive = field_dict['case_sensitive'] if 'case_sensitive' in field_dict else False - self.regex_pattern = field_dict['regex_pattern'] if 'regex_pattern' in field_dict else None + self.keywords = field_dict["keywords"] if "keywords" in field_dict else None + self.methodology = ( + field_dict["methodology"] if "methodology" in field_dict else None + ) + self.case_sensitive = ( + field_dict["case_sensitive"] if "case_sensitive" in field_dict else False + ) + self.regex_pattern = ( + field_dict["regex_pattern"] if "regex_pattern" in field_dict else None + ) self.retrieved_text = None - self.format = field_dict['format'] if 'format' in field_dict else None - self.allow_na = field_dict['allow_na'] if 'allow_na' in field_dict else True - + self.format = field_dict["format"] if "format" in field_dict else None + self.allow_na = field_dict["allow_na"] if "allow_na" in field_dict else True + def __repr__(self): """Returns a string representation of the Field object.""" return self.field_name @@ -51,20 +53,20 @@ class Field: :param field_name: The field name to look for in the JSON data. :return: A Field object if found, otherwise raises a ValueError. """ - with open(file_path, 'r') as file: + with open(file_path, "r") as file: data = json.load(file) for field_dict in data: if field_dict.get("field_name") == field_name: return cls(field_dict) - + raise ValueError(f"Field '{field_name}' not found in {file_path}") @classmethod def from_values(cls, **kwargs): """Creates a Field instance from given keyword arguments.""" return cls(kwargs) - + def to_dict(self): """Converts the Field instance to a dictionary.""" return { @@ -78,30 +80,37 @@ class Field: "methodology": self.methodology, "case_sensitive": self.case_sensitive, "regex_pattern": self.regex_pattern, - "format": self.format + "format": self.format, } - def print(self): """Prints the details of a FieldInfo object in a readable format.""" print(f"Field Name: {self.field_name}") print(f"Relationship: {self.relationship}") print(f"Field Type: {self.field_type}") print(f"Prompt: {self.prompt}") - print(f"Valid Values: {'None specified' if not self.valid_values else self.valid_values}") + print( + f"Valid Values: {'None specified' if not self.valid_values else self.valid_values}" + ) print(f"Keywords: {'None specified' if not self.keywords else self.keywords}") - print(f"Methodology: {'None specified' if not self.methodology else self.methodology}") + print( + f"Methodology: {'None specified' if not self.methodology else self.methodology}" + ) print(f"Case Sensitive: {self.case_sensitive}") - print(f"Regex Pattern: {'None specified' if not self.regex_pattern else self.regex_pattern}") - + print( + f"Regex Pattern: {'None specified' if not self.regex_pattern else self.regex_pattern}" + ) + def get_prompt_dict(self): """Returns a dictionary with the field name as the key and its prompt as the value.""" - return {self.field_name : self.get_prompt()} + return {self.field_name: self.get_prompt()} def get_prompt(self): """Resolves placeholders in the prompt using the valid_values property.""" if not hasattr(self, "valid_values"): - raise AttributeError("Field object must have a 'valid_values' property to resolve placeholders.") + raise AttributeError( + "Field object must have a 'valid_values' property to resolve placeholders." + ) resolved_prompt = self.prompt # Replace all placeholders with self.valid_values @@ -112,15 +121,19 @@ class Field: break # Extract the placeholder - placeholder = resolved_prompt[start_idx + 1:end_idx] + placeholder = resolved_prompt[start_idx + 1 : end_idx] # Replace the placeholder with the valid_values if placeholder == "valid_values": resolved_value = self.valid_values try: - resolved_prompt = resolved_prompt.replace(f"{{{placeholder}}}", str(eval(str(resolved_value)))) + resolved_prompt = resolved_prompt.replace( + f"{{{placeholder}}}", str(eval(str(resolved_value))) + ) except: - resolved_prompt = resolved_prompt.replace(f"{{{placeholder}}}", str(resolved_value)) + resolved_prompt = resolved_prompt.replace( + f"{{{placeholder}}}", str(resolved_value) + ) else: pass # raise ValueError(f"unknown placeholder: {placeholder}") @@ -129,17 +142,16 @@ class Field: # Return the updated Field object return resolved_prompt - + def update_valid_values(self, new_valid): self.valid_values = str(new_valid) - class FieldSet: def __init__(self, file_path=None, fields=None, **filters): """ Initialize the FieldSet instance with optional filters, file loading, and a list of fields. - + :param file_path: Path to a JSON file to load fields from (default: None). :param fields: List of Field objects to initialize the FieldSet instance with (default: None). :param filters: Key-value pairs for filtering fields based on their attributes. @@ -148,29 +160,30 @@ class FieldSet: if file_path: self.load_from_file(file_path, **filters) - def load_from_file(self, file_path, **filters): """Loads Fields from an external JSON file with dynamic filtering""" - with open(file_path, 'r', encoding='utf-8') as file: + with open(file_path, "r", encoding="utf-8") as file: data = json.load(file) self.fields = [ Field(field_dict) for field_dict in data if all( - (value is True and field_dict.get(attr) is not None) or # Check for "not None" values - (value is not True and field_dict.get(attr) == value) # Check for exact match + ( + value is True and field_dict.get(attr) is not None + ) # Check for "not None" values + or ( + value is not True and field_dict.get(attr) == value + ) # Check for exact match for attr, value in filters.items() ) ] - def add_field(self, field): """Adds a new field manually to the FieldSet instance. If field is already in the FieldSet instance, do nothing """ if field.field_name not in [field.field_name for field in self.fields]: self.fields.append(field) - def get_field(self, field_name): for field in self.fields: @@ -193,13 +206,15 @@ class FieldSet: return { field.field_name: field.get_prompt() for field in self.fields - if (field_type is None or field.field_type == field_type) and - (relationship is None or field.relationship == relationship) + if (field_type is None or field.field_type == field_type) + and (relationship is None or field.relationship == relationship) } - + def print_prompt_dict(self): - return "\n".join([f"{field.field_name} : {field.get_prompt()}" for field in self.fields]) - + return "\n".join( + [f"{field.field_name} : {field.get_prompt()}" for field in self.fields] + ) + def print(self): """Prints all FieldInfo objects in the Prompts instance.""" if not self.fields: @@ -207,19 +222,18 @@ class FieldSet: return for field in self.fields: field.print() - + def __repr__(self): """Returns a string representation of all fields in the FieldSet.""" if not self.fields: return "FieldSet: No fields loaded" return str([field.field_name for field in self.fields]) - + def print_fields(self): if not self.fields: print("No fields loaded.") return print([field.field_name for field in self.fields]) - def filter(self, **criteria): """ @@ -230,13 +244,15 @@ class FieldSet: """ filtered_fields = self.fields for attr, value in criteria.items(): - filtered_fields = [field for field in filtered_fields if getattr(field, attr) == value] - + filtered_fields = [ + field for field in filtered_fields if getattr(field, attr) == value + ] + return FieldSet(fields=filtered_fields) - + def contains_fields(self): return len(self.fields) > 0 - + def combine(self, other, inplace=False): """ Combines the fields from another FieldSet into this one, avoiding duplicates. @@ -257,7 +273,8 @@ class FieldSet: ################################## PROMPT-TEMPLATES ################################# ##################################################################################### -def EXHIBIT_HEADER(context): + +def EXHIBIT_HEADER(context, EXHIBIT_HEADER_MARKERS): return f"""Analyze the following contract excerpt and extract the full header found. Capture the complete hierarchy of document identifiers present in the text. The following keywords and phrases should be considered as exhibit/attachment headers: @@ -288,6 +305,7 @@ Before returning any output, ensure that all instructions for inclusion were fol Enclose your final answer in |pipes|, followed by a brief explanation. """ + def EXHIBIT_LINKAGE(header1, header2): return f""" @@ -308,8 +326,9 @@ Header 2: Briefly explain your answer, then enclose your final answer in |pipes|. """ + def VALIDATE_REIMBURSEMENTS_PROMPT(service_term: str, reimb_term: str) -> str: - """ Validates whether a service-reimbursement pair represents a complete reimbursement methodology. + """Validates whether a service-reimbursement pair represents a complete reimbursement methodology. Args: service_term (str): The service term to validate. reimb_term (str): The reimbursement term to validate. @@ -358,7 +377,7 @@ Briefly explain your reasoning, then return YES or NO in |pipes|.""" def SPLIT_REIMBURSEMENTS_PROMPT(service_term: str, reimb_term: str) -> str: - """ Prompt for splitting a service-reimbursement pair into its component parts. + """Prompt for splitting a service-reimbursement pair into its component parts. Args: service_term (str): The service term to split. reimb_term (str): The reimbursement term to split. @@ -400,6 +419,7 @@ Example output: Feel free to justify your answer, but ensure the output is a valid JSON array with each entry containing SERVICE_TERM and REIMB_TERM keys. """ + def EXHIBIT_LEVEL(context, fields): return f"""Extract attributes from a section of a contract. @@ -585,6 +605,7 @@ Note: Both services receive the same global cap treatment regardless of table po Return properly formatted JSON array with all extracted reimbursement items. """ + def REIMB_TERM_BREAKOUT(service, methodology, questions): return f"""[OBJECTIVE] Analyze a given service-reimbursement methodology pair from a Payer-Provider contract: @@ -624,6 +645,7 @@ Methodology: {methodology.replace('"', "'")} Please explain your reasoning as you work through the document. After your explanation, include the heading "FINAL REIMBURSEMENT METHODOLOGY:" followed by a properly formatted JSON list with your final output. """ + def FEE_SCHEDULE_BREAKOUT(methodology, fee_schedule, questions): return f"""Analyze a given reimbursement methodology from a Payer-Provider contract: @@ -637,7 +659,10 @@ Use the text in the methodology to populate a JSON dictionary with the following Write your JSON dictionary below: """ -def SERVICE_METHODOLOGY_BREAKOUT(service, methodology, questions): # we'll eventually replace this with more specific templates + +def SERVICE_METHODOLOGY_BREAKOUT( + service, methodology, questions +): # we'll eventually replace this with more specific templates """ Use this prompt for any fields that are derived directly from the combination of Service and Methodology @@ -657,6 +682,7 @@ Methodology: {methodology.replace('"', "'")} Write your JSON dictionary below: """ + def CODE_EXPLICIT(service, questions): return f"""Analyze a given medical service: @@ -702,6 +728,7 @@ Here is the Service Term to analyze: Explain your answer in 1-2 sentences. Write your final answer in JSON list format. """ + def CODE_IMPLICIT_SPECIAL(service): return f"""Analyze a given medical Service Term. @@ -719,6 +746,7 @@ Here is the Service to analyze: Briefly explain your answer, but put your final answer in |pipes| """ + def CODE_IMPLICIT(service, choices): return f"""Analyze a given medical Service Term. @@ -751,6 +779,7 @@ Here is the Service Term to analyze: Explain your answer, ensuring each point in the instructions is addressed. Then return your final answer in JSON list format. """ + def CODE_LAST_CHECK(service): return f"""Analyze the given healthcare-related term. @@ -798,7 +827,8 @@ def ONE_TO_ONE_SINGLE_FIELD_TEMPLATE(context, question): {PIPE_FORMAT_INSTRUCTIONS} """ -def DATE_FIX_PROMPT(context:str) -> str: + +def DATE_FIX_PROMPT(context: str) -> str: return f"""Given a date string, convert it to YYYY/MM/DD format following these rules: Input: Text potentially containing a date Output: Date in YYYY/MM/DD format, or input if it's a duration, or "N/A" if ambiguous/incomplete @@ -840,6 +870,7 @@ Please analyze this date: {PIPE_FORMAT_INSTRUCTIONS} """ + def CHECK_REDUNDANT_LESSER(reimb_term, exhibit_lesser_of): return f"""You are analyzing whether an exhibit-level lesser-of constraint is semantically redundant with a reimbursement methodology. @@ -868,7 +899,9 @@ Answer YES only if the exhibit constraint adds NO additional limitation beyond w Briefly explain your reasoning, then return YES or NO in |pipes|.""" -def DERIVED_TERM_DATE_PROMPT(effective_date:str=None, termination_information:str=None) -> str: +def DERIVED_TERM_DATE_PROMPT( + effective_date: str = None, termination_information: str = None +) -> str: """ Generates a prompt to extract the termination date from a legal contract. @@ -881,8 +914,11 @@ def DERIVED_TERM_DATE_PROMPT(effective_date:str=None, termination_information:st """ return f"The following are excerpts from a legal contract related to effective and termination dates. Determine the initial termination date of the contract, excluding any renewals or extensions.\nIf termination information comes in as a duration, derive the termination date exclusive of the final day. For example, if effective date is 2023/02/01 and termination information is 'two years', termination date would be 2025/01/31 and not 2025/02/01. If effective date is 2023/02/01 and termination information is 'year-to-year', termination date would be 2024/01/31.\nIf the termination information comes in as a date, return that date. For example, if effective date is 2023/02/01 and termination information is 2024/07/01, return 2024/07/01.\nEffective Date: {effective_date}\nTermination Information: {termination_information}\nOnly provide the first termination date, ignoring any automatic or optional renewals. Provide the date in YYYY/MM/DD format only. \n{PIPE_FORMAT_INSTRUCTIONS}" + @cache -def invoke_derived_term_date(effective_date:str=None, termination_information:str=None) -> str: +def invoke_derived_term_date( + effective_date: str = None, termination_information: str = None +) -> str: prompt = DERIVED_TERM_DATE_PROMPT(effective_date, termination_information) response = llm_utils.invoke_claude(prompt, "sonnet_latest", "derive_term_date") return extract_text_from_delimiters(response, Delimiter.PIPE) @@ -914,8 +950,12 @@ The term length appears in two places but I'm selecting... """ -def SPECIAL_CASE_CHECK(service, methodology): - special_definitions = {special : definition for special, definition in VALID_SPECIAL.items() if definition} +def SPECIAL_CASE_CHECK(service, methodology, VALID_SPECIAL): + special_definitions = { + special: definition + for special, definition in VALID_SPECIAL.items() + if definition + } return f"""Examine the combination of Service and Methodology below and identify which of the following cases it falls under. Here are the Cases, with their definitions: @@ -930,8 +970,13 @@ Determine which case description(s) clearly corresponds to the Service and Metho {PIPE_FORMAT_INSTRUCTIONS} """ -def CARVEOUT_CHECK(service, methodology): - carveout_definitions = {carveout : definition for carveout, definition in VALID_CARVEOUTS.items() if definition} # Get only not None + +def CARVEOUT_CHECK(service, methodology, VALID_CARVEOUTS): + carveout_definitions = { + carveout: definition + for carveout, definition in VALID_CARVEOUTS.items() + if definition + } # Get only not None return f"""Examine the combination of Service and Methodology below and identify which of the following cases it falls under. Here are the Cases, with their definitions: @@ -946,6 +991,7 @@ Determine which case description corresponds to the Service and Methodology comb {PIPE_FORMAT_INSTRUCTIONS} """ + def DUAL_LOB_CHECK(service_term): return f"""Examine the given Service and determine if the Service applies to Medicare, Medicaid, or Both. @@ -960,14 +1006,17 @@ Here is the Service: Explain your answer in one or two sentences, but put your final answer in |pipes|. """ -def AARETE_DERIVED_PROMPT_TEMPLATE(contract_answer: str, valid_derived_values: list[str]) -> str: + +def AARETE_DERIVED_PROMPT_TEMPLATE( + contract_answer: str, valid_derived_values: list[str] +) -> str: """ prompt template for matching contract answers to valid derived values. - + Args: contract_answer (str): The answer from the contract to be matched valid_derived_values (list[str]): List of valid derived values - + Returns: str: A formatted prompt string for the LLM """ @@ -977,15 +1026,15 @@ def AARETE_DERIVED_PROMPT_TEMPLATE(contract_answer: str, valid_derived_values: l raise TypeError("Expected a single value, but got a Pandas Series.") # Handle None, empty string, or N/A input cases - if not contract_answer or contract_answer.lower() in ['n/a', 'none']: + if not contract_answer or contract_answer.lower() in ["n/a", "none"]: return "|N/A|" - + # Handle empty valid_derived_values list if not valid_derived_values: return "|N/A|" - + formatted_values = "\n".join(f"- {value}" for value in valid_derived_values) - + prompt = f"""Task: Match the following contract answer to the most appropriate valid derived value. Contract Answer: {contract_answer} @@ -1050,6 +1099,7 @@ Contract text: {context.replace('"', "'")} """ + def GROUP_TIN_NPI_TEMPLATE(key_sections: str, provider_list: str) -> str: return f""" Identify which provider is the MAIN CONTRACTING GROUP in this contract. @@ -1083,6 +1133,7 @@ def GROUP_TIN_NPI_TEMPLATE(key_sections: str, provider_list: str) -> str: Feel free to justify your answer, but enclose your final answer in |pipes|. """ + def EFFECTIVE_DATE_FIX_PROMPT() -> str: return """Extract the effective date of the contract or contract effective date or Effective Date of Agreement or Effective Date of Amendment or the amendment effective date from the given image data. ALSO LOOK FOR dates in phrases that semantically indicate an effective date (may or may not be explictly labelled as effective date), even if they do not exactly match the phrases above.\nExamples include but ARE NOT LIMITED TO:\nPhrases indicating when an contract or amendment begins or takes effect, Phrases specifying the start date of the contract or amendment, Phrases defining when the contract or amendment becomes operational, or Any phrase indicating contract or amendment creation or execution date.\nIt is possible that the contract will specify that the effective date is derived from elsewhere in the contract. Give the date in a JSON FORMAT with AARETE_DERIVED_EFFECTIVE_DATE as the key and effective date value in YYYY/MM/DD format as the value. Rules: @@ -1093,18 +1144,21 @@ def EFFECTIVE_DATE_FIX_PROMPT() -> str: Return N/A only for date if NO valid effective date is found. give the answer in the format: {{\"AARETE_DERIVED_EFFECTIVE_DT\": \"YYYY/MM/DD\"}}. nothing else.""" -def RATE_ESCALATOR_BREAKOUT_PROMPT(rate_escalator_statement: str, rate_escalator_questions) -> str: - """ - Creates a prompt to extract rate escalator field information from a rate escalator statement. - - Args: - rate_escalator_statement: The text containing rate escalator information - rate_escalator_questions: Dictionary or list of questions with instructions for each field - - Returns: - Formatted prompt string for rate escalator field extraction - """ - return f""" + +def RATE_ESCALATOR_BREAKOUT_PROMPT( + rate_escalator_statement: str, rate_escalator_questions +) -> str: + """ + Creates a prompt to extract rate escalator field information from a rate escalator statement. + + Args: + rate_escalator_statement: The text containing rate escalator information + rate_escalator_questions: Dictionary or list of questions with instructions for each field + + Returns: + Formatted prompt string for rate escalator field extraction + """ + return f""" You are tasked with extracting specific rate escalator information from the given statement. RATE ESCALATOR STATEMENT: @@ -1129,4 +1183,4 @@ Final answers must be provided in JSON format: "field_name_2": "extracted_value_2", ... }} -""" \ No newline at end of file +""" diff --git a/fieldExtraction/src/prompts/postprocessing_prompts.py b/fieldExtraction/src/prompts/postprocessing_prompts.py index 92bbc47..38d0aac 100644 --- a/fieldExtraction/src/prompts/postprocessing_prompts.py +++ b/fieldExtraction/src/prompts/postprocessing_prompts.py @@ -1,5 +1,4 @@ - -from src.constants import valid +from constants import valid def LOB_SWEEPER(exhibit): diff --git a/fieldExtraction/src/prompts/preprocessing_prompts.py b/fieldExtraction/src/prompts/preprocessing_prompts.py index dd66153..96535aa 100644 --- a/fieldExtraction/src/prompts/preprocessing_prompts.py +++ b/fieldExtraction/src/prompts/preprocessing_prompts.py @@ -1,5 +1,3 @@ - - def EXHIBIT_CHECK(page_start): return f""" Analyze the following contract excerpt and determine if it indicates the start of a section. Follow the following instructions: @@ -27,54 +25,3 @@ Modify the values and formatting of the string to make it json parsable. Remove Return only the json object, with no other commentary. """ - - -def ALIGN_TABLES(page): - return f"""You will be given a page with a json-formatted table on it. The table is not where it should be on the page. -Return the page you are given, with the table moved into it's proper position. - -### Alignment Instructions -1. The table header text (found after -------Table Start--------) is the best indicator of where the table should be relocated. Look for text elsewhere on the page that is identical to the table header text. The json-formatted table should be moved under the matching text on the page. -2. If there is no header text, align the table(s) based on context clues in the text such as "the table below", etc, that may help indicate where the table is meant to be located. -3. If the table header is "None", place the table on the top of the page, before any other text. - -### Output Instructions -Write the EXACT page text as presented to you, with the reformatted and repositioned table in it's place. Do not add any additional commentary or explanation. - -Here is the page: -{page} -""" - - -def ALIGN_AND_FORMAT_TABLES(page): - return f"""{page} - ------ End Page ----- - -Above is a page with one or more misplaced tables in json format. - -Return the exact same page with the tables properly aligned and formatted, without changing anything else about the page. Here are instructions and examples to help: - -Alignment Instructions: -Align the tables in the text using the Table Header text found after -------Table Start--------. If there is no header, align based on context clues in the text such as "the table below", etc. -If there are multiple tables on the page, the first table should be placed in the first valid spot, the second in the second, etc. -If the Table Header is None, place the updated table at the top of the page. -Do not replicate the table header text twice. Do not remove the -------Table Start-------- or -------Table End-------- lines. - -Formatting Instructions: -Reformat the table from json format to a human-readable fixed-width format. Use the following tempate format: - -Original JSON: "-------Table Start-------- Table Header {{'Column 1' : ['Value 1', 'Value 2', 'Value 3'], 'Column 2' : ['Value 4', 'Value 5', 'Value 6']}}-------Table End--------" - -Output Format: --------Table Start-------- -Table Header -Column 1 : Value 1 | Column 2 : Value 4 -Column 1 : Value 2 | Column 2 : Value 5 -Column 1 : Value 3 | Column 2 : Value 6 --------Table End-------- - -Ensure that all text other than the tables is exactly the same - -Write only the page text, with no additional commentary. -""" diff --git a/fieldExtraction/src/qa_qc/__init__.py b/fieldExtraction/src/qa_qc/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/fieldExtraction/src/regex/__init__.py b/fieldExtraction/src/regex/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/fieldExtraction/src/regex/regex_patterns.py b/fieldExtraction/src/regex/regex_patterns.py deleted file mode 100644 index 0d3b221..0000000 --- a/fieldExtraction/src/regex/regex_patterns.py +++ /dev/null @@ -1,8 +0,0 @@ -# Delimiter Patterns -PIPE_PATTERN = r'\|([^|]+)\|' -BACKTICK_PATTERN = r'`([^`]+)`' -TRIPLE_BACKTICK_PATTERN = r'```([^`]+)```' - -# TIN and NPI Regex Patterns -TIN_PATTERN = r"\b\d{9}\b|\b\d{2}[-.]\d{7}\b|\b\d{3}[-.]\d{2}[-.]\d{4}\b" -NPI_PATTERN = r"\b\d{10}\b" \ No newline at end of file diff --git a/fieldExtraction/src/regex/regex_utils.py b/fieldExtraction/src/regex/regex_utils.py deleted file mode 100644 index cc2e698..0000000 --- a/fieldExtraction/src/regex/regex_utils.py +++ /dev/null @@ -1,293 +0,0 @@ -import re - -from src import postprocessing_funcs - -## These functions are all client-only, except `find_regex_matches` which is used for reimbursement TIN/NPI. - -################################################################################################################################################# -# NPI -def signature_page_npi(text_dict, page_list, matches): - """In case of Multiple 10-digit regex matches, this function will check if any of those matches are on the signature page. - We check for the word 'Signature' or 'SIGNATURE' to determine the signature page - Args: - text_dict (dict): Dictionary keyed by string page-number and valued by actual textract output page - page_list(list): List of pages where the match was found - matches(list): List of 10-digit regex matches - - Returns: - npi(str): NPI on the signature page or N/A - npi_other (str): Remaining matches other than the ones on Signature page or N/A - """ - npi_on_signature_page = [] - for npi in matches: - for page in page_list: - if npi in text_dict[page] and ( - "Signature" in text_dict[page] or "SIGNATURE" in text_dict[page] - ): - npi_on_signature_page.append(npi) - - # remove duplicates while maintaining order - npi_on_signature_page = list(dict.fromkeys(npi_on_signature_page)) - - # return NPI if only 1 match on signature page - if len(npi_on_signature_page) == 1: - npi = npi_on_signature_page[0] - npi_other = [item for item in matches if item not in npi_on_signature_page] - npi_other = ", ".join(npi_other) - return npi, npi_other - - # all matches go to NPI_other is no match on signature page - elif len(npi_on_signature_page) == 0: - npi = "N/A" - npi_other = ", ".join(matches) - return npi, npi_other - - # all matches on signature page go as NPI and remaining as NPI_other - else: - npi = ", ".join(npi_on_signature_page) - npi_other = [item for item in matches if item not in npi_on_signature_page] - if len(npi_other) == 0: - npi_other = "N/A" - return npi, npi_other - else: - npi_other = ", ".join(npi_other) - return npi, npi_other - - -def find_regex_matches( # regex_funcs.py - pattern: str, text_dict: dict -) -> tuple[list, list]: # TODO: Write few unit tests - """Helper function to find matches and list of pages of a given regex pattern in text_dict. - - Args: - pattern(str): A particular regex match that with a pattern. - text_dict (dict): Dictionary keyed by string page-number and valued by actual textract output page - - Returns: - matches(list): List of match(s) - page_list(list): List of pages where the match was found - - """ - matches = [] - page_list = [] - for page, text in text_dict.items(): - found = re.findall(pattern, text) - if found: - matches.extend(found) # make it append - page_list.append(page) - - return matches, page_list - - -def npi_pattern_match(text_dict: dict[str, str]) -> dict[str, str]: - """Returns dictionery with Provider and other TIN(s) based on regex matches and presence on Signatory page - - Args: - filename: Name of the contract - text_dict (dict): Dictionary keyed by string page-number and valued by actual textract output page - - Returns: - NPI_dict(dict): Dict with keys NPI and NPI_other - """ - NPI_dict = {} - - patterns = [r"\b\d{10}\b"] - - for pattern in patterns: - matches, page_list = find_regex_matches(pattern, text_dict) - matches = [s for s in matches if s[0] in ("1", "2")] # NPI can only start with 1 or 2 - matches = list(dict.fromkeys(matches)) - if len(matches) == 1: - NPI_dict["PROV_GROUP_NPI"] = matches[0] - NPI_dict["PROV_NPI_OTHER"] = "N/A" - elif len(matches) == 0: - NPI_dict["PROV_GROUP_NPI"] = "N/A" - NPI_dict["PROV_NPI_OTHER"] = "N/A" - else: - npi, npi_other = signature_page_npi(text_dict, page_list, matches) - NPI_dict["PROV_GROUP_NPI"] = npi - NPI_dict["PROV_NPI_OTHER"] = npi_other - return NPI_dict - - -def get_npi_fields( - text_dict: dict[str, str], top_sheet_dict: dict[str, str] -) -> dict[str, str]: - """Checks for NPI 10-digit matches in the text_dict first. If N/A checks in TOP SHEET. - If we have multiple eligible matches in top sheet, we pass them to NPI_Other - - Args: - text_dict(dict): Dictionary keyed by string page-number and valued by actual textract output page - top_sheet_dict(dict): Dictionary keyed by string page-number and valued by actual textract output with only TOP Sheet pages - - Returns: - npi_answers(dict): Dict with keys NPI and NPI_other - """ - - npi_answers = npi_pattern_match(text_dict) - if npi_answers["PROV_GROUP_NPI"] == "N/A" and npi_answers["PROV_NPI_OTHER"] == "N/A": - npi_answers = npi_pattern_match(top_sheet_dict) - return npi_answers - - -################################################################################################################################################# -# IRS -def check_tin_in_filename(filename): - """ - Checks for the pattern match in filename - - Args: - filename(str): Name of the file - Returns: - tin_from_title(str): regex match for patterns in filename - """ - patterns = [r"\b\d{2}-\d{7}\b", r"\b\d{9}\b"] - for pattern in patterns: - tin_from_title = re.findall(pattern, filename) - if len(tin_from_title) == 1: - tin_from_title = [postprocessing_funcs.add_hyphen_if_needed(s) for s in tin_from_title] - return tin_from_title[0] - return "empty" - - -def signature_page_irs(text_dict, page_list, matches, filename): - """ - In case of multiple matches, checks if a TIN is on the Signature page - - Args: - text_dict (dict): Dictionary keyed by string page-number and valued by actual textract output page - page_list(list): List of pages where the match was found - matches(list): List of regex match(s) - Returns - provider_tin(str): Provider group TIN - either from signature page or filename - other_tins(str): Remaining TINs - """ - # loops through regex matches and thier pages to check for signature page - tin_on_signature_page = [] - for tin in matches: - for page in page_list: - if tin in text_dict[page] and ( - "Signature" in text_dict[page] or "SIGNATURE" in text_dict[page] - ): - tin_on_signature_page.append(tin) - tin_on_signature_page = list(dict.fromkeys(tin_on_signature_page)) - - # adds hyphen if needed - matches = [postprocessing_funcs.add_hyphen_if_needed(s) for s in matches] - tin_on_signature_page = [postprocessing_funcs.add_hyphen_if_needed(s) for s in tin_on_signature_page] - - # case1 - If 1 TIN found on signature page, return it, put the remaining ones in "TIN Others" have signature_tin = N/A - if len(tin_on_signature_page) == 1: - provider_tin = tin_on_signature_page[0] - signature_tin = "N/A" - matches = [item for item in matches if item not in tin_on_signature_page] - other_tins = ", ".join(matches) - return provider_tin, signature_tin, other_tins - - # case2 - No TIN found on the signature page, check in filename - elif len(tin_on_signature_page) == 0: - provider_tin = check_tin_in_filename(filename) - if provider_tin == "empty": - provider_tin = "N/A" - signature_tin = "N/A" - other_tins = [item for item in matches if item != provider_tin] - other_tins = ", ".join(other_tins) - return provider_tin, signature_tin, other_tins - - # case3 - More than 1 TIN on signature page - check for TIN in file name, let the others go in signatory_tins - # and finally check for TINs not on signature page but in "matches" list to add them to "tin_others" - else: - filename_tin_check = check_tin_in_filename(filename) - if filename_tin_check == "empty": - provider_tin = "N/A" - signature_tin = ", ".join(tin_on_signature_page) - other_tins = "N/A" - return provider_tin, signature_tin, other_tins - else: - provider_tin = filename_tin_check - signature_tin = [ - item for item in tin_on_signature_page if item != provider_tin - ] - signature_tin = ", ".join(signature_tin) - - if len(matches) > len(tin_on_signature_page): - other_tins = [ - item - for item in matches - if item not in tin_on_signature_page and item != provider_tin - ] - other_tins = ", ".join(other_tins) - return provider_tin, signature_tin, other_tins - else: - other_tins = "N/A" - return provider_tin, signature_tin, other_tins - - -def irs_pattern_match(filename: str, text_dict: dict[str, str]): - """Returns dictionery with Provider and other TIN(s) based on regex matches and presence on Signatory page - - Args: - filename: Name of the contract - text_dict (dict): Dictionary keyed by string page-number and valued by actual textract output page - - Returns: - TIN_dict(dict): A dictionery with keys 'PROV_GROUP_TIN' & 'PROV_TIN_OTHER' and values as respective TIN(s) - """ - TIN_dict = {} - - patterns = [r"\b\d{2}-\d{7}\b", r"\b\d{9}\b"] - for pattern in patterns: - matches, page_list = find_regex_matches(pattern, text_dict) - matches = list(dict.fromkeys(matches)) - if matches: - if len(matches) == 1: - matches = [postprocessing_funcs.add_hyphen_if_needed(s) for s in matches] - TIN_dict["PROV_GROUP_TIN"] = matches[0] - TIN_dict["PROV_TIN_OTHER"] = "N/A" - TIN_dict["PROV_GROUP_TIN_SIGNATORY"] = "N/A" - return TIN_dict - - elif len(matches) >= 1: - irs_tin, signatory_tin, other_tins = signature_page_irs( - text_dict, page_list, matches, filename - ) - - TIN_dict["PROV_GROUP_TIN"] = irs_tin - TIN_dict["PROV_TIN_OTHER"] = other_tins - TIN_dict["PROV_GROUP_TIN_SIGNATORY"] = signatory_tin - return TIN_dict - - # If no matches were found in the text_dict, check the filename - for pattern in patterns: - tin_from_title = check_tin_in_filename(filename) - TIN_dict["PROV_TIN_OTHER"] = "N/A" - TIN_dict["PROV_GROUP_TIN_SIGNATORY"] = "N/A" - if tin_from_title == "empty": - TIN_dict["PROV_GROUP_TIN"] = "N/A" - else: - TIN_dict["PROV_GROUP_TIN"] = tin_from_title - return TIN_dict - - -def get_tin_fields( - filename: str, text_dict: dict[str, str], top_sheet_dict: dict[str, str] -) -> dict[str, str]: - """Checks for IRS pattern matches in the text_dict first. If N/A checks in TOP SHEET. - If we have multiple eligible matches in top sheet, we pass them to TIN Other - - Args: - text_dict(dict): Dictionary keyed by string page-number and valued by actual textract output page - top_sheet_dict(dict): Dictionary keyed by string page-number and valued by actual textract output with only TOP Sheet pages - - Returns: - irs_answers(dict): Dict with keys IRS, IRS other and IRS Signatory - """ - - irs_answers = irs_pattern_match(filename, text_dict) - if ( - irs_answers["PROV_GROUP_TIN"] == "N/A" - and irs_answers["PROV_TIN_OTHER"] == "N/A" - and irs_answers["PROV_GROUP_TIN_SIGNATORY"] == "N/A" - ): - irs_answers = irs_pattern_match(filename, top_sheet_dict) - return irs_answers diff --git a/fieldExtraction/src/testbed/add_reimb_id_to_testbed.py b/fieldExtraction/src/testbed/add_reimb_id_to_testbed.py index d35ac44..ca7d4ca 100644 --- a/fieldExtraction/src/testbed/add_reimb_id_to_testbed.py +++ b/fieldExtraction/src/testbed/add_reimb_id_to_testbed.py @@ -9,9 +9,9 @@ Run it with `poetry run python -m src.testbed.add_reimb_id_to_testbed` # Imports import pandas as pd -from src.postprocessing_funcs import generate_reimb_ids -from src.constants.investment_columns import COLUMN_ORDER -from src.investment.investment_postprocessing_funcs import reorder_columns + +from constants.investment_columns import COLUMN_ORDER +from src.investment.postprocessing_funcs import generate_reimb_ids, reorder_columns # Read the testbed file testbed = pd.read_excel("Doczy-Testbed.xlsx") @@ -20,4 +20,4 @@ testbed = pd.read_excel("Doczy-Testbed.xlsx") testbed_w_ids = reorder_columns(generate_reimb_ids(testbed), COLUMN_ORDER) # Save the updated testbed with reimbursement IDs -testbed_w_ids.to_excel("Doczy-Testbed-with-Reimb-IDs.xlsx", index=False) \ No newline at end of file +testbed_w_ids.to_excel("Doczy-Testbed-with-Reimb-IDs.xlsx", index=False) diff --git a/fieldExtraction/src/testbed/one_to_n_comparison.py b/fieldExtraction/src/testbed/one_to_n_comparison.py index a3e1316..ffa8225 100644 --- a/fieldExtraction/src/testbed/one_to_n_comparison.py +++ b/fieldExtraction/src/testbed/one_to_n_comparison.py @@ -1,5 +1,4 @@ import pandas as pd - from rapidfuzz import fuzz @@ -66,8 +65,12 @@ def evaluate_one_to_n_fields_fuzzy_matching_field_by_field( contract_predictions = predictions_df[predictions_df[key_field] == contract_id] # Create lists of entries as tuples for comparison - labeled_entries = [tuple(row) for row in contract_labels[comparison_fields].values] - predicted_entries = [tuple(row) for row in contract_predictions[comparison_fields].values] + labeled_entries = [ + tuple(row) for row in contract_labels[comparison_fields].values + ] + predicted_entries = [ + tuple(row) for row in contract_predictions[comparison_fields].values + ] # Track matches to avoid double counting matched_labels = set() @@ -76,16 +79,21 @@ def evaluate_one_to_n_fields_fuzzy_matching_field_by_field( # Find matches using field-by-field fuzzy comparison for i, labeled_entry in enumerate(labeled_entries): for j, predicted_entry in enumerate(predicted_entries): - if j not in matched_predictions and are_entries_similar_field_by_field(labeled_entry, predicted_entry, thresholds): + if j not in matched_predictions and are_entries_similar_field_by_field( + labeled_entry, predicted_entry, thresholds + ): # If a match is found, increment true positives and mark entries as matched total_true_positives += 1 matched_labels.add(i) matched_predictions.add(j) break # Calculate unmatched entries - total_false_negatives += len(labeled_entries) - len(matched_labels) # False negatives are labeled entries that were not matched - total_false_positives += len(predicted_entries) - len(matched_predictions) # False positives are predicted entries that were not matched - + total_false_negatives += len(labeled_entries) - len( + matched_labels + ) # False negatives are labeled entries that were not matched + total_false_positives += len(predicted_entries) - len( + matched_predictions + ) # False positives are predicted entries that were not matched # Calculate overall metrics precision = ( @@ -101,7 +109,9 @@ def evaluate_one_to_n_fields_fuzzy_matching_field_by_field( f1_score = ( 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0 ) - accuracy = total_true_positives / (total_true_positives + total_false_positives + total_false_negatives) + accuracy = total_true_positives / ( + total_true_positives + total_false_positives + total_false_negatives + ) return { "precision": precision, @@ -111,7 +121,27 @@ def evaluate_one_to_n_fields_fuzzy_matching_field_by_field( "true_positives": total_true_positives, "false_positives": total_false_positives, "false_negatives": total_false_negatives, - "matched_pairs": {contract_id: [{"label": labeled_entries[i], "prediction": predicted_entries[j]} for i, j in zip(matched_labels, matched_predictions)] for contract_id in contract_ids}, - "unmatched_labels": {contract_id: [labeled_entries[i] for i in range(len(labeled_entries)) if i not in matched_labels] for contract_id in contract_ids}, - "unmatched_predictions": {contract_id: [predicted_entries[j] for j in range(len(predicted_entries)) if j not in matched_predictions] for contract_id in contract_ids} - } \ No newline at end of file + "matched_pairs": { + contract_id: [ + {"label": labeled_entries[i], "prediction": predicted_entries[j]} + for i, j in zip(matched_labels, matched_predictions) + ] + for contract_id in contract_ids + }, + "unmatched_labels": { + contract_id: [ + labeled_entries[i] + for i in range(len(labeled_entries)) + if i not in matched_labels + ] + for contract_id in contract_ids + }, + "unmatched_predictions": { + contract_id: [ + predicted_entries[j] + for j in range(len(predicted_entries)) + if j not in matched_predictions + ] + for contract_id in contract_ids + }, + } diff --git a/fieldExtraction/src/testbed/one_to_one_comparison.py b/fieldExtraction/src/testbed/one_to_one_comparison.py index f3f1f49..a98e5a5 100644 --- a/fieldExtraction/src/testbed/one_to_one_comparison.py +++ b/fieldExtraction/src/testbed/one_to_one_comparison.py @@ -1,25 +1,30 @@ import pandas as pd from src import config - from src.prompts.investment_prompts import FieldSet -script_config = {"labels_location_dates" : "/home/arg/20250219_test_bed_facilitation/fieldExtraction/src/testbed/labeled_files/Document_Dates_2-13.xlsx", - "sheetname_labels_dates" : "doczy-ai-test-20240211", - "labels_location_tin_npi" : "/home/arg/20250219_test_bed_facilitation/fieldExtraction/src/testbed/labeled_files/TIN_NPI_Names_2-14.xlsx", - "sheetname_labels_tin_npi" : "doczy-ai-test-20240211", - "predictions_location" : "/home/arg/20250219_test_bed_facilitation/fieldExtraction/src/testbed/prediction_files/doczy-inv-consolidated-FEB12.csv", - } +script_config = { + "labels_location_dates": "/home/arg/20250219_test_bed_facilitation/fieldExtraction/src/testbed/labeled_files/Document_Dates_2-13.xlsx", + "sheetname_labels_dates": "doczy-ai-test-20240211", + "labels_location_tin_npi": "/home/arg/20250219_test_bed_facilitation/fieldExtraction/src/testbed/labeled_files/TIN_NPI_Names_2-14.xlsx", + "sheetname_labels_tin_npi": "doczy-ai-test-20240211", + "predictions_location": "/home/arg/20250219_test_bed_facilitation/fieldExtraction/src/testbed/prediction_files/doczy-inv-consolidated-FEB12.csv", +} + def load_labels_file(filename: str, sheetname: str) -> pd.DataFrame: df = pd.read_excel(filename, sheet_name=sheetname) return df + def load_predictions(filename: str) -> pd.DataFrame: df = pd.read_csv(filename) return df -def consolidate_one_to_one_fields_in_predictions(predictions_df: pd.DataFrame) -> pd.DataFrame: + +def consolidate_one_to_one_fields_in_predictions( + predictions_df: pd.DataFrame, +) -> pd.DataFrame: """One-to-one fields need to be consolidated (by taking the first value) across FILE_NAMEs. Args: @@ -28,15 +33,20 @@ def consolidate_one_to_one_fields_in_predictions(predictions_df: pd.DataFrame) - Returns: pd.DataFrame: DataFrame with consolidated one-to-one fields """ - one_to_one_fields = FieldSet(file_path=config.FIELD_JSON_PATH).filter(relationship="one_to_one").list_fields() + one_to_one_fields = ( + FieldSet(file_path=config.FIELD_JSON_PATH) + .filter(relationship="one_to_one") + .list_fields() + ) print(one_to_one_fields) # Group by FILE_NAME and take just the one-to-one fields. Since they'll be repeated # across rows, we'll just take the first one - - consolidated_df = predictions_df.groupby('FILE_NAME').first().reset_index() + + consolidated_df = predictions_df.groupby("FILE_NAME").first().reset_index() return consolidated_df + def sort_label_and_prediction_columns(merged_df: pd.DataFrame) -> pd.DataFrame: """Place prediction and label columns next to each other in the DataFrame. @@ -50,14 +60,14 @@ def sort_label_and_prediction_columns(merged_df: pd.DataFrame) -> pd.DataFrame: columns = merged_df.columns.tolist() # Separate labels and predictions columns - labels_columns = [col for col in columns if col.endswith('_labels')] - predictions_columns = [col for col in columns if col.endswith('_predictions')] + labels_columns = [col for col in columns if col.endswith("_labels")] + predictions_columns = [col for col in columns if col.endswith("_predictions")] # Reorder columns to put corresponding labels and predictions together ordered_columns = [] for label_col in labels_columns: # Find corresponding prediction column by removing '_labels' and adding '_predictions' - pred_col = label_col.replace('_labels', '_predictions') + pred_col = label_col.replace("_labels", "_predictions") if pred_col in predictions_columns: ordered_columns.append(label_col) ordered_columns.append(pred_col) @@ -67,6 +77,7 @@ def sort_label_and_prediction_columns(merged_df: pd.DataFrame) -> pd.DataFrame: return merged_df + if __name__ == "__main__": # Load predictions @@ -74,7 +85,9 @@ if __name__ == "__main__": df_predictions = load_predictions(script_config["predictions_location"]) # Consolidate one-to-one fields in predictions - consolidated_one_to_one_predictions = consolidate_one_to_one_fields_in_predictions(df_predictions) + consolidated_one_to_one_predictions = consolidate_one_to_one_fields_in_predictions( + df_predictions + ) ## Dates fields # Load labels @@ -88,7 +101,12 @@ if __name__ == "__main__": predicted_date_fields = consolidated_one_to_one_predictions[date_fields] # Join labels and predictions on FILE_NAME - merged_df = pd.merge(df_labels_dates, predicted_date_fields, on="FILE_NAME", suffixes=['_labels', '_predictions']) + merged_df = pd.merge( + df_labels_dates, + predicted_date_fields, + on="FILE_NAME", + suffixes=["_labels", "_predictions"], + ) sorted_df = sort_label_and_prediction_columns(merged_df) @@ -105,6 +123,11 @@ if __name__ == "__main__": predicted_tin_npi_fields = consolidated_one_to_one_predictions[tin_npi_fields] # Join labels and predictions on FILE_NAME - merged_df = pd.merge(df_labels_tin_npi, predicted_tin_npi_fields, on="FILE_NAME", suffixes=['_labels', '_predictions']) + merged_df = pd.merge( + df_labels_tin_npi, + predicted_tin_npi_fields, + on="FILE_NAME", + suffixes=["_labels", "_predictions"], + ) sorted_df = sort_label_and_prediction_columns(merged_df) - sorted_df.to_csv("merged_predictions_labels_TIN_NPI.csv", index=False) \ No newline at end of file + sorted_df.to_csv("merged_predictions_labels_TIN_NPI.csv", index=False) diff --git a/fieldExtraction/src/testbed/test.py b/fieldExtraction/src/testbed/test.py index 241d02e..8df7d1c 100644 --- a/fieldExtraction/src/testbed/test.py +++ b/fieldExtraction/src/testbed/test.py @@ -1,19 +1,19 @@ - # Imports +import warnings + import pandas as pd -from src.prompts.investment_prompts import FieldSet -import warnings import src.config as config -import src.utils.string_utils as string_utils import src.testbed.testbed_utils as testbed_utils +import src.utils.string_utils as string_utils +from src.prompts.investment_prompts import FieldSet warnings.filterwarnings("ignore") ########################## READ AND PREPROCESS ########################## # Read files testbed = pd.read_excel("Doczy-Testbed.xlsx") # This is the testbed -results = pd.read_csv("Doczy-Results.csv") # This is the doczy output +results = pd.read_csv("Doczy-Results.csv") # This is the doczy output # Preprocess testbed and results data testbed = testbed_utils.testbed_preprocess(testbed) @@ -41,23 +41,23 @@ one_to_n_metrics = testbed_utils.get_one_to_n_analysis(testbed, results) reimb_primary_df = testbed_utils.get_reimb_primary(testbed, results) ########################## Order-Independent Provider Analysis ########################## -provider_results, provider_metrics_df = testbed_utils.evaluate_provider_fields_separately(testbed, results, verbose=True) +provider_results, provider_metrics_df = ( + testbed_utils.evaluate_provider_fields_separately(testbed, results, verbose=True) +) ########################## Export to Excel ########################## print("\nExporting results to Excel...") # Create Excel writer object output_file = f"{config.TODAY}-testbed-metrics.xlsx" -testbed_utils.export_to_excel(output_file, - testbed, - row_comparison, - page_comparison_df, - metrics_df, - comparison_df, - one_to_n_metrics, - reimb_primary_df, - provider_metrics_df) - - - - +testbed_utils.export_to_excel( + output_file, + testbed, + row_comparison, + page_comparison_df, + metrics_df, + comparison_df, + one_to_n_metrics, + reimb_primary_df, + provider_metrics_df, +) diff --git a/fieldExtraction/src/testbed/testbed_postprocess.py b/fieldExtraction/src/testbed/testbed_postprocess.py index 9eea1f4..4c95866 100644 --- a/fieldExtraction/src/testbed/testbed_postprocess.py +++ b/fieldExtraction/src/testbed/testbed_postprocess.py @@ -1,5 +1,5 @@ # This script postprocesses the testbed data from an Excel file. It uses a custom -# function `testbed_postprocess` from the `testbed_utils` module (similar to +# function `testbed_postprocess` from the `testbed_utils` module (similar to # postprocess.postprocess() but skipping some steps) # Run this from the fieldExtraction directory: @@ -7,19 +7,22 @@ #### CONFIG #### testbed_filename = "src/testbed/v6_DoczyAI_all_test_bed_LIVE_5.6.25.xlsx" -testbed_sheetname = 'all_test_bed_labels' +testbed_sheetname = "all_test_bed_labels" output_filename = "src/testbed/v6_DoczyAI_all_test_bed_LIVE_5.6.25_POSTPROCESSED.xlsx" #### IMPORTS #### import pandas as pd + from src.testbed.testbed_utils import testbed_postprocess #### EXECUTION #### # Load the testbed df = pd.read_excel(testbed_filename, sheet_name=testbed_sheetname) -print(f"Loaded testbed from {testbed_filename} with {df.shape[0]} rows and {df.shape[1]} columns") +print( + f"Loaded testbed from {testbed_filename} with {df.shape[0]} rows and {df.shape[1]} columns" +) # Postprocess the testbed testbed_postprocess(df).to_excel(output_filename, index=False) -print(f"Postprocessed testbed saved to {output_filename}") \ No newline at end of file +print(f"Postprocessed testbed saved to {output_filename}") diff --git a/fieldExtraction/src/testbed/testbed_utils.py b/fieldExtraction/src/testbed/testbed_utils.py index aa58e75..7ad5713 100644 --- a/fieldExtraction/src/testbed/testbed_utils.py +++ b/fieldExtraction/src/testbed/testbed_utils.py @@ -1,45 +1,61 @@ - -import pandas as pd -import src.utils.string_utils as string_utils import itertools import re - from collections import Counter + +import pandas as pd from rapidfuzz import fuzz -# Imports for the testbed postprocessing functions -from src.investment import investment_postprocessing_funcs -from src.constants.investment_columns import COLUMN_ORDER -from src.prompts.investment_prompts import FieldSet import src.config as config +import src.utils.string_utils as string_utils +from constants.investment_columns import COLUMN_ORDER + +# Imports for the testbed postprocessing functions +from src.investment import postprocessing_funcs +from src.prompts.investment_prompts import FieldSet + def filter_file_names(testbed, results): # Ensure FILE_NAME values match between testbed and results - common_file_names = set(testbed['FILE_NAME']).intersection(set(results['FILE_NAME'])) + common_file_names = set(testbed["FILE_NAME"]).intersection( + set(results["FILE_NAME"]) + ) print(f"common_file_names: {len(common_file_names)} items") - if "86-0898663-Founder Health, LLC dba Preferred Homecare-ICMProviderAgreement_120661".upper() in common_file_names: - print("found 86-0898663-Founder Health, LLC dba Preferred Homecare-ICMProviderAgreement_120661 in both testbed and results; removing it from both datasets") - common_file_names.remove("86-0898663-Founder Health, LLC dba Preferred Homecare-ICMProviderAgreement_120661".upper()) + if ( + "86-0898663-Founder Health, LLC dba Preferred Homecare-ICMProviderAgreement_120661".upper() + in common_file_names + ): + print( + "found 86-0898663-Founder Health, LLC dba Preferred Homecare-ICMProviderAgreement_120661 in both testbed and results; removing it from both datasets" + ) + common_file_names.remove( + "86-0898663-Founder Health, LLC dba Preferred Homecare-ICMProviderAgreement_120661".upper() + ) print(f"common_file_names now has {len(common_file_names)} items") - testbed = testbed[testbed['FILE_NAME'].isin(common_file_names)] - results = results[results['FILE_NAME'].isin(common_file_names)] + testbed = testbed[testbed["FILE_NAME"].isin(common_file_names)] + results = results[results["FILE_NAME"].isin(common_file_names)] return testbed, results + def convert_to_float(value): try: - return f"{float(value):.2f}" if isinstance(value, (int, float)) or value.replace('.', '', 1).isdigit() else value + return ( + f"{float(value):.2f}" + if isinstance(value, (int, float)) or value.replace(".", "", 1).isdigit() + else value + ) except: return value + def normalize_tin_npi(value): """Normalize TIN/NPI values by removing decimal points and trailing zeros.""" if string_utils.is_empty(value): return "" - + # Skip "UNKNOWN" and non-numeric values - if value == "UNKNOWN" or not value.replace('.', '', 1).isdigit(): + if value == "UNKNOWN" or not value.replace(".", "", 1).isdigit(): return value - + # Convert to integer-like string by removing decimal part try: # Remove decimal and any trailing zeros @@ -47,16 +63,22 @@ def normalize_tin_npi(value): except: return value + def testbed_preprocess(df): # Fix column names with leading/trailing spaces df.columns = df.columns.str.strip() # Capitalize and remove ".TXT" suffixes from FILE_NAME - df['FILE_NAME'] = df['FILE_NAME'].str.upper().str.replace(".TXT", "", regex=False) + df["FILE_NAME"] = df["FILE_NAME"].str.upper().str.replace(".TXT", "", regex=False) # Apply numeric conversion to non-CPT/diag/rev fields only for col in df.columns: - if "_CD" not in col and "CPT" not in col and "DIAG" not in col and "REV" not in col: + if ( + "_CD" not in col + and "CPT" not in col + and "DIAG" not in col + and "REV" not in col + ): df[col] = df[col].apply(convert_to_float) else: # For CPT/diag/rev fields, apply string conversion AND convert | delimiters @@ -69,14 +91,16 @@ def testbed_preprocess(df): df = df.replace("N/A", "") # Normalize TIN and NPI columns - tin_columns = [col for col in df.columns if 'TIN' in col] - npi_columns = [col for col in df.columns if 'NPI' in col] + tin_columns = [col for col in df.columns if "TIN" in col] + npi_columns = [col for col in df.columns if "NPI" in col] for col in tin_columns + npi_columns: df[col] = df[col].str.replace("-", "", regex=False) df[col] = df[col].apply(normalize_tin_npi) df[col] = df[col].str.replace(" ", "", regex=False) - df["AUTO_RENEWAL_TERM"] = df['AUTO_RENEWAL_TERM'].str.replace("ONE YEAR", "1 YEAR", regex=False) + df["AUTO_RENEWAL_TERM"] = df["AUTO_RENEWAL_TERM"].str.replace( + "ONE YEAR", "1 YEAR", regex=False + ) # Ensure that all empty cells are filled with empty strings df = df.fillna("") @@ -99,9 +123,11 @@ def testbed_preprocess(df): def is_positive(val): return not string_utils.is_empty(val) + def is_negative(val): return string_utils.is_empty(val) + def normalize_term(text: str) -> str: """Normalize term text for comparison""" replacements = { @@ -119,86 +145,85 @@ def normalize_term(text: str) -> str: "YEAR(S)": "YEAR", "YEARS": "YEAR", "-YEAR": " YEAR", - " ": " " + " ": " ", } - + text = text.upper() for old, new in replacements.items(): text = text.replace(old, new) - + return text.strip() + def is_fuzzy_match(pred, truth): """Compare strings using fuzzy matching with preprocessing - + Args: pred: Predicted value truth: Ground truth value - + Returns: bool: True if strings match within threshold """ if is_negative(pred) or is_negative(truth): return False - + # Normalize both strings pred_norm = normalize_term(str(pred)) truth_norm = normalize_term(str(truth)) - + # First try exact match on normalized strings if pred_norm == truth_norm or pred_norm in truth_norm or truth_norm in pred_norm: return True - + # Then try fuzzy match match_ratio = fuzz.ratio(pred_norm, truth_norm) - + # # Debugging line # if match_ratio < 100 and field == "field of interest": # print(f"Fuzzy match ratio for '{pred_norm}' | '{truth_norm}': {match_ratio}") - + return match_ratio >= 80 def calculate_field_metrics(merged, field): """Calculate precision, recall, and accuracy for a single field using fuzzy matching. - + Args: merged: DataFrame containing predicted and truth values field: Name of field to evaluate - + Returns: tuple: (precision, recall, accuracy) """ # True Positive: both pred and truth are non-empty and fuzzy match >= 90% TP = sum( - is_positive(pred) and - is_positive(truth) and - is_fuzzy_match(pred, truth) - for pred, truth in zip(merged[f'{field}_pred'], merged[f'{field}_truth']) + is_positive(pred) and is_positive(truth) and is_fuzzy_match(pred, truth) + for pred, truth in zip(merged[f"{field}_pred"], merged[f"{field}_truth"]) ) # False Positive: pred is non-empty, truth is empty or no fuzzy match FP = sum( - is_positive(pred) and - (is_negative(truth) or not is_fuzzy_match(pred, truth)) - for pred, truth in zip(merged[f'{field}_pred'], merged[f'{field}_truth']) + is_positive(pred) and (is_negative(truth) or not is_fuzzy_match(pred, truth)) + for pred, truth in zip(merged[f"{field}_pred"], merged[f"{field}_truth"]) ) # True Negative: both pred and truth are empty TN = sum( is_negative(pred) and is_negative(truth) - for pred, truth in zip(merged[f'{field}_pred'], merged[f'{field}_truth']) + for pred, truth in zip(merged[f"{field}_pred"], merged[f"{field}_truth"]) ) # False Negative: pred is empty, truth is non-empty FN = sum( is_negative(pred) and is_positive(truth) - for pred, truth in zip(merged[f'{field}_pred'], merged[f'{field}_truth']) + for pred, truth in zip(merged[f"{field}_pred"], merged[f"{field}_truth"]) ) FN_list = [ - truth for pred, truth in zip(merged[f'{field}_pred'], merged[f'{field}_truth']) + truth + for pred, truth in zip(merged[f"{field}_pred"], merged[f"{field}_truth"]) if is_negative(pred) and is_positive(truth) ] @@ -215,21 +240,20 @@ def evaluate_provider_fields_separately(testbed_df, results_df, verbose=False): print("\n*************** Order-Independent Provider Analysis ****************") # Initialize metrics for each field type metrics = { - "TIN": {'tp' : 0, 'fp': 0, 'fn': 0, 'accuracy_file': []}, - "NPI": {'tp' : 0, 'fp': 0, 'fn': 0, 'accuracy_file': []}, - "NAME": {'tp' : 0, 'fp': 0, 'fn': 0, 'accuracy_file': []} + "TIN": {"tp": 0, "fp": 0, "fn": 0, "accuracy_file": []}, + "NPI": {"tp": 0, "fp": 0, "fn": 0, "accuracy_file": []}, + "NAME": {"tp": 0, "fp": 0, "fn": 0, "accuracy_file": []}, } - if verbose: # instantiate provider_metrics.txt if verbose + if verbose: # instantiate provider_metrics.txt if verbose csv_output_dicts = [] with open("provider_metrics.txt", "w") as f: f.write("Provider Metrics\n") - - for file in testbed_df['FILE_NAME'].unique(): + for file in testbed_df["FILE_NAME"].unique(): # Get rows for this file - testbed_file = testbed_df[testbed_df['FILE_NAME'] == file] - results_file = results_df[results_df['FILE_NAME'] == file] + testbed_file = testbed_df[testbed_df["FILE_NAME"] == file] + results_file = results_df[results_df["FILE_NAME"] == file] # Extract all TINs, NPIs, and NAMEs for ground truth truth_tins = [] @@ -240,21 +264,23 @@ def evaluate_provider_fields_separately(testbed_df, results_df, verbose=False): for col_type, truth_list in [ ("PROV_GROUP_TIN", truth_tins), ("PROV_GROUP_NPI", truth_npis), - ("PROV_GROUP_NAME_FULL", truth_names) + ("PROV_GROUP_NAME_FULL", truth_names), ]: - + value = testbed_file[col_type].iloc[0] values = [] pipe_split = value.split("|") for item in pipe_split: - if col_type in ["PROV_GROUP_NPI", "PROV_GROUP_TIN"] and "," in item: # further split by comma, only if col_type is PROV_GROUP_NPI or PROV_GROUP_TIN + if ( + col_type in ["PROV_GROUP_NPI", "PROV_GROUP_TIN"] and "," in item + ): # further split by comma, only if col_type is PROV_GROUP_NPI or PROV_GROUP_TIN for comma_item in item.split(","): comma_item = comma_item.strip() if comma_item: values.append(comma_item) elif item.strip(): values.append(item.strip()) - + # Now append all values to the list for v in values: if v and v != "UNKNOWN": @@ -264,49 +290,49 @@ def evaluate_provider_fields_separately(testbed_df, results_df, verbose=False): for col_type, truth_list in [ ("PROV_OTHER_TIN", truth_tins), ("PROV_OTHER_NPI", truth_npis), - ("PROV_OTHER_NAME_FULL", truth_names) + ("PROV_OTHER_NAME_FULL", truth_names), ]: if not string_utils.is_empty(testbed_file[col_type].iloc[0]): values = testbed_file[col_type].iloc[0].split("|") - if len(values) == 1: # possibly delimited by comma + if len(values) == 1: # possibly delimited by comma values = testbed_file[col_type].iloc[0].split(",") for value in values: value = value.strip() if value: truth_list.append(value) - + # Similarly, extract all TINs, NPIs, and NAMEs for predictions pred_tins = [] pred_npis = [] pred_names = [] # Add group provider info - value = results_file['PROV_GROUP_TIN'].iloc[0] + value = results_file["PROV_GROUP_TIN"].iloc[0] pred_tins.append(value) - value = results_file['PROV_GROUP_NPI'].iloc[0] + value = results_file["PROV_GROUP_NPI"].iloc[0] pred_npis.append(value) - value = results_file['PROV_GROUP_NAME_FULL'].iloc[0] + value = results_file["PROV_GROUP_NAME_FULL"].iloc[0] pred_names.append(value) # Add other provider info for col_type, pred_list in [ ("PROV_OTHER_TIN", pred_tins), ("PROV_OTHER_NPI", pred_npis), - ("PROV_OTHER_NAME_FULL", pred_names) + ("PROV_OTHER_NAME_FULL", pred_names), ]: values = results_file[col_type].iloc[0].split("|") - if len(values) == 1: # possibly delimited by comma + if len(values) == 1: # possibly delimited by comma values = results_file[col_type].iloc[0].split(",") for value in values: value = value.strip() pred_list.append(value) - file_metrics = {} # Store file-specific metrics for verbose output + file_metrics = {} # Store file-specific metrics for verbose output # Calculate metrics for each field type and print verbose output if requested for field_type, truth_list, pred_list in [ ("TIN", truth_tins, pred_tins), ("NPI", truth_npis, pred_npis), - ("NAME", truth_names, pred_names) + ("NAME", truth_names, pred_names), ]: # Count occurrences in lists, excluding empty strings and "UNKNOWN" truth_counter = Counter([x for x in truth_list if x and x != "UNKNOWN"]) @@ -325,17 +351,21 @@ def evaluate_provider_fields_separately(testbed_df, results_df, verbose=False): pred_set = set(filtered_pred_list) # Compute metrics using set operations - file_tp = len(truth_set.intersection(pred_set)) # True Positives - file_fp = len(pred_set - truth_set) # False Positives - file_fn = len(truth_set - pred_set) # False Negatives + file_tp = len(truth_set.intersection(pred_set)) # True Positives + file_fp = len(pred_set - truth_set) # False Positives + file_fn = len(truth_set - pred_set) # False Negatives # Calculate accuracy for this file - file_accuracy = file_tp / (file_tp + file_fp + file_fn) if (file_tp + file_fp + file_fn) > 0 else 1 + file_accuracy = ( + file_tp / (file_tp + file_fp + file_fn) + if (file_tp + file_fp + file_fn) > 0 + else 1 + ) # Update overall metrics - metrics[field_type]['tp'] += file_tp - metrics[field_type]['fp'] += file_fp - metrics[field_type]['fn'] += file_fn - metrics[field_type]['accuracy_file'].append(file_accuracy) + metrics[field_type]["tp"] += file_tp + metrics[field_type]["fp"] += file_fp + metrics[field_type]["fn"] += file_fn + metrics[field_type]["accuracy_file"].append(file_accuracy) # Store file-specific metrics for verbose output file_metrics[field_type] = { @@ -344,7 +374,7 @@ def evaluate_provider_fields_separately(testbed_df, results_df, verbose=False): "file_tp": file_tp, "file_fp": file_fp, "file_fn": file_fn, - "file_accuracy": file_accuracy + "file_accuracy": file_accuracy, } if verbose: @@ -353,20 +383,38 @@ def evaluate_provider_fields_separately(testbed_df, results_df, verbose=False): with open("provider_metrics.txt", "a") as f: f.write(f"File: {file}\n") - f.write(f"Total TINs: {len(set([tin for tin in truth_tins if tin and tin != 'UNKNOWN']))}, Predicted TINs: {len(set([tin for tin in pred_tins if tin and tin != 'UNKNOWN']))}\n") - f.write(f"Total NPIs: {len(set([npi for npi in truth_npis if npi and npi != 'UNKNOWN']))}, Predicted NPIs: {len(set([npi for npi in pred_npis if npi and npi != 'UNKNOWN']))}\n") - f.write(f"Total Names: {len(set([name for name in truth_names if name and name != 'UNKNOWN']))}, Predicted Names: {len(set([name for name in pred_names if name and name != 'UNKNOWN']))}\n") + f.write( + f"Total TINs: {len(set([tin for tin in truth_tins if tin and tin != 'UNKNOWN']))}, Predicted TINs: {len(set([tin for tin in pred_tins if tin and tin != 'UNKNOWN']))}\n" + ) + f.write( + f"Total NPIs: {len(set([npi for npi in truth_npis if npi and npi != 'UNKNOWN']))}, Predicted NPIs: {len(set([npi for npi in pred_npis if npi and npi != 'UNKNOWN']))}\n" + ) + f.write( + f"Total Names: {len(set([name for name in truth_names if name and name != 'UNKNOWN']))}, Predicted Names: {len(set([name for name in pred_names if name and name != 'UNKNOWN']))}\n" + ) f.write("-" * 50 + "\n") for field_type in metrics: csv_output_dict = {} csv_output_dict["Filename"] = file - csv_output_dict["Test Bed TINs"] = len(set([tin for tin in truth_tins if tin and tin != 'UNKNOWN'])) - csv_output_dict["Test Bed NPIs"] = len(set([npi for npi in truth_npis if npi and npi != 'UNKNOWN'])) - csv_output_dict["Test Bed Names"] = len(set([name for name in truth_names if name and name != 'UNKNOWN'])) - csv_output_dict["Predicted TINs"] = len(set([tin for tin in pred_tins if tin and tin != 'UNKNOWN'])) - csv_output_dict["Predicted NPIs"] = len(set([npi for npi in pred_npis if npi and npi != 'UNKNOWN'])) - csv_output_dict["Predicted Names"] = len(set([name for name in pred_names if name and name != 'UNKNOWN'])) + csv_output_dict["Test Bed TINs"] = len( + set([tin for tin in truth_tins if tin and tin != "UNKNOWN"]) + ) + csv_output_dict["Test Bed NPIs"] = len( + set([npi for npi in truth_npis if npi and npi != "UNKNOWN"]) + ) + csv_output_dict["Test Bed Names"] = len( + set([name for name in truth_names if name and name != "UNKNOWN"]) + ) + csv_output_dict["Predicted TINs"] = len( + set([tin for tin in pred_tins if tin and tin != "UNKNOWN"]) + ) + csv_output_dict["Predicted NPIs"] = len( + set([npi for npi in pred_npis if npi and npi != "UNKNOWN"]) + ) + csv_output_dict["Predicted Names"] = len( + set([name for name in pred_names if name and name != "UNKNOWN"]) + ) print_lines = [ f"Field Type: {field_type}", f"Ground Truth: {dict(file_metrics[field_type]['truth_counter'])}", @@ -377,68 +425,127 @@ def evaluate_provider_fields_separately(testbed_df, results_df, verbose=False): f"File Accuracy: {file_metrics[field_type]['file_accuracy']}", f"Precision: {file_metrics[field_type]['file_tp'] / (file_metrics[field_type]['file_tp'] + file_metrics[field_type]['file_fp']) if (file_metrics[field_type]['file_tp'] + file_metrics[field_type]['file_fp']) > 0 else 0}", f"Recall: {file_metrics[field_type]['file_tp'] / (file_metrics[field_type]['file_tp'] + file_metrics[field_type]['file_fn']) if (file_metrics[field_type]['file_tp'] + file_metrics[field_type]['file_fn']) > 0 else 0}", - "-" * 50 + "-" * 50, ] csv_output_dict["field_type"] = field_type - csv_output_dict["Ground Truth"] = dict(file_metrics[field_type]['truth_counter']).copy() - csv_output_dict["Predictions"] = dict(file_metrics[field_type]['pred_counter']).copy() - csv_output_dict["True Positives"] = file_metrics[field_type]['file_tp'] - csv_output_dict["False Positives"] = file_metrics[field_type]['file_fp'] - csv_output_dict["False Negatives"] = file_metrics[field_type]['file_fn'] - csv_output_dict["File Accuracy"] = file_metrics[field_type]['file_accuracy'] - csv_output_dict["Precision"] = file_metrics[field_type]['file_tp'] / (file_metrics[field_type]['file_tp'] + file_metrics[field_type]['file_fp']) if (file_metrics[field_type]['file_tp'] + file_metrics[field_type]['file_fp']) > 0 else 0 - csv_output_dict["Recall"] = file_metrics[field_type]['file_tp'] / (file_metrics[field_type]['file_tp'] + file_metrics[field_type]['file_fn']) if (file_metrics[field_type]['file_tp'] + file_metrics[field_type]['file_fn']) > 0 else 0 + csv_output_dict["Ground Truth"] = dict( + file_metrics[field_type]["truth_counter"] + ).copy() + csv_output_dict["Predictions"] = dict( + file_metrics[field_type]["pred_counter"] + ).copy() + csv_output_dict["True Positives"] = file_metrics[field_type]["file_tp"] + csv_output_dict["False Positives"] = file_metrics[field_type]["file_fp"] + csv_output_dict["False Negatives"] = file_metrics[field_type]["file_fn"] + csv_output_dict["File Accuracy"] = file_metrics[field_type][ + "file_accuracy" + ] + csv_output_dict["Precision"] = ( + file_metrics[field_type]["file_tp"] + / ( + file_metrics[field_type]["file_tp"] + + file_metrics[field_type]["file_fp"] + ) + if ( + file_metrics[field_type]["file_tp"] + + file_metrics[field_type]["file_fp"] + ) + > 0 + else 0 + ) + csv_output_dict["Recall"] = ( + file_metrics[field_type]["file_tp"] + / ( + file_metrics[field_type]["file_tp"] + + file_metrics[field_type]["file_fn"] + ) + if ( + file_metrics[field_type]["file_tp"] + + file_metrics[field_type]["file_fn"] + ) + > 0 + else 0 + ) csv_output_dicts.append(csv_output_dict) with open("provider_metrics.txt", "a") as f: f.write("\n".join(print_lines) + "\n") - + # Convert the list of dictionaries to a DataFrame csv_output_df = pd.DataFrame(csv_output_dicts) # Save the DataFrame to a CSV file csv_output_df.to_csv("provider_metrics.csv", index=False) - + # Calculate accuracy, precision, recall, and F1 for each field type results = {} for field_type, field_metrics in metrics.items(): - precision = field_metrics['tp'] / (field_metrics['tp'] + field_metrics['fp']) if (field_metrics['tp'] + field_metrics['fp']) > 0 else 0 - recall = field_metrics['tp'] / (field_metrics['tp'] + field_metrics['fn']) if (field_metrics['tp'] + field_metrics['fn']) > 0 else 0 - f1 = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0 + precision = ( + field_metrics["tp"] / (field_metrics["tp"] + field_metrics["fp"]) + if (field_metrics["tp"] + field_metrics["fp"]) > 0 + else 0 + ) + recall = ( + field_metrics["tp"] / (field_metrics["tp"] + field_metrics["fn"]) + if (field_metrics["tp"] + field_metrics["fn"]) > 0 + else 0 + ) + f1 = ( + (2 * precision * recall) / (precision + recall) + if (precision + recall) > 0 + else 0 + ) results[field_type] = { "precision": precision, "recall": recall, "f1": f1, - "tp": field_metrics['tp'], - "fp": field_metrics['fp'], - "fn": field_metrics['fn'], - "average_accuracy": sum(field_metrics['accuracy_file']) / len(field_metrics['accuracy_file']) if field_metrics['accuracy_file'] else 0 + "tp": field_metrics["tp"], + "fp": field_metrics["fp"], + "fn": field_metrics["fn"], + "average_accuracy": ( + sum(field_metrics["accuracy_file"]) + / len(field_metrics["accuracy_file"]) + if field_metrics["accuracy_file"] + else 0 + ), } if verbose: print("Verbose output written to provider_metrics.txt") - - # Display the results - provider_metrics_df = pd.DataFrame([ - { - 'Field': f"PROV_OTHER_{field_type}", - 'Precision': metrics['precision'], - 'Recall': metrics['recall'], - 'F1': metrics['f1'], - 'TP': metrics['tp'], - 'FP': metrics['fp'], - 'FN': metrics['fn'], - 'average_accuracy': metrics['average_accuracy'], - } - for field_type, metrics in results.items() - ]) - print(provider_metrics_df.to_string( - index=False, - float_format=lambda x: '{:.2f}'.format(x) if pd.notnull(x) else 'Not found', - justify='left', - col_space={'Field': 15, 'Precision': 12, 'Recall': 12, 'F1': 12, 'TP': 8, 'FP': 8, 'FN': 8} - )) - + # Display the results + provider_metrics_df = pd.DataFrame( + [ + { + "Field": f"PROV_OTHER_{field_type}", + "Precision": metrics["precision"], + "Recall": metrics["recall"], + "F1": metrics["f1"], + "TP": metrics["tp"], + "FP": metrics["fp"], + "FN": metrics["fn"], + "average_accuracy": metrics["average_accuracy"], + } + for field_type, metrics in results.items() + ] + ) + + print( + provider_metrics_df.to_string( + index=False, + float_format=lambda x: "{:.2f}".format(x) if pd.notnull(x) else "Not found", + justify="left", + col_space={ + "Field": 15, + "Precision": 12, + "Recall": 12, + "F1": 12, + "TP": 8, + "FP": 8, + "FN": 8, + }, + ) + ) + return results, provider_metrics_df @@ -457,32 +564,47 @@ def match_rows(fields, labels_df, predictions_df, N): # Check equivalent after cleaning if label == prediction: return True - + # Check if lists match label_list = [] for part in label.split("|"): label_list.extend(part.split(",")) - label_list = [term.strip() for term in label_list if not string_utils.is_empty(term.strip())] + label_list = [ + term.strip() + for term in label_list + if not string_utils.is_empty(term.strip()) + ] prediction_list = [] for part in prediction.split("|"): prediction_list.extend(part.split(",")) - prediction_list = [term.strip() for term in prediction_list if not string_utils.is_empty(term.strip())] + prediction_list = [ + term.strip() + for term in prediction_list + if not string_utils.is_empty(term.strip()) + ] # If all items in the lists match, regardless of order, return True if len(label_list) == len(prediction_list): label_counter = Counter(label_list) prediction_counter = Counter(prediction_list) - if all(label_counter[term] == prediction_counter[term] for term in label_counter): + if all( + label_counter[term] == prediction_counter[term] + for term in label_counter + ): return True - + return False - confusion_matrix = {field_name: {"tp": 0, "fp": 0, "unmatched_labels": []} for field_name in fields} + confusion_matrix = { + field_name: {"tp": 0, "fp": 0, "unmatched_labels": []} for field_name in fields + } # Check for completely blank fields and handle them separately for field in fields: labels_all_blank = string_utils.is_empty(labels_df[field], pd_mask=True).all() - preds_all_blank = string_utils.is_empty(predictions_df[field], pd_mask=True).all() + preds_all_blank = string_utils.is_empty( + predictions_df[field], pd_mask=True + ).all() if labels_all_blank and preds_all_blank: # Perfect agreement on blank field @@ -492,11 +614,11 @@ def match_rows(fields, labels_df, predictions_df, N): confusion_matrix[field]["unmatched_labels"] = [] # Skip this field in the regular matching process fields = [f for f in fields if f != field] - + # Only proceed with matching if there are fields less to process if not fields: return confusion_matrix - + # Track which label rows have been matched matched_label_indices = set() available_predictions = set(predictions_df.index) @@ -516,11 +638,11 @@ def match_rows(fields, labels_df, predictions_df, N): # Find the best match among available predictions for prediction_idx in available_predictions: prediction_row = predictions_df.loc[prediction_idx] - + # Count matching fields for this pair field_counter_dict = {field_name: {"tp": 0} for field_name in fields} num_matches = 0 - + for field in fields: if is_equivalent(label_row[field], prediction_row[field]): num_matches += 1 @@ -530,7 +652,7 @@ def match_rows(fields, labels_df, predictions_df, N): max_matches = num_matches best_match_idx = prediction_idx best_matches = field_counter_dict - + # If we found a good enough match if max_matches >= num_columns_to_match and best_match_idx is not None: # Mark this label row as matched @@ -541,31 +663,44 @@ def match_rows(fields, labels_df, predictions_df, N): for field in fields: confusion_matrix[field]["tp"] += best_matches[field]["tp"] if best_matches[field]["tp"] == 0: - confusion_matrix[field]["unmatched_labels"].append((label_idx, label_row[field])) - + confusion_matrix[field]["unmatched_labels"].append( + (label_idx, label_row[field]) + ) + # Calculate FP and FN based on the number of matched fields for field in fields: # FN = number of label rows where this field wasn't matched - confusion_matrix[field]['fn'] = len(labels_df) - confusion_matrix[field]['tp'] + confusion_matrix[field]["fn"] = len(labels_df) - confusion_matrix[field]["tp"] # FP = number of prediction rows where this field wasn't matched - confusion_matrix[field]['fp'] = len(predictions_df) - confusion_matrix[field]['tp'] + confusion_matrix[field]["fp"] = ( + len(predictions_df) - confusion_matrix[field]["tp"] + ) # add unmatched labels to the confusion matrix for cases where whole rows were unmatched if list(set(labels_df.index) - set(matched_label_indices)): confusion_matrix[field]["unmatched_labels"].append(("|", "|")) for idx in list(set(labels_df.index) - set(matched_label_indices)): - confusion_matrix[field]["unmatched_labels"].append((idx, labels_df.loc[idx, field])) - + confusion_matrix[field]["unmatched_labels"].append( + (idx, labels_df.loc[idx, field]) + ) + return confusion_matrix -def compare_term_extraction_results(file_name: str, predictions_df: pd.DataFrame, gt_df: pd.DataFrame, comparison_columns: list[str] = ['SERVICE_TERM', 'REIMB_TERM'], text_threshold=65): + +def compare_term_extraction_results( + file_name: str, + predictions_df: pd.DataFrame, + gt_df: pd.DataFrame, + comparison_columns: list[str] = ["SERVICE_TERM", "REIMB_TERM"], + text_threshold=65, +): """Compare term extraction results between predictions and ground truth. This function was originally written to evaluate the results of the Methodology Primary process, - which extracts `SERVICE_TERM` and `REIMB_TERM` from the text. + which extracts `SERVICE_TERM` and `REIMB_TERM` from the text. This is a 1:n comparison, meaning that the ground truth and predictions can have different - numbers of rows for the same file. - + numbers of rows for the same file. + Args: - file_name (str): The name of the file being processed. This is to filter each + file_name (str): The name of the file being processed. This is to filter each individual dataframe to the rows that are relevant to the file. predictions_df (pd.DataFrame): The dataframe containing the predictions. gt_df (pd.DataFrame): The dataframe containing the ground truth labels. @@ -583,61 +718,73 @@ def compare_term_extraction_results(file_name: str, predictions_df: pd.DataFrame - error: Error message if columns are missing (only included if error occurs) """ # Step 1: Filter dataframes to only rows for the current file - pred_rows = predictions_df[predictions_df['FILE_NAME'] == file_name].copy() - gt_rows = gt_df[gt_df['FILE_NAME'] == file_name].copy() + pred_rows = predictions_df[predictions_df["FILE_NAME"] == file_name].copy() + gt_rows = gt_df[gt_df["FILE_NAME"] == file_name].copy() # Step 2: Validate that the comparison columns exist in both dataframes for col in comparison_columns: if col not in pred_rows.columns or col not in gt_rows.columns: return {"error": f"Column {col} not found in one of the dataframes."} - + # Step 3: normalize text by converting to lowercase for case-insensitive comparison - pred_rows[comparison_columns] = pred_rows[comparison_columns].apply(lambda x: x.str.lower()) - gt_rows[comparison_columns] = gt_rows[comparison_columns].apply(lambda x: x.str.lower()) + pred_rows[comparison_columns] = pred_rows[comparison_columns].apply( + lambda x: x.str.lower() + ) + gt_rows[comparison_columns] = gt_rows[comparison_columns].apply( + lambda x: x.str.lower() + ) # Step 4: extract unique tuples of the comparison columns from both dataframes # This removes duplicates and converts to lists for indexed access - pred_tuples = list(set(map(tuple, pred_rows[comparison_columns].drop_duplicates().values))) - gt_tuples = list(set(map(tuple, gt_rows[comparison_columns].drop_duplicates().values))) + pred_tuples = list( + set(map(tuple, pred_rows[comparison_columns].drop_duplicates().values)) + ) + gt_tuples = list( + set(map(tuple, gt_rows[comparison_columns].drop_duplicates().values)) + ) # Step 5: Initialize tracking sets for the matching algorithm - matched_pred = set() # Indices of prediction tuples that have been matched - matched_gt = set() # Indices of ground truth tuples that have been matched - common_pairs = set() # Indices that were successfully matched + matched_pred = set() # Indices of prediction tuples that have been matched + matched_gt = set() # Indices of ground truth tuples that have been matched + common_pairs = set() # Indices that were successfully matched # Step 6: Main matching loop - find best fuzzy match for each prediction for i, pred_tuple in enumerate(pred_tuples): # Initialize variables to track best match for this prediction - best_match= None # The GT tuple that matches best - best_score = 0 # The similarity score of the best match - best_gt_idx = None # The index of the best matching GT tuple - + best_match = None # The GT tuple that matches best + best_score = 0 # The similarity score of the best match + best_gt_idx = None # The index of the best matching GT tuple + # Step 7: Compare this prediction against all unmatched ground truth tuples for j, gt_tuple in enumerate(gt_tuples): - if j in matched_gt: # Skip already matched ground truth tuples + if j in matched_gt: # Skip already matched ground truth tuples continue # Step 8: Check if all columns in the tuple pair match using fuzzy logic - total_score = 0 # Sum of similarity scores across all columns in the tuple - all_match = True # Flag to track if all columns pass the threshold + total_score = 0 # Sum of similarity scores across all columns in the tuple + all_match = True # Flag to track if all columns pass the threshold # Compare each column value in the tuple pair for pred_val, gt_val in zip(pred_tuple, gt_tuple): # Use fuzzy matching that treats numbers exactly but text fuzzily - is_match, score = fuzzy_match_except_numbers(pred_val, gt_val, text_threshold=text_threshold) + is_match, score = fuzzy_match_except_numbers( + pred_val, gt_val, text_threshold=text_threshold + ) if not is_match: - all_match = False # If any column fails, the whole tuple fails + all_match = False # If any column fails, the whole tuple fails break - total_score += score # Accumulates scores for successful matches + total_score += score # Accumulates scores for successful matches # Step 9: If all columns match, check if this is the best match so far if all_match: - avg_score = total_score / len(pred_tuple) # Calculate average similarity + avg_score = total_score / len( + pred_tuple + ) # Calculate average similarity if avg_score > best_score: best_score = avg_score best_match = gt_tuple best_gt_idx = j - + # Step 10: if we found a valid match, record it and prevent re-matching if best_match is not None: matched_pred.add(i) @@ -664,12 +811,16 @@ def compare_term_extraction_results(file_name: str, predictions_df: pd.DataFrame "matched_count": len(common_pairs), "false_negatives_count": len(false_negatives), "false_positives_count": len(false_positives), - # Convert tuples to readable strings for Excel - "common_pairs_readable": "\n".join([" | ".join(map(str, pair)) for pair in common_pairs]), - "false_negatives_readable": "\n".join([" | ".join(map(str, pair)) for pair in false_negatives]), - "false_positives_readable": "\n".join([" | ".join(map(str, pair)) for pair in false_positives]), - + "common_pairs_readable": "\n".join( + [" | ".join(map(str, pair)) for pair in common_pairs] + ), + "false_negatives_readable": "\n".join( + [" | ".join(map(str, pair)) for pair in false_negatives] + ), + "false_positives_readable": "\n".join( + [" | ".join(map(str, pair)) for pair in false_positives] + ), # Keep original tuples for programmatic use if needed "common_pairs": common_pairs, "false_negatives": false_negatives, @@ -678,49 +829,51 @@ def compare_term_extraction_results(file_name: str, predictions_df: pd.DataFrame "all_testbed_pairs": set(gt_tuples), } + def fuzzy_match_except_numbers(str1, str2, text_threshold=80): """ Compare two strings with fuzzy matching for text but exact matching for numbers. - + Parameters: - str1, str2: Strings to compare - text_threshold: Minimum similarity score (0-100) for text parts to be considered matching - + Returns: - Boolean: True if strings match according to criteria, False otherwise - Float: Overall similarity score """ if not str1 or not str2: return str1 == str2, 0 if str1 != str2 else 100 - + # Extract numbers separately - numbers1 = re.findall(r'-?\d+\.?\d*', str1) # Handles decimals and negatives - numbers2 = re.findall(r'-?\d+\.?\d*', str2) - + numbers1 = re.findall(r"-?\d+\.?\d*", str1) # Handles decimals and negatives + numbers2 = re.findall(r"-?\d+\.?\d*", str2) + # If numbers don't match exactly, return False if numbers1 != numbers2: return False, 0 - + # Remove numbers and clean text for fuzzy comparison - text1 = re.sub(r'-?\d+\.?\d*', '', str1).strip() - text2 = re.sub(r'-?\d+\.?\d*', '', str2).strip() + text1 = re.sub(r"-?\d+\.?\d*", "", str1).strip() + text2 = re.sub(r"-?\d+\.?\d*", "", str2).strip() # Remove extra punctuation and normalize whitespace - text1 = re.sub(r'[^\w\s]', ' ', text1) - text2 = re.sub(r'[^\w\s]', ' ', text2) - text1 = ' '.join(text1.split()) # Normalize whitespace - text2 = ' '.join(text2.split()) - + text1 = re.sub(r"[^\w\s]", " ", text1) + text2 = re.sub(r"[^\w\s]", " ", text2) + text1 = " ".join(text1.split()) # Normalize whitespace + text2 = " ".join(text2.split()) + # If no text remains after cleaning, check if both are empty if not text1 and not text2: return True, 100.0 - + # Use fuzzy matching for text parts similarity = fuzz.ratio(text1.lower(), text2.lower()) return similarity >= text_threshold, similarity + def calculate_precision_recall(accuracies): - """ FOR ONE-TO-N FIELDS + """FOR ONE-TO-N FIELDS Calculate precision and recall for each file and overall metrics for all fields. Args: @@ -733,7 +886,11 @@ def calculate_precision_recall(accuracies): - overall_metrics_df: Overall precision, recall, and F1 score for all fields. """ precision_dicts, recall_dicts = [], [] - overall_metrics = {field: {"tp": 0, "fp": 0, "fn": 0} for field in accuracies[0] if field != "Filename"} + overall_metrics = { + field: {"tp": 0, "fp": 0, "fn": 0} + for field in accuracies[0] + if field != "Filename" + } for confusion_matrix in accuracies: filename = confusion_matrix["Filename"] @@ -759,7 +916,9 @@ def calculate_precision_recall(accuracies): precision_dict[field] = precision recall_dict[field] = recall - recall_dict_value[field+"_missed"] = confusion_matrix[field]["unmatched_labels"] + recall_dict_value[field + "_missed"] = confusion_matrix[field][ + "unmatched_labels" + ] precision_dicts.append(precision_dict) recall_dicts.append(recall_dict | recall_dict_value) @@ -773,17 +932,23 @@ def calculate_precision_recall(accuracies): precision = tp / (tp + fp) if (tp + fp) > 0 else 0 recall = tp / (tp + fn) if (tp + fn) > 0 else 0 - f1 = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0 + f1 = ( + (2 * precision * recall) / (precision + recall) + if (precision + recall) > 0 + else 0 + ) - overall_metrics_list.append({ - "Field": field, - "tp": tp, - "fp": fp, - "fn": fn, - "prec": precision, - "rec": recall, - "f1": f1 - }) + overall_metrics_list.append( + { + "Field": field, + "tp": tp, + "fp": fp, + "fn": fn, + "prec": precision, + "rec": recall, + "f1": f1, + } + ) # Convert overall metrics to a DataFrame overall_metrics_df = pd.DataFrame(overall_metrics_list).set_index("Field") @@ -795,18 +960,16 @@ def calculate_precision_recall(accuracies): return precision_df, recall_df, overall_metrics_df - ########## TESTBED POSTPROCESSING CODE ########## # These functions are to take MCS's test bed that they provided and apply # (most of) the same postprocessing steps that we apply to the results of the model. - def group_providers_into_json_array(df): """Groups provider information from a DataFrame into a JSON array format and appends it as a new column. - This function processes a DataFrame containing provider information, groups the data by the "FILE_NAME" column, - and creates a JSON array for each group. The JSON array includes details about the group provider and other + This function processes a DataFrame containing provider information, groups the data by the "FILE_NAME" column, + and creates a JSON array for each group. The JSON array includes details about the group provider and other associated providers. The resulting JSON array is added as a new column, "PROV_INFO_JSON", to the DataFrame. Args: @@ -820,19 +983,19 @@ def group_providers_into_json_array(df): - "PROV_OTHER_NAME_FULL": Pipe-separated full names of other providers. Returns: - pandas.DataFrame: A modified DataFrame with an additional column "PROV_INFO_JSON", + pandas.DataFrame: A modified DataFrame with an additional column "PROV_INFO_JSON", which contains a JSON array for each "FILE_NAME" group. Each JSON array includes: - "TIN": TIN of the provider (or "N/A" if not available). - "NPI": NPI of the provider (or "N/A" if not available). - "NAME": Full name of the provider (or "N/A" if not available). - - "IS_GROUP": Boolean indicating whether the provider is the group provider (True) + - "IS_GROUP": Boolean indicating whether the provider is the group provider (True) or an associated provider (False). Notes: - Missing or NaN values in the input DataFrame are replaced with "N/A" in the JSON output. - - If the lengths of "PROV_OTHER_TIN", "PROV_OTHER_NPI", and "PROV_OTHER_NAME_FULL" are not equal, + - If the lengths of "PROV_OTHER_TIN", "PROV_OTHER_NPI", and "PROV_OTHER_NAME_FULL" are not equal, missing values are filled with "N/A" using itertools.zip_longest. - - If the OTHER columns are misaligned with one another, the function will NOT + - If the OTHER columns are misaligned with one another, the function will NOT be able to correct them. It will simply fill in the gaps with "N/A". """ @@ -846,17 +1009,20 @@ def group_providers_into_json_array(df): # Get the first row of the grouped DataFrame first_row = grouped_df.iloc[0] - # Extract TIN/NPI/NAME for group provider. group_tin = str(first_row["PROV_GROUP_TIN"]).replace("nan", "N/A") group_npi = str(first_row["PROV_GROUP_NPI"]).replace("nan", "N/A") group_name_full = str(first_row["PROV_GROUP_NAME_FULL"]) # Append the group provider's TIN/NPI/NAME to the list. - json_dict_list.append({ - "TIN": group_tin, - "NPI": group_npi, - "NAME": group_name_full, "IS_GROUP": True}) + json_dict_list.append( + { + "TIN": group_tin, + "NPI": group_npi, + "NAME": group_name_full, + "IS_GROUP": True, + } + ) # Create lists of TIN/NPI/NAME for other providers. # Split on pipe, then strip whitespace. @@ -870,20 +1036,18 @@ def group_providers_into_json_array(df): # Append each other provider's TIN/NPI/NAME to the list. # Use zip to iterate over the three lists in parallel. # IF the lengths of the lists are not equal, use itertools.zip_longest to fill in the gaps with N/A. - for tin, npi, name in itertools.zip_longest(other_tins, other_npis, other_names, fillvalue="N/A"): - json_dict_list.append({ - "TIN": tin, - "NPI": npi, - "NAME": name, - "IS_GROUP": False - }) + for tin, npi, name in itertools.zip_longest( + other_tins, other_npis, other_names, fillvalue="N/A" + ): + json_dict_list.append( + {"TIN": tin, "NPI": npi, "NAME": name, "IS_GROUP": False} + ) group_providers_dict[group] = json_dict_list # Add JSON data back to original DataFrame df["PROV_INFO_JSON"] = df["FILE_NAME"].map(group_providers_dict) - ## Reorder columns to place PROV_INFO_JSON right after PROV_OTHER_NAME_FULL cols = list(df.columns) # Find the position of PROV_OTHER_NAME_FULL @@ -897,6 +1061,7 @@ def group_providers_into_json_array(df): return df + def testbed_postprocess(df): """Postprocess the testbed DataFrame. @@ -908,84 +1073,93 @@ def testbed_postprocess(df): """ # Apply preprocessing steps df = testbed_preprocess(df) - + # Group providers into JSON array format df = group_providers_into_json_array(df) # Add CLIENT_NAME column - df['CLIENT_NAME'] = "AARETE_TESTBED" + df["CLIENT_NAME"] = "AARETE_TESTBED" # Reformat rate fields if "REIMB_FEE_RATE" in df.columns: # Format rate fields - df['REIMB_FEE_RATE'] = df['REIMB_FEE_RATE'].apply(investment_postprocessing_funcs.format_rate_fields_with_commas) + df["REIMB_FEE_RATE"] = df["REIMB_FEE_RATE"].apply( + postprocessing_funcs.format_rate_fields_with_commas + ) if "REIMB_PCT_RATE" in df.columns: - df['REIMB_PCT_RATE'] = df['REIMB_PCT_RATE'].apply(investment_postprocessing_funcs.format_rate_fields_with_commas) + df["REIMB_PCT_RATE"] = df["REIMB_PCT_RATE"].apply( + postprocessing_funcs.format_rate_fields_with_commas + ) for col in df.columns: if "_IND" in col: - df[col] = df[col].apply(investment_postprocessing_funcs.normalize_indicator_field) + df[col] = df[col].apply(postprocessing_funcs.normalize_indicator_field) # Normalize _IND fields and clean up TIN/NPI fields if "TIN" in col or "NPI" in col: - df[col] = df[col].apply(investment_postprocessing_funcs.remove_hyphens) + df[col] = df[col].apply(postprocessing_funcs.remove_hyphens) # Apply the flatten_singleton_string_list function if "_CD" in col and "CPT" not in col: - df[col] = df[col].apply(investment_postprocessing_funcs.flatten_singleton_string_list) + df[col] = df[col].apply(postprocessing_funcs.flatten_singleton_string_list) # Convert DATE to YYYY/MM/DD format if "_DT" in col or "DATE" in col: - df[col] = df[col].apply(investment_postprocessing_funcs.validate_and_reformat_date) + df[col] = df[col].apply(postprocessing_funcs.validate_and_reformat_date) # Normalize CPT fields if "CPT" in col: - df[col] = df[col].apply(investment_postprocessing_funcs.normalize_cpt_fields) - - df = investment_postprocessing_funcs.update_reimb_prov_name(df) + df[col] = df[col].apply(postprocessing_funcs.normalize_cpt_fields) # Add reimb_id - df = investment_postprocessing_funcs.generate_reimb_ids(df) + df = postprocessing_funcs.generate_reimb_ids(df) # Standardize output column order - this should ALWAYS be the final postprocessing step - df = investment_postprocessing_funcs.reorder_columns(df, COLUMN_ORDER) + df = postprocessing_funcs.reorder_columns(df, COLUMN_ORDER) return df + def get_df_stats(df): print("*************** Test Bed Stats ****************") print(f"{len(df['FILE_NAME'].unique())} contracts") print(f"{len(df.columns)} fields") - print(f"{(df == '').all().sum()} fields not populated:") # Count of completely empty fields + print( + f"{(df == '').all().sum()} fields not populated:" + ) # Count of completely empty fields print(f"{df.columns[(df == '').all()].tolist()}") # List of completely empty fields + def get_row_comparison(testbed, results): - row_comparison = pd.DataFrame(index=testbed['FILE_NAME'].unique()) - row_comparison['testbed'] = testbed.groupby('FILE_NAME').size() - row_comparison['results'] = results.groupby('FILE_NAME').size() + row_comparison = pd.DataFrame(index=testbed["FILE_NAME"].unique()) + row_comparison["testbed"] = testbed.groupby("FILE_NAME").size() + row_comparison["results"] = results.groupby("FILE_NAME").size() print("\n*************** Row Count Comparison ****************") - print(f"Files with different row counts: {(row_comparison['testbed'] != row_comparison['results']).sum()}") + print( + f"Files with different row counts: {(row_comparison['testbed'] != row_comparison['results']).sum()}" + ) return row_comparison + def get_consecutive_page_groups(pages) -> list: """Group consecutive page numbers together. e.g., [4, 5, 6, 8, 9] -> [[4, 5, 6], [8, 9]] - + """ if not pages: return [] - + # Convert to sorted list of integers page_nums = sorted([int(p) for p in pages if str(p).isdigit()]) if not page_nums: return [] - + groups = [] current_group = [page_nums[0]] for i in range(1, len(page_nums)): - if page_nums[i] == page_nums[i - 1] + 1: # consecutive pages + if page_nums[i] == page_nums[i - 1] + 1: # consecutive pages current_group.append(page_nums[i]) else: groups.append(current_group) @@ -993,6 +1167,7 @@ def get_consecutive_page_groups(pages) -> list: groups.append(current_group) # Append the last group return groups + def group_name(group): """Convert a group of consecutive pages to a readable name. E.g., [4, 5, 6] -> "4-6", [8, 9] -> "8-9", [13] -> "13" @@ -1002,6 +1177,7 @@ def group_name(group): else: return f"{group[0]}-{group[-1]}" + def normalize_page(page_str): """Normalize page numbers to integer strings for comparison (e.g., "4.0" -> "4", "4" -> "4", "13.0.0" -> "13")""" if pd.isna(page_str) or page_str == "": @@ -1012,22 +1188,27 @@ def normalize_page(page_str): except (ValueError, TypeError): return str(page_str).strip() + def get_page_comparison_df(testbed, results): - + print("\n*************** Page-Level Row Count Comparison ****************") if "EXHIBIT_PAGE" in testbed.columns and "EXHIBIT_PAGE" in results.columns: page_mismatches = [] - for file in testbed['FILE_NAME'].unique(): - testbed_file = testbed[testbed['FILE_NAME'] == file].copy() - results_file = results[results['FILE_NAME'] == file].copy() + for file in testbed["FILE_NAME"].unique(): + testbed_file = testbed[testbed["FILE_NAME"] == file].copy() + results_file = results[results["FILE_NAME"] == file].copy() # Normalize page numbers - testbed_file['EXHIBIT_PAGE_NORM'] = testbed_file['EXHIBIT_PAGE'].apply(normalize_page) - results_file['EXHIBIT_PAGE_NORM'] = results_file['EXHIBIT_PAGE'].apply(normalize_page) + testbed_file["EXHIBIT_PAGE_NORM"] = testbed_file["EXHIBIT_PAGE"].apply( + normalize_page + ) + results_file["EXHIBIT_PAGE_NORM"] = results_file["EXHIBIT_PAGE"].apply( + normalize_page + ) # Get unique pages from each dataset (excluding None/empty) - testbed_pages = set(testbed_file['EXHIBIT_PAGE_NORM'].dropna()) - results_pages = set(results_file['EXHIBIT_PAGE_NORM'].dropna()) + testbed_pages = set(testbed_file["EXHIBIT_PAGE_NORM"].dropna()) + results_pages = set(results_file["EXHIBIT_PAGE_NORM"].dropna()) testbed_pages.discard("") # Remove empty strings results_pages.discard("") # Remove empty strings @@ -1040,18 +1221,24 @@ def get_page_comparison_df(testbed, results): for group in testbed_groups: group_key = group_name(group) - count = sum(testbed_file['EXHIBIT_PAGE_NORM'].isin([str(p) for p in group])) + count = sum( + testbed_file["EXHIBIT_PAGE_NORM"].isin([str(p) for p in group]) + ) testbed_group_counts[group_key] = count - + results_group_counts = {} for group in results_groups: group_key = group_name(group) - count = sum(results_file['EXHIBIT_PAGE_NORM'].isin([str(p) for p in group])) + count = sum( + results_file["EXHIBIT_PAGE_NORM"].isin([str(p) for p in group]) + ) results_group_counts[group_key] = count - + # Compare group counts - all_groups = set(testbed_group_counts.keys()) | set(results_group_counts.keys()) + all_groups = set(testbed_group_counts.keys()) | set( + results_group_counts.keys() + ) group_comparison = {} has_mismatch = False @@ -1061,260 +1248,368 @@ def get_page_comparison_df(testbed, results): results_count = results_group_counts.get(group_key, 0) group_comparison[group_key] = { - 'testbed_count': testbed_count, - 'results_count': results_count, - 'difference': results_count - testbed_count + "testbed_count": testbed_count, + "results_count": results_count, + "difference": results_count - testbed_count, } if testbed_count != results_count: has_mismatch = True - - if has_mismatch: - page_mismatches.append({ - 'FILE_NAME': file, - 'group_comparison': group_comparison, - 'total_testbed_rows': len(testbed_file), - 'total_results_rows': len(results_file) - }) + if has_mismatch: + page_mismatches.append( + { + "FILE_NAME": file, + "group_comparison": group_comparison, + "total_testbed_rows": len(testbed_file), + "total_results_rows": len(results_file), + } + ) if page_mismatches: # Create detailed comparison DataFrame detailed_page_comparison = [] for file_data in page_mismatches: - file_name = file_data['FILE_NAME'] - for group_key, counts in file_data['group_comparison'].items(): - detailed_page_comparison.append({ - 'FILE_NAME': file_name, - 'PAGE_GROUP': group_key, - 'TESTBED_COUNT': counts['testbed_count'], - 'RESULTS_COUNT': counts['results_count'], - 'DIFFERENCE': counts['difference'] - }) + file_name = file_data["FILE_NAME"] + for group_key, counts in file_data["group_comparison"].items(): + detailed_page_comparison.append( + { + "FILE_NAME": file_name, + "PAGE_GROUP": group_key, + "TESTBED_COUNT": counts["testbed_count"], + "RESULTS_COUNT": counts["results_count"], + "DIFFERENCE": counts["difference"], + } + ) page_comparison_df = pd.DataFrame(detailed_page_comparison) # Sort by file name and first page number in group def sort_key(group_str): """Extract the first page number from a group string for sorting.""" - parts = group_str.split('-') - return int(parts[0]) if parts else float('inf') - page_comparison_df['SORT_KEY'] = page_comparison_df['PAGE_GROUP'].apply(sort_key) - page_comparison_df = page_comparison_df.sort_values(by=['FILE_NAME', 'SORT_KEY']).drop(columns=['SORT_KEY']) + parts = group_str.split("-") + return int(parts[0]) if parts else float("inf") + + page_comparison_df["SORT_KEY"] = page_comparison_df["PAGE_GROUP"].apply( + sort_key + ) + page_comparison_df = page_comparison_df.sort_values( + by=["FILE_NAME", "SORT_KEY"] + ).drop(columns=["SORT_KEY"]) # Summary statistics - total_missing_rows = page_comparison_df[page_comparison_df['DIFFERENCE'] < 0]['DIFFERENCE'].sum() - total_extra_rows = page_comparison_df[page_comparison_df['DIFFERENCE'] > 0]['DIFFERENCE'].sum() - pages_with_missing = (page_comparison_df['DIFFERENCE'] < 0).sum() - pages_with_extra = (page_comparison_df['DIFFERENCE'] > 0).sum() + total_missing_rows = page_comparison_df[ + page_comparison_df["DIFFERENCE"] < 0 + ]["DIFFERENCE"].sum() + total_extra_rows = page_comparison_df[page_comparison_df["DIFFERENCE"] > 0][ + "DIFFERENCE" + ].sum() + pages_with_missing = (page_comparison_df["DIFFERENCE"] < 0).sum() + pages_with_extra = (page_comparison_df["DIFFERENCE"] > 0).sum() print(f"Pages with missing rows in results: {pages_with_missing}") print(f"Pages with extra rows in results: {pages_with_extra}") print(f"Total missing rows: {abs(total_missing_rows)}") print(f"Total extra rows: {total_extra_rows}") - - else: + else: print("All files have matching row counts for consecutive page groups!") page_comparison_df = pd.DataFrame() else: - print("EXHIBIT_PAGE column not found in either testbed or results. Skipping page-level row count comparison.") + print( + "EXHIBIT_PAGE column not found in either testbed or results. Skipping page-level row count comparison." + ) page_comparison_df = pd.DataFrame() - + return page_comparison_df def get_one_to_one_analysis(testbed, results): print("\n*************** One-to-One Stats ****************") - one_to_one_fields = FieldSet(file_path=config.FIELD_JSON_PATH).filter(relationship="one_to_one").list_fields() - one_to_one_fields.extend(['PROV_GROUP_TIN', - 'PROV_GROUP_NPI', - 'PROV_GROUP_NAME_FULL']) - one_to_one_fields = [f for f in one_to_one_fields if f not in {'TIN', 'NPI', 'NAME', "CLIENT_NAME", "GLOBAL_LESSER_OF_STATEMENT"}] + one_to_one_fields = ( + FieldSet(file_path=config.FIELD_JSON_PATH) + .filter(relationship="one_to_one") + .list_fields() + ) + one_to_one_fields.extend( + ["PROV_GROUP_TIN", "PROV_GROUP_NPI", "PROV_GROUP_NAME_FULL"] + ) + one_to_one_fields = [ + f + for f in one_to_one_fields + if f not in {"TIN", "NPI", "NAME", "CLIENT_NAME", "GLOBAL_LESSER_OF_STATEMENT"} + ] # Create filtered dataframes with only one-to-one fields - one_to_one_columns = ['FILE_NAME'] + [f for f in one_to_one_fields if f in testbed.columns and f in results.columns] + one_to_one_columns = ["FILE_NAME"] + [ + f for f in one_to_one_fields if f in testbed.columns and f in results.columns + ] testbed_one_to_one = testbed[one_to_one_columns].drop_duplicates() results_one_to_one = results[one_to_one_columns].drop_duplicates() - metrics_df = pd.DataFrame(columns=['Field', 'Precision', 'Recall', 'Accuracy', 'FN_list']) - comparison_df = pd.DataFrame(index=testbed['FILE_NAME'].unique()) + metrics_df = pd.DataFrame( + columns=["Field", "Precision", "Recall", "Accuracy", "FN_list"] + ) + comparison_df = pd.DataFrame(index=testbed["FILE_NAME"].unique()) # for field in one_to_one_fields: - for field in ['PROV_GROUP_TIN']: - - if field in testbed.columns and field in results.columns and field != 'FILE_NAME': + for field in ["PROV_GROUP_TIN"]: + + if ( + field in testbed.columns + and field in results.columns + and field != "FILE_NAME" + ): # Merge and compare values (no need to drop_duplicates here since we already did) - merged = pd.merge(testbed_one_to_one[['FILE_NAME', field]], - results_one_to_one[['FILE_NAME', field]], - on='FILE_NAME', - suffixes=('_truth', '_pred')) - + merged = pd.merge( + testbed_one_to_one[["FILE_NAME", field]], + results_one_to_one[["FILE_NAME", field]], + on="FILE_NAME", + suffixes=("_truth", "_pred"), + ) # Normalize strings by stripping whitespace and converting to same case - merged[f"{field}_truth_norm"] = merged[f"{field}_truth"].astype(str).str.strip().str.upper() - merged[f"{field}_pred_norm"] = merged[f"{field}_pred"].astype(str).str.strip().str.upper() - + merged[f"{field}_truth_norm"] = ( + merged[f"{field}_truth"].astype(str).str.strip().str.upper() + ) + merged[f"{field}_pred_norm"] = ( + merged[f"{field}_pred"].astype(str).str.strip().str.upper() + ) + # Compare values using normalized strings - merged[field] = (merged[f"{field}_truth_norm"] == merged[f"{field}_pred_norm"]) & (merged[f"{field}_truth"] != '') + merged[field] = ( + merged[f"{field}_truth_norm"] == merged[f"{field}_pred_norm"] + ) & (merged[f"{field}_truth"] != "") # Drop duplicate rows - merged = merged.drop_duplicates(subset=['FILE_NAME', field]) + merged = merged.drop_duplicates(subset=["FILE_NAME", field]) # Add to comparison DataFrame try: - comparison_df[field] = merged.set_index('FILE_NAME')[field] + comparison_df[field] = merged.set_index("FILE_NAME")[field] except Exception as e: - print(f"\n❌ ERROR: Field '{field}' has inconsistent values for the same FILE_NAME", - f"This indicates a data quality issue in your testbed - one-to-one fields should have", - f"the same value for all rows of the same file.\n") - + print( + f"\n❌ ERROR: Field '{field}' has inconsistent values for the same FILE_NAME", + f"This indicates a data quality issue in your testbed - one-to-one fields should have", + f"the same value for all rows of the same file.\n", + ) + # Check for problematic files in the original data before any deduplication problem_files = [] - for file_name in testbed_one_to_one['FILE_NAME'].unique(): - file_data = testbed_one_to_one[testbed_one_to_one['FILE_NAME'] == file_name] + for file_name in testbed_one_to_one["FILE_NAME"].unique(): + file_data = testbed_one_to_one[ + testbed_one_to_one["FILE_NAME"] == file_name + ] unique_values = file_data[field].unique() if len(unique_values) > 1: problem_files.append((file_name, unique_values.tolist())) - + if problem_files: print("Problematic file(s) and ALL their conflicting values:") for file_name, values in problem_files: print(f"\nFile: {file_name}") print(f" All '{field}' values found: {values}") print(f" (Should all be the same value)") - - print(f"\n💡 Fix: Ensure all rows for the same file have identical '{field}' values in your testbed.") + + print( + f"\n💡 Fix: Ensure all rows for the same file have identical '{field}' values in your testbed." + ) print(f"Original pandas error: {e}") - - raise ValueError(f"Data quality error: Field '{field}' has inconsistent values for FILE_NAME. " - f"Please fix the testbed data (or the results data) before proceeding.") - + + raise ValueError( + f"Data quality error: Field '{field}' has inconsistent values for FILE_NAME. " + f"Please fix the testbed data (or the results data) before proceeding." + ) + # Calculate metrics - precision, recall, accuracy, FN_list = calculate_field_metrics(merged, field) - metrics_df.loc[len(metrics_df)] = [field, precision, recall, accuracy, FN_list] + precision, recall, accuracy, FN_list = calculate_field_metrics( + merged, field + ) + metrics_df.loc[len(metrics_df)] = [ + field, + precision, + recall, + accuracy, + FN_list, + ] else: metrics_df.loc[len(metrics_df)] = [field, None, None, None, None] comparison_df[field] = None # Display results with clean formatting - print(metrics_df[["Field", "Precision", "Recall", "Accuracy"]].to_string( - index=False, - float_format=lambda x: '{:.2f}'.format(x) if pd.notnull(x) else 'Not found', - justify='left', - col_space={'Field': 40, 'Precision': 15, 'Recall': 15, 'Accuracy': 15} - )) + print( + metrics_df[["Field", "Precision", "Recall", "Accuracy"]].to_string( + index=False, + float_format=lambda x: "{:.2f}".format(x) if pd.notnull(x) else "Not found", + justify="left", + col_space={"Field": 40, "Precision": 15, "Recall": 15, "Accuracy": 15}, + ) + ) return metrics_df, comparison_df - def get_one_to_n_analysis(testbed, results): print("\n*************** One-to-N Stats ****************") - one_to_n_fields = {"methodology_breakout" : [field for field in FieldSet(file_path=config.FIELD_JSON_PATH).filter(field_type="methodology_breakout").list_fields() if field != "GREATER_OF_IND"], - "fee_schedule_breakout" : ["AARETE_DERIVED_FEE_SCHEDULE", "AARETE_DERIVED_FEE_SCHEDULE_VERSION"], - "trigger_cap":["TRIGGER_CAP_THRESHOLD_AMT","TRIGGER_BASE_THRESHOLD"], - "rate_escalator":["RATE_ESCALATOR_IND","RATE_ESCALATOR_DESC","RATE_ESCALATOR_MAX_RATE_INC_PCT","RATE_ESCALATOR_RATE_CHANGE_TIMELINE"], - "addition":["ADDITION_DESC","ADDITION_MAX_PCT_RATE_INC","ADDITION_MAX_FEE_RATE_INC","ADDITION_RATE_CHANGE_TIMELINE","AARETE_DERIVED_ADDITION_RATE_CHANGE_TIMELINE"], - "dynamic_primary" : ["AARETE_DERIVED_LOB", "AARETE_DERIVED_PROGRAM", "AARETE_DERIVED_NETWORK", "AARETE_DERIVED_PRODUCT"], - "reimb_prov_info" : ["REIMB_PROV_TIN", "REIMB_PROV_NPI", "REIMB_PROV_NAME"] + one_to_n_fields = { + "methodology_breakout": [ + field + for field in FieldSet(file_path=config.FIELD_JSON_PATH) + .filter(field_type="methodology_breakout") + .list_fields() + if field != "GREATER_OF_IND" + ], + "fee_schedule_breakout": [ + "AARETE_DERIVED_FEE_SCHEDULE", + "AARETE_DERIVED_FEE_SCHEDULE_VERSION", + ], + "trigger_cap": ["TRIGGER_CAP_THRESHOLD_AMT", "TRIGGER_BASE_THRESHOLD"], + "rate_escalator": [ + "RATE_ESCALATOR_IND", + "RATE_ESCALATOR_DESC", + "RATE_ESCALATOR_MAX_RATE_INC_PCT", + "RATE_ESCALATOR_RATE_CHANGE_TIMELINE", + ], + "addition": [ + "ADDITION_DESC", + "ADDITION_MAX_PCT_RATE_INC", + "ADDITION_MAX_FEE_RATE_INC", + "ADDITION_RATE_CHANGE_TIMELINE", + "AARETE_DERIVED_ADDITION_RATE_CHANGE_TIMELINE", + ], + "dynamic_primary": [ + "AARETE_DERIVED_LOB", + "AARETE_DERIVED_PROGRAM", + "AARETE_DERIVED_NETWORK", + "AARETE_DERIVED_PRODUCT", + ], + "reimb_prov_info": ["REIMB_PROV_TIN", "REIMB_PROV_NPI", "REIMB_PROV_NAME"], } one_to_n_metrics = {} for field_type, fields in one_to_n_fields.items(): N = len(fields) accuracies = [] - for file in testbed['FILE_NAME'].unique(): - testbed_file = testbed[testbed['FILE_NAME'] == file] - results_file = results[results['FILE_NAME'] == file] + for file in testbed["FILE_NAME"].unique(): + testbed_file = testbed[testbed["FILE_NAME"] == file] + results_file = results[results["FILE_NAME"] == file] # Ensure there are rows in both dataframes if testbed_file.shape[0] == 0 or results_file.shape[0] == 0: print(f"No rows in either labels or predictions for file: {file}") continue - + confusion_matrix = match_rows(fields, testbed_file, results_file, N) confusion_matrix["Filename"] = file accuracies.append(confusion_matrix) precision_df, recall_df, overall = calculate_precision_recall(accuracies) - one_to_n_metrics[field_type] = {'precision': precision_df, 'recall': recall_df, 'overall': overall} + one_to_n_metrics[field_type] = { + "precision": precision_df, + "recall": recall_df, + "overall": overall, + } print(f"\nOverall Metrics: {field_type}") - print(overall.to_string( - index=True, - float_format=lambda x: '{:.2f}'.format(x) if pd.notnull(x) else 'Not found', - justify='left', - col_space={'tp': 15, 'fp': 15, 'fn': 15, 'prec': 15, 'rec': 15, 'f1': 15} - )) - + print( + overall.to_string( + index=True, + float_format=lambda x: ( + "{:.2f}".format(x) if pd.notnull(x) else "Not found" + ), + justify="left", + col_space={ + "tp": 15, + "fp": 15, + "fn": 15, + "prec": 15, + "rec": 15, + "f1": 15, + }, + ) + ) + return one_to_n_metrics def get_reimb_primary(testbed, results): - common_files = set(testbed['FILE_NAME']).intersection(set(results['FILE_NAME'])) + common_files = set(testbed["FILE_NAME"]).intersection(set(results["FILE_NAME"])) reimb_primary_metric_rows = [] for file in common_files: - result = compare_term_extraction_results(file, predictions_df=results, gt_df=testbed) + result = compare_term_extraction_results( + file, predictions_df=results, gt_df=testbed + ) reimb_primary_metric_rows.append(result.copy()) reimb_primary_df = pd.DataFrame(reimb_primary_metric_rows) return reimb_primary_df -def export_to_excel(output_file, - testbed, - row_comparison, - page_comparison_df, - metrics_df, - comparison_df, - one_to_n_metrics, - reimb_primary_df, - provider_metrics_df): - with pd.ExcelWriter(output_file, engine='openpyxl') as writer: +def export_to_excel( + output_file, + testbed, + row_comparison, + page_comparison_df, + metrics_df, + comparison_df, + one_to_n_metrics, + reimb_primary_df, + provider_metrics_df, +): + with pd.ExcelWriter(output_file, engine="openpyxl") as writer: # Test Bed Stats - stats_df = pd.DataFrame({ - 'Metric': ['Total Contracts', 'Total Fields', 'Empty Fields'], - 'Value': [ - len(testbed['FILE_NAME'].unique()), - len(testbed.columns), - (testbed == '').all().sum() - ] - }) - empty_fields_df = pd.DataFrame({'Empty Fields': testbed.columns[(testbed == '').all()].tolist()}) - - stats_df.to_excel(writer, sheet_name='Testbed Stats', index=False) - row_comparison.to_excel(writer, sheet_name='Row Counts') - empty_fields_df.to_excel(writer, sheet_name='Empty Fields', index=False) + stats_df = pd.DataFrame( + { + "Metric": ["Total Contracts", "Total Fields", "Empty Fields"], + "Value": [ + len(testbed["FILE_NAME"].unique()), + len(testbed.columns), + (testbed == "").all().sum(), + ], + } + ) + empty_fields_df = pd.DataFrame( + {"Empty Fields": testbed.columns[(testbed == "").all()].tolist()} + ) + + stats_df.to_excel(writer, sheet_name="Testbed Stats", index=False) + row_comparison.to_excel(writer, sheet_name="Row Counts") + empty_fields_df.to_excel(writer, sheet_name="Empty Fields", index=False) # Add page-level row count comparison if it exists if not page_comparison_df.empty: - page_comparison_df.to_excel(writer, sheet_name='Page-Level Row Counts', index=False) + page_comparison_df.to_excel( + writer, sheet_name="Page-Level Row Counts", index=False + ) else: print("No page-level row count mismatches found; skipping export.") - + # One-to-One Results - metrics_df.to_excel(writer, sheet_name='One-to-One Metrics', index=False) - comparison_df.to_excel(writer, sheet_name='One-to-One Details') # Add new sheet - + metrics_df.to_excel(writer, sheet_name="One-to-One Metrics", index=False) + comparison_df.to_excel(writer, sheet_name="One-to-One Details") # Add new sheet + # One-to-N Results for field_type, metrics in one_to_n_metrics.items(): - precision_df = metrics['precision'] - recall_df = metrics['recall'] - overall = metrics['overall'] + precision_df = metrics["precision"] + recall_df = metrics["recall"] + overall = metrics["overall"] # Add the field type to the sheet names - precision_df.to_excel(writer, sheet_name=f'{field_type} Precision', index=False) - recall_df.to_excel(writer, sheet_name=f'{field_type} Recall', index=False) - overall.to_excel(writer, sheet_name=f'{field_type} Overall') + precision_df.to_excel( + writer, sheet_name=f"{field_type} Precision", index=False + ) + recall_df.to_excel(writer, sheet_name=f"{field_type} Recall", index=False) + overall.to_excel(writer, sheet_name=f"{field_type} Overall") # Reimbursement Primary Results - reimb_primary_df.to_excel(writer, sheet_name='Reimbursement Primary', index=False) + reimb_primary_df.to_excel( + writer, sheet_name="Reimbursement Primary", index=False + ) # Order-independent provider analysis - provider_metrics_df.to_excel(writer, sheet_name='Provider Fields', index=False) - + provider_metrics_df.to_excel(writer, sheet_name="Provider Fields", index=False) + # Auto-adjust column widths for sheet_name in writer.sheets: worksheet = writer.sheets[sheet_name] @@ -1327,15 +1622,9 @@ def export_to_excel(output_file, max_length = len(str(cell.value)) except: pass - adjusted_width = (max_length + 2) - worksheet.column_dimensions[column[0].column_letter].width = adjusted_width + adjusted_width = max_length + 2 + worksheet.column_dimensions[column[0].column_letter].width = ( + adjusted_width + ) print(f"Results exported to {output_file}") - - - - - - - - diff --git a/fieldExtraction/src/tracking/batch_tracking.py b/fieldExtraction/src/tracking/batch_tracking.py index 17f0fcd..51ab981 100644 --- a/fieldExtraction/src/tracking/batch_tracking.py +++ b/fieldExtraction/src/tracking/batch_tracking.py @@ -12,105 +12,116 @@ from src import config class BatchTracker: def __init__(self): - self.s3_client = boto3.client('s3') + self.s3_client = boto3.client("s3") self.batch_tracker_bucket = config.S3_OUTPUT_BUCKET - self.batch_tracker_key = 'batch-tracker/doczyai-batch-tracker.csv' + self.batch_tracker_key = "batch-tracker/doczyai-batch-tracker.csv" self.current_batch_start = datetime.now() self.files_processed = 0 self.processed_files = set() # Track unique files - self.processing_events = 0 # Track total processing events + self.processing_events = 0 # Track total processing events self.total_input_files = 0 self.reimbursement_files = 0 - + # Define the file log fields in correct order self.file_log_fields = [ - 'filename', - 'process_type', - 'start_time', - 'end_time', - 'processing_time_seconds', - 'delta_time_hours', - 'status', - 'error', - 'memory_usage_mb', - 'cpu_percent' + "filename", + "process_type", + "start_time", + "end_time", + "processing_time_seconds", + "delta_time_hours", + "status", + "error", + "memory_usage_mb", + "cpu_percent", ] + def set_input_file_counts(self, total_files, reimbursement_files): """Set the input file counts for tracking""" self.total_input_files = total_files self.reimbursement_files = reimbursement_files - - + def get_git_info(self): """Get current git branch, commit hash, and metadata""" try: # Get current branch branch = subprocess.check_output( - ['git', 'rev-parse', '--abbrev-ref', 'HEAD'], - universal_newlines=True + ["git", "rev-parse", "--abbrev-ref", "HEAD"], universal_newlines=True ).strip() - + # Get current commit hash commit = subprocess.check_output( - ['git', 'rev-parse', 'HEAD'], - universal_newlines=True + ["git", "rev-parse", "HEAD"], universal_newlines=True ).strip() - + # Get commit message commit_msg = subprocess.check_output( - ['git', 'log', '-1', '--pretty=%B'], - universal_newlines=True + ["git", "log", "-1", "--pretty=%B"], universal_newlines=True ).strip() - + # Get last commit date commit_date = subprocess.check_output( - ['git', 'log', '-1', '--format=%cd'], - universal_newlines=True + ["git", "log", "-1", "--format=%cd"], universal_newlines=True ).strip() - + return { - 'branch': branch, - 'commit_hash': commit, - 'commit_message': commit_msg, - 'commit_date': commit_date + "branch": branch, + "commit_hash": commit, + "commit_message": commit_msg, + "commit_date": commit_date, } except Exception as e: return { - 'branch': 'Unknown', - 'commit_hash': 'Unknown', - 'commit_message': 'Unknown', - 'commit_date': 'Unknown' + "branch": "Unknown", + "commit_hash": "Unknown", + "commit_message": "Unknown", + "commit_date": "Unknown", } def get_batch_tracker(self): """Retrieve or create the batch tracker DataFrame""" try: response = self.s3_client.get_object( - Bucket=self.batch_tracker_bucket, - Key=self.batch_tracker_key + Bucket=self.batch_tracker_bucket, Key=self.batch_tracker_key ) - return pd.read_csv(response['Body']) + return pd.read_csv(response["Body"]) except Exception as e: print(f"Creating new batch tracker: {str(e)}") - return pd.DataFrame(columns=[ - 'batch_id', 'state', 'run_mode', 'read_mode', 'input_dir', - 'output_dir', 'fields', 'max_workers', - 'start_time', 'end_time', 'total_input_files', 'reimbursement_files', - 'files_processed', 'contracts_per_hour', 'status', 'machine_info', - 'git_branch', 'git_commit', 'git_commit_message', - 'git_commit_date' - ]) + return pd.DataFrame( + columns=[ + "batch_id", + "state", + "run_mode", + "read_mode", + "input_dir", + "output_dir", + "fields", + "max_workers", + "start_time", + "end_time", + "total_input_files", + "reimbursement_files", + "files_processed", + "contracts_per_hour", + "status", + "machine_info", + "git_branch", + "git_commit", + "git_commit_message", + "git_commit_date", + ] + ) def get_machine_info(self): """Get information about the machine running the process""" import platform import socket - + return { - 'hostname': socket.gethostname(), - 'platform': platform.platform(), - 'processor': platform.processor(), - 'python_version': platform.python_version() + "hostname": socket.gethostname(), + "platform": platform.platform(), + "processor": platform.processor(), + "python_version": platform.python_version(), } def add_batch_run(self): @@ -118,78 +129,85 @@ class BatchTracker: df = self.get_batch_tracker() machine_info = self.get_machine_info() git_info = self.get_git_info() - + new_row = { - 'batch_id': config.BATCH_ID, - 'state': config.STATE, - 'run_mode': config.RUN_MODE, - 'read_mode': config.READ_MODE, - 'input_dir': config.LOCAL_PATH, - 'output_dir': config.OUTPUT_DIRECTORY, - 'fields': config.FIELDS, - 'max_workers': config.MAX_WORKERS, - 'start_time': self.current_batch_start.strftime('%Y-%m-%d %H:%M:%S'), - 'end_time': None, - 'total_input_files': self.total_input_files, - 'reimbursement_files': self.reimbursement_files, - 'files_processed': 0, - 'contracts_per_hour': 0, - 'status': 'Running', - 'machine_info': str(machine_info), - 'git_branch': git_info['branch'], - 'git_commit': git_info['commit_hash'], - 'git_commit_message': git_info['commit_message'], - 'git_commit_date': git_info['commit_date'] + "batch_id": config.BATCH_ID, + "state": config.STATE, + "run_mode": config.RUN_MODE, + "read_mode": config.READ_MODE, + "input_dir": config.LOCAL_PATH, + "output_dir": config.OUTPUT_DIRECTORY, + "fields": config.FIELDS, + "max_workers": config.MAX_WORKERS, + "start_time": self.current_batch_start.strftime("%Y-%m-%d %H:%M:%S"), + "end_time": None, + "total_input_files": self.total_input_files, + "reimbursement_files": self.reimbursement_files, + "files_processed": 0, + "contracts_per_hour": 0, + "status": "Running", + "machine_info": str(machine_info), + "git_branch": git_info["branch"], + "git_commit": git_info["commit_hash"], + "git_commit_message": git_info["commit_message"], + "git_commit_date": git_info["commit_date"], } - + df = pd.concat([df, pd.DataFrame([new_row])], ignore_index=True) self.update_tracker(df) - + # Initialize the file log with headers if it doesn't exist if not os.path.exists(config.FILE_LOG_NAME): - with open(config.FILE_LOG_NAME, 'w', newline='', encoding='utf-8') as f: + with open(config.FILE_LOG_NAME, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=self.file_log_fields) writer.writeheader() - def update_batch_run(self, end_time=None): """Update the current batch run stats""" df = self.get_batch_tracker() - - current_batch_entries = df[df['batch_id'] == config.BATCH_ID] - + + current_batch_entries = df[df["batch_id"] == config.BATCH_ID] + if current_batch_entries.empty: self.add_batch_run() return - + latest_idx = current_batch_entries.index[-1] - + if end_time: - df.loc[latest_idx, 'end_time'] = end_time.strftime('%Y-%m-%d %H:%M:%S') - df.loc[latest_idx, 'status'] = 'Completed' - - df.loc[latest_idx, 'files_processed'] = len(self.processed_files) # Use unique files count - + df.loc[latest_idx, "end_time"] = end_time.strftime("%Y-%m-%d %H:%M:%S") + df.loc[latest_idx, "status"] = "Completed" + + df.loc[latest_idx, "files_processed"] = len( + self.processed_files + ) # Use unique files count + try: - start_time = datetime.strptime(df.loc[latest_idx, 'start_time'], '%Y-%m-%d %H:%M:%S') + start_time = datetime.strptime( + df.loc[latest_idx, "start_time"], "%Y-%m-%d %H:%M:%S" + ) current_time = end_time if end_time else datetime.now() hours_elapsed = (current_time - start_time).total_seconds() / 3600 - + if hours_elapsed > 0: - contracts_per_hour = len(self.processed_files) / hours_elapsed # Use unique files count - df.loc[latest_idx, 'contracts_per_hour'] = round(contracts_per_hour, 2) + contracts_per_hour = ( + len(self.processed_files) / hours_elapsed + ) # Use unique files count + df.loc[latest_idx, "contracts_per_hour"] = round(contracts_per_hour, 2) except Exception as e: print(f"Error calculating processing speed: {str(e)}") - + self.update_tracker(df) - + if end_time and len(self.processed_files) > 0: try: print(f"\nBatch Performance Metrics:") print(f"Unique files processed: {len(self.processed_files)}") print(f"Total processing events: {self.processing_events}") print(f"Total time elapsed: {round(hours_elapsed, 2)} hours") - print(f"Average processing speed: {round(contracts_per_hour, 2)} unique files/hour") + print( + f"Average processing speed: {round(contracts_per_hour, 2)} unique files/hour" + ) except: print("Could not calculate final performance metrics") @@ -200,72 +218,75 @@ class BatchTracker: self.s3_client.put_object( Bucket=self.batch_tracker_bucket, Key=self.batch_tracker_key, - Body=csv_buffer + Body=csv_buffer, ) - - df.to_csv('doczyai-batch-tracker-local.csv', index=False) + + df.to_csv("doczyai-batch-tracker-local.csv", index=False) except Exception as e: print(f"Error updating tracker in S3: {str(e)}") - - df.to_csv('doczyai-batch-tracker-local.csv', index=False) - + df.to_csv("doczyai-batch-tracker-local.csv", index=False) - def update_file_progress(self, filename, process_type, start_time, end_time, status='Completed', error=None): + def update_file_progress( + self, + filename, + process_type, + start_time, + end_time, + status="Completed", + error=None, + ): """Update progress for individual file processing""" try: processing_time = (end_time - start_time).total_seconds() delta_time_hours = processing_time / 3600 # Convert to hours system_metrics = self.get_system_metrics() - + new_row = { - 'filename': filename, - 'process_type': process_type, - 'start_time': start_time.strftime('%Y-%m-%d %H:%M:%S'), - 'end_time': end_time.strftime('%Y-%m-%d %H:%M:%S'), - 'processing_time_seconds': processing_time, - 'delta_time_hours': round(delta_time_hours, 4), - 'status': status, - 'error': error, - 'memory_usage_mb': system_metrics['memory_usage_mb'], - 'cpu_percent': system_metrics['cpu_percent'] + "filename": filename, + "process_type": process_type, + "start_time": start_time.strftime("%Y-%m-%d %H:%M:%S"), + "end_time": end_time.strftime("%Y-%m-%d %H:%M:%S"), + "processing_time_seconds": processing_time, + "delta_time_hours": round(delta_time_hours, 4), + "status": status, + "error": error, + "memory_usage_mb": system_metrics["memory_usage_mb"], + "cpu_percent": system_metrics["cpu_percent"], } # Write to file log if not os.path.exists(config.FILE_LOG_NAME): - with open(config.FILE_LOG_NAME, 'w', newline='', encoding='utf-8') as f: + with open(config.FILE_LOG_NAME, "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=self.file_log_fields) writer.writeheader() - - with open(config.FILE_LOG_NAME, 'a', newline='', encoding='utf-8') as f: + + with open(config.FILE_LOG_NAME, "a", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=self.file_log_fields) writer.writerow(new_row) - + # Track both unique files and processing events - if status == 'Completed': + if status == "Completed": self.processed_files.add(filename) # Add to set of unique files - self.processing_events += 1 # Increment total events - self.files_processed = len(self.processed_files) # Update files_processed to unique count - + self.processing_events += 1 # Increment total events + self.files_processed = len( + self.processed_files + ) # Update files_processed to unique count + self.update_batch_run() - + except Exception as e: print(f"Error updating file progress: {str(e)}") - - def get_system_metrics(self): """Get current system metrics""" import psutil - + try: process = psutil.Process() return { - 'memory_usage_mb': process.memory_info().rss / 1024 / 1024, - 'cpu_percent': process.cpu_percent() + "memory_usage_mb": process.memory_info().rss / 1024 / 1024, + "cpu_percent": process.cpu_percent(), } except: - return { - 'memory_usage_mb': 0, - 'cpu_percent': 0 - } \ No newline at end of file + return {"memory_usage_mb": 0, "cpu_percent": 0} diff --git a/fieldExtraction/src/tracking/tracking_utils.py b/fieldExtraction/src/tracking/tracking_utils.py index c5ec1b8..e6fe9f7 100644 --- a/fieldExtraction/src/tracking/tracking_utils.py +++ b/fieldExtraction/src/tracking/tracking_utils.py @@ -7,7 +7,6 @@ from datetime import datetime import pandas as pd from src import config -from src.client import consolidate_output def initialize_tracking_stats(): @@ -31,11 +30,12 @@ def initialize_tracking_stats(): that tracking starts with a clean slate. """ return { - 'completed_ac': 0, - 'completed_b': 0, - 'processed_count': 0, - 'processed_files': {'AC': set(), 'B': set()} - } + "completed_ac": 0, + "completed_b": 0, + "processed_count": 0, + "processed_files": {"AC": set(), "B": set()}, + } + def write_stats_to_csv(filename=config.TRACKING_FILE): """Write model and global stats to CSV file with retry logic""" @@ -44,11 +44,19 @@ def write_stats_to_csv(filename=config.TRACKING_FILE): try: with open(filename, "w", newline="") as csvfile: writer = csv.writer(csvfile) - writer.writerow([ - "Filename", "Requests", "Tokens Sent", "Tokens Received", - "Total Time (s)", "Avg Time per Request (s)", - "Claude 2 Requests", "Claude 3 Requests", "Claude 3.5 Requests", - ]) + writer.writerow( + [ + "Filename", + "Requests", + "Tokens Sent", + "Tokens Received", + "Total Time (s)", + "Avg Time per Request (s)", + "Claude 2 Requests", + "Claude 3 Requests", + "Claude 3.5 Requests", + ] + ) for filename, stats in config.MODEL_STATS.items(): avg_time = ( @@ -56,12 +64,19 @@ def write_stats_to_csv(filename=config.TRACKING_FILE): if stats["requests"] > 0 else 0 ) - writer.writerow([ - filename, stats["requests"], stats["tokens_sent"], - stats["tokens_received"], round(stats["total_time"], 2), - avg_time, stats["claude2_requests"], - stats["claude3_requests"], stats["claude35_requests"], - ]) + writer.writerow( + [ + filename, + stats["requests"], + stats["tokens_sent"], + stats["tokens_received"], + round(stats["total_time"], 2), + avg_time, + stats["claude2_requests"], + stats["claude3_requests"], + stats["claude35_requests"], + ] + ) # Write global stats writer.writerow([]) @@ -76,7 +91,10 @@ def write_stats_to_csv(filename=config.TRACKING_FILE): if attempt < max_retries - 1: time.sleep(random.uniform(0.1, 0.5)) else: - print(f"Unable to write to {filename} after {max_retries} attempts. It may be open in another program.") + print( + f"Unable to write to {filename} after {max_retries} attempts. It may be open in another program." + ) + def get_master_batch_tracking(batch_id): """ @@ -86,118 +104,157 @@ def get_master_batch_tracking(batch_id): s3_key = f"{batch_id}/master_batch_tracking.csv" try: response = config.S3_CLIENT.get_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=s3_key + Bucket=config.S3_OUTPUT_BUCKET, Key=s3_key ) - df = pd.read_csv(response['Body']) + df = pd.read_csv(response["Body"]) # Ensure required columns exist - required_cols = ['filename', 'batch_id', 'run_timestamp', 'output_type', 's3_path', 'upload_status'] + required_cols = [ + "filename", + "batch_id", + "run_timestamp", + "output_type", + "s3_path", + "upload_status", + ] for col in required_cols: if col not in df.columns: df[col] = None return df except config.S3_CLIENT.exceptions.NoSuchKey: return pd.DataFrame( - columns=['filename', 'batch_id', 'run_timestamp', 'output_type', 's3_path', 'upload_status'] + columns=[ + "filename", + "batch_id", + "run_timestamp", + "output_type", + "s3_path", + "upload_status", + ] ) -def update_master_batch_tracking(batch_id, run_timestamp, processed_files, output_type, s3_path=None, upload_status='Uploaded'): + +def update_master_batch_tracking( + batch_id, + run_timestamp, + processed_files, + output_type, + s3_path=None, + upload_status="Uploaded", +): """ Update master batch tracking with newly processed files Only updates S3 when files are successfully uploaded """ tracking_df = get_master_batch_tracking(batch_id) - - - if output_type == 'ABC': + + if output_type == "ABC": return tracking_df - + new_records = [] for filename in processed_files: - - mask = (tracking_df['filename'] == filename) & \ - (tracking_df['output_type'] == output_type) & \ - (tracking_df['upload_status'] == 'Uploaded') - + + mask = ( + (tracking_df["filename"] == filename) + & (tracking_df["output_type"] == output_type) + & (tracking_df["upload_status"] == "Uploaded") + ) + if not mask.any(): - new_records.append({ - 'filename': filename, - 'batch_id': batch_id, - 'run_timestamp': run_timestamp, - 'output_type': output_type, - 's3_path': s3_path or f"{batch_id}/{run_timestamp}/{output_type}", - 'upload_status': upload_status - }) - + new_records.append( + { + "filename": filename, + "batch_id": batch_id, + "run_timestamp": run_timestamp, + "output_type": output_type, + "s3_path": s3_path or f"{batch_id}/{run_timestamp}/{output_type}", + "upload_status": upload_status, + } + ) + if new_records: new_df = pd.DataFrame(new_records) tracking_df = pd.concat([tracking_df, new_df], ignore_index=True) - - - if upload_status == 'Uploaded': + + if upload_status == "Uploaded": csv_buffer = tracking_df.to_csv(index=False).encode() s3_key = f"{batch_id}/master_batch_tracking.csv" config.S3_CLIENT.put_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=s3_key, - Body=csv_buffer + Bucket=config.S3_OUTPUT_BUCKET, Key=s3_key, Body=csv_buffer ) - + return tracking_df + def get_processed_files(batch_id): """Get list of successfully processed and uploaded files""" tracking_df = get_master_batch_tracking(batch_id) - + if tracking_df.empty: return set(), set() - + # Only consider successfully uploaded files - successful_df = tracking_df[tracking_df['upload_status'] == 'Uploaded'] - - ac_files = set(successful_df[successful_df['output_type'] == 'AC']['filename'].unique()) - b_files = set(successful_df[successful_df['output_type'] == 'B']['filename'].unique()) - + successful_df = tracking_df[tracking_df["upload_status"] == "Uploaded"] + + ac_files = set( + successful_df[successful_df["output_type"] == "AC"]["filename"].unique() + ) + b_files = set( + successful_df[successful_df["output_type"] == "B"]["filename"].unique() + ) + return ac_files, b_files -def log_file_processing(filename, process_type, s3_path, status='Completed', error=None): + +def log_file_processing( + filename, process_type, s3_path, status="Completed", error=None +): """Log file processing details to file_log""" - timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") log_entry = { - 'timestamp': timestamp, - 'filename': filename, - 'process_type': process_type, - 's3_path': s3_path, - 'status': status, - 'error': error or 'None' + "timestamp": timestamp, + "filename": filename, + "process_type": process_type, + "s3_path": s3_path, + "status": status, + "error": error or "None", } - + try: if os.path.exists(config.FILE_LOG_NAME): df = pd.read_csv(config.FILE_LOG_NAME) else: - df = pd.DataFrame(columns=['timestamp', 'filename', 'process_type', 's3_path', 'status', 'error']) - + df = pd.DataFrame( + columns=[ + "timestamp", + "filename", + "process_type", + "s3_path", + "status", + "error", + ] + ) + df = pd.concat([df, pd.DataFrame([log_entry])], ignore_index=True) df.to_csv(config.FILE_LOG_NAME, index=False) except Exception as e: print(f"Error writing to file log: {str(e)}") + def upload_tracking_logs(run_timestamp, batch_id): """Upload all tracking logs to S3""" if not config.WRITE_TO_S3: return - + BUCKET_NAME = config.S3_OUTPUT_BUCKET - + write_stats_to_csv() - + tracking_files = { - 'cost_log': config.COST_LOG_NAME, - 'file_log': config.FILE_LOG_NAME, - 'tracking': config.TRACKING_FILE + "cost_log": config.COST_LOG_NAME, + "file_log": config.FILE_LOG_NAME, + "tracking": config.TRACKING_FILE, } - + for file_type, file_name in tracking_files.items(): if os.path.exists(file_name): try: @@ -208,32 +265,33 @@ def upload_tracking_logs(run_timestamp, batch_id): print(f"Error uploading {file_name}: {str(e)}") raise + def check_file_processed(batch_id, filename, output_type): """Check if a specific file has been successfully processed and uploaded""" tracking_df = get_master_batch_tracking(batch_id) - + if tracking_df.empty: return False - + return any( - (tracking_df['filename'] == filename) & - (tracking_df['output_type'] == output_type) & - (tracking_df['upload_status'] == 'Uploaded') + (tracking_df["filename"] == filename) + & (tracking_df["output_type"] == output_type) + & (tracking_df["upload_status"] == "Uploaded") ) -def calculate_progress_stats(completed_ac, total_ac, completed_b, total_b): # tracking.py +def calculate_progress_stats( + completed_ac, total_ac, completed_b, total_b +): # tracking.py """Calculate and return progress statistics""" ac_progress = min((completed_ac / total_ac * 100), 100.0) if total_ac > 0 else 0 b_progress = min((completed_b / total_b * 100), 100.0) if total_b > 0 else 0 return ac_progress, b_progress -def calculate_and_display_progress(stats, - total_ac, - total_b, - total_processing_events, - run_timestamp): +def calculate_and_display_progress( + stats, total_ac, total_b, total_processing_events, run_timestamp +): """ Calculates and displays the progress of processing tasks for AC and B and overall processing events. @@ -250,28 +308,24 @@ def calculate_and_display_progress(stats, run_timestamp (str): A timestamp marking the start of the run, used in data consolidation operations. """ ac_progress, b_progress = calculate_progress_stats( - stats['completed_ac'], total_ac, - stats['completed_b'], total_b - ) + stats["completed_ac"], total_ac, stats["completed_b"], total_b + ) print(f"\nProgress:") print(f"AC: {ac_progress:.2f}% ({stats['completed_ac']}/{total_ac})") print(f"B: {b_progress:.2f}% ({stats['completed_b']}/{total_b})") print(f"Overall: {(stats['processed_count'] / total_processing_events * 100):.2f}%") last_upload_count = 0 - if stats['processed_count'] - last_upload_count >= config.UPLOAD_FREQUENCY: + if stats["processed_count"] - last_upload_count >= config.UPLOAD_FREQUENCY: try: consolidate_output.consolidate_output(run_timestamp) - last_upload_count = stats['processed_count'] + last_upload_count = stats["processed_count"] except Exception as e: print(f"Warning: Could not update tracking data: {str(e)}") - -def print_final_statistics(batch_tracker, - stats, - total_ac, - total_b, - total_processing_events): +def print_final_statistics( + batch_tracker, stats, total_ac, total_b, total_processing_events +): """ Displays final processing statistics for a batch job. @@ -290,8 +344,9 @@ def print_final_statistics(batch_tracker, print(f"Files with reimbursement: {batch_tracker.reimbursement_files}") print(f"AC files processed: {stats['completed_ac']}/{total_ac}") print(f"B files processed: {stats['completed_b']}/{total_b}") - print(f"Total processing events completed: {stats['processed_count']}/{total_processing_events}") - if stats['processed_count'] > 0 and total_processing_events > 0: - success_rate = (stats['processed_count'] / total_processing_events) * 100 + print( + f"Total processing events completed: {stats['processed_count']}/{total_processing_events}" + ) + if stats["processed_count"] > 0 and total_processing_events > 0: + success_rate = (stats["processed_count"] / total_processing_events) * 100 print(f"Overall success rate: {success_rate:.2f}%") - diff --git a/fieldExtraction/src/utils/crosswalk_utils.py b/fieldExtraction/src/utils/crosswalk_utils.py new file mode 100644 index 0000000..5446672 --- /dev/null +++ b/fieldExtraction/src/utils/crosswalk_utils.py @@ -0,0 +1,68 @@ +import ast + +import src.utils.string_utils as string_utils + + +def apply_crosswalk(val: str, mapping: dict[str, str], default: str = "") -> str: + """Apply a crosswalk to a value + + Args: + val (str): Input value + mapping (dict[str, str]): Mapping dictionary + default (str | None, optional): Default value; if None and val is not found in the mapping, val is returned. Defaults to "". + + Returns: + str: Crosswalked value + """ + # Handle None or blank values defensively + if val is None: + return default + + # Store original for fallback + original_val = str(val).strip() + + # IF empty after stripping, return default + if string_utils.is_empty(original_val): + return default if default else original_val + + # Ensure val is a string + val = original_val + + try: + if "[" in val and "]" in val: + # Try to parse as literal, but handle any structure returned + parsed_val = ast.literal_eval(val) + values = string_utils.flatten_to_strings(parsed_val) + elif "," in val: + values = [v.strip() for v in val.split(",")] # Split by comma if present + elif "|" in val: + values = [v.strip() for v in val.split("|")] # Split by pipe if present + else: + values = [val.strip()] + except (ValueError, SyntaxError): + # If parsing fails, treat val as a regular string + values = [val.strip()] + + # Apply mapping with defensive string handling + mapped_values = set() + + for v in values: + v_str = str(v).strip() + if v_str in mapping: + mapped_result = mapping[v_str] + if mapped_result and not string_utils.is_empty(mapped_result): + mapped_values.add(mapped_result) + elif v_str in mapping.values(): + mapped_values.add( + v_str + ) # If the value is already a mapped value, add it directly + + # Convert to list and filter out empty values + final_mapped = [v for v in mapped_values if v and not string_utils.is_empty(v)] + + if len(final_mapped) == 1: + return final_mapped[0] + elif len(final_mapped) > 1: + return "|".join(final_mapped) + else: + return default if default else original_val diff --git a/fieldExtraction/src/utils/embedding_utils.py b/fieldExtraction/src/utils/embedding_utils.py index 61b00ad..2c2e094 100644 --- a/fieldExtraction/src/utils/embedding_utils.py +++ b/fieldExtraction/src/utils/embedding_utils.py @@ -1,44 +1,51 @@ +import os +import pickle import faiss -import pickle import numpy as np -import os import src.config as config from src.utils.string_utils import datetime_str -def create_faiss_index(choices, model, save_path="faiss_index.bin", embedding_path="embeddings.npy", choices_path="choices.pkl"): + +def create_faiss_index( + choices, + model, + save_path="faiss_index.bin", + embedding_path="embeddings.npy", + choices_path="choices.pkl", +): embeddings = model.encode(choices, normalize_embeddings=True).astype("float32") - + # Create FAISS index index = faiss.IndexFlatIP(embeddings.shape[1]) index.add(embeddings) - + # Save index faiss.write_index(index, save_path) - + # Save embeddings and choices for future use np.save(embedding_path, embeddings) with open(choices_path, "wb") as f: pickle.dump(choices, f) - + return index -def load_faiss_index(index_path="faiss_index.bin", embedding_path="embeddings.npy", choices_path="choices.pkl"): +def load_faiss_index( + index_path="faiss_index.bin", + embedding_path="embeddings.npy", + choices_path="choices.pkl", +): index = faiss.read_index(index_path) embeddings = np.load(embedding_path) - with open(choices_path, "rb") as f: choices = pickle.load(f) - return index, embeddings, choices - def load_embeddings(): - """loads all embedding files required for processing indirect codes (e.g. proc codes) - """ + """loads all embedding files required for processing indirect codes (e.g. proc codes)""" # list all embeddings present in s3 s3_client = config.S3_CLIENT @@ -65,7 +72,7 @@ def load_embeddings(): directory = os.path.dirname(local_path) if not os.path.exists(directory): os.makedirs(directory) - + # check if file already exists if not os.path.exists(local_path): try: diff --git a/fieldExtraction/src/utils/io_utils.py b/fieldExtraction/src/utils/io_utils.py index 00b2771..24d6fce 100644 --- a/fieldExtraction/src/utils/io_utils.py +++ b/fieldExtraction/src/utils/io_utils.py @@ -1,4 +1,5 @@ import os +import tempfile from io import StringIO import pandas as pd @@ -8,10 +9,8 @@ from pyxlsb import open_workbook import src.tracking.tracking_utils as tracking_utils from src import config -import tempfile - -def read_local(file_path): # io_utils.py +def read_local(file_path): # io_utils.py # Check if the file is a text file if os.path.isfile(file_path) and file_path.endswith(".txt"): try: @@ -34,21 +33,22 @@ def read_local(file_path): # io_utils.py df = pd.read_csv(file_path) return df except Exception: - print('Failed to read .csv file.') + print("Failed to read .csv file.") if os.path.isfile(file_path) and file_path.endswith(".xlsx"): try: df = pd.read_excel(file_path) return df except Exception: - print('Failed to read .xlsx file.') + print("Failed to read .xlsx file.") -def list_s3_files(prefix=config.S3_PREFIX, bucket=config.S3_BUCKET): # io_utils.py +def list_s3_files(prefix=config.S3_PREFIX, bucket=config.S3_BUCKET): # io_utils.py """List all files in an S3 folder.""" response = config.S3_CLIENT.list_objects_v2(Bucket=bucket, Prefix=prefix) return [item["Key"] for item in response.get("Contents", [])] + def read_s3_pdf(pdf_path): """ Downloads a PDF file from an S3 bucket and returns a temporary file path. @@ -111,7 +111,7 @@ def read_s3(): print(f"Reading {contract}") try: data = s3_client.get_object(Bucket=bucket, Key=contract) - if contract.endswith('.txt'): + if contract.endswith(".txt"): contents = data["Body"].read() context = contents.decode("utf-8") path, filename = os.path.split(contract) @@ -122,7 +122,7 @@ def read_s3(): return files -def read_s3_csv(csv_path): # io_utils.py +def read_s3_csv(csv_path): # io_utils.py s3_client = config.S3_CLIENT s3_parts = csv_path[5:].split("/", 1) bucket = s3_parts[0] @@ -138,7 +138,7 @@ def read_s3_csv(csv_path): # io_utils.py print(f"Error listing S3 objects: {e}") -def read_input(local_path=config.LOCAL_PATH): # io_utils.py +def read_input(local_path=config.LOCAL_PATH): # io_utils.py if config.READ_MODE == "local": files = {} for file in os.listdir(local_path): @@ -150,37 +150,41 @@ def read_input(local_path=config.LOCAL_PATH): # io_utils.py return read_s3() -def read_input_csv(): # io_utils.py +def read_input_csv(): # io_utils.py if config.DF_READ_MODE == "local": ac_df = read_local(config.AC_DF) if config.AC_DF else None b_df = read_local(config.B_DF) if config.B_DF else None elif config.DF_READ_MODE == "s3": - ac_df = read_s3_csv(config.AC_DF) if config.AC_DF else None + ac_df = read_s3_csv(config.AC_DF) if config.AC_DF else None b_df = read_s3_csv(config.B_DF) if config.B_DF else None return ac_df, b_df -def filter_already_processed(input_dict, input_dict_b): # io_utils.py +def filter_already_processed(input_dict, input_dict_b): # io_utils.py """Filter out already processed files based on either local files or S3 master tracking""" if config.WRITE_TO_S3: # Get processed files from S3 master tracking for this specific batch - already_processed_ac, already_processed_b = tracking_utils.get_processed_files(config.BATCH_ID) + already_processed_ac, already_processed_b = tracking_utils.get_processed_files( + config.BATCH_ID + ) # Keep files that still need either AC or B processing input_dict_ac = { - k: v for k, v in input_dict.items() - if k not in already_processed_ac + k: v for k, v in input_dict.items() if k not in already_processed_ac } input_dict_b = { - k: v for k, v in input_dict_b.items() - if k not in already_processed_b + k: v for k, v in input_dict_b.items() if k not in already_processed_b } print(f"Filtering based on S3 master tracking for batch {config.BATCH_ID}:") - print(f"AC: Found {len(already_processed_ac)} already processed, {len(input_dict_ac)} remaining") - print(f"B: Found {len(already_processed_b)} already processed, {len(input_dict_b)} remaining") + print( + f"AC: Found {len(already_processed_ac)} already processed, {len(input_dict_ac)} remaining" + ) + print( + f"B: Found {len(already_processed_b)} already processed, {len(input_dict_b)} remaining" + ) else: # Original local directory checking logic @@ -194,37 +198,42 @@ def filter_already_processed(input_dict, input_dict_b): # io_utils.py already_processed_b.append(folder_name + ".txt") input_dict_ac = { - k: v for k, v in input_dict.items() - if k not in already_processed_ac + k: v for k, v in input_dict.items() if k not in already_processed_ac } input_dict_b = { - k: v for k, v in input_dict_b.items() - if k not in already_processed_b + k: v for k, v in input_dict_b.items() if k not in already_processed_b } print(f"Filtering based on local output directory:") - print(f"AC: Found {len(already_processed_ac)} already processed, {len(input_dict_ac)} remaining") - print(f"B: Found {len(already_processed_b)} already processed, {len(input_dict_b)} remaining") + print( + f"AC: Found {len(already_processed_ac)} already processed, {len(input_dict_ac)} remaining" + ) + print( + f"B: Found {len(already_processed_b)} already processed, {len(input_dict_b)} remaining" + ) return input_dict_ac, input_dict_b -def remove_txt_extension(filename): # io_utils.py +def remove_txt_extension(filename): # io_utils.py if isinstance(filename, str) and filename.lower().endswith(".txt"): return filename[:-4] return filename -def read_xlsb(path): # io_utils.py +def read_xlsb(path): # io_utils.py with open_workbook(path) as wb: with wb.get_sheet(1) as sheet: rows = [] for row in sheet.rows(): rows.append([item.v for item in row]) # Extracting cell values - df = pd.DataFrame(rows[1:], columns=rows[0]) # Converting to DataFrame, assuming first row is header + df = pd.DataFrame( + rows[1:], columns=rows[0] + ) # Converting to DataFrame, assuming first row is header return df + def write_local(df, filename, run_timestamp, output_type): """ Saves a DataFrame as a CSV file in a local directory. @@ -233,18 +242,21 @@ def write_local(df, filename, run_timestamp, output_type): df (pd.DataFrame): The DataFrame to save. run_timestamp (str): The timestamp used to organize output files. - The function creates a directory based on the configured output path - and the provided timestamp, then saves the DataFrame as a CSV file + The function creates a directory based on the configured output path + and the provided timestamp, then saves the DataFrame as a CSV file named "-RESULTS.csv" within it. """ if output_type == "final": output_dir = os.path.join(config.CONSOLIDATED_OUTPUT_DIRECTORY, run_timestamp) os.makedirs(output_dir, exist_ok=True) - df.to_csv(os.path.join(output_dir, f'{config.BATCH_ID}-RESULTS.csv')) + df.to_csv(os.path.join(output_dir, f"{config.BATCH_ID}-RESULTS.csv")) elif output_type == "individual": base_filename = os.path.splitext(filename)[0].strip() os.makedirs(config.OUTPUT_DIRECTORY, exist_ok=True) - df.to_csv(os.path.join(config.OUTPUT_DIRECTORY, f'{base_filename}_-RESULTS.csv'), index=False) + df.to_csv( + os.path.join(config.OUTPUT_DIRECTORY, f"{base_filename}_-RESULTS.csv"), + index=False, + ) def write_s3(df, filename, run_timestamp, output_type): @@ -255,9 +267,9 @@ def write_s3(df, filename, run_timestamp, output_type): df (pd.DataFrame): The DataFrame to be written to S3. run_timestamp (str): A timestamp representing the current run. - The function generates an S3 key based on the configured BATCH_ID and - uploads the DataFrame as a CSV file to the configured S3 bucket. - The CSV file is saved in the "consolidated" folder with the naming + The function generates an S3 key based on the configured BATCH_ID and + uploads the DataFrame as a CSV file to the configured S3 bucket. + The CSV file is saved in the "consolidated" folder with the naming convention "-RESULTS.csv". Configurations used: @@ -271,7 +283,9 @@ def write_s3(df, filename, run_timestamp, output_type): if output_type == "final": output_path = f"{config.BATCH_ID}/{run_timestamp}/{config.BATCH_ID}-RESULTS.csv" elif output_type == "individual": - output_path = f"{config.BATCH_ID}/{run_timestamp}/individual/{filename}-RESULTS.csv" + output_path = ( + f"{config.BATCH_ID}/{run_timestamp}/individual/{filename}-RESULTS.csv" + ) elif output_type == "error": output_path = f"{config.BATCH_ID}/{run_timestamp}/{config.BATCH_ID}-ERRORS.csv" else: @@ -281,9 +295,7 @@ def write_s3(df, filename, run_timestamp, output_type): csv_buffer = df.to_csv(index=False).encode() try: config.S3_CLIENT.put_object( - Bucket=config.S3_OUTPUT_BUCKET, - Key=output_path, - Body=csv_buffer + Bucket=config.S3_OUTPUT_BUCKET, Key=output_path, Body=csv_buffer ) except ClientError as e: print(f"Error uploading to S3: {e}") @@ -292,5 +304,3 @@ def write_s3(df, filename, run_timestamp, output_type): print(f"Saved batch output file: {output_path}") elif output_type == "individual": print(f"Saved individual output file: {output_path}") - - diff --git a/fieldExtraction/src/utils/llm_utils.py b/fieldExtraction/src/utils/llm_utils.py index aa09df4..60445bc 100644 --- a/fieldExtraction/src/utils/llm_utils.py +++ b/fieldExtraction/src/utils/llm_utils.py @@ -1,22 +1,20 @@ +import base64 import csv +import hashlib import inspect import json +import logging import math import random import time +from collections import OrderedDict import anthropic from botocore.exceptions import ClientError, ReadTimeoutError import src.utils.string_utils as string_utils from src import config -from collections import OrderedDict -import hashlib -import inspect -import csv -import base64 -import logging def update_stats(filename, tokens_sent, tokens_received, elapsed_time, model_type): # Update per-file statistics @@ -51,6 +49,7 @@ def update_stats(filename, tokens_sent, tokens_received, elapsed_time, model_typ config.GLOBAL_STATS.get(global_model_key, 0) + 1 ) + def count_tokens(s: str) -> int: """Count number of tokens in a string Currently uses very simplistic "divide characters by 6" to estimate token @@ -64,14 +63,15 @@ def count_tokens(s: str) -> int: """ return math.ceil(len(s) / 6) + def create_cost_log_entry( - filename: str, - caller_name: str, - prompt: str, - response: str, - input_cost_per_1k: float, - output_cost_per_1k: float, - ) -> dict: + filename: str, + caller_name: str, + prompt: str, + response: str, + input_cost_per_1k: float, + output_cost_per_1k: float, +) -> dict: """Generates an entry for the running cost log. Args: @@ -89,7 +89,7 @@ def create_cost_log_entry( input_cost = input_cost_per_1k * input_tokens / 1000 output_tokens = count_tokens(response) output_cost = output_cost_per_1k * output_tokens / 1000 - + cost_log_entry = { "Filename": filename, "Caller Function": caller_name, @@ -103,20 +103,25 @@ def create_cost_log_entry( } return cost_log_entry + # In-memory cache claude_cache = OrderedDict() CACHE_LIMIT = 20000 + def get_cache_key(prompt, model_id): """Generates a unique hash key for caching.""" return hashlib.sha256(f"{model_id}:{prompt}".encode()).hexdigest() -def invoke_claude(prompt: str, model_id: str, filename: str, max_tokens: int = 4096) -> str: + +def invoke_claude( + prompt: str, model_id: str, filename: str, max_tokens: int = 4096 +) -> str: """Invokes the Claude model with the given parameters. Args: prompt (str): The input prompt to send to the model. - model_id (str): The ID of the model to use. Supports both direct model IDs + model_id (str): The ID of the model to use. Supports both direct model IDs (e.g. 'anthropic.claude-3-5-sonnet-20240620-v1:0') as well as aliases (e.g. 'sonnet_latest', 'haiku_latest', etc.). filename (str): The name of the file being processed, for cost logging purposes. @@ -135,12 +140,12 @@ def invoke_claude(prompt: str, model_id: str, filename: str, max_tokens: int = 4 - Model aliases are resolved via config.resolve_model_id() """ cache_key = get_cache_key(prompt, model_id) - + # Check cache first - use get() method from LRUCache if cache_key in claude_cache: claude_cache.move_to_end(cache_key) # Mark as recently used return claude_cache[cache_key] - + # Resolve alias to actual model ID resolved_model_id = config.resolve_model_id(model_id) @@ -150,16 +155,24 @@ def invoke_claude(prompt: str, model_id: str, filename: str, max_tokens: int = 4 response = local_claude_2(prompt, resolved_model_id, filename, max_tokens) input_cost_per_1k, output_cost_per_1k = 0.008, 0.024 elif resolved_model_id == config.MODEL_ID_CLAUDE3_HAIKU: - response = local_claude_3_and_up(prompt, resolved_model_id, filename, max_tokens) + response = local_claude_3_and_up( + prompt, resolved_model_id, filename, max_tokens + ) input_cost_per_1k, output_cost_per_1k = 0.00025, 0.00125 elif resolved_model_id == config.MODEL_ID_CLAUDE35_SONNET: - response = local_claude_3_and_up(prompt, resolved_model_id, filename, max_tokens) + response = local_claude_3_and_up( + prompt, resolved_model_id, filename, max_tokens + ) input_cost_per_1k, output_cost_per_1k = 0.003, 0.015 elif resolved_model_id == config.MODEL_ID_CLAUDE37_SONNET: - response = local_claude_3_and_up(prompt, resolved_model_id, filename, max_tokens) + response = local_claude_3_and_up( + prompt, resolved_model_id, filename, max_tokens + ) input_cost_per_1k, output_cost_per_1k = 0.003, 0.015 elif resolved_model_id == config.MODEL_ID_CLAUDE4_SONNET: - response = local_claude_3_and_up(prompt, resolved_model_id, filename, max_tokens) + response = local_claude_3_and_up( + prompt, resolved_model_id, filename, max_tokens + ) input_cost_per_1k, output_cost_per_1k = 0.003, 0.015 else: raise ValueError(f"Unsupported model_id: {model_id}") @@ -168,16 +181,24 @@ def invoke_claude(prompt: str, model_id: str, filename: str, max_tokens: int = 4 response = ec2_claude2(prompt, resolved_model_id, filename, max_tokens) input_cost_per_1k, output_cost_per_1k = 0.008, 0.024 elif resolved_model_id == config.MODEL_ID_CLAUDE3_HAIKU: - response = ec2_claude_3_and_up(prompt, resolved_model_id, filename, max_tokens) + response = ec2_claude_3_and_up( + prompt, resolved_model_id, filename, max_tokens + ) input_cost_per_1k, output_cost_per_1k = 0.00025, 0.00125 elif resolved_model_id == config.MODEL_ID_CLAUDE35_SONNET: - response = ec2_claude_3_and_up(prompt, resolved_model_id, filename, max_tokens) + response = ec2_claude_3_and_up( + prompt, resolved_model_id, filename, max_tokens + ) input_cost_per_1k, output_cost_per_1k = 0.003, 0.015 elif resolved_model_id == config.MODEL_ID_CLAUDE37_SONNET: - response = ec2_claude_3_and_up(prompt, resolved_model_id, filename, max_tokens) + response = ec2_claude_3_and_up( + prompt, resolved_model_id, filename, max_tokens + ) input_cost_per_1k, output_cost_per_1k = 0.003, 0.015 elif resolved_model_id == config.MODEL_ID_CLAUDE4_SONNET: - response = ec2_claude_3_and_up(prompt, resolved_model_id, filename, max_tokens) + response = ec2_claude_3_and_up( + prompt, resolved_model_id, filename, max_tokens + ) input_cost_per_1k, output_cost_per_1k = 0.003, 0.015 else: raise ValueError(f"Unsupported model_id: {model_id}") @@ -203,42 +224,56 @@ def invoke_claude(prompt: str, model_id: str, filename: str, max_tokens: int = 4 cost_log_entry = create_cost_log_entry( filename, caller_name, prompt, response, input_cost_per_1k, output_cost_per_1k ) - with open(config.COST_LOG_NAME, 'a', newline="", encoding='utf-8') as f: + with open(config.COST_LOG_NAME, "a", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=config.COST_LOG_FIELDS) writer.writerow(cost_log_entry) return response + def get_cache_key_with_image_array(prompt, model_id, image_base64_list): """Generates a unique hash key for caching with a list of base64 images.""" - combined_images = ":".join(image_base64_list) # Combine all base64 images into a single string + combined_images = ":".join( + image_base64_list + ) # Combine all base64 images into a single string return hashlib.sha256(f"{model_id}:{prompt}:{combined_images}".encode()).hexdigest() -def invoke_claude_with_image_array(prompt, model_id, filename, base64_images,max_tokens=4096): + +def invoke_claude_with_image_array( + prompt, model_id, filename, base64_images, max_tokens=4096 +): cache_key = get_cache_key_with_image_array(prompt, model_id, base64_images) # print(f"Cache key for {filename}: {cache_key}") # printing for debugging # Check cache first - use get() method from LRUCache if cache_key in claude_cache: claude_cache.move_to_end(cache_key) # Mark as recently used return claude_cache[cache_key] - + # Resolve alias to actual model ID resolved_model_id = config.resolve_model_id(model_id) - + # Select execution mode if config.RUN_MODE == "local": if resolved_model_id == config.MODEL_ID_CLAUDE35_SONNET: - response = local_claude_3_and_up_with_image_array(prompt, resolved_model_id, filename, max_tokens,base64_images) - logging.info(f"Effective Date Response from local Claude 3 for {filename}: {response}") + response = local_claude_3_and_up_with_image_array( + prompt, resolved_model_id, filename, max_tokens, base64_images + ) + logging.info( + f"Effective Date Response from local Claude 3 for {filename}: {response}" + ) input_cost_per_1k, output_cost_per_1k = 0.003, 0.015 else: - raise ValueError(f"Unsupported model_id for image array: {resolved_model_id}") + raise ValueError( + f"Unsupported model_id for image array: {resolved_model_id}" + ) elif config.RUN_MODE == "ec2": if resolved_model_id == config.MODEL_ID_CLAUDE2: response = ec2_claude2(prompt, resolved_model_id, filename, max_tokens) input_cost_per_1k, output_cost_per_1k = 0.008, 0.024 else: - raise ValueError(f"Unsupported model_id for image array: {resolved_model_id}") + raise ValueError( + f"Unsupported model_id for image array: {resolved_model_id}" + ) else: print("Usage: python local_main.py <-local> OR python main.py ") @@ -262,12 +297,13 @@ def invoke_claude_with_image_array(prompt, model_id, filename, base64_images,max cost_log_entry = create_cost_log_entry( filename, caller_name, prompt, response, input_cost_per_1k, output_cost_per_1k ) - with open(config.COST_LOG_NAME, 'a', newline="", encoding='utf-8') as f: + with open(config.COST_LOG_NAME, "a", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=config.COST_LOG_FIELDS) writer.writerow(cost_log_entry) return response + # Claude calls def local_claude_2(prompt, model_id, filename, max_tokens): @@ -301,14 +337,19 @@ def local_claude_2(prompt, model_id, filename, max_tokens): def local_claude_3_and_up( - prompt: str, model_id: str, filename: str, max_tokens: int, max_retries: int = 5, initial_delay: int = 1 + prompt: str, + model_id: str, + filename: str, + max_tokens: int, + max_retries: int = 5, + initial_delay: int = 1, ): """ Handles local invocation of Claude 3 and above models (3.x, 3.5, 4.x) via AWS Bedrock. - + This function provides a unified interface for all modern Claude models using the messages API format. Includes exponential backoff retry logic for throttling errors. - + Args: prompt (str): Input text prompt to send to the model model_id (str): AWS Bedrock model identifier (e.g., 'anthropic.claude-3-5-sonnet-20241022-v2:0') @@ -316,14 +357,14 @@ def local_claude_3_and_up( max_tokens (int): Maximum number of tokens to generate in the response max_retries (int, optional): Maximum number of retry attempts for throttling errors. Defaults to 5. initial_delay (int, optional): Initial delay in seconds for exponential backoff. Defaults to 1. - + Returns: str: Generated text response from the Claude model - + Raises: ClientError: For AWS Bedrock API errors (non-throttling) Exception: When max retries are exceeded or other unexpected errors occur - + Note: - Uses the modern messages API format (anthropic_version: "bedrock-2023-05-31") - Supports answer tracking via config.ENABLE_ANSWER_TRACKING for caching/debugging @@ -377,11 +418,18 @@ def local_claude_3_and_up( def local_claude_3_and_up_with_image_array( - prompt, model_id, filename, max_tokens, base64_images, max_retries=5, initial_delay=1, image_media_type="image/png" + prompt, + model_id, + filename, + max_tokens, + base64_images, + max_retries=5, + initial_delay=1, + image_media_type="image/png", ): """ Processes a prompt along with an image in base64 format using the Claude 3 model via AWS Bedrock. - + Args: prompt (str): Input prompt to LLM. model_id (str): Model ID to use. @@ -391,13 +439,13 @@ def local_claude_3_and_up_with_image_array( max_retries (int): Maximum number of retries for the request. initial_delay (int): Initial delay for exponential backoff. image_media_type (str): Media type of the image (default: "image/jpeg"). - + Returns: str: Response text from the model. """ if config.ENABLE_ANSWER_TRACKING and prompt in config.ANSWER_TRACKING_DICT.keys(): return config.ANSWER_TRACKING_DICT[prompt] - + # Create the request body with the new message format body = json.dumps( { @@ -408,10 +456,7 @@ def local_claude_3_and_up_with_image_array( { "role": "user", "content": [ - { - "type": "text", - "text": "Images:" - }, + {"type": "text", "text": "Images:"}, *[ { "type": "image", @@ -423,16 +468,13 @@ def local_claude_3_and_up_with_image_array( } for image_base64 in base64_images ], - { - "type": "text", - "text": prompt - } + {"type": "text", "text": prompt}, ], } ], } ) - + for attempt in range(max_retries): try: response = config.BEDROCK_RUNTIME.invoke_model( @@ -443,12 +485,12 @@ def local_claude_3_and_up_with_image_array( ) response_body = json.loads(response.get("body").read()) response_text = response_body["content"][0]["text"] - + if config.ENABLE_ANSWER_TRACKING: config.ANSWER_TRACKING_DICT[prompt] = response_text - + return response_text - + except ClientError as e: if e.response["Error"]["Code"] in [ "ThrottlingException", @@ -461,9 +503,10 @@ def local_claude_3_and_up_with_image_array( raise else: raise - + raise Exception("Max retries exceeded") + def ec2_claude2(prompt, model_id, filename, max_tokens): start_time = time.time() body = json.dumps( @@ -491,15 +534,21 @@ def ec2_claude2(prompt, model_id, filename, max_tokens): update_stats(filename, tokens_sent, tokens_received, elapsed_time, "claude2") return response_text + def ec2_claude_3_and_up( - prompt: str, model_id: str, filename: str, max_tokens: int, max_retries: int = 5, initial_delay: int = 1 + prompt: str, + model_id: str, + filename: str, + max_tokens: int, + max_retries: int = 5, + initial_delay: int = 1, ): """ Handles EC2 invocation of Claude 3 and above models. When ENABLE_RUNTIME_ROTATION is True: Provides multi-runtime failover (UAT -> DEV -> PROD) with exponential backoff. - + When ENABLE_RUNTIME_ROTATION is False: Uses single runtime with exponential backoff retry logic. @@ -512,25 +561,25 @@ def ec2_claude_3_and_up( max_tokens (int): Maximum number of tokens to generate in the response max_retries (int, optional): Maximum retry attempts per runtime environment. Defaults to 5. initial_delay (int, optional): Initial delay in seconds for exponential backoff. Defaults to 1. - + Returns: str: Generated text response from the Claude model - + Raises: ClientError: For non-recoverable AWS Bedrock API errors ReadTimeoutError: For network timeout issues Exception: When all runtimes (when applicable) and retries are exhausted - + Runtime Failover Order (when ENABLE_RUNTIME_ROTATION is True): 1. UAT (primary runtime from config.BEDROCK_RUNTIME) - 2. DEV (fallback via role assumption) + 2. DEV (fallback via role assumption) 3. PROD (final fallback via role assumption) - + Stats Tracking: - Automatically determines model type (claude3/claude35/claude4) for granular tracking - Updates per-file and global statistics including tokens, timing, and request counts - Tracks model-specific usage patterns for cost analysis - + Note: - Uses messages API format for all Claude 3+ models - When rotation is enabled, runtime gets full retry cycle before switching to next @@ -587,17 +636,26 @@ def ec2_claude_3_and_up( elif model_id == config.MODEL_ID_CLAUDE4_SONNET: model_type = "claude4" else: - model_type = "claude3_plus" # fallback for unknown Claude 3+ models - + model_type = ( + "claude3_plus" # fallback for unknown Claude 3+ models + ) + update_stats( - filename, tokens_sent, tokens_received, elapsed_time, model_type + filename, + tokens_sent, + tokens_received, + elapsed_time, + model_type, ) return response_text except (ReadTimeoutError, ClientError) as e: error_code = e.response["Error"]["Code"] - if error_code in ["ThrottlingException", "TooManyRequestsException"]: + if error_code in [ + "ThrottlingException", + "TooManyRequestsException", + ]: if attempt < max_retries - 1: delay = (2**attempt + random.uniform(0, 1)) * initial_delay print( @@ -616,71 +674,75 @@ def ec2_claude_3_and_up( except Exception as e: print(f"Unexpected error: {str(e)}") raise - else: # Runtime rotation is disabled + else: # Runtime rotation is disabled for attempt in range(max_retries): - try: - response = config.BEDROCK_RUNTIME.invoke_model( - body=body, - modelId=model_id, - accept="application/json", - contentType="application/json", - ) - response_body = json.loads(response.get("body").read()) - response_text = response_body["content"][0]["text"] + try: + response = config.BEDROCK_RUNTIME.invoke_model( + body=body, + modelId=model_id, + accept="application/json", + contentType="application/json", + ) + response_body = json.loads(response.get("body").read()) + response_text = response_body["content"][0]["text"] - elapsed_time = time.time() - start_time - tokens_sent = len(prompt.split()) - tokens_received = len(response_text.split()) + elapsed_time = time.time() - start_time + tokens_sent = len(prompt.split()) + tokens_received = len(response_text.split()) - if filename: - # Determine model type for stats tracking - if model_id == config.MODEL_ID_CLAUDE3_HAIKU: - model_type = "claude3" - elif model_id == config.MODEL_ID_CLAUDE35_SONNET: - model_type = "claude35" - elif model_id == config.MODEL_ID_CLAUDE4_SONNET: - model_type = "claude4" - else: - model_type = "claude3_plus" # fallback for unknown Claude 3+ models - - update_stats( - filename, tokens_sent, tokens_received, elapsed_time, model_type + if filename: + # Determine model type for stats tracking + if model_id == config.MODEL_ID_CLAUDE3_HAIKU: + model_type = "claude3" + elif model_id == config.MODEL_ID_CLAUDE35_SONNET: + model_type = "claude35" + elif model_id == config.MODEL_ID_CLAUDE4_SONNET: + model_type = "claude4" + else: + model_type = ( + "claude3_plus" # fallback for unknown Claude 3+ models ) - return response_text - - except ReadTimeoutError as e: - # Timeout errors should probably retry + update_stats( + filename, tokens_sent, tokens_received, elapsed_time, model_type + ) + + return response_text + + except ReadTimeoutError as e: + # Timeout errors should probably retry + if attempt < max_retries - 1: + delay = (2**attempt + random.uniform(0, 1)) * initial_delay + print( + f"Attempt {attempt + 1} failed due to timeout. Retrying in {delay:.2f} seconds..." + ) + time.sleep(delay) + else: + raise + except ClientError as e: + error_code = e.response["Error"]["Code"] + if error_code in ["ThrottlingException", "TooManyRequestsException"]: if attempt < max_retries - 1: delay = (2**attempt + random.uniform(0, 1)) * initial_delay print( - f"Attempt {attempt + 1} failed due to timeout. Retrying in {delay:.2f} seconds..." + f"Attempt {attempt + 1} failed due to rate limiting. Retrying in {delay:.2f} seconds..." ) time.sleep(delay) else: raise - except ClientError as e: - error_code = e.response["Error"]["Code"] - if error_code in ["ThrottlingException", "TooManyRequestsException"]: - if attempt < max_retries - 1: - delay = (2**attempt + random.uniform(0, 1)) * initial_delay - print( - f"Attempt {attempt + 1} failed due to rate limiting. Retrying in {delay:.2f} seconds..." - ) - time.sleep(delay) - else: - raise - else: - print(f"Unexpected error: {str(e)}") - raise # Non-throttling client errors should fail immediately - - except Exception as e: + else: print(f"Unexpected error: {str(e)}") - raise + raise # Non-throttling client errors should fail immediately + + except Exception as e: + print(f"Unexpected error: {str(e)}") + raise raise Exception(f"Max retries ({max_retries}) exceeded for model {model_id}.") -def get_image_array_answer(prompt,filename,base64_images): - response_text = invoke_claude_with_image_array(prompt,config.MODEL_ID_CLAUDE35_SONNET,filename,base64_images) +def get_image_array_answer(prompt, filename, base64_images): + response_text = invoke_claude_with_image_array( + prompt, config.MODEL_ID_CLAUDE35_SONNET, filename, base64_images + ) response_dict = json.loads(response_text) return response_dict diff --git a/fieldExtraction/src/qa_qc/qa_qc_utils.py b/fieldExtraction/src/utils/qa_qc_utils.py similarity index 60% rename from fieldExtraction/src/qa_qc/qa_qc_utils.py rename to fieldExtraction/src/utils/qa_qc_utils.py index b597493..f8d23a0 100644 --- a/fieldExtraction/src/qa_qc/qa_qc_utils.py +++ b/fieldExtraction/src/utils/qa_qc_utils.py @@ -8,9 +8,9 @@ from openpyxl.utils.dataframe import dataframe_to_rows import src.utils.io_utils as io_utils import src.utils.string_utils as string_utils -from src import config, preprocessing_funcs -from src.constants.valid import (STATE_MAP, - keywords_to_detect_invalid_state_name) +from constants.valid import STATE_MAP, keywords_to_detect_invalid_state_name +from src import config +from src.investment import preprocessing_funcs def detect_date_anomalies(series, field_name, is_merged=False): @@ -49,8 +49,9 @@ def detect_date_anomalies(series, field_name, is_merged=False): return result + def tin_check(tin: str) -> tuple[str, bool]: - pattern = r'^\b\d{2}-\d{7}\b$' + pattern = r"^\b\d{2}-\d{7}\b$" if not isinstance(tin, str): try: @@ -59,17 +60,17 @@ def tin_check(tin: str) -> tuple[str, bool]: check = False return tin, check - - match=re.match(pattern, tin) + + match = re.match(pattern, tin) check = bool(match) - + tin = match.group(0) if match else tin return tin, check - + def npi_check(npi: str) -> tuple[str, bool]: - pattern = r'^[1-2]\d{9}$' + pattern = r"^[1-2]\d{9}$" if not isinstance(npi, str): try: npi = str(npi) @@ -77,17 +78,18 @@ def npi_check(npi: str) -> tuple[str, bool]: check = False return npi, check - + match = re.match(pattern, npi) check = bool(match) - + npi = match.group(0) if match else npi return npi, check + def date_format_check(date: str) -> tuple: - - format = '%m/%d/%Y' + + format = "%m/%d/%Y" if not isinstance(date, str): try: @@ -95,9 +97,9 @@ def date_format_check(date: str) -> tuple: except TypeError: check = False date = date - try: + try: dt = datetime.strptime(date, format) - date = dt.strftime('%-m/%-d/%Y') + date = dt.strftime("%-m/%-d/%Y") check = True except ValueError: date = date @@ -105,6 +107,7 @@ def date_format_check(date: str) -> tuple: return date, check + def yn_check(yn: str) -> tuple[str, bool]: if not isinstance(yn, str): try: @@ -113,8 +116,8 @@ def yn_check(yn: str) -> tuple[str, bool]: check = False return yn, check - - return yn, (yn == 'Y' or yn == 'N') + + return yn, (yn == "Y" or yn == "N") def state_check(state: str) -> tuple[str, bool]: @@ -137,6 +140,7 @@ def state_check(state: str) -> tuple[str, bool]: return state, check + def pages_pagenum_check(pages: int, pagenum: int) -> bool: if not isinstance(pages, int): @@ -146,7 +150,7 @@ def pages_pagenum_check(pages: int, pagenum: int) -> bool: check = False return check - + if not isinstance(pagenum, int): try: pagenum = int(pagenum) @@ -155,42 +159,57 @@ def pages_pagenum_check(pages: int, pagenum: int) -> bool: return check - check = (pages >= pagenum) + check = pages >= pagenum return check - + + def blanks_check(df: pd.DataFrame, threshold=config.BLANKS_THRESHOLD) -> pd.DataFrame: highly_blank_cols = [] for col in df.columns: - if df[col].apply(lambda x: string_utils.is_empty(x)).sum() / len(df[col]) > threshold: + if ( + df[col].apply(lambda x: string_utils.is_empty(x)).sum() / len(df[col]) + > threshold + ): highly_blank_cols.append(col) - return '\n'.join(highly_blank_cols) + return "\n".join(highly_blank_cols) + def self_talk_check(df: pd.DataFrame): - lang_cols = [col for col in df.columns if ('Language' in col or 'Clause' in col) and ('(Y/N)' not in col)] + lang_cols = [ + col + for col in df.columns + if ("Language" in col or "Clause" in col) and ("(Y/N)" not in col) + ] selftalkers = [] for col in lang_cols: - df['selftalk'] = df[col].apply(lambda x: any([(kw in x) for kw in keywords_to_detect_invalid_state_name]) if not string_utils.is_empty(x) else False) + df["selftalk"] = df[col].apply( + lambda x: ( + any([(kw in x) for kw in keywords_to_detect_invalid_state_name]) + if not string_utils.is_empty(x) + else False + ) + ) - if df['selftalk'].any(): + if df["selftalk"].any(): - pct = df['selftalk'].sum() / len(df['selftalk']) + pct = df["selftalk"].sum() / len(df["selftalk"]) - selftalkers.append(f'{col} - {pct:.1%}') + selftalkers.append(f"{col} - {pct:.1%}") + + return "\n".join(selftalkers) - - return '\n'.join(selftalkers) def perform_qc_qa_tests_both(ac_df, b_df, merged_df, input_dict, processed_files): file_name_column = config.FILE_NAME_COLUMN - + total_input_files = len(input_dict) if input_dict else 0 processed_ac_files = len( [f for f in processed_files if f in ac_df[file_name_column].values] @@ -205,8 +224,8 @@ def perform_qc_qa_tests_both(ac_df, b_df, merged_df, input_dict, processed_files total_input_files - processed_b_files if total_input_files > 0 else 0 ) - yn_cols_b = [col for col in b_df.columns if '(Y/N)' in col] - yn_cols_ac = [col for col in ac_df.columns if '(Y/N)' in col] + yn_cols_b = [col for col in b_df.columns if "(Y/N)" in col] + yn_cols_ac = [col for col in ac_df.columns if "(Y/N)" in col] general_stats = { "Total Number of Contracts processed": total_input_files, @@ -235,22 +254,70 @@ def perform_qc_qa_tests_both(ac_df, b_df, merged_df, input_dict, processed_files "# of duplicate rows for AC run": ac_df.duplicated().sum(), "# of duplicate rows for B run": b_df.duplicated().sum(), "Number of duplicate rows in merged DataFrame": merged_df.duplicated().sum(), - "# of fields for AC run" : len(ac_df.columns), - "# of fields for B run" : len(b_df.columns), - "Highly Blank Fields in merged DataFrame" : blanks_check(merged_df), - "Potential 'Self-Talk' columns in merged DataFrame" : self_talk_check(merged_df), - "# of incorrectly formatted Provider Group TINs" : ac_df["IRS #"].apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of incorrectly formatted Provider Group NPIs" : ac_df["NPI (10-digits)"].apply(lambda x: not npi_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of incorrectly formatted Provider Group Signatory TINs": ac_df["PROV_GROUP_TIN_SIGNATORY"].apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of incorrectly formatted Provider TINs (other)" : ac_df["PROV_TIN_OTHER"].apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of incorrectly formatted Provider NPIs (other)": ac_df["PROV_NPI_OTHER"].apply(lambda x: not npi_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of incorrect Health Plan States" : ac_df['Health Plan State'].apply(lambda x: not state_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of rows with Pages greater than Page Num" : merged_df.apply(lambda row: pages_pagenum_check(row['Pages'], row['Page_Num']) if (not string_utils.is_empty(row['Pages']) and string_utils.is_empty(row['Page_Num'])) else False, axis=1).sum(), - "# of Payer Names found in Notice to Provider Name" : ac_df.apply(lambda row: row['PAYER NAME'] in row['Notice to Provider Name'] if not string_utils.is_empty(row['Notice to Provider Name']) and not string_utils.is_empty(row['PAYER NAME']) else False, axis = 1).sum(), - "# of invalid Y/N values for B run" : b_df[yn_cols_b].apply(lambda x: yn_check(x)[1], axis = 1).sum().sum(), - "# of invalid Y/N values for AC run" : ac_df[yn_cols_ac].apply(lambda x: yn_check(x)[1], axis = 1).sum().sum(), - "# of invalid Termination Dates" : ac_df['Termination Date'].apply(lambda x: not date_format_check(x) if not string_utils.is_empty(x) else x).sum(), - "# of invalid Conract Effective Dates" : ac_df['Contract Effective Date'].apply(lambda x: not date_format_check(x) if not string_utils.is_empty(x) else x).sum() + "# of fields for AC run": len(ac_df.columns), + "# of fields for B run": len(b_df.columns), + "Highly Blank Fields in merged DataFrame": blanks_check(merged_df), + "Potential 'Self-Talk' columns in merged DataFrame": self_talk_check(merged_df), + "# of incorrectly formatted Provider Group TINs": ac_df["IRS #"] + .apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False) + .sum(), + "# of incorrectly formatted Provider Group NPIs": ac_df["NPI (10-digits)"] + .apply(lambda x: not npi_check(x)[1] if not string_utils.is_empty(x) else False) + .sum(), + "# of incorrectly formatted Provider Group Signatory TINs": ac_df[ + "PROV_GROUP_TIN_SIGNATORY" + ] + .apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False) + .sum(), + "# of incorrectly formatted Provider TINs (other)": ac_df["PROV_TIN_OTHER"] + .apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False) + .sum(), + "# of incorrectly formatted Provider NPIs (other)": ac_df["PROV_NPI_OTHER"] + .apply(lambda x: not npi_check(x)[1] if not string_utils.is_empty(x) else False) + .sum(), + "# of incorrect Health Plan States": ac_df["Health Plan State"] + .apply( + lambda x: not state_check(x)[1] if not string_utils.is_empty(x) else False + ) + .sum(), + "# of rows with Pages greater than Page Num": merged_df.apply( + lambda row: ( + pages_pagenum_check(row["Pages"], row["Page_Num"]) + if ( + not string_utils.is_empty(row["Pages"]) + and string_utils.is_empty(row["Page_Num"]) + ) + else False + ), + axis=1, + ).sum(), + "# of Payer Names found in Notice to Provider Name": ac_df.apply( + lambda row: ( + row["PAYER NAME"] in row["Notice to Provider Name"] + if not string_utils.is_empty(row["Notice to Provider Name"]) + and not string_utils.is_empty(row["PAYER NAME"]) + else False + ), + axis=1, + ).sum(), + "# of invalid Y/N values for B run": b_df[yn_cols_b] + .apply(lambda x: yn_check(x)[1], axis=1) + .sum() + .sum(), + "# of invalid Y/N values for AC run": ac_df[yn_cols_ac] + .apply(lambda x: yn_check(x)[1], axis=1) + .sum() + .sum(), + "# of invalid Termination Dates": ac_df["Termination Date"] + .apply( + lambda x: not date_format_check(x) if not string_utils.is_empty(x) else x + ) + .sum(), + "# of invalid Conract Effective Dates": ac_df["Contract Effective Date"] + .apply( + lambda x: not date_format_check(x) if not string_utils.is_empty(x) else x + ) + .sum(), } ac_stats = {} @@ -300,7 +367,7 @@ def perform_qc_qa_tests_ac(ac_df, input_dict, processed_files): total_input_files - processed_ac_files if total_input_files > 0 else 0 ) - yn_cols_ac = [col for col in ac_df.columns if '(Y/N)' in col] + yn_cols_ac = [col for col in ac_df.columns if "(Y/N)" in col] general_stats = { "Total Number of Contracts processed": total_input_files, @@ -315,19 +382,53 @@ def perform_qc_qa_tests_ac(ac_df, input_dict, processed_files): .all(axis=1) .sum(), "# of duplicate rows for AC run": ac_df.duplicated().sum(), - "# of fields for AC run" : len(ac_df.columns), - "Highly Blank Fields in AC DataFrame" : blanks_check(ac_df), - "Potential 'Self-Talk' columns in merged DataFrame" : self_talk_check(ac_df), - "# of incorrectly formatted Provider Group TINs" : ac_df["IRS #"].apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of incorrectly formatted Provider Group NPIs" : ac_df["NPI (10-digits)"].apply(lambda x: not npi_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of incorrectly formatted Provider Group Signatory TINs": ac_df["PROV_GROUP_TIN_SIGNATORY"].apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of incorrectly formatted Provider TINs (other)" : ac_df["PROV_TIN_OTHER"].apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of incorrectly formatted Provider NPIs (other)": ac_df["PROV_NPI_OTHER"].apply(lambda x: not npi_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of incorrect Health Plan States" : ac_df['Health Plan State'].apply(lambda x: not state_check(x)[1] if not string_utils.is_empty(x) else False).sum(), - "# of Payer Names found in Notice to Provider Name" : ac_df.apply(lambda row: row['PAYER NAME'] in row['Notice to Provider Name'] if not string_utils.is_empty(row['Notice to Provider Name']) else False, axis = 1).sum(), - "# of invalid Y/N values for AC run" : ac_df[yn_cols_ac].apply(lambda x: yn_check(x)[1], axis = 1).sum().sum(), - "# of invalid Termination Dates" : ac_df['Termination Date'].apply(lambda x: not date_format_check(x) if not string_utils.is_empty(x) else x).sum(), - "# of invalid Conract Effective Dates" : ac_df['Contract Effective Date'].apply(lambda x: not date_format_check(x) if not string_utils.is_empty(x) else x).sum() + "# of fields for AC run": len(ac_df.columns), + "Highly Blank Fields in AC DataFrame": blanks_check(ac_df), + "Potential 'Self-Talk' columns in merged DataFrame": self_talk_check(ac_df), + "# of incorrectly formatted Provider Group TINs": ac_df["IRS #"] + .apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False) + .sum(), + "# of incorrectly formatted Provider Group NPIs": ac_df["NPI (10-digits)"] + .apply(lambda x: not npi_check(x)[1] if not string_utils.is_empty(x) else False) + .sum(), + "# of incorrectly formatted Provider Group Signatory TINs": ac_df[ + "PROV_GROUP_TIN_SIGNATORY" + ] + .apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False) + .sum(), + "# of incorrectly formatted Provider TINs (other)": ac_df["PROV_TIN_OTHER"] + .apply(lambda x: not tin_check(x)[1] if not string_utils.is_empty(x) else False) + .sum(), + "# of incorrectly formatted Provider NPIs (other)": ac_df["PROV_NPI_OTHER"] + .apply(lambda x: not npi_check(x)[1] if not string_utils.is_empty(x) else False) + .sum(), + "# of incorrect Health Plan States": ac_df["Health Plan State"] + .apply( + lambda x: not state_check(x)[1] if not string_utils.is_empty(x) else False + ) + .sum(), + "# of Payer Names found in Notice to Provider Name": ac_df.apply( + lambda row: ( + row["PAYER NAME"] in row["Notice to Provider Name"] + if not string_utils.is_empty(row["Notice to Provider Name"]) + else False + ), + axis=1, + ).sum(), + "# of invalid Y/N values for AC run": ac_df[yn_cols_ac] + .apply(lambda x: yn_check(x)[1], axis=1) + .sum() + .sum(), + "# of invalid Termination Dates": ac_df["Termination Date"] + .apply( + lambda x: not date_format_check(x) if not string_utils.is_empty(x) else x + ) + .sum(), + "# of invalid Conract Effective Dates": ac_df["Contract Effective Date"] + .apply( + lambda x: not date_format_check(x) if not string_utils.is_empty(x) else x + ) + .sum(), } ac_stats = {} @@ -353,8 +454,7 @@ def perform_qc_qa_tests_b(b_df, input_dict, processed_files): total_input_files - processed_b_files if total_input_files > 0 else 0 ) - - yn_cols_b = [col for col in b_df.columns if '(Y/N)' in col] + yn_cols_b = [col for col in b_df.columns if "(Y/N)" in col] general_stats = { "Total Number of Contracts processed": total_input_files, @@ -369,11 +469,17 @@ def perform_qc_qa_tests_b(b_df, input_dict, processed_files): .all(axis=1) .sum(), "# of duplicate rows for B run": b_df.duplicated().sum(), - "# of fields for B run" : len(b_df.columns), - "Highly Blank Fields in B DataFrame" : blanks_check(b_df), - "Potential 'Self-Talk' columns in merged DataFrame" : self_talk_check(b_df), - "# of rows with Pages greater than Page Num" : b_df.apply(lambda row: pages_pagenum_check(row['Pages'], row['Page_Num']), axis=1).sum(), - "# of invalid Y/N values for B run" : b_df[yn_cols_b].apply(lambda x: yn_check(x)[1], axis = 1).sum().sum()} + "# of fields for B run": len(b_df.columns), + "Highly Blank Fields in B DataFrame": blanks_check(b_df), + "Potential 'Self-Talk' columns in merged DataFrame": self_talk_check(b_df), + "# of rows with Pages greater than Page Num": b_df.apply( + lambda row: pages_pagenum_check(row["Pages"], row["Page_Num"]), axis=1 + ).sum(), + "# of invalid Y/N values for B run": b_df[yn_cols_b] + .apply(lambda x: yn_check(x)[1], axis=1) + .sum() + .sum(), + } b_stats = {} for field in [col for col in b_df.columns if col != file_name_column]: @@ -397,7 +503,6 @@ def perform_qc_qa_tests_b(b_df, input_dict, processed_files): return general_stats, b_stats - def get_table_stats(text_dict): table_count, num_table_pages, rate_count = 0, 0, 0 @@ -432,6 +537,7 @@ def get_table_stats(text_dict): "Num >=10 Rates": len(rates_more_than_10), } + def table_stats_qaqc(input_dict, ac_df, b_df, file_name_column) -> list[dict]: table_stats = [] @@ -460,122 +566,126 @@ def table_stats_qaqc(input_dict, ac_df, b_df, file_name_column) -> list[dict]: ">=10 Rate Pages": ", ".join(map(str, stats[">=10 Rate Pages"])), } ) - + return table_stats + def perform_qc_qa(ac_final_df, b_final_df, abc_final_df, input_dict): - # Perform QA/QC analysis - wb = Workbook() - - ws_general = wb.active - ws_general.title = "General Stats" - timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - - # Get list of all processed files - processed_files = [] - if ac_final_df is not None: - processed_files.extend(ac_final_df["Contract Name"].tolist()) - if b_final_df is not None: - processed_files.extend(b_final_df["Contract Name"].tolist()) - processed_files = list(set(processed_files)) - - # Generate QA/QC stats based on available dataframes - if ac_final_df is not None and b_final_df is not None: - general_stats, ac_stats, b_stats = perform_qc_qa_tests_both( - ac_final_df, b_final_df, abc_final_df, input_dict, processed_files - ) - elif ac_final_df is not None: - general_stats, ac_stats = perform_qc_qa_tests_ac( - ac_final_df, input_dict, processed_files - ) - else: - general_stats, b_stats = perform_qc_qa_tests_b( - b_final_df, input_dict, processed_files - ) + # Perform QA/QC analysis + wb = Workbook() + ws_general = wb.active + ws_general.title = "General Stats" + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + # Get list of all processed files + processed_files = [] + if ac_final_df is not None: + processed_files.extend(ac_final_df["Contract Name"].tolist()) + if b_final_df is not None: + processed_files.extend(b_final_df["Contract Name"].tolist()) + processed_files = list(set(processed_files)) + + # Generate QA/QC stats based on available dataframes + if ac_final_df is not None and b_final_df is not None: + general_stats, ac_stats, b_stats = perform_qc_qa_tests_both( + ac_final_df, b_final_df, abc_final_df, input_dict, processed_files + ) + elif ac_final_df is not None: + general_stats, ac_stats = perform_qc_qa_tests_ac( + ac_final_df, input_dict, processed_files + ) + else: + general_stats, b_stats = perform_qc_qa_tests_b( + b_final_df, input_dict, processed_files + ) + + # Add metadata to general stats + general_stats_with_metadata = { + "Report Generation Time": timestamp, + "Batch ID": config.BATCH_ID, + "AC Processing": ( + "Yes" + if ("a" in config.FIELDS and "c" in config.FIELDS) or (config.AC_DF) + else "No" + ), + "B Processing": "Yes" if ("b" in config.FIELDS) or (config.B_DF) else "No", + **general_stats, + } + + # Write general stats + general_df = pd.DataFrame.from_dict( + general_stats_with_metadata, orient="index", columns=["Value"] + ) + for r in dataframe_to_rows(general_df, index=True, header=True): + ws_general.append(r) + + # Write AC stats if available + if ac_final_df is not None: + ws_ac = wb.create_sheet("AC Fields Stats") + ac_stats_df = pd.DataFrame(ac_stats).T + for r in dataframe_to_rows(ac_stats_df, index=True, header=True): + ws_ac.append(r) + + # Write B stats if available + if b_final_df is not None: + ws_b = wb.create_sheet("B Fields Stats") + b_stats_df = pd.DataFrame(b_stats).T + for r in dataframe_to_rows(b_stats_df, index=True, header=True): + ws_b.append(r) - # Add metadata to general stats - general_stats_with_metadata = { - "Report Generation Time": timestamp, - "Batch ID": config.BATCH_ID, - "AC Processing": "Yes" if ("a" in config.FIELDS and "c" in config.FIELDS) or (config.AC_DF) else "No", - "B Processing": "Yes" if ("b" in config.FIELDS) or (config.B_DF) else "No", - **general_stats, } - - - # Write general stats - general_df = pd.DataFrame.from_dict( - general_stats_with_metadata, orient="index", columns=["Value"] - ) - for r in dataframe_to_rows(general_df, index=True, header=True): - ws_general.append(r) - - - # Write AC stats if available - if ac_final_df is not None: - ws_ac = wb.create_sheet("AC Fields Stats") - ac_stats_df = pd.DataFrame(ac_stats).T - for r in dataframe_to_rows(ac_stats_df, index=True, header=True): - ws_ac.append(r) - - - # Write B stats if available - if b_final_df is not None: - ws_b = wb.create_sheet("B Fields Stats") - b_stats_df = pd.DataFrame(b_stats).T - for r in dataframe_to_rows(b_stats_df, index=True, header=True): - ws_b.append(r) - - # Generate table statistics - table_stats = table_stats_qaqc(input_dict, ac_final_df, b_final_df, config.FILE_NAME_COLUMN) - - ws_table_stats = wb.create_sheet("Table and Reimbursement Stats") - table_stats_df = pd.DataFrame(table_stats) - for r in dataframe_to_rows(table_stats_df, index=False, header=True): + table_stats = table_stats_qaqc( + input_dict, ac_final_df, b_final_df, config.FILE_NAME_COLUMN + ) + + ws_table_stats = wb.create_sheet("Table and Reimbursement Stats") + table_stats_df = pd.DataFrame(table_stats) + for r in dataframe_to_rows(table_stats_df, index=False, header=True): ws_table_stats.append(r) return wb + def save_qcqa_wb(wb, run_timestamp): - - # Save QC report locally - report_filename = f'QC_QA_Report_{config.BATCH_ID}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx' - report_path = os.path.join(config.CONSOLIDATED_OUTPUT_DIRECTORY, report_filename) - wb.save(report_path) - print(f"QC/QA Report saved to {report_path}") - - - # Upload to S3 - if config.WRITE_TO_S3: - BUCKET_NAME = config.S3_OUTPUT_BUCKET - print(f"Uploading final outputs to s3://{BUCKET_NAME}/qcqa/{run_timestamp}") - - - # Upload consolidated outputs - consolidated_files = [ - f"{config.BATCH_ID}-AC.csv", - f"{config.BATCH_ID}-B.csv", - f"{config.BATCH_ID}-ABC.csv", - report_filename #Add QC Report to upload list - ] - - - for filename in consolidated_files: - local_path = os.path.join(config.CONSOLIDATED_OUTPUT_DIRECTORY, filename) + + # Save QC report locally + report_filename = f'QC_QA_Report_{config.BATCH_ID}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx' + report_path = os.path.join(config.CONSOLIDATED_OUTPUT_DIRECTORY, report_filename) + wb.save(report_path) + print(f"QC/QA Report saved to {report_path}") + + # Upload to S3 + if config.WRITE_TO_S3: + BUCKET_NAME = config.S3_OUTPUT_BUCKET + print(f"Uploading final outputs to s3://{BUCKET_NAME}/qcqa/{run_timestamp}") + + # Upload consolidated outputs + consolidated_files = [ + f"{config.BATCH_ID}-AC.csv", + f"{config.BATCH_ID}-B.csv", + f"{config.BATCH_ID}-ABC.csv", + report_filename, # Add QC Report to upload list + ] + + for filename in consolidated_files: + local_path = os.path.join(config.CONSOLIDATED_OUTPUT_DIRECTORY, filename) if os.path.exists(local_path): if filename == report_filename: - s3_key=f"{config.BATCH_ID}/{run_timestamp}/consolidated/qcqa/{filename}" + s3_key = f"{config.BATCH_ID}/{run_timestamp}/consolidated/qcqa/{filename}" else: - s3_key = f"{config.BATCH_ID}/{run_timestamp}/consolidated/{filename}" - try: - print(f"uploading {local_path} to s3://{BUCKET_NAME}/{s3_key}") - config.S3_CLIENT.upload_file(local_path, BUCKET_NAME, s3_key) - except Exception as e: - print(f"Error uploading {local_path}: {str(e)}") - else: - print(f"File not found: {local_path}") - - print(f"Upload completed to s3://{BUCKET_NAME}/{config.BATCH_ID}/{run_timestamp}") \ No newline at end of file + s3_key = ( + f"{config.BATCH_ID}/{run_timestamp}/consolidated/{filename}" + ) + try: + print(f"uploading {local_path} to s3://{BUCKET_NAME}/{s3_key}") + config.S3_CLIENT.upload_file(local_path, BUCKET_NAME, s3_key) + except Exception as e: + print(f"Error uploading {local_path}: {str(e)}") + else: + print(f"File not found: {local_path}") + + print( + f"Upload completed to s3://{BUCKET_NAME}/{config.BATCH_ID}/{run_timestamp}" + ) diff --git a/fieldExtraction/src/utils/string_utils.py b/fieldExtraction/src/utils/string_utils.py index 33239eb..8ec82ee 100644 --- a/fieldExtraction/src/utils/string_utils.py +++ b/fieldExtraction/src/utils/string_utils.py @@ -7,12 +7,12 @@ from datetime import datetime import numpy as np import pandas as pd -import src.utils.llm_utils as llm_utils -from src import config -from src.enums.delimiters import Delimiter -from src.prompts import preprocessing_prompts -from src.regex.regex_patterns import (BACKTICK_PATTERN, PIPE_PATTERN, - TRIPLE_BACKTICK_PATTERN) +from constants.delimiters import Delimiter +from constants.regex_patterns import ( + BACKTICK_PATTERN, + PIPE_PATTERN, + TRIPLE_BACKTICK_PATTERN, +) def extract_text_from_delimiters( @@ -184,78 +184,6 @@ def json_parsing_search(response_text: str, field_list: list[str]) -> dict[str, return dict(zip(field_l, answer_l)) -def secondary_string_to_dict(dict_string: str, filename: str) -> dict: - """ - Converts a string representation of a dictionary into an actual dictionary object, handling potential formatting issues. - - This function cleans the string by removing specific unwanted characters and markers that may interfere with JSON parsing. - It then locates the substring that correctly forms a dictionary format, attempts to parse it as JSON, and handles common - parsing errors by replacing problematic single quotes with double quotes before re-parsing. - - Parameters: - dict_string (str): The string containing the dictionary-like content, potentially surrounded by extra text or characters. - filename (str): The filename associated with these entries, used to tag each resulting dictionary. - - Returns: - dict: The dictionary obtained from parsing the cleaned and corrected string. - """ - start_index = dict_string.find("{") - end_index = dict_string.rfind("}") + 1 - dict_substring = dict_string[start_index:end_index] - try: - result_dict = json.loads(dict_substring) - except: - try: - dict_substring = dict_substring.replace("'", '"') - result_dict = json.loads(dict_substring) - except: - try: - prompt = preprocessing_prompts.FIX_JSON(dict_substring) - dict_substring = llm_utils.invoke_claude( - prompt, "haiku_latest", filename - ) - result_dict = json.loads(dict_substring) - except Exception as e: - print(e) - result_dict = {} - return result_dict - - -def primary_string_to_dict(string_dict, filename): - """ - Converts a dictionary of strings, where each string represents multiple dictionary entries, into a list of dictionaries, - augmenting each with metadata such as page number and filename. - - This function iterates over each page number's string data, extracting and converting string representations of - dictionaries into actual dictionary objects. It handles and cleans the string format to properly parse it into dictionaries. - All dictionaries are then augmented with their respective page number and the filename before being compiled into a list. - - Parameters: - string_dict (dict): A dictionary where keys are page numbers and values are strings containing multiple dictionary entries. - filename (str): The filename associated with these entries, used to tag each resulting dictionary. - - Returns: - list of dict: A list of dictionaries, each representing data extracted and converted from the input string, tagged with - their page number and filename. - """ - data = [] - pattern = r"\{.*?\}" - for page_num in string_dict.keys(): - primary_list = string_dict[page_num] - dicts = primary_list.split("[")[1] # Strip front - dicts = dicts.split("]")[0] # Strip back - dicts = dicts.replace("\n", "") # Remove new lines - dicts = dicts.replace("<<<", "") - dicts = dicts.replace(">>>", "") - dicts = re.sub(r"(? int: """Count number of tables found in an exhibit, through use of a regex that catches the table start and end strings. @@ -147,7 +130,6 @@ def get_page_metadata(page: str) -> Union[List[str], List[Tuple]]: print("Malformed table in get_page_metadata!") return [] - # return the common metadata for the table (or the group of tables starting with this one) if not page_contains_multiple_tables(page): group_metadata = page[: page.find(START_MARKER)].strip() @@ -195,7 +177,7 @@ def convert_str_to_dict(text: str) -> Dict[str, Any]: def group_pages_containing_tables( text_dict: Dict[str, str] ) -> Dict[str, Dict[str, Any]]: - #TODO: add test cases for this function + # TODO: add test cases for this function """ Groups pages containing tables into start and continuation groups based on patterns. @@ -311,7 +293,7 @@ def correct_column_headers_and_merge( page_tables_map = {} - columns_to_use: List[str] = [] # for mypy + columns_to_use: List[str] = [] # for mypy for group_start_page, group_data in groups.items(): # Loop through groups dfs_list = [] @@ -325,10 +307,12 @@ def correct_column_headers_and_merge( data_dict = convert_str_to_dict(str_dict) # if the page contains multiple tables, rewrite the latest column headers each time # Otherwise, use the column headers from the first table in the group - columns_to_use = data_dict.keys() if page == group_start_page else columns_to_use + columns_to_use = ( + data_dict.keys() if page == group_start_page else columns_to_use + ) col_corrected_dict = insert_column_headers(data_dict, columns_to_use) - + if ( page == group_start_page ): # on the first table, we've already changed the keys to be the first row, so we need to drop that row @@ -368,9 +352,7 @@ def group_and_merge_tables(text_dict: dict[str, str]) -> tuple[dict, dict]: return groups, page_tables_map -def split_table_to_pages( - groups: dict, page_tables_map: dict, row_limit: int -) -> dict: +def split_table_to_pages(groups: dict, page_tables_map: dict, row_limit: int) -> dict: """ Splits tables into pages based on the provided groups and page tables map. Args: @@ -510,6 +492,7 @@ def reinsert_new_tables_to_text(group_table_split_info: dict, text_dict: dict) - return modified_text_dict + def split_tables_to_separate_subpages(text_dict: dict[str, str]) -> dict[str, str]: """ Splits the text content of each page in the input dictionary into separate subpages based on a table end marker. @@ -524,9 +507,11 @@ def split_tables_to_separate_subpages(text_dict: dict[str, str]) -> dict[str, st for page in text_dict.keys(): # split the text into separate pages based on the table end marker if END_MARKER in text_dict[page]: - new_text_list = re.split(fr"(?<={END_MARKER})", text_dict[page]) # this regex will split on the end marker, and keep the end marker in the text + new_text_list = re.split( + rf"(?<={END_MARKER})", text_dict[page] + ) # this regex will split on the end marker, and keep the end marker in the text for i, text in enumerate(new_text_list): new_text_dict[f"{page}.{i}"] = text else: new_text_dict[page] = text_dict[page] - return {k : v for k, v in new_text_dict.items() if v.strip()} # remove empty strings + return {k: v for k, v in new_text_dict.items() if v.strip()} # remove empty strings diff --git a/fieldExtraction/tests/exhibit_chunk_mapping_test.py b/fieldExtraction/tests/exhibit_chunk_mapping_test.py deleted file mode 100644 index f8d4da5..0000000 --- a/fieldExtraction/tests/exhibit_chunk_mapping_test.py +++ /dev/null @@ -1,31 +0,0 @@ - -from src.preprocessing_funcs import chunk_by_exhibit - -def test1(): - """Test non-empty text_dict with empty exhibit_pages.""" - text_dict = {'1': 'This', '2': 'is', '3': 'an', '4': 'exhibit', '5': 'test'} - exhibit_pages = [] - assert chunk_by_exhibit(text_dict, exhibit_pages) == {'1': '1', '2': '2', '3': '3', '4': '4', '5': '5'} - -def test2(): - """Test when exhibit_pages contains some keys that are also in text_dict.""" - text_dict = {'1': 'Welcome', '2': 'to', '3': 'the', '4': 'exhibit'} - exhibit_pages = ['1', '2', '4'] - expected_output = {'1': '1', '2': '2', '3': '2', '4': '4'} - assert chunk_by_exhibit(text_dict, exhibit_pages) == expected_output - - -def test3(): - """Test multiple exhibit pages with transitions between them.""" - text_dict = {'1': 'Page 1', '2': 'Page 2', '3': 'Page 3', '4': 'Page 4', '5': 'Page 5'} - exhibit_pages = ['2', '4'] - expected_output = {'1': '0', '2': '2', '3': '2', '4': '4', '5': '4'} - assert chunk_by_exhibit(text_dict, exhibit_pages) == expected_output - - -test1() - -test2() - -test3() - diff --git a/fieldExtraction/tests/fieldset_test.py b/fieldExtraction/tests/fieldset_test.py deleted file mode 100644 index d273d37..0000000 --- a/fieldExtraction/tests/fieldset_test.py +++ /dev/null @@ -1,134 +0,0 @@ -import unittest -from unittest.mock import patch, MagicMock, mock_open - -from src.prompts.investment_prompts import Field, FieldSet - -class TestField(unittest.TestCase): - - def setUp(self): - self.field_dict = { - 'field_name': 'test_field', - 'relationship': 'one_to_one', - 'field_type': 'exhibit_level', - 'prompt': 'Please provide {valid_values}.', - 'valid_values': 'some_value' - } - self.field = Field(self.field_dict) - - def test_init(self): - """Test initialization of Field object.""" - self.assertEqual(self.field.field_name, 'test_field') - self.assertEqual(self.field.relationship, 'one_to_one') - self.assertEqual(self.field.field_type, 'exhibit_level') - self.assertEqual(self.field.prompt, 'Please provide {valid_values}.') - self.assertEqual(self.field.valid_values, 'some_value') - - def test_from_values(self): - """Test the class method 'from_values'.""" - new_field = Field.from_values( - field_name='new_field', - relationship='one_to_n', - field_type='dynamic', - prompt='Choose only from the following: {valid_values}.', - valid_values='new_value' - ) - self.assertEqual(new_field.field_name, 'new_field') - self.assertEqual(new_field.relationship, 'one_to_n') - self.assertEqual(new_field.field_type, 'dynamic') - self.assertEqual(new_field.prompt, 'Choose only from the following: {valid_values}.') - self.assertEqual(new_field.valid_values, 'new_value') - - def test_to_dict(self): - """Test conversion to dictionary.""" - field_dict = self.field.to_dict() - self.assertEqual(field_dict['field_name'], 'test_field') - self.assertEqual(field_dict['relationship'], 'one_to_one') - self.assertEqual(field_dict['field_type'], 'exhibit_level') - self.assertEqual(field_dict['prompt'], 'Please provide {valid_values}.') - self.assertEqual(field_dict['valid_values'], 'some_value') - - def test_print(self): - """Test printing of Field object.""" - # Normally we'd check print output, but here we'll assert we reach print calls. - with patch('builtins.print') as mocked_print: - self.field.print() - mocked_print.assert_any_call('Field Name: test_field') - - def test_get_prompt_dict(self): - """Test get_prompt_dict method.""" - prompt_dict = self.field.get_prompt_dict() - self.assertEqual(prompt_dict['test_field'], 'Please provide some_value.') - - def test_get_prompt(self): - """Test prompt resolution.""" - resolved_prompt = self.field.get_prompt() - self.assertEqual(resolved_prompt, 'Please provide some_value.') - - def test_update_valid_values(self): - """Test updating valid values.""" - self.field.update_valid_values('new_value') - self.assertEqual(self.field.valid_values, 'new_value') - - -class TestFieldSet(unittest.TestCase): - - def setUp(self): - self.field_dict = { - 'field_name': 'test_field', - 'relationship': 'one_to_one', - 'field_type': 'exhibit_level', - 'prompt': 'Please provide {valid_values}.', - 'valid_values': 'some_value' - } - self.field = Field(self.field_dict) - self.field_set = FieldSet() - - def test_add_field(self): - """Test adding a field.""" - self.field_set.add_field(self.field) - self.assertEqual(len(self.field_set.fields), 1) - - def test_add_duplicate_field(self): - """Test adding a duplicate field (should do nothing)""" - self.field_set.add_field(self.field) - self.assertEqual(len(self.field_set.fields), 1) - - - def test_get_field(self): - """Test getting a specific field.""" - self.field_set.add_field(self.field) - fetched_field = self.field_set.get_field('test_field') - self.assertEqual(fetched_field.field_name, 'test_field') - - def test_get_field_not_found(self): - """Test getting a non-existent field (should raise ValueError).""" - with self.assertRaises(ValueError): - self.field_set.get_field('non_existent_field') - - def test_list_fields(self): - """Test listing all fields.""" - self.field_set.add_field(self.field) - fields = self.field_set.list_fields() - self.assertIn('test_field', fields) - - def test_remove_field(self): - """Test removing a field.""" - self.field_set.add_field(self.field) - self.field_set.remove_field('test_field') - self.assertEqual(len(self.field_set.fields), 0) - - def test_get_prompt_dict(self): - """Test getting prompt dictionary.""" - self.field_set.add_field(self.field) - prompt_dict = self.field_set.get_prompt_dict() - self.assertIn('test_field', prompt_dict) - - def test_load_from_file(self): - """Test loading fields from a file (mocked).""" - with patch('builtins.open', mock_open(read_data='[{"field_name": "test_field", "relationship": "one_to_one", "field_type": "exhibit_level", "prompt": "Please provide {valid_values}.", "valid_values": "some_value"}]')): - self.field_set.load_from_file('mock_file.json') - self.assertEqual(len(self.field_set.fields), 1) - - -if __name__ == '__main__': - unittest.main() diff --git a/fieldExtraction/tests/postprocessing_funcs_test.py b/fieldExtraction/tests/postprocessing_funcs_test.py deleted file mode 100644 index dc83ad0..0000000 --- a/fieldExtraction/tests/postprocessing_funcs_test.py +++ /dev/null @@ -1,625 +0,0 @@ - -import pytest -import pandas as pd -import numpy as np -from src.postprocessing_funcs import check_add_on -from src.postprocessing_funcs import ( - InvalidDateException, - yn_fixes, - convert_to_us_date_format, - filter_service_column, - filter_methodology_column, - clean_td, - get_parent_agreement_code, - consolidate_subheader, - clean_msr_lesser, - clean_default_term, - clean_lesser_rate, - clean_prov_2, - clean_term_clause, - clean_auto_renewal_ind, - clean_npi, - clean_network_access_fees, - clean_health_plan_state, - get_health_plan_state, - state_check, - clean_health_plan_state_from_hotfix, - clean_notice_provider_name_and_address, - get_tin_from_filename, - clean_policies_and_procedures, - clean_tin, - clean_tin_npi_other, - filter_add_ons, - add_scmr, - add_hyphen_if_needed, - remove_unnamed_columns, - format_tin, - count_dollar_values -) -class TestPostprocessingFuncs: - @pytest.fixture - def valid_dates(self): - return [ - ("2023-12-01", "12/01/2023"), - ("2023-01-12", "01/12/2023"), - ("2023-13-01", "01/13/2023"), - ] - @pytest.fixture - def invalid_dates(self): - return ["2023-13-32", "2023-00-01", "2023-12-00", "2023-12-32", "2023-13-13"] - @pytest.fixture - def empty_dates(self): - return ["", "N/A", "null", "none"] - @pytest.fixture - def invalid_format_dates(self): - return ["2023/12/01", "12-01-2023", "2023.12.01"] - @pytest.fixture - def invalid_type_dates(self): - return [None] - def test_convert_to_us_date_format( - self, - valid_dates, - invalid_dates, - empty_dates, - invalid_format_dates, - invalid_type_dates, - ): - for input_date, expected_output in valid_dates: - assert convert_to_us_date_format(input_date) == expected_output - for input_date in invalid_dates: - with pytest.raises(InvalidDateException): - convert_to_us_date_format(input_date) - for input_date in empty_dates: - with pytest.raises(InvalidDateException): - convert_to_us_date_format(input_date) - for input_date in invalid_format_dates: - with pytest.raises(InvalidDateException): - convert_to_us_date_format(input_date) - for input_date in invalid_type_dates: - with pytest.raises(TypeError): - convert_to_us_date_format(input_date) - - @pytest.fixture - def sample_answer_dicts(self): - return [ - {"FULL_SERVICE": "Service Risk", "FULL_METHODOLOGY": "Methodology interest"}, - {"FULL_SERVICE": "Service B", "FULL_METHODOLOGY": "PMPM"}, - {"FULL_SERVICE": "damages", "FULL_METHODOLOGY": "Invalid Methodology"}, - ] - - def test_filter_service_column(self, sample_answer_dicts): - filtered = filter_service_column(sample_answer_dicts) - assert len(filtered) == 1 - assert all(d["FULL_SERVICE"] == "Service B" for d in filtered) - def test_filter_methodology_column(self, sample_answer_dicts): - filtered = filter_methodology_column(sample_answer_dicts) - assert len(filtered) == 1 - assert all(d["FULL_METHODOLOGY"] == "Invalid Methodology" for d in filtered) - @pytest.mark.parametrize("input_td, expected_output", [ - ( - {"DATE": "01/01/2023", "Health Plan State": "Chicago", "page_num": 1, "Filename": "file1.txt"}, - {"DATE": ["01/01/2023"], "Health Plan State": ["Chicago"], "page_num": 1, "Filename": "file1.txt"} - ), - ( - {"DATE": "01/01/2023", "Health Plan State": ["New York", "Michigan"], "page_num": 1, "Filename": "file1.txt"}, - {"DATE": ["01/01/2023"], "Health Plan State": ["New York", "Michigan"], "page_num": 1, "Filename": "file1.txt"} - ), - ( - {"DATE": "N/A", "Health Plan State": "N/A", "page_num": 2, "Filename": "file2.txt"}, - {"DATE": ["N/A"], "Health Plan State": [], "page_num": 2, "Filename": "file2.txt"} - ), - ( - {"DATE": "02/01/2023", "Health Plan State": "California, Texas", "page_num": 3, "Filename": "file3.txt"}, - {"DATE": ["02/01/2023"], "Health Plan State": ["California", "Texas"], "page_num": 3, "Filename": "file3.txt"} - ), - ]) - def test_clean_td(self, input_td, expected_output): - cleaned = clean_td([input_td]) - assert cleaned[0] == expected_output - @pytest.mark.parametrize("filename, expected_code", [ - ("contract_1234(1).txt", "1234"), - ("contract_5678.txt", "5678"), - ("invalid_filename.txt", "N/A"), - ]) - def test_get_parent_agreement_code(self, filename, expected_code): - code = get_parent_agreement_code(filename) - assert code == expected_code - - @pytest.mark.parametrize("input_dict, expected_output", [ - ({"FULL_SERVICE": "Service A", "SUBHEADER": "Header A"}, {"FULL_SERVICE": "Header A - Service A"}), - ({"FULL_SERVICE": "Service B", "SUBHEADER": ""}, {"FULL_SERVICE": " - Service B"}), - ({"FULL_SERVICE": "Service C", "SUBHEADER": "N/A"}, {"FULL_SERVICE": "Service C"}), - ]) - def test_consolidate_subheader(self, input_dict, expected_output): - consolidated = consolidate_subheader([input_dict]) - assert consolidated[0] == expected_output - @pytest.mark.parametrize("input_df, expected_lesser, expected_lesser_rate", [ - ( - pd.DataFrame({ - "FULL_SERVICE": ["Multiple Outpatient", "Multiple Outpatient", "Not multiple"], - "EXHIBIT": ["Type A", "Type A", "Type B"], - "LESSER": ["", "Y", "N"], - "LESSER_RATE": [100, 200, 300] - }), - ["Y", "Y", "N"], - [200, 200, 300] - ), - ( - pd.DataFrame({ - "FULL_SERVICE": ["Multiple Outpatient", "Multiple Outpatient", "Not multiple"], - "EXHIBIT": ["Type A", "Type A", "Type B"], - "LESSER": ["Y", "Y", "N"], - "LESSER_RATE": [100, 200, 300] - }), - ["Y", "Y", "N"], - [100, 100, 300] - ), - ]) - def test_clean_msr_lesser(self, input_df, expected_lesser, expected_lesser_rate): - cleaned_df = clean_msr_lesser(input_df) - assert cleaned_df["LESSER"].tolist() == expected_lesser - assert cleaned_df["LESSER_RATE"].tolist() == expected_lesser_rate - @pytest.mark.parametrize("input_df, expected_df", [ - ( - pd.DataFrame({ - "DEFAULT_TERM": ["Except as otherwise provided"], - "DEFAULT_RATE": ["Invalid"], - "IP_OP": ["IP"] - }), - pd.DataFrame({ - "DEFAULT_TERM": ["N/A"], - "DEFAULT_RATE": ["N/A"], - "IP_OP": ["IP"] - }) - ), - ( - pd.DataFrame({ - "DEFAULT_TERM": ["Medicare Outpatient", "Medicaid Inpatient", "Invalid"], - "DEFAULT_RATE": ["AC", "BC", "Amount Payable by Medicaid:NA"], - "IP_OP": ["IP", "OP", "IP"] - }), - pd.DataFrame({ - "DEFAULT_TERM": ["N/A", "N/A", "Invalid"], - "DEFAULT_RATE": ["N/A", "N/A", "Amount Payable by MCD:NA"], - "IP_OP": ["IP", "OP", "IP"] - }) - ), - ]) - def test_clean_default_term(self, input_df, expected_df): - cleaned_df = clean_default_term(input_df) - pd.testing.assert_frame_equal(cleaned_df, expected_df) - @pytest.mark.parametrize("input_df, expected_output", [ - ( - pd.DataFrame({ - "LESSER_RATE": [np.nan, "200", "300"], - "RATE_STANDARD": ["150", "250", "350"], - "LESSER": ["Y", "Y", "N"] - }), - pd.DataFrame({ - "LESSER_RATE": ["150", "200", "300"], - "RATE_STANDARD": ["N/A", "250", "350"], - "LESSER": ["Y", "Y", "N"] - }) - ) - ]) - def test_clean_lesser_rate(self, input_df, expected_output): - cleaned_df = clean_lesser_rate(input_df) - pd.testing.assert_frame_equal(cleaned_df, expected_output) - - @pytest.mark.parametrize("input_df, expected_output", [ - ( - pd.DataFrame({ - "PROV_TYPE_LEVEL_2": ["", "Type A", "Type B"], - "FULL_SERVICE": ["Specialist", "ASC", "Invalid Service - Skilled Nursing Facility"], - "EXHIBIT": ["Exhibit A", "Exhibit B", "Exhibit C"], - "PROV_TYPE": ["Professional", "Ancilliary","Invalid"] - }), - ["Specialist", "ASC", "Skilled Nursing Facility"], - ), - ( - pd.DataFrame({ - "PROV_TYPE_LEVEL_2": [""], - "FULL_SERVICE": [""], - "EXHIBIT": ["This has keyword, Primary Care Provider"], - "PROV_TYPE": ["Invalid"] - }), - ["Primary Care Provider"], - ), - ( - pd.DataFrame({ - "PROV_TYPE_LEVEL_2": ["", "Lab","",""], - "FULL_SERVICE": ["", "ASC","","Hospital"], - "EXHIBIT": ["Invalid", "Invalid","Invalid2","Invalid2"], - "PROV_TYPE": ["", "Ancilliary","","Facility"] - }), - ["Lab", "ASC", "Hospital", "Hospital"], - ) - ]) - def test_clean_prov_2(self, input_df, expected_output): - cleaned_df = clean_prov_2(input_df) - cleaned_df['PROV_TYPE_LEVEL_2'].tolist() == expected_output - @pytest.mark.parametrize("input_df, expected_output", [ - ( - pd.DataFrame({ - "TERM_CLAUSE": ["IL-4 Termination", "bonus payment shall be effective", "No term or termination", "does not contain", "valid term clause"] - }), - pd.DataFrame({ - "TERM_CLAUSE": ["N/A", "N/A", "N/A", "N/A", "valid term clause"] - }) - ) - ]) - def test_clean_term_clause(self, input_df, expected_output): - cleaned_df = clean_term_clause(input_df) - pd.testing.assert_frame_equal(cleaned_df, expected_output) - @pytest.mark.parametrize("input_df, column, expected_output", [ - ( - pd.DataFrame({ - "TERM_CLAUSE": ["Contract automatically renews", "coterminous with the agreement", "N/A", " "], - "CONTRACT_AUTO_RENEWAL_IND": ["N", "N", "N", "N"]}), - "CONTRACT_AUTO_RENEWAL_IND", - ["Y", "Y", "N", "N"] - ), - ( - pd.DataFrame({ - "TERM_CLAUSE": ["N/A", ""], - "CONTRACT_TERMINATION_DT": ["Y", "Y"]}), - "CONTRACT_TERMINATION_DT", - [np.nan, np.nan] - ), - ( - pd.DataFrame({ - "CONTRACT_AUTO_RENEWAL_IND": ["Yes", "Y", "N"], - "CONTRACT_TERMINATION_DT": ["", "N/A", "12/31/2022"], - }), - "CONTRACT_TERMINATION_DT", - [np.nan, np.nan, "12/31/2022"] - ), - ( - pd.DataFrame({ - "CONTRACT_AUTO_RENEWAL_IND": ["Y",""], - }), - "CONTRACT_AUTO_RENEWAL_IND", - ["Y", "N"] - ), - ( - pd.DataFrame({ - "Contract Name": ["1234_contract.txt"]}), - "CONTRACT_AUTO_RENEWAL_IND", - ["N"] - ) - ]) - def test_clean_auto_renewal_ind(self, input_df, column, expected_output): - cleaned_df = clean_auto_renewal_ind(input_df) - assert cleaned_df[column].tolist() == expected_output - @pytest.mark.parametrize("input_df, expected_npi", [ - ( - pd.DataFrame({"PROV_GROUP_NPI": ["1234567890", "123", "N/A"]}), - ["1234567890", "N/A - (model detected: 123)", "N/A"] - ) - ]) - def test_clean_npi(self, input_df, expected_npi): - cleaned_df = clean_npi(input_df) - assert cleaned_df["PROV_GROUP_NPI"].tolist() == expected_npi - @pytest.mark.parametrize("input_df, expected_output", [ - ( - pd.DataFrame({ - "NETWORK_ACCESS_FEES_IND": ["Yes", "N/A", np.nan, None, "nan","Test", "No"] - }), - ["Yes", "N/A", np.nan, None, "nan", "Yes", "Yes"] - ) - ]) - def test_clean_network_access_fees(self, input_df, expected_output): - cleaned_df = clean_network_access_fees(input_df) - assert cleaned_df["NETWORK_ACCESS_FEES_IND"].tolist() == expected_output - @pytest.mark.parametrize("input_df, expected_state", [ - (pd.DataFrame({"HEALTH_PLAN_STATE": ["IL", "TX", "CA"], - "PAYER_NAME":["Illinios Payer","",""]}), ["Illinois","Texas","California"]), - (pd.DataFrame({"HEALTH_PLAN_STATE": ["TX", "TX", "CA"]}), ["Texas","Texas","California"]), - (pd.DataFrame({"HEALTH_PLAN_STATE": ["CA", "TX", "CA"]}), ["California","Texas","California"]), - ]) - def test_clean_health_plan_state(self, input_df, expected_state): - cleaned_df = clean_health_plan_state(input_df) - assert cleaned_df["HEALTH_PLAN_STATE"].tolist() == expected_state - @pytest.mark.parametrize("text_dict, expected_state", [ - ({"page1": "This contract is for Illinois and Texas."}, "Multiple"), - ({"page1": "This contract is for IL."}, "Illinois"), - ({"page1": "This contract is for TX and Texas."}, "Texas"), - ({"page1": "This contract is for California."}, "California"), - ({"page1": "This contract is for New York."}, "New York"), - ({"page1": "This contract is for Illinois, Texas, and California."}, "Multiple"), - ({"page1": "This contract is for Illinois and California."}, "Multiple"), - ({"page1": "This contract is for Texas and New York."}, "Multiple"), - ({"page1": "This contract is for."}, "N/A"), - ({"page1": ""}, "N/A"), - ]) - def test_get_health_plan_state(self, text_dict, expected_state): - state = get_health_plan_state(text_dict) - assert state == expected_state - @pytest.mark.parametrize("state_abbr, text_dict, expected_state", [ - ("IL", {"page1": "This contract is for Illinois and Texas."}, "Illinois"), - ("TX", {"page1": "This contract is for Illinois and Texas."}, "Texas"), - ("CA", {"page1": "This contract is for California."}, "California"), - ("NY", {"page1": "This contract is for New York."}, "New York"), - ("FL", {"page1": "This contract is for Hlorida."}, "Florida"), - ("", {"page1": "This contract is for Illinois and Texas."}, "Multiple"), - ]) - def test_state_check(self, state_abbr, text_dict, expected_state): - state = state_check(state_abbr, text_dict) - assert state == expected_state - - @pytest.mark.parametrize("input_df, contract_name, text_dict, expected_state", [ - ( - pd.DataFrame({"HEALTH_PLAN_STATE": [["IL", "TX", "CA"]]}), - "contract_1234_v1.txt", - {"page1": "This is a sample contract contract"}, - "Multiple" - ), - ( - pd.DataFrame({"HEALTH_PLAN_STATE": ["IL,"]}), - "contract_1234_v1.txt", - {"page1": "This is a sample contract"}, - "Illinois" - ), - ( - pd.DataFrame({"HEALTH_PLAN_STATE": [""]}), - "contract_1234_v1.txt", - {"page1": "This contract is for Illinois and Texas."}, - "Multiple" - ) - ]) - def test_clean_health_plan_state_from_hotfix(self, input_df, contract_name, text_dict, expected_state): - input_df["HEALTH_PLAN_STATE"] = input_df["HEALTH_PLAN_STATE"].apply( - lambda x: f"[{','.join(x)}]" if isinstance(x, list) else x - ) - cleaned_df = clean_health_plan_state_from_hotfix(input_df, contract_name, text_dict) - assert cleaned_df.loc[0, "HEALTH_PLAN_STATE"] == expected_state - @pytest.mark.parametrize("input_df, expected_name, expected_address", [ - ( - pd.DataFrame({ - "NOTICE_PROVIDER_NAME": ["Superior HealthPlan", "Provider A", "Provider B"], - "NOTICE_PROVIDER_ADDRESS": ["Address A", "Address B", "Address C"] - }), - [np.nan, "Provider A", "Provider B"], - [np.nan, "Address B", "Address C"] - ), - ]) - def test_clean_notice_provider_name_and_address(self, input_df, expected_name, expected_address): - cleaned_df = clean_notice_provider_name_and_address(input_df) - assert cleaned_df["NOTICE_PROVIDER_NAME"].tolist() == expected_name - assert cleaned_df["NOTICE_PROVIDER_ADDRESS"].tolist() == expected_address - - @pytest.mark.parametrize("input_df, expected_tin", [ - (pd.DataFrame({"Contract Name":["12-3456789_contract(1)", "12-3456789_contract(2)"], - "PROV_GROUP_TIN": ["", "123456789"]} - ), ["123456789", "123456789"] - ), - ]) - def test_get_tin_from_filename(self, input_df, expected_tin): - cleaned_df = get_tin_from_filename(input_df) - assert cleaned_df["PROV_GROUP_TIN"].tolist() == expected_tin - @pytest.mark.parametrize("input_df, expected_output", [ - ( - pd.DataFrame({ - "POLICIES_AND_PROCEDURES": ["Policy A", "Procedure B", "Invalid", ""] - }), - pd.DataFrame({ - "POLICIES_AND_PROCEDURES": ["Policy A", "Procedure B", "N/A", "N/A"] - }) - ), - ]) - def test_clean_policies_and_procedures(self, input_df, expected_output): - cleaned_df = clean_policies_and_procedures(input_df) - pd.testing.assert_frame_equal(cleaned_df, expected_output) - @pytest.mark.parametrize("input_df, expected_tin", [ - (pd.DataFrame({"PROV_GROUP_TIN": ["123-45-6789"]}), "N/A"), - (pd.DataFrame({"PROV_GROUP_TIN": ["12-34-56"]}), "N/A - (model detected: 12-34-56)"), - (pd.DataFrame({"PROV_GROUP_TIN": ["12-3456789"]}), "12-3456789"), - (pd.DataFrame({"PROV_GROUP_TIN": ["N/A"]}), "N/A"), - ]) - def test_clean_tin(self, input_df, expected_tin): - cleaned_df = clean_tin(input_df) - assert cleaned_df.loc[0, "PROV_GROUP_TIN"] == expected_tin - @pytest.mark.parametrize("input_df, column, expected_tin", [ - (pd.DataFrame({"PROV_TIN_OTHER": [["123456789", "123456789"], "N/A"]}), "PROV_TIN_OTHER", ["123456789,123456789","N/A"]), - (pd.DataFrame({"PROV_NPI_OTHER": ["12-3456789"]}), "PROV_NPI_OTHER", ["12-3456789"]), - (pd.DataFrame({"PROV_TIN_OTHER": [""]}), "PROV_TIN_OTHER", [""]), - (pd.DataFrame({"PROV_NPI_OTHER": [""]}), "PROV_NPI_OTHER", [""]), - ]) - - def test_clean_tin_npi_other(self, input_df, column, expected_tin): - input_df[column] = input_df[column].apply(lambda x: f"[{','.join(x)}]" if isinstance(x, list) else x) - cleaned_df = clean_tin_npi_other(input_df) - assert cleaned_df[column].tolist() == expected_tin - @pytest.mark.parametrize("input_df, expected_output", [ - ( - pd.DataFrame({ - "ADD_ON_REIMBURSEMENT_LANGUAGE": ["in no event", "forward", "Valid"], - "ADD_ON_REIMBURSEMENT_IND": ["Y", "N", "Y"], - "FULL_SERVICE": ["Add-on service", "No add-on", "Valid service"], - "Contract Name": ["Contract A", "Contract A", "Contract B"], - "EXHIBIT": ["Exhibit 1", "Exhibit 1", "Exhibit 2"] - }), - pd.DataFrame({ - "ADD_ON_REIMBURSEMENT_LANGUAGE": ["N/A", "N/A", "Valid"], - "ADD_ON_REIMBURSEMENT_IND": ["N", "N", "Y"], - "FULL_SERVICE": ["Add-on service", "No add-on", "Valid service"], - "Contract Name": ["Contract A", "Contract A", "Contract B"], - "EXHIBIT": ["Exhibit 1", "Exhibit 1", "Exhibit 2"] - }) - ), - ( - pd.DataFrame({ - "ADD_ON_REIMBURSEMENT_LANGUAGE": ["", "additional fees", "N/A"], - "ADD_ON_REIMBURSEMENT_IND": ["Y", "Y", "N"], - "FULL_SERVICE": ["No add-on", "Add On service", "N/A"], - "Filename": ["File1", "File1", "File2"], - "EXHIBIT": ["Exhibit 1", "Exhibit 1", "Exhibit 2"] - }), - pd.DataFrame({ - "ADD_ON_REIMBURSEMENT_LANGUAGE": ["N/A", "N/A", "N/A"], - "ADD_ON_REIMBURSEMENT_IND": ["N", "N", "N"], - "FULL_SERVICE": ["No add-on", "Add On service", "N/A"], - "Filename": ["File1", "File1", "File2"], - "EXHIBIT": ["Exhibit 1", "Exhibit 1", "Exhibit 2"] - }) - ) - ]) - - @pytest.mark.filterwarnings("ignore::UserWarning") - @pytest.mark.filterwarnings("ignore::DeprecationWarning") - def test_filter_add_ons(self, input_df, expected_output): - # Call the function and reset the index - cleaned_df = filter_add_ons(input_df).reset_index(drop=True) - - # Assert that the cleaned DataFrame matches the expected output - pd.testing.assert_frame_equal(cleaned_df, expected_output) - - @pytest.mark.parametrize("input_df, expected_df", [ - (pd.DataFrame({"FLAT_FEE_STANDARD": ["$100", "$200 30%", "$400"]}), - pd.DataFrame({ - "Single Code Multiple Rates (Language)": ["N/A", "$200 30%", "N/A"], - "Single Code Multiple Rates (Y/N)": ["N", "Y", "N"] - })) - ]) - - def test_add_scmr(self, input_df, expected_df): - cleaned_df = add_scmr(input_df) - - pd.testing.assert_series_equal( - cleaned_df["Single Code Multiple Rates (Language)"], - expected_df["Single Code Multiple Rates (Language)"], - ) - pd.testing.assert_series_equal( - cleaned_df["Single Code Multiple Rates (Y/N)"], - expected_df["Single Code Multiple Rates (Y/N)"], - ) - @pytest.mark.parametrize("input_df, expected_df", [ - ( - pd.DataFrame({ - "DUMMY_COL(Y/N)": ["Yes", "No", ""], - "SINGLE_CODE_MULTIPLE_RATES_IND": ["Y", "N", "Y"], - "SINGLE_CODE_MULTIPLE_RATES_LANGUAGE": ["Language 1", "Language 2", "N/A"], - "RATE_ESCALATOR_IND": ["Y", "N", "N"], - "RATE_ESCALATOR_DT": ["12/1/2022", "12/11/2022", "01/05/2018"], - }), - pd.DataFrame({ - "DUMMY_COL(Y/N)": ["Y", "N", "N"], - "SINGLE_CODE_MULTIPLE_RATES_IND": ["Y", "N", "Y"], - "SINGLE_CODE_MULTIPLE_RATES_LANGUAGE": ["Language 1", "", "N/A"], - "RATE_ESCALATOR_IND": ["Y", "N", "N"], - "RATE_ESCALATOR_DT": ["12/1/2022", "", ""], - }) - ), - - ]) - def test_yn_fixes(self, input_df, expected_df): - cleaned_df = yn_fixes(input_df) - pd.testing.assert_frame_equal(cleaned_df, expected_df) - - @pytest.mark.parametrize("input_tin, expected_output", [ - ("12345678", "01-2345678"), - ("123456789", "12-3456789"), - ("1-2345678", "01-2345678"), - ("123-45", None), - ("", None), - ]) - - def test_add_hyphen_if_needed(self, input_tin, expected_output): - assert add_hyphen_if_needed(input_tin) == expected_output - @pytest.mark.parametrize("input_date, expected_output", [ - ("2023-12-31", "12/31/2023"), - ("2023-01-12", "01/12/2023"), - ]) - def test_convert_to_us_date_format_valid(self, input_date, expected_output): - assert convert_to_us_date_format(input_date) == expected_output - @pytest.mark.parametrize("input_date", [ - "N/A", - "2023-13-32", - "2023-00-01", - "2023-12-00", - "2023-12-32", - "2023-13-13", - "", - "null", - "none", - "2023/12/01", - "12-01-2023", - "2023.12.01", - None, - ]) - def test_convert_to_us_date_format_invalid(self, input_date): - with pytest.raises((InvalidDateException, TypeError)): - convert_to_us_date_format(input_date) - @pytest.mark.parametrize("input_df, expected_columns", [ - (pd.DataFrame({"Unnamed: 0": [1, 2], "A": [3, 4]}), ["A"]), - (pd.DataFrame({"Unnamed: 0": [1, 2], "Unnamed: 1": [3, 4], "B": [5, 6]}), ["B"]), - (pd.DataFrame({"A": [1, 2], "B": [3, 4]}), ["A", "B"]), - ]) - def test_remove_unnamed_columns(self, input_df, expected_columns): - cleaned_df = remove_unnamed_columns(input_df) - assert list(cleaned_df.columns) == expected_columns - @pytest.mark.parametrize("input_tin, expected_output", [ - ("123456789", "12-3456789"), - ("12-3456789", "12-3456789"), - ("123-45-6789", "12-3456789"), - ("N/A", "N/A"), - ("invalidTIN", "N/A - (model detected: invalidTIN)"), - ("", "N/A - (model detected: )"), - ]) - def test_format_tin(self, input_tin, expected_output): - assert format_tin(input_tin) == expected_output - @pytest.mark.parametrize("input_str, expected_count", [ - ("$100", 1), - ("100%", 1), - ("$100 and 50%", 2), - ("No dollar or percentage signs", 0), - ("$$$ and %%%", 6), - ("$100 $200 $300", 3), - ("100% 200% 300%", 3), - ("", 0), - ("$%$%", 4), - ]) - def test_count_dollar_values(self, input_str, expected_count): - assert count_dollar_values(input_str) == expected_count - - @pytest.mark.parametrize("input_df, expected_df", [ - ( - pd.DataFrame({ - "FULL_SERVICE": ["Add-on Service", "Add-on Service"], - "ADD_ON_REIMBURSEMENT_LANGUAGE": ["Language A", "Language B"], - "ADD_ON_REIMBURSEMENT_IND": ["Y", "Y"] - }), - pd.DataFrame({ - "FULL_SERVICE": ["Add-on Service", "Add-on Service"], - "ADD_ON_REIMBURSEMENT_LANGUAGE": ["N/A", "N/A"], - "ADD_ON_REIMBURSEMENT_IND": ["N", "N"] - }) - ), - ( - pd.DataFrame({ - "FULL_SERVICE": ["Add On Service", "Add On Service"], - "ADD_ON_REIMBURSEMENT_LANGUAGE": ["Language A", "Language B"], - "ADD_ON_REIMBURSEMENT_IND": ["Y", "Y"] - }), - pd.DataFrame({ - "FULL_SERVICE": ["Add On Service", "Add On Service"], - "ADD_ON_REIMBURSEMENT_LANGUAGE": ["N/A", "N/A"], - "ADD_ON_REIMBURSEMENT_IND": ["N", "N"] - }) - ), - ( - pd.DataFrame({ - "FULL_SERVICE": ["Regular Service", "Regular Service"], - "ADD_ON_REIMBURSEMENT_LANGUAGE": ["Language A", "Language B"], - "ADD_ON_REIMBURSEMENT_IND": ["Y", "Y"] - }), - pd.DataFrame({ - "FULL_SERVICE": ["Regular Service", "Regular Service"], - "ADD_ON_REIMBURSEMENT_LANGUAGE": ["Language A", "Language B"], - "ADD_ON_REIMBURSEMENT_IND": ["Y", "Y"] - }) - ) - ]) - def test_check_add_on(self, input_df, expected_df): - cleaned_df = check_add_on(input_df) - pd.testing.assert_frame_equal(cleaned_df, expected_df) diff --git a/fieldExtraction/tests/preprocessing_funcs_test.py b/fieldExtraction/tests/preprocessing_funcs_test.py deleted file mode 100644 index 281d033..0000000 --- a/fieldExtraction/tests/preprocessing_funcs_test.py +++ /dev/null @@ -1,112 +0,0 @@ -import pytest -from src.preprocessing_funcs import ( - remove_page_indicators, - clean_law_symbols, - filter_quick_review, - get_exhibit_pages, -) -import src.prompts.investment_prompts as investment_prompts -import src.config as config - -from src.client.smart_chunking_funcs import smart_chunk_ac -from unittest.mock import patch - - -class TestPreprocessingFuncs: - # Test cases for clean_newlines - @pytest.mark.parametrize("input_text, expected_output", [ - # Removes page number indicators - ("Page 1 of 10\n\nThis is a test.", " This is a test."), - # Handles multiple newlines - ("Line 1\n\nLine 2\n\nLine 3", "Line 1\n\nLine 2\n\nLine 3"), - # Handles no newlines - ("This is a single line.", "This is a single line."), - ]) - def test_clean_newlines(self, input_text, expected_output): - assert remove_page_indicators(input_text) == expected_output - - # Test cases for clean_law_symbols - @pytest.mark.parametrize("input_text, expected_output", [ - # Replaces double dollar signs - ("This is a $$ test.", "This is a $ test."), - # Replaces U.S.C. symbol - ("U.S.C. $1234", "U.S.C.§1234"), - # Replaces C.F.R. symbol - ("C.F.R. $1234", "C.F.R.§1234"), - # Replaces $ followed by three decimal places - ("$123.456", "§123.456"), - # Handles no replacements - ("This is a normal text.", "This is a normal text."), - # Handles mixed replacements - ("U.S.C. $1234 and C.F.R. $5678 and $123.456", "U.S.C.§1234 and C.F.R.§5678 and §123.456"), - ]) - def test_clean_law_symbols(self, input_text, expected_output): - assert clean_law_symbols(input_text) == expected_output - - # Test cases for smart_chunk_ac - @pytest.mark.parametrize("methodology, mock_return, chunking_fn_name, expected_chunk, expected_pages", [ - # OR methodology - ("or", [1, 2], "or", "Content1\nContent2", {"test": [1, 2]}), - # Hierarchy methodology - ("hierarchy", [1, 2], "hierarchical", "Content1\nContent2", {"test": [1, 2]}), - # AND methodology - ("and", [1], "and", "Content1", {"test": [1]}), - # Regex methodology - ("regex", [1, 2], "regex", "Content1\nContent2", {"test": [1, 2]}), - # Include/Exclude methodology - ("include_exclude", [1], "with_include_exclude_keywords", "Content1", {"test": [1]}), - ]) - - def test_smart_chunk_ac(self, mocker, methodology, mock_return, chunking_fn_name, expected_chunk, expected_pages): - # Mock the chunking functions - mock_chunk_func = mocker.patch(f"src.client.smart_chunking_funcs.chunk_{chunking_fn_name}", return_value=mock_return) - mock_keyword_search = mocker.patch("src.client.smart_chunking_funcs.keyword_search", return_value={"test": mock_return}) - - text_dict = {"1": "Content1", "2": "Content2"} - keyword_mappings = {"group1": {"methodology": methodology, "keywords": ["test"], "case_sensitive": False}} - - if methodology == "regex": keyword_mappings["group1"]["regex"] = "Content" - - if methodology == "include_exclude": - del keyword_mappings["group1"]["keywords"] - keyword_mappings["group1"]["included_keywords"] = ["Content"] - keyword_mappings["group1"]["excluded_keywords"] = [] - - result = smart_chunk_ac(text_dict, "Content1\nContent2", keyword_mappings) - - assert result["group1"] == expected_chunk - assert result["group1_pages"] == expected_pages - mock_chunk_func.assert_called_once() - mock_keyword_search.assert_called_once() - - # Test cases for filter_quick_review - @pytest.mark.parametrize("input_dict, expected_contract, expected_quick_review", [ - # Filters quick review pages - ( - {"1": "QUICK REVIEW", "2": "Normal content", "3": "COVER SHEET"}, - {"2": "Normal content"}, - {"1": "QUICK REVIEW", "3": "COVER SHEET"} - ), - # No quick review pages - ( - {"1": "Normal content", "2": "More content"}, - {"1": "Normal content", "2": "More content"}, - {} - ), - # All quick review pages - ( - {"1": "QUICK REVIEW", "2": "TOP SHEET"}, - {}, - {"1": "QUICK REVIEW", "2": "TOP SHEET"} - ), - # Case-insensitive matching - ( - {"1": "quick review", "2": "Normal content"}, - {"2": "Normal content"}, - {"1": "quick review"} - ), - ]) - def test_filter_quick_review(self, input_dict, expected_contract, expected_quick_review): - contract, quick_review = filter_quick_review(input_dict) - assert contract == expected_contract - assert quick_review == expected_quick_review diff --git a/fieldExtraction/tests/qa_qc/blanks_test.py b/fieldExtraction/tests/qa_qc/blanks_test.py deleted file mode 100644 index bd5c635..0000000 --- a/fieldExtraction/tests/qa_qc/blanks_test.py +++ /dev/null @@ -1,40 +0,0 @@ -import pandas as pd -import pytest - -from src.qa_qc.qa_qc_utils import blanks_check - - -def test_blanks_check_no_highly_blank_columns(): - # Create a sample DataFrame with no highly blank columns - df = pd.DataFrame({ - "A": [1, 2, 3, 4, 5], - "B": ["a", "b", "c", "d", "e"], - "C": [10.5, 11.2, 12.3, 13.4, 14.5] - }) - - result = blanks_check(df) - assert result == "" - -def test_blanks_check_with_highly_blank_columns(): - # Create a sample DataFrame with highly blank columns - df = pd.DataFrame({ - "A": [1, 2, 3, None, 5, None, 6], - "B": ["a", "b", "", "d", "", "", ""], - "C": [10.5, None, 12.3, None, 14.5, None, None] - }) - - result = blanks_check(df) - expected_output = "B\nC" - assert result == expected_output - -def test_blanks_check_all_columns_highly_blank(): - # Create a sample DataFrame where all columns are highly blank - df = pd.DataFrame({ - "A": [None, None, None, None, None], - "B": ["", "", "", "", ""], - "C": [None, None, None, None, None] - }) - - result = blanks_check(df) - expected_output = "A\nB\nC" - assert result == expected_output \ No newline at end of file diff --git a/fieldExtraction/tests/qa_qc/selftalk_test.py b/fieldExtraction/tests/qa_qc/selftalk_test.py deleted file mode 100644 index 4739b25..0000000 --- a/fieldExtraction/tests/qa_qc/selftalk_test.py +++ /dev/null @@ -1,44 +0,0 @@ -### These tests (`selftalk_test.py`) were previously saved as just `selftalk_test` (no extension). As such, they were previously not running; when I added the extension they started failing. I will leave these commented until a later date. -### - Alex 2025-01-08 - -# import pytest -# import pandas as pd -# import sys -# sys.path.append('src') -# from qa_qc.qa_qc_utils import self_talk_check - - -# def test_self_talk_check_no_self_talk(): -# # Create a sample DataFrame with no self-talk columns -# df = pd.DataFrame({ -# "Name": ["John", "Jane", "Bob", "Alice", "Tom"], -# "Age": [25, 30, 35, 40, 45], -# "City": ["New York", "Los Angeles", "Chicago", "Houston", "Miami"] -# }) - -# result = self_talk_check(df) -# assert result == "" - -# def test_self_talk_check_with_self_talk(): -# # Create a sample DataFrame with self-talk columns -# df = pd.DataFrame({ -# "Name": ["John", "Jane", "Bob", "Alice", "Tom"], -# "Language": ["English", "Spanish", "The Contract Text suggests Armenian", "French", "German"], -# "Clause": ["", "this suggests", "Mike refers to xyz", "The Contract Text says", "My Answer is this: "] -# }) - -# result = self_talk_check(df) -# expected_output = "Language - 20.0%\nClause - 80.0%" -# assert result == expected_output - -# def test_self_talk_check_all_self_talk(): -# # Create a sample DataFrame where all language columns have self-talk -# df = pd.DataFrame({ -# "Name": ["John", "Jane", "Bob", "Alice", "Tom"], -# "Language": ["Statewide", "Multiple sources imply", "The contract text says", "For this text, it would seem", "This suggests that"], -# "Clause": ["The Contract Text contains this", "My Answer Is This: ", "It would seem that multiple clauses say", "I conclude that the exhibit suggests", "For each clause, this is the case"] -# }) - -# result = self_talk_check(df) -# expected_output = "Language - 100.0%\nClause - 100.0%" -# assert result == expected_output \ No newline at end of file diff --git a/fieldExtraction/tests/qa_qc/test_blanks.py b/fieldExtraction/tests/qa_qc/test_blanks.py new file mode 100644 index 0000000..14a27ea --- /dev/null +++ b/fieldExtraction/tests/qa_qc/test_blanks.py @@ -0,0 +1,48 @@ +import pandas as pd +import pytest + +from src.utils.qa_qc_utils import blanks_check + + +def test_blanks_check_no_highly_blank_columns(): + # Create a sample DataFrame with no highly blank columns + df = pd.DataFrame( + { + "A": [1, 2, 3, 4, 5], + "B": ["a", "b", "c", "d", "e"], + "C": [10.5, 11.2, 12.3, 13.4, 14.5], + } + ) + + result = blanks_check(df) + assert result == "" + + +def test_blanks_check_with_highly_blank_columns(): + # Create a sample DataFrame with highly blank columns + df = pd.DataFrame( + { + "A": [1, 2, 3, None, 5, None, 6], + "B": ["a", "b", "", "d", "", "", ""], + "C": [10.5, None, 12.3, None, 14.5, None, None], + } + ) + + result = blanks_check(df) + expected_output = "B\nC" + assert result == expected_output + + +def test_blanks_check_all_columns_highly_blank(): + # Create a sample DataFrame where all columns are highly blank + df = pd.DataFrame( + { + "A": [None, None, None, None, None], + "B": ["", "", "", "", ""], + "C": [None, None, None, None, None], + } + ) + + result = blanks_check(df) + expected_output = "A\nB\nC" + assert result == expected_output diff --git a/fieldExtraction/tests/qa_qc/date_format_test.py b/fieldExtraction/tests/qa_qc/test_date_format.py similarity index 76% rename from fieldExtraction/tests/qa_qc/date_format_test.py rename to fieldExtraction/tests/qa_qc/test_date_format.py index 57c7fa5..7721ecb 100644 --- a/fieldExtraction/tests/qa_qc/date_format_test.py +++ b/fieldExtraction/tests/qa_qc/test_date_format.py @@ -1,22 +1,24 @@ import pytest -from src.qa_qc.qa_qc_utils import date_format_check + +from src.utils.qa_qc_utils import date_format_check def test_valid_date_formats(): """Test valid date formats are correctly validated""" valid_dates = { - "01/01/2024" : '1/1/2024', - "12/31/2023" : '12/31/2023', - "02/29/2024" : '2/29/2024', # Leap year - "07/04/1776" : '7/4/1776', - "09/08/2024" : '9/8/2024' + "01/01/2024": "1/1/2024", + "12/31/2023": "12/31/2023", + "02/29/2024": "2/29/2024", # Leap year + "07/04/1776": "7/4/1776", + "09/08/2024": "9/8/2024", } - + for date in valid_dates.keys(): result, is_valid = date_format_check(date) assert result == valid_dates[date] assert is_valid is True + def test_invalid_date_formats(): """Test various invalid date formats""" invalid_dates = [ @@ -27,15 +29,16 @@ def test_invalid_date_formats(): "01/00/2024", # Zero day "01-01-2024", # Wrong separator "2024/01/01", # Wrong order - "01/01/24", # Two-digit year - "Jan/01/2024", # Text month + "01/01/24", # Two-digit year + "Jan/01/2024", # Text month ] - + for date in invalid_dates: result, is_valid = date_format_check(date) assert result == date assert is_valid is False + def test_non_string_inputs(): """Test handling of non-string inputs""" test_cases = [ @@ -45,27 +48,29 @@ def test_non_string_inputs(): (["01/01/2024"], "['01/01/2024']"), ({"date": "01/01/2024"}, "{'date': '01/01/2024'}"), ] - + for input_val, expected_str in test_cases: result, is_valid = date_format_check(input_val) assert result == str(input_val) assert is_valid is False + def test_edge_cases(): """Test edge cases with valid-looking but incorrect formats""" test_cases = [ - "01/01/2024 ", # Extra space at end - " 01/01/2024", # Extra space at start - "01/01/2024\n", # Newline at end - "01 /01/2024", # Space in middle - "01/01/ 2024", # Space in middle + "01/01/2024 ", # Extra space at end + " 01/01/2024", # Extra space at start + "01/01/2024\n", # Newline at end + "01 /01/2024", # Space in middle + "01/01/ 2024", # Space in middle ] - + for date in test_cases: result, is_valid = date_format_check(date) assert result == date assert is_valid is False + def test_special_dates(): """Test boundary cases for dates""" test_cases = [ @@ -75,8 +80,8 @@ def test_special_dates(): ("12/31/2023", True), # Last day of year ("1/1/2024", True), # First day of year ] - + for date, expected_valid in test_cases: result, is_valid = date_format_check(date) assert result == date - assert is_valid == expected_valid \ No newline at end of file + assert is_valid == expected_valid diff --git a/fieldExtraction/tests/qa_qc/npi_test.py b/fieldExtraction/tests/qa_qc/test_npi.py similarity index 72% rename from fieldExtraction/tests/qa_qc/npi_test.py rename to fieldExtraction/tests/qa_qc/test_npi.py index 95a7f12..7fcc024 100644 --- a/fieldExtraction/tests/qa_qc/npi_test.py +++ b/fieldExtraction/tests/qa_qc/test_npi.py @@ -1,5 +1,6 @@ import pytest -from src.qa_qc.qa_qc_utils import npi_check + +from src.utils.qa_qc_utils import npi_check def test_valid_npi_format(): @@ -9,57 +10,59 @@ def test_valid_npi_format(): assert result == npi assert is_valid is True + def test_invalid_npi_format(): """Test various invalid NPI formats""" invalid_npis = [ "123456789", # Too few digits - "12345678900", # Too many digits - "12-4567890", # TIN format + "12345678900", # Too many digits + "12-4567890", # TIN format "1ab3456789", # Letters instead of numbers - "3234567890", # Doesn't start with 1 or 2 + "3234567890", # Doesn't start with 1 or 2 ] - + for npi in invalid_npis: result, is_valid = npi_check(npi) assert result == npi assert is_valid is False + def test_non_string_inputs(): """Test handling of non-string inputs""" test_cases = [ (1234567890, "1234567890", True), (12.3456789, "12.3456789", False), - (None, 'None', False), + (None, "None", False), ([12, 3456789], "[12, 3456789]", False), - ({"npi" : "1234567890"}, "{'npi': '1234567890'}", False), + ({"npi": "1234567890"}, "{'npi': '1234567890'}", False), ] - + for input_val, expected_str, expected_valid in test_cases: result, is_valid = npi_check(input_val) assert result == expected_str assert is_valid is expected_valid + def test_edge_cases_npi(): """Test edge cases with valid-looking but incorrect formats""" - test_cases = [ - "12345 67890" # Space in middle - ] - + test_cases = ["12345 67890"] # Space in middle + for npi in test_cases: result, is_valid = npi_check(npi) assert result == npi assert is_valid is False + def test_multiple_tins_in_string(): """Test strings containing multiple TIN-like patterns""" test_cases = [ "1234567890 1256789001", # Two TINs with space - "12345678901234567890", # Two TINs without space - "Some text 1234567890", # TIN with preceding text - "1234567890 some text", # TIN with following text + "12345678901234567890", # Two TINs without space + "Some text 1234567890", # TIN with preceding text + "1234567890 some text", # TIN with following text ] - + for npi in test_cases: result, is_valid = npi_check(npi) assert result == npi - assert is_valid is False \ No newline at end of file + assert is_valid is False diff --git a/fieldExtraction/tests/qa_qc/pages_pagenum_test.py b/fieldExtraction/tests/qa_qc/test_pages_pagenum.py similarity index 92% rename from fieldExtraction/tests/qa_qc/pages_pagenum_test.py rename to fieldExtraction/tests/qa_qc/test_pages_pagenum.py index 2a386c2..9397122 100644 --- a/fieldExtraction/tests/qa_qc/pages_pagenum_test.py +++ b/fieldExtraction/tests/qa_qc/test_pages_pagenum.py @@ -1,6 +1,6 @@ import pytest -from src.qa_qc.qa_qc_utils import pages_pagenum_check +from src.utils.qa_qc_utils import pages_pagenum_check def test_pages_pagenum_check_valid_inputs(): @@ -8,11 +8,13 @@ def test_pages_pagenum_check_valid_inputs(): assert pages_pagenum_check("100", "50") is True assert pages_pagenum_check(50, 10) is True + def test_pages_pagenum_check_invalid_inputs(): # Test with invalid string inputs assert pages_pagenum_check("abc", "50") is False assert pages_pagenum_check("50", "xyz") is False + def test_pages_pagenum_check_non_string_inputs(): # Test with non-string inputs for pages assert pages_pagenum_check(100, "50") is True @@ -22,6 +24,7 @@ def test_pages_pagenum_check_non_string_inputs(): assert pages_pagenum_check("100", 50) is True assert pages_pagenum_check("50", 10) is True + def test_pages_pagenum_check_non_convertible_inputs(): # Test with inputs that cannot be converted to string for pages def raise_type_error(): @@ -32,10 +35,12 @@ def test_pages_pagenum_check_non_convertible_inputs(): # Test with inputs that cannot be converted to string for pagenum assert pages_pagenum_check("100", raise_type_error) is False + def test_pages_pagenum_check_equality(): # Test when pages and pagenum are equal assert pages_pagenum_check("100", "100") is True + def test_pages_pagenum_check_pagenum_greater(): # Test when pagenum is greater than pages - assert pages_pagenum_check("50", "100") is False \ No newline at end of file + assert pages_pagenum_check("50", "100") is False diff --git a/fieldExtraction/tests/qa_qc/state_test.py b/fieldExtraction/tests/qa_qc/test_state.py similarity index 90% rename from fieldExtraction/tests/qa_qc/state_test.py rename to fieldExtraction/tests/qa_qc/test_state.py index c02c540..242cc3a 100644 --- a/fieldExtraction/tests/qa_qc/state_test.py +++ b/fieldExtraction/tests/qa_qc/test_state.py @@ -1,6 +1,6 @@ import pytest -from src.qa_qc.qa_qc_utils import state_check +from src.utils.qa_qc_utils import state_check def test_state_check_valid_abbreviations(): @@ -8,24 +8,28 @@ def test_state_check_valid_abbreviations(): assert state_check("CA") == ("California", True) assert state_check("NY") == ("New York", True) + def test_state_check_valid_full_names(): # Test with valid state full names assert state_check("California") == ("California", True) assert state_check("new york") == ("New York", True) + def test_state_check_invalid_input(): # Test with invalid input assert state_check("ABC") == ("ABC", False) assert state_check("Invalid State") == ("Invalid State", False) + def test_state_check_non_string_input(): # Test with non-string input assert state_check(123) == ("123", False) assert state_check(True) == ("True", False) + def test_state_check_non_convertible_input(): # Test with input that cannot be converted to string def raise_type_error(): raise TypeError("Cannot convert to string") - assert state_check(raise_type_error)[1] == False \ No newline at end of file + assert state_check(raise_type_error)[1] == False diff --git a/fieldExtraction/tests/qa_qc/tin_test.py b/fieldExtraction/tests/qa_qc/tin_test.py index 4099516..9dae840 100644 --- a/fieldExtraction/tests/qa_qc/tin_test.py +++ b/fieldExtraction/tests/qa_qc/tin_test.py @@ -1,6 +1,6 @@ import pytest -from src.qa_qc.qa_qc_utils import tin_check +from src.utils.qa_qc_utils import tin_check def test_valid_tin_format(): @@ -10,64 +10,67 @@ def test_valid_tin_format(): assert result == tin assert is_valid is True + def test_invalid_tin_format(): """Test various invalid TIN formats""" invalid_tins = [ "123-456789", # Too many digits before hyphen - "12-456789", # Too few digits after hyphen - "12-45678901", # Too many digits after hyphen + "12-456789", # Too few digits after hyphen + "12-45678901", # Too many digits after hyphen "12_3456789", # Wrong separator "ab-3456789", # Letters instead of numbers "12-abcdefg", # Letters instead of numbers - "123456789", # No hyphen - "-12345678", # Hyphen at start - "12-34567-", # Hyphen at end + "123456789", # No hyphen + "-12345678", # Hyphen at start + "12-34567-", # Hyphen at end ] - + for tin in invalid_tins: result, is_valid = tin_check(tin) assert result == tin assert is_valid is False + def test_non_string_inputs(): """Test handling of non-string inputs""" test_cases = [ (123456789, "123456789", False), (12.3456789, "12.3456789", False), - (None, 'None', False), + (None, "None", False), ([12, 3456789], "[12, 3456789]", False), - ({"tin":"12-3456789"}, "{'tin': '12-3456789'}", False), + ({"tin": "12-3456789"}, "{'tin': '12-3456789'}", False), ] - + for input_val, expected_str, expected_valid in test_cases: result, is_valid = tin_check(input_val) assert result == expected_str assert is_valid is expected_valid + def test_edge_cases(): """Test edge cases with valid-looking but incorrect formats""" test_cases = [ - "12 3456789", #Space instead of dash - "1 2-3456789", # Space at beginning + "12 3456789", # Space instead of dash + "1 2-3456789", # Space at beginning "12-345 6789", # Space in middle ] - + for tin in test_cases: result, is_valid = tin_check(tin) assert result == tin assert is_valid is False + def test_multiple_tins_in_string(): """Test strings containing multiple TIN-like patterns""" test_cases = [ "12-3456789 34-5678901", # Two TINs with space - "12-345678912-3456789", # Two TINs without space - "Some text 12-3456789", # TIN with preceding text - "12-3456789 some text", # TIN with following text + "12-345678912-3456789", # Two TINs without space + "Some text 12-3456789", # TIN with preceding text + "12-3456789 some text", # TIN with following text ] - + for tin in test_cases: result, is_valid = tin_check(tin) assert result == tin assert is_valid is False - diff --git a/fieldExtraction/tests/qa_qc/yn_test.py b/fieldExtraction/tests/qa_qc/yn_test.py index caba83b..b2404b4 100644 --- a/fieldExtraction/tests/qa_qc/yn_test.py +++ b/fieldExtraction/tests/qa_qc/yn_test.py @@ -1,6 +1,6 @@ import pytest -from src.qa_qc.qa_qc_utils import yn_check +from src.utils.qa_qc_utils import yn_check def test_yn_check_with_valid_string_input(): @@ -8,19 +8,22 @@ def test_yn_check_with_valid_string_input(): assert yn_check("Y") == ("Y", True) assert yn_check("N") == ("N", True) + def test_yn_check_with_invalid_string_input(): # Test with invalid string inputs assert yn_check("a") == ("a", False) assert yn_check("yes") == ("yes", False) + def test_yn_check_with_non_string_input(): # Test with non-string inputs assert yn_check(1) == ("1", False) assert yn_check(True) == ("True", False) + def test_yn_check_with_non_convertible_input(): # Test with inputs that cannot be converted to string def raise_type_error(): raise TypeError("Cannot convert to string") - assert yn_check(raise_type_error)[1] == False \ No newline at end of file + assert yn_check(raise_type_error)[1] == False diff --git a/fieldExtraction/tests/calculate_progress_stats_test.py b/fieldExtraction/tests/test_calculate_progress_stats.py similarity index 98% rename from fieldExtraction/tests/calculate_progress_stats_test.py rename to fieldExtraction/tests/test_calculate_progress_stats.py index 872cbb4..2aab301 100644 --- a/fieldExtraction/tests/calculate_progress_stats_test.py +++ b/fieldExtraction/tests/test_calculate_progress_stats.py @@ -10,19 +10,18 @@ class TestCalculateProgressStats(unittest.TestCase): ac_progress, b_progress = calculate_progress_stats(50, 100, 25, 50) self.assertAlmostEqual(ac_progress, 50.0) self.assertAlmostEqual(b_progress, 50.0) - + def test_zero_total(self): ac_progress, b_progress = calculate_progress_stats(50, 0, 25, 50) self.assertEqual(ac_progress, 0) self.assertAlmostEqual(b_progress, 50.0) - + def test_completed_exceeds_total(self): ac_progress, b_progress = calculate_progress_stats(120, 100, 60, 50) self.assertEqual(ac_progress, 100.0) self.assertEqual(b_progress, 100.0) - + def test_all_zeros(self): ac_progress, b_progress = calculate_progress_stats(0, 0, 0, 0) self.assertEqual(ac_progress, 0) self.assertEqual(b_progress, 0) - diff --git a/fieldExtraction/tests/test_chunking.py b/fieldExtraction/tests/test_chunking.py deleted file mode 100644 index dceacc1..0000000 --- a/fieldExtraction/tests/test_chunking.py +++ /dev/null @@ -1,192 +0,0 @@ - -import pytest - -from src.client.smart_chunking_funcs import smart_chunk_ac - -test_chunk_dict_term = { - "1": "NA", - "2": "Termination", - "3": "NA", - "4": "NA", -} - -test_full_text_term = '\n'.join([v for v in test_chunk_dict_term.values()]) - -test_late_paid_claims = { - "1": "NA", - "2": "This is not a late term", # try lowercase - "3": "NA", - "4": "NA", - "10": "NA", - "11": "LATE PAID", - "12": "NA", -} - -test_full_text_late_paid_claims = '\n'.join([v for v in test_late_paid_claims.values()]) - -test_keyword_mappings = { - "or_1": { - "methodology": "or", - "keywords": ["kw1", "kw2", "kw3"], - "case_sensitive": False, - }, - "or_2": {"methodology": "or", "keywords": ["kw4"], "case_sensitive": False}, - "hierarchy_1": { - "methodology": "hierarchy", - "keywords": ["h5", "h6"], - "case_sensitive": False, - }, - "cs_or_1": { - "methodology": "or", - "keywords": ["KW1", "kw2", "kw3"], - "case_sensitive": True, - }, - "and": {"methodology": "and", "keywords": ["KW1", "KW2"], "case_sensitive": False}, - "regex": { - "methodology": "regex", - "regex": r"\b\d{2}-?\d{7}\b", - "case_sensitive": True, - "keywords": ["KW1", "KW2"], - }, -} - -case_1 = { - "1": "NA", - "2": "kw1", - "3": "NA", - "4": "h6", - "5": "NA", - "6": "kw3", -} - -case_2 = { - "1": "NA", - "2": "h6", - "3": "NA", - "4": "kw2", - "10": "NA", - "11": "h5", - "12": "NA", - "15": "kw4", -} - -case_3 = { - "1": "NA", - "2": "KW1", - "3": "NA", - "4": "h6", - "5": "NA", - "6": "kw3", -} - -case_4 = { - "1": "NA", - "2": "KW1", - "3": "NA", - "4": "KW1KW2", - "5": "NA", - "6": "kw3", -} - -case_5 = { - "1": "NA", - "2": "12-3456789", - "3": "987654321", - "4": "Next", - "5": "1234567-89", - "6": "kw3", -} - - -def test_smart_chunk_term(): - assert smart_chunk_ac(text_dict=test_chunk_dict_term, - contract_text=test_full_text_term)["term_group"] == "\n".join(["NA", "Termination", "NA"]) - - -def test_smart_chunk_late_paid(): - result = smart_chunk_ac(text_dict=test_late_paid_claims, - contract_text=test_full_text_late_paid_claims) - assert result["LATE_PAID_CLAIMS_LANGUAGE"] == "\n".join(["NA", "LATE PAID", "NA"]) - - -def test_or_1(): - assert smart_chunk_ac(text_dict=case_1, - contract_text='\n'.join([v for v in case_1.values()]), - keyword_mappings=test_keyword_mappings)[ - "or_1" - ] == "\n".join(["NA", "kw1", "NA", "NA", "kw3"]) - assert smart_chunk_ac(text_dict=case_2, - contract_text='\n'.join([v for v in case_2.values()]), - keyword_mappings=test_keyword_mappings)[ - "or_1" - ] == "\n".join(["NA", "kw2"]) - - -def test_or_2(): - assert smart_chunk_ac(text_dict=case_1, - contract_text='\n'.join([v for v in case_1.values()]), - keyword_mappings=test_keyword_mappings)[ - "or_2" - ] == "\n".join([]) - assert smart_chunk_ac(text_dict=case_2, - contract_text='\n'.join([v for v in case_2.values()]), - keyword_mappings=test_keyword_mappings)[ - "or_2" - ] == "\n".join(["kw4"]) - - -def test_case_sensitive_or_1(): - assert smart_chunk_ac(text_dict=case_1, - contract_text='\n'.join([v for v in case_1.values()]), - keyword_mappings=test_keyword_mappings)[ - "cs_or_1" - ] == "\n".join(["NA", "kw3"]) - assert smart_chunk_ac(text_dict=case_3, - contract_text='\n'.join([v for v in case_3.values()]), - keyword_mappings=test_keyword_mappings)[ - "cs_or_1" - ] == "\n".join(["NA", "KW1", "NA", "NA", "kw3"]) - - -def test_hierarchy_1(): - assert smart_chunk_ac(text_dict=case_1, - contract_text='\n'.join([v for v in case_1.values()]), - keyword_mappings=test_keyword_mappings)[ - "hierarchy_1" - ] == "\n".join(["NA", "h6", "NA"]) - assert smart_chunk_ac(text_dict=case_2, - contract_text='\n'.join([v for v in case_2.values()]), - keyword_mappings=test_keyword_mappings)[ - "hierarchy_1" - ] == "\n".join(["NA", "h5", "NA"]) - - -def test_and(): - assert smart_chunk_ac(text_dict=case_4, - contract_text='\n'.join([v for v in case_4.values()]), - keyword_mappings=test_keyword_mappings)[ - "and" - ] == "\n".join(["NA", "KW1KW2", "NA"]) - - -def test_regex(): - assert smart_chunk_ac(text_dict=case_5, - contract_text='\n'.join([v for v in case_5.values()]), - keyword_mappings=test_keyword_mappings)[ - "regex" - ] == "\n".join(["NA", "12-3456789", "987654321", "Next"]) - - -def test_undefined_methodology(): - with pytest.raises(ValueError): - smart_chunk_ac( - text_dict=case_1, - contract_text='\n'.join([v for v in case_1.values()]), - keyword_mappings={ - "e1": { - "methodology": "Not a real methodology", - "keywords": ["NA"], - "case_sensitive": True, - } - }, - ) diff --git a/fieldExtraction/tests/test_code_funcs.py b/fieldExtraction/tests/test_code_funcs.py index 4c75a25..e7bb733 100644 --- a/fieldExtraction/tests/test_code_funcs.py +++ b/fieldExtraction/tests/test_code_funcs.py @@ -1,317 +1,453 @@ -import unittest -from unittest.mock import patch, MagicMock import json -import os +import re +import unittest +from unittest.mock import MagicMock, patch + import pandas as pd import src.codes.code_funcs as code_funcs -import src.codes.code_constants as code_constants -from src.enums.delimiters import Delimiter +from constants.constants import Constants +from constants.delimiters import Delimiter class TestCodeFuncs(unittest.TestCase): - - def test_clean_service(self): - """Test the clean_service function with various inputs""" - # Test empty input - self.assertEqual(code_funcs.clean_service(""), "") - self.assertEqual(code_funcs.clean_service(None), "") - - # Test uppercase conversion - self.assertEqual(code_funcs.clean_service("test service"), "TEST SERVICE") - - # Test synonym mapping - with patch.dict(code_constants.SYNONYM_MAP, {"CT": "COMPUTED TOMOGRAPHY"}): - self.assertEqual(code_funcs.clean_service("CT SCAN"), "COMPUTED TOMOGRAPHY SCAN") - - # Test removal list - with patch.object(code_constants, "REMOVAL_LIST", ["SCAN"]): - self.assertEqual(code_funcs.clean_service("CT SCAN"), "CT") - - # Test unlisted service - self.assertEqual(code_funcs.clean_service("UNLISTED PROCEDURE"), "UNLISTED") - - # Test stop words - with patch.object(code_constants, "STOP_WORD_LIST", ["THE", "AND"]): - self.assertEqual(code_funcs.clean_service("THE AND"), "") - - @patch('src.utils.llm_utils.invoke_claude') - @patch('src.utils.string_utils.universal_json_load') + def setUp(self): + """Set up test fixtures before each test method.""" + # Create a mock Constants object + self.constants = MagicMock(spec=Constants) + + # Configure mock constants + self.constants.SYNONYM_MAP = { + "DME": "DURABLE MEDICAL EQUIPMENT", + "SNF": "SKILLED NURSING FACILITY", + } + self.constants.REMOVAL_LIST = ["INPATIENT", "OUTPATIENT", "SERVICES"] + self.constants.STOP_WORD_LIST = ["THE", "AND", "OF"] + self.constants.DO_NOT_RUN = ["BY REPORT", "STOP-LOSS"] + + # Setup mappings + self.constants.CPT_LEVEL1_MAPPING = {"12345": "Test Procedure"} + self.constants.HCPCS_LEVEL1_MAPPING = {"J0001": "Test Drug"} + self.constants.REV_LEVEL1_MAPPING = {"123": "Test Revenue"} + + self.constants.CPT_LEVEL2_MAPPING = {"67890": "Advanced Procedure"} + self.constants.HCPCS_LEVEL2_MAPPING = {"J0002": "Advanced Drug"} + self.constants.REV_MAPPING = {"456": "Advanced Revenue"} + + self.constants.BILL_TYPE_MAPPING = {"11X": "Inpatient Hospital"} + self.constants.BILL_TYPE_REVERSE_MAPPING = {"Inpatient Hospital": "11X"} + + # Mock the embedding model and function + mock_embedding = MagicMock() + mock_embedding.index.search.return_value = ([0.9], [[0]]) + mock_embedding.choices = ["Test Description"] + self.constants.get_embedding.return_value = mock_embedding + + self.constants.EMBEDDING_MODEL = MagicMock() + mock_encoded = MagicMock() + mock_encoded.astype.return_value = "mock_vector" + self.constants.EMBEDDING_MODEL.encode.return_value = mock_encoded + + def test_clean_service(self): + """Test the clean_service function with various inputs.""" + # Test empty input + self.assertEqual(code_funcs.clean_service("", self.constants), "") + self.assertEqual(code_funcs.clean_service(None, self.constants), "") + + # Test synonym mapping + self.assertEqual( + code_funcs.clean_service("DME", self.constants), "DURABLE MEDICAL EQUIPMENT" + ) + + # Test removal list + self.assertEqual( + code_funcs.clean_service("INPATIENT SERVICES", self.constants), "" + ) + + # Test stop words only + self.assertEqual(code_funcs.clean_service("THE AND OF", self.constants), "") + + # Test unlisted service + self.assertEqual( + code_funcs.clean_service("UNLISTED PROCEDURE", self.constants), "UNLISTED" + ) + + # Test normal case + self.assertEqual( + code_funcs.clean_service("CT SCAN SNF", self.constants), + "CT SCAN SKILLED NURSING FACILITY", + ) + + @patch("src.utils.llm_utils.invoke_claude") + @patch("src.utils.string_utils.universal_json_load") def test_code_explicit(self, mock_json_load, mock_invoke_claude): - """Test the code_explicit function for invoking Claude""" - # Setup mock responses - mock_invoke_claude.return_value = '{"CPT4_PROC_CD": "12345"}' - mock_json_load.return_value = {"CPT4_PROC_CD": "12345"} - + """Test the code_explicit function for extracting explicit codes.""" + # Setup mocks + mock_invoke_claude.return_value = '{"PROCEDURE_CD": "12345"}' + mock_json_load.return_value = {"PROCEDURE_CD": "12345"} + # Test successful extraction result = code_funcs.code_explicit("TEST SERVICE", "test.pdf") + self.assertEqual(result, {"PROCEDURE_CD": "12345"}) mock_invoke_claude.assert_called_once() - self.assertEqual(result, {"CPT4_PROC_CD": "12345"}) - - - @patch('src.utils.llm_utils.invoke_claude') - @patch('src.utils.string_utils.extract_text_from_delimiters') + + @patch("src.utils.llm_utils.invoke_claude") + @patch("src.utils.string_utils.universal_json_load") + def test_code_category(self, mock_json_load, mock_invoke_claude): + """Test the code_category function for detecting code categories.""" + # Setup mocks + mock_invoke_claude.return_value = '["Drug A", "Drug B"]' + mock_json_load.return_value = ["Drug A", "Drug B"] + + # Setup test data + proc_category = ["Category: J"] + hcpcs_mapping = {"J0001": "Drug A", "J0002": "Drug B", "K0001": "Other Drug"} + + # Test category matching + result = code_funcs.code_category( + "J CODES", proc_category, hcpcs_mapping, "test.pdf" + ) + + # Verify the results + self.assertEqual(len(result["PROCEDURE_CD"]), 2) + self.assertEqual(len(result["PROCEDURE_CD_DESC"]), 2) + self.assertIn("J0001", result["PROCEDURE_CD"]) + self.assertIn("J0002", result["PROCEDURE_CD"]) + self.assertIn("Drug A", result["PROCEDURE_CD_DESC"]) + self.assertIn("Drug B", result["PROCEDURE_CD_DESC"]) + + @patch("src.utils.llm_utils.invoke_claude") + @patch("src.utils.string_utils.extract_text_from_delimiters") def test_code_implicit_special(self, mock_extract, mock_invoke_claude): - """Test the code_implicit_special function for special case handling""" - # Setup mock responses + """Test the code_implicit_special function for special case handling.""" + # Setup mocks mock_invoke_claude.return_value = "mock_response" mock_extract.return_value = "Drugs" - + + # Test drug category result = code_funcs.code_implicit_special("DRUG SERVICE", "test.pdf") self.assertEqual(result["PROCEDURE_CD"], "J0000-J9999") self.assertEqual(result["PROCEDURE_CD_DESC"], "Drugs") - + # Test no match mock_extract.return_value = "" result = code_funcs.code_implicit_special("OTHER SERVICE", "test.pdf") self.assertEqual(result, {}) - + def test_get_embedding_levels(self): - """Test the get_embedding_levels function for determining embedding levels""" + """Test the get_embedding_levels function for determining embedding levels.""" # Test level 1 with both codes enabled implicit_run_dict = {"PROCEDURE_CD": True, "REVENUE_CD": True} - embedding_levels, _, _, _ = code_funcs.get_embedding_levels(1, implicit_run_dict) + embedding_levels, cpt_mapping, hcpcs_mapping, rev_mapping = ( + code_funcs.get_embedding_levels(1, implicit_run_dict, self.constants) + ) + self.assertIn("cpt_level1", embedding_levels) self.assertIn("hcpcs_level1", embedding_levels) self.assertIn("rev_level1", embedding_levels) - + self.assertEqual(cpt_mapping, self.constants.CPT_LEVEL1_MAPPING) + # Test level 2 with only procedure code enabled implicit_run_dict = {"PROCEDURE_CD": True, "REVENUE_CD": False} - embedding_levels, _, _, _ = code_funcs.get_embedding_levels(2, implicit_run_dict) + embedding_levels, cpt_mapping, hcpcs_mapping, rev_mapping = ( + code_funcs.get_embedding_levels(2, implicit_run_dict, self.constants) + ) + self.assertIn("cpt_level2", embedding_levels) self.assertIn("hcpcs_level2", embedding_levels) self.assertNotIn("rev", embedding_levels) - - @patch('src.utils.embedding_utils.load_faiss_index') - @patch('src.codes.code_constants.model.encode') - def test_get_match_list(self, mock_encode, mock_load_faiss): - """Test the get_match_list function for retrieving matches from embeddings""" - # Setup mock responses - mock_encoded_vector = MagicMock() - mock_encoded_vector.astype.return_value = "mock_vector" - mock_encode.return_value = mock_encoded_vector - - # Create a mock FAISS index - mock_index = MagicMock() - # Configure the search method to return appropriate values - mock_index.search.return_value = ( + self.assertEqual(cpt_mapping, self.constants.CPT_LEVEL2_MAPPING) + + def test_get_match_list(self): + """Test the get_match_list function for retrieving matches from embeddings.""" + # Configure mock for proper array-like return values + mock_embedding = MagicMock() + # Search returns a tuple of (scores, indices) where each is a 2D array + mock_embedding.index.search.return_value = ( # Scores array - 2D array with shape [1, top_k] - [[0.9, 0.8]], + [[0.9]], # Indices array - 2D array with shape [1, top_k] - [[0, 1]] + [[0]], ) - - # Setup mock choices for the returned index - choices = ["Description A", "Description B"] - - # Configure load_faiss_index to return our mock objects - mock_load_faiss.return_value = (mock_index, None, choices) - - # Call the function - match_list, highest_similarity = code_funcs.get_match_list("TEST SERVICE", ["test_level"], 2) - + mock_embedding.choices = ["Test Description"] + + # Replace the mock embedding in constants for this test + self.constants.get_embedding.return_value = mock_embedding + + # Test with constants mock configured in setUp + match_list, highest_similarity = code_funcs.get_match_list( + "TEST SERVICE", ["cpt_level1"], self.constants, 1 + ) + # Verify results - self.assertEqual(match_list, ["Description A", "Description B"]) + self.assertEqual(match_list, ["Test Description"]) self.assertEqual(highest_similarity, 0.9) - - # Verify calls were made correctly - mock_encode.assert_called_once_with(["TEST SERVICE"], normalize_embeddings=True) - # Make sure load_faiss_index was called for the test_level - mock_load_faiss.assert_called_once() - # Check that search was called with the encoded vector - mock_index.search.assert_called_once_with("mock_vector", 2) - - @patch('src.codes.code_funcs.get_embedding_levels') - @patch('src.codes.code_funcs.get_match_list') - @patch('src.utils.llm_utils.invoke_claude') - @patch('src.utils.string_utils.universal_json_load') - def test_code_implicit_rag(self, mock_json_load, mock_invoke_claude, mock_get_match_list, mock_get_embedding_levels): - """Test the code_implicit_rag function for RAG-based code extraction""" - # Setup mock responses - mock_get_embedding_levels.return_value = (["test_level"], - {"12345": "Test Procedure"}, - {}, - {"678": "Test Revenue"}) + + # Verify the appropriate method calls + self.constants.EMBEDDING_MODEL.encode.assert_called_once() + self.constants.get_embedding.assert_called_once_with("cpt_level1") + + @patch("src.utils.llm_utils.invoke_claude") + @patch("src.utils.string_utils.universal_json_load") + @patch("src.codes.code_funcs.get_match_list") + def test_code_implicit_rag( + self, mock_get_match_list, mock_json_load, mock_invoke_claude + ): + """Test the code_implicit_rag function for RAG-based code extraction.""" + # Mock get_match_list to return properly formatted results mock_get_match_list.return_value = (["Test Procedure"], 0.9) + + # Setup mocks for Claude responses mock_invoke_claude.return_value = '["Test Procedure"]' mock_json_load.return_value = ["Test Procedure"] - - result = code_funcs.code_implicit_rag("TEST SERVICE", {"PROCEDURE_CD": True, "REVENUE_CD": True}, "test.pdf") - self.assertEqual(result["PROCEDURE_CD"], "12345") - self.assertEqual(result["PROCEDURE_CD_DESC"], "Test Procedure") - self.assertIn("CODE_METHODOLOGY", result) - + + # Test with successful match + implicit_run_dict = {"PROCEDURE_CD": True, "REVENUE_CD": True} + + # Patch the get_embedding_levels function to return predictable values + with patch( + "src.codes.code_funcs.get_embedding_levels" + ) as mock_get_embedding_levels: + # Configure mock to return appropriate values for embedding levels + mock_get_embedding_levels.return_value = ( + ["cpt_level1"], + {"12345": "Test Procedure"}, + {}, + {}, + ) + + # Call the function + result = code_funcs.code_implicit_rag( + "TEST SERVICE", implicit_run_dict, "test.pdf", self.constants + ) + + # Verify results + self.assertEqual(result["PROCEDURE_CD"], "12345") + self.assertEqual(result["PROCEDURE_CD_DESC"], "Test Procedure") + self.assertEqual(result["CODE_METHODOLOGY"], "Implicit - Level 1") + # Test exception handling mock_json_load.side_effect = Exception("Test error") - result = code_funcs.code_implicit_rag("TEST SERVICE", {"PROCEDURE_CD": True, "REVENUE_CD": True}, "test.pdf") + result = code_funcs.code_implicit_rag( + "TEST SERVICE", implicit_run_dict, "test.pdf", self.constants + ) self.assertIn("CODE_METHODOLOGY", result) - - @patch('src.utils.llm_utils.invoke_claude') - @patch('src.utils.string_utils.extract_text_from_delimiters') + + @patch("src.utils.llm_utils.invoke_claude") + @patch("src.utils.string_utils.extract_text_from_delimiters") def test_code_last_check(self, mock_extract, mock_invoke_claude): - """Test the code_last_check function for categorizing non-matched services""" - # Setup mock responses + """Test the code_last_check function for categorizing non-matched services.""" + # Setup mocks mock_invoke_claude.return_value = "mock_response" + + # Test specific case mock_extract.return_value = "Specific" - result = code_funcs.code_last_check("SPECIAL SERVICE", "test.pdf") self.assertEqual(result, "Specific") - + # Test generic case mock_extract.return_value = "Generic" result = code_funcs.code_last_check("GENERIC SERVICE", "test.pdf") self.assertEqual(result, "Generic") - - @patch('src.utils.llm_utils.invoke_claude') - @patch('src.utils.string_utils.universal_json_load') + + @patch("src.utils.llm_utils.invoke_claude") + @patch("src.utils.string_utils.universal_json_load") def test_fill_bill_type(self, mock_json_load, mock_invoke_claude): - """Test the fill_bill_type function for populating bill type information""" - # Setup mock responses + """Test the fill_bill_type function for populating bill type information.""" + # Setup mocks mock_invoke_claude.return_value = '["Inpatient Hospital"]' mock_json_load.return_value = ["Inpatient Hospital"] - - # Mock bill type mappings - with patch.dict(code_constants.bill_type_reverse_mapping, {"Inpatient Hospital": "11X"}): - result = code_funcs.fill_bill_type("INPATIENT SERVICE", {}) - self.assertEqual(result["BILL_TYPE_CD"], "11X") - self.assertEqual(result["BILL_TYPE_CD_DESC"], "Inpatient Hospital") - + + # Test with valid bill type + answer_dict = {} + result = code_funcs.fill_bill_type( + "INPATIENT SERVICE", + answer_dict, + self.constants.BILL_TYPE_MAPPING, + self.constants.BILL_TYPE_REVERSE_MAPPING, + ) + + self.assertEqual(result["BILL_TYPE_CD"], "11X") + self.assertEqual(result["BILL_TYPE_CD_DESC"], "Inpatient Hospital") + + # Test with empty response + mock_json_load.return_value = [] + answer_dict = {"SERVICE_TERM": "Test"} + result = code_funcs.fill_bill_type( + "OTHER SERVICE", + answer_dict, + self.constants.BILL_TYPE_MAPPING, + self.constants.BILL_TYPE_REVERSE_MAPPING, + ) + + self.assertEqual(result, {"SERVICE_TERM": "Test"}) + def test_get_implicit_runs(self): - """Test the get_implicit_runs function for determining which code runs to execute""" + """Test the get_implicit_runs function for determining which code runs to execute.""" # Test hospital claim type result = code_funcs.get_implicit_runs({"AARETE_DERIVED_CLAIM_TYPE_CD": "H"}) self.assertTrue(result["REVENUE_CD"]) - + # Test medical claim type with bill type - result = code_funcs.get_implicit_runs({ - "AARETE_DERIVED_CLAIM_TYPE_CD": "M", - "BILL_TYPE_CD_DESC": "Outpatient" - }) + result = code_funcs.get_implicit_runs( + {"AARETE_DERIVED_CLAIM_TYPE_CD": "M", "BILL_TYPE_CD_DESC": "Outpatient"} + ) self.assertTrue(result["REVENUE_CD"]) self.assertTrue(result["PROCEDURE_CD"]) - + # Test inpatient bill type - result = code_funcs.get_implicit_runs({ - "AARETE_DERIVED_CLAIM_TYPE_CD": "M", - "BILL_TYPE_CD_DESC": "Inpatient Hospital" - }) + result = code_funcs.get_implicit_runs( + { + "AARETE_DERIVED_CLAIM_TYPE_CD": "M", + "BILL_TYPE_CD_DESC": "Inpatient Hospital", + } + ) self.assertTrue(result["REVENUE_CD"]) self.assertFalse(result["PROCEDURE_CD"]) - - @patch('src.codes.code_funcs.clean_service') - @patch('src.codes.code_funcs.fill_bill_type') - @patch('src.codes.code_funcs.get_implicit_runs') - @patch('src.codes.code_funcs.code_explicit') - @patch('src.codes.code_funcs.code_category') - @patch('src.codes.code_funcs.code_implicit_special') - @patch('src.codes.code_funcs.code_implicit_rag') - @patch('src.codes.code_funcs.code_last_check') - def test_extract_codes_from_service(self, mock_last_check, mock_implicit_rag, mock_implicit_special, - mock_category, mock_explicit, mock_implicit_runs, mock_fill_bill_type, - mock_clean_service): - """Test the extract_codes_from_service function end-to-end""" - # Setup mock responses + + @patch("src.codes.code_funcs.clean_service") + @patch("src.codes.code_funcs.fill_bill_type") + @patch("src.codes.code_funcs.get_implicit_runs") + @patch("src.codes.code_funcs.code_explicit") + @patch("src.codes.code_funcs.code_category") + @patch("src.codes.code_funcs.code_implicit_special") + @patch("src.codes.code_funcs.code_implicit_rag") + @patch("src.codes.code_funcs.code_last_check") + def test_extract_codes_from_service( + self, + mock_last_check, + mock_implicit_rag, + mock_implicit_special, + mock_category, + mock_explicit, + mock_implicit_runs, + mock_fill_bill_type, + mock_clean_service, + ): + """Test the extract_codes_from_service function end-to-end.""" + # Setup mocks mock_clean_service.return_value = "CLEAN SERVICE" - mock_fill_bill_type.return_value = {"SERVICE_TERM": "TEST SERVICE", "BILL_TYPE_CD_DESC": "Outpatient"} + mock_fill_bill_type.return_value = { + "SERVICE_TERM": "TEST SERVICE", + "BILL_TYPE_CD_DESC": "Outpatient", + } mock_implicit_runs.return_value = {"PROCEDURE_CD": True, "REVENUE_CD": True} - + # Test explicit code match mock_explicit.return_value = {"PROCEDURE_CD": "12345"} - result = code_funcs.extract_codes_from_service({"SERVICE_TERM": "TEST SERVICE"}) + result = code_funcs.extract_codes_from_service( + {"SERVICE_TERM": "TEST SERVICE"}, self.constants + ) self.assertEqual(result["PROCEDURE_CD"], "12345") self.assertEqual(result["CODE_METHODOLOGY"], "Explicit") - + # Test unlisted service mock_clean_service.return_value = "UNLISTED" - result = code_funcs.extract_codes_from_service({"SERVICE_TERM": "UNLISTED SERVICE"}) + result = code_funcs.extract_codes_from_service( + {"SERVICE_TERM": "UNLISTED SERVICE"}, self.constants + ) self.assertEqual(result["PROCEDURE_CD_DESC"], "UNLISTED") - + # Test category match mock_clean_service.return_value = "CLEAN SERVICE" mock_explicit.return_value = {"PROCEDURE_CD": ["Category: J"]} - mock_category.return_value = {"PROCEDURE_CD": ["J0001"], "PROCEDURE_CD_DESC": ["Drug A"]} - result = code_funcs.extract_codes_from_service({"SERVICE_TERM": "J CODES"}) + mock_category.return_value = { + "PROCEDURE_CD": ["J0001"], + "PROCEDURE_CD_DESC": ["Drug A"], + } + result = code_funcs.extract_codes_from_service( + {"SERVICE_TERM": "J CODES"}, self.constants + ) self.assertEqual(result["CODE_METHODOLOGY"], "Implicit - Letter Category") - + # Test special case match mock_explicit.return_value = {} - mock_implicit_special.return_value = {"PROCEDURE_CD": "J0000-J9999", "PROCEDURE_CD_DESC": "Drugs"} - result = code_funcs.extract_codes_from_service({"SERVICE_TERM": "DRUG SERVICE"}) + mock_implicit_special.return_value = { + "PROCEDURE_CD": "J0000-J9999", + "PROCEDURE_CD_DESC": "Drugs", + } + result = code_funcs.extract_codes_from_service( + {"SERVICE_TERM": "DRUG SERVICE"}, self.constants + ) self.assertEqual(result["CODE_METHODOLOGY"], "Implicit - Special Case") - + # Test RAG match mock_implicit_special.return_value = {} - mock_implicit_rag.return_value = {"PROCEDURE_CD": "12345", "CODE_METHODOLOGY": "Implicit - Level 1"} - result = code_funcs.extract_codes_from_service({"SERVICE_TERM": "TEST SERVICE"}) + mock_implicit_rag.return_value = { + "PROCEDURE_CD": "12345", + "CODE_METHODOLOGY": "Implicit - Level 1", + } + result = code_funcs.extract_codes_from_service( + {"SERVICE_TERM": "TEST SERVICE"}, self.constants + ) self.assertEqual(result["CODE_METHODOLOGY"], "Implicit - Level 1") - - # Test no match + + # Test no match - specific mock_implicit_rag.return_value = {} mock_last_check.return_value = "Specific" - result = code_funcs.extract_codes_from_service({"SERVICE_TERM": "TEST SERVICE"}) + result = code_funcs.extract_codes_from_service( + {"SERVICE_TERM": "TEST SERVICE"}, self.constants + ) self.assertEqual(result["CODE_METHODOLOGY"], "Specific - No Match") - + + # Test no match - generic + mock_last_check.return_value = "Generic" + result = code_funcs.extract_codes_from_service( + {"SERVICE_TERM": "TEST SERVICE"}, self.constants + ) + self.assertEqual(result["CODE_METHODOLOGY"], "Generic - No Match") + def test_fill_claim_type(self): - """Test the fill_claim_type function for determining claim types""" + """Test the fill_claim_type function for determining claim types.""" # Test with existing claim types answer_dicts = [ {"AARETE_DERIVED_CLAIM_TYPE_CD": "H"}, {"AARETE_DERIVED_CLAIM_TYPE_CD": "H"}, {"AARETE_DERIVED_CLAIM_TYPE_CD": "M"}, - {} + {}, ] result = code_funcs.fill_claim_type(answer_dicts) self.assertEqual(result[3]["AARETE_DERIVED_CLAIM_TYPE_CD"], "H") - - # Test with no claim types + + # Test with all empty claim types answer_dicts = [{}, {}] result = code_funcs.fill_claim_type(answer_dicts) self.assertEqual(result, answer_dicts) - - @patch('src.codes.code_funcs.fill_claim_type') - @patch('src.codes.code_funcs.extract_codes_from_service') + + @patch("src.codes.code_funcs.fill_claim_type") + @patch("src.codes.code_funcs.extract_codes_from_service") def test_code_breakout(self, mock_extract_codes, mock_fill_claim_type): - """Test the code_breakout function for processing a DataFrame of service records""" - # Create a test DataFrame - input_df = pd.DataFrame([ - {"SERVICE_TERM": "SERVICE A"}, - {"SERVICE_TERM": "SERVICE B"} - ]) - - # Setup mock responses for the records after conversion to list of dicts + """Test the code_breakout function for processing DataFrame records.""" + # Create test data + test_df = pd.DataFrame( + [{"SERVICE_TERM": "SERVICE A"}, {"SERVICE_TERM": "SERVICE B"}] + ) + + # Setup mocks mock_fill_claim_type.return_value = [ {"SERVICE_TERM": "SERVICE A"}, - {"SERVICE_TERM": "SERVICE B"} + {"SERVICE_TERM": "SERVICE B"}, ] - - # Setup extract_codes_from_service mock to return augmented records + mock_extract_codes.side_effect = [ {"SERVICE_TERM": "SERVICE A", "PROCEDURE_CD": "12345"}, - {"SERVICE_TERM": "SERVICE B", "PROCEDURE_CD": "67890"} + {"SERVICE_TERM": "SERVICE B", "PROCEDURE_CD": "67890"}, ] - - # Call the function - result_df = code_funcs.code_breakout(input_df) - - # Verify the result is a DataFrame + + # Test function + result_df = code_funcs.code_breakout(test_df, self.constants) + + # Verify results self.assertIsInstance(result_df, pd.DataFrame) - - # Verify the correct procedure codes were added + self.assertEqual(len(result_df), 2) self.assertEqual(result_df.iloc[0]["PROCEDURE_CD"], "12345") self.assertEqual(result_df.iloc[1]["PROCEDURE_CD"], "67890") - - # Verify the DataFrame has the expected number of rows - self.assertEqual(len(result_df), 2) - - # Verify fill_claim_type was called with the correct data + + # Verify mock calls mock_fill_claim_type.assert_called_once() - args, _ = mock_fill_claim_type.call_args - self.assertIsInstance(args[0], list) - - # Verify extract_codes_from_service was called for each record self.assertEqual(mock_extract_codes.call_count, 2) -if __name__ == '__main__': - unittest.main() \ No newline at end of file +if __name__ == "__main__": + unittest.main() diff --git a/fieldExtraction/tests/costlog_funcs_test.py b/fieldExtraction/tests/test_costlog_funcs.py similarity index 79% rename from fieldExtraction/tests/costlog_funcs_test.py rename to fieldExtraction/tests/test_costlog_funcs.py index a7ecd90..a23c065 100644 --- a/fieldExtraction/tests/costlog_funcs_test.py +++ b/fieldExtraction/tests/test_costlog_funcs.py @@ -6,4 +6,4 @@ from src.tracking.costlog_utils import aggregate_costlog def test_missing_columns(): with pytest.raises(ValueError): - aggregate_costlog(pd.DataFrame()) \ No newline at end of file + aggregate_costlog(pd.DataFrame()) diff --git a/fieldExtraction/crosswalk/test_crosswalk_utils.py b/fieldExtraction/tests/test_crosswalk_utils.py similarity index 91% rename from fieldExtraction/crosswalk/test_crosswalk_utils.py rename to fieldExtraction/tests/test_crosswalk_utils.py index 3b3fd64..771c31a 100644 --- a/fieldExtraction/crosswalk/test_crosswalk_utils.py +++ b/fieldExtraction/tests/test_crosswalk_utils.py @@ -3,7 +3,8 @@ import json import pandas as pd import pytest -from crosswalk.crosswalk_utils import CrosswalkBuilder, apply_crosswalk +from src.crosswalk.crosswalk_builder import CrosswalkBuilder +from src.utils.crosswalk_utils import apply_crosswalk @pytest.fixture @@ -140,55 +141,64 @@ def test_create_reverse_mapping(): builder.from_dict({"A": "1", "B": "1", "C": "2", "D": "2", "E": "3"}) reversed_mapping = builder.create_reverse_mapping() - assert reversed_mapping == {"1": "A, B", "2": "C, D", "3": "E"} + assert reversed_mapping == {"1": "A|B", "2": "C|D", "3": "E"} # Defensive programming tests def test_apply_crosswalk_edge_cases(): """Test defensive programming features in apply_crosswalk""" mapping = {"A": "1", "B": "2", "C": "3"} - + # Test None and empty values assert apply_crosswalk(None, mapping, default="default") == "default" assert apply_crosswalk("", mapping, default="default") == "default" - assert apply_crosswalk(" ", mapping, default="default") == "default" # Empty after strip, WITH default + assert ( + apply_crosswalk(" ", mapping, default="default") == "default" + ) # Empty after strip, WITH default assert apply_crosswalk(" ", mapping) == "" # Empty after strip, no default - + # Test non-string inputs (should return original as string) assert apply_crosswalk(123, mapping) == "123" # No default = return original assert apply_crosswalk(123, mapping, default="default") == "default" # With default - + # Test list-like string input assert apply_crosswalk("['A']", mapping) == "1" # String representation of list - + # Test comma-separated values result = apply_crosswalk("A,B", mapping) assert result in ["1|2", "2|1"] # Order may vary due to set - + # Test pipe-separated values result = apply_crosswalk("A|B", mapping) assert result in ["1|2", "2|1"] - + # Test list string format result = apply_crosswalk("['A', 'B']", mapping) assert result in ["1|2", "2|1"] - + def test_apply_crosswalk_nested_structures(): """Test handling of nested data structures""" mapping = {"A": "1", "B": "2", "C": "3"} - + # Test nested lists assert apply_crosswalk("[['A'], ['B']]", mapping) in ["1|2", "2|1"] - + # Test mixed nested structures - assert apply_crosswalk("['A', ['B', 'C']]", mapping) in ["1|2|3", "1|3|2", "2|1|3", "2|3|1", "3|1|2", "3|2|1"] + assert apply_crosswalk("['A', ['B', 'C']]", mapping) in [ + "1|2|3", + "1|3|2", + "2|1|3", + "2|3|1", + "3|1|2", + "3|2|1", + ] def test_apply_crosswalk_reverse_mapping(): """Test when value is already in mapping.values()""" mapping = {"A": "1", "B": "2", "C": "3"} - + # Value already mapped should be preserved assert apply_crosswalk("1", mapping) == "1" assert apply_crosswalk("2", mapping) == "2" @@ -197,7 +207,7 @@ def test_apply_crosswalk_reverse_mapping(): def test_apply_crosswalk_empty_results(): """Test when mapping results in empty values""" mapping = {"A": "", "B": "2", "C": ""} # Some empty mappings - + # Should return non-empty results only assert apply_crosswalk("A,B", mapping) == "2" assert apply_crosswalk("A,C", mapping, default="default") == "default" @@ -205,19 +215,19 @@ def test_apply_crosswalk_empty_results(): def test_flatten_to_strings(): """Test the flatten_to_strings helper function""" - from crosswalk.crosswalk_utils import flatten_to_strings - + from src.utils.string_utils import flatten_to_strings + # Test simple cases assert flatten_to_strings("A") == ["A"] assert flatten_to_strings(["A", "B"]) == ["A", "B"] assert flatten_to_strings(("A", "B")) == ["A", "B"] - + # Test nested structures assert flatten_to_strings([["A"], ["B"]]) == ["A", "B"] assert flatten_to_strings([["A", "B"], "C"]) == ["A", "B", "C"] - + # Test mixed types assert flatten_to_strings([1, ["2", 3]]) == ["1", "2", "3"] - + # Test with whitespace - assert flatten_to_strings([" A ", [" B "]]) == ["A", "B"] \ No newline at end of file + assert flatten_to_strings([" A ", [" B "]]) == ["A", "B"] diff --git a/fieldExtraction/tests/dynamic_test.py b/fieldExtraction/tests/test_dynamic.py similarity index 68% rename from fieldExtraction/tests/dynamic_test.py rename to fieldExtraction/tests/test_dynamic.py index 3d755bc..a890c6c 100644 --- a/fieldExtraction/tests/dynamic_test.py +++ b/fieldExtraction/tests/test_dynamic.py @@ -1,14 +1,15 @@ import unittest -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch import src.investment.one_to_n_funcs as one_to_n_funcs -from src.prompts.investment_prompts import FieldSet, Field -import src.config as config +from constants.constants import Constants + class TestOneToN(unittest.TestCase): def setUp(self): self.exhibit_text = "Sample exhibit text with some content" self.filename = "test_file.pdf" + self.constants = Constants() def test_get_exhibit_level_answers(self): # Save original functions to restore later @@ -16,7 +17,7 @@ class TestOneToN(unittest.TestCase): original_exhibit_level = one_to_n_funcs.EXHIBIT_LEVEL original_invoke_claude = one_to_n_funcs.llm_utils.invoke_claude original_json_load = one_to_n_funcs.string_utils.universal_json_load - + try: # Create mocks mock_fieldset = MagicMock() @@ -24,24 +25,30 @@ class TestOneToN(unittest.TestCase): mock_fieldset.return_value = mock_fieldset_instance mock_exhibit_level = MagicMock(return_value="test prompt") mock_invoke_claude = MagicMock(return_value="raw json") - mock_json_load = MagicMock(return_value={"field1": "value1", "field2": "value2"}) - + mock_json_load = MagicMock( + return_value={"field1": "value1", "field2": "value2"} + ) + # Replace functions with mocks one_to_n_funcs.FieldSet = mock_fieldset one_to_n_funcs.EXHIBIT_LEVEL = mock_exhibit_level one_to_n_funcs.llm_utils.invoke_claude = mock_invoke_claude one_to_n_funcs.string_utils.universal_json_load = mock_json_load - + # Test Case 1: contains_fields() returns False mock_fieldset_instance.contains_fields.return_value = False - result = one_to_n_funcs.get_exhibit_level_answers(self.exhibit_text, self.filename) + result = one_to_n_funcs.get_exhibit_level_answers( + self.exhibit_text, self.filename + ) self.assertEqual(result, {}) - + # Test Case 2: contains_fields() returns True mock_fieldset_instance.contains_fields.return_value = True - result = one_to_n_funcs.get_exhibit_level_answers(self.exhibit_text, self.filename) + result = one_to_n_funcs.get_exhibit_level_answers( + self.exhibit_text, self.filename + ) self.assertEqual(result, {"field1": "value1", "field2": "value2"}) - + finally: # Restore original functions one_to_n_funcs.FieldSet = original_fieldset @@ -49,61 +56,59 @@ class TestOneToN(unittest.TestCase): one_to_n_funcs.llm_utils.invoke_claude = original_invoke_claude one_to_n_funcs.string_utils.universal_json_load = original_json_load - - @patch('src.utils.llm_utils.invoke_claude') + @patch("src.utils.llm_utils.invoke_claude") def test_get_reimbursement_primary(self, mock_invoke_claude): # Setup mock_fields = MagicMock() mock_fields.get_prompt_dict.return_value = {"field1": "prompt1"} mock_invoke_claude.return_value = '{"answer1": "value1"}' - + # Execute result = one_to_n_funcs.get_reimbursement_primary( mock_fields, self.exhibit_text, self.filename ) - + # Assert self.assertEqual(result, {"answer1": "value1"}) mock_invoke_claude.assert_called_once() - @patch('src.utils.llm_utils.invoke_claude') + @patch("src.utils.llm_utils.invoke_claude") def test_get_special_cases(self, mock_invoke_claude): # Setup - test_answers = [{ - "SERVICE_TERM": "service1", - "REIMB_TERM": "reimbursement1" - }] + test_answers = [{"SERVICE_TERM": "service1", "REIMB_TERM": "reimbursement1"}] mock_invoke_claude.return_value = "|N/A|" - + # Execute - result = one_to_n_funcs.get_special_cases(test_answers, self.filename) - + result = one_to_n_funcs.get_special_cases( + test_answers, + self.constants.VALID_CARVEOUTS, + self.constants.VALID_SPECIAL, + self.filename, + ) + # Assert self.assertEqual(len(result), 1) self.assertEqual(result[0]["CARVEOUT_CD"], "N/A") self.assertEqual(result[0]["CARVEOUT_IND"], "N") self.assertEqual(result[0]["DEFAULT_IND"], "N") - @patch('src.utils.llm_utils.invoke_claude') + @patch("src.utils.llm_utils.invoke_claude") def test_methodology_breakout_single_row(self, mock_invoke_claude): # Setup - test_answer = { - "SERVICE_TERM": "service1", - "REIMB_TERM": "Fee Schedule" - } + test_answer = {"SERVICE_TERM": "service1", "REIMB_TERM": "Fee Schedule"} mock_invoke_claude.side_effect = [ '{"AARETE_DERIVED_REIMB_METHOD": "Fee Schedule", "REIMB_PCT_RATE": "80%"}', - '{"FEE_SCHEDULE": "TEST", "AARETE_DERIVED_FEE_SCHEDULE": "TEST", "AARETE_DERIVED_FEE_SCHEDULE_VERSION": "1.0"}' + '{"FEE_SCHEDULE": "TEST", "AARETE_DERIVED_FEE_SCHEDULE": "TEST", "AARETE_DERIVED_FEE_SCHEDULE_VERSION": "1.0"}', ] - + # Execute result = one_to_n_funcs.methodology_breakout_single_row( test_answer, {"methodology": "questions"}, {"fs": "questions"}, - self.filename + self.filename, ) - + # Assert self.assertEqual(len(result), 1) self.assertEqual(result[0]["AARETE_DERIVED_REIMB_METHOD"], "Fee Schedule") @@ -112,45 +117,59 @@ class TestOneToN(unittest.TestCase): def test_combine_one_to_n_answers(self): # Setup exhibit_level = {"EXHIBIT_TITLE": "Test Exhibit"} - reimbursement_level = [ - {"REIMB_TERM": "term1"}, - {"REIMB_TERM": "term2"} - ] + reimbursement_level = [{"REIMB_TERM": "term1"}, {"REIMB_TERM": "term2"}] tin_npi = {"TIN": "123456789"} - + # Execute result = one_to_n_funcs.combine_one_to_n_answers( exhibit_level, reimbursement_level, tin_npi ) - + # Assert self.assertEqual(len(result), 2) self.assertTrue(all("EXHIBIT_TITLE" in d for d in result)) self.assertTrue(all("TIN" in d for d in result)) self.assertTrue(all("REIMB_TERM" in d for d in result)) - @patch('src.investment.one_to_n_funcs.get_reimbursement_primary') - @patch('src.investment.one_to_n_funcs.get_special_cases') - @patch('src.investment.one_to_n_funcs.validate_reimbursements_for_llm') - def test_reimbursement_level(self, mock_validate_llm, mock_special_cases, mock_reimbursement_primary): + @patch("src.investment.one_to_n_funcs.get_reimbursement_primary") + @patch("src.investment.one_to_n_funcs.get_special_cases") + @patch("src.investment.one_to_n_funcs.validate_reimbursements_for_llm") + def test_reimbursement_level( + self, mock_validate_llm, mock_special_cases, mock_reimbursement_primary + ): # Setup - mock_reimbursement_primary.return_value = [{"SERVICE_TERM": "Test Service", "REIMB_TERM": "term1$"}] - mock_special_cases.return_value = [{"SERVICE_TERM": "Test Service", "REIMB_TERM": "term1$", "CARVEOUT_IND": "N"}] + mock_reimbursement_primary.return_value = [ + {"SERVICE_TERM": "Test Service", "REIMB_TERM": "term1$"} + ] + mock_special_cases.return_value = [ + { + "SERVICE_TERM": "Test Service", + "REIMB_TERM": "term1$", + "CARVEOUT_IND": "N", + } + ] mock_validate_llm.return_value = True # Mock all entries pass LLM validation - + mock_fields = MagicMock() exhibit_page = "1" seen_pairs = set() - + # Execute result = one_to_n_funcs.reimbursement_level( - self.exhibit_text, self.filename, mock_fields, exhibit_page, seen_pairs, exhibit_lesser_of="N/A" + self.exhibit_text, + self.filename, + mock_fields, + exhibit_page, + seen_pairs, + exhibit_lesser_of="N/A", + constants=self.constants, ) - + # Assert self.assertEqual(len(result), 1) mock_reimbursement_primary.assert_called_once() mock_special_cases.assert_called_once() -if __name__ == '__main__': - unittest.main() \ No newline at end of file + +if __name__ == "__main__": + unittest.main() diff --git a/fieldExtraction/tests/test_exhibit_chunk_mapping.py b/fieldExtraction/tests/test_exhibit_chunk_mapping.py new file mode 100644 index 0000000..e478bcd --- /dev/null +++ b/fieldExtraction/tests/test_exhibit_chunk_mapping.py @@ -0,0 +1,43 @@ +from src.investment.preprocessing_funcs import chunk_by_exhibit + + +def test1(): + """Test non-empty text_dict with empty exhibit_pages.""" + text_dict = {"1": "This", "2": "is", "3": "an", "4": "exhibit", "5": "test"} + exhibit_pages = [] + assert chunk_by_exhibit(text_dict, exhibit_pages) == { + "1": "1", + "2": "2", + "3": "3", + "4": "4", + "5": "5", + } + + +def test2(): + """Test when exhibit_pages contains some keys that are also in text_dict.""" + text_dict = {"1": "Welcome", "2": "to", "3": "the", "4": "exhibit"} + exhibit_pages = ["1", "2", "4"] + expected_output = {"1": "1", "2": "2", "3": "2", "4": "4"} + assert chunk_by_exhibit(text_dict, exhibit_pages) == expected_output + + +def test3(): + """Test multiple exhibit pages with transitions between them.""" + text_dict = { + "1": "Page 1", + "2": "Page 2", + "3": "Page 3", + "4": "Page 4", + "5": "Page 5", + } + exhibit_pages = ["2", "4"] + expected_output = {"1": "0", "2": "2", "3": "2", "4": "4", "5": "4"} + assert chunk_by_exhibit(text_dict, exhibit_pages) == expected_output + + +test1() + +test2() + +test3() diff --git a/fieldExtraction/tests/test_fieldset.py b/fieldExtraction/tests/test_fieldset.py new file mode 100644 index 0000000..8fee9ab --- /dev/null +++ b/fieldExtraction/tests/test_fieldset.py @@ -0,0 +1,141 @@ +import unittest +from unittest.mock import MagicMock, mock_open, patch + +from src.prompts.investment_prompts import Field, FieldSet + + +class TestField(unittest.TestCase): + + def setUp(self): + self.field_dict = { + "field_name": "test_field", + "relationship": "one_to_one", + "field_type": "exhibit_level", + "prompt": "Please provide {valid_values}.", + "valid_values": "some_value", + } + self.field = Field(self.field_dict) + + def test_init(self): + """Test initialization of Field object.""" + self.assertEqual(self.field.field_name, "test_field") + self.assertEqual(self.field.relationship, "one_to_one") + self.assertEqual(self.field.field_type, "exhibit_level") + self.assertEqual(self.field.prompt, "Please provide {valid_values}.") + self.assertEqual(self.field.valid_values, "some_value") + + def test_from_values(self): + """Test the class method 'from_values'.""" + new_field = Field.from_values( + field_name="new_field", + relationship="one_to_n", + field_type="dynamic", + prompt="Choose only from the following: {valid_values}.", + valid_values="new_value", + ) + self.assertEqual(new_field.field_name, "new_field") + self.assertEqual(new_field.relationship, "one_to_n") + self.assertEqual(new_field.field_type, "dynamic") + self.assertEqual( + new_field.prompt, "Choose only from the following: {valid_values}." + ) + self.assertEqual(new_field.valid_values, "new_value") + + def test_to_dict(self): + """Test conversion to dictionary.""" + field_dict = self.field.to_dict() + self.assertEqual(field_dict["field_name"], "test_field") + self.assertEqual(field_dict["relationship"], "one_to_one") + self.assertEqual(field_dict["field_type"], "exhibit_level") + self.assertEqual(field_dict["prompt"], "Please provide {valid_values}.") + self.assertEqual(field_dict["valid_values"], "some_value") + + def test_print(self): + """Test printing of Field object.""" + # Normally we'd check print output, but here we'll assert we reach print calls. + with patch("builtins.print") as mocked_print: + self.field.print() + mocked_print.assert_any_call("Field Name: test_field") + + def test_get_prompt_dict(self): + """Test get_prompt_dict method.""" + prompt_dict = self.field.get_prompt_dict() + self.assertEqual(prompt_dict["test_field"], "Please provide some_value.") + + def test_get_prompt(self): + """Test prompt resolution.""" + resolved_prompt = self.field.get_prompt() + self.assertEqual(resolved_prompt, "Please provide some_value.") + + def test_update_valid_values(self): + """Test updating valid values.""" + self.field.update_valid_values("new_value") + self.assertEqual(self.field.valid_values, "new_value") + + +class TestFieldSet(unittest.TestCase): + + def setUp(self): + self.field_dict = { + "field_name": "test_field", + "relationship": "one_to_one", + "field_type": "exhibit_level", + "prompt": "Please provide {valid_values}.", + "valid_values": "some_value", + } + self.field = Field(self.field_dict) + self.field_set = FieldSet() + + def test_add_field(self): + """Test adding a field.""" + self.field_set.add_field(self.field) + self.assertEqual(len(self.field_set.fields), 1) + + def test_add_duplicate_field(self): + """Test adding a duplicate field (should do nothing)""" + self.field_set.add_field(self.field) + self.assertEqual(len(self.field_set.fields), 1) + + def test_get_field(self): + """Test getting a specific field.""" + self.field_set.add_field(self.field) + fetched_field = self.field_set.get_field("test_field") + self.assertEqual(fetched_field.field_name, "test_field") + + def test_get_field_not_found(self): + """Test getting a non-existent field (should raise ValueError).""" + with self.assertRaises(ValueError): + self.field_set.get_field("non_existent_field") + + def test_list_fields(self): + """Test listing all fields.""" + self.field_set.add_field(self.field) + fields = self.field_set.list_fields() + self.assertIn("test_field", fields) + + def test_remove_field(self): + """Test removing a field.""" + self.field_set.add_field(self.field) + self.field_set.remove_field("test_field") + self.assertEqual(len(self.field_set.fields), 0) + + def test_get_prompt_dict(self): + """Test getting prompt dictionary.""" + self.field_set.add_field(self.field) + prompt_dict = self.field_set.get_prompt_dict() + self.assertIn("test_field", prompt_dict) + + def test_load_from_file(self): + """Test loading fields from a file (mocked).""" + with patch( + "builtins.open", + mock_open( + read_data='[{"field_name": "test_field", "relationship": "one_to_one", "field_type": "exhibit_level", "prompt": "Please provide {valid_values}.", "valid_values": "some_value"}]' + ), + ): + self.field_set.load_from_file("mock_file.json") + self.assertEqual(len(self.field_set.fields), 1) + + +if __name__ == "__main__": + unittest.main() diff --git a/fieldExtraction/tests/investment_postprocess_test.py b/fieldExtraction/tests/test_investment_postprocess.py similarity index 54% rename from fieldExtraction/tests/investment_postprocess_test.py rename to fieldExtraction/tests/test_investment_postprocess.py index 7f8b255..6bc5f2b 100644 --- a/fieldExtraction/tests/investment_postprocess_test.py +++ b/fieldExtraction/tests/test_investment_postprocess.py @@ -1,22 +1,15 @@ -from unittest.mock import patch, MagicMock import unittest -from unittest.mock import patch -import pytest +from unittest.mock import MagicMock, patch + import pandas as pd -from src.investment.investment_postprocessing_funcs import ( - normalize_indicator_field, - format_rate_fields_with_commas, - remove_hyphens, - flatten_singleton_string_list, - validate_and_reformat_date, - date_postprocess, - normalize_auto_renewal_term, - normalize_cpt_fields, - process_patient_age_range, - remove_redundant_reimb_info, - deduplicate_provider_columns, - rename_columns -) +import pytest + +from src.investment.postprocessing_funcs import ( + date_postprocess, deduplicate_provider_columns, + flatten_singleton_string_list, format_rate_fields_with_commas, + normalize_auto_renewal_term, normalize_cpt_fields, + normalize_indicator_field, process_patient_age_range, remove_hyphens, + remove_redundant_reimb_info, rename_columns, validate_and_reformat_date) class TestPostprocessFunctions(unittest.TestCase): @@ -50,7 +43,7 @@ class TestPostprocessFunctions(unittest.TestCase): def test_rename_columns(self): """Tests the rename_columns function to ensure it correctly renames specified columns. - + Tests: 1. Basic column renaming from PROCEDURE_CD to CPT4_PROC_CD 2. Multiple columns being renamed @@ -58,66 +51,68 @@ class TestPostprocessFunctions(unittest.TestCase): 4. Empty DataFrame """ # Test case 1: Basic column renaming - input_df1 = pd.DataFrame({ - "PROCEDURE_CD": ["12345", "67890"], - "PROCEDURE_CD_DESC": ["Test Procedure", "Another Procedure"], - "OTHER_COLUMN": ["value1", "value2"] - }) - - expected_df1 = pd.DataFrame({ - "CPT4_PROC_CD": ["12345", "67890"], - "CPT4_PROC_CD_DESC": ["Test Procedure", "Another Procedure"], - "OTHER_COLUMN": ["value1", "value2"] - }) - + input_df1 = pd.DataFrame( + { + "PROCEDURE_CD": ["12345", "67890"], + "PROCEDURE_CD_DESC": ["Test Procedure", "Another Procedure"], + "OTHER_COLUMN": ["value1", "value2"], + } + ) + + expected_df1 = pd.DataFrame( + { + "CPT4_PROC_CD": ["12345", "67890"], + "CPT4_PROC_CD_DESC": ["Test Procedure", "Another Procedure"], + "OTHER_COLUMN": ["value1", "value2"], + } + ) + result_df1 = rename_columns(input_df1) pd.testing.assert_frame_equal(result_df1, expected_df1) - + # Test case 2: Only some columns need renaming - input_df2 = pd.DataFrame({ - "PROCEDURE_CD": ["12345", "67890"], - "OTHER_COLUMN": ["value1", "value2"] - }) - - expected_df2 = pd.DataFrame({ - "CPT4_PROC_CD": ["12345", "67890"], - "OTHER_COLUMN": ["value1", "value2"] - }) - + input_df2 = pd.DataFrame( + {"PROCEDURE_CD": ["12345", "67890"], "OTHER_COLUMN": ["value1", "value2"]} + ) + + expected_df2 = pd.DataFrame( + {"CPT4_PROC_CD": ["12345", "67890"], "OTHER_COLUMN": ["value1", "value2"]} + ) + result_df2 = rename_columns(input_df2) pd.testing.assert_frame_equal(result_df2, expected_df2) - + # Test case 3: None of the columns need renaming - input_df3 = pd.DataFrame({ - "COLUMN_A": ["a", "b"], - "COLUMN_B": ["c", "d"] - }) - + input_df3 = pd.DataFrame({"COLUMN_A": ["a", "b"], "COLUMN_B": ["c", "d"]}) + result_df3 = rename_columns(input_df3) pd.testing.assert_frame_equal(result_df3, input_df3) # Should be unchanged - + # Test case 4: Empty DataFrame empty_df = pd.DataFrame() result_empty_df = rename_columns(empty_df) pd.testing.assert_frame_equal(result_empty_df, empty_df) # Should be unchanged - @patch("src.utils.llm_utils.invoke_claude") - @patch("src.investment.investment_postprocessing_funcs.invoke_derived_term_date") - @patch("src.prompts.investment_prompts.FieldSet.load_from_file") # Patch this specific method - def test_date_postprocess(self, mock_load_from_file, mock_derived_term_date, mock_invoke_claude): + @patch("src.investment.postprocessing_funcs.invoke_derived_term_date") + @patch( + "src.prompts.investment_prompts.FieldSet.load_from_file" + ) # Patch this specific method + def test_date_postprocess( + self, mock_load_from_file, mock_derived_term_date, mock_invoke_claude + ): """Tests the date_postprocess function with mocked dependencies. - + This test verifies that: 1. Date fields are standardized to YYYYMMDD format using the LLM (mocked) 2. The derived termination date is properly calculated and added to the DataFrame - + The test uses multiple patches to isolate the function from external dependencies: - - Mocks `invoke_claude` to return a fixed date string ("|20230101|") + - Mocks `invoke_claude` to return a fixed date string ("|20230101|") - Mocks `invoke_derived_term_date` to return a fixed termination date ("20231231") - Mocks `FieldSet.load_from_file` and `FieldSet.filter` to avoid file operations and provide controlled field definitions - + The test checks that the date fields in the DataFrame are correctly formatted and that the derived termination date is added as expected. """ @@ -125,28 +120,42 @@ class TestPostprocessFunctions(unittest.TestCase): field_mocks = [ MagicMock(field_name="CONTRACT_EFFECTIVE_DT"), MagicMock(field_name="CONTRACT_TERMINATION_DT"), - MagicMock(field_name="TERMINATION_DT") + MagicMock(field_name="TERMINATION_DT"), ] - + # Configure the mock methods mock_invoke_claude.return_value = "|20230101|" mock_derived_term_date.return_value = "20231231" - - df = pd.DataFrame({ - "CONTRACT_EFFECTIVE_DT": ["2023-01-01", "01/01/2023", "invalid"], - "CONTRACT_TERMINATION_DT": ["2023-12-31", "12/31/2023", "invalid"], - "TERMINATION_DT": ["2023-12-31", "12/31/2023", "invalid"], - "AARETE_DERIVED_EFFECTIVE_DT": ["2023-01-01", "2023-01-01", "2023-01-01"] - }) + + df = pd.DataFrame( + { + "CONTRACT_EFFECTIVE_DT": ["2023-01-01", "01/01/2023", "invalid"], + "CONTRACT_TERMINATION_DT": ["2023-12-31", "12/31/2023", "invalid"], + "TERMINATION_DT": ["2023-12-31", "12/31/2023", "invalid"], + "AARETE_DERIVED_EFFECTIVE_DT": [ + "2023-01-01", + "2023-01-01", + "2023-01-01", + ], + } + ) with patch("src.prompts.investment_prompts.FieldSet.filter") as mock_filter: # Configure the filter to return a mock object with fields attribute mock_filter.return_value = MagicMock(fields=field_mocks) - + result_df = date_postprocess(df, "mock_path") - assert result_df["CONTRACT_EFFECTIVE_DT"].tolist() == ["20230101", "20230101", "20230101"] - assert result_df["AARETE_DERIVED_TERMINATION_DT"].tolist() == ["20231231", "20231231", "20231231"] + assert result_df["CONTRACT_EFFECTIVE_DT"].tolist() == [ + "20230101", + "20230101", + "20230101", + ] + assert result_df["AARETE_DERIVED_TERMINATION_DT"].tolist() == [ + "20231231", + "20231231", + "20231231", + ] def test_normalize_auto_renewal_term(self): self.assertEqual(normalize_auto_renewal_term("12 months"), "1 year") @@ -158,14 +167,17 @@ class TestPostprocessFunctions(unittest.TestCase): def test_normalize_cpt_fields(self): self.assertEqual(normalize_cpt_fields("[123, 456]"), "['123', '456']") self.assertEqual(normalize_cpt_fields("123-456"), "['123-456']") - self.assertEqual(normalize_cpt_fields("['T0000-T9999, S0000-S9999']"), "['T0000-T9999', 'S0000-S9999']") + self.assertEqual( + normalize_cpt_fields("['T0000-T9999, S0000-S9999']"), + "['T0000-T9999', 'S0000-S9999']", + ) self.assertEqual(normalize_cpt_fields("123"), "['123']") self.assertEqual(normalize_cpt_fields(None), "") self.assertEqual(normalize_cpt_fields(123), "['123']") def test_process_patient_age_range(self): """Tests the process_patient_age_range function with various age range formats. - + Tests: 1. Standard hyphenated ranges (e.g., "0-18") 2. Single age values @@ -177,55 +189,55 @@ class TestPostprocessFunctions(unittest.TestCase): 8. Missing PATIENT_AGE_RANGE column """ # Test case 1: DataFrame with PATIENT_AGE_RANGE column - input_df = pd.DataFrame({ - "PATIENT_AGE_RANGE": [ - "0-18", # Standard hyphenated range - "21", # Single age - ] - }) - + input_df = pd.DataFrame( + { + "PATIENT_AGE_RANGE": [ + "0-18", # Standard hyphenated range + "21", # Single age + ] + } + ) + result_df = process_patient_age_range(input_df) - + # Verify columns self.assertIn("PATIENT_AGE_MIN", result_df.columns) self.assertIn("PATIENT_AGE_MAX", result_df.columns) self.assertNotIn("PATIENT_AGE_RANGE", result_df.columns) - + # Expected values - expected_min = ["0", "21"] + expected_min = ["0", "21"] expected_max = ["18", "21"] - + # Check transformations pd.testing.assert_series_equal( result_df["PATIENT_AGE_MIN"], pd.Series(expected_min, name="PATIENT_AGE_MIN"), - check_dtype=False + check_dtype=False, ) pd.testing.assert_series_equal( result_df["PATIENT_AGE_MAX"], pd.Series(expected_max, name="PATIENT_AGE_MAX"), - check_dtype=False + check_dtype=False, ) # Test case 2: DataFrame without PATIENT_AGE_RANGE column - input_df_no_age = pd.DataFrame({ - "OTHER_COLUMN": ["value1", "value2"] - }) + input_df_no_age = pd.DataFrame({"OTHER_COLUMN": ["value1", "value2"]}) result_df_no_age = process_patient_age_range(input_df_no_age) - + # Verify the DataFrame is unchanged pd.testing.assert_frame_equal(input_df_no_age, result_df_no_age) # Test case 3: Empty DataFrame empty_df = pd.DataFrame() result_empty_df = process_patient_age_range(empty_df) - + # Verify empty DataFrame is unchanged pd.testing.assert_frame_equal(empty_df, result_empty_df) def test_validate_and_reformat_date(self): """Tests validate_and_reformat_date with various date formats. - + Tests: 1. Date already in YYYY/MM/DD format 2. Common alternative formats (YYYY-MM-DD, MM/DD/YYYY, etc.) @@ -234,23 +246,23 @@ class TestPostprocessFunctions(unittest.TestCase): """ # Date already in correct format self.assertEqual(validate_and_reformat_date("2023/01/15"), "2023/01/15") - + # Test various date formats that should be reformatted self.assertEqual(validate_and_reformat_date("2023-01-15"), "2023/01/15") self.assertEqual(validate_and_reformat_date("01/15/2023"), "2023/01/15") self.assertEqual(validate_and_reformat_date("15-Jan-2023"), "2023/01/15") - + # Test invalid formats - should return the original string self.assertEqual(validate_and_reformat_date("Invalid date"), "Invalid date") self.assertEqual(validate_and_reformat_date("01-15"), "01-15") - + # Test None and non-string values self.assertEqual(validate_and_reformat_date(None), None) self.assertEqual(validate_and_reformat_date(12345), 12345) def test_remove_redundant_reimb_info(self): """Tests remove_redundant_reimb_date function. - + Tests: 1. When reimbursement dates match derived dates (should remove) 2. When reimbursement dates differ from derived dates (should keep) @@ -258,66 +270,78 @@ class TestPostprocessFunctions(unittest.TestCase): 4. When columns are missing (should return unchanged DataFrame) """ # Test case 1: When dates match (should remove) - input_df1 = pd.DataFrame({ - "REIMB_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], - "REIMB_TERMINATION_DT": ["2023/12/31", "2023/12/31"], - "AARETE_DERIVED_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], - "AARETE_DERIVED_TERMINATION_DT": ["2023/12/31", "2023/12/31"] - }) - - expected_df1 = pd.DataFrame({ - "REIMB_EFFECTIVE_DT": ["", ""], - "REIMB_TERMINATION_DT": ["", ""], - "AARETE_DERIVED_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], - "AARETE_DERIVED_TERMINATION_DT": ["2023/12/31", "2023/12/31"] - }) - + input_df1 = pd.DataFrame( + { + "REIMB_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], + "REIMB_TERMINATION_DT": ["2023/12/31", "2023/12/31"], + "AARETE_DERIVED_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], + "AARETE_DERIVED_TERMINATION_DT": ["2023/12/31", "2023/12/31"], + } + ) + + expected_df1 = pd.DataFrame( + { + "REIMB_EFFECTIVE_DT": ["", ""], + "REIMB_TERMINATION_DT": ["", ""], + "AARETE_DERIVED_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], + "AARETE_DERIVED_TERMINATION_DT": ["2023/12/31", "2023/12/31"], + } + ) + result_df1 = remove_redundant_reimb_info(input_df1) pd.testing.assert_frame_equal(result_df1, expected_df1) - + # Test case 2: When dates differ (should keep) - input_df2 = pd.DataFrame({ - "REIMB_EFFECTIVE_DT": ["2023/01/15", "2023/02/15"], - "REIMB_TERMINATION_DT": ["2023/12/15", "2023/12/15"], - "AARETE_DERIVED_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], - "AARETE_DERIVED_TERMINATION_DT": ["2023/12/31", "2023/12/31"] - }) - + input_df2 = pd.DataFrame( + { + "REIMB_EFFECTIVE_DT": ["2023/01/15", "2023/02/15"], + "REIMB_TERMINATION_DT": ["2023/12/15", "2023/12/15"], + "AARETE_DERIVED_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], + "AARETE_DERIVED_TERMINATION_DT": ["2023/12/31", "2023/12/31"], + } + ) + # Result should be unchanged result_df2 = remove_redundant_reimb_info(input_df2) pd.testing.assert_frame_equal(result_df2, input_df2) - + # Test case 3: Mixed case - some match, some don't - input_df3 = pd.DataFrame({ - "REIMB_EFFECTIVE_DT": ["2023/01/01", "2023/02/15"], - "REIMB_TERMINATION_DT": ["2023/12/31", "2023/12/15"], - "AARETE_DERIVED_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], - "AARETE_DERIVED_TERMINATION_DT": ["2023/12/31", "2023/12/31"] - }) - - expected_df3 = pd.DataFrame({ - "REIMB_EFFECTIVE_DT": ["", "2023/02/15"], - "REIMB_TERMINATION_DT": ["", "2023/12/15"], - "AARETE_DERIVED_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], - "AARETE_DERIVED_TERMINATION_DT": ["2023/12/31", "2023/12/31"] - }) - + input_df3 = pd.DataFrame( + { + "REIMB_EFFECTIVE_DT": ["2023/01/01", "2023/02/15"], + "REIMB_TERMINATION_DT": ["2023/12/31", "2023/12/15"], + "AARETE_DERIVED_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], + "AARETE_DERIVED_TERMINATION_DT": ["2023/12/31", "2023/12/31"], + } + ) + + expected_df3 = pd.DataFrame( + { + "REIMB_EFFECTIVE_DT": ["", "2023/02/15"], + "REIMB_TERMINATION_DT": ["", "2023/12/15"], + "AARETE_DERIVED_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], + "AARETE_DERIVED_TERMINATION_DT": ["2023/12/31", "2023/12/31"], + } + ) + result_df3 = remove_redundant_reimb_info(input_df3) pd.testing.assert_frame_equal(result_df3, expected_df3) - + # Test case 4: Missing columns - input_df4 = pd.DataFrame({ - "REIMB_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], - "OTHER_COLUMN": ["value1", "value2"] - }) - + input_df4 = pd.DataFrame( + { + "REIMB_EFFECTIVE_DT": ["2023/01/01", "2023/02/01"], + "OTHER_COLUMN": ["value1", "value2"], + } + ) + # Result should be unchanged result_df4 = remove_redundant_reimb_info(input_df4) pd.testing.assert_frame_equal(result_df4, input_df4) def test_deduplicate_provider_columns(self): """Tests deduplicate_provider_columns function. - + Tests: 1. Basic deduplication - removes GROUP values from OTHER fields 2. Internal deduplication - removes duplicates within OTHER fields @@ -326,122 +350,146 @@ class TestPostprocessFunctions(unittest.TestCase): 5. Missing columns - should return unchanged DataFrame 6. Empty DataFrame """ - + # Test case 1: Basic GROUP removal - input_df1 = pd.DataFrame({ - "PROV_GROUP_TIN": ["123456789"], - "PROV_GROUP_NPI": ["1234567890"], - "PROV_GROUP_NAME_FULL": ["Main Hospital"], - "PROV_OTHER_TIN": ["123456789|987654321"], - "PROV_OTHER_NPI": ["1234567890|0987654321"], - "PROV_OTHER_NAME_FULL": ["Main Hospital|Other Clinic"] - }) - - expected_df1 = pd.DataFrame({ - "PROV_GROUP_TIN": ["123456789"], - "PROV_GROUP_NPI": ["1234567890"], - "PROV_GROUP_NAME_FULL": ["Main Hospital"], - "PROV_OTHER_TIN": ["987654321"], - "PROV_OTHER_NPI": ["0987654321"], - "PROV_OTHER_NAME_FULL": ["Other Clinic"] - }) - + input_df1 = pd.DataFrame( + { + "PROV_GROUP_TIN": ["123456789"], + "PROV_GROUP_NPI": ["1234567890"], + "PROV_GROUP_NAME_FULL": ["Main Hospital"], + "PROV_OTHER_TIN": ["123456789|987654321"], + "PROV_OTHER_NPI": ["1234567890|0987654321"], + "PROV_OTHER_NAME_FULL": ["Main Hospital|Other Clinic"], + } + ) + + expected_df1 = pd.DataFrame( + { + "PROV_GROUP_TIN": ["123456789"], + "PROV_GROUP_NPI": ["1234567890"], + "PROV_GROUP_NAME_FULL": ["Main Hospital"], + "PROV_OTHER_TIN": ["987654321"], + "PROV_OTHER_NPI": ["0987654321"], + "PROV_OTHER_NAME_FULL": ["Other Clinic"], + } + ) + result_df1 = deduplicate_provider_columns(input_df1) pd.testing.assert_frame_equal(result_df1, expected_df1) - + # Test case 2: Internal deduplication (your original example) - input_df2 = pd.DataFrame({ - "PROV_GROUP_TIN": ["061798267"], - "PROV_GROUP_NPI": ["1111111111"], - "PROV_GROUP_NAME_FULL": ["Group Practice"], - "PROV_OTHER_TIN": ["061798267|061798267|UNKNOWN|UNKNOWN|061992277|061798267"], - "PROV_OTHER_NPI": ["1111111111|2222222222|2222222222|UNKNOWN"], - "PROV_OTHER_NAME_FULL": ["Group Practice|Other Practice|Other Practice|UNKNOWN"] - }) - - expected_df2 = pd.DataFrame({ - "PROV_GROUP_TIN": ["061798267"], - "PROV_GROUP_NPI": ["1111111111"], - "PROV_GROUP_NAME_FULL": ["Group Practice"], - "PROV_OTHER_TIN": ["061992277"], - "PROV_OTHER_NPI": ["2222222222"], - "PROV_OTHER_NAME_FULL": ["Other Practice"] - }) - + input_df2 = pd.DataFrame( + { + "PROV_GROUP_TIN": ["061798267"], + "PROV_GROUP_NPI": ["1111111111"], + "PROV_GROUP_NAME_FULL": ["Group Practice"], + "PROV_OTHER_TIN": [ + "061798267|061798267|UNKNOWN|UNKNOWN|061992277|061798267" + ], + "PROV_OTHER_NPI": ["1111111111|2222222222|2222222222|UNKNOWN"], + "PROV_OTHER_NAME_FULL": [ + "Group Practice|Other Practice|Other Practice|UNKNOWN" + ], + } + ) + + expected_df2 = pd.DataFrame( + { + "PROV_GROUP_TIN": ["061798267"], + "PROV_GROUP_NPI": ["1111111111"], + "PROV_GROUP_NAME_FULL": ["Group Practice"], + "PROV_OTHER_TIN": ["061992277"], + "PROV_OTHER_NPI": ["2222222222"], + "PROV_OTHER_NAME_FULL": ["Other Practice"], + } + ) + result_df2 = deduplicate_provider_columns(input_df2) pd.testing.assert_frame_equal(result_df2, expected_df2) - + # Test case 3: Empty OTHER fields after deduplication - input_df3 = pd.DataFrame({ - "PROV_GROUP_TIN": ["123456789"], - "PROV_GROUP_NPI": ["1234567890"], - "PROV_GROUP_NAME_FULL": ["Main Hospital"], - "PROV_OTHER_TIN": ["123456789|123456789|UNKNOWN"], - "PROV_OTHER_NPI": ["1234567890|UNKNOWN|UNKNOWN"], - "PROV_OTHER_NAME_FULL": ["Main Hospital|UNKNOWN"] - }) - - expected_df3 = pd.DataFrame({ - "PROV_GROUP_TIN": ["123456789"], - "PROV_GROUP_NPI": ["1234567890"], - "PROV_GROUP_NAME_FULL": ["Main Hospital"], - "PROV_OTHER_TIN": [""], - "PROV_OTHER_NPI": [""], - "PROV_OTHER_NAME_FULL": [""] - }) - + input_df3 = pd.DataFrame( + { + "PROV_GROUP_TIN": ["123456789"], + "PROV_GROUP_NPI": ["1234567890"], + "PROV_GROUP_NAME_FULL": ["Main Hospital"], + "PROV_OTHER_TIN": ["123456789|123456789|UNKNOWN"], + "PROV_OTHER_NPI": ["1234567890|UNKNOWN|UNKNOWN"], + "PROV_OTHER_NAME_FULL": ["Main Hospital|UNKNOWN"], + } + ) + + expected_df3 = pd.DataFrame( + { + "PROV_GROUP_TIN": ["123456789"], + "PROV_GROUP_NPI": ["1234567890"], + "PROV_GROUP_NAME_FULL": ["Main Hospital"], + "PROV_OTHER_TIN": [""], + "PROV_OTHER_NPI": [""], + "PROV_OTHER_NAME_FULL": [""], + } + ) + result_df3 = deduplicate_provider_columns(input_df3) pd.testing.assert_frame_equal(result_df3, expected_df3) - + # Test case 4: Multiple rows - input_df4 = pd.DataFrame({ - "PROV_GROUP_TIN": ["111111111", "222222222"], - "PROV_GROUP_NPI": ["1111111111", "2222222222"], - "PROV_GROUP_NAME_FULL": ["Hospital A", "Hospital B"], - "PROV_OTHER_TIN": ["111111111|333333333", "444444444|222222222|444444444"], - "PROV_OTHER_NPI": ["3333333333|1111111111", "4444444444|2222222222"], - "PROV_OTHER_NAME_FULL": ["Clinic C|Hospital A", "Clinic D|Hospital B"] - }) - - expected_df4 = pd.DataFrame({ - "PROV_GROUP_TIN": ["111111111", "222222222"], - "PROV_GROUP_NPI": ["1111111111", "2222222222"], - "PROV_GROUP_NAME_FULL": ["Hospital A", "Hospital B"], - "PROV_OTHER_TIN": ["333333333", "444444444"], - "PROV_OTHER_NPI": ["3333333333", "4444444444"], - "PROV_OTHER_NAME_FULL": ["Clinic C", "Clinic D"] - }) - + input_df4 = pd.DataFrame( + { + "PROV_GROUP_TIN": ["111111111", "222222222"], + "PROV_GROUP_NPI": ["1111111111", "2222222222"], + "PROV_GROUP_NAME_FULL": ["Hospital A", "Hospital B"], + "PROV_OTHER_TIN": [ + "111111111|333333333", + "444444444|222222222|444444444", + ], + "PROV_OTHER_NPI": ["3333333333|1111111111", "4444444444|2222222222"], + "PROV_OTHER_NAME_FULL": ["Clinic C|Hospital A", "Clinic D|Hospital B"], + } + ) + + expected_df4 = pd.DataFrame( + { + "PROV_GROUP_TIN": ["111111111", "222222222"], + "PROV_GROUP_NPI": ["1111111111", "2222222222"], + "PROV_GROUP_NAME_FULL": ["Hospital A", "Hospital B"], + "PROV_OTHER_TIN": ["333333333", "444444444"], + "PROV_OTHER_NPI": ["3333333333", "4444444444"], + "PROV_OTHER_NAME_FULL": ["Clinic C", "Clinic D"], + } + ) + result_df4 = deduplicate_provider_columns(input_df4) pd.testing.assert_frame_equal(result_df4, expected_df4) - + # Test case 5: Missing columns - should return unchanged - input_df5 = pd.DataFrame({ - "PROV_GROUP_TIN": ["123456789"], - "OTHER_COLUMN": ["value1"] - }) - + input_df5 = pd.DataFrame( + {"PROV_GROUP_TIN": ["123456789"], "OTHER_COLUMN": ["value1"]} + ) + result_df5 = deduplicate_provider_columns(input_df5) pd.testing.assert_frame_equal(result_df5, input_df5) - + # Test case 6: Empty DataFrame empty_df = pd.DataFrame() result_empty_df = deduplicate_provider_columns(empty_df) pd.testing.assert_frame_equal(empty_df, result_empty_df) - + # Test case 7: Empty OTHER fields (already empty strings) - input_df7 = pd.DataFrame({ - "PROV_GROUP_TIN": ["123456789"], - "PROV_GROUP_NPI": ["1234567890"], - "PROV_GROUP_NAME_FULL": ["Main Hospital"], - "PROV_OTHER_TIN": [""], - "PROV_OTHER_NPI": [""], - "PROV_OTHER_NAME_FULL": [""] - }) - + input_df7 = pd.DataFrame( + { + "PROV_GROUP_TIN": ["123456789"], + "PROV_GROUP_NPI": ["1234567890"], + "PROV_GROUP_NAME_FULL": ["Main Hospital"], + "PROV_OTHER_TIN": [""], + "PROV_OTHER_NPI": [""], + "PROV_OTHER_NAME_FULL": [""], + } + ) + result_df7 = deduplicate_provider_columns(input_df7) pd.testing.assert_frame_equal(result_df7, input_df7) + if __name__ == "__main__": unittest.main() - \ No newline at end of file diff --git a/fieldExtraction/tests/test_investment_prompts.py b/fieldExtraction/tests/test_investment_prompts.py index d02b699..44d41ac 100644 --- a/fieldExtraction/tests/test_investment_prompts.py +++ b/fieldExtraction/tests/test_investment_prompts.py @@ -1,27 +1,44 @@ import pytest -from src.prompts.investment_prompts import DERIVED_TERM_DATE_PROMPT -from src.prompts.investment_prompts import PIPE_FORMAT_INSTRUCTIONS + +from src.prompts.investment_prompts import (DERIVED_TERM_DATE_PROMPT, + PIPE_FORMAT_INSTRUCTIONS) + def test_derived_term_date_prompt_with_all_params(): effective_date = "January 1, 2024" termination_information = "The contract terminates after 3 years." expected_output = f"The following are excerpts from a legal contract related to effective and termination dates. Determine the initial termination date of the contract, excluding any renewals or extensions.\nIf termination information comes in as a duration, derive the termination date exclusive of the final day. For example, if effective date is 2023/02/01 and termination information is 'two years', termination date would be 2025/01/31 and not 2025/02/01. If effective date is 2023/02/01 and termination information is 'year-to-year', termination date would be 2024/01/31.\nIf the termination information comes in as a date, return that date. For example, if effective date is 2023/02/01 and termination information is 2024/07/01, return 2024/07/01.\nEffective Date: January 1, 2024\nTermination Information: The contract terminates after 3 years.\nOnly provide the first termination date, ignoring any automatic or optional renewals. Provide the date in YYYY/MM/DD format only. \n{PIPE_FORMAT_INSTRUCTIONS}" - assert DERIVED_TERM_DATE_PROMPT(effective_date, termination_information) == expected_output + assert ( + DERIVED_TERM_DATE_PROMPT(effective_date, termination_information) + == expected_output + ) + def test_derived_term_date_prompt_with_only_effective_date(): effective_date = "January 1, 2024" termination_information = None expected_output = f"The following are excerpts from a legal contract related to effective and termination dates. Determine the initial termination date of the contract, excluding any renewals or extensions.\nIf termination information comes in as a duration, derive the termination date exclusive of the final day. For example, if effective date is 2023/02/01 and termination information is 'two years', termination date would be 2025/01/31 and not 2025/02/01. If effective date is 2023/02/01 and termination information is 'year-to-year', termination date would be 2024/01/31.\nIf the termination information comes in as a date, return that date. For example, if effective date is 2023/02/01 and termination information is 2024/07/01, return 2024/07/01.\nEffective Date: January 1, 2024\nTermination Information: None\nOnly provide the first termination date, ignoring any automatic or optional renewals. Provide the date in YYYY/MM/DD format only. \n{PIPE_FORMAT_INSTRUCTIONS}" - assert DERIVED_TERM_DATE_PROMPT(effective_date, termination_information) == expected_output + assert ( + DERIVED_TERM_DATE_PROMPT(effective_date, termination_information) + == expected_output + ) + def test_derived_term_date_prompt_with_only_termination_information(): effective_date = None termination_information = "The contract terminates after 3 years." expected_output = f"The following are excerpts from a legal contract related to effective and termination dates. Determine the initial termination date of the contract, excluding any renewals or extensions.\nIf termination information comes in as a duration, derive the termination date exclusive of the final day. For example, if effective date is 2023/02/01 and termination information is 'two years', termination date would be 2025/01/31 and not 2025/02/01. If effective date is 2023/02/01 and termination information is 'year-to-year', termination date would be 2024/01/31.\nIf the termination information comes in as a date, return that date. For example, if effective date is 2023/02/01 and termination information is 2024/07/01, return 2024/07/01.\nEffective Date: None\nTermination Information: The contract terminates after 3 years.\nOnly provide the first termination date, ignoring any automatic or optional renewals. Provide the date in YYYY/MM/DD format only. \n{PIPE_FORMAT_INSTRUCTIONS}" - assert DERIVED_TERM_DATE_PROMPT(effective_date, termination_information) == expected_output + assert ( + DERIVED_TERM_DATE_PROMPT(effective_date, termination_information) + == expected_output + ) + def test_derived_term_date_prompt_with_no_params(): effective_date = None termination_information = None expected_output = f"The following are excerpts from a legal contract related to effective and termination dates. Determine the initial termination date of the contract, excluding any renewals or extensions.\nIf termination information comes in as a duration, derive the termination date exclusive of the final day. For example, if effective date is 2023/02/01 and termination information is 'two years', termination date would be 2025/01/31 and not 2025/02/01. If effective date is 2023/02/01 and termination information is 'year-to-year', termination date would be 2024/01/31.\nIf the termination information comes in as a date, return that date. For example, if effective date is 2023/02/01 and termination information is 2024/07/01, return 2024/07/01.\nEffective Date: None\nTermination Information: None\nOnly provide the first termination date, ignoring any automatic or optional renewals. Provide the date in YYYY/MM/DD format only. \n{PIPE_FORMAT_INSTRUCTIONS}" - assert DERIVED_TERM_DATE_PROMPT(effective_date, termination_information) == expected_output \ No newline at end of file + assert ( + DERIVED_TERM_DATE_PROMPT(effective_date, termination_information) + == expected_output + ) diff --git a/fieldExtraction/tests/io_utils_test.py b/fieldExtraction/tests/test_io_utils.py similarity index 73% rename from fieldExtraction/tests/io_utils_test.py rename to fieldExtraction/tests/test_io_utils.py index 53a3ec8..415ffe1 100644 --- a/fieldExtraction/tests/io_utils_test.py +++ b/fieldExtraction/tests/test_io_utils.py @@ -1,18 +1,13 @@ -import pytest from io import StringIO -import pandas as pd -from src.utils.io_utils import ( - read_local, - list_s3_files, - read_s3, - read_s3_csv, - read_input, - read_input_csv, - filter_already_processed, - remove_txt_extension, - read_xlsb, -) +import pandas as pd +import pytest + +from src.utils.io_utils import (filter_already_processed, list_s3_files, + read_input, read_input_csv, read_local, + read_s3, read_s3_csv, read_xlsb, + remove_txt_extension) + class TestIOUtils: # Fixtures for reusable test data @@ -36,9 +31,7 @@ class TestIOUtils: @pytest.fixture def mock_s3_csv_response(self): - return { - "Body": StringIO("col1,col2\n1,2\n3,4") - } + return {"Body": StringIO("col1,col2\n1,2\n3,4")} # Tests for read_local def test_read_local_txt_utf8(self, mock_txt_data, mocker): @@ -61,7 +54,9 @@ class TestIOUtils: def test_list_s3_files(self, mocker): # Test case 1: Empty S3 bucket - mocker.patch("src.config.S3_CLIENT.list_objects_v2", return_value={"Contents": []}) + mocker.patch( + "src.config.S3_CLIENT.list_objects_v2", return_value={"Contents": []} + ) result = list_s3_files() assert result == [] @@ -73,7 +68,9 @@ class TestIOUtils: {"Key": "file3.xlsx"}, ] } - mocker.patch("src.config.S3_CLIENT.list_objects_v2", return_value=mock_s3_response) + mocker.patch( + "src.config.S3_CLIENT.list_objects_v2", return_value=mock_s3_response + ) result = list_s3_files() assert result == ["file1.txt", "file2.csv", "file3.xlsx"] @@ -85,7 +82,9 @@ class TestIOUtils: "IsTruncated": False, } mock_s3_client.get_object.return_value = { - "Body": mocker.MagicMock(read=mocker.MagicMock(return_value=mock_txt_data.encode("utf-8"))) + "Body": mocker.MagicMock( + read=mocker.MagicMock(return_value=mock_txt_data.encode("utf-8")) + ) } mocker.patch("src.config.S3_CLIENT", mock_s3_client) result = read_s3() @@ -99,7 +98,9 @@ class TestIOUtils: } # Mock get_object to return a response with a bytes object mock_s3_client.get_object.return_value = { - "Body": mocker.MagicMock(read=mocker.MagicMock(return_value=mock_csv_data.encode("utf-8"))) + "Body": mocker.MagicMock( + read=mocker.MagicMock(return_value=mock_csv_data.encode("utf-8")) + ) } mocker.patch("src.config.S3_CLIENT", mock_s3_client) result = read_s3_csv("s3://bucket/file2.csv") @@ -107,10 +108,14 @@ class TestIOUtils: pd.testing.assert_frame_equal(result, expected_df) # Verify the S3 client was called correctly - mock_s3_client.list_objects_v2.assert_called_once_with(Bucket="bucket", Prefix="file2.csv") - mock_s3_client.get_object.assert_called_once_with(Bucket="bucket", Key="file2.csv") - - # Tests for read_input + mock_s3_client.list_objects_v2.assert_called_once_with( + Bucket="bucket", Prefix="file2.csv" + ) + mock_s3_client.get_object.assert_called_once_with( + Bucket="bucket", Key="file2.csv" + ) + + # Tests for read_input def test_read_input_local(self, mock_txt_data, mocker): mocker.patch("os.listdir", return_value=["file1.txt"]) mocker.patch("src.utils.io_utils.read_local", return_value=mock_txt_data) @@ -119,7 +124,9 @@ class TestIOUtils: assert result == {"file1.txt": mock_txt_data} def test_read_input_s3(self, mock_txt_data, mocker): - mocker.patch("src.utils.io_utils.read_s3", return_value={"file1.txt": mock_txt_data}) + mocker.patch( + "src.utils.io_utils.read_s3", return_value={"file1.txt": mock_txt_data} + ) mocker.patch("src.config.READ_MODE", "s3") result = read_input() assert result == {"file1.txt": mock_txt_data} @@ -127,16 +134,18 @@ class TestIOUtils: # Tests for read_input_csv def test_read_input_csv_local(self, mock_csv_data, mocker): mock_df = pd.read_csv(StringIO(mock_csv_data)) - mock_read_local = mocker.patch("src.utils.io_utils.read_local", return_value=mock_df) + mock_read_local = mocker.patch( + "src.utils.io_utils.read_local", return_value=mock_df + ) mocker.patch("src.config.DF_READ_MODE", "local") mocker.patch("src.config.AC_DF", "ac.csv") mocker.patch("src.config.B_DF", "b.csv") - + ac_df, b_df = read_input_csv() - + pd.testing.assert_frame_equal(ac_df, mock_df) pd.testing.assert_frame_equal(b_df, mock_df) - + # Verify read_local was called with the correct paths mock_read_local.assert_any_call("ac.csv") mock_read_local.assert_any_call("b.csv") @@ -146,7 +155,9 @@ class TestIOUtils: mock_df = pd.read_csv(StringIO(mock_csv_data)) # Mock read_s3_csv to return the mock DataFrame - mock_read_s3_csv = mocker.patch("src.utils.io_utils.read_s3_csv", return_value=mock_df) + mock_read_s3_csv = mocker.patch( + "src.utils.io_utils.read_s3_csv", return_value=mock_df + ) # Set the src.config to use S3 mode mocker.patch("src.config.DF_READ_MODE", "s3") @@ -169,7 +180,10 @@ class TestIOUtils: # Mock src.config.BATCH_ID mock_batch_id = "mock_batch_123" mocker.patch("src.config.BATCH_ID", mock_batch_id) - mock_get_processed_files = mocker.patch("src.tracking.tracking_utils.get_processed_files", return_value=(set(["file1.txt"]), set([]))) + mock_get_processed_files = mocker.patch( + "src.tracking.tracking_utils.get_processed_files", + return_value=(set(["file1.txt"]), set([])), + ) mock_write_to_s3 = mocker.patch("src.config.WRITE_TO_S3", True) input_dict = {"file1.txt": "content1", "file2.txt": "content2"} input_dict_b = {"file1.txt": "content1", "file2.txt": "content2"} @@ -179,7 +193,7 @@ class TestIOUtils: # Verify src.tracking.tracking_utils.get_processed_files was called mock_get_processed_files.assert_called_once_with(mock_batch_id) - + def test_filter_already_processed_local(self, mocker): # Case 2: WRITE_TO_S3 is False (local mode) mocker.patch("src.config.WRITE_TO_S3", False) @@ -204,15 +218,29 @@ class TestIOUtils: mocker.patch("os.path.join", side_effect=lambda *args: "/".join(args)) # Input dictionaries - input_dict = {"folder1.txt": "content1", "folder2.txt": "content2", "folder3.txt": "content3"} - input_dict_b = {"folder1.txt": "content1", "folder2.txt": "content2", "folder3.txt": "content3"} + input_dict = { + "folder1.txt": "content1", + "folder2.txt": "content2", + "folder3.txt": "content3", + } + input_dict_b = { + "folder1.txt": "content1", + "folder2.txt": "content2", + "folder3.txt": "content3", + } # Call the function under test result_ac, result_b = filter_already_processed(input_dict, input_dict_b) # Verify the results - assert result_ac == {"folder2.txt": "content2", "folder3.txt": "content3"} # folder1.txt is already processed - assert result_b == {"folder1.txt": "content1", "folder3.txt": "content3"} # folder2.txt is already processed + assert result_ac == { + "folder2.txt": "content2", + "folder3.txt": "content3", + } # folder1.txt is already processed + assert result_b == { + "folder1.txt": "content1", + "folder3.txt": "content3", + } # folder2.txt is already processed # Tests for remove_txt_extension def test_remove_txt_extension(self): @@ -221,20 +249,25 @@ class TestIOUtils: assert remove_txt_extension(123) == 123 assert remove_txt_extension(None) == None - def test_read_xlsb(self,mocker): + def test_read_xlsb(self, mocker): # Mock the sheet mock_sheet = mocker.MagicMock() - + # Mock the rows returned by the sheet # Use a list of rows, where each row is a list of MagicMock objects with a 'v' attribute mock_rows = [ - [mocker.MagicMock(v='Header1'), mocker.MagicMock(v='Header2')], # Header row - [mocker.MagicMock(v='Data1'), mocker.MagicMock(v='Data2')], # Data row 1 - [mocker.MagicMock(v='Data3'), mocker.MagicMock(v='Data4')], # Data row 2 + [ + mocker.MagicMock(v="Header1"), + mocker.MagicMock(v="Header2"), + ], # Header row + [mocker.MagicMock(v="Data1"), mocker.MagicMock(v="Data2")], # Data row 1 + [mocker.MagicMock(v="Data3"), mocker.MagicMock(v="Data4")], # Data row 2 ] - + # Mock sheet.rows to return an iterable (e.g., a generator) - mock_sheet.rows.return_value = iter(mock_rows) # Use return_value for the method + mock_sheet.rows.return_value = iter( + mock_rows + ) # Use return_value for the method # Mock the workbook mock_workbook = mocker.MagicMock() @@ -242,24 +275,24 @@ class TestIOUtils: mock_workbook.get_sheet.return_value.__enter__.return_value = mock_sheet # Mock open_workbook to return the mocked workbook - mock_open_workbook = mocker.patch('src.utils.io_utils.open_workbook') + mock_open_workbook = mocker.patch("src.utils.io_utils.open_workbook") mock_open_workbook.return_value.__enter__.return_value = mock_workbook # Call the function - result = read_xlsb('dummy_path.xlsb') + result = read_xlsb("dummy_path.xlsb") # Expected DataFrame expected_df = pd.DataFrame( [ - ['Data1', 'Data2'], # Data row 1 - ['Data3', 'Data4'], # Data row 2 + ["Data1", "Data2"], # Data row 1 + ["Data3", "Data4"], # Data row 2 ], - columns=['Header1', 'Header2'] # Header row + columns=["Header1", "Header2"], # Header row ) # Assert that the result matches the expected DataFrame pd.testing.assert_frame_equal(result, expected_df) # Verify that the mock was called correctly - mock_open_workbook.assert_called_once_with('dummy_path.xlsb') - mock_workbook.get_sheet.assert_called_once() \ No newline at end of file + mock_open_workbook.assert_called_once_with("dummy_path.xlsb") + mock_workbook.get_sheet.assert_called_once() diff --git a/fieldExtraction/tests/llm_utils_test.py b/fieldExtraction/tests/test_llm_utils.py similarity index 85% rename from fieldExtraction/tests/llm_utils_test.py rename to fieldExtraction/tests/test_llm_utils.py index 4365eb2..a3ff622 100644 --- a/fieldExtraction/tests/llm_utils_test.py +++ b/fieldExtraction/tests/test_llm_utils.py @@ -1,11 +1,13 @@ -import unittest -from unittest.mock import patch, mock_open -import hashlib import csv +import hashlib +import unittest from collections import OrderedDict +from unittest.mock import mock_open, patch + import src.utils.llm_utils as llm_utils from src import config + class TestLLMUtils(unittest.TestCase): def setUp(self): self.filename = "test_file.txt" @@ -14,7 +16,14 @@ class TestLLMUtils(unittest.TestCase): self.model_type = "test_model" self.input_cost_per_1k = 0.01 self.output_cost_per_1k = 0.02 - self.mock_stats = {self.filename: {"requests": 0, "tokens_sent": 0, "tokens_received": 0, "total_time": 0}} + self.mock_stats = { + self.filename: { + "requests": 0, + "tokens_sent": 0, + "tokens_received": 0, + "total_time": 0, + } + } config.MODEL_STATS = self.mock_stats config.GLOBAL_STATS = {} @@ -36,7 +45,12 @@ class TestLLMUtils(unittest.TestCase): def test_create_cost_log_entry(self): entry = llm_utils.create_cost_log_entry( - self.filename, "caller_func", self.prompt, self.response, self.input_cost_per_1k, self.output_cost_per_1k + self.filename, + "caller_func", + self.prompt, + self.response, + self.input_cost_per_1k, + self.output_cost_per_1k, ) self.assertEqual(entry["Filename"], self.filename) self.assertEqual(entry["Caller Function"], "caller_func") @@ -54,11 +68,12 @@ class TestLLMUtils(unittest.TestCase): def test_invoke_claude_local_mode(self, mock_csv_writer, mock_open_file): config.RUN_MODE = "local" model_id = config.MODEL_ID_CLAUDE2 - + with patch("src.utils.llm_utils.local_claude_2", return_value=self.response): response = llm_utils.invoke_claude(self.prompt, model_id, self.filename) self.assertEqual(response, self.response) mock_csv_writer.assert_called() + if __name__ == "__main__": unittest.main() diff --git a/fieldExtraction/tests/one_to_n_test.py b/fieldExtraction/tests/test_one_to_n.py similarity index 56% rename from fieldExtraction/tests/one_to_n_test.py rename to fieldExtraction/tests/test_one_to_n.py index c227bae..c152dee 100644 --- a/fieldExtraction/tests/one_to_n_test.py +++ b/fieldExtraction/tests/test_one_to_n.py @@ -1,69 +1,69 @@ - import unittest -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch import src.investment.one_to_n_funcs as one_to_n_funcs +from constants.constants import Constants class TestOneToN(unittest.TestCase): def setUp(self): self.exhibit_text = "Sample exhibit text with some content" self.filename = "test_file.pdf" + self.constants = Constants() - @patch('src.utils.llm_utils.invoke_claude') + @patch("src.utils.llm_utils.invoke_claude") def test_get_reimbursement_primary(self, mock_invoke_claude): # Setup mock_fields = MagicMock() mock_fields.get_prompt_dict.return_value = {"field1": "prompt1"} mock_invoke_claude.return_value = '{"answer1": "value1"}' - + # Execute result = one_to_n_funcs.get_reimbursement_primary( mock_fields, self.exhibit_text, self.filename ) - + # Assert self.assertEqual(result, {"answer1": "value1"}) mock_invoke_claude.assert_called_once() - @patch('src.utils.llm_utils.invoke_claude') + @patch("src.utils.llm_utils.invoke_claude") def test_get_special_cases(self, mock_invoke_claude): # Setup - test_answers = [{ - "SERVICE_TERM": "service1", - "REIMB_TERM": "reimbursement1" - }] + test_answers = [{"SERVICE_TERM": "service1", "REIMB_TERM": "reimbursement1"}] mock_invoke_claude.return_value = "|N/A|" - + # Execute - result = one_to_n_funcs.get_special_cases(test_answers, self.filename) - + result = one_to_n_funcs.get_special_cases( + test_answers, + self.constants.VALID_CARVEOUTS, + self.constants.VALID_SPECIAL, + self.filename, + ) + # Assert self.assertEqual(len(result), 1) self.assertEqual(result[0]["CARVEOUT_CD"], "N/A") self.assertEqual(result[0]["CARVEOUT_IND"], "N") self.assertEqual(result[0]["DEFAULT_IND"], "N") - @patch('src.utils.llm_utils.invoke_claude') + @patch("src.utils.llm_utils.invoke_claude") def test_methodology_breakout_single_row(self, mock_invoke_claude): # Setup - test_answer = { - "SERVICE_TERM": "service1", - "REIMB_TERM": "Fee Schedule" - } + test_answer = {"SERVICE_TERM": "service1", "REIMB_TERM": "Fee Schedule"} mock_invoke_claude.side_effect = [ '{"AARETE_DERIVED_REIMB_METHOD": "Fee Schedule", "REIMB_PCT_RATE": "80%"}', - '{"FEE_SCHEDULE": "TEST", "AARETE_DERIVED_FEE_SCHEDULE": "TEST", "AARETE_DERIVED_FEE_SCHEDULE_VERSION": "1.0"}' + '{"FEE_SCHEDULE": "TEST", "AARETE_DERIVED_FEE_SCHEDULE": "TEST", "AARETE_DERIVED_FEE_SCHEDULE_VERSION": "1.0"}', ] - + # Execute result = one_to_n_funcs.methodology_breakout_single_row( test_answer, {"methodology": "questions"}, {"fs": "questions"}, - self.filename + self.filename, ) - + # Assert self.assertEqual(len(result), 1) self.assertEqual(result[0]["AARETE_DERIVED_REIMB_METHOD"], "Fee Schedule") @@ -72,75 +72,102 @@ class TestOneToN(unittest.TestCase): def test_combine_one_to_n_answers(self): # Setup exhibit_level = {"EXHIBIT_TITLE": "Test Exhibit"} - reimbursement_level = [ - {"REIMB_TERM": "term1"}, - {"REIMB_TERM": "term2"} - ] + reimbursement_level = [{"REIMB_TERM": "term1"}, {"REIMB_TERM": "term2"}] tin_npi = {"TIN": "123456789"} - + # Execute result = one_to_n_funcs.combine_one_to_n_answers( exhibit_level, reimbursement_level, tin_npi ) - + # Assert self.assertEqual(len(result), 2) self.assertTrue(all("EXHIBIT_TITLE" in d for d in result)) self.assertTrue(all("TIN" in d for d in result)) self.assertTrue(all("REIMB_TERM" in d for d in result)) - @patch('src.investment.one_to_n_funcs.get_reimbursement_primary') - @patch('src.investment.one_to_n_funcs.get_special_cases') - @patch('src.investment.one_to_n_funcs.validate_reimbursements_for_llm') - def test_reimbursement_level(self, mock_validate_llm, mock_special_cases, mock_reimbursement_primary): + @patch("src.investment.one_to_n_funcs.get_reimbursement_primary") + @patch("src.investment.one_to_n_funcs.get_special_cases") + @patch("src.investment.one_to_n_funcs.validate_reimbursements_for_llm") + def test_reimbursement_level( + self, mock_validate_llm, mock_special_cases, mock_reimbursement_primary + ): # Setup - mock_reimbursement_primary.return_value = [{"SERVICE_TERM": "Test Service", "REIMB_TERM": "term1 paid at rate"}] + mock_reimbursement_primary.return_value = [ + {"SERVICE_TERM": "Test Service", "REIMB_TERM": "term1 paid at rate"} + ] mock_validate_llm.return_value = True - mock_special_cases.return_value = [{"SERVICE_TERM": "Test Service", "REIMB_TERM": "term1 paid at rate", "CARVEOUT_IND": "N"}] + mock_special_cases.return_value = [ + { + "SERVICE_TERM": "Test Service", + "REIMB_TERM": "term1 paid at rate", + "CARVEOUT_IND": "N", + } + ] mock_fields = MagicMock() exhibit_page = "1" seen_pairs = set() exhibit_lesser_of = "N/A" # Simulate no lesser of statement rate_escalator_statement = "N/A" # Simulate no rate escalator statement - + # Execute result = one_to_n_funcs.reimbursement_level( - self.exhibit_text, self.filename, mock_fields, exhibit_page, seen_pairs, exhibit_lesser_of + self.exhibit_text, + self.filename, + mock_fields, + exhibit_page, + seen_pairs, + exhibit_lesser_of, + self.constants, ) - + # Assert self.assertEqual(len(result), 1) mock_reimbursement_primary.assert_called_once() mock_special_cases.assert_called_once() - @patch('src.investment.one_to_n_funcs.get_reimbursement_primary') - @patch('src.investment.one_to_n_funcs.validate_reimbursements_for_llm') - def test_reimbursement_level_deduplication(self, mock_validate_llm, mock_reimbursement_primary): + @patch("src.investment.one_to_n_funcs.get_reimbursement_primary") + @patch("src.investment.one_to_n_funcs.validate_reimbursements_for_llm") + def test_reimbursement_level_deduplication( + self, mock_validate_llm, mock_reimbursement_primary + ): """Test that duplicate service-reimbursement pairs are properly filtered""" # Setup - simulate getting duplicate pairs mock_reimbursement_primary.return_value = [ {"SERVICE_TERM": "Service A", "REIMB_TERM": "paid at 80% of fee schedule"}, {"SERVICE_TERM": "Service B", "REIMB_TERM": "$50 per visit"}, - {"SERVICE_TERM": "Service A", "REIMB_TERM": "paid at 80% of fee schedule"} # Duplicate + { + "SERVICE_TERM": "Service A", + "REIMB_TERM": "paid at 80% of fee schedule", + }, # Duplicate ] mock_validate_llm.return_value = True - + mock_fields = MagicMock() exhibit_page = "1" seen_pairs = set() - - with patch('src.investment.one_to_n_funcs.get_special_cases') as mock_special_cases: - - # Mock the downstream functions - mock_special_cases.side_effect = lambda x, _: x # Pass through - + + with patch( + "src.investment.one_to_n_funcs.get_special_cases" + ) as mock_special_cases: + + # Fix: Update lambda to accept the correct number of arguments + mock_special_cases.side_effect = ( + lambda x, y, *args: x + ) # Pass through, accept any extra args + # Execute result = one_to_n_funcs.reimbursement_level( - self.exhibit_text, self.filename, mock_fields, - exhibit_page, seen_pairs, exhibit_lesser_of="N/A" + self.exhibit_text, + self.filename, + mock_fields, + exhibit_page, + seen_pairs, + exhibit_lesser_of="N/A", + constants=self.constants, ) - + # Assert - should only have 2 unique pairs self.assertEqual(len(result), 2) # Verify seen_pairs was updated @@ -148,67 +175,105 @@ class TestOneToN(unittest.TestCase): self.assertIn(("1", "Service A", "paid at 80% of fee schedule"), seen_pairs) self.assertIn(("1", "Service B", "$50 per visit"), seen_pairs) - @patch('src.investment.one_to_n_funcs.get_reimbursement_primary') - @patch('src.investment.one_to_n_funcs.validate_reimbursements_for_llm') - def test_reimbursement_level_cross_exhibit_deduplication(self, mock_validate_llm, mock_reimbursement_primary): + @patch("src.investment.one_to_n_funcs.get_reimbursement_primary") + @patch("src.investment.one_to_n_funcs.validate_reimbursements_for_llm") + def test_reimbursement_level_cross_exhibit_deduplication( + self, mock_validate_llm, mock_reimbursement_primary + ): """Test that pairs seen in previous exhibits are filtered out""" # Setup - pre-populate seen_pairs as if previous exhibit processed - seen_pairs = {("2", "Service A", "paid at 80% of fee schedule"),} + seen_pairs = { + ("2", "Service A", "paid at 80% of fee schedule"), + } mock_validate_llm.return_value = True - + # Current exhibit has same pair plus new one mock_reimbursement_primary.return_value = [ - {"SERVICE_TERM": "Service A", "REIMB_TERM": "paid at 80% of fee schedule"}, # Should be filtered - {"SERVICE_TERM": "Service B", "REIMB_TERM": "$50 per visit"} # Should be kept + { + "SERVICE_TERM": "Service A", + "REIMB_TERM": "paid at 80% of fee schedule", + }, # Should be filtered + { + "SERVICE_TERM": "Service B", + "REIMB_TERM": "$50 per visit", + }, # Should be kept ] - + mock_fields = MagicMock() exhibit_page = "2" - - with patch('src.investment.one_to_n_funcs.get_special_cases') as mock_special_cases: - - mock_special_cases.side_effect = lambda x, _: x - + + with patch( + "src.investment.one_to_n_funcs.get_special_cases" + ) as mock_special_cases: + + mock_special_cases.side_effect = ( + lambda x, y, *args: x + ) # Pass through, accept any extra args + # Execute result = one_to_n_funcs.reimbursement_level( - self.exhibit_text, self.filename, mock_fields, - exhibit_page, seen_pairs, exhibit_lesser_of="N/A" + self.exhibit_text, + self.filename, + mock_fields, + exhibit_page, + seen_pairs, + exhibit_lesser_of="N/A", + constants=self.constants, ) - + # Assert - should only process the new pair self.assertEqual(len(result), 1) self.assertEqual(result[0]["SERVICE_TERM"], "Service B") # Verify both pairs are now in seen_pairs self.assertEqual(len(seen_pairs), 2) - @patch('src.investment.one_to_n_funcs.get_reimbursement_primary') - @patch('src.investment.one_to_n_funcs.validate_reimbursements_for_llm') - def test_reimbursement_level_cross_exhibit_deduplication_same_base_page(self, mock_validate_llm, mock_reimbursement_primary): + @patch("src.investment.one_to_n_funcs.get_reimbursement_primary") + @patch("src.investment.one_to_n_funcs.validate_reimbursements_for_llm") + def test_reimbursement_level_cross_exhibit_deduplication_same_base_page( + self, mock_validate_llm, mock_reimbursement_primary + ): """Test that pairs seen in previous exhibits within same base page are filtered out""" # Setup - pre-populate seen_pairs as if previous exhibit in same base page processed - seen_pairs = {("2", "Service A", "paid at 80% of fee schedule"),} + seen_pairs = { + ("2", "Service A", "paid at 80% of fee schedule"), + } mock_validate_llm.return_value = True - + # Current exhibit (2.1) has same pair plus new one - same base page mock_reimbursement_primary.return_value = [ - {"SERVICE_TERM": "Service A", "REIMB_TERM": "paid at 80% of fee schedule"}, # Should be filtered (duplicate in base page 2) - {"SERVICE_TERM": "Service B", "REIMB_TERM": "$50 per visit"} # Should be kept + { + "SERVICE_TERM": "Service A", + "REIMB_TERM": "paid at 80% of fee schedule", + }, # Should be filtered (duplicate in base page 2) + { + "SERVICE_TERM": "Service B", + "REIMB_TERM": "$50 per visit", + }, # Should be kept ] - + mock_fields = MagicMock() mock_dataset = {} exhibit_page = "2.1" # Same base page (2) as seen_pairs - - with patch('src.investment.one_to_n_funcs.get_special_cases') as mock_special_cases: - - mock_special_cases.side_effect = lambda x, _: x - + + with patch( + "src.investment.one_to_n_funcs.get_special_cases" + ) as mock_special_cases: + + mock_special_cases.side_effect = ( + lambda x, y, *args: x + ) # Pass through, accept any extra args + # Execute result = one_to_n_funcs.reimbursement_level( - self.exhibit_text, self.filename, mock_fields, - exhibit_page, seen_pairs, exhibit_lesser_of="N/A" + self.exhibit_text, + self.filename, + mock_fields, + exhibit_page, + seen_pairs, + exhibit_lesser_of="N/A", + constants=self.constants, ) - + # Assert - should only process the new pair (Service A filtered out) self.assertEqual(len(result), 1) self.assertEqual(result[0]["SERVICE_TERM"], "Service B") @@ -216,78 +281,103 @@ class TestOneToN(unittest.TestCase): self.assertEqual(len(seen_pairs), 2) self.assertIn(("2", "Service B", "$50 per visit"), seen_pairs) - @patch('src.investment.one_to_n_funcs.get_reimbursement_primary') - @patch('src.investment.one_to_n_funcs.validate_reimbursements_for_llm') - def test_reimbursement_level_cross_exhibit_deduplication_different_base_page(self, mock_validate_llm, mock_reimbursement_primary): + @patch("src.investment.one_to_n_funcs.get_reimbursement_primary") + @patch("src.investment.one_to_n_funcs.validate_reimbursements_for_llm") + def test_reimbursement_level_cross_exhibit_deduplication_different_base_page( + self, mock_validate_llm, mock_reimbursement_primary + ): """Test that pairs from different base pages are NOT filtered out""" # Setup - pre-populate seen_pairs as if previous exhibit from different base page processed - seen_pairs = {("23", "Service A", "paid at 80% of fee schedule"),} + seen_pairs = { + ("23", "Service A", "paid at 80% of fee schedule"), + } mock_validate_llm.return_value = True - + # Current exhibit has same pair but from different base page mock_reimbursement_primary.return_value = [ - {"SERVICE_TERM": "Service A", "REIMB_TERM": "paid at 80% of fee schedule"}, # Should be kept (different base page) - {"SERVICE_TERM": "Service B", "REIMB_TERM": "$50 per visit"} # Should be kept + { + "SERVICE_TERM": "Service A", + "REIMB_TERM": "paid at 80% of fee schedule", + }, # Should be kept (different base page) + { + "SERVICE_TERM": "Service B", + "REIMB_TERM": "$50 per visit", + }, # Should be kept ] - + mock_fields = MagicMock() mock_dataset = {} exhibit_page = "24.0" # Different base page (24) from seen_pairs (23) - - with patch('src.investment.one_to_n_funcs.get_special_cases') as mock_special_cases: - - mock_special_cases.side_effect = lambda x, _: x - + + with patch( + "src.investment.one_to_n_funcs.get_special_cases" + ) as mock_special_cases: + + mock_special_cases.side_effect = ( + lambda x, y, *args: x + ) # Pass through, accept any extra args + # Execute result = one_to_n_funcs.reimbursement_level( - self.exhibit_text, self.filename, mock_fields, - exhibit_page, seen_pairs, exhibit_lesser_of="N/A" + self.exhibit_text, + self.filename, + mock_fields, + exhibit_page, + seen_pairs, + exhibit_lesser_of="N/A", + constants=self.constants, ) - + # Assert - should process both pairs (no filtering due to different base page) self.assertEqual(len(result), 2) # Verify all pairs are now in seen_pairs (original + 2 new) self.assertEqual(len(seen_pairs), 3) - self.assertIn(("23", "Service A", "paid at 80% of fee schedule"), seen_pairs) # Original - self.assertIn(("24", "Service A", "paid at 80% of fee schedule"), seen_pairs) # New + self.assertIn( + ("23", "Service A", "paid at 80% of fee schedule"), seen_pairs + ) # Original + self.assertIn( + ("24", "Service A", "paid at 80% of fee schedule"), seen_pairs + ) # New self.assertIn(("24", "Service B", "$50 per visit"), seen_pairs) # New def test_deduplicate_with_accumulator_base_page_extraction(self): """Test that base page extraction works correctly for various exhibit page formats""" answers = [ {"SERVICE_TERM": "Service A", "REIMB_TERM": "term1"}, - {"SERVICE_TERM": "Service B", "REIMB_TERM": "term2"} + {"SERVICE_TERM": "Service B", "REIMB_TERM": "term2"}, ] - + test_cases = [ - ("23", "23"), # No decimal - ("23.0", "23"), # Single decimal - ("23.1", "23"), # Single decimal with sub-exhibit + ("23", "23"), # No decimal + ("23.0", "23"), # Single decimal + ("23.1", "23"), # Single decimal with sub-exhibit ("23.0.0", "23"), # Double decimal - ("123.45.67", "123") # Multiple decimals + ("123.45.67", "123"), # Multiple decimals ] - + for exhibit_page, expected_base_page in test_cases: seen_pairs = set() - + result = one_to_n_funcs.deduplicate_with_accumulator( answers, seen_pairs, exhibit_page, "test_file.pdf" ) - + # Verify correct base page was used in the keys expected_keys = { (expected_base_page, "Service A", "term1"), - (expected_base_page, "Service B", "term2") + (expected_base_page, "Service B", "term2"), } - self.assertEqual(seen_pairs, expected_keys, f"Failed for exhibit_page: {exhibit_page}") + self.assertEqual( + seen_pairs, expected_keys, f"Failed for exhibit_page: {exhibit_page}" + ) def test_deduplicate_with_accumulator_within_vs_across_base_pages(self): """Test deduplication behavior within same vs different base pages""" answers = [ {"SERVICE_TERM": "Service A", "REIMB_TERM": "term1"}, - {"SERVICE_TERM": "Service B", "REIMB_TERM": "term2"} + {"SERVICE_TERM": "Service B", "REIMB_TERM": "term2"}, ] - + # First exhibit in base page 23 seen_pairs = set() result1 = one_to_n_funcs.deduplicate_with_accumulator( @@ -295,49 +385,64 @@ class TestOneToN(unittest.TestCase): ) self.assertEqual(len(result1), 2) # Both kept self.assertEqual(len(seen_pairs), 2) - + # Second exhibit in same base page 23 - should filter duplicates result2 = one_to_n_funcs.deduplicate_with_accumulator( answers, seen_pairs, "23.1", "test_file.pdf" ) self.assertEqual(len(result2), 0) # Both filtered (duplicates within base page) self.assertEqual(len(seen_pairs), 2) # No new pairs added - + # Third exhibit in different base page 24 - should keep all result3 = one_to_n_funcs.deduplicate_with_accumulator( answers, seen_pairs, "24.0", "test_file.pdf" ) self.assertEqual(len(result3), 2) # Both kept (different base page) self.assertEqual(len(seen_pairs), 4) # 2 new pairs added - + # Verify final state expected_pairs = { - ("23", "Service A", "term1"), ("23", "Service B", "term2"), - ("24", "Service A", "term1"), ("24", "Service B", "term2") + ("23", "Service A", "term1"), + ("23", "Service B", "term2"), + ("24", "Service A", "term1"), + ("24", "Service B", "term2"), } self.assertEqual(seen_pairs, expected_pairs) - @patch('src.investment.one_to_n_funcs.validate_reimbursements_for_llm') + @patch("src.investment.one_to_n_funcs.validate_reimbursements_for_llm") def test_reimbursement_level_all_duplicates(self, mock_validate_llm): """Test behavior when all pairs are duplicates""" - seen_pairs = {("3", "Service A", "paid at 80% of fee schedule"), ("3", "Service B", "$50 per visit")} + seen_pairs = { + ("3", "Service A", "paid at 80% of fee schedule"), + ("3", "Service B", "$50 per visit"), + } mock_validate_llm.return_value = True - with patch('src.investment.one_to_n_funcs.get_reimbursement_primary') as mock_primary: + with patch( + "src.investment.one_to_n_funcs.get_reimbursement_primary" + ) as mock_primary: mock_primary.return_value = [ - {"SERVICE_TERM": "Service A", "REIMB_TERM": "paid at 80% of fee schedule"}, - {"SERVICE_TERM": "Service B", "REIMB_TERM": "$50 per visit"} + { + "SERVICE_TERM": "Service A", + "REIMB_TERM": "paid at 80% of fee schedule", + }, + {"SERVICE_TERM": "Service B", "REIMB_TERM": "$50 per visit"}, ] - + mock_fields = MagicMock() mock_dataset = {} exhibit_page = "3" - + # Execute result = one_to_n_funcs.reimbursement_level( - self.exhibit_text, self.filename, mock_fields, - exhibit_page, seen_pairs, exhibit_lesser_of="N/A" + self.exhibit_text, + self.filename, + mock_fields, + exhibit_page, + seen_pairs, + exhibit_lesser_of="N/A", + constants=self.constants, ) - + # Assert - should return empty list, no downstream processing self.assertEqual(result, []) @@ -346,22 +451,26 @@ class TestOneToN(unittest.TestCase): answers = [ {"SERVICE_TERM": "Service A", "REIMB_TERM": "paid at 80% of fee schedule"}, {"SERVICE_TERM": "Service B", "REIMB_TERM": "$50 per visit"}, - {"SERVICE_TERM": "Service A", "REIMB_TERM": "paid at 80% of fee schedule"}, # Duplicate - {"SERVICE_TERM": " Service C ", "REIMB_TERM": " $150 "} # Test stripping + { + "SERVICE_TERM": "Service A", + "REIMB_TERM": "paid at 80% of fee schedule", + }, # Duplicate + {"SERVICE_TERM": " Service C ", "REIMB_TERM": " $150 "}, # Test stripping ] - + seen_pairs = set() - + result = one_to_n_funcs.deduplicate_with_accumulator( answers, seen_pairs, "test_page", "test_file.pdf" ) - + # Should have 3 unique pairs (duplicates removed) self.assertEqual(len(result), 3) self.assertEqual(len(seen_pairs), 3) - + # Verify stripping works self.assertIn(("test_page", "Service C", "$150"), seen_pairs) -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/fieldExtraction/tests/test_preprocessing_funcs.py b/fieldExtraction/tests/test_preprocessing_funcs.py new file mode 100644 index 0000000..125f36d --- /dev/null +++ b/fieldExtraction/tests/test_preprocessing_funcs.py @@ -0,0 +1,83 @@ +import pytest + +from src.investment.preprocessing_funcs import (clean_law_symbols, + filter_quick_review, + remove_page_indicators) + + +class TestPreprocessingFuncs: + # Test cases for clean_newlines + @pytest.mark.parametrize( + "input_text, expected_output", + [ + # Removes page number indicators + ("Page 1 of 10\n\nThis is a test.", " This is a test."), + # Handles multiple newlines + ("Line 1\n\nLine 2\n\nLine 3", "Line 1\n\nLine 2\n\nLine 3"), + # Handles no newlines + ("This is a single line.", "This is a single line."), + ], + ) + def test_clean_newlines(self, input_text, expected_output): + assert remove_page_indicators(input_text) == expected_output + + # Test cases for clean_law_symbols + @pytest.mark.parametrize( + "input_text, expected_output", + [ + # Replaces double dollar signs + ("This is a $$ test.", "This is a $ test."), + # Replaces U.S.C. symbol + ("U.S.C. $1234", "U.S.C.§1234"), + # Replaces C.F.R. symbol + ("C.F.R. $1234", "C.F.R.§1234"), + # Replaces $ followed by three decimal places + ("$123.456", "§123.456"), + # Handles no replacements + ("This is a normal text.", "This is a normal text."), + # Handles mixed replacements + ( + "U.S.C. $1234 and C.F.R. $5678 and $123.456", + "U.S.C.§1234 and C.F.R.§5678 and §123.456", + ), + ], + ) + def test_clean_law_symbols(self, input_text, expected_output): + assert clean_law_symbols(input_text) == expected_output + + # Test cases for filter_quick_review + @pytest.mark.parametrize( + "input_dict, expected_contract, expected_quick_review", + [ + # Filters quick review pages + ( + {"1": "QUICK REVIEW", "2": "Normal content", "3": "COVER SHEET"}, + {"2": "Normal content"}, + {"1": "QUICK REVIEW", "3": "COVER SHEET"}, + ), + # No quick review pages + ( + {"1": "Normal content", "2": "More content"}, + {"1": "Normal content", "2": "More content"}, + {}, + ), + # All quick review pages + ( + {"1": "QUICK REVIEW", "2": "TOP SHEET"}, + {}, + {"1": "QUICK REVIEW", "2": "TOP SHEET"}, + ), + # Case-insensitive matching + ( + {"1": "quick review", "2": "Normal content"}, + {"2": "Normal content"}, + {"1": "quick review"}, + ), + ], + ) + def test_filter_quick_review( + self, input_dict, expected_contract, expected_quick_review + ): + contract, quick_review = filter_quick_review(input_dict) + assert contract == expected_contract + assert quick_review == expected_quick_review diff --git a/fieldExtraction/tests/preprocessing_integration_test.py b/fieldExtraction/tests/test_preprocessing_integration.py similarity index 86% rename from fieldExtraction/tests/preprocessing_integration_test.py rename to fieldExtraction/tests/test_preprocessing_integration.py index 040f789..eec4c9d 100644 --- a/fieldExtraction/tests/preprocessing_integration_test.py +++ b/fieldExtraction/tests/test_preprocessing_integration.py @@ -3,11 +3,17 @@ from unittest.mock import patch import pytest +from constants.constants import Constants from src.investment.preprocess import clean_tables class TestPreprocessingIntegration(unittest.TestCase): + def setUp(self): + """Set up test fixtures before each test method.""" + # Define common exhibit header markers used in tests + self.EXHIBIT_HEADER_MARKERS = Constants().EXHIBIT_HEADER_MARKERS + def test_clean_tables_simple_end_to_end(self): """Test the complete simplified table cleaning pipeline.""" text_dict = { @@ -17,7 +23,7 @@ class TestPreprocessingIntegration(unittest.TestCase): } # This should: 1) Combine continuations, 2) Split by row count - result = clean_tables(text_dict, "test_file.txt") + result = clean_tables(text_dict, self.EXHIBIT_HEADER_MARKERS, "test_file.txt") # Should have combined page 2 into page 1, then split the long table self.assertNotIn("2", result) # Continuation removed @@ -30,7 +36,7 @@ class TestPreprocessingIntegration(unittest.TestCase): "15": "Rate Table\n-------Table Start--------\nRate Table\n[['HCPC', 'Rate'], ['T1019', '$5.00']]\n-------Table End--------" } - result = clean_tables(text_dict, "test_file.pdf") + result = clean_tables(text_dict, self.EXHIBIT_HEADER_MARKERS, "test_file.pdf") # Should get .0 suffix because it contains tables self.assertIn("15.0", result) @@ -50,7 +56,7 @@ class TestPreprocessingIntegration(unittest.TestCase): "2": "-------Table Start--------\n\n[['T1021', '$4.00']]\n-------Table End--------", # Continuation } - result = clean_tables(text_dict, "test_file.pdf") + result = clean_tables(text_dict, self.EXHIBIT_HEADER_MARKERS, "test_file.pdf") # Should combine and get .0 suffix since contains tables self.assertIn("1.0", result) @@ -71,7 +77,7 @@ class TestPreprocessingIntegration(unittest.TestCase): "31": f"EXHIBIT F\n-------Table Start--------\nPCAP Rates\n{large_table_rows}\n-------Table End--------\nPost-table text" } - result = clean_tables(text_dict, "test_file.pdf") + result = clean_tables(text_dict, self.EXHIBIT_HEADER_MARKERS, "test_file.pdf") # Should create multiple chunks due to row limit (default 50) chunk_keys = [k for k in result.keys() if k.startswith("31.")] @@ -101,7 +107,7 @@ class TestPreprocessingIntegration(unittest.TestCase): "6": "More text content without any table markers.", } - result = clean_tables(text_dict, "test_file.pdf") + result = clean_tables(text_dict, self.EXHIBIT_HEADER_MARKERS, "test_file.pdf") # Should remain unchanged (no .0 suffix) assert "5" in result @@ -119,7 +125,7 @@ class TestPreprocessingIntegration(unittest.TestCase): "1": "Page metadata\n-------Table Start--------\nFirst Table\n[['Col1', 'Col2'], ['A%', 'B%']]\n-------Table End--------\n\nSome text\n-------Table Start--------\nSecond Table\n[['X', 'Y'], ['1%', '2%']]\n-------Table End--------" } - result = clean_tables(text_dict, "test_file.pdf") + result = clean_tables(text_dict, self.EXHIBIT_HEADER_MARKERS, "test_file.pdf") # Should get .0 suffix because it contains tables assert "1.0" in result diff --git a/fieldExtraction/tests/test_smart_chunking_funcs.py b/fieldExtraction/tests/test_smart_chunking_funcs.py index ae9e454..374a0d5 100644 --- a/fieldExtraction/tests/test_smart_chunking_funcs.py +++ b/fieldExtraction/tests/test_smart_chunking_funcs.py @@ -1,72 +1,73 @@ import pytest + from src.investment.smart_chunking_funcs import parse_chunk, stitch_chunks + def test_parse_chunk_valid(): chunk = "chunk 001: This is a test chunk." chunk_number, content = parse_chunk(chunk) assert chunk_number == 1 assert content == "This is a test chunk." + def test_parse_chunk_valid_with_large_number(): chunk = "chunk 123: Another test chunk with a larger number." chunk_number, content = parse_chunk(chunk) assert chunk_number == 123 assert content == "Another test chunk with a larger number." + def test_parse_chunk_empty_content(): chunk = "chunk 005: " chunk_number, content = parse_chunk(chunk) assert chunk_number == 5 assert content == "" + def test_parse_chunk_invalid_format(): chunk = "invalid chunk format" with pytest.raises(ValueError): parse_chunk(chunk) + def test_parse_chunk_short_prefix(): chunk = "chunk 01: Short prefix" with pytest.raises(ValueError): parse_chunk(chunk) + def test_stitch_chunks_empty_list(): assert stitch_chunks([]) == "" + def test_stitch_chunks_single_chunk(): assert stitch_chunks(["chunk 001: hello world"]) == "hello world" + def test_stitch_chunks_two_chunks(): - chunks = [ - "chunk 001: hello world", - "chunk 002: world and more" - ] + chunks = ["chunk 001: hello world", "chunk 002: world and more"] assert stitch_chunks(chunks, overlap_size=5) == "hello world and more" + def test_stitch_chunks_out_of_order(): - chunks = [ - "chunk 002: world", - "chunk 001: hello" - ] + chunks = ["chunk 002: world", "chunk 001: hello"] assert stitch_chunks(chunks, overlap_size=0) in ["helloworld", "hello\nworld"] -def test_stitch_noncontiguous_chunks(): - chunks = [ - "chunk 001: hello", - "chunk 003: world" - ] - assert stitch_chunks(chunks, overlap_size=10) in ["helloworld", "hello\nworld"] # the overlap size will be ignored in the noncontiguous case -def test_stitch_long_overlap(): # not enough text to overlap, so we just append them - chunks = [ - "chunk 001: hello", - "chunk 002: world" - ] +def test_stitch_noncontiguous_chunks(): + chunks = ["chunk 001: hello", "chunk 003: world"] + assert stitch_chunks(chunks, overlap_size=10) in [ + "helloworld", + "hello\nworld", + ] # the overlap size will be ignored in the noncontiguous case + + +def test_stitch_long_overlap(): # not enough text to overlap, so we just append them + chunks = ["chunk 001: hello", "chunk 002: world"] assert stitch_chunks(chunks, overlap_size=10) in ["helloworld", "hello\nworld"] + def test_stitch_chunks_negative_overlap(): - chunks = [ - "chunk 001: hello", - "chunk 002: world" - ] + chunks = ["chunk 001: hello", "chunk 002: world"] with pytest.raises(ValueError): - stitch_chunks(chunks, overlap_size=-1) \ No newline at end of file + stitch_chunks(chunks, overlap_size=-1) diff --git a/fieldExtraction/tests/string_utils_test.py b/fieldExtraction/tests/test_string_utils.py similarity index 73% rename from fieldExtraction/tests/string_utils_test.py rename to fieldExtraction/tests/test_string_utils.py index f881bab..992a9e2 100644 --- a/fieldExtraction/tests/string_utils_test.py +++ b/fieldExtraction/tests/test_string_utils.py @@ -1,21 +1,15 @@ -import pytest -import src.utils as utils -from src.utils.string_utils import ( - extract_text_from_delimiters, - json_parsing_search, - secondary_string_to_dict, - primary_string_to_dict, - contains_reimbursement, - is_empty, - count_reimbursements_in_exhibit, - extract_signature_page, - page_key_sort, -) -import src.utils.llm_utils as llm_utils import numpy as np import pandas as pd +import pytest -from src.enums.delimiters import Delimiter +import src.utils as utils +import src.utils.llm_utils as llm_utils +from constants.delimiters import Delimiter +from src.utils.string_utils import (contains_reimbursement, + count_reimbursements_in_exhibit, + extract_signature_page, + extract_text_from_delimiters, is_empty, + json_parsing_search, page_key_sort) class TestStringUtils: @@ -118,128 +112,6 @@ class TestStringUtils: def mock_invoke_claude(self, mocker): return mocker.patch.object(llm_utils, "invoke_claude") - def test_valid_json(self, mock_invoke_claude): - dict_string = '{"key": "value"}' - filename = "test_file.txt" - result = secondary_string_to_dict(dict_string, filename) - assert result == {"key": "value"} - mock_invoke_claude.assert_not_called() - - def test_invalid_json_fixed_by_replacing_quotes(self, mock_invoke_claude): - dict_string = "{'key': 'value'}" - filename = "test_file.txt" - result = secondary_string_to_dict(dict_string, filename) - assert result == {"key": "value"} - mock_invoke_claude.assert_not_called() - - def test_invalid_json_fixed_by_llm(self, mock_invoke_claude): - mock_invoke_claude.return_value = '{"key": "value"}' - dict_string = "{key: value}" - filename = "test_file.txt" - result = secondary_string_to_dict(dict_string, filename) - assert result == {"key": "value"} - mock_invoke_claude.assert_called_once() - - def test_invalid_json_failed_by_llm(self, mock_invoke_claude): - mock_invoke_claude.side_effect = Exception("LLM call failed") - dict_string = "{key: value}" - filename = "test_file.txt" - result = secondary_string_to_dict(dict_string, filename) - assert result == {} - mock_invoke_claude.assert_called_once() - - def test_no_dict_found(self, mock_invoke_claude): - mock_invoke_claude.return_value = "{}" - dict_string = "This is a plain text without any dictionary." - filename = "test_file.txt" - result = secondary_string_to_dict(dict_string, filename) - assert result == {} - mock_invoke_claude.assert_called_once() - - @pytest.fixture - def mock_secondary_string_to_dict(self, mocker): - return mocker.patch.object(utils.string_utils, "secondary_string_to_dict") - - def test_valid_input(self, mock_secondary_string_to_dict): - mock_secondary_string_to_dict.side_effect = [ - {"key1": "value1"}, - {"key2": "value2"}, - {"key3": "value3"}, - ] - string_dict = { - "1": "[{'key1': 'value1'}, {'key2': 'value2'}]", - "2": "[{'key3': 'value3'}]", - } - filename = "test_file.txt" - expected_output = [ - {"key1": "value1", "page_num": "1"}, - {"key2": "value2", "page_num": "1"}, - {"key3": "value3", "page_num": "2"}, - ] - result = primary_string_to_dict(string_dict, filename) - assert result == expected_output - assert mock_secondary_string_to_dict.call_count == len(expected_output) - - def test_invalid_input_no_dicts(self, mock_secondary_string_to_dict): - mock_secondary_string_to_dict.return_value = {} - string_dict = { - "1": "[This is not a dictionary]", - "2": "[Still not a dictionary]", - } - filename = "test_file.txt" - expected_output = [] - result = primary_string_to_dict(string_dict, filename) - assert result == expected_output - assert mock_secondary_string_to_dict.call_count == len(expected_output) - - def test_empty_input(self, mock_secondary_string_to_dict): - string_dict = {} - filename = "test_file.txt" - expected_output = [] - result = primary_string_to_dict(string_dict, filename) - assert result == expected_output - mock_secondary_string_to_dict.assert_not_called() - - def test_malformed_input(self, mock_secondary_string_to_dict): - mock_secondary_string_to_dict.side_effect = [ - {"key1": "value1"}, - {"key2": "value2"}, - ] - string_dict = { - "1": "[{'key1': 'value1'}, malformed]", - "2": "[{'key2': 'value2'}, <<>>]", - } - filename = "test_file.txt" - expected_output = [ - {"key1": "value1", "page_num": "1"}, - {"key2": "value2", "page_num": "2"}, - ] - result = primary_string_to_dict(string_dict, filename) - assert result == expected_output - assert mock_secondary_string_to_dict.call_count == 2 - - def test_multiple_pages(self, mock_secondary_string_to_dict): - mock_secondary_string_to_dict.side_effect = [ - {"key1": "value1"}, - {"key2": "value2"}, - {"key3": "value3"}, - {"key4": "value4"}, - ] - string_dict = { - "1": "[{'key1': 'value1'}, {'key2': 'value2'}]", - "2": "[{'key3': 'value3'}, {'key4': 'value4'}]", - } - filename = "test_file.txt" - expected_output = [ - {"key1": "value1", "page_num": "1"}, - {"key2": "value2", "page_num": "1"}, - {"key3": "value3", "page_num": "2"}, - {"key4": "value4", "page_num": "2"}, - ] - result = primary_string_to_dict(string_dict, filename) - assert result == expected_output - assert mock_secondary_string_to_dict.call_count == 4 - @pytest.mark.parametrize( "text, page, expected", [ diff --git a/fieldExtraction/tests/table_funcs_test.py b/fieldExtraction/tests/test_table_funcs.py similarity index 94% rename from fieldExtraction/tests/table_funcs_test.py rename to fieldExtraction/tests/test_table_funcs.py index aa91690..8a70e07 100644 --- a/fieldExtraction/tests/table_funcs_test.py +++ b/fieldExtraction/tests/test_table_funcs.py @@ -3,13 +3,14 @@ import unittest from unittest.mock import patch import pandas as pd -from sympy import content -from src.investment.table_funcs import (Table, combine_continuous_tables, +from constants.constants import Constants +from src.investment.table_funcs import (END_MARKER, START_MARKER, Table, + combine_continuous_tables, convert_str_to_dataframe, get_table_info, recreate_page, split_large_table_with_headers, - split_tables_by_rows, START_MARKER, END_MARKER) + split_tables_by_rows) logging.basicConfig(level=logging.INFO) @@ -35,6 +36,8 @@ class TestTableFunctions(unittest.TestCase): "1": "Long table metadata\n-------Table Start--------\nLong Table\n[['Name%', 'Value'], ['Row1', '1'], ['Row2', '2'], ['Row3', '3'], ['Row4', '4'], ['Row5', '5']]\n-------Table End--------" } + self.EXHIBIT_HEADER_MARKERS = Constants().EXHIBIT_HEADER_MARKERS + def test_convert_str_to_dataframe_new_format(self): """Test conversion from new list-of-rows format.""" table_str = "[['A', 'B'], ['1', '3'], ['2', '4']]" @@ -201,7 +204,7 @@ class TestTableFunctions(unittest.TestCase): } # Debug: Let's see what get_table_info thinks about these tables - table_dict = get_table_info(text_dict) + table_dict = get_table_info(text_dict, self.EXHIBIT_HEADER_MARKERS) print(f"Table dict keys: {list(table_dict.keys())}") for page_num, tables in table_dict.items(): for i, table in enumerate(tables): @@ -218,7 +221,9 @@ class TestTableFunctions(unittest.TestCase): self.assertIn("3", result) # Page 1 should now contain the combined table - combined_table_dict = get_table_info({"1": result["1"]}) + combined_table_dict = get_table_info( + {"1": result["1"]}, self.EXHIBIT_HEADER_MARKERS + ) combined_table = combined_table_dict["1"][0] # Should have 4 rows total (2 from main + 2 from continuation) @@ -242,7 +247,7 @@ class TestTableFunctions(unittest.TestCase): ), } - table_dict = get_table_info(text_dict) + table_dict = get_table_info(text_dict, self.EXHIBIT_HEADER_MARKERS) # Verify setup: Page 1 should have 2 tables, Page 2 should have 1 continuation table self.assertEqual(len(table_dict["1"]), 2) @@ -270,7 +275,9 @@ class TestTableFunctions(unittest.TestCase): self.assertIn("1", result) # Parse the combined result to verify tables - combined_table_dict = get_table_info({"1": result["1"]}) + combined_table_dict = get_table_info( + {"1": result["1"]}, self.EXHIBIT_HEADER_MARKERS + ) combined_tables = combined_table_dict["1"] # Should still have 2 tables on page 1 @@ -313,11 +320,13 @@ class TestTableFunctions(unittest.TestCase): "2": "-------Table Start--------\n\n[['B2', '2%']]\n-------Table End--------", # Should combine with Table B, not Table A } - table_dict = get_table_info(text_dict) + table_dict = get_table_info(text_dict, self.EXHIBIT_HEADER_MARKERS) result = combine_continuous_tables(text_dict, table_dict) # Get the result and verify correct combination - combined_table_dict = get_table_info({"1": result["1"]}) + combined_table_dict = get_table_info( + {"1": result["1"]}, self.EXHIBIT_HEADER_MARKERS + ) # Table A (index 0) should have 1 row still - NOT combined with continuation table_a = combined_table_dict["1"][0] @@ -529,7 +538,7 @@ class TestTableFunctions(unittest.TestCase): "1": "Metadata\n-------Table Start--------\nTable\n[['A', 'B'], ['1', '2%']]\n-------Table End--------\nImportant footnote\nSignature line" } - table_dict = get_table_info(text_dict) + table_dict = get_table_info(text_dict, self.EXHIBIT_HEADER_MARKERS) table = table_dict["1"][0] # Should capture post-table text @@ -632,7 +641,9 @@ class TestTableFunctions(unittest.TestCase): def test_table_info_new_format(self): """Test getting table info from new list-of-rows format.""" - table_dict = get_table_info(self.sample_text_dict_new) + table_dict = get_table_info( + self.sample_text_dict_new, self.EXHIBIT_HEADER_MARKERS + ) self.assertIn("1", table_dict) self.assertEqual(len(table_dict["1"]), 1) @@ -649,7 +660,9 @@ class TestTableFunctions(unittest.TestCase): def test_table_info_old_format(self): """Test getting table info from old dict format.""" with self.assertLogs(level="INFO") as log: # Expect a deprecation warning - table_dict = get_table_info(self.sample_text_dict_old) + table_dict = get_table_info( + self.sample_text_dict_old, self.EXHIBIT_HEADER_MARKERS + ) self.assertIn("1", table_dict) self.assertEqual(len(table_dict["1"]), 1) @@ -659,7 +672,7 @@ class TestTableFunctions(unittest.TestCase): def test_get_table_info_multiple_tables(self): """Test getting table info from a page with multiple tables.""" - table_dict = get_table_info(self.multi_table_text) + table_dict = get_table_info(self.multi_table_text, self.EXHIBIT_HEADER_MARKERS) self.assertIn("1", table_dict) self.assertEqual(len(table_dict["1"]), 1) @@ -667,7 +680,9 @@ class TestTableFunctions(unittest.TestCase): def test_recreate_page(self): """Test recreating page text from tables.""" - table_dict = get_table_info(self.sample_text_dict_new) + table_dict = get_table_info( + self.sample_text_dict_new, self.EXHIBIT_HEADER_MARKERS + ) page_tables = table_dict["1"] recreated_text = recreate_page(page_tables) @@ -686,7 +701,9 @@ class TestTableFunctions(unittest.TestCase): "5": f"Table metadata\n-------Table Start--------\nLong Table\n{long_table_data}\n-------Table End--------\nPost-table text" } - result = split_tables_by_rows(text_dict, row_limit=3) + result = split_tables_by_rows( + text_dict, self.EXHIBIT_HEADER_MARKERS, row_limit=3 + ) # Should create multiple pages with .0, .1, .2 suffixes expected_pages = ["5.0", "5.1", "5.2"] @@ -705,7 +722,9 @@ class TestTableFunctions(unittest.TestCase): "3": "Metadata\n-------Table Start--------\nSmall Table\n[['A', 'B'], ['1%', '2']]\n-------Table End--------" } - result = split_tables_by_rows(text_dict, row_limit=10) + result = split_tables_by_rows( + text_dict, self.EXHIBIT_HEADER_MARKERS, row_limit=10 + ) # Should have .0 suffix but no additional splits self.assertIn("3.0", result) @@ -719,7 +738,9 @@ class TestTableFunctions(unittest.TestCase): "2": "More text, no tables here either.", } - result = split_tables_by_rows(text_dict, row_limit=5) + result = split_tables_by_rows( + text_dict, self.EXHIBIT_HEADER_MARKERS, row_limit=5 + ) # Pages without tables should keep original numbers self.assertIn("1", result) @@ -734,7 +755,7 @@ class TestTableFunctions(unittest.TestCase): "2": "Metadata 2\n-------Table Start--------\nTable 2\n[['C', 'D'], ['3%', '4']]\n-------Table End--------", } - table_dict = get_table_info(text_dict) + table_dict = get_table_info(text_dict, self.EXHIBIT_HEADER_MARKERS) result = combine_continuous_tables(text_dict, table_dict) # Both pages should remain (no continuation tables to combine) @@ -854,20 +875,19 @@ class TestTableFunctions(unittest.TestCase): self.assertEqual(table1.table.iloc[2, 1], "") # Padded self.assertEqual(table1.table.iloc[2, 2], "") # Padded - def test_exhibit_boundary_detection_prevents_combination(self): """Test that exhibit boundaries prevent incorrect table combination.""" text_dict = { "1": "EXHIBIT A-1\nCommercial rates\n-------Table Start--------\n\n[['Service', 'Rate'], ['A', '1%']]\n-------Table End--------", "2": "EXHIBIT B-1\nMedicare rates\n-------Table Start--------\n\n[['Service', 'Rate'], ['B', '2%']]\n-------Table End--------", } - - table_dict = get_table_info(text_dict) - + + table_dict = get_table_info(text_dict, self.EXHIBIT_HEADER_MARKERS) + # Both should be detected as non-continuation tables due to exhibit boundaries self.assertFalse(table_dict["1"][0].is_continuation) self.assertFalse(table_dict["2"][0].is_continuation) - + # Combination should not remove any pages result = combine_continuous_tables(text_dict, table_dict) self.assertIn("1", result) @@ -878,21 +898,21 @@ class TestTableFunctions(unittest.TestCase): """Test that exhibit detection works with different cases.""" test_cases = [ "EXHIBIT A-1", - "Exhibit A-1", + "Exhibit A-1", "exhibit a-1", "ATTACHMENT B", "Schedule C", - "ADDENDUM FOR MEDICARE" + "ADDENDUM FOR MEDICARE", ] - + for exhibit_text in test_cases: with self.subTest(exhibit=exhibit_text): text_dict = { "1": f"{exhibit_text}\nRate information\n-------Table Start--------\n\n[['A', 'B'], ['1%', '2']]\n-------Table End--------" } - - table_dict = get_table_info(text_dict) - + + table_dict = get_table_info(text_dict, self.EXHIBIT_HEADER_MARKERS) + # Should not be detected as continuation due to exhibit marker self.assertFalse(table_dict["1"][0].is_continuation) @@ -903,9 +923,9 @@ class TestTableFunctions(unittest.TestCase): text_dict = { "1": f"{long_prefix}EXHIBIT A-1\nRate info\n-------Table Start--------\n\n[['A', 'B'], ['1%', '2']]\n-------Table End--------" } - - table_dict = get_table_info(text_dict) - + + table_dict = get_table_info(text_dict, self.EXHIBIT_HEADER_MARKERS) + # Should be treated as continuation since exhibit marker is beyond position 100 self.assertTrue(table_dict["1"][0].is_continuation) @@ -919,7 +939,7 @@ class TestTableFunctions(unittest.TestCase): start_index=0, end_index=100, ) - + self.assertTrue(table.table.empty) self.assertEqual(table.table.shape, (0, 0)) @@ -933,7 +953,7 @@ class TestTableFunctions(unittest.TestCase): start_index=0, end_index=100, ) - + self.assertEqual(table.table.shape, (0, 2)) # 0 rows, 2 columns self.assertEqual(list(table.table.columns), ["Column A", "Column B"]) @@ -947,7 +967,7 @@ class TestTableFunctions(unittest.TestCase): start_index=0, end_index=100, ) - + # Continuation table with generic column names continuation_table = Table( page_num="2", @@ -958,15 +978,15 @@ class TestTableFunctions(unittest.TestCase): end_index=100, is_continuation=True, ) - + main_table.combine(continuation_table) - + # Should have 4 data rows total self.assertEqual(main_table.table.shape, (4, 2)) - + # Column names should remain from main table self.assertEqual(list(main_table.table.columns), ["Service", "Rate"]) - + # Data should be properly combined self.assertEqual(main_table.table.iloc[0, 0], "A") # Original self.assertEqual(main_table.table.iloc[2, 0], "C") # Continuation @@ -986,52 +1006,54 @@ class TestTableFunctions(unittest.TestCase): Table( page_num="1", metadata="", - header="Second Table", + header="Second Table", table_data=[["X", "Y"], ["3%", "4"]], start_index=200, end_index=300, post_table_text="Important footnote\nSignature required", ), ] - + recreated = recreate_page(tables) - + # Should contain both tables self.assertIn("First Table", recreated) self.assertIn("Second Table", recreated) - + # Should contain post-table text from last table self.assertIn("Important footnote", recreated) self.assertIn("Signature required", recreated) - + # Should have proper structure self.assertIn(START_MARKER, recreated) self.assertIn(END_MARKER, recreated) def test_split_tables_by_rows_preserves_metadata_and_post_text(self): """Test that table splitting preserves all text components.""" - table_data = [['Code', 'Rate']] + [[f"T{i:04d}", f"${i}.00"] for i in range(12)] - + table_data = [["Code", "Rate"]] + [[f"T{i:04d}", f"${i}.00"] for i in range(12)] + text_dict = { "5": f"EXHIBIT F\nRate Schedule\n-------Table Start--------\nLarge Table\n{table_data}\n-------Table End--------\nEffective 2024\nContact: admin@health.net" } - - result = split_tables_by_rows(text_dict, row_limit=5) - + + result = split_tables_by_rows( + text_dict, self.EXHIBIT_HEADER_MARKERS, row_limit=5 + ) + # Should create multiple pages expected_pages = ["5.0", "5.1", "5.2"] for page in expected_pages: self.assertIn(page, result) - + # All pages should have metadata and header for page in expected_pages: self.assertIn("EXHIBIT F", result[page]) self.assertIn("Large Table", result[page]) - + # Only last page should have post-table text self.assertIn("Effective 2024", result["5.2"]) self.assertIn("Contact: admin@health.net", result["5.2"]) - + # Earlier pages should not have post-table text self.assertNotIn("Effective 2024", result["5.0"]) self.assertNotIn("Effective 2024", result["5.1"]) @@ -1045,10 +1067,12 @@ class TestTableFunctions(unittest.TestCase): ("[[], ['1', '2']]", False), # Empty header row ("[['']]", False), # Single empty string ] - + for test_input, is_continuation in edge_cases: with self.subTest(input=test_input, continuation=is_continuation): - df = convert_str_to_dataframe(test_input, is_continuation=is_continuation) + df = convert_str_to_dataframe( + test_input, is_continuation=is_continuation + ) self.assertIsInstance(df, pd.DataFrame) # Should not raise exceptions diff --git a/fieldExtraction/tests/test_tin_npi_funcs.py b/fieldExtraction/tests/test_tin_npi_funcs.py index ee78715..527ec8c 100644 --- a/fieldExtraction/tests/test_tin_npi_funcs.py +++ b/fieldExtraction/tests/test_tin_npi_funcs.py @@ -1,10 +1,12 @@ -import pytest -from unittest.mock import patch, MagicMock import json +from unittest.mock import MagicMock, patch + +import pytest import src.investment.tin_npi_funcs as tin_npi_funcs from src.prompts.investment_prompts import FieldSet + class TestTinNpiFuncs: @pytest.fixture def sample_text_dict(self): @@ -12,18 +14,18 @@ class TestTinNpiFuncs: "1": "First page with TIN 12-3456789", "2": "Second page with NPI 1234567890", "2.1": "Continuation with same TIN 12-3456789", - "3": "Page with both TIN 98-7654321 and NPI 9876543210" + "3": "Page with both TIN 98-7654321 and NPI 9876543210", } def test_get_all_matches(self): text = "TIN: 12-3456789 and another TIN: 98-7654321" - pattern = r'\d{2}[-\s]?\d{7}' + pattern = r"\d{2}[-\s]?\d{7}" result = tin_npi_funcs.get_all_matches(text, pattern, "test.pdf") - assert sorted(result) == sorted(['12-3456789', '98-7654321']) + assert sorted(result) == sorted(["12-3456789", "98-7654321"]) # Test empty pattern assert tin_npi_funcs.get_all_matches(text, "", "test.pdf") == [] - + # Test no matches assert tin_npi_funcs.get_all_matches("No TINs here", pattern, "test.pdf") == [] @@ -31,7 +33,7 @@ class TestTinNpiFuncs: text_dict = { "1": "Page with match1", "2": "Page without matches", - "3": "Page with match2" + "3": "Page with match2", } matches = ["match1", "match2"] result = tin_npi_funcs.chunk_on_matches(matches, text_dict) @@ -45,21 +47,21 @@ class TestTinNpiFuncs: "TIN": "12-345.6789", "NPI": "12.34567890", "NAME": "Test Provider", - "IS_GROUP": "Y" + "IS_GROUP": "Y", }, { "TIN": "", "NPI": "invalid", "NAME": "", "IS_GROUP": "N", - } + }, ] result = tin_npi_funcs.clean_provider_info(providers) - + assert result[0]["TIN"] == "123456789" assert result[0]["NPI"] == "1234567890" assert result[0]["NAME"] == "Test Provider" - + assert result[1]["TIN"] == "UNKNOWN" assert result[1]["NPI"] == "UNKNOWN" assert result[1]["NAME"] == "UNKNOWN" @@ -71,79 +73,71 @@ class TestTinNpiFuncs: "TIN": "123456789", "NPI": "1234567890", "NAME": "Group Provider", - "IS_GROUP": "Y" + "IS_GROUP": "Y", }, { "TIN": "987654321", "NPI": "9876543210", "NAME": "Other Provider", - "IS_GROUP": "N" - } + "IS_GROUP": "N", + }, ] - + result = tin_npi_funcs.merge_provider_info(one_to_one_results, provider_info) - + assert result["PROV_GROUP_TIN"] == "123456789" assert result["PROV_OTHER_TIN"] == "987654321" assert "Group Provider" in result["PROV_GROUP_NAME_FULL"] assert "Other Provider" in result["PROV_OTHER_NAME_FULL"] - def test_deduplicate_providers(self): providers = [ - { - "TIN": "123456789", - "NPI": "1234567890", - "NAME": "Provider A" - }, - { - "TIN": "123456789", - "NPI": "1234567890", - "NAME": "Provider A" - }, - { - "TIN": "UNKNOWN", - "NPI": "UNKNOWN", - "NAME": "UNKNOWN" - } + {"TIN": "123456789", "NPI": "1234567890", "NAME": "Provider A"}, + {"TIN": "123456789", "NPI": "1234567890", "NAME": "Provider A"}, + {"TIN": "UNKNOWN", "NPI": "UNKNOWN", "NAME": "UNKNOWN"}, ] - + result = tin_npi_funcs.deduplicate_providers(providers) assert len(result) == 1 assert result[0]["TIN"] == "123456789" - @patch('src.utils.llm_utils.invoke_claude') + @patch("src.utils.llm_utils.invoke_claude") def test_get_provider_info(self, mock_invoke_claude, sample_text_dict): - mock_invoke_claude.return_value = json.dumps([{ - "TIN": "123456789", - "NPI": "1234567890", - "NAME": "Test Provider", - "ON_SIGNATURE_PAGE": "N" - }]) - + mock_invoke_claude.return_value = json.dumps( + [ + { + "TIN": "123456789", + "NPI": "1234567890", + "NAME": "Test Provider", + "ON_SIGNATURE_PAGE": "N", + } + ] + ) + result = tin_npi_funcs.get_provider_info(sample_text_dict, "1", "test.pdf") assert len(result) == 1 assert result[0]["IS_GROUP"] == "Y" # Page 1 should be marked as intro - @patch('src.utils.llm_utils.invoke_claude') + @patch("src.utils.llm_utils.invoke_claude") def test_run_provider_info_fields(self, mock_invoke_claude, sample_text_dict): - mock_invoke_claude.return_value = json.dumps([{ - "TIN": "123456789", - "NPI": "1234567890", - "NAME": "Test Provider", - "ON_SIGNATURE_PAGE": "N" - }]) - + mock_invoke_claude.return_value = json.dumps( + [ + { + "TIN": "123456789", + "NPI": "1234567890", + "NAME": "Test Provider", + "ON_SIGNATURE_PAGE": "N", + } + ] + ) + one_to_one_fields = FieldSet(relationship="one_to_one") contract_text = "Contract with TIN 12-3456789" - + results, fields = tin_npi_funcs.run_provider_info_fields( - contract_text, - one_to_one_fields, - sample_text_dict, - "test.pdf" + contract_text, one_to_one_fields, sample_text_dict, "test.pdf" ) - + assert "PROV_INFO_JSON" in results assert "PROV_INFO_JSON_FORMATTED" in results - assert isinstance(results["PROV_INFO_JSON"], str) \ No newline at end of file + assert isinstance(results["PROV_INFO_JSON"], str)