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 ¶
const ReportOnlyKWh = 5.0
ReportOnlyKWh: rows under this energy are reported, never deleted (devils-advocate: low-value trickle cases are ambiguous).
Variables ¶
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.
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 ¶
MonthTotals is the counted energy and cost of the rows to delete, per month (for the dry-run summary: "counted vs resulting").
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 RestoreRows ¶
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.