From 9b15c21d48a004f7870497b9effe0afe2a3c7cab Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Thu, 31 Jul 2025 17:29:23 +0100 Subject: [PATCH 01/19] Replace connectors with adaptors --- healthchain/io/__init__.py | 10 +- healthchain/io/base.py | 67 +++++-- healthchain/io/cdaadapter.py | 186 ++++++++++++++++++ healthchain/io/cdsfhiradapter.py | 133 +++++++++++++ healthchain/pipeline/base.py | 68 +------ healthchain/pipeline/medicalcodingpipeline.py | 67 ++++--- healthchain/pipeline/summarizationpipeline.py | 67 ++++--- 7 files changed, 462 insertions(+), 136 deletions(-) create mode 100644 healthchain/io/cdaadapter.py create mode 100644 healthchain/io/cdsfhiradapter.py diff --git a/healthchain/io/__init__.py b/healthchain/io/__init__.py index 1e0272e6..1a7e9fda 100644 --- a/healthchain/io/__init__.py +++ b/healthchain/io/__init__.py @@ -1,15 +1,21 @@ from .containers import DataContainer, Document, Tabular -from .base import BaseConnector +from .base import BaseConnector, BaseAdapter from .cdaconnector import CdaConnector from .cdsfhirconnector import CdsFhirConnector +from .cdaadapter import CdaAdapter +from .cdsfhiradapter import CdsFhirAdapter __all__ = [ # Containers "DataContainer", "Document", "Tabular", - # Connectors + # Connectors (legacy) "BaseConnector", "CdaConnector", "CdsFhirConnector", + # Adapters (new) + "BaseAdapter", + "CdaAdapter", + "CdsFhirAdapter", ] diff --git a/healthchain/io/base.py b/healthchain/io/base.py index b13b02dd..fc67eedc 100644 --- a/healthchain/io/base.py +++ b/healthchain/io/base.py @@ -1,40 +1,77 @@ from abc import ABC, abstractmethod -from typing import Generic, TypeVar -from healthchain.io.containers import DataContainer +from typing import Generic, TypeVar, Optional, Any +from healthchain.io.containers import Document -T = TypeVar("T") +RequestType = TypeVar("RequestType") +ResponseType = TypeVar("ResponseType") -class BaseConnector(Generic[T], ABC): +class BaseAdapter(Generic[RequestType, ResponseType], ABC): """ - Abstract base class for all connectors in the pipeline. + Abstract base class for all adapters in HealthChain. - This class should be subclassed to create specific connectors. - Subclasses must implement the input and output methods. + Adapters handle conversion between external healthcare data formats + (CDA, CDS Hooks, etc.) and HealthChain's internal Document objects. + + This class should be subclassed to create specific adapters. + Subclasses must implement the parse and format methods. """ + def __init__(self, engine: Optional[Any] = None): + """ + Initialize BaseAdapter with optional interop engine. + + Args: + engine (Optional[Any]): Optional interoperability engine for format conversions. + Only used by adapters that require format conversion (e.g., CDA). + """ + self.engine = engine + @abstractmethod - def input(self, data: DataContainer[T]) -> DataContainer[T]: + def parse(self, request: RequestType) -> Document: """ - Convert input data to the pipeline's internal format. + Parse external format data into HealthChain's internal Document format. Args: - data (DataContainer[T]): The input data to be converted. + request (RequestType): The external format request to be parsed. Returns: - DataContainer[T]: The converted data. + Document: The parsed data as a Document object. """ pass @abstractmethod - def output(self, data: DataContainer[T]) -> DataContainer[T]: + def format(self, document: Document) -> ResponseType: """ - Convert pipeline's internal format to output data. + Format HealthChain's internal Document into external format response. Args: - data (DataContainer[T]): The data to be converted for output. + document (Document): The Document object to be formatted. Returns: - DataContainer[T]: The converted output data. + ResponseType: The formatted response in external format. + """ + pass + + +# Legacy connector class for backwards compatibility +class BaseConnector(Generic[RequestType], ABC): + """ + DEPRECATED: Use BaseAdapter instead. + + Abstract base class for legacy connectors. + """ + + @abstractmethod + def input(self, data: RequestType) -> Document: + """ + DEPRECATED: Use BaseAdapter.parse() instead. + """ + pass + + @abstractmethod + def output(self, data: Document) -> ResponseType: + """ + DEPRECATED: Use BaseAdapter.format() instead. """ pass diff --git a/healthchain/io/cdaadapter.py b/healthchain/io/cdaadapter.py new file mode 100644 index 00000000..4dd7b709 --- /dev/null +++ b/healthchain/io/cdaadapter.py @@ -0,0 +1,186 @@ +import logging +from typing import Optional + +from healthchain.io.containers import Document +from healthchain.io.base import BaseAdapter +from healthchain.interop import create_engine, FormatType, InteropEngine +from healthchain.models.requests.cdarequest import CdaRequest +from healthchain.models.responses.cdaresponse import CdaResponse +from healthchain.fhir import ( + create_bundle, + set_problem_list_item_category, + create_document_reference, + read_content_attachment, +) +from fhir.resources.condition import Condition +from fhir.resources.medicationstatement import MedicationStatement +from fhir.resources.allergyintolerance import AllergyIntolerance +from fhir.resources.documentreference import DocumentReference + +log = logging.getLogger(__name__) + + +class CdaAdapter(BaseAdapter[CdaRequest, CdaResponse]): + """ + CdaAdapter class for handling CDA (Clinical Document Architecture) documents. + + This adapter facilitates parsing CDA documents into Document objects and formatting + Document objects back into CDA responses. It uses the InteropEngine to convert + between CDA and FHIR formats, preserving clinical content while allowing for + manipulation of the data within HealthChain pipelines. + + Attributes: + engine (InteropEngine): The interoperability engine for CDA conversions. + If not provided, the default engine is used. + original_cda (str): The original CDA document for use in output. + note_document_reference (DocumentReference): Reference to the note document + extracted from the CDA. + + Methods: + parse: Parses a CDA document and extracts clinical data into a Document. + format: Converts a Document back to CDA format and returns a CdaResponse. + """ + + def __init__(self, engine: Optional[InteropEngine] = None): + """ + Initialize CdaAdapter with optional interop engine. + + Args: + engine (Optional[InteropEngine]): Custom interop engine for CDA conversions. + If None, creates a default engine. + """ + # Initialize engine with default if not provided + initialized_engine = engine or create_engine() + super().__init__(engine=initialized_engine) + self.engine = initialized_engine + self.original_cda = None + self.note_document_reference = None + + def parse(self, cda_request: CdaRequest) -> Document: + """ + Parse a CDA document and extract clinical data into a HealthChain Document object. + + This method takes a CdaRequest object as input, parses it using the InteropEngine to convert + CDA to FHIR resources, and creates a Document object with the extracted data. It creates a + DocumentReference for the original CDA XML and extracts clinical data (problems, medications, + allergies) into FHIR resources. + + Args: + cda_request (CdaRequest): Request object containing the CDA XML document to process. + + Returns: + Document: A Document object containing: + - The extracted note text as the document data + - FHIR resources organized into appropriate lists: + - problem_list: List of Condition resources + - medication_list: List of MedicationStatement resources + - allergy_list: List of AllergyIntolerance resources + - DocumentReference resources for the original CDA and extracted notes + + Note: + If a DocumentReference resource is found in the converted FHIR resources, + it is assumed to contain the note text and is stored for later use. + """ + # Store original CDA for later use + self.original_cda = cda_request.document + + # Convert CDA to FHIR using the InteropEngine + fhir_resources = self.engine.to_fhir( + self.original_cda, src_format=FormatType.CDA + ) + + # Create a FHIR DocumentReference for the original CDA document + cda_document_reference = create_document_reference( + data=self.original_cda, + content_type="text/xml", + description="Original CDA Document processed by HealthChain", + attachment_title="Original CDA document in XML format", + ) + + # Extract any DocumentReference resources for notes + note_text = "" + doc = Document(data=note_text) # Create document with empty text initially + + # Create FHIR Bundle and add documents + doc.fhir.bundle = create_bundle() + doc.fhir.add_document_reference(cda_document_reference) + + problem_list = [] + medication_list = [] + allergy_list = [] + + for resource in fhir_resources: + if isinstance(resource, Condition): + problem_list.append(resource) + set_problem_list_item_category(resource) + elif isinstance(resource, MedicationStatement): + medication_list.append(resource) + elif isinstance(resource, AllergyIntolerance): + allergy_list.append(resource) + elif isinstance(resource, DocumentReference): + if ( + resource.content + and resource.content[0].attachment + and resource.content[0].attachment.data is not None + ): + content = read_content_attachment(resource) + if content is not None: + note_text = content[0]["data"] + self.note_document_reference = resource + else: + log.warning( + f"No content found in DocumentReference: {resource.id}" + ) + + doc.fhir.problem_list = problem_list + doc.fhir.medication_list = medication_list + doc.fhir.allergy_list = allergy_list + + # Update document text + doc.data = note_text + + # Add the note document reference + if self.note_document_reference is not None: + doc.fhir.add_document_reference( + self.note_document_reference, parent_id=cda_document_reference.id + ) + + return doc + + def format(self, document: Document) -> CdaResponse: + """ + Convert a Document object back to CDA format and return the response. + + This method takes a Document object containing FHIR resources (problems, + medications, allergies) and converts them back to CDA format using the + InteropEngine. It combines all resources from the document's FHIR lists + and includes the note document reference if available. + + Args: + document (Document): A Document object containing FHIR resources + in problem_list, medication_list, and allergy_list. + + Returns: + CdaResponse: A response object containing the CDA document generated + from the FHIR resources. + """ + # Collect all FHIR resources to convert to CDA + resources = [] + + if document.fhir.problem_list: + resources.extend(document.fhir.problem_list) + + if document.fhir.allergy_list: + resources.extend(document.fhir.allergy_list) + + if document.fhir.medication_list: + resources.extend(document.fhir.medication_list) + + # Add the note document reference + if self.note_document_reference is not None: + resources.append(self.note_document_reference) + + # Convert FHIR resources to CDA using InteropEngine + response_document = self.engine.from_fhir(resources, dest_format=FormatType.CDA) + + return CdaResponse(document=response_document) diff --git a/healthchain/io/cdsfhiradapter.py b/healthchain/io/cdsfhiradapter.py new file mode 100644 index 00000000..882071a3 --- /dev/null +++ b/healthchain/io/cdsfhiradapter.py @@ -0,0 +1,133 @@ +import logging +from typing import Optional, Any + +from fhir.resources.documentreference import DocumentReference + +from healthchain.io.containers import Document +from healthchain.io.base import BaseAdapter +from healthchain.models.requests.cdsrequest import CDSRequest +from healthchain.models.responses.cdsresponse import CDSResponse +from healthchain.fhir import read_content_attachment +from healthchain.models.hooks.prefetch import Prefetch + +log = logging.getLogger(__name__) + + +class CdsFhirAdapter(BaseAdapter[CDSRequest, CDSResponse]): + """ + CdsFhirAdapter class for handling FHIR (Fast Healthcare Interoperability Resources) documents + for CDS Hooks. + + This adapter facilitates the conversion between CDSRequest objects and Document objects, + as well as the creation of CDSResponse objects from processed Documents. Unlike CdaAdapter, + this adapter works directly with FHIR data and does not require interop conversion. + + Attributes: + hook_name (str): The name of the CDS Hook being used. + engine (Optional[Any]): Optional interoperability engine (not used by this adapter). + + Methods: + parse: Converts a CDSRequest object into a Document object. + format: Converts a Document object into a CDSResponse object. + """ + + def __init__(self, hook_name: str = None, engine: Optional[Any] = None): + """ + Initialize CdsFhirAdapter with hook name and optional engine. + + Args: + hook_name (str): The name of the CDS Hook being used. Defaults to None. + engine (Optional[Any]): Optional interoperability engine (not used by this adapter). + """ + super().__init__(engine=engine) + self.hook_name = hook_name + + def parse( + self, cds_request: CDSRequest, prefetch_document_key: Optional[str] = "document" + ) -> Document: + """ + Convert a CDSRequest object into a Document object. + + Takes a CDSRequest containing FHIR resources and extracts them into a Document object. + The Document will contain all prefetched FHIR resources in its fhir.prefetch_resources. + If a DocumentReference resource is provided via prefetch_document_key, its text content + will be extracted into Document.data. For multiple attachments, the text content will be + concatenated with newlines. + + Args: + cds_request (CDSRequest): The CDSRequest containing FHIR resources in its prefetch + and/or a FHIR server URL. + prefetch_document_key (str, optional): Key in the prefetch data containing a + DocumentReference resource whose text content should be extracted. + Defaults to "document". + + Returns: + Document: A Document object containing: + - All prefetched FHIR resources in fhir.prefetch_resources + - Any text content from the DocumentReference in data (empty string if none found) + - For multiple attachments, text content is concatenated with newlines + + Raises: + ValueError: If neither prefetch nor fhirServer is provided in cds_request + ValueError: If the prefetch data is invalid or cannot be processed + NotImplementedError: If fhirServer is provided (FHIR server support not implemented) + """ + if cds_request.prefetch is None and cds_request.fhirServer is None: + raise ValueError( + "Either prefetch or fhirServer must be provided to extract FHIR data!" + ) + + if cds_request.fhirServer is not None: + raise NotImplementedError("FHIR server is not implemented yet!") + + # Create an empty Document object + doc = Document(data="") + + # Validate the prefetch data + validated_prefetch = Prefetch(prefetch=cds_request.prefetch) + + # Set the prefetch resources + doc.fhir.prefetch_resources = validated_prefetch.prefetch + + # Extract text content from DocumentReference resource if provided + document_resource = validated_prefetch.prefetch.get(prefetch_document_key) + + if not document_resource: + log.warning( + f"No DocumentReference resource found in prefetch data with key {prefetch_document_key}" + ) + elif isinstance(document_resource, DocumentReference): + try: + attachments = read_content_attachment( + document_resource, include_data=True + ) + for attachment in attachments: + if len(attachments) > 1: + doc.data += attachment.get("data", "") + "\n" + else: + doc.data += attachment.get("data", "") + except Exception as e: + log.warning(f"Error extracting text from DocumentReference: {e}") + + return doc + + def format(self, document: Document) -> CDSResponse: + """ + Convert Document to CDSResponse. + + This method takes a Document object containing CDS cards and actions, + and converts them into a CDSResponse object that follows the CDS Hooks + specification. + + Args: + document (Document): The Document object containing CDS results. + + Returns: + CDSResponse: A response object containing CDS cards and optional system actions. + If no cards are found in the Document, an empty list of cards is returned. + """ + if document.cds.cards is None: + log.warning("No CDS cards found in Document, returning empty list of cards") + return CDSResponse(cards=[]) + + return CDSResponse(cards=document.cds.cards, systemActions=document.cds.actions) diff --git a/healthchain/pipeline/base.py b/healthchain/pipeline/base.py index f557c80e..540017ff 100644 --- a/healthchain/pipeline/base.py +++ b/healthchain/pipeline/base.py @@ -19,7 +19,6 @@ from dataclasses import dataclass, field from enum import Enum -from healthchain.io.base import BaseConnector from healthchain.io.containers import DataContainer from healthchain.pipeline.components.base import BaseComponent @@ -80,8 +79,7 @@ class BasePipeline(Generic[T], ABC): The BasePipeline class provides a framework for building modular data processing pipelines by allowing users to add, remove, and configure components with defined dependencies and - execution order. Components can be added at specific positions, grouped into stages, and - connected via input/output connectors. + execution order. Components can be added at specific positions and grouped into stages. This is an abstract base class that should be subclassed to create specific pipeline implementations. @@ -90,28 +88,23 @@ class BasePipeline(Generic[T], ABC): _components (List[PipelineNode[T]]): Ordered list of pipeline components _stages (Dict[str, List[Callable]]): Components grouped by processing stage _built_pipeline (Optional[Callable]): Compiled pipeline function - _input_connector (Optional[BaseConnector[T]]): Connector for processing input data - _output_connector (Optional[BaseConnector[T]]): Connector for processing output data _output_template (Optional[str]): Template string for formatting pipeline outputs - _model_config (Optional[ModelConfig]): Configuration for the pipeline model Example: - >>> class MyPipeline(BasePipeline[str]): + >>> class MyPipeline(BasePipeline[Document]): ... def configure_pipeline(self, config: ModelConfig) -> None: ... self.add_node(preprocess, stage="preprocessing") ... self.add_node(process, stage="processing") ... self.add_node(postprocess, stage="postprocessing") ... >>> pipeline = MyPipeline() - >>> result = pipeline("input text") + >>> result = pipeline(document) # Document → Document """ def __init__(self): self._components: List[PipelineNode[T]] = [] self._stages: Dict[str, List[Callable]] = {} self._built_pipeline: Optional[Callable] = None - self._input_connector: Optional[BaseConnector[T]] = None - self._output_connector: Optional[BaseConnector[T]] = None self._output_template: Optional[str] = None self._output_template_path: Optional[Path] = None @@ -350,10 +343,9 @@ def configure_pipeline(self, model_config: ModelConfig) -> None: This method should be implemented by subclasses to add specific components and configure the pipeline according to the given model configuration. The configuration typically involves: - 1. Setting up input/output connectors - 2. Adding model components based on the model source - 3. Adding any additional processing nodes - 4. Configuring the pipeline stages and execution order + 1. Adding model components based on the model source + 2. Adding any additional processing nodes + 3. Configuring the pipeline stages and execution order Args: model_config (ModelConfig): Configuration object containing: @@ -371,17 +363,12 @@ def configure_pipeline(self, model_config: ModelConfig) -> None: Example: >>> def configure_pipeline(self, config: ModelConfig): - ... # Add FHIR connector for input/output - ... connector = FhirConnector() - ... self.add_input(connector) - ... ... # Add model component ... model = self.get_model_component(config) ... self.add_node(model, stage="processing") ... ... # Add output formatting ... self.add_node(OutputFormatter(), stage="formatting") - ... self.add_output(connector) """ raise NotImplementedError("This method must be implemented by subclasses.") @@ -419,44 +406,6 @@ def stages(self, new_stages: Dict[str, List[Callable]]): """ self._stages = new_stages - def add_input(self, connector: BaseConnector[T]) -> None: - """ - Adds an input connector to the pipeline. - - This method sets the input connector for the pipeline, which is responsible - for processing the input data before it's passed to the pipeline components. - - Args: - connector (Connector[T]): The input connector to be added to the pipeline. - - Returns: - None - - Note: - Only one input connector can be set for the pipeline. If this method is - called multiple times, the last connector will overwrite the previous ones. - """ - self._input_connector = connector - - def add_output(self, connector: BaseConnector[T]) -> None: - """ - Adds an output connector to the pipeline. - - This method sets the output connector for the pipeline, which is responsible - for processing the output data after it has passed through all pipeline components. - - Args: - connector (Connector[T]): The output connector to be added to the pipeline. - - Returns: - None - - Note: - Only one output connector can be set for the pipeline. If this method is - called multiple times, the last connector will overwrite the previous ones. - """ - self._output_connector = connector - def add_node( self, component: Union[ @@ -771,15 +720,10 @@ def resolve_dependencies(): ordered_components = resolve_dependencies() def pipeline(data: Union[T, DataContainer[T]]) -> DataContainer[T]: - if self._input_connector: - data = self._input_connector.input(data) - if not isinstance(data, DataContainer): data = DataContainer(data) data = reduce(lambda d, comp: comp(d), ordered_components, data) - if self._output_connector: - data = self._output_connector.output(data) return data diff --git a/healthchain/pipeline/medicalcodingpipeline.py b/healthchain/pipeline/medicalcodingpipeline.py index eb70fc16..301ab1a0 100644 --- a/healthchain/pipeline/medicalcodingpipeline.py +++ b/healthchain/pipeline/medicalcodingpipeline.py @@ -1,36 +1,24 @@ -from healthchain.io.cdaconnector import CdaConnector from healthchain.pipeline.base import BasePipeline, ModelConfig from healthchain.pipeline.mixins import ModelRoutingMixin class MedicalCodingPipeline(BasePipeline, ModelRoutingMixin): """ - A pipeline for medical coding tasks using NLP models. - - This pipeline processes clinical documents using medical NLP models to extract - and code medical concepts. It uses CDA format for input/output handling and - supports named entity recognition and linking (NER+L) to medical ontologies. - - The pipeline consists of the following stages: - 1. Input: CDA connector loads clinical documents - 2. NER+L: Medical NLP model extracts and links medical concepts - 3. Output: Returns coded results via CDA connector - - Examples: - >>> # Using with SpaCy/MedCAT - >>> pipeline = MedicalCodingPipeline.from_model_id("medcatlite", source="spacy") - >>> cda_response = pipeline(documents) - >>> - >>> # Using with Hugging Face - >>> pipeline = MedicalCodingPipeline.from_model_id( - ... "bert-base-uncased", - ... task="ner" - ... ) - >>> # Using with LangChain + Pipeline for extracting and coding medical concepts from clinical documents using NLP models. + + Stages: + 1. NER+L: Extracts and links medical concepts from document text. + + Usage Examples: + # With SpaCy + >>> pipeline = MedicalCodingPipeline.from_model_id("en_core_sci_sm", source="spacy") + + # With Hugging Face + >>> pipeline = MedicalCodingPipeline.from_model_id("bert-base-uncased", task="ner") + + # With LangChain >>> chain = ChatPromptTemplate.from_template("Extract medical codes: {text}") | ChatOpenAI() >>> pipeline = MedicalCodingPipeline.load(chain) - >>> - >>> cda_response = pipeline(documents) """ def __init__(self): @@ -38,15 +26,36 @@ def __init__(self): ModelRoutingMixin.__init__(self) def configure_pipeline(self, config: ModelConfig) -> None: - """Configure pipeline with CDA connector and NER+L model. + """Configure pipeline with NER+L model. Args: config (ModelConfig): Configuration for the NER+L model """ - cda_connector = CdaConnector() config.task = "ner" # set task if hf model = self.get_model_component(config) - self.add_input(cda_connector) self.add_node(model, stage="ner+l") - self.add_output(cda_connector) + + def process_request(self, request, adapter=None): + """ + Process a CDA request and return CDA response using an adapter. + + Args: + request: CdaRequest object + adapter: Optional CdaAdapter instance + + Returns: + CdaResponse: Processed response + + Example: + >>> pipeline = MedicalCodingPipeline.from_model_id("en_core_sci_sm", source="spacy") + >>> response = pipeline.process_request(cda_request) # CdaRequest → CdaResponse + """ + if adapter is None: + from healthchain.io import CdaAdapter + + adapter = CdaAdapter() + + doc = adapter.parse(request) + doc = self(doc) + return adapter.format(doc) diff --git a/healthchain/pipeline/summarizationpipeline.py b/healthchain/pipeline/summarizationpipeline.py index d13091de..fd3eb21f 100644 --- a/healthchain/pipeline/summarizationpipeline.py +++ b/healthchain/pipeline/summarizationpipeline.py @@ -2,34 +2,23 @@ from healthchain.pipeline.components import CdsCardCreator from healthchain.pipeline.modelrouter import ModelConfig from healthchain.pipeline.mixins import ModelRoutingMixin -from healthchain.io import CdsFhirConnector class SummarizationPipeline(BasePipeline, ModelRoutingMixin): """ - A pipeline for text summarization tasks using NLP models. - - This pipeline processes clinical documents using a summarization model to generate - concise summaries. It uses CDS FHIR format for input/output handling and creates - CDS Hooks cards containing the generated summaries. - - The pipeline consists of the following stages: - 1. Input: CDS FHIR connector loads clinical documents - 2. Summarization: NLP model generates summaries - 3. Card Creation: Formats summaries into CDS Hooks cards - 4. Output: Returns cards via CDS FHIR connector - - Examples: - >>> # Using with GPT model - >>> pipeline = SummarizationPipeline.from_model_id("gpt-4o", source="openai") - >>> cds_response = pipeline(documents) - >>> - >>> # Using with Hugging Face - >>> pipeline = SummarizationPipeline.from_model_id( - ... "facebook/bart-large-cnn", - ... task="summarization" - ... ) - >>> cds_response = pipeline(documents) + Pipeline for generating summaries from clinical documents using NLP models. + + Stages: + 1. Summarization: Generates summaries from document text. + 2. Card Creation: Formats summaries into CDS Hooks cards. + + Usage Examples: + # With Hugging Face + >>> pipeline = SummarizationPipeline.from_model_id("facebook/bart-large-cnn", source="huggingface") + + # With LangChain + >>> chain = ChatPromptTemplate.from_template("Summarize: {text}") | ChatOpenAI() + >>> pipeline = SummarizationPipeline.load(chain) """ def __init__(self): @@ -37,16 +26,14 @@ def __init__(self): ModelRoutingMixin.__init__(self) def configure_pipeline(self, config: ModelConfig) -> None: - """Configure pipeline with FHIR connector and summarization model. + """Configure pipeline with summarization model and card creator. Args: config: Model configuration for the summarization model """ - cds_fhir_connector = CdsFhirConnector(hook_name="encounter-discharge") config.task = "summarization" model = self.get_model_component(config) - self.add_input(cds_fhir_connector) self.add_node(model, stage="summarization") self.add_node( CdsCardCreator( @@ -58,4 +45,28 @@ def configure_pipeline(self, config: ModelConfig) -> None: ), stage="card-creation", ) - self.add_output(cds_fhir_connector) + + def process_request(self, request, hook_name=None, adapter=None): + """ + Process a CDS request and return CDS response using an adapter. + + Args: + request: CDSRequest object + hook_name: CDS hook name for the adapter + adapter: Optional CdsFhirAdapter instance + + Returns: + CDSResponse: Processed CDS response with cards + + Example: + >>> pipeline = SummarizationPipeline.from_model_id("facebook/bart-large-cnn", source="huggingface") + >>> response = pipeline.process_request(cds_request) # CDSRequest → CDSResponse + """ + if adapter is None: + from healthchain.io import CdsFhirAdapter + + adapter = CdsFhirAdapter(hook_name=hook_name) + + doc = adapter.parse(request) + doc = self(doc) + return adapter.format(doc) From e0384518493191bfd867786c2bbbbffb6f272f3c Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Thu, 31 Jul 2025 17:29:49 +0100 Subject: [PATCH 02/19] Update tests --- tests/pipeline/conftest.py | 41 ++++++++++ tests/pipeline/prebuilt/test_medicalcoding.py | 73 +++++++++++------- tests/pipeline/prebuilt/test_summarization.py | 77 +++++++++++++------ tests/pipeline/test_pipeline.py | 19 ----- 4 files changed, 138 insertions(+), 72 deletions(-) diff --git a/tests/pipeline/conftest.py b/tests/pipeline/conftest.py index 349b5a62..45299a6d 100644 --- a/tests/pipeline/conftest.py +++ b/tests/pipeline/conftest.py @@ -137,6 +137,47 @@ def mock_cda_connector(test_document): yield mock +# Adapter fixtures + + +@pytest.fixture +def mock_cda_adapter(): + with patch("healthchain.io.cdaadapter.CdaAdapter") as mock: + adapter_instance = mock.return_value + + # Mock parse method + adapter_instance.parse.return_value = Document(data="Test note") + + # Mock format method + adapter_instance.format.return_value = CdaResponse( + document="Updated CDA" + ) + + yield mock + + +@pytest.fixture +def mock_cds_fhir_adapter(): + with patch("healthchain.io.cdsfhiradapter.CdsFhirAdapter") as mock: + adapter_instance = mock.return_value + + # Mock parse method + adapter_instance.parse.return_value = Document(data="Test CDS data") + + # Mock format method + adapter_instance.format.return_value = CDSResponse( + cards=[ + Card( + summary="Summarized discharge information", + indicator="info", + source={"label": "Test Source"}, + ) + ] + ) + + yield mock + + # NLP component fixtures diff --git a/tests/pipeline/prebuilt/test_medicalcoding.py b/tests/pipeline/prebuilt/test_medicalcoding.py index 05f5f205..dd3f62df 100644 --- a/tests/pipeline/prebuilt/test_medicalcoding.py +++ b/tests/pipeline/prebuilt/test_medicalcoding.py @@ -3,12 +3,12 @@ from healthchain.models.responses.cdaresponse import CdaResponse from healthchain.pipeline.base import ModelConfig, ModelSource from healthchain.pipeline.medicalcodingpipeline import MedicalCodingPipeline +from healthchain.io.containers import Document -def test_coding_pipeline(mock_cda_connector, mock_spacy_nlp): +def test_coding_pipeline(mock_spacy_nlp, test_document): + """Test pure pipeline processing (Document → Document)""" with patch( - "healthchain.pipeline.medicalcodingpipeline.CdaConnector", mock_cda_connector - ), patch( "healthchain.pipeline.mixins.ModelRoutingMixin.get_model_component", mock_spacy_nlp, ): @@ -21,19 +21,13 @@ def test_coding_pipeline(mock_cda_connector, mock_spacy_nlp): ) pipeline.configure_pipeline(config) - # Create a sample CdaRequest - test_cda_request = CdaRequest(document="Sample CDA") - - # Process the request through the pipeline - cda_response = pipeline(test_cda_request) - - # Assertions - assert isinstance(cda_response, CdaResponse) - assert cda_response.document == "Updated CDA" + # Process Document through pure pipeline + result_doc = pipeline(test_document) - # Verify that CdaConnector methods were called correctly - mock_cda_connector.return_value.input.assert_called_once_with(test_cda_request) - mock_cda_connector.return_value.output.assert_called_once() + # Assertions - pipeline should return processed Document + assert isinstance(result_doc, Document) + assert result_doc.data == "Test note" + assert result_doc.fhir.problem_list[0].code.coding[0].display == "Hypertension" # Verify that the Model was called mock_spacy_nlp.assert_called_once_with( @@ -47,25 +41,45 @@ def test_coding_pipeline(mock_cda_connector, mock_spacy_nlp): ) mock_spacy_nlp.return_value.assert_called_once() - # Verify the pipeline used the mocked input and output - input_doc = mock_cda_connector.return_value.input.return_value - assert input_doc.data == "Test note" - assert input_doc.fhir.problem_list[0].code.coding[0].display == "Hypertension" - assert ( - input_doc.fhir.medication_list[0].medication.concept.coding[0].display - == "Aspirin" - ) - assert ( - input_doc.fhir.allergy_list[0].code.coding[0].display - == "Allergy to peanuts" - ) - # Verify stages are set correctly assert len(pipeline._stages) == 1 assert "ner+l" in pipeline._stages +def test_coding_pipeline_process_request(mock_spacy_nlp, mock_cda_adapter): + """Test process_request method with adapter""" + with patch( + "healthchain.pipeline.mixins.ModelRoutingMixin.get_model_component", + mock_spacy_nlp, + ), patch("healthchain.io.CdaAdapter", mock_cda_adapter): + pipeline = MedicalCodingPipeline() + config = ModelConfig( + source=ModelSource.SPACY, + pipeline_object="en_core_sci_sm", + path=None, + kwargs={}, + ) + pipeline.configure_pipeline(config) + + # Create a sample CdaRequest + test_cda_request = CdaRequest(document="Sample CDA") + + # Process via convenience method + cda_response = pipeline.process_request(test_cda_request) + + # Assertions + assert isinstance(cda_response, CdaResponse) + + # Verify adapter was used correctly + mock_cda_adapter.return_value.parse.assert_called_once_with(test_cda_request) + mock_cda_adapter.return_value.format.assert_called_once() + + # Verify model was called + mock_spacy_nlp.return_value.assert_called_once() + + def test_full_coding_pipeline_integration(mock_spacy_nlp, test_cda_request): + """Test integration with process_request method""" with patch( "healthchain.pipeline.mixins.ModelRoutingMixin.get_model_component", mock_spacy_nlp, @@ -74,7 +88,8 @@ def test_full_coding_pipeline_integration(mock_spacy_nlp, test_cda_request): "./spacy/path/to/production/model", source="spacy" ) - cda_response = pipeline(test_cda_request) + # Use process_request for end-to-end processing + cda_response = pipeline.process_request(test_cda_request) assert isinstance(cda_response, CdaResponse) diff --git a/tests/pipeline/prebuilt/test_summarization.py b/tests/pipeline/prebuilt/test_summarization.py index 392cb984..a98aa00e 100644 --- a/tests/pipeline/prebuilt/test_summarization.py +++ b/tests/pipeline/prebuilt/test_summarization.py @@ -2,19 +2,16 @@ from healthchain.models.responses.cdsresponse import CDSResponse from healthchain.pipeline.base import ModelConfig, ModelSource from healthchain.pipeline.summarizationpipeline import SummarizationPipeline +from healthchain.io.containers import Document def test_summarization_pipeline( - mock_cds_fhir_connector, mock_hf_transformer, mock_cds_card_creator, - test_cds_request, - test_condition, + test_document, ): + """Test pure pipeline processing (Document → Document)""" with patch( - "healthchain.pipeline.summarizationpipeline.CdsFhirConnector", - mock_cds_fhir_connector, - ), patch( "healthchain.pipeline.mixins.ModelRoutingMixin.get_model_component", mock_hf_transformer, ), patch( @@ -31,19 +28,11 @@ def test_summarization_pipeline( ) pipeline.configure_pipeline(config) - # Process the request through the pipeline - cds_response = pipeline(test_cds_request) + # Process Document through pure pipeline + result_doc = pipeline(test_document) - # Assertions - assert isinstance(cds_response, CDSResponse) - assert len(cds_response.cards) == 1 - assert cds_response.cards[0].summary == "Summarized discharge information" - - # Verify that CdsFhirConnector methods were called correctly - mock_cds_fhir_connector.return_value.input.assert_called_once_with( - test_cds_request - ) - mock_cds_fhir_connector.return_value.output.assert_called_once() + # Assertions - pipeline should return processed Document + assert isinstance(result_doc, Document) # Verify that the LLM was called mock_hf_transformer.assert_called_once_with( @@ -65,20 +54,59 @@ def test_summarization_pipeline( delimiter="\n", ) - # Verify the pipeline used the mocked input and output - input_data = mock_cds_fhir_connector.return_value.input.return_value - - assert input_data.fhir.get_prefetch_resources("problem") == test_condition - # Verify stages are set correctly assert len(pipeline._stages) == 2 assert "summarization" in pipeline._stages assert "card-creation" in pipeline._stages +def test_summarization_pipeline_process_request( + mock_hf_transformer, + mock_cds_card_creator, + mock_cds_fhir_adapter, + test_cds_request, +): + """Test process_request method with adapter""" + with patch( + "healthchain.pipeline.mixins.ModelRoutingMixin.get_model_component", + mock_hf_transformer, + ), patch( + "healthchain.pipeline.summarizationpipeline.CdsCardCreator", + mock_cds_card_creator, + ), patch( + "healthchain.io.CdsFhirAdapter", + mock_cds_fhir_adapter, + ): + pipeline = SummarizationPipeline() + config = ModelConfig( + source=ModelSource.HUGGINGFACE, + pipeline_object="llama3", + task="summarization", + path=None, + kwargs={}, + ) + pipeline.configure_pipeline(config) + + # Process via convenience method + cds_response = pipeline.process_request(test_cds_request) + + # Assertions + assert isinstance(cds_response, CDSResponse) + + # Verify adapter was used correctly + mock_cds_fhir_adapter.return_value.parse.assert_called_once_with( + test_cds_request + ) + mock_cds_fhir_adapter.return_value.format.assert_called_once() + + # Verify model was called + mock_hf_transformer.return_value.assert_called_once() + + def test_full_summarization_pipeline_integration( mock_hf_transformer, test_cds_request, tmp_path ): + """Test integration with process_request method""" # Use mock LLM object for now with patch( "healthchain.pipeline.mixins.ModelRoutingMixin.get_model_component", @@ -100,7 +128,8 @@ def test_full_summarization_pipeline_integration( "llama3", source="huggingface", template_path=template_file ) - cds_response = pipeline(test_cds_request) + # Use process_request for end-to-end processing + cds_response = pipeline.process_request(test_cds_request) assert isinstance(cds_response, CDSResponse) assert len(cds_response.cards) == 1 diff --git a/tests/pipeline/test_pipeline.py b/tests/pipeline/test_pipeline.py index 03351d07..d9e02b78 100644 --- a/tests/pipeline/test_pipeline.py +++ b/tests/pipeline/test_pipeline.py @@ -187,25 +187,6 @@ def test_build_and_execute_pipeline(mock_basic_pipeline): mock_basic_pipeline.build() -def test_pipeline_with_connectors(mock_basic_pipeline): - # Test with input and output connectors - class MockConnector: - def input(self, data): - data.data += 10 - return data - - def output(self, data): - data.data *= 2 - return data - - mock_basic_pipeline.add_input(MockConnector()) - mock_basic_pipeline.add_node(mock_component) - mock_basic_pipeline.add_output(MockConnector()) - - result = mock_basic_pipeline(DataContainer(1)) - assert result.data == 24 # (1 + 10 + 1) * 2 - - # Test input and output model validation def test_input_output_validation(mock_basic_pipeline): def validated_component(data: DataContainer) -> DataContainer: From b643877e2458022dabfe8ac43dce364357dd4e88 Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Thu, 31 Jul 2025 17:30:16 +0100 Subject: [PATCH 03/19] Update docs --- docs/api/adapters.md | 4 + docs/api/connectors.md | 4 +- docs/cookbook/cds_sandbox.md | 8 +- docs/cookbook/notereader_sandbox.md | 4 +- docs/quickstart.md | 25 +++--- docs/reference/gateway/cdshooks.md | 56 +++++++++++++ docs/reference/pipeline/adapters/adapters.md | 84 +++++++++++++++++++ .../reference/pipeline/adapters/cdaadapter.md | 35 ++++++++ .../pipeline/adapters/cdsfhiradapter.md | 72 ++++++++++++++++ .../pipeline/connectors/cdaconnector.md | 6 +- .../pipeline/connectors/cdsfhirconnector.md | 6 +- .../pipeline/connectors/connectors.md | 34 +++++++- docs/reference/pipeline/pipeline.md | 49 +++++++---- mkdocs.yml | 7 +- 14 files changed, 352 insertions(+), 42 deletions(-) create mode 100644 docs/api/adapters.md create mode 100644 docs/reference/pipeline/adapters/adapters.md create mode 100644 docs/reference/pipeline/adapters/cdaadapter.md create mode 100644 docs/reference/pipeline/adapters/cdsfhiradapter.md diff --git a/docs/api/adapters.md b/docs/api/adapters.md new file mode 100644 index 00000000..889ca1d1 --- /dev/null +++ b/docs/api/adapters.md @@ -0,0 +1,4 @@ +# Adapters + +::: healthchain.io.cdaadapter +::: healthchain.io.cdsfhiradapter diff --git a/docs/api/connectors.md b/docs/api/connectors.md index c633cc11..dacd769d 100644 --- a/docs/api/connectors.md +++ b/docs/api/connectors.md @@ -1,4 +1,6 @@ -# Connectors +# Connectors (Legacy) + +> **⚠️ Deprecated:** Connectors are deprecated. Use [Adapters](adapters.md) for new projects. ::: healthchain.io.base ::: healthchain.io.cdaconnector diff --git a/docs/cookbook/cds_sandbox.md b/docs/cookbook/cds_sandbox.md index 12467033..2cfe69ef 100644 --- a/docs/cookbook/cds_sandbox.md +++ b/docs/cookbook/cds_sandbox.md @@ -35,7 +35,7 @@ If you are using a chat model, we recommend you initialize the pipeline with the === "Non-chat model" ```python - from healthchain.pipelines import SummarizationPipeline + from healthchain.pipeline import SummarizationPipeline pipeline = SummarizationPipeline.from_model_id( "google/pegasus-xsum", source="huggingface", task="summarization" @@ -45,7 +45,7 @@ If you are using a chat model, we recommend you initialize the pipeline with the === "Chat model" ```python - from healthchain.pipelines import SummarizationPipeline + from healthchain.pipeline import SummarizationPipeline from langchain_huggingface.llms import HuggingFaceEndpoint from langchain_huggingface import ChatHuggingFace @@ -96,7 +96,7 @@ class DischargeNoteSummarizer(ClinicalDecisionSupport): @hc.api def my_service(self, request: CDSRequest) -> CDSResponse: - result = self.pipeline(request) + result = self.pipeline.process_request(request) return result ``` @@ -147,7 +147,7 @@ class DischargeNoteSummarizer(ClinicalDecisionSupport): @hc.api def my_service(self, request: CDSRequest) -> CDSResponse: - result = self.pipeline(request) + result = self.pipeline.process_request(request) return result @hc.ehr(workflow="encounter-discharge") diff --git a/docs/cookbook/notereader_sandbox.md b/docs/cookbook/notereader_sandbox.md index 8c9573e7..1e6cdcc8 100644 --- a/docs/cookbook/notereader_sandbox.md +++ b/docs/cookbook/notereader_sandbox.md @@ -10,7 +10,7 @@ import healthchain as hc from healthchain.io import Document from healthchain.models.requests import CdaRequest from healthchain.models.responses import CdaResponse -from healthchain.pipeline.medicalcodingpipeline import MedicalCodingPipeline +from healthchain.pipeline import MedicalCodingPipeline from healthchain.sandbox.use_cases import ClinicalDocumentation from healthchain.fhir import create_document_reference @@ -64,7 +64,7 @@ class NotereaderSandbox(ClinicalDocumentation): @hc.api def my_service(self, request: CdaRequest) -> CdaResponse: - result = self.pipeline(request) + result = self.pipeline.process_request(request) return result diff --git a/docs/quickstart.md b/docs/quickstart.md index 660ca885..b586dade 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -102,30 +102,31 @@ doc = Document("Patient presents with hypertension.") output = pipe(doc) ``` -Let's go one step further! You can use [Connectors](./reference/pipeline/connectors/connectors.md) to work directly with [CDA](https://www.hl7.org.uk/standards/hl7-standards/cda-clinical-document-architecture/) and [FHIR](https://hl7.org/fhir/) data received from healthcare system APIs. Add Connectors to your pipeline with the `.add_input()` and `.add_output()` methods. +Let's go one step further! You can use [Adapters](./reference/pipeline/adapters/adapters.md) to work directly with [CDA](https://www.hl7.org.uk/standards/hl7-standards/cda-clinical-document-architecture/) and [FHIR](https://hl7.org/fhir/) data received from healthcare system APIs. Adapters handle format conversion while keeping your pipeline pure ML processing. ```python from healthchain.pipeline import Pipeline from healthchain.pipeline.components import SpacyNLP -from healthchain.io import CdaConnector +from healthchain.io import CdaAdapter from healthchain.models import CdaRequest pipeline = Pipeline() -cda_connector = CdaConnector() - -pipeline.add_input(cda_connector) pipeline.add_node(SpacyNLP.from_model_id("en_core_sci_sm")) -pipeline.add_output(cda_connector) - pipe = pipeline.build() -cda_data = CdaRequest(document="") -output = pipe(cda_data) +# Use adapter for format conversion +adapter = CdaAdapter() +cda_request = CdaRequest(document="") + +# Parse, process, format +doc = adapter.parse(cda_request) +processed_doc = pipe(doc) +output = adapter.format(processed_doc) ``` #### 3. Use Prebuilt Pipelines -Prebuilt pipelines are pre-configured collections of Components, Models, and Connectors. They are built for specific use cases, offering the highest level of abstraction. This is the easiest way to get started if you already know the use case you want to build for. +Prebuilt pipelines are pre-configured collections of Components and Models optimized for specific healthcare AI use cases. They offer the highest level of abstraction and are the easiest way to get started. For a full list of available prebuilt pipelines and details on how to configure and customize them, see the [Pipelines](./reference/pipeline/pipeline.md) documentation page. @@ -143,8 +144,8 @@ pipeline = MedicalCodingPipeline.from_model_id("facebook/bart-large-cnn", source # Or load from local model pipeline = MedicalCodingPipeline.from_local_model("./path/to/model", source="spacy") -cda_data = CdaRequest(document="") -output = pipeline(cda_data) +cda_request = CdaRequest(document="") +output = pipeline.process_request(cda_request) ``` ### Interoperability 🔄 diff --git a/docs/reference/gateway/cdshooks.md b/docs/reference/gateway/cdshooks.md index 6564a4a4..c57412ca 100644 --- a/docs/reference/gateway/cdshooks.md +++ b/docs/reference/gateway/cdshooks.md @@ -103,3 +103,59 @@ When registered with HealthChainAPI, the following endpoints are automatically c - `MedicationRequest` For more information, see the [official CDS Hooks documentation](https://cds-hooks.org/). + +## Advanced Workflow Example + +This example demonstrates how to build a custom CDS Hooks workflow that performs advanced clinical analysis and generates tailored decision support cards. By combining adapters and a custom pipeline, you can process incoming FHIR data, apply your own logic (such as risk assessment), and return dynamic CDS cards to the EHR. + +```python +from healthchain.io import CdsFhirAdapter, Document +from healthchain.pipeline import Pipeline +from healthchain.pipeline.components import CdsCardCreator +from healthchain.models import CDSRequest, CDSResponse +from healthchain.gateway import HealthChainAPI, CDSHooksService + +# Build custom pipeline with analysis and card creation +pipeline = Pipeline([Document]) + +@pipeline.add_node +def analyze_patient_data(doc: Document) -> Document: + """Custom function to analyze patient data and document content""" + # Access FHIR prefetch resources + patient = doc.fhir.get_prefetch_resources("patient") + document_ref = doc.fhir.get_prefetch_resources("document") + + # Perform custom analysis + if patient: + age = 2024 - int(patient.birthDate[:4]) # Simple age calculation + if age > 65: + doc._custom_analysis = {"high_risk": True, "reason": "Age > 65"} + else: + doc._custom_analysis = {"high_risk": False} + return doc + +# Add card creator to format output +pipeline.add_node(CdsCardCreator( + template='{"summary": "Risk Assessment", "detail": "Patient risk level: {{ high_risk }}"}' +)) + +pipe = pipeline.build() + +# Set up CDS service with custom workflow +app = HealthChainAPI() +cds = CDSHooksService() + +@cds.hook("encounter-discharge", id="risk-assessment") +def assess_patient_risk(request: CDSRequest) -> CDSResponse: + # Use adapter for explicit format conversion + adapter = CdsFhirAdapter() + + # Manual conversion with full document access + doc = adapter.parse(request) # CDSRequest → Document + processed_doc = pipe(doc) # Custom analysis + card creation + + # Convert back to CDS response + return adapter.format(processed_doc) # Document → CDSResponse + +app.register_service(cds, path="/cds") +``` diff --git a/docs/reference/pipeline/adapters/adapters.md b/docs/reference/pipeline/adapters/adapters.md new file mode 100644 index 00000000..d6ca84cc --- /dev/null +++ b/docs/reference/pipeline/adapters/adapters.md @@ -0,0 +1,84 @@ +# Adapters + +Adapters handle conversion between healthcare data formats (CDA, FHIR) and HealthChain's internal `Document` objects. They enable clean separation between ML processing logic and healthcare format handling, making your pipelines more maintainable and testable. + +Unlike the legacy connector pattern, adapters are used explicitly and provide clear control over data flow. + +## Available adapters + +Adapters parse data from specific healthcare formats into FHIR resources and store them in a `Document` container for processing. + +([Document API Reference](../../../api/containers.md#healthchain.io.containers.document.Document)) + +| Adapter | Input Format | Output Format | FHIR Resources | Document Access | +|---------|--------------|---------------|----------------|-----------------| +| [**CdaAdapter**](cdaadapter.md) | `CdaRequest` | `CdaResponse` | [**DocumentReference**](https://www.hl7.org/fhir/documentreference.html) | `Document.text`, `Document.fhir.problem_list`, `Document.fhir.medication_list`, `Document.fhir.allergy_list` | +| [**CdsFhirAdapter**](cdsfhiradapter.md) | `CDSRequest` | `CDSResponse` | [**Any FHIR Resource**](https://www.hl7.org/fhir/resourcelist.html) | `Document.fhir.get_prefetch_resources()` | + +## Use Cases +Each adapter is designed for specific healthcare integration scenarios. + +| Adapter | Use Case | Protocol | +|---------|----------|----------| +| `CdaAdapter` | [**Clinical Documentation**](../../gateway/soap_cda.md) | SOAP/CDA | +| `CdsFhirAdapter` | [**Clinical Decision Support**](../../gateway/cdshooks.md) | CDS Hooks/FHIR | + +## Usage Patterns + +### 1. Simple End-to-End Processing + +Use prebuilt pipelines with the `process_request()` method for straightforward workflows: + +```python +from healthchain.pipeline import MedicalCodingPipeline +from healthchain.models import CdaRequest + +pipeline = MedicalCodingPipeline.from_model_id("en_core_sci_sm", source="spacy") +cda_request = CdaRequest(document="") + +# Adapter used internally +response = pipeline.process_request(cda_request) +``` + +### 2. Manual Adapter Control (Document Access) + +Use adapters `parse()` and `format()` methods directly when you need access to the intermediate `Document` object: + +```python +from healthchain.io import CdaAdapter +from healthchain.pipeline import MedicalCodingPipeline +from healthchain.models import CdaRequest + +pipeline = MedicalCodingPipeline.from_model_id("en_core_sci_sm", source="spacy") +adapter = CdaAdapter() + +cda_request = CdaRequest(document="") + +# Manual adapter control +doc = adapter.parse(cda_request) # CdaRequest → Document +doc = pipeline(doc) # Document → Document (pure ML) + +# Access extracted clinical data +print(f"Problems: {doc.fhir.problem_list}") +print(f"Medications: {doc.fhir.medication_list}") +print(f"Allergies: {doc.fhir.allergy_list}") + +# Convert back to healthcare format +response = adapter.format(doc) # Document → CdaResponse +``` + +## Adapter Configuration + +### Custom Interop Engine + +Both CDA and CDS adapters can be configured with custom interoperability engines. By default, the adapter uses the built-in InteropEngine with default CDA templates. + +```python +from healthchain.io import CdaAdapter +from healthchain.interop import create_engine + +# Custom engine with specific configuration +custom_engine = create_engine(config_dir="/path/to/custom/config") +adapter = CdaAdapter(engine=custom_engine) +``` +For more information on the InteropEngine, see the [InteropEngine documentation](../../interop/interop.md). diff --git a/docs/reference/pipeline/adapters/cdaadapter.md b/docs/reference/pipeline/adapters/cdaadapter.md new file mode 100644 index 00000000..2da4301b --- /dev/null +++ b/docs/reference/pipeline/adapters/cdaadapter.md @@ -0,0 +1,35 @@ +# CDA Adapter + +The `CdaAdapter` handles conversion between CDA (Clinical Document Architecture) documents and HealthChain's internal `Document` objects. It parses CDA documents to extract free-text notes and structured clinical data into FHIR resources, and can convert processed Documents back into annotated CDA format. + +This adapter is particularly useful for clinical documentation improvement (CDI) workflows where documents need to be processed with ML models and updated with additional structured data. + +[(Full Documentation on Clinical Documentation)](../../gateway/soap_cda.md) + +## Input and Output + +| Input | Output | Document Access | +|-------|--------|-----------------| +| [**CdaRequest**](../../../api/use_cases.md#healthchain.models.requests.cdarequest.CdaRequest) | [**CdaResponse**](../../../api/use_cases.md#healthchain.models.responses.cdaresponse.CdaResponse) | `Document.fhir.problem_list`, `Document.fhir.medication_list`, `Document.fhir.allergy_list`, `Document.text` | + +## Document Data Access + +Data parsed from the CDA document is converted into FHIR resources and stored in the `Document.fhir` attribute. The adapter supports the following CDA section to FHIR resource mappings: + +| CDA Section | FHIR Resource | Document.fhir Attribute | +|-------------|---------------|--------------------------| +| Problem List | [Condition](https://www.hl7.org/fhir/condition.html) | `Document.fhir.problem_list` | +| Medication List | [MedicationStatement](https://www.hl7.org/fhir/medicationstatement.html) | `Document.fhir.medication_list` | +| Allergy List | [AllergyIntolerance](https://www.hl7.org/fhir/allergyintolerance.html) | `Document.fhir.allergy_list` | +| Clinical Notes | [DocumentReference](https://www.hl7.org/fhir/documentreference.html) | `Document.text` + `Document.fhir.bundle` | + +All FHIR resources are Pydantic models, so you can access them using the `model_dump()` method: + +```python +# Access structured clinical data +for condition in doc.fhir.problem_list: + print(condition.model_dump()) + +# Access free-text content +print(f"Clinical notes: {doc.text}") +``` diff --git a/docs/reference/pipeline/adapters/cdsfhiradapter.md b/docs/reference/pipeline/adapters/cdsfhiradapter.md new file mode 100644 index 00000000..55ef62f1 --- /dev/null +++ b/docs/reference/pipeline/adapters/cdsfhiradapter.md @@ -0,0 +1,72 @@ +# CDS FHIR Adapter + +The `CdsFhirAdapter` handles conversion between CDS Hooks requests/responses and HealthChain's internal `Document` objects. It processes FHIR data in the context of Clinical Decision Support (CDS) services, following the [CDS Hooks specification](https://cds-hooks.org/). + +This adapter is specifically designed for building CDS services that receive FHIR data through prefetch and return clinical decision cards. + +[(Full Documentation on Clinical Decision Support)](../../gateway/cdshooks.md) + +## Input and Output + +| Input | Output | Document Access | +|-------|--------|-----------------| +| [**CDSRequest**](../../../api/use_cases.md#healthchain.models.requests.cdsrequest.CDSRequest) | [**CDSResponse**](../../../api/use_cases.md#healthchain.models.responses.cdsresponse.CDSResponse) | `Document.fhir.get_prefetch_resources()`, `Document.cds.cards` | + + +## Document Data Access + +### FHIR Prefetch Resources + +Data from the CDS request's `prefetch` field is stored in the `Document.fhir.prefetch_resources` attribute as a dictionary mapping prefetch keys to FHIR resources: + +```python +# After processing with adapter.parse() +doc = adapter.parse(cds_request) + +# Access prefetch resources by key +patient = doc.fhir.get_prefetch_resources("patient") +conditions = doc.fhir.get_prefetch_resources("condition") +document_ref = doc.fhir.get_prefetch_resources("document") + +# Access all prefetch resources +all_resources = doc.fhir.prefetch_resources +for key, resource in all_resources.items(): + print(f"Resource '{key}': {resource.resourceType}") +``` + +### CDS Cards + +Generated CDS cards are stored in the `Document.cds.cards` attribute: + +```python +# After ML processing +for card in processed_doc.cds.cards: + print(f"Summary: {card.summary}") + print(f"Indicator: {card.indicator}") + print(f"Detail: {card.detail}") + print(f"Source: {card.source}") +``` + +### Document Text Extraction + +When the prefetch contains a `DocumentReference` resource, the adapter automatically extracts the document content and stores it in `Document.text`: + +```python +# If prefetch contains document with base64 content +cds_request = CDSRequest( + prefetch={ + "document": { + "resourceType": "DocumentReference", + "content": [{ + "attachment": { + "contentType": "text/plain", + "data": "UGF0aWVudCBkaXNjaGFyZ2Ugbm90ZXM=" # base64 encoded + } + }] + } + } +) + +doc = adapter.parse(cds_request) +print(doc.text) # "Patient discharge notes" (decoded) +``` diff --git a/docs/reference/pipeline/connectors/cdaconnector.md b/docs/reference/pipeline/connectors/cdaconnector.md index a6338470..932e9dd0 100644 --- a/docs/reference/pipeline/connectors/cdaconnector.md +++ b/docs/reference/pipeline/connectors/cdaconnector.md @@ -1,8 +1,10 @@ -# CDA Connector +# CDA Connector (Legacy) + +> **⚠️ Deprecated:** `CdaConnector` is deprecated. Use [`CdaAdapter`](../adapters/cdaadapter.md) for new projects, which provides explicit control over data conversion and enables pure `Document → Document` pipeline processing. The `CdaConnector` parses CDA documents, extracting free-text notes and relevant structured clinical data into FHIR resources in the `Document` container, and returns an annotated CDA document as output. It will also extract the text from the note section of the document and store it in the `Document.text` attribute. -This connector is particularly useful for clinical documentation improvement (CDI) workflows where a document needs to be processed and updated with additional structured data. +**For new projects, use [`CdaAdapter`](../adapters/cdaadapter.md) instead.** [(Full Documentation on Clinical Documentation)](../../gateway/soap_cda.md) diff --git a/docs/reference/pipeline/connectors/cdsfhirconnector.md b/docs/reference/pipeline/connectors/cdsfhirconnector.md index 088dc8e4..2aa2a0aa 100644 --- a/docs/reference/pipeline/connectors/cdsfhirconnector.md +++ b/docs/reference/pipeline/connectors/cdsfhirconnector.md @@ -1,7 +1,11 @@ -# CDS FHIR Connector +# CDS FHIR Connector (Legacy) + +> **⚠️ Deprecated:** `CdsFhirConnector` is deprecated. Use [`CdsFhirAdapter`](../adapters/cdsfhiradapter.md) for new projects, which provides explicit control over data conversion and enables pure `Document → Document` pipeline processing. The `CdsFhirConnector` handles FHIR data in the context of Clinical Decision Support (CDS) services, specifically using the [CDS Hooks specification](https://cds-hooks.org/). +**For new projects, use [`CdsFhirAdapter`](../adapters/cdsfhiradapter.md) instead.** + [(Full Documentation on Clinical Decision Support)](../../gateway/cdshooks.md) ## Input and Output diff --git a/docs/reference/pipeline/connectors/connectors.md b/docs/reference/pipeline/connectors/connectors.md index 9e2f1463..ab857a0d 100644 --- a/docs/reference/pipeline/connectors/connectors.md +++ b/docs/reference/pipeline/connectors/connectors.md @@ -1,10 +1,38 @@ -# Connectors +# Connectors (Legacy) + +> **⚠️ Deprecated:** Connectors are being replaced by the new [Adapter pattern](../adapters/adapters.md). For new projects, use Adapters for cleaner separation between ML processing and healthcare format handling. Connectors transform your data into a format that can be understood by healthcare systems such as EHRs. They allow your pipelines to work directly with data in HL7 interoperability standard formats, such as [CDA](https://www.hl7.org.uk/standards/hl7-standards/cda-clinical-document-architecture/) or [FHIR](https://hl7.org/fhir/), without the headache of parsing and validating the data yourself. -Connectors are what give you the power to build *end-to-end* pipelines that interact with real-time healthcare systems. +**For new projects, consider using [Adapters](../adapters/adapters.md) instead**, which provide explicit control over data conversion and enable pure `Document → Document` pipeline processing. + +## Migration to Adapters + +| Legacy Connector | New Adapter | Migration Guide | +|------------------|-------------|-----------------| +| `CdaConnector` | [`CdaAdapter`](../adapters/cdaadapter.md) | Remove `add_input()` and `add_output()` calls, use explicit `parse()` and `format()` methods | +| `CdsFhirConnector` | [`CdsFhirAdapter`](../adapters/cdsfhiradapter.md) | Remove `add_input()` and `add_output()` calls, use explicit `parse()` and `format()` methods | + +### Quick Migration Example + +**Before (Connectors):** +```python +pipeline.add_input(CdaConnector()) +pipeline.add_output(CdaConnector()) +response = pipeline(cda_request) +``` + +**After (Adapters):** +```python +adapter = CdaAdapter() +doc = adapter.parse(cda_request) +doc = pipeline(doc) +response = adapter.format(doc) +``` + +[→ Full Adapter Documentation](../adapters/adapters.md) -## Available connectors +## Available connectors (Legacy) Connectors parse data from a specific format into FHIR resources and store them in a `Document` container. diff --git a/docs/reference/pipeline/pipeline.md b/docs/reference/pipeline/pipeline.md index 20c64f42..eefef0da 100644 --- a/docs/reference/pipeline/pipeline.md +++ b/docs/reference/pipeline/pipeline.md @@ -8,24 +8,24 @@ Depending on your need, you can either go top down, where you use prebuilt pipel HealthChain comes with a set of prebuilt pipelines that are out-of-the-box implementations of common healthcare data processing tasks: -| Pipeline | Container | Compatible Connector | Description | Example Use Case | -|----------|-----------|-----------|-------------|------------------| -| [**MedicalCodingPipeline**](./prebuilt_pipelines/medicalcoding.md) | `Document` | `CdaConnector` | An NLP pipeline that processes free-text clinical notes into structured data | Automatically generating SNOMED CT codes from clinical notes | -| [**SummarizationPipeline**](./prebuilt_pipelines/summarization.md) | `Document` | `CdsFhirConnector` | An NLP pipeline for summarizing clinical notes | Generating discharge summaries from patient history and notes | -| **QAPipeline** [TODO] | `Document` | N/A | A Question Answering pipeline suitable for conversational AI applications | Developing a chatbot to answer patient queries about their medical records | -| **ClassificationPipeline** [TODO] | `Tabular` | `CdsFhirConnector` | A pipeline for machine learning classification tasks | Predicting patient readmission risk based on historical health data | +| Pipeline | Container | Use Case | Description | Example Application | +|----------|-----------|----------|-------------|---------------------| +| [**MedicalCodingPipeline**](./prebuilt_pipelines/medicalcoding.md) | `Document` | Clinical Documentation | An NLP pipeline that processes free-text clinical notes into structured data | Automatically generating SNOMED CT codes from clinical notes | +| [**SummarizationPipeline**](./prebuilt_pipelines/summarization.md) | `Document` | Clinical Decision Support | An NLP pipeline for summarizing clinical notes | Generating discharge summaries from patient history and notes | +| **QAPipeline** [TODO] | `Document` | Conversational AI | A Question Answering pipeline suitable for conversational AI applications | Developing a chatbot to answer patient queries about their medical records | +| **ClassificationPipeline** [TODO] | `Tabular` | Predictive Analytics | A pipeline for machine learning classification tasks | Predicting patient readmission risk based on historical health data | -Prebuilt pipelines are end-to-end workflows with Connectors built into them. They interact with raw data received from EHR interfaces, usually CDA or FHIR data from specific [protocols](../gateway/gateway.md). +Prebuilt pipelines are end-to-end workflows optimized for specific healthcare AI tasks. They can be used with adapters for seamless integration with EHR systems via [protocols](../gateway/gateway.md). You can load your models directly as a pipeline object, from local files or from a remote model repository such as Hugging Face. ```python -from healthchain.pipeline import Pipeline +from healthchain.pipeline import MedicalCodingPipeline from healthchain.models import CdaRequest # Load from Hugging Face pipeline = MedicalCodingPipeline.from_model_id( - 'gpt2', source="huggingface" + 'blaze999/Medical-NER', task="token-classification", source="huggingface" ) # Load from local model files pipeline = MedicalCodingPipeline.from_local_model( @@ -34,8 +34,17 @@ pipeline = MedicalCodingPipeline.from_local_model( # Load from a pipeline object pipeline = MedicalCodingPipeline.load(pipeline_object) +# Simple end-to-end processing cda_request = CdaRequest(document="") -cda_response = pipeline(cda_request) +cda_response = pipeline.process_request(cda_request) + +# Or manual adapter control for more granular control +from healthchain.io import CdaAdapter +adapter = CdaAdapter() +doc = adapter.parse(cda_request) +doc = pipeline(doc) +# Access: doc.fhir.problem_list, doc.fhir.medication_list +response = adapter.format(doc) ``` ### Customizing Prebuilt Pipelines @@ -135,19 +144,27 @@ pipeline.add_node(RemoveStopwords(stopwords)) [(BaseComponent API Reference)](../../api/component.md#healthchain.pipeline.components.base.BaseComponent) -### Adding Connectors 🔗 +### Working with Healthcare Data Formats 🔄 -Connectors are added to the pipeline using the `.add_input()` and `.add_output()` methods. You can learn more about connectors at the [Connectors](./connectors/connectors.md) documentation page. +Use adapters to handle conversion between healthcare formats (CDA, FHIR) and HealthChain's internal Document objects. Adapters enable clean separation between ML processing and format handling. ```python -from healthchain.io import CdaConnector +from healthchain.io import CdaAdapter, Document + +adapter = CdaAdapter() -cda_connector = CdaConnector() +# Parse healthcare data into Document +doc = adapter.parse(cda_request) -pipeline.add_input(cda_connector) -pipeline.add_output(cda_connector) +# Process with pure pipeline +processed_doc = pipeline(doc) + +# Convert back to healthcare format +response = adapter.format(processed_doc) ``` +You can learn more about adapters at the [Adapters](./adapters/adapters.md) documentation page. + ## Pipeline Management 🔨 #### Adding diff --git a/mkdocs.yml b/mkdocs.yml index c5c4ed2b..1b4a25dd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -32,7 +32,11 @@ nav: - Components: - Overview: reference/pipeline/components/components.md - CdsCardCreator: reference/pipeline/components/cdscardcreator.md - - Connectors: + - Adapters: + - Overview: reference/pipeline/adapters/adapters.md + - CDA Adapter: reference/pipeline/adapters/cdaadapter.md + - CDS FHIR Adapter: reference/pipeline/adapters/cdsfhiradapter.md + - Connectors (Legacy): - Overview: reference/pipeline/connectors/connectors.md - CDA Connector: reference/pipeline/connectors/cdaconnector.md - CDS FHIR Connector: reference/pipeline/connectors/cdsfhirconnector.md @@ -59,6 +63,7 @@ nav: - api/pipeline.md - api/component.md - api/containers.md + - api/adapters.md - api/connectors.md - api/use_cases.md - api/cds_hooks.md From e00e7595f1dda7adc832721116dbe0585f784f48 Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Thu, 31 Jul 2025 17:30:46 +0100 Subject: [PATCH 04/19] Update README --- README.md | 43 +++++++++++++++++++++++++++---------------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 9f9fed06..b1943b4e 100644 --- a/README.md +++ b/README.md @@ -163,27 +163,32 @@ result = nlp(Document("Patient has a history of heart attack and high blood pres print(f"Entities: {result.nlp.get_entities()}") ``` -#### Adding connectors -Connectors give your pipelines the ability to interface with EHRs. +#### Working with healthcare data formats +Adapters handle conversion between healthcare formats (CDA, FHIR) and internal Document objects for seamless EHR integration. ```python -from healthchain.io import CdaConnector +from healthchain.io import CdaAdapter, Document from healthchain.models import CdaRequest -cda_connector = CdaConnector() +adapter = CdaAdapter() -pipeline.add_input(cda_connector) -pipeline.add_output(cda_connector) +# Parse healthcare data into Document +cda_request = CdaRequest(document="") +doc = adapter.parse(cda_request) -pipe = pipeline.build() +# Process with your pipeline +processed_doc = nlp_pipeline(doc) -cda_data = CdaRequest(document="") -output = pipe(cda_data) -# output: CdsResponse model +# Access extracted clinical data +print(f"Problems: {processed_doc.fhir.problem_list}") +print(f"Medications: {processed_doc.fhir.medication_list}") + +# Convert back to healthcare format +response = adapter.format(processed_doc) ``` ### Using pre-built pipelines -Pre-built pipelines are use case specific end-to-end workflows that already have connectors and models built-in. +Pre-built pipelines are use case specific end-to-end workflows optimized for common healthcare AI tasks. ```python from healthchain.pipeline import MedicalCodingPipeline @@ -194,11 +199,17 @@ pipeline = MedicalCodingPipeline.from_model_id( model="blaze999/Medical-NER", task="token-classification", source="huggingface" ) -# Or load from local model -pipeline = MedicalCodingPipeline.from_local_model("./path/to/model", source="spacy") - -cda_data = CdaRequest(document="") -output = pipeline(cda_data) +# Simple end-to-end processing +cda_request = CdaRequest(document="") +response = pipeline.process_request(cda_request) + +# Or manual control for document access +from healthchain.io import CdaAdapter +adapter = CdaAdapter() +doc = adapter.parse(cda_request) +doc = pipeline(doc) +# Access: doc.fhir.problem_list, doc.fhir.medication_list +response = adapter.format(doc) ``` ## Interoperability From 96e3fefc1171bb6696c5b1a3a7633ceff5e391b0 Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Thu, 31 Jul 2025 17:30:57 +0100 Subject: [PATCH 05/19] poetry.lock --- poetry.lock | 706 ++++++++++++++++++++++++++-------------------------- 1 file changed, 355 insertions(+), 351 deletions(-) diff --git a/poetry.lock b/poetry.lock index 49e06b35..3047e90a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -75,17 +75,18 @@ dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest [[package]] name = "backrefs" -version = "5.8" +version = "5.9" description = "A wrapper around re and regex that adds additional back references." optional = false python-versions = ">=3.9" files = [ - {file = "backrefs-5.8-py310-none-any.whl", hash = "sha256:c67f6638a34a5b8730812f5101376f9d41dc38c43f1fdc35cb54700f6ed4465d"}, - {file = "backrefs-5.8-py311-none-any.whl", hash = "sha256:2e1c15e4af0e12e45c8701bd5da0902d326b2e200cafcd25e49d9f06d44bb61b"}, - {file = "backrefs-5.8-py312-none-any.whl", hash = "sha256:bbef7169a33811080d67cdf1538c8289f76f0942ff971222a16034da88a73486"}, - {file = "backrefs-5.8-py313-none-any.whl", hash = "sha256:e3a63b073867dbefd0536425f43db618578528e3896fb77be7141328642a1585"}, - {file = "backrefs-5.8-py39-none-any.whl", hash = "sha256:a66851e4533fb5b371aa0628e1fee1af05135616b86140c9d787a2ffdf4b8fdc"}, - {file = "backrefs-5.8.tar.gz", hash = "sha256:2cab642a205ce966af3dd4b38ee36009b31fa9502a35fd61d59ccc116e40a6bd"}, + {file = "backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f"}, + {file = "backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf"}, + {file = "backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa"}, + {file = "backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b"}, + {file = "backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9"}, + {file = "backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60"}, + {file = "backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59"}, ] [package.extras] @@ -144,13 +145,13 @@ files = [ [[package]] name = "certifi" -version = "2025.4.26" +version = "2025.7.14" description = "Python package for providing Mozilla's CA Bundle." optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"}, - {file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"}, + {file = "certifi-2025.7.14-py3-none-any.whl", hash = "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2"}, + {file = "certifi-2025.7.14.tar.gz", hash = "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995"}, ] [[package]] @@ -391,18 +392,15 @@ files = [ [[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" 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"] @@ -517,37 +515,37 @@ files = [ [[package]] name = "debugpy" -version = "1.8.14" +version = "1.8.15" description = "An implementation of the Debug Adapter Protocol for Python" optional = false python-versions = ">=3.8" files = [ - {file = "debugpy-1.8.14-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:93fee753097e85623cab1c0e6a68c76308cd9f13ffdf44127e6fab4fbf024339"}, - {file = "debugpy-1.8.14-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d937d93ae4fa51cdc94d3e865f535f185d5f9748efb41d0d49e33bf3365bd79"}, - {file = "debugpy-1.8.14-cp310-cp310-win32.whl", hash = "sha256:c442f20577b38cc7a9aafecffe1094f78f07fb8423c3dddb384e6b8f49fd2987"}, - {file = "debugpy-1.8.14-cp310-cp310-win_amd64.whl", hash = "sha256:f117dedda6d969c5c9483e23f573b38f4e39412845c7bc487b6f2648df30fe84"}, - {file = "debugpy-1.8.14-cp311-cp311-macosx_14_0_universal2.whl", hash = "sha256:1b2ac8c13b2645e0b1eaf30e816404990fbdb168e193322be8f545e8c01644a9"}, - {file = "debugpy-1.8.14-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf431c343a99384ac7eab2f763980724834f933a271e90496944195318c619e2"}, - {file = "debugpy-1.8.14-cp311-cp311-win32.whl", hash = "sha256:c99295c76161ad8d507b413cd33422d7c542889fbb73035889420ac1fad354f2"}, - {file = "debugpy-1.8.14-cp311-cp311-win_amd64.whl", hash = "sha256:7816acea4a46d7e4e50ad8d09d963a680ecc814ae31cdef3622eb05ccacf7b01"}, - {file = "debugpy-1.8.14-cp312-cp312-macosx_14_0_universal2.whl", hash = "sha256:8899c17920d089cfa23e6005ad9f22582fd86f144b23acb9feeda59e84405b84"}, - {file = "debugpy-1.8.14-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6bb5c0dcf80ad5dbc7b7d6eac484e2af34bdacdf81df09b6a3e62792b722826"}, - {file = "debugpy-1.8.14-cp312-cp312-win32.whl", hash = "sha256:281d44d248a0e1791ad0eafdbbd2912ff0de9eec48022a5bfbc332957487ed3f"}, - {file = "debugpy-1.8.14-cp312-cp312-win_amd64.whl", hash = "sha256:5aa56ef8538893e4502a7d79047fe39b1dae08d9ae257074c6464a7b290b806f"}, - {file = "debugpy-1.8.14-cp313-cp313-macosx_14_0_universal2.whl", hash = "sha256:329a15d0660ee09fec6786acdb6e0443d595f64f5d096fc3e3ccf09a4259033f"}, - {file = "debugpy-1.8.14-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f920c7f9af409d90f5fd26e313e119d908b0dd2952c2393cd3247a462331f15"}, - {file = "debugpy-1.8.14-cp313-cp313-win32.whl", hash = "sha256:3784ec6e8600c66cbdd4ca2726c72d8ca781e94bce2f396cc606d458146f8f4e"}, - {file = "debugpy-1.8.14-cp313-cp313-win_amd64.whl", hash = "sha256:684eaf43c95a3ec39a96f1f5195a7ff3d4144e4a18d69bb66beeb1a6de605d6e"}, - {file = "debugpy-1.8.14-cp38-cp38-macosx_14_0_x86_64.whl", hash = "sha256:d5582bcbe42917bc6bbe5c12db1bffdf21f6bfc28d4554b738bf08d50dc0c8c3"}, - {file = "debugpy-1.8.14-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5349b7c3735b766a281873fbe32ca9cca343d4cc11ba4a743f84cb854339ff35"}, - {file = "debugpy-1.8.14-cp38-cp38-win32.whl", hash = "sha256:7118d462fe9724c887d355eef395fae68bc764fd862cdca94e70dcb9ade8a23d"}, - {file = "debugpy-1.8.14-cp38-cp38-win_amd64.whl", hash = "sha256:d235e4fa78af2de4e5609073972700523e372cf5601742449970110d565ca28c"}, - {file = "debugpy-1.8.14-cp39-cp39-macosx_14_0_x86_64.whl", hash = "sha256:413512d35ff52c2fb0fd2d65e69f373ffd24f0ecb1fac514c04a668599c5ce7f"}, - {file = "debugpy-1.8.14-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c9156f7524a0d70b7a7e22b2e311d8ba76a15496fb00730e46dcdeedb9e1eea"}, - {file = "debugpy-1.8.14-cp39-cp39-win32.whl", hash = "sha256:b44985f97cc3dd9d52c42eb59ee9d7ee0c4e7ecd62bca704891f997de4cef23d"}, - {file = "debugpy-1.8.14-cp39-cp39-win_amd64.whl", hash = "sha256:b1528cfee6c1b1c698eb10b6b096c598738a8238822d218173d21c3086de8123"}, - {file = "debugpy-1.8.14-py2.py3-none-any.whl", hash = "sha256:5cd9a579d553b6cb9759a7908a41988ee6280b961f24f63336835d9418216a20"}, - {file = "debugpy-1.8.14.tar.gz", hash = "sha256:7cd287184318416850aa8b60ac90105837bb1e59531898c07569d197d2ed5322"}, + {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]] @@ -563,13 +561,13 @@ files = [ [[package]] name = "distlib" -version = "0.3.9" +version = "0.4.0" description = "Distribution utilities" optional = false python-versions = "*" files = [ - {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, - {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, + {file = "distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16"}, + {file = "distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d"}, ] [[package]] @@ -633,13 +631,13 @@ python-dateutil = ">=2.4" [[package]] name = "fastapi" -version = "0.115.12" +version = "0.115.14" 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"}, + {file = "fastapi-0.115.14-py3-none-any.whl", hash = "sha256:6c0c8bf9420bd58f565e585036d971872472b4f7d3f6c73b698e10cffdefb3ca"}, + {file = "fastapi-0.115.14.tar.gz", hash = "sha256:b1de15cdc1c499a4da47914db35d0e4ef8f1ce62b624e94e0e5824421df99739"}, ] [package.dependencies] @@ -669,40 +667,40 @@ otel = ["opentelemetry-api (>=1.12.0,<2.0)"] [[package]] name = "fhir-core" -version = "1.0.1" +version = "1.1.4" description = "FHIR Core library" optional = false python-versions = ">=3.8" files = [ - {file = "fhir_core-1.0.1-py2.py3-none-any.whl", hash = "sha256:199af6d68dc85cd09c947ec6ecb02b109a3d116ef016d1b4903ec22c36bbe03a"}, - {file = "fhir_core-1.0.1.tar.gz", hash = "sha256:1f1b04027053e5a844f69d00bda6acfced555697778fa1a0cf58d38fd18ef39b"}, + {file = "fhir_core-1.1.4-py2.py3-none-any.whl", hash = "sha256:66d81639f9a45e646cf21cae492d68d89550a22409bf68a9b292b789e0d0061d"}, + {file = "fhir_core-1.1.4.tar.gz", hash = "sha256:d6549665c32f6b710da19d1309851d5d5f8902af899925623d6f4441eb1f2176"}, ] [package.dependencies] pydantic = ">=2.7.4,<3.0" [package.extras] -dev = ["Jinja2 (==2.11.1)", "MarkupSafe (==1.1.1)", "PyYAML (>=6.0.1)", "black", "certifi", "colorlog (==2.10.0)", "coverage", "fhirspec", "flake8 (==6.0)", "flake8-bugbear (>=22.12.6)", "flake8-isort (>=6.0.0)", "importlib-metadata (>=5.2.0)", "isort (>=5.11.4)", "lxml", "mypy", "pytest (>5.4.0)", "pytest-cov (>=2.10.0)", "requests (==2.23.0)", "setuptools (==65.6.3)", "types-PyYAML", "types-requests", "types-simplejson", "zest-releaser[recommended]"] -test = ["PyYAML (>=6.0.1)", "black", "coverage", "flake8 (==6.0)", "flake8-bugbear (>=22.12.6)", "flake8-isort (>=6.0.0)", "importlib-metadata (>=5.2.0)", "isort (>=5.11.4)", "lxml", "mypy", "pytest (>5.4.0)", "pytest-cov (>=2.10.0)", "pytest-runner", "requests (==2.23.0)", "setuptools (==65.6.3)", "types-PyYAML", "types-requests", "types-simplejson"] +dev = ["Jinja2 (==2.11.1)", "MarkupSafe (==1.1.1)", "PyYAML (>=6.0.1)", "black (>=23.0,<24.0)", "certifi", "colorlog (==2.10.0)", "coverage", "fhirspec", "flake8 (==6.0)", "flake8-bugbear (>=22.12.6)", "flake8-isort (>=6.0.0)", "importlib-metadata (>=5.2.0)", "isort (>=5.11.4)", "lxml", "mypy", "pytest (>5.4.0)", "pytest-cov (>=2.10.0)", "requests (==2.23.0)", "setuptools (==65.6.3)", "types-PyYAML", "types-requests", "types-simplejson", "zest-releaser[recommended]"] +test = ["PyYAML (>=6.0.1)", "black (>=23.0,<24.0)", "coverage", "flake8 (==6.0)", "flake8-bugbear (>=22.12.6)", "flake8-isort (>=6.0.0)", "importlib-metadata (>=5.2.0)", "isort (>=5.11.4)", "lxml", "mypy", "pytest (>5.4.0)", "pytest-cov (>=2.10.0)", "pytest-runner", "requests (==2.23.0)", "setuptools (==65.6.3)", "types-PyYAML", "types-requests", "types-simplejson"] [[package]] name = "fhir-resources" -version = "8.0.0" +version = "8.1.0" description = "FHIR Resources as Model Class" optional = false python-versions = ">=3.8" files = [ - {file = "fhir.resources-8.0.0-py2.py3-none-any.whl", hash = "sha256:9c46d6d79c6d6629c3bea6f244bcc6e8e0e4d15757a675f19d9d1c05c9ab2199"}, - {file = "fhir.resources-8.0.0.tar.gz", hash = "sha256:84dac3af31eaf90d5b0386cac21d26c50e6fb1526d68b88a2c42d112978e9cf9"}, + {file = "fhir_resources-8.1.0-py2.py3-none-any.whl", hash = "sha256:4370a5b6b35f278705328368bf79b3a17db91025fd4ec896fb963edd44ecc5de"}, + {file = "fhir_resources-8.1.0.tar.gz", hash = "sha256:8d64a717f37ea50bde97c1b8ff3fd969a6074df99c167183a273abe4da8bbfa5"}, ] [package.dependencies] -fhir-core = ">=1.0.0" +fhir-core = ">=1.1.3" [package.extras] all = ["PyYAML (>=5.4.1)", "lxml"] -dev = ["Jinja2 (==2.11.1)", "MarkupSafe (==1.1.1)", "black", "certifi", "colorlog (==2.10.0)", "coverage", "fhirspec", "flake8 (==6.0)", "flake8-bugbear (>=22.12.6)", "flake8-isort (>=6.0.0)", "importlib-metadata (>=5.2.0)", "isort (>=5.11.4)", "mypy", "pytest (>5.4.0)", "pytest-cov (>=2.10.0)", "requests (==2.23.0)", "setuptools (==65.6.3)", "typed-ast (>=1.5.4)", "types-PyYAML", "types-requests", "types-simplejson", "zest-releaser[recommended]"] -test = ["PyYAML (>=5.4.1)", "black", "coverage", "flake8 (==6.0)", "flake8-bugbear (>=22.12.6)", "flake8-isort (>=6.0.0)", "importlib-metadata (>=5.2.0)", "isort (>=5.11.4)", "lxml", "mypy", "pytest (>5.4.0)", "pytest-cov (>=2.10.0)", "pytest-runner", "requests (==2.23.0)", "setuptools (==65.6.3)", "typed-ast (>=1.5.4)", "types-PyYAML", "types-requests", "types-simplejson"] +dev = ["Jinja2 (==3.1.6)", "MarkupSafe (==2.1.5)", "black (>=23.0,<24.0)", "certifi", "colorlog (==2.10.0)", "coverage", "fhirspec (>=0.6.0)", "flake8 (==6.0)", "flake8-bugbear (>=22.12.6)", "flake8-isort (>=6.0.0)", "importlib-metadata (>=5.2.0)", "isort (>=5.11.4)", "mypy", "pytest (>5.4.0)", "pytest-cov (>=2.10.0)", "requests (==2.23.0)", "setuptools (==65.6.3)", "typed-ast (>=1.5.4)", "types-PyYAML", "types-requests", "types-simplejson", "zest-releaser[recommended]"] +test = ["PyYAML (>=5.4.1)", "black (>=23.0,<24.0)", "coverage", "flake8 (==6.0)", "flake8-bugbear (>=22.12.6)", "flake8-isort (>=6.0.0)", "importlib-metadata (>=5.2.0)", "isort (>=5.11.4)", "lxml", "mypy", "pytest (>5.4.0)", "pytest-cov (>=2.10.0)", "pytest-runner", "requests (==2.23.0)", "setuptools (==65.6.3)", "typed-ast (>=1.5.4)", "types-PyYAML", "types-requests", "types-simplejson"] xml = ["lxml"] yaml = ["PyYAML (>=5.4.1)"] @@ -741,13 +739,13 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "griffe" -version = "1.7.3" +version = "1.9.0" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.9" files = [ - {file = "griffe-1.7.3-py3-none-any.whl", hash = "sha256:c6b3ee30c2f0f17f30bcdef5068d6ab7a2a4f1b8bf1a3e74b56fffd21e1c5f75"}, - {file = "griffe-1.7.3.tar.gz", hash = "sha256:52ee893c6a3a968b639ace8015bec9d36594961e156e23315c8e8e51401fa50b"}, + {file = "griffe-1.9.0-py3-none-any.whl", hash = "sha256:bcf90ee3ad42bbae70a2a490c782fc8e443de9b84aa089d857c278a4e23215fc"}, + {file = "griffe-1.9.0.tar.gz", hash = "sha256:b5531cf45e9b73f0842c2121cc4d4bcbb98a55475e191fc9830e7aef87a920a0"}, ] [package.dependencies] @@ -896,36 +894,36 @@ files = [ [[package]] name = "ipykernel" -version = "6.29.5" +version = "6.30.0" description = "IPython Kernel for Jupyter" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" 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.0-py3-none-any.whl", hash = "sha256:fd2936e55c4a1c2ee8b1e5fa6a372b8eecc0ab1338750dee76f48fa5cca1301e"}, + {file = "ipykernel-6.30.0.tar.gz", hash = "sha256:b7b808ddb2d261aae2df3a26ff3ff810046e6de3dfbc6f7de8c98ea0a6cb632c"}, ] [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" @@ -1045,17 +1043,22 @@ test = ["ipykernel", "pre-commit", "pytest (<9)", "pytest-cov", "pytest-timeout" [[package]] name = "jwt" -version = "1.3.1" +version = "1.4.0" description = "JSON Web Token library for Python 3." optional = false -python-versions = ">= 3.6" +python-versions = ">=3.9" files = [ - {file = "jwt-1.3.1-py3-none-any.whl", hash = "sha256:61c9170f92e736b530655e75374681d4fcca9cfa8763ab42be57353b2b203494"}, + {file = "jwt-1.4.0-py3-none-any.whl", hash = "sha256:7560a7f1de4f90de94ac645ee0303ac60c95b9e08e058fb69f6c330f71d71b11"}, + {file = "jwt-1.4.0.tar.gz", hash = "sha256:f6f789128ac247142c79ee10f3dba6e366ec4e77c9920d18c1592e28aa0a7952"}, ] [package.dependencies] cryptography = ">=3.1,<3.4.0 || >3.4.0" +[package.extras] +dev = ["black", "isort", "mypy", "types-freezegun"] +test = ["freezegun", "pytest (>=6.0,<7.0)", "pytest-cov"] + [[package]] name = "langcodes" version = "3.5.0" @@ -1177,8 +1180,11 @@ files = [ {file = "lxml-5.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7ce1a171ec325192c6a636b64c94418e71a1964f56d002cc28122fceff0b6121"}, {file = "lxml-5.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:795f61bcaf8770e1b37eec24edf9771b307df3af74d1d6f27d812e15a9ff3872"}, {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29f451a4b614a7b5b6c2e043d7b64a15bd8304d7e767055e8ab68387a8cacf4e"}, + {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:891f7f991a68d20c75cb13c5c9142b2a3f9eb161f1f12a9489c82172d1f133c0"}, {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4aa412a82e460571fad592d0f93ce9935a20090029ba08eca05c614f99b0cc92"}, + {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:ac7ba71f9561cd7d7b55e1ea5511543c0282e2b6450f122672a2694621d63b7e"}, {file = "lxml-5.4.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:c5d32f5284012deaccd37da1e2cd42f081feaa76981f0eaa474351b68df813c5"}, + {file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:ce31158630a6ac85bddd6b830cffd46085ff90498b397bd0a259f59d27a12188"}, {file = "lxml-5.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:31e63621e073e04697c1b2d23fcb89991790eef370ec37ce4d5d469f40924ed6"}, {file = "lxml-5.4.0-cp37-cp37m-win32.whl", hash = "sha256:be2ba4c3c5b7900246a8f866580700ef0d538f2ca32535e991027bdaba944063"}, {file = "lxml-5.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:09846782b1ef650b321484ad429217f5154da4d6e786636c38e434fa32e94e49"}, @@ -1330,13 +1336,13 @@ test = ["hypothesis", "pytest", "readme-renderer"] [[package]] name = "markdown" -version = "3.8" +version = "3.8.2" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.9" files = [ - {file = "markdown-3.8-py3-none-any.whl", hash = "sha256:794a929b79c5af141ef5ab0f2f642d0f7b1872981250230e72682346f7cc90dc"}, - {file = "markdown-3.8.tar.gz", hash = "sha256:7df81e63f0df5c4b24b7d156eb81e4690595239b7d70937d0409f1b0de319c6f"}, + {file = "markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24"}, + {file = "markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45"}, ] [package.dependencies] @@ -1542,13 +1548,13 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-material" -version = "9.6.14" +version = "9.6.16" description = "Documentation that simply works" optional = false python-versions = ">=3.8" files = [ - {file = "mkdocs_material-9.6.14-py3-none-any.whl", hash = "sha256:3b9cee6d3688551bf7a8e8f41afda97a3c39a12f0325436d76c86706114b721b"}, - {file = "mkdocs_material-9.6.14.tar.gz", hash = "sha256:39d795e90dce6b531387c255bd07e866e027828b7346d3eba5ac3de265053754"}, + {file = "mkdocs_material-9.6.16-py3-none-any.whl", hash = "sha256:8d1a1282b892fe1fdf77bfeb08c485ba3909dd743c9ba69a19a40f637c6ec18c"}, + {file = "mkdocs_material-9.6.16.tar.gz", hash = "sha256:d07011df4a5c02ee0877496d9f1bfc986cfb93d964799b032dd99fe34c0e9d19"}, ] [package.dependencies] @@ -1764,37 +1770,53 @@ lint = ["black"] [[package]] name = "pandas" -version = "2.3.0" +version = "2.3.1" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:625466edd01d43b75b1883a64d859168e4556261a5035b32f9d743b67ef44634"}, - {file = "pandas-2.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a6872d695c896f00df46b71648eea332279ef4077a409e2fe94220208b6bb675"}, - {file = "pandas-2.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4dd97c19bd06bc557ad787a15b6489d2614ddaab5d104a0310eb314c724b2d2"}, - {file = "pandas-2.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:034abd6f3db8b9880aaee98f4f5d4dbec7c4829938463ec046517220b2f8574e"}, - {file = "pandas-2.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:39ff73ec07be5e90330cc6ff5705c651ace83374189dcdcb46e6ff54b4a72cd6"}, - {file = "pandas-2.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:40cecc4ea5abd2921682b57532baea5588cc5f80f0231c624056b146887274d2"}, - {file = "pandas-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8adff9f138fc614347ff33812046787f7d43b3cef7c0f0171b3340cae333f6ca"}, - {file = "pandas-2.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa35c266c8cd1a67d75971a1912b185b492d257092bdd2709bbdebe574ed228d"}, - {file = "pandas-2.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a0cc77b0f089d2d2ffe3007db58f170dae9b9f54e569b299db871a3ab5bf46"}, - {file = "pandas-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed16339bc354a73e0a609df36d256672c7d296f3f767ac07257801aa064ff73c"}, - {file = "pandas-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:fa07e138b3f6c04addfeaf56cc7fdb96c3b68a3fe5e5401251f231fce40a0d7a"}, - {file = "pandas-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2eb4728a18dcd2908c7fccf74a982e241b467d178724545a48d0caf534b38ebf"}, - {file = "pandas-2.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba24af48643b12ffe49b27065d3babd52702d95ab70f50e1b34f71ca703e2c0d"}, - {file = "pandas-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6021910b086b3ca756755e86ddc64e0ddafd5e58e076c72cb1585162e5ad259b"}, - {file = "pandas-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:094e271a15b579650ebf4c5155c05dcd2a14fd4fdd72cf4854b2f7ad31ea30be"}, - {file = "pandas-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c7e2fc25f89a49a11599ec1e76821322439d90820108309bf42130d2f36c983"}, - {file = "pandas-2.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb32dc743b52467d488e7a7c8039b821da2826a9ba4f85b89ea95274f863280f"}, - {file = "pandas-2.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:213cd63c43263dbb522c1f8a7c9d072e25900f6975596f883f4bebd77295d4f3"}, - {file = "pandas-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:430a63bae10b5086995db1b02694996336e5a8ac9a96b4200572b413dfdfccb9"}, - {file = "pandas-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:4930255e28ff5545e2ca404637bcc56f031893142773b3468dc021c6c32a1390"}, - {file = "pandas-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f925f1ef673b4bd0271b1809b72b3270384f2b7d9d14a189b12b7fc02574d575"}, - {file = "pandas-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78ad363ddb873a631e92a3c063ade1ecfb34cae71e9a2be6ad100f875ac1042"}, - {file = "pandas-2.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951805d146922aed8357e4cc5671b8b0b9be1027f0619cea132a9f3f65f2f09c"}, - {file = "pandas-2.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a881bc1309f3fce34696d07b00f13335c41f5f5a8770a33b09ebe23261cfc67"}, - {file = "pandas-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bb3be958022198531eb7ec2008cfc78c5b1eed51af8600c6c5d9160d89d8d249"}, - {file = "pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133"}, + {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] @@ -2176,13 +2198,13 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" [[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" 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] @@ -2190,13 +2212,13 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" -version = "10.15" +version = "10.16.1" description = "Extension pack for Python Markdown." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "pymdown_extensions-10.15-py3-none-any.whl", hash = "sha256:46e99bb272612b0de3b7e7caf6da8dd5f4ca5212c0b273feb9304e236c484e5f"}, - {file = "pymdown_extensions-10.15.tar.gz", hash = "sha256:0e5994e32155f4b03504f939e501b981d306daf7ec2aa1cd2eb6bd300784f8f7"}, + {file = "pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d"}, + {file = "pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91"}, ] [package.dependencies] @@ -2208,13 +2230,13 @@ extra = ["pygments (>=2.19.1)"] [[package]] name = "pytest" -version = "8.4.0" +version = "8.4.1" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.9" files = [ - {file = "pytest-8.4.0-py3-none-any.whl", hash = "sha256:f40f825768ad76c0977cbacdf1fd37c6f7a468e460ea6a0636078f8972d4517e"}, - {file = "pytest-8.4.0.tar.gz", hash = "sha256:14d920b48472ea0dbf68e45b96cd1ffda4705f33307dcc86c676c1b5104838a6"}, + {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"}, + {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"}, ] [package.dependencies] @@ -2293,27 +2315,31 @@ files = [ [[package]] name = "pywin32" -version = "310" +version = "311" description = "Python for Window Extensions" optional = false python-versions = "*" 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]] @@ -2394,104 +2420,90 @@ pyyaml = "*" [[package]] name = "pyzmq" -version = "26.4.0" +version = "27.0.0" description = "Python bindings for 0MQ" optional = false python-versions = ">=3.8" 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.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:b973ee650e8f442ce482c1d99ca7ab537c69098d53a3d046676a484fd710c87a"}, + {file = "pyzmq-27.0.0-cp310-cp310-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:661942bc7cd0223d569d808f2e5696d9cc120acc73bf3e88a1f1be7ab648a7e4"}, + {file = "pyzmq-27.0.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50360fb2a056ffd16e5f4177eee67f1dd1017332ea53fb095fe7b5bf29c70246"}, + {file = "pyzmq-27.0.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf209a6dc4b420ed32a7093642843cbf8703ed0a7d86c16c0b98af46762ebefb"}, + {file = "pyzmq-27.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c2dace4a7041cca2fba5357a2d7c97c5effdf52f63a1ef252cfa496875a3762d"}, + {file = "pyzmq-27.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:63af72b2955fc77caf0a77444baa2431fcabb4370219da38e1a9f8d12aaebe28"}, + {file = "pyzmq-27.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e8c4adce8e37e75c4215297d7745551b8dcfa5f728f23ce09bf4e678a9399413"}, + {file = "pyzmq-27.0.0-cp310-cp310-win32.whl", hash = "sha256:5d5ef4718ecab24f785794e0e7536436698b459bfbc19a1650ef55280119d93b"}, + {file = "pyzmq-27.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:e40609380480b3d12c30f841323f42451c755b8fece84235236f5fe5ffca8c1c"}, + {file = "pyzmq-27.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:6b0397b0be277b46762956f576e04dc06ced265759e8c2ff41a0ee1aa0064198"}, + {file = "pyzmq-27.0.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:21457825249b2a53834fa969c69713f8b5a79583689387a5e7aed880963ac564"}, + {file = "pyzmq-27.0.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1958947983fef513e6e98eff9cb487b60bf14f588dc0e6bf35fa13751d2c8251"}, + {file = "pyzmq-27.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0dc628b5493f9a8cd9844b8bee9732ef587ab00002157c9329e4fc0ef4d3afa"}, + {file = "pyzmq-27.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7bbe9e1ed2c8d3da736a15694d87c12493e54cc9dc9790796f0321794bbc91f"}, + {file = "pyzmq-27.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dc1091f59143b471d19eb64f54bae4f54bcf2a466ffb66fe45d94d8d734eb495"}, + {file = "pyzmq-27.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7011ade88c8e535cf140f8d1a59428676fbbce7c6e54fefce58bf117aefb6667"}, + {file = "pyzmq-27.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:2c386339d7e3f064213aede5d03d054b237937fbca6dd2197ac8cf3b25a6b14e"}, + {file = "pyzmq-27.0.0-cp311-cp311-win32.whl", hash = "sha256:0546a720c1f407b2172cb04b6b094a78773491497e3644863cf5c96c42df8cff"}, + {file = "pyzmq-27.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:15f39d50bd6c9091c67315ceb878a4f531957b121d2a05ebd077eb35ddc5efed"}, + {file = "pyzmq-27.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c5817641eebb391a2268c27fecd4162448e03538387093cdbd8bf3510c316b38"}, + {file = "pyzmq-27.0.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:cbabc59dcfaac66655c040dfcb8118f133fb5dde185e5fc152628354c1598e52"}, + {file = "pyzmq-27.0.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:cb0ac5179cba4b2f94f1aa208fbb77b62c4c9bf24dd446278b8b602cf85fcda3"}, + {file = "pyzmq-27.0.0-cp312-abi3-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53a48f0228eab6cbf69fde3aa3c03cbe04e50e623ef92ae395fce47ef8a76152"}, + {file = "pyzmq-27.0.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:111db5f395e09f7e775f759d598f43cb815fc58e0147623c4816486e1a39dc22"}, + {file = "pyzmq-27.0.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c8878011653dcdc27cc2c57e04ff96f0471e797f5c19ac3d7813a245bcb24371"}, + {file = "pyzmq-27.0.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:c0ed2c1f335ba55b5fdc964622254917d6b782311c50e138863eda409fbb3b6d"}, + {file = "pyzmq-27.0.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e918d70862d4cfd4b1c187310015646a14e1f5917922ab45b29f28f345eeb6be"}, + {file = "pyzmq-27.0.0-cp312-abi3-win32.whl", hash = "sha256:88b4e43cab04c3c0f0d55df3b1eef62df2b629a1a369b5289a58f6fa8b07c4f4"}, + {file = "pyzmq-27.0.0-cp312-abi3-win_amd64.whl", hash = "sha256:dce4199bf5f648a902ce37e7b3afa286f305cd2ef7a8b6ec907470ccb6c8b371"}, + {file = "pyzmq-27.0.0-cp312-abi3-win_arm64.whl", hash = "sha256:56e46bbb85d52c1072b3f809cc1ce77251d560bc036d3a312b96db1afe76db2e"}, + {file = "pyzmq-27.0.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c36ad534c0c29b4afa088dc53543c525b23c0797e01b69fef59b1a9c0e38b688"}, + {file = "pyzmq-27.0.0-cp313-cp313t-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:67855c14173aec36395d7777aaba3cc527b393821f30143fd20b98e1ff31fd38"}, + {file = "pyzmq-27.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8617c7d43cd8ccdb62aebe984bfed77ca8f036e6c3e46dd3dddda64b10f0ab7a"}, + {file = "pyzmq-27.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:67bfbcbd0a04c575e8103a6061d03e393d9f80ffdb9beb3189261e9e9bc5d5e9"}, + {file = "pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5cd11d46d7b7e5958121b3eaf4cd8638eff3a720ec527692132f05a57f14341d"}, + {file = "pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:b801c2e40c5aa6072c2f4876de8dccd100af6d9918d4d0d7aa54a1d982fd4f44"}, + {file = "pyzmq-27.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:20d5cb29e8c5f76a127c75b6e7a77e846bc4b655c373baa098c26a61b7ecd0ef"}, + {file = "pyzmq-27.0.0-cp313-cp313t-win32.whl", hash = "sha256:a20528da85c7ac7a19b7384e8c3f8fa707841fd85afc4ed56eda59d93e3d98ad"}, + {file = "pyzmq-27.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d8229f2efece6a660ee211d74d91dbc2a76b95544d46c74c615e491900dc107f"}, + {file = "pyzmq-27.0.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:f4162dbbd9c5c84fb930a36f290b08c93e35fce020d768a16fc8891a2f72bab8"}, + {file = "pyzmq-27.0.0-cp38-cp38-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4e7d0a8d460fba526cc047333bdcbf172a159b8bd6be8c3eb63a416ff9ba1477"}, + {file = "pyzmq-27.0.0-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:29f44e3c26b9783816ba9ce274110435d8f5b19bbd82f7a6c7612bb1452a3597"}, + {file = "pyzmq-27.0.0-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e435540fa1da54667f0026cf1e8407fe6d8a11f1010b7f06b0b17214ebfcf5e"}, + {file = "pyzmq-27.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:51f5726de3532b8222e569990c8aa34664faa97038304644679a51d906e60c6e"}, + {file = "pyzmq-27.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:42c7555123679637c99205b1aa9e8f7d90fe29d4c243c719e347d4852545216c"}, + {file = "pyzmq-27.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a979b7cf9e33d86c4949df527a3018767e5f53bc3b02adf14d4d8db1db63ccc0"}, + {file = "pyzmq-27.0.0-cp38-cp38-win32.whl", hash = "sha256:26b72c5ae20bf59061c3570db835edb81d1e0706ff141747055591c4b41193f8"}, + {file = "pyzmq-27.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:55a0155b148fe0428285a30922f7213539aa84329a5ad828bca4bbbc665c70a4"}, + {file = "pyzmq-27.0.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:100f6e5052ba42b2533011d34a018a5ace34f8cac67cb03cfa37c8bdae0ca617"}, + {file = "pyzmq-27.0.0-cp39-cp39-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:bf6c6b061efd00404b9750e2cfbd9507492c8d4b3721ded76cb03786131be2ed"}, + {file = "pyzmq-27.0.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ee05728c0b0b2484a9fc20466fa776fffb65d95f7317a3419985b8c908563861"}, + {file = "pyzmq-27.0.0-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7cdf07fe0a557b131366f80727ec8ccc4b70d89f1e3f920d94a594d598d754f0"}, + {file = "pyzmq-27.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:90252fa2ff3a104219db1f5ced7032a7b5fc82d7c8d2fec2b9a3e6fd4e25576b"}, + {file = "pyzmq-27.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ea6d441c513bf18c578c73c323acf7b4184507fc244762193aa3a871333c9045"}, + {file = "pyzmq-27.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ae2b34bcfaae20c064948a4113bf8709eee89fd08317eb293ae4ebd69b4d9740"}, + {file = "pyzmq-27.0.0-cp39-cp39-win32.whl", hash = "sha256:5b10bd6f008937705cf6e7bf8b6ece5ca055991e3eb130bca8023e20b86aa9a3"}, + {file = "pyzmq-27.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:00387d12a8af4b24883895f7e6b9495dc20a66027b696536edac35cb988c38f3"}, + {file = "pyzmq-27.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:4c19d39c04c29a6619adfeb19e3735c421b3bfee082f320662f52e59c47202ba"}, + {file = "pyzmq-27.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:656c1866505a5735d0660b7da6d7147174bbf59d4975fc2b7f09f43c9bc25745"}, + {file = "pyzmq-27.0.0-pp310-pypy310_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:74175b9e12779382432dd1d1f5960ebe7465d36649b98a06c6b26be24d173fab"}, + {file = "pyzmq-27.0.0-pp310-pypy310_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8c6de908465697a8708e4d6843a1e884f567962fc61eb1706856545141d0cbb"}, + {file = "pyzmq-27.0.0-pp310-pypy310_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c644aaacc01d0df5c7072826df45e67301f191c55f68d7b2916d83a9ddc1b551"}, + {file = "pyzmq-27.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:10f70c1d9a446a85013a36871a296007f6fe4232b530aa254baf9da3f8328bc0"}, + {file = "pyzmq-27.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd1dc59763effd1576f8368047c9c31468fce0af89d76b5067641137506792ae"}, + {file = "pyzmq-27.0.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:60e8cc82d968174650c1860d7b716366caab9973787a1c060cf8043130f7d0f7"}, + {file = "pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:14fe7aaac86e4e93ea779a821967360c781d7ac5115b3f1a171ced77065a0174"}, + {file = "pyzmq-27.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6ad0562d4e6abb785be3e4dd68599c41be821b521da38c402bc9ab2a8e7ebc7e"}, + {file = "pyzmq-27.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:9df43a2459cd3a3563404c1456b2c4c69564daa7dbaf15724c09821a3329ce46"}, + {file = "pyzmq-27.0.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c86ea8fe85e2eb0ffa00b53192c401477d5252f6dd1db2e2ed21c1c30d17e5e"}, + {file = "pyzmq-27.0.0-pp38-pypy38_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:c45fee3968834cd291a13da5fac128b696c9592a9493a0f7ce0b47fa03cc574d"}, + {file = "pyzmq-27.0.0-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cae73bb6898c4e045fbed5024cb587e4110fddb66f6163bcab5f81f9d4b9c496"}, + {file = "pyzmq-27.0.0-pp38-pypy38_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:26d542258c7a1f35a9cff3d887687d3235006134b0ac1c62a6fe1ad3ac10440e"}, + {file = "pyzmq-27.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:04cd50ef3b28e35ced65740fb9956a5b3f77a6ff32fcd887e3210433f437dd0f"}, + {file = "pyzmq-27.0.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:39ddd3ba0a641f01d8f13a3cfd4c4924eb58e660d8afe87e9061d6e8ca6f7ac3"}, + {file = "pyzmq-27.0.0-pp39-pypy39_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:8ca7e6a0388dd9e1180b14728051068f4efe83e0d2de058b5ff92c63f399a73f"}, + {file = "pyzmq-27.0.0-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2524c40891be6a3106885a3935d58452dd83eb7a5742a33cc780a1ad4c49dec0"}, + {file = "pyzmq-27.0.0-pp39-pypy39_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a56e3e5bd2d62a01744fd2f1ce21d760c7c65f030e9522738d75932a14ab62a"}, + {file = "pyzmq-27.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:096af9e133fec3a72108ddefba1e42985cb3639e9de52cfd336b6fc23aa083e9"}, + {file = "pyzmq-27.0.0.tar.gz", hash = "sha256:b1f08eeb9ce1510e6939b6e5dcd46a17765e2333daae78ecf4606808442e52cf"}, ] [package.dependencies] @@ -2499,105 +2511,98 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "regex" -version = "2024.11.6" +version = "2025.7.31" description = "Alternative regular expression module, to replace re." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" 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.31-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b40a8f8064c3b8032babb2049b7ab40812cbb394179556deb7c40c1e3b28630f"}, + {file = "regex-2025.7.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6aef1895f27875421e6d8047747702d6e512793c6d95614c56479a375541edb"}, + {file = "regex-2025.7.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f124ff95b4cbedfd762897d4bd9da2b20b7574df1d60d498f16a42d398d524e9"}, + {file = "regex-2025.7.31-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea5b162c50745694606f50170cc7cc84c14193ac5fd6ecb26126e826a7c12bd6"}, + {file = "regex-2025.7.31-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f970a3e058f587988a18ed4ddff6a6363fa72a41dfb29077d0efe8dc4df00da"}, + {file = "regex-2025.7.31-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2dadf5788af5b10a78b996d24263e352e5f99dbfce9db4c48e9c875a9a7d051c"}, + {file = "regex-2025.7.31-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f67f9f8216a8e645c568daf104abc52cd5387127af8e8b17c7bc11b014d88fcb"}, + {file = "regex-2025.7.31-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:407da7504642830d4211d39dc23b8a9d400913b3f2d242774b8d17ead3487e00"}, + {file = "regex-2025.7.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff7753bd717a9f2286d2171d758eebf11b3bfb21e6520b201e01169ec9cd5ec0"}, + {file = "regex-2025.7.31-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:de088fe37d4c58a42401bf4ce2328b00a760c7d85473ccf6e489094e13452510"}, + {file = "regex-2025.7.31-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:67d708f8bfb89dcd57c3190cb5c343c7f40d3c81319a00c8188982a08c64b977"}, + {file = "regex-2025.7.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3fe81cd00ef1eaef1ef00d61200bacb55b1a130570cd9be2e793b98981c6cd9c"}, + {file = "regex-2025.7.31-cp310-cp310-win32.whl", hash = "sha256:8542ee1fd8c8be4db1c58902956a220bdbe7c38362decec989f57ace0e37f14c"}, + {file = "regex-2025.7.31-cp310-cp310-win_amd64.whl", hash = "sha256:77be56e167e2685828ab0abc1bdf38db3ab385e624c3ea2694b0d4ea70a2b7bc"}, + {file = "regex-2025.7.31-cp310-cp310-win_arm64.whl", hash = "sha256:7ddc7ab76d917cb680a3b0fa53fc2971d40cc17415539007e15fa31c829dcf2b"}, + {file = "regex-2025.7.31-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:55dc9f4094656d273562718d68cd8363f688e0b813d62696aad346bcd7b1c7d4"}, + {file = "regex-2025.7.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8ff37cac0e1c7ba943bf46f6431b0c86cbe42d42ae862ff7b152b4ccc232bdd"}, + {file = "regex-2025.7.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:622aa4ca90d7cf38433d425a4f00543b08d3b109cca379df8f31827cf5e2ecb3"}, + {file = "regex-2025.7.31-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbd4ee61dddfcff625f8642e940ba61121b28e98d0eca24d79114209e3e8ce1b"}, + {file = "regex-2025.7.31-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca7c9af8f33540b51f1b76092e732b62211092af947239e5db471323ae39ead4"}, + {file = "regex-2025.7.31-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:beda88db2cae5dc82a64cba465f7e8686392d96116f87e664af46c4dfcdd9cbc"}, + {file = "regex-2025.7.31-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055baef91bb31474bd919fd245cf154db00cbac449596952d3e6bc1e1b226808"}, + {file = "regex-2025.7.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:02e660c2d02854eed41b13f0e2c98d24efce4fb439aa316742f8d32aeda2803b"}, + {file = "regex-2025.7.31-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4372ca5c43d0e255e68a9aa6812d9be3447c4ce7ba7cb1429c7b96d2c63ee9b1"}, + {file = "regex-2025.7.31-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:481f069facacb4f40bf37a51748a88952f5dd5707dd849f216d53bf5522c8add"}, + {file = "regex-2025.7.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e8b4896ec5a9d0ae73d04e260ff6e1f366985b46505b2fa36d91501e4a7a98f0"}, + {file = "regex-2025.7.31-cp311-cp311-win32.whl", hash = "sha256:47ceaa1e5eb243595306dfd5e5e294e251900aa94a0e2e1037fce125f432d2fb"}, + {file = "regex-2025.7.31-cp311-cp311-win_amd64.whl", hash = "sha256:c4f6b34f509bb26507509b6f9ba85debcc6ca512d2d4a6fd5e96b9de2c187c83"}, + {file = "regex-2025.7.31-cp311-cp311-win_arm64.whl", hash = "sha256:75f74892df1593036e83b48ba50d1e1951af650b6fabbfcf7531e7082e3561d4"}, + {file = "regex-2025.7.31-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1af64eed343f19e1f09da9e9e8cfb82570050c4ed9fec400f9f118aab383da4b"}, + {file = "regex-2025.7.31-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eab98712c0a6d053fb67b021fae43422f7eab8fa2aaa25034f5ef01585112cc7"}, + {file = "regex-2025.7.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34dcb7c4d89b83e7e3cb5a2679595f6f97d253815ed9402edbdfc56780668b89"}, + {file = "regex-2025.7.31-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52f1925d123338835e5b13e5ef8e6a744c02aef8e538e661ad5c76185e6ad87a"}, + {file = "regex-2025.7.31-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:569c2b6812d223ae82a2a13c36362ca5933b88011ba869111eba8fb769ccf492"}, + {file = "regex-2025.7.31-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:27f17ade67d06ce4abff48f2ee99c6419f73e70882fe7ca51960916c75844e1f"}, + {file = "regex-2025.7.31-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45622fab3a90590a41a541afea739a732bf110dd081c15c84538b115cf5f59f5"}, + {file = "regex-2025.7.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:defab878ce91944baf2ade775895a097ad7eeeab3618d87b4c29753aad98a5c4"}, + {file = "regex-2025.7.31-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8ae02caf994a0a0d958b9b0fc5aebbdb48fa93491a582dd00db3733d258a6ac4"}, + {file = "regex-2025.7.31-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7c40ab21112711363d7612f35781c8b2d2d59c27e0a057a6486eea60ee01e54"}, + {file = "regex-2025.7.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4723c01dd28c1b1de5f463bba8672e3d0dc3d94d5db056e4bbc3cbc84bf23c1c"}, + {file = "regex-2025.7.31-cp312-cp312-win32.whl", hash = "sha256:3ebf32b2b2f60aecd6f8d375ff310849251946cf953aac69b8b5b10e3ccebaf9"}, + {file = "regex-2025.7.31-cp312-cp312-win_amd64.whl", hash = "sha256:12f9ab65b4cc771dd6d8af806ded7425ca50d2a188d2fc3a5aba3dc49f5684b7"}, + {file = "regex-2025.7.31-cp312-cp312-win_arm64.whl", hash = "sha256:fd454ed1fe245f983c2376b6f01948d6ec4a1e5869a8c883e320e1739cc63e57"}, + {file = "regex-2025.7.31-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ead2cf9d92f90d2fd7c5eb84b383a82154298742011b8f892fdee2f724f76106"}, + {file = "regex-2025.7.31-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:81d865d195f9c94b7e7f043c973a7ee1003b29f6e75caa9125aa5a92cf6b334d"}, + {file = "regex-2025.7.31-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3e58b95f62df0300496a2244ac5818312a80a5f786c9727125d62b49deede1b9"}, + {file = "regex-2025.7.31-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc2939e3e1837822803afebe38f42aab739e1135ea63ba0fdfe499672b21fc39"}, + {file = "regex-2025.7.31-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:51211fd9bfe544f7ad543a683bd2546636ce5b55ab65752e8f8ebe477378dfa2"}, + {file = "regex-2025.7.31-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff1359141a378d8fa1ade7ca8a7a94988c830e5e588d232eded0e5900fa953cf"}, + {file = "regex-2025.7.31-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a57aacb1974bd04a5ace8f93c9ab7fa49b868091032b38afd79b2c1ac70da35a"}, + {file = "regex-2025.7.31-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2784d4afa58a87f5f522037d10cf96c05d3a91ab82b2152a66e8ccea55e703f6"}, + {file = "regex-2025.7.31-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:339d1c579cea1d525ef2b2fefdc1f108596b8252acca6ef012a51206d3f01ac4"}, + {file = "regex-2025.7.31-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3bb9bf5a0c1c1c353bc5da6cb58db8a12b1ec76a9e8dc8a23ce56d63ee867392"}, + {file = "regex-2025.7.31-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a7bedc5b499bd0a5cc05b3407ab0aa09f224fb9cd13c52253ecb1619957a6b4"}, + {file = "regex-2025.7.31-cp313-cp313-win32.whl", hash = "sha256:c8ae328524e7bb67ae12a9e314d935e7bb67eb5135e57196b0faa4ecab3f2999"}, + {file = "regex-2025.7.31-cp313-cp313-win_amd64.whl", hash = "sha256:8ab2d9cd1c13e7127194b5cb36ecfb323fec0b80845195842d8e8ac9fb581e1b"}, + {file = "regex-2025.7.31-cp313-cp313-win_arm64.whl", hash = "sha256:5560b6c9fb428281b472b665e4d046eaaaf37523135cb1ee3dc699f3e82dae7a"}, + {file = "regex-2025.7.31-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:45fd783fd91ec849c64ebd5c0498ded966e829b8d2ea44daba2a2c35b6b5f4a8"}, + {file = "regex-2025.7.31-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:81a193e6138b61976903357fc7a67dd9e256cf98f73bbfb2758abf3b8d396c35"}, + {file = "regex-2025.7.31-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fccac19e5f1053e4da34ae5a651b938dba12e5f54f04def1cd349b24fd5f28cf"}, + {file = "regex-2025.7.31-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f6755afaed9948dd4dda4d093663fe60e9a8784993b733697551bf6b0921d7c"}, + {file = "regex-2025.7.31-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c7eea6eb0f4c1ff7eee051a6780acc40717be9736bf67873c3c932b7ac5743a2"}, + {file = "regex-2025.7.31-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89358d48fbc33614185c18b3a397b870e388f13d882f379b9a33c970a4945dcc"}, + {file = "regex-2025.7.31-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b284b8042d97f4eb9caf4d9423307ee1c9ff9c2abd14c781d44aef070ac7cc9"}, + {file = "regex-2025.7.31-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2348cedab6adee1a7649e2a157d219196044588a58024509def2b8b30c0f63f8"}, + {file = "regex-2025.7.31-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:833292f5ebfbe4f104e02718f0e2d05d51ac43cdc023a217672119989c4a0be6"}, + {file = "regex-2025.7.31-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:74f348e26ff09bb2684c67535f516cec362624566127d9f4158cd7ab5038c1fe"}, + {file = "regex-2025.7.31-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b2d5523c236594c055e5752e088406dfe3214c4e986abeceaea24967371ad890"}, + {file = "regex-2025.7.31-cp314-cp314-win32.whl", hash = "sha256:144d7550d13770ab994ef6616cff552ed01c892499eb1df74b6775e9b6f6a571"}, + {file = "regex-2025.7.31-cp314-cp314-win_amd64.whl", hash = "sha256:5792ff4bb2836ca2b041321eada3a1918f8ba05bceac4f6e9f06f0fefa1b8e44"}, + {file = "regex-2025.7.31-cp314-cp314-win_arm64.whl", hash = "sha256:59b94c02b435d7d5a9621381bf338a36c7efa6d9025a888cc39aa256b2869299"}, + {file = "regex-2025.7.31-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac97385aadafe3a2f7cb9c48c5ca3cabb91c1f89e47fdf5a55945c61b186254f"}, + {file = "regex-2025.7.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1b600ff5e80d2b4cf2cabc451dab5b9a3ed7e1e5aa845dd5cf41eabefb957179"}, + {file = "regex-2025.7.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1282de93a20d143180bd3500488877d888185a5e78ef02f7cd410140299f0941"}, + {file = "regex-2025.7.31-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b1329dcb4cd688ebabd2560d5a82567e1e3d05885169f6bece40ca9e7dcfe3d"}, + {file = "regex-2025.7.31-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56508bf5da86c96b7f87da70ee28019a1bdd4c0ec31adfcd62300c4a08e927e4"}, + {file = "regex-2025.7.31-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1778b27e2d4e07cf1e3350f1e74dae5d0511d1ca2b001f4d985b0739182ba2a8"}, + {file = "regex-2025.7.31-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60162442fd631ead1ca58c16f6f9d6b1aa32d2a2f749b51a7b4262fc294105e1"}, + {file = "regex-2025.7.31-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc9eb820140126219ac9d6b488176cfdde2f5e8891b0fbf2cbd2526c0d441d37"}, + {file = "regex-2025.7.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b2b0f700237b73ec0df2e13e2b1c10d36b8ea45c7a3c7eb6d99843c39feaa0e6"}, + {file = "regex-2025.7.31-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:46572b60e9cc5c09e17d5ecb648dc9fb1c44c12274ae791921350f0f6d0eebea"}, + {file = "regex-2025.7.31-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:019ad36e4ea89af6abd2915ffc06b4e109234655148a45f8f32b42ea9b503514"}, + {file = "regex-2025.7.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:261f9a6dcb1fd9dc204cc587fceac2e071720a15fc4fa36156651c886e574ad0"}, + {file = "regex-2025.7.31-cp39-cp39-win32.whl", hash = "sha256:f7858175abee523c5b04cc1de5d3d03168aed4805aad747641752c027aaa6335"}, + {file = "regex-2025.7.31-cp39-cp39-win_amd64.whl", hash = "sha256:097c2adaedf5fba5819df298750cd3966da94fdd549e2d9e5040d7e315de97dd"}, + {file = "regex-2025.7.31-cp39-cp39-win_arm64.whl", hash = "sha256:c28c00fbe30dd5e99162b88765c8d014d06581927ceab8fa851267041e48820c"}, + {file = "regex-2025.7.31.tar.gz", hash = "sha256:80a1af156ea8670ae63184e5c112b481326ece1879e09447f6fbb49d1b49330b"}, ] [[package]] @@ -2623,19 +2628,18 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[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" 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] markdown-it-py = ">=2.2.0" pygments = ">=2.13.0,<3.0.0" -typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.11\""} [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] @@ -2710,26 +2714,26 @@ files = [ [[package]] name = "smart-open" -version = "7.1.0" -description = "Utils for streaming large files (S3, HDFS, GCS, Azure Blob Storage, gzip, bz2...)" +version = "7.3.0.post1" +description = "Utils for streaming large files (S3, HDFS, GCS, SFTP, Azure Blob Storage, gzip, bz2, zst...)" optional = false -python-versions = "<4.0,>=3.7" +python-versions = "<4.0,>=3.8" files = [ - {file = "smart_open-7.1.0-py3-none-any.whl", hash = "sha256:4b8489bb6058196258bafe901730c7db0dcf4f083f316e97269c66f45502055b"}, - {file = "smart_open-7.1.0.tar.gz", hash = "sha256:a4f09f84f0f6d3637c6543aca7b5487438877a21360e7368ccf1f704789752ba"}, + {file = "smart_open-7.3.0.post1-py3-none-any.whl", hash = "sha256:c73661a2c24bf045c1e04e08fffc585b59af023fe783d57896f590489db66fb4"}, + {file = "smart_open-7.3.0.post1.tar.gz", hash = "sha256:ce6a3d9bc1afbf6234ad13c010b77f8cd36d24636811e3c52c3b5160f5214d1e"}, ] [package.dependencies] wrapt = "*" [package.extras] -all = ["azure-common", "azure-core", "azure-storage-blob", "boto3", "google-cloud-storage (>=2.6.0)", "paramiko", "requests", "zstandard"] +all = ["smart_open[azure,gcs,http,s3,ssh,webhdfs,zst]"] azure = ["azure-common", "azure-core", "azure-storage-blob"] gcs = ["google-cloud-storage (>=2.6.0)"] http = ["requests"] s3 = ["boto3"] ssh = ["paramiko"] -test = ["awscli", "azure-common", "azure-core", "azure-storage-blob", "boto3", "google-cloud-storage (>=2.6.0)", "moto[server]", "numpy", "paramiko", "pyopenssl", "pytest", "pytest-benchmark", "pytest-rerunfailures", "requests", "responses", "zstandard"] +test = ["awscli", "moto[server]", "numpy", "pyopenssl", "pytest", "pytest-rerunfailures", "pytest_benchmark", "responses", "smart_open[all]"] webhdfs = ["requests"] zst = ["zstandard"] @@ -3158,13 +3162,13 @@ typing-extensions = ">=3.7.4.3" [[package]] name = "typing-extensions" -version = "4.14.0" +version = "4.14.1" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" files = [ - {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"}, - {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"}, + {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]] @@ -3180,13 +3184,13 @@ files = [ [[package]] name = "urllib3" -version = "2.4.0" +version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" files = [ - {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, - {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, + {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, + {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, ] [package.extras] @@ -3216,13 +3220,13 @@ standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", [[package]] name = "virtualenv" -version = "20.31.2" +version = "20.32.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" files = [ - {file = "virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11"}, - {file = "virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af"}, + {file = "virtualenv-20.32.0-py3-none-any.whl", hash = "sha256:2c310aecb62e5aa1b06103ed7c2977b81e042695de2697d01017ff0f1034af56"}, + {file = "virtualenv-20.32.0.tar.gz", hash = "sha256:886bf75cadfdc964674e6e33eb74d787dff31ca314ceace03ca5810620f4ecf0"}, ] [package.dependencies] From 145fd557aa2cf088af9c95e0e3205ae625d3e8b8 Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Thu, 31 Jul 2025 17:31:30 +0100 Subject: [PATCH 06/19] Update cookbook --- cookbook/cds_discharge_summarizer_hf_chat.py | 2 +- cookbook/cds_discharge_summarizer_hf_trf.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cookbook/cds_discharge_summarizer_hf_chat.py b/cookbook/cds_discharge_summarizer_hf_chat.py index ea1f7a12..259a1341 100644 --- a/cookbook/cds_discharge_summarizer_hf_chat.py +++ b/cookbook/cds_discharge_summarizer_hf_chat.py @@ -57,7 +57,7 @@ def load_data_in_client(self) -> Prefetch: @hc.api def my_service(self, request: CDSRequest) -> CDSResponse: # Process the request through our pipeline - result = self.pipeline(request) + result = self.pipeline.process_request(request) return result diff --git a/cookbook/cds_discharge_summarizer_hf_trf.py b/cookbook/cds_discharge_summarizer_hf_trf.py index dc3eb549..36ee119f 100644 --- a/cookbook/cds_discharge_summarizer_hf_trf.py +++ b/cookbook/cds_discharge_summarizer_hf_trf.py @@ -30,7 +30,7 @@ def load_data_in_client(self) -> Prefetch: @hc.api def my_service(self, request: CDSRequest) -> CDSResponse: - result = self.pipeline(request) + result = self.pipeline.process_request(request) return result From 831f92df5dbf4374f4beee70cfede50194ef0d8b Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Thu, 31 Jul 2025 18:22:34 +0100 Subject: [PATCH 07/19] Add config bundling and autodiscovery for pip installable interop --- healthchain/configs/defaults.yaml | 59 +++++ .../configs/environments/development.yaml | 33 +++ .../configs/environments/production.yaml | 37 ++++ healthchain/configs/environments/testing.yaml | 39 ++++ .../configs/interop/cda/document/ccd.yaml | 55 +++++ .../interop/cda/sections/allergies.yaml | 89 ++++++++ .../interop/cda/sections/medications.yaml | 72 +++++++ .../configs/interop/cda/sections/notes.yaml | 34 +++ .../interop/cda/sections/problems.yaml | 61 ++++++ healthchain/configs/mappings/README.md | 33 +++ .../configs/mappings/cda_default/README.md | 53 +++++ .../mappings/cda_default/severity_codes.yaml | 12 ++ .../mappings/cda_default/status_codes.yaml | 12 ++ .../configs/mappings/cda_default/systems.yaml | 23 ++ .../cda_fhir/allergy_intolerance.liquid | 79 +++++++ .../templates/cda_fhir/condition.liquid | 49 +++++ .../cda_fhir/document_reference.liquid | 22 ++ .../cda_fhir/medication_statement.liquid | 69 ++++++ .../templates/fhir_cda/allergy_entry.liquid | 204 ++++++++++++++++++ .../templates/fhir_cda/document.liquid | 41 ++++ .../fhir_cda/medication_entry.liquid | 111 ++++++++++ .../templates/fhir_cda/note_entry.liquid | 23 ++ .../templates/fhir_cda/problem_entry.liquid | 92 ++++++++ .../configs/templates/fhir_cda/section.liquid | 15 ++ healthchain/interop/__init__.py | 95 +++++++- pyproject.toml | 6 +- 26 files changed, 1410 insertions(+), 8 deletions(-) create mode 100644 healthchain/configs/defaults.yaml create mode 100644 healthchain/configs/environments/development.yaml create mode 100644 healthchain/configs/environments/production.yaml create mode 100644 healthchain/configs/environments/testing.yaml create mode 100644 healthchain/configs/interop/cda/document/ccd.yaml create mode 100644 healthchain/configs/interop/cda/sections/allergies.yaml create mode 100644 healthchain/configs/interop/cda/sections/medications.yaml create mode 100644 healthchain/configs/interop/cda/sections/notes.yaml create mode 100644 healthchain/configs/interop/cda/sections/problems.yaml create mode 100644 healthchain/configs/mappings/README.md create mode 100644 healthchain/configs/mappings/cda_default/README.md create mode 100644 healthchain/configs/mappings/cda_default/severity_codes.yaml create mode 100644 healthchain/configs/mappings/cda_default/status_codes.yaml create mode 100644 healthchain/configs/mappings/cda_default/systems.yaml create mode 100644 healthchain/configs/templates/cda_fhir/allergy_intolerance.liquid create mode 100644 healthchain/configs/templates/cda_fhir/condition.liquid create mode 100644 healthchain/configs/templates/cda_fhir/document_reference.liquid create mode 100644 healthchain/configs/templates/cda_fhir/medication_statement.liquid create mode 100644 healthchain/configs/templates/fhir_cda/allergy_entry.liquid create mode 100644 healthchain/configs/templates/fhir_cda/document.liquid create mode 100644 healthchain/configs/templates/fhir_cda/medication_entry.liquid create mode 100644 healthchain/configs/templates/fhir_cda/note_entry.liquid create mode 100644 healthchain/configs/templates/fhir_cda/problem_entry.liquid create mode 100644 healthchain/configs/templates/fhir_cda/section.liquid diff --git a/healthchain/configs/defaults.yaml b/healthchain/configs/defaults.yaml new file mode 100644 index 00000000..16e33509 --- /dev/null +++ b/healthchain/configs/defaults.yaml @@ -0,0 +1,59 @@ +# HealthChain Interoperability Engine Default Configuration +# This file contains default values used throughout the engine + +defaults: + # Common defaults for all resources + common: + id_prefix: "hc-" + timestamp: "%Y%m%d" + reference_name: "#{uuid}name" + subject: + reference: "Patient/example" + + # Mapping directory configuration + mappings_dir: "cda_default" + + # Resource-specific defaults + resources: + Condition: + clinicalStatus: + coding: + - system: "http://terminology.hl7.org/CodeSystem/condition-clinical" + code: "unknown" + display: "Unknown" + MedicationStatement: + status: "unknown" + medication: + concept: + coding: + - system: "http://terminology.hl7.org/CodeSystem/v3-NullFlavor" + code: "UNK" + display: "Unknown" + +# TODO: More control over settings +# # Validation settings +# validation: +# strict_mode: true +# warn_on_missing: true +# ignore_unknown_fields: true + +# # Parser settings +# parser: +# max_entries: 1000 +# skip_empty_sections: true + +# # Logging settings +# logging: +# level: "INFO" +# include_timestamps: true + +# # Error handling +# errors: +# retry_count: 3 +# fail_on_critical: true + +# # Performance settings +# performance: +# cache_templates: true +# cache_mappings: true +# batch_size: 100 diff --git a/healthchain/configs/environments/development.yaml b/healthchain/configs/environments/development.yaml new file mode 100644 index 00000000..03ce37ec --- /dev/null +++ b/healthchain/configs/environments/development.yaml @@ -0,0 +1,33 @@ +# Development Environment Configuration +# This file contains settings specific to the development environment +# TODO: Implement + +# Logging settings for development +logging: + level: "DEBUG" + include_timestamps: true + console_output: true + file_output: false + +# Error handling for development +errors: + retry_count: 1 + fail_on_critical: true + verbose_errors: true + +# Performance settings for development +performance: + cache_templates: false # Disable caching for easier template development + cache_mappings: false # Disable caching for easier mapping development + batch_size: 10 # Smaller batch size for easier debugging + +# Default resource fields for development +defaults: + common: + id_prefix: "dev-" # Development-specific ID prefix + subject: + reference: "Patient/Foo" + +# Template settings for development +templates: + reload_on_change: true # Automatically reload templates when they change diff --git a/healthchain/configs/environments/production.yaml b/healthchain/configs/environments/production.yaml new file mode 100644 index 00000000..846a914c --- /dev/null +++ b/healthchain/configs/environments/production.yaml @@ -0,0 +1,37 @@ +# Production Environment Configuration +# This file contains settings specific to the production environment +# TODO: Implement + +# Logging settings for production +logging: + level: "WARNING" + include_timestamps: true + console_output: false + file_output: true + file_path: "/var/log/healthchain/interop.log" + rotate_logs: true + max_log_size_mb: 10 + backup_count: 5 + +# Error handling for production +errors: + retry_count: 3 + fail_on_critical: true + verbose_errors: false + +# Performance settings for production +performance: + cache_templates: true # Enable caching for better performance + cache_mappings: true # Enable caching for better performance + batch_size: 100 # Larger batch size for better throughput + +# Default resource fields for production +defaults: + common: + id_prefix: "hc-" # Production ID prefix + subject: + reference: "Patient/example" + +# Template settings for production +templates: + reload_on_change: false # Don't reload templates in production diff --git a/healthchain/configs/environments/testing.yaml b/healthchain/configs/environments/testing.yaml new file mode 100644 index 00000000..2c6aca95 --- /dev/null +++ b/healthchain/configs/environments/testing.yaml @@ -0,0 +1,39 @@ +# Testing Environment Configuration +# This file contains settings specific to the testing environment +# TODO: Implement +# Logging settings for testing +logging: + level: "INFO" + include_timestamps: true + console_output: true + file_output: true + file_path: "./logs/test-interop.log" + +# Error handling for testing +errors: + retry_count: 2 + fail_on_critical: true + verbose_errors: true + +# Performance settings for testing +performance: + cache_templates: true # Enable caching for realistic testing + cache_mappings: true # Enable caching for realistic testing + batch_size: 50 # Medium batch size for testing + +# Default resource fields for testing +defaults: + common: + id_prefix: "test-" # Testing-specific ID prefix + subject: + reference: "Patient/test-example" + +# Template settings for testing +templates: + reload_on_change: false # Don't reload templates in testing + +# Validation settings for testing +validation: + strict_mode: true + warn_on_missing: true + ignore_unknown_fields: false # Stricter validation for testing diff --git a/healthchain/configs/interop/cda/document/ccd.yaml b/healthchain/configs/interop/cda/document/ccd.yaml new file mode 100644 index 00000000..2afe5eed --- /dev/null +++ b/healthchain/configs/interop/cda/document/ccd.yaml @@ -0,0 +1,55 @@ +# CCD Document Configuration +# This file contains configuration for CCD documents + +# Document templates (required) +templates: + document: "fhir_cda/document" + section: "fhir_cda/section" + +# Basic document information +code: + code: "34133-9" + code_system: "2.16.840.1.113883.6.1" + code_system_name: "LOINC" + display: "Summarization of Episode Note" +confidentiality_code: + code: "N" + code_system: "2.16.840.1.113883.5.25" +language_code: "en-US" +realm_code: "GB" +type_id: + extension: "POCD_HD000040" + root: "2.16.840.1.113883.1.3" +template_id: + root: "1.2.840.114350.1.72.1.51693" + +# Document structure +structure: + # Header configuration + header: + include_patient: false + include_author: false + include_custodian: false + include_legal_authenticator: false + + # Body configuration + body: + structured_body: true + non_xml_body: false + include_sections: + - "allergies" + - "medications" + - "problems" + - "notes" + +# Rendering configuration +rendering: + # XML formatting + xml: + pretty_print: true + encoding: "UTF-8" + + # Narrative generation + narrative: + include: true + generate_if_missing: true diff --git a/healthchain/configs/interop/cda/sections/allergies.yaml b/healthchain/configs/interop/cda/sections/allergies.yaml new file mode 100644 index 00000000..ab4cc208 --- /dev/null +++ b/healthchain/configs/interop/cda/sections/allergies.yaml @@ -0,0 +1,89 @@ +# Allergies Section Configuration +# ======================== + +# Metadata for both extraction and rendering processes +resource: "AllergyIntolerance" +resource_template: "cda_fhir/allergy_intolerance" +entry_template: "fhir_cda/allergy_entry" + +# Section identifiers (used for extraction) +identifiers: + template_id: "2.16.840.1.113883.10.20.1.2" + code: "48765-2" + code_system: "2.16.840.1.113883.6.1" + code_system_name: "LOINC" + display: "Allergies" + reaction: + template_id: "1.3.6.1.4.1.19376.1.5.3.1.4.5" + severity: + template_id: "1.3.6.1.4.1.19376.1.5.3.1.4.1" + +# Template configuration (used for rendering/generation) +template: + # Act element configuration + act: + template_id: + - "1.3.6.1.4.1.19376.1.5.3.1.4.5.1" + - "1.3.6.1.4.1.19376.1.5.3.1.4.5.3" + - "2.16.840.1.113883.3.88.11.32.6" + - "2.16.840.1.113883.3.88.11.83.6" + status_code: "active" + + # Allergy observation configuration + allergy_obs: + type_code: "SUBJ" + inversion_ind: false + template_id: + - "1.3.6.1.4.1.19376.1.5.3.1.4.5" + - "1.3.6.1.4.1.19376.1.5.3.1.4.6" + - "2.16.840.1.113883.10.20.1.18" + - "1.3.6.1.4.1.19376.1.5.3.1" + - "2.16.840.1.113883.10.20.1.28" + code: "420134006" + code_system: "2.16.840.1.113883.6.96" + code_system_name: "SNOMED CT" + display_name: "Propensity to adverse reactions" + status_code: "completed" + + # Reaction observation configuration + reaction_obs: + template_id: + - "2.16.840.1.113883.10.20.1.54" + - "1.3.6.1.4.1.19376.1.5.3.1.4.5" + code: "RXNASSESS" + status_code: "completed" + + # Severity observation configuration + severity_obs: + template_id: + - "2.16.840.1.113883.10.20.1.55" + - "1.3.6.1.4.1.19376.1.5.3.1.4.1" + code: "SEV" + code_system: "2.16.840.1.113883.5.4" + code_system_name: "ActCode" + display_name: "Severity" + status_code: "completed" + value: + code_system: "2.16.840.1.113883.5.1063" + code_system_name: "SeverityObservation" + + # Clinical status observation configuration + clinical_status_obs: + template_id: + - "2.16.840.1.113883.10.20.1.39" + code: "33999-4" + code_system: "2.16.840.1.113883.6.1" + code_system_name: "LOINC" + display_name: "Status" + status_code: "completed" + +# Rendering configuration (not used) +rendering: + narrative: + include: true + template: "narratives/allergy_narrative" + entry: + include_status: true + include_reaction: true + include_severity: true + include_dates: true diff --git a/healthchain/configs/interop/cda/sections/medications.yaml b/healthchain/configs/interop/cda/sections/medications.yaml new file mode 100644 index 00000000..c37c7195 --- /dev/null +++ b/healthchain/configs/interop/cda/sections/medications.yaml @@ -0,0 +1,72 @@ +# Medications Section Configuration +# ======================== + +# Metadata for both extraction and rendering processes +resource: "MedicationStatement" +resource_template: "cda_fhir/medication_statement" +entry_template: "fhir_cda/medication_entry" + +# Section identifiers (used for extraction) +identifiers: + template_id: "2.16.840.1.113883.10.20.1.8" + code: "10160-0" + code_system: "2.16.840.1.113883.6.1" + code_system_name: "LOINC" + display: "Medications" + clinical_status: + template_id: "2.16.840.1.113883.10.20.1.47" + code: "33999-4" + +# Template configuration (used for rendering/generation) +template: + # Substance administration configuration + substance_admin: + template_id: + - "2.16.840.1.113883.10.20.1.24" + - "2.16.840.1.113883.3.88.11.83.8" + - "1.3.6.1.4.1.19376.1.5.3.1.4.7" + - "1.3.6.1.4.1.19376.1.5.3.1.4.7.1" + - "2.16.840.1.113883.3.88.11.32.8" + status_code: "completed" + + # Manufactured product configuration + manufactured_product: + template_id: + - "1.3.6.1.4.1.19376.1.5.3.1.4.7.2" + - "2.16.840.1.113883.10.20.1.53" + - "2.16.840.1.113883.3.88.11.32.9" + - "2.16.840.1.113883.3.88.11.83.8.2" + + # Clinical status observation configuration + clinical_status_obs: + template_id: "2.16.840.1.113883.10.20.1.47" + status_code: "completed" + code: "33999-4" + code_system: "2.16.840.1.113883.6.1" + code_system_name: "LOINC" + display_name: "Status" + value: + code: "755561003" + code_system: "2.16.840.1.113883.6.96" + code_system_name: "SNOMED CT" + display_name: "Active" + +# Rendering configuration +rendering: + narrative: + include: true + template: "narratives/medication_narrative" + entry: + include_status: true + include_dates: true + include_dosage: true + include_route: true + include_frequency: true + +# Default values for template +defaults: + status_code: "active" + type_code: "REFR" + medication_status_code: "755561003" + medication_status_display: "Active" + medication_status_system: "2.16.840.1.113883.6.96" diff --git a/healthchain/configs/interop/cda/sections/notes.yaml b/healthchain/configs/interop/cda/sections/notes.yaml new file mode 100644 index 00000000..bdbae936 --- /dev/null +++ b/healthchain/configs/interop/cda/sections/notes.yaml @@ -0,0 +1,34 @@ +# Notes Section Configuration +# ===================== + +# Metadata for both extraction and rendering processes +resource: "DocumentReference" +resource_template: "cda_fhir/document_reference" +entry_template: "fhir_cda/note_entry" + +# Section identifiers (used for extraction) +identifiers: + template_id: "1.2.840.114350.1.72.1.200001" + code: "51847-2" + code_system: "2.16.840.1.113883.6.1" + code_system_name: "LOINC" + display: "Progress Notes" + +# Template configuration (used for rendering/generation) +template: + # Note section configuration + note_section: + template_id: + - "1.2.840.114350.1.72.1.200001" + code: "51847-2" + code_system: "2.16.840.1.113883.6.1" + code_system_name: "LOINC" + display_name: "Progress Notes" + status_code: "completed" + +# Rendering configuration +rendering: + narrative: + include: true + template: "narratives/note_narrative" + description: "Progress Notes extracted from CDA notes section" diff --git a/healthchain/configs/interop/cda/sections/problems.yaml b/healthchain/configs/interop/cda/sections/problems.yaml new file mode 100644 index 00000000..92614471 --- /dev/null +++ b/healthchain/configs/interop/cda/sections/problems.yaml @@ -0,0 +1,61 @@ +# Problems Section Configuration +# ======================== + +# Metadata for both extraction and rendering processes +resource: "Condition" +resource_template: "cda_fhir/condition" +entry_template: "fhir_cda/problem_entry" + +# Section identifiers (used for extraction) +identifiers: + template_id: "2.16.840.1.113883.10.20.1.11" + code: "11450-4" + code_system: "2.16.840.1.113883.6.1" + code_system_name: "LOINC" + display: "Problem List" + clinical_status: + template_id: "2.16.840.1.113883.10.20.1.47" + code: "33999-4" + +# Template configuration (used for rendering/generation) +template: + # Act element configuration + act: + template_id: + - "2.16.840.1.113883.10.20.1.27" + - "1.3.6.1.4.1.19376.1.5.3.1.4.5.1" + - "1.3.6.1.4.1.19376.1.5.3.1.4.5.2" + - "2.16.840.1.113883.3.88.11.32.7" + - "2.16.840.1.113883.3.88.11.83.7" + status_code: "completed" + + # Problem observation configuration + problem_obs: + type_code: "SUBJ" + inversion_ind: false + template_id: + - "1.3.6.1.4.1.19376.1.5.3.1.4.5" + - "2.16.840.1.113883.10.20.1.28" + code: "55607006" + code_system: "2.16.840.1.113883.6.96" + code_system_name: "SNOMED CT" + display_name: "Problem" + status_code: "completed" + + # Clinical status observation configuration + clinical_status_obs: + template_id: "2.16.840.1.113883.10.20.1.47" + code: "33999-4" + code_system: "2.16.840.1.113883.6.1" + code_system_name: "LOINC" + display_name: "Status" + status_code: "completed" + +# Rendering configuration (not used) +rendering: + narrative: + include: true + template: "narratives/problem_narrative" + entry: + include_status: true + include_dates: true diff --git a/healthchain/configs/mappings/README.md b/healthchain/configs/mappings/README.md new file mode 100644 index 00000000..dea5d5c6 --- /dev/null +++ b/healthchain/configs/mappings/README.md @@ -0,0 +1,33 @@ +# HealthChain Mappings + +This directory contains mapping configurations used by the HealthChain interoperability module to translate between different healthcare data formats. + +## Organization + +The mappings are organized by the formats they translate between: + +- `cda_default/` - Default mappings for CDA format + - `systems.yaml` - Code system mappings (SNOMED CT, LOINC, etc.) + - `status_codes.yaml` - Status code mappings (active, inactive, resolved) + - `severity_codes.yaml` - Severity code mappings (mild, moderate, severe) + + +## Configuration + +The mapping directory to use is configurable through the `defaults.mappings_dir` configuration value. The default is `"cda_default"`. + +## Structure + +Each mapping file uses a flat structure designed for clarity and simplicity. See the README in each subdirectory for detailed examples and documentation. + +## Adding New Mappings + +To add mappings for new format combinations: + +1. Create a new subdirectory (e.g., `hl7v2/` for HL7v2 mappings, or `cda_local/` for local CDA mappings) +2. Add yaml files for each mapping type needed +3. Update the mapping directory in your configuration to use the new mappings + +## Usage + +These mapping files are loaded automatically by the `InteropConfigManager` and are used by the filters in the `healthchain.interop.filters` module to translate codes between formats. diff --git a/healthchain/configs/mappings/cda_default/README.md b/healthchain/configs/mappings/cda_default/README.md new file mode 100644 index 00000000..817129e4 --- /dev/null +++ b/healthchain/configs/mappings/cda_default/README.md @@ -0,0 +1,53 @@ +# CDA Default Mappings + +This directory contains default mapping configurations used to translate between CDA (Clinical Document Architecture) and FHIR (Fast Healthcare Interoperability Resources) formats. + +## Files + +- `systems.yaml` - Code system mappings between FHIR URLs and CDA OIDs +- `status_codes.yaml` - Status code mappings (FHIR status codes to CDA status codes) +- `severity_codes.yaml` - Severity code mappings (FHIR severity codes to CDA severity codes) + +## Structure + +Each mapping file uses a flat structure designed for clarity and simplicity, consistently using FHIR values as keys mapping to CDA values: + +### systems.yaml +```yaml +"http://snomed.info/sct": + oid: "2.16.840.1.113883.6.96" + name: "SNOMED CT" + +"http://loinc.org": + oid: "2.16.840.1.113883.6.1" + name: "LOINC" +``` + +### status_codes.yaml +```yaml +"active": + code: "55561003" + display: "Active" + +"resolved": + code: "413322009" + display: "Resolved" +``` + +### severity_codes.yaml +```yaml +"severe": + code: "H" + display: "Severe" + +"moderate": + code: "M" + display: "Moderate" +``` + +## Usage + +These mappings are used by the filters in the interoperability module for bidirectional translation between CDA and FHIR: + +1. **FHIR to CDA**: Maps FHIR URLs to CDA OIDs, FHIR status codes to CDA status codes, etc. +2. **CDA to FHIR**: Maps CDA OIDs to FHIR URLs, CDA status codes to FHIR status codes, etc. (reverse lookup) diff --git a/healthchain/configs/mappings/cda_default/severity_codes.yaml b/healthchain/configs/mappings/cda_default/severity_codes.yaml new file mode 100644 index 00000000..e9d63e2b --- /dev/null +++ b/healthchain/configs/mappings/cda_default/severity_codes.yaml @@ -0,0 +1,12 @@ +# Allergy and reaction severity codes (FHIR to CDA) +"severe": + code: "H" + display: "Severe" + +"moderate": + code: "M" + display: "Moderate" + +"mild": + code: "L" + display: "Mild" diff --git a/healthchain/configs/mappings/cda_default/status_codes.yaml b/healthchain/configs/mappings/cda_default/status_codes.yaml new file mode 100644 index 00000000..6203d8cf --- /dev/null +++ b/healthchain/configs/mappings/cda_default/status_codes.yaml @@ -0,0 +1,12 @@ +# Clinical status codes (FHIR to CDA) +"active": + code: "55561003" + display: "Active" + +"resolved": + code: "413322009" + display: "Resolved" + +"inactive": + code: "73425007" + display: "Inactive" diff --git a/healthchain/configs/mappings/cda_default/systems.yaml b/healthchain/configs/mappings/cda_default/systems.yaml new file mode 100644 index 00000000..d7414502 --- /dev/null +++ b/healthchain/configs/mappings/cda_default/systems.yaml @@ -0,0 +1,23 @@ +"http://snomed.info/sct": + oid: "2.16.840.1.113883.6.96" + name: "SNOMED CT" + +"http://loinc.org": + oid: "2.16.840.1.113883.6.1" + name: "LOINC" + +"http://www.nlm.nih.gov/research/umls/rxnorm": + oid: "2.16.840.1.113883.6.88" + name: "RxNorm" + +"http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl": + oid: "2.16.840.1.113883.3.26.1.1" + name: "NCI Thesaurus" + +"http://terminology.hl7.org/CodeSystem/condition-clinical": + oid: "2.16.840.1.113883.6.96" + name: "SNOMED CT" + +"http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical": + oid: "2.16.840.1.113883.6.96" + name: "SNOMED CT" diff --git a/healthchain/configs/templates/cda_fhir/allergy_intolerance.liquid b/healthchain/configs/templates/cda_fhir/allergy_intolerance.liquid new file mode 100644 index 00000000..e77f4a34 --- /dev/null +++ b/healthchain/configs/templates/cda_fhir/allergy_intolerance.liquid @@ -0,0 +1,79 @@ +{ + {% if entry.act.entryRelationship.size %} + {% assign obs = entry.act.entryRelationship[0].observation %} + {% else %} + {% assign obs = entry.act.entryRelationship.observation %} + {% endif %} + + {% assign clinical_status = obs | extract_clinical_status: config %} + {% assign reactions = obs | extract_reactions: config %} + + "resourceType": "AllergyIntolerance" + + {% if clinical_status != blank %} + , + "clinicalStatus": { + "coding": [{ + "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical", + "code": "{{ clinical_status | map_status: 'cda_to_fhir' }}" + }] + } + {% endif %} + + {% if obs.code %} + , + "type": { + "coding": [{ + "system": "{{ obs.code['@codeSystem'] | map_system: 'cda_to_fhir' }}", + "code": "{{ obs.code['@code'] }}", + "display": "{{ obs.code['@displayName'] }}" + }] + } + {% endif %} + + {% if obs.participant.participantRole.playingEntity %} + , + {% assign playing_entity = obs.participant.participantRole.playingEntity %} + "code": { + "coding": [{ + "system": "{{ playing_entity.code['@codeSystem'] | map_system: 'cda_to_fhir' }}", + "code": "{{ playing_entity.code['@code'] }}", + "display": "{{ playing_entity.name | default: playing_entity.code['@displayName'] }}" + }] + } + {% elsif obs.value %} + , + "code": { + "coding": [{ + "system": "{{ obs.value['@codeSystem'] | map_system: 'cda_to_fhir' }}", + "code": "{{ obs.value['@code'] }}", + "display": "{{ obs.value['@displayName'] }}" + }] + } + {% endif %} + + {% if obs.effectiveTime.low['@value'] %} + , + "onsetDateTime": "{{ obs.effectiveTime.low['@value'] | format_date }}" + {% endif %} + + {% if reactions.size > 0 %} + , + "reaction": [ + {% for reaction in reactions %} + { + "manifestation": [{ + "concept": { + "coding": [{ + "system": "{{ reaction.system | map_system: 'cda_to_fhir' }}", + "code": "{{ reaction.code }}", + "display": "{{ reaction.display }}" + }] + } + }]{% if reaction.severity != blank %}, + "severity": "{{ reaction.severity | map_severity: 'cda_to_fhir' }}"{% endif %} + }{% unless forloop.last %},{% endunless %} + {% endfor %} + ] + {% endif %} +} diff --git a/healthchain/configs/templates/cda_fhir/condition.liquid b/healthchain/configs/templates/cda_fhir/condition.liquid new file mode 100644 index 00000000..8089a92b --- /dev/null +++ b/healthchain/configs/templates/cda_fhir/condition.liquid @@ -0,0 +1,49 @@ +{ + "resourceType": "Condition", + {% if entry.act.entryRelationship.is_array %} + {% assign obs = entry.act.entryRelationship[0].observation %} + {% else %} + {% assign obs = entry.act.entryRelationship.observation %} + {% endif %} + {% if obs.entryRelationship.observation.code['@code'] == config.identifiers.clinical_status.code %} + {% if obs.entryRelationship.observation.value %} + "clinicalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", + "code": "{{ obs.entryRelationship.observation.value['@code'] | map_status: 'cda_to_fhir' }}" + }, + { + "system": "{{ obs.entryRelationship.observation.value['@codeSystem'] | map_system: 'cda_to_fhir' }}", + "code": "{{ obs.entryRelationship.observation.value['@code'] }}", + "display": "{{ obs.entryRelationship.observation.value['@displayName'] }}" + } + ] + }{% if true %},{% endif %} + {% endif %} + {% endif %} + "category": [{ + "coding": [{ + "system": "http://terminology.hl7.org/CodeSystem/condition-category", + "code": "problem-list-item", + "display": "Problem List Item" + }] + }]{% if obs.value or obs.effectiveTime %},{% endif %} + {% if obs.value %} + "code": { + "coding": [{ + "system": "{{ obs.value['@codeSystem'] | map_system: 'cda_to_fhir' }}", + "code": "{{ obs.value['@code'] }}", + "display": "{{ obs.value['@displayName'] }}" + }] + }{% if obs.effectiveTime %},{% endif %} + {% endif %} + {% if obs.effectiveTime %} + {% if obs.effectiveTime.low %} + "onsetDateTime": "{{ obs.effectiveTime.low['@value'] | format_date }}"{% if obs.effectiveTime.high %},{% endif %} + {% endif %} + {% if obs.effectiveTime.high %} + "abatementDateTime": "{{ obs.effectiveTime.high['@value'] | format_date }}" + {% endif %} + {% endif %} +} diff --git a/healthchain/configs/templates/cda_fhir/document_reference.liquid b/healthchain/configs/templates/cda_fhir/document_reference.liquid new file mode 100644 index 00000000..657a48b5 --- /dev/null +++ b/healthchain/configs/templates/cda_fhir/document_reference.liquid @@ -0,0 +1,22 @@ +{ + "resourceType": "DocumentReference", + "status": "current", + "type": { + "coding": [{ + "system": "{{ entry.code['@codeSystem'] | map_system: 'cda_to_fhir' }}", + "code": "{{ entry.code['@code'] }}", + "display": "{{ entry.code['@displayName'] }}" + }] + }, + {% if entry.effectiveTime %} + "date": "{{ entry.effectiveTime['@value'] | format_timestamp }}", + {% endif %} + "description": "{{ config.rendering.narrative.description }}", + "content": [{ + "attachment": { + "contentType": "text/plain", + "data": "{{ entry.text | xmldict_to_html | to_base64 }}", + "title": "{{ entry.title }}" + } + }] +} diff --git a/healthchain/configs/templates/cda_fhir/medication_statement.liquid b/healthchain/configs/templates/cda_fhir/medication_statement.liquid new file mode 100644 index 00000000..9d31f1ab --- /dev/null +++ b/healthchain/configs/templates/cda_fhir/medication_statement.liquid @@ -0,0 +1,69 @@ +{ + "resourceType": "MedicationStatement", + {% assign substance_admin = entry.substanceAdministration %} + "status": "{{ substance_admin.statusCode['@code'] | map_status: 'cda_to_fhir' }}", + "medication": { + "concept": { + "coding": [{ + "system": "{{ substance_admin.consumable.manufacturedProduct.manufacturedMaterial.code['@codeSystem'] | map_system: 'cda_to_fhir' }}", + "code": "{{ substance_admin.consumable.manufacturedProduct.manufacturedMaterial.code['@code'] }}", + "display": "{{ substance_admin.consumable.manufacturedProduct.manufacturedMaterial.code['@displayName'] }}" + }] + } + } + + {% comment %}Process effectiveTime and extract period/timing information if exists{% endcomment %} + {% if substance_admin.effectiveTime %} + , + {% assign effective_period = substance_admin.effectiveTime | extract_effective_period %} + {% if effective_period %} + "effectivePeriod": { + {% if effective_period.start %}"start": "{{ effective_period.start }}"{% if effective_period.end %},{% endif %}{% endif %} + {% if effective_period.end %}"end": "{{ effective_period.end }}"{% endif %} + } + {% assign effective_timing = substance_admin.effectiveTime | extract_effective_timing %} + {% if substance_admin.doseQuantity or substance_admin.routeCode or effective_timing %},{% endif %} + {% endif %} + {% endif %} + + {% comment %}Add dosage if any dosage related fields are present{% endcomment %} + {% assign effective_timing = substance_admin.effectiveTime | extract_effective_timing %} + {% if substance_admin.doseQuantity or substance_admin.routeCode or effective_timing %} + {% if substance_admin.effectiveTime == nil %},{% endif %} + "dosage": [ + { + {% if substance_admin.doseQuantity %} + "doseAndRate": [ + { + "doseQuantity": { + "value": {{ substance_admin.doseQuantity['@value'] }}, + "unit": "{{ substance_admin.doseQuantity['@unit'] }}" + } + } + ]{% if substance_admin.routeCode or effective_timing %},{% endif %} + {% endif %} + + {% if substance_admin.routeCode %} + "route": { + "coding": [ + { + "system": "{{ substance_admin.routeCode['@codeSystem'] | map_system: 'cda_to_fhir' }}", + "code": "{{ substance_admin.routeCode['@code'] }}", + "display": "{{ substance_admin.routeCode['@displayName'] }}" + } + ] + }{% if effective_timing %},{% endif %} + {% endif %} + + {% if effective_timing %} + "timing": { + "repeat": { + "period": {{ effective_timing.period }}, + "periodUnit": "{{ effective_timing.periodUnit }}" + } + } + {% endif %} + } + ] + {% endif %} +} diff --git a/healthchain/configs/templates/fhir_cda/allergy_entry.liquid b/healthchain/configs/templates/fhir_cda/allergy_entry.liquid new file mode 100644 index 00000000..303edb73 --- /dev/null +++ b/healthchain/configs/templates/fhir_cda/allergy_entry.liquid @@ -0,0 +1,204 @@ +{ + "act": { + "@classCode": "ACT", + "@moodCode": "EVN", + "templateId": [ + {% for template_id in config.template.act.template_id %} + {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} + {% endfor %} + ], + {% if resource.id %} + "id": {"@root": "{{ resource.id }}"}, + {% endif %} + "code": {"@nullFlavor": "NA"}, + "statusCode": { + "@code": "{{ config.template.act.status_code }}" + }, + "effectiveTime": { + "low": {"@value": "{{ timestamp }}"} + }, + "entryRelationship": { + "@typeCode": "{{ config.template.allergy_obs.type_code }}", + "@inversionInd": {{ config.template.allergy_obs.inversion_ind }}, + "observation": { + "@classCode": "OBS", + "@moodCode": "EVN", + "templateId": [ + {% for template_id in config.template.allergy_obs.template_id %} + {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} + {% endfor %} + ], + {% if resource.id %} + "id": {"@root": "{{ resource.id }}_obs"}, + {% endif %} + "text": { + "reference": {"@value": "{{ text_reference_name }}"} + }, + "statusCode": {"@code": "{{ config.template.allergy_obs.status_code }}"}, + "effectiveTime": { + "low": {"@value": "{{ timestamp }}"} + }, + {% if resource.type %} + "code": { + "@code": "{{ resource.type.coding[0].code }}", + "@codeSystem": "{{ resource.type.coding[0].system | map_system: 'fhir_to_cda' }}", + "@displayName": "{{ resource.type.coding[0].display }}" + }, + {% else %} + "code": { + "@code": "{{ config.template.allergy_obs.code }}", + "@codeSystem": "{{ config.template.allergy_obs.code_system }}", + "@codeSystemName": "{{ config.template.allergy_obs.code_system_name }}", + "@displayName": "{{ config.template.allergy_obs.display_name }}" + }, + {% endif %} + "value": { + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "@xsi:type": "CD", + "@code": "{{ resource.code.coding[0].code }}", + "@codeSystem": "{{ resource.code.coding[0].system | map_system: 'fhir_to_cda' }}", + "@displayName": "{{ resource.code.coding[0].display }}", + "originalText": { + "reference": {"@value": "{{ text_reference_name }}"} + } + }, + "participant": { + "@typeCode": "CSM", + "participantRole": { + "@classCode": "MANU", + "playingEntity": { + "@classCode": "MMAT", + "code": { + "originalText": { + "reference": {"@value": "{{ text_reference_name }}"} + }, + "@code": "{{ resource.code.coding[0].code }}", + "@codeSystem": "{{ resource.code.coding[0].system | map_system: 'fhir_to_cda' }}", + "@displayName": "{{ resource.code.coding[0].display }}" + }, + "name": "{{ resource.code.coding[0].display }}" + } + } + }{% if resource.clinicalStatus or resource.reaction %},{% endif %} + + {% if resource.reaction %} + "entryRelationship": [ + { + "@typeCode": "REFR", + "@inversionInd": true, + "observation": { + "@classCode": "OBS", + "@moodCode": "EVN", + "templateId": {"@root": "{{config.template.clinical_status_obs.template_id}}"}, + "code": { + "@code": "{{ config.template.clinical_status_obs.code }}", + "@codeSystem": "{{ config.template.clinical_status_obs.code_system }}", + "@displayName": "{{ config.template.clinical_status_obs.display_name }}" + }, + "statusCode": {"@code": "{{ config.template.clinical_status_obs.status_code }}"}, + "value": { + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "@xsi:type": "CE", + "@code": "{{ resource.clinicalStatus.coding[0].code | map_status: 'fhir_to_cda' }}", + "@codeSystem": "{{ resource.clinicalStatus.coding[0].system | map_system: 'fhir_to_cda' }}", + "@displayName": "{{ resource.clinicalStatus.coding[0].display }}" + } + } + }, + { + "@typeCode": "MFST", + "observation": { + "@classCode": "OBS", + "@moodCode": "EVN", + "templateId": [ + {% for template_id in config.template.reaction_obs.template_id %} + {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} + {% endfor %} + ], + "id": {"@root": "{{ resource.id }}_reaction"}, + "code": {"@code": "{{ config.template.reaction_obs.code }}"}, + "text": { + "reference": {"@value": "{{ text_reference_name }}reaction"} + }, + "statusCode": {"@code": "{{ config.template.reaction_obs.status_code }}"}, + "effectiveTime": { + "low": {"@value": "{{ timestamp }}"} + }, + "value": { + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "@xsi:type": "CD", + "@code": "{{ resource.reaction[0].manifestation[0].concept.coding[0].code }}", + "@codeSystem": "{{ resource.reaction[0].manifestation[0].concept.coding[0].system | map_system: 'fhir_to_cda' }}", + "@displayName": "{{ resource.reaction[0].manifestation[0].concept.coding[0].display }}", + "originalText": { + "reference": {"@value": "{{ text_reference_name }}reaction"} + } + }{% if resource.reaction[0].severity %}, + "entryRelationship": { + "@typeCode": "SUBJ", + "observation": { + "@classCode": "OBS", + "@moodCode": "EVN", + "templateId": [ + {% for template_id in config.template.severity_obs.template_id %} + {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} + {% endfor %} + ], + "code": { + "@code": "{{ config.template.severity_obs.code }}", + "@codeSystem": "{{ config.template.severity_obs.code_system }}", + "@codeSystemName": "{{ config.template.severity_obs.code_system_name }}", + "@displayName": "{{ config.template.severity_obs.display_name }}" + }, + "text": { + "reference": {"@value": "{{ text_reference_name }}severity"} + }, + "statusCode": {"@code": "{{ config.template.severity_obs.status_code }}"}, + "value": { + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "@xsi:type": "CD", + "@code": "{{ resource.reaction[0].severity | map_severity: 'fhir_to_cda'}}", + "@codeSystem": "{{ config.template.severity_obs.value.code_system }}", + "@codeSystemName": "{{ config.template.severity_obs.value.code_system_name }}", + "@displayName": "{{ resource.reaction[0].severity | map_severity: 'fhir_to_cda'}}" + } + } + } + {% endif %} + } + } + ] + {% else %} + {% if resource.clinicalStatus %} + "entryRelationship": { + "@typeCode": "REFR", + "@inversionInd": true, + "observation": { + "@classCode": "OBS", + "@moodCode": "EVN", + "templateId": [ + {% for template_id in config.template.clinical_status_obs.template_id %} + {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} + {% endfor %} + ], + "code": { + "@code": "{{ config.template.clinical_status_obs.code }}", + "@codeSystem": "{{ config.template.clinical_status_obs.code_system }}", + "@displayName": "{{ config.template.clinical_status_obs.display_name }}" + }, + "statusCode": {"@code": "{{ config.template.clinical_status_obs.status_code }}"}, + "value": { + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "@xsi:type": "CE", + "@code": "{{ resource.clinicalStatus.coding[0].code }}", + "@codeSystem": "{{ resource.clinicalStatus.coding[0].system | map_system: 'fhir_to_cda' }}", + "@displayName": "{{ resource.clinicalStatus.coding[0].display }}" + } + } + } + {% endif %} + {% endif %} + } + } + } +} diff --git a/healthchain/configs/templates/fhir_cda/document.liquid b/healthchain/configs/templates/fhir_cda/document.liquid new file mode 100644 index 00000000..460d7196 --- /dev/null +++ b/healthchain/configs/templates/fhir_cda/document.liquid @@ -0,0 +1,41 @@ +{ + "ClinicalDocument": { + "@xmlns": "urn:hl7-org:v3", + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "id": { + "@root": "{{ bundle.identifier.value | generate_id }}" + }, + "realmCode": { + "@code": "{{ config.realm_code }}" + }, + "typeId": { + "@extension": "{{ config.type_id.extension}}", + "@root": "{{ config.type_id.root }}" + }, + "templateId": { + "@root": "{{ config.template_id.root }}" + }, + "code": { + "@code": "{{ config.code.code }}", + "@codeSystem": "{{ config.code.code_system }}", + "@codeSystemName": "{{ config.code.code_system_name }}", + "@displayName": "{{ config.code.display }}" + }, + "title": "Clinical Document", + "effectiveTime": { + "@value": "{{ bundle.timestamp | format_timestamp }}" + }, + "confidentialityCode": { + "@code": "{{ config.confidentiality_code.code }}", + "@codeSystem": "{{ config.confidentiality_code.code_system }}" + }, + "languageCode": { + "@code": "{{ config.language_code }}" + }, + "component": { + "structuredBody": { + "component": {{ sections | json }} + } + } + } +} diff --git a/healthchain/configs/templates/fhir_cda/medication_entry.liquid b/healthchain/configs/templates/fhir_cda/medication_entry.liquid new file mode 100644 index 00000000..65f00954 --- /dev/null +++ b/healthchain/configs/templates/fhir_cda/medication_entry.liquid @@ -0,0 +1,111 @@ +{ + "substanceAdministration": { + "@classCode": "SBADM", + "@moodCode": "INT", + "templateId": [ + {% for template_id in config.template.substance_admin.template_id %} + {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} + {% endfor %} + ], + {% if resource.id %} + "id": {"@root": "{{ resource.id }}"}, + {% endif %} + "statusCode": {"@code": "{{ config.template.substance_admin.status_code }}"}, + {% if resource.dosage and resource.dosage[0].doseAndRate %} + "doseQuantity": { + "@value": "{{ resource.dosage[0].doseAndRate[0].doseQuantity.value }}", + "@unit": "{{ resource.dosage[0].doseAndRate[0].doseQuantity.unit }}" + }, + {% endif %} + {% if resource.dosage and resource.dosage[0].route %} + "routeCode": { + "@code": "{{ resource.dosage[0].route.coding[0].code }}", + "@codeSystem": "{{ resource.dosage[0].route.coding[0].system | map_system: 'fhir_to_cda' }}", + "@displayName": "{{ resource.dosage[0].route.coding[0].display }}" + }, + {% endif %} + {% if resource.dosage and resource.dosage[0].timing or resource.effectivePeriod %} + "effectiveTime": [ + {% if resource.dosage and resource.dosage[0].timing %} + { + "@xsi:type": "PIVL_TS", + "@institutionSpecified": true, + "@operator": "A", + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "period": { + "@unit": "{{ resource.dosage[0].timing.repeat.periodUnit }}", + "@value": "{{ resource.dosage[0].timing.repeat.period }}" + } + }{% if resource.effectivePeriod %},{% endif %} + {% endif %} + {% if resource.effectivePeriod %} + { + "@xsi:type": "IVL_TS", + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + {% if resource.effectivePeriod.start %} + "low": { + "@value": "{{ resource.effectivePeriod.start | format_date }}" + }, + {% else %} + "low": {"@nullFlavor": "UNK"}, + {% endif %} + {% if resource.effectivePeriod.end %} + "high": { + "@value": "{{ resource.effectivePeriod.end }}" + } + {% else %} + "high": {"@nullFlavor": "UNK"} + {% endif %} + } + {% endif %} + ], + {% endif %} + "consumable": { + "@typeCode": "CSM", + "manufacturedProduct": { + "@classCode": "MANU", + "templateId": [ + {% for template_id in config.template.manufactured_product.template_id %} + {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} + {% endfor %} + ], + "manufacturedMaterial": { + "code": { + "@code": "{{ resource.medication.concept.coding[0].code }}", + "@codeSystem": "{{ resource.medication.concept.coding[0].system | map_system: 'fhir_to_cda' }}", + "@displayName": "{{ resource.medication.concept.coding[0].display }}", + "originalText": { + "reference": {"@value": "{{ text_reference_name }}"} + } + } + } + } + }, + "entryRelationship": { + "@typeCode": "REFR", + "observation": { + "@classCode": "OBS", + "@moodCode": "EVN", + "templateId": {"@root": "{{ config.template.clinical_status_obs.template_id }}"}, + "code": { + "@code": "{{ config.template.clinical_status_obs.code }}", + "@codeSystem": "{{ config.template.clinical_status_obs.code_system }}", + "@codeSystemName": "{{ config.template.clinical_status_obs.code_system_name }}", + "@displayName": "{{ config.template.clinical_status_obs.display_name }}" + }, + "value": { + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "@code": "{{ config.template.clinical_status_obs.value.code }}", + "@codeSystem": "{{ config.template.clinical_status_obs.value.code_system }}", + "@codeSystemName": "{{ config.template.clinical_status_obs.value.code_system_name }}", + "@xsi:type": "CE", + "@displayName": "{{ config.template.clinical_status_obs.value.display_name }}" + }, + "statusCode": {"@code": "{{ config.template.clinical_status_obs.status_code }}"}, + "effectiveTime": { + "low": {"@value": "{{ timestamp }}"} + } + } + } + } +} diff --git a/healthchain/configs/templates/fhir_cda/note_entry.liquid b/healthchain/configs/templates/fhir_cda/note_entry.liquid new file mode 100644 index 00000000..3403f958 --- /dev/null +++ b/healthchain/configs/templates/fhir_cda/note_entry.liquid @@ -0,0 +1,23 @@ +{ + "component": { + "section": { + "templateId": [ + {% for template_id in config.template.note_section.template_id %} + {"@root": "{{ template_id }}"} {% if forloop.last != true %},{% endif %} + {% endfor %} + ], + "code": { + "@code": "{{ resource.type.coding[0].code | default: config.template.note_section.code }}", + "@codeSystem": "{{ resource.type.coding[0].system | map_system: 'fhir_to_cda' | default: config.template.note_section.code_system }}", + "@displayName": "{{ resource.type.coding[0].display | default: config.template.note_section.display_name }}" + }, + "title": "{{ resource.content[0].attachment.title }}", + {% if resource.date %} + "effectiveTime": { + "@value": "{{ resource.date | format_date: 'cda' }}" + }, + {% endif %} + "text": {{ resource.content[0].attachment.data | from_base64 | json }} + } + } +} diff --git a/healthchain/configs/templates/fhir_cda/problem_entry.liquid b/healthchain/configs/templates/fhir_cda/problem_entry.liquid new file mode 100644 index 00000000..68deb288 --- /dev/null +++ b/healthchain/configs/templates/fhir_cda/problem_entry.liquid @@ -0,0 +1,92 @@ +{ + "act": { + "@classCode": "ACT", + "@moodCode": "EVN", + "templateId": [ + {% for template_id in config.template.act.template_id %} + {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} + {% endfor %} + ], + {% if resource.id %} + "id": {"@root": "{{ resource.id }}"}, + {% endif %} + "code": {"@nullFlavor": "NA"}, + "statusCode": { + "@code": "{{ config.template.act.status_code }}" + }, + "effectiveTime": { + "low": {"@value": "{{ timestamp }}"} + }, + "entryRelationship": { + "@typeCode": "{{ config.template.problem_obs.type_code }}", + "@inversionInd": {{ config.template.problem_obs.inversion_ind }}, + "observation": { + "@classCode": "OBS", + "@moodCode": "EVN", + "templateId": [ + {% for template_id in config.template.problem_obs.template_id %} + {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} + {% endfor %} + ], + {% if resource.id %} + "id": {"@root": "{{ resource.id }}_obs"}, + {% endif %} + "code": { + "@code": "{{ config.template.problem_obs.code }}", + "@codeSystem": "{{ config.template.problem_obs.code_system }}", + "@codeSystemName": "{{ config.template.problem_obs.code_system_name }}", + "@displayName": "{{ config.template.problem_obs.display_name }}" + }, + "text": { + "reference": {"@value": "{{ text_reference_name }}"} + }, + "statusCode": {"@code": "{{ config.template.problem_obs.status_code }}"}, + "effectiveTime": { + {% if resource.onsetDateTime and resource.abatementDateTime %} + "low": {"@value": "{{ resource.onsetDateTime }}"}, + "high": {"@value": "{{ resource.abatementDateTime }}"} + {% elsif resource.onsetDateTime %} + "low": {"@value": "{{ resource.onsetDateTime }}"} + {% elsif resource.abatementDateTime %} + "high": {"@value": "{{ resource.abatementDateTime }}"} + {% endif %} + }, + "value": { + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "@xsi:type": "CD", + "@code": "{{ resource.code.coding[0].code }}", + "@codeSystem": "{{ resource.code.coding[0].system | map_system: 'fhir_to_cda' }}", + "@displayName": "{{ resource.code.coding[0].display }}", + "originalText": { + "reference": {"@value": "{{ text_reference_name }}"} + } + }, + "entryRelationship": { + "@typeCode": "REFR", + "observation": { + "@classCode": "OBS", + "@moodCode": "EVN", + "templateId": {"@root": "{{ config.template.clinical_status_obs.template_id }}"}, + "code": { + "@code": "{{ config.template.clinical_status_obs.code }}", + "@codeSystem": "{{ config.template.clinical_status_obs.code_system }}", + "@codeSystemName": "{{config.template.clinical_status_obs.code_system_name }}", + "@displayName": "{{ config.template.clinical_status_obs.display_name }}" + }, + "value": { + "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "@code": "{{ resource.clinicalStatus.coding[0].code | map_status: 'fhir_to_cda' }}", + "@codeSystem": "{{ resource.clinicalStatus.coding[0].system | map_system: 'fhir_to_cda' }}", + "@displayName": "{{ resource.clinicalStatus.coding[0].display }}", + "@xsi:type": "CE" + }, + "statusCode": {"@code": "{{ config.template.clinical_status_obs.status_code }}"}, + "effectiveTime": { + "low": {"@value": "{{ timestamp }}"} + } + } + } + } + } + } +} diff --git a/healthchain/configs/templates/fhir_cda/section.liquid b/healthchain/configs/templates/fhir_cda/section.liquid new file mode 100644 index 00000000..3e35256c --- /dev/null +++ b/healthchain/configs/templates/fhir_cda/section.liquid @@ -0,0 +1,15 @@ +{ + "section": { + "templateId": { + "@root": "{{ config.identifiers.template_id }}" + }, + "code": { + "@code": "{{ config.identifiers.code }}", + "@codeSystem": "{{ config.identifiers.code_system | default: '2.16.840.1.113883.6.1' }}", + "@codeSystemName": "{{ config.identifiers.code_system_name | default: 'LOINC' }}", + "@displayName": "{{ config.identifiers.display }}" + }, + "title": "{{ config.identifiers.display }}", + "entry": {{ entries | json }} + } +} diff --git a/healthchain/interop/__init__.py b/healthchain/interop/__init__.py index 1ac2df07..0326ebcc 100644 --- a/healthchain/interop/__init__.py +++ b/healthchain/interop/__init__.py @@ -15,20 +15,88 @@ import logging from pathlib import Path -from typing import Optional +from typing import Optional, Union + +try: + from importlib import resources +except ImportError: + # Python < 3.9 fallback + try: + import importlib_resources as resources + except ImportError: + resources = None + + +def _get_bundled_configs() -> Path: + """Get path to bundled default configs. + + Returns: + Path to bundled configuration directory + """ + if resources: + try: + # Modern approach (Python 3.9+) + configs_ref = resources.files("healthchain") / "configs" + if hasattr(resources, "as_file"): + # For Python 3.9+ + with resources.as_file(configs_ref) as config_path: + return Path(config_path) + else: + # For older importlib_resources + return Path(str(configs_ref)) + except Exception: + pass + + # Fallback for development/editable installs + return Path(__file__).parent.parent / "configs" + + +def init_config_templates(target_dir: str = "./healthchain_configs") -> Path: + """Copy default configuration templates to a directory for customization. + + Creates a complete set of customizable configuration files that users can + modify for their specific interoperability needs. + + Args: + target_dir: Directory to create configuration templates in + + Returns: + Path to the created configuration directory + + Raises: + FileExistsError: If target directory already exists + OSError: If unable to copy configuration files + """ + import shutil + + source = _get_bundled_configs() + target = Path(target_dir) + + if target.exists(): + raise FileExistsError(f"Target directory already exists: {target}") + + try: + shutil.copytree(source, target) + print(f"✅ Configuration templates copied to {target}") + print(f"📝 Customize them, then use: create_engine(config_dir='{target}')") + print("📚 See documentation for configuration options") + return target + except Exception as e: + raise OSError(f"Failed to copy configuration templates: {str(e)}") def create_engine( - config_dir: Optional[Path] = None, + config_dir: Optional[Union[str, Path]] = None, validation_level: str = "strict", environment: str = "development", ) -> InteropEngine: """Create and initialize an InteropEngine instance Creates a configured InteropEngine for converting between healthcare data formats. + Automatically discovers configuration files from local directory or bundled defaults. Args: - config_dir: Base directory containing configuration files. If None, defaults to "configs" + config_dir: Base directory containing configuration files. If None, auto-discovers configs validation_level: Level of configuration validation ("strict", "warn", "ignore") environment: Configuration environment to use ("development", "testing", "production") @@ -39,11 +107,22 @@ def create_engine( ValueError: If config_dir doesn't exist or if validation_level/environment has invalid values """ logger = logging.getLogger(__name__) + if config_dir is None: - logger.warning("config_dir is not provided, looking for configs in /configs") - config_dir = Path("configs") - if not config_dir.exists(): - raise ValueError("config_dir does not exist") + # Try local configs first (for customization), fall back to bundled + local_configs = Path("configs") + if local_configs.exists(): + config_dir = local_configs + logger.info("Using local configs from ./configs") + else: + config_dir = _get_bundled_configs() + logger.info("Using bundled default configs") + else: + # Convert string to Path if needed + config_dir = Path(config_dir) + + if not config_dir.exists(): + raise ValueError(f"Config directory does not exist: {config_dir}") # TODO: Remove this once we have a proper environment system if environment not in ["development", "testing", "production"]: @@ -67,5 +146,7 @@ def create_engine( # Generators "CDAGenerator", "FHIRGenerator", + # Factory functions "create_engine", + "init_config_templates", ] diff --git a/pyproject.toml b/pyproject.toml index c2a679f4..916bd0e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,11 @@ classifiers = [ "Programming Language :: Python :: 3", "Topic :: Scientific/Engineering :: Artificial Intelligence", ] -include = ["healthchain/templates/*"] +include = [ + "healthchain/templates/*", + "healthchain/configs/**/*.yaml", + "healthchain/configs/**/*.liquid" +] [project.urls] "Homepage" = "https://dotimplement.github.io/HealthChain/" From c2a23239dcd42a2b886943a711823939f3f7450d Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Thu, 31 Jul 2025 18:30:06 +0100 Subject: [PATCH 08/19] Add interop template copying cli --- healthchain/cli.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/healthchain/cli.py b/healthchain/cli.py index 72c2fbb7..d4949a1b 100644 --- a/healthchain/cli.py +++ b/healthchain/cli.py @@ -10,6 +10,28 @@ def run_file(filename): print(f"An error occurred while trying to run the file: {e}") +def init_configs(target_dir: str): + """Initialize configuration templates for customization.""" + try: + from healthchain.interop import init_config_templates + + target_path = init_config_templates(target_dir) + print(f"\n🎉 Success! Configuration templates created at: {target_path}") + print("\n📖 Next steps:") + print(" 1. Customize the configuration files in the created directory") + print(" 2. Use them in your code:") + print(" from healthchain.interop import create_engine") + print(f" engine = create_engine(config_dir='{target_dir}')") + print("\n📚 See documentation for configuration options") + + except FileExistsError as e: + print(f"❌ Error: {str(e)}") + print("💡 Tip: Choose a different directory name or remove the existing one") + except Exception as e: + print(f"❌ Error initializing configs: {str(e)}") + print("💡 Tip: Make sure HealthChain is properly installed") + + def main(): parser = argparse.ArgumentParser(description="HealthChain command-line interface") subparsers = parser.add_subparsers(dest="command", required=True) @@ -18,10 +40,25 @@ def main(): run_parser = subparsers.add_parser("run", help="Run a specified file") run_parser.add_argument("filename", type=str, help="The filename to run") + # Subparser for the 'init-configs' command + init_parser = subparsers.add_parser( + "init-configs", + help="Initialize configuration templates for interop customization", + ) + init_parser.add_argument( + "target_dir", + type=str, + nargs="?", + default="./healthchain_configs", + help="Directory to create configuration templates (default: ./healthchain_configs)", + ) + args = parser.parse_args() if args.command == "run": run_file(args.filename) + elif args.command == "init-configs": + init_configs(args.target_dir) if __name__ == "__main__": From 367ac1d7b7b43dc02ce8eca96cbba974325ba663 Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Fri, 1 Aug 2025 10:36:00 +0100 Subject: [PATCH 09/19] Rename create_engine to create_interop --- healthchain/cli.py | 4 ++-- healthchain/interop/__init__.py | 6 +++--- healthchain/io/cdaadapter.py | 4 ++-- healthchain/io/cdaconnector.py | 4 ++-- tests/pipeline/test_cdaconnector.py | 12 ++++++------ 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/healthchain/cli.py b/healthchain/cli.py index d4949a1b..292b55bf 100644 --- a/healthchain/cli.py +++ b/healthchain/cli.py @@ -20,8 +20,8 @@ def init_configs(target_dir: str): print("\n📖 Next steps:") print(" 1. Customize the configuration files in the created directory") print(" 2. Use them in your code:") - print(" from healthchain.interop import create_engine") - print(f" engine = create_engine(config_dir='{target_dir}')") + print(" from healthchain.interop import create_interop") + print(f" engine = create_interop(config_dir='{target_dir}')") print("\n📚 See documentation for configuration options") except FileExistsError as e: diff --git a/healthchain/interop/__init__.py b/healthchain/interop/__init__.py index 0326ebcc..9682d8e7 100644 --- a/healthchain/interop/__init__.py +++ b/healthchain/interop/__init__.py @@ -78,14 +78,14 @@ def init_config_templates(target_dir: str = "./healthchain_configs") -> Path: try: shutil.copytree(source, target) print(f"✅ Configuration templates copied to {target}") - print(f"📝 Customize them, then use: create_engine(config_dir='{target}')") + print(f"📝 Customize them, then use: create_interop(config_dir='{target}')") print("📚 See documentation for configuration options") return target except Exception as e: raise OSError(f"Failed to copy configuration templates: {str(e)}") -def create_engine( +def create_interop( config_dir: Optional[Union[str, Path]] = None, validation_level: str = "strict", environment: str = "development", @@ -147,6 +147,6 @@ def create_engine( "CDAGenerator", "FHIRGenerator", # Factory functions - "create_engine", + "create_interop", "init_config_templates", ] diff --git a/healthchain/io/cdaadapter.py b/healthchain/io/cdaadapter.py index 4dd7b709..ac7be23a 100644 --- a/healthchain/io/cdaadapter.py +++ b/healthchain/io/cdaadapter.py @@ -3,7 +3,7 @@ from healthchain.io.containers import Document from healthchain.io.base import BaseAdapter -from healthchain.interop import create_engine, FormatType, InteropEngine +from healthchain.interop import create_interop, FormatType, InteropEngine from healthchain.models.requests.cdarequest import CdaRequest from healthchain.models.responses.cdaresponse import CdaResponse from healthchain.fhir import ( @@ -50,7 +50,7 @@ def __init__(self, engine: Optional[InteropEngine] = None): If None, creates a default engine. """ # Initialize engine with default if not provided - initialized_engine = engine or create_engine() + initialized_engine = engine or create_interop() super().__init__(engine=initialized_engine) self.engine = initialized_engine self.original_cda = None diff --git a/healthchain/io/cdaconnector.py b/healthchain/io/cdaconnector.py index a51f2793..6647c7ba 100644 --- a/healthchain/io/cdaconnector.py +++ b/healthchain/io/cdaconnector.py @@ -2,7 +2,7 @@ from healthchain.io.containers import Document from healthchain.io.base import BaseConnector -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType from healthchain.models.requests.cdarequest import CdaRequest from healthchain.models.responses.cdaresponse import CdaResponse from healthchain.fhir import ( @@ -43,7 +43,7 @@ class CdaConnector(BaseConnector): """ def __init__(self, config_dir: str = None): - self.engine = create_engine(config_dir=config_dir) + self.engine = create_interop(config_dir=config_dir) self.original_cda = None self.note_document_reference = None diff --git a/tests/pipeline/test_cdaconnector.py b/tests/pipeline/test_cdaconnector.py index 9cbedafd..d386462f 100644 --- a/tests/pipeline/test_cdaconnector.py +++ b/tests/pipeline/test_cdaconnector.py @@ -5,7 +5,7 @@ from fhir.resources.documentreference import DocumentReference -@patch("healthchain.io.cdaconnector.create_engine") +@patch("healthchain.io.cdaconnector.create_interop") @patch("healthchain.io.cdaconnector.create_document_reference") @patch("healthchain.io.cdaconnector.read_content_attachment") @patch("healthchain.io.cdaconnector.set_problem_list_item_category") @@ -15,7 +15,7 @@ def test_input( mock_set_problem_category, mock_read_content, mock_create_doc_ref, - mock_create_engine, + mock_create_interop, cda_connector, test_condition, test_medication, @@ -23,7 +23,7 @@ def test_input( ): # Create mock engine mock_engine = Mock() - mock_create_engine.return_value = mock_engine + mock_create_interop.return_value = mock_engine # Mock document reference content extraction mock_read_content.return_value = [{"data": "Extracted note text"}] @@ -108,13 +108,13 @@ def test_input( assert result is mock_doc -@patch("healthchain.io.cdaconnector.create_engine") +@patch("healthchain.io.cdaconnector.create_interop") def test_output( - mock_create_engine, cda_connector, test_condition, test_medication, test_allergy + mock_create_interop, cda_connector, test_condition, test_medication, test_allergy ): # Create mock engine mock_engine = Mock() - mock_create_engine.return_value = mock_engine + mock_create_interop.return_value = mock_engine # Configure mock engine to return CDA XML mock_engine.from_fhir.return_value = "Updated CDA" From 7f81215f0d595ae3cf5657833cc21208ad371f6b Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Fri, 1 Aug 2025 10:36:27 +0100 Subject: [PATCH 10/19] Update docs --- README.md | 4 +- docs/cookbook/interop/basic_conversion.md | 4 +- docs/quickstart.md | 14 +- docs/reference/interop/configuration.md | 139 ++++++++----------- docs/reference/interop/engine.md | 33 +++-- docs/reference/interop/generators.md | 12 +- docs/reference/interop/interop.md | 6 +- docs/reference/interop/mappings.md | 4 +- docs/reference/interop/parsers.md | 8 +- docs/reference/interop/templates.md | 12 +- docs/reference/pipeline/adapters/adapters.md | 4 +- 11 files changed, 118 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index b1943b4e..6f76b829 100644 --- a/README.md +++ b/README.md @@ -217,9 +217,9 @@ response = adapter.format(doc) The InteropEngine is a template-based system that allows you to convert between FHIR, CDA, and HL7v2. ```python -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType -engine = create_engine() +engine = create_interop() with open("tests/data/test_cda.xml", "r") as f: cda_data = f.read() diff --git a/docs/cookbook/interop/basic_conversion.md b/docs/cookbook/interop/basic_conversion.md index bbee570c..25c8f3ac 100644 --- a/docs/cookbook/interop/basic_conversion.md +++ b/docs/cookbook/interop/basic_conversion.md @@ -13,12 +13,12 @@ This tutorial demonstrates how to use the HealthChain interoperability module to First, let's import the required modules and create an interoperability engine: ```python -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType from pathlib import Path import json # Create an engine -engine = create_engine() +engine = create_interop() ``` ## Converting CDA to FHIR diff --git a/docs/quickstart.md b/docs/quickstart.md index b586dade..4d8a719b 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -155,10 +155,10 @@ The HealthChain Interoperability module provides tools for converting between di [(Full Documentation on Interoperability Engine)](./reference/interop/interop.md) ```python -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType # Create an interoperability engine -engine = create_engine() +engine = create_interop() # Load a CDA document with open("tests/data/test_cda.xml", "r") as f: @@ -171,6 +171,16 @@ fhir_resources = engine.to_fhir(cda_xml, src_format=FormatType.CDA) cda_document = engine.from_fhir(fhir_resources, dest_format=FormatType.CDA) ``` +**Need to customize?** Use the CLI to create editable configuration templates: + +```bash +# Create customizable config templates +healthchain init-configs ./my_configs + +# Then use them in your code +engine = create_interop(config_dir="./my_configs") +``` + The interop module provides a flexible, template-based approach to healthcare format conversion: | Feature | Description | diff --git a/docs/reference/interop/configuration.md b/docs/reference/interop/configuration.md index 60d8519a..5eb66304 100644 --- a/docs/reference/interop/configuration.md +++ b/docs/reference/interop/configuration.md @@ -2,6 +2,44 @@ The interoperability module uses a configuration system to control its behavior. This includes mappings between different healthcare data formats, validation rules, and environment-specific settings. +## Configuration Overview + +HealthChain works out-of-the-box with default configurations, but you can customize them for your specific needs. + +### Default Usage + +```python +from healthchain.interop import create_interop + +# Uses bundled default configurations +engine = create_interop() +``` + +### Custom Configuration + +```python +# Use custom config directory +engine = create_interop(config_dir="/path/to/custom/configs") +``` + +### Creating Custom Configs + +To create editable configuration templates: + +```bash +# Create customizable config templates +healthchain init-configs ./my_configs + +# Then use them in your code +engine = create_interop(config_dir="./my_configs") +``` + +This gives you editable copies of: +- **Templates**: CDA ↔ FHIR conversion templates +- **Mappings**: Code system mappings (SNOMED, LOINC, etc.) +- **Validation**: Schema validation rules +- **Environment settings**: Development, testing, production configs + ## Configuration Components | Component | Description | @@ -188,12 +226,12 @@ defaults: ### Basic Configuration Access ```python -from healthchain.interop import create_engine +from healthchain.interop import create_interop # Create an engine -engine = create_engine() +engine = create_interop() # OR -engine = create_engine(config_dir="custom_configs/") +engine = create_interop(config_dir="custom_configs/") # Get all configurations engine.config.get_configs() @@ -202,10 +240,10 @@ engine.config.get_configs() id_prefix = engine.config.get_config_value("defaults.common.id_prefix") # Set the environment (this reloads configuration from the specified environment) -engine = create_engine(environment="production") +engine = create_interop(environment="production") # Validation level is set during initialization or using set_validation_level -engine = create_engine(validation_level="warn") +engine = create_interop(validation_level="warn") # OR engine.config.set_validation_level("strict") @@ -216,10 +254,10 @@ engine.config.set_config_value("cda.sections.problems.identifiers.code", "10160- ### Section Configuration ```python -from healthchain.interop import create_engine +from healthchain.interop import create_interop # Create an engine -engine = create_engine() +engine = create_interop() # Get all section configurations sections = engine.config.get_cda_section_configs() @@ -235,10 +273,10 @@ code = problems_config["identifiers"]["code"] ### Mapping Access ```python -from healthchain.interop import create_engine +from healthchain.interop import create_interop # Create an engine -engine = create_engine() +engine = create_interop() # Get all mappings mappings = engine.config.get_mappings() @@ -249,83 +287,20 @@ snomed = systems.get("http://snomed.info/sct", {}) snomed_oid = snomed.get("oid") # "2.16.840.1.113883.6.96" ``` -## Configuration Loading Order - -The configuration system follows a hierarchical loading order, where each layer can override values from previous layers: - -``` -┌─────────────────────────────────┐ -│ 1. BASE CONFIGURATION │ -│ configs/defaults.yaml │ -│ │ -│ • Default values for all envs │ -│ • Complete set of all options │ -│ • Lowest precedence │ -└─────────────────┬───────────────┘ - │ - ▼ -┌─────────────────────────────────┐ -│ 2. ENVIRONMENT CONFIGURATION │ -│ configs/environments/{env}.yaml│ -│ │ -│ • Environment-specific values │ -│ • Overrides matching defaults │ -│ • Medium precedence │ -└─────────────────┬───────────────┘ - │ - ▼ -┌─────────────────────────────────┐ -│ 3. RUNTIME CONFIGURATION │ -│ Programmatic overrides │ -│ │ -│ • Set via API at runtime │ -│ • For temporary changes │ -│ • Highest precedence │ -└─────────────────────────────────┘ -``` - -### Example of Configuration Override +## Configuration Precedence -Consider the following values set across different configuration layers: +Configuration values are loaded in order of precedence: -1. In `defaults.yaml`: -```yaml -defaults: - common: - id_prefix: "hc-" - subject: - reference: "Patient/example" -``` +1. **Base configuration** (defaults.yaml) - lowest precedence +2. **Environment configuration** (environments/{env}.yaml) - overrides defaults +3. **Runtime overrides** (via API) - highest precedence -2. In `environments/development.yaml`: -```yaml -defaults: - common: - id_prefix: "dev-" - subject: - reference: "Patient/Foo" -``` - -3. Runtime override in code: ```python -from healthchain.interop import create_engine -from healthchain.interop.types import FormatType - -# Create an engine -engine = create_engine() - -# Override a configuration value at runtime -engine.config.set_config_value("defaults.common.id_prefix", "test-") - -with open("tests/data/test_cda.xml", "r") as f: - cda = f.read() - -fhir_resources = engine.to_fhir(cda, FormatType.CDA) -print(fhir_resources[0].id) # Will use "test-" prefix instead of "dev-" or "hc-" +# Example: Override configuration at runtime +engine = create_interop() +engine.config.set_config_value("defaults.common.id_prefix", "custom-") ``` -In this example, the final value of `id_prefix` would be `"test-"` because the runtime override has the highest precedence, followed by the environment configuration, and finally the base configuration. - ## Validation Levels The configuration system supports different validation levels: @@ -337,10 +312,10 @@ The configuration system supports different validation levels: | `ignore` | Ignore configuration errors | ```python -from healthchain.interop import create_engine +from healthchain.interop import create_interop # Create an engine with a specific validation level -engine = create_engine(validation_level="warn") +engine = create_interop(validation_level="warn") # Change the validation level engine.config.set_validation_level("strict") diff --git a/docs/reference/interop/engine.md b/docs/reference/interop/engine.md index 2a77c310..903dabe5 100644 --- a/docs/reference/interop/engine.md +++ b/docs/reference/interop/engine.md @@ -5,10 +5,10 @@ The `InteropEngine` is the core component of the HealthChain interoperability mo ## Basic Usage ```python -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType # Create an interoperability engine -engine = create_engine() +engine = create_interop() # Convert CDA XML to FHIR resources fhir_resources = engine.to_fhir(cda_xml, src_format=FormatType.CDA) @@ -22,23 +22,34 @@ fhir_resources = engine.to_fhir(hl7v2_message, src_format=FormatType.HL7V2) ## Creating an Engine -The `create_engine()` function is the recommended way to create an engine instance: +The `create_interop()` function is the recommended way to create an engine instance: ```python -from healthchain.interop import create_engine +from healthchain.interop import create_interop # Create with default configuration -engine = create_engine() +engine = create_interop() +``` + +### Custom Configuration -# Create with custom configuration directory -from pathlib import Path -config_dir = Path("/path/to/configs") -engine = create_engine(config_dir=config_dir) +```python +# Use custom config directory +engine = create_interop(config_dir="/path/to/custom/configs") -# Create with custom validation level -engine = create_engine(validation_level="warn") +# Create with custom validation level and environment +engine = create_interop(validation_level="warn", environment="production") ``` + +> **💡 Tip:** +> To create editable configuration templates, run: +> +> ```bash +> healthchain init-configs ./my_configs +> ``` +> This will create a `my_configs` directory with editable default configuration templates. + ## Conversion Methods All conversions convert to and from FHIR. diff --git a/docs/reference/interop/generators.md b/docs/reference/interop/generators.md index 5477c914..0c2c5979 100644 --- a/docs/reference/interop/generators.md +++ b/docs/reference/interop/generators.md @@ -20,11 +20,11 @@ The CDA Generator produces Clinical Document Architecture (CDA) XML documents fr ### Usage Examples ```python -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType from healthchain.fhir import create_condition # Create an engine -engine = create_engine() +engine = create_interop() # Use the FHIR helper functions to create a condition resource condition = create_condition( @@ -50,10 +50,10 @@ The FHIR Generator transforms data from other formats into FHIR resources. It cu ### Usage Examples ```python -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType # Create an engine -engine = create_engine() +engine = create_interop() # CDA section entries in dictionary format (@ is used to represent XML attributes) cda_section_entries = { @@ -203,7 +203,7 @@ The HL7v2 Generator produces HL7 version 2 messages from FHIR resources (Coming You can create a custom generator by implementing a class that inherits from `BaseGenerator` and registering it with the engine (this will replace the default generator for the format type): ```python -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType from healthchain.interop.config_manager import InteropConfigManager from healthchain.interop.template_registry import TemplateRegistry from healthchain.interop.generators import BaseGenerator @@ -221,6 +221,6 @@ class CustomGenerator(BaseGenerator): return "Custom output format" # Register the custom generator with the engine -engine = create_engine() +engine = create_interop() engine.register_generator(FormatType.CDA, CustomGenerator(engine.config, engine.template_registry)) ``` diff --git a/docs/reference/interop/interop.md b/docs/reference/interop/interop.md index bd0084c6..03af0478 100644 --- a/docs/reference/interop/interop.md +++ b/docs/reference/interop/interop.md @@ -49,10 +49,10 @@ The main conversion methods are (hold on to your hats): - `.from_fhir()` - Convert FHIR resources to a destination format ```python -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType # Create an interoperability engine -engine = create_engine() +engine = create_interop() # Convert CDA XML to FHIR resources with open('patient_ccd.xml', 'r') as f: @@ -98,7 +98,7 @@ You can customize the engine's behavior for different environments: ```python # Create an engine with specific environment settings -engine = create_engine( +engine = create_interop( config_dir=Path("/path/to/custom/configs"), validation_level="warn", # Options: strict, warn, ignore environment="production" # Options: development, testing, production diff --git a/docs/reference/interop/mappings.md b/docs/reference/interop/mappings.md index c5c756e5..df2c0ded 100644 --- a/docs/reference/interop/mappings.md +++ b/docs/reference/interop/mappings.md @@ -94,10 +94,10 @@ The `severity_codes.yaml` file maps severity designations between formats: The `InteropEngine` automatically loads and applies mappings during the conversion process. You can also access mappings directly through the configuration manager: ```python -from healthchain.interop import create_engine +from healthchain.interop import create_interop # Create an engine -engine = create_engine() +engine = create_interop() # Get all mappings mappings = engine.config.get_mappings() diff --git a/docs/reference/interop/parsers.md b/docs/reference/interop/parsers.md index cb325f6c..90001b32 100644 --- a/docs/reference/interop/parsers.md +++ b/docs/reference/interop/parsers.md @@ -24,10 +24,10 @@ The input data should be in the format `{}: {}`. ### Usage Examples ```python -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType # Create an engine -engine = create_engine() +engine = create_interop() # Parse a CDA document directly to FHIR with open("tests/data/test_cda.xml", "r") as f: @@ -193,7 +193,7 @@ cda: You can create a custom parser by implementing a class that inherits from `BaseParser` and registering it with the engine (this will replace the default parser for the format type): ```python -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType from healthchain.interop.config_manager import InteropConfigManager from healthchain.interop.parsers.base import BaseParser @@ -206,6 +206,6 @@ class CustomParser(BaseParser): return {"structured_data": "example"} # Register the custom parser with the engine -engine = create_engine() +engine = create_interop() engine.register_parser(FormatType.CDA, CustomParser(engine.config)) ``` diff --git a/docs/reference/interop/templates.md b/docs/reference/interop/templates.md index d3968f93..3a114163 100644 --- a/docs/reference/interop/templates.md +++ b/docs/reference/interop/templates.md @@ -97,11 +97,11 @@ Example template for a CDA to FHIR conversion: The Interoperability Engine API provides a high-level interface (`.to_fhir()` and `.from_fhir()`) for transforming healthcare data between different formats using templates. ```python -from healthchain.interop import create_engine, FormatType +from healthchain.interop import create_interop, FormatType from healthchain.fhir import create_condition # Create an engine -engine = create_engine() +engine = create_interop() # Create a FHIR resource condition = create_condition( @@ -124,10 +124,10 @@ fhir_resources = engine.to_fhir(cda_xml, src_format=FormatType.CDA) For advanced use cases, you can access the template system directly: ```python -from healthchain.interop import create_engine +from healthchain.interop import create_interop # Create an engine -engine = create_engine() +engine = create_interop() # Access the template registry registry = engine.template_registry @@ -229,13 +229,13 @@ For more information on using filters, see Liquid's [official documentation](htt You can add custom filters to the template system: ```python -from healthchain.interop import create_engine +from healthchain.interop import create_interop def custom_filter(value): return f"CUSTOM:{value}" # Create an engine -engine = create_engine() +engine = create_interop() # Add a custom filter engine.template_registry.add_filter("custom", custom_filter) diff --git a/docs/reference/pipeline/adapters/adapters.md b/docs/reference/pipeline/adapters/adapters.md index d6ca84cc..f68f96f0 100644 --- a/docs/reference/pipeline/adapters/adapters.md +++ b/docs/reference/pipeline/adapters/adapters.md @@ -75,10 +75,10 @@ Both CDA and CDS adapters can be configured with custom interoperability engines ```python from healthchain.io import CdaAdapter -from healthchain.interop import create_engine +from healthchain.interop import create_interop # Custom engine with specific configuration -custom_engine = create_engine(config_dir="/path/to/custom/config") +custom_engine = create_interop(config_dir="/path/to/custom/config") adapter = CdaAdapter(engine=custom_engine) ``` For more information on the InteropEngine, see the [InteropEngine documentation](../../interop/interop.md). From e9d8d9c4c77f8a648c873130d4dd26760639d77e Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Fri, 1 Aug 2025 10:36:43 +0100 Subject: [PATCH 11/19] Update poetry.lock --- poetry.lock | 176 ++++++++++++++++++++++++++-------------------------- 1 file changed, 88 insertions(+), 88 deletions(-) diff --git a/poetry.lock b/poetry.lock index 3047e90a..8e2047e1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2511,98 +2511,98 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""} [[package]] name = "regex" -version = "2025.7.31" +version = "2025.7.34" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" files = [ - {file = "regex-2025.7.31-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b40a8f8064c3b8032babb2049b7ab40812cbb394179556deb7c40c1e3b28630f"}, - {file = "regex-2025.7.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f6aef1895f27875421e6d8047747702d6e512793c6d95614c56479a375541edb"}, - {file = "regex-2025.7.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f124ff95b4cbedfd762897d4bd9da2b20b7574df1d60d498f16a42d398d524e9"}, - {file = "regex-2025.7.31-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ea5b162c50745694606f50170cc7cc84c14193ac5fd6ecb26126e826a7c12bd6"}, - {file = "regex-2025.7.31-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f970a3e058f587988a18ed4ddff6a6363fa72a41dfb29077d0efe8dc4df00da"}, - {file = "regex-2025.7.31-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2dadf5788af5b10a78b996d24263e352e5f99dbfce9db4c48e9c875a9a7d051c"}, - {file = "regex-2025.7.31-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f67f9f8216a8e645c568daf104abc52cd5387127af8e8b17c7bc11b014d88fcb"}, - {file = "regex-2025.7.31-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:407da7504642830d4211d39dc23b8a9d400913b3f2d242774b8d17ead3487e00"}, - {file = "regex-2025.7.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff7753bd717a9f2286d2171d758eebf11b3bfb21e6520b201e01169ec9cd5ec0"}, - {file = "regex-2025.7.31-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:de088fe37d4c58a42401bf4ce2328b00a760c7d85473ccf6e489094e13452510"}, - {file = "regex-2025.7.31-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:67d708f8bfb89dcd57c3190cb5c343c7f40d3c81319a00c8188982a08c64b977"}, - {file = "regex-2025.7.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3fe81cd00ef1eaef1ef00d61200bacb55b1a130570cd9be2e793b98981c6cd9c"}, - {file = "regex-2025.7.31-cp310-cp310-win32.whl", hash = "sha256:8542ee1fd8c8be4db1c58902956a220bdbe7c38362decec989f57ace0e37f14c"}, - {file = "regex-2025.7.31-cp310-cp310-win_amd64.whl", hash = "sha256:77be56e167e2685828ab0abc1bdf38db3ab385e624c3ea2694b0d4ea70a2b7bc"}, - {file = "regex-2025.7.31-cp310-cp310-win_arm64.whl", hash = "sha256:7ddc7ab76d917cb680a3b0fa53fc2971d40cc17415539007e15fa31c829dcf2b"}, - {file = "regex-2025.7.31-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:55dc9f4094656d273562718d68cd8363f688e0b813d62696aad346bcd7b1c7d4"}, - {file = "regex-2025.7.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8ff37cac0e1c7ba943bf46f6431b0c86cbe42d42ae862ff7b152b4ccc232bdd"}, - {file = "regex-2025.7.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:622aa4ca90d7cf38433d425a4f00543b08d3b109cca379df8f31827cf5e2ecb3"}, - {file = "regex-2025.7.31-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbd4ee61dddfcff625f8642e940ba61121b28e98d0eca24d79114209e3e8ce1b"}, - {file = "regex-2025.7.31-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca7c9af8f33540b51f1b76092e732b62211092af947239e5db471323ae39ead4"}, - {file = "regex-2025.7.31-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:beda88db2cae5dc82a64cba465f7e8686392d96116f87e664af46c4dfcdd9cbc"}, - {file = "regex-2025.7.31-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055baef91bb31474bd919fd245cf154db00cbac449596952d3e6bc1e1b226808"}, - {file = "regex-2025.7.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:02e660c2d02854eed41b13f0e2c98d24efce4fb439aa316742f8d32aeda2803b"}, - {file = "regex-2025.7.31-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4372ca5c43d0e255e68a9aa6812d9be3447c4ce7ba7cb1429c7b96d2c63ee9b1"}, - {file = "regex-2025.7.31-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:481f069facacb4f40bf37a51748a88952f5dd5707dd849f216d53bf5522c8add"}, - {file = "regex-2025.7.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e8b4896ec5a9d0ae73d04e260ff6e1f366985b46505b2fa36d91501e4a7a98f0"}, - {file = "regex-2025.7.31-cp311-cp311-win32.whl", hash = "sha256:47ceaa1e5eb243595306dfd5e5e294e251900aa94a0e2e1037fce125f432d2fb"}, - {file = "regex-2025.7.31-cp311-cp311-win_amd64.whl", hash = "sha256:c4f6b34f509bb26507509b6f9ba85debcc6ca512d2d4a6fd5e96b9de2c187c83"}, - {file = "regex-2025.7.31-cp311-cp311-win_arm64.whl", hash = "sha256:75f74892df1593036e83b48ba50d1e1951af650b6fabbfcf7531e7082e3561d4"}, - {file = "regex-2025.7.31-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1af64eed343f19e1f09da9e9e8cfb82570050c4ed9fec400f9f118aab383da4b"}, - {file = "regex-2025.7.31-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:eab98712c0a6d053fb67b021fae43422f7eab8fa2aaa25034f5ef01585112cc7"}, - {file = "regex-2025.7.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:34dcb7c4d89b83e7e3cb5a2679595f6f97d253815ed9402edbdfc56780668b89"}, - {file = "regex-2025.7.31-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:52f1925d123338835e5b13e5ef8e6a744c02aef8e538e661ad5c76185e6ad87a"}, - {file = "regex-2025.7.31-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:569c2b6812d223ae82a2a13c36362ca5933b88011ba869111eba8fb769ccf492"}, - {file = "regex-2025.7.31-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:27f17ade67d06ce4abff48f2ee99c6419f73e70882fe7ca51960916c75844e1f"}, - {file = "regex-2025.7.31-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:45622fab3a90590a41a541afea739a732bf110dd081c15c84538b115cf5f59f5"}, - {file = "regex-2025.7.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:defab878ce91944baf2ade775895a097ad7eeeab3618d87b4c29753aad98a5c4"}, - {file = "regex-2025.7.31-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8ae02caf994a0a0d958b9b0fc5aebbdb48fa93491a582dd00db3733d258a6ac4"}, - {file = "regex-2025.7.31-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7c40ab21112711363d7612f35781c8b2d2d59c27e0a057a6486eea60ee01e54"}, - {file = "regex-2025.7.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4723c01dd28c1b1de5f463bba8672e3d0dc3d94d5db056e4bbc3cbc84bf23c1c"}, - {file = "regex-2025.7.31-cp312-cp312-win32.whl", hash = "sha256:3ebf32b2b2f60aecd6f8d375ff310849251946cf953aac69b8b5b10e3ccebaf9"}, - {file = "regex-2025.7.31-cp312-cp312-win_amd64.whl", hash = "sha256:12f9ab65b4cc771dd6d8af806ded7425ca50d2a188d2fc3a5aba3dc49f5684b7"}, - {file = "regex-2025.7.31-cp312-cp312-win_arm64.whl", hash = "sha256:fd454ed1fe245f983c2376b6f01948d6ec4a1e5869a8c883e320e1739cc63e57"}, - {file = "regex-2025.7.31-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ead2cf9d92f90d2fd7c5eb84b383a82154298742011b8f892fdee2f724f76106"}, - {file = "regex-2025.7.31-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:81d865d195f9c94b7e7f043c973a7ee1003b29f6e75caa9125aa5a92cf6b334d"}, - {file = "regex-2025.7.31-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3e58b95f62df0300496a2244ac5818312a80a5f786c9727125d62b49deede1b9"}, - {file = "regex-2025.7.31-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc2939e3e1837822803afebe38f42aab739e1135ea63ba0fdfe499672b21fc39"}, - {file = "regex-2025.7.31-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:51211fd9bfe544f7ad543a683bd2546636ce5b55ab65752e8f8ebe477378dfa2"}, - {file = "regex-2025.7.31-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff1359141a378d8fa1ade7ca8a7a94988c830e5e588d232eded0e5900fa953cf"}, - {file = "regex-2025.7.31-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a57aacb1974bd04a5ace8f93c9ab7fa49b868091032b38afd79b2c1ac70da35a"}, - {file = "regex-2025.7.31-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2784d4afa58a87f5f522037d10cf96c05d3a91ab82b2152a66e8ccea55e703f6"}, - {file = "regex-2025.7.31-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:339d1c579cea1d525ef2b2fefdc1f108596b8252acca6ef012a51206d3f01ac4"}, - {file = "regex-2025.7.31-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:3bb9bf5a0c1c1c353bc5da6cb58db8a12b1ec76a9e8dc8a23ce56d63ee867392"}, - {file = "regex-2025.7.31-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1a7bedc5b499bd0a5cc05b3407ab0aa09f224fb9cd13c52253ecb1619957a6b4"}, - {file = "regex-2025.7.31-cp313-cp313-win32.whl", hash = "sha256:c8ae328524e7bb67ae12a9e314d935e7bb67eb5135e57196b0faa4ecab3f2999"}, - {file = "regex-2025.7.31-cp313-cp313-win_amd64.whl", hash = "sha256:8ab2d9cd1c13e7127194b5cb36ecfb323fec0b80845195842d8e8ac9fb581e1b"}, - {file = "regex-2025.7.31-cp313-cp313-win_arm64.whl", hash = "sha256:5560b6c9fb428281b472b665e4d046eaaaf37523135cb1ee3dc699f3e82dae7a"}, - {file = "regex-2025.7.31-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:45fd783fd91ec849c64ebd5c0498ded966e829b8d2ea44daba2a2c35b6b5f4a8"}, - {file = "regex-2025.7.31-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:81a193e6138b61976903357fc7a67dd9e256cf98f73bbfb2758abf3b8d396c35"}, - {file = "regex-2025.7.31-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fccac19e5f1053e4da34ae5a651b938dba12e5f54f04def1cd349b24fd5f28cf"}, - {file = "regex-2025.7.31-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f6755afaed9948dd4dda4d093663fe60e9a8784993b733697551bf6b0921d7c"}, - {file = "regex-2025.7.31-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c7eea6eb0f4c1ff7eee051a6780acc40717be9736bf67873c3c932b7ac5743a2"}, - {file = "regex-2025.7.31-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:89358d48fbc33614185c18b3a397b870e388f13d882f379b9a33c970a4945dcc"}, - {file = "regex-2025.7.31-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8b284b8042d97f4eb9caf4d9423307ee1c9ff9c2abd14c781d44aef070ac7cc9"}, - {file = "regex-2025.7.31-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2348cedab6adee1a7649e2a157d219196044588a58024509def2b8b30c0f63f8"}, - {file = "regex-2025.7.31-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:833292f5ebfbe4f104e02718f0e2d05d51ac43cdc023a217672119989c4a0be6"}, - {file = "regex-2025.7.31-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:74f348e26ff09bb2684c67535f516cec362624566127d9f4158cd7ab5038c1fe"}, - {file = "regex-2025.7.31-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b2d5523c236594c055e5752e088406dfe3214c4e986abeceaea24967371ad890"}, - {file = "regex-2025.7.31-cp314-cp314-win32.whl", hash = "sha256:144d7550d13770ab994ef6616cff552ed01c892499eb1df74b6775e9b6f6a571"}, - {file = "regex-2025.7.31-cp314-cp314-win_amd64.whl", hash = "sha256:5792ff4bb2836ca2b041321eada3a1918f8ba05bceac4f6e9f06f0fefa1b8e44"}, - {file = "regex-2025.7.31-cp314-cp314-win_arm64.whl", hash = "sha256:59b94c02b435d7d5a9621381bf338a36c7efa6d9025a888cc39aa256b2869299"}, - {file = "regex-2025.7.31-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ac97385aadafe3a2f7cb9c48c5ca3cabb91c1f89e47fdf5a55945c61b186254f"}, - {file = "regex-2025.7.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1b600ff5e80d2b4cf2cabc451dab5b9a3ed7e1e5aa845dd5cf41eabefb957179"}, - {file = "regex-2025.7.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1282de93a20d143180bd3500488877d888185a5e78ef02f7cd410140299f0941"}, - {file = "regex-2025.7.31-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b1329dcb4cd688ebabd2560d5a82567e1e3d05885169f6bece40ca9e7dcfe3d"}, - {file = "regex-2025.7.31-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56508bf5da86c96b7f87da70ee28019a1bdd4c0ec31adfcd62300c4a08e927e4"}, - {file = "regex-2025.7.31-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1778b27e2d4e07cf1e3350f1e74dae5d0511d1ca2b001f4d985b0739182ba2a8"}, - {file = "regex-2025.7.31-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:60162442fd631ead1ca58c16f6f9d6b1aa32d2a2f749b51a7b4262fc294105e1"}, - {file = "regex-2025.7.31-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc9eb820140126219ac9d6b488176cfdde2f5e8891b0fbf2cbd2526c0d441d37"}, - {file = "regex-2025.7.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b2b0f700237b73ec0df2e13e2b1c10d36b8ea45c7a3c7eb6d99843c39feaa0e6"}, - {file = "regex-2025.7.31-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:46572b60e9cc5c09e17d5ecb648dc9fb1c44c12274ae791921350f0f6d0eebea"}, - {file = "regex-2025.7.31-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:019ad36e4ea89af6abd2915ffc06b4e109234655148a45f8f32b42ea9b503514"}, - {file = "regex-2025.7.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:261f9a6dcb1fd9dc204cc587fceac2e071720a15fc4fa36156651c886e574ad0"}, - {file = "regex-2025.7.31-cp39-cp39-win32.whl", hash = "sha256:f7858175abee523c5b04cc1de5d3d03168aed4805aad747641752c027aaa6335"}, - {file = "regex-2025.7.31-cp39-cp39-win_amd64.whl", hash = "sha256:097c2adaedf5fba5819df298750cd3966da94fdd549e2d9e5040d7e315de97dd"}, - {file = "regex-2025.7.31-cp39-cp39-win_arm64.whl", hash = "sha256:c28c00fbe30dd5e99162b88765c8d014d06581927ceab8fa851267041e48820c"}, - {file = "regex-2025.7.31.tar.gz", hash = "sha256:80a1af156ea8670ae63184e5c112b481326ece1879e09447f6fbb49d1b49330b"}, + {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]] From 9531ec7962a09ac4fa23ab75dbe249a5c6086bc7 Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Mon, 4 Aug 2025 12:11:22 +0100 Subject: [PATCH 12/19] Fix doc links --- docs/api/connectors.md | 7 ------- docs/api/interop.md | 6 ------ docs/quickstart.md | 2 +- docs/reference/index.md | 2 +- .../pipeline/components/fhirproblemextractor.md | 2 +- docs/reference/pipeline/pipeline.md | 4 ++-- docs/reference/utilities/sandbox.md | 1 - healthchain/interop/config_manager.py | 3 ++- healthchain/interop/engine.py | 14 ++++++++++---- healthchain/interop/generators/cda.py | 4 ++-- healthchain/interop/generators/fhir.py | 4 ++-- healthchain/interop/template_registry.py | 4 ++-- healthchain/io/adapters/cdaadapter.py | 3 +-- mkdocs.yml | 2 +- 14 files changed, 25 insertions(+), 33 deletions(-) delete mode 100644 docs/api/connectors.md diff --git a/docs/api/connectors.md b/docs/api/connectors.md deleted file mode 100644 index dacd769d..00000000 --- a/docs/api/connectors.md +++ /dev/null @@ -1,7 +0,0 @@ -# Connectors (Legacy) - -> **⚠️ Deprecated:** Connectors are deprecated. Use [Adapters](adapters.md) for new projects. - -::: healthchain.io.base -::: healthchain.io.cdaconnector -::: healthchain.io.cdsfhirconnector diff --git a/docs/api/interop.md b/docs/api/interop.md index 1df7b425..71eab4e0 100644 --- a/docs/api/interop.md +++ b/docs/api/interop.md @@ -1,15 +1,9 @@ # Interoperability Engine ::: healthchain.interop.engine - ::: healthchain.interop.config_manager - ::: healthchain.interop.template_registry - ::: healthchain.interop.parsers.cda - ::: healthchain.interop.generators.cda - ::: healthchain.interop.generators.fhir - ::: healthchain.config.validators diff --git a/docs/quickstart.md b/docs/quickstart.md index 4d8a719b..564f5feb 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -199,7 +199,7 @@ For more details, see the [conversion examples](cookbook/interop/basic_conversio Test your AI applications in realistic healthcare contexts with sandbox environments for CDS Hooks and clinical documentation workflows. -[(Full Documentation on Sandbox)](./reference/sandbox/sandbox.md) +[(Full Documentation on Sandbox)](./reference/utilities/sandbox.md) ```python import healthchain as hc diff --git a/docs/reference/index.md b/docs/reference/index.md index 3aa9afeb..a5776a0e 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -4,6 +4,6 @@ - [Gateway](gateway/gateway.md): Connect to multiple healthcare systems and services. - [Pipeline](pipeline/pipeline.md): Build and manage processing pipelines for healthcare NLP and ML tasks. -- [Sandbox](sandbox/sandbox.md): Test your pipelines in a simulated healthcare environment. +- [Sandbox](utilities/sandbox.md): Test your pipelines in a simulated healthcare environment. - [Interoperability](interop/interop.md): Convert between healthcare data formats like FHIR, CDA, and HL7v2. - [Utilities](utilities/fhir_helpers.md): Additional tools for development and testing. diff --git a/docs/reference/pipeline/components/fhirproblemextractor.md b/docs/reference/pipeline/components/fhirproblemextractor.md index d38c7e48..add22c59 100644 --- a/docs/reference/pipeline/components/fhirproblemextractor.md +++ b/docs/reference/pipeline/components/fhirproblemextractor.md @@ -104,4 +104,4 @@ pipeline = MedicalCodingPipeline( - [FHIR Condition Resources](https://www.hl7.org/fhir/condition.html) - [Medical Coding Pipeline](../prebuilt_pipelines/medicalcoding.md) -- [Document Container](../../interop/containers.md) +- [Document Container](../data_container.md) diff --git a/docs/reference/pipeline/pipeline.md b/docs/reference/pipeline/pipeline.md index eefef0da..53626db1 100644 --- a/docs/reference/pipeline/pipeline.md +++ b/docs/reference/pipeline/pipeline.md @@ -12,8 +12,8 @@ HealthChain comes with a set of prebuilt pipelines that are out-of-the-box imple |----------|-----------|----------|-------------|---------------------| | [**MedicalCodingPipeline**](./prebuilt_pipelines/medicalcoding.md) | `Document` | Clinical Documentation | An NLP pipeline that processes free-text clinical notes into structured data | Automatically generating SNOMED CT codes from clinical notes | | [**SummarizationPipeline**](./prebuilt_pipelines/summarization.md) | `Document` | Clinical Decision Support | An NLP pipeline for summarizing clinical notes | Generating discharge summaries from patient history and notes | -| **QAPipeline** [TODO] | `Document` | Conversational AI | A Question Answering pipeline suitable for conversational AI applications | Developing a chatbot to answer patient queries about their medical records | -| **ClassificationPipeline** [TODO] | `Tabular` | Predictive Analytics | A pipeline for machine learning classification tasks | Predicting patient readmission risk based on historical health data | + Prebuilt pipelines are end-to-end workflows optimized for specific healthcare AI tasks. They can be used with adapters for seamless integration with EHR systems via [protocols](../gateway/gateway.md). diff --git a/docs/reference/utilities/sandbox.md b/docs/reference/utilities/sandbox.md index cc6c8cba..4bfe4c1f 100644 --- a/docs/reference/utilities/sandbox.md +++ b/docs/reference/utilities/sandbox.md @@ -35,7 +35,6 @@ class TestCDS(ClinicalDecisionSupport): The `@hc.ehr` decorator simulates EHR client behavior for testing. You must specify a **workflow** that determines how your data will be formatted. -Data should be wrapped in a [Prefetch](../../../api/data_models.md#healthchain.models.data.prefetch) object for CDS workflows, or return appropriate FHIR resources for document workflows. === "Clinical Decision Support" ```python diff --git a/healthchain/interop/config_manager.py b/healthchain/interop/config_manager.py index 8f0aba0a..7080330f 100644 --- a/healthchain/interop/config_manager.py +++ b/healthchain/interop/config_manager.py @@ -5,6 +5,7 @@ """ import logging +from pathlib import Path from typing import Dict, Optional, List, Type from pydantic import BaseModel @@ -43,7 +44,7 @@ class InteropConfigManager(ConfigManager): def __init__( self, - config_dir, + config_dir: Path, validation_level: str = ValidationLevel.STRICT, environment: Optional[str] = None, ): diff --git a/healthchain/interop/engine.py b/healthchain/interop/engine.py index ddf71b95..db7258d5 100644 --- a/healthchain/interop/engine.py +++ b/healthchain/interop/engine.py @@ -1,7 +1,7 @@ import logging from functools import cached_property -from typing import List, Union, Optional +from typing import List, Union, Optional, Any from pathlib import Path from fhir.resources.resource import Resource @@ -10,6 +10,8 @@ from healthchain.config.base import ValidationLevel from healthchain.interop.config_manager import InteropConfigManager +from healthchain.interop.generators.base import BaseGenerator +from healthchain.interop.parsers.base import BaseParser from healthchain.interop.types import FormatType, validate_format from healthchain.interop.parsers.cda import CDAParser @@ -191,7 +193,9 @@ def _get_generator(self, format_type: FormatType): return self._generators[format_type] - def register_parser(self, format_type: FormatType, parser_instance): + def register_parser( + self, format_type: FormatType, parser_instance: BaseParser + ) -> "InteropEngine": """Register a custom parser for a format type. This will replace the default parser for the format type. Args: @@ -207,7 +211,9 @@ def register_parser(self, format_type: FormatType, parser_instance): self._parsers[format_type] = parser_instance return self - def register_generator(self, format_type: FormatType, generator_instance): + def register_generator( + self, format_type: FormatType, generator_instance: BaseGenerator + ) -> "InteropEngine": """Register a custom generator for a format type. This will replace the default generator for the format type. Args: @@ -299,7 +305,7 @@ def from_fhir( self, resources: Union[List[Resource], Bundle], dest_format: Union[str, FormatType], - **kwargs, + **kwargs: Any, ) -> str: """Convert FHIR resources to a target format diff --git a/healthchain/interop/generators/cda.py b/healthchain/interop/generators/cda.py index e85a11d5..967f44ca 100644 --- a/healthchain/interop/generators/cda.py +++ b/healthchain/interop/generators/cda.py @@ -9,7 +9,7 @@ import xmltodict import uuid from datetime import datetime -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Any from fhir.resources.resource import Resource from healthchain.interop.models.cda import ClinicalDocument @@ -59,7 +59,7 @@ class CDAGenerator(BaseGenerator): ) """ - def transform(self, resources, **kwargs) -> str: + def transform(self, resources: List[Resource], **kwargs: Any) -> str: """Transform FHIR resources to CDA format. Args: diff --git a/healthchain/interop/generators/fhir.py b/healthchain/interop/generators/fhir.py index 570494f3..40e9926f 100644 --- a/healthchain/interop/generators/fhir.py +++ b/healthchain/interop/generators/fhir.py @@ -6,7 +6,7 @@ import uuid import logging -from typing import Dict, List, Optional, Type +from typing import Dict, List, Optional, Type, Any from fhir.resources.resource import Resource from liquid import Template @@ -42,7 +42,7 @@ class FHIRGenerator(BaseGenerator): ) """ - def transform(self, data, **kwargs) -> str: + def transform(self, data: List[Dict], **kwargs: Any) -> List[Resource]: """Transform input data to FHIR resources. Args: diff --git a/healthchain/interop/template_registry.py b/healthchain/interop/template_registry.py index a7442690..cda58802 100644 --- a/healthchain/interop/template_registry.py +++ b/healthchain/interop/template_registry.py @@ -2,7 +2,7 @@ from pathlib import Path from typing import Dict, Callable -from liquid import Environment, FileSystemLoader +from liquid import Environment, FileSystemLoader, Template log = logging.getLogger(__name__) @@ -141,7 +141,7 @@ def _load_templates(self) -> None: log.info(f"Loaded {len(self._templates)} templates") - def get_template(self, template_key: str): + def get_template(self, template_key: str) -> Template: """Get a template by key Args: diff --git a/healthchain/io/adapters/cdaadapter.py b/healthchain/io/adapters/cdaadapter.py index ac7be23a..cb56e5af 100644 --- a/healthchain/io/adapters/cdaadapter.py +++ b/healthchain/io/adapters/cdaadapter.py @@ -30,8 +30,7 @@ class CdaAdapter(BaseAdapter[CdaRequest, CdaResponse]): manipulation of the data within HealthChain pipelines. Attributes: - engine (InteropEngine): The interoperability engine for CDA conversions. - If not provided, the default engine is used. + engine (InteropEngine): The interoperability engine for CDA conversions. If not provided, the default engine is used. original_cda (str): The original CDA document for use in output. note_document_reference (DocumentReference): Reference to the note document extracted from the CDA. diff --git a/mkdocs.yml b/mkdocs.yml index 9f76e91f..90c13394 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -53,7 +53,7 @@ nav: - Working with xmltodict: reference/interop/xmltodict.md - Utilities: - FHIR Helpers: reference/utilities/fhir_helpers.md - - Sandbox: reference/sandbox/sandbox.md + - Sandbox: reference/utilities/sandbox.md - Data Generator: reference/utilities/data_generator.md - API Reference: - api/index.md From 59b5d4d95f45f3f814ae0215548d87bb996b6447 Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Mon, 4 Aug 2025 12:18:28 +0100 Subject: [PATCH 13/19] Remove duplicate files --- healthchain/io/cdaadapter.py | 186 ------------------------------- healthchain/io/cdsfhiradapter.py | 133 ---------------------- 2 files changed, 319 deletions(-) delete mode 100644 healthchain/io/cdaadapter.py delete mode 100644 healthchain/io/cdsfhiradapter.py diff --git a/healthchain/io/cdaadapter.py b/healthchain/io/cdaadapter.py deleted file mode 100644 index ac7be23a..00000000 --- a/healthchain/io/cdaadapter.py +++ /dev/null @@ -1,186 +0,0 @@ -import logging -from typing import Optional - -from healthchain.io.containers import Document -from healthchain.io.base import BaseAdapter -from healthchain.interop import create_interop, FormatType, InteropEngine -from healthchain.models.requests.cdarequest import CdaRequest -from healthchain.models.responses.cdaresponse import CdaResponse -from healthchain.fhir import ( - create_bundle, - set_problem_list_item_category, - create_document_reference, - read_content_attachment, -) -from fhir.resources.condition import Condition -from fhir.resources.medicationstatement import MedicationStatement -from fhir.resources.allergyintolerance import AllergyIntolerance -from fhir.resources.documentreference import DocumentReference - -log = logging.getLogger(__name__) - - -class CdaAdapter(BaseAdapter[CdaRequest, CdaResponse]): - """ - CdaAdapter class for handling CDA (Clinical Document Architecture) documents. - - This adapter facilitates parsing CDA documents into Document objects and formatting - Document objects back into CDA responses. It uses the InteropEngine to convert - between CDA and FHIR formats, preserving clinical content while allowing for - manipulation of the data within HealthChain pipelines. - - Attributes: - engine (InteropEngine): The interoperability engine for CDA conversions. - If not provided, the default engine is used. - original_cda (str): The original CDA document for use in output. - note_document_reference (DocumentReference): Reference to the note document - extracted from the CDA. - - Methods: - parse: Parses a CDA document and extracts clinical data into a Document. - format: Converts a Document back to CDA format and returns a CdaResponse. - """ - - def __init__(self, engine: Optional[InteropEngine] = None): - """ - Initialize CdaAdapter with optional interop engine. - - Args: - engine (Optional[InteropEngine]): Custom interop engine for CDA conversions. - If None, creates a default engine. - """ - # Initialize engine with default if not provided - initialized_engine = engine or create_interop() - super().__init__(engine=initialized_engine) - self.engine = initialized_engine - self.original_cda = None - self.note_document_reference = None - - def parse(self, cda_request: CdaRequest) -> Document: - """ - Parse a CDA document and extract clinical data into a HealthChain Document object. - - This method takes a CdaRequest object as input, parses it using the InteropEngine to convert - CDA to FHIR resources, and creates a Document object with the extracted data. It creates a - DocumentReference for the original CDA XML and extracts clinical data (problems, medications, - allergies) into FHIR resources. - - Args: - cda_request (CdaRequest): Request object containing the CDA XML document to process. - - Returns: - Document: A Document object containing: - - The extracted note text as the document data - - FHIR resources organized into appropriate lists: - - problem_list: List of Condition resources - - medication_list: List of MedicationStatement resources - - allergy_list: List of AllergyIntolerance resources - - DocumentReference resources for the original CDA and extracted notes - - Note: - If a DocumentReference resource is found in the converted FHIR resources, - it is assumed to contain the note text and is stored for later use. - """ - # Store original CDA for later use - self.original_cda = cda_request.document - - # Convert CDA to FHIR using the InteropEngine - fhir_resources = self.engine.to_fhir( - self.original_cda, src_format=FormatType.CDA - ) - - # Create a FHIR DocumentReference for the original CDA document - cda_document_reference = create_document_reference( - data=self.original_cda, - content_type="text/xml", - description="Original CDA Document processed by HealthChain", - attachment_title="Original CDA document in XML format", - ) - - # Extract any DocumentReference resources for notes - note_text = "" - doc = Document(data=note_text) # Create document with empty text initially - - # Create FHIR Bundle and add documents - doc.fhir.bundle = create_bundle() - doc.fhir.add_document_reference(cda_document_reference) - - problem_list = [] - medication_list = [] - allergy_list = [] - - for resource in fhir_resources: - if isinstance(resource, Condition): - problem_list.append(resource) - set_problem_list_item_category(resource) - elif isinstance(resource, MedicationStatement): - medication_list.append(resource) - elif isinstance(resource, AllergyIntolerance): - allergy_list.append(resource) - elif isinstance(resource, DocumentReference): - if ( - resource.content - and resource.content[0].attachment - and resource.content[0].attachment.data is not None - ): - content = read_content_attachment(resource) - if content is not None: - note_text = content[0]["data"] - self.note_document_reference = resource - else: - log.warning( - f"No content found in DocumentReference: {resource.id}" - ) - - doc.fhir.problem_list = problem_list - doc.fhir.medication_list = medication_list - doc.fhir.allergy_list = allergy_list - - # Update document text - doc.data = note_text - - # Add the note document reference - if self.note_document_reference is not None: - doc.fhir.add_document_reference( - self.note_document_reference, parent_id=cda_document_reference.id - ) - - return doc - - def format(self, document: Document) -> CdaResponse: - """ - Convert a Document object back to CDA format and return the response. - - This method takes a Document object containing FHIR resources (problems, - medications, allergies) and converts them back to CDA format using the - InteropEngine. It combines all resources from the document's FHIR lists - and includes the note document reference if available. - - Args: - document (Document): A Document object containing FHIR resources - in problem_list, medication_list, and allergy_list. - - Returns: - CdaResponse: A response object containing the CDA document generated - from the FHIR resources. - """ - # Collect all FHIR resources to convert to CDA - resources = [] - - if document.fhir.problem_list: - resources.extend(document.fhir.problem_list) - - if document.fhir.allergy_list: - resources.extend(document.fhir.allergy_list) - - if document.fhir.medication_list: - resources.extend(document.fhir.medication_list) - - # Add the note document reference - if self.note_document_reference is not None: - resources.append(self.note_document_reference) - - # Convert FHIR resources to CDA using InteropEngine - response_document = self.engine.from_fhir(resources, dest_format=FormatType.CDA) - - return CdaResponse(document=response_document) diff --git a/healthchain/io/cdsfhiradapter.py b/healthchain/io/cdsfhiradapter.py deleted file mode 100644 index 882071a3..00000000 --- a/healthchain/io/cdsfhiradapter.py +++ /dev/null @@ -1,133 +0,0 @@ -import logging -from typing import Optional, Any - -from fhir.resources.documentreference import DocumentReference - -from healthchain.io.containers import Document -from healthchain.io.base import BaseAdapter -from healthchain.models.requests.cdsrequest import CDSRequest -from healthchain.models.responses.cdsresponse import CDSResponse -from healthchain.fhir import read_content_attachment -from healthchain.models.hooks.prefetch import Prefetch - -log = logging.getLogger(__name__) - - -class CdsFhirAdapter(BaseAdapter[CDSRequest, CDSResponse]): - """ - CdsFhirAdapter class for handling FHIR (Fast Healthcare Interoperability Resources) documents - for CDS Hooks. - - This adapter facilitates the conversion between CDSRequest objects and Document objects, - as well as the creation of CDSResponse objects from processed Documents. Unlike CdaAdapter, - this adapter works directly with FHIR data and does not require interop conversion. - - Attributes: - hook_name (str): The name of the CDS Hook being used. - engine (Optional[Any]): Optional interoperability engine (not used by this adapter). - - Methods: - parse: Converts a CDSRequest object into a Document object. - format: Converts a Document object into a CDSResponse object. - """ - - def __init__(self, hook_name: str = None, engine: Optional[Any] = None): - """ - Initialize CdsFhirAdapter with hook name and optional engine. - - Args: - hook_name (str): The name of the CDS Hook being used. Defaults to None. - engine (Optional[Any]): Optional interoperability engine (not used by this adapter). - """ - super().__init__(engine=engine) - self.hook_name = hook_name - - def parse( - self, cds_request: CDSRequest, prefetch_document_key: Optional[str] = "document" - ) -> Document: - """ - Convert a CDSRequest object into a Document object. - - Takes a CDSRequest containing FHIR resources and extracts them into a Document object. - The Document will contain all prefetched FHIR resources in its fhir.prefetch_resources. - If a DocumentReference resource is provided via prefetch_document_key, its text content - will be extracted into Document.data. For multiple attachments, the text content will be - concatenated with newlines. - - Args: - cds_request (CDSRequest): The CDSRequest containing FHIR resources in its prefetch - and/or a FHIR server URL. - prefetch_document_key (str, optional): Key in the prefetch data containing a - DocumentReference resource whose text content should be extracted. - Defaults to "document". - - Returns: - Document: A Document object containing: - - All prefetched FHIR resources in fhir.prefetch_resources - - Any text content from the DocumentReference in data (empty string if none found) - - For multiple attachments, text content is concatenated with newlines - - Raises: - ValueError: If neither prefetch nor fhirServer is provided in cds_request - ValueError: If the prefetch data is invalid or cannot be processed - NotImplementedError: If fhirServer is provided (FHIR server support not implemented) - """ - if cds_request.prefetch is None and cds_request.fhirServer is None: - raise ValueError( - "Either prefetch or fhirServer must be provided to extract FHIR data!" - ) - - if cds_request.fhirServer is not None: - raise NotImplementedError("FHIR server is not implemented yet!") - - # Create an empty Document object - doc = Document(data="") - - # Validate the prefetch data - validated_prefetch = Prefetch(prefetch=cds_request.prefetch) - - # Set the prefetch resources - doc.fhir.prefetch_resources = validated_prefetch.prefetch - - # Extract text content from DocumentReference resource if provided - document_resource = validated_prefetch.prefetch.get(prefetch_document_key) - - if not document_resource: - log.warning( - f"No DocumentReference resource found in prefetch data with key {prefetch_document_key}" - ) - elif isinstance(document_resource, DocumentReference): - try: - attachments = read_content_attachment( - document_resource, include_data=True - ) - for attachment in attachments: - if len(attachments) > 1: - doc.data += attachment.get("data", "") + "\n" - else: - doc.data += attachment.get("data", "") - except Exception as e: - log.warning(f"Error extracting text from DocumentReference: {e}") - - return doc - - def format(self, document: Document) -> CDSResponse: - """ - Convert Document to CDSResponse. - - This method takes a Document object containing CDS cards and actions, - and converts them into a CDSResponse object that follows the CDS Hooks - specification. - - Args: - document (Document): The Document object containing CDS results. - - Returns: - CDSResponse: A response object containing CDS cards and optional system actions. - If no cards are found in the Document, an empty list of cards is returned. - """ - if document.cds.cards is None: - log.warning("No CDS cards found in Document, returning empty list of cards") - return CDSResponse(cards=[]) - - return CDSResponse(cards=document.cds.cards, systemActions=document.cds.actions) From 4a7eb8d49f8a5d08aef4181c3d7bede21ddf1aa8 Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Mon, 4 Aug 2025 15:57:13 +0100 Subject: [PATCH 14/19] Delete duplicate configs/ and move allergies to dev-templates/ --- configs/defaults.yaml | 59 ----- configs/environments/development.yaml | 33 --- configs/environments/production.yaml | 37 ---- configs/environments/testing.yaml | 39 ---- configs/interop/cda/document/ccd.yaml | 55 ----- configs/interop/cda/sections/medications.yaml | 72 ------- configs/interop/cda/sections/notes.yaml | 34 --- configs/interop/cda/sections/problems.yaml | 61 ------ configs/mappings/README.md | 33 --- configs/mappings/cda_default/README.md | 53 ----- .../mappings/cda_default/severity_codes.yaml | 12 -- .../mappings/cda_default/status_codes.yaml | 12 -- configs/mappings/cda_default/systems.yaml | 23 -- configs/templates/cda_fhir/condition.liquid | 49 ----- .../cda_fhir/document_reference.liquid | 22 -- .../cda_fhir/medication_statement.liquid | 69 ------ configs/templates/fhir_cda/document.liquid | 41 ---- .../fhir_cda/medication_entry.liquid | 111 ---------- configs/templates/fhir_cda/note_entry.liquid | 23 -- .../templates/fhir_cda/problem_entry.liquid | 92 -------- configs/templates/fhir_cda/section.liquid | 15 -- dev-templates/README.md | 36 ++++ dev-templates/allergies/README.md | 48 +++++ .../allergies}/allergies.yaml | 0 .../allergies}/allergy_entry.liquid | 0 .../allergies}/allergy_intolerance.liquid | 0 .../configs/interop/cda/document/ccd.yaml | 1 - .../interop/cda/sections/allergies.yaml | 89 -------- .../cda_fhir/allergy_intolerance.liquid | 79 ------- .../templates/fhir_cda/allergy_entry.liquid | 204 ------------------ 30 files changed, 84 insertions(+), 1318 deletions(-) delete mode 100644 configs/defaults.yaml delete mode 100644 configs/environments/development.yaml delete mode 100644 configs/environments/production.yaml delete mode 100644 configs/environments/testing.yaml delete mode 100644 configs/interop/cda/document/ccd.yaml delete mode 100644 configs/interop/cda/sections/medications.yaml delete mode 100644 configs/interop/cda/sections/notes.yaml delete mode 100644 configs/interop/cda/sections/problems.yaml delete mode 100644 configs/mappings/README.md delete mode 100644 configs/mappings/cda_default/README.md delete mode 100644 configs/mappings/cda_default/severity_codes.yaml delete mode 100644 configs/mappings/cda_default/status_codes.yaml delete mode 100644 configs/mappings/cda_default/systems.yaml delete mode 100644 configs/templates/cda_fhir/condition.liquid delete mode 100644 configs/templates/cda_fhir/document_reference.liquid delete mode 100644 configs/templates/cda_fhir/medication_statement.liquid delete mode 100644 configs/templates/fhir_cda/document.liquid delete mode 100644 configs/templates/fhir_cda/medication_entry.liquid delete mode 100644 configs/templates/fhir_cda/note_entry.liquid delete mode 100644 configs/templates/fhir_cda/problem_entry.liquid delete mode 100644 configs/templates/fhir_cda/section.liquid create mode 100644 dev-templates/README.md create mode 100644 dev-templates/allergies/README.md rename {configs/interop/cda/sections => dev-templates/allergies}/allergies.yaml (100%) rename {configs/templates/fhir_cda => dev-templates/allergies}/allergy_entry.liquid (100%) rename {configs/templates/cda_fhir => dev-templates/allergies}/allergy_intolerance.liquid (100%) delete mode 100644 healthchain/configs/interop/cda/sections/allergies.yaml delete mode 100644 healthchain/configs/templates/cda_fhir/allergy_intolerance.liquid delete mode 100644 healthchain/configs/templates/fhir_cda/allergy_entry.liquid diff --git a/configs/defaults.yaml b/configs/defaults.yaml deleted file mode 100644 index 16e33509..00000000 --- a/configs/defaults.yaml +++ /dev/null @@ -1,59 +0,0 @@ -# HealthChain Interoperability Engine Default Configuration -# This file contains default values used throughout the engine - -defaults: - # Common defaults for all resources - common: - id_prefix: "hc-" - timestamp: "%Y%m%d" - reference_name: "#{uuid}name" - subject: - reference: "Patient/example" - - # Mapping directory configuration - mappings_dir: "cda_default" - - # Resource-specific defaults - resources: - Condition: - clinicalStatus: - coding: - - system: "http://terminology.hl7.org/CodeSystem/condition-clinical" - code: "unknown" - display: "Unknown" - MedicationStatement: - status: "unknown" - medication: - concept: - coding: - - system: "http://terminology.hl7.org/CodeSystem/v3-NullFlavor" - code: "UNK" - display: "Unknown" - -# TODO: More control over settings -# # Validation settings -# validation: -# strict_mode: true -# warn_on_missing: true -# ignore_unknown_fields: true - -# # Parser settings -# parser: -# max_entries: 1000 -# skip_empty_sections: true - -# # Logging settings -# logging: -# level: "INFO" -# include_timestamps: true - -# # Error handling -# errors: -# retry_count: 3 -# fail_on_critical: true - -# # Performance settings -# performance: -# cache_templates: true -# cache_mappings: true -# batch_size: 100 diff --git a/configs/environments/development.yaml b/configs/environments/development.yaml deleted file mode 100644 index 03ce37ec..00000000 --- a/configs/environments/development.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# Development Environment Configuration -# This file contains settings specific to the development environment -# TODO: Implement - -# Logging settings for development -logging: - level: "DEBUG" - include_timestamps: true - console_output: true - file_output: false - -# Error handling for development -errors: - retry_count: 1 - fail_on_critical: true - verbose_errors: true - -# Performance settings for development -performance: - cache_templates: false # Disable caching for easier template development - cache_mappings: false # Disable caching for easier mapping development - batch_size: 10 # Smaller batch size for easier debugging - -# Default resource fields for development -defaults: - common: - id_prefix: "dev-" # Development-specific ID prefix - subject: - reference: "Patient/Foo" - -# Template settings for development -templates: - reload_on_change: true # Automatically reload templates when they change diff --git a/configs/environments/production.yaml b/configs/environments/production.yaml deleted file mode 100644 index 846a914c..00000000 --- a/configs/environments/production.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# Production Environment Configuration -# This file contains settings specific to the production environment -# TODO: Implement - -# Logging settings for production -logging: - level: "WARNING" - include_timestamps: true - console_output: false - file_output: true - file_path: "/var/log/healthchain/interop.log" - rotate_logs: true - max_log_size_mb: 10 - backup_count: 5 - -# Error handling for production -errors: - retry_count: 3 - fail_on_critical: true - verbose_errors: false - -# Performance settings for production -performance: - cache_templates: true # Enable caching for better performance - cache_mappings: true # Enable caching for better performance - batch_size: 100 # Larger batch size for better throughput - -# Default resource fields for production -defaults: - common: - id_prefix: "hc-" # Production ID prefix - subject: - reference: "Patient/example" - -# Template settings for production -templates: - reload_on_change: false # Don't reload templates in production diff --git a/configs/environments/testing.yaml b/configs/environments/testing.yaml deleted file mode 100644 index 2c6aca95..00000000 --- a/configs/environments/testing.yaml +++ /dev/null @@ -1,39 +0,0 @@ -# Testing Environment Configuration -# This file contains settings specific to the testing environment -# TODO: Implement -# Logging settings for testing -logging: - level: "INFO" - include_timestamps: true - console_output: true - file_output: true - file_path: "./logs/test-interop.log" - -# Error handling for testing -errors: - retry_count: 2 - fail_on_critical: true - verbose_errors: true - -# Performance settings for testing -performance: - cache_templates: true # Enable caching for realistic testing - cache_mappings: true # Enable caching for realistic testing - batch_size: 50 # Medium batch size for testing - -# Default resource fields for testing -defaults: - common: - id_prefix: "test-" # Testing-specific ID prefix - subject: - reference: "Patient/test-example" - -# Template settings for testing -templates: - reload_on_change: false # Don't reload templates in testing - -# Validation settings for testing -validation: - strict_mode: true - warn_on_missing: true - ignore_unknown_fields: false # Stricter validation for testing diff --git a/configs/interop/cda/document/ccd.yaml b/configs/interop/cda/document/ccd.yaml deleted file mode 100644 index 2afe5eed..00000000 --- a/configs/interop/cda/document/ccd.yaml +++ /dev/null @@ -1,55 +0,0 @@ -# CCD Document Configuration -# This file contains configuration for CCD documents - -# Document templates (required) -templates: - document: "fhir_cda/document" - section: "fhir_cda/section" - -# Basic document information -code: - code: "34133-9" - code_system: "2.16.840.1.113883.6.1" - code_system_name: "LOINC" - display: "Summarization of Episode Note" -confidentiality_code: - code: "N" - code_system: "2.16.840.1.113883.5.25" -language_code: "en-US" -realm_code: "GB" -type_id: - extension: "POCD_HD000040" - root: "2.16.840.1.113883.1.3" -template_id: - root: "1.2.840.114350.1.72.1.51693" - -# Document structure -structure: - # Header configuration - header: - include_patient: false - include_author: false - include_custodian: false - include_legal_authenticator: false - - # Body configuration - body: - structured_body: true - non_xml_body: false - include_sections: - - "allergies" - - "medications" - - "problems" - - "notes" - -# Rendering configuration -rendering: - # XML formatting - xml: - pretty_print: true - encoding: "UTF-8" - - # Narrative generation - narrative: - include: true - generate_if_missing: true diff --git a/configs/interop/cda/sections/medications.yaml b/configs/interop/cda/sections/medications.yaml deleted file mode 100644 index c37c7195..00000000 --- a/configs/interop/cda/sections/medications.yaml +++ /dev/null @@ -1,72 +0,0 @@ -# Medications Section Configuration -# ======================== - -# Metadata for both extraction and rendering processes -resource: "MedicationStatement" -resource_template: "cda_fhir/medication_statement" -entry_template: "fhir_cda/medication_entry" - -# Section identifiers (used for extraction) -identifiers: - template_id: "2.16.840.1.113883.10.20.1.8" - code: "10160-0" - code_system: "2.16.840.1.113883.6.1" - code_system_name: "LOINC" - display: "Medications" - clinical_status: - template_id: "2.16.840.1.113883.10.20.1.47" - code: "33999-4" - -# Template configuration (used for rendering/generation) -template: - # Substance administration configuration - substance_admin: - template_id: - - "2.16.840.1.113883.10.20.1.24" - - "2.16.840.1.113883.3.88.11.83.8" - - "1.3.6.1.4.1.19376.1.5.3.1.4.7" - - "1.3.6.1.4.1.19376.1.5.3.1.4.7.1" - - "2.16.840.1.113883.3.88.11.32.8" - status_code: "completed" - - # Manufactured product configuration - manufactured_product: - template_id: - - "1.3.6.1.4.1.19376.1.5.3.1.4.7.2" - - "2.16.840.1.113883.10.20.1.53" - - "2.16.840.1.113883.3.88.11.32.9" - - "2.16.840.1.113883.3.88.11.83.8.2" - - # Clinical status observation configuration - clinical_status_obs: - template_id: "2.16.840.1.113883.10.20.1.47" - status_code: "completed" - code: "33999-4" - code_system: "2.16.840.1.113883.6.1" - code_system_name: "LOINC" - display_name: "Status" - value: - code: "755561003" - code_system: "2.16.840.1.113883.6.96" - code_system_name: "SNOMED CT" - display_name: "Active" - -# Rendering configuration -rendering: - narrative: - include: true - template: "narratives/medication_narrative" - entry: - include_status: true - include_dates: true - include_dosage: true - include_route: true - include_frequency: true - -# Default values for template -defaults: - status_code: "active" - type_code: "REFR" - medication_status_code: "755561003" - medication_status_display: "Active" - medication_status_system: "2.16.840.1.113883.6.96" diff --git a/configs/interop/cda/sections/notes.yaml b/configs/interop/cda/sections/notes.yaml deleted file mode 100644 index bdbae936..00000000 --- a/configs/interop/cda/sections/notes.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# Notes Section Configuration -# ===================== - -# Metadata for both extraction and rendering processes -resource: "DocumentReference" -resource_template: "cda_fhir/document_reference" -entry_template: "fhir_cda/note_entry" - -# Section identifiers (used for extraction) -identifiers: - template_id: "1.2.840.114350.1.72.1.200001" - code: "51847-2" - code_system: "2.16.840.1.113883.6.1" - code_system_name: "LOINC" - display: "Progress Notes" - -# Template configuration (used for rendering/generation) -template: - # Note section configuration - note_section: - template_id: - - "1.2.840.114350.1.72.1.200001" - code: "51847-2" - code_system: "2.16.840.1.113883.6.1" - code_system_name: "LOINC" - display_name: "Progress Notes" - status_code: "completed" - -# Rendering configuration -rendering: - narrative: - include: true - template: "narratives/note_narrative" - description: "Progress Notes extracted from CDA notes section" diff --git a/configs/interop/cda/sections/problems.yaml b/configs/interop/cda/sections/problems.yaml deleted file mode 100644 index 92614471..00000000 --- a/configs/interop/cda/sections/problems.yaml +++ /dev/null @@ -1,61 +0,0 @@ -# Problems Section Configuration -# ======================== - -# Metadata for both extraction and rendering processes -resource: "Condition" -resource_template: "cda_fhir/condition" -entry_template: "fhir_cda/problem_entry" - -# Section identifiers (used for extraction) -identifiers: - template_id: "2.16.840.1.113883.10.20.1.11" - code: "11450-4" - code_system: "2.16.840.1.113883.6.1" - code_system_name: "LOINC" - display: "Problem List" - clinical_status: - template_id: "2.16.840.1.113883.10.20.1.47" - code: "33999-4" - -# Template configuration (used for rendering/generation) -template: - # Act element configuration - act: - template_id: - - "2.16.840.1.113883.10.20.1.27" - - "1.3.6.1.4.1.19376.1.5.3.1.4.5.1" - - "1.3.6.1.4.1.19376.1.5.3.1.4.5.2" - - "2.16.840.1.113883.3.88.11.32.7" - - "2.16.840.1.113883.3.88.11.83.7" - status_code: "completed" - - # Problem observation configuration - problem_obs: - type_code: "SUBJ" - inversion_ind: false - template_id: - - "1.3.6.1.4.1.19376.1.5.3.1.4.5" - - "2.16.840.1.113883.10.20.1.28" - code: "55607006" - code_system: "2.16.840.1.113883.6.96" - code_system_name: "SNOMED CT" - display_name: "Problem" - status_code: "completed" - - # Clinical status observation configuration - clinical_status_obs: - template_id: "2.16.840.1.113883.10.20.1.47" - code: "33999-4" - code_system: "2.16.840.1.113883.6.1" - code_system_name: "LOINC" - display_name: "Status" - status_code: "completed" - -# Rendering configuration (not used) -rendering: - narrative: - include: true - template: "narratives/problem_narrative" - entry: - include_status: true - include_dates: true diff --git a/configs/mappings/README.md b/configs/mappings/README.md deleted file mode 100644 index dea5d5c6..00000000 --- a/configs/mappings/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# HealthChain Mappings - -This directory contains mapping configurations used by the HealthChain interoperability module to translate between different healthcare data formats. - -## Organization - -The mappings are organized by the formats they translate between: - -- `cda_default/` - Default mappings for CDA format - - `systems.yaml` - Code system mappings (SNOMED CT, LOINC, etc.) - - `status_codes.yaml` - Status code mappings (active, inactive, resolved) - - `severity_codes.yaml` - Severity code mappings (mild, moderate, severe) - - -## Configuration - -The mapping directory to use is configurable through the `defaults.mappings_dir` configuration value. The default is `"cda_default"`. - -## Structure - -Each mapping file uses a flat structure designed for clarity and simplicity. See the README in each subdirectory for detailed examples and documentation. - -## Adding New Mappings - -To add mappings for new format combinations: - -1. Create a new subdirectory (e.g., `hl7v2/` for HL7v2 mappings, or `cda_local/` for local CDA mappings) -2. Add yaml files for each mapping type needed -3. Update the mapping directory in your configuration to use the new mappings - -## Usage - -These mapping files are loaded automatically by the `InteropConfigManager` and are used by the filters in the `healthchain.interop.filters` module to translate codes between formats. diff --git a/configs/mappings/cda_default/README.md b/configs/mappings/cda_default/README.md deleted file mode 100644 index 817129e4..00000000 --- a/configs/mappings/cda_default/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# CDA Default Mappings - -This directory contains default mapping configurations used to translate between CDA (Clinical Document Architecture) and FHIR (Fast Healthcare Interoperability Resources) formats. - -## Files - -- `systems.yaml` - Code system mappings between FHIR URLs and CDA OIDs -- `status_codes.yaml` - Status code mappings (FHIR status codes to CDA status codes) -- `severity_codes.yaml` - Severity code mappings (FHIR severity codes to CDA severity codes) - -## Structure - -Each mapping file uses a flat structure designed for clarity and simplicity, consistently using FHIR values as keys mapping to CDA values: - -### systems.yaml -```yaml -"http://snomed.info/sct": - oid: "2.16.840.1.113883.6.96" - name: "SNOMED CT" - -"http://loinc.org": - oid: "2.16.840.1.113883.6.1" - name: "LOINC" -``` - -### status_codes.yaml -```yaml -"active": - code: "55561003" - display: "Active" - -"resolved": - code: "413322009" - display: "Resolved" -``` - -### severity_codes.yaml -```yaml -"severe": - code: "H" - display: "Severe" - -"moderate": - code: "M" - display: "Moderate" -``` - -## Usage - -These mappings are used by the filters in the interoperability module for bidirectional translation between CDA and FHIR: - -1. **FHIR to CDA**: Maps FHIR URLs to CDA OIDs, FHIR status codes to CDA status codes, etc. -2. **CDA to FHIR**: Maps CDA OIDs to FHIR URLs, CDA status codes to FHIR status codes, etc. (reverse lookup) diff --git a/configs/mappings/cda_default/severity_codes.yaml b/configs/mappings/cda_default/severity_codes.yaml deleted file mode 100644 index e9d63e2b..00000000 --- a/configs/mappings/cda_default/severity_codes.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Allergy and reaction severity codes (FHIR to CDA) -"severe": - code: "H" - display: "Severe" - -"moderate": - code: "M" - display: "Moderate" - -"mild": - code: "L" - display: "Mild" diff --git a/configs/mappings/cda_default/status_codes.yaml b/configs/mappings/cda_default/status_codes.yaml deleted file mode 100644 index 6203d8cf..00000000 --- a/configs/mappings/cda_default/status_codes.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Clinical status codes (FHIR to CDA) -"active": - code: "55561003" - display: "Active" - -"resolved": - code: "413322009" - display: "Resolved" - -"inactive": - code: "73425007" - display: "Inactive" diff --git a/configs/mappings/cda_default/systems.yaml b/configs/mappings/cda_default/systems.yaml deleted file mode 100644 index d7414502..00000000 --- a/configs/mappings/cda_default/systems.yaml +++ /dev/null @@ -1,23 +0,0 @@ -"http://snomed.info/sct": - oid: "2.16.840.1.113883.6.96" - name: "SNOMED CT" - -"http://loinc.org": - oid: "2.16.840.1.113883.6.1" - name: "LOINC" - -"http://www.nlm.nih.gov/research/umls/rxnorm": - oid: "2.16.840.1.113883.6.88" - name: "RxNorm" - -"http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl": - oid: "2.16.840.1.113883.3.26.1.1" - name: "NCI Thesaurus" - -"http://terminology.hl7.org/CodeSystem/condition-clinical": - oid: "2.16.840.1.113883.6.96" - name: "SNOMED CT" - -"http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical": - oid: "2.16.840.1.113883.6.96" - name: "SNOMED CT" diff --git a/configs/templates/cda_fhir/condition.liquid b/configs/templates/cda_fhir/condition.liquid deleted file mode 100644 index 8089a92b..00000000 --- a/configs/templates/cda_fhir/condition.liquid +++ /dev/null @@ -1,49 +0,0 @@ -{ - "resourceType": "Condition", - {% if entry.act.entryRelationship.is_array %} - {% assign obs = entry.act.entryRelationship[0].observation %} - {% else %} - {% assign obs = entry.act.entryRelationship.observation %} - {% endif %} - {% if obs.entryRelationship.observation.code['@code'] == config.identifiers.clinical_status.code %} - {% if obs.entryRelationship.observation.value %} - "clinicalStatus": { - "coding": [ - { - "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", - "code": "{{ obs.entryRelationship.observation.value['@code'] | map_status: 'cda_to_fhir' }}" - }, - { - "system": "{{ obs.entryRelationship.observation.value['@codeSystem'] | map_system: 'cda_to_fhir' }}", - "code": "{{ obs.entryRelationship.observation.value['@code'] }}", - "display": "{{ obs.entryRelationship.observation.value['@displayName'] }}" - } - ] - }{% if true %},{% endif %} - {% endif %} - {% endif %} - "category": [{ - "coding": [{ - "system": "http://terminology.hl7.org/CodeSystem/condition-category", - "code": "problem-list-item", - "display": "Problem List Item" - }] - }]{% if obs.value or obs.effectiveTime %},{% endif %} - {% if obs.value %} - "code": { - "coding": [{ - "system": "{{ obs.value['@codeSystem'] | map_system: 'cda_to_fhir' }}", - "code": "{{ obs.value['@code'] }}", - "display": "{{ obs.value['@displayName'] }}" - }] - }{% if obs.effectiveTime %},{% endif %} - {% endif %} - {% if obs.effectiveTime %} - {% if obs.effectiveTime.low %} - "onsetDateTime": "{{ obs.effectiveTime.low['@value'] | format_date }}"{% if obs.effectiveTime.high %},{% endif %} - {% endif %} - {% if obs.effectiveTime.high %} - "abatementDateTime": "{{ obs.effectiveTime.high['@value'] | format_date }}" - {% endif %} - {% endif %} -} diff --git a/configs/templates/cda_fhir/document_reference.liquid b/configs/templates/cda_fhir/document_reference.liquid deleted file mode 100644 index 657a48b5..00000000 --- a/configs/templates/cda_fhir/document_reference.liquid +++ /dev/null @@ -1,22 +0,0 @@ -{ - "resourceType": "DocumentReference", - "status": "current", - "type": { - "coding": [{ - "system": "{{ entry.code['@codeSystem'] | map_system: 'cda_to_fhir' }}", - "code": "{{ entry.code['@code'] }}", - "display": "{{ entry.code['@displayName'] }}" - }] - }, - {% if entry.effectiveTime %} - "date": "{{ entry.effectiveTime['@value'] | format_timestamp }}", - {% endif %} - "description": "{{ config.rendering.narrative.description }}", - "content": [{ - "attachment": { - "contentType": "text/plain", - "data": "{{ entry.text | xmldict_to_html | to_base64 }}", - "title": "{{ entry.title }}" - } - }] -} diff --git a/configs/templates/cda_fhir/medication_statement.liquid b/configs/templates/cda_fhir/medication_statement.liquid deleted file mode 100644 index 9d31f1ab..00000000 --- a/configs/templates/cda_fhir/medication_statement.liquid +++ /dev/null @@ -1,69 +0,0 @@ -{ - "resourceType": "MedicationStatement", - {% assign substance_admin = entry.substanceAdministration %} - "status": "{{ substance_admin.statusCode['@code'] | map_status: 'cda_to_fhir' }}", - "medication": { - "concept": { - "coding": [{ - "system": "{{ substance_admin.consumable.manufacturedProduct.manufacturedMaterial.code['@codeSystem'] | map_system: 'cda_to_fhir' }}", - "code": "{{ substance_admin.consumable.manufacturedProduct.manufacturedMaterial.code['@code'] }}", - "display": "{{ substance_admin.consumable.manufacturedProduct.manufacturedMaterial.code['@displayName'] }}" - }] - } - } - - {% comment %}Process effectiveTime and extract period/timing information if exists{% endcomment %} - {% if substance_admin.effectiveTime %} - , - {% assign effective_period = substance_admin.effectiveTime | extract_effective_period %} - {% if effective_period %} - "effectivePeriod": { - {% if effective_period.start %}"start": "{{ effective_period.start }}"{% if effective_period.end %},{% endif %}{% endif %} - {% if effective_period.end %}"end": "{{ effective_period.end }}"{% endif %} - } - {% assign effective_timing = substance_admin.effectiveTime | extract_effective_timing %} - {% if substance_admin.doseQuantity or substance_admin.routeCode or effective_timing %},{% endif %} - {% endif %} - {% endif %} - - {% comment %}Add dosage if any dosage related fields are present{% endcomment %} - {% assign effective_timing = substance_admin.effectiveTime | extract_effective_timing %} - {% if substance_admin.doseQuantity or substance_admin.routeCode or effective_timing %} - {% if substance_admin.effectiveTime == nil %},{% endif %} - "dosage": [ - { - {% if substance_admin.doseQuantity %} - "doseAndRate": [ - { - "doseQuantity": { - "value": {{ substance_admin.doseQuantity['@value'] }}, - "unit": "{{ substance_admin.doseQuantity['@unit'] }}" - } - } - ]{% if substance_admin.routeCode or effective_timing %},{% endif %} - {% endif %} - - {% if substance_admin.routeCode %} - "route": { - "coding": [ - { - "system": "{{ substance_admin.routeCode['@codeSystem'] | map_system: 'cda_to_fhir' }}", - "code": "{{ substance_admin.routeCode['@code'] }}", - "display": "{{ substance_admin.routeCode['@displayName'] }}" - } - ] - }{% if effective_timing %},{% endif %} - {% endif %} - - {% if effective_timing %} - "timing": { - "repeat": { - "period": {{ effective_timing.period }}, - "periodUnit": "{{ effective_timing.periodUnit }}" - } - } - {% endif %} - } - ] - {% endif %} -} diff --git a/configs/templates/fhir_cda/document.liquid b/configs/templates/fhir_cda/document.liquid deleted file mode 100644 index 460d7196..00000000 --- a/configs/templates/fhir_cda/document.liquid +++ /dev/null @@ -1,41 +0,0 @@ -{ - "ClinicalDocument": { - "@xmlns": "urn:hl7-org:v3", - "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "id": { - "@root": "{{ bundle.identifier.value | generate_id }}" - }, - "realmCode": { - "@code": "{{ config.realm_code }}" - }, - "typeId": { - "@extension": "{{ config.type_id.extension}}", - "@root": "{{ config.type_id.root }}" - }, - "templateId": { - "@root": "{{ config.template_id.root }}" - }, - "code": { - "@code": "{{ config.code.code }}", - "@codeSystem": "{{ config.code.code_system }}", - "@codeSystemName": "{{ config.code.code_system_name }}", - "@displayName": "{{ config.code.display }}" - }, - "title": "Clinical Document", - "effectiveTime": { - "@value": "{{ bundle.timestamp | format_timestamp }}" - }, - "confidentialityCode": { - "@code": "{{ config.confidentiality_code.code }}", - "@codeSystem": "{{ config.confidentiality_code.code_system }}" - }, - "languageCode": { - "@code": "{{ config.language_code }}" - }, - "component": { - "structuredBody": { - "component": {{ sections | json }} - } - } - } -} diff --git a/configs/templates/fhir_cda/medication_entry.liquid b/configs/templates/fhir_cda/medication_entry.liquid deleted file mode 100644 index 65f00954..00000000 --- a/configs/templates/fhir_cda/medication_entry.liquid +++ /dev/null @@ -1,111 +0,0 @@ -{ - "substanceAdministration": { - "@classCode": "SBADM", - "@moodCode": "INT", - "templateId": [ - {% for template_id in config.template.substance_admin.template_id %} - {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} - {% endfor %} - ], - {% if resource.id %} - "id": {"@root": "{{ resource.id }}"}, - {% endif %} - "statusCode": {"@code": "{{ config.template.substance_admin.status_code }}"}, - {% if resource.dosage and resource.dosage[0].doseAndRate %} - "doseQuantity": { - "@value": "{{ resource.dosage[0].doseAndRate[0].doseQuantity.value }}", - "@unit": "{{ resource.dosage[0].doseAndRate[0].doseQuantity.unit }}" - }, - {% endif %} - {% if resource.dosage and resource.dosage[0].route %} - "routeCode": { - "@code": "{{ resource.dosage[0].route.coding[0].code }}", - "@codeSystem": "{{ resource.dosage[0].route.coding[0].system | map_system: 'fhir_to_cda' }}", - "@displayName": "{{ resource.dosage[0].route.coding[0].display }}" - }, - {% endif %} - {% if resource.dosage and resource.dosage[0].timing or resource.effectivePeriod %} - "effectiveTime": [ - {% if resource.dosage and resource.dosage[0].timing %} - { - "@xsi:type": "PIVL_TS", - "@institutionSpecified": true, - "@operator": "A", - "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "period": { - "@unit": "{{ resource.dosage[0].timing.repeat.periodUnit }}", - "@value": "{{ resource.dosage[0].timing.repeat.period }}" - } - }{% if resource.effectivePeriod %},{% endif %} - {% endif %} - {% if resource.effectivePeriod %} - { - "@xsi:type": "IVL_TS", - "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - {% if resource.effectivePeriod.start %} - "low": { - "@value": "{{ resource.effectivePeriod.start | format_date }}" - }, - {% else %} - "low": {"@nullFlavor": "UNK"}, - {% endif %} - {% if resource.effectivePeriod.end %} - "high": { - "@value": "{{ resource.effectivePeriod.end }}" - } - {% else %} - "high": {"@nullFlavor": "UNK"} - {% endif %} - } - {% endif %} - ], - {% endif %} - "consumable": { - "@typeCode": "CSM", - "manufacturedProduct": { - "@classCode": "MANU", - "templateId": [ - {% for template_id in config.template.manufactured_product.template_id %} - {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} - {% endfor %} - ], - "manufacturedMaterial": { - "code": { - "@code": "{{ resource.medication.concept.coding[0].code }}", - "@codeSystem": "{{ resource.medication.concept.coding[0].system | map_system: 'fhir_to_cda' }}", - "@displayName": "{{ resource.medication.concept.coding[0].display }}", - "originalText": { - "reference": {"@value": "{{ text_reference_name }}"} - } - } - } - } - }, - "entryRelationship": { - "@typeCode": "REFR", - "observation": { - "@classCode": "OBS", - "@moodCode": "EVN", - "templateId": {"@root": "{{ config.template.clinical_status_obs.template_id }}"}, - "code": { - "@code": "{{ config.template.clinical_status_obs.code }}", - "@codeSystem": "{{ config.template.clinical_status_obs.code_system }}", - "@codeSystemName": "{{ config.template.clinical_status_obs.code_system_name }}", - "@displayName": "{{ config.template.clinical_status_obs.display_name }}" - }, - "value": { - "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@code": "{{ config.template.clinical_status_obs.value.code }}", - "@codeSystem": "{{ config.template.clinical_status_obs.value.code_system }}", - "@codeSystemName": "{{ config.template.clinical_status_obs.value.code_system_name }}", - "@xsi:type": "CE", - "@displayName": "{{ config.template.clinical_status_obs.value.display_name }}" - }, - "statusCode": {"@code": "{{ config.template.clinical_status_obs.status_code }}"}, - "effectiveTime": { - "low": {"@value": "{{ timestamp }}"} - } - } - } - } -} diff --git a/configs/templates/fhir_cda/note_entry.liquid b/configs/templates/fhir_cda/note_entry.liquid deleted file mode 100644 index 3403f958..00000000 --- a/configs/templates/fhir_cda/note_entry.liquid +++ /dev/null @@ -1,23 +0,0 @@ -{ - "component": { - "section": { - "templateId": [ - {% for template_id in config.template.note_section.template_id %} - {"@root": "{{ template_id }}"} {% if forloop.last != true %},{% endif %} - {% endfor %} - ], - "code": { - "@code": "{{ resource.type.coding[0].code | default: config.template.note_section.code }}", - "@codeSystem": "{{ resource.type.coding[0].system | map_system: 'fhir_to_cda' | default: config.template.note_section.code_system }}", - "@displayName": "{{ resource.type.coding[0].display | default: config.template.note_section.display_name }}" - }, - "title": "{{ resource.content[0].attachment.title }}", - {% if resource.date %} - "effectiveTime": { - "@value": "{{ resource.date | format_date: 'cda' }}" - }, - {% endif %} - "text": {{ resource.content[0].attachment.data | from_base64 | json }} - } - } -} diff --git a/configs/templates/fhir_cda/problem_entry.liquid b/configs/templates/fhir_cda/problem_entry.liquid deleted file mode 100644 index 68deb288..00000000 --- a/configs/templates/fhir_cda/problem_entry.liquid +++ /dev/null @@ -1,92 +0,0 @@ -{ - "act": { - "@classCode": "ACT", - "@moodCode": "EVN", - "templateId": [ - {% for template_id in config.template.act.template_id %} - {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} - {% endfor %} - ], - {% if resource.id %} - "id": {"@root": "{{ resource.id }}"}, - {% endif %} - "code": {"@nullFlavor": "NA"}, - "statusCode": { - "@code": "{{ config.template.act.status_code }}" - }, - "effectiveTime": { - "low": {"@value": "{{ timestamp }}"} - }, - "entryRelationship": { - "@typeCode": "{{ config.template.problem_obs.type_code }}", - "@inversionInd": {{ config.template.problem_obs.inversion_ind }}, - "observation": { - "@classCode": "OBS", - "@moodCode": "EVN", - "templateId": [ - {% for template_id in config.template.problem_obs.template_id %} - {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} - {% endfor %} - ], - {% if resource.id %} - "id": {"@root": "{{ resource.id }}_obs"}, - {% endif %} - "code": { - "@code": "{{ config.template.problem_obs.code }}", - "@codeSystem": "{{ config.template.problem_obs.code_system }}", - "@codeSystemName": "{{ config.template.problem_obs.code_system_name }}", - "@displayName": "{{ config.template.problem_obs.display_name }}" - }, - "text": { - "reference": {"@value": "{{ text_reference_name }}"} - }, - "statusCode": {"@code": "{{ config.template.problem_obs.status_code }}"}, - "effectiveTime": { - {% if resource.onsetDateTime and resource.abatementDateTime %} - "low": {"@value": "{{ resource.onsetDateTime }}"}, - "high": {"@value": "{{ resource.abatementDateTime }}"} - {% elsif resource.onsetDateTime %} - "low": {"@value": "{{ resource.onsetDateTime }}"} - {% elsif resource.abatementDateTime %} - "high": {"@value": "{{ resource.abatementDateTime }}"} - {% endif %} - }, - "value": { - "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@xsi:type": "CD", - "@code": "{{ resource.code.coding[0].code }}", - "@codeSystem": "{{ resource.code.coding[0].system | map_system: 'fhir_to_cda' }}", - "@displayName": "{{ resource.code.coding[0].display }}", - "originalText": { - "reference": {"@value": "{{ text_reference_name }}"} - } - }, - "entryRelationship": { - "@typeCode": "REFR", - "observation": { - "@classCode": "OBS", - "@moodCode": "EVN", - "templateId": {"@root": "{{ config.template.clinical_status_obs.template_id }}"}, - "code": { - "@code": "{{ config.template.clinical_status_obs.code }}", - "@codeSystem": "{{ config.template.clinical_status_obs.code_system }}", - "@codeSystemName": "{{config.template.clinical_status_obs.code_system_name }}", - "@displayName": "{{ config.template.clinical_status_obs.display_name }}" - }, - "value": { - "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@code": "{{ resource.clinicalStatus.coding[0].code | map_status: 'fhir_to_cda' }}", - "@codeSystem": "{{ resource.clinicalStatus.coding[0].system | map_system: 'fhir_to_cda' }}", - "@displayName": "{{ resource.clinicalStatus.coding[0].display }}", - "@xsi:type": "CE" - }, - "statusCode": {"@code": "{{ config.template.clinical_status_obs.status_code }}"}, - "effectiveTime": { - "low": {"@value": "{{ timestamp }}"} - } - } - } - } - } - } -} diff --git a/configs/templates/fhir_cda/section.liquid b/configs/templates/fhir_cda/section.liquid deleted file mode 100644 index 3e35256c..00000000 --- a/configs/templates/fhir_cda/section.liquid +++ /dev/null @@ -1,15 +0,0 @@ -{ - "section": { - "templateId": { - "@root": "{{ config.identifiers.template_id }}" - }, - "code": { - "@code": "{{ config.identifiers.code }}", - "@codeSystem": "{{ config.identifiers.code_system | default: '2.16.840.1.113883.6.1' }}", - "@codeSystemName": "{{ config.identifiers.code_system_name | default: 'LOINC' }}", - "@displayName": "{{ config.identifiers.display }}" - }, - "title": "{{ config.identifiers.display }}", - "entry": {{ entries | json }} - } -} diff --git a/dev-templates/README.md b/dev-templates/README.md new file mode 100644 index 00000000..adf7072c --- /dev/null +++ b/dev-templates/README.md @@ -0,0 +1,36 @@ +# Developer Templates + +This directory contains **work-in-progress templates** for HealthChain developers. + +## Purpose + +- 🔧 **Development workspace** for new templates +- ⚠️ **Experimental features** not ready for bundling +- 🧪 **Testing ground** before promoting to `healthchain/configs/` + +## Workflow + +1. **Develop here** - Create/fix templates in subdirectories +2. **Test thoroughly** - Use integration tests and real data +3. **Promote when stable** - Move to `healthchain/configs/` for bundling +4. **Update docs** - Move from "experimental" to "stable" in docs + +## Current Templates + +- `allergies/` - Allergy templates with known bugs (see README) + +## Guidelines + +- Each template type gets its own subdirectory +- Include README with known issues and usage +- Test with example CDAs in `resources/` +- Follow existing template patterns + +## Not for End Users + +End users should use: +```bash +healthchain init-configs my_configs # Gets stable bundled templates +``` + +This directory is for **HealthChain contributors only**. diff --git a/dev-templates/allergies/README.md b/dev-templates/allergies/README.md new file mode 100644 index 00000000..a369ba9b --- /dev/null +++ b/dev-templates/allergies/README.md @@ -0,0 +1,48 @@ +# Experimental Allergy Templates + +⚠️ **Warning: These templates are experimental and contain known bugs.** + +This directory contains allergy-related configuration and templates that were removed from the default bundled configs due to reliability issues. + +📖 **For full documentation on experimental templates, see:** [docs/reference/interop/experimental.md](../docs/reference/interop/experimental.md) + +## Contents + +- `allergies.yaml` - Section configuration for allergy processing +- `allergy_entry.liquid` - Template for converting FHIR AllergyIntolerance to CDA +- `allergy_intolerance.liquid` - Template for converting CDA allergies to FHIR + +## Known Issues + +- Clinical status parsing has bugs (see integration test comments) +- Round-trip conversion may not preserve all data correctly +- Template logic is fragile and may fail with edge cases + +## Usage + +If you need allergy support despite the bugs: + +1. Copy these files to your custom config directory: + ```bash + # After running: healthchain init-configs my_configs + cp cookbook/experimental_templates/allergies/* my_configs/interop/cda/sections/ + cp cookbook/experimental_templates/allergies/allergy_*.liquid my_configs/templates/cda_fhir/ + cp cookbook/experimental_templates/allergies/allergy_*.liquid my_configs/templates/fhir_cda/ + ``` + +2. Add "allergies" to your CCD document config: + ```yaml + # my_configs/interop/cda/document/ccd.yaml + body: + include_sections: + - "allergies" # Add this + - "medications" + - "problems" + - "notes" + ``` + +3. Test thoroughly with your specific data before using in production. + +## Contributing + +If you fix the bugs in these templates, please consider contributing back to the main project! diff --git a/configs/interop/cda/sections/allergies.yaml b/dev-templates/allergies/allergies.yaml similarity index 100% rename from configs/interop/cda/sections/allergies.yaml rename to dev-templates/allergies/allergies.yaml diff --git a/configs/templates/fhir_cda/allergy_entry.liquid b/dev-templates/allergies/allergy_entry.liquid similarity index 100% rename from configs/templates/fhir_cda/allergy_entry.liquid rename to dev-templates/allergies/allergy_entry.liquid diff --git a/configs/templates/cda_fhir/allergy_intolerance.liquid b/dev-templates/allergies/allergy_intolerance.liquid similarity index 100% rename from configs/templates/cda_fhir/allergy_intolerance.liquid rename to dev-templates/allergies/allergy_intolerance.liquid diff --git a/healthchain/configs/interop/cda/document/ccd.yaml b/healthchain/configs/interop/cda/document/ccd.yaml index 2afe5eed..b6b7b928 100644 --- a/healthchain/configs/interop/cda/document/ccd.yaml +++ b/healthchain/configs/interop/cda/document/ccd.yaml @@ -37,7 +37,6 @@ structure: structured_body: true non_xml_body: false include_sections: - - "allergies" - "medications" - "problems" - "notes" diff --git a/healthchain/configs/interop/cda/sections/allergies.yaml b/healthchain/configs/interop/cda/sections/allergies.yaml deleted file mode 100644 index ab4cc208..00000000 --- a/healthchain/configs/interop/cda/sections/allergies.yaml +++ /dev/null @@ -1,89 +0,0 @@ -# Allergies Section Configuration -# ======================== - -# Metadata for both extraction and rendering processes -resource: "AllergyIntolerance" -resource_template: "cda_fhir/allergy_intolerance" -entry_template: "fhir_cda/allergy_entry" - -# Section identifiers (used for extraction) -identifiers: - template_id: "2.16.840.1.113883.10.20.1.2" - code: "48765-2" - code_system: "2.16.840.1.113883.6.1" - code_system_name: "LOINC" - display: "Allergies" - reaction: - template_id: "1.3.6.1.4.1.19376.1.5.3.1.4.5" - severity: - template_id: "1.3.6.1.4.1.19376.1.5.3.1.4.1" - -# Template configuration (used for rendering/generation) -template: - # Act element configuration - act: - template_id: - - "1.3.6.1.4.1.19376.1.5.3.1.4.5.1" - - "1.3.6.1.4.1.19376.1.5.3.1.4.5.3" - - "2.16.840.1.113883.3.88.11.32.6" - - "2.16.840.1.113883.3.88.11.83.6" - status_code: "active" - - # Allergy observation configuration - allergy_obs: - type_code: "SUBJ" - inversion_ind: false - template_id: - - "1.3.6.1.4.1.19376.1.5.3.1.4.5" - - "1.3.6.1.4.1.19376.1.5.3.1.4.6" - - "2.16.840.1.113883.10.20.1.18" - - "1.3.6.1.4.1.19376.1.5.3.1" - - "2.16.840.1.113883.10.20.1.28" - code: "420134006" - code_system: "2.16.840.1.113883.6.96" - code_system_name: "SNOMED CT" - display_name: "Propensity to adverse reactions" - status_code: "completed" - - # Reaction observation configuration - reaction_obs: - template_id: - - "2.16.840.1.113883.10.20.1.54" - - "1.3.6.1.4.1.19376.1.5.3.1.4.5" - code: "RXNASSESS" - status_code: "completed" - - # Severity observation configuration - severity_obs: - template_id: - - "2.16.840.1.113883.10.20.1.55" - - "1.3.6.1.4.1.19376.1.5.3.1.4.1" - code: "SEV" - code_system: "2.16.840.1.113883.5.4" - code_system_name: "ActCode" - display_name: "Severity" - status_code: "completed" - value: - code_system: "2.16.840.1.113883.5.1063" - code_system_name: "SeverityObservation" - - # Clinical status observation configuration - clinical_status_obs: - template_id: - - "2.16.840.1.113883.10.20.1.39" - code: "33999-4" - code_system: "2.16.840.1.113883.6.1" - code_system_name: "LOINC" - display_name: "Status" - status_code: "completed" - -# Rendering configuration (not used) -rendering: - narrative: - include: true - template: "narratives/allergy_narrative" - entry: - include_status: true - include_reaction: true - include_severity: true - include_dates: true diff --git a/healthchain/configs/templates/cda_fhir/allergy_intolerance.liquid b/healthchain/configs/templates/cda_fhir/allergy_intolerance.liquid deleted file mode 100644 index e77f4a34..00000000 --- a/healthchain/configs/templates/cda_fhir/allergy_intolerance.liquid +++ /dev/null @@ -1,79 +0,0 @@ -{ - {% if entry.act.entryRelationship.size %} - {% assign obs = entry.act.entryRelationship[0].observation %} - {% else %} - {% assign obs = entry.act.entryRelationship.observation %} - {% endif %} - - {% assign clinical_status = obs | extract_clinical_status: config %} - {% assign reactions = obs | extract_reactions: config %} - - "resourceType": "AllergyIntolerance" - - {% if clinical_status != blank %} - , - "clinicalStatus": { - "coding": [{ - "system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical", - "code": "{{ clinical_status | map_status: 'cda_to_fhir' }}" - }] - } - {% endif %} - - {% if obs.code %} - , - "type": { - "coding": [{ - "system": "{{ obs.code['@codeSystem'] | map_system: 'cda_to_fhir' }}", - "code": "{{ obs.code['@code'] }}", - "display": "{{ obs.code['@displayName'] }}" - }] - } - {% endif %} - - {% if obs.participant.participantRole.playingEntity %} - , - {% assign playing_entity = obs.participant.participantRole.playingEntity %} - "code": { - "coding": [{ - "system": "{{ playing_entity.code['@codeSystem'] | map_system: 'cda_to_fhir' }}", - "code": "{{ playing_entity.code['@code'] }}", - "display": "{{ playing_entity.name | default: playing_entity.code['@displayName'] }}" - }] - } - {% elsif obs.value %} - , - "code": { - "coding": [{ - "system": "{{ obs.value['@codeSystem'] | map_system: 'cda_to_fhir' }}", - "code": "{{ obs.value['@code'] }}", - "display": "{{ obs.value['@displayName'] }}" - }] - } - {% endif %} - - {% if obs.effectiveTime.low['@value'] %} - , - "onsetDateTime": "{{ obs.effectiveTime.low['@value'] | format_date }}" - {% endif %} - - {% if reactions.size > 0 %} - , - "reaction": [ - {% for reaction in reactions %} - { - "manifestation": [{ - "concept": { - "coding": [{ - "system": "{{ reaction.system | map_system: 'cda_to_fhir' }}", - "code": "{{ reaction.code }}", - "display": "{{ reaction.display }}" - }] - } - }]{% if reaction.severity != blank %}, - "severity": "{{ reaction.severity | map_severity: 'cda_to_fhir' }}"{% endif %} - }{% unless forloop.last %},{% endunless %} - {% endfor %} - ] - {% endif %} -} diff --git a/healthchain/configs/templates/fhir_cda/allergy_entry.liquid b/healthchain/configs/templates/fhir_cda/allergy_entry.liquid deleted file mode 100644 index 303edb73..00000000 --- a/healthchain/configs/templates/fhir_cda/allergy_entry.liquid +++ /dev/null @@ -1,204 +0,0 @@ -{ - "act": { - "@classCode": "ACT", - "@moodCode": "EVN", - "templateId": [ - {% for template_id in config.template.act.template_id %} - {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} - {% endfor %} - ], - {% if resource.id %} - "id": {"@root": "{{ resource.id }}"}, - {% endif %} - "code": {"@nullFlavor": "NA"}, - "statusCode": { - "@code": "{{ config.template.act.status_code }}" - }, - "effectiveTime": { - "low": {"@value": "{{ timestamp }}"} - }, - "entryRelationship": { - "@typeCode": "{{ config.template.allergy_obs.type_code }}", - "@inversionInd": {{ config.template.allergy_obs.inversion_ind }}, - "observation": { - "@classCode": "OBS", - "@moodCode": "EVN", - "templateId": [ - {% for template_id in config.template.allergy_obs.template_id %} - {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} - {% endfor %} - ], - {% if resource.id %} - "id": {"@root": "{{ resource.id }}_obs"}, - {% endif %} - "text": { - "reference": {"@value": "{{ text_reference_name }}"} - }, - "statusCode": {"@code": "{{ config.template.allergy_obs.status_code }}"}, - "effectiveTime": { - "low": {"@value": "{{ timestamp }}"} - }, - {% if resource.type %} - "code": { - "@code": "{{ resource.type.coding[0].code }}", - "@codeSystem": "{{ resource.type.coding[0].system | map_system: 'fhir_to_cda' }}", - "@displayName": "{{ resource.type.coding[0].display }}" - }, - {% else %} - "code": { - "@code": "{{ config.template.allergy_obs.code }}", - "@codeSystem": "{{ config.template.allergy_obs.code_system }}", - "@codeSystemName": "{{ config.template.allergy_obs.code_system_name }}", - "@displayName": "{{ config.template.allergy_obs.display_name }}" - }, - {% endif %} - "value": { - "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@xsi:type": "CD", - "@code": "{{ resource.code.coding[0].code }}", - "@codeSystem": "{{ resource.code.coding[0].system | map_system: 'fhir_to_cda' }}", - "@displayName": "{{ resource.code.coding[0].display }}", - "originalText": { - "reference": {"@value": "{{ text_reference_name }}"} - } - }, - "participant": { - "@typeCode": "CSM", - "participantRole": { - "@classCode": "MANU", - "playingEntity": { - "@classCode": "MMAT", - "code": { - "originalText": { - "reference": {"@value": "{{ text_reference_name }}"} - }, - "@code": "{{ resource.code.coding[0].code }}", - "@codeSystem": "{{ resource.code.coding[0].system | map_system: 'fhir_to_cda' }}", - "@displayName": "{{ resource.code.coding[0].display }}" - }, - "name": "{{ resource.code.coding[0].display }}" - } - } - }{% if resource.clinicalStatus or resource.reaction %},{% endif %} - - {% if resource.reaction %} - "entryRelationship": [ - { - "@typeCode": "REFR", - "@inversionInd": true, - "observation": { - "@classCode": "OBS", - "@moodCode": "EVN", - "templateId": {"@root": "{{config.template.clinical_status_obs.template_id}}"}, - "code": { - "@code": "{{ config.template.clinical_status_obs.code }}", - "@codeSystem": "{{ config.template.clinical_status_obs.code_system }}", - "@displayName": "{{ config.template.clinical_status_obs.display_name }}" - }, - "statusCode": {"@code": "{{ config.template.clinical_status_obs.status_code }}"}, - "value": { - "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@xsi:type": "CE", - "@code": "{{ resource.clinicalStatus.coding[0].code | map_status: 'fhir_to_cda' }}", - "@codeSystem": "{{ resource.clinicalStatus.coding[0].system | map_system: 'fhir_to_cda' }}", - "@displayName": "{{ resource.clinicalStatus.coding[0].display }}" - } - } - }, - { - "@typeCode": "MFST", - "observation": { - "@classCode": "OBS", - "@moodCode": "EVN", - "templateId": [ - {% for template_id in config.template.reaction_obs.template_id %} - {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} - {% endfor %} - ], - "id": {"@root": "{{ resource.id }}_reaction"}, - "code": {"@code": "{{ config.template.reaction_obs.code }}"}, - "text": { - "reference": {"@value": "{{ text_reference_name }}reaction"} - }, - "statusCode": {"@code": "{{ config.template.reaction_obs.status_code }}"}, - "effectiveTime": { - "low": {"@value": "{{ timestamp }}"} - }, - "value": { - "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@xsi:type": "CD", - "@code": "{{ resource.reaction[0].manifestation[0].concept.coding[0].code }}", - "@codeSystem": "{{ resource.reaction[0].manifestation[0].concept.coding[0].system | map_system: 'fhir_to_cda' }}", - "@displayName": "{{ resource.reaction[0].manifestation[0].concept.coding[0].display }}", - "originalText": { - "reference": {"@value": "{{ text_reference_name }}reaction"} - } - }{% if resource.reaction[0].severity %}, - "entryRelationship": { - "@typeCode": "SUBJ", - "observation": { - "@classCode": "OBS", - "@moodCode": "EVN", - "templateId": [ - {% for template_id in config.template.severity_obs.template_id %} - {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} - {% endfor %} - ], - "code": { - "@code": "{{ config.template.severity_obs.code }}", - "@codeSystem": "{{ config.template.severity_obs.code_system }}", - "@codeSystemName": "{{ config.template.severity_obs.code_system_name }}", - "@displayName": "{{ config.template.severity_obs.display_name }}" - }, - "text": { - "reference": {"@value": "{{ text_reference_name }}severity"} - }, - "statusCode": {"@code": "{{ config.template.severity_obs.status_code }}"}, - "value": { - "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@xsi:type": "CD", - "@code": "{{ resource.reaction[0].severity | map_severity: 'fhir_to_cda'}}", - "@codeSystem": "{{ config.template.severity_obs.value.code_system }}", - "@codeSystemName": "{{ config.template.severity_obs.value.code_system_name }}", - "@displayName": "{{ resource.reaction[0].severity | map_severity: 'fhir_to_cda'}}" - } - } - } - {% endif %} - } - } - ] - {% else %} - {% if resource.clinicalStatus %} - "entryRelationship": { - "@typeCode": "REFR", - "@inversionInd": true, - "observation": { - "@classCode": "OBS", - "@moodCode": "EVN", - "templateId": [ - {% for template_id in config.template.clinical_status_obs.template_id %} - {"@root": "{{template_id}}"} {% if forloop.last != true %},{% endif %} - {% endfor %} - ], - "code": { - "@code": "{{ config.template.clinical_status_obs.code }}", - "@codeSystem": "{{ config.template.clinical_status_obs.code_system }}", - "@displayName": "{{ config.template.clinical_status_obs.display_name }}" - }, - "statusCode": {"@code": "{{ config.template.clinical_status_obs.status_code }}"}, - "value": { - "@xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", - "@xsi:type": "CE", - "@code": "{{ resource.clinicalStatus.coding[0].code }}", - "@codeSystem": "{{ resource.clinicalStatus.coding[0].system | map_system: 'fhir_to_cda' }}", - "@displayName": "{{ resource.clinicalStatus.coding[0].display }}" - } - } - } - {% endif %} - {% endif %} - } - } - } -} From 5d34e59f2540190af6ffbda631cb5afa775b2b98 Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Mon, 4 Aug 2025 15:58:37 +0100 Subject: [PATCH 15/19] Update code and docs refs to allergy --- docs/reference/interop/experimental.md | 77 ++++++++++++ docs/reference/interop/templates.md | 10 +- .../reference/pipeline/adapters/cdaadapter.md | 3 +- healthchain/config/validators.py | 3 +- .../test_interop_engine_integration.py | 114 +++++++++--------- tests/pipeline/prebuilt/test_medicalcoding.py | 2 +- 6 files changed, 142 insertions(+), 67 deletions(-) create mode 100644 docs/reference/interop/experimental.md diff --git a/docs/reference/interop/experimental.md b/docs/reference/interop/experimental.md new file mode 100644 index 00000000..2e0a3b16 --- /dev/null +++ b/docs/reference/interop/experimental.md @@ -0,0 +1,77 @@ +# Experimental Templates + +This page tracks templates that are under development or have known issues. Use these at your own risk and please contribute fixes! + +## Template Status + +| Template Type | Status | Known Issues | Location | +|---------------|--------|--------------|----------| +| **Problems** | ✅ **Stable** | None | Bundled in default configs | +| **Medications** | ✅ **Stable** | None | Bundled in default configs | +| **Notes** | ✅ **Stable** | None | Bundled in default configs | +| **Allergies** | ⚠️ **Experimental** | Clinical status parsing bugs, round-trip issues | `dev-templates/allergies/` | + +## Using Experimental Templates + +### Allergies (AllergyIntolerance) + +**Status:** ⚠️ Experimental - Known bugs prevent inclusion in bundled configs + +**Known Issues:** +- Clinical status parsing has bugs (see integration test comments) +- Round-trip conversion may not preserve all data correctly +- Template logic is fragile and may fail with edge cases + +**Location:** `dev-templates/allergies/` + +**Usage:** +1. Copy experimental files to your custom config: + ```bash + # After running: healthchain init-configs my_configs + cp dev-templates/allergies/allergies.yaml my_configs/interop/cda/sections/ + cp dev-templates/allergies/allergy_*.liquid my_configs/templates/cda_fhir/ + cp dev-templates/allergies/allergy_*.liquid my_configs/templates/fhir_cda/ + ``` + +2. Enable in your CCD document config: + ```yaml + # my_configs/interop/cda/document/ccd.yaml + body: + include_sections: + - "allergies" # Add this line + - "medications" + - "problems" + - "notes" + ``` + +3. **Test thoroughly** with your specific data before production use. + +## Contributing Template Fixes + +We welcome contributions to improve experimental templates! + +### For Allergies: +- **Clinical status mapping** - The biggest issue is parsing clinical status from CDA observations +- **Round-trip fidelity** - Ensure CDA → FHIR → CDA preserves all important data +- **Edge case handling** - Make templates robust to various CDA structures + +### General Guidelines: +1. **Test with real data** - Use the example CDAs in `resources/` for testing +2. **Add comprehensive tests** - Include both unit and integration tests +3. **Document limitations** - Be clear about what your fix does/doesn't solve +4. **Follow template patterns** - Keep consistent with existing stable templates + +### Submitting Fixes: +1. Fix the templates in `dev-templates/` +2. Add/update tests to cover your changes +3. Move stable templates to bundled configs in your PR +4. Update this documentation + +## Roadmap + +**Next Priorities:** +1. 🎯 **Allergies stabilization** - Fix clinical status parsing and round-trip issues +2. 🔮 **Future sections** - Procedures, Vital Signs, Lab Results +3. 🔧 **Template tooling** - Better validation and testing framework + +Want to help? Check our [contribution guidelines](../../community/contribution_guide.md) and pick up one of these challenges! diff --git a/docs/reference/interop/templates.md b/docs/reference/interop/templates.md index 3a114163..67f81826 100644 --- a/docs/reference/interop/templates.md +++ b/docs/reference/interop/templates.md @@ -36,13 +36,16 @@ Using full paths is recommended for clarity and to avoid confusion when template ## Default Templates -HealthChain provides default templates for the transformation of Problems, Medications, and Allergies sections in a Continuity of Care (CCD) CDA to FHIR and the reverse. They are configured to work out of the box with the default configuration. You are welcome to modify these templates at your own discretion or use them as a starting reference point for your writing your own templates. +HealthChain provides default templates for the transformation of Problems, Medications, and Notes sections in a Continuity of Care (CCD) CDA to FHIR and the reverse. They are configured to work out of the box with the default configuration and the example CDAs [here](https://github.com/dotimplement/HealthChain/tree/main/resources). + +You are welcome to modify these templates at your own discretion or use them as a starting reference point for your writing your own templates. **Always verify that templates work for your use case.** + +**Note:** Some templates are experimental and not included in the default configs. See [Experimental Templates](experimental.md) for details on templates under development. | CDA Section | FHIR Resource | |-------------|---------------| | **Problems** | [**Condition**](https://www.hl7.org/fhir/condition.html) | | **Medications** | [**MedicationStatement**](https://www.hl7.org/fhir/medicationstatement.html) | -| **Allergies** | [**AllergyIntolerance**](https://www.hl7.org/fhir/allergyintolerance.html) | | **Notes** | [**DocumentReference**](https://www.hl7.org/fhir/documentreference.html) | CDA to FHIR templates: @@ -51,7 +54,6 @@ CDA to FHIR templates: |-------------|------------------| | **Problems** | `fhir_cda/problem_entry.liquid` | | **Medications** | `fhir_cda/medication_entry.liquid` | -| **Allergies** | `fhir_cda/allergy_entry.liquid` | | **Notes** | `fhir_cda/note_entry.liquid` | FHIR to CDA templates: @@ -60,7 +62,6 @@ FHIR to CDA templates: |---------------|------------------| | **Condition** | `cda_fhir/condition.liquid` | | **MedicationStatement** | `cda_fhir/medication_statement.liquid` | -| **AllergyIntolerance** | `cda_fhir/allergy_intolerance.liquid` | | **DocumentReference** | `cda_fhir/document_reference.liquid` | ## Template Format @@ -198,7 +199,6 @@ The template system provides several custom filters for common healthcare docume | `extract_effective_period` | Extracts effective period data from CDA effectiveTime elements | | `extract_effective_timing` | Extracts timing data from effectiveTime elements | | `extract_clinical_status` | Extracts clinical status from an observation | -| `extract_reactions` | Extracts reactions from an observation | | `clean_empty` | Recursively removes empty values from dictionaries and lists | | `to_base64` | Encodes text to base64 | | `from_base64` | Decodes base64 to text | diff --git a/docs/reference/pipeline/adapters/cdaadapter.md b/docs/reference/pipeline/adapters/cdaadapter.md index 2da4301b..909b14c1 100644 --- a/docs/reference/pipeline/adapters/cdaadapter.md +++ b/docs/reference/pipeline/adapters/cdaadapter.md @@ -10,7 +10,7 @@ This adapter is particularly useful for clinical documentation improvement (CDI) | Input | Output | Document Access | |-------|--------|-----------------| -| [**CdaRequest**](../../../api/use_cases.md#healthchain.models.requests.cdarequest.CdaRequest) | [**CdaResponse**](../../../api/use_cases.md#healthchain.models.responses.cdaresponse.CdaResponse) | `Document.fhir.problem_list`, `Document.fhir.medication_list`, `Document.fhir.allergy_list`, `Document.text` | +| [**CdaRequest**](../../../api/use_cases.md#healthchain.models.requests.cdarequest.CdaRequest) | [**CdaResponse**](../../../api/use_cases.md#healthchain.models.responses.cdaresponse.CdaResponse) | `Document.fhir.problem_list`, `Document.fhir.medication_list`, `Document.text` | ## Document Data Access @@ -20,7 +20,6 @@ Data parsed from the CDA document is converted into FHIR resources and stored in |-------------|---------------|--------------------------| | Problem List | [Condition](https://www.hl7.org/fhir/condition.html) | `Document.fhir.problem_list` | | Medication List | [MedicationStatement](https://www.hl7.org/fhir/medicationstatement.html) | `Document.fhir.medication_list` | -| Allergy List | [AllergyIntolerance](https://www.hl7.org/fhir/allergyintolerance.html) | `Document.fhir.allergy_list` | | Clinical Notes | [DocumentReference](https://www.hl7.org/fhir/documentreference.html) | `Document.text` + `Document.fhir.bundle` | All FHIR resources are Pydantic models, so you can access them using the `model_dump()` method: diff --git a/healthchain/config/validators.py b/healthchain/config/validators.py index 3940614d..4541d180 100644 --- a/healthchain/config/validators.py +++ b/healthchain/config/validators.py @@ -193,7 +193,7 @@ def validate_templates(cls, v): class CcdDocumentConfig(DocumentConfigBase): """Configuration model specific to CCD documents""" - allowed_sections: List[str] = ["problems", "medications", "allergies", "notes"] + allowed_sections: List[str] = ["problems", "medications", "notes"] class NoteSectionTemplateConfig(SectionTemplateConfigBase): @@ -218,7 +218,6 @@ def validate_note_section(cls, v): CDA_SECTION_CONFIG_REGISTRY = { "Condition": ProblemSectionTemplateConfig, "MedicationStatement": MedicationSectionTemplateConfig, - "AllergyIntolerance": AllergySectionTemplateConfig, "DocumentReference": NoteSectionTemplateConfig, } diff --git a/tests/integration_tests/test_interop_engine_integration.py b/tests/integration_tests/test_interop_engine_integration.py index 86fc7bc2..c67a6c91 100644 --- a/tests/integration_tests/test_interop_engine_integration.py +++ b/tests/integration_tests/test_interop_engine_integration.py @@ -4,7 +4,8 @@ from fhir.resources.condition import Condition from fhir.resources.medicationstatement import MedicationStatement -from fhir.resources.allergyintolerance import AllergyIntolerance + +# from fhir.resources.allergyintolerance import AllergyIntolerance # Removed from bundled configs from fhir.resources.documentreference import DocumentReference from healthchain.interop.engine import InteropEngine @@ -35,7 +36,7 @@ def test_cda_to_fhir_conversion(interop_engine, test_cda_xml): This test verifies that: - The engine successfully converts CDA to FHIR resources - - The expected resource types (Condition, MedicationStatement, AllergyIntolerance) are present + - The expected resource types (Condition, MedicationStatement) are present - The FHIR resources contain the correct data from the source CDA document """ # Convert CDA to FHIR @@ -48,12 +49,12 @@ def test_cda_to_fhir_conversion(interop_engine, test_cda_xml): # Check individual resources conditions = [r for r in resources if isinstance(r, Condition)] medications = [r for r in resources if isinstance(r, MedicationStatement)] - allergies = [r for r in resources if isinstance(r, AllergyIntolerance)] + # allergies = [r for r in resources if isinstance(r, AllergyIntolerance)] # Removed from bundled configs notes = [r for r in resources if isinstance(r, DocumentReference)] assert len(conditions) > 0 assert len(medications) > 0 - assert len(allergies) > 0 + # assert len(allergies) > 0 # Removed from bundled configs assert len(notes) > 0 # Verify specific data in the resources @@ -100,30 +101,31 @@ def test_cda_to_fhir_conversion(interop_engine, test_cda_xml): assert medication.dosage[0].doseAndRate[0].doseQuantity.unit == "mg" assert medication.effectivePeriod.end - # Allergy - allergy = allergies[0] - assert "dev-" in allergy.id - assert allergy.patient.reference == "Patient/Foo" - # TODO: fix this!! - # assert allergy.clinicalStatus.coding[0].code == "active" - assert ( - allergy.clinicalStatus.coding[0].system - == "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical" - ) - assert allergy.type.coding[0].code == "418471000" - assert allergy.type.coding[0].display == "Propensity to adverse reactions to food" - assert allergy.type.coding[0].system == "http://snomed.info/sct" - assert allergy.code.coding[0].code == "102263004" - assert allergy.code.coding[0].display == "EGGS" - assert allergy.code.coding[0].system == "http://snomed.info/sct" - assert allergy.reaction[0].manifestation[0].concept.coding[0].code == "65124004" - assert allergy.reaction[0].manifestation[0].concept.coding[0].display == "Swelling" - assert ( - allergy.reaction[0].manifestation[0].concept.coding[0].system - == "http://snomed.info/sct" - ) - assert allergy.reaction[0].severity == "severe" - assert allergy.onsetDateTime + # Allergy tests removed - allergies not in bundled configs due to known bugs + # See dev-templates/allergies/ for experimental allergy support + # allergy = allergies[0] + # assert "dev-" in allergy.id + # assert allergy.patient.reference == "Patient/Foo" + # # TODO: fix this!! + # # assert allergy.clinicalStatus.coding[0].code == "active" + # assert ( + # allergy.clinicalStatus.coding[0].system + # == "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical" + # ) + # assert allergy.type.coding[0].code == "418471000" + # assert allergy.type.coding[0].display == "Propensity to adverse reactions to food" + # assert allergy.type.coding[0].system == "http://snomed.info/sct" + # assert allergy.code.coding[0].code == "102263004" + # assert allergy.code.coding[0].display == "EGGS" + # assert allergy.code.coding[0].system == "http://snomed.info/sct" + # assert allergy.reaction[0].manifestation[0].concept.coding[0].code == "65124004" + # assert allergy.reaction[0].manifestation[0].concept.coding[0].display == "Swelling" + # assert ( + # allergy.reaction[0].manifestation[0].concept.coding[0].system + # == "http://snomed.info/sct" + # ) + # assert allergy.reaction[0].severity == "severe" + # assert allergy.onsetDateTime # Note note = notes[0] @@ -165,7 +167,7 @@ def test_fhir_to_cda_conversion(interop_engine, test_cda_xml): # Check for common elements that should be in the CDA assert "Problem List" in cda assert "Medications" in cda - assert "Allergies" in cda + # assert "Allergies" in cda # Removed from bundled configs assert "Progress Notes" in cda assert '' in cda @@ -196,26 +198,26 @@ def test_fhir_to_cda_conversion(interop_engine, test_cda_xml): '' in cda ) - assert ( - '' - in cda - ) - - assert ( - 'code="102263004" codeSystem="2.16.840.1.113883.6.96" displayName="EGGS"' in cda - ) - assert ( - 'code="65124004" codeSystem="2.16.840.1.113883.6.96" displayName="Swelling"' - in cda - ) - assert ( - '' - in cda - ) - assert ( - 'code="H" codeSystem="2.16.840.1.113883.5.1063" codeSystemName="SeverityObservation" displayName="H"' - in cda - ) + # assert ( + # '' + # in cda + # ) + + # assert ( + # 'code="102263004" codeSystem="2.16.840.1.113883.6.96" displayName="EGGS"' in cda + # ) + # assert ( + # 'code="65124004" codeSystem="2.16.840.1.113883.6.96" displayName="Swelling"' + # in cda + # ) + # assert ( + # '' + # in cda + # ) + # assert ( + # 'code="H" codeSystem="2.16.840.1.113883.5.1063" codeSystemName="SeverityObservation" displayName="H"' + # in cda + # ) assert "CDATA" in cda assert "test" in cda @@ -258,10 +260,8 @@ def test_round_trip_equivalence(interop_engine, test_cda_xml): == original_medications[0].medication.concept.coding[0].code ) - # TODO: allergy intolerance reverse parsing isn't quite right look into this - # print(resources_result[2].model_dump_json(indent=2)) - # print(resources[2].model_dump_json(indent=2)) - # assert resources_result[2].code.coding[0].code == resources[2].code.coding[0].code + # Note: Allergy tests removed - allergies not in bundled configs due to known bugs + # See dev-templates/allergies/ for experimental allergy support def test_cda_adapter_with_interop_engine( @@ -281,7 +281,7 @@ def test_cda_adapter_with_interop_engine( # Verify FHIR resources were extracted assert len(result.fhir.problem_list) == 1 assert len(result.fhir.medication_list) == 1 - assert len(result.fhir.allergy_list) == 1 + # assert len(result.fhir.allergy_list) == 1 # Removed from bundled configs # Verify types of extracted resources assert result.fhir.problem_list[0].code.coding[0].code == "38341003" @@ -289,7 +289,7 @@ def test_cda_adapter_with_interop_engine( result.fhir.problem_list[0].category[0].coding[0].code == "problem-list-item" ) # Should be set by the adapter assert result.fhir.medication_list[0].medication.concept.coding[0].code == "314076" - assert result.fhir.allergy_list[0].code.coding[0].code == "102263004" + # assert result.fhir.allergy_list[0].code.coding[0].code == "102263004" # Removed from bundled configs # Check document references assert result.data == "test" @@ -329,7 +329,7 @@ def test_cda_adapter_with_interop_engine( assert "Test Condition" in response.document # New problem assert "Medications" in response.document assert "314076" in response.document - assert "Allergies" in response.document - assert "102263004" in response.document + # assert "Allergies" in response.document # Removed from bundled configs + # assert "102263004" in response.document # Removed from bundled configs assert "Progress Notes" in response.document assert "test" in response.document diff --git a/tests/pipeline/prebuilt/test_medicalcoding.py b/tests/pipeline/prebuilt/test_medicalcoding.py index 853db091..75fe2da0 100644 --- a/tests/pipeline/prebuilt/test_medicalcoding.py +++ b/tests/pipeline/prebuilt/test_medicalcoding.py @@ -96,4 +96,4 @@ def test_full_coding_pipeline_integration(mock_spacy_nlp, test_cda_request): assert "Aspirin" in cda_response.document assert "Hypertension" in cda_response.document - assert "Allergy to peanuts" in cda_response.document + # assert "Allergy to peanuts" in cda_response.document From a5e760503e5df0e00e5b0febf67e5f3ac19af008 Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Mon, 4 Aug 2025 16:00:19 +0100 Subject: [PATCH 16/19] Use bundled templates as default --- healthchain/interop/__init__.py | 11 +-- tests/interop/test_init_functions.py | 101 +++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 tests/interop/test_init_functions.py diff --git a/healthchain/interop/__init__.py b/healthchain/interop/__init__.py index 9682d8e7..7a7db681 100644 --- a/healthchain/interop/__init__.py +++ b/healthchain/interop/__init__.py @@ -109,14 +109,9 @@ def create_interop( logger = logging.getLogger(__name__) if config_dir is None: - # Try local configs first (for customization), fall back to bundled - local_configs = Path("configs") - if local_configs.exists(): - config_dir = local_configs - logger.info("Using local configs from ./configs") - else: - config_dir = _get_bundled_configs() - logger.info("Using bundled default configs") + # Use bundled configs as default + config_dir = _get_bundled_configs() + logger.info("Using bundled default configs") else: # Convert string to Path if needed config_dir = Path(config_dir) diff --git a/tests/interop/test_init_functions.py b/tests/interop/test_init_functions.py new file mode 100644 index 00000000..5c772eb0 --- /dev/null +++ b/tests/interop/test_init_functions.py @@ -0,0 +1,101 @@ +"""Tests for interop initialization functions.""" + +import pytest +import tempfile +from pathlib import Path +from unittest.mock import Mock, patch + +from healthchain.interop import init_config_templates, create_interop +from healthchain.interop.engine import InteropEngine + + +def test_init_config_templates_prevents_overwriting_existing_configs(): + """init_config_templates prevents accidentally overwriting existing configuration.""" + with tempfile.TemporaryDirectory() as temp_dir: + target_dir = Path(temp_dir) / "existing" + target_dir.mkdir() # Create directory first + + with pytest.raises(FileExistsError, match="Target directory already exists"): + init_config_templates(str(target_dir)) + + +def test_init_config_templates_creates_customizable_config_structure(): + """init_config_templates creates complete configuration structure for user customization.""" + with tempfile.TemporaryDirectory() as temp_dir: + # Create minimal mock source structure + source_dir = Path(temp_dir) / "source" + source_dir.mkdir() + (source_dir / "defaults.yaml").write_text("version: 1.0") + + target_dir = Path(temp_dir) / "target" + + with patch("healthchain.interop._get_bundled_configs", return_value=source_dir): + result = init_config_templates(str(target_dir)) + + # Verify structure is created and files are copied + assert result == target_dir + assert target_dir.exists() + assert (target_dir / "defaults.yaml").exists() + + +def test_init_config_templates_handles_copy_failures_gracefully(): + """init_config_templates provides clear error message when copy operation fails.""" + with tempfile.TemporaryDirectory() as temp_dir: + nonexistent_source = Path(temp_dir) / "nonexistent" + target_dir = Path(temp_dir) / "target" + + with patch( + "healthchain.interop._get_bundled_configs", return_value=nonexistent_source + ): + with pytest.raises(OSError, match="Failed to copy configuration templates"): + init_config_templates(str(target_dir)) + + +@pytest.mark.parametrize("environment", ["invalid_env", "staging", "local"]) +def test_create_interop_validates_environment_parameter(environment): + """create_interop enforces valid environment values for configuration consistency.""" + with tempfile.TemporaryDirectory() as temp_dir: + with pytest.raises(ValueError, match="environment must be one of"): + create_interop(config_dir=temp_dir, environment=environment) + + +def test_create_interop_rejects_nonexistent_config_directory(): + """create_interop validates config directory exists before engine creation.""" + nonexistent_dir = Path("/nonexistent/path") + + with pytest.raises(ValueError, match="Config directory does not exist"): + create_interop(config_dir=nonexistent_dir) + + +@patch("healthchain.interop.InteropEngine") +def test_create_interop_supports_custom_validation_and_environment_settings( + mock_engine_class, +): + """create_interop passes validation and environment settings to engine for proper configuration.""" + with tempfile.TemporaryDirectory() as temp_dir: + mock_engine = Mock(spec=InteropEngine) + mock_engine_class.return_value = mock_engine + + result = create_interop( + config_dir=temp_dir, validation_level="warn", environment="testing" + ) + + # Verify configuration is passed correctly + mock_engine_class.assert_called_once_with(Path(temp_dir), "warn", "testing") + assert result == mock_engine + + +@patch("healthchain.interop.InteropEngine") +def test_create_interop_auto_discovers_configuration_when_none_specified( + mock_engine_class, +): + """create_interop automatically finds and uses available configuration when no config_dir provided.""" + mock_engine = Mock(spec=InteropEngine) + mock_engine_class.return_value = mock_engine + + # Should successfully create engine with auto-discovered configs + result = create_interop() + + # Verify engine was created (discovery mechanism is implementation detail) + mock_engine_class.assert_called_once() + assert result == mock_engine From 4d58762af11561ee971ee86d167686d7da43499c Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Mon, 4 Aug 2025 16:00:36 +0100 Subject: [PATCH 17/19] Add tests for cli --- tests/test_cli.py | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 tests/test_cli.py diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..8051012e --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,95 @@ +"""Tests for HealthChain CLI functionality.""" + +import pytest +import subprocess +from unittest.mock import patch + +from healthchain.cli import init_configs, run_file, main + + +@pytest.mark.parametrize( + "error,expected_messages", + [ + ( + FileExistsError("Directory already exists"), + [ + "❌ Error: Directory already exists", + "💡 Tip: Choose a different directory name or remove the existing one", + ], + ), + ( + Exception("Something went wrong"), + [ + "❌ Error initializing configs: Something went wrong", + "💡 Tip: Make sure HealthChain is properly installed", + ], + ), + ], +) +@patch("healthchain.interop.init_config_templates") +def test_init_configs_error_handling_provides_helpful_guidance( + mock_init_templates, error, expected_messages +): + """init_configs provides helpful error messages and guidance when template creation fails.""" + mock_init_templates.side_effect = error + + with patch("builtins.print") as mock_print: + init_configs("./test_configs") + + # Verify helpful error messages are displayed + for expected_msg in expected_messages: + assert any(expected_msg in str(call) for call in mock_print.call_args_list) + + +@patch("healthchain.interop.init_config_templates") +def test_init_configs_success_provides_usage_instructions(mock_init_templates): + """init_configs provides clear usage instructions when successful.""" + target_dir = "./test_configs" + mock_init_templates.return_value = target_dir + + with patch("builtins.print") as mock_print: + init_configs(target_dir) + + # Verify success message and usage instructions are provided + print_output = " ".join(str(call) for call in mock_print.call_args_list) + assert "🎉 Success!" in print_output + assert "create_interop(config_dir=" in print_output + assert "📖 Next steps:" in print_output + + +@patch("subprocess.run") +def test_run_file_handles_execution_errors_gracefully(mock_run): + """run_file provides clear error message when script execution fails.""" + mock_run.side_effect = subprocess.CalledProcessError(1, "poetry") + + with patch("builtins.print") as mock_print: + run_file("failing_script.py") + + # Verify error message is informative + error_message = mock_print.call_args[0][0] + assert "An error occurred while trying to run the file:" in error_message + + +@pytest.mark.parametrize( + "args,expected_call", + [ + (["healthchain", "run", "test.py"], ("run_file", "test.py")), + (["healthchain", "init-configs", "my_configs"], ("init_configs", "my_configs")), + (["healthchain", "init-configs"], ("init_configs", "./healthchain_configs")), + ], +) +def test_main_routes_commands_correctly(args, expected_call): + """Main function correctly routes CLI commands to appropriate handlers.""" + function_name, expected_arg = expected_call + + with patch(f"healthchain.cli.{function_name}") as mock_function: + with patch("sys.argv", args): + main() + mock_function.assert_called_once_with(expected_arg) + + +def test_main_requires_command_argument(): + """Main function enforces required command argument.""" + with patch("sys.argv", ["healthchain"]): + with pytest.raises(SystemExit): + main() From f662fd3136426734188c6daa07ad9a72d482e7dc Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Mon, 4 Aug 2025 16:49:12 +0100 Subject: [PATCH 18/19] Update docs --- docs/quickstart.md | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/docs/quickstart.md b/docs/quickstart.md index 564f5feb..d588b655 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -154,10 +154,14 @@ The HealthChain Interoperability module provides tools for converting between di [(Full Documentation on Interoperability Engine)](./reference/interop/interop.md) + +**Choose your setup based on your needs:** + +✅ **Default configs** - For basic testing and prototyping only: ```python from healthchain.interop import create_interop, FormatType -# Create an interoperability engine +# Uses bundled configs - basic CDA ↔ FHIR conversion engine = create_interop() # Load a CDA document @@ -171,26 +175,31 @@ fhir_resources = engine.to_fhir(cda_xml, src_format=FormatType.CDA) cda_document = engine.from_fhir(fhir_resources, dest_format=FormatType.CDA) ``` -**Need to customize?** Use the CLI to create editable configuration templates: +> ⚠️ **Default configs are limited** - Only supports problems, medications, and notes. No allergies, custom mappings, or organization-specific templates. +🛠️ **Custom configs** - **Required for real-world use**: ```bash -# Create customizable config templates +# Create editable configuration templates healthchain init-configs ./my_configs - -# Then use them in your code -engine = create_interop(config_dir="./my_configs") ``` -The interop module provides a flexible, template-based approach to healthcare format conversion: +```python +# Use your customized configs +engine = create_interop(config_dir="./my_configs") -| Feature | Description | -|---------|-------------| -| Format conversion | Convert legacy formats (CDA, HL7v2) to FHIR resources and back | -| Template-based generation | Customize syntactic output using [Liquid](https://shopify.github.io/liquid/) templates | -| Configuration | Configure terminology mappings, validation rules, and environments | -| Extension | Register custom parsers, generators, and validators | +# Now you can customize: +# • Add experimental features (allergies, procedures) +# • Modify terminology mappings (SNOMED, LOINC codes) +# • Customize templates for your organization's CDA format +# • Configure validation rules and environments +``` -For more details, see the [conversion examples](cookbook/interop/basic_conversion.md). +**When you need custom configs:** +- 🏥 **Production healthcare applications** +- 🔧 **Organization-specific CDA templates** +- 🧪 **Experimental features** (allergies, procedures) +- 🗺️ **Custom terminology mappings** +- 🛡️ **Specific validation requirements** ## Utilities ⚙️ From 57fa6553d70952982fe20305b66a694c16e5ac3f Mon Sep 17 00:00:00 2001 From: jenniferjiangkells Date: Mon, 4 Aug 2025 17:03:21 +0100 Subject: [PATCH 19/19] Tweak description --- README.md | 2 +- docs/index.md | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6f76b829..086daa0f 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ First time here? Check out our [Docs](https://dotimplement.github.io/HealthChain ## HealthChainAPI -The HealthChainAPI provides a secure, asynchronous integration layer that coordinates multiple healthcare systems in a single application. +The HealthChainAPI provides a secure integration layer that coordinates multiple healthcare systems in a single application. ### Multi-Protocol Support diff --git a/docs/index.md b/docs/index.md index 53d9e534..f5102fdd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ # Welcome to HealthChain 💫 🏥 -HealthChain is an open-source Python framework for building real-time AI applications in a healthcare context. +HealthChain is an open-source Python framework that makes it easier to connect your AI/ML pipelines to healthcare systems. [ :fontawesome-brands-discord: Join our Discord](https://discord.gg/UQC6uAepUz){ .md-button .md-button--primary }      diff --git a/pyproject.toml b/pyproject.toml index 916bd0e8..ce33499b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "healthchain" version = "0.0.0" -description = "Remarkably simple testing and validation of AI/NLP applications in healthcare context." +description = "Python toolkit that makes it easier to connect your AI/ML pipelines to healthcare systems" authors = ["Jennifer Jiang-Kells ", "Adam Kells "] license = "Apache-2.0" readme = "README.md"