Add trading scores

This commit is contained in:
Nise Void 2018-06-11 12:59:14 +02:00
parent b0909c6f7e
commit ae1d9331ad
Signed by: NiseVoid
GPG Key ID: FBA14AC83EA602F3
4 changed files with 229 additions and 0 deletions

102
app/trading.go Normal file
View File

@ -0,0 +1,102 @@
package app
import (
"fmt"
"math"
"git.fuyu.moe/5GPowerQuality/api/app/internal/model"
"git.ultraware.nl/NiseVoid/qb"
"git.ultraware.nl/NiseVoid/qb/qc"
)
// GetScores returns the scores for the trading page
func GetScores(meterID int) []Scores {
m := model.Measurement()
q := m.Select(
m.UGem1, m.UGem2, m.UGem3,
m.IMax1, m.IMax2, m.IMax3,
m.CGem1, m.CGem2, m.CGem3,
).
Where(qc.Eq(m.MeterID, meterID)).
OrderBy(qb.Desc(m.Time))
data := [3]powerData{}
err := db.QueryRow(q).Scan(
&data[0].Voltage, &data[1].Voltage, &data[2].Voltage,
&data[0].MaxAmpere, &data[1].MaxAmpere, &data[2].MaxAmpere,
&data[0].CosPhi, &data[1].CosPhi, &data[2].CosPhi,
)
if err != nil {
panic(err)
}
scores := make([]Scores, 3)
for k, v := range data {
scores[k] = Scores{
Phase: k + 1,
Producation: productionScore(v),
Usage: usageScore(v),
Storage: storageScore(v),
}
}
return scores
}
// Scores contains all scores for a phase
type Scores struct {
Phase int `json:"phase"`
Producation float64 `json:"production"`
Usage float64 `json:"usage"`
Storage float64 `json:"storage"`
}
type powerData struct {
Voltage float64
MaxAmpere float64
CosPhi float64
}
func (data powerData) String() string {
return fmt.Sprintf(`%3.0fV, %3.0fA, %.1fφ`, data.Voltage, data.MaxAmpere, data.CosPhi)
}
func getScoreModifier(expected, value, step float64) float64 {
var neg bool
if value-expected < 0 {
neg = true
}
r := math.Pow((value-expected)/step, 2)
if neg {
r = r * -1
}
return r
}
func productionScore(data powerData) float64 {
vMod := getScoreModifier(230, data.Voltage, 5) * -1
aMod := getScoreModifier(100, data.MaxAmpere, 25) * -1
if aMod > 0 {
aMod = 0
}
cpMod := getScoreModifier(0.8, math.Abs(data.CosPhi), 0.1)
return vMod + aMod + cpMod
}
func usageScore(data powerData) float64 {
vMod := getScoreModifier(230, data.Voltage, 5)
aMod := getScoreModifier(100, data.MaxAmpere, 25) * -1
if aMod > 0 {
aMod = 0
}
return vMod + aMod
}
func storageScore(data powerData) float64 {
vMod := getScoreModifier(230, data.Voltage, 5)
cpMod := getScoreModifier(0.8, math.Abs(data.CosPhi), 0.1) * -1
return vMod + cpMod
}

115
app/trading_test.go Normal file
View File

@ -0,0 +1,115 @@
package app
import (
"fmt"
"testing"
"github.com/kortschak/ct"
)
type testcase struct {
Data powerData
Result resultType
}
type resultType uint8
func (r resultType) String() string {
return []string{``, `positive`, `negative`, ` neutral`}[r]
}
const (
resultPositive = iota + 1
resultNegative
resultNeutral
)
func resultTypeFromScore(s float64) resultType {
switch {
case s < 1 && s > -1:
return resultNeutral
case s >= 1:
return resultPositive
case s <= -1:
return resultNegative
}
panic(`Unreachable`)
}
var (
passColor = ct.Fg(ct.BoldGreen).Paint
infoColor = ct.Fg(ct.Yellow).Paint
)
func TestProductionScore(t *testing.T) {
cases := []testcase{
{powerData{Voltage: 240, MaxAmpere: 10, CosPhi: 0.8}, resultNegative},
{powerData{Voltage: 230, MaxAmpere: 10, CosPhi: 0.4}, resultNegative},
{powerData{Voltage: 230, MaxAmpere: 200, CosPhi: 0.8}, resultNegative},
{powerData{Voltage: 220, MaxAmpere: 10, CosPhi: 0.8}, resultPositive},
{powerData{Voltage: 230, MaxAmpere: 10, CosPhi: 1}, resultPositive},
{powerData{Voltage: 230, MaxAmpere: 0, CosPhi: 0.8}, resultNeutral},
{powerData{Voltage: 234, MaxAmpere: 10, CosPhi: 0.8}, resultNeutral},
{powerData{Voltage: 226, MaxAmpere: 10, CosPhi: 0.8}, resultNeutral},
{powerData{Voltage: 230, MaxAmpere: 10, CosPhi: 0.71}, resultNeutral},
{powerData{Voltage: 230, MaxAmpere: 10, CosPhi: 0.89}, resultNeutral},
{powerData{Voltage: 230, MaxAmpere: 120, CosPhi: 0.8}, resultNeutral},
}
testScore(t, cases, productionScore)
}
func TestUsageScore(t *testing.T) {
cases := []testcase{
{powerData{Voltage: 240, MaxAmpere: 10, CosPhi: 0.8}, resultPositive},
{powerData{Voltage: 230, MaxAmpere: 200, CosPhi: 0.8}, resultNegative},
{powerData{Voltage: 220, MaxAmpere: 10, CosPhi: 0.8}, resultNegative},
{powerData{Voltage: 230, MaxAmpere: 0, CosPhi: 0.8}, resultNeutral},
{powerData{Voltage: 234, MaxAmpere: 10, CosPhi: 0.8}, resultNeutral},
{powerData{Voltage: 226, MaxAmpere: 10, CosPhi: 0.8}, resultNeutral},
{powerData{Voltage: 230, MaxAmpere: 120, CosPhi: 0.8}, resultNeutral},
}
testScore(t, cases, usageScore)
}
func TestStorageScore(t *testing.T) {
cases := []testcase{
{powerData{Voltage: 240, MaxAmpere: 10, CosPhi: 0.8}, resultPositive},
{powerData{Voltage: 230, MaxAmpere: 10, CosPhi: 0.4}, resultPositive},
{powerData{Voltage: 220, MaxAmpere: 10, CosPhi: 0.8}, resultNegative},
{powerData{Voltage: 230, MaxAmpere: 10, CosPhi: 1}, resultNegative},
{powerData{Voltage: 234, MaxAmpere: 10, CosPhi: 0.8}, resultNeutral},
{powerData{Voltage: 226, MaxAmpere: 10, CosPhi: 0.8}, resultNeutral},
{powerData{Voltage: 230, MaxAmpere: 10, CosPhi: 0.71}, resultNeutral},
{powerData{Voltage: 230, MaxAmpere: 10, CosPhi: 0.89}, resultNeutral},
}
testScore(t, cases, storageScore)
}
func testScore(t *testing.T, cases []testcase, f func(powerData) float64) {
t.Helper()
for _, v := range cases {
s := f(v.Data)
r := resultTypeFromScore(s)
if r != v.Result {
t.Error(`Expected a`, v.Result, `score, but got`, s)
continue
}
score := fmt.Sprintf(`%6.2f`, s)
t.Log(passColor(`PASS`), infoColor(v.Data), `->`, infoColor(r, `(`+score+`)`))
}
}

View File

@ -40,6 +40,9 @@ func main() {
attrs = append(attrs, strings.ToLower(v.Name)) attrs = append(attrs, strings.ToLower(v.Name))
} }
t := e.Group(`/trading`)
t.GET(`/:meter/scores`, getScores)
panic(e.Start(`localhost:33333`)) panic(e.Start(`localhost:33333`))
} }
@ -72,3 +75,12 @@ func getAttr(index int) echo.HandlerFunc {
return c.JSON(200, app.GetAttribute(index, meter, date)) return c.JSON(200, app.GetAttribute(index, meter, date))
} }
} }
func getScores(c echo.Context) error {
meter, err := strconv.Atoi(c.Param(`meter`))
if err != nil {
return c.NoContent(400)
}
return c.JSON(200, app.GetScores(meter))
}