Skip to content

Extract DB records from compound state tuples: simple atoms (Phase 5 final)#5259

Open
brianmay wants to merge 6 commits intomcu2-upgraded-carsfrom
refactor/extract-records-from-states
Open

Extract DB records from compound state tuples: simple atoms (Phase 5 final)#5259
brianmay wants to merge 6 commits intomcu2-upgraded-carsfrom
refactor/extract-records-from-states

Conversation

@brianmay
Copy link
Copy Markdown
Collaborator

@brianmay brianmay commented Apr 5, 2026

Summary

This PR completes the state machine simplification by replacing compound state tuples like {:driving, status, %Log.Drive{}}, {:charging, %Log.ChargingProcess{}}, and {:updating, %Log.Update{}} with simple atoms (:driving, :charging, :updating). DB records and sub-states are stored in Data struct fields.

Changes (4 commits)

Commit 1: Add preparatory fields to Data struct

Adds current_drive, current_charging_process, current_update, and driving_status fields.

Commit 2: Populate Data struct fields in parallel with compound state tuples

Every transition into a compound state now also updates the corresponding struct fields.

Commit 3: Migrate compound state reads to struct field guards

Replaces pattern-match reads with guards on the struct fields.

Commit 4: Replace compound state tuples with simple atoms throughout state machine

This is the main simplification commit. After this:

  • The GenState state is always a simple atom
  • {:driving, status, drive}:driving (status in data.driving_status, drive in data.current_drive)
  • {:charging, cproc}:charging (cproc in data.current_charging_process)
  • {:updating, update}:updating (update in data.current_update)

Key changes in this commit:

  • All 12 next_state positions now use simple atoms
  • Driving sub-state machine (available → unavailable → offline) is tracked in data.driving_status instead of the state tuple's second element
  • All driving handlers match :driving with guards on driving_status
  • suspend_logging, fetch/can_fall_asleep, and fetch/reachable all use simple atom matching
  • Summary module updated to handle the new state format

Refactoring sequence (complete!)

  1. ✅ Replace fake_online_state integers with pre_online_check atoms
  2. ✅ Add struct fields + populate them + migrate reads
  3. This PR: Replace compound states with simple atoms

Before vs After

Before (hard to read):

def handle_event(:internal, {:update, {:offline, _}}, {:driving, {:unavailable, n}, drv}, ...)
def handle_event(:internal, {:update, {:offline, _}}, {:driving, {:offline, last}, drive}, ...)
def handle_event(:info, {:stream, %Stream.Data{} = stream_data}, {:driving, status, drv}, ...)

After (clean):

def handle_event(:internal, {:update, {:offline, _}}, :driving, %Data{driving_status: {:unavailable, n}}) do ...
def handle_event(:internal, {:update, {:offline, _}}, :driving, %Data{driving_status: {:offline, last}}) do ...
def handle_event(:info, {:stream, %Stream.Data{}}, :driving, %Data{driving_status: :available, current_drive: drv}) do ...

Testing

All commits are no-behaviour-change mechanical refactors. Existing tests pass.

brianmay added 2 commits April 6, 2026 09:01
…compound states

These fields prepare for the next refactoring step where we'll move DB records
out of compound state tuples like {:driving, status, %Log.Drive{}} into dedicated
Data struct fields.

This commit only adds the fields without using them yet, keeping the change
minimal and focused. Future commits will:
1. Update state transitions to populate these fields
2. Update pattern matches to read from fields instead of state tuples
3. Remove compound states in favor of simple atoms

No behavior changes in this commit.
Each transition into {:driving, ...}, {:charging, ...}, or {:updating, ...}
now also updates the corresponding Data struct fields:

  - current_drive / driving_status   (all 9 driving transitions)
  - current_charging_process         (both charging entry points)
  - current_update                   (single update entry)

The compound state tuples are unchanged so all existing pattern matches
continue to work. The struct fields mirror the tuple contents and will be
used for reads in a future PR, at which point the compound tuples can be
replaced with simple atoms.

No behaviour changes.
@brianmay brianmay changed the title Add preparatory fields to Data struct for extracting DB records Extract DB records from compound state tuples: preparatory Data struct changes Apr 5, 2026
@brianmay
Copy link
Copy Markdown
Collaborator Author

brianmay commented Apr 5, 2026

This is only the first phase so far, we haven't seen the simplification to the compound state tuples yet. That will come later.

@brianmay
Copy link
Copy Markdown
Collaborator Author

brianmay commented Apr 5, 2026

As before, this was done with AI. It was going to make one big pull request, then we agreed that breaking it up might be better.

This PR migrates all pattern-match reads of compound state tuples
({:driving, status, drive}, {:charging, cproc}, {:updating, update})
to use the corresponding Data struct fields instead.

Changes:
- vehicle_in_service driving case: guard on data.current_drive != nil,
  read drive from data.current_drive, clear both current_drive and
  driving_status on exit from driving state
- remaining_distance interval: use {current_drive, current_charging_process}
  tuple instead of compound state pattern match
- asleep handler: guard on driving_status != nil && driving_status != :available
  to detect offline sub-states, clear driving_status on :start exit
- fetch can_fall_asleep?: add guards on current_drive/update/charging_process
- fetch reachable: same guards added
- All 6 transitions to :start from driving state now clear driving_status: nil

The compound state tuples remain in next_state positions (handled in prior
PR), so this is purely a read migration. No behaviour changes expected.

No behaviour changes - all guards are guaranteed true at runtime given
correct dual-write population from prior PR.
@brianmay brianmay changed the title Extract DB records from compound state tuples: preparatory Data struct changes Extract DB records from compound state tuples: struct fields + read migration Apr 11, 2026
This is the final step in the state machine simplification. After this
commit, the GenState state is always a simple atom (:driving, :charging,
:updating, :start, :asleep, :offline, :online, :suspended) instead of
compound tuples like {:driving, status, drive}.

Changes:
- All 12 next_state positions now use simple atoms instead of compound
  tuples {:driving, :available, drive}, {:driving, {:unavailable, n}, drv},
  {:driving, {:offline, last}, drive}, {:charging, cproc},
  {:updating, update}
- Driving sub-state (available/unavailable/offline) is tracked in
  data.driving_status instead of the state tuple's second element
- DB records (drive, cproc, update) are read from data.current_drive,
  data.current_charging_process, data.current_update
- All driving handlers now match :driving with guards on driving_status:
  :available, {:unavailable, n}, {:offline, last}
- suspend_logging handlers updated to match simple atoms
- fetch/can_fall_asleep and fetch/reachable updated to use simple atoms
- Summary module updated: format_state/2 now takes driving_status to
  correctly format :driving/:offline display state
- broadcast_summary passes driving_status to Summary.into

This change makes the state machine significantly more readable. The 3-element
tuple {:driving, status, drive} is now just :driving, with status and
drive accessible as data.driving_status and data.current_drive.
@brianmay brianmay changed the title Extract DB records from compound state tuples: struct fields + read migration Extract DB records from compound state tuples: simple atoms (Phase 5 final) Apr 11, 2026
@brianmay
Copy link
Copy Markdown
Collaborator Author

brianmay commented Apr 11, 2026

I decided to do the full refactor in one pull request. While this the PR more complicated, don't think you can really review this probably unless you can see the entire picture.

The steps are still there, but as separate commits. Which probably should stay as separate commits.

Key questions before merging:

  1. Does this make things simpler and easier to understand?

I think by making the state an enum only and putting state data into the data variable we have made it easier to understand. If anyone disagrees, feel free to speak out :-)

  1. Is it correct?

It looks OK to me... Although certainly is scope for errors to creep in. Guess next step will be to test this.

The interval calculation for API errors was unnecessarily restructured
during the read-migration phase. The original case on state was still
correct at that point and should have remained unchanged.

With simple atoms now in place, the correct form is restored:
  :driving -> 10
  :charging -> 15
  :online -> 20
  _ -> 30

This is a 4-line case expression instead of the 14-line restructure
that was added during read migration.
@brianmay brianmay force-pushed the refactor/extract-records-from-states branch from f2d2ed5 to 2f1f3fe Compare April 11, 2026 04:18
@brianmay
Copy link
Copy Markdown
Collaborator Author

I spotted a place where the logic had been changed and was more complicated then required. Fixed now.

The guards 'when data.current_drive != nil' etc. on the simple atom
patterns ':driving', ':updating', ':charging' are unnecessary. The state
atom itself is the source of truth - if we're in ':driving', the
dual-write invariant guarantees current_drive is populated. The guards
added nothing over the atom pattern match.
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