"""Pydantic 스키마 (요청/응답 직렬화)""" from datetime import datetime from typing import Optional from pydantic import BaseModel, Field # ━━━━━━━━ 충전기 ━━━━━━━━ class ChargerCreate(BaseModel): charge_box_id: str = Field(..., example="CHARGER_001") name: str = Field("", example="A동 주차장 1번") location: str = Field("", example="수원시 영통구 아파트 지하1층") connector_count: int = 1 power_kw: float = 7.0 class ChargerOut(BaseModel): id: int charge_box_id: str name: str location: str connector_count: int power_kw: float status: str last_heartbeat: Optional[datetime] = None is_active: bool class Config: from_attributes = True class ChargerStatusUpdate(BaseModel): status: str timestamp: Optional[datetime] = None # ━━━━━━━━ 충전 세션 ━━━━━━━━ class SessionCreate(BaseModel): """QR 스캔 → 세션 생성 요청""" charge_box_id: str = Field(..., example="CHARGER_001") connector_id: int = 1 class SessionOut(BaseModel): id: int session_uid: str charger_id: int id_tag: Optional[str] = None status: str meter_start: Optional[int] = None meter_stop: Optional[int] = None charged_wh: int = 0 total_bill: int = 0 started_at: Optional[datetime] = None stopped_at: Optional[datetime] = None created_at: datetime class Config: from_attributes = True class SessionBilling(BaseModel): """정산 결과""" session_uid: str charged_kwh: float electricity_cost: int service_fee: int total_bill: int saved_vs_cpo: int # ━━━━━━━━ 결제 ━━━━━━━━ class PaymentRequest(BaseModel): """결제 시작 요청 (모바일 웹에서 호출)""" session_uid: str amount: int = Field(..., ge=100, example=10000, description="선결제 금액 (원)") class PaymentConfirm(BaseModel): """토스페이먼츠 결제 승인 요청""" payment_key: str order_id: str amount: int class PaymentOut(BaseModel): order_id: str payment_key: Optional[str] = None amount: int actual_amount: Optional[int] = None status: str method: Optional[str] = None confirmed_at: Optional[datetime] = None class Config: from_attributes = True # ━━━━━━━━ Steve OCPP 콜백 ━━━━━━━━ class OcppBootNotification(BaseModel): charge_box_id: str charge_point_vendor: Optional[str] = None charge_point_model: Optional[str] = None firmware_version: Optional[str] = None class OcppStatusNotification(BaseModel): charge_box_id: str connector_id: int = 1 status: str # Available, Charging, Faulted ... error_code: str = "NoError" timestamp: Optional[datetime] = None class OcppStartTransaction(BaseModel): charge_box_id: str connector_id: int = 1 id_tag: str meter_start: int # Wh transaction_id: Optional[int] = None timestamp: Optional[datetime] = None class OcppStopTransaction(BaseModel): charge_box_id: str transaction_id: int id_tag: Optional[str] = None meter_stop: int # Wh reason: str = "Local" timestamp: Optional[datetime] = None class OcppMeterValues(BaseModel): charge_box_id: str connector_id: int = 1 transaction_id: Optional[int] = None value: float # Wh measurand: str = "Energy.Active.Import.Register" timestamp: Optional[datetime] = None # ━━━━━━━━ 대시보드 ━━━━━━━━ class DashboardSummary(BaseModel): total_chargers: int active_chargers: int charging_now: int today_sessions: int today_revenue: int today_kwh: float month_revenue: int month_kwh: float