| 151 |
151 |
return civ.group(1), cp.group(1).upper().replace(" ", "") |
| 152 |
152 |
|
| 153 |
153 |
|
| 154 |
|
−def _montant(d: dict) -> tuple[float | None, float | None]: |
| 155 |
|
− """Retrouve le coût annuel/mensuel dans la réponse d'estimation.""" |
| 156 |
|
− txt = json.dumps(d) |
| 157 |
|
− an = re.search(r'"(?:coutAnnuel|montantAnnuel|cout|montant)"\s*:\s*' |
| 158 |
|
− r'([0-9]+(?:\.[0-9]+)?)', txt) |
| 159 |
|
− a = float(an.group(1)) if an else None |
| 160 |
|
− if a and a < 400: # valeur mensuelle -> annualiser |
| 161 |
|
− return round(a * 12), round(a) |
| 162 |
|
− return (round(a) if a else None, |
| 163 |
|
− round(a / 12) if a else None) |
|
154 |
+def _montant(d: dict) -> tuple[float | None, float | None, int | None]: |
|
155 |
+ """(coût annuel, coût mensuel, conso annuelle kWh) de l'estimation.""" |
|
156 |
+ a = d.get("montantAnnuel") |
|
157 |
+ kwh = d.get("consommationAnnuelle") |
|
158 |
+ if a is None: |
|
159 |
+ m = re.search(r'"montantAnnuel"\s*:\s*([0-9]+(?:\.[0-9]+)?)', |
|
160 |
+ json.dumps(d)) |
|
161 |
+ a = float(m.group(1)) if m else None |
|
162 |
+ if a is None: |
|
163 |
+ return None, None, kwh |
|
164 |
+ return round(a), round(a / 12), kwh |
| 164 |
165 |
|
| 165 |
166 |
|
| 166 |
167 |
def estimate(civic: str | None = None, postal: str | None = None, |
| 178 |
179 |
postal = postal.upper().replace(" ", "") |
| 179 |
180 |
cle = f"{civic}|{postal}" |
| 180 |
181 |
|
|
182 |
+ postal_espace = re.sub(r"^([A-Z]\d[A-Z])\s*(\d[A-Z]\d)$", r"\1 \2", postal) |
|
183 |
+ |
| 181 |
184 |
con = _connect() |
| 182 |
185 |
row = con.execute("SELECT * FROM hydro_cache WHERE cle=? AND fetched_at>?", |
| 183 |
186 |
(cle, time.time() - TTL)).fetchone() |
| 197 |
200 |
return {"disponible": False, "raison": "échec du captcha"} |
| 198 |
201 |
|
| 199 |
202 |
opener = _opener() |
| 200 |
|
− lieux = _post(opener, URL_LIEU, {"noCivique": civic, "codePostal": postal, |
|
203 |
+ lieux = _post(opener, URL_LIEU, {"noCivique": civic, |
|
204 |
+ "codePostal": postal_espace, |
| 201 |
205 |
"recaptchaResponse": token}) |
| 202 |
|
− items = (lieux or {}).get("lieuxConsommation") or (lieux or {}).get( |
| 203 |
|
− "lieux") or (lieux if isinstance(lieux, list) else []) |
| 204 |
|
− if not items: |
|
206 |
+ items = (lieux or {}).get("listeLieuxConsommation") or [] |
|
207 |
+ cle_unique = (lieux or {}).get("cleUnique") |
|
208 |
+ if not items or not cle_unique: |
| 205 |
209 |
con.close() |
| 206 |
|
− return {"disponible": False, "raison": "adresse inconnue d'Hydro-Québec"} |
|
210 |
+ return {"disponible": False, |
|
211 |
+ "raison": "adresse inconnue d'Hydro-Québec"} |
| 207 |
212 |
lieu = items[0] |
| 208 |
|
− id_conso = lieu.get("idConso") or lieu.get("id") |
| 209 |
|
− cle_unique = lieu.get("cleUnique") or lieu.get("cle") |
| 210 |
|
− adr = lieu.get("adresse") or f"{civic}, {postal}" |
|
213 |
+ id_conso = lieu.get("id") |
|
214 |
+ adr = lieu.get("libelleAdresse") or f"{civic} {lieu.get('rue', '')}".strip() |
| 211 |
215 |
|
| 212 |
216 |
est = _post(opener, URL_ESTIM, {"cleUnique": cle_unique, |
| 213 |
217 |
"idConso": id_conso}) |
| 214 |
218 |
if not est: |
| 215 |
219 |
con.close() |
| 216 |
220 |
return {"disponible": False, "raison": "estimation indisponible"} |
| 217 |
|
− an, mens = _montant(est) |
|
221 |
+ an, mens, kwh = _montant(est) |
| 218 |
222 |
if not an: |
| 219 |
223 |
con.close() |
| 220 |
224 |
return {"disponible": False, "raison": "montant introuvable"} |
| 223 |
227 |
(cle, adr, an, mens, json.dumps(est), time.time())) |
| 224 |
228 |
con.close() |
| 225 |
229 |
return {"disponible": True, "adresse": adr, "cout_annuel": an, |
| 226 |
|
− "cout_mensuel": mens, "cache": False} |
|
230 |
+ "cout_mensuel": mens, "kwh_annuel": kwh, "cache": False} |
| 227 |
231 |
|