Add trading scores
This commit is contained in:
parent
b0909c6f7e
commit
ae1d9331ad
102
app/trading.go
Normal file
102
app/trading.go
Normal 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
115
app/trading_test.go
Normal 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+`)`))
|
||||||
|
}
|
||||||
|
}
|
@ -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))
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user