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))
|
||||
}
|
||||
|
||||
t := e.Group(`/trading`)
|
||||
t.GET(`/:meter/scores`, getScores)
|
||||
|
||||
panic(e.Start(`localhost:33333`))
|
||||
}
|
||||
|
||||
@ -72,3 +75,12 @@ func getAttr(index int) echo.HandlerFunc {
|
||||
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