Skip to content

Treat missing SignedMeterValue observation as no reading#40

Merged
martijnversluis merged 1 commit into
mainfrom
handle-missing-signed-meter-value
Jun 18, 2026
Merged

Treat missing SignedMeterValue observation as no reading#40
martijnversluis merged 1 commit into
mainfrom
handle-missing-signed-meter-value

Conversation

@martijnversluis

Copy link
Copy Markdown
Contributor

Voorkomt KeyError: key not found: :SignedMeterValue wanneer Zaptec's /api/chargers/{id}/state endpoint geen SignedMeterValue observatie teruggeeft. Zaptec::State#meter_reading retourneert nu nil in plaats van te crashen, wat aansluit op de callers in StekkerWeb die al een return if meter_reading.nil? guard hebben.

Root cause

Zaptec::State#meter_reading (lib/zaptec/state.rb:39) gebruikt @data.fetch(:SignedMeterValue). Het Zaptec state endpoint retourneert observaties spaars: alleen de StateIds die een charger ooit heeft geëmit. Een charger die nog nooit een OCMF signed meter value heeft geproduceerd (sessie net begonnen, niet-MID-certified hardware) heeft simpelweg geen row 554 in de response. De callers in StekkerWeb (ChargePointRefreshJob#create_meter_reading regel 107-108 en #create_start_meter_reading regel 124-125) doen vervolgens return if meter_reading.nil? — maar zien die nil nooit omdat fetch eerder al KeyError raised.

Waarom-keten

  1. Waarom crasht ChargePointRefreshJob#create_start_meter_reading? → charge_point.meter_reading raised KeyError: key not found: :SignedMeterValue.
  2. Waarom raised die? → Zaptec::State#meter_reading doet @data.fetch(:SignedMeterValue) en de hash bevat die key niet.
  3. Waarom zit :SignedMeterValue niet in de hash? → Zaptec's state API retourneert observaties spaars (alleen wat de charger ooit heeft geëmit). Observatie 554 verschijnt alleen op MID-certified chargers en pas vanaf de eerste signed meter event in een sessie. Op andere chargers / vroeg in een sessie is hij afwezig.
  4. Waarom verwacht de gem dat hij er altijd is? → Inconsistentie. De gem heeft in dezelfde file al het nullable-patroon voor optionele observaties: session_identifier (regel 31-34) gebruikt @data[:SessionIdentifier]; value.presence, en final_stop_active? (regel 27, na Treat missing FinalStopActive observation as inactive #38) gebruikt @data[:FinalStopActive].to_i. meter_reading is per ongeluk strict gebleven.
  5. Waarom is "ontbrekend" gelijk aan "geen meter reading"? → De OCMF-string wordt door MeterReading.parse op semantische gronden ook al nullable behandeld (return if meter_reading.blank? op regel 30). Geen SignedMeterValue observatie betekent: er is nog niets om te parsen. nil is precies de waarde die callers verwachten — ChargePointRefreshJob schrijft die conditie al op twee plekken.

Waarom nu

Dit bug-patroon bestaat sinds 6440245 (2024-03-29, "Use Zaptec signed meter reading"). Eerste Sentry-event vandaag (2026-06-18) suggereert dat een net gekoppelde charger of een charger in een vroege sessie-fase voor het eerst gepolled werd zonder dat hij nog een signed meter value had geëmit. Vergelijkbare sparse-observation bug (#38, gemerged 2026-06-03) dook ook pas op nadat een externe caller een tot dan toe onbenutte code-path raakte.

Blast radius

Iedere Zaptec charger waarvan de state-response (nog) geen SignedMeterValue observatie bevat. In de praktijk: chargers in de eerste seconden/minuten van een sessie waar nog geen signed meter event is geweest, en non-MID-certified chargers die het feature niet ondersteunen. Tot dusver één event in productie, maar zonder de fix valt elke ChargePointRefreshJob voor zo'n charger om bij create_meter_reading / create_start_meter_reading.

Gekozen oplossing

Zaptec::State#meter_reading gebruikt nu @data[:SignedMeterValue] met een nil short-circuit, in plaats van fetch. Een ontbrekende key retourneert nil — wat StekkerWeb's callers al verwachten. Dit volgt het bestaande nullable-patroon in dezelfde file (session_identifier, final_stop_active? na #38) en aligneert de gem met het werkelijke API-contract (sparse observation set). Adresseert why 3 (sparse API) door het contract goed te zetten, niet why 1 (de KeyError op zich).

Alternatieven overwogen

  • Rescue KeyError in StekkerWeb's ChargePointRefreshJob: symptom-fix op de verkeerde laag. De meter_reading API in de gem hoort nullable te zijn omdat het onderliggende data-veld dat is. Een rescue op twee call-sites in StekkerWeb verbergt het probleem; een gem-fix lost het structureel op (zoals Treat missing FinalStopActive observation as inactive #38 voor FinalStopActive).
  • Default @data[:SignedMeterValue] || "" en laten falen in MeterReading.parse: legt een lege-string-conventie op die de gem nergens anders gebruikt. nil is de natuurlijke "geen waarde"-representatie en sluit aan op MeterReading.parse's eigen nullable contract (regel 30).

Reproductie

spec/zaptec/state_spec.rb: nieuwe test "is nil when the SignedMeterValue observation is missing from the API response" faalt vóór de fix met exact dezelfde KeyError: key not found: :SignedMeterValue als in Sentry 2072, en slaagt erna. Een aanvullende happy-path test borgt dat de OCMF-parsing intact blijft wanneer de observatie wél aanwezig is.

Follow-up

Na merge: bundle update stekker_zaptec in stekker/stekker om de gem-bump uit te rollen — anders blijft Sentry 2072 doorvuren tot productie de nieuwe sha pakt.

Sentry: https://sentry2.stekker.app/organizations/sentry/issues/2072/

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aligns Zaptec::State#meter_reading with Zaptec’s sparse /api/chargers/{id}/state observation set by treating a missing SignedMeterValue observation as “no reading” (returning nil) instead of raising a KeyError.

Changes:

  • Update Zaptec::State#meter_reading to safely handle missing/blank SignedMeterValue by short-circuiting to nil.
  • Add specs covering both the happy path (parsing when present) and the missing-observation case.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
lib/zaptec/state.rb Makes meter_reading resilient to missing/blank SignedMeterValue by returning nil rather than raising.
spec/zaptec/state_spec.rb Adds regression coverage for missing SignedMeterValue and verifies parsing still works when present.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@martijnversluis martijnversluis merged commit 4f0a5c7 into main Jun 18, 2026
2 checks passed
@martijnversluis martijnversluis deleted the handle-missing-signed-meter-value branch June 18, 2026 18:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants