Files
ems/backend/tests/test_tesla_client.py
Dusan Vojacek 4095f0f912
All checks were successful
CI and deploy / migration-check (push) Successful in 19s
CI and deploy / deploy (push) Successful in 56s
EV spotřební forecast: týdenní rytmus vozidla → target SoC a deadline session
Myšlenka uživatele: pondělní služebka ~150 km (~35 kWh) chce skoro plnou,
konec týdne stačí míň, víkend = levné sloty na přípravu pondělka.

- V089: ev_vehicle_obs (odometer+SoC při příjezdu/ODJEZDU — auto v obou
  okamžicích vzhůru, žádné buzení navíc), ev_trip (km z odometru, kWh z ΔSoC;
  nabíjení cestou → charged_away flag), ev_usage_stats per (vozidlo, DOW);
  asset_vehicle: target_soc_forecast_enabled (default false), min_target_soc_pct
- R__096: fn_ev_build_trips (párování), fn_update_ev_usage_stats (job 00:50),
  fn_ev_next_departure (příští typický odjezd, >=4 vzorky, >=3 km),
  fn_ev_required_soc (P80 spotřeby dne + 10 p.b., clamp [min_target, 100])
- R__016: session při příjezdu bere forecast target+deadline (za per-vozidlo
  flagem, fallback defaulty, ruční patch vždy vyhrává) → víkendová session
  s pondělním deadline = v2 solver přirozeně nabije v levných slotech
- tesla_client: + vehicle_state endpoint (odometer v MÍLÍCH → km), collector:
  departure hook, lifespan: job 00:50

Aktivace po nasbírání dat: update asset_vehicle set target_soc_forecast_enabled=true.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 09:06:10 +02:00

52 lines
1.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Tesla Fleet API čisté parsery (bez sítě/DB)."""
from __future__ import annotations
import unittest
from services.tesla_client import parse_charge_state
class ParseChargeStateTests(unittest.TestCase):
def test_full_response(self) -> None:
data = {
"response": {
"vin": "5YJYGDEE0MF000000",
"charge_state": {
"battery_level": 47,
"charge_limit_soc": 80,
"charging_state": "Stopped",
},
}
}
out = parse_charge_state(data)
self.assertEqual(out["battery_level"], 47)
self.assertEqual(out["charge_limit_soc"], 80)
self.assertEqual(out["vin"], "5YJYGDEE0MF000000")
def test_missing_level_returns_none(self) -> None:
self.assertIsNone(parse_charge_state({"response": {"charge_state": {}}}))
self.assertIsNone(parse_charge_state({}))
def test_odometer_miles_to_km(self) -> None:
data = {
"response": {
"charge_state": {"battery_level": 60},
"vehicle_state": {"odometer": 12345.6}, # míle!
}
}
out = parse_charge_state(data)
self.assertAlmostEqual(out["odometer_km"], 19868.3, places=1)
def test_missing_odometer_is_none(self) -> None:
data = {"response": {"charge_state": {"battery_level": 60}}}
self.assertIsNone(parse_charge_state(data)["odometer_km"])
def test_zero_limit_normalized_to_none(self) -> None:
data = {"response": {"charge_state": {"battery_level": 10, "charge_limit_soc": 0}}}
self.assertIsNone(parse_charge_state(data)["charge_limit_soc"])
if __name__ == "__main__":
unittest.main()