prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  _tsf_reader.convert_base doesn't handle "10 minutes" frequency correctly
  ## Description
  For Monash datasets with the "10 minutes" frequency, the frequency converter will convert it to a frequency 10 MonthEnd (10M), instead of the expect 10 Minutes (10T) frequency.
  
  There is already code to properly handle the "minutely" frequency, but it checks for that string explicitly, so it doesn't catch the "10 minutes" case.
  
  ## To Reproduce
  One dataset which has this frequency is the 10 minutes observation Solar dataset: https://zenodo.org/record/4656144
  filename: `"solar_10_minutes_dataset.zip"`
  record: `"4656132"`
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/gluonts/dataset/repository/_tsf_reader.py`
  Content:
  ```
  1 # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
  2 #
  3 # Licensed under the Apache License, Version 2.0 (the "License").
  4 # You may not use this file except in compliance with the License.
  5 # A copy of the License is located at
  6 #
  7 #     http://www.apache.org/licenses/LICENSE-2.0
  8 #
  9 # or in the "license" file accompanying this file. This file is distributed
  10 # on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
  11 # express or implied. See the License for the specific language governing
  12 # permissions and limitations under the License.
  13 
  14 from datetime import datetime
  15 from distutils.util import strtobool
  16 from multiprocessing import cpu_count
  17 from types import SimpleNamespace
  18 
  19 import numpy as np
  20 from toolz import compose_left
  21 
  22 from gluonts import json
  23 from gluonts.nursery import glide
  24 
  25 parse_bool = compose_left(strtobool, bool)
  26 
  27 
  28 def parse_attribute(ty, value: str):
  29     if ty == "numeric":
  30         return int(value)
  31 
  32     if ty == "string":
  33         return value
  34 
  35     if ty == "date":
  36         return datetime.strptime(value, "%Y-%m-%d %H-%M-%S")
  37 
  38     raise AttributeError(ty)
  39 
  40 
  41 def frequency_converter(freq: str):
  42     parts = freq.split("_")
  43     if len(parts) == 1:
  44         return convert_base(parts[0])
  45     if len(parts) == 2:
  46         return convert_multiple(parts[0]) + convert_base(parts[1])
  47     raise ValueError(f"Invalid frequency string {freq}.")
  48 
  49 
  50 def convert_base(text: str) -> str:
  51     if text.lower() == "minutely":
  52         return "T"
  53     return text[0].upper()
  54 
  55 
  56 def convert_multiple(text: str) -> str:
  57     if text.isnumeric():
  58         return text
  59     if text == "half":
  60         return "0.5"
  61     raise ValueError(f"Unknown frequency multiple {text}.")
  62 
  63 
  64 class TSFReader:
  65     def __init__(
  66         self,
  67         path,
  68         target_name="target",
  69     ):
  70         self.path = path
  71         self.target_name = target_name
  72 
  73         self.meta = SimpleNamespace(columns={})
  74 
  75     def read(self):
  76         with open(self.path, encoding="latin1") as in_file:
  77             # strip whitespace
  78             lines = map(str.strip, in_file)
  79 
  80             # ignore all lines starting with #
  81             lines = filter(lambda line: not line.startswith("#"), lines)
  82 
  83             data_tag_found = self._read_header(lines)
  84             assert data_tag_found, "Missing @data tag."
  85             assert (
  86                 self.meta.columns
  87             ), "Missing attribute section. Attribute section must come before data."
  88 
  89             assert self.target_name not in self.meta.columns
  90             self.meta.columns[self.target_name] = None
  91 
  92             data = self._read_data_section(lines)
  93 
  94             return self.meta, data
  95 
  96     def _read_header(self, lines):
  97         for line in lines:
  98             assert line.startswith("@")
  99             stop = self._tag(line[1:])
  100 
  101             if stop:
  102                 return True
  103 
  104         return False
  105 
  106     def _read_data_section(self, lines):
  107         lines = list(lines)
  108 
  109         lines = glide.imap_unordered(
  110             self._read_data, lines, num_workers=cpu_count(), batch_size=8092
  111         )
  112 
  113         return list(lines)
  114 
  115     def _read_data(self, line):
  116         parts = line.split(":")
  117 
  118         assert len(parts) == len(
  119             self.meta.columns
  120         ), "Missing attributes/values in series."
  121 
  122         *attributes, target = parts
  123 
  124         record = {}
  125 
  126         record[self.target_name] = self._data_target(target)
  127 
  128         for (column, ty), attr in zip(self.meta.columns.items(), attributes):
  129             record[column] = parse_attribute(ty, attr)
  130 
  131         return record
  132 
  133     def _data_target(self, s):
  134         s = s.replace("?", '"nan"')
  135 
  136         values = json.loads(f"[{s}]")
  137         assert (
  138             values
  139         ), "A given series should contains a set of comma separated numeric values. At least one numeric value should be there in a series. Missing values should be indicated with ? symbol"
  140 
  141         return np.array(values, dtype=float)
  142 
  143     def _tag(self, line):
  144         fn_by_tag = {
  145             "attribute": self._tag_attribute,
  146             "frequency": self._tag_frequency,
  147             "horizon": self._tag_horizon,
  148             "missing": self._tag_missing,
  149             "equallength": self._tag_equallength,
  150             "data": self._tag_data,
  151         }
  152         tag, *args = line.split(" ")
  153 
  154         if tag not in fn_by_tag:
  155             return
  156 
  157         return fn_by_tag[tag](*args)
  158 
  159     def _tag_attribute(self, name, ty):
  160         self.meta.columns[name] = ty
  161 
  162     def _tag_frequency(self, frequency):
  163         self.meta.frequency = frequency
  164 
  165     def _tag_horizon(self, horizon):
  166         self.meta.forecast_horizon = horizon
  167 
  168     def _tag_missing(self, missing):
  169         self.meta.has_missing_values = parse_bool(missing)
  170 
  171     def _tag_equallength(self, equallength):
  172         self.meta.has_equal_length = parse_bool(equallength)
  173 
  174     def _tag_data(self):
  175         return True
  176
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/src/gluonts/dataset/repository/_tsf_reader.py b/src/gluonts/dataset/repository/_tsf_reader.py
  --- a/src/gluonts/dataset/repository/_tsf_reader.py
  +++ b/src/gluonts/dataset/repository/_tsf_reader.py
  @@ -15,11 +15,13 @@
   from distutils.util import strtobool
   from multiprocessing import cpu_count
   from types import SimpleNamespace
  +from typing import Dict
   
   import numpy as np
   from toolz import compose_left
   
   from gluonts import json
  +from gluonts.exceptions import GluonTSDataError
   from gluonts.nursery import glide
   
   parse_bool = compose_left(strtobool, bool)
  @@ -47,10 +49,32 @@
       raise ValueError(f"Invalid frequency string {freq}.")
   
   
  +BASE_FREQ_TO_PANDAS_OFFSET: Dict[str, str] = {
  +    "seconds": "S",
  +    "minutely": "T",
  +    "minutes": "T",
  +    "hourly": "H",
  +    "hours": "H",
  +    "daily": "D",
  +    "days": "D",
  +    "weekly": "W",
  +    "weeks": "W",
  +    "monthly": "M",
  +    "months": "M",
  +    "quarterly": "Q",
  +    "quarters": "Q",
  +    "yearly": "Y",
  +    "years": "Y",
  +}
  +
  +
   def convert_base(text: str) -> str:
  -    if text.lower() == "minutely":
  -        return "T"
  -    return text[0].upper()
  +    try:
  +        return BASE_FREQ_TO_PANDAS_OFFSET[text]
  +    except KeyError:
  +        raise GluonTSDataError(
  +            f'"{text}" is not recognized as a frequency string'
  +        )
   
   
   def convert_multiple(text: str) -> str:
  
verification_info: {"golden_diff": "diff --git a/src/gluonts/dataset/repository/_tsf_reader.py b/src/gluonts/dataset/repository/_tsf_reader.py\n--- a/src/gluonts/dataset/repository/_tsf_reader.py\n+++ b/src/gluonts/dataset/repository/_tsf_reader.py\n@@ -15,11 +15,13 @@\n from distutils.util import strtobool\n from multiprocessing import cpu_count\n from types import SimpleNamespace\n+from typing import Dict\n \n import numpy as np\n from toolz import compose_left\n \n from gluonts import json\n+from gluonts.exceptions import GluonTSDataError\n from gluonts.nursery import glide\n \n parse_bool = compose_left(strtobool, bool)\n@@ -47,10 +49,32 @@\n     raise ValueError(f\"Invalid frequency string {freq}.\")\n \n \n+BASE_FREQ_TO_PANDAS_OFFSET: Dict[str, str] = {\n+    \"seconds\": \"S\",\n+    \"minutely\": \"T\",\n+    \"minutes\": \"T\",\n+    \"hourly\": \"H\",\n+    \"hours\": \"H\",\n+    \"daily\": \"D\",\n+    \"days\": \"D\",\n+    \"weekly\": \"W\",\n+    \"weeks\": \"W\",\n+    \"monthly\": \"M\",\n+    \"months\": \"M\",\n+    \"quarterly\": \"Q\",\n+    \"quarters\": \"Q\",\n+    \"yearly\": \"Y\",\n+    \"years\": \"Y\",\n+}\n+\n+\n def convert_base(text: str) -> str:\n-    if text.lower() == \"minutely\":\n-        return \"T\"\n-    return text[0].upper()\n+    try:\n+        return BASE_FREQ_TO_PANDAS_OFFSET[text]\n+    except KeyError:\n+        raise GluonTSDataError(\n+            f'\"{text}\" is not recognized as a frequency string'\n+        )\n \n \n def convert_multiple(text: str) -> str:\n", "issue": "_tsf_reader.convert_base doesn't handle \"10 minutes\" frequency correctly\n## Description\r\nFor Monash datasets with the \"10 minutes\" frequency, the frequency converter will convert it to a frequency 10 MonthEnd (10M), instead of the expect 10 Minutes (10T) frequency.\r\n\r\nThere is already code to properly handle the \"minutely\" frequency, but it checks for that string explicitly, so it doesn't catch the \"10 minutes\" case.\r\n\r\n## To Reproduce\r\nOne dataset which has this frequency is the 10 minutes observation Solar dataset: https://zenodo.org/record/4656144\r\nfilename: `\"solar_10_minutes_dataset.zip\"`\r\nrecord: `\"4656132\"`\n", "before_files": [{"content": "# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\").\n# You may not use this file except in compliance with the License.\n# A copy of the License is located at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# or in the \"license\" file accompanying this file. This file is distributed\n# on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n# express or implied. See the License for the specific language governing\n# permissions and limitations under the License.\n\nfrom datetime import datetime\nfrom distutils.util import strtobool\nfrom multiprocessing import cpu_count\nfrom types import SimpleNamespace\n\nimport numpy as np\nfrom toolz import compose_left\n\nfrom gluonts import json\nfrom gluonts.nursery import glide\n\nparse_bool = compose_left(strtobool, bool)\n\n\ndef parse_attribute(ty, value: str):\n    if ty == \"numeric\":\n        return int(value)\n\n    if ty == \"string\":\n        return value\n\n    if ty == \"date\":\n        return datetime.strptime(value, \"%Y-%m-%d %H-%M-%S\")\n\n    raise AttributeError(ty)\n\n\ndef frequency_converter(freq: str):\n    parts = freq.split(\"_\")\n    if len(parts) == 1:\n        return convert_base(parts[0])\n    if len(parts) == 2:\n        return convert_multiple(parts[0]) + convert_base(parts[1])\n    raise ValueError(f\"Invalid frequency string {freq}.\")\n\n\ndef convert_base(text: str) -> str:\n    if text.lower() == \"minutely\":\n        return \"T\"\n    return text[0].upper()\n\n\ndef convert_multiple(text: str) -> str:\n    if text.isnumeric():\n        return text\n    if text == \"half\":\n        return \"0.5\"\n    raise ValueError(f\"Unknown frequency multiple {text}.\")\n\n\nclass TSFReader:\n    def __init__(\n        self,\n        path,\n        target_name=\"target\",\n    ):\n        self.path = path\n        self.target_name = target_name\n\n        self.meta = SimpleNamespace(columns={})\n\n    def read(self):\n        with open(self.path, encoding=\"latin1\") as in_file:\n            # strip whitespace\n            lines = map(str.strip, in_file)\n\n            # ignore all lines starting with #\n            lines = filter(lambda line: not line.startswith(\"#\"), lines)\n\n            data_tag_found = self._read_header(lines)\n            assert data_tag_found, \"Missing @data tag.\"\n            assert (\n                self.meta.columns\n            ), \"Missing attribute section. Attribute section must come before data.\"\n\n            assert self.target_name not in self.meta.columns\n            self.meta.columns[self.target_name] = None\n\n            data = self._read_data_section(lines)\n\n            return self.meta, data\n\n    def _read_header(self, lines):\n        for line in lines:\n            assert line.startswith(\"@\")\n            stop = self._tag(line[1:])\n\n            if stop:\n                return True\n\n        return False\n\n    def _read_data_section(self, lines):\n        lines = list(lines)\n\n        lines = glide.imap_unordered(\n            self._read_data, lines, num_workers=cpu_count(), batch_size=8092\n        )\n\n        return list(lines)\n\n    def _read_data(self, line):\n        parts = line.split(\":\")\n\n        assert len(parts) == len(\n            self.meta.columns\n        ), \"Missing attributes/values in series.\"\n\n        *attributes, target = parts\n\n        record = {}\n\n        record[self.target_name] = self._data_target(target)\n\n        for (column, ty), attr in zip(self.meta.columns.items(), attributes):\n            record[column] = parse_attribute(ty, attr)\n\n        return record\n\n    def _data_target(self, s):\n        s = s.replace(\"?\", '\"nan\"')\n\n        values = json.loads(f\"[{s}]\")\n        assert (\n            values\n        ), \"A given series should contains a set of comma separated numeric values. At least one numeric value should be there in a series. Missing values should be indicated with ? symbol\"\n\n        return np.array(values, dtype=float)\n\n    def _tag(self, line):\n        fn_by_tag = {\n            \"attribute\": self._tag_attribute,\n            \"frequency\": self._tag_frequency,\n            \"horizon\": self._tag_horizon,\n            \"missing\": self._tag_missing,\n            \"equallength\": self._tag_equallength,\n            \"data\": self._tag_data,\n        }\n        tag, *args = line.split(\" \")\n\n        if tag not in fn_by_tag:\n            return\n\n        return fn_by_tag[tag](*args)\n\n    def _tag_attribute(self, name, ty):\n        self.meta.columns[name] = ty\n\n    def _tag_frequency(self, frequency):\n        self.meta.frequency = frequency\n\n    def _tag_horizon(self, horizon):\n        self.meta.forecast_horizon = horizon\n\n    def _tag_missing(self, missing):\n        self.meta.has_missing_values = parse_bool(missing)\n\n    def _tag_equallength(self, equallength):\n        self.meta.has_equal_length = parse_bool(equallength)\n\n    def _tag_data(self):\n        return True\n", "path": "src/gluonts/dataset/repository/_tsf_reader.py"}], "after_files": [{"content": "# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\").\n# You may not use this file except in compliance with the License.\n# A copy of the License is located at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# or in the \"license\" file accompanying this file. This file is distributed\n# on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either\n# express or implied. See the License for the specific language governing\n# permissions and limitations under the License.\n\nfrom datetime import datetime\nfrom distutils.util import strtobool\nfrom multiprocessing import cpu_count\nfrom types import SimpleNamespace\nfrom typing import Dict\n\nimport numpy as np\nfrom toolz import compose_left\n\nfrom gluonts import json\nfrom gluonts.exceptions import GluonTSDataError\nfrom gluonts.nursery import glide\n\nparse_bool = compose_left(strtobool, bool)\n\n\ndef parse_attribute(ty, value: str):\n    if ty == \"numeric\":\n        return int(value)\n\n    if ty == \"string\":\n        return value\n\n    if ty == \"date\":\n        return datetime.strptime(value, \"%Y-%m-%d %H-%M-%S\")\n\n    raise AttributeError(ty)\n\n\ndef frequency_converter(freq: str):\n    parts = freq.split(\"_\")\n    if len(parts) == 1:\n        return convert_base(parts[0])\n    if len(parts) == 2:\n        return convert_multiple(parts[0]) + convert_base(parts[1])\n    raise ValueError(f\"Invalid frequency string {freq}.\")\n\n\nBASE_FREQ_TO_PANDAS_OFFSET: Dict[str, str] = {\n    \"seconds\": \"S\",\n    \"minutely\": \"T\",\n    \"minutes\": \"T\",\n    \"hourly\": \"H\",\n    \"hours\": \"H\",\n    \"daily\": \"D\",\n    \"days\": \"D\",\n    \"weekly\": \"W\",\n    \"weeks\": \"W\",\n    \"monthly\": \"M\",\n    \"months\": \"M\",\n    \"quarterly\": \"Q\",\n    \"quarters\": \"Q\",\n    \"yearly\": \"Y\",\n    \"years\": \"Y\",\n}\n\n\ndef convert_base(text: str) -> str:\n    try:\n        return BASE_FREQ_TO_PANDAS_OFFSET[text]\n    except KeyError:\n        raise GluonTSDataError(\n            f'\"{text}\" is not recognized as a frequency string'\n        )\n\n\ndef convert_multiple(text: str) -> str:\n    if text.isnumeric():\n        return text\n    if text == \"half\":\n        return \"0.5\"\n    raise ValueError(f\"Unknown frequency multiple {text}.\")\n\n\nclass TSFReader:\n    def __init__(\n        self,\n        path,\n        target_name=\"target\",\n    ):\n        self.path = path\n        self.target_name = target_name\n\n        self.meta = SimpleNamespace(columns={})\n\n    def read(self):\n        with open(self.path, encoding=\"latin1\") as in_file:\n            # strip whitespace\n            lines = map(str.strip, in_file)\n\n            # ignore all lines starting with #\n            lines = filter(lambda line: not line.startswith(\"#\"), lines)\n\n            data_tag_found = self._read_header(lines)\n            assert data_tag_found, \"Missing @data tag.\"\n            assert (\n                self.meta.columns\n            ), \"Missing attribute section. Attribute section must come before data.\"\n\n            assert self.target_name not in self.meta.columns\n            self.meta.columns[self.target_name] = None\n\n            data = self._read_data_section(lines)\n\n            return self.meta, data\n\n    def _read_header(self, lines):\n        for line in lines:\n            assert line.startswith(\"@\")\n            stop = self._tag(line[1:])\n\n            if stop:\n                return True\n\n        return False\n\n    def _read_data_section(self, lines):\n        lines = list(lines)\n\n        lines = glide.imap_unordered(\n            self._read_data, lines, num_workers=cpu_count(), batch_size=8092\n        )\n\n        return list(lines)\n\n    def _read_data(self, line):\n        parts = line.split(\":\")\n\n        assert len(parts) == len(\n            self.meta.columns\n        ), \"Missing attributes/values in series.\"\n\n        *attributes, target = parts\n\n        record = {}\n\n        record[self.target_name] = self._data_target(target)\n\n        for (column, ty), attr in zip(self.meta.columns.items(), attributes):\n            record[column] = parse_attribute(ty, attr)\n\n        return record\n\n    def _data_target(self, s):\n        s = s.replace(\"?\", '\"nan\"')\n\n        values = json.loads(f\"[{s}]\")\n        assert (\n            values\n        ), \"A given series should contains a set of comma separated numeric values. At least one numeric value should be there in a series. Missing values should be indicated with ? symbol\"\n\n        return np.array(values, dtype=float)\n\n    def _tag(self, line):\n        fn_by_tag = {\n            \"attribute\": self._tag_attribute,\n            \"frequency\": self._tag_frequency,\n            \"horizon\": self._tag_horizon,\n            \"missing\": self._tag_missing,\n            \"equallength\": self._tag_equallength,\n            \"data\": self._tag_data,\n        }\n        tag, *args = line.split(\" \")\n\n        if tag not in fn_by_tag:\n            return\n\n        return fn_by_tag[tag](*args)\n\n    def _tag_attribute(self, name, ty):\n        self.meta.columns[name] = ty\n\n    def _tag_frequency(self, frequency):\n        self.meta.frequency = frequency\n\n    def _tag_horizon(self, horizon):\n        self.meta.forecast_horizon = horizon\n\n    def _tag_missing(self, missing):\n        self.meta.has_missing_values = parse_bool(missing)\n\n    def _tag_equallength(self, equallength):\n        self.meta.has_equal_length = parse_bool(equallength)\n\n    def _tag_data(self):\n        return True\n", "path": "src/gluonts/dataset/repository/_tsf_reader.py"}]}