Repository: earthobservations/wetterdienst

Issue:
No SI unit conversion for mosmix and dmo requests
**Describe the bug**
I am collecting observation and mosmix data. Thereby, I found that the mosmix data seems to always be in non-SI-units, no matter what is defined in the Settings. This is at least true for the "radiation_global" parameter. You can check it by yourself by executing the code below and toggling "ts_si_units" between False and True.

**To Reproduce**

```python
from wetterdienst.provider.dwd.mosmix import DwdMosmixRequest
from wetterdienst import Settings
settings=Settings(ts_shape="long", ts_humanize=True, ts_si_units=False)
request = DwdMosmixRequest(parameter="radiation_global",
                           mosmix_type="SMALL",
                           start_date='2024-09-15T00:00:00',
                           end_date='2024-09-16T00:00:00',
                          start_issue='2024-09-15T00:00:00',
                          settings=settings)
station_result = request.filter_by_station_id("Q104")
list(station_result.values.query())[0].df.to_pandas()
```

**Expected behavior**
Normally, the values should differ by a factor of 1000, as the non-SI-unit is kJ/m2 and the SI unit is J/m2. But they do not.

**Desktop (please complete the following information):**
 - OS: Windows 11 64 bit
 - Python-Version 3.10
 - wetterdienst 0.95.0


Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index f3752942..cb9a2ad6 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -5,6 +5,8 @@ Development
 ***********
 
 - Bump polars to `>=1.0.0`
+- Change `DWDMosmixValues` and `DWDDmoValues` to follow the core `_collect_station_parameter` method
+- Allow only single issue retrieving with `DWDMosmixRequest` and `DWDDmoRequest`
 
 0.95.1 (04.09.2024)
 *******************
diff --git a/examples/provider/dwd/mosmix/dwd_mosmix_forecasts.py b/examples/provider/dwd/mosmix/dwd_mosmix_forecasts.py
index b6200cc0..eb499c20 100644
--- a/examples/provider/dwd/mosmix/dwd_mosmix_forecasts.py
+++ b/examples/provider/dwd/mosmix/dwd_mosmix_forecasts.py
@@ -29,7 +29,7 @@ def mosmix_example():
 
     request = DwdMosmixRequest(
         parameter=["DD", "ww"],
-        start_issue=DwdForecastDate.LATEST,  # automatically set if left empty
+        issue=DwdForecastDate.LATEST,  # automatically set if left empty
         mosmix_type=DwdMosmixType.LARGE,
         settings=settings,
     )
@@ -50,7 +50,7 @@ def mosmix_example():
 
     request = DwdMosmixRequest(
         parameter=["DD", "ww"],
-        start_issue=DwdForecastDate.LATEST,  # automatically set if left empty
+        issue=DwdForecastDate.LATEST,  # automatically set if left empty
         mosmix_type=DwdMosmixType.LARGE,
         station_group="all_stations",
     )
@@ -69,7 +69,7 @@ def mosmix_example():
 
     request = DwdMosmixRequest(
         parameter=["DD", "ww"],
-        start_issue=DwdForecastDate.LATEST,  # automatically set if left empty
+        issue=DwdForecastDate.LATEST,  # automatically set if left empty
         mosmix_type=DwdMosmixType.SMALL,
     )
 
diff --git a/examples/provider/dwd/mosmix/dwd_mosmix_validation.py b/examples/provider/dwd/mosmix/dwd_mosmix_validation.py
index 8d5945cf..dac59217 100644
--- a/examples/provider/dwd/mosmix/dwd_mosmix_validation.py
+++ b/examples/provider/dwd/mosmix/dwd_mosmix_validation.py
@@ -23,20 +23,20 @@ def find_observation_id(string: str) -> str:
     return input(f"Select the observation id from {observation_request.df.get_column('station_id')}")  # noqa: S608
 
 
-def get_earliest_start_issue() -> dt.datetime:
+def get_earliest_issue() -> dt.datetime:
     """Get the earliest start issue for the mosmix request."""
-    start_issue = dt.datetime.now(ZoneInfo("UTC")) - dt.timedelta(days=2)
-    if start_issue.hour < 3:
-        start_issue = start_issue.replace(hour=3)
-    elif start_issue.hour < 9:
-        start_issue = start_issue.replace(hour=9)
-    elif start_issue.hour < 15:
-        start_issue = start_issue.replace(hour=15)
-    elif start_issue.hour < 21:
-        start_issue = start_issue.replace(hour=21)
+    issue = dt.datetime.now(ZoneInfo("UTC")) - dt.timedelta(days=2)
+    if issue.hour < 3:
+        issue = issue.replace(hour=3)
+    elif issue.hour < 9:
+        issue = issue.replace(hour=9)
+    elif issue.hour < 15:
+        issue = issue.replace(hour=15)
+    elif issue.hour < 21:
+        issue = issue.replace(hour=21)
     else:
-        start_issue = start_issue.replace(hour=3) + dt.timedelta(days=1)
-    return start_issue
+        issue = issue.replace(hour=3) + dt.timedelta(days=1)
+    return issue
 
 
 def main(obs_id: str, for_id: str) -> None:
@@ -44,9 +44,9 @@ def main(obs_id: str, for_id: str) -> None:
     forecast_request = DwdMosmixRequest(
         parameter="temperature_air_mean_2m",
         mosmix_type="large",
-        start_issue=get_earliest_start_issue(),
         start_date=dt.datetime.now() - dt.timedelta(days=2),
         end_date=dt.datetime.now(),
+        issue=get_earliest_issue(),
     ).filter_by_station_id(station_id=for_id)
     print(forecast_request.df)
     forecast_values = forecast_request.values.all()
diff --git a/wetterdienst/core/timeseries/result.py b/wetterdienst/core/timeseries/result.py
index 643337f2..6de4203f 100644
--- a/wetterdienst/core/timeseries/result.py
+++ b/wetterdienst/core/timeseries/result.py
@@ -171,14 +171,6 @@ class StationsResult(ExportMixin):
     def end_date(self) -> datetime:
         return self.stations.end_date
 
-    @property
-    def start_issue(self) -> datetime:
-        return self.stations.start_issue
-
-    @property
-    def end_issue(self) -> datetime:
-        return self.stations.end_issue
-
     @property
     def tidy(self) -> bool:
         return self.stations.tidy
diff --git a/wetterdienst/core/timeseries/values.py b/wetterdienst/core/timeseries/values.py
index ab48fc0e..088cca9a 100644
--- a/wetterdienst/core/timeseries/values.py
+++ b/wetterdienst/core/timeseries/values.py
@@ -285,6 +285,8 @@ class TimeseriesValues(metaclass=ABCMeta):
             # for water with density 1 g / cm³
             if origin_unit == OriginUnit.MILLIMETER.value:
                 return operator.mul, 1
+            elif origin_unit == si_unit:
+                return operator.mul, 1
             else:
                 raise ValueError("manually set conversion factor for precipitation unit")
         elif si_unit == SIUnit.DEGREE_KELVIN.value:
@@ -494,11 +496,11 @@ class TimeseriesValues(metaclass=ABCMeta):
                     sort_columns = [Columns.DATASET.value, Columns.DATE.value]
                 station_df = station_df.sort(sort_columns)
 
-            yield ValuesResult(stations=self.sr, values=self, df=station_df)
-
             self.stations_counter += 1
             self.stations_collected.append(station_id)
 
+            yield ValuesResult(stations=self.sr, values=self, df=station_df)
+
     @abstractmethod
     def _collect_station_parameter(self, station_id: str, parameter: Enum, dataset: Enum) -> pl.DataFrame:
         """
diff --git a/wetterdienst/provider/dwd/dmo/api.py b/wetterdienst/provider/dwd/dmo/api.py
index b99c6b0b..506a2268 100644
--- a/wetterdienst/provider/dwd/dmo/api.py
+++ b/wetterdienst/provider/dwd/dmo/api.py
@@ -7,7 +7,6 @@ import logging
 from enum import Enum
 from io import StringIO
 from typing import TYPE_CHECKING
-from urllib.error import HTTPError
 from urllib.parse import urljoin
 from zoneinfo import ZoneInfo
 
@@ -15,7 +14,6 @@ import polars as pl
 
 from wetterdienst import Kind, Parameter, Period, Provider, Settings
 from wetterdienst.core.timeseries.request import TimeseriesRequest
-from wetterdienst.core.timeseries.result import StationsResult, ValuesResult
 from wetterdienst.core.timeseries.values import TimeseriesValues
 from wetterdienst.exceptions import InvalidEnumerationError, NoParametersFoundError
 from wetterdienst.metadata.columns import Columns
@@ -33,7 +31,9 @@ from wetterdienst.util.polars_util import read_fwf_from_df
 from wetterdienst.util.python import to_list
 
 if TYPE_CHECKING:
-    from collections.abc import Iterator, Sequence
+    from collections.abc import Sequence
+
+    from wetterdienst.core.timeseries.result import StationsResult
 
 try:
     from backports.datetime_fromisoformat import MonkeyPatch
@@ -1092,184 +1092,46 @@ class DwdDmoValues(TimeseriesValues):
         """
         return self.sr.df
 
-    def query(self) -> Iterator[ValuesResult]:
-        """
-        Replace collect data method as all information is read once from kml file
-
-        :return:
-        """
-        hpm = self._create_humanized_parameters_mapping()
-        self.stations_counter = 0
-        self.stations_collected = []
-
-        parameter_base = self.sr.stations._parameter_base
-        dataset_accessor = self.sr.stations._dataset_accessor
-
-        parameter_names = set()
-        for parameter, dataset in self.sr.parameter:
-            if parameter == dataset:
-                parameter = [par.value for par in parameter_base[dataset_accessor][dataset_accessor]]
-                parameter_names.update(parameter)
-            else:
-                parameter_names.add(parameter.value)
-
-        for df in self._collect_station_parameter():
-            df = self._tidy_up_df(df)
-            station_id = df.get_column(Columns.STATION_ID.value).gather(0).item()
-            df = self._organize_df_columns(df, station_id, self.sr.stations.dmo_type)
-            df = df.filter(pl.col(Columns.PARAMETER.value).is_in(parameter_names))
-
-            if self.sr.humanize:
-                df = self._humanize(df, hpm)
-
-            # Filter for dates range if start_date and end_date are defined
-            if not df.is_empty() and self.sr.start_date and self.sr.start_date != DwdForecastDate.LATEST:
-                df = df.filter(pl.col(Columns.DATE.value).is_between(self.sr.start_date, self.sr.end_date))
-
-            if self.sr.dropna:
-                df = df.drop_nulls(subset="value")
-
-            if not self.sr.tidy:
-                df = self._widen_df(df)
-
-            self.stations_counter += 1
-            self.stations_collected.append(station_id)
-
-            if self.sr.tidy:
-                sort_columns = [Columns.DATASET.value, Columns.PARAMETER.value, Columns.DATE.value]
-            else:
-                sort_columns = [Columns.DATASET.value, Columns.DATE.value]
-            df = df.sort(sort_columns)
-
-            yield ValuesResult(stations=self.sr, values=self, df=df)
-
-            if self.stations_counter == self.sr.rank:
-                return
-
-    def _collect_station_parameter(self) -> Iterator[pl.DataFrame]:
-        """
-        Wrapper of read_dmo to collect dmo data (either latest or for
-        defined dates)
-
-        :return: pandas DataFrame with data corresponding to station id and parameter
-        """
-        if self.sr.start_issue == DwdForecastDate.LATEST:
-            for df in self.read_dmo(self.sr.stations.start_issue):
-                yield df
-        else:
-            for date in pl.datetime_range(
-                self.sr.stations.start_issue,
-                self.sr.stations.end_issue,
-                interval=self.sr.frequency.value,
-                eager=True,
-            ):
-                try:
-                    for df in self.read_dmo(date):
-                        yield df
-                except IndexError as e:
-                    log.warning(e)
-                    continue
-
-    @staticmethod
-    def _tidy_up_df(df: pl.DataFrame) -> pl.DataFrame:
-        """
-
-        :param df: pandas DataFrame that is being tidied
-        :param dataset: enum of dataset, used for writing dataset in pandas DataFrame
-        :return: tidied pandas DataFrame
-        """
+    def _collect_station_parameter(self, station_id: str, parameter, dataset) -> pl.DataFrame:  # noqa: ARG002
+        df = self.read_dmo(station_id, self.sr.stations.issue)
+        if df.is_empty():
+            return df
         df = df.unpivot(
             index=[
-                Columns.STATION_ID.value,
                 Columns.DATE.value,
             ],
             variable_name=Columns.PARAMETER.value,
             value_name=Columns.VALUE.value,
         )
+        return df.with_columns(
+            pl.lit(station_id, dtype=pl.String).alias(Columns.STATION_ID.value),
+            pl.lit(value=None, dtype=pl.Float64).alias(Columns.QUALITY.value),
+        )
 
-        return df.with_columns(pl.lit(value=None, dtype=pl.Float64).alias(Columns.QUALITY.value))
-
-    def read_dmo(self, date: dt.datetime | DwdForecastDate) -> Iterator[pl.DataFrame]:
-        """
-        Manage data acquisition for a given date that is used to filter the found files
-        on the DMO path of the DWD server.
-
-        :param date: datetime
-        :return: pandas DataFrame with gathered information
-        """
-        for df_forecast in self._read_dmo(date):
-            df_forecast = df_forecast.rename(
-                mapping={
-                    "datetime": Columns.DATE.value,
-                },
-            )
-
-            yield df_forecast
-
-    def _read_dmo(self, date: DwdForecastDate | dt.datetime) -> Iterator[pl.DataFrame]:
-        """
-        Wrapper that either calls read_icon_eu or read_icon depending on
-        defined period type
-
-        :param date: datetime
-        :return: pandas DataFrame
-        """
+    def read_dmo(self, station_id: str, date: dt.datetime | DwdForecastDate) -> pl.DataFrame:
         if self.sr.stations.dmo_type == DwdDmoType.ICON_EU:
-            yield from self.read_icon_eu(date)
-        else:
-            yield from self.read_icon(date)
-
-    def read_icon_eu(self, date: DwdForecastDate | dt.datetime) -> Iterator[pl.DataFrame]:
-        """
-        Reads single icon_eu file with all stations_result and returns every chunk that
-        matches with one of the defined station ids.
-
-        :param date: datetime
-        :return: pandas DataFrame with data
-        """
-        if self.sr.stations.station_group == DwdDmoStationGroup.ALL_STATIONS:
-            dmo_path = self.get_dwd_dmo_path(DwdDmoDataset.ICON_EU)
-            url = urljoin("https://opendata.dwd.de", dmo_path)
-            file_url = self.get_url_for_date(url, date)
-            self.kml.read(file_url)
-            yield from self.kml.get_forecasts()
+            return self.read_icon_eu(station_id, date)
         else:
-            for station_id in self.sr.station_id:
-                dmo_path = self.get_dwd_dmo_path(DwdDmoDataset.ICON_EU, station_id=station_id)
-                station_url = urljoin("https://opendata.dwd.de", dmo_path)
-                try:
-                    file_url = self.get_url_for_date(station_url, date)
-                except HTTPError:
-                    log.warning(f"Files for {station_id} do not exist on the server")
-                    continue
-                self.kml.read(file_url)
-                yield next(self.kml.get_forecasts())
-
-    def read_icon(self, date: DwdForecastDate | dt.datetime) -> Iterator[pl.DataFrame]:
-        """
-        Reads multiple icon files with one per each station and returns a
-        chunk per file.
-
-        :param date:
-        :return:
-        """
+            return self.read_icon(station_id, date)
+
+    def read_icon_eu(self, station_id: str, date: DwdForecastDate | dt.datetime) -> pl.DataFrame:
+        """Reads large icon_eu file with all stations."""
+        dmo_path = self.get_dwd_dmo_path(DwdDmoDataset.ICON_EU)
+        url = urljoin("https://opendata.dwd.de", dmo_path)
+        file_url = self.get_url_for_date(url, date)
+        self.kml.read(file_url)
+        return self.kml.get_station_forecast(station_id)
+
+    def read_icon(self, station_id: str, date: DwdForecastDate | dt.datetime) -> pl.DataFrame:
+        """Reads either large icon file with all stations or small single station file."""
         if self.sr.stations.station_group == DwdDmoStationGroup.ALL_STATIONS:
             dmo_path = self.get_dwd_dmo_path(DwdDmoDataset.ICON)
-            url = urljoin("https://opendata.dwd.de", dmo_path)
-            file_url = self.get_url_for_date(url, date)
-            self.kml.read(file_url)
-            yield from self.kml.get_forecasts()
         else:
-            for station_id in self.sr.station_id:
-                dmo_path = self.get_dwd_dmo_path(DwdDmoDataset.ICON, station_id=station_id)
-                station_url = urljoin("https://opendata.dwd.de", dmo_path)
-                try:
-                    file_url = self.get_url_for_date(station_url, date)
-                except HTTPError:
-                    log.warning(f"Files for {station_id} do not exist on the server")
-                    continue
-                self.kml.read(file_url)
-                yield next(self.kml.get_forecasts())
+            dmo_path = self.get_dwd_dmo_path(DwdDmoDataset.ICON, station_id=station_id)
+        url = urljoin("https://opendata.dwd.de", dmo_path)
+        file_url = self.get_url_for_date(url, date)
+        self.kml.read(file_url)
+        return self.kml.get_station_forecast(station_id)
 
     def get_url_for_date(self, url: str, date: dt.datetime | DwdForecastDate) -> str:
         """
@@ -1378,10 +1240,9 @@ class DwdDmoRequest(TimeseriesRequest):
         self,
         parameter: str | DwdDmoParameter | Parameter | Sequence[str | DwdDmoParameter | Parameter],
         dmo_type: str | DwdDmoType,
-        start_issue: str | dt.datetime | DwdForecastDate = DwdForecastDate.LATEST,
-        end_issue: str | dt.datetime | None = None,
         start_date: str | dt.datetime | None = None,
         end_date: str | dt.datetime | None = None,
+        issue: str | dt.datetime | DwdForecastDate = DwdForecastDate.LATEST,
         station_group: str | DwdDmoStationGroup | None = None,
         lead_time: str | DwdDmoLeadTime | None = None,
         settings: Settings | None = None,
@@ -1390,10 +1251,9 @@ class DwdDmoRequest(TimeseriesRequest):
 
         :param parameter: parameter(s) to be collected
         :param dmo_type: dmo type, either small or large
-        :param start_issue: start of issue of dmo which should be caught (DMO run at time XX:YY)
-        :param end_issue: end of issue
         :param start_date: start date for filtering returned dataframe
         :param end_date: end date
+        :param issue: issue of dmo which should be caught (DMO run at time XX:YY)
         """
         try:
             self.dmo_type = parse_enumeration_from_template(dmo_type, DwdDmoType)
@@ -1422,31 +1282,20 @@ class DwdDmoRequest(TimeseriesRequest):
             settings=settings,
         )
 
-        if not start_issue:
-            start_issue = DwdForecastDate.LATEST
+        if not issue:
+            issue = DwdForecastDate.LATEST
 
         try:
-            start_issue = parse_enumeration_from_template(start_issue, DwdForecastDate)
+            issue = parse_enumeration_from_template(issue, DwdForecastDate)
         except InvalidEnumerationError:
             pass
 
-        if start_issue is not DwdForecastDate.LATEST:
-            if not start_issue and not end_issue:
-                start_issue = DwdForecastDate.LATEST
-            elif not end_issue:
-                end_issue = start_issue
-            elif not start_issue:
-                start_issue = end_issue
-            if isinstance(start_issue, str):
-                start_issue = dt.datetime.fromisoformat(start_issue)
-            start_issue = dt.datetime(start_issue.year, start_issue.month, start_issue.day, start_issue.hour)
-            if isinstance(end_issue, str):
-                end_issue = dt.datetime.fromisoformat(end_issue)
-            end_issue = dt.datetime(end_issue.year, end_issue.month, end_issue.day, end_issue.hour)
-
-            # Shift start date and end date to 0, 12 hour format
-            start_issue = self.adjust_datetime(start_issue)
-            end_issue = self.adjust_datetime(end_issue)
+        if issue is not DwdForecastDate.LATEST:
+            if isinstance(issue, str):
+                issue = dt.datetime.fromisoformat(issue)
+            issue = dt.datetime(issue.year, issue.month, issue.day, issue.hour)
+            # Shift issue date to 0, 12 hour format
+            issue = self.adjust_datetime(issue)
 
         # TODO: this should be replaced by the freq property in the main class
         if self.dmo_type == DwdDmoType.ICON_EU:
@@ -1454,18 +1303,7 @@ class DwdDmoRequest(TimeseriesRequest):
         else:
             self.resolution = Resolution.HOUR_6
 
-        self.start_issue = start_issue
-        self.end_issue = end_issue
-
-    @property
-    def issue_start(self):
-        """Required for typing"""
-        return self.issue_start
-
-    @property
-    def issue_end(self):
-        """Required for typing"""
-        return self.issue_end
+        self.issue = issue
 
     # required patches for stations as data is corrupted for these at least atm
     _station_patches = pl.DataFrame(
diff --git a/wetterdienst/provider/dwd/mosmix/access.py b/wetterdienst/provider/dwd/mosmix/access.py
index 63871fff..147c1f2a 100644
--- a/wetterdienst/provider/dwd/mosmix/access.py
+++ b/wetterdienst/provider/dwd/mosmix/access.py
@@ -15,14 +15,13 @@ from fsspec.implementations.zip import ZipFileSystem
 from lxml.etree import iterparse  # noqa: S410
 from tqdm import tqdm
 
+from wetterdienst.metadata.columns import Columns
 from wetterdienst.util.cache import CacheExpiry
 from wetterdienst.util.io import read_in_chunks
 from wetterdienst.util.logging import TqdmToLogger
 from wetterdienst.util.network import NetworkFilesystemManager
 
 if TYPE_CHECKING:
-    from collections.abc import Iterator
-
     from wetterdienst.settings import Settings
 
 try:
@@ -146,9 +145,7 @@ class KMLReader:
                     clear = False
             elif event == "end":
                 if element.tag == placemark_tag:
-                    station_id = element.find("kml:name", self.nsmap).text
-                    if (self.station_ids is None) or station_id in self.station_ids:
-                        yield element
+                    yield element
                     clear = True
                 if clear:
                     element.clear()
@@ -157,24 +154,20 @@ class KMLReader:
         """Get metadata as DataFrame."""
         return pl.DataFrame([self.metadata])
 
-    def get_forecasts(self) -> Iterator[pl.DataFrame]:
+    def get_station_forecast(self, station_id: str) -> pl.DataFrame:
         """Get forecasts as DataFrame."""
         for station_forecast in self.iter_items():
-            station_ids = station_forecast.find("kml:name", self.nsmap).text
-
+            if station_forecast.find("kml:name", self.nsmap).text != station_id:
+                continue
             measurement_list = station_forecast.findall("kml:ExtendedData/dwd:Forecast", self.nsmap)
-
-            data_dict = {"station_id": station_ids, "datetime": self.timesteps}
-
+            data_dict = {Columns.DATE.value: self.timesteps}
             for measurement_item in measurement_list:
                 measurement_parameter = measurement_item.get(f"{{{self.nsmap['dwd']}}}elementName")
                 measurement_string = measurement_item.getchildren()[0].text
                 measurement_values = " ".join(measurement_string.split()).split(" ")
                 measurement_values = [None if i == "-" else float(i) for i in measurement_values]
-                assert len(measurement_values) == len(  # noqa:S101
-                    self.timesteps,
-                ), "Number of time steps does not match number of measurement values"
                 data_dict[measurement_parameter.lower()] = measurement_values
-
             station_forecast.clear()
-            yield pl.DataFrame(data_dict)
+            return pl.DataFrame(data_dict)
+        else:
+            raise IndexError(f"Station {station_id} not found in KML file")
diff --git a/wetterdienst/provider/dwd/mosmix/api.py b/wetterdienst/provider/dwd/mosmix/api.py
index db62a168..71265049 100644
--- a/wetterdienst/provider/dwd/mosmix/api.py
+++ b/wetterdienst/provider/dwd/mosmix/api.py
@@ -10,10 +10,8 @@ from typing import TYPE_CHECKING
 from urllib.parse import urljoin
 
 import polars as pl
-from requests import HTTPError
 
 from wetterdienst.core.timeseries.request import TimeseriesRequest
-from wetterdienst.core.timeseries.result import StationsResult, ValuesResult
 from wetterdienst.core.timeseries.values import TimeseriesValues
 from wetterdienst.exceptions import InvalidEnumerationError
 from wetterdienst.metadata.columns import Columns
@@ -35,8 +33,9 @@ from wetterdienst.util.polars_util import read_fwf_from_df
 from wetterdienst.util.python import to_list
 
 if TYPE_CHECKING:
-    from collections.abc import Iterator, Sequence
+    from collections.abc import Sequence
 
+    from wetterdienst.core.timeseries.result import StationsResult
     from wetterdienst.metadata.parameter import Parameter
     from wetterdienst.settings import Settings
 
@@ -1039,104 +1038,29 @@ class DwdMosmixValues(TimeseriesValues):
         """
         return self.sr.df
 
-    def query(self) -> Iterator[ValuesResult]:
-        """
-        Replace collect data method as all information is read once from kml file
-
-        :return:
-        """
-        hpm = self._create_humanized_parameters_mapping()
-        self.stations_counter = 0
-        self.stations_collected = []
-
-        parameter_base = self.sr.stations._parameter_base
-        dataset_accessor = self.sr.stations._dataset_accessor
-
-        parameter_names = set()
-        for parameter, dataset in self.sr.parameter:
-            if parameter == dataset:
-                parameter = [par.value for par in parameter_base[dataset_accessor][dataset_accessor]]
-                parameter_names.update(parameter)
-            else:
-                parameter_names.add(parameter.value)
-
-        for df in self._collect_station_parameter():
-            df = self._tidy_up_df(df)
-            station_id = df.get_column(Columns.STATION_ID.value).gather(0).item()
-            df = self._organize_df_columns(df, station_id, self.sr.stations.mosmix_type)
-            df = df.filter(pl.col(Columns.PARAMETER.value).is_in(parameter_names))
-
-            if self.sr.humanize:
-                df = self._humanize(df, hpm)
-
-            # Filter for dates range if start_date and end_date are defined
-            if not df.is_empty() and self.sr.start_date and self.sr.start_date != DwdForecastDate.LATEST:
-                df = df.filter(pl.col(Columns.DATE.value).is_between(self.sr.start_date, self.sr.end_date))
-
-            if self.sr.dropna:
-                df = df.drop_nulls(subset="value")
-
-            if not self.sr.tidy:
-                df = self._widen_df(df)
-
-            self.stations_counter += 1
-            self.stations_collected.append(station_id)
-
-            if self.sr.tidy:
-                sort_columns = [Columns.DATASET.value, Columns.PARAMETER.value, Columns.DATE.value]
-            else:
-                sort_columns = [Columns.DATASET.value, Columns.DATE.value]
-            df = df.sort(sort_columns)
-
-            yield ValuesResult(stations=self.sr, values=self, df=df)
-
-            if self.stations_counter == self.sr.rank:
-                return
-
-    def _collect_station_parameter(self) -> Iterator[pl.DataFrame]:
+    def _collect_station_parameter(self, station_id: str, parameter: str, dataset: str) -> pl.DataFrame:  # noqa: ARG002
         """
         Wrapper of read_mosmix to collect mosmix data (either latest or for
         defined dates)
 
         :return: pandas DataFrame with data corresponding to station id and parameter
         """
-        if self.sr.start_issue == DwdForecastDate.LATEST:
-            for df in self.read_mosmix(self.sr.stations.start_issue):
-                yield df
-        else:
-            for date in pl.datetime_range(
-                self.sr.stations.start_issue,
-                self.sr.stations.end_issue,
-                interval=self.sr.frequency.value,
-                eager=True,
-            ):
-                try:
-                    for df in self.read_mosmix(date):
-                        yield df
-                except IndexError as e:
-                    log.warning(e)
-                    continue
-
-    @staticmethod
-    def _tidy_up_df(df: pl.DataFrame) -> pl.DataFrame:
-        """
-
-        :param df: pandas DataFrame that is being tidied
-        :param dataset: enum of dataset, used for writing dataset in pandas DataFrame
-        :return: tidied pandas DataFrame
-        """
+        df = self.read_mosmix(station_id, self.sr.stations.issue)
+        if df.is_empty():
+            return df
         df = df.unpivot(
             index=[
-                Columns.STATION_ID.value,
                 Columns.DATE.value,
             ],
             variable_name=Columns.PARAMETER.value,
             value_name=Columns.VALUE.value,
         )
+        return df.with_columns(
+            pl.lit(station_id, dtype=pl.String).alias(Columns.STATION_ID.value),
+            pl.lit(value=None, dtype=pl.Float64).alias(Columns.QUALITY.value),
+        )
 
-        return df.with_columns(pl.lit(value=None, dtype=pl.Float64).alias(Columns.QUALITY.value))
-
-    def read_mosmix(self, date: dt.datetime | DwdForecastDate) -> Iterator[pl.DataFrame]:
+    def read_mosmix(self, station_id: str, date: dt.datetime | DwdForecastDate) -> pl.DataFrame:
         """
         Manage data acquisition for a given date that is used to filter the found files
         on the MOSMIX path of the DWD server.
@@ -1144,73 +1068,31 @@ class DwdMosmixValues(TimeseriesValues):
         :param date: datetime or enumeration for latest MOSMIX mosmix
         :return: pandas DataFrame with gathered information
         """
-        for df_forecast in self._read_mosmix(date):
-            df_forecast = df_forecast.rename(
-                mapping={
-                    "datetime": Columns.DATE.value,
-                },
-            )
-
-            yield df_forecast
-
-    def _read_mosmix(self, date: DwdForecastDate | dt.datetime) -> Iterator[pl.DataFrame]:
-        """
-        Wrapper that either calls read_mosmix_s or read_mosmix_l depending on
-        defined period type
-
-        :param date: datetime or enumeration for latest MOSMIX mosmix
-        :return: pandas DataFrame
-        """
         if self.sr.stations.mosmix_type == DwdMosmixType.SMALL:
-            yield from self.read_mosmix_small(date)
+            return self.read_mosmix_small(station_id, date)
         else:
-            yield from self.read_mosmix_large(date)
+            return self.read_mosmix_large(station_id, date)
 
-    def read_mosmix_small(self, date: DwdForecastDate | dt.datetime) -> Iterator[pl.DataFrame]:
-        """
-        Reads single MOSMIX-S file with all stations_result and returns every mosmix that
-        matches with one of the defined station ids.
-
-        :param date: datetime or enumeration for latest MOSMIX mosmix
-        :return: pandas DataFrame with data
-        """
+    def read_mosmix_small(self, station_id, date: DwdForecastDate | dt.datetime) -> pl.DataFrame:
+        """Reads single MOSMIX-S file for all stations."""
         url = urljoin("https://opendata.dwd.de", DWD_MOSMIX_S_PATH)
         file_url = self.get_url_for_date(url, date)
         self.kml.read(file_url)
-        yield from self.kml.get_forecasts()
+        return self.kml.get_station_forecast(station_id)
 
     def read_mosmix_large(
         self,
+        station_id: str,
         date: DwdForecastDate | dt.datetime,
-    ) -> Iterator[tuple[pl.DataFrame, pl.DataFrame]]:
-        """
-        Reads multiple MOSMIX-L files with one per each station and returns a
-        mosmix per file.
-
-        :param date:
-        :return:
-        """
+    ) -> pl.DataFrame:
+        """Reads either a large MOSMIX-L file with all stations or a small MOSMIX-L file for a single station."""
         if self.sr.stations.station_group == DwdMosmixStationGroup.ALL_STATIONS:
             url = urljoin("https://opendata.dwd.de", DWD_MOSMIX_L_PATH)
-
-            file_url = self.get_url_for_date(url, date)
-
-            self.kml.read(file_url)
-
-            yield from self.kml.get_forecasts()
         else:
-            for station_id in self.sr.station_id:
-                station_url = urljoin("https://opendata.dwd.de", DWD_MOSMIX_L_SINGLE_PATH).format(station_id=station_id)
-
-                try:
-                    file_url = self.get_url_for_date(station_url, date)
-                except HTTPError:
-                    log.warning(f"Files for {station_id} do not exist on the server")
-                    continue
-
-                self.kml.read(file_url)
-
-                yield next(self.kml.get_forecasts())
+            url = urljoin("https://opendata.dwd.de", DWD_MOSMIX_L_SINGLE_PATH).format(station_id=station_id)
+        file_url = self.get_url_for_date(url, date)
+        self.kml.read(file_url)
+        return self.kml.get_station_forecast(station_id)
 
     def get_url_for_date(self, url: str, date: dt.datetime | DwdForecastDate) -> str:
         """
@@ -1330,10 +1212,9 @@ class DwdMosmixRequest(TimeseriesRequest):
         self,
         parameter: str | DwdMosmixParameter | Parameter | Sequence[str | DwdMosmixParameter | Parameter],
         mosmix_type: str | DwdMosmixType,
-        start_issue: str | dt.datetime | DwdForecastDate | None = DwdForecastDate.LATEST,
-        end_issue: str | dt.datetime | None = None,
         start_date: str | dt.datetime | None = None,
         end_date: str | dt.datetime | None = None,
+        issue: str | dt.datetime | DwdForecastDate | None = DwdForecastDate.LATEST,
         station_group: DwdMosmixStationGroup | None = None,
         settings: Settings | None = None,
     ) -> None:
@@ -1341,10 +1222,9 @@ class DwdMosmixRequest(TimeseriesRequest):
 
         :param parameter: parameter(s) to be collected
         :param mosmix_type: mosmix type, either small or large
-        :param start_issue: start of issue of mosmix which should be caught (Mosmix run at time XX:YY)
-        :param end_issue: end of issue
         :param start_date: start date for filtering returned dataframe
         :param end_date: end date
+        :param issue: start of issue of mosmix which should be caught (Mosmix run at time XX:YY)
         """
         self.mosmix_type = parse_enumeration_from_template(mosmix_type, DwdMosmixType)
 
@@ -1365,40 +1245,21 @@ class DwdMosmixRequest(TimeseriesRequest):
             settings=settings,
         )
 
-        if not start_issue:
-            start_issue = DwdForecastDate.LATEST
+        if not issue:
+            issue = DwdForecastDate.LATEST
 
         try:
-            start_issue = parse_enumeration_from_template(start_issue, DwdForecastDate)
+            issue = parse_enumeration_from_template(issue, DwdForecastDate)
         except InvalidEnumerationError:
             pass
 
-        # Parse issue date if not set to fixed "latest" string
-        if start_issue is DwdForecastDate.LATEST and end_issue:
-            log.info("end_issue will be ignored as 'latest' was selected for issue date")
-
-        if start_issue is not DwdForecastDate.LATEST:
-            if not start_issue and not end_issue:
-                start_issue = DwdForecastDate.LATEST
-            elif not end_issue:
-                end_issue = start_issue
-            elif not start_issue:
-                start_issue = end_issue
-            if isinstance(start_issue, str):
-                start_issue = dt.datetime.fromisoformat(start_issue)
-            start_issue = dt.datetime(
-                start_issue.year, start_issue.month, start_issue.day, start_issue.hour, tzinfo=start_issue.tzinfo
-            )
-            if isinstance(end_issue, str):
-                end_issue = dt.datetime.fromisoformat(end_issue)
-            end_issue = dt.datetime(
-                end_issue.year, end_issue.month, end_issue.day, end_issue.hour, tzinfo=end_issue.tzinfo
-            )
-
-            # Shift start date and end date to 3, 9, 15, 21 hour format
+        if issue is not DwdForecastDate.LATEST:
+            if isinstance(issue, str):
+                issue = dt.datetime.fromisoformat(issue)
+            issue = dt.datetime(issue.year, issue.month, issue.day, issue.hour, tzinfo=issue.tzinfo)
+            # Shift issue date to 3, 9, 15, 21 hour format
             if mosmix_type == DwdMosmixType.LARGE:
-                start_issue = self.adjust_datetime(start_issue)
-                end_issue = self.adjust_datetime(end_issue)
+                issue = self.adjust_datetime(issue)
 
         # TODO: this should be replaced by the freq property in the main class
         if self.mosmix_type == DwdMosmixType.SMALL:
@@ -1406,18 +1267,7 @@ class DwdMosmixRequest(TimeseriesRequest):
         else:
             self.resolution = Resolution.HOUR_6
 
-        self.start_issue = start_issue
-        self.end_issue = end_issue
-
-    @property
-    def issue_start(self):
-        """Required for typing"""
-        return self.issue_start
-
-    @property
-    def issue_end(self):
-        """Required for typing"""
-        return self.issue_end
+        self.issue = issue
 
     def _all(self) -> pl.LazyFrame:
         """
diff --git a/wetterdienst/ui/core.py b/wetterdienst/ui/core.py
index 563fca1d..f3e06c31 100644
--- a/wetterdienst/ui/core.py
+++ b/wetterdienst/ui/core.py
@@ -129,10 +129,10 @@ def _get_stations_request(
     }
     if issubclass(api, DwdMosmixRequest):
         kwargs["mosmix_type"] = resolution
-        kwargs["start_issue"] = issue
+        kwargs["issue"] = issue
     elif issubclass(api, DwdDmoRequest):
         kwargs["dmo_type"] = resolution
-        kwargs["start_issue"] = issue
+        kwargs["issue"] = issue
         kwargs["lead_time"] = lead_time
     elif api._resolution_type == ResolutionType.MULTI:
         kwargs["resolution"] = resolution


Test patch:
diff --git a/tests/provider/dwd/mosmix/test_api_data.py b/tests/provider/dwd/mosmix/test_api_data.py
index e560def0..b14930a0 100644
--- a/tests/provider/dwd/mosmix/test_api_data.py
+++ b/tests/provider/dwd/mosmix/test_api_data.py
@@ -245,7 +245,7 @@ def test_mosmix_date_filter(default_settings):
         mosmix_type=DwdMosmixType.SMALL,
         start_date=now - dt.timedelta(hours=1),
         end_date=now,
-        start_issue=now - dt.timedelta(hours=5),
+        issue=now - dt.timedelta(hours=5),
         settings=default_settings,
     ).filter_by_rank(latlon=(52.122050, 11.619845), rank=1)
     given_df = request.values.all().df