You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Charge breakdown verdicts can contradict the row's own values and points #2466
The Charge breakdown ("what shaped it") can contradict itself within a single row. Each row prints the value and its baseline at display precision, plus the term's rounded points. The verdict, however, is decided by comparing the raw values. So it can disagree with both of the things printed beside it:
Resting HR:51 bpm · 51 bpm baseline · 0 pts — above baseline, limiting recovery. This happens whenever the raw values round to the same integer but differ (for example 51.3 vs 50.8).
Skin temperature:near baseline beside −1 pt. skinTempVerdict calls anything within ±0.3 °C "near baseline", but the symmetric penalty still charged a point for it.
A real difference that rounds to 0 points still claims an effect. For example, respiration at 15.0 vs a 16.0 baseline, worth 0 points, still reads "supporting recovery".
Where
Swift: Packages/StrandAnalytics/Sources/StrandAnalytics/ChargeDrivers.swift. hrvVerdict (L254), rhrVerdict (L269) and respVerdict (L275) compare value with baseline unrounded. skinTempVerdict (L287) uses a fixed band that is independent of the points.
Decide the effect (supporting or limiting) from the row's rounded points. Decide the direction (above or below) from the values at the precision the row prints them:
Printed values equal and 0 points: "at baseline".
Printed values equal but the term still moved the score: "slightly above/below baseline, …".
Printed values differ but 0 points: "above/below baseline, too small to change Charge".
Skin temperature: "near baseline" only when it cost 0 points.
This adds six verdict keys. The verdict-catalog pin in Tools/test_home_i18n.py goes from 12 to 18. The Kotlin twin needs the same change. A Swift version with tests exists on a branch in my fork, if that's useful as a reference.
Related, smaller
The classic Today breakdown passes skinTempRel (TodayView.swift L2199). So skin temperature appears twice, once as a driver row and once as the relative row, stating the same deviation in different words.
Landed on main as 6151113a6b3ac6d21ee16d8deb5e0e1d7ba35c1f and a0bf1b6, via #2500, from @kavemang's #2468.
Thanks @andremiliano. The report was precise and every part of it held up under checking. All three contradictions you named are gone: a row whose printed values match no longer claims a direction, near baseline no longer sits beside a charged point, and a real difference worth 0 points now says so instead of claiming an effect. Your suggested shape is what shipped, effect from the rounded points and direction from the printed precision, and your prediction that the verdict-catalogue pin would go from 12 to 18 was exact.
Two things your framing got right that were easy to miss. Naming skinTempVerdict's fixed band as a separate fault from the raw-value comparisons was correct, because they needed different fixes. And leaving the gate question as a maintainer decision kept the scope clean.
Checking the fix turned up a fourth instance of the same shape, which is really your bug one layer down. Respiration is the only row printed to a decimal, and it rounded twice by two different rules: the row through String(format: "%.1f"), the verdict through (value * 10).rounded(.toNearestOrAwayFromZero). Those disagree at a half, and the three formatters involved disagree with each other as well, so a row could print 8.2 next to an 8.3 baseline while the verdict, seeing both as 8.3, called it "at baseline". Both platforms now round once through a shared displayRounded and format the already-rounded value, so the printed number and the verdict cannot diverge, and Apple stops showing a different figure from Android for the same input.
I also went looking for siblings and found none: RecoveryForecast's effortSpread and strainAdjCap read like candidates but are documented as points on the 0-100 axis and are correct.
One item from your report is NOT addressed and is worth tracking on its own rather than being buried here: the classic Today breakdown still states skin temperature twice, once as a driver row and once as the relative row via skinTempRel, saying the same deviation in different words. That is the same two-readouts shape as the rest, and it needs a decision about which of the two should go, so it is a separate change.
Activity
Charge driver verdicts: round once, and pair the new helpers
ryanbr commentedon Sep 26, 2026
Landed on main as 6151113a6b3ac6d21ee16d8deb5e0e1d7ba35c1f and a0bf1b6, via #2500, from @kavemang's #2468.
Thanks @andremiliano. The report was precise and every part of it held up under checking. All three contradictions you named are gone: a row whose printed values match no longer claims a direction,
near baselineno longer sits beside a charged point, and a real difference worth 0 points now says so instead of claiming an effect. Your suggested shape is what shipped, effect from the rounded points and direction from the printed precision, and your prediction that the verdict-catalogue pin would go from 12 to 18 was exact.Two things your framing got right that were easy to miss. Naming
skinTempVerdict's fixed band as a separate fault from the raw-value comparisons was correct, because they needed different fixes. And leaving the gate question as a maintainer decision kept the scope clean.Checking the fix turned up a fourth instance of the same shape, which is really your bug one layer down. Respiration is the only row printed to a decimal, and it rounded twice by two different rules: the row through
String(format: "%.1f"), the verdict through(value * 10).rounded(.toNearestOrAwayFromZero). Those disagree at a half, and the three formatters involved disagree with each other as well, so a row could print 8.2 next to an 8.3 baseline while the verdict, seeing both as 8.3, called it "at baseline". Both platforms now round once through a shareddisplayRoundedand format the already-rounded value, so the printed number and the verdict cannot diverge, and Apple stops showing a different figure from Android for the same input.I also went looking for siblings and found none:
RecoveryForecast'seffortSpreadandstrainAdjCapread like candidates but are documented as points on the 0-100 axis and are correct.One item from your report is NOT addressed and is worth tracking on its own rather than being buried here: the classic Today breakdown still states skin temperature twice, once as a driver row and once as the relative row via
skinTempRel, saying the same deviation in different words. That is the same two-readouts shape as the rest, and it needs a decision about which of the two should go, so it is a separate change.