EV 충전 플랫폼 초기 백업
This commit is contained in:
180
app/models/__init__.py
Normal file
180
app/models/__init__.py
Normal file
@@ -0,0 +1,180 @@
|
||||
"""DB 모델 정의"""
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum as PyEnum
|
||||
|
||||
from sqlalchemy import (
|
||||
Column, Integer, BigInteger, String, Float, DateTime,
|
||||
Enum, ForeignKey, Text, Boolean, Index,
|
||||
)
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
# ── 상태 Enum ──
|
||||
|
||||
class ChargerStatus(str, PyEnum):
|
||||
AVAILABLE = "Available"
|
||||
CHARGING = "Charging"
|
||||
FAULTED = "Faulted"
|
||||
UNAVAILABLE = "Unavailable"
|
||||
RESERVED = "Reserved"
|
||||
|
||||
|
||||
class SessionStatus(str, PyEnum):
|
||||
PENDING = "pending" # 결제 대기
|
||||
AUTHORIZED = "authorized" # 결제 승인, 충전 시작 대기
|
||||
CHARGING = "charging" # 충전 중
|
||||
COMPLETED = "completed" # 정상 종료
|
||||
FAILED = "failed" # 실패
|
||||
CANCELLED = "cancelled" # 취소
|
||||
|
||||
|
||||
class PaymentStatus(str, PyEnum):
|
||||
PENDING = "pending"
|
||||
CONFIRMED = "confirmed"
|
||||
FAILED = "failed"
|
||||
REFUNDED = "refunded"
|
||||
PARTIAL_REFUND = "partial_refund"
|
||||
|
||||
|
||||
# ── 충전기 ──
|
||||
|
||||
class Charger(Base):
|
||||
__tablename__ = "chargers"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
charge_box_id = Column(String(64), unique=True, nullable=False, index=True,
|
||||
comment="OCPP chargeBoxId (Steve 등록명)")
|
||||
name = Column(String(128), comment="표시 이름 (예: A동 주차장 1번)")
|
||||
location = Column(String(256), comment="설치 위치")
|
||||
connector_count = Column(Integer, default=1)
|
||||
power_kw = Column(Float, default=7.0, comment="충전 용량 kW")
|
||||
status = Column(Enum(ChargerStatus), default=ChargerStatus.UNAVAILABLE)
|
||||
last_heartbeat = Column(DateTime(timezone=True))
|
||||
is_active = Column(Boolean, default=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
sessions = relationship("ChargingSession", back_populates="charger")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Charger {self.charge_box_id} [{self.status}]>"
|
||||
|
||||
|
||||
# ── 충전 세션 ──
|
||||
|
||||
class ChargingSession(Base):
|
||||
__tablename__ = "charging_sessions"
|
||||
__table_args__ = (
|
||||
Index("ix_session_charger_status", "charger_id", "status"),
|
||||
Index("ix_session_created", "created_at"),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
session_uid = Column(String(64), unique=True, nullable=False, index=True,
|
||||
comment="고유 세션 ID (UUID)")
|
||||
charger_id = Column(Integer, ForeignKey("chargers.id"), nullable=False)
|
||||
connector_id = Column(Integer, default=1)
|
||||
|
||||
# OCPP 트랜잭션 정보
|
||||
ocpp_transaction_id = Column(Integer, comment="Steve에서 발급한 transactionId")
|
||||
id_tag = Column(String(64), comment="OCPP 인증 태그")
|
||||
|
||||
# 전력량 (Wh 단위)
|
||||
meter_start = Column(BigInteger, comment="시작 전력량 Wh")
|
||||
meter_stop = Column(BigInteger, comment="종료 전력량 Wh")
|
||||
charged_wh = Column(BigInteger, default=0, comment="충전된 전력량 Wh")
|
||||
last_meter_value = Column(BigInteger, comment="마지막 MeterValues Wh")
|
||||
|
||||
# 요금
|
||||
electricity_cost = Column(Integer, default=0, comment="전기 원가 (원)")
|
||||
service_fee = Column(Integer, default=0, comment="서비스 수수료 (원)")
|
||||
total_bill = Column(Integer, default=0, comment="총 요금 (원)")
|
||||
|
||||
# 시간
|
||||
status = Column(Enum(SessionStatus), default=SessionStatus.PENDING)
|
||||
started_at = Column(DateTime(timezone=True))
|
||||
stopped_at = Column(DateTime(timezone=True))
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
charger = relationship("Charger", back_populates="sessions")
|
||||
payment = relationship("Payment", back_populates="session", uselist=False)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Session {self.session_uid} [{self.status}]>"
|
||||
|
||||
|
||||
# ── 결제 ──
|
||||
|
||||
class Payment(Base):
|
||||
__tablename__ = "payments"
|
||||
__table_args__ = (
|
||||
Index("ix_payment_order", "order_id"),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
session_id = Column(Integer, ForeignKey("charging_sessions.id"), nullable=False)
|
||||
|
||||
# 토스페이먼츠
|
||||
order_id = Column(String(128), unique=True, nullable=False,
|
||||
comment="토스 주문 ID")
|
||||
payment_key = Column(String(256), comment="토스 paymentKey")
|
||||
amount = Column(Integer, nullable=False, comment="결제 요청 금액 (원)")
|
||||
actual_amount = Column(Integer, comment="실제 충전 후 정산 금액")
|
||||
|
||||
status = Column(Enum(PaymentStatus), default=PaymentStatus.PENDING)
|
||||
method = Column(String(32), comment="결제 수단 (카드, 간편결제 등)")
|
||||
card_company = Column(String(32), comment="카드사 이름")
|
||||
|
||||
confirmed_at = Column(DateTime(timezone=True))
|
||||
refunded_at = Column(DateTime(timezone=True))
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
session = relationship("ChargingSession", back_populates="payment")
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Payment {self.order_id} [{self.status}]>"
|
||||
|
||||
|
||||
# ── 충전기 MeterValue 로그 ──
|
||||
|
||||
class MeterValueLog(Base):
|
||||
"""실시간 MeterValues 로그 (디버깅/분석용)"""
|
||||
__tablename__ = "meter_value_logs"
|
||||
|
||||
id = Column(BigInteger, primary_key=True, autoincrement=True)
|
||||
charger_id = Column(Integer, ForeignKey("chargers.id"), nullable=False)
|
||||
transaction_id = Column(Integer)
|
||||
connector_id = Column(Integer, default=1)
|
||||
measurand = Column(String(64), default="Energy.Active.Import.Register")
|
||||
value = Column(Float, nullable=False, comment="측정값")
|
||||
unit = Column(String(16), default="Wh")
|
||||
timestamp = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
|
||||
# ── 사용자 ──
|
||||
|
||||
class UserRole(str, PyEnum):
|
||||
ADMIN = "admin"
|
||||
OPERATOR = "operator"
|
||||
VIEWER = "viewer"
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
username = Column(String(64), unique=True, nullable=False, index=True)
|
||||
hashed_password = Column(String(256), nullable=False)
|
||||
display_name = Column(String(128), comment="표시 이름")
|
||||
role = Column(Enum(UserRole), default=UserRole.VIEWER)
|
||||
is_active = Column(Boolean, default=True)
|
||||
last_login = Column(DateTime(timezone=True))
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
def __repr__(self):
|
||||
return f"<User {self.username} [{self.role}]>"
|
||||
Reference in New Issue
Block a user