Source code for imednet_workflows.uat.models
"""Pydantic models describing a complete UAT specification."""
from __future__ import annotations
import json
from datetime import date, datetime, timezone
from enum import Enum
from pathlib import Path
from typing import Any
from pydantic import ConfigDict, Field, field_validator, model_validator
from pydantic.alias_generators import to_camel
from imednet.spi.models import ImednetBaseModel
class UATBaseModel(ImednetBaseModel):
"""UAT base model with camelCase alias generation."""
model_config = ConfigDict(
alias_generator=to_camel,
)
[docs]class VariableTestStrategy(str, Enum):
"""How a variable's test value should be determined."""
SYNTHETIC = "synthetic"
FIXED = "fixed"
SKIP = "skip"
BOUNDARY = "boundary"
CODED_ALL = "coded_all"
[docs]class RecordTestType(str, Enum):
"""What kind of record submission is being tested."""
REGISTER_SUBJECT = "register_subject"
UPDATE_SCHEDULED_RECORD = "update_scheduled_record"
CREATE_NEW_RECORD = "create_new_record"
[docs]class UATVariableSpec(UATBaseModel):
"""Variable-level test specification."""
variable_name: str
variable_key: str
variable_type: str
form_key: str
strategy: VariableTestStrategy = VariableTestStrategy.SYNTHETIC
fixed_value: Any | None = None
min_value: float | None = None
max_value: float | None = None
max_length: int | None = None
coded_values: list[str] = Field(default_factory=list)
required: bool = False
notes: str | None = None
[docs] @model_validator(mode="before")
@classmethod
def validate_fixed_strategy(cls, data: Any) -> Any:
"""Require a fixed value when FIXED strategy is selected."""
if not isinstance(data, dict):
return data
strategy = data.get("strategy", VariableTestStrategy.SYNTHETIC)
fixed_value = data.get("fixed_value", data.get("fixedValue"))
if strategy in (VariableTestStrategy.FIXED, VariableTestStrategy.FIXED.value) and (
fixed_value is None
):
raise ValueError("fixed_value must be provided when strategy is 'fixed'.")
return data
[docs]class UATSubjectSpec(UATBaseModel):
"""Defines synthetic test subjects to register before form submission."""
site_name: str
subject_count: int = 1
subject_key_prefix: str = "UAT-TEST-"
keyword_tag: str = "UAT"
[docs] @field_validator("subject_count")
@classmethod
def validate_subject_count(cls, value: int) -> int:
"""Enforce safe subject count bounds."""
if not 1 <= value <= 100:
raise ValueError("subject_count must be between 1 and 100.")
return value
[docs]class UATSpecification(UATBaseModel):
"""Root model for a complete UAT test plan."""
spec_version: str = "1.0"
study_key: str
study_name: str
generated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
generated_by: str = "imednet-sdk-uat"
subject_specs: list[UATSubjectSpec] = Field(default_factory=list)
form_specs: list[UATFormSpec] = Field(default_factory=list)
global_date_value: date | None = None
global_text_length: int = 10
forms_snapshot_count: int = 0
variables_snapshot_count: int = 0
intervals_snapshot_count: int = 0
sites_snapshot_count: int = 0
[docs] @field_validator("spec_version")
@classmethod
def validate_spec_version(cls, value: str) -> str:
"""Pin this release to v1.0 schema."""
if value != "1.0":
raise ValueError("spec_version must be '1.0'.")
return value
[docs] @classmethod
def from_json(cls, payload: str | bytes | Path) -> UATSpecification:
"""Load a UAT specification from JSON text, bytes, or file path."""
if isinstance(payload, Path):
content = payload.read_text(encoding="utf-8")
elif isinstance(payload, bytes):
content = payload.decode("utf-8")
else:
content = payload
data = json.loads(content)
return cls.model_validate(data)
__all__ = [
"RecordTestType",
"UATFormSpec",
"UATSpecification",
"UATSubjectSpec",
"UATVariableSpec",
"VariableTestStrategy",
]