diff --git a/src/vipscore_common/data_utils.py b/src/vipscore_common/data_utils.py index 10098ecc837c05b0da783010835a099717e787ec..6d1abdd4c3b38b43afeb33aaf5afc292926aae96 100644 --- a/src/vipscore_common/data_utils.py +++ b/src/vipscore_common/data_utils.py @@ -64,6 +64,24 @@ def get_dataframe_from_weather_observations(weather_observations: list, timezone return df +def get_result_list_from_dataframe(df: DataFrame): + """ + Create a list of VIPS Result objects from a dataframe + Requires timeseries as index and a column named WARNING_STATUS with INT values ranging from 0-4 (see Result class) + """ + result_list = [] + dictframe = df.to_dict(orient="index") + for timestamp in dictframe: + values = dictframe[timestamp] + result_list.append(Result( + valid_time_start = timestamp, # valid_time_start + valid_time_end = None, + warning_status = values.pop("WARNING_STATUS"), + all_values = values + )) + return result_list + + def get_weather_observations_from_json(weather_data_raw: str) -> list: """ diff --git a/src/vipscore_common/entities.py b/src/vipscore_common/entities.py index 43b17a8005066978803aabc30bfb046cb018f02f..1eda39a320696cd4e7268b8c7dcb4b6c96dcb726 100755 --- a/src/vipscore_common/entities.py +++ b/src/vipscore_common/entities.py @@ -31,7 +31,7 @@ import pytz class Result(BaseModel): """Represents a set of DSS model result values for a given point in space (Point, Polygon, MultiPolygon) and time (Period or immediate) """ valid_time_start: datetime # TODO make sure it's always timezone aware - valid_time_end: datetime # TODO make sure it's always timezone aware + valid_time_end: datetime | None = ... # TODO make sure it's always timezone aware valid_geometry: Any warning_status: int all_values: dict @@ -47,12 +47,20 @@ class Result(BaseModel): def get_keys(self): return set(self.all_values.keys) if self.all_values is not None else set() - @validator("valid_time_start","valid_time_end") + @validator("valid_time_start") def ensure_timezone(cls, v): if v.tzinfo is None or v.tzinfo.utcoffset(v) is None: raise ValueError("%s must be timezone aware" % v) return v + @validator("valid_time_end") + def ensure_none_or_timezone(cls, v): + if v is None: + return v + if v.tzinfo is None or v.tzinfo.utcoffset(v) is None: + raise ValueError("%s must be timezone aware" % v) + return v + @validator("valid_geometry") def ensure_geometry(cls,v): if v is not None and not isinstance(v, Point) and not isinstance(v, Polygon): diff --git a/src/vipscore_common/reference_model.py b/src/vipscore_common/reference_model.py index da96f082adeae5eadb306ed3b63315223ee5dcd5..717882182d0cd03e24411eff51d93fcee18fa2a7 100755 --- a/src/vipscore_common/reference_model.py +++ b/src/vipscore_common/reference_model.py @@ -56,6 +56,10 @@ class ReferenceModel(VIPSModel): def determine_warning_status(self, TMDD: float) -> int: + """ + Used in get_result as a dataframe operation. Determines the warning status + based on the model thresholds + """ if TMDD < ReferenceModel.THRESHOLD_MEDIUM: return Result.WARNING_STATUS_NO_WARNING if TMDD < ReferenceModel.THRESHOLD_HIGH: @@ -77,12 +81,13 @@ class ReferenceModel(VIPSModel): self.df["THRESHOLD_LOW"] = ReferenceModel.THRESHOLD_LOW self.df["THRESHOLD_MEDIUM"] = ReferenceModel.THRESHOLD_MEDIUM self.df["THRESHOLD_HIGH"] = ReferenceModel.THRESHOLD_HIGH - self.df["WARNING_STATUS"] = self.df["TMDD"].apply(self.determine_warning_status) - print(self.df) # For each day: check accumulated day-degrees and decide warning status - - - + self.df["WARNING_STATUS"] = self.df["TMDD"].apply(self.determine_warning_status) + #print(self.df) + result = data_utils.get_result_list_from_dataframe(self.df) + #print(result) + return result + @property def model_id(self) -> str: diff --git a/src/vipscore_common/tests/test_reference_model.py b/src/vipscore_common/tests/test_reference_model.py index 161a04b6738cc19ffaae8df4844f550a43c8ef10..743855eaa2b6f40a9992d1c3bc4c4b79c85d2342 100644 --- a/src/vipscore_common/tests/test_reference_model.py +++ b/src/vipscore_common/tests/test_reference_model.py @@ -28,11 +28,15 @@ class TestReferenceModel(unittest.TestCase): def test_get_result(self): """ - We get a series of results from the calculation + We get a series of results from the calculation, + and the TMDD is as expected """ instance = ReferenceModel() instance.set_configuration(get_model_configuration()) result_list = instance.get_result() + self.assertIsNotNone(result_list) + last_result = result_list[len(result_list)-1] + self.assertEqual(555.8507083333333, last_result.all_values["TMDD"]) def test_get_model_id(self): """