We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Data for French Guyana (up to today)
Hourly production data for French Guyana: https://opendata-guyane.edf.fr/explore/dataset/production-d-electricite-par-filiere-en-temps-reel/

This one is up-to-date to today!

Also mentioned in #1912, but that was closed due to inactivity after some of the data sources were implemented. Also, the URLs have changed.

Edit: looks like the FR_O parser supports a 'historical dataset', but not this real-time dataset.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `parsers/FR_O.py`
Content:
```
1 from datetime import datetime
2 from logging import getLogger
3 from typing import Dict, Optional, Tuple, Union
4 
5 from requests import Response, Session
6 
7 from electricitymap.contrib.lib.models.event_lists import (
8     PriceList,
9     ProductionBreakdownList,
10 )
11 from electricitymap.contrib.lib.models.events import (
12     EventSourceType,
13     ProductionMix,
14     StorageMix,
15 )
16 from electricitymap.contrib.lib.types import ZoneKey
17 
18 from .lib.exceptions import ParserException
19 
20 DOMAIN_MAPPING = {
21     "FR-COR": "https://opendata-corse.edf.fr",
22     "RE": "https://opendata-reunion.edf.fr",
23     "GF": "https://opendata-guyane.edf.fr",
24     "MQ": "https://opendata-martinique.edf.fr",
25     "GP": "https://opendata-guadeloupe.edf.fr",
26 }
27 
28 LIVE_DATASETS = {
29     "FR-COR": "production-delectricite-par-filiere-en-temps-reel",
30     "GP": "mix-temps-reel-guadeloupe",
31     "RE": "prod-electricite-temps-reel",
32 }
33 
34 HISTORICAL_DATASETS = {
35     "FR-COR": "production-delectricite-par-filiere",
36     "RE": "courbe-de-charge-de-la-production-delectricite-par-filiere",
37     "GF": "courbe-de-charge-de-la-production-delectricite-par-filiere",
38     "MQ": "courbe-de-charge-de-la-production-delectricite-par-filiere",
39     "GP": "courbe-de-charge-de-la-production-delectricite-par-filiere",
40 }
41 
42 API_PARAMETER_GROUPS = {
43     "production": {
44         "biomass": [
45             "biomasse",
46             "biomasse_mw",
47             "biomasse_mwh",
48             "bioenergies",
49             "bioenergies_mw",
50             "bioenergies_mwh",
51         ],
52         "coal": [
53             "charbon",
54         ],
55         "gas": [
56             "thermique_mw",
57             "thermique_mwh",
58             "turbines_a_combustion",
59         ],
60         "geothermal": [
61             "geothermie",
62             "geothermie_mw",
63         ],
64         "hydro": [
65             "hydraulique",
66             "hydraulique_mw",
67             "hydraulique_mwh",
68             "micro_hydro",
69             "micro_hydraulique_mw",
70         ],
71         "oil": ["diesel", "moteur_diesel"],
72         "solar": [
73             "photovoltaique",
74             "photovoltaique0",
75             "photovoltaique_mw",
76             "photovoltaique_mwh",
77             "solaire_mw",
78         ],
79         "wind": [
80             "eolien",
81             "eolien_mw",
82             "eolien_mwh",
83         ],
84         "unknown": ["bagasse_charbon_mwh", "charbon_bagasse_mw"],
85     },
86     "storage": {"battery": ["solde_stockage", "stockage"]},
87     "price": {
88         "price": ["cout_moyen_de_production_eur_mwh"],
89     },
90 }
91 
92 PRODUCTION_MAPPING = {
93     API_TYPE: type
94     for key in ["production"]
95     for type, groups in API_PARAMETER_GROUPS[key].items()
96     for API_TYPE in groups
97 }
98 
99 STORAGE_MAPPING = {
100     API_TYPE: type
101     for key in ["storage"]
102     for type, groups in API_PARAMETER_GROUPS[key].items()
103     for API_TYPE in groups
104 }
105 
106 PRICE_MAPPING = {
107     API_TYPE: type
108     for key in ["price"]
109     for type, groups in API_PARAMETER_GROUPS[key].items()
110     for API_TYPE in groups
111 }
112 
113 IGNORED_VALUES = ["jour", "total", "statut", "date", "heure", "liaisons", "tac"]
114 
115 
116 def generate_url(zone_key, target_datetime):
117     return f"{DOMAIN_MAPPING[zone_key]}/api/v2/catalog/datasets/{HISTORICAL_DATASETS[zone_key] if target_datetime else LIVE_DATASETS[zone_key]}/exports/json"
118 
119 
120 def generate_source(zone_key: ZoneKey):
121     # Return the domain name of the source without the protocol
122     return DOMAIN_MAPPING[zone_key].split("//")[1]
123 
124 
125 def fetch_data(
126     zone_key: ZoneKey,
127     session: Optional[Session] = None,
128     target_datetime: Optional[datetime] = None,
129 ) -> Tuple[list, str]:
130     ses = session or Session()
131 
132     DATE_STRING_MAPPING = {
133         "FR-COR": "date_heure" if target_datetime else "date",
134         "RE": "date_heure" if target_datetime else "date",
135         "GF": "date",
136         "MQ": "date_heure",
137         "GP": "date",
138     }
139 
140     if target_datetime and zone_key not in HISTORICAL_DATASETS.keys():
141         raise ParserException(
142             "FR_O.py",
143             f"Historical data not implemented for {zone_key} in this parser.",
144             zone_key,
145         )
146     elif target_datetime is None and zone_key not in LIVE_DATASETS.keys():
147         raise ParserException(
148             "FR_O.py",
149             f"Live data not implemented for {zone_key} in this parser.",
150             zone_key,
151         )
152 
153     URL_QUERIES: Dict[str, Union[str, None]] = {
154         #   "refine": "statut:Validé" if target_datetime else None,
155         "timezone": "UTC",
156         "order_by": f"{DATE_STRING_MAPPING[zone_key]} desc",
157         "refine": f"{DATE_STRING_MAPPING[zone_key]}:{target_datetime.strftime('%Y')}"
158         if target_datetime
159         else None,
160     }
161 
162     url = generate_url(zone_key, target_datetime)
163     response: Response = ses.get(url, params=URL_QUERIES)
164     data: Union[dict, list, None] = response.json()
165     if data == []:
166         raise ParserException(
167             "FR_O.py",
168             f"No data available for {zone_key} for {target_datetime.strftime('%Y')}"
169             if target_datetime
170             else f"No live data available for {zone_key}.",
171             zone_key,
172         )
173     elif isinstance(data, dict):
174         if data.get("errorcode") == "10002":
175             raise ParserException(
176                 "FR_O.py",
177                 f"Rate limit exceeded. Please try again later after: {data.get('reset_time')}",
178             )
179         elif data.get("error_code") == "ODSQLError":
180             raise ParserException(
181                 "FR_O.py",
182                 "Query malformed. Please check the parameters. If this was previously working there has likely been a change in the API.",
183             )
184     if not isinstance(data, list):
185         raise ParserException(
186             "FR_O.py",
187             f"Unexpected data format for {zone_key} for {target_datetime.strftime('%Y')}"
188             if target_datetime
189             else f"Unexpected data format for {zone_key}.",
190             zone_key,
191         )
192     return data, DATE_STRING_MAPPING[zone_key]
193 
194 
195 def fetch_production(
196     zone_key: ZoneKey,
197     session: Optional[Session] = None,
198     target_datetime: Optional[datetime] = None,
199     logger=getLogger(__name__),
200 ):
201     production_objects, date_string = fetch_data(zone_key, session, target_datetime)
202 
203     production_breakdown_list = ProductionBreakdownList(logger=logger)
204     for production_object in production_objects:
205         production = ProductionMix()
206         storage = StorageMix()
207         for mode_key in production_object:
208             if mode_key in PRODUCTION_MAPPING:
209                 production.add_value(
210                     PRODUCTION_MAPPING[mode_key],
211                     production_object[mode_key],
212                     correct_negative_with_zero=True,
213                 )
214             elif mode_key in STORAGE_MAPPING:
215                 storage.add_value(
216                     STORAGE_MAPPING[mode_key], -production_object[mode_key]
217                 )
218             elif mode_key in IGNORED_VALUES:
219                 pass
220             else:
221                 logger.warning(
222                     f"Unknown mode_key: '{mode_key}' encountered for {zone_key}."
223                 )
224 
225         production_breakdown_list.append(
226             zoneKey=zone_key,
227             datetime=datetime.fromisoformat(production_object[date_string]),
228             production=production,
229             storage=storage,
230             source=generate_source(zone_key),
231             sourceType=EventSourceType.estimated
232             if production_object["statut"] == "Estimé"
233             else EventSourceType.measured,
234         )
235     return production_breakdown_list.to_list()
236 
237 
238 def fetch_price(
239     zone_key: ZoneKey,
240     session: Optional[Session] = None,
241     target_datetime: Optional[datetime] = None,
242     logger=getLogger(__name__),
243 ):
244     data_objects, date_string = fetch_data(zone_key, session, target_datetime)
245 
246     price_list = PriceList(logger=logger)
247     for data_object in data_objects:
248         price: Union[float, int, None] = None
249         for mode_key in data_object:
250             if mode_key in PRICE_MAPPING:
251                 price = data_object[mode_key]
252                 break
253         if price is not None:
254             price_list.append(
255                 zoneKey=zone_key,
256                 currency="EUR",
257                 datetime=datetime.fromisoformat(data_object[date_string]),
258                 source=generate_source(zone_key),
259                 price=price,
260             )
261     return price_list.to_list()
262
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/parsers/FR_O.py b/parsers/FR_O.py
--- a/parsers/FR_O.py
+++ b/parsers/FR_O.py
@@ -29,6 +29,8 @@
     "FR-COR": "production-delectricite-par-filiere-en-temps-reel",
     "GP": "mix-temps-reel-guadeloupe",
     "RE": "prod-electricite-temps-reel",
+    "GF": "production-d-electricite-par-filiere-en-temps-reel",
+    "MQ": "production-delectricite-par-filiere-en-temps-reel",
 }
 
 HISTORICAL_DATASETS = {
@@ -68,7 +70,7 @@
             "micro_hydro",
             "micro_hydraulique_mw",
         ],
-        "oil": ["diesel", "moteur_diesel"],
+        "oil": ["diesel", "moteur_diesel", "centrale_au_fioul", "moteurs_diesels"],
         "solar": [
             "photovoltaique",
             "photovoltaique0",
@@ -133,7 +135,7 @@
         "FR-COR": "date_heure" if target_datetime else "date",
         "RE": "date_heure" if target_datetime else "date",
         "GF": "date",
-        "MQ": "date_heure",
+        "MQ": "date_heure" if target_datetime else "date",
         "GP": "date",
     }
 
@@ -229,7 +231,7 @@
             storage=storage,
             source=generate_source(zone_key),
             sourceType=EventSourceType.estimated
-            if production_object["statut"] == "Estimé"
+            if production_object.get("statut") == "Estimé"
             else EventSourceType.measured,
         )
     return production_breakdown_list.to_list()

{"golden_diff": "diff --git a/parsers/FR_O.py b/parsers/FR_O.py\n--- a/parsers/FR_O.py\n+++ b/parsers/FR_O.py\n@@ -29,6 +29,8 @@\n     \"FR-COR\": \"production-delectricite-par-filiere-en-temps-reel\",\n     \"GP\": \"mix-temps-reel-guadeloupe\",\n     \"RE\": \"prod-electricite-temps-reel\",\n+    \"GF\": \"production-d-electricite-par-filiere-en-temps-reel\",\n+    \"MQ\": \"production-delectricite-par-filiere-en-temps-reel\",\n }\n \n HISTORICAL_DATASETS = {\n@@ -68,7 +70,7 @@\n             \"micro_hydro\",\n             \"micro_hydraulique_mw\",\n         ],\n-        \"oil\": [\"diesel\", \"moteur_diesel\"],\n+        \"oil\": [\"diesel\", \"moteur_diesel\", \"centrale_au_fioul\", \"moteurs_diesels\"],\n         \"solar\": [\n             \"photovoltaique\",\n             \"photovoltaique0\",\n@@ -133,7 +135,7 @@\n         \"FR-COR\": \"date_heure\" if target_datetime else \"date\",\n         \"RE\": \"date_heure\" if target_datetime else \"date\",\n         \"GF\": \"date\",\n-        \"MQ\": \"date_heure\",\n+        \"MQ\": \"date_heure\" if target_datetime else \"date\",\n         \"GP\": \"date\",\n     }\n \n@@ -229,7 +231,7 @@\n             storage=storage,\n             source=generate_source(zone_key),\n             sourceType=EventSourceType.estimated\n-            if production_object[\"statut\"] == \"Estim\u00e9\"\n+            if production_object.get(\"statut\") == \"Estim\u00e9\"\n             else EventSourceType.measured,\n         )\n     return production_breakdown_list.to_list()\n", "issue": "Data for French Guyana (up to today)\nHourly production data for French Guyana: https://opendata-guyane.edf.fr/explore/dataset/production-d-electricite-par-filiere-en-temps-reel/\r\n\r\nThis one is up-to-date to today!\r\n\r\nAlso mentioned in #1912, but that was closed due to inactivity after some of the data sources were implemented. Also, the URLs have changed.\r\n\r\nEdit: looks like the FR_O parser supports a 'historical dataset', but not this real-time dataset.\n", "before_files": [{"content": "from datetime import datetime\nfrom logging import getLogger\nfrom typing import Dict, Optional, Tuple, Union\n\nfrom requests import Response, Session\n\nfrom electricitymap.contrib.lib.models.event_lists import (\n    PriceList,\n    ProductionBreakdownList,\n)\nfrom electricitymap.contrib.lib.models.events import (\n    EventSourceType,\n    ProductionMix,\n    StorageMix,\n)\nfrom electricitymap.contrib.lib.types import ZoneKey\n\nfrom .lib.exceptions import ParserException\n\nDOMAIN_MAPPING = {\n    \"FR-COR\": \"https://opendata-corse.edf.fr\",\n    \"RE\": \"https://opendata-reunion.edf.fr\",\n    \"GF\": \"https://opendata-guyane.edf.fr\",\n    \"MQ\": \"https://opendata-martinique.edf.fr\",\n    \"GP\": \"https://opendata-guadeloupe.edf.fr\",\n}\n\nLIVE_DATASETS = {\n    \"FR-COR\": \"production-delectricite-par-filiere-en-temps-reel\",\n    \"GP\": \"mix-temps-reel-guadeloupe\",\n    \"RE\": \"prod-electricite-temps-reel\",\n}\n\nHISTORICAL_DATASETS = {\n    \"FR-COR\": \"production-delectricite-par-filiere\",\n    \"RE\": \"courbe-de-charge-de-la-production-delectricite-par-filiere\",\n    \"GF\": \"courbe-de-charge-de-la-production-delectricite-par-filiere\",\n    \"MQ\": \"courbe-de-charge-de-la-production-delectricite-par-filiere\",\n    \"GP\": \"courbe-de-charge-de-la-production-delectricite-par-filiere\",\n}\n\nAPI_PARAMETER_GROUPS = {\n    \"production\": {\n        \"biomass\": [\n            \"biomasse\",\n            \"biomasse_mw\",\n            \"biomasse_mwh\",\n            \"bioenergies\",\n            \"bioenergies_mw\",\n            \"bioenergies_mwh\",\n        ],\n        \"coal\": [\n            \"charbon\",\n        ],\n        \"gas\": [\n            \"thermique_mw\",\n            \"thermique_mwh\",\n            \"turbines_a_combustion\",\n        ],\n        \"geothermal\": [\n            \"geothermie\",\n            \"geothermie_mw\",\n        ],\n        \"hydro\": [\n            \"hydraulique\",\n            \"hydraulique_mw\",\n            \"hydraulique_mwh\",\n            \"micro_hydro\",\n            \"micro_hydraulique_mw\",\n        ],\n        \"oil\": [\"diesel\", \"moteur_diesel\"],\n        \"solar\": [\n            \"photovoltaique\",\n            \"photovoltaique0\",\n            \"photovoltaique_mw\",\n            \"photovoltaique_mwh\",\n            \"solaire_mw\",\n        ],\n        \"wind\": [\n            \"eolien\",\n            \"eolien_mw\",\n            \"eolien_mwh\",\n        ],\n        \"unknown\": [\"bagasse_charbon_mwh\", \"charbon_bagasse_mw\"],\n    },\n    \"storage\": {\"battery\": [\"solde_stockage\", \"stockage\"]},\n    \"price\": {\n        \"price\": [\"cout_moyen_de_production_eur_mwh\"],\n    },\n}\n\nPRODUCTION_MAPPING = {\n    API_TYPE: type\n    for key in [\"production\"]\n    for type, groups in API_PARAMETER_GROUPS[key].items()\n    for API_TYPE in groups\n}\n\nSTORAGE_MAPPING = {\n    API_TYPE: type\n    for key in [\"storage\"]\n    for type, groups in API_PARAMETER_GROUPS[key].items()\n    for API_TYPE in groups\n}\n\nPRICE_MAPPING = {\n    API_TYPE: type\n    for key in [\"price\"]\n    for type, groups in API_PARAMETER_GROUPS[key].items()\n    for API_TYPE in groups\n}\n\nIGNORED_VALUES = [\"jour\", \"total\", \"statut\", \"date\", \"heure\", \"liaisons\", \"tac\"]\n\n\ndef generate_url(zone_key, target_datetime):\n    return f\"{DOMAIN_MAPPING[zone_key]}/api/v2/catalog/datasets/{HISTORICAL_DATASETS[zone_key] if target_datetime else LIVE_DATASETS[zone_key]}/exports/json\"\n\n\ndef generate_source(zone_key: ZoneKey):\n    # Return the domain name of the source without the protocol\n    return DOMAIN_MAPPING[zone_key].split(\"//\")[1]\n\n\ndef fetch_data(\n    zone_key: ZoneKey,\n    session: Optional[Session] = None,\n    target_datetime: Optional[datetime] = None,\n) -> Tuple[list, str]:\n    ses = session or Session()\n\n    DATE_STRING_MAPPING = {\n        \"FR-COR\": \"date_heure\" if target_datetime else \"date\",\n        \"RE\": \"date_heure\" if target_datetime else \"date\",\n        \"GF\": \"date\",\n        \"MQ\": \"date_heure\",\n        \"GP\": \"date\",\n    }\n\n    if target_datetime and zone_key not in HISTORICAL_DATASETS.keys():\n        raise ParserException(\n            \"FR_O.py\",\n            f\"Historical data not implemented for {zone_key} in this parser.\",\n            zone_key,\n        )\n    elif target_datetime is None and zone_key not in LIVE_DATASETS.keys():\n        raise ParserException(\n            \"FR_O.py\",\n            f\"Live data not implemented for {zone_key} in this parser.\",\n            zone_key,\n        )\n\n    URL_QUERIES: Dict[str, Union[str, None]] = {\n        #   \"refine\": \"statut:Valid\u00e9\" if target_datetime else None,\n        \"timezone\": \"UTC\",\n        \"order_by\": f\"{DATE_STRING_MAPPING[zone_key]} desc\",\n        \"refine\": f\"{DATE_STRING_MAPPING[zone_key]}:{target_datetime.strftime('%Y')}\"\n        if target_datetime\n        else None,\n    }\n\n    url = generate_url(zone_key, target_datetime)\n    response: Response = ses.get(url, params=URL_QUERIES)\n    data: Union[dict, list, None] = response.json()\n    if data == []:\n        raise ParserException(\n            \"FR_O.py\",\n            f\"No data available for {zone_key} for {target_datetime.strftime('%Y')}\"\n            if target_datetime\n            else f\"No live data available for {zone_key}.\",\n            zone_key,\n        )\n    elif isinstance(data, dict):\n        if data.get(\"errorcode\") == \"10002\":\n            raise ParserException(\n                \"FR_O.py\",\n                f\"Rate limit exceeded. Please try again later after: {data.get('reset_time')}\",\n            )\n        elif data.get(\"error_code\") == \"ODSQLError\":\n            raise ParserException(\n                \"FR_O.py\",\n                \"Query malformed. Please check the parameters. If this was previously working there has likely been a change in the API.\",\n            )\n    if not isinstance(data, list):\n        raise ParserException(\n            \"FR_O.py\",\n            f\"Unexpected data format for {zone_key} for {target_datetime.strftime('%Y')}\"\n            if target_datetime\n            else f\"Unexpected data format for {zone_key}.\",\n            zone_key,\n        )\n    return data, DATE_STRING_MAPPING[zone_key]\n\n\ndef fetch_production(\n    zone_key: ZoneKey,\n    session: Optional[Session] = None,\n    target_datetime: Optional[datetime] = None,\n    logger=getLogger(__name__),\n):\n    production_objects, date_string = fetch_data(zone_key, session, target_datetime)\n\n    production_breakdown_list = ProductionBreakdownList(logger=logger)\n    for production_object in production_objects:\n        production = ProductionMix()\n        storage = StorageMix()\n        for mode_key in production_object:\n            if mode_key in PRODUCTION_MAPPING:\n                production.add_value(\n                    PRODUCTION_MAPPING[mode_key],\n                    production_object[mode_key],\n                    correct_negative_with_zero=True,\n                )\n            elif mode_key in STORAGE_MAPPING:\n                storage.add_value(\n                    STORAGE_MAPPING[mode_key], -production_object[mode_key]\n                )\n            elif mode_key in IGNORED_VALUES:\n                pass\n            else:\n                logger.warning(\n                    f\"Unknown mode_key: '{mode_key}' encountered for {zone_key}.\"\n                )\n\n        production_breakdown_list.append(\n            zoneKey=zone_key,\n            datetime=datetime.fromisoformat(production_object[date_string]),\n            production=production,\n            storage=storage,\n            source=generate_source(zone_key),\n            sourceType=EventSourceType.estimated\n            if production_object[\"statut\"] == \"Estim\u00e9\"\n            else EventSourceType.measured,\n        )\n    return production_breakdown_list.to_list()\n\n\ndef fetch_price(\n    zone_key: ZoneKey,\n    session: Optional[Session] = None,\n    target_datetime: Optional[datetime] = None,\n    logger=getLogger(__name__),\n):\n    data_objects, date_string = fetch_data(zone_key, session, target_datetime)\n\n    price_list = PriceList(logger=logger)\n    for data_object in data_objects:\n        price: Union[float, int, None] = None\n        for mode_key in data_object:\n            if mode_key in PRICE_MAPPING:\n                price = data_object[mode_key]\n                break\n        if price is not None:\n            price_list.append(\n                zoneKey=zone_key,\n                currency=\"EUR\",\n                datetime=datetime.fromisoformat(data_object[date_string]),\n                source=generate_source(zone_key),\n                price=price,\n            )\n    return price_list.to_list()\n", "path": "parsers/FR_O.py"}], "after_files": [{"content": "from datetime import datetime\nfrom logging import getLogger\nfrom typing import Dict, Optional, Tuple, Union\n\nfrom requests import Response, Session\n\nfrom electricitymap.contrib.lib.models.event_lists import (\n    PriceList,\n    ProductionBreakdownList,\n)\nfrom electricitymap.contrib.lib.models.events import (\n    EventSourceType,\n    ProductionMix,\n    StorageMix,\n)\nfrom electricitymap.contrib.lib.types import ZoneKey\n\nfrom .lib.exceptions import ParserException\n\nDOMAIN_MAPPING = {\n    \"FR-COR\": \"https://opendata-corse.edf.fr\",\n    \"RE\": \"https://opendata-reunion.edf.fr\",\n    \"GF\": \"https://opendata-guyane.edf.fr\",\n    \"MQ\": \"https://opendata-martinique.edf.fr\",\n    \"GP\": \"https://opendata-guadeloupe.edf.fr\",\n}\n\nLIVE_DATASETS = {\n    \"FR-COR\": \"production-delectricite-par-filiere-en-temps-reel\",\n    \"GP\": \"mix-temps-reel-guadeloupe\",\n    \"RE\": \"prod-electricite-temps-reel\",\n    \"GF\": \"production-d-electricite-par-filiere-en-temps-reel\",\n    \"MQ\": \"production-delectricite-par-filiere-en-temps-reel\",\n}\n\nHISTORICAL_DATASETS = {\n    \"FR-COR\": \"production-delectricite-par-filiere\",\n    \"RE\": \"courbe-de-charge-de-la-production-delectricite-par-filiere\",\n    \"GF\": \"courbe-de-charge-de-la-production-delectricite-par-filiere\",\n    \"MQ\": \"courbe-de-charge-de-la-production-delectricite-par-filiere\",\n    \"GP\": \"courbe-de-charge-de-la-production-delectricite-par-filiere\",\n}\n\nAPI_PARAMETER_GROUPS = {\n    \"production\": {\n        \"biomass\": [\n            \"biomasse\",\n            \"biomasse_mw\",\n            \"biomasse_mwh\",\n            \"bioenergies\",\n            \"bioenergies_mw\",\n            \"bioenergies_mwh\",\n        ],\n        \"coal\": [\n            \"charbon\",\n        ],\n        \"gas\": [\n            \"thermique_mw\",\n            \"thermique_mwh\",\n            \"turbines_a_combustion\",\n        ],\n        \"geothermal\": [\n            \"geothermie\",\n            \"geothermie_mw\",\n        ],\n        \"hydro\": [\n            \"hydraulique\",\n            \"hydraulique_mw\",\n            \"hydraulique_mwh\",\n            \"micro_hydro\",\n            \"micro_hydraulique_mw\",\n        ],\n        \"oil\": [\"diesel\", \"moteur_diesel\", \"centrale_au_fioul\", \"moteurs_diesels\"],\n        \"solar\": [\n            \"photovoltaique\",\n            \"photovoltaique0\",\n            \"photovoltaique_mw\",\n            \"photovoltaique_mwh\",\n            \"solaire_mw\",\n        ],\n        \"wind\": [\n            \"eolien\",\n            \"eolien_mw\",\n            \"eolien_mwh\",\n        ],\n        \"unknown\": [\"bagasse_charbon_mwh\", \"charbon_bagasse_mw\"],\n    },\n    \"storage\": {\"battery\": [\"solde_stockage\", \"stockage\"]},\n    \"price\": {\n        \"price\": [\"cout_moyen_de_production_eur_mwh\"],\n    },\n}\n\nPRODUCTION_MAPPING = {\n    API_TYPE: type\n    for key in [\"production\"]\n    for type, groups in API_PARAMETER_GROUPS[key].items()\n    for API_TYPE in groups\n}\n\nSTORAGE_MAPPING = {\n    API_TYPE: type\n    for key in [\"storage\"]\n    for type, groups in API_PARAMETER_GROUPS[key].items()\n    for API_TYPE in groups\n}\n\nPRICE_MAPPING = {\n    API_TYPE: type\n    for key in [\"price\"]\n    for type, groups in API_PARAMETER_GROUPS[key].items()\n    for API_TYPE in groups\n}\n\nIGNORED_VALUES = [\"jour\", \"total\", \"statut\", \"date\", \"heure\", \"liaisons\", \"tac\"]\n\n\ndef generate_url(zone_key, target_datetime):\n    return f\"{DOMAIN_MAPPING[zone_key]}/api/v2/catalog/datasets/{HISTORICAL_DATASETS[zone_key] if target_datetime else LIVE_DATASETS[zone_key]}/exports/json\"\n\n\ndef generate_source(zone_key: ZoneKey):\n    # Return the domain name of the source without the protocol\n    return DOMAIN_MAPPING[zone_key].split(\"//\")[1]\n\n\ndef fetch_data(\n    zone_key: ZoneKey,\n    session: Optional[Session] = None,\n    target_datetime: Optional[datetime] = None,\n) -> Tuple[list, str]:\n    ses = session or Session()\n\n    DATE_STRING_MAPPING = {\n        \"FR-COR\": \"date_heure\" if target_datetime else \"date\",\n        \"RE\": \"date_heure\" if target_datetime else \"date\",\n        \"GF\": \"date\",\n        \"MQ\": \"date_heure\" if target_datetime else \"date\",\n        \"GP\": \"date\",\n    }\n\n    if target_datetime and zone_key not in HISTORICAL_DATASETS.keys():\n        raise ParserException(\n            \"FR_O.py\",\n            f\"Historical data not implemented for {zone_key} in this parser.\",\n            zone_key,\n        )\n    elif target_datetime is None and zone_key not in LIVE_DATASETS.keys():\n        raise ParserException(\n            \"FR_O.py\",\n            f\"Live data not implemented for {zone_key} in this parser.\",\n            zone_key,\n        )\n\n    URL_QUERIES: Dict[str, Union[str, None]] = {\n        #   \"refine\": \"statut:Valid\u00e9\" if target_datetime else None,\n        \"timezone\": \"UTC\",\n        \"order_by\": f\"{DATE_STRING_MAPPING[zone_key]} desc\",\n        \"refine\": f\"{DATE_STRING_MAPPING[zone_key]}:{target_datetime.strftime('%Y')}\"\n        if target_datetime\n        else None,\n    }\n\n    url = generate_url(zone_key, target_datetime)\n    response: Response = ses.get(url, params=URL_QUERIES)\n    data: Union[dict, list, None] = response.json()\n    if data == []:\n        raise ParserException(\n            \"FR_O.py\",\n            f\"No data available for {zone_key} for {target_datetime.strftime('%Y')}\"\n            if target_datetime\n            else f\"No live data available for {zone_key}.\",\n            zone_key,\n        )\n    elif isinstance(data, dict):\n        if data.get(\"errorcode\") == \"10002\":\n            raise ParserException(\n                \"FR_O.py\",\n                f\"Rate limit exceeded. Please try again later after: {data.get('reset_time')}\",\n            )\n        elif data.get(\"error_code\") == \"ODSQLError\":\n            raise ParserException(\n                \"FR_O.py\",\n                \"Query malformed. Please check the parameters. If this was previously working there has likely been a change in the API.\",\n            )\n    if not isinstance(data, list):\n        raise ParserException(\n            \"FR_O.py\",\n            f\"Unexpected data format for {zone_key} for {target_datetime.strftime('%Y')}\"\n            if target_datetime\n            else f\"Unexpected data format for {zone_key}.\",\n            zone_key,\n        )\n    return data, DATE_STRING_MAPPING[zone_key]\n\n\ndef fetch_production(\n    zone_key: ZoneKey,\n    session: Optional[Session] = None,\n    target_datetime: Optional[datetime] = None,\n    logger=getLogger(__name__),\n):\n    production_objects, date_string = fetch_data(zone_key, session, target_datetime)\n\n    production_breakdown_list = ProductionBreakdownList(logger=logger)\n    for production_object in production_objects:\n        production = ProductionMix()\n        storage = StorageMix()\n        for mode_key in production_object:\n            if mode_key in PRODUCTION_MAPPING:\n                production.add_value(\n                    PRODUCTION_MAPPING[mode_key],\n                    production_object[mode_key],\n                    correct_negative_with_zero=True,\n                )\n            elif mode_key in STORAGE_MAPPING:\n                storage.add_value(\n                    STORAGE_MAPPING[mode_key], -production_object[mode_key]\n                )\n            elif mode_key in IGNORED_VALUES:\n                pass\n            else:\n                logger.warning(\n                    f\"Unknown mode_key: '{mode_key}' encountered for {zone_key}.\"\n                )\n\n        production_breakdown_list.append(\n            zoneKey=zone_key,\n            datetime=datetime.fromisoformat(production_object[date_string]),\n            production=production,\n            storage=storage,\n            source=generate_source(zone_key),\n            sourceType=EventSourceType.estimated\n            if production_object.get(\"statut\") == \"Estim\u00e9\"\n            else EventSourceType.measured,\n        )\n    return production_breakdown_list.to_list()\n\n\ndef fetch_price(\n    zone_key: ZoneKey,\n    session: Optional[Session] = None,\n    target_datetime: Optional[datetime] = None,\n    logger=getLogger(__name__),\n):\n    data_objects, date_string = fetch_data(zone_key, session, target_datetime)\n\n    price_list = PriceList(logger=logger)\n    for data_object in data_objects:\n        price: Union[float, int, None] = None\n        for mode_key in data_object:\n            if mode_key in PRICE_MAPPING:\n                price = data_object[mode_key]\n                break\n        if price is not None:\n            price_list.append(\n                zoneKey=zone_key,\n                currency=\"EUR\",\n                datetime=datetime.fromisoformat(data_object[date_string]),\n                source=generate_source(zone_key),\n                price=price,\n            )\n    return price_list.to_list()\n", "path": "parsers/FR_O.py"}]}