phantom

package
v0.36.21 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

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). It deletes only the ids it is given, re-checks the classification rule at run time, backs every row up (table + CSV) before deleting, deletes per vehicle in one transaction and can restore a batch.

Nothing here is automatic: a human supplies the audited id list.

Index

Constants

View Source
const ReportOnlyKWh = 5.0

ReportOnlyKWh: rows under this energy are reported, never deleted (devils-advocate: low-value trickle cases are ambiguous).

Variables

View Source
var ErrDrift = errors.New("phantom: rows changed since the dry run; nothing deleted for this vehicle")

ErrDrift is returned when rows no longer classify as phantoms at apply time (they changed since the dry run).

Functions

func Apply

func Apply(ctx context.Context, d *sql.DB, batchID, userID, vehicleID uuid.UUID, ids []uuid.UUID, reason string, sink func([]BackupRow) error) (int, error)

Apply deletes the given rows of ONE vehicle in a single transaction: it re-loads and re-classifies them inside the transaction (any row that no longer qualifies aborts the vehicle and every changed id is listed), collects every row and cascading dependent, hands them to sink (the durable CSV; an error aborts before anything is deleted), copies them to charges_deleted_backup when that table exists, deletes, and aborts unless the counts match.

func BackupTableExists

func BackupTableExists(ctx context.Context, d *sql.DB) (bool, error)

BackupTableExists reports whether charges_deleted_backup is present (migration 0049). Production may not have it yet; the CSV sink is then the only backup.

Types

type BackupRow

type BackupRow struct {
	Kind     string          `json:"kind"` // charge | pack_health_sample | charge_restamp
	ChargeID uuid.UUID       `json:"charge_id"`
	UserID   uuid.UUID       `json:"user_id"`
	Row      json.RawMessage `json:"row"`
}

BackupRow is one row copied out before deletion. Row is the full to_jsonb of the deleted row; restoring re-inserts it exactly.

type MonthTotals

type MonthTotals struct {
	Month string
	Rows  int
	KWh   float64
	Cost  float64
}

MonthTotals is the counted energy and cost of the rows to delete, per month (for the dry-run summary: "counted vs resulting").

func Summarise

func Summarise(rows []Row, verdicts map[uuid.UUID]Verdict) []MonthTotals

Summarise groups the deletable rows by UTC month.

type Queryer

type Queryer interface {
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
}

Queryer is satisfied by *sql.DB and *sql.Tx.

type RestoreResult

type RestoreResult struct {
	Charges, PackHealth, Restamps int
}

RestoreResult is what a restore put back.

func Restore

func Restore(ctx context.Context, d *sql.DB, batchID uuid.UUID) (RestoreResult, error)

Restore re-inserts a batch from charges_deleted_backup, then marks it restored.

func RestoreRows

func RestoreRows(ctx context.Context, d *sql.DB, rows []BackupRow) (RestoreResult, error)

RestoreRows re-inserts rows read from a CSV backup (no backup table needed). Same refusals as Restore.

type Row

type Row struct {
	ID         uuid.UUID
	ExternalID string
	UserID     uuid.UUID
	VehicleID  uuid.UUID
	RivianID   string
	StartedAt  time.Time
	EndedAt    time.Time
	EnergyKWh  float64
	StartSoC   *float64
	EndSoC     *float64
	PackKWh    *float64
	MaxPowerKW *float64
	Cost       *float64
	Currency   string
	FinalState string
	CostSource string
	Source     string
	// PowerKWh is the integral of vehicle_state.charger_power_kw over
	// the row's window, counting only charging_active frames with each
	// gap capped at 10 min (stale non-zero power after pre-#51 charges
	// must not inflate it).
	PowerKWh float64
	// HasRestamp: a charge_restamps row (a user's place correction)
	// points at it. User edits are never deleted.
	HasRestamp bool
}

Row is one charge with what the rule needs.

func Load

func Load(ctx context.Context, q Queryer, userID uuid.UUID, ids []uuid.UUID) ([]Row, error)

Load reads the requested ids for one user, in id order not guaranteed, with the power integral and restamp flag. Ids not found (or another user's) are simply absent: the caller reports them.

type Verdict

type Verdict struct {
	Delete bool
	// Reason explains a skip ("" when Delete).
	Reason string
}

Verdict is the rule's answer for one row.

func Classify

func Classify(r Row) Verdict

Classify applies the rule. Order matters: user edits and out-of-scope rows are refused before the physics test.

Jump to

Keyboard shortcuts

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