From 8e4220530187acebcb58e9a7a8853b3e84974434 Mon Sep 17 00:00:00 2001 From: BluemediaDev Date: Sun, 25 May 2025 21:04:35 +0000 Subject: [PATCH] Make transaction.meter_end required and force tz utc for all datetimes --- backend/app/models/transaction.py | 2 +- backend/app/schemas/auth_token.py | 4 ++++ backend/app/schemas/chargepoint.py | 4 ++-- backend/app/schemas/meter_value.py | 4 ++-- backend/app/schemas/session.py | 5 ++++- backend/app/schemas/transaction.py | 6 +++--- backend/app/services/transaction_service.py | 10 ++++++++++ backend/app/util/encoders.py | 7 ++++++- 8 files changed, 32 insertions(+), 10 deletions(-) diff --git a/backend/app/models/transaction.py b/backend/app/models/transaction.py index b4dcbce..a9d4452 100644 --- a/backend/app/models/transaction.py +++ b/backend/app/models/transaction.py @@ -12,7 +12,7 @@ class Transaction(Base): started_at = Column(DateTime, index=True) ended_at = Column(DateTime, nullable=True, index=True) meter_start = Column(Numeric(10,2)) - meter_end = Column(Numeric(10,2), nullable=True) + meter_end = Column(Numeric(10,2)) end_reason = Column(Enum(TransactionEventTriggerReason), nullable=True) price = Column(Numeric(10,2)) diff --git a/backend/app/schemas/auth_token.py b/backend/app/schemas/auth_token.py index bb9ee60..9ae5534 100644 --- a/backend/app/schemas/auth_token.py +++ b/backend/app/schemas/auth_token.py @@ -3,6 +3,7 @@ from datetime import datetime from pydantic import BaseModel from app.schemas.user import Role +from app.util.encoders import force_utc_datetime @dataclass @@ -19,3 +20,6 @@ class TokenResponse(BaseModel): access_token: str refresh_token: str not_after: datetime + + class Config: + json_encoders = {datetime: force_utc_datetime} diff --git a/backend/app/schemas/chargepoint.py b/backend/app/schemas/chargepoint.py index a7d4972..878b20a 100644 --- a/backend/app/schemas/chargepoint.py +++ b/backend/app/schemas/chargepoint.py @@ -8,7 +8,7 @@ from app.schemas.connector import Connector from ocpp.v201.enums import ResetEnumType, ResetStatusEnumType -from app.util.encoders import decimal_encoder +from app.util.encoders import decimal_encoder, force_utc_datetime class ChargePointBase(BaseModel): identity: str @@ -34,7 +34,7 @@ class ChargePoint(ChargePointBase): class Config: from_attributes = True - json_encoders = {Decimal: decimal_encoder} + json_encoders = {Decimal: decimal_encoder, datetime: force_utc_datetime} class ChargePointThumb(BaseModel): id: UUID diff --git a/backend/app/schemas/meter_value.py b/backend/app/schemas/meter_value.py index 99f6945..abc08f1 100644 --- a/backend/app/schemas/meter_value.py +++ b/backend/app/schemas/meter_value.py @@ -5,7 +5,7 @@ from typing import Optional from uuid import UUID from pydantic import BaseModel -from app.util.encoders import decimal_encoder +from app.util.encoders import decimal_encoder, force_utc_datetime class PhaseType(enum.Enum): L1 = "L1" @@ -57,4 +57,4 @@ class MeterValue(BaseModel): class Config: from_attributes = True - json_encoders = {Decimal: decimal_encoder} + json_encoders = {Decimal: decimal_encoder, datetime: force_utc_datetime} diff --git a/backend/app/schemas/session.py b/backend/app/schemas/session.py index 07b98a1..d9bbf35 100644 --- a/backend/app/schemas/session.py +++ b/backend/app/schemas/session.py @@ -2,6 +2,8 @@ from datetime import datetime from uuid import UUID from pydantic import BaseModel +from app.util.encoders import force_utc_datetime + class Session(BaseModel): id: UUID @@ -9,4 +11,5 @@ class Session(BaseModel): last_used: datetime class Config: - from_attributes = True \ No newline at end of file + from_attributes = True + json_encoders = {datetime: force_utc_datetime} \ No newline at end of file diff --git a/backend/app/schemas/transaction.py b/backend/app/schemas/transaction.py index 7ad8b8d..ca242ee 100644 --- a/backend/app/schemas/transaction.py +++ b/backend/app/schemas/transaction.py @@ -6,7 +6,7 @@ from pydantic import BaseModel from app.schemas.chargepoint import ChargePointThumb from app.schemas.user import UserThumb -from app.util.encoders import decimal_encoder +from app.util.encoders import decimal_encoder, force_utc_datetime class TransactionStatus(enum.Enum): ONGOING = "ongoing" @@ -45,7 +45,7 @@ class Transaction(BaseModel): started_at: datetime ended_at: Optional[datetime] = None meter_start: Decimal - meter_end: Optional[Decimal] = None + meter_end: Decimal end_reason: Optional[TransactionEventTriggerReason] = None price: Decimal user: UserThumb @@ -53,7 +53,7 @@ class Transaction(BaseModel): class Config: from_attributes = True - json_encoders = {Decimal: decimal_encoder} + json_encoders = {Decimal: decimal_encoder, datetime: force_utc_datetime} class RemoteTransactionStartStopResponse(BaseModel): status: RemoteTransactionStartStopStatus diff --git a/backend/app/services/transaction_service.py b/backend/app/services/transaction_service.py index d8719b4..9d3c992 100644 --- a/backend/app/services/transaction_service.py +++ b/backend/app/services/transaction_service.py @@ -21,19 +21,23 @@ async def create_transaction( with SessionLocal() as db: chargepoint = db.query(ChargePoint).filter(ChargePoint.identity == chargepoint_identity).first() meter_start=0 + meter_end=0 if "meter_value" in transaction_data.keys(): for meter_value_entry in transaction_data['meter_value']: for sampled_value in meter_value_entry['sampled_value']: if "measurand" in sampled_value.keys(): if sampled_value['measurand'] == str(Measurand.ENERGY_ACTIVE_IMPORT_REGISTER): meter_start = sampled_value['value'] + meter_end = sampled_value['value'] else: meter_start = sampled_value['value'] + meter_end = sampled_value['value'] transaction = Transaction( id=transaction_info["transaction_id"], status=TransactionStatus.ONGOING, started_at=timestamp, meter_start=meter_start, + meter_end=meter_end, price=chargepoint.price, chargepoint_id=chargepoint.id, user_id=user_id @@ -55,6 +59,12 @@ async def update_transaction( transaction_id=transaction.id, meter_value_data=meter_value_entry ) + # Update current meter_end value + for sampled_value in meter_value_entry['sampled_value']: + if "measurand" in sampled_value.keys(): + if sampled_value['measurand'] == str(Measurand.ENERGY_ACTIVE_IMPORT_REGISTER): + transaction.meter_end = sampled_value['value'] + db.commit() async def end_transaction( transaction_id: str, diff --git a/backend/app/util/encoders.py b/backend/app/util/encoders.py index fbdec8d..0f0d21f 100644 --- a/backend/app/util/encoders.py +++ b/backend/app/util/encoders.py @@ -1,3 +1,4 @@ +from datetime import datetime, timezone from decimal import Decimal from typing import Union @@ -19,4 +20,8 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]: if isinstance(exponent, int) and exponent >= 0: return int(dec_value) else: - return float(dec_value) \ No newline at end of file + return float(dec_value) + +def force_utc_datetime(datetime_value: datetime) -> datetime: + """Force a datetime to be in the UTC timzone""" + return datetime_value.replace(tzinfo=timezone.utc) \ No newline at end of file