Skip to content

Add opt-in 15-minute PVPC price resolution / Resolución opcional de PVPC a 15 minutos - #14

Open
txubelaxu wants to merge 2 commits into
privatecoder:mainfrom
txubelaxu:feature/15min-pvpc
Open

Add opt-in 15-minute PVPC price resolution / Resolución opcional de PVPC a 15 minutos#14
txubelaxu wants to merge 2 commits into
privatecoder:mainfrom
txubelaxu:feature/15min-pvpc

Conversation

@txubelaxu

Copy link
Copy Markdown

EN

Context

Since Oct 2025, Spain's day-ahead/intraday electricity market settles per 15-minute slot instead of hourly (BOE-A-2025-4908, BOE-A-2025-5342, ENTSO-E 15-min ISP harmonization). ESIOS indicator 1001 (used today for PVPC) stays hourly regardless of time_trunc; indicator 791 ("Precio medio cuarto horario final comercializadores de referencia") is the genuinely quarter-hourly PVPC-equivalent series (96 values/day), confirmed against the live ESIOS API.

What this adds

An opt-in "Fetch PVPC at 15-minute resolution" toggle, reusing the existing private-API token (no new credential, no new config step) — selectable in the price_mode step of both the initial config flow and the options flow, so it's only offered once a token is already configured.

When enabled:

  • The PVPC series is fetched from indicator 791 instead of 1001.
  • "Current Price" and the price-series-derived attributes (Next Price, Next Best Price, Better Prices Ahead, Current/Next Price Level...) update every 15 min instead of hourly.
  • Tariff/power-period attributes (P1/P2/P3, available power) are untouched — those are clock-based by regulation, not price-resolution-based, so they stay hourly regardless of this setting.
  • The price_XXh per-hour attribute tags only pick the top-of-hour sample (avoids 4 quarter-hour values colliding into the same key — see below).

Disabled by default; existing hourly-mode behavior is unchanged.

Files touched

  • aiopvpc/const.py: new ESIOS_PVPC_QUARTER_HOURLY = "791".
  • aiopvpc/parser.py: get_daily_urls_to_download() takes an optional per-sensor data_id override.
  • aiopvpc/pvpc_data.py: PVPCData gets a quarter_hourly_pvpc flag; the "current price" lookup rounds to the current 15-min bucket instead of the top of the hour when enabled.
  • aiopvpc/prices.py: fixed a latent bug in _make_price_tag_attributes — with a quarter-hourly series, all 4 samples of an hour would collide into the same price_XXh key, incorrectly triggering the "DST duplicate hour" disambiguation logic 4x/hour. Now only the top-of-hour sample is tagged when quarter_hourly=True.
  • sensor.py: added a shared _current_price_bucket() helper; _next_price_candidate() and _num_better_prices_ahead() now use it instead of a hardcoded 1-hour jump. The two async_track_time_change triggers (ElecPriceSensor and update_on_hour attribute sensors) fire at :00/:15/:30/:45 instead of just :00 when the option is on.
  • config_flow.py / const.py / coordinator.py: new ATTR_QUARTER_HOURLY_PVPC option, default False, gated behind use_private_api.
  • strings.json / translations/es.json: label for the new option.

Testing

I don't have a PR-ready new automated test yet — happy to add one if you point me at the pattern you'd prefer (e.g. extending test_sensor_periods.py/test_price_mode.py). What I did verify:

  • Standalone checks (no HA needed) against the pure functions I touched: URL/indicator selection with and without the override, _make_price_tag_attributes producing exactly 24 non-colliding price_XXh keys from a synthetic 96-value quarter-hourly series (and unchanged behavior on a plain 24-value hourly series), and the minute→bucket rounding for all 60 minute values.
  • python -m py_compile on every touched file.
  • I could not run the existing tests/ suite in my environment: the latest pytest-homeassistant-custom-component available there pins a Home Assistant core older than AddConfigEntryEntitiesCallback (used in sensor.py), and this fails identically on a clean, unmodified checkout — so it's a pre-existing environment limitation on my end, not something introduced by this change. If your CI runs green, that's the real signal here.

I'm not on this integration's actively-tested HA version locally, so please treat this as a starting point for review rather than a finished, CI-verified PR.

Happy to adjust scope/naming/anything else you'd prefer.


ES

Contexto

Desde octubre de 2025, el mercado eléctrico español (diario/intradiario) liquida por franjas de 15 minutos en vez de por hora (BOE-A-2025-4908, BOE-A-2025-5342, armonización ISP de 15 min de ENTSO-E). El indicador ESIOS 1001 (el que se usa hoy para el PVPC) sigue siendo horario aunque se pida con time_trunc; el indicador 791 ("Precio medio cuarto horario final comercializadores de referencia") es la serie realmente de cuarto de hora equivalente al PVPC (96 valores/día), confirmado contra la API real de ESIOS.

Qué añade esto

Un interruptor opcional "Fetch PVPC at 15-minute resolution" (obtener el PVPC con resolución de 15 min), que reutiliza el token de la API privada ya existente (sin credencial nueva, sin paso de configuración nuevo) — seleccionable en el paso price_mode tanto del flujo de configuración inicial como del de opciones, de modo que solo se ofrece cuando ya hay un token configurado.

Cuando está activo:

  • La serie PVPC se obtiene del indicador 791 en vez del 1001.
  • "Current Price" y los atributos derivados de la serie de precios (Next Price, Next Best Price, Better Prices Ahead, Current/Next Price Level...) se actualizan cada 15 min en vez de cada hora.
  • Los atributos de periodo tarifario/de potencia (P1/P2/P3, potencia disponible) no se tocan — son horarios por normativa, no por la resolución del precio, así que siguen siendo horarios se active o no esta opción.
  • Las etiquetas del atributo por hora price_XXh solo recogen la muestra en punto (evita que choquen los 4 valores de cada hora, ver más abajo).

Desactivado por defecto; el comportamiento horario existente no cambia.

Ficheros modificados

  • aiopvpc/const.py: nuevo ESIOS_PVPC_QUARTER_HOURLY = "791".
  • aiopvpc/parser.py: get_daily_urls_to_download() admite un override opcional de data_id por sensor.
  • aiopvpc/pvpc_data.py: PVPCData incorpora un flag quarter_hourly_pvpc; la búsqueda del "precio actual" redondea al cuarto de hora en curso en vez de a la hora en punto cuando está activo.
  • aiopvpc/prices.py: arreglado un bug latente en _make_price_tag_attributes — con una serie de cuarto de hora, las 4 muestras de cada hora chocarían en la misma clave price_XXh, disparando incorrectamente 4 veces/hora la lógica de desambiguación de "hora duplicada por DST". Ahora solo se etiqueta la muestra en punto cuando quarter_hourly=True.
  • sensor.py: añadido un helper compartido _current_price_bucket(); _next_price_candidate() y _num_better_prices_ahead() lo usan en vez de un salto fijo de 1 hora. Los dos disparadores async_track_time_change (ElecPriceSensor y los sensores de atributo update_on_hour) se disparan en :00/:15/:30/:45 en vez de solo :00 cuando la opción está activa.
  • config_flow.py / const.py / coordinator.py: nueva opción ATTR_QUARTER_HOURLY_PVPC, por defecto False, condicionada a use_private_api.
  • strings.json / translations/es.json: etiqueta para la nueva opción.

Pruebas

Todavía no tengo un test automatizado nuevo listo para la PR — encantado de añadir uno si me indicas el patrón que prefieras (p.ej. extendiendo test_sensor_periods.py/test_price_mode.py). Lo que sí verifiqué:

  • Comprobaciones independientes (sin necesidad de HA) sobre las funciones puras que toqué: selección de URL/indicador con y sin el override, _make_price_tag_attributes generando exactamente 24 claves price_XXh sin colisiones a partir de una serie sintética de 96 valores de cuarto de hora (y comportamiento sin cambios sobre una serie horaria normal de 24 valores), y el redondeo minuto→franja para los 60 valores de minuto posibles.
  • python -m py_compile sobre todos los ficheros modificados.
  • No pude ejecutar la suite tests/ existente en mi entorno: la última versión disponible de pytest-homeassistant-custom-component fija un core de Home Assistant más antiguo que AddConfigEntryEntitiesCallback (usado en sensor.py), y esto falla igual en una copia limpia sin modificar — es una limitación previa de mi entorno, no algo introducido por este cambio. Si vuestro CI pasa en verde, esa es la señal real aquí.

No tengo localmente la versión de HA sobre la que se prueba activamente esta integración, así que trata esto como un punto de partida para revisar, no como una PR terminada y verificada por CI.

Encantado de ajustar alcance/nombres/lo que prefieras.

Since Oct 2025 Spain's day-ahead/intraday settlement moved to 15-min
slots (BOE-A-2025-4908/5342). ESIOS indicator 1001 (used today) stays
hourly regardless; indicator 791 ("Precio medio cuarto horario final
comercializadores de referencia") is the genuinely quarter-hourly
PVPC-equivalent series.

This adds an opt-in "Fetch PVPC at 15-minute resolution" toggle,
gated behind the existing private-API token (no new credential),
selectable in the price-mode step of both config flows.

- aiopvpc/const.py: new ESIOS_PVPC_QUARTER_HOURLY = "791".
- aiopvpc/parser.py: get_daily_urls_to_download() takes an optional
  per-sensor data_id override, used to swap PVPC to indicator 791.
- aiopvpc/pvpc_data.py: PVPCData gets a quarter_hourly_pvpc flag; when
  set, the "current price" lookup rounds to the current 15-min bucket
  instead of the top of the hour.
- aiopvpc/prices.py: the price_XXh per-hour tag attributes only tag
  the top-of-hour sample in quarter-hourly mode, avoiding the
  DST-duplicate-hour collision logic misfiring 4x/hour.
- sensor.py: "Current Price" and the price-series-derived attributes
  (Next Price, Better Prices Ahead, ...) refresh every 15 min instead
  of hourly when the option is on. Tariff/power-period attributes are
  untouched (they're clock-based, not price-resolution-based).
- config_flow.py / const.py / coordinator.py: new
  ATTR_QUARTER_HOURLY_PVPC option, defaulting to off.
- strings.json / translations/es.json: label for the new option.

Verified with standalone checks against the changed pure functions
(URL/indicator selection, price_XXh tagging, minute-bucket rounding)
since pytest-homeassistant-custom-component's latest available release
in this environment pins an older Home Assistant core than this
integration targets, and collection fails identically on a clean
unmodified checkout (pre-existing environment issue, unrelated to
this change).
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