Skip to content

solcast_poll_hours 4.8 is silently truncated to 4h, exhausting the Solcast daily quota #4925

Description

@wwicks

Found and drafted with help of GLM 5.3 Flash in the new AI chat.

predbat (6).log

Describe the bug

UserInterface.get_arg() coerces the resolved value to the Python type of the default it was given:
elif isinstance(default, int) and not isinstance(default, bool):
Convert to int?
value = int(float(value)) # userinterface.py — truncates 4.8 -> 4

Component args are resolved through this path with the defaults declared in COMPONENT_LIST (components.py). solcast_poll_hours is declared "default": 8 (a bare int), so get_arg("solcast_poll_hours", 8) returns int(float(4.8)) = 4, and solcast.py computes max_age = 4 * 60 = 240 minutes.

With two Solcast sites this causes 6 refreshes/day (12 API calls) instead of the documented 5/day (10 calls), which exhausts the hobbyist 10 poll/day limit and produces nightly HTTP 429 failures. Validation doesn't catch this because it validates the raw apps.yaml value (a valid float, per APPS_SCHEMA "type": "float") before get_arg runs — and there is no warning on the lossy coercion, so apps.yaml still says 4.8 while the runtime behaves as 4.

Related: #4296 / #4441 — same mechanism, different code path

#4441 fixed this exact truncation class for CONFIG_ITEMS-driven reads (metric_battery_cycle 0.5 → 0 via the same int(float(value)), because CONFIG_ITEMS declared "default": 0 as an int) by normalising int defaults to float in get_ha_config(). solcast_poll_hours is not a CONFIG_ITEMS entry: it is resolved through the component framework, where Components.initialize() passes COMPONENT_LIST's spec default directly to get_arg(). The get_ha_config() normalisation cannot reach this path because there is no config_index entry to match. v8.54.2 includes #4441 (merged 2026-08-08) and still truncates.

Scope — other keys on the same path

Same int-default/float-schema pattern, all confirmed in the installed source and on main:

Key COMPONENT_LIST default APPS_SCHEMA type Consumer Impact if user configures a fraction
solcast_poll_hours 8 float solcast.py:555 max_age = x * 60 poll TTL 4h instead of 4.8h → quota overuse → 429s
forecast_solar_max_age 8 float solcast.py:458 max_age = x * 60 Forecast.Solar cache TTL silently shortened
open_meteo_forecast_max_age 4 float solcast.py:236/310 max_age = x * 60 Open-Meteo cache TTL silently shortened
axle_pence_per_kwh 100 float Axle pricing money: configured 12.5p/kWh becomes 12p

(Plausible further candidates with int defaults and fractional semantics: octopus_saving_session_rate, car_charging_battery_size.)

Expected behaviour

Either:

  1. solcast_poll_hours: 4.8 works as documented (the docs at apps-yaml/#solcast-solar-forecast recommend 4.8 for two-array hobbyist accounts to hit exactly 10 polls/day), or
  2. at minimum, a warning is logged whenever get_arg's coercion is lossy — the failure is otherwise invisible: the YAML still says 4.8 and validation passes.

Suggested fix

Mirror #4441's "fix at the source" approach on the component path: when resolving arg specs in Components.initialize(), normalise an int default to float where APPS_SCHEMA declares the key "type": "float" — so a future component author writing 8 vs 8.0 can't change behaviour, rather than hand-editing the four literals above. If a narrower change is preferred, the one-line alternative is float literals in COMPONENT_LIST (8.0, 8.0, 4.0, 100.0). A warning on lossy coercion in get_arg() would also be worth having regardless.

Regression test: extend apps/predbat/tests/test_integer_config.py (the #4296 home) — assert that a component arg spec with an int default and an APPS_SCHEMA float type resolves a configured 4.8 to max_age == 288 minutes.

Workaround in the meantime: set an integer — solcast_poll_hours: 5 gives 5 refreshes/day = 10 calls, the same outcome the 4.8 documentation recommendation was aiming for.

Version: v8.54.2. Still present on main as of 2026-09-03 — apps/predbat/components.py still declares "default": 8 for solcast_poll_hours.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BOT_PR_FAILEDBot PR flow's quality gate failed; see the comment for detailsBOT_TRIAGEDHas been through the triage bot

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions