diff pkg/controllers/gauges.go @ 2741:87aed4f9b1b8

Added calculation of Nash Sutcliffe efficiency coefficents. GET /api/data/nash-sutcliffe/{gauge}?when={WHEN} 'when' is optional in form of "2006-01-02T15:04:05.000" and defaults to current server time. curl -H "X-Gemma-Auth:$KEY" http://${server}:${port}/api/data/nash-sutcliffe/${gauge} | jq . { "when": "2019-03-20T10:38:05.687", "coeffs": [ { "value": 0, "samples": 0, "hours": 24 }, { "value": 0, "samples": 0, "hours": 48 }, { "value": 0, "samples": 0, "hours": 72 } ] }
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Wed, 20 Mar 2019 10:47:01 +0100
parents 0d7a4fdb9e12
children f95ec0bb565c
line wrap: on
line diff
--- a/pkg/controllers/gauges.go	Tue Mar 19 19:21:04 2019 +0100
+++ b/pkg/controllers/gauges.go	Wed Mar 20 10:47:01 2019 +0100
@@ -14,6 +14,7 @@
 package controllers
 
 import (
+	"database/sql"
 	"encoding/csv"
 	"fmt"
 	"log"
@@ -23,11 +24,34 @@
 
 	"github.com/gorilla/mux"
 
+	"gemma.intevation.de/gemma/pkg/common"
 	"gemma.intevation.de/gemma/pkg/middleware"
 	"gemma.intevation.de/gemma/pkg/models"
 )
 
 const (
+	selectPredictedObserveredSQL = `
+SELECT
+  a.measure_date AS measure_date,
+  a.water_level  AS predicted,
+  b.water_level  AS observed
+FROM waterway.gauge_measurements a JOIN waterway.gauge_measurements b
+  ON a.fk_gauge_id  = b.fk_gauge_id AND
+     a.measure_date = b.measure_date AND
+     a.predicted AND NOT b.predicted
+WHERE
+  a.fk_gauge_id = (
+    $1::char(1),
+	$2::char(2),
+	$3::char(3),
+	$4::char(4),
+	$5::int
+  ) AND
+  a.measure_date BETWEEN
+    $6::timestamp AND $6::timestamp - '72hours'::interval
+ORDER BY a.measure_date
+`
+
 	selectWaterlevelsSQL = `
 SELECT
   measure_date,
@@ -38,6 +62,99 @@
 `
 )
 
+func nashSutcliffe(
+	_ interface{},
+	req *http.Request,
+	conn *sql.Conn,
+) (jr JSONResult, err error) {
+	gauge := mux.Vars(req)["gauge"]
+
+	var isrs *models.Isrs
+	if isrs, err = models.IsrsFromString(gauge); err != nil {
+		err = JSONError{
+			Code:    http.StatusBadRequest,
+			Message: fmt.Sprintf("error: Invalid ISRS code: %v", err),
+		}
+		return
+	}
+
+	var when time.Time
+	if w := req.FormValue("when"); w != "" {
+		if when, err = time.Parse(models.ImportTimeFormat, w); err != nil {
+			err = JSONError{
+				Code:    http.StatusBadRequest,
+				Message: fmt.Sprintf("error: wrong time format: %v", err),
+			}
+			return
+		}
+	} else {
+		when = time.Now()
+	}
+
+	ctx := req.Context()
+
+	var rows *sql.Rows
+	if rows, err = conn.QueryContext(
+		ctx,
+		selectPredictedObserveredSQL,
+		isrs.CountryCode,
+		isrs.LoCode,
+		isrs.FairwaySection,
+		isrs.Orc,
+		isrs.Hectometre,
+		when,
+	); err != nil {
+		return
+	}
+	defer rows.Close()
+
+	var measurements []common.NSMeasurement
+
+	for rows.Next() {
+		var m common.NSMeasurement
+		if err = rows.Scan(
+			&m.When,
+			&m.Predicted,
+			&m.Observed,
+		); err != nil {
+			return
+		}
+		measurements = append(measurements, m)
+	}
+	if err = rows.Err(); err != nil {
+		return
+	}
+
+	type coeff struct {
+		Value   float64 `json:"value"`
+		Samples int     `json:"samples"`
+		Hours   int     `json:"hours"`
+	}
+
+	type coeffs struct {
+		When   models.ImportTime `json:"when"`
+		Coeffs []coeff           `json:"coeffs"`
+	}
+
+	cs := make([]coeff, 3)
+	for i := range cs {
+		cs[i].Hours = (i + 1) * 24
+		cs[i].Value, cs[i].Samples = common.NashSutcliffe(
+			measurements,
+			when,
+			when.Add(time.Duration(-cs[i].Hours)*time.Hour),
+		)
+	}
+
+	jr = JSONResult{
+		Result: &coeffs{
+			When:   models.ImportTime{when},
+			Coeffs: cs,
+		},
+	}
+	return
+}
+
 func waterlevels(rw http.ResponseWriter, req *http.Request) {
 	gauge := mux.Vars(req)["gauge"]