Skip to content

Implemented charge windows instead of just picking random low price s…#951

Draft
fredli74 wants to merge 20 commits intomasterfrom
charge_window_planner
Draft

Implemented charge windows instead of just picking random low price s…#951
fredli74 wants to merge 20 commits intomasterfrom
charge_window_planner

Conversation

@fredli74
Copy link
Owner

@fredli74 fredli74 commented Feb 6, 2026

No description provided.

…egments

Signed-off-by: Fredrik Lidström <fredli74@gmail.com>
@fredli74
Copy link
Owner Author

fredli74 commented Feb 6, 2026

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f4aff26557

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request implements a sophisticated charge window scheduling algorithm that replaces the simpler approach of just picking random low-price slots. The new system intelligently schedules charge windows by considering price data, time constraints, and multiple competing charge intents (manual, trip, routine, fill) to optimize both cost and charging behavior.

Changes:

  • Replaced the old GeneratePlan function with a new "soft intents" model that collects all charge requirements first, then schedules them optimally across available price windows
  • Implemented a greedy sliding-window algorithm to find optimal charge windows that minimize cost while respecting constraints like minimum window duration (to avoid fragmentation in cold weather) and maximum price thresholds
  • Added support for splitting charge sessions across multiple windows when cost savings justify it, with configurable thresholds for when to split vs. consolidate charging

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

fredli74 and others added 6 commits February 6, 2026 02:46
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Fredrik Lidström <fredli74@gmail.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@fredli74
Copy link
Owner Author

fredli74 commented Feb 6, 2026

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 289a128246

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Signed-off-by: Fredrik Lidström <fredli74@gmail.com>
… quantumMs

Signed-off-by: Fredrik Lidström <fredli74@gmail.com>
…ehicle not waking up to charge, cause it though the level was ok already

Signed-off-by: Fredrik Lidström <fredli74@gmail.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Signed-off-by: Fredrik Lidström <fredli74@gmail.com>
@fredli74
Copy link
Owner Author

@codex review

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1165 to +1168
// DP over (startSlot, neededSteps). Gap penalty is modeled as extra score cost, not extra time.
const dpTable = Array.from({ length: numSlots + 1 }, () => new Array(timeNeededSteps + 1).fill(Infinity));
const dpWindows = Array.from({ length: numSlots + 1 }, () => new Array(timeNeededSteps + 1).fill(Infinity));
const choiceTable = Array.from({ length: numSlots + 1 }, () => new Array(timeNeededSteps + 1).fill(null));
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dynamic programming algorithm creates three large 2D arrays (dpTable, dpWindows, choiceTable) with dimensions (numSlots + 1) × (timeNeededSteps + 1). With many price slots and long charging durations, this could consume significant memory. For example, 100 slots and 1000 steps would create arrays with 100,000 elements each. Consider adding bounds checking or logging warnings when the arrays become very large.

Suggested change
// DP over (startSlot, neededSteps). Gap penalty is modeled as extra score cost, not extra time.
const dpTable = Array.from({ length: numSlots + 1 }, () => new Array(timeNeededSteps + 1).fill(Infinity));
const dpWindows = Array.from({ length: numSlots + 1 }, () => new Array(timeNeededSteps + 1).fill(Infinity));
const choiceTable = Array.from({ length: numSlots + 1 }, () => new Array(timeNeededSteps + 1).fill(null));
// Estimate DP memory usage and warn if very large.
const dpRows = numSlots + 1;
const dpCols = timeNeededSteps + 1;
const dpCellsPerTable = dpRows * dpCols;
const dpTablesCount = 3;
const totalDpCells = dpCellsPerTable * dpTablesCount;
const LARGE_DP_THRESHOLD = 500_000;
if (totalDpCells > LARGE_DP_THRESHOLD) {
vehicleLog(
LogLevel.Warn,
vehicle.vehicle_uuid,
`scheduleWindows(${scheduleTag}): large DP allocation: ` +
`slots=${numSlots} steps=${timeNeededSteps} cellsPerTable=${dpCellsPerTable} totalCells=${totalDpCells}`
);
}
// DP over (startSlot, neededSteps). Gap penalty is modeled as extra score cost, not extra time.
const dpTable = Array.from({ length: dpRows }, () => new Array(dpCols).fill(Infinity));
const dpWindows = Array.from({ length: dpRows }, () => new Array(dpCols).fill(Infinity));
const choiceTable = Array.from({ length: dpRows }, () => new Array(dpCols).fill(null));

Copilot uses AI. Check for mistakes.
// Convert to numeric charge plans with start and stop times rounded to 15 minutes
.map((p): NumericChargePlan => {
if (wantedSoc === undefined && p.level) {
if (p.level !== undefined && (wantedSoc === undefined || wantedSoc < p.level)) {
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition has been changed from checking if wantedSoc === undefined to checking if p.level !== undefined && (wantedSoc === undefined || wantedSoc < p.level). This changes the logic to update wantedSoc to the maximum level from all charge plans, not just the first one encountered. While this may be intentional to find the highest charge level needed, it could cause unexpected behavior if multiple plans specify different levels and the intent was to use a specific one. Verify this behavior is correct for your use case.

Suggested change
if (p.level !== undefined && (wantedSoc === undefined || wantedSoc < p.level)) {
if (wantedSoc === undefined && p.level !== undefined) {

Copilot uses AI. Check for mistakes.
Comment on lines +142 to 143
vehicleLog(LogLevel.Debug, input.id, `updateVehicle: ${JSON.stringify(input)}`);
await context.db.getVehicle(accountFilter(context.accountUUID), input.id);
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The vehicleLog is being called with input.id before the vehicle is retrieved from the database. If the vehicle doesn't exist or the user doesn't have access, this will log with an incorrect vehicle UUID. The log should be moved after line 143 where the vehicle is retrieved, and use vehicle.vehicle_uuid instead.

Suggested change
vehicleLog(LogLevel.Debug, input.id, `updateVehicle: ${JSON.stringify(input)}`);
await context.db.getVehicle(accountFilter(context.accountUUID), input.id);
const vehicle = await context.db.getVehicle(accountFilter(context.accountUUID), input.id);
vehicleLog(LogLevel.Debug, vehicle.vehicle_uuid, `updateVehicle: ${JSON.stringify(input)}`);

Copilot uses AI. Check for mistakes.
// verify vehicle ownage
log(LogLevel.Debug, `updateSchedule: ${JSON.stringify(id)} ${JSON.stringify(vehicleID)}`);
const vehicle = await context.db.getVehicle(accountFilter(context.accountUUID), vehicleID);
vehicleLog(LogLevel.Debug, vehicle.vehicle_uuid, `updateSchedule: ${JSON.stringify(id)} ${JSON.stringify(vehicleID)}`);
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The vehicleLog is being called with input.id before the vehicle is retrieved from the database. If the vehicle doesn't exist or the user doesn't have access, this will log with an incorrect vehicle UUID. The log should be moved after line 216 where the vehicle is retrieved, and use vehicle.vehicle_uuid instead.

Copilot uses AI. Check for mistakes.
log(LogLevel.Trace, `TeslaAPI.renewToken(${redactSecret(refresh_token)}) response: ${JSON.stringify({ expires_in: authResponse?.expires_in })}`);
return this.parseTokenResponse(authResponse);
} catch (e) {
console.debug(`TeslaAPI.renewToken(${refresh_token}) error: ${e}`);
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handler logs the unredacted refresh_token in the error message. This could expose sensitive credentials in logs. Consider using redactSecret(refresh_token) here as well, consistent with the success path logging.

Copilot uses AI. Check for mistakes.
Comment on lines +1028 to +1031
setSmartStatusFromIntent(allocations[0], "scheduled");
const sorted = windows.slice().sort((a, b) => a.start - b.start);
let i = 0;
let remaining = allocations.length > 0 ? allocations[0].durationMs : 0;
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The applyWindows function accesses allocations[0] without checking if the array is empty. While line 1028 calls setSmartStatusFromIntent(allocations[0], "scheduled") which safely handles undefined, if the function is ever called with an empty allocations array from other contexts, it could cause issues. Consider adding a guard check at the beginning of the function to return early if allocations is empty.

Suggested change
setSmartStatusFromIntent(allocations[0], "scheduled");
const sorted = windows.slice().sort((a, b) => a.start - b.start);
let i = 0;
let remaining = allocations.length > 0 ? allocations[0].durationMs : 0;
if (!allocations.length) {
return;
}
setSmartStatusFromIntent(allocations[0], "scheduled");
const sorted = windows.slice().sort((a, b) => a.start - b.start);
let i = 0;
let remaining = allocations[0].durationMs;

Copilot uses AI. Check for mistakes.
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8ec11594a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1061 to +1062
if (i.beforeTs !== undefined) intentDeadline = Math.min(intentDeadline, i.beforeTs);
if (i.level > intentMaxLevel) intentMaxLevel = i.level;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep each soft intent deadline when building charge windows

In scheduleSoftIntents, the planner collapses all soft intents into intentDeadline = min(beforeTs) and intentMaxLevel = max(level), then schedules that max level against the earliest deadline. This couples unrelated targets and can force high future targets to be charged too early (for example, a lower routine target due tomorrow plus a higher trip target due later gets planned as the higher level by tomorrow), which is a behavioral regression from the previous per-intent scheduling logic and increases unnecessary early charging.

Useful? React with 👍 / 👎.

logVehicle(LogLevel.Warning, v, `Telemetry config blocked by scopes; backoff ${Math.round(delayMs / 60e3)}m`);
}
}
return;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Await queued work before returning on telemetry scope errors

The missing scopes handler returns immediately from serviceWork, bypassing the final await Promise.all(waitFor) that normally joins already queued async work. When this branch is hit, previously started vehicleWork/telemetry API calls run detached from the service loop, so their failures are no longer observed here and the next poll can start while prior work is still in flight.

Useful? React with 👍 / 👎.

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

Comments