Soporte para Hydro-Québec (Tarifa D / Flex D): no basado en potencia, modelo de consumo por tercios

Hola @pierre-gilles

Quería abrir un espacio de discusión sobre GitHub - GladysAssistant/energy-contracts: Gladys Assistant Energy Contracts as JSON · GitHub, pero solo puedo abrir issues.

Contexto

Quiero agregar las tarifas residenciales de Hydro-Québec (proveedor de electricidad público de Quebec, Canadá) a este repositorio, pero la estructura tarifaria no encaja en el modelo actual.

Cómo funciona el modelo actual

Al mirar EDF (contracts/edf/base) y otros proveedores franceses del repositorio, el precio siempre está organizado alrededor de una potencia suscrita (kVA):

  • una clave por nivel de potencia (3, 6, 9, 12, 15 kVA…)
  • para cada nivel: una suscripción fija (anualizada a mensual) + un precio variable constante por kWh
  • variantes existentes: base, peak-off-peak (horas pico/valle), tempo (día azul/blanco/rojo)

Por qué la Tarifa D de Hydro-Québec no encaja en este modelo

La Tarifa D (residencial básica) no tiene ninguna noción de potencia suscrita. Su estructura es:

  • una tarifa de acceso a la red fija por día, independiente de la potencia
  • un precio por kWh progresivo según el consumo real del día (no una elección de nivel con antelación):
    • hasta 40 kWh/día: un primer precio
    • más de 40 kWh/día: un segundo precio, más alto

Tabla vigente desde el 1ᵉʳ de abril de 2026 (fuente oficial: grille-tarifaire.pdf):

Componente Valor
Tarifa de acceso a la red 0,46154 $ CAD / día
Energía, ≤ 40 kWh/día 0,07065 $ CAD / kWh
Energía, > 40 kWh/día 0,11142 $ CAD / kWh

También existe una opción de tarificación dinámica, Flex D, con un precio fuera de punta reducido en invierno y un precio mayorado durante eventos de punta crítica anunciados 24 horas antes (cifras a verificar precisamente en la tabla oficial antes de la implementación):

Componente Valor (a confirmar)
Tarifa de acceso a la red idéntica a la Tarifa D
Fuera de punta, período invernal (1ᵉʳ de diciembre–31 de marzo) ≈ 0,04886 $ CAD / kWh
Punta crítica (eventos puntuales) ≈ 0,50 $ CAD / kWh
Resto del año Tarifa D estándar

Otras diferencias estructurales:

  • moneda CAD, no utilizada aún en el repositorio (solo eur parece utilizado en la práctica)
  • no hay noción de potencia suscrita a elegir por el cliente

Lo que propongo discutir

  1. Cómo codificar una tarifa sin potencia suscrita? Una clave neutral ("none" / "standard") en lugar del nivel de kVA, o una evolución del esquema para hacer esta dimensión opcional?
  2. Cómo codificar una tarifa progresiva por kWh (precio que cambia según el volumen consumido en el período, no según la hora ni el día)? Nada en el repositorio actual parece cubrir este caso — las variantes existentes (base, peak-off-peak, tempo) son todas independientes del volumen.
  3. ¿Sería pertinente un nuevo tipo de carpeta, por ejemplo, tiered-consumption, en complemento de base / peak-off-peak / tempo?
  4. Confirmación de que la moneda cad es bien soportada/aceptada por el pipeline process.js / el frontend Gladys que consume este JSON.

Estoy abierto a comentarios antes de invertir tiempo (bueno, tokens :wink:) en el CSV + convert.js. Una vez el formato validado, puedo proponer un PR con:

  • contracts/hydro-quebec/base (Tarifa D)
  • contracts/hydro-quebec/flex (Flex D)

Hola @lmilcent,

¡Gracias por este tema muy completo, llega en el momento justo! :raising_hands:

Estoy rediseñando a fondo el seguimiento de la energía en Gladys (lo presenté aquí: API : permettre aux intégrations externes de déclarer leurs propres types de contrat énergie - #3 par pierre-gilles).

Los contratos ya no están limitados a Base / Horas valle / Tempo: cada contrato se describe mediante reglas, y esto cubre exactamente los puntos que mencionas.

Tarifa D: será posible directamente

  • Sin potencia suscrita: este campo se vuelve opcional.
  • Niveles de consumo: «los primeros 40 kWh del día a un precio, el resto a otro» se gestiona de forma nativa.
  • Tarifa de acceso diaria: una cantidad fija por día.
  • Dólar canadiense: cada contrato tiene su propia moneda (CAD, USD, EUR…).

Concretamente, con los precios de tu mensaje, la Tarifa D se escribe así:

{
  "tariff_version": 1,
  "components": [
    {
      "key": "energy",
      "kind": "consumption",
      "rules": [
        { "label": "40 primeros kWh del día", "when": { "tier": { "cumulative": "day", "from_kwh": 0, "to_kwh": 40 } }, "price": 0.07065 }
      ],
      "fallback": { "label": "Más de 40 kWh", "price": 0.11142 }
    },
    { "key": "acces", "kind": "fixed", "amount": 0.46154, "per": "day" }
  ]
}

Podrás pegarlo en el editor de contratos de Gladys, y también se podrá agregar al catálogo comunitario para que todos lo tengan con un clic.

Flex D: el cálculo está previsto, falta la fuente de los picos críticos

El cálculo en sí está cubierto: período de invierno del 1 de diciembre al 31 de marzo, precio reducido fuera de los picos, precio alto durante los eventos de pico crítico. Pero como los eventos se anuncian el día anterior, es necesario que algo los recupere y los transmita a Gladys.

Será el papel de una integración externa, que publique los días de pico en un «calendario» que el contrato utiliza. Todo está previsto en Gladys para esto, pero esta integración aún debe escribirse.

Dos preguntas para asegurarnos de ajustarnos a tu factura

  1. Para el umbral de 40 kWh, ¿Hydro-Québec lo calcula día a día, o como «40 kWh × número de días» durante todo el período de facturación? Esto cambia ligeramente el resultado los días en que superas los 40 kWh.
  2. Para Flex D, ¿puedes confirmarme los horarios de los eventos de pico, y si los niveles de 40 kWh también se aplican durante el invierno?

Aún no se ha lanzado, está en revisión antes de una próxima versión.

¡Qué bueno ver que estás retrabajando el tema! :tada:

Umbral de 40 kWh (Tarifa D)

Hydro-Québec lo calcula de forma acumulativa durante todo el período de facturación, no día por día: el primer nivel = 40 kWh × número de días del período, aplicado al total del consumo del período.

Ejemplo concreto de mi factura: 368 kWh en 63 días, umbral = 40 × 63 = 2 520 kWh → todo se factura al primer precio (368 × 0,07065 $ = 26,00 $), independientemente de si un día individual superó los 40 kWh.

En tu ejemplo JSON, "cumulative": "day" probablemente no corresponde al método real, sería más bien un acumulado durante todo el período de facturación ("cumulative": "billing_period" o equivalente), sin reinicialización diaria.

Flex D basado en la cuadrícula oficial 2026 (artículo 2.72)

Componente Precio
Tarifa de acceso a la red, por día 46,154 ¢
Invierno, fuera de hora punta — hasta 40 kWh/día 4,886 ¢/kWh
Invierno, fuera de hora punta — resto de la energía 9,103 ¢/kWh
Invierno, durante un evento de hora punta 46,463 ¢/kWh (precio único)
Verano (resto del año) — hasta 40 kWh/día 7,065 ¢/kWh
Verano (resto del año) — resto de la energía 11,142 ¢/kWh

El umbral de 40 kWh se aplica correctamente durante el invierno Flex D, fuera de evento: solo durante un evento de hora punta un precio único reemplaza los umbrales.

Eventos de hora punta: entre 6h-10h y 16h-20h, todos los días de la semana (incluidos fines de semana), máximo 120 horas por invierno, período invernal del 1 de diciembre al 31 de marzo inclusive. Resto del año: Tarifa D estándar.

Integración Hydro Quebec

Para la integración externa que publicaría los días de hora punta en un calendario, mi integración actual basada en el proyecto hydroqc podría soportarlo (tengo que adaptar el módulo Gladys). Pero este proyecto requiere las credenciales de la cuenta.

De lo contrario, encontré que Hydro-Québec publica un portal de datos abiertos oficiales (https://donnees.hydroquebec.com) con un conjunto de datos evenements-pointe que lista todos los eventos de hora punta (Crédito invernal Y Flex D, filtrable por oferta: CPC-D, TPC-DPC, etc.), a través de una API REST estándar (OpenDataSoft), sin cuenta ni autenticación. El proyecto Beat-YT/hydropeak-ha (MIT) ya se basa en esto para Home Assistant, sin tocar nunca las credenciales del cliente.

Esto permitiría una integración externa mucho más simple y segura: un solo flujo público compartido para todos los usuarios, en lugar de tener que almacenar las credenciales de Hydro-Québec de cada uno. Bueno, esto no cambia el hecho de que las credenciales siguen siendo necesarias para recuperar el consumo.

¡Gracias @lmilcent, es exactamente lo que necesitaba! :folded_hands:

Corrección del umbral: mi primer JSON aplicaba los 40 kWh día a día, lo cual es incorrecto. Como Gladys lo divide por meses, aplico el umbral «40 kWh × días del mes» al total mensual (1 240 / 1 200 / 1 120 kWh). En un ejemplo, esto da exactamente el cálculo de Hydro-Québec, donde la versión «por día» sobreestimaba en un 14 %.

Única limitación: tu período de facturación dura aproximadamente 2 meses. Si un mes con alto consumo es seguido de un mes con bajo consumo en el mismo período, Gladys contará un poco más de kWh en el segundo escalón. Febrero bisiesto (2028) también está desplazado en 40 kWh.

Tarifa D

{
  "tariff_version": 1,
  "components": [
    { "key": "energy", "kind": "consumption",
      "rules": [
        { "label": "Escalón 1", "when": { "months": [1,3,5,7,8,10,12], "tier": { "cumulative": "month", "from_kwh": 0, "to_kwh": 1240 } }, "price": 0.07065 },
        { "label": "Escalón 1", "when": { "months": [4,6,9,11], "tier": { "cumulative": "month", "from_kwh": 0, "to_kwh": 1200 } }, "price": 0.07065 },
        { "label": "Escalón 1", "when": { "months": [2], "tier": { "cumulative": "month", "from_kwh": 0, "to_kwh": 1120 } }, "price": 0.07065 }
      ],
      "fallback": { "label": "Escalón 2", "price": 0.11142 } },
    { "key": "acces", "kind": "fixed", "amount": 0.46154, "per": "day" }
  ]
}

Flex D

{
  "tariff_version": 1,
  "calendars": ["hq-critical-peaks"],
  "components": [
    { "key": "energy", "kind": "consumption",
      "rules": [
        { "label": "Pico", "when": { "months": [12,1,2,3], "calendar": { "hq-critical-peaks": "critical-peak" } }, "price": 0.46463 },
        { "label": "Invierno escalón 1", "when": { "months": [12,1,3], "tier": { "cumulative": "month", "from_kwh": 0, "to_kwh": 1240 } }, "price": 0.04886 },
        { "label": "Invierno escalón 1", "when": { "months": [2], "tier": { "cumulative": "month", "from_kwh": 0, "to_kwh": 1120 } }, "price": 0.04886 },
        { "label": "Invierno escalón 2", "when": { "months": [12,1,2,3] }, "price": 0.09103 },
        { "label": "Verano escalón 1", "when": { "months": [5,7,8,10], "tier": { "cumulative": "month", "from_kwh": 0, "to_kwh": 1240 } }, "price": 0.07065 },
        { "label": "Verano escalón 1", "when": { "months": [4,6,9,11], "tier": { "cumulative": "month", "from_kwh": 0, "to_kwh": 1200 } }, "price": 0.07065 }
      ],
      "fallback": { "label": "Verano escalón 2", "price": 0.11142 } },
    { "key": "acces", "kind": "fixed", "amount": 0.46154, "per": "day" }
  ]
}

Para los picos, el conjunto de datos «evenements-pointe» de los datos abiertos es perfecto: una pequeña integración externa puede leerlo y publicar los horarios de pico en el calendario hq-critical-peaks, sin ninguna identificación. Como los horarios provienen de los datos, la regla no necesita los rangos 6h–10h / 16h–20h. hydropeak-ha es una buena base para la recuperación. Si alguien quiere empezar, ¡estoy aquí para ayudar!

Última pregunta: ¿los kWh consumidos durante un pico cuentan en el umbral de los 40 kWh × días? Por ahora los cuento.

¡Esa sí que es una buena pregunta! No tenía ni idea, pero Claude me ayudó mucho con todos los documentos que le di :grinning_face_with_smiling_eyes:

Para los kWh de punta: la tarifa oficial (artículo 2.72) describe tres categorías distintas y mutuamente excluyentes:

  • « Hasta 40 kWh de energía por día fuera de los eventos de punta »
  • « Resto de la energía consumida fuera de los eventos de punta »
  • « Energía consumida durante los eventos de punta »

El umbral de 40 kWh × días está explícitamente limitado a fuera de punta en el texto. No indica que la energía de punta se sume a este mismo acumulado — son dos contadores separados. Por lo tanto, los kWh consumidos durante un evento de punta no deberían contar para el umbral del nivel fuera de punta.

Esto también coincide con la lógica del programa: de lo contrario, un cliente que no puede reducir su consumo durante un evento se vería penalizado dos veces (precio de punta + pérdida de nivel ventajoso en el resto del mes), lo cual va en contra del objetivo de una tarifa dinámica.

Para la implementación, esto significaría: en la regla « Invierno nivel 1 / nivel 2 », la ventana tier.cumulative: "month" solo debería contabilizar los kWh fuera de calendar: critical-peak, no el total mensual bruto.

Hola Louis,

¡Gracias por el artículo 2.72, tenías toda la razón! He actualizado el motor de Gladys con dos añadidos en los escalones:

  • to_kwh_per_day: el umbral se convierte en «40 kWh × número de días del período». Se calcula sobre el total, y se gestiona febrero bisiesto.
  • counts_when: se elige qué kWh alimentan el escalón. Los kWh consumidos durante un pico ya no consumen tu escalón fuera de pico.

Lo he verificado en algunos casos, con precisión hasta el céntimo en comparación con la tarifa:

  • enero con días irregulares (1 220 kWh): 86,19 $;
  • febrero 2028 bisiesto: 98,11 $;
  • Flex D en enero con 20 horas de pico: 116,90 $ (sin la corrección, estábamos en 118,45 $).

Tarifa D

{
  "tariff_version": 1,
  "components": [
    { "key": "energy", "kind": "consumption",
      "rules": [
        { "label": "Escalón 1", "when": { "tier": { "cumulative": "billing_period", "from_kwh_per_day": 0, "to_kwh_per_day": 40 } }, "price": 0.07065 }
      ],
      "fallback": { "label": "Escalón 2", "price": 0.11142 } },
    { "key": "acces", "kind": "fixed", "amount": 0.46154, "per": "day" }
  ]
}

Flex D

{
  "tariff_version": 1,
  "calendars": ["hq-critical-peaks"],
  "components": [
    { "key": "energy", "kind": "consumption",
      "rules": [
        { "label": "Pico", "when": { "calendar": { "hq-critical-peaks": "critical-peak" } }, "price": 0.46463 },
        { "label": "Invierno escalón 1", "when": { "season": { "from": "12-01", "to": "03-31" }, "tier": { "cumulative": "billing_period", "from_kwh_per_day": 0, "to_kwh_per_day": 40, "counts_when": { "not_calendar": { "hq-critical-peaks": "critical-peak" } } } }, "price": 0.04886 },
        { "label": "Invierno escalón 2", "when": { "season": { "from": "12-01", "to": "03-31" } }, "price": 0.09103 },
        { "label": "Verano escalón 1", "when": { "tier": { "cumulative": "billing_period", "from_kwh_per_day": 0, "to_kwh_per_day": 40 } }, "price": 0.07065 }
      ],
      "fallback": { "label": "Verano escalón 2", "price": 0.11142 } },
    { "key": "acces", "kind": "fixed", "amount": 0.46154, "per": "day" }
  ]
}

Única limitación restante: Gladys divide la facturación por meses, mientras que tus períodos de Hydro-Québec son de aproximadamente 2 meses. La diferencia solo juega si un mes con alto consumo y un mes ligero quedan en el mismo período.

Para que los picos sean realmente facturados, queda la integración que lee los eventos en los datos abiertos de Hydro-Québec.

¡Excelente! Todo me parece correcto, qué pena por la pequeña falla, es poco probable (a menos que abandone su vivienda) tener dos meses muy diferentes en el consumo durante el invierno, dado el aislamiento de los edificios y el tipo de calefacción aquí :rofl:

Cuando se lance, actualizaré mi módulo para sincronizar el horario de punta de Hydro Québec.