We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
SV production parser down
## Description

This is an automatic error report generated for El Salvador (SV).

Issues:
- No recent data found for `production` parser

## Suggestions
- Try running the parser locally using the command `poetry run test_parser SV production`
- <a href="https://storage.googleapis.com/electricitymap-parser-logs/SV.html">Explore the runtime logs</a>

You can see an overview of all parser issues [here](https://github.com/tmrowco/electricitymap-contrib/wiki/Parser-issues).


--- 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/SV.py`
Content:
```
1 #!/usr/bin/env python3
2 
3 import json
4 import re
5 from collections import defaultdict
6 from datetime import datetime
7 from logging import Logger, getLogger
8 from operator import itemgetter
9 from typing import Optional
10 
11 import arrow
12 from bs4 import BeautifulSoup
13 from requests import Session
14 
15 # This parser gets hourly electricity generation data from ut.com.sv for El Salvador.
16 # El Salvador does have wind generation but there is no data available.
17 # The 'Termico' category only consists of generation from oil/diesel according to historical data.
18 # See: https://www.iea.org/statistics/?country=ELSALVADOR&year=2016&category=Key%20indicators&indicator=ElecGenByFuel
19 # A new Liquid Natural Gas power plant may come online in 2020/2021.
20 # See: https://gastechinsights.com/article/what-energa-del-pacficos-lng-to-power-project-means-for-el-salvador
21 
22 # Thanks to jarek for figuring out how to make the correct POST request to the data url.
23 
24 url = "http://estadistico.ut.com.sv/OperacionDiaria.aspx"
25 
26 generation_map = {
27     0: "biomass",
28     1: "wind",
29     2: "geothermal",
30     3: "hydro",
31     4: "interconnection",
32     5: "thermal",
33     6: "solar",
34     "datetime": "datetime",
35 }
36 
37 
38 def get_data(session: Optional[Session] = None):
39     """
40     Makes a get request to data url.
41     Parses the response then makes a post request to the same url using
42     parsed parameters from the get request.
43     Returns a requests response object.
44     """
45 
46     s = session or Session()
47     pagereq = s.get(url)
48 
49     soup = BeautifulSoup(pagereq.content, "html.parser")
50 
51     # Find and define parameters needed to send a POST request for the actual data.
52     viewstategenerator = soup.find("input", attrs={"id": "__VIEWSTATEGENERATOR"})[
53         "value"
54     ]
55     viewstate = soup.find("input", attrs={"id": "__VIEWSTATE"})["value"]
56     eventvalidation = soup.find("input", attrs={"id": "__EVENTVALIDATION"})["value"]
57     DXCss = "1_33,1_4,1_9,1_5,15_2,15_4"
58     DXScript = "1_232,1_134,1_225,1_169,1_187,15_1,1_183,1_182,1_140,1_147,1_148,1_142,1_141,1_143,1_144,1_145,1_146,15_0,15_6,15_7"
59     callback_param_init = 'c0:{"Task":"Initialize","DashboardId":"OperacionDiaria","Settings":{"calculateHiddenTotals":false},"RequestMarker":0,"ClientState":{}}'
60 
61     postdata = {
62         "__VIEWSTATE": viewstate,
63         "__VIEWSTATEGENERATOR": viewstategenerator,
64         "__EVENTVALIDATION": eventvalidation,
65         "__CALLBACKPARAM": callback_param_init,
66         "__CALLBACKID": "ASPxDashboardViewer1",
67         "DXScript": DXScript,
68         "DXCss": DXCss,
69     }
70 
71     datareq = s.post(url, data=postdata)
72 
73     return datareq
74 
75 
76 def data_parser(datareq) -> list:
77     """
78     Accepts a requests response.text object.
79     Slices the object down to a smaller size then converts to usable json.
80     Loads the data as json then finds the 'result' key.
81     Uses regex to find the start
82     and endpoints of the actual data.
83     Splits the data into datapoints then cleans them up for processing.
84     """
85 
86     double_json = datareq.text[len("0|/*DX*/(") : -1]
87     double_json = double_json.replace("'", '"')
88     data = json.loads(double_json)
89     jsresult = data["result"]
90 
91     startpoints = [m.end(0) for m in re.finditer('"Data":{', jsresult)]
92     endpoints = [m.start(0) for m in re.finditer('"KeyIds"', jsresult)]
93 
94     sliced = jsresult[startpoints[1] : endpoints[2]]
95     sliced = "".join(sliced.split())
96     sliced = sliced[1:-4]
97 
98     chopped = sliced.split(',"')
99 
100     diced = []
101     for item in chopped:
102         item = item.replace("}", "")
103         np = item.split('":')
104         diced.append(np[0::2])
105 
106     clean_data = []
107     for item in diced:
108         j = json.loads(item[0])
109         k = float(item[1])
110         j.append(k)
111         clean_data.append(j)
112 
113     return clean_data
114 
115 
116 def data_processer(data) -> list:
117     """
118     Takes data in the form of a list of lists.
119     Converts each list to a dictionary.
120     Joins dictionaries based on shared datetime key.
121     Maps generation to type.
122     """
123 
124     converted = []
125     for val in data:
126         newval = {"datetime": val[2], val[0]: val[3]}
127         converted.append(newval)
128 
129     # Join dicts on 'datetime' key.
130     d = defaultdict(dict)
131     for elem in converted:
132         d[elem["datetime"]].update(elem)
133 
134     joined_data = sorted(d.values(), key=itemgetter("datetime"))
135 
136     def get_datetime(hour):
137         at = arrow.now("UTC-6").floor("hour")
138         dt = (at.replace(hour=int(hour), minute=0, second=0)).datetime
139         return dt
140 
141     mapped_data = []
142     for point in joined_data:
143         point = {generation_map[num]: val for num, val in point.items()}
144         point["datetime"] = get_datetime(point["datetime"])
145         mapped_data.append(point)
146 
147     return mapped_data
148 
149 
150 def fetch_production(
151     zone_key: str = "SV",
152     session: Optional[Session] = None,
153     target_datetime: Optional[datetime] = None,
154     logger: Logger = getLogger(__name__),
155 ) -> list:
156     """Requests the last known production mix (in MW) of a given country."""
157     if target_datetime:
158         raise NotImplementedError("This parser is not yet able to parse past dates")
159 
160     req = get_data(session=None)
161     parsed = data_parser(req)
162     data = data_processer(parsed)
163     production_mix_by_hour = []
164     for hour in data:
165         production_mix = {
166             "zoneKey": zone_key,
167             "datetime": hour["datetime"],
168             "production": {
169                 "biomass": hour.get("biomass", 0.0),
170                 "coal": 0.0,
171                 "gas": 0.0,
172                 "hydro": hour.get("hydro", 0.0),
173                 "nuclear": 0.0,
174                 "oil": hour.get("thermal", 0.0),
175                 "solar": hour.get("solar", 0.0),
176                 "wind": hour.get("wind", 0.0),
177                 "geothermal": hour.get("geothermal", 0.0),
178                 "unknown": 0.0,
179             },
180             "storage": {
181                 "hydro": None,
182             },
183             "source": "ut.com.sv",
184         }
185         production_mix_by_hour.append(production_mix)
186 
187     return production_mix_by_hour
188 
189 
190 if __name__ == "__main__":
191     """Main method, never used by the Electricity Map backend, but handy for testing."""
192 
193     print("fetch_production() ->")
194     print(fetch_production())
195
```
--- 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/SV.py b/parsers/SV.py
--- a/parsers/SV.py
+++ b/parsers/SV.py
@@ -21,7 +21,7 @@
 
 # Thanks to jarek for figuring out how to make the correct POST request to the data url.
 
-url = "http://estadistico.ut.com.sv/OperacionDiaria.aspx"
+url = "https://estadistico.ut.com.sv/OperacionDiaria.aspx"
 
 generation_map = {
     0: "biomass",

{"golden_diff": "diff --git a/parsers/SV.py b/parsers/SV.py\n--- a/parsers/SV.py\n+++ b/parsers/SV.py\n@@ -21,7 +21,7 @@\n \n # Thanks to jarek for figuring out how to make the correct POST request to the data url.\n \n-url = \"http://estadistico.ut.com.sv/OperacionDiaria.aspx\"\n+url = \"https://estadistico.ut.com.sv/OperacionDiaria.aspx\"\n \n generation_map = {\n     0: \"biomass\",\n", "issue": "SV production parser down\n## Description\n\nThis is an automatic error report generated for El Salvador (SV).\n\nIssues:\n- No recent data found for `production` parser\n\n## Suggestions\n- Try running the parser locally using the command `poetry run test_parser SV production`\n- <a href=\"https://storage.googleapis.com/electricitymap-parser-logs/SV.html\">Explore the runtime logs</a>\n\nYou can see an overview of all parser issues [here](https://github.com/tmrowco/electricitymap-contrib/wiki/Parser-issues).\n\n", "before_files": [{"content": "#!/usr/bin/env python3\n\nimport json\nimport re\nfrom collections import defaultdict\nfrom datetime import datetime\nfrom logging import Logger, getLogger\nfrom operator import itemgetter\nfrom typing import Optional\n\nimport arrow\nfrom bs4 import BeautifulSoup\nfrom requests import Session\n\n# This parser gets hourly electricity generation data from ut.com.sv for El Salvador.\n# El Salvador does have wind generation but there is no data available.\n# The 'Termico' category only consists of generation from oil/diesel according to historical data.\n# See: https://www.iea.org/statistics/?country=ELSALVADOR&year=2016&category=Key%20indicators&indicator=ElecGenByFuel\n# A new Liquid Natural Gas power plant may come online in 2020/2021.\n# See: https://gastechinsights.com/article/what-energa-del-pacficos-lng-to-power-project-means-for-el-salvador\n\n# Thanks to jarek for figuring out how to make the correct POST request to the data url.\n\nurl = \"http://estadistico.ut.com.sv/OperacionDiaria.aspx\"\n\ngeneration_map = {\n    0: \"biomass\",\n    1: \"wind\",\n    2: \"geothermal\",\n    3: \"hydro\",\n    4: \"interconnection\",\n    5: \"thermal\",\n    6: \"solar\",\n    \"datetime\": \"datetime\",\n}\n\n\ndef get_data(session: Optional[Session] = None):\n    \"\"\"\n    Makes a get request to data url.\n    Parses the response then makes a post request to the same url using\n    parsed parameters from the get request.\n    Returns a requests response object.\n    \"\"\"\n\n    s = session or Session()\n    pagereq = s.get(url)\n\n    soup = BeautifulSoup(pagereq.content, \"html.parser\")\n\n    # Find and define parameters needed to send a POST request for the actual data.\n    viewstategenerator = soup.find(\"input\", attrs={\"id\": \"__VIEWSTATEGENERATOR\"})[\n        \"value\"\n    ]\n    viewstate = soup.find(\"input\", attrs={\"id\": \"__VIEWSTATE\"})[\"value\"]\n    eventvalidation = soup.find(\"input\", attrs={\"id\": \"__EVENTVALIDATION\"})[\"value\"]\n    DXCss = \"1_33,1_4,1_9,1_5,15_2,15_4\"\n    DXScript = \"1_232,1_134,1_225,1_169,1_187,15_1,1_183,1_182,1_140,1_147,1_148,1_142,1_141,1_143,1_144,1_145,1_146,15_0,15_6,15_7\"\n    callback_param_init = 'c0:{\"Task\":\"Initialize\",\"DashboardId\":\"OperacionDiaria\",\"Settings\":{\"calculateHiddenTotals\":false},\"RequestMarker\":0,\"ClientState\":{}}'\n\n    postdata = {\n        \"__VIEWSTATE\": viewstate,\n        \"__VIEWSTATEGENERATOR\": viewstategenerator,\n        \"__EVENTVALIDATION\": eventvalidation,\n        \"__CALLBACKPARAM\": callback_param_init,\n        \"__CALLBACKID\": \"ASPxDashboardViewer1\",\n        \"DXScript\": DXScript,\n        \"DXCss\": DXCss,\n    }\n\n    datareq = s.post(url, data=postdata)\n\n    return datareq\n\n\ndef data_parser(datareq) -> list:\n    \"\"\"\n    Accepts a requests response.text object.\n    Slices the object down to a smaller size then converts to usable json.\n    Loads the data as json then finds the 'result' key.\n    Uses regex to find the start\n    and endpoints of the actual data.\n    Splits the data into datapoints then cleans them up for processing.\n    \"\"\"\n\n    double_json = datareq.text[len(\"0|/*DX*/(\") : -1]\n    double_json = double_json.replace(\"'\", '\"')\n    data = json.loads(double_json)\n    jsresult = data[\"result\"]\n\n    startpoints = [m.end(0) for m in re.finditer('\"Data\":{', jsresult)]\n    endpoints = [m.start(0) for m in re.finditer('\"KeyIds\"', jsresult)]\n\n    sliced = jsresult[startpoints[1] : endpoints[2]]\n    sliced = \"\".join(sliced.split())\n    sliced = sliced[1:-4]\n\n    chopped = sliced.split(',\"')\n\n    diced = []\n    for item in chopped:\n        item = item.replace(\"}\", \"\")\n        np = item.split('\":')\n        diced.append(np[0::2])\n\n    clean_data = []\n    for item in diced:\n        j = json.loads(item[0])\n        k = float(item[1])\n        j.append(k)\n        clean_data.append(j)\n\n    return clean_data\n\n\ndef data_processer(data) -> list:\n    \"\"\"\n    Takes data in the form of a list of lists.\n    Converts each list to a dictionary.\n    Joins dictionaries based on shared datetime key.\n    Maps generation to type.\n    \"\"\"\n\n    converted = []\n    for val in data:\n        newval = {\"datetime\": val[2], val[0]: val[3]}\n        converted.append(newval)\n\n    # Join dicts on 'datetime' key.\n    d = defaultdict(dict)\n    for elem in converted:\n        d[elem[\"datetime\"]].update(elem)\n\n    joined_data = sorted(d.values(), key=itemgetter(\"datetime\"))\n\n    def get_datetime(hour):\n        at = arrow.now(\"UTC-6\").floor(\"hour\")\n        dt = (at.replace(hour=int(hour), minute=0, second=0)).datetime\n        return dt\n\n    mapped_data = []\n    for point in joined_data:\n        point = {generation_map[num]: val for num, val in point.items()}\n        point[\"datetime\"] = get_datetime(point[\"datetime\"])\n        mapped_data.append(point)\n\n    return mapped_data\n\n\ndef fetch_production(\n    zone_key: str = \"SV\",\n    session: Optional[Session] = None,\n    target_datetime: Optional[datetime] = None,\n    logger: Logger = getLogger(__name__),\n) -> list:\n    \"\"\"Requests the last known production mix (in MW) of a given country.\"\"\"\n    if target_datetime:\n        raise NotImplementedError(\"This parser is not yet able to parse past dates\")\n\n    req = get_data(session=None)\n    parsed = data_parser(req)\n    data = data_processer(parsed)\n    production_mix_by_hour = []\n    for hour in data:\n        production_mix = {\n            \"zoneKey\": zone_key,\n            \"datetime\": hour[\"datetime\"],\n            \"production\": {\n                \"biomass\": hour.get(\"biomass\", 0.0),\n                \"coal\": 0.0,\n                \"gas\": 0.0,\n                \"hydro\": hour.get(\"hydro\", 0.0),\n                \"nuclear\": 0.0,\n                \"oil\": hour.get(\"thermal\", 0.0),\n                \"solar\": hour.get(\"solar\", 0.0),\n                \"wind\": hour.get(\"wind\", 0.0),\n                \"geothermal\": hour.get(\"geothermal\", 0.0),\n                \"unknown\": 0.0,\n            },\n            \"storage\": {\n                \"hydro\": None,\n            },\n            \"source\": \"ut.com.sv\",\n        }\n        production_mix_by_hour.append(production_mix)\n\n    return production_mix_by_hour\n\n\nif __name__ == \"__main__\":\n    \"\"\"Main method, never used by the Electricity Map backend, but handy for testing.\"\"\"\n\n    print(\"fetch_production() ->\")\n    print(fetch_production())\n", "path": "parsers/SV.py"}], "after_files": [{"content": "#!/usr/bin/env python3\n\nimport json\nimport re\nfrom collections import defaultdict\nfrom datetime import datetime\nfrom logging import Logger, getLogger\nfrom operator import itemgetter\nfrom typing import Optional\n\nimport arrow\nfrom bs4 import BeautifulSoup\nfrom requests import Session\n\n# This parser gets hourly electricity generation data from ut.com.sv for El Salvador.\n# El Salvador does have wind generation but there is no data available.\n# The 'Termico' category only consists of generation from oil/diesel according to historical data.\n# See: https://www.iea.org/statistics/?country=ELSALVADOR&year=2016&category=Key%20indicators&indicator=ElecGenByFuel\n# A new Liquid Natural Gas power plant may come online in 2020/2021.\n# See: https://gastechinsights.com/article/what-energa-del-pacficos-lng-to-power-project-means-for-el-salvador\n\n# Thanks to jarek for figuring out how to make the correct POST request to the data url.\n\nurl = \"https://estadistico.ut.com.sv/OperacionDiaria.aspx\"\n\ngeneration_map = {\n    0: \"biomass\",\n    1: \"wind\",\n    2: \"geothermal\",\n    3: \"hydro\",\n    4: \"interconnection\",\n    5: \"thermal\",\n    6: \"solar\",\n    \"datetime\": \"datetime\",\n}\n\n\ndef get_data(session: Optional[Session] = None):\n    \"\"\"\n    Makes a get request to data url.\n    Parses the response then makes a post request to the same url using\n    parsed parameters from the get request.\n    Returns a requests response object.\n    \"\"\"\n\n    s = session or Session()\n    pagereq = s.get(url)\n\n    soup = BeautifulSoup(pagereq.content, \"html.parser\")\n\n    # Find and define parameters needed to send a POST request for the actual data.\n    viewstategenerator = soup.find(\"input\", attrs={\"id\": \"__VIEWSTATEGENERATOR\"})[\n        \"value\"\n    ]\n    viewstate = soup.find(\"input\", attrs={\"id\": \"__VIEWSTATE\"})[\"value\"]\n    eventvalidation = soup.find(\"input\", attrs={\"id\": \"__EVENTVALIDATION\"})[\"value\"]\n    DXCss = \"1_33,1_4,1_9,1_5,15_2,15_4\"\n    DXScript = \"1_232,1_134,1_225,1_169,1_187,15_1,1_183,1_182,1_140,1_147,1_148,1_142,1_141,1_143,1_144,1_145,1_146,15_0,15_6,15_7\"\n    callback_param_init = 'c0:{\"Task\":\"Initialize\",\"DashboardId\":\"OperacionDiaria\",\"Settings\":{\"calculateHiddenTotals\":false},\"RequestMarker\":0,\"ClientState\":{}}'\n\n    postdata = {\n        \"__VIEWSTATE\": viewstate,\n        \"__VIEWSTATEGENERATOR\": viewstategenerator,\n        \"__EVENTVALIDATION\": eventvalidation,\n        \"__CALLBACKPARAM\": callback_param_init,\n        \"__CALLBACKID\": \"ASPxDashboardViewer1\",\n        \"DXScript\": DXScript,\n        \"DXCss\": DXCss,\n    }\n\n    datareq = s.post(url, data=postdata)\n\n    return datareq\n\n\ndef data_parser(datareq) -> list:\n    \"\"\"\n    Accepts a requests response.text object.\n    Slices the object down to a smaller size then converts to usable json.\n    Loads the data as json then finds the 'result' key.\n    Uses regex to find the start\n    and endpoints of the actual data.\n    Splits the data into datapoints then cleans them up for processing.\n    \"\"\"\n\n    double_json = datareq.text[len(\"0|/*DX*/(\") : -1]\n    double_json = double_json.replace(\"'\", '\"')\n    data = json.loads(double_json)\n    jsresult = data[\"result\"]\n\n    startpoints = [m.end(0) for m in re.finditer('\"Data\":{', jsresult)]\n    endpoints = [m.start(0) for m in re.finditer('\"KeyIds\"', jsresult)]\n\n    sliced = jsresult[startpoints[1] : endpoints[2]]\n    sliced = \"\".join(sliced.split())\n    sliced = sliced[1:-4]\n\n    chopped = sliced.split(',\"')\n\n    diced = []\n    for item in chopped:\n        item = item.replace(\"}\", \"\")\n        np = item.split('\":')\n        diced.append(np[0::2])\n\n    clean_data = []\n    for item in diced:\n        j = json.loads(item[0])\n        k = float(item[1])\n        j.append(k)\n        clean_data.append(j)\n\n    return clean_data\n\n\ndef data_processer(data) -> list:\n    \"\"\"\n    Takes data in the form of a list of lists.\n    Converts each list to a dictionary.\n    Joins dictionaries based on shared datetime key.\n    Maps generation to type.\n    \"\"\"\n\n    converted = []\n    for val in data:\n        newval = {\"datetime\": val[2], val[0]: val[3]}\n        converted.append(newval)\n\n    # Join dicts on 'datetime' key.\n    d = defaultdict(dict)\n    for elem in converted:\n        d[elem[\"datetime\"]].update(elem)\n\n    joined_data = sorted(d.values(), key=itemgetter(\"datetime\"))\n\n    def get_datetime(hour):\n        at = arrow.now(\"UTC-6\").floor(\"hour\")\n        dt = (at.replace(hour=int(hour), minute=0, second=0)).datetime\n        return dt\n\n    mapped_data = []\n    for point in joined_data:\n        point = {generation_map[num]: val for num, val in point.items()}\n        point[\"datetime\"] = get_datetime(point[\"datetime\"])\n        mapped_data.append(point)\n\n    return mapped_data\n\n\ndef fetch_production(\n    zone_key: str = \"SV\",\n    session: Optional[Session] = None,\n    target_datetime: Optional[datetime] = None,\n    logger: Logger = getLogger(__name__),\n) -> list:\n    \"\"\"Requests the last known production mix (in MW) of a given country.\"\"\"\n    if target_datetime:\n        raise NotImplementedError(\"This parser is not yet able to parse past dates\")\n\n    req = get_data(session=None)\n    parsed = data_parser(req)\n    data = data_processer(parsed)\n    production_mix_by_hour = []\n    for hour in data:\n        production_mix = {\n            \"zoneKey\": zone_key,\n            \"datetime\": hour[\"datetime\"],\n            \"production\": {\n                \"biomass\": hour.get(\"biomass\", 0.0),\n                \"coal\": 0.0,\n                \"gas\": 0.0,\n                \"hydro\": hour.get(\"hydro\", 0.0),\n                \"nuclear\": 0.0,\n                \"oil\": hour.get(\"thermal\", 0.0),\n                \"solar\": hour.get(\"solar\", 0.0),\n                \"wind\": hour.get(\"wind\", 0.0),\n                \"geothermal\": hour.get(\"geothermal\", 0.0),\n                \"unknown\": 0.0,\n            },\n            \"storage\": {\n                \"hydro\": None,\n            },\n            \"source\": \"ut.com.sv\",\n        }\n        production_mix_by_hour.append(production_mix)\n\n    return production_mix_by_hour\n\n\nif __name__ == \"__main__\":\n    \"\"\"Main method, never used by the Electricity Map backend, but handy for testing.\"\"\"\n\n    print(\"fetch_production() ->\")\n    print(fetch_production())\n", "path": "parsers/SV.py"}]}