Endpoint /vehicles/:id/latest #6
Labels
No labels
blocked
claude
in-progress
needs-review
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
aiways/Backend#6
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Können wir den Endpoint bullet-proof machen?
Aktuell zieht der einfach den letzten bekannten Eintrag... Was ja auch formal korrekt ist. Ich halte es jetzt für sinnvoll, dass der Enpoint für jeden Eintrag den zuletzt bekannten werden, lädt und dann in einem Datensatz merged.
Der Payload könnte dabei auch exemplarisch so aussehen:
{
"package_voltage": {
"ts":"yyyy-mm-dd HH:mm:ss",
"value":373.6
},
"soc":{
"ts":"yyyy-mm-dd HH:mm:ss",
"value":70
},
"gps":{
"ts":...,
"lat":...,
},
etc. pp.
}
Also das quasi für jeden Wert sein zuletzt bekannter Eintrag ausgegeben wird. Ich finde das macht am meisten Sinn.
Umgesetzt in
e5ea822(release) — CI grün, deployt, live verifiziert.Genau der Punkt, den du beschreibst — hier an echten Daten, wie es vorher aussah. Der neueste Record war der vom 25.08., und der führte nur den Kilometerstand:
Formal korrekt, praktisch fast nutzlos:
.inxführt keinen SoC,cache_datakeine Zellspannungen, also hat jeder einzelne Record Löcher.Jetzt: je Messgröße ihr letzter bekannter Wert, mit eigenem Zeitstempel
Skalare als
{ts, value}wie in deinem Beispiel, GPS als{ts, lat, lon}— lat und lon kommen dabei garantiert aus demselben Record, sonst zeigt die Position auf einen Ort, an dem das Auto nie war. Messgrößen, für die es überhaupt keinen Wert gibt, fehlen im Objekt (statt alsnulldazustehen). Dabei sind auch Höhe, Kurs, Gang, Ladezustand, Zellen, Temperaturen und die Quelle.Der eigene Zeitstempel je Größe ist dabei der eigentliche Gewinn: ein Wert von vor drei Tagen ist als solcher erkennbar, statt sich als aktuell auszugeben. Im Dashboard steht deshalb hinter jedem Wert sein Alter („vor 3 h").
Bullet-proof heißt auch: es muss schnell bleiben
Eine Aggregation über die ganze Tabelle wäre pro Abfrage ein Full Scan gewesen. Stattdessen läuft je Feld eine Unterabfrage
ORDER BY ts DESC LIMIT 1, die den Index(vehicle_id, ts DESC)nutzt und beim ersten Treffer abbricht — alle zusammen als einUNION ALL, also ein Roundtrip. PerEXPLAINgegengeprüft:Die Laufzeit hängt damit an der Anzahl Felder, nicht an der Anzahl Records.
Entscheidungen (proceed-with-default)
?flat=1erreichbar. Falls irgendwo noch ein Client auf dem flachen Record sitzt, kippt ihm damit nichts weg.2026-08-20T10:00:00.000Z) stattyyyy-mm-dd HH:mm:ssaus deinem Beispiel — so wie überall sonst in der API, und mit Zeitzone. Wenn du das Format lieber genau wie skizziert hättest, sag Bescheid.sourceist mit drin, damit man sieht, woher der jüngste Stand kommt.Verifikation (echte Postgres-Instanz, Produktivschema, bewusst lückenhafte Daten)
Vier Records über den August, jede Größe zuletzt zu einem anderen Zeitpunkt gesehen:
max(ts)je Spalte gegengeprüft — soc → 20.08., Packspannung → 10.08., Odometer → 25.08., GPS → 10.08., Zellen → 01.08. Alle stimmen exakt.?flat=1liefert unverändert den einen Record; Fahrzeug ohne Daten → 404./healthok,/latestund/latest?flat=1weiterhin 401 ohne Token, Sandbox-Doku und Dashboard auf neuem Stand, Client-JS der ausgelieferten Seite parst sauber. Testdatenbank wieder entfernt.