harden telegram webapp production readiness
This commit is contained in:
@@ -38,7 +38,7 @@ async def get_ownership_stats(
|
||||
|
||||
distance_km = int(max_odo - min_odo) if min_odo is not None and max_odo is not None else 0
|
||||
total_cost = Decimal(fuel_cost) + Decimal(service_cost)
|
||||
avg_consumption = float(Decimal(liters) * Decimal(100) / distance_km) if distance_km else None
|
||||
avg_consumption = await full_tank_consumption(session, car_id, date_from, date_to)
|
||||
cost_per_km = float(total_cost / distance_km) if distance_km else None
|
||||
|
||||
return OwnershipStats(
|
||||
@@ -57,6 +57,48 @@ async def get_ownership_stats(
|
||||
)
|
||||
|
||||
|
||||
async def full_tank_consumption(
|
||||
session: AsyncSession, car_id: int, date_from: date, date_to: date
|
||||
) -> float | None:
|
||||
result = await session.execute(
|
||||
select(FuelEntry)
|
||||
.where(
|
||||
FuelEntry.car_id == car_id,
|
||||
FuelEntry.entry_date <= date_to,
|
||||
)
|
||||
.order_by(FuelEntry.entry_date.asc(), FuelEntry.odometer.asc(), FuelEntry.id.asc())
|
||||
)
|
||||
entries = list(result.scalars())
|
||||
full_indexes = [index for index, entry in enumerate(entries) if entry.is_full_tank]
|
||||
if len(full_indexes) < 2:
|
||||
return None
|
||||
|
||||
total_liters = Decimal("0")
|
||||
total_distance = 0
|
||||
previous_full_index = full_indexes[0]
|
||||
for current_full_index in full_indexes[1:]:
|
||||
previous = entries[previous_full_index]
|
||||
current = entries[current_full_index]
|
||||
if current.entry_date < date_from:
|
||||
previous_full_index = current_full_index
|
||||
continue
|
||||
distance = current.odometer - previous.odometer
|
||||
if distance <= 0:
|
||||
previous_full_index = current_full_index
|
||||
continue
|
||||
interval_liters = sum(
|
||||
Decimal(entry.liters) for entry in entries[previous_full_index + 1 : current_full_index + 1]
|
||||
)
|
||||
if interval_liters > 0:
|
||||
total_liters += interval_liters
|
||||
total_distance += distance
|
||||
previous_full_index = current_full_index
|
||||
|
||||
if total_distance <= 0 or total_liters <= 0:
|
||||
return None
|
||||
return float(total_liters * Decimal(100) / Decimal(total_distance))
|
||||
|
||||
|
||||
async def dataframe_from_query(session: AsyncSession, stmt: Select) -> pd.DataFrame:
|
||||
result = await session.execute(stmt)
|
||||
rows = result.mappings().all()
|
||||
|
||||
@@ -14,6 +14,8 @@ def _secret_key(bot_token: str, *, webapp: bool) -> bytes:
|
||||
|
||||
|
||||
def verify_webapp_init_data(init_data: str, bot_token: str, max_age_seconds: int = 86400) -> dict:
|
||||
if not bot_token:
|
||||
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="BOT_TOKEN is not configured")
|
||||
values = dict(parse_qsl(init_data, keep_blank_values=True))
|
||||
received_hash = values.pop("hash", "")
|
||||
if not received_hash:
|
||||
@@ -34,6 +36,8 @@ def verify_webapp_init_data(init_data: str, bot_token: str, max_age_seconds: int
|
||||
|
||||
|
||||
def verify_login_widget(payload: dict, bot_token: str, max_age_seconds: int = 86400) -> dict:
|
||||
if not bot_token:
|
||||
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="BOT_TOKEN is not configured")
|
||||
values = {key: value for key, value in payload.items() if value is not None}
|
||||
received_hash = str(values.pop("hash", ""))
|
||||
if not received_hash:
|
||||
|
||||
Reference in New Issue
Block a user