339 lines
13 KiB
Python
339 lines
13 KiB
Python
"""
|
|
Pydantic models for Demand Response Service
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import List, Dict, Optional, Literal
|
|
from pydantic import BaseModel, Field
|
|
from enum import Enum
|
|
|
|
|
|
# Enums
|
|
class InvitationStatus(str, Enum):
|
|
"""Invitation status states"""
|
|
PENDING = "pending"
|
|
SCHEDULED = "scheduled"
|
|
ACTIVE = "active"
|
|
COMPLETED = "completed"
|
|
CANCELLED = "cancelled"
|
|
|
|
|
|
class ResponseType(str, Enum):
|
|
"""Device response types"""
|
|
WAITING = "WAITING"
|
|
YES = "YES"
|
|
NO = "NO"
|
|
|
|
|
|
class EventStatus(str, Enum):
|
|
"""DR event status states"""
|
|
SCHEDULED = "scheduled"
|
|
ACTIVE = "active"
|
|
COMPLETED = "completed"
|
|
CANCELLED = "cancelled"
|
|
|
|
|
|
class InstructionType(str, Enum):
|
|
"""Device participation instruction types"""
|
|
PARTICIPATION = "participation" # Full DR participation (100%)
|
|
SHIFTING = "shifting" # Partial participation (0-20%)
|
|
OFF = "off" # No DR participation
|
|
|
|
|
|
# Invitation Models
|
|
class EventRequest(BaseModel):
|
|
"""Request model for creating a DR event (alias for DRInvitationCreate)"""
|
|
event_time: datetime = Field(..., description="When the DR event should occur")
|
|
load_kwh: float = Field(..., description="Target load reduction in kWh", gt=0)
|
|
load_percentage: float = Field(..., description="Target reduction as percentage of total load", ge=0, le=100)
|
|
iots: List[str] = Field(..., description="List of device IDs to participate", min_items=1)
|
|
duration_minutes: int = Field(59, description="Event duration in minutes", gt=0, le=120)
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"event_time": "2025-12-10T14:00:00",
|
|
"load_kwh": 5.0,
|
|
"load_percentage": 15.0,
|
|
"iots": ["sensor_1", "sensor_2"],
|
|
"duration_minutes": 59
|
|
}
|
|
}
|
|
|
|
|
|
class DRInvitationCreate(BaseModel):
|
|
"""Request model for creating a DR invitation"""
|
|
event_time: datetime = Field(..., description="When the DR event should occur")
|
|
load_kwh: float = Field(..., description="Target load reduction in kWh", gt=0)
|
|
load_percentage: float = Field(..., description="Target reduction as percentage of total load", ge=0, le=100)
|
|
iots: List[str] = Field(..., description="List of device IDs to participate", min_items=1)
|
|
duration_minutes: int = Field(59, description="Event duration in minutes", gt=0, le=120)
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"event_time": "2025-12-10T14:00:00",
|
|
"load_kwh": 5.0,
|
|
"load_percentage": 15.0,
|
|
"iots": ["sensor_1", "sensor_2"],
|
|
"duration_minutes": 59
|
|
}
|
|
}
|
|
|
|
|
|
class DRInvitationResponse(BaseModel):
|
|
"""Response model for device answering invitation"""
|
|
event_id: str = Field(..., description="Event identifier")
|
|
iot_id: str = Field(..., description="Device identifier")
|
|
response: ResponseType = Field(..., description="Device response (YES/NO)")
|
|
committed_reduction_kw: Optional[float] = Field(None, description="Committed power reduction in kW", ge=0)
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"event_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"iot_id": "sensor_1",
|
|
"response": "YES",
|
|
"committed_reduction_kw": 2.5
|
|
}
|
|
}
|
|
|
|
|
|
class DRInvitation(BaseModel):
|
|
"""Full DR invitation model"""
|
|
event_id: str = Field(..., description="Unique event identifier")
|
|
created_at: datetime = Field(..., description="Invitation creation time")
|
|
event_time: datetime = Field(..., description="Scheduled event start time")
|
|
load_kwh: float = Field(..., description="Target load reduction in kWh")
|
|
load_percentage: float = Field(..., description="Target reduction percentage")
|
|
iots: List[str] = Field(..., description="Participating device IDs")
|
|
duration_minutes: int = Field(..., description="Event duration in minutes")
|
|
response: str = Field(..., description="Overall response status")
|
|
status: str = Field(..., description="Invitation status")
|
|
|
|
class Config:
|
|
json_encoders = {
|
|
datetime: lambda v: v.isoformat() if v else None
|
|
}
|
|
json_schema_extra = {
|
|
"example": {
|
|
"event_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"created_at": "2025-12-10T13:45:00",
|
|
"event_time": "2025-12-10T14:00:00",
|
|
"load_kwh": 5.0,
|
|
"load_percentage": 15.0,
|
|
"iots": ["sensor_1", "sensor_2"],
|
|
"duration_minutes": 59,
|
|
"response": "WAITING",
|
|
"status": "pending"
|
|
}
|
|
}
|
|
|
|
|
|
# Event Models
|
|
class EventScheduleRequest(BaseModel):
|
|
"""Request model for scheduling a DR event"""
|
|
event_time: datetime = Field(..., description="Event start time")
|
|
iots: List[str] = Field(..., description="Participating device IDs", min_items=1)
|
|
load_reduction_kw: float = Field(..., description="Target reduction in kW", gt=0)
|
|
duration_minutes: int = Field(59, description="Event duration in minutes", gt=0, le=120)
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"event_time": "2025-12-10T14:00:00",
|
|
"iots": ["sensor_1", "sensor_2"],
|
|
"load_reduction_kw": 5.0,
|
|
"duration_minutes": 59
|
|
}
|
|
}
|
|
|
|
|
|
class PowerSample(BaseModel):
|
|
"""Individual power sample during event"""
|
|
timestamp: datetime = Field(..., description="Sample timestamp")
|
|
device_powers: Dict[str, float] = Field(..., description="Device power readings (device_id -> kW)")
|
|
interval_reduction_kwh: Optional[float] = Field(None, description="Reduction for this interval")
|
|
|
|
|
|
class DREvent(BaseModel):
|
|
"""DR event execution model"""
|
|
event_id: str = Field(..., description="Unique event identifier")
|
|
invitation_id: Optional[str] = Field(None, description="Source invitation ID if applicable")
|
|
start_time: datetime = Field(..., description="Event start time")
|
|
end_time: datetime = Field(..., description="Event end time")
|
|
status: EventStatus = Field(..., description="Event status")
|
|
participating_devices: List[str] = Field(..., description="Device IDs participating")
|
|
target_reduction_kw: float = Field(..., description="Target power reduction in kW")
|
|
actual_reduction_kw: float = Field(0.0, description="Actual achieved reduction in kWh")
|
|
power_samples: List[Dict] = Field(default_factory=list, description="Power samples during event")
|
|
|
|
class Config:
|
|
json_encoders = {
|
|
datetime: lambda v: v.isoformat() if v else None
|
|
}
|
|
|
|
|
|
class ActiveEventResponse(BaseModel):
|
|
"""Response model for active event with real-time data"""
|
|
event_id: str = Field(..., description="Event identifier")
|
|
status: EventStatus = Field(..., description="Current status")
|
|
start_time: datetime = Field(..., description="Event start time")
|
|
end_time: datetime = Field(..., description="Event end time")
|
|
participating_devices: List[str] = Field(..., description="Participating devices")
|
|
target_reduction_kw: float = Field(..., description="Target reduction")
|
|
actual_reduction_kw: float = Field(..., description="Current achieved reduction")
|
|
current_device_powers: Optional[Dict[str, float]] = Field(None, description="Current device power readings")
|
|
progress_percentage: Optional[float] = Field(None, description="Event progress (0-100%)")
|
|
|
|
class Config:
|
|
json_encoders = {
|
|
datetime: lambda v: v.isoformat() if v else None
|
|
}
|
|
|
|
|
|
class LoadReductionRequest(BaseModel):
|
|
"""Request model for executing load reduction"""
|
|
event_time: datetime = Field(..., description="Event start time")
|
|
iot: str = Field(..., description="Device ID")
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"event_time": "2025-12-10T14:00:00",
|
|
"iot": "sensor_1"
|
|
}
|
|
}
|
|
|
|
|
|
# Flexibility Models
|
|
class DeviceFlexibility(BaseModel):
|
|
"""Per-device flexibility information"""
|
|
device_id: str = Field(..., description="Device identifier")
|
|
available_kw: float = Field(..., description="Available flexibility in kW", ge=0)
|
|
instruction: str = Field(..., description="Current DR instruction")
|
|
current_power: float = Field(..., description="Current power consumption in kW", ge=0)
|
|
|
|
|
|
class FlexibilityResponse(BaseModel):
|
|
"""Response model for current flexibility"""
|
|
timestamp: datetime = Field(..., description="Calculation timestamp")
|
|
total_flexibility_kw: float = Field(..., description="Total available flexibility in kW", ge=0)
|
|
devices: List[DeviceFlexibility] = Field(..., description="Per-device breakdown")
|
|
|
|
class Config:
|
|
json_encoders = {
|
|
datetime: lambda v: v.isoformat() if v else None
|
|
}
|
|
json_schema_extra = {
|
|
"example": {
|
|
"timestamp": "2025-12-10T13:45:00",
|
|
"total_flexibility_kw": 15.5,
|
|
"devices": [
|
|
{
|
|
"device_id": "sensor_1",
|
|
"available_kw": 3.5,
|
|
"instruction": "participation",
|
|
"current_power": 3.5
|
|
},
|
|
{
|
|
"device_id": "sensor_2",
|
|
"available_kw": 0.8,
|
|
"instruction": "shifting",
|
|
"current_power": 4.0
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
|
|
class DeviceInstructionUpdate(BaseModel):
|
|
"""Model for updating device instructions"""
|
|
device_id: str = Field(..., description="Device identifier")
|
|
instructions: Dict[str, str] = Field(..., description="Hourly instructions (hour -> instruction type)")
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"device_id": "sensor_1",
|
|
"instructions": {
|
|
"0": "participation",
|
|
"1": "shifting",
|
|
"2": "off",
|
|
"3": "participation"
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
# Configuration Models
|
|
class AutoResponseConfig(BaseModel):
|
|
"""Auto-response configuration model"""
|
|
enabled: bool = Field(..., description="Whether auto-response is enabled")
|
|
max_reduction_percentage: float = Field(20.0, description="Maximum reduction percentage for auto-accept", ge=0, le=100)
|
|
response_delay_seconds: int = Field(300, description="Delay before auto-responding (seconds)", ge=0)
|
|
min_notice_minutes: int = Field(60, description="Minimum notice required for auto-accept (minutes)", ge=0)
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"enabled": True,
|
|
"max_reduction_percentage": 20.0,
|
|
"response_delay_seconds": 300,
|
|
"min_notice_minutes": 60
|
|
}
|
|
}
|
|
|
|
|
|
# Response Models
|
|
class InvitationSendResponse(BaseModel):
|
|
"""Response for sending invitation"""
|
|
event_id: str = Field(..., description="Created event identifier")
|
|
response: str = Field(..., description="Initial response status")
|
|
message: str = Field(..., description="Status message")
|
|
|
|
|
|
class InvitationAnswerResponse(BaseModel):
|
|
"""Response for answering invitation"""
|
|
success: bool = Field(..., description="Whether answer was recorded")
|
|
message: str = Field(..., description="Status message")
|
|
|
|
|
|
class EventScheduleResponse(BaseModel):
|
|
"""Response for scheduling event"""
|
|
event_id: str = Field(..., description="Scheduled event identifier")
|
|
message: str = Field(..., description="Status message")
|
|
|
|
|
|
class PerformanceAnalytics(BaseModel):
|
|
"""Performance analytics response"""
|
|
period_days: int = Field(..., description="Analysis period in days")
|
|
total_events: int = Field(..., description="Total number of events")
|
|
total_reduction_kwh: float = Field(..., description="Total energy reduced")
|
|
total_target_kwh: float = Field(..., description="Total target reduction")
|
|
average_reduction_kwh: float = Field(..., description="Average reduction per event")
|
|
achievement_rate: float = Field(..., description="Achievement rate (%)")
|
|
average_event_duration_minutes: int = Field(..., description="Average event duration")
|
|
|
|
|
|
# Health Check Model
|
|
class HealthResponse(BaseModel):
|
|
"""Health check response model"""
|
|
service: str = Field(..., description="Service name")
|
|
status: str = Field(..., description="Service status")
|
|
timestamp: datetime = Field(..., description="Check timestamp")
|
|
version: str = Field(..., description="Service version")
|
|
|
|
class Config:
|
|
json_encoders = {
|
|
datetime: lambda v: v.isoformat() if v else None
|
|
}
|
|
json_schema_extra = {
|
|
"example": {
|
|
"service": "demand-response-service",
|
|
"status": "healthy",
|
|
"timestamp": "2025-12-10T13:45:00",
|
|
"version": "1.0.0"
|
|
}
|
|
}
|