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>
52 lines
1.7 KiB
Python
52 lines
1.7 KiB
Python
"""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()
|