Hydro-Quebec-API-Wrapper 4.1.0__tar.gz → 4.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/.gitlab-ci.yml +3 -0
  2. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/.pre-commit-config.yaml +3 -3
  3. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/Hydro_Quebec_API_Wrapper.egg-info/PKG-INFO +1 -1
  4. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/PKG-INFO +1 -1
  5. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/contract/contract_dpc.py +81 -19
  6. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/hydro_api/client.py +7 -3
  7. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/hydro_api/consts.py +2 -2
  8. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/types/__init__.py +2 -2
  9. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/types/consump.py +6 -19
  10. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/types/dpc.py +31 -10
  11. hydro_quebec_api_wrapper-4.2.1/hydroqc/types/dt.py +29 -0
  12. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/test_requirements.txt +5 -5
  13. hydro_quebec_api_wrapper-4.1.0/hydroqc/types/dt.py +0 -15
  14. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/.coveragerc +0 -0
  15. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/.devcontainer/Dockerfile +0 -0
  16. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/.devcontainer/devcontainer.json +0 -0
  17. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/.flake8 +0 -0
  18. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/.gitignore +0 -0
  19. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/.pylintrc +0 -0
  20. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/Hydro_Quebec_API_Wrapper.egg-info/SOURCES.txt +0 -0
  21. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/Hydro_Quebec_API_Wrapper.egg-info/dependency_links.txt +0 -0
  22. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/Hydro_Quebec_API_Wrapper.egg-info/entry_points.txt +0 -0
  23. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/Hydro_Quebec_API_Wrapper.egg-info/requires.txt +0 -0
  24. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/Hydro_Quebec_API_Wrapper.egg-info/top_level.txt +0 -0
  25. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/LICENSE +0 -0
  26. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/MANIFEST.in +0 -0
  27. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/README.md +0 -0
  28. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/config.default.yaml +0 -0
  29. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/conflict_test_requirements.txt +0 -0
  30. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/Makefile +0 -0
  31. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/make.bat +0 -0
  32. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/_static/custom.css +0 -0
  33. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/_static/hydroqc-logo-128.png +0 -0
  34. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/_static/hydroqc-logo.svg +0 -0
  35. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/conf.py +0 -0
  36. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/external/description-des-codes-interruption.pdf +0 -0
  37. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/index.rst +0 -0
  38. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/reference/hydroqc.contract.rst +0 -0
  39. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/reference/hydroqc.hydro_api.rst +0 -0
  40. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/reference/hydroqc.peak.cpc.rst +0 -0
  41. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/reference/hydroqc.peak.dpc.rst +0 -0
  42. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/reference/hydroqc.peak.rst +0 -0
  43. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/reference/hydroqc.rst +0 -0
  44. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/reference/hydroqc.types.rst +0 -0
  45. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/reference/modules.rst +0 -0
  46. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/docs/source/todo.rst +0 -0
  47. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/examples/hydro.py +0 -0
  48. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/__init__.py +0 -0
  49. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/account.py +0 -0
  50. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/consts.py +0 -0
  51. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/contract/__init__.py +0 -0
  52. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/contract/common.py +0 -0
  53. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/contract/contract_d.py +0 -0
  54. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/contract/contract_d_cpc.py +0 -0
  55. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/contract/contract_dt.py +0 -0
  56. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/contract/contract_m.py +0 -0
  57. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/contract/contract_m_gdp.py +0 -0
  58. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/contract/contract_residential.py +0 -0
  59. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/contract/outage.py +0 -0
  60. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/customer.py +0 -0
  61. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/diagnostic.py +0 -0
  62. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/error.py +0 -0
  63. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/hydro_api/__init__.py +0 -0
  64. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/hydro_api/cache.py +0 -0
  65. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/logger.py +0 -0
  66. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/__init__.py +0 -0
  67. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/basepeak.py +0 -0
  68. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/basepeakhandler.py +0 -0
  69. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/consts.py +0 -0
  70. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/cpc/__init__.py +0 -0
  71. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/cpc/consts.py +0 -0
  72. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/cpc/handler.py +0 -0
  73. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/cpc/peak.py +0 -0
  74. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/dpc/__init__.py +0 -0
  75. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/dpc/consts.py +0 -0
  76. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/dpc/handler.py +0 -0
  77. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/peak/dpc/peak.py +0 -0
  78. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/public_client.py +0 -0
  79. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/py.typed +0 -0
  80. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/timerange.py +0 -0
  81. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/types/account.py +0 -0
  82. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/types/common.py +0 -0
  83. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/types/contract.py +0 -0
  84. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/types/cpc.py +0 -0
  85. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/types/outage.py +0 -0
  86. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/utils.py +0 -0
  87. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/hydroqc/webuser.py +0 -0
  88. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/logo/hydroqc-logo-128.png +0 -0
  89. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/logo/hydroqc-logo.svg +0 -0
  90. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/pyproject.toml +0 -0
  91. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/renovate.json5 +0 -0
  92. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/setup.cfg +0 -0
  93. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/__init__.py +0 -0
  94. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/conftest.py +0 -0
  95. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/subtest_common.py +0 -0
  96. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/subtest_contract_d.py +0 -0
  97. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/subtest_contract_d_cpc.py +0 -0
  98. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/subtest_contract_dpc.py +0 -0
  99. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/subtest_contract_dt.py +0 -0
  100. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/subtest_contract_m.py +0 -0
  101. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/subtest_contract_m_gdp.py +0 -0
  102. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/test_basic.py +0 -0
  103. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/test_timerange.py +0 -0
  104. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tests/tools.py +0 -0
  105. {hydro_quebec_api_wrapper-4.1.0 → hydro_quebec_api_wrapper-4.2.1}/tox.ini +0 -0
@@ -314,6 +314,9 @@ package:
314
314
  # Disable if it's a tag pipeline
315
315
  - if: "$CI_COMMIT_TAG"
316
316
  when: never
317
+ # Disable if it's a scheduled pipeline
318
+ - if: "$CI_PIPELINE_SOURCE == 'schedule'"
319
+ when: never
317
320
  # Enabled for main branch
318
321
  - if: "$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH"
319
322
  when: on_success
@@ -16,7 +16,7 @@ repos:
16
16
  - id: check-ast
17
17
 
18
18
  - repo: https://github.com/psf/black
19
- rev: 25.9.0
19
+ rev: 25.11.0
20
20
  hooks:
21
21
  - id: black
22
22
  language_version: python3
@@ -47,9 +47,9 @@ repos:
47
47
  additional_dependencies:
48
48
  - "types-pytz==2025.2.0.20251108"
49
49
  - "types-PyYAML==6.0.12.20250915"
50
- - "types-python-dateutil==2.9.0.20251108"
50
+ - "types-python-dateutil==2.9.0.20251115"
51
51
  - "pytest-stub==1.1.0"
52
52
  - "aiohttp==3.13.2"
53
53
  - "aiocache==0.12.3"
54
54
  - "pkce==1.0.3"
55
- - "pytest-asyncio==1.2.0"
55
+ - "pytest-asyncio==1.3.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Hydro-Quebec-API-Wrapper
3
- Version: 4.1.0
3
+ Version: 4.2.1
4
4
  Summary: A wrapper library to access hydro quebec API and more
5
5
  Home-page: https://hydroqc.ca
6
6
  Author-email: Hydroqc Team <info@hydroqc.ca>
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Hydro-Quebec-API-Wrapper
3
- Version: 4.1.0
3
+ Version: 4.2.1
4
4
  Summary: A wrapper library to access hydro quebec API and more
5
5
  Home-page: https://hydroqc.ca
6
6
  Author-email: Hydroqc Team <info@hydroqc.ca>
@@ -1,7 +1,8 @@
1
1
  """Hydroquebec contract module."""
2
2
 
3
3
  from collections.abc import Callable
4
- from datetime import date
4
+ from datetime import date, datetime
5
+ from enum import Enum
5
6
 
6
7
  from hydroqc.contract.common import check_period_data_present
7
8
  from hydroqc.contract.contract_residential import ContractResidential
@@ -10,12 +11,21 @@ from hydroqc.peak.dpc.handler import DPCPeakHandler
10
11
  from hydroqc.types import DPCDataTyping
11
12
 
12
13
 
14
+ class WinterState(str, Enum):
15
+ """Winter period state."""
16
+
17
+ CURRENT = "C" # Current/Active (within winter period)
18
+ FINISHED = "F" # Finished (past winter period)
19
+
20
+
13
21
  def check_dpc_data_present(
14
- method: Callable[..., None | str | bool | float | date],
15
- ) -> Callable[..., None | str | bool | float | date]:
22
+ method: Callable[..., None | str | bool | float | int | date | WinterState],
23
+ ) -> Callable[..., None | str | bool | float | int | date | WinterState]:
16
24
  """Check if contractDPC's data are present."""
17
25
 
18
- def wrapper(contract: "ContractDPC") -> None | str | bool | float | date:
26
+ def wrapper(
27
+ contract: "ContractDPC",
28
+ ) -> None | str | bool | float | int | date | WinterState:
19
29
  if not hasattr(contract, "_dpc_data"):
20
30
  contract._logger.warning("You need to call get_dpc_data method first")
21
31
  return None
@@ -26,6 +36,20 @@ def check_dpc_data_present(
26
36
  "Maybe you did a rate change recently. "
27
37
  "This message should disappear at the beginning of the next bill period."
28
38
  )
39
+ return None
40
+
41
+ # Check if winter period data is available
42
+ if (
43
+ "periodesHiver" not in contract._dpc_data
44
+ or not contract._dpc_data.get("periodesHiver")
45
+ or len(contract._dpc_data["periodesHiver"]) == 0
46
+ ):
47
+ contract._logger.info(
48
+ "DPC data is empty or incomplete. "
49
+ "This may occur outside the winter period or during a rate transition."
50
+ )
51
+ return None
52
+
29
53
  return method(contract)
30
54
 
31
55
  return wrapper
@@ -86,6 +110,19 @@ class ContractDPC(ContractResidential):
86
110
  )
87
111
  return self._dpc_data
88
112
 
113
+ def _get_dpc_field(self, field_name: str) -> str | float | int:
114
+ """Get DPC field from periodesHiver data.
115
+
116
+ Raises:
117
+ KeyError: If the required field is not present in the API response.
118
+ """
119
+ winter_period = self._dpc_data["periodesHiver"][0]
120
+ if field_name not in winter_period:
121
+ raise KeyError(
122
+ f"Required field '{field_name}' not found in DPC periodesHiver data"
123
+ )
124
+ return winter_period[field_name] # type: ignore
125
+
89
126
  @property
90
127
  @check_period_data_present
91
128
  def cp_lower_price_consumption(self) -> float:
@@ -111,44 +148,69 @@ class ContractDPC(ContractResidential):
111
148
  @check_dpc_data_present
112
149
  def last_update_date(self) -> date:
113
150
  """DPC data last update."""
114
- return date.fromisoformat(self._dpc_data["results"][0]["dateDernMaj"])
151
+ date_str = str(self._get_dpc_field("dateMaj"))
152
+ # Handle ISO format with timezone
153
+ if "T" in date_str:
154
+ return date.fromisoformat(date_str.split("T", maxsplit=1)[0])
155
+ return date.fromisoformat(date_str)
115
156
 
116
157
  @property
117
158
  @check_dpc_data_present
118
159
  def critical_called_hours(self) -> int:
119
160
  """Get number of critical hours."""
120
- return int(self._dpc_data["results"][0]["hrsCritiquesAppelees"])
161
+ return int(self._get_dpc_field("hrsCritiquesAppelees"))
121
162
 
122
163
  @property
123
164
  @check_dpc_data_present
124
165
  def max_critical_called_hours(self) -> int:
125
166
  """Get max number of critical hours."""
126
- return int(self._dpc_data["results"][0]["hrsCritiquesAppeleesMax"])
167
+ return int(self._get_dpc_field("hrsCritiquesAppeleesMax"))
127
168
 
128
169
  @property
129
170
  @check_dpc_data_present
130
- def amount_saved_vs_base_rate(self) -> float:
131
- """Amount saved or not versus the base rate."""
132
- return self._dpc_data["results"][0]["montantEconPerteVSTarifBase"]
171
+ def amount_saved_vs_base_rate(self) -> float | None:
172
+ """Amount saved or not versus the base rate.
173
+
174
+ Returns None if comparison data is not yet available (e.g., first year on Flex D).
175
+ """
176
+ try:
177
+ return float(self._get_dpc_field("montantEconPerteVSTarifBase"))
178
+ except KeyError:
179
+ return None
133
180
 
134
181
  @property
135
182
  @check_dpc_data_present
136
183
  def winter_total_days(self) -> int:
137
- """Get number of winter days."""
138
- return self._dpc_data["results"][0]["nbJoursTotauxHiver"]
184
+ """Get number of winter days (calculated from period dates)."""
185
+ winter = self._dpc_data["periodesHiver"][0]
186
+ start = datetime.fromisoformat(winter["dateDebut"].replace("Z", "+00:00"))
187
+ end = datetime.fromisoformat(winter["dateFin"].replace("Z", "+00:00"))
188
+ return (end - start).days
139
189
 
140
190
  @property
141
191
  @check_dpc_data_present
142
192
  def winter_total_days_last_update(self) -> int:
143
- """Get number of days since winter starts."""
144
- return self._dpc_data["results"][0]["nbJoursDernMaj"]
193
+ """Get number of days since winter starts (calculated from dates)."""
194
+ winter = self._dpc_data["periodesHiver"][0]
195
+ start = datetime.fromisoformat(winter["dateDebut"].replace("Z", "+00:00"))
196
+ update = datetime.fromisoformat(winter["dateMaj"].replace("Z", "+00:00"))
197
+ return (update - start).days
145
198
 
146
199
  @property
147
200
  @check_dpc_data_present
148
- def winter_state(self) -> str:
149
- """Get winter state.
201
+ def winter_state(self) -> WinterState:
202
+ """Get winter state (computed from dates).
150
203
 
151
- C: started
152
- ?: ???????
204
+ Returns:
205
+ WinterState.CURRENT: Within winter period
206
+ WinterState.FINISHED: Past winter period
153
207
  """
154
- return self._dpc_data["results"][0]["etatHiver"]
208
+ winter = self._dpc_data["periodesHiver"][0]
209
+ now = datetime.now(
210
+ datetime.fromisoformat(winter["dateDebut"].replace("Z", "+00:00")).tzinfo
211
+ )
212
+ start = datetime.fromisoformat(winter["dateDebut"].replace("Z", "+00:00"))
213
+ end = datetime.fromisoformat(winter["dateFin"].replace("Z", "+00:00"))
214
+ if start <= now <= end:
215
+ return WinterState.CURRENT
216
+ return WinterState.FINISHED
@@ -862,12 +862,16 @@ class HydroClient:
862
862
  ) -> DPCDataTyping:
863
863
  """Fetch FlexD data of the current year.
864
864
 
865
- API URL: https://cl-ec-spring.hydroquebec.com/portail/fr/group/clientele/
866
- portrait-de-consommation/resourceObtenirDonneesMoisHiverFlex
865
+ API URL: https://services-cl.solutions.hydroquebec.com/wsapi/web/prive/api/
866
+ v3_0/tarificationDynamique/tarifPointeCritique
867
867
 
868
868
  """
869
869
  await self._select_contract(applicant_id, customer_id, contract_id)
870
- api_call_response = await self.http_request(FLEXD_DATA_URL, "get")
870
+ headers = await self._get_customer_http_headers(applicant_id, customer_id)
871
+ params = {"noContrat": contract_id}
872
+ api_call_response = await self.http_request(
873
+ FLEXD_DATA_URL, "get", params=params, headers=headers
874
+ )
871
875
  # We can not use res.json() because the response header are not application/json
872
876
  data: DPCDataTyping = self._load_json(await api_call_response.text())
873
877
  return data
@@ -57,8 +57,8 @@ OPEN_DATA_PEAK_URL = (
57
57
  RELATION_URL = f"{HOST_SERVICES}/wsapi/web/prive/api/v1_0/relations"
58
58
 
59
59
  FLEXD_DATA_URL = (
60
- f"{HOST_SERVICES}/lsw/portail/fr/group/clientele/portrait-de-consommation/"
61
- "resourceObtenirDonneesMoisHiverFlex"
60
+ f"{HOST_SERVICES}/wsapi/web/prive/api/v3_0/"
61
+ "tarificationDynamique/tarifPointeCritique"
62
62
  )
63
63
  FLEXD_PEAK_URL = f"{HOST_SERVICES}/conso/portraitweb/api/v3_0/conso"
64
64
  CONSO_CSV_URL = (
@@ -17,13 +17,13 @@ from hydroqc.types.consump import (
17
17
  ConsumpDailyTyping,
18
18
  ConsumpHourlyTyping,
19
19
  ConsumpMonthlyTyping,
20
- DPCDataTyping,
21
20
  DPCPeakDataTyping,
22
21
  DPCPeakListDataTyping,
23
- DTDataTyping,
24
22
  )
25
23
  from hydroqc.types.contract import ContractTyping, ListContractsTyping
26
24
  from hydroqc.types.cpc import CPCDataTyping, CriticalPeakDataTyping, PeriodDataTyping
25
+ from hydroqc.types.dpc import DPCDataTyping
26
+ from hydroqc.types.dt import DTDataTyping
27
27
  from hydroqc.types.outage import (
28
28
  OutageCause,
29
29
  OutageCode,
@@ -155,8 +155,12 @@ class ConsumpAnnualTyping(TypedDict, total=True):
155
155
  results: list[ConsumpAnnualResultsTyping]
156
156
 
157
157
 
158
- class DPCDataResultsTyping(TypedDict, total=True):
159
- """FlexD data json sub output format."""
158
+ class DPCDataResultsTyping(TypedDict, total=False):
159
+ """FlexD data json sub output format.
160
+
161
+ Note: montantEconPerteVSTarifBase may not be available in the first year of Flex D
162
+ as it requires comparison data from previous Rate D billing.
163
+ """
160
164
 
161
165
  dateDebut: str
162
166
  dateFin: str
@@ -169,23 +173,6 @@ class DPCDataResultsTyping(TypedDict, total=True):
169
173
  etatHiver: str
170
174
 
171
175
 
172
- class DPCDataTyping(TypedDict, total=True):
173
- """FlexD data json output format."""
174
-
175
- success: bool
176
- results: list[DPCDataResultsTyping]
177
-
178
-
179
- DTDataResultsTyping = DPCDataResultsTyping
180
-
181
-
182
- class DTDataTyping(TypedDict, total=True):
183
- """FlexD data json output format."""
184
-
185
- success: bool
186
- results: list[DTDataResultsTyping]
187
-
188
-
189
176
  class DPCPeakDataTyping(TypedDict, total=True):
190
177
  """DPC Peak data json output format."""
191
178
 
@@ -4,25 +4,46 @@
4
4
  from typing import TypedDict
5
5
 
6
6
 
7
- class DPCDataResultsTyping(TypedDict, total=True):
8
- """FlexD data json sub output format."""
7
+ class DPCPeriodeHiverTyping(TypedDict, total=False):
8
+ """FlexD winter period data (API v3_0 format).
9
+
10
+ Note: montantEconPerteVSTarifBase may not be available in the first year of Flex D
11
+ as it requires comparison data from previous Rate D billing.
12
+ """
9
13
 
10
14
  dateDebut: str
11
15
  dateFin: str
12
- dateDernMaj: str
16
+ dateMaj: str
13
17
  hrsCritiquesAppelees: str
14
18
  hrsCritiquesAppeleesMax: str
15
19
  montantEconPerteVSTarifBase: float
16
- nbJoursTotauxHiver: int
17
- nbJoursDernMaj: int
18
- etatHiver: str
19
20
 
20
21
 
21
- class DPCDataTyping(TypedDict, total=True):
22
- """FlexD data json output format."""
22
+ class DPCEvenementPointeTyping(TypedDict, total=True):
23
+ """FlexD peak event data (API v3_0 format)."""
24
+
25
+ dateEvenement: str
26
+ heureDebut: str
27
+ heureFin: str
28
+ consoReelle: float
29
+ codeConso: str
30
+ indFacture: bool
31
+
32
+
33
+ class DPCEvenementsPointeWrapperTyping(TypedDict, total=True):
34
+ """FlexD peak events wrapper (API v3_0 format)."""
35
+
36
+ dateDebutPeriodeHiver: str
37
+ dateFinPeriodeHiver: str
38
+ evenementsPointe: list[DPCEvenementPointeTyping]
39
+
40
+
41
+ class DPCDataTyping(TypedDict, total=False):
42
+ """FlexD data json output format (API v3_0)."""
23
43
 
24
- success: bool
25
- results: list[DPCDataResultsTyping]
44
+ dateTestAffichageWeb: str
45
+ periodesHiver: list[DPCPeriodeHiverTyping]
46
+ evenementsPointe: list[DPCEvenementsPointeWrapperTyping]
26
47
 
27
48
 
28
49
  class DPCPeakDataTyping(TypedDict, total=True):
@@ -0,0 +1,29 @@
1
+ """Hydroqc custom types."""
2
+
3
+ # pylint: disable=invalid-name
4
+ from typing import TypedDict
5
+
6
+
7
+ class DTDataResultsTyping(TypedDict, total=False):
8
+ """DT winter period data (legacy format).
9
+
10
+ Note: montantEconPerteVSTarifBase may not be available when comparison
11
+ data from previous billing is unavailable.
12
+ """
13
+
14
+ dateDebut: str
15
+ dateFin: str
16
+ dateDernMaj: str
17
+ hrsCritiquesAppelee: str
18
+ hrsCritiquesAppeleesMax: str
19
+ montantEconPerteVSTarifBase: float
20
+ nbJoursTotauxHiver: int
21
+ nbJoursDernMaj: int
22
+ etatHiver: str
23
+
24
+
25
+ class DTDataTyping(TypedDict, total=True):
26
+ """FlexD data json output format."""
27
+
28
+ success: bool
29
+ results: list[DTDataResultsTyping]
@@ -1,14 +1,14 @@
1
1
  flake8==7.3.0
2
2
  pydocstyle==6.3.0
3
3
  parameterized==0.9.0
4
- pylint==4.0.2
5
- pytest==8.4.2
4
+ pylint==4.0.3
5
+ pytest==9.0.1
6
6
  pytest-cov==7.0.0
7
- pytest-asyncio==1.2.0
8
- black==25.9.0
7
+ pytest-asyncio==1.3.0
8
+ black==25.11.0
9
9
  mypy==1.18.2
10
10
  types-pytz==2025.2.0.20251108
11
11
  types-PyYAML==6.0.12.20250915
12
- types-python-dateutil==2.9.0.20251108
12
+ types-python-dateutil==2.9.0.20251115
13
13
  pyright==1.1.407
14
14
  lxml==6.0.2
@@ -1,15 +0,0 @@
1
- """Hydroqc custom types."""
2
-
3
- # pylint: disable=invalid-name
4
- from typing import TypedDict
5
-
6
- from hydroqc.types.dpc import DPCDataResultsTyping
7
-
8
- DTDataResultsTyping = DPCDataResultsTyping
9
-
10
-
11
- class DTDataTyping(TypedDict, total=True):
12
- """FlexD data json output format."""
13
-
14
- success: bool
15
- results: list[DTDataResultsTyping]