Make transaction.meter_end required and force tz utc for all datetimes

This commit is contained in:
Oliver Traber 2025-05-25 21:04:35 +00:00
parent 4272f2878e
commit 8e42205301
Signed by: Bluemedia
GPG key ID: C0674B105057136C
8 changed files with 32 additions and 10 deletions

View file

@ -12,7 +12,7 @@ class Transaction(Base):
started_at = Column(DateTime, index=True) started_at = Column(DateTime, index=True)
ended_at = Column(DateTime, nullable=True, index=True) ended_at = Column(DateTime, nullable=True, index=True)
meter_start = Column(Numeric(10,2)) 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) end_reason = Column(Enum(TransactionEventTriggerReason), nullable=True)
price = Column(Numeric(10,2)) price = Column(Numeric(10,2))

View file

@ -3,6 +3,7 @@ from datetime import datetime
from pydantic import BaseModel from pydantic import BaseModel
from app.schemas.user import Role from app.schemas.user import Role
from app.util.encoders import force_utc_datetime
@dataclass @dataclass
@ -19,3 +20,6 @@ class TokenResponse(BaseModel):
access_token: str access_token: str
refresh_token: str refresh_token: str
not_after: datetime not_after: datetime
class Config:
json_encoders = {datetime: force_utc_datetime}

View file

@ -8,7 +8,7 @@ from app.schemas.connector import Connector
from ocpp.v201.enums import ResetEnumType, ResetStatusEnumType 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): class ChargePointBase(BaseModel):
identity: str identity: str
@ -34,7 +34,7 @@ class ChargePoint(ChargePointBase):
class Config: class Config:
from_attributes = True from_attributes = True
json_encoders = {Decimal: decimal_encoder} json_encoders = {Decimal: decimal_encoder, datetime: force_utc_datetime}
class ChargePointThumb(BaseModel): class ChargePointThumb(BaseModel):
id: UUID id: UUID

View file

@ -5,7 +5,7 @@ from typing import Optional
from uuid import UUID from uuid import UUID
from pydantic import BaseModel 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): class PhaseType(enum.Enum):
L1 = "L1" L1 = "L1"
@ -57,4 +57,4 @@ class MeterValue(BaseModel):
class Config: class Config:
from_attributes = True from_attributes = True
json_encoders = {Decimal: decimal_encoder} json_encoders = {Decimal: decimal_encoder, datetime: force_utc_datetime}

View file

@ -2,6 +2,8 @@ from datetime import datetime
from uuid import UUID from uuid import UUID
from pydantic import BaseModel from pydantic import BaseModel
from app.util.encoders import force_utc_datetime
class Session(BaseModel): class Session(BaseModel):
id: UUID id: UUID
@ -10,3 +12,4 @@ class Session(BaseModel):
class Config: class Config:
from_attributes = True from_attributes = True
json_encoders = {datetime: force_utc_datetime}

View file

@ -6,7 +6,7 @@ from pydantic import BaseModel
from app.schemas.chargepoint import ChargePointThumb from app.schemas.chargepoint import ChargePointThumb
from app.schemas.user import UserThumb 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): class TransactionStatus(enum.Enum):
ONGOING = "ongoing" ONGOING = "ongoing"
@ -45,7 +45,7 @@ class Transaction(BaseModel):
started_at: datetime started_at: datetime
ended_at: Optional[datetime] = None ended_at: Optional[datetime] = None
meter_start: Decimal meter_start: Decimal
meter_end: Optional[Decimal] = None meter_end: Decimal
end_reason: Optional[TransactionEventTriggerReason] = None end_reason: Optional[TransactionEventTriggerReason] = None
price: Decimal price: Decimal
user: UserThumb user: UserThumb
@ -53,7 +53,7 @@ class Transaction(BaseModel):
class Config: class Config:
from_attributes = True from_attributes = True
json_encoders = {Decimal: decimal_encoder} json_encoders = {Decimal: decimal_encoder, datetime: force_utc_datetime}
class RemoteTransactionStartStopResponse(BaseModel): class RemoteTransactionStartStopResponse(BaseModel):
status: RemoteTransactionStartStopStatus status: RemoteTransactionStartStopStatus

View file

@ -21,19 +21,23 @@ async def create_transaction(
with SessionLocal() as db: with SessionLocal() as db:
chargepoint = db.query(ChargePoint).filter(ChargePoint.identity == chargepoint_identity).first() chargepoint = db.query(ChargePoint).filter(ChargePoint.identity == chargepoint_identity).first()
meter_start=0 meter_start=0
meter_end=0
if "meter_value" in transaction_data.keys(): if "meter_value" in transaction_data.keys():
for meter_value_entry in transaction_data['meter_value']: for meter_value_entry in transaction_data['meter_value']:
for sampled_value in meter_value_entry['sampled_value']: for sampled_value in meter_value_entry['sampled_value']:
if "measurand" in sampled_value.keys(): if "measurand" in sampled_value.keys():
if sampled_value['measurand'] == str(Measurand.ENERGY_ACTIVE_IMPORT_REGISTER): if sampled_value['measurand'] == str(Measurand.ENERGY_ACTIVE_IMPORT_REGISTER):
meter_start = sampled_value['value'] meter_start = sampled_value['value']
meter_end = sampled_value['value']
else: else:
meter_start = sampled_value['value'] meter_start = sampled_value['value']
meter_end = sampled_value['value']
transaction = Transaction( transaction = Transaction(
id=transaction_info["transaction_id"], id=transaction_info["transaction_id"],
status=TransactionStatus.ONGOING, status=TransactionStatus.ONGOING,
started_at=timestamp, started_at=timestamp,
meter_start=meter_start, meter_start=meter_start,
meter_end=meter_end,
price=chargepoint.price, price=chargepoint.price,
chargepoint_id=chargepoint.id, chargepoint_id=chargepoint.id,
user_id=user_id user_id=user_id
@ -55,6 +59,12 @@ async def update_transaction(
transaction_id=transaction.id, transaction_id=transaction.id,
meter_value_data=meter_value_entry 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( async def end_transaction(
transaction_id: str, transaction_id: str,

View file

@ -1,3 +1,4 @@
from datetime import datetime, timezone
from decimal import Decimal from decimal import Decimal
from typing import Union from typing import Union
@ -20,3 +21,7 @@ def decimal_encoder(dec_value: Decimal) -> Union[int, float]:
return int(dec_value) return int(dec_value)
else: else:
return float(dec_value) 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)