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:
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
- 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.
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_hoursis declared"default": 8(a bare int), soget_arg("solcast_poll_hours", 8)returnsint(float(4.8)) = 4, andsolcast.pycomputesmax_age = 4 * 60 = 240minutes.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": 0as an int) by normalising int defaults to float inget_ha_config().solcast_poll_hoursis not a CONFIG_ITEMS entry: it is resolved through the component framework, whereComponents.initialize()passesCOMPONENT_LIST's spec default directly toget_arg(). Theget_ha_config()normalisation cannot reach this path because there is noconfig_indexentry 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:solcast_poll_hours8max_age = x * 60forecast_solar_max_age8max_age = x * 60open_meteo_forecast_max_age4max_age = x * 60axle_pence_per_kwh100(Plausible further candidates with int defaults and fractional semantics:
octopus_saving_session_rate,car_charging_battery_size.)Expected behaviour
Either:
solcast_poll_hours: 4.8works as documented (the docs at apps-yaml/#solcast-solar-forecast recommend 4.8 for two-array hobbyist accounts to hit exactly 10 polls/day), orSuggested 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 writing8vs8.0can't change behaviour, rather than hand-editing the four literals above. If a narrower change is preferred, the one-line alternative is float literals inCOMPONENT_LIST(8.0,8.0,4.0,100.0). A warning on lossy coercion inget_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 tomax_age == 288minutes.Workaround in the meantime: set an integer —
solcast_poll_hours: 5gives 5 refreshes/day = 10 calls, the same outcome the 4.8 documentation recommendation was aiming for.Version: v8.54.2. Still present on
mainas of 2026-09-03 —apps/predbat/components.pystill declares"default": 8for solcast_poll_hours.