Source code for rivretrieve.portugal

"""Fetcher for Portuguese river gauge data from SNIRH."""

import html
import logging
from datetime import datetime
from io import StringIO
from typing import Optional

import pandas as pd

from . import base, constants, utils

logger = logging.getLogger(__name__)

# SITE_MAP from https://github.com/kratzert/RivRetrieve-Python/issues/40
SITE_MAP = {
    "19B/01H": 1627743378,
    "03J/02H": 1627743350,
    "17H/04H": 1627743352,
    "15G/02H": 1627743380,
    "11H/02H": 1627743382,
    "21O/08H": 2443345812,
    "21O/03H": 1627743386,
    "26J/01H": 1627743392,
    "04K/04A": 11508860178,
    "11H/01A": 1627743384,
    "17M/01A": 1627743458,
    "06S/01A": 1627743548,
    "28I/01A": 1627743554,
    "14H/01A": 1627743562,
    "30E/01A": 1627743568,
    "05O/01A": 1627743572,
    "04H/01A": 1627743674,
    "12M/01A": 1627743678,
    "12L/02A": 1627751888,
    "05P/01A": 1627753166,
    "11L/03A": 1627758736,
    "13H/05A": 1627758750,
    "11H/05A": 7945659548,
    "11K/02A": 1627758884,
    "03J/01A": 1627758928,
    "16K/01A": 1627759292,
    "08N/01AE": 1627759302,
    "12H/01A": 1627759328,
    "07K/01A": 1627759340,
    "02Q/01A": 1627759434,
    "27L/01A": 1627759452,
    "14O/01A": 1627759474,
    "07M/02A": 1627759510,
    "07K/02A": 1627759512,
    "22L/01A": 1627759534,
    "29M/04A": 7914972596,
    "07K/05A": 7556914548,
    "05R/01A": 1627743534,
    "17J/01A": 1627743544,
    "08H/01A": 1627743574,
    "05P/02A": 1627743666,
    "26F/01A": 1627743668,
    "30M/06A": 7927143708,
    "16H/01A": 1627743896,
    "15J/01A": 1627751876,
    "28G/02A": 1627751880,
    "12L/01AE": 1627751890,
    "13L/01AE": 1627751892,
    "07G/01A": 1627751912,
    "04J/08A": 9978398426,
    "09G/04A": 7554829608,
    "12N/01AE": 1627752558,
    "10K/01A": 1627758608,
    "06N/01A": 1627758622,
    "26G/01AE": 1627758624,
    "12I/01A": 1627758668,
    "17L/03A": 1627758676,
    "05K/02A": 10908513554,
    "04I/01A": 1627758686,
    "14N/01A": 1627758730,
    "22M/01A": 1627758752,
    "20E/01A": 1627758756,
    "12O/01A": 1627758774,
    "22I/01A": 1627758784,
    "05T/01A": 1627758786,
    "19H/01A": 1627758816,
    "29I/01A": 1627758826,
    "27H/01A": 1627758846,
    "27H/03AE": 1627758832,
    "26E/01A": 1627758860,
    "18K/02A": 1627758874,
    "30L/01A": 1627758894,
    "24I/01A": 1627758906,
    "18F/03A": 1627758936,
    "13O/01A": 1627758954,
    "04F/01A": 1627758956,
    "23G/01A": 1627758948,
    "17L/01A": 1627759290,
    "07M/01A": 1627759330,
    "21A/01A": 1627759358,
    "03I/01A": 1627759378,
    "28G/01A": 1627759384,
    "13J/01A": 1627759390,
    "08P/01A": 1627759396,
    "14M/01A": 1627759382,
    "17B/01A": 1627759402,
    "08N/02AE": 1627759456,
    "06P/02AE": 1627759488,
    "24H/01A": 1627759492,
    "05O/02A": 7556914544,
    "02Q/02AE": 8235958464,
    "03J/04A": 1627759522,
    "03J/05A": 1627759524,
    "03I/04A": 12303382358,
    "06P/01A": 1627759552,
    "08L/01A": 1627759554,
    "03H/01A": 1627759560,
    "17C/06A": 7796342256,
    "19O/01A": 1627743354,
    "06M/01A": 1627743406,
    "24M/06A": 1627743418,
    "24M/07A": 1627743416,
    "24M/08A": 7554862818,
    "12J/01A": 1627743426,
    "03J/02AE": 1627743422,
    "02H/01A": 1627743428,
    "03J/03A": 1627743430,
    "24J/01A": 1627743440,
    "06K/05A": 1627743432,
    "30G/02A": 1627743460,
    "07P/01A": 1627743468,
    "04P/01A": 1627743482,
    "06O/09A": 7554777518,
    "30L/02A": 1627743538,
    "14I/01A": 1627743586,
    "20O/01A": 1627743648,
    "10M/01A": 1627743662,
    "07I/01A": 1627743682,
    "16G/03A": 7557137960,
    "21J/01A": 1627752100,
    "26M/01A": 1627752188,
    "06O/10A": 7554829602,
    "16K/02A": 1627758658,
    "30G/01A": 1627758670,
    "11L/01A": 1627758738,
    "19J/01A": 1627758764,
    "27H/02AE": 1627758830,
    "22K/01A": 1627758834,
    "16I/02A": 1627758876,
    "30G/11AE": 3507536920,
    "06N/02AE": 1627758894,
    "06O/01A": 1627758952,
    "05S/01A": 1627758968,
    "13L/02A": 1627758972,
    "07O/02A": 1627758976,
    "16L/01AE": 1627758980,
    "09H/06A": 7554777512,
    "26I/01A": 1627759368,
    "12O/02A": 1627759374,
    "06O/02A": 1627759380,
    "06K/01A": 1627759446,
    "07H/01A": 1627759468,
    "03G/01A": 1627759476,
    "11L/02A": 1627759502,
    "06M/06A": 9321904688,
    "29M/03H": 1627743398,
    "23F/01H": 1627743394,
    "23I/02H": 1627743396,
    "17C/01H": 1627743400,
    "17I/01H": 1627743402,
    "14H/02H": 1627743404,
    "11H/01H": 1627743408,
    "18F/02H": 1627743410,
    "17G/02H": 1627743414,
    "15K/01H": 1627743412,
    "03J/01H": 1627743424,
    "03J/06H": 1627743420,
    "08H/05H": 1701658505,
    "08H/01H": 1627743434,
    "17I/02H": 1627743436,
    "24J/01H": 1627743438,
    "31F/04H": 1627743442,
    "06I/01H": 1627743444,
    "06I/04H": 1627743446,
    "02I/01H": 1627743448,
    "24L/01H": 1627743450,
    "11J/01H": 1627743452,
    "09F/03H": 1627743456,
    "25M/01H": 1627743462,
    "24O/01H": 1627743464,
    "07F/04H": 1627743466,
    "03D/01H": 1627743470,
    "30L/04H": 1627743474,
    "21A/06H": 10065708580,
    "07F/02H": 1627743472,
    "26K/02H": 1627743476,
    "26I/01H": 1627743478,
    "05Q/01H": 1627743480,
    "17F/04H": 1627743484,
    "13O/02A": 1627743362,
    "27L/02A": 1627743364,
    "04H/05A": 1627743358,
    "04H/02A": 1627743454,
    "04J/09A": 11029759146,
    "20I/01A": 1627758678,
    "05E/02A": 1627743376,
    "09H/05A": 1627743356,
    "15E/05A": 1627743360,
    "25P/01A": 1627743366,
    "20H/01A": 1627743368,
    "25L/01A": 1627758944,
    "12G/01AE": 1627743374,
    "25E/02H": 1627743486,
    "07K/02H": 1627743488,
    "06G/01H": 1627743490,
    "24M/01H": 1627743492,
    "07P/01H": 1627743494,
    "04E/03H": 1627743500,
    "04F/02H": 1627743502,
    "03H/01H": 1627743504,
    "12L/01H": 1627743506,
    "17G/05H": 1627743508,
    "14H/03H": 1627743512,
    "14H/04H": 1627743514,
    "03J/04H": 1627743528,
    "14I/02H": 1627743516,
    "05S/01H": 1627743530,
    "30G/10H": 1627743520,
    "30G/09H": 1627743522,
    "02H/02H": 1627743524,
    "30G/04H": 1627743532,
    "17N/01H": 1627743536,
    "30L/03H": 1627743540,
    "30L/01H": 1627743542,
    "06S/02H": 1627743546,
    "20E/01H": 1627743550,
    "31E/01H": 1627743552,
    "31K/03H": 1627743556,
    "03L/01H": 1627743558,
    "14H/01H": 1627743560,
    "30E/02H": 1627743566,
    "17F/01H": 1627743576,
    "25N/01H": 1627743578,
    "20I/03H": 1627743580,
    "04J/02H": 1627743582,
    "04J/07H": 1627743584,
    "09I/05H": 1695334936,
    "07I/05H": 1627743592,
    "07I/04H": 1627743590,
    "07I/01H": 1627743616,
    "20O/03H": 1627743650,
    "19D/03H": 1627743656,
    "07F/05H": 1627743654,
    "10J/01H": 1627743658,
    "10M/07H": 4928135292,
    "10M/06H": 1627743660,
    "19F/01H": 1627743664,
    "12H/04H": 1627743670,
    "07H/03H": 1627743672,
    "05G/02H": 1627743676,
    "07I/02H": 1627743680,
    "28K/03H": 1627743688,
    "30G/07H": 1627743698,
    "01H/03H": 1627743706,
    "12M/01H": 1627743716,
    "06M/05H": 1627743784,
    "06M/01H": 1627743730,
    "10P/02H": 1627744042,
    "10P/01H": 1627743970,
    "08J/02H": 1627744270,
    "08J/01H": 1627744152,
    "02E/02H": 1627744404,
    "09M/01H": 1627744494,
    "04J/03H": 1627744618,
    "30E/01H": 1627744712,
    "17G/08H": 1627744828,
    "03M/01H": 1627744996,
    "20B/02H": 1627745096,
    "12J/02H": 1627744920,
    "08O/03H": 1627745264,
    "08O/02H": 1627745200,
    "31J/01H": 1627751868,
    "21A/05H": 1627751870,
    "23E/02H": 1627751872,
    "17G/01H": 1627751874,
    "28F/01H": 1627751878,
    "15E/04H": 1627751882,
    "31H/01H": 1627751884,
    "18L/01H": 1627751886,
    "03H/04H": 1627751894,
    "07G/01H": 1627751900,
    "04J/04H": 1627751922,
    "03L/04H": 8259755350,
    "03L/02H": 1627751948,
    "30L/02H": 1627751978,
    "10H/01H": 1627752042,
    "31F/02H": 1627752088,
    "20M/01H": 1627752122,
    "10F/01H": 1627752130,
    "27I/01H": 1627752142,
    "07H/01H": 1627752166,
    "06K/05H": 1627752302,
    "06K/01H": 1627752254,
    "07J/01H": 1627752204,
    "26G/01H": 1627752328,
    "08P/03H": 1627752390,
    "08P/01H": 1627752346,
    "07H/02H": 1627752420,
    "08Q/02H": 1627752674,
    "08Q/01H": 1627752468,
    "16G/02H": 1627752796,
    "19D/02H": 1627752886,
    "04E/01H": 1627752978,
    "21B/02H": 1627753084,
    "11I/01H": 1627758610,
    "16D/01H": 1627758614,
    "06G/04H": 1627758612,
    "19K/01H": 1627758616,
    "16K/03H": 1627758618,
    "23I/01H": 1627758620,
    "02G/02H": 8770661530,
    "15E/06H": 9087187588,
    "04E/06H": 1627758626,
    "03F/01H": 1627758628,
    "30G/05H": 1627758634,
    "04J/01H": 1627758630,
    "15K/02H": 1695334942,
    "14D/01H": 1627758636,
    "01G/03H": 1627758640,
    "01G/02H": 1627758638,
    "30H/02H": 1627758642,
    "16L/01H": 1627758644,
    "13E/03H": 1627758646,
    "07F/03H": 1627758648,
    "16H/01H": 1627758650,
    "06M/02H": 1627758652,
    "08H/04H": 1627758656,
    "08H/02H": 1627758654,
    "07F/01H": 1627758662,
    "06I/03H": 1627758664,
    "12I/01H": 1627758666,
    "16G/01H": 1627758606,
    "13H/01H": 1627758604,
    "17C/05H": 1627758674,
    "20I/02H": 1627743370,
    "04H/01H": 1627758680,
    "13H/04H": 1627758682,
    "03Q/01H": 1627758684,
    "21L/01H": 1627758690,
    "25K/01H": 1627758688,
    "22G/02H": 1627758692,
    "24G/01H": 1627758694,
    "03L/03AE": 1701658501,
    "03O/01AE": 1627758724,
    "08J/01AE": 1627758700,
    "10K/02AE": 1627758702,
    "03P/01AE": 1627758704,
    "07J/02AE": 1627758706,
    "09H/02AE": 1627758710,
    "12H/02AE": 1627758712,
    "08J/03AE": 1627758714,
    "05G/01AE": 1627758716,
    "08J/02AE": 1627758718,
    "07H/02AE": 1627758726,
    "09I/01AE": 1627758696,
    "04K/03AE": 2602492446,
    "09H/01AE": 1627758698,
    "09G/01AE": 1627758708,
    "06K/01AE": 1627758720,
    "10H/03AE": 4240009782,
    "06K/02AE": 1627758722,
    "21I/01H": 1627758728,
    "14N/01H": 1627758732,
    "21A/02H": 1627759318,
    "10M/04H": 1627758734,
    "21B/03H": 1627758740,
    "15E/01H": 1627758744,
    "06F/01H": 1627758742,
    "27L/04H": 1627758746,
    "13H/03H": 1627758748,
    "09J/01H": 1627758754,
    "11L/02H": 1627758760,
    "11L/01H": 1627758758,
    "04E/04H": 1627758766,
    "18G/02H": 1627758768,
    "01F/03H": 1627758770,
    "12O/01H": 1627758772,
    "25E/01H": 1627758776,
    "21B/01H": 1627758778,
    "01G/01H": 1627758780,
    "13K/01H": 1627758782,
    "05T/01H": 1627743526,
    "21O/02H": 1627758794,
    "04J/06H": 1627758796,
    "25G/03H": 1627758798,
    "07L/04H": 1627758808,
    "07L/01H": 1627758806,
    "20O/01H": 1627758810,
    "25G/02H": 1627758790,
    "16H/02H": 1627758792,
    "27H/01H": 1627758800,
    "21J/01H": 1627758802,
    "18I/01H": 1627758804,
    "10K/04H": 1627758812,
    "19M/01H": 1627758814,
    "24M/03H": 1627758820,
    "20I/01H": 1627758824,
    "26K/01H": 1627758818,
    "27J/01H": 1627758840,
    "27H/02H": 1627758848,
    "21O/01H": 1627758852,
    "24M/02H": 1627758822,
    "29L/01H": 1627758828,
    "30G/01H": 1627758836,
    "19N/01H": 1627758838,
    "14D/03H": 1627758842,
    "24L/02H": 1627758844,
    "12G/06H": 1627758850,
    "12E/03H": 1627758854,
    "27G/01H": 1627758856,
    "19E/02H": 1627758858,
    "17F/06H": 1627758862,
    "18E/05H": 1627758864,
    "07I/03H": 1627758866,
    "05M/01H": 1627758868,
    "25P/02H": 1627758870,
    "26H/01H": 1627758872,
    "10K/08H": 1627758880,
    "10K/03H": 1627758878,
    "28J/01H": 1627758882,
    "16K/05H": 1627758886,
    "29E/01H": 1627758892,
    "30M/05H": 1627758896,
    "30M/04H": 1627758898,
    "30M/01H": 1627758900,
    "30M/02H": 1627758902,
    "30E/05H": 1627758890,
    "24I/01H": 1627758910,
    "28K/02H": 1627758912,
    "17E/01H": 1627758914,
    "04G/04H": 1627758918,
    "11M/03H": 1627758922,
    "11M/01H": 1627758920,
    "04K/01H": 1627758926,
    "03J/03H": 1627758930,
    "30G/02H": 1627758932,
    "03E/01H": 1627758934,
    "20I/04H": 1627758938,
    "09H/01H": 1627758940,
    "25L/01H": 1627758942,
    "28G/02H": 1627758946,
    "19C/01H": 1627758950,
    "04F/01H": 1627758958,
    "06G/03H": 1627758960,
    "30F/01H": 1627758962,
    "17F/05H": 1627758964,
    "09G/02H": 1627758966,
    "06L/01H": 1627758970,
    "07O/01H": 1627758974,
    "09H/02H": 1627758978,
    "28L/03H": 7633043532,
    "28L/04H": 11791068834,
    "22C/02H": 1627758990,
    "19C/03H": 1627758992,
    "19N/02H": 1627758994,
    "26G/04H": 1627758996,
    "26G/05H": 1627758998,
    "18F/04H": 1627759000,
    "22C/01H": 1627759002,
    "10G/01H": 1627759004,
    "05E/03H": 1627759006,
    "12E/01H": 1627759008,
    "05Q/03H": 1627759010,
    "09I/06H": 10080899844,
    "11H/03H": 1627758986,
    "14D/02H": 1627759012,
    "18E/06H": 1627759014,
    "19C/02H": 1627759018,
    "07H/04H": 1627759020,
    "04G/01H": 1627759024,
    "04G/07H": 1627759022,
    "05G/01H": 1627759026,
    "20H/02H": 1627759028,
    "12G/02H": 1627759030,
    "12G/09H": 11204879762,
    "14I/01H": 1627759032,
    "19D/06H": 1627759034,
    "03G/05H": 1627759036,
    "12G/03H": 1627759040,
    "13H/02H": 1627759038,
    "20C/01H": 1627759042,
    "06I/02H": 1627759044,
    "21F/01H": 1627759046,
    "25H/01H": 1627759048,
    "20C/03H": 1627759050,
    "13E/04H": 1627759052,
    "22G/03H": 1627759054,
    "02P/01H": 1627759058,
    "13F/02H": 1627759056,
    "04J/05H": 1627759060,
    "17G/07H": 1627759062,
    "22C/03H": 1627759064,
    "12J/01H": 1627759066,
    "12G/08H": 1627759068,
    "20F/02H": 1627759070,
    "19D/05H": 1627759072,
    "02P/02H": 1627758788,
    "22D/01H": 1627759074,
    "03G/02H": 1627759016,
    "13I/02H": 11204877412,
    "17H/03H": 1627758984,
    "13J/03H": 11204877406,
    "03F/02H": 1627759116,
    "03F/03H": 1627759118,
    "03G/01H": 1627759232,
    "11I/07H": 1627759076,
    "13J/02H": 11204877400,
    "10K/06H": 1627759078,
    "05J/01H": 1627759080,
    "20G/01H": 1627759082,
    "10K/02H": 1627759084,
    "10N/01H": 1627759086,
    "04E/02H": 1627759088,
    "21K/01H": 1627759090,
    "09O/03H": 1627759094,
    "09O/02H": 1627759092,
    "18E/01H": 1627759096,
    "07K/03H": 1627759100,
    "04O/02H": 1627759102,
    "13I/01H": 1627759098,
    "11K/01H": 1627759106,
    "10L/01H": 1627759108,
    "05E/01H": 1627759110,
    "21B/05H": 1627759112,
    "04N/02H": 1627759114,
    "21A/01H": 1627759120,
    "02F/01H": 1627759122,
    "12N/01H": 1627759124,
    "30G/08H": 1627759126,
    "15E/03H": 1627759128,
    "09F/01H": 1627759130,
    "21B/06H": 1627759132,
    "10M/05H": 1627759134,
    "13F/04H": 1627759136,
    "06F/02H": 1627759138,
    "23M/01H": 1627759140,
    "12H/03H": 1627759142,
    "19E/03H": 1627759144,
    "14N/02H": 1627759146,
    "17F/02H": 1627759148,
    "10M/01H": 1627759150,
    "19D/04H": 1627759154,
    "17L/01H": 1627759156,
    "04O/01H": 1627759158,
    "12M/02H": 1627759160,
    "12H/02H": 1627759162,
    "30E/04H": 1627759164,
    "10G/04H": 1627759166,
    "04R/01H": 1627759168,
    "21C/01H": 1627759170,
    "21A/03H": 1627759172,
    "22G/01H": 1627759174,
    "12G/05H": 1627759176,
    "15E/02H": 1627759178,
    "09I/03H": 1627759180,
    "04G/02H": 1627759182,
    "19C/04H": 1627759184,
    "10K/05H": 1627759186,
    "12E/02H": 1627759190,
    "12F/01H": 1627759192,
    "26L/01H": 1627759194,
    "02Q/01H": 1627759196,
    "10G/05H": 1627759198,
    "05Q/04H": 1627759202,
    "05Q/02H": 1627759200,
    "16F/01H": 1627759204,
    "10F/02H": 1627759206,
    "21C/02H": 1627759208,
    "26K/03H": 1627759210,
    "17F/03H": 1627759212,
    "03H/05H": 1627759214,
    "31H/02H": 1627759218,
    "13F/01H": 1627759216,
    "06O/02H": 1627759220,
    "12G/04H": 1627759222,
    "10K/01H": 1627759224,
    "11I/03H": 1627759228,
    "11I/02H": 1627759226,
    "13E/01H": 1627759230,
    "18E/02H": 1627759234,
    "20E/02H": 1627759236,
    "13G/01H": 11204877430,
    "26F/02H": 1627759238,
    "10F/03H": 7927143702,
    "03H/02H": 1627759242,
    "11I/06H": 1627759240,
    "30K/01H": 10682227146,
    "25G/04H": 1627759244,
    "09G/01H": 1627759246,
    "04N/01H": 1627759248,
    "11M/02H": 1627759250,
    "06P/01H": 1627759252,
    "13E/02H": 1627759254,
    "18K/01H": 1627759256,
    "04O/04H": 1627759260,
    "04O/03H": 1627759258,
    "09I/02H": 1627759262,
    "20C/02H": 1627759264,
    "10G/02H": 1627758988,
    "17C/04H": 1627759152,
    "02G/01H": 1627759266,
    "09I/04H": 1627758982,
    "20D/02H": 1627759268,
    "25O/01H": 1627759270,
    "12H/01H": 1627759272,
    "19E/04H": 1627759278,
    "19E/01H": 1627759274,
    "18F/01H": 1627759276,
    "12F/04H": 1627759280,
    "11I/05H": 1627759282,
    "12G/01H": 1627759284,
    "12F/02H": 1627759286,
    "04G/05H": 1627759288,
    "16K/02H": 1627759294,
    "17G/03H": 1627759298,
    "27L/01H": 1627759300,
    "31L/01H": 10682227148,
    "11H/04H": 1627759308,
    "07L/03H": 1627759310,
    "17G/06H": 1627759306,
    "21A/04H": 1627759304,
    "06K/02H": 1627759312,
    "06O/08H": 1627759316,
    "06O/03H": 1627759314,
    "21A/07H": 10065708582,
    "08L/04H": 1627759322,
    "08L/01H": 1627759320,
    "02O/01H": 1627759324,
    "03G/06H": 8202036962,
    "03G/03H": 1627759326,
    "17M/01H": 1627759332,
    "19O/01H": 1627759334,
    "04G/06H": 1627759336,
    "03N/01H": 1627759338,
    "09J/02H": 1627759344,
    "03M/05H": 1627759346,
    "09H/03H": 1627759350,
    "10G/03H": 1627759352,
    "17D/01H": 1627759354,
    "07G/03H": 1627759356,
    "27L/05H": 1627759362,
    "27L/03H": 1627759364,
    "15O/01H": 1627759366,
    "19B/02H": 1627759370,
    "07K/04H": 2443345822,
    "07K/01H": 1627759342,
    "11O/01H": 1627759372,
    "25N/02H": 10018589836,
    "03I/02H": 1627759376,
    "28G/01H": 1627759386,
    "06O/05H": 1627759388,
    "17H/01H": 1627759392,
    "25G/01H": 1627759394,
    "05K/01H": 1627759398,
    "29G/01H": 1627759412,
    "07L/02H": 1627759414,
    "12I/02H": 1627759416,
    "01G/04H": 1627759418,
    "15P/02H": 1627759422,
    "15P/01H": 1627759420,
    "05S/02H": 1627759424,
    "21B/04H": 1627759426,
    "06O/06H": 1627759428,
    "09G/03H": 1627759432,
    "11I/09H": 1627759438,
    "31K/02H": 1627759440,
    "11I/10H": 1693384198,
    "11I/08H": 1627759442,
    "17B/01H": 1627759444,
    "13F/05H": 1627759448,
    "24H/01H": 1627759400,
    "10H/02H": 1627759404,
    "29G/02H": 3158125162,
    "04S/02H": 1627759408,
    "24G/02H": 1627759410,
    "17G/04H": 1627759450,
    "07G/02H": 1627759454,
    "29M/01H": 1627759458,
    "02H/01H": 1627759460,
    "03I/01H": 1627759464,
    "03I/03H": 1627759462,
    "17C/02H": 1627759466,
    "12G/07H": 1627759472,
    "24H/03H": 1627759470,
    "17H/02H": 1627759478,
    "10M/02H": 1627759480,
    "16C/01H": 1627759482,
    "24H/02H": 1627759506,
    "30H/03H": 1627759486,
    "18G/03H": 1627759490,
    "11I/04H": 1627759498,
    "19F/02H": 1627759500,
    "09O/01H": 1627759484,
    "03K/01H": 1627759494,
    "06L/02H": 1627759496,
    "08O/04H": 1693384202,
    "08O/01H": 1627759504,
    "06M/03H": 1627759508,
    "28L/02H": 1627759514,
    "12F/03H": 1627759516,
    "20L/01H": 1627759518,
    "03J/05H": 1627759520,
    "23K/01H": 1627759526,
    "10M/03H": 1627759528,
    "30F/02H": 1627759530,
    "15E/07H": 9087187590,
    "04K/02H": 1627759532,
    "03G/04H": 1627759536,
    "09L/01H": 1627759538,
    "10L/02H": 1627759540,
    "20D/01H": 1627759542,
    "13F/03H": 1627759544,
    "11G/01H": 1627759546,
    "16K/01H": 1627759548,
    "08L/02H": 1627759550,
    "03M/04H": 1627759556,
    "05N/01H": 1627759558,
    "03H/03H": 1627759562,
    "04R/02H": 1627759564,
    "03P/01H": 1627759566,
    "05H/01H": 1627759568,
    "05H/02H": 1627759570,
    "09K/01H": 1627759572,
    "19O/02H": 1627759574,
    "22E/01H": 1627759576,
    "23M/02H": 1627743390,
    "17C/03H": 1627758888,
    "18E/04H": 1627758916,
}

# --- SNIRH PARAMETER MAP (Portuguese + English) ----------------------------
SNIRH_PARAMS = {
    1850: (constants.DISCHARGE_DAILY_MEAN, "Caudal médio diário"),
    1845: (constants.STAGE_DAILY_MEAN, "Nível médio diário"),
}


[docs] class PortugalFetcher(base.RiverDataFetcher): """Fetches river gauge data from Portugal's National Water Resources Information System (SNIRH). Data Source: SNIRH (https://snirh.apambiente.pt/) Supported Variables: - ``constants.DISCHARGE_DAILY_MEAN`` (m³/s) - ``constants.STAGE_DAILY_MEAN`` (m) """ BASE_URL = "https://snirh.apambiente.pt/snirh/_dadosbase/site/janela_verdados.php"
[docs] @staticmethod def get_cached_metadata() -> pd.DataFrame: """Retrieves a DataFrame of available Portuguese gauge IDs and metadata. This method loads the metadata from a cached CSV file located in the ``rivretrieve/cached_site_data/`` directory. Returns: pd.DataFrame: A DataFrame indexed by gauge_id, containing site metadata. """ return utils.load_cached_metadata_csv("portugal")
[docs] @staticmethod def get_available_variables() -> tuple[str, ...]: return tuple(val[0] for val in SNIRH_PARAMS.values())
def _get_param_id(self, variable: str) -> Optional[int]: for param_id, (var_name, _) in SNIRH_PARAMS.items(): if var_name == variable: return param_id return None def _download_data(self, gauge_id: str, variable: str, start_date: str, end_date: str) -> Optional[pd.DataFrame]: """Downloads and parses the data table from SNIRH.""" if gauge_id not in SITE_MAP: logger.error(f"Gauge ID {gauge_id} not found in SITE_MAP.") return None site_id = SITE_MAP[gauge_id] param_id = self._get_param_id(variable) if param_id is None: raise ValueError(f"Unsupported variable: {variable}") _, param_en = SNIRH_PARAMS[param_id] # The API seems to return all data regardless of tmin/tmax, so we fetch all and filter later. tmin = "01/01/1900" tmax = datetime.now().strftime("%d/%m/%Y") url = f"{self.BASE_URL}?sites={site_id}&pars={param_id}&tmin={tmin}&tmax={tmax}" headers = { "User-Agent": "Mozilla/5.0", "Referer": "https://snirh.apambiente.pt/", } logger.info(f"Fetching {variable} for site {gauge_id} ({site_id}) from SNIRH") try: s = utils.requests_retry_session() r = s.get(url, headers=headers) r.raise_for_status() tables = pd.read_html(StringIO(r.text)) except Exception as e: logger.error(f"Failed to retrieve data for {gauge_id}, param {param_id}: {e}") return None logger.info(f"Found {len(tables)} tables for {gauge_id}, param {param_id}.") if len(tables) <= 4: logger.warning(f"No data table found for {gauge_id}, param {param_id} (expected more than 4 tables).") return None df = tables[4].copy().dropna(how="all").reset_index(drop=True) logger.debug(f"Table 4 head:\n{df.head()}") # Detect header pattern if len(df) >= 2 and "Data" in str(df.iloc[0, 0]) and "Data" in str(df.iloc[1, 0]): df = df.iloc[2:].copy() df.columns = [constants.TIME_INDEX, variable] elif "Data" in str(df.iloc[0, 0]): df.columns = [html.unescape(str(c)).strip() for c in df.iloc[0]] df = df.iloc[1:].copy() df.columns = [constants.TIME_INDEX, variable] else: df.columns = [constants.TIME_INDEX, variable] # Clean and format df[constants.TIME_INDEX] = pd.to_datetime(df[constants.TIME_INDEX], errors="coerce", format="%d/%m/%Y %H:%M") df[variable] = pd.to_numeric(df[variable], errors="coerce") return df def _parse_data(self, gauge_id: str, raw_data: Optional[pd.DataFrame], variable: str) -> pd.DataFrame: """Parses the raw DataFrame.""" if raw_data is None or raw_data.empty: return pd.DataFrame(columns=[constants.TIME_INDEX, variable]) return raw_data.set_index(constants.TIME_INDEX)
[docs] def get_data( self, gauge_id: str, variable: str, start_date: Optional[str] = None, end_date: Optional[str] = None, ) -> pd.DataFrame: """Fetches and parses time series data for a specific gauge and variable. This method retrieves the requested data from the provider's API or data source, parses it, and returns it in a standardized pandas DataFrame format. Args: gauge_id: The site-specific identifier for the gauge. variable: The variable to fetch. Must be one of the strings listed in the fetcher's ``get_available_variables()`` output. These are typically defined in ``rivretrieve.constants``. start_date: Optional start date for the data retrieval in 'YYYY-MM-DD' format. If None, data is fetched from the earliest available date. end_date: Optional end date for the data retrieval in 'YYYY-MM-DD' format. If None, data is fetched up to the latest available date. Returns: pd.DataFrame: A pandas DataFrame indexed by datetime objects (``constants.TIME_INDEX``) with a single column named after the requested ``variable``. The DataFrame will be empty if no data is found for the given parameters. Raises: ValueError: If the requested ``variable`` is not supported by this fetcher. requests.exceptions.RequestException: If a network error occurs during data download. Exception: For other unexpected errors during data fetching or parsing. """ start_date = utils.format_start_date(start_date) end_date = utils.format_end_date(end_date) if variable not in self.get_available_variables(): raise ValueError(f"Unsupported variable: {variable}") try: df = self._download_data(gauge_id, variable, start_date, end_date) if df is None or df.empty: return pd.DataFrame(columns=[constants.TIME_INDEX, variable]) df = self._parse_data(gauge_id, df, variable) # Filter by date range start_date_dt = pd.to_datetime(start_date) end_date_dt = pd.to_datetime(end_date) df = df[(df.index >= start_date_dt) & (df.index <= end_date_dt)] except Exception as e: logger.error(f"Failed to get data for site {gauge_id}, variable {variable}: {e}") return pd.DataFrame(columns=[constants.TIME_INDEX, variable]) return df