EV 충전 플랫폼 초기 백업

This commit is contained in:
root
2026-04-18 05:59:31 +09:00
commit 4558ac10c0
40 changed files with 6246 additions and 0 deletions

180
app/models/__init__.py Normal file
View 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}]>"