Skip to content

Treat missing FinalStopActive observation as inactive#38

Merged
martijnversluis merged 1 commit into
mainfrom
handle-missing-final-stop-active-observation
Jun 3, 2026
Merged

Treat missing FinalStopActive observation as inactive#38
martijnversluis merged 1 commit into
mainfrom
handle-missing-final-stop-active-observation

Conversation

@martijnversluis

Copy link
Copy Markdown
Contributor

Voorkomt KeyError: key not found: :FinalStopActive wanneer Zaptec's /api/chargers/{id}/state endpoint geen FinalStopActive observatie teruggeeft. Een charger zonder die observatie wordt nu correct als "niet final-stopped" behandeld in plaats van te crashen.

Root cause

Zaptec::State#final_stop_active? (lib/zaptec/state.rb:27) gebruikt @data.fetch(:FinalStopActive), terwijl het Zaptec state endpoint observaties spaars teruggeeft — een charger die nooit een FinalStop event heeft geëmit heeft simpelweg geen row 718 in de response. De omliggende paused? short-circuit'te dit toen de methode nog privé was (mode-check eerst), maar sinds #35 wordt final_stop_active? ook door externe callers (StekkerWeb's Connections::Zaptec#info_for) onvoorwaardelijk aangeroepen, en die zien wél de KeyError.

Waarom-keten

  1. Waarom crasht ChargePointRefreshJob? → Connections::Zaptec#info_for roept charger_state.final_stop_active? aan en die raised KeyError.
  2. Waarom raised die? → @data.fetch(:FinalStopActive) faalt omdat :FinalStopActive niet in de hash zit.
  3. Waarom zit hij niet in de hash? → Zaptec's state API retourneert observaties sparse: alleen de StateIds die de charger ooit heeft geëmit. Chargers die nooit in FinalStop zijn geweest hebben geen row 718.
  4. Waarom werd dit pas een probleem? → Tot #35 was final_stop_active? privé en alleen aangeroepen via paused?, die eerst charger_operation_mode == CONNECTED_FINISHED checkt. Een charger zonder FinalStopActive zit per definitie niet in dat mode, dus de korte-sluiting zorgde dat fetch nooit liep. Sinds Expose final_stop_active? for diagnostic sampling #35 callen externe consumers final_stop_active? rechtstreeks zonder die guard.
  5. Waarom is "ontbrekend" gelijk aan "niet actief"? → De observatie is een binaire flag (0/1). Aanwezig-en-1 betekent actief; afwezig betekent "nooit gerapporteerd", wat semantisch identiek is aan "niet actief".

Waarom nu

#35 (gemerged 2026-04-17) maakte final_stop_active? publiek. StekkerWeb PR #7479 (zelfde dag) begon meteen info_for onvoorwaardelijk te callen voor connector_status sampling, en commit 5af1f603 (2026-05-20) deed hetzelfde voor resume_charging. Beide call sites raken nu chargers waarvan de Zaptec state response geen :FinalStopActive bevat → KeyError in productie (Sentry issue 2016).

Blast radius

Iedere Zaptec charger waarvan de state-response geen FinalStopActive observatie bevat — typisch nieuw gekoppelde chargers en chargers die nooit een FinalStop event hebben geëmit. Elke ChargePointRefreshJob voor zo'n charger faalt sinds StekkerWeb #7479 (2026-04-17). Resume-paths (Connections::Zaptec#resume_charging) zijn sinds 2026-05-20 ook gevoelig.

Gekozen oplossing

final_stop_active? gebruikt nu @data[:FinalStopActive].to_i == 1 in plaats van fetch. nil.to_i == 0, dus een ontbrekende key evalueert naar false. Dit volgt het bestaande patroon in dezelfde file: session_identifier (regel 31-34) gebruikt al @data[:SessionIdentifier] voor dezelfde reden. Dit adresseert why 3 (sparse API-respons) door de gem te aligneren met het werkelijke API-contract — niet why 1 (de KeyError op zich).

Alternatieven overwogen

  • Rescue KeyError in StekkerWeb's info_for / resume_charging: symptom-fix op de verkeerde laag. De gem heeft het verkeerde contract; zelfs paused? zou crashen als ooit een externe caller hem met CONNECTED_FINISHED mode + missing key zou raken. Twee rescues op twee call-sites in plaats van één fix op de juiste plek.
  • Pre-fill missende keys in Client#state met defaults: vereist een mapping van elke observation naar zijn semantisch correcte default. Zwaar voor een gem die het bewust aan elke method overlaat (zie session_identifier vs total_charge_power).

Reproductie

spec/zaptec/state_spec.rb: een nieuwe test "is false when the FinalStopActive observation is missing from the API response" faalt vóór de fix met exact dezelfde KeyError: key not found: :FinalStopActive als in Sentry, en slaagt erna.

Follow-up

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

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

@martijnversluis
martijnversluis merged commit 6508f2b into main Jun 3, 2026
1 check passed
@martijnversluis
martijnversluis deleted the handle-missing-final-stop-active-observation branch June 3, 2026 08:01
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.

1 participant