1. What Geotab Data Feed provides
Sohly’s primary distribution is the Geotab Marketplace. Each fleet customer authorizes Sohly as a MyGeotab integration; the relevant telemetry lands in their MyGeotab database and Sohly subscribes to two object types via the Data Feed.
| Data point | Source | Available | Quality |
|---|---|---|---|
| State of Charge (SoC) | StatusData (DiagnosticId
BatteryStateOfChargeId) |
Yes | BMS-reported; ±1–3 % typical |
| Pack voltage | StatusData (PrimaryBatteryVoltageId) |
Yes | BMS-reported; the signal that carries the curve shape |
| Pack current | StatusData (charging-current diagnostic) |
Yes (most modern EVs) | Sign discriminates charge vs discharge |
| Pack / cell temperature | StatusData (battery-temperature diagnostic) |
Most modern EVs | BMS-reported; the physically correct temperature for chemistry correction |
| Odometer | StatusData (OdometerId) |
Yes | Reliable |
| GPS / location | LogRecord |
Yes | 1–60 s cadence depending on device profile |
| Ignition state | LogRecord |
Yes | Discriminates trip vs key-off vs charging |
| Charge-session boundaries | Derived (ignition + charging-current sign) | Computed | The unit of analysis for the curve regime (§4.1) |
| Per-session energy added (kWh) | Derived (∫ I dt over the session) | Computed | Replaces Smartcar’s BMW/MINI-only charge/records |
| Nominal battery capacity | OEM spec table keyed by {make, model, year} |
Static | Factory spec — not current degraded capacity |
| Internal resistance / impedance | Estimated from CC-phase voltage intercept (§9.2) | Computed | Direct degradation signature |
What’s different from the consumer-Smartcar regime
- Continuous stream, not on-demand snapshots.
StatusDatapushes rows when a diagnostic changes; Sohly does not poll. A charging session yields tens to thousands of points depending on device profile and SoC delta, not “one range estimate per OAuth call”. - Pack voltage and current are visible. This is the
single most important difference. The charging-curve methodology (§9) is
unlocked because Sohly sees
V(t)andI(t)during charging, not just the SoC delta. - Pack temperature is BMS-reported. No need to triangulate ambient air temperature for chemistry correction (see §7) — though Sohly still uses ambient as a fallback when the BMS temperature diagnostic is absent.
- No per-vehicle OAuth. Authorization is once per fleet at MyGeotab integration time, not once per vehicle by a consumer (see §5).
Critical caveats
- Static capacity tables, not BMS-reported. Nominal
capacity per
{make, model, year}is sourced from public OEM specifications. Some OEMs publish “usable” vs “total” pack capacity; Sohly stores both and prefers “usable” for the residual-value model. - Pre-integration unknown —
StatusDatacadence. The push rate of voltage/current during a charging session is not documented; it varies by device profile (GO9, GO RUGGED, etc.) and OEM data-bus coverage. The methodology handles this via the regime gate (§4): each session is auto-classified as Curve / Snapshot / Cold-start based on the density of points it produced. - Vehicle coverage is bounded by MyGeotab device support. A vehicle missing the battery-current diagnostic falls to the Snapshot regime (§4.2). A vehicle with only ignition + GPS falls to Cold-start (§4.3).
2. Why a single range/SoC snapshot is not enough
The original consumer-Smartcar formula was:
SoH = Range / (SoC × NominalRange)
Equivalent to: EstimatedFullRange / NominalRange. It
looks plausible but has a fatal signal-to-noise problem. The physics is
the same under Geotab — the difference is that Geotab streams the curve,
so Sohly does not depend on this formula. This section is retained
because it justifies why the Curve regime exists at all (§4.1,
§9) and why the Snapshot regime (§4.2) needs multi-sample
averaging and temperature filtering to be defensible.
Confounders and their measured magnitudes
| Factor | Range impact | Source |
|---|---|---|
| Temperature: -7°C with heating | -41% | AAA testing |
| Temperature: -18°C urban | -50 to -59% | U.S. DOE 2024 |
| Temperature: +35°C with AC | -17% | Consumer Reports |
| Speed: 110 km/h vs 105 km/h | -25–30% vs EPA estimate | Geotab |
| Driving style: aggressive vs smooth | -10–30% | field data |
| Terrain: uphill loaded vs flat | -20–40% | field data |
| HVAC without heat pump | -41% | DOE |
| Cold battery (not warmed up) | -10–15% | BMS studies |
Composite worst case: a healthy battery on a cold day at high speed can read 60–70% of nominal range. The formula would output SoH ≈ 65% — a massive false positive.
Composite best case: a healthy battery on a mild spring day in city traffic with regen can read 110%+ of nominal range. The formula outputs SoH > 100% — physically uninterpretable.
The core problem
- Signal you need to detect: 2.3% degradation per year (Geotab, 22,700 vehicles). A 5-year-old car ≈ 88% SoH.
- Noise from confounders: ±30–50% per single snapshot.
- Signal-to-noise ratio: ~0.1:1. The noise is 10× larger than the degradation signal.
3. What the science says
Geotab (22,700 EVs, multi-year): compute SoH from energy in/out across thousands of sessions — never from a single snapshot.
Recurrent Auto: longitudinal fleet comparison, multiple readings per day, thousands of peer vehicles per climate zone. Their own docs state a single reading can move due to driving style or a highway trip — without any cell degradation.
Nature Communications (2025): 300 EVs, 3 years, 1.2M charging snippets, deep learning multi-modal approach — meaningful SoH only through longitudinal large-scale data.
IEEE/SAE consensus: OCV-based methods require 2+ hours of rest. Coulomb counting requires many cycles. ML models need voltage curves, current, temperature, and many charging cycles.
Legal standards (ISO 12405, SAE J2950, UL 1974): all require full cycle testing, impedance measurement, or sustained monitoring. No standard recognises range/SoC snapshot estimation as sufficient for certification.
4. Data regimes — automatic, per session
A vehicle on the Sohly platform does not pick a “tier” — each individual session (or each scoring window) is classified by a regime gate based on what the data feed actually delivered. The customer-facing product is the same; the methodology backing each prediction is regime-dependent and labelled in the report.
The three regimes, in increasing data quality:
4.1 Curve regime (preferred) — full charging-curve ML
Trigger: a charging session produced ≥0.1 Hz pack voltage + pack current samples, ≥20 % SoC delta, ≥10 minutes duration, and no ignition interruption.
Method: the five physics-derived features from §9.2 (CC slope, time-to-CV, CV taper time-constant, internal-resistance estimate, peak temperature rise) feed three independent LightGBM quantile boosters; the conformalized-quantile-regression calibrator widens the output to guaranteed coverage (§9.4).
Output: P05/P50/P95 SoH triple plus a risk band (LOW / MEDIUM / HIGH per §9.6). Accuracy: mean interval width 1.73 pp SoH on the validation set (§9.5). Tight enough to underwrite residual-value pricing. Cadence: once per qualifying charging session — typically daily or every other day for an active fleet vehicle.
4.2 Snapshot regime (fallback) — continuous multi-sample averaging
Trigger: the feed delivered SoC, odometer, and
(ideally) BMS pack temperature, but the per-session voltage/current
density was insufficient for the Curve regime — for example, an OEM
whose StatusData only publishes SoC on ignition events, or
a charging session with <10 minutes of bus activity.
Method: 1. Collect ≥10 SoC + range snapshots over a rolling window (no fixed 14-day cap — Geotab vehicles stream continuously, so the window slides until enough qualifying points accumulate). 2. Apply temperature filtering using the BMS pack temperature when available, otherwise the GPS-keyed Open-Meteo lookup (§7, §7bis). 3. Discard outliers (>2 σ). 4. Average the temperature-filtered range against the OEM nominal range table. 5. Cross-reference against the cross-fleet cohort (§8).
Output: Range Score with a confidence band that
depends on whether BMS temperature was available (band tightens when it
is). No CQR guarantee — the report carries an explicit
regime = snapshot flag and the residual-value pipeline
(§11) treats P05 as score − band/2, not as a
calibrated quantile.
4.3 Cold-start regime — cohort prior only
Trigger: insufficient data for either of the above
(< 3 qualifying sessions of any kind, vehicle just joined the fleet,
or the cross-fleet cohort has < 10 peers in this
{make, model, year} band).
Method: the Geotab-published 2.3 %/year linear degradation curve (§8) is the prior, conditioned only on age and the static OEM capacity table. No vehicle-specific signal is used.
Output: a point estimate framed as “expected SoH for a vehicle of this age, no vehicle-specific data yet”. Percentile is suppressed. The pricing pipeline (§11) widens the residual-value band substantially to absorb the absence of evidence.
Transitions and report labelling
A vehicle’s regime is per-session, not per-vehicle. A given vehicle can land in Cold-start on Monday, Curve on Tuesday’s charging session, and Snapshot on Wednesday if a partial bus dropout truncated the curve. Sohly’s pricing layer aggregates across the latest N sessions per vehicle, weighting Curve evidence more heavily than Snapshot, and falling back to the prior when the running aggregate is still thin.
Every certificate / report carries a regime field, plus
the count of sessions in each regime that contributed to the displayed
SoH range. Auditors see what produced each number.
5. Onboarding & consent — B2B fleet via Geotab Marketplace
The product surface is continuous SoH telemetry, not a one-shot certificate, and the contract is at the fleet level, not per vehicle.
Customer-facing onboarding
- Fleet customer installs Sohly from the Geotab Marketplace and
approves the MyGeotab add-in’s data scopes (
StatusData,LogRecordfor the diagnostics listed in §1). - A Sohly account is provisioned for the fleet. Existing vehicles backfill from the customer’s MyGeotab database (typically 90 days of history available depending on retention plan); new vehicles begin streaming on first ignition.
- Per-vehicle scoring begins in the Cold-start regime (§4.3); each vehicle transitions automatically to Snapshot or Curve as the feed produces qualifying data.
- A fleet dashboard surfaces per-vehicle SoH ranges, residual-value forecasts (§11), and the “sell these first” ranking. The deterministic JSON feed is the canonical product; PDF reports and the LLM-written paragraph (§11.6) are optional outputs.
No Stripe consumer checkout. No per-vehicle OAuth. No 14-day passive-collection clock. Billing is per vehicle-year per the contract, invoiced to the fleet.
Data Processing Agreement (DPA)
The fleet’s DPA with Sohly establishes:
- Data scope:
LogRecord(GPS, ignition, speed, odometer) andStatusData(battery diagnostics enumerated in §1). Sohly never requests trip-by-trip GPS for analytics beyond what is required to look up ambient temperature at a charging-session location (§7). - Retention: raw event stream retained for the
duration of the contract plus 30 days; derived SoH snapshots retained
for the duration plus 7 years to support residual-value warranty claims
and EU Battery Passport audit (regulatory horizon 2027-02-18 per BACKLOG.md
[REGULATORY]). - Sub-processors: Google Cloud Platform (EU region —
europe-west1), Anthropic (for the optional NarratorSkill in §11.6; can be disabled per the audit contract). - Cohort contribution clause: by default, anonymized
SoH snapshots (no VIN, no GPS, no customer identifier — only
{make, model, year, age_bucket, mileage_bucket, soh_quantile_triple, regime}) are contributed to the cross-fleet cohort baseline (§8) so that Sohly can build a make/model/year peer percentile for every customer. Opt-out is available at fleet onboarding for customers whose internal data classification policy excludes any third-party data pooling — opting out narrows the customer’s own percentile reporting to “vs the published Geotab 2.3 %/year curve only”, but otherwise leaves the per-vehicle methodology unchanged. - Subject access & deletion: drivers are not Sohly’s data subjects (the fleet is the data controller). Sohly responds to deletion requests routed through the fleet operator, including purging contributed cohort snapshots when a vehicle is removed from contract.
Why this replaces the consumer 14-day passive-collection flow
The 14-day window only existed to gather enough Smartcar reads over varied weather and SoC to fight signal-to-noise in the snapshot formula. The Geotab feed dissolves that constraint: a fleet vehicle produces qualifying data continuously, and the regime gate (§4) moves it to the higher-quality methodology as soon as the data supports it. There is no point in time at which “the score is final” — the score is the running aggregate of the latest qualifying sessions, refreshed as new sessions land.
6. Impact on product & marketing
Sohly is sold to leasers, insurers, fleet operators, and used-EV marketplaces (see STRATEGY.md §4 for the ranked segments). The marketing copy must match the methodology and the contract — not a consumer-grade certificate, not a single-number SoH claim.
| Don’t say | Do say |
|---|---|
| “Battery Health Certificate” | “Continuous SoH telemetry” / “Battery Health Data Feed” |
| “State of Health: 92 %” | “P05/P50/P95 SoH triple, conformal 90 % coverage” |
| “Certified by Sohly” | “Independently produced, methodology-transparent SoH range with audit-replayable provenance” |
| “Battery Range Score” | “Per-vehicle residual-value forecast at 3/6/12/24-month horizons” |
The optional per-vehicle PDF report exists for end-of-lease handover paperwork, used-EV listing pages, and warranty-claim packaging — but it is a thin output of the feed, not the product. The product is the deterministic JSON feed, the dashboard, and the fleet-ranked “sell these first” list.
Why “feed” beats “certificate” in this segment
A leasing or insurance customer underwrites portfolios, not vehicles
one by one. They consume the feed programmatically, run it through their
own pricing models, and produce decisions across thousands of vehicles.
A static PDF certificate per vehicle is friction, not value. The PDF
survives as a regulatory artifact (EU Battery Passport readiness — see
BACKLOG.md [REGULATORY]) and a
customer-facing handover document, not as the unit of sale.
7. Temperature data — BMS first, Open-Meteo fallback
Temperature is a first-order driver of both range estimates and battery chemistry. Sohly uses the best available source per session, in this preference order:
- BMS pack temperature from
StatusData(the battery-temperature diagnostic, available on most modern EVs). This is the physically correct input — it measures the cells themselves, not the air outside. Used directly in the temperature filter (§7bis) and as a feature in the Curve regime (§9.2,peak_temp_rise_c). - Ambient temperature from Open-Meteo, GPS-keyed via
LogRecordcoordinates at the session’s start timestamp. Used only when the BMS diagnostic is absent or sparse for the session.
Both are recorded in the per-session feature row so that an audit can trace which source fed the filter and the model.
Open-Meteo plan, endpoint, attribution
Vendor: Open-Meteo, commercial license — API Standard plan (€29/mo HT, 1M calls/mo, Switzerland-based, GDPR adequacy region). Required even though the BMS path covers most sessions: Sohly is a commercial product per Open-Meteo’s TOS the moment it has paying customers.
Volume sanity check: at fleet-pilot scale (≈500 vehicles × ≈1 charging session/day × ≈30 % BMS-missing rate × ≈1 fractional call per session) we consume ≈4,500 calls/mo — still <0.5 % of the Standard tier. Re-cost when monthly volume crosses ~500k calls (typical only above ~50k continuously monitored vehicles with BMS-missing-rate held constant).
VAT handling: Open-Meteo invoices via Stripe. Provide Sohly’s FR VAT ID at billing to trigger reverse-charge — otherwise 20 % TVA is added (€34.80/mo all-in).
Endpoint:
GET https://customer-archive-api.open-meteo.com/v1/archive
with apikey query param.
Dataset: Météo-France AROME (1.3 km resolution) for sessions in metropolitan France, included in the Standard plan. ERA5-Land (~9 km, 5-day lag) is the fallback outside AROME coverage. The 5-day lag is acceptable — Sohly aggregates over rolling windows of multiple sessions, so individual session lookups can wait for ERA5 finalization without blocking customer-facing reports.
Request shape per session-with-BMS-missing:
?latitude={lat}&longitude={lon}
&start_date={session_date}&end_date={session_date}
&hourly=temperature_2m
&timezone=auto
&apikey={secret}
Location source: GPS coordinates from
LogRecord at the session’s first timestamp, rounded to 4
decimal places (≈11 m precision — sufficient for ambient temperature,
coarse enough to share cache hits across a depot).
Caching: GCS object keyed by
gs://sohly-prod-data/weather_cache/{lat_4dp}/{lon_4dp}/{date}.json.
Immutable once written (ERA5 finalized after 5 days). Fleet vehicles
often charge at the same depot day after day, so cache hit-rate is
materially higher than the consumer case — most of the per-day depot
lookups are free after the first call.
Attribution: CC-BY 4.0 — required attribution in the PDF report footer when Open-Meteo data contributed to that vehicle’s score: “Weather data: Open-Meteo.com”.
Region: France-first launch. AROME (Météo-France) gives 1.3 km resolution across metropolitan France. No special regional endpoint required — Open-Meteo selects the model per request coordinates.
Temperature correction curve: see §7bis below.
7bis. Temperature correction strategy
The AAA/DOE figures in §2 bundle three unrelated effects — battery chemistry slowdown, cabin heating load, and cold rolling resistance. Applying them as a single multiplicative correction would inflate range estimates on cold days and mask real degradation. The methodology requires honest uncertainty, not laundered precision. This section applies only to the Snapshot regime (§4.2). The Curve regime (§4.1, §9) consumes pack temperature directly as a feature and needs no correction layer.
Filter-first, correct only as fallback
Primary path — filter, do not correct. For each
candidate snapshot, read its temperature input (BMS pack temperature
when available per §7, else Open-Meteo ambient). Drop the snapshot when
T < 5 °C or T > 30 °C. Average the
remaining snapshots against OEM nominal range. The filter is
conservative in both directions — under the Curve regime we never enter
this branch, so the cost of throwing away cold/hot snapshots is only
that the Snapshot regime takes a longer rolling window to accumulate
qualifying points.
Fallback — soft correction, only when fewer than 5 snapshots pass the filter in the running window. Apply a linear battery-chemistry correction to the filtered-out cold snapshots to recover them:
if T < 5°C:
correction_factor = 1 + min(0.01 × (5 - T), 0.25) # +1% per °C below 5°C, capped at +25%
range_corrected = range_observed × correction_factor
This curve is calibrated against Recurrent / Geotab published SoH-vs-temperature data (battery capacity loss of ~5–8 % at 0 °C, ~15 % at -20 °C), not AAA headline range numbers (which include accessories). It corrects less than the AAA figures would suggest, on purpose — under-correcting is the safer error.
No hot-side fallback. Above 30 °C is rare in France and the AC-load confound is not separable from chemistry effects with the data we have.
Confidence reporting (Snapshot regime)
| Scenario | temperature_corrected flag |
Temperature source | Confidence band |
|---|---|---|---|
| ≥5 snapshots in 5–30 °C window, BMS temp available | false |
bms |
±12 |
| ≥5 snapshots in 5–30 °C window, Open-Meteo only | false |
open_meteo |
±15 |
| <5 in window, fallback applied | true |
mixed | ±20 |
| <5 even after fallback | false |
n/a | regime falls back to Cold-start (§4.3) |
BMS-temperature inputs tighten the confidence band by 3 pp because they remove the ambient-vs-cell-temperature confounder.
Product implication
In a fleet streaming continuously, the “winter vehicle stuck in fallback” problem largely disappears: the rolling window slides past Jan–Feb and accumulates qualifying spring/summer snapshots. The trade is now “first-report latency in winter” rather than “score quality in winter” — a vehicle joining the platform in January may report Cold-start for longer than one joining in May.
What this is not
This is not a real SoH temperature model. It is a defensive filter that preserves the integrity of the multi-sample average in the Snapshot regime. The Curve regime (§9) replaces this section with first-principles physics — temperature is a model feature, not a filter knob.
8. Cohort cold-start strategy
Percentile reporting (“this vehicle is in the top 35 % of MG MG4 2023
peers”) needs a peer cohort. The cohort lookup fails until enough
vehicles of the same {make, model, year} have been
measured. This section defines the cold-start fallback and the policy
for transitioning to a real cohort.
Cohort scope — pooled across all Sohly fleet customers
Sohly’s cohort is pooled across the entire customer
base, not built per fleet. A single fleet of 200 vehicles
cannot produce a credible peer percentile for a
Renault Megane E-Tech 2023 if it only operates two such
vehicles; pooling across customers gets the
per-{make, model, year} cohort to N ≥ 10 within months
instead of years.
The pool stores anonymized snapshots:
{make, model, year, age_bucket, mileage_bucket, soh_quantile_triple, regime, capture_month}
— no VIN, no GPS, no customer identifier. Customers consent to
anonymized contribution by default at fleet onboarding; opt-out is
documented in §5 and only narrows that customer’s own percentile
reporting (their per-vehicle SoH measurement is unaffected). Sohly never
resurfaces a contributor’s data to another customer at row level — only
as a percentile over the pooled distribution.
What the “Geotab prior” is
Geotab published a 2020 study of 22,700 EVs across multiple years with a headline finding: EV batteries degrade ≈2.3 % per year on average (public, peer-reviewed). That gives a degradation curve any car’s expected SoH can be plotted against: a 3-year-old EV ≈ 93 %, a 5-year-old ≈ 88 %, a 10-year-old ≈ 77 %.
A Bayesian prior is a starting belief used before the vehicle-specific data arrives. Here:
In the absence of real peer data, assume this car should be at
100 % − (2.3 % × age_in_years)of nominal.
Sohly starts with this published belief and shifts toward the pooled cohort as it grows.
Policy
- < 10 peers in cohort → suppress percentile in the report; show the absolute SoH range only with an explicit note: “Percentile unavailable — fewer than 10 peer vehicles for this make / model / year.” Add a Geotab-relative sentence framing the score against expected degradation for the car’s age (e.g., “Expected ≈93/100 at 3 years per published EV fleet averages; this vehicle’s P50 is 89.”).
- Seed every cohort with the Geotab 2.3 %/year curve as the prior.
- Update toward real cohort data as the pool grows.
Once N ≥ 10 for a
{make, model, year}, the percentile is computed from real pooled data and the Geotab prior is dropped. The report switches to peer-mode: “Top 35 % of 14 MG MG4 2023 vehicles in the Sohly cohort.”
The N = 10 threshold and the linear 2.3 %/year curve are working assumptions — revisit if percentiles look unstable in production, or if a model-specific degradation curve becomes available.
Why the prior gets dropped at N = 10
Geotab’s 2.3 %/year is an average across all EVs.
The Sohly cohort is specific to one
{make, model, year}. Once the specific cohort is
stable, it is more informative than the broad average — the prior has
served its purpose.
9. Curve-derived ML methodology — quantile regression + conformal prediction
Status: working pipeline (synthetic CC-CV data),
wired end-to-end with empirical validation in
voltcheck-api/ml/. Real-fleet validation pending Geotab
Data Feed integration (BACKLOG [VENDOR/GEOTAB]).
This section specifies the methodology behind the Curve regime (§4.1). When the Geotab Data Feed delivers pack voltage + current at ≥0.1 Hz during a charging session — together with the BMS pack temperature where available (§7) — Sohly extracts five physics-derived features and feeds them into a conformalized quantile-regression pipeline. The Snapshot regime (§4.2) is the graceful fallback when curve density is insufficient. Direct OEM streams (under EU Data Act Article 5, post Sept 2026) are an interchangeable substitute for the Geotab feed at the input boundary. This is the methodology Sohly publishes on the Geotab Marketplace and against which fleet, leasing, and insurance customers underwrite decisions.
9.1 What changes when we have the full charging curve
A CC-CV charging session (the standard Li-ion fast-charge protocol) has two phases: - Constant-Current (CC): the charger holds current fixed; terminal voltage rises as State of Charge and internal resistance both climb. - Constant-Voltage (CV): when terminal voltage reaches the upper limit, the charger pins voltage and current tapers exponentially until cutoff.
State of Health drives the shape of this curve via two coupled
effects: 1. Usable capacity shrinks with age — the same
charger fills a 70%-SoH pack faster than a 100%-SoH pack, so the voltage
curve is steeper. 2. Internal resistance grows — a
70%-SoH pack has roughly 1.8× the internal resistance of a new pack,
which lifts the terminal-voltage baseline (by I × R_int)
and brings the CV transition forward to a lower SoC.
Aggregate features that a snapshot pipeline can extract (“total energy added”, “charge duration”) throw away nearly all of this signal. The full curve preserves it.
9.2 Five physics-derived features
The feature extractor
(voltcheck-api/ml/feature_extraction.py) computes five
scalar features per charging session:
| Feature | What it measures | Direction with aging |
|---|---|---|
cc_slope_v_per_min |
Rate of voltage rise during the CC phase | ↑ (smaller capacity → faster SoC rise → steeper dV/dt) |
time_to_cv_min |
Minutes from charge start to CV transition | ↓ (higher R_int hits v_max sooner) |
cv_taper_tau_min |
Exponential decay time-constant of CV current | ↑ (τ ∝ R_int × capacity; the product grows with age) |
internal_resistance_est_ohm |
R_int estimated from the CC-phase voltage intercept | ↑ (direct degradation signature) |
peak_temp_rise_c |
Peak cell temperature minus baseline | ↑ (aged packs dissipate more I²R heat) |
Each feature is a direct electrochemical signature of degradation, not a proxy. Smoke-testing on simulated curves at SoH ∈ {100, 90, 80, 70}% shows all five features change monotonically in the physically expected direction across the 70–100% range.
9.3 Why we publish a range, not a single number
A point estimate (“SoH = 87%”) sounds precise. It is misleading. The Geotab fleet study’s headline 2.3%/year degradation is an average; individual packs deviate by ±5 percentage points around that line at any given age. Driving style, climate zone, depth-of-discharge habits, and DC-fast-charge frequency all matter, and none of them are summarised by a single number.
Sohly publishes three numbers, fit by independent LightGBM quantile-regression boosters at quantiles 0.05, 0.50, and 0.95: - P50 — median estimate. The most likely SoH given the inputs. - P05 — pessimistic bound. True SoH is at least this high with 95% confidence. - P95 — optimistic bound. True SoH is no higher than this with 95% confidence.
The interval [P05, P95] is the actionable input for
downstream decisions: - Residual-value pricing uses the
P05 (worst-case capacity). - End-of-policy
underwriting uses the interval width as the
uncertainty premium. - Operational decisions
(replacement scheduling, fleet rotation) use the
P50.
9.4 Why the range can be trusted: conformal prediction
Quantile regression alone gives only
marginal-in-expectation coverage: across a population
of held-out tests, roughly 90% of true SoH values fall inside
[P05, P95]. There is no finite-sample guarantee — and
“roughly 90%” is not a number an insurer can underwrite against.
Conformalized Quantile Regression (Romano, Patterson, Candès, NeurIPS 2019) wraps the quantile estimator and provides a distribution-free coverage guarantee:
For any data distribution, given an i.i.d. calibration set, the calibrated interval contains the true SoH with marginal probability ≥ 90%.
Mechanically: 1. The training corpus is split 80/20 into a training
slice and a held-out calibration slice. 2. The three
quantile boosters are fit on the training slice only. 3. On the held-out
calibration slice we compute the nonconformity score
for each example — how much the true SoH exceeds the predicted P95 or
falls below the predicted P05. 4. We take the empirically-90%
(finite-sample-corrected) quantile of those scores: this is
q_hat, a single scalar. 5. At inference, every interval is
widened by q_hat on both sides:
[P05 − q_hat, P95 + q_hat].
The guarantee comes from a single-page theorem about exchangeable sequences (Vovk, Gammerman & Shafer, Algorithmic Learning in a Random World, 2005; Romano et al. 2019 for the quantile-regression variant). There is no Gaussian assumption, no parametric model of the residuals, no asymptotic argument. The guarantee holds in finite samples, on any data distribution, on any base predictor.
9.5 What the empirical numbers look like
Validation on a held-out set of 2,000 simulated CC-CV sessions,
ambient temperature distribution matched to the training prior
(seed=2026, target 90% coverage, calibrator n=400):
| Metric | Value | Reference |
|---|---|---|
| Empirical 90% interval coverage | 88.1% ± 1.4% | Target 90% (95% CI [86.7%, 89.5%] includes target); acceptance floor ≥87% per AI_ML_ROADMAP §6 |
| Median absolute error of P50 | 0.05 pp SoH | Point estimate is essentially unbiased |
| Mean interval width (P95 − P05) | 1.73 pp SoH | Tight enough for residual-value pricing without manual review |
| P50 above true (well-centered if ≈50%) | 47% | No systematic bias |
The 1.73 pp mean spread compares favourably to the AAA / U.S. DOE single-snapshot range-noise figures of ±30–50% (see §2): an actuarial team can price residuals from a 2 pp interval; they cannot from a 50 pp interval.
9.6 Surfacing the limit of the guarantee: risk band
The CQR coverage guarantee is conditional on exchangeability — the test data must come from the same distribution as the calibration set. When a fleet operates in a temperature or driving-style regime underrepresented in the calibration data, coverage drops and the interval widens.
Every Sohly prediction includes a categorical risk_band
field derived from the interval width:
| Band | Interval width | Recommended action |
|---|---|---|
| LOW | < 3 pp SoH | Use the P50 directly for operational decisions; price residuals at P05. |
| MEDIUM | 3–8 pp SoH | Manual review or additional data pull before high-stakes decisions. |
| HIGH | ≥ 8 pp SoH | Do not auto-price. Pull more telemetry; re-run; flag for engineering. |
Under in-distribution test data, ~97% of predictions fall in LOW. Under heavy out-of-distribution stress (60% extreme-temperature curves, double the training rate), the HIGH-band rate climbs to ~7% and overall coverage drops to 79.5%. This is by design: the risk band is the model’s self-reported uncertainty signal, and it gets louder when the model’s assumptions break down. An underwriter should treat a HIGH band the way they treat a flagged claim — work the case manually, do not auto-price.
9.7 Auditability and reproducibility
Every Sohly certificate is reproducible:
- The CQR calibrator is persisted as a human-readable JSON
file (
models/conformal.json) containingq_hat,alpha, andn_calibration. Anyone with the file can reproduce the widening step bit-exact. - The five physics features are extracted by a pure
function of the input curve
(
ml/feature_extraction.py) — no hidden state, no learned preprocessing. - Booster training, calibrator fitting, and inference are all deterministic given a seed (default 42). Re-running the pipeline produces byte-identical artifacts.
- Each certificate carries a
scoring_method_versionfield (per AI_ML_ROADMAP §7) — auditors can map any historical certificate to the exact pipeline version that produced it. - The methodology itself is open: this document, the code repository, and the algorithm references (Romano et al. 2019; Vovk, Gammerman & Shafer 2005) are public. The only proprietary asset is the calibration data — and that is replaceable by any party with access to a fleet’s charging curves.
This is the opposite of the black-box AI pitch many incumbents lean on. We expose the math because actuarial and risk teams cannot underwrite anything they cannot audit.
10. Plain-language summary
Reusable ~200-word block for slide decks, landing-page sections, incubator submissions, and partner conversations. Audience: technically literate but not ML-specialist — fleet manager, lease portfolio analyst, insurance underwriter, incubator programme director.
How Sohly produces a battery State-of-Health number you can underwrite against.
Sohly never publishes a single SoH percentage. It publishes a three-point range: a median estimate (P50), a pessimistic lower bound (P05), and an optimistic upper bound (P95). The bounds come from independent quantile-regression models trained on five physics-derived features extracted from each vehicle’s charging curves — voltage slope during constant-current, time-to-CV transition, tapering time-constant, internal-resistance estimate, peak temperature rise. These features are direct electrochemical signatures of cell degradation, not proxies.
The interval is then calibrated using Conformalized Quantile Regression (Romano, Patterson & Candès, NeurIPS 2019), a method with a distribution-free coverage guarantee: on held-out data the 90% interval contains the true SoH at least 90% of the time, regardless of the underlying data distribution. On the Sohly validation set (2,000 simulated charging sessions, in-distribution), empirical coverage is 88.1% ± 1.4%, median absolute error of the point estimate is 0.05 percentage points, and mean interval width is 1.73 pp SoH — tight enough to price residuals directly.
Every prediction also carries a risk band — LOW, MEDIUM, or HIGH — that flags when interval width grows beyond what’s safe for unattended use. Underwrite the LOW band, manually review the MEDIUM band, never auto-price the HIGH band. The methodology, the algorithm references, and the calibration artefacts are public; only the fleet-specific calibration data is proprietary.
11. Risk pricing — from SoH quantiles to fleet financial decisions
Status: design locked 2026-05-16, implementation
pending (BACKLOG [PRICING], [NARRATOR]). This
section is the methodology fleet managers and incubator reviewers should
evaluate before underwriting any pricing decision Sohly produces.
The SoH quantile interval from §9 is engineering output. It does not tell a fleet manager what to do. Section 11 is the translation layer: SoH quantiles → degradation forecast → residual value in €€€ → action recommendation (HOLD / SELL_NOW / SELL_BEFORE).
11.1 Why we publish € rather than only SoH %
A fleet manager does not buy SoH numbers — they buy arbitrage decisions. The actionable question is “if I hold this vehicle for six more months instead of selling it today, what is the financial downside?” The answer is in euros, not percentage points.
Conformal quantile regression (§9.4) provides exactly the inputs this question needs: - P05 answers “how bad is the worst plausible case?” — the downside an insurer or treasurer prices against. - P50 answers “what is the most likely outcome?” — the operational planning input. - P95 answers “how much upside am I leaving on the table by selling early?” — the opportunity-cost input.
The residual-value function RV(soh, age, mileage) is
monotonic in SoH, so the SoH quantile triple pushes
through cleanly into a residual-value quantile triple. The coverage
guarantee from §9.4 is preserved end-to-end: if the 90 % SoH interval
covers the truth 90 % of the time, so does the 90 % residual-value
interval.
11.2 Pipeline overview
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
│ SoH history │ → │ forecast_soh() │ → │ Forecast SoH │
│ (longitudinal │ │ per-vehicle slope │ │ at horizons │
│ from Geotab │ │ OR cohort prior │ │ 3/6/12/24 months │
│ Marketplace, │ └──────────────────────┘ │ with P05/P50/P95 │
│ or single snapshot)│ └──────────┬───────────┘
└──────────────────────┘ │
▼
┌──────────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
│ Vehicle metadata │ → │ rv() │ ← │ push SoH quantiles │
│ (make/model/year/ │ │ baseline × g(soh) │ │ through monotonic │
│ mileage/MSRP) │ │ warranty cliff │ │ RV() │
└──────────────────────┘ └──────────┬───────────┘ └──────────────────────┘
│
▼
┌──────────────────────┐ ┌──────────────────────┐
│ decide() │ → │ VehiclePricingReport│
│ HOLD / SELL_NOW / │ │ (structured) │
│ SELL_BEFORE │ │ │
└──────────────────────┘ └──────────┬───────────┘
│
▼
┌──────────────────────┐
│ NarratorSkill (LLM) │
│ paragraph, FR/EN, │
│ numbers verbatim │
│ from the report │
└──────────────────────┘
Each box is a pure function. The output of every stage is reproducible from the input plus a versioned code path. No state, no hidden inputs, no sampled values until the optional NarratorSkill stage — and the narrator is constrained to cite only the structured report (see §11.6).
11.3 Forecasting SoH with longitudinal data
In a Geotab Marketplace deployment, a vehicle accumulates multiple SoH snapshots over time as charging sessions land in the data feed. Two regimes:
Per-vehicle slope (≥3 snapshots). Fit ordinary least
squares on (captured_at, soh_p50). The fitted slope
μ_dot is the vehicle’s measured degradation rate; the
standard error of the slope quantifies how much we trust it. Project all
three quantile lines forward independently to each target horizon:
soh_q(horizon) = soh_q(today) + μ_dot × (horizon − today)
where q ∈ {p05, p50, p95}
Widen the band by slope_se × horizon_years to absorb
trend-extrapolation uncertainty. This is the regime Geotab Marketplace
puts us in for any vehicle that has been streaming for more than a few
weeks.
Cohort prior (<3 snapshots). Fall back to the published Geotab 2.3 %/year linear degradation curve (already used as the cold-start prior in §8). The forecast carries an additional fixed uncertainty term that grows with horizon — we are extrapolating from a population average, not from this vehicle’s own trajectory.
Known V0 limitation: the snapshot CQR guarantee
(§9.4) is exchangeability-based. Projecting it forward by linear
regression does not preserve the coverage guarantee at the
forecast horizon — the i.i.d. assumption breaks across the time axis. V1
will re-calibrate CQR on historical (t, t+Δ) pairs to
restore a horizon-aware coverage claim. V0 ships with the simpler
propagation and the limitation is disclosed in every report.
11.4 Residual value model — baseline curve and warranty cliff
RV(soh, age_months, mileage_km, msrp) = msrp × baseline(age, mileage) × g(soh)
Two factors, both transparent and replaceable.
baseline(age, mileage) — published
depreciation curve, parametric:
baseline(age_months, mileage_km) = exp(−α · age_months / 12) · exp(−β · mileage_km / 10_000)
with α ≈ 0.18 (≈ 17 % first-year depreciation, tapering)
and β ≈ 0.025. V0 constants are sourced from public Geotab
data and Autobiz’s monthly French resale index. V1 swaps these for paid
market data (Argus / La Centrale / Indicata) when a customer demands
market-grade figures — the function signature is unchanged.
g(soh) — piecewise SoH penalty with the
warranty cliff:
| SoH band | Multiplier | Rationale |
|---|---|---|
| ≥ 85 % | 1.00 | No buyer concern; range matches expectations |
| 75 – 85 % | 1.00 → 0.92 linear | Mild range concern |
| 70 – 75 % | 0.92 → 0.70 steep | Warranty proximity — buyers price in lost coverage |
| < 70 % | 0.70 × (soh / 70) | Replacement-cost regime — pack swap is a real risk |
The kink at 70 % SoH is the OEM warranty floor for the dominant European EV makers (Renault, Stellantis, Tesla). When the P05 of a vehicle’s SoH forecast crosses this threshold, the lower bound of the residual-value forecast drops sharply — that drop is the arbitrage signal the fleet manager needs.
These constants are placeholders pending validation. They will be revisited per make once a B2B pilot accumulates enough resale data to calibrate the kink. The piecewise structure does not change; only the breakpoints and multipliers do.
11.5 Decision rules — HOLD, SELL_NOW, SELL_BEFORE
The decide() function returns one of three actions plus a rationale. Two parameters control the threshold:
tolerance— maximum acceptable downside vs today’s residual value (default 5 %).holding_cost_pct_per_year— depreciation + insurance + parking + financing (default 8 %, customer-overridable per fleet).
The logic walks each forecast horizon in order:
for horizon T in [3, 6, 12, 24] months:
if rv_p05(T) < rv_today × (1 − tolerance):
return SELL_NOW
rationale = "P05 RV at {T} months is {x} € below today's RV"
eur_at_risk = rv_today − rv_p05(T)
if forecast P50 SoH crosses 70 % warranty floor within T:
crossing_date = solve(soh_p50(t) == 70)
return SELL_BEFORE(crossing_date − 30 days)
rationale = "P50 SoH crosses 70 % warranty floor on {crossing_date}"
if no horizon triggers:
return HOLD
rationale = "All horizons within {tolerance}% of today's RV"
The rationale string is deterministic — generated from which branch fired and which numbers tripped the threshold. It contains the same € figures that the structured report carries. The optional LLM narrator (§11.6) only rephrases this rationale for tone and language; it does not regenerate the numbers.
11.6 Deterministic engine + LLM narrator — the audit contract
A fleet manager looking at 200 vehicles will not read 200 JSON blobs. They will read 200 short paragraphs. The paragraphs are the product surface. So Sohly produces them — but with a hard constraint that keeps the conformal coverage guarantee intact end-to-end.
The split:
| Layer | What it produces | How it produces it | Auditability |
|---|---|---|---|
| PricingSkill (deterministic) | All numbers, all dates, the action label | Versioned pure functions of structured input | Re-run → byte-identical |
| NarratorSkill (LLM, Claude Haiku) | The fleet-manager-facing paragraph | Prompt-constrained generation over the structured report | LLM may not invent any figure absent from the input |
The narrator’s system prompt enforces the rule:
“Tu es un analyste financier expert en dépréciation de flottes automobiles. Tu reçois un rapport structuré (quantiles SoH, RV projetée P05/P50/P95, action recommandée, dates clés, montant à risque). Rédige un paragraphe d’action directe pour le gestionnaire de flotte. Règle absolue : n’invente aucun chiffre — n’utilise que les valeurs présentes dans le rapport, citées au centime près. Ton : direct, sans hedging, max 4 phrases.”
The rule is enforced post-generation by a regex-based check: every euro figure, percentage, and date in the paragraph must appear verbatim in the structured input. Paragraphs that fail the check are rejected and replaced by a deterministic template fallback.
Why the split matters:
- Reproducibility. Same vehicle → same numbers, every run. The numbers are what gets underwritten against.
- Conformal guarantee. The 90 % coverage claim from §9.4 only carries if the numbers it covers come from the deterministic pipeline. An LLM-invented figure has no coverage claim.
- Fall-back. Customers in audit-heavy contexts (insurance, regulated leasers) can disable the LLM stage entirely — the structured report and a deterministic template paragraph are unchanged.
- Cost. At fleet scale (500 vehicles × daily refresh) deterministic compute is microseconds; per-vehicle LLM calls are a real cost line. The split keeps the expensive layer optional.
- Liability. A fleet manager who acts on a € figure has a documented, replayable provenance for that figure. “The LLM said so” is not a defensible audit trail.
The constraint check is also a regression-safety net: if a future prompt change accidentally lets the model paraphrase a number (“about €2,300” vs the exact “€2,317”), the check catches it and the deterministic fallback ships instead.
12. Plain-language summary — risk pricing
Reusable ~200-word block for slide decks, landing-page sections, incubator submissions, and partner conversations with fleet, leasing, and insurance audiences.
How Sohly turns a battery State-of-Health interval into a fleet pricing decision you can act on.
Sohly’s calibrated SoH range (P05 / P50 / P95) is the input, not the product. The product is a per-vehicle financial recommendation — HOLD, SELL NOW, or SELL BEFORE a given date — backed by a residual-value forecast in euros at horizons of 3, 6, 12, and 24 months. The forecast pipeline is fully deterministic: a published baseline depreciation curve is multiplied by a piecewise SoH penalty with a kink at the 70 % warranty floor, the point at which a used-EV buyer prices in the cost of a future pack replacement. The SoH quantiles are pushed through monotonically, so the conformal coverage guarantee from the SoH model carries through to the euro figures.
For each vehicle the report ranks the downside (
rv_today − rv_p05_horizon) against today’s residual value; the fleet view returns vehicles sorted by euros at risk, so the manager sees the “sell these first” list rather than 200 individual PDFs. The accompanying paragraph is written by a small LLM (Claude Haiku) under a strict no-invent-numbers constraint — every figure in the prose comes verbatim from the structured report, and a post-generation check enforces it. The euro recommendation, the action label, and the underlying SoH interval are all reproducible, versioned, and auditable. The methodology, the algorithm references, and the depreciation curve constants are public.