LIBRE / src /application /dto /ppg_dto.py
RyZ
feat: adding full working local ETL Pipeline
e391a84
Raw
History Blame Contribute Delete
3.48 kB
"""
application/dto/ppg_dto.py
───────────────────────────
Pydantic DTOs for PPG ingestion β€” the "shape" of data crossing the
API boundary into the Application layer.
Why separate from domain entity?
β€’ PPGSignal entity enforces domain rules (validate()).
β€’ PPGIngestRequest handles HTTP deserialization & input validation.
β€’ Keeps domain entity clean β€” no Pydantic Field annotations, no JSON aliases.
"""
from __future__ import annotations
from datetime import datetime, timezone
from typing import Optional
from pydantic import BaseModel, Field, field_validator
class PPGIngestRequest(BaseModel):
"""
Incoming payload from the mobile app (POST /api/v1/ppg/ingest).
Sent by the smartphone after acquiring a PPG signal from the IoT sensor.
"""
device_id: str = Field(
...,
min_length=1,
max_length=128,
description="Unique identifier of the IoT sensor/device.",
examples=["sensor-001", "ppg-wristband-A3F2"],
)
user_id: str = Field(
...,
min_length=1,
max_length=128,
description="Unique identifier of the patient/user.",
examples=["user-123", "patient-456"],
)
sampling_rate: float = Field(
...,
gt=0,
le=1000,
description="Signal sampling rate in Hz.",
examples=[125.0, 250.0],
)
ppg_values: list[float] = Field(
...,
min_length=1,
description="Ordered list of raw PPG amplitude values.",
)
duration_seconds: float = Field(
...,
gt=0,
description="Duration of the recording in seconds.",
examples=[10.0, 30.0, 60.0],
)
timestamp: datetime = Field(
default_factory=lambda: datetime.now(timezone.utc),
description="UTC timestamp of capture. Defaults to server time if omitted.",
)
@field_validator("ppg_values")
@classmethod
def validate_ppg_values_not_empty(cls, v: list[float]) -> list[float]:
if len(v) == 0:
raise ValueError("ppg_values must contain at least one sample")
return v
model_config = {
"json_schema_extra": {
"example": {
"device_id": "sensor-001",
"user_id": "user-123",
"sampling_rate": 125.0,
"ppg_values": [0.10, 0.15, 0.40, 0.85, 0.92, 0.75, 0.42, 0.18, 0.12],
"duration_seconds": 10.0,
}
}
}
class PPGIngestResponse(BaseModel):
"""
Response returned after a successful PPG ingestion (ETL #1).
"""
signal_id: str = Field(description="UUID of the stored PPGSignal record.")
user_id: str
device_id: str
num_samples: int = Field(description="Number of PPG samples stored.")
duration_seconds: float
queued: bool = Field(
description="Whether the signal was successfully published to the message queue."
)
message: str = Field(default="PPG signal ingested successfully.")
model_config = {
"json_schema_extra": {
"example": {
"signal_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"user_id": "user-123",
"device_id": "sensor-001",
"num_samples": 1250,
"duration_seconds": 10.0,
"queued": True,
"message": "PPG signal ingested successfully.",
}
}
}