Simulation initial

This commit is contained in:
Daniel 2024-10-26 07:53:25 -04:00
parent dbcef1f229
commit 5da1eb2944
4 changed files with 78 additions and 9 deletions

View file

@ -39,6 +39,7 @@ type alerter struct {
scorer trending.Scorer[cl.Talkgroup]
scores trending.Scores[cl.Talkgroup]
lastScore time.Time
sim *Simulation
}
type noopAlerter struct{}
@ -126,7 +127,7 @@ func (as *alerter) startBackfill(ctx context.Context) {
now := time.Now()
since := now.Add(-24 * time.Hour * time.Duration(as.cfg.LookbackDays))
log.Debug().Time("since", since).Msg("starting stats backfill")
count, err := as.backfill(ctx, since)
count, err := as.backfill(ctx, since, now)
if err != nil {
log.Error().Err(err).Msg("backfill failed")
return
@ -143,11 +144,11 @@ func (as *alerter) score(ctx context.Context, now time.Time) {
sort.Sort(as.scores)
}
func (as *alerter) backfill(ctx context.Context, since time.Time) (count int, err error) {
func (as *alerter) backfill(ctx context.Context, since time.Time, until time.Time) (count int, err error) {
db := database.FromCtx(ctx)
const backfillStatsQuery = `SELECT system, talkgroup, call_date FROM calls WHERE call_date > $1 AND call_date < $2`
rows, err := db.Query(ctx, backfillStatsQuery, since, timeseries.DefaultClock.Now())
rows, err := db.Query(ctx, backfillStatsQuery, since, until)
if err != nil {
return count, err
}

View file

@ -0,0 +1,59 @@
package alerting
import (
"context"
"encoding/json"
"net/http"
"sort"
"time"
"dynatron.me/x/stillbox/internal/trending"
cl "dynatron.me/x/stillbox/pkg/calls"
"dynatron.me/x/stillbox/pkg/gordio/config"
"github.com/rs/zerolog/log"
)
type Simulation struct {
config.Alerting
ScoreStart time.Time `json:"scoreStart"`
clock offsetClock
*alerter `json:"-"`
}
func (s *Simulation) Simulate(ctx context.Context) trending.Scores[cl.Talkgroup] {
s.Enable = true
s.alerter = New(s.Alerting, WithClock(&s.clock)).(*alerter)
s.alerter.sim = s
now := time.Now()
sinceLookback := now.Add(-24 * time.Hour * time.Duration(s.LookbackDays))
s.backfill(ctx, sinceLookback, s.ScoreStart)
for s.clock = offsetClock(now.Sub(s.ScoreStart)); now.After(now.Add(s.clock.Duration())); s.clock += offsetClock(time.Minute) {
s.backfill(ctx, s.clock.Now().Add(-1*time.Minute), s.clock.Now())
s.scores = s.scorer.Score()
}
s.lastScore = now
sort.Sort(s.scores)
return s.scores
}
func simulateHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s := new(Simulation)
err := json.NewDecoder(r.Body).Decode(s)
if err != nil {
log.Error().Err(err).Msg("simulate decode")
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// TODO: sanity check that ScoreStart is not before lookback and lookback is sane
s.Simulate(ctx)
s.tgStats(w, r)
}

View file

@ -63,10 +63,12 @@ func (as *alerter) tgStats(w http.ResponseWriter, r *http.Request) {
TGs map[calls.Talkgroup]database.GetTalkgroupsByPackedIDsRow
Scores trending.Scores[calls.Talkgroup]
LastScore time.Time
Simulation *Simulation
}{
TGs: tgMap,
Scores: as.scores,
LastScore: as.lastScore,
Simulation: as.sim,
}
w.WriteHeader(http.StatusOK)

View file

@ -46,7 +46,14 @@
</style>
</head>
<body>
<div id="simform">
</div>
<table>
{{ if .Simluation }}
<tr>
<td colspan="10">Simulating from {{ .Simulation.ScoreStart }} until now</td>
</tr>
{{ end }}
<tr>
<th>System</th>
<th>TG</th>