comparison pkg/controllers/gauges.go @ 3088:09d1ffce3d00

Reworked Nash-Sutcliffe calculation.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Sun, 21 Apr 2019 18:59:00 +0200
parents cf6f595af2cb
children 813309225e35
comparison
equal deleted inserted replaced
3087:014be2194bd1 3088:09d1ffce3d00
12 // * Sascha L. Teichmann <sascha.teichmann@intevation.de> 12 // * Sascha L. Teichmann <sascha.teichmann@intevation.de>
13 13
14 package controllers 14 package controllers
15 15
16 import ( 16 import (
17 "context"
17 "database/sql" 18 "database/sql"
18 "encoding/csv" 19 "encoding/csv"
19 "fmt" 20 "fmt"
20 "log" 21 "log"
21 "math" 22 "math"
36 37
37 const ( 38 const (
38 selectPredictedObserveredSQL = ` 39 selectPredictedObserveredSQL = `
39 SELECT 40 SELECT
40 measure_date, 41 measure_date,
42 date_issue,
41 predicted, 43 predicted,
42 water_level 44 water_level
43 FROM waterway.gauge_measurements 45 FROM waterway.gauge_measurements
44 WHERE 46 WHERE
45 fk_gauge_id = ( 47 fk_gauge_id = (
404 // Too late for an HTTP error code. 406 // Too late for an HTTP error code.
405 return 407 return
406 } 408 }
407 } 409 }
408 410
411 func parseISRS(code string) (*models.Isrs, error) {
412 isrs, err := models.IsrsFromString(code)
413 if err != nil {
414 return nil, JSONError{
415 Code: http.StatusBadRequest,
416 Message: fmt.Sprintf("error: Invalid ISRS code: %v", err),
417 }
418 }
419 return isrs, nil
420 }
421
422 type observedPredictedValues struct {
423 when time.Time
424 observed float64
425 predicted common.TimedValues
426 }
427
428 func loadNashSutcliffeData(
429 ctx context.Context,
430 conn *sql.Conn,
431 gauge *models.Isrs,
432 when time.Time,
433 ) ([]observedPredictedValues, error) {
434
435 var rows *sql.Rows
436 var err error
437 if rows, err = conn.QueryContext(
438 ctx,
439 selectPredictedObserveredSQL,
440 gauge.CountryCode,
441 gauge.LoCode,
442 gauge.FairwaySection,
443 gauge.Orc,
444 gauge.Hectometre,
445 when,
446 ); err != nil {
447 return nil, err
448 }
449 defer rows.Close()
450
451 var (
452 hasCurrent bool
453 current observedPredictedValues
454 values []observedPredictedValues
455 )
456
457 for rows.Next() {
458 var (
459 measureDate time.Time
460 issueDate time.Time
461 predicted bool
462 value float64
463 )
464 if err := rows.Scan(
465 &measureDate,
466 &issueDate,
467 &predicted,
468 &value,
469 ); err != nil {
470 return nil, err
471 }
472 measureDate = measureDate.UTC()
473 issueDate = issueDate.UTC()
474
475 if hasCurrent {
476 if !current.when.Equal(measureDate) {
477 if !math.IsNaN(current.observed) {
478 values = append(values, current)
479 }
480 current = observedPredictedValues{
481 observed: math.NaN(),
482 when: measureDate,
483 }
484 }
485 } else {
486 hasCurrent = true
487 current = observedPredictedValues{
488 observed: math.NaN(),
489 when: measureDate,
490 }
491 }
492
493 if predicted {
494 current.predicted = append(
495 current.predicted,
496 common.TimedValue{When: issueDate, Value: value},
497 )
498 } else {
499 current.observed = value
500 }
501 }
502
503 if err := rows.Err(); err != nil {
504 return nil, err
505 }
506
507 if hasCurrent && !math.IsNaN(current.observed) {
508 values = append(values, current)
509 }
510
511 return values, nil
512 }
513
409 func nashSutcliffe( 514 func nashSutcliffe(
410 _ interface{}, 515 _ interface{},
411 req *http.Request, 516 req *http.Request,
412 conn *sql.Conn, 517 conn *sql.Conn,
413 ) (jr JSONResult, err error) { 518 ) (jr JSONResult, err error) {
414 gauge := mux.Vars(req)["gauge"] 519 gauge := mux.Vars(req)["gauge"]
415 520
416 var isrs *models.Isrs 521 var isrs *models.Isrs
417 if isrs, err = models.IsrsFromString(gauge); err != nil { 522 if isrs, err = parseISRS(gauge); err != nil {
418 err = JSONError{
419 Code: http.StatusBadRequest,
420 Message: fmt.Sprintf("error: Invalid ISRS code: %v", err),
421 }
422 return 523 return
423 } 524 }
424 525
425 var when time.Time 526 var when time.Time
426 if w := req.FormValue("when"); w != "" { 527 if w := req.FormValue("when"); w != "" {
432 return 533 return
433 } 534 }
434 } else { 535 } else {
435 when = time.Now() 536 when = time.Now()
436 } 537 }
538 when = when.UTC()
437 539
438 ctx := req.Context() 540 ctx := req.Context()
439 541
440 var rows *sql.Rows 542 var values []observedPredictedValues
441 if rows, err = conn.QueryContext( 543
442 ctx, 544 if values, err = loadNashSutcliffeData(ctx, conn, isrs, when); err != nil {
443 selectPredictedObserveredSQL, 545 return
444 isrs.CountryCode, 546 }
445 isrs.LoCode, 547
446 isrs.FairwaySection, 548 log.Printf("info: found %d value(s) for Nash Sutcliffe.\n", len(values))
447 isrs.Orc,
448 isrs.Hectometre,
449 when,
450 ); err != nil {
451 return
452 }
453 defer rows.Close()
454
455 var measurements []common.NSMeasurement
456
457 invalid := common.NSMeasurement{
458 Predicted: math.NaN(),
459 Observed: math.NaN(),
460 }
461 current := invalid
462
463 for rows.Next() {
464 var (
465 when time.Time
466 predicted bool
467 value float64
468 )
469 if err = rows.Scan(
470 &when,
471 &predicted,
472 &value,
473 ); err != nil {
474 return
475 }
476 if !when.Equal(current.When) {
477 if current.Valid() {
478 measurements = append(measurements, current)
479 }
480 current = invalid
481 }
482 if predicted {
483 current.Predicted = value
484 } else {
485 current.Observed = value
486 }
487 }
488
489 if err = rows.Err(); err != nil {
490 return
491 }
492
493 if current.Valid() {
494 measurements = append(measurements, current)
495 }
496 549
497 type coeff struct { 550 type coeff struct {
498 Value float64 `json:"value"` 551 Value float64 `json:"value"`
499 Samples int `json:"samples"` 552 Samples int `json:"samples"`
500 Hours int `json:"hours"` 553 Hours int `json:"hours"`
503 type coeffs struct { 556 type coeffs struct {
504 When models.ImportTime `json:"when"` 557 When models.ImportTime `json:"when"`
505 Coeffs []coeff `json:"coeffs"` 558 Coeffs []coeff `json:"coeffs"`
506 } 559 }
507 560
561 var measurements []common.NSMeasurement
562
508 cs := make([]coeff, 3) 563 cs := make([]coeff, 3)
509 for i := range cs { 564 for i := range cs {
510 cs[i].Hours = (i + 1) * 24 565 cs[i].Hours = (i + 1) * 24
566 back := when.Add(time.Duration(-cs[i].Hours) * time.Hour)
567
568 for j := range values {
569 if predicted, ok := values[j].predicted.Interpolate(back); ok {
570 measurements = append(measurements, common.NSMeasurement{
571 Observed: values[i].observed,
572 Predicted: predicted,
573 })
574 }
575 }
576
511 cs[i].Value, cs[i].Samples = common.NashSutcliffe( 577 cs[i].Value, cs[i].Samples = common.NashSutcliffe(
512 measurements, 578 measurements,
513 when, 579 when,
514 when.Add(time.Duration(-cs[i].Hours)*time.Hour), 580 back,
515 ) 581 )
582
583 measurements = measurements[:0]
516 } 584 }
517 585
518 jr = JSONResult{ 586 jr = JSONResult{
519 Result: &coeffs{ 587 Result: &coeffs{
520 When: models.ImportTime{when}, 588 When: models.ImportTime{when},