aboutsummaryrefslogtreecommitdiff
path: root/cmds/serve.go
diff options
context:
space:
mode:
authorSam Scholten2026-08-30 20:34:38 +1000
committerSam Scholten2026-08-30 20:34:38 +1000
commiteff960c54620bfba47d40bc3792ba805dea3dab3 (patch)
treea04fd365bb08a5a043b99665f8d7435ba1e989e0 /cmds/serve.go
parent25f47ca899527ed2b16d19209c0546413e057126 (diff)
downloadfluxrec-eff960c54620bfba47d40bc3792ba805dea3dab3.tar.gz
fluxrec-eff960c54620bfba47d40bc3792ba805dea3dab3.zip
rss: provenance + score calibration in items
Item titles gain a rune-capped source-feed suffix so list views (which only ever show the channel title) display provenance inline. Item bodies open with feed, model score, and the batch-wide score median; the median is computed over every scored entry at poll time and stored on the run row as a nil-safe pointer, so old rows render without it. /api/status exposes the median too.
Diffstat (limited to 'cmds/serve.go')
-rw-r--r--cmds/serve.go23
1 files changed, 23 insertions, 0 deletions
diff --git a/cmds/serve.go b/cmds/serve.go
index 9d82d38..0df5003 100644
--- a/cmds/serve.go
+++ b/cmds/serve.go
@@ -257,6 +257,7 @@ func (c *ServeCommand) pollOnce(client *miniflux.Client, vec *core.TFIDFVectoriz
Kept: assembleBatch(scored, c.TopN, c.ExploreFrac, rng),
DurationMs: time.Since(started).Milliseconds(),
ModelCreatedAt: model.CreatedAt,
+ ScoreMedian: medianScore(scored),
}
if err := core.AppendRun(c.Runs, row); err != nil {
return err
@@ -275,6 +276,27 @@ type scoredEntry struct {
score float64
}
+// medianScore returns the median score across every scored entry in a
+// poll — the batch-wide background (typical-entry) calibration rendered
+// alongside kept items' scores in the RSS body. Nil when nothing was
+// scored, so the run row omits the field rather than logging a fake 0.00.
+func medianScore(entries []scoredEntry) *float64 {
+ if len(entries) == 0 {
+ return nil
+ }
+ scores := make([]float64, len(entries))
+ for i, e := range entries {
+ scores[i] = e.score
+ }
+ sort.Float64s(scores)
+ n := len(scores)
+ median := scores[n/2]
+ if n%2 == 0 {
+ median = (scores[n/2-1] + scores[n/2]) / 2
+ }
+ return &median
+}
+
// assembleBatch ranks scored entries by score descending and selects the
// batch: the top (topN minus the exploration budget) are ranked; the
// budget splits at a fixed 1/3 : 2/3 into runner-ups (highest-scored just
@@ -443,6 +465,7 @@ func (c *ServeCommand) routes(mins []int, modelCreatedAt time.Time) http.Handler
"kept": len(last.Kept),
"duration_ms": last.DurationMs,
"model_created_at": last.ModelCreatedAt,
+ "score_median": last.ScoreMedian,
}
}
w.Header().Set("Content-Type", "application/json")