EU Data Act: identify ID.3 cruising range by data point key (#30851)

This commit is contained in:
andig 2026-06-15 07:08:52 +02:00 • committed by GitHub
parent 3842c3da52
commit bcd75510d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 49 additions and 10 deletions

View file

@ -128,3 +128,16 @@ func TestMergeDeliveryOrder(t *testing.T) {
assert.Equal(t, "75", data[FieldSoc].Value, "the newest delivered SoC wins")
}
// TestPoints guards that a data point with a generic field name ("value") is
// indexed by its unique key while the name stays indexed (and thus logged).
func TestPoints(t *testing.T) {
data := points([]dataPoint{
{Key: KeyRangeID3, DataFieldName: "value", Value: "317"},
{DataFieldName: FieldOdometer, Value: "22164"},
})
assert.Equal(t, "317", data[KeyRangeID3].Value, "ID.3 range indexed by key")
assert.Equal(t, "317", data["value"].Value, "field name remains indexed")
assert.Equal(t, "22164", data[FieldOdometer].Value, "named field indexed by name")
}

View file

@ -95,7 +95,7 @@ func (v *Provider) Range() (int64, error) {
return 0, err
}
if p := lookup(data, FieldRangeSecondary, FieldRangePrimary, FieldRangeCombined); p != nil {
if p := lookup(data, FieldRangeSecondary, FieldRangePrimary, FieldRangeCombined, KeyRangeID3); p != nil {
f, err := strconv.ParseFloat(p.Value, 64)
return int64(f), err
}

View file

@ -81,8 +81,10 @@ type dataset struct {
CreatedOn time.Time `json:"createdOn"`
}
// dataPoint is a single data point as delivered in the dataset JSON document
// dataPoint is a single data point as delivered in the dataset JSON document.
// Key is the data point's unique GUID, used when DataFieldName is generic (e.g. "value").
type dataPoint struct {
Key string `json:"key"`
DataFieldName string `json:"dataFieldName"`
Value string `json:"value"`
TimestampUtc *time.Time `json:"timestampUtc"`
@ -121,6 +123,7 @@ const (
FieldRangeCombined = "cruising_range_combined"
FieldRangePrimary = "cruising_range_primary_engine"
FieldRangeSecondary = "cruising_range_secondary_engine"
KeyRangeID3 = "0ca40e18-0564-3eda-bcc0-7aee9ef44f04" // VW ID.3 cruising range, delivered as "value"
// odo
FieldOdometer = "mileage"
@ -130,6 +133,12 @@ const (
FieldRemainingTime = "remaining_charging_time"
)
// knownKeys lists data point GUIDs that are indexed by their key instead of the
// generic, non-unique DataFieldName they are delivered with
var knownKeys = map[string]struct{}{
KeyRangeID3: {},
}
// contentDatasets returns the datasets that actually carry content, with their
// delivery time parsed into Timestamp and sorted from oldest to newest. The
// portal emits "..._no_content_found.zip" placeholders while the vehicle is
@ -192,9 +201,26 @@ func parseDataset(log *log.Logger, b []byte) (map[string]point, error) {
return nil, err
}
res := make(map[string]point, len(ds.Data))
for _, p := range ds.Data {
if p.DataFieldName == "" || p.Value == "" {
return points(ds.Data), nil
}
// points indexes data points by field name (newest timestamp wins), and known
// data points additionally by their unique key, as their name is not unique.
func points(data []dataPoint) map[string]point {
res := make(map[string]point, len(data))
set := func(name string, p point) {
if name == "" {
return
}
if cur, ok := res[name]; ok && cur.Timestamp.After(p.Timestamp) {
return
}
res[name] = p
}
for _, p := range data {
if p.Value == "" {
continue
}
@ -202,13 +228,13 @@ func parseDataset(log *log.Logger, b []byte) (map[string]point, error) {
if p.TimestampUtc != nil {
ts = *p.TimestampUtc
}
pt := point{Value: p.Value, Timestamp: ts}
if cur, ok := res[p.DataFieldName]; ok && cur.Timestamp.After(ts) {
continue
set(p.DataFieldName, pt)
if _, ok := knownKeys[p.Key]; ok {
set(p.Key, pt)
}
res[p.DataFieldName] = point{Value: p.Value, Timestamp: ts}
}
return res, nil
return res
}