feat(api): My Wave sessions with a Go fallback and wave metrics (M4)
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 40s
ci / mobile / mobile (Metro-бандл Android) (push) Failing after 9s
ci / api / api (push) Successful in 35s
ci / ts / ts (packages + web) (push) Successful in 51s

- /wave/options, POST /wave/sessions (activity, mood, seed),
  /next with reactions, PATCH to switch the context on the fly, DELETE
- the recommender is called with an 800 ms budget; on failure the api
  serves a fallback wave of likes, favourite artists and random
  available tracks with the same dislike / recent / served filters
- every served track is an impression with its source and model; wave
  events (context_type wave) set its outcome, reactions win
- library status shows the analysis queue and 7-day wave metrics:
  listen-through, early skip and like rate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mrleo1nidandClaude Opus 5.5 committed 2026-10-20 12:23:17 +03:00
1 parent ef29b85320
commit 3908814cda
13 files changed
+1811 -162

No files matched your search

+25 -1
View File
@@ -268,11 +268,20 @@ func (h *Handler) PostEvents(ctx context.Context, req gen.PostEventsRequestObjec
if e.PlayedMs != nil {
played = *e.PlayedMs
}
if o := Judge(ev.Type, played, dur); o != (Outcome{}) {
o := Judge(ev.Type, played, dur)
if o != (Outcome{}) {
if err := h.store.ApplyOutcome(ctx, tx, p.UserID, ev, o); err != nil {
return nil, err
}
}
if outcome := impressionOutcome(ev.Type, o); outcome != "" && ev.ContextType != nil && *ev.ContextType == "wave" &&
ev.ContextID != nil {
if sid, err := uuid.Parse(*ev.ContextID); err == nil {
if err := h.store.MarkImpression(ctx, tx, sid, ev.TrackID, outcome); err != nil {
return nil, err
}
}
}
}
if err := tx.Commit(ctx); err != nil {
return nil, err
@@ -551,3 +560,18 @@ func (h *Handler) ClearTrackReaction(ctx context.Context, req gen.ClearTrackReac
}
var _ library.Personalizer = (*Handler)(nil)
// impressionOutcome — исход показа волны по событию: complete | skip_early | skip | like | dislike.
func impressionOutcome(eventType string, o Outcome) string {
switch {
case eventType == "like" || eventType == "dislike":
return eventType
case o.Complete:
return "complete"
case o.EarlySkip:
return "skip_early"
case o.Skip:
return "skip"
}
return ""
}