Source code for imednet_workflows.study_structure
"""Study structure module."""
from typing import Any
"""Workflow for retrieving and aggregating study structural metadata."""
import asyncio
from typing import TYPE_CHECKING, Any
# Import potential exceptions
from imednet.spi.errors import ImednetError
# Import the models we need
from imednet.spi.models import (
Form,
FormStructure,
Interval,
IntervalStructure,
StudyStructure,
Variable,
)
# Use TYPE_CHECKING to avoid circular import at runtime
if TYPE_CHECKING:
from imednet.spi.facade import AsyncImednetFacade, ImednetFacade
def _build_study_structure(
study_key: str,
intervals: list[Interval],
forms: list[Form],
variables: list[Variable],
) -> StudyStructure:
"""Helper to assemble the StudyStructure from its component parts."""
forms_by_id: dict[int, Form] = {f.form_id: f for f in forms if f.form_id is not None}
variables_by_form_id: dict[int, list[Variable]] = {}
for var in variables:
if var.form_id is not None:
variables_by_form_id.setdefault(var.form_id, []).append(var)
interval_structures: list[IntervalStructure] = []
for interval in intervals:
form_structures: list[FormStructure] = []
for form_summary in getattr(interval, 'forms', []):
if form_summary.form_id is not None:
full_form = forms_by_id.get(form_summary.form_id)
if full_form and full_form.form_id is not None:
form_vars = variables_by_form_id.get(full_form.form_id, [])
form_structures.append(FormStructure.from_form(full_form, form_vars))
interval_structures.append(IntervalStructure.from_interval(interval, form_structures))
return StudyStructure(studyKey=study_key, intervals=interval_structures)
[docs]def get_study_structure(sdk: "ImednetFacade", study_key: str) -> StudyStructure:
"""Fetches and aggregates study structure information (intervals, forms, variables).
Args:
sdk: An initialized ImednetSDK instance.
study_key: The key of the study to fetch structure for.
Returns:
A StudyStructure object containing nested intervals, forms, and variables.
Raises:
ImednetError: If fetching any part of the structure fails.
"""
try:
# Fetch all components concurrently (if async were used) or sequentially
intervals: list[Interval] = list(sdk.get_intervals(study_key))
forms: list[Form] = list(sdk.get_forms(study_key))
variables: list[Variable] = list(sdk.get_variables(study_key))
return _build_study_structure(study_key, intervals, forms, variables)
except Exception as e:
# Catch potential API errors or processing errors
raise ImednetError(
f"Failed to retrieve or process study structure for {study_key}: {e}"
) from e
[docs]async def async_get_study_structure(sdk: "AsyncImednetFacade", study_key: str) -> StudyStructure:
"""Asynchronous variant of :func:`get_study_structure`."""
try:
async def fetch_intervals() -> list[Any]:
"""Asynchronously fetch visit intervals."""
return await sdk.async_get_intervals(study_key)
async def fetch_forms() -> list[Any]:
"""Asynchronously fetch form definitions."""
return await sdk.async_get_forms(study_key)
async def fetch_variables() -> list[Any]:
"""Asynchronously fetch variable definitions."""
return await sdk.async_get_variables(study_key)
intervals, forms, variables = await asyncio.gather(
fetch_intervals(),
fetch_forms(),
fetch_variables(),
)
return _build_study_structure(study_key, intervals, forms, variables)
except Exception as e: # pragma: no cover - unexpected
raise ImednetError(
f"Failed to retrieve or process study structure for {study_key}: {e}"
) from e