Skip to content

Commit 7566457

Browse files
malysclaude
andcommitted
feat(eco): short spoken eco-driving advice
The trip-end voice read the full journal line: headline, up to four tips and the pack-stress clause, often 60+ words. It now says one headline and the first lever ("12 % to gain: smoother driving."), and nothing when there is nothing to say. Live coaching speaks a few-word form too. Screen and advice journal keep the full sentences. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 8d2ebdd commit 7566457

5 files changed

Lines changed: 77 additions & 7 deletions

File tree

‎app/src/main/java/com/evsuite/chargepilot/EcoCoach.kt‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ class EcoCoach(context: Context) {
127127
*/
128128
@Synchronized
129129
fun speak(verdict: EcoVerdict, speedKmh: Float?, nowMs: Long) {
130-
val line = sentence(verdict.advice ?: return)
130+
val line = sentence(verdict.advice ?: return, spoken = true)
131131
if (line == null || !voiceEnabled(app) || !adviceEnabled(app)) return
132132
if (speedKmh == null || speedKmh <= MOVING_KMH) return
133133
if (line == spokenLine || nowMs - lastSpokeAtMs < SPEAK_COOLDOWN_MS) return
@@ -136,17 +136,20 @@ class EcoCoach(context: Context) {
136136
EcoVoice.say(app, line)
137137
}
138138

139-
/** Consequence with a number in it. No imperative is written here, in any language. */
140-
private fun sentence(advice: EcoAdvice): String? {
139+
/**
140+
* Consequence with a number in it. No imperative is written here, in any language.
141+
* [spoken] picks the few-word form: a driver takes in a short line, not a sentence.
142+
*/
143+
private fun sentence(advice: EcoAdvice, spoken: Boolean = false): String? {
141144
return when (advice.lever) {
142145
EcoLever.CRUISE_SPEED -> app.getString(
143-
R.string.eco_advice_cruise,
146+
if (spoken) R.string.eco_voice_cruise else R.string.eco_advice_cruise,
144147
advice.toSpeedKmh?.roundToInt() ?: return null,
145148
advice.fromSpeedKmh?.roundToInt() ?: return null,
146149
advice.savingPercent?.roundToInt() ?: return null,
147150
)
148151
EcoLever.STEADINESS -> app.getString(
149-
R.string.eco_advice_steadiness,
152+
if (spoken) R.string.eco_voice_steadiness else R.string.eco_advice_steadiness,
150153
advice.harshSharePercent?.roundToInt() ?: return null,
151154
)
152155
}

‎app/src/main/java/com/evsuite/chargepilot/EcoTripAdvice.kt‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,32 @@ class EcoAdviceText(context: Context) {
253253
// Not the application context: the service hands in one carrying the app's own language.
254254
private val app = context
255255

256-
/** The line spoken when the trip closes in P. */
256+
/**
257+
* What is said out loud when the trip closes in P: one headline and the first lever, nothing
258+
* the driver has to hold in memory. Empty when there is nothing worth saying. The full
259+
* [spoken] text stays in the advice journal.
260+
*/
261+
fun voice(advice: TripAdvice): String {
262+
val lever = advice.tips.firstOrNull()?.let(::lever)
263+
val saving = advice.saving?.percent?.roundToInt()
264+
val delta = advice.deltaPercent?.let { abs(it).roundToInt() }
265+
if (saving != null && lever != null) {
266+
return app.getString(R.string.eco_voice_trip_gain_by, saving, lever)
267+
}
268+
val head = when {
269+
saving != null -> app.getString(R.string.eco_voice_trip_gain, saving)
270+
advice.band == EcoBand.BETTER && delta != null ->
271+
app.getString(R.string.eco_voice_trip_better, delta)
272+
advice.band == EcoBand.WORSE && delta != null ->
273+
app.getString(R.string.eco_voice_trip_worse, delta)
274+
advice.band != null -> app.getString(R.string.eco_voice_trip_typical)
275+
else -> null
276+
}
277+
return listOfNotNull(head, lever?.let { app.getString(R.string.eco_voice_lever, it) })
278+
.joinToString(" ")
279+
}
280+
281+
/** The full line for the advice journal when the trip closes in P. */
257282
fun spoken(advice: TripAdvice): String {
258283
val saving = advice.saving
259284
val sentences = ArrayList<String>()
@@ -353,6 +378,17 @@ class EcoAdviceText(context: Context) {
353378
is EcoTip.SnowMode, is EcoTip.Cabin, is EcoTip.Windows -> null
354379
}
355380

381+
/** A tip in two or three words, for the voice. */
382+
private fun lever(tip: EcoTip): String = when (tip) {
383+
is EcoTip.Smoother -> app.getString(R.string.eco_lever_smoother)
384+
is EcoTip.EcoMode -> app.getString(R.string.eco_lever_eco_mode)
385+
is EcoTip.SlowerMotorway ->
386+
app.getString(R.string.eco_lever_motorway, tip.finding.referenceSpeedKmh)
387+
is EcoTip.SnowMode -> app.getString(R.string.eco_lever_snow_mode)
388+
is EcoTip.Cabin -> app.getString(R.string.eco_lever_cabin)
389+
is EcoTip.Windows -> app.getString(R.string.eco_lever_windows)
390+
}
391+
356392
private fun sentence(tip: EcoTip): String = when (tip) {
357393
is EcoTip.Smoother ->
358394
app.getString(R.string.eco_tip_smoother, tip.harshMinutes, tip.sharePercent)

‎app/src/main/java/com/evsuite/chargepilot/TripRecordingService.kt‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,9 @@ class TripRecordingService : Service() {
337337
val line = listOfNotNull(text.spoken(advice), text.stress(trip.samples.orEmpty())).joinToString(" ")
338338
// CP-087: kept whether or not it is said out loud.
339339
batteryAdvisor.record(AdviceKind.TRIP_END, line, trip.summary.endedAtMs)
340-
if (EcoCoach.voiceEnabled(this)) EcoVoice.say(this, line)
340+
// Spoken short: headline and one lever; the stress clause stays in the journal.
341+
val voice = text.voice(advice)
342+
if (EcoCoach.voiceEnabled(this) && voice.isNotEmpty()) EcoVoice.say(this, voice)
341343
}
342344

343345
/**

‎app/src/main/res/values-fr/strings.xml‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,8 @@ Consommation %6$s %% par km</string>
510510
<string name="eco_delta">%1$+d %%</string>
511511
<string name="eco_advice_cruise">À %1$d plutôt que %2$d km/h, cette route coûte environ %3$d %% de moins.</string>
512512
<string name="eco_advice_steadiness">Accélérations ou freinages francs sur %1$d %% de ce trajet ; la même route à vitesse stable coûte moins.</string>
513+
<string name="eco_voice_cruise">%1$d au lieu de %2$d km/h : %3$d %% de moins.</string>
514+
<string name="eco_voice_steadiness">Conduite brusque, %1$d %% du temps.</string>
513515
<string name="eco_gauge_listening">Coach à l’écoute</string>
514516
<string name="eco_gauge_smooth">Souple · %1$d %%</string>
515517
<string name="eco_gauge_harsh">Brusque · %1$d %%</string>
@@ -583,6 +585,18 @@ Consommation %6$s %% par km</string>
583585
<string name="eco_trip_typical">Trajet habituel, dans votre propre moyenne.</string>
584586
<string name="eco_trip_no_verdict">Pas encore assez de trajets comparables pour un bilan éco.</string>
585587
<string name="eco_trip_stress">%1$.0f min de forte puissance à charge basse ou sous 0 °C dehors : c’est là que le pack souffre le plus, et des accélérations plus douces l’épargneraient.</string>
588+
<string name="eco_voice_trip_gain">%1$d %% à gagner sur ce trajet.</string>
589+
<string name="eco_voice_trip_gain_by">%1$d %% à gagner : %2$s.</string>
590+
<string name="eco_voice_trip_better">Trajet économe, %1$d %% sous la moyenne.</string>
591+
<string name="eco_voice_trip_worse">Trajet gourmand, %1$d %% au-dessus de la moyenne.</string>
592+
<string name="eco_voice_trip_typical">Trajet habituel.</string>
593+
<string name="eco_voice_lever">Levier : %1$s.</string>
594+
<string name="eco_lever_smoother">conduite plus souple</string>
595+
<string name="eco_lever_eco_mode">mode Éco</string>
596+
<string name="eco_lever_motorway">%1$d km/h sur voie rapide</string>
597+
<string name="eco_lever_snow_mode">mode Snow</string>
598+
<string name="eco_lever_cabin">moins de chauffage ou de clim</string>
599+
<string name="eco_lever_windows">fenêtres fermées</string>
586600
<string name="eco_tip_smoother">Accélérations et freinages francs pendant %1$d min, %2$d %% de la conduite : une conduite plus souple consommerait moins.</string>
587601
<string name="eco_tip_eco_mode">Le mode Éco conviendrait mieux à cette conduite que le mode %1$s.</string>
588602
<string name="eco_tip_eco_mode_unknown">Le mode Éco, s’il n’est pas déjà choisi, adoucirait cette conduite.</string>

‎app/src/main/res/values/strings.xml‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,9 @@ Spending %6$s %% per km</string>
526526
<string name="eco_delta">%1$+d %%</string>
527527
<string name="eco_advice_cruise">At %1$d rather than %2$d km/h, this road costs about %3$d %% less.</string>
528528
<string name="eco_advice_steadiness">Hard acceleration or braking on %1$d %% of this drive; the same road at a steady speed costs less.</string>
529+
<!-- Spoken forms: a few words a driver can take in without looking. -->
530+
<string name="eco_voice_cruise">%1$d instead of %2$d km/h: %3$d %% less.</string>
531+
<string name="eco_voice_steadiness">Jerky driving, %1$d %% of the time.</string>
529532
<!-- CP-084. The gauge's word; the bar beside it is the share, the mark the advice threshold. -->
530533
<string name="eco_gauge_listening">Coach listening</string>
531534
<string name="eco_gauge_smooth">Smooth · %1$d %%</string>
@@ -605,6 +608,18 @@ Spending %6$s %% per km</string>
605608
<string name="eco_trip_typical">Usual trip, in line with your own average.</string>
606609
<string name="eco_trip_no_verdict">Not enough comparable trips yet for an eco verdict.</string>
607610
<string name="eco_trip_stress">%1$.0f min of hard power at a low charge or below 0 °C outside: that is when the pack is strained most, and gentler pulls there spare it.</string>
611+
<string name="eco_voice_trip_gain">%1$d %% to gain on this trip.</string>
612+
<string name="eco_voice_trip_gain_by">%1$d %% to gain: %2$s.</string>
613+
<string name="eco_voice_trip_better">Economical trip, %1$d %% below average.</string>
614+
<string name="eco_voice_trip_worse">Heavy trip, %1$d %% above average.</string>
615+
<string name="eco_voice_trip_typical">Usual trip.</string>
616+
<string name="eco_voice_lever">Lever: %1$s.</string>
617+
<string name="eco_lever_smoother">smoother driving</string>
618+
<string name="eco_lever_eco_mode">Eco mode</string>
619+
<string name="eco_lever_motorway">%1$d km/h on fast roads</string>
620+
<string name="eco_lever_snow_mode">Snow mode</string>
621+
<string name="eco_lever_cabin">less heating or cooling</string>
622+
<string name="eco_lever_windows">windows closed</string>
608623
<string name="eco_tip_smoother">Sharp acceleration and braking for %1$d min, %2$d %% of the driving: a smoother drive would use less.</string>
609624
<string name="eco_tip_eco_mode">Eco mode would suit this drive better than %1$s mode.</string>
610625
<string name="eco_tip_eco_mode_unknown">Eco mode, if not already selected, would soften this drive.</string>

0 commit comments

Comments
 (0)