from __future__ import annotations
from typing import Optional
from pydantic import Field, field_validator
from imednet.models.json_base import JsonModel
from imednet.models.standards import PROFILE_REGISTRY
[docs]class MappingRule(JsonModel):
"""Mapping from raw source variable to canonical reporting field."""
domain: str = Field(..., alias="domain")
target_field: str = Field(..., alias="targetField")
source_form_key: str = Field(..., alias="sourceFormKey")
source_variable_name: str = Field(..., alias="sourceVariableName")
fallback_value: Optional[str] = Field(None, alias="fallbackValue")
[docs]class StudyConfiguration(JsonModel):
"""Serialized study reporting dashboard configuration."""
version: str = Field("1.0.0", alias="version")
study_key: str = Field(..., alias="studyKey")
reporting_profile: str = Field("general", alias="reportingProfile")
mappings: list[MappingRule] = Field(default_factory=list, alias="mappings")
terminology_lookups: dict[str, dict[str, str]] = Field(
default_factory=dict, alias="terminologyLookups"
)
widgets: list[WidgetConfig] = Field(default_factory=list, alias="widgets")
@field_validator("reporting_profile", mode="before")
@classmethod
def _validate_reporting_profile(cls, value: object) -> object:
if not isinstance(value, str):
raise ValueError("reportingProfile must be a string.")
profile_name = value.strip().lower()
if profile_name in PROFILE_REGISTRY.list_profiles():
return profile_name
available_profiles = ", ".join(PROFILE_REGISTRY.list_profiles())
raise ValueError(f"reportingProfile must be one of: {available_profiles}")