@@ -7,6 +7,7 @@ import com.evsuite.hardware.telemetry.StoredTrip
77import com.evsuite.hardware.telemetry.TripSample
88import java.nio.file.Files
99import kotlin.math.abs
10+ import kotlin.math.ceil
1011import org.junit.Assert.assertEquals
1112import org.junit.Test
1213
@@ -19,56 +20,63 @@ class LocalEnergyModelTest {
1920 fun `new bounded history replaces model and failed fit keeps last good model` () {
2021 val directory = Files .createTempDirectory(" chargepilot-local-model" ).toFile()
2122 try {
22- val firstTrip = trip(startedAtMs = 1L , sampleCount = 60 )
23+ val firstTrip = trip(startedAtMs = 1L , kilometres = 12 )
2324 val first = checkNotNull(
2425 LocalEnergyModel .loadOrTrain(directory, listOf (firstTrip), evidence)
2526 )
26- assertEquals(60 , first.sampleCount)
27+ // One sample per kilometre window: twelve is the least a model is trained from.
28+ assertEquals(12 , first.sampleCount)
2729
28- val secondTrip = trip(startedAtMs = 2L , sampleCount = 80 )
30+ val secondTrip = trip(startedAtMs = 2L , kilometres = 18 )
2931 val refreshed = checkNotNull(
3032 LocalEnergyModel .loadOrTrain(
3133 directory,
3234 listOf (secondTrip, firstTrip),
3335 evidence,
3436 )
3537 )
36- assertEquals(140 , refreshed.sampleCount)
38+ assertEquals(30 , refreshed.sampleCount)
3739
3840 val fallback = checkNotNull(
3941 LocalEnergyModel .loadOrTrain(directory, emptyList(), evidence)
4042 )
41- assertEquals(140 , fallback.sampleCount)
43+ assertEquals(30 , fallback.sampleCount)
4244 } finally {
4345 directory.deleteRecursively()
4446 }
4547 }
4648
47- private fun trip (startedAtMs : Long , sampleCount : Int ): StoredTrip {
48- val samples = List (sampleCount) { index ->
49- val speed = 30.0 + index % 6 * 10.0
50- val temperature = if ((index / 6 ) % 2 == 0 ) 10.0 else 20.0
49+ /* * [kilometres] steady kilometres, six speeds and two temperatures, a hole after each. */
50+ private fun trip (startedAtMs : Long , kilometres : Int ): StoredTrip {
51+ var atMs = startedAtMs
52+ val samples = (0 until kilometres).flatMap { km ->
53+ val speed = 30.0 + km % 6 * 10.0
54+ val temperature = if ((km / 6 ) % 2 == 0 ) 10.0 else 20.0
5155 val consumption = 10.0 + 0.0005 * speed * speed +
5256 0.1 * abs(temperature - 20.0 )
53- TripSample (
54- atMs = startedAtMs + index * 5_000L ,
55- speedKmh = speed.toFloat(),
56- batteryPowerKw = (consumption * speed / 100.0 ).toFloat(),
57- socPercent = null ,
58- outsideTempCelsius = temperature.toFloat(),
59- cabinTempCelsius = null ,
60- batteryTempCelsius = null ,
61- climatePowerOn = null ,
62- climateAcOn = null ,
63- climateFanLevel = null ,
64- )
57+ // One interval past the kilometre, so rounding cannot leave the window short.
58+ val count = ceil(3_600.0 / (5.0 * speed)).toInt() + 2
59+ List (count) { index ->
60+ TripSample (
61+ atMs = atMs + index * 5_000L ,
62+ speedKmh = speed.toFloat(),
63+ batteryPowerKw = (consumption * speed / 100.0 ).toFloat(),
64+ socPercent = null ,
65+ outsideTempCelsius = temperature.toFloat(),
66+ cabinTempCelsius = null ,
67+ batteryTempCelsius = null ,
68+ climatePowerOn = null ,
69+ climateAcOn = null ,
70+ climateFanLevel = null ,
71+ )
72+ }.also { atMs + = count * 5_000L + 600_000L }
6573 }
6674 return StoredTrip (
6775 summary = EnergyTripSummary (
6876 startedAtMs = startedAtMs,
69- endedAtMs = startedAtMs + sampleCount * 5_000L ,
70- durationMs = sampleCount * 5_000L ,
71- distanceKm = 10.0 ,
77+ endedAtMs = atMs ,
78+ durationMs = atMs - startedAtMs ,
79+ distanceKm = kilometres.toDouble() ,
7280 startSocPercent = 80f ,
7381 endSocPercent = 78f ,
7482 consumedKwh = 2.0 ,
0 commit comments