From 9f55f3ebedc324703a7202e8724aedf3fce8ff5a Mon Sep 17 00:00:00 2001
From: Tor-Einar Skog <tor-einar.skog@nibio.no>
Date: Fri, 11 Aug 2023 14:54:53 +0200
Subject: [PATCH] First test version (with mock result data)

---
 VIPSWeb/settings.py                          |   1 +
 VIPSWeb/urls.py                              |   1 +
 ipmd/__init__.py                             |   0
 ipmd/admin.py                                |   3 +
 ipmd/apps.py                                 |   6 +
 ipmd/migrations/__init__.py                  |   0
 ipmd/models.py                               |   3 +
 ipmd/static/ipmd/js/ipmdlib.js               |  96 ++++
 ipmd/templates/ipmd/index.html               |  32 ++
 ipmd/templates/ipmd/saddlegallmidgeform.html | 515 +++++++++++++++++++
 ipmd/tests.py                                |   3 +
 ipmd/urls.py                                 |  28 +
 ipmd/views.py                                |  11 +
 13 files changed, 699 insertions(+)
 create mode 100644 ipmd/__init__.py
 create mode 100644 ipmd/admin.py
 create mode 100644 ipmd/apps.py
 create mode 100644 ipmd/migrations/__init__.py
 create mode 100644 ipmd/models.py
 create mode 100644 ipmd/static/ipmd/js/ipmdlib.js
 create mode 100755 ipmd/templates/ipmd/index.html
 create mode 100644 ipmd/templates/ipmd/saddlegallmidgeform.html
 create mode 100644 ipmd/tests.py
 create mode 100755 ipmd/urls.py
 create mode 100644 ipmd/views.py

diff --git a/VIPSWeb/settings.py b/VIPSWeb/settings.py
index 64169738..6d0be063 100755
--- a/VIPSWeb/settings.py
+++ b/VIPSWeb/settings.py
@@ -143,6 +143,7 @@ INSTALLED_APPS = (
     'observations',
     'information',
     'cerealblotchmodels',
+    'ipmd',
     'calculators',
     'roughage',
     'applefruitmoth',
diff --git a/VIPSWeb/urls.py b/VIPSWeb/urls.py
index a518e333..d5b0f126 100755
--- a/VIPSWeb/urls.py
+++ b/VIPSWeb/urls.py
@@ -57,6 +57,7 @@ else:
         re_path(r'^observations/', include('observations.urls', namespace = "observations")),
         re_path(r'^information/', include('information.urls', namespace = "information")),
         re_path(r'^blotch/', include('cerealblotchmodels.urls', namespace = "cerealblotchmodels")),
+        re_path(r'^ipmd/', include('ipmd.urls', namespace = "ipmd")),
         re_path(r'^calculators/', include('calculators.urls', namespace = "calculators")),
         re_path(r'^roughage/', include('roughage.urls', namespace = "roughage")),
         re_path(r'^security/', include('security.urls', namespace = "security")),
diff --git a/ipmd/__init__.py b/ipmd/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ipmd/admin.py b/ipmd/admin.py
new file mode 100644
index 00000000..8c38f3f3
--- /dev/null
+++ b/ipmd/admin.py
@@ -0,0 +1,3 @@
+from django.contrib import admin
+
+# Register your models here.
diff --git a/ipmd/apps.py b/ipmd/apps.py
new file mode 100644
index 00000000..a56463fc
--- /dev/null
+++ b/ipmd/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class IpmdConfig(AppConfig):
+    default_auto_field = 'django.db.models.BigAutoField'
+    name = 'ipmd'
diff --git a/ipmd/migrations/__init__.py b/ipmd/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/ipmd/models.py b/ipmd/models.py
new file mode 100644
index 00000000..71a83623
--- /dev/null
+++ b/ipmd/models.py
@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.
diff --git a/ipmd/static/ipmd/js/ipmdlib.js b/ipmd/static/ipmd/js/ipmdlib.js
new file mode 100644
index 00000000..97711188
--- /dev/null
+++ b/ipmd/static/ipmd/js/ipmdlib.js
@@ -0,0 +1,96 @@
+/* 
+ * Copyright (c) 2023 NIBIO <http://www.nibio.no/>. 
+ * 
+ * This file is part of VIPSWeb.
+ * VIPSLogic 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.
+ * 
+ * VIPSWeb 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 VIPSWeb.  If not, see <http://www.nibio.no/licenses/>.
+ * 
+ */
+
+const ipmdDSSApiURL = "https://platform.ipmdecisions.net/api/dss/";
+const ipmdWeatherApiURL = "https://platform.ipmdecisions.net/api/wx/";
+
+
+async function getModelMetadata(dssId,modelId) {
+    const response = await fetch(ipmdDSSApiURL + "rest/dss/" + dssId);
+    const dss = await response.json();
+    for(let i=0; i< dss.models.length;i++)
+    {
+        let model = dss.models[i];
+        if(model.id == modelId)
+        {
+            return model;
+        }
+    }
+    return null;
+}
+
+async function getModelInputSchema(dssId,modelId) {
+    const response = await fetch(ipmdDSSApiURL + "rest/model/" + dssId + "/" + modelId + "/input_schema/ui_form");
+    return await response.json();
+}
+
+async function getWeatherDatasource(weatherDatasourceId)
+{
+    const response = await fetch(ipmdWeatherApiURL + "rest/weatherdatasource/" + weatherDatasourceId);
+    return await response.json();
+}
+
+function getWeatherStationList(weatherDatasource){
+    geoJson = JSON.parse(weatherDatasource.spatial.geoJSON);
+    let stationList = [];
+    for(let i=0;i<geoJson.features.length;i++)
+    {
+        let feature = geoJson.features[i];
+        let station = {"id":feature.id, "name":feature.properties.name}
+        stationList.push(station);
+    }
+    return stationList;
+}
+
+async function getLocationWeatherData(endpoint, weatherStationId, parameters, interval, dateStart, dateEnd){
+    const response = await fetch(endpoint 
+        + "?timeStart="        + dateStart
+        + "&timeEnd="          + dateEnd
+        + "&interval="         + interval
+        + "&weatherStationId=" + weatherStationId
+        + "&parameters=" + parameters.join(",")
+        );
+    return await response.json();
+}
+
+async function runModel(endpoint, inputData)
+{
+    const response = await fetch(endpoint, {
+        method: "POST",
+        mode: "cors",
+        cache: "no-cache",
+        headers: {
+            "Content-Type": "application/json"
+        },
+        body: JSON.stringify(inputData)
+    });
+    return await response.json();
+}
+
+function getDateArray(timestart, interval, length)
+{
+    let dateArray = [];
+    currentTime = moment(timestart);
+    for(let i=0;i< length; i++)
+    {
+        dateArray.push(currentTime.format("YYYY-MM-DD"));
+        currentTime.add(interval,'seconds');
+    }
+    return dateArray;
+}
\ No newline at end of file
diff --git a/ipmd/templates/ipmd/index.html b/ipmd/templates/ipmd/index.html
new file mode 100755
index 00000000..1e005223
--- /dev/null
+++ b/ipmd/templates/ipmd/index.html
@@ -0,0 +1,32 @@
+{% extends "base.html" %}
+{% load static %}
+{% comment %}
+
+#
+# Copyright (c) 2023 NIBIO <http://www.nibio.no/>. 
+# 
+# This file is part of VIPSWeb.
+# VIPSWeb 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.
+# 
+# VIPSWeb 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 VIPSWeb.  If not, see <http://www.nibio.no/licenses/>.
+# 
+ 
+{% endcomment %}
+{% load i18n %}
+{% block title%}{% trans "IPM Decisions models" %}{%endblock%}
+{% block content %}
+<h1>{% trans "IPM Decisions models" %}</h1>
+<ul>
+    <li><a href="/ipmd/saddlegallmidge">{% trans "Saddle gall midge" %}</a></li>
+</ul>
+
+{% endblock %}
\ No newline at end of file
diff --git a/ipmd/templates/ipmd/saddlegallmidgeform.html b/ipmd/templates/ipmd/saddlegallmidgeform.html
new file mode 100644
index 00000000..56097a52
--- /dev/null
+++ b/ipmd/templates/ipmd/saddlegallmidgeform.html
@@ -0,0 +1,515 @@
+{% extends "base_with_date_picker.html" %}
+{% load static %}
+{% comment %}
+
+#
+# Copyright (c) 2023 NIBIO <http://www.nibio.no/>. 
+# 
+# This file is part of VIPSWeb.
+# VIPSWeb 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.
+# 
+# VIPSWeb 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 VIPSWeb.  If not, see <http://www.nibio.no/licenses/>.
+# 
+ 
+{% endcomment %}
+{% load i18n %}
+{% block title %}{% trans "Saddle gall midge" %}{% endblock %}
+{% block content %}
+<div class="singleBlockContainer">
+	<h1>{% trans "Saddle gall midge" %}</h1>
+    <div id="inputForm"></div>
+    <div id="weatherStations" style="display: none;">
+    <h2>Weather stations</h2>
+    <select class="form-control" name="weatherStationId" id="weatherStationId"></select>
+    </div>
+    <button class="btn btn-primary" type="button" onclick="submitData();">Submit</button>
+    <div>
+        <canvas id="myChart"></canvas>
+    </div>
+    <pre id="modelDescription"></pre>
+</div>
+{% endblock %}
+{% block customJS %}
+<script src="https://cdn.jsdelivr.net/npm/@json-editor/json-editor@latest/dist/jsoneditor.min.js"></script>
+<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
+<script type="text/javascript" src="{% static "js/3rdparty/moment.min.js" %}"></script>
+<script type="text/javascript" src="{% static "ipmd/js/ipmdlib.js" %}"></script>
+<script type="text/javascript">
+    // Page globals
+    var modelMetaData = undefined;
+    var weatherDatasource = undefined;
+    var editor = undefined;
+    const selectList = document.getElementById("weatherStationId");
+
+    async function initPage() {
+        modelMetaData = await getModelMetadata("adas.dss","HAPDMA");
+        document.getElementById("modelDescription").innerHTML= await modelMetaData["description"];
+        inputFormSchema = await getModelInputSchema("adas.dss","HAPDMA")
+        console.info(inputFormSchema);
+        const element = document.getElementById('inputForm');
+        editor = new JSONEditor(element, 
+        {
+            ajax: true,
+            schema: inputFormSchema,
+            theme: "bootstrap4"
+        });
+        let fullSchema = JSON.parse(modelMetaData["execution"]["input_schema"]);
+        if(fullSchema["properties"]["weatherData"] !== undefined)
+        {
+            console.info("Adding weather stations");
+            // Pull weather stations from web service, render list
+            weatherDatasource = await getWeatherDatasource("no.nibio.lmt");
+            stationList = await getWeatherStationList(weatherDatasource);
+            stationList.sort((a, b) => {
+                return (a.name < b.name) ? -1 : (a.name > b.name) ?  1 : 0;
+            });
+            
+            for(let i=0;i<stationList.length; i++)
+            {
+                selectList.add(new Option(stationList[i].name, stationList[i].id))
+            }
+            document.getElementById("weatherStations").style.display = "block";
+            //console.info(weatherDatasource);
+        }
+    }
+
+    initPage();
+
+
+    async function submitData(){
+        console.info("submitData!");
+        let inputData = editor.getValue();
+        console.info(inputData);
+        // Add hidden parameters
+        let fullSchema = JSON.parse(modelMetaData["execution"]["input_schema"]);
+        const hiddenParameters = modelMetaData["execution"]["input_schema_categories"]["hidden"];
+        console.info(hiddenParameters);
+        for(let i=0;i<hiddenParameters.length;i++)
+        {
+            let hiddenParameter = hiddenParameters[i];
+            inputData[hiddenParameter] = fullSchema["properties"][hiddenParameter]["default"];
+        }
+        // Check for weatherData element. Assuming it's at the root node
+        if(fullSchema["properties"]["weatherData"] !== undefined)
+        {
+            console.info("Need to get weather data!");
+            let weatherStationId = selectList.options[selectList.selectedIndex].value;
+
+            let weatherData = await getLocationWeatherData(
+                weatherDatasource.endpoint,
+                weatherStationId,
+                (function (){
+                    let parameterList = []
+                    modelMetaData.input.weather_parameters.forEach(function(weatherParameter){
+                        parameterList.push(weatherParameter.parameter_code)
+                    })
+                    return parameterList;
+                }()),
+                3600,
+                inputData.optionalData.startDate,
+                inputData.optionalData.endDate,
+            );
+            inputData["weatherData"] = weatherData;
+        }
+        // Ready to call server?
+        //console.info(JSON.stringify(inputData));
+        //let result = await runModel(modelMetaData.execution.endpoint, inputData);
+        let result = mockResult;
+        //console.info(result);
+        displayResult(result);
+    };
+
+    function displayResult(result) {
+
+        let chartData = [];
+        // Generate dates
+        let dates = getDateArray(result.timeStart, 86400, result.locationResult[0].length);
+        //console.info(dates);
+        for(let i=0; i< result.resultParameters.length;i++)
+        {
+            let dataset = {label:result.resultParameters[i], data: []}
+            for(let j=0;j<dates.length;j++)
+            {
+                dataset.data.push({x:dates[j],y:result.locationResult[0].data[j][i]});
+            }
+            chartData.push(dataset);
+        }
+
+        const ctx = document.getElementById('myChart');
+
+        new Chart(ctx, {
+            type: "line",
+            data: {
+                datasets: chartData
+            }
+        })
+    }
+
+    // Prototype result!!!
+    const mockResult={
+        "timeStart": "2023-06-30T22:00:00+00:00",
+        "timeEnd": "2023-08-10T22:00:00+00:00",
+        "interval": 86400,
+        "resultParameters": [
+            "Cumulative_DayDegrees",
+            "StartOfEmergenceThreshold",
+            "FirstThreshold",
+            "SecondThreshold",
+            "ThirdThreshold"
+        ],
+        "locationResult": [
+            {
+                "longitude": 10.62687,
+                "latitude": 62.10944,
+                "altitude": 478.0,
+                "data": [
+                    [
+                        12.957083333333332,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        26.471249999999998,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        38.164583333333333,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        50.12833333333333,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        63.424166666666665,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        76.315416666666664,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        90.9525,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        106.4175,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        124.07208333333334,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        143.23041666666666,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        158.54916666666665,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        173.02458333333331,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        186.06041666666664,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        198.31124999999997,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        210.43499999999997,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        224.8820833333333,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        239.05541666666665,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        251.71874999999997,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        263.06991666666664,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        274.32391666666666,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        284.68975,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        296.90266666666668,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        309.94766666666669,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        322.764,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        334.34983333333332,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        346.98191666666668,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        359.79066666666665,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        372.52816666666666,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        386.75358333333332,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        402.21066666666667,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        416.82858333333331,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        428.63025,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        442.98983333333331,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        456.554,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        470.31691666666666,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        483.14233333333334,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        497.56275,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        508.33566666666667,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        521.79775,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        533.56191666666666,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ],
+                    [
+                        545.3566992753623,
+                        500.0,
+                        750.47,
+                        1023.93,
+                        1500.0
+                    ]
+                ],
+                "warningStatus": [
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    2,
+                    4,
+                    4,
+                    4,
+                    4
+                ],
+                "width": 5,
+                "length": 41
+            }
+        ],
+        "message": "Start of cumulative emergence: 07/08/2023 .",
+        "messageType": 0
+    };
+
+</script>
+{% endblock %}
\ No newline at end of file
diff --git a/ipmd/tests.py b/ipmd/tests.py
new file mode 100644
index 00000000..7ce503c2
--- /dev/null
+++ b/ipmd/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/ipmd/urls.py b/ipmd/urls.py
new file mode 100755
index 00000000..f076576d
--- /dev/null
+++ b/ipmd/urls.py
@@ -0,0 +1,28 @@
+#
+# Copyright (c) 2023 NIBIO <http://www.nibio.no/>. 
+# 
+# This file is part of VIPSWeb.
+# VIPSWeb 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.
+# 
+# VIPSWeb 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 VIPSWeb.  If not, see <http://www.nibio.no/licenses/>.
+# 
+
+from django.urls import re_path
+from ipmd import views
+
+app_name = "ipmd"
+
+urlpatterns = [
+    # ex: /forecasts/                   
+    re_path(r'^$', views.index, name='index'),
+    re_path(r'saddlegallmidge/', views.saddlegallmidgeform, name='saddlegallmidgeform')
+]
\ No newline at end of file
diff --git a/ipmd/views.py b/ipmd/views.py
new file mode 100644
index 00000000..8f147afb
--- /dev/null
+++ b/ipmd/views.py
@@ -0,0 +1,11 @@
+from django.shortcuts import render
+
+# Create your views here.
+
+def index(request):
+    context = {}
+    return render(request, 'ipmd/index.html', context)
+
+def saddlegallmidgeform(request):
+    context = {}
+    return render(request, 'ipmd/saddlegallmidgeform.html', context)
\ No newline at end of file
-- 
GitLab