From 61e0a76bfe4aeaf8a39d28f57260f6bee3aae4b3 Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:52:38 +0000 Subject: [PATCH 1/5] [fern-generated] Update SDK Generated by Fern CLI Version: unknown Generators: - fernapi/fern-python-sdk: 5.15.2 --- .fern/metadata.json | 8 ++--- poetry.lock | 20 +++++------ pyproject.toml | 8 ++--- src/phenoml/client.py | 32 +++++++++++++---- src/phenoml/core/client_wrapper.py | 36 ++++++++++++++++--- src/phenoml/core/http_client.py | 12 ++++--- src/phenoml/core/http_sse/_api.py | 36 +++++++++++++++++-- src/phenoml/core/request_options.py | 2 ++ src/phenoml/fhir2omop/__init__.py | 15 ++++++++ src/phenoml/fhir2omop/types/__init__.py | 15 ++++++++ src/phenoml/fhir2omop/types/care_site_row.py | 24 +++++++++++++ .../types/condition_occurrence_row.py | 1 + src/phenoml/fhir2omop/types/death_row.py | 25 +++++++++++++ .../fhir2omop/types/drug_exposure_row.py | 1 + src/phenoml/fhir2omop/types/location_row.py | 36 +++++++++++++++++++ .../fhir2omop/types/measurement_row.py | 1 + .../fhir2omop/types/observation_period_row.py | 23 ++++++++++++ .../fhir2omop/types/observation_row.py | 1 + src/phenoml/fhir2omop/types/omop_tables.py | 10 ++++++ src/phenoml/fhir2omop/types/person_row.py | 1 + .../types/procedure_occurrence_row.py | 1 + src/phenoml/fhir2omop/types/provider_row.py | 31 ++++++++++++++++ .../fhir2omop/types/visit_occurrence_row.py | 2 ++ .../create_multi_response_resources_item.py | 8 +++++ wiremock/wiremock-mappings.json | 6 ++-- 25 files changed, 317 insertions(+), 38 deletions(-) create mode 100644 src/phenoml/fhir2omop/types/care_site_row.py create mode 100644 src/phenoml/fhir2omop/types/death_row.py create mode 100644 src/phenoml/fhir2omop/types/location_row.py create mode 100644 src/phenoml/fhir2omop/types/observation_period_row.py create mode 100644 src/phenoml/fhir2omop/types/provider_row.py diff --git a/.fern/metadata.json b/.fern/metadata.json index 69b204f..c85d6e8 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -1,17 +1,17 @@ { - "cliVersion": "5.50.4", + "cliVersion": "5.64.0", "generatorName": "fernapi/fern-python-sdk", - "generatorVersion": "5.14.13", + "generatorVersion": "5.15.2", "generatorConfig": { "client_class_name": "PhenomlClient", "wire_tests": { "enabled": true } }, - "originGitCommit": "e2c477c4583f8f9e7d0d1a5b5591c1aa8cabef10", + "originGitCommit": "5ca96bb9b4766f13b667043f4937d5f0cea5f6a7", "originGitCommitIsDirty": true, "invokedBy": "ci", "requestedVersion": "AUTO", "ciProvider": "unknown", - "sdkVersion": "16.4.0" + "sdkVersion": "0.0.0.dev0" } \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 2250508..0cbff58 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,15 +2,15 @@ [[package]] name = "aiohappyeyeballs" -version = "2.6.2" +version = "2.7.1" description = "Happy Eyeballs for asyncio" optional = true python-versions = ">=3.10" groups = ["main"] markers = "extra == \"aiohttp\"" files = [ - {file = "aiohappyeyeballs-2.6.2-py3-none-any.whl", hash = "sha256:4708045e2d7a6c6bdf8aafa8ed39649eaf926a4543b54560659129e3365953c4"}, - {file = "aiohappyeyeballs-2.6.2.tar.gz", hash = "sha256:e202810ee718bd01fc6ef49e8ea53d023d5cb6b581076d7925aa499fa55dbe64"}, + {file = "aiohappyeyeballs-2.7.1-py3-none-any.whl", hash = "sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472"}, + {file = "aiohappyeyeballs-2.7.1.tar.gz", hash = "sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d"}, ] [[package]] @@ -188,14 +188,14 @@ files = [ [[package]] name = "anyio" -version = "4.14.0" +version = "4.14.1" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9"}, - {file = "anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89"}, + {file = "anyio-4.14.1-py3-none-any.whl", hash = "sha256:4e5533c5b8ff0a24f5d7a176cbe6877129cd183893f66b537f8f227d10527d72"}, + {file = "anyio-4.14.1.tar.gz", hash = "sha256:8d648a3544c1a700e3ff78615cd679e4c5c3f149904287e73687b2596963629e"}, ] [package.dependencies] @@ -1473,14 +1473,14 @@ urllib3 = ">=2" [[package]] name = "typing-extensions" -version = "4.15.0" +version = "4.16.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548"}, - {file = "typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466"}, + {file = "typing_extensions-4.16.0-py3-none-any.whl", hash = "sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8"}, + {file = "typing_extensions-4.16.0.tar.gz", hash = "sha256:dc983d19a509c94dba722ee6abd33940f7c05a89e243c47e907eb4db6f1a43e5"}, ] [[package]] @@ -1642,4 +1642,4 @@ aiohttp = ["aiohttp", "httpx-aiohttp"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "41990018ea498046b10397ee315af35d572db818b58b1a0d2d9cb66f867b596f" +content-hash = "65ef513ef69a32344fa2133df094babe6493482ea7586d0e19edbcb2f4bbe7c8" diff --git a/pyproject.toml b/pyproject.toml index 6c649b6..cea8b20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] [tool.poetry] name = "phenoml" -version = "16.4.0" +version = "0.0.0.dev0" description = "" readme = "README.md" authors = [] @@ -31,16 +31,13 @@ classifiers = [ packages = [ { include = "phenoml", from = "src"} ] -include = [ - { path = "src/phenoml/openapi/openapi.json", format = ["sdist", "wheel"] } -] [tool.poetry.urls] Repository = 'https://github.com/phenoml/phenoml-python-sdk' [tool.poetry.dependencies] python = "^3.10" -aiohttp = { version = ">=3.14.0,<4", optional = true, python = ">=3.10"} +aiohttp = { version = ">=3.14.1,<4", optional = true, python = ">=3.10"} httpx = ">=0.21.2" httpx-aiohttp = { version = "0.1.8", optional = true, python = ">=3.10"} pydantic = ">= 1.9.2" @@ -62,6 +59,7 @@ ruff = "==0.11.5" [tool.pytest.ini_options] testpaths = [ "tests" ] asyncio_mode = "auto" +norecursedirs = [ "src" ] markers = [ "aiohttp: tests that require httpx_aiohttp to be installed", ] diff --git a/src/phenoml/client.py b/src/phenoml/client.py index 126a68e..4910b15 100644 --- a/src/phenoml/client.py +++ b/src/phenoml/client.py @@ -104,6 +104,8 @@ def __init__( headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = None, max_retries: typing.Optional[int] = None, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.Client] = None, logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, @@ -120,6 +122,8 @@ def __init__( headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = None, max_retries: typing.Optional[int] = None, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.Client] = None, logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, @@ -138,13 +142,13 @@ def __init__( _token_getter_override: typing.Optional[typing.Callable[[], str]] = None, timeout: typing.Optional[float] = None, max_retries: typing.Optional[int] = None, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.Client] = None, logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, ): - _defaulted_timeout = ( - timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read - ) + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None _defaulted_max_retries = max_retries if max_retries is not None else 2 if instance_url is not None: _instance_url = instance_url if instance_url is not None else "experiment.app.pheno.ml" @@ -160,6 +164,8 @@ def __init__( else httpx.Client(timeout=_defaulted_timeout), timeout=_defaulted_timeout, max_retries=_defaulted_max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, logging=logging, token=_token_getter_override if _token_getter_override is not None else token, ) @@ -177,6 +183,8 @@ def __init__( else httpx.Client(timeout=_defaulted_timeout), timeout=_defaulted_timeout, max_retries=_defaulted_max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, logging=logging, ), ) @@ -191,6 +199,8 @@ def __init__( else httpx.Client(timeout=_defaulted_timeout), timeout=_defaulted_timeout, max_retries=_defaulted_max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, logging=logging, ) else: @@ -402,6 +412,8 @@ def __init__( headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = None, max_retries: typing.Optional[int] = None, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.AsyncClient] = None, logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, @@ -418,6 +430,8 @@ def __init__( headers: typing.Optional[typing.Dict[str, str]] = None, timeout: typing.Optional[float] = None, max_retries: typing.Optional[int] = None, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.AsyncClient] = None, logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, @@ -436,13 +450,13 @@ def __init__( _token_getter_override: typing.Optional[typing.Callable[[], str]] = None, timeout: typing.Optional[float] = None, max_retries: typing.Optional[int] = None, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, follow_redirects: typing.Optional[bool] = True, httpx_client: typing.Optional[httpx.AsyncClient] = None, logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, ): - _defaulted_timeout = ( - timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read - ) + _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None _defaulted_max_retries = max_retries if max_retries is not None else 2 if instance_url is not None: _instance_url = instance_url if instance_url is not None else "experiment.app.pheno.ml" @@ -456,6 +470,8 @@ def __init__( else _make_default_async_client(timeout=_defaulted_timeout, follow_redirects=follow_redirects), timeout=_defaulted_timeout, max_retries=_defaulted_max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, logging=logging, token=_token_getter_override if _token_getter_override is not None else token, ) @@ -473,6 +489,8 @@ def __init__( else httpx.AsyncClient(timeout=_defaulted_timeout), timeout=_defaulted_timeout, max_retries=_defaulted_max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, logging=logging, ), ) @@ -486,6 +504,8 @@ def __init__( else _make_default_async_client(timeout=_defaulted_timeout, follow_redirects=follow_redirects), timeout=_defaulted_timeout, max_retries=_defaulted_max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, logging=logging, ) else: diff --git a/src/phenoml/core/client_wrapper.py b/src/phenoml/core/client_wrapper.py index 536cb52..c03a243 100644 --- a/src/phenoml/core/client_wrapper.py +++ b/src/phenoml/core/client_wrapper.py @@ -16,6 +16,8 @@ def __init__( base_url: str, timeout: typing.Optional[float] = None, max_retries: int = 2, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, ): self._token = token @@ -23,18 +25,20 @@ def __init__( self._base_url = base_url self._timeout = timeout self._max_retries = max_retries + self._stream_reconnection_enabled = stream_reconnection_enabled + self._max_stream_reconnection_attempts = max_stream_reconnection_attempts self._logging = logging def get_headers(self) -> typing.Dict[str, str]: import platform headers: typing.Dict[str, str] = { - "User-Agent": "phenoml/16.4.0", + "User-Agent": "phenoml/0.0.0-fern-placeholder", "X-Fern-Language": "Python", "X-Fern-Runtime": f"python/{platform.python_version()}", "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", "X-Fern-SDK-Name": "phenoml", - "X-Fern-SDK-Version": "16.4.0", + "X-Fern-SDK-Version": "0.0.0.dev0", **(self.get_custom_headers() or {}), } token = self._get_token() @@ -60,6 +64,12 @@ def get_timeout(self) -> typing.Optional[float]: def get_max_retries(self) -> int: return self._max_retries + def get_stream_reconnection_enabled(self) -> bool: + return self._stream_reconnection_enabled if self._stream_reconnection_enabled is not None else True + + def get_max_stream_reconnection_attempts(self) -> typing.Optional[int]: + return self._max_stream_reconnection_attempts + class SyncClientWrapper(BaseClientWrapper): def __init__( @@ -70,11 +80,20 @@ def __init__( base_url: str, timeout: typing.Optional[float] = None, max_retries: int = 2, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, httpx_client: httpx.Client, ): super().__init__( - token=token, headers=headers, base_url=base_url, timeout=timeout, max_retries=max_retries, logging=logging + token=token, + headers=headers, + base_url=base_url, + timeout=timeout, + max_retries=max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, + logging=logging, ) self.httpx_client = HttpClient( httpx_client=httpx_client, @@ -95,12 +114,21 @@ def __init__( base_url: str, timeout: typing.Optional[float] = None, max_retries: int = 2, + stream_reconnection_enabled: typing.Optional[bool] = None, + max_stream_reconnection_attempts: typing.Optional[int] = None, logging: typing.Optional[typing.Union[LogConfig, Logger]] = None, async_token: typing.Optional[typing.Callable[[], typing.Awaitable[str]]] = None, httpx_client: httpx.AsyncClient, ): super().__init__( - token=token, headers=headers, base_url=base_url, timeout=timeout, max_retries=max_retries, logging=logging + token=token, + headers=headers, + base_url=base_url, + timeout=timeout, + max_retries=max_retries, + stream_reconnection_enabled=stream_reconnection_enabled, + max_stream_reconnection_attempts=max_stream_reconnection_attempts, + logging=logging, ) self._async_token = async_token self.httpx_client = AsyncHttpClient( diff --git a/src/phenoml/core/http_client.py b/src/phenoml/core/http_client.py index f686c57..aae91a2 100644 --- a/src/phenoml/core/http_client.py +++ b/src/phenoml/core/http_client.py @@ -312,11 +312,12 @@ def request( force_multipart: typing.Optional[bool] = None, ) -> httpx.Response: base_url = self.get_base_url(base_url) - timeout = ( + _timeout = ( request_options.get("timeout_in_seconds") if request_options is not None and request_options.get("timeout_in_seconds") is not None else self.base_timeout() ) + timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit) @@ -472,11 +473,12 @@ def stream( force_multipart: typing.Optional[bool] = None, ) -> typing.Iterator[httpx.Response]: base_url = self.get_base_url(base_url) - timeout = ( + _timeout = ( request_options.get("timeout_in_seconds") if request_options is not None and request_options.get("timeout_in_seconds") is not None else self.base_timeout() ) + timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT request_files: typing.Optional[RequestFiles] = ( convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) @@ -601,11 +603,12 @@ async def request( force_multipart: typing.Optional[bool] = None, ) -> httpx.Response: base_url = self.get_base_url(base_url) - timeout = ( + _timeout = ( request_options.get("timeout_in_seconds") if request_options is not None and request_options.get("timeout_in_seconds") is not None else self.base_timeout() ) + timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT request_files: typing.Optional[RequestFiles] = ( convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) @@ -764,11 +767,12 @@ async def stream( force_multipart: typing.Optional[bool] = None, ) -> typing.AsyncIterator[httpx.Response]: base_url = self.get_base_url(base_url) - timeout = ( + _timeout = ( request_options.get("timeout_in_seconds") if request_options is not None and request_options.get("timeout_in_seconds") is not None else self.base_timeout() ) + timeout = _timeout if _timeout is not None else httpx.USE_CLIENT_DEFAULT request_files: typing.Optional[RequestFiles] = ( convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit)) diff --git a/src/phenoml/core/http_sse/_api.py b/src/phenoml/core/http_sse/_api.py index b375399..192ec0d 100644 --- a/src/phenoml/core/http_sse/_api.py +++ b/src/phenoml/core/http_sse/_api.py @@ -3,17 +3,29 @@ import codecs import re from contextlib import asynccontextmanager, contextmanager -from typing import Any, AsyncGenerator, AsyncIterator, Iterator +from typing import Any, AsyncGenerator, AsyncIterator, Iterator, Optional import httpx from ._decoders import SSEDecoder from ._exceptions import SSEError from ._models import ServerSentEvent +MAX_LINE_SIZE: int = 1_048_576 # 1 MiB + class EventSource: - def __init__(self, response: httpx.Response) -> None: + def __init__( + self, + response: httpx.Response, + *, + resumable: bool = False, + stream_reconnection_enabled: bool = True, + max_stream_reconnection_attempts: Optional[int] = None, + ) -> None: self._response = response + self._resumable = resumable + self._stream_reconnection_enabled = stream_reconnection_enabled + self._max_stream_reconnection_attempts = max_stream_reconnection_attempts def _check_content_type(self) -> None: content_type = self._response.headers.get("content-type", "").partition(";")[0] @@ -75,10 +87,20 @@ def iter_sse(self) -> Iterator[ServerSentEvent]: if sse is not None: yield sse + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + # Flush any remaining bytes from the incremental decoder buf += text_decoder.decode(b"", final=True) buf = buf.replace("\r\n", "\n").replace("\r", "\n") + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + while "\n" in buf: line, buf = buf.split("\n", 1) sse = decoder.decode(line) @@ -107,10 +129,20 @@ async def aiter_sse(self) -> AsyncGenerator[ServerSentEvent, None]: if sse is not None: yield sse + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + # Flush any remaining bytes from the incremental decoder buf += text_decoder.decode(b"", final=True) buf = buf.replace("\r\n", "\n").replace("\r", "\n") + if len(buf) > MAX_LINE_SIZE: + raise SSEError( + f"SSE line exceeded maximum size of {MAX_LINE_SIZE} characters without encountering a newline" + ) + while "\n" in buf: line, buf = buf.split("\n", 1) sse = decoder.decode(line) diff --git a/src/phenoml/core/request_options.py b/src/phenoml/core/request_options.py index 1b38804..ebf17bc 100644 --- a/src/phenoml/core/request_options.py +++ b/src/phenoml/core/request_options.py @@ -33,3 +33,5 @@ class RequestOptions(typing.TypedDict, total=False): additional_query_parameters: NotRequired[typing.Dict[str, typing.Any]] additional_body_parameters: NotRequired[typing.Dict[str, typing.Any]] chunk_size: NotRequired[int] + stream_reconnection_enabled: NotRequired[bool] + max_stream_reconnection_attempts: NotRequired[int] diff --git a/src/phenoml/fhir2omop/__init__.py b/src/phenoml/fhir2omop/__init__.py index fa39886..8906ff1 100644 --- a/src/phenoml/fhir2omop/__init__.py +++ b/src/phenoml/fhir2omop/__init__.py @@ -7,33 +7,43 @@ if typing.TYPE_CHECKING: from .types import ( + CareSiteRow, ConditionOccurrenceRow, CreateOmopResponse, + DeathRow, DroppedResource, DrugExposureRow, + LocationRow, MappingEntry, MeasurementRow, + ObservationPeriodRow, ObservationRow, OmopTables, PersonRow, ProcedureOccurrenceRow, + ProviderRow, Summary, VisitOccurrenceRow, ) from .errors import BadRequestError, InternalServerError, ServiceUnavailableError, UnauthorizedError _dynamic_imports: typing.Dict[str, str] = { "BadRequestError": ".errors", + "CareSiteRow": ".types", "ConditionOccurrenceRow": ".types", "CreateOmopResponse": ".types", + "DeathRow": ".types", "DroppedResource": ".types", "DrugExposureRow": ".types", "InternalServerError": ".errors", + "LocationRow": ".types", "MappingEntry": ".types", "MeasurementRow": ".types", + "ObservationPeriodRow": ".types", "ObservationRow": ".types", "OmopTables": ".types", "PersonRow": ".types", "ProcedureOccurrenceRow": ".types", + "ProviderRow": ".types", "ServiceUnavailableError": ".errors", "Summary": ".types", "UnauthorizedError": ".errors", @@ -64,17 +74,22 @@ def __dir__(): __all__ = [ "BadRequestError", + "CareSiteRow", "ConditionOccurrenceRow", "CreateOmopResponse", + "DeathRow", "DroppedResource", "DrugExposureRow", "InternalServerError", + "LocationRow", "MappingEntry", "MeasurementRow", + "ObservationPeriodRow", "ObservationRow", "OmopTables", "PersonRow", "ProcedureOccurrenceRow", + "ProviderRow", "ServiceUnavailableError", "Summary", "UnauthorizedError", diff --git a/src/phenoml/fhir2omop/types/__init__.py b/src/phenoml/fhir2omop/types/__init__.py index 693397b..669a67a 100644 --- a/src/phenoml/fhir2omop/types/__init__.py +++ b/src/phenoml/fhir2omop/types/__init__.py @@ -6,29 +6,39 @@ from importlib import import_module if typing.TYPE_CHECKING: + from .care_site_row import CareSiteRow from .condition_occurrence_row import ConditionOccurrenceRow from .create_omop_response import CreateOmopResponse + from .death_row import DeathRow from .dropped_resource import DroppedResource from .drug_exposure_row import DrugExposureRow + from .location_row import LocationRow from .mapping_entry import MappingEntry from .measurement_row import MeasurementRow + from .observation_period_row import ObservationPeriodRow from .observation_row import ObservationRow from .omop_tables import OmopTables from .person_row import PersonRow from .procedure_occurrence_row import ProcedureOccurrenceRow + from .provider_row import ProviderRow from .summary import Summary from .visit_occurrence_row import VisitOccurrenceRow _dynamic_imports: typing.Dict[str, str] = { + "CareSiteRow": ".care_site_row", "ConditionOccurrenceRow": ".condition_occurrence_row", "CreateOmopResponse": ".create_omop_response", + "DeathRow": ".death_row", "DroppedResource": ".dropped_resource", "DrugExposureRow": ".drug_exposure_row", + "LocationRow": ".location_row", "MappingEntry": ".mapping_entry", "MeasurementRow": ".measurement_row", + "ObservationPeriodRow": ".observation_period_row", "ObservationRow": ".observation_row", "OmopTables": ".omop_tables", "PersonRow": ".person_row", "ProcedureOccurrenceRow": ".procedure_occurrence_row", + "ProviderRow": ".provider_row", "Summary": ".summary", "VisitOccurrenceRow": ".visit_occurrence_row", } @@ -56,16 +66,21 @@ def __dir__(): __all__ = [ + "CareSiteRow", "ConditionOccurrenceRow", "CreateOmopResponse", + "DeathRow", "DroppedResource", "DrugExposureRow", + "LocationRow", "MappingEntry", "MeasurementRow", + "ObservationPeriodRow", "ObservationRow", "OmopTables", "PersonRow", "ProcedureOccurrenceRow", + "ProviderRow", "Summary", "VisitOccurrenceRow", ] diff --git a/src/phenoml/fhir2omop/types/care_site_row.py b/src/phenoml/fhir2omop/types/care_site_row.py new file mode 100644 index 0000000..e60c4d2 --- /dev/null +++ b/src/phenoml/fhir2omop/types/care_site_row.py @@ -0,0 +1,24 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class CareSiteRow(UniversalBaseModel): + care_site_id: typing.Optional[int] = None + care_site_name: typing.Optional[str] = None + place_of_service_concept_id: typing.Optional[int] = None + location_id: typing.Optional[int] = None + care_site_source_value: typing.Optional[str] = None + place_of_service_source_value: typing.Optional[str] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/phenoml/fhir2omop/types/condition_occurrence_row.py b/src/phenoml/fhir2omop/types/condition_occurrence_row.py index 22edfe9..0e7d829 100644 --- a/src/phenoml/fhir2omop/types/condition_occurrence_row.py +++ b/src/phenoml/fhir2omop/types/condition_occurrence_row.py @@ -15,6 +15,7 @@ class ConditionOccurrenceRow(UniversalBaseModel): condition_end_date: typing.Optional[str] = None condition_type_concept_id: typing.Optional[int] = None visit_occurrence_id: typing.Optional[int] = None + provider_id: typing.Optional[int] = None condition_source_value: typing.Optional[str] = None condition_source_concept_id: typing.Optional[int] = None condition_status_source_value: typing.Optional[str] = None diff --git a/src/phenoml/fhir2omop/types/death_row.py b/src/phenoml/fhir2omop/types/death_row.py new file mode 100644 index 0000000..0afd6ce --- /dev/null +++ b/src/phenoml/fhir2omop/types/death_row.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class DeathRow(UniversalBaseModel): + person_id: typing.Optional[int] = None + death_date: typing.Optional[str] = None + death_datetime: typing.Optional[str] = None + death_type_concept_id: typing.Optional[int] = None + cause_concept_id: typing.Optional[int] = None + cause_source_value: typing.Optional[str] = None + cause_source_concept_id: typing.Optional[int] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/phenoml/fhir2omop/types/drug_exposure_row.py b/src/phenoml/fhir2omop/types/drug_exposure_row.py index 69ea538..48803cb 100644 --- a/src/phenoml/fhir2omop/types/drug_exposure_row.py +++ b/src/phenoml/fhir2omop/types/drug_exposure_row.py @@ -17,6 +17,7 @@ class DrugExposureRow(UniversalBaseModel): stop_reason: typing.Optional[str] = None sig: typing.Optional[str] = None visit_occurrence_id: typing.Optional[int] = None + provider_id: typing.Optional[int] = None drug_source_value: typing.Optional[str] = None drug_source_concept_id: typing.Optional[int] = None diff --git a/src/phenoml/fhir2omop/types/location_row.py b/src/phenoml/fhir2omop/types/location_row.py new file mode 100644 index 0000000..395a274 --- /dev/null +++ b/src/phenoml/fhir2omop/types/location_row.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from ...core.serialization import FieldMetadata + + +class LocationRow(UniversalBaseModel): + location_id: typing.Optional[int] = None + address1: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="address_1"), pydantic.Field(alias="address_1") + ] = None + address2: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="address_2"), pydantic.Field(alias="address_2") + ] = None + city: typing.Optional[str] = None + state: typing.Optional[str] = None + zip: typing.Optional[str] = None + county: typing.Optional[str] = None + location_source_value: typing.Optional[str] = None + country_concept_id: typing.Optional[int] = None + country_source_value: typing.Optional[str] = None + latitude: typing.Optional[float] = None + longitude: typing.Optional[float] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/phenoml/fhir2omop/types/measurement_row.py b/src/phenoml/fhir2omop/types/measurement_row.py index d48243d..169fc17 100644 --- a/src/phenoml/fhir2omop/types/measurement_row.py +++ b/src/phenoml/fhir2omop/types/measurement_row.py @@ -24,6 +24,7 @@ class MeasurementRow(UniversalBaseModel): range_low: typing.Optional[float] = None range_high: typing.Optional[float] = None visit_occurrence_id: typing.Optional[int] = None + provider_id: typing.Optional[int] = None measurement_source_value: typing.Optional[str] = None measurement_source_concept_id: typing.Optional[int] = None unit_source_value: typing.Optional[str] = None diff --git a/src/phenoml/fhir2omop/types/observation_period_row.py b/src/phenoml/fhir2omop/types/observation_period_row.py new file mode 100644 index 0000000..8dc956f --- /dev/null +++ b/src/phenoml/fhir2omop/types/observation_period_row.py @@ -0,0 +1,23 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ObservationPeriodRow(UniversalBaseModel): + observation_period_id: typing.Optional[int] = None + person_id: typing.Optional[int] = None + observation_period_start_date: typing.Optional[str] = None + observation_period_end_date: typing.Optional[str] = None + period_type_concept_id: typing.Optional[int] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/phenoml/fhir2omop/types/observation_row.py b/src/phenoml/fhir2omop/types/observation_row.py index 43df628..8a1732f 100644 --- a/src/phenoml/fhir2omop/types/observation_row.py +++ b/src/phenoml/fhir2omop/types/observation_row.py @@ -18,6 +18,7 @@ class ObservationRow(UniversalBaseModel): value_as_concept_id: typing.Optional[int] = None unit_concept_id: typing.Optional[int] = None visit_occurrence_id: typing.Optional[int] = None + provider_id: typing.Optional[int] = None observation_source_value: typing.Optional[str] = None observation_source_concept_id: typing.Optional[int] = None unit_source_value: typing.Optional[str] = None diff --git a/src/phenoml/fhir2omop/types/omop_tables.py b/src/phenoml/fhir2omop/types/omop_tables.py index 906c038..10f0cba 100644 --- a/src/phenoml/fhir2omop/types/omop_tables.py +++ b/src/phenoml/fhir2omop/types/omop_tables.py @@ -4,12 +4,17 @@ import pydantic from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel +from .care_site_row import CareSiteRow from .condition_occurrence_row import ConditionOccurrenceRow +from .death_row import DeathRow from .drug_exposure_row import DrugExposureRow +from .location_row import LocationRow from .measurement_row import MeasurementRow +from .observation_period_row import ObservationPeriodRow from .observation_row import ObservationRow from .person_row import PersonRow from .procedure_occurrence_row import ProcedureOccurrenceRow +from .provider_row import ProviderRow from .visit_occurrence_row import VisitOccurrenceRow @@ -18,7 +23,12 @@ class OmopTables(UniversalBaseModel): OMOP CDM v5.4 rows grouped by destination table. """ + location: typing.Optional[typing.List[LocationRow]] = None + care_site: typing.Optional[typing.List[CareSiteRow]] = None + provider: typing.Optional[typing.List[ProviderRow]] = None person: typing.Optional[typing.List[PersonRow]] = None + death: typing.Optional[typing.List[DeathRow]] = None + observation_period: typing.Optional[typing.List[ObservationPeriodRow]] = None visit_occurrence: typing.Optional[typing.List[VisitOccurrenceRow]] = None condition_occurrence: typing.Optional[typing.List[ConditionOccurrenceRow]] = None drug_exposure: typing.Optional[typing.List[DrugExposureRow]] = None diff --git a/src/phenoml/fhir2omop/types/person_row.py b/src/phenoml/fhir2omop/types/person_row.py index 2ed1a8f..39c31ee 100644 --- a/src/phenoml/fhir2omop/types/person_row.py +++ b/src/phenoml/fhir2omop/types/person_row.py @@ -15,6 +15,7 @@ class PersonRow(UniversalBaseModel): birth_datetime: typing.Optional[str] = None race_concept_id: typing.Optional[int] = None ethnicity_concept_id: typing.Optional[int] = None + location_id: typing.Optional[int] = None person_source_value: typing.Optional[str] = None gender_source_value: typing.Optional[str] = None race_source_value: typing.Optional[str] = None diff --git a/src/phenoml/fhir2omop/types/procedure_occurrence_row.py b/src/phenoml/fhir2omop/types/procedure_occurrence_row.py index 3266117..ace4167 100644 --- a/src/phenoml/fhir2omop/types/procedure_occurrence_row.py +++ b/src/phenoml/fhir2omop/types/procedure_occurrence_row.py @@ -14,6 +14,7 @@ class ProcedureOccurrenceRow(UniversalBaseModel): procedure_datetime: typing.Optional[str] = None procedure_type_concept_id: typing.Optional[int] = None visit_occurrence_id: typing.Optional[int] = None + provider_id: typing.Optional[int] = None procedure_source_value: typing.Optional[str] = None procedure_source_concept_id: typing.Optional[int] = None diff --git a/src/phenoml/fhir2omop/types/provider_row.py b/src/phenoml/fhir2omop/types/provider_row.py new file mode 100644 index 0000000..4ba4745 --- /dev/null +++ b/src/phenoml/fhir2omop/types/provider_row.py @@ -0,0 +1,31 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ...core.pydantic_utilities import IS_PYDANTIC_V2, UniversalBaseModel + + +class ProviderRow(UniversalBaseModel): + provider_id: typing.Optional[int] = None + provider_name: typing.Optional[str] = None + npi: typing.Optional[str] = None + dea: typing.Optional[str] = None + specialty_concept_id: typing.Optional[int] = None + care_site_id: typing.Optional[int] = None + year_of_birth: typing.Optional[int] = None + gender_concept_id: typing.Optional[int] = None + provider_source_value: typing.Optional[str] = None + specialty_source_value: typing.Optional[str] = None + specialty_source_concept_id: typing.Optional[int] = None + gender_source_value: typing.Optional[str] = None + gender_source_concept_id: typing.Optional[int] = None + + if IS_PYDANTIC_V2: + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 + else: + + class Config: + frozen = True + smart_union = True + extra = pydantic.Extra.allow diff --git a/src/phenoml/fhir2omop/types/visit_occurrence_row.py b/src/phenoml/fhir2omop/types/visit_occurrence_row.py index 901160d..c7b6a1a 100644 --- a/src/phenoml/fhir2omop/types/visit_occurrence_row.py +++ b/src/phenoml/fhir2omop/types/visit_occurrence_row.py @@ -15,6 +15,8 @@ class VisitOccurrenceRow(UniversalBaseModel): visit_end_date: typing.Optional[str] = None visit_end_datetime: typing.Optional[str] = None visit_type_concept_id: typing.Optional[int] = None + provider_id: typing.Optional[int] = None + care_site_id: typing.Optional[int] = None visit_source_value: typing.Optional[str] = None if IS_PYDANTIC_V2: diff --git a/src/phenoml/lang2fhir/types/create_multi_response_resources_item.py b/src/phenoml/lang2fhir/types/create_multi_response_resources_item.py index 8a2594b..e7cdcec 100644 --- a/src/phenoml/lang2fhir/types/create_multi_response_resources_item.py +++ b/src/phenoml/lang2fhir/types/create_multi_response_resources_item.py @@ -29,6 +29,14 @@ class CreateMultiResponseResourcesItem(UniversalBaseModel): FieldMetadata(alias="originalText"), pydantic.Field(alias="originalText", description="Verbatim text excerpt from the original clinical document"), ] = None + source_pages: typing_extensions.Annotated[ + typing.Optional[typing.List[int]], + FieldMetadata(alias="sourcePages"), + pydantic.Field( + alias="sourcePages", + description="1-indexed source document page number(s) this resource was extracted from. Populated only by the /lang2fhir/document/multi endpoint; omitted when the source page could not be determined (e.g. raw-text create/multi, or a resource with no verbatim source text).", + ), + ] = None if IS_PYDANTIC_V2: model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict(extra="allow", frozen=True) # type: ignore # Pydantic v2 diff --git a/wiremock/wiremock-mappings.json b/wiremock/wiremock-mappings.json index de3b3de..c728123 100644 --- a/wiremock/wiremock-mappings.json +++ b/wiremock/wiremock-mappings.json @@ -1092,7 +1092,7 @@ }, "response": { "status": 200, - "body": "{\n \"success\": true,\n \"message\": \"FHIR resources mapped to OMOP CDM v5.4\",\n \"tables\": {\n \"person\": [\n {\n \"person_id\": 1,\n \"gender_concept_id\": 0,\n \"year_of_birth\": 1985,\n \"month_of_birth\": 7,\n \"day_of_birth\": 22,\n \"birth_datetime\": \"1985-07-22\",\n \"race_concept_id\": 0,\n \"ethnicity_concept_id\": 0,\n \"person_source_value\": \"patient-1\",\n \"gender_source_value\": \"female\"\n }\n ],\n \"visit_occurrence\": [\n {}\n ],\n \"condition_occurrence\": [\n {\n \"condition_occurrence_id\": 1,\n \"person_id\": 1,\n \"condition_concept_id\": 201826,\n \"condition_start_date\": \"2024-01-15\",\n \"condition_start_datetime\": \"2024-01-15\",\n \"condition_type_concept_id\": 32817,\n \"condition_source_value\": \"http://snomed.info/sct#44054006\",\n \"condition_source_concept_id\": 201826\n }\n ],\n \"drug_exposure\": [\n {\n \"drug_exposure_id\": 1,\n \"person_id\": 1,\n \"drug_concept_id\": 40163924,\n \"drug_exposure_start_date\": \"2024-01-16\",\n \"drug_exposure_start_datetime\": \"2024-01-16\",\n \"drug_type_concept_id\": 32817,\n \"drug_source_value\": \"http://www.nlm.nih.gov/research/umls/rxnorm#860975\",\n \"drug_source_concept_id\": 40163924\n }\n ],\n \"procedure_occurrence\": [\n {}\n ],\n \"measurement\": [\n {}\n ],\n \"observation\": [\n {}\n ]\n },\n \"mappings\": [\n {\n \"resource_type\": \"Condition\",\n \"resource_id\": \"condition-1\",\n \"omop_table\": \"condition_occurrence\",\n \"omop_id\": 1,\n \"source_system\": \"http://snomed.info/sct\",\n \"source_code\": \"44054006\",\n \"source_name\": \"Type 2 diabetes mellitus\",\n \"target_vocabulary\": \"SNOMED\",\n \"target_code\": \"44054006\",\n \"target_name\": \"Type 2 diabetes mellitus\",\n \"mapping_status\": \"ALREADY_STANDARD\",\n \"note\": \"note\"\n },\n {\n \"resource_type\": \"MedicationRequest\",\n \"resource_id\": \"medreq-1\",\n \"omop_table\": \"drug_exposure\",\n \"omop_id\": 1,\n \"source_system\": \"http://www.nlm.nih.gov/research/umls/rxnorm\",\n \"source_code\": \"860975\",\n \"source_name\": \"metformin hydrochloride 500 MG\",\n \"target_vocabulary\": \"RXNORM\",\n \"target_code\": \"860975\",\n \"target_name\": \"metformin hydrochloride 500 MG\",\n \"mapping_status\": \"ALREADY_STANDARD\",\n \"note\": \"note\"\n }\n ],\n \"dropped\": [\n {\n \"resource_type\": \"resource_type\",\n \"resource_id\": \"resource_id\",\n \"reason\": \"reason\"\n }\n ],\n \"vocab_version\": \"v20240229\",\n \"summary\": {\n \"codes_already_standard\": 2,\n \"codes_normalized\": 0,\n \"codes_unmapped\": 0,\n \"off_vocab_rate\": 0\n }\n}", + "body": "{\n \"success\": true,\n \"message\": \"FHIR resources mapped to OMOP CDM v5.4\",\n \"tables\": {\n \"location\": [\n {}\n ],\n \"care_site\": [\n {}\n ],\n \"provider\": [\n {}\n ],\n \"person\": [\n {\n \"person_id\": 1,\n \"gender_concept_id\": 0,\n \"year_of_birth\": 1985,\n \"month_of_birth\": 7,\n \"day_of_birth\": 22,\n \"birth_datetime\": \"1985-07-22\",\n \"race_concept_id\": 0,\n \"ethnicity_concept_id\": 0,\n \"person_source_value\": \"patient-1\",\n \"gender_source_value\": \"female\"\n }\n ],\n \"death\": [\n {}\n ],\n \"observation_period\": [\n {}\n ],\n \"visit_occurrence\": [\n {}\n ],\n \"condition_occurrence\": [\n {\n \"condition_occurrence_id\": 1,\n \"person_id\": 1,\n \"condition_concept_id\": 201826,\n \"condition_start_date\": \"2024-01-15\",\n \"condition_start_datetime\": \"2024-01-15\",\n \"condition_type_concept_id\": 32817,\n \"condition_source_value\": \"http://snomed.info/sct#44054006\",\n \"condition_source_concept_id\": 201826\n }\n ],\n \"drug_exposure\": [\n {\n \"drug_exposure_id\": 1,\n \"person_id\": 1,\n \"drug_concept_id\": 40163924,\n \"drug_exposure_start_date\": \"2024-01-16\",\n \"drug_exposure_start_datetime\": \"2024-01-16\",\n \"drug_type_concept_id\": 32817,\n \"drug_source_value\": \"http://www.nlm.nih.gov/research/umls/rxnorm#860975\",\n \"drug_source_concept_id\": 40163924\n }\n ],\n \"procedure_occurrence\": [\n {}\n ],\n \"measurement\": [\n {}\n ],\n \"observation\": [\n {}\n ]\n },\n \"mappings\": [\n {\n \"resource_type\": \"Condition\",\n \"resource_id\": \"condition-1\",\n \"omop_table\": \"condition_occurrence\",\n \"omop_id\": 1,\n \"source_system\": \"http://snomed.info/sct\",\n \"source_code\": \"44054006\",\n \"source_name\": \"Type 2 diabetes mellitus\",\n \"target_vocabulary\": \"SNOMED\",\n \"target_code\": \"44054006\",\n \"target_name\": \"Type 2 diabetes mellitus\",\n \"mapping_status\": \"ALREADY_STANDARD\",\n \"note\": \"note\"\n },\n {\n \"resource_type\": \"MedicationRequest\",\n \"resource_id\": \"medreq-1\",\n \"omop_table\": \"drug_exposure\",\n \"omop_id\": 1,\n \"source_system\": \"http://www.nlm.nih.gov/research/umls/rxnorm\",\n \"source_code\": \"860975\",\n \"source_name\": \"metformin hydrochloride 500 MG\",\n \"target_vocabulary\": \"RXNORM\",\n \"target_code\": \"860975\",\n \"target_name\": \"metformin hydrochloride 500 MG\",\n \"mapping_status\": \"ALREADY_STANDARD\",\n \"note\": \"note\"\n }\n ],\n \"dropped\": [\n {\n \"resource_type\": \"resource_type\",\n \"resource_id\": \"resource_id\",\n \"reason\": \"reason\"\n }\n ],\n \"vocab_version\": \"v20240229\",\n \"summary\": {\n \"codes_already_standard\": 2,\n \"codes_normalized\": 0,\n \"codes_unmapped\": 0,\n \"off_vocab_rate\": 0\n }\n}", "headers": { "Content-Type": "application/json" } @@ -1352,7 +1352,7 @@ }, "response": { "status": 200, - "body": "{\n \"success\": true,\n \"message\": \"Successfully extracted 3 resources\",\n \"bundle\": {\n \"resourceType\": \"Bundle\",\n \"type\": \"transaction\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:patient-001\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"name\": [\n {\n \"given\": [\n \"John\"\n ],\n \"family\": \"Smith\"\n }\n ],\n \"gender\": \"male\"\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Patient\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:condition-001\",\n \"resource\": {\n \"resourceType\": \"Condition\",\n \"code\": {\n \"text\": \"Type 2 Diabetes\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Condition\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:medication-001\",\n \"resource\": {\n \"resourceType\": \"MedicationRequest\",\n \"medicationCodeableConcept\": {\n \"text\": \"Metformin 500mg\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"MedicationRequest\"\n }\n }\n ]\n },\n \"resources\": [\n {\n \"tempId\": \"urn:uuid:a842c4bc-f6cb-4555-9741-ac3aec4ef0b8\",\n \"resourceType\": \"Patient\",\n \"description\": \"John Smith (DOB 1980-05-12) was diagnosed with Type 2 Diabetes during office visit on 2025-03-01 with Dr. Chen\",\n \"originalText\": \"diagnosed with Type 2 Diabetes\"\n }\n ],\n \"validation\": {\n \"passes\": [\n {}\n ],\n \"fixed\": true,\n \"attempts\": 1,\n \"summary\": \"summary\"\n }\n}", + "body": "{\n \"success\": true,\n \"message\": \"Successfully extracted 3 resources\",\n \"bundle\": {\n \"resourceType\": \"Bundle\",\n \"type\": \"transaction\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:patient-001\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"name\": [\n {\n \"given\": [\n \"John\"\n ],\n \"family\": \"Smith\"\n }\n ],\n \"gender\": \"male\"\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Patient\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:condition-001\",\n \"resource\": {\n \"resourceType\": \"Condition\",\n \"code\": {\n \"text\": \"Type 2 Diabetes\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Condition\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:medication-001\",\n \"resource\": {\n \"resourceType\": \"MedicationRequest\",\n \"medicationCodeableConcept\": {\n \"text\": \"Metformin 500mg\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"MedicationRequest\"\n }\n }\n ]\n },\n \"resources\": [\n {\n \"tempId\": \"urn:uuid:a842c4bc-f6cb-4555-9741-ac3aec4ef0b8\",\n \"resourceType\": \"Patient\",\n \"description\": \"John Smith (DOB 1980-05-12) was diagnosed with Type 2 Diabetes during office visit on 2025-03-01 with Dr. Chen\",\n \"originalText\": \"diagnosed with Type 2 Diabetes\",\n \"sourcePages\": [\n 2\n ]\n }\n ],\n \"validation\": {\n \"passes\": [\n {}\n ],\n \"fixed\": true,\n \"attempts\": 1,\n \"summary\": \"summary\"\n }\n}", "headers": { "Content-Type": "application/json" } @@ -1456,7 +1456,7 @@ }, "response": { "status": 200, - "body": "{\n \"success\": true,\n \"message\": \"Successfully extracted 3 resources\",\n \"bundle\": {\n \"resourceType\": \"Bundle\",\n \"type\": \"transaction\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:patient-001\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"name\": [\n {\n \"given\": [\n \"John\"\n ],\n \"family\": \"Doe\"\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1979-03-15\"\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Patient\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:condition-001\",\n \"resource\": {\n \"resourceType\": \"Condition\",\n \"code\": {\n \"text\": \"Type 2 Diabetes Mellitus\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:patient-001\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Condition\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:medication-001\",\n \"resource\": {\n \"resourceType\": \"MedicationRequest\",\n \"medicationCodeableConcept\": {\n \"text\": \"Metformin 500mg\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:patient-001\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"MedicationRequest\"\n }\n }\n ]\n },\n \"resources\": [\n {\n \"tempId\": \"urn:uuid:patient-001\",\n \"resourceType\": \"Patient\",\n \"description\": \"John Doe, born 1979-03-15\",\n \"originalText\": \"John Doe, DOB 1979-03-15\"\n },\n {\n \"tempId\": \"urn:uuid:condition-001\",\n \"resourceType\": \"Condition\",\n \"description\": \"Type 2 Diabetes Mellitus diagnosis\",\n \"originalText\": \"diagnosed with Type 2 Diabetes\"\n },\n {\n \"tempId\": \"urn:uuid:medication-001\",\n \"resourceType\": \"MedicationRequest\",\n \"description\": \"Metformin 500mg prescription\",\n \"originalText\": \"Prescribed Metformin 500mg\"\n }\n ],\n \"validation\": {\n \"passes\": [\n {}\n ],\n \"fixed\": true,\n \"attempts\": 1,\n \"summary\": \"summary\"\n },\n \"page_classifications\": [\n {\n \"page_number\": 1,\n \"include\": true,\n \"reason\": \"clinical notes with diagnoses\"\n }\n ]\n}", + "body": "{\n \"success\": true,\n \"message\": \"Successfully extracted 3 resources\",\n \"bundle\": {\n \"resourceType\": \"Bundle\",\n \"type\": \"transaction\",\n \"entry\": [\n {\n \"fullUrl\": \"urn:uuid:patient-001\",\n \"resource\": {\n \"resourceType\": \"Patient\",\n \"name\": [\n {\n \"given\": [\n \"John\"\n ],\n \"family\": \"Doe\"\n }\n ],\n \"gender\": \"male\",\n \"birthDate\": \"1979-03-15\"\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Patient\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:condition-001\",\n \"resource\": {\n \"resourceType\": \"Condition\",\n \"code\": {\n \"text\": \"Type 2 Diabetes Mellitus\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:patient-001\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"Condition\"\n }\n },\n {\n \"fullUrl\": \"urn:uuid:medication-001\",\n \"resource\": {\n \"resourceType\": \"MedicationRequest\",\n \"medicationCodeableConcept\": {\n \"text\": \"Metformin 500mg\"\n },\n \"subject\": {\n \"reference\": \"urn:uuid:patient-001\"\n }\n },\n \"request\": {\n \"method\": \"POST\",\n \"url\": \"MedicationRequest\"\n }\n }\n ]\n },\n \"resources\": [\n {\n \"tempId\": \"urn:uuid:patient-001\",\n \"resourceType\": \"Patient\",\n \"description\": \"John Doe, born 1979-03-15\",\n \"originalText\": \"John Doe, DOB 1979-03-15\",\n \"sourcePages\": [\n 1\n ]\n },\n {\n \"tempId\": \"urn:uuid:condition-001\",\n \"resourceType\": \"Condition\",\n \"description\": \"Type 2 Diabetes Mellitus diagnosis\",\n \"originalText\": \"diagnosed with Type 2 Diabetes\",\n \"sourcePages\": [\n 1\n ]\n },\n {\n \"tempId\": \"urn:uuid:medication-001\",\n \"resourceType\": \"MedicationRequest\",\n \"description\": \"Metformin 500mg prescription\",\n \"originalText\": \"Prescribed Metformin 500mg\",\n \"sourcePages\": [\n 2\n ]\n }\n ],\n \"validation\": {\n \"passes\": [\n {}\n ],\n \"fixed\": true,\n \"attempts\": 1,\n \"summary\": \"summary\"\n },\n \"page_classifications\": [\n {\n \"page_number\": 1,\n \"include\": true,\n \"reason\": \"clinical notes with diagnoses\"\n }\n ]\n}", "headers": { "Content-Type": "application/json" } From 3089d97716b254aee552d3288e2fd3e1fb2969d4 Mon Sep 17 00:00:00 2001 From: fern-api <115122769+fern-api[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:53:05 +0000 Subject: [PATCH 2/5] [fern-autoversion] feat: add stream reconnection, new OMOP table types, and SSE safety limits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand the SDK with several new capabilities: configurable SSE stream reconnection support, five new FHIR-to-OMOP row types, additional `provider_id` and `location_id` fields on existing OMOP row models, and a 1 MiB per-line safety guard in the SSE event source. Key changes: - Add `stream_reconnection_enabled` and `max_stream_reconnection_attempts` optional parameters to `PhenomlClient`, `AsyncPhenomlClient`, and `BaseClientWrapper`, enabling automatic SSE stream reconnection - Add `stream_reconnection_enabled` and `max_stream_reconnection_attempts` fields to `RequestOptions` TypedDict for per-request override - Introduce five new OMOP CDM v5.4 row types: `CareSiteRow`, `DeathRow`, `LocationRow`, `ObservationPeriodRow`, and `ProviderRow`, all exported from `phenoml.fhir2omop` - Add optional `provider_id` field to `ConditionOccurrenceRow`, `DrugExposureRow`, `MeasurementRow`, `ObservationRow`, and `ProcedureOccurrenceRow`; add optional `location_id` to `PersonRow` - Add `location`, `care_site`, `provider`, `death`, and `observation_period` table lists to `OmopTables` - Add 1 MiB per-line size guard in `EventSource` to prevent unbounded memory growth on malformed SSE streams - Add `User-Agent` header to all outgoing requests 🌿 Generated with Fern --- .fern/metadata.json | 2 +- .fern/replay.lock | 2 +- changelog.md | 12 ++++++++++++ pyproject.toml | 2 +- src/phenoml/core/client_wrapper.py | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.fern/metadata.json b/.fern/metadata.json index c85d6e8..61e7613 100644 --- a/.fern/metadata.json +++ b/.fern/metadata.json @@ -13,5 +13,5 @@ "invokedBy": "ci", "requestedVersion": "AUTO", "ciProvider": "unknown", - "sdkVersion": "0.0.0.dev0" + "sdkVersion": "16.5.0" } \ No newline at end of file diff --git a/.fern/replay.lock b/.fern/replay.lock index ae7ed39..d6d0218 100644 --- a/.fern/replay.lock +++ b/.fern/replay.lock @@ -79,7 +79,7 @@ patches: [tool.poetry] name = "phenoml" - -version = "0.0.0.dev0" + -version = "16.5.0" +version = "16.4.0" description = "" readme = "README.md" diff --git a/changelog.md b/changelog.md index dfb6bc0..de78a0d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,15 @@ +## [16.5.0] - 2026-07-02 +### Added +- **`stream_reconnection_enabled` and `max_stream_reconnection_attempts`** — new optional parameters on `PhenomlClient`, `AsyncPhenomlClient`, and `RequestOptions` to configure automatic SSE stream reconnection behavior. +- **`CareSiteRow`, `DeathRow`, `LocationRow`, `ObservationPeriodRow`, `ProviderRow`** — five new OMOP CDM v5.4 row types exported from `phenoml.fhir2omop` representing care site, death, location, observation period, and provider records. +- **`OmopTables`** — now includes optional `location`, `care_site`, `provider`, `death`, and `observation_period` table lists alongside existing tables. +- **`VisitOccurrenceRow.provider_id` and `VisitOccurrenceRow.care_site_id`** — new optional fields linking visit occurrences to OMOP provider and care site records. +- **`CreateMultiResponseResourcesItem.source_pages`** — new optional `List[int]` field containing 1-indexed source document page numbers from which a FHIR resource was extracted, populated by the `/lang2fhir/document/multi` endpoint. + +### Changed +- **`ConditionOccurrenceRow`, `DrugExposureRow`, `MeasurementRow`, `ObservationRow`, and `ProcedureOccurrenceRow`** — each gains an optional `provider_id` field linking the clinical event to an OMOP provider record. +- **`PersonRow`** — gains an optional `location_id` field linking the person to an OMOP location record. + ## [16.4.0] - 2026-06-23 ### Added - **`client.voice.voice.transcribe(...)`** — new sync and async method that accepts raw audio bytes (WAV, FLAC, MP3, OGG/WebM Opus) and returns a `TranscribeResponse` with the full transcript, supporting up to ~5 minutes of audio per request. diff --git a/pyproject.toml b/pyproject.toml index cea8b20..0ace34e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] [tool.poetry] name = "phenoml" -version = "0.0.0.dev0" +version = "16.5.0" description = "" readme = "README.md" authors = [] diff --git a/src/phenoml/core/client_wrapper.py b/src/phenoml/core/client_wrapper.py index c03a243..42bcf9b 100644 --- a/src/phenoml/core/client_wrapper.py +++ b/src/phenoml/core/client_wrapper.py @@ -38,7 +38,7 @@ def get_headers(self) -> typing.Dict[str, str]: "X-Fern-Runtime": f"python/{platform.python_version()}", "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}", "X-Fern-SDK-Name": "phenoml", - "X-Fern-SDK-Version": "0.0.0.dev0", + "X-Fern-SDK-Version": "16.5.0", **(self.get_custom_headers() or {}), } token = self._get_token() From c8a326405ea8ae65f70ab6f54ed55cfdde80e33b Mon Sep 17 00:00:00 2001 From: "fern-api[bot]" <115122769+fern-api[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:53:07 +0000 Subject: [PATCH 3/5] [fern-replay] Applied customizations Patches with unresolved conflicts (1): - patch-6516695e: Release 15.0.2: restore bundled openapi.json packaging (#169) Run `fern-replay resolve` to apply these customizations. --- .fern/replay.lock | 11 +++++++++-- pyproject.toml | 3 +++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.fern/replay.lock b/.fern/replay.lock index d6d0218..5e7f8f8 100644 --- a/.fern/replay.lock +++ b/.fern/replay.lock @@ -60,7 +60,13 @@ generations: cli_version: unknown generator_versions: fernapi/fern-python-sdk: 5.14.13 -current_generation: e0932f75557c87fc1914f6c54c5917b59231d3a1 + - commit_sha: 61e0a76bfe4aeaf8a39d28f57260f6bee3aae4b3 + tree_hash: 54049df782e11d0321f949134bbb46dc5b9d1943 + timestamp: 2026-07-02T17:52:38.118Z + cli_version: unknown + generator_versions: + fernapi/fern-python-sdk: 5.15.2 +current_generation: 61e0a76bfe4aeaf8a39d28f57260f6bee3aae4b3 patches: - id: patch-6516695e content_hash: sha256:d2b3264c983a6bb7ce6db1b48d80d28aa93b1f5c838f654cd489a6e8569bee20 @@ -79,7 +85,7 @@ patches: [tool.poetry] name = "phenoml" - -version = "16.5.0" + -version = "0.0.0.dev0" +version = "16.4.0" description = "" readme = "README.md" @@ -197,3 +203,4 @@ patches: [tool.poetry.extras] aiohttp=["aiohttp", "httpx-aiohttp"] + status: unresolved diff --git a/pyproject.toml b/pyproject.toml index 0ace34e..8b65a77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,9 @@ classifiers = [ packages = [ { include = "phenoml", from = "src"} ] +include = [ + { path = "src/phenoml/openapi/openapi.json", format = ["sdist", "wheel"] } +] [tool.poetry.urls] Repository = 'https://github.com/phenoml/phenoml-python-sdk' From e71c271b12d8ea781fe68f7ab725c46909fe4e40 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Jul 2026 17:53:22 +0000 Subject: [PATCH 4/5] chore: sync OpenAPI spec + code-examples for 5ca96bb9b4766f13b667043f4937d5f0cea5f6a7 [skip ci] --- code-examples.json | 19 ++- src/phenoml/openapi/openapi.json | 263 ++++++++++++++++++++++++++++++- 2 files changed, 273 insertions(+), 9 deletions(-) diff --git a/code-examples.json b/code-examples.json index a84c5de..1e921e9 100644 --- a/code-examples.json +++ b/code-examples.json @@ -2,8 +2,8 @@ "metadata": { "language": "python", "packageName": "phenoml", - "sdkVersion": "16.4.0", - "specCommit": "e2c477c4583f8f9e7d0d1a5b5591c1aa8cabef10", + "sdkVersion": "16.5.0", + "specCommit": "5ca96bb9b4766f13b667043f4937d5f0cea5f6a7", "generatorName": "fernapi/fern-python-sdk" }, "renderRules": { @@ -2999,19 +2999,28 @@ "tempId": "urn:uuid:patient-001", "resourceType": "Patient", "description": "John Doe, born 1979-03-15", - "originalText": "John Doe, DOB 1979-03-15" + "originalText": "John Doe, DOB 1979-03-15", + "sourcePages": [ + 1 + ] }, { "tempId": "urn:uuid:condition-001", "resourceType": "Condition", "description": "Type 2 Diabetes Mellitus diagnosis", - "originalText": "diagnosed with Type 2 Diabetes" + "originalText": "diagnosed with Type 2 Diabetes", + "sourcePages": [ + 1 + ] }, { "tempId": "urn:uuid:medication-001", "resourceType": "MedicationRequest", "description": "Metformin 500mg prescription", - "originalText": "Prescribed Metformin 500mg" + "originalText": "Prescribed Metformin 500mg", + "sourcePages": [ + 2 + ] } ] } diff --git a/src/phenoml/openapi/openapi.json b/src/phenoml/openapi/openapi.json index 979abe8..be321b1 100644 --- a/src/phenoml/openapi/openapi.json +++ b/src/phenoml/openapi/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "Phenoml API", - "version": "e2c477c4583f8f9e7d0d1a5b5591c1aa8cabef10" + "version": "5ca96bb9b4766f13b667043f4937d5f0cea5f6a7" }, "x-services": [ { @@ -4994,19 +4994,28 @@ "tempId": "urn:uuid:patient-001", "resourceType": "Patient", "description": "John Doe, born 1979-03-15", - "originalText": "John Doe, DOB 1979-03-15" + "originalText": "John Doe, DOB 1979-03-15", + "sourcePages": [ + 1 + ] }, { "tempId": "urn:uuid:condition-001", "resourceType": "Condition", "description": "Type 2 Diabetes Mellitus diagnosis", - "originalText": "diagnosed with Type 2 Diabetes" + "originalText": "diagnosed with Type 2 Diabetes", + "sourcePages": [ + 1 + ] }, { "tempId": "urn:uuid:medication-001", "resourceType": "MedicationRequest", "description": "Metformin 500mg prescription", - "originalText": "Prescribed Metformin 500mg" + "originalText": "Prescribed Metformin 500mg", + "sourcePages": [ + 2 + ] } ] } @@ -8674,12 +8683,42 @@ "type": "object", "description": "OMOP CDM v5.4 rows grouped by destination table.", "properties": { + "location": { + "type": "array", + "items": { + "$ref": "#/components/schemas/fhir2omop_LocationRow" + } + }, + "care_site": { + "type": "array", + "items": { + "$ref": "#/components/schemas/fhir2omop_CareSiteRow" + } + }, + "provider": { + "type": "array", + "items": { + "$ref": "#/components/schemas/fhir2omop_ProviderRow" + } + }, "person": { "type": "array", "items": { "$ref": "#/components/schemas/fhir2omop_PersonRow" } }, + "death": { + "type": "array", + "items": { + "$ref": "#/components/schemas/fhir2omop_DeathRow" + } + }, + "observation_period": { + "type": "array", + "items": { + "$ref": "#/components/schemas/fhir2omop_ObservationPeriodRow" + } + }, "visit_occurrence": { "type": "array", "items": { @@ -8718,6 +8757,127 @@ } } }, + "fhir2omop_LocationRow": { + "type": "object", + "properties": { + "location_id": { + "type": "integer", + "format": "int64" + }, + "address_1": { + "type": "string" + }, + "address_2": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "zip": { + "type": "string" + }, + "county": { + "type": "string" + }, + "location_source_value": { + "type": "string" + }, + "country_concept_id": { + "type": "integer", + "format": "int64" + }, + "country_source_value": { + "type": "string" + }, + "latitude": { + "type": "number", + "format": "double" + }, + "longitude": { + "type": "number", + "format": "double" + } + } + }, + "fhir2omop_CareSiteRow": { + "type": "object", + "properties": { + "care_site_id": { + "type": "integer", + "format": "int64" + }, + "care_site_name": { + "type": "string" + }, + "place_of_service_concept_id": { + "type": "integer", + "format": "int64" + }, + "location_id": { + "type": "integer", + "format": "int64" + }, + "care_site_source_value": { + "type": "string" + }, + "place_of_service_source_value": { + "type": "string" + } + } + }, + "fhir2omop_ProviderRow": { + "type": "object", + "properties": { + "provider_id": { + "type": "integer", + "format": "int64" + }, + "provider_name": { + "type": "string" + }, + "npi": { + "type": "string" + }, + "dea": { + "type": "string" + }, + "specialty_concept_id": { + "type": "integer", + "format": "int64" + }, + "care_site_id": { + "type": "integer", + "format": "int64" + }, + "year_of_birth": { + "type": "integer" + }, + "gender_concept_id": { + "type": "integer", + "format": "int64" + }, + "provider_source_value": { + "type": "string" + }, + "specialty_source_value": { + "type": "string" + }, + "specialty_source_concept_id": { + "type": "integer", + "format": "int64" + }, + "gender_source_value": { + "type": "string" + }, + "gender_source_concept_id": { + "type": "integer", + "format": "int64" + } + } + }, "fhir2omop_PersonRow": { "type": "object", "properties": { @@ -8749,6 +8909,10 @@ "type": "integer", "format": "int64" }, + "location_id": { + "type": "integer", + "format": "int64" + }, "person_source_value": { "type": "string" }, @@ -8763,6 +8927,59 @@ } } }, + "fhir2omop_DeathRow": { + "type": "object", + "properties": { + "person_id": { + "type": "integer", + "format": "int64" + }, + "death_date": { + "type": "string" + }, + "death_datetime": { + "type": "string" + }, + "death_type_concept_id": { + "type": "integer", + "format": "int64" + }, + "cause_concept_id": { + "type": "integer", + "format": "int64" + }, + "cause_source_value": { + "type": "string" + }, + "cause_source_concept_id": { + "type": "integer", + "format": "int64" + } + } + }, + "fhir2omop_ObservationPeriodRow": { + "type": "object", + "properties": { + "observation_period_id": { + "type": "integer", + "format": "int64" + }, + "person_id": { + "type": "integer", + "format": "int64" + }, + "observation_period_start_date": { + "type": "string" + }, + "observation_period_end_date": { + "type": "string" + }, + "period_type_concept_id": { + "type": "integer", + "format": "int64" + } + } + }, "fhir2omop_VisitOccurrenceRow": { "type": "object", "properties": { @@ -8794,6 +9011,14 @@ "type": "integer", "format": "int64" }, + "provider_id": { + "type": "integer", + "format": "int64" + }, + "care_site_id": { + "type": "integer", + "format": "int64" + }, "visit_source_value": { "type": "string" } @@ -8831,6 +9056,10 @@ "type": "integer", "format": "int64" }, + "provider_id": { + "type": "integer", + "format": "int64" + }, "condition_source_value": { "type": "string" }, @@ -8881,6 +9110,10 @@ "type": "integer", "format": "int64" }, + "provider_id": { + "type": "integer", + "format": "int64" + }, "drug_source_value": { "type": "string" }, @@ -8919,6 +9152,10 @@ "type": "integer", "format": "int64" }, + "provider_id": { + "type": "integer", + "format": "int64" + }, "procedure_source_value": { "type": "string" }, @@ -8982,6 +9219,10 @@ "type": "integer", "format": "int64" }, + "provider_id": { + "type": "integer", + "format": "int64" + }, "measurement_source_value": { "type": "string" }, @@ -9041,6 +9282,10 @@ "type": "integer", "format": "int64" }, + "provider_id": { + "type": "integer", + "format": "int64" + }, "observation_source_value": { "type": "string" }, @@ -10220,6 +10465,16 @@ "type": "string", "description": "Verbatim text excerpt from the original clinical document", "example": "diagnosed with Type 2 Diabetes" + }, + "sourcePages": { + "type": "array", + "items": { + "type": "integer" + }, + "description": "1-indexed source document page number(s) this resource was extracted from. Populated only by the /lang2fhir/document/multi endpoint; omitted when the source page could not be determined (e.g. raw-text create/multi, or a resource with no verbatim source text).\n", + "example": [ + 2 + ] } } } From ae0b06ce38ef3bab027662e56fefdbdb7dd0b115 Mon Sep 17 00:00:00 2001 From: Gavin Sharp Date: Thu, 2 Jul 2026 14:14:19 -0400 Subject: [PATCH 5/5] [fern-replay] Resolved conflicts Patches replayed: - patch-6516695e: Release 15.0.2: restore bundled openapi.json packaging (#169) --- .fern/replay.lock | 14 +++++++------- src/phenoml/core/client_wrapper.py | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.fern/replay.lock b/.fern/replay.lock index 5e7f8f8..64c6043 100644 --- a/.fern/replay.lock +++ b/.fern/replay.lock @@ -69,16 +69,16 @@ generations: current_generation: 61e0a76bfe4aeaf8a39d28f57260f6bee3aae4b3 patches: - id: patch-6516695e - content_hash: sha256:d2b3264c983a6bb7ce6db1b48d80d28aa93b1f5c838f654cd489a6e8569bee20 + content_hash: sha256:9869990131d19d23639e0389f5019537b9ebf0aa5c27eb35cb42879c983506c6 original_commit: 6516695ecaba47ae4bcc8119acca86a1113adeeb original_message: "Release 15.0.2: restore bundled openapi.json packaging (#169)" original_author: Gavin Sharp - base_generation: e0932f75557c87fc1914f6c54c5917b59231d3a1 + base_generation: 61e0a76bfe4aeaf8a39d28f57260f6bee3aae4b3 files: - pyproject.toml patch_content: | diff --git a/pyproject.toml b/pyproject.toml - index cc86d42..6c649b6 100644 + index cea8b20..8b65a77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ dynamic = ["version"] @@ -86,7 +86,7 @@ patches: [tool.poetry] name = "phenoml" -version = "0.0.0.dev0" - +version = "16.4.0" + +version = "16.5.0" description = "" readme = "README.md" authors = [] @@ -108,7 +108,7 @@ patches: [tool.poetry] name = "phenoml" - version = "16.4.0" + version = "16.5.0" description = "" readme = "README.md" authors = [] @@ -144,7 +144,7 @@ patches: [tool.poetry.dependencies] python = "^3.10" - aiohttp = { version = ">=3.14.0,<4", optional = true, python = ">=3.10"} + aiohttp = { version = ">=3.14.1,<4", optional = true, python = ">=3.10"} httpx = ">=0.21.2" httpx-aiohttp = { version = "0.1.8", optional = true, python = ">=3.10"} pydantic = ">= 1.9.2" @@ -166,6 +166,7 @@ patches: [tool.pytest.ini_options] testpaths = [ "tests" ] asyncio_mode = "auto" + norecursedirs = [ "src" ] markers = [ "aiohttp: tests that require httpx_aiohttp to be installed", ] @@ -203,4 +204,3 @@ patches: [tool.poetry.extras] aiohttp=["aiohttp", "httpx-aiohttp"] - status: unresolved diff --git a/src/phenoml/core/client_wrapper.py b/src/phenoml/core/client_wrapper.py index 42bcf9b..e8e78c7 100644 --- a/src/phenoml/core/client_wrapper.py +++ b/src/phenoml/core/client_wrapper.py @@ -33,7 +33,7 @@ def get_headers(self) -> typing.Dict[str, str]: import platform headers: typing.Dict[str, str] = { - "User-Agent": "phenoml/0.0.0-fern-placeholder", + "User-Agent": "phenoml/16.5.0", "X-Fern-Language": "Python", "X-Fern-Runtime": f"python/{platform.python_version()}", "X-Fern-Platform": f"{platform.system().lower()}/{platform.release()}",