Skip to content

Conversation

@TylonHH
Copy link

@TylonHH TylonHH commented Jan 2, 2026

Summary

  • add a percent-based smart cost limit that derives an effective threshold from the daily average tariff
  • expose smartCostLimitPercent via config, HTTP, and MQTT, alongside existing absolute limits
  • add UI controls to set a relative limit percentage (and translations)
grafik

Description

This change introduces a percent-based smart cost limit derived from the daily average tariff. The effective price threshold adjusts automatically each day and complements the existing absolute smart cost limits.

Motivation

Fixed price limits work poorly with dynamic electricity tariffs. Daily price levels vary significantly, and a static threshold often prevents charging entirely on days where prices never drop below the configured value. A relative limit solves this by adapting to the daily market conditions.

Testing

  • tested localy

Why a percent-based limit is better for dynamic tariffs

  • Daily prices shift up and down. A fixed limit often blocks charging for a full day.
  • A relative limit follows the daily average. Charging still targets the cheapest known hours.
  • Users avoid long idle periods while waiting for an absolute price point.
  • All low-price windows get used, even on generally expensive days.
  • No manual reconfiguration after seasonal or market changes.
  • Easier setup. Users select a percentage instead of guessing a price.
  • Predictable behavior. Charging always focuses on the lower end of the daily price curve.
  • Matches how dynamic tariffs work. Relative ranking matters more than absolute values.
  • Reduces configuration churn and support questions.
  • Improves energy utilization under volatile market conditions.

Scope of change

  • Add percent-based smart cost limit derived from daily average tariff
  • Expose smartCostLimitPercent via config, HTTP, and MQTT
  • Keep absolute and relative limits side by side
  • Add UI control for relative limit percentage
  • Add translations

Example behavior
If the daily average price is 0.30 EUR/kWh and the limit is set to 80 percent, charging is allowed at or below 0.24 EUR/kWh. This adapts automatically every day.

Note
This is my first pull request on GitHub. Many parts of the workflow are new to me. If anything is structured incorrectly or does not follow project conventions, please let me know. I am happy to adjust and learn.

@evcc-bot evcc-bot added enhancement New feature or request tariffs Specific tariff support labels Jan 2, 2026
@TylonHH TylonHH marked this pull request as ready for review January 2, 2026 12:34
Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In effectiveSmartCostLimit, when percent is set but averageRateValue cannot be computed you return (nil, true), which causes Update to publish a SmartCostLimit of nil; consider leaving the previous absolute/derived limit unchanged in this case to avoid surprising users when tariff data is temporarily unavailable.
  • The duration‑weighted average calculations for tariffs are now implemented both in the frontend (averageTariffValue in SmartCostLimit.vue) and backend (averageRateValue in smart_cost_limit.go); consider aligning these via a shared helper or at least ensuring their formulas and edge‑case handling (zero/invalid durations) stay in sync.
  • In SmartTariffBase.toggleRelative, when switching off the relative limit you call saveLimit(this.lastLimit) even if lastLimit is null; it may be safer to guard against null and either skip saving or fall back to a reasonable default to avoid emitting an unintended save-limit payload.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `effectiveSmartCostLimit`, when percent is set but `averageRateValue` cannot be computed you return `(nil, true)`, which causes `Update` to publish a `SmartCostLimit` of `nil`; consider leaving the previous absolute/derived limit unchanged in this case to avoid surprising users when tariff data is temporarily unavailable.
- The duration‑weighted average calculations for tariffs are now implemented both in the frontend (`averageTariffValue` in `SmartCostLimit.vue`) and backend (`averageRateValue` in `smart_cost_limit.go`); consider aligning these via a shared helper or at least ensuring their formulas and edge‑case handling (zero/invalid durations) stay in sync.
- In `SmartTariffBase.toggleRelative`, when switching off the relative limit you call `saveLimit(this.lastLimit)` even if `lastLimit` is `null`; it may be safer to guard against `null` and either skip saving or fall back to a reasonable default to avoid emitting an unintended `save-limit` payload.

## Individual Comments

### Comment 1
<location> `assets/js/components/Tariff/SmartTariffBase.vue:468-474` </location>
<code_context>
+				this.saveLimit(this.lastLimit);
+			}
+		},
+		changeRelative($event: Event) {
+			const value = parseFloat(($event.target as HTMLInputElement).value);
+			if (Number.isNaN(value)) {
+				return;
+			}
+			this.relativePercent = value;
+			if (this.relativeActive) {
+				this.saveRelativeLimit(value);
+			}
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Clamp relative percentage to the backend’s allowed range before emitting.

The backend `SetSmartCostLimitPercent` rejects values outside 0–200, but the UI can currently send any number (e.g., via devtools or programmatic changes). Please clamp `value`/`this.relativePercent` to [0, 200] before calling `saveRelativeLimit`, and do the same in `toggleRelative` when defaulting to `90`, so the UI state always matches what the backend accepts.

Suggested implementation:

```
		changeRelative($event: Event) {
			const value = parseFloat(($event.target as HTMLInputElement).value);
			if (Number.isNaN(value)) {
				return;
			}
			const clamped = Math.min(200, Math.max(0, value));
			this.relativePercent = clamped;
			if (this.relativeActive) {
				this.saveRelativeLimit(clamped);
			}

```

```
				if (this.relativePercent === null) {
					this.relativePercent = 90;
				}
				this.relativePercent = Math.min(200, Math.max(0, this.relativePercent));
				this.saveRelativeLimit(this.relativePercent);

```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@andig
Copy link
Member

andig commented Jan 2, 2026

This is a bit subjective. I don‘t think we want to do this since it can be added externally via API. Also, optimizer will eventually remove the need for this logic.

@andig andig closed this Jan 3, 2026
@andig andig added the wontfix This will not be worked on label Jan 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request tariffs Specific tariff support wontfix This will not be worked on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants