Skip to content
Snippets Groups Projects
Commit 59109433 authored by Tor-Einar Skog's avatar Tor-Einar Skog
Browse files

Refactored reference_model out of this module

parent aa1e6a86
No related branches found
No related tags found
No related merge requests found
#!/usr/bin/python3
LICENSE = """
Copyright (c) 2023 NIBIO <http://www.nibio.no/>.
This file is part of VIPSCore-Python-Common.
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 .vips_model import VIPSModel
from .entities import Result, ModelConfiguration, WeatherObservation
from .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"
\ No newline at end of file
import unittest
import json
from src.vipscore_common.reference_model import *
from src.vipscore_common.entities import *
def get_model_configuration():
with open ("tests/weather_data_2015_NO_aas_TMD.json") as f:
weather_observations = json.load(f)
return ModelConfiguration(
model_id=ReferenceModel.MODEL_ID,
config_parameters={
"sowingDate": "2022-03-01",
"timeZone" : "Europe/Oslo",
"observations": weather_observations
}
)
class TestReferenceModel(unittest.TestCase):
def test_set_configuration(self):
"""
Passing a configuration object does not fail
"""
#print(get_model_configuration())
instance = ReferenceModel()
instance.set_configuration(get_model_configuration())
def test_get_result(self):
"""
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):
"""
The model returns the correct ID
"""
instance = ReferenceModel()
self.assertEqual(instance.model_id, ReferenceModel.MODEL_ID)
def test_get_license(self):
"""
The model returns its license
"""
instance = ReferenceModel()
self.assertIsNotNone(instance.license)
def test_get_copyright(self):
"""
The model returns its copyright notice
"""
instance = ReferenceModel()
self.assertEqual(instance.copyright, ReferenceModel.COPYRIGHT)
def test_get_sample_config(self):
"""
The model returns its sample configuration
"""
instance = ReferenceModel()
self.assertIsNotNone(instance.sample_config)
def test_get_model_name(self):
"""
The model returns its name in the default language
"""
instance = ReferenceModel()
self.assertEqual(instance.get_model_name(), "Reference Model")
def test_get_model_description(self):
"""
The model returns a description
"""
instance = ReferenceModel()
self.assertIsNotNone(instance.get_model_description())
def test_get_warning_status_interpretation(self):
"""
The model returns a warning status interpretation
"""
instance = ReferenceModel()
self.assertIsNotNone(instance.get_warning_status_interpretation())
def test_get_model_usage(self):
"""
The model returns a description of usage
"""
if __name__ == '__main__':
unittest.main()
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment