repair

package
v0.38.21 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package repair holds one-off data repairs run from the rivolt CLI.

Fragments (repair-charge-fragments): a single physical charging session that the recorder stored as several back-to-back rows, each carrying the session's RUNNING energy total, so the ledger counts the energy once per piece (2026-10-10: 11 pieces, 496.6 kWh counted for ~82 kWh). Cause: a replayed stale Parallax gear frame made the recorder close and reopen the charge on every resubscribe. This package finds those runs and merges each into one row.

Everything here is dry-run first and reversible: the caller writes a CSV backup of every touched row before Apply.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Apply

func Apply(ctx context.Context, d *sql.DB, g Group) error

Apply merges one group in a single transaction: the keeper takes the session's start, start SoC, end, final state, peak power and active seconds, and the other pieces are deleted. Every statement is scoped by user_id and guarded by the rows still being what Load saw.

func FindGroups

func FindGroups(ps []Piece, maxGap time.Duration) ([]Group, []Skipped)

FindGroups returns the fragment runs in ps and the runs it refuses.

Pieces chain when they are live charges of one user and vehicle, the next starts within maxGap of the previous end (stale-gear flaps leave ~3 s gaps and ~178 s gaps, one resubscribe TTL), the previous was closed 'abandoned', no drive happened in between, and the next piece is a RUNNING TOTAL rather than an independent session:

  • its energy is not below the previous energy, and
  • it claims more than its own SoC gain can explain (energy > 1.3 x own gain + 1 kWh), or it repeats the previous energy while its own SoC gain is ~0 (a stale duplicate).

A piece whose SoC gain explains its energy is its own session (a real unplug and re-plug) and ends the run. Missing SoC data means "cannot verify": the run ends. Null-energy pieces ride along (they are removed, never kept). A group needs two or more pieces and at least one with energy. A group whose removed pieces carry a user price or a restamp is returned as skipped, not merged.

Types

type Group

type Group struct{ Pieces []Piece }

Group is a run of two or more pieces of one session, in time order. The keeper is the LAST piece that has an energy value; every other piece (including null-energy ones) is removed.

func (Group) Keeper

func (g Group) Keeper() Piece

Keeper is the row that survives (the zero Piece if no piece has energy; FindGroups never returns such a group).

func (Group) Plan

func (g Group) Plan() Merged

Plan computes the merged row. The keeper keeps its own energy (the session's running total) and its own cost (priced on that total); it takes the first piece's start and start SoC, the peak power, the summed active seconds, and the last piece's end and final state.

func (Group) Removed

func (g Group) Removed() []Piece

Removed are the rows deleted.

type Merged

type Merged struct {
	StartedAt     time.Time
	EndedAt       time.Time
	FinalState    string
	StartSoC      *float64
	MaxPowerKW    *float64
	ActiveSeconds *float64
	EnergyKWh     *float64
	Cost          *float64
	// CountedKWh / CountedCost are what the pieces contribute today.
	CountedKWh  float64
	CountedCost float64
}

Merged is what the keeper becomes.

type Piece

type Piece struct {
	ID            uuid.UUID
	ExternalID    string
	UserID        uuid.UUID
	Username      string
	VehicleID     uuid.UUID
	RivianID      string
	StartedAt     time.Time
	EndedAt       time.Time
	StartSoC      *float64
	EndSoC        *float64
	EnergyKWh     *float64
	MaxPowerKW    *float64
	ActiveSeconds *float64
	Cost          *float64
	Currency      string
	FinalState    string
	CostSource    string
	Source        string
	// PackKWh is the vehicle's pack capacity (nil = unknown).
	PackKWh *float64
	// DriveBefore is true when a drive of the same vehicle falls
	// between the previous live charge's end and this piece's start.
	DriveBefore bool
	// HasRestamp is true when a charge_restamps row (a user's place
	// correction) points at this piece.
	HasRestamp bool
}

Piece is one charges row considered for merging.

func Load

func Load(ctx context.Context, d *sql.DB, user string, since, until time.Time, maxGap time.Duration) ([]Piece, error)

Load reads the candidate pieces: live charges within maxGap of another live charge of the same vehicle. user ("" = all) filters by username; since/until (zero = open) bound started_at.

type Skipped

type Skipped struct {
	Group  Group
	Reason string
}

Skipped is a candidate group the repair refuses to touch.

Directories

Path Synopsis
Package phantom is the repair for phantom (stale-copy) live charges: rows the recorder fabricated from a replayed previous-session total, with no power and no SoC change behind them (architect's scratchpad/phantom-charges.md; devils-advocate's audit).
Package phantom is the repair for phantom (stale-copy) live charges: rows the recorder fabricated from a replayed previous-session total, with no power and no SoC change behind them (architect's scratchpad/phantom-charges.md; devils-advocate's audit).
Package r2 rebuilds the drives and charges of a vehicle whose vehicleState gear was unusable (R2 before #214: the gear stayed at the last D/R for days, so drives never closed and no charge opened).
Package r2 rebuilds the drives and charges of a vehicle whose vehicleState gear was unusable (R2 before #214: the gear stayed at the last D/R for days, so drives never closed and no charge opened).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL