-
Tor-Einar Skog authoredTor-Einar Skog authored
reference_model.py 6.65 KiB
#!/usr/bin/python3
LICENSE = """
Copyright (c) 2023 NIBIO <http://www.nibio.no/>.
This file is part of ReferenceModel.
VIPSCore-Python-Common is free software: you can redistribute it and/or modify
it under the terms of the NIBIO Open Source License as published by
NIBIO, either version 1 of the License, or (at your option) any
later version.
VIPSCore-Python-Common is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
NIBIO Open Source License for more details.
You should have received a copy of the NIBIO Open Source License
along with VIPSCore-Python-Common. If not, see <http://www.nibio.no/licenses/>.
"""
from vipscore_common.vips_model import VIPSModel
from vipscore_common.entities import Result, ModelConfiguration, WeatherObservation
from vipscore_common.data_utils import *
import numpy as np
class ReferenceModel(VIPSModel):
"""
A reference implementation of the VIPSModel abstract class. Showcasing core functionality
and best practices
"""
MODEL_ID = "REFERENCEM"
COPYRIGHT = "(c) 2023 NIBIO"
THRESHOLD_LOW = 100.0
THRESHOLD_MEDIUM = 300.0
THRESHOLD_HIGH = 500.0
def set_configuration(self, model_configuration: ModelConfiguration):
if not isinstance(model_configuration, ModelConfiguration):
raise ValueError("%s is not a ModelConfiguration object" % model_configuration)
if model_configuration.model_id != ReferenceModel.MODEL_ID:
raise ValueError("%s is not the correct model ID!" % model_configuration.model_id)
# Input data check
self.sowing_date = model_configuration.get_config_parameter_as_date("sowingDate")
self.timezone = model_configuration.get_config_parameter_as_timezone("timeZone")
# Weather data is turned into Pandas dataframe
self.df = get_dataframe_from_weather_observations(
get_weather_observations_from_json_list(
model_configuration.config_parameters["observations"]
),
self.timezone
)
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:
return Result.WARNING_STATUS_MINOR_RISK
else:
return Result.WARNING_STATUS_HIGH_RISK
def get_result(self) -> list[Result]:
"""Get the results as a list of Result objects (TODO ref)"""
# Calculate day degrees from sowingDate and as far as weather data goes
# Adjusting for base temperature
self.df["TMContrib"] = self.df["TM"].apply(get_temp_adjusted_for_base, args=(5,))
# Aggregating the day degrees
self.df["TMDD"] = self.df["TMContrib"].cumsum()
# Adding the thresholds to the data frame
self.df["THRESHOLD_LOW"] = ReferenceModel.THRESHOLD_LOW
self.df["THRESHOLD_MEDIUM"] = ReferenceModel.THRESHOLD_MEDIUM
self.df["THRESHOLD_HIGH"] = ReferenceModel.THRESHOLD_HIGH
# 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 = get_result_list_from_dataframe(self.df)
#print(result)
return result
@property
def model_id(self) -> str:
"""10-character ID of the model. Must be unique (at least in the current system)"""
return ReferenceModel.MODEL_ID
@property
def license(self) -> str:
"""Returns the license for this piece of software"""
return LICENSE
@property
def copyright(self) -> str:
"""Name of person/organization that holds the copyright, and contact information"""
return ReferenceModel.COPYRIGHT
@property
def sample_config(self) -> dict:
"""A sample configuration in JSON format (TODO check relation with Dict)"""
return """
{
model_id:'REFERENCEM',
config_parameters: {
'sowingDate': '2022-03-01',
'observations': [
{'timeMeasured': '2015-03-01T00:00:00+01:00', 'elementMeasurementTypeId': 'TM', 'logIntervalId': '2', 'value': '1.41025'},
{'timeMeasured': '2015-03-02T00:00:00+01:00', 'elementMeasurementTypeId': 'TM', 'logIntervalId': '2', 'value': '2.87608333333333'},
{'timeMeasured': '2015-03-03T00:00:00+01:00', 'elementMeasurementTypeId': 'TM', 'logIntervalId': '2', 'value': '1.00854166666667'},
{'timeMeasured': '2015-03-04T00:00:00+01:00', 'elementMeasurementTypeId': 'TM', 'logIntervalId': '2', 'value': '-1.44675'}
]
}
}
"""
def get_model_name(self, language = VIPSModel.default_language) -> str:
"""Returns the model name in the specified language (<a href="http://www.loc.gov/standards/iso639-2/php/English_list.php">ISO-639-2</a>)"""
return "Reference Model"
def get_model_description(self, language = VIPSModel.default_language) -> str:
"""Returns the model description in the specified language (<a href="http://www.loc.gov/standards/iso639-2/php/English_list.php">ISO-639-2</a>)"""
return """
The model is a reference model for developers, showcasing best practices and functionalities of a model.
It's a simple day degree model for an imagined pest, and when thresholds are passed, the warning status progresses.
"""
def get_warning_status_interpretation(self, language = VIPSModel.default_language) -> str:
"""How to interpret the warning status (red-yellow-green, what does it mean?) in the specified language (<a href="http://www.loc.gov/standards/iso639-2/php/English_list.php">ISO-639-2</a>)"""
return """
Gray status (warning status == 0): Warning not applicable
Blue status (warning status == 1): Missing data
Green status (warning status == 2): No risk. Sleep well
Yellow status (warning status == 3): The day-degree hreshold for medium risk has been passed. Be on the alert, inspect your field
Red status (warning status == 4): The day-degree threshold for high risk has been passed. When the going gets tough, the tough get going
"""
def get_model_usage(self, language = VIPSModel.default_language) -> str:
"""Technical manual for this model, in the specified language (<a href="http://www.loc.gov/standards/iso639-2/php/English_list.php">ISO-639-2</a>)"""
return "TODO"