Rewrites the internal/db/queries comments to rule 32 and regenerates dbq, whose doc comments and embedded SQL carry them. The bugs behind the tiered mixes, the md5 order seed, the score bands and the round-robin arms are restated as the constraint each one enforces; counts from one library and stale cross-references are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
163 lines
5.3 KiB
Go
163 lines
5.3 KiB
Go
// Code generated by sqlc. DO NOT EDIT.
|
||
// versions:
|
||
// sqlc v1.31.1
|
||
// source: recommendation_metrics.sql
|
||
|
||
package dbq
|
||
|
||
import (
|
||
"context"
|
||
|
||
"github.com/jackc/pgx/v5/pgtype"
|
||
)
|
||
|
||
const recommendationSourceMetricsForUser = `-- name: RecommendationSourceMetricsForUser :many
|
||
SELECT
|
||
pe.source,
|
||
pe.pick_kind,
|
||
count(*)::bigint AS plays,
|
||
count(*) FILTER (WHERE pe.was_skipped)::bigint AS skips,
|
||
count(pe.completion_ratio)::bigint AS completion_n,
|
||
COALESCE(avg(pe.completion_ratio), 0)::float8 AS avg_completion,
|
||
COALESCE(sum(pe.completion_ratio * pe.completion_ratio), 0)::float8 AS completion_sqsum
|
||
FROM play_events pe
|
||
WHERE pe.user_id = $1
|
||
AND pe.started_at > now() - ($2::float8 * INTERVAL '1 day')
|
||
GROUP BY pe.source, pe.pick_kind
|
||
ORDER BY plays DESC
|
||
`
|
||
|
||
type RecommendationSourceMetricsForUserParams struct {
|
||
UserID pgtype.UUID
|
||
Column2 float64
|
||
}
|
||
|
||
type RecommendationSourceMetricsForUserRow struct {
|
||
Source *string
|
||
PickKind *string
|
||
Plays int64
|
||
Skips int64
|
||
CompletionN int64
|
||
AvgCompletion float64
|
||
CompletionSqsum float64
|
||
}
|
||
|
||
// $1 user_id, $2 window_days. plays/skips are counts; avg_completion is the
|
||
// mean completion ratio over the completion_n plays that recorded one.
|
||
// pick_kind splits For You plays into taste/fresh/unattributed;
|
||
// it is NULL for every other source, so those still group to one row.
|
||
//
|
||
// completion_sqsum carries the sum of SQUARED completion ratios so the Go
|
||
// handler can compute a variance — needed for the margin of error on a
|
||
// completion delta. It is the sum rather than `stddev_samp` on purpose:
|
||
// raw source rows get merged into surface families in Go, and sums of squares
|
||
// add across groups exactly, whereas standard deviations cannot be combined
|
||
// without them. Variance = (sqsum - sum²/n) / (n-1), with sum = avg × n.
|
||
func (q *Queries) RecommendationSourceMetricsForUser(ctx context.Context, arg RecommendationSourceMetricsForUserParams) ([]RecommendationSourceMetricsForUserRow, error) {
|
||
rows, err := q.db.Query(ctx, recommendationSourceMetricsForUser, arg.UserID, arg.Column2)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []RecommendationSourceMetricsForUserRow
|
||
for rows.Next() {
|
||
var i RecommendationSourceMetricsForUserRow
|
||
if err := rows.Scan(
|
||
&i.Source,
|
||
&i.PickKind,
|
||
&i.Plays,
|
||
&i.Skips,
|
||
&i.CompletionN,
|
||
&i.AvgCompletion,
|
||
&i.CompletionSqsum,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|
||
|
||
const recommendationWeeklyTrends = `-- name: RecommendationWeeklyTrends :many
|
||
|
||
SELECT
|
||
date_trunc('week', pe.started_at)::date AS week_start,
|
||
pe.source,
|
||
count(*)::bigint AS plays,
|
||
count(*) FILTER (WHERE pe.was_skipped)::bigint AS skips,
|
||
count(pe.completion_ratio)::bigint AS completion_n,
|
||
COALESCE(avg(pe.completion_ratio), 0)::float8 AS avg_completion,
|
||
count(*) FILTER (WHERE tpa.artist_id IS NOT NULL)::bigint AS taste_hits
|
||
FROM play_events pe
|
||
JOIN tracks t ON t.id = pe.track_id
|
||
LEFT JOIN taste_profile_artists tpa
|
||
ON tpa.user_id = pe.user_id
|
||
AND tpa.artist_id = t.artist_id
|
||
AND tpa.weight > 0
|
||
WHERE pe.started_at > now() - ($1::int * INTERVAL '1 week')
|
||
GROUP BY 1, 2
|
||
ORDER BY 1, 2
|
||
`
|
||
|
||
type RecommendationWeeklyTrendsRow struct {
|
||
WeekStart pgtype.Date
|
||
Source *string
|
||
Plays int64
|
||
Skips int64
|
||
CompletionN int64
|
||
AvgCompletion float64
|
||
TasteHits int64
|
||
}
|
||
|
||
// Recommendation observability. Per-source play outcomes so the
|
||
// operator can see whether each recommendation surface is landing and tune the
|
||
// taste weights. Source is stamped on play_events when a play is launched from
|
||
// a recommendation surface; NULL means the user picked the track manually —
|
||
// those rows are INCLUDED here as the baseline control group the surfaces are
|
||
// judged against (delta-vs-baseline is what makes the numbers actionable).
|
||
// Raw source strings are bucketed into stable surface families in the Go
|
||
// handler; completion_n is carried so family merges can weight
|
||
// avg_completion correctly.
|
||
// Weekly per-source outcome series for the tuning lab's trend view
|
||
// . Aggregated across ALL users: the tuning knobs are global,
|
||
// so judging a knob turn needs global outcomes — rows carry rates
|
||
// only, no track or user identity. NULL-source (manual) rows are
|
||
// included as the baseline family.
|
||
//
|
||
// taste_hits counts plays whose track's artist has a positive weight
|
||
// in that user's CURRENT taste profile — the "cheap recompute" option:
|
||
// retroactive over the whole window, at the cost of drift (the profile
|
||
// is today's, the play may be weeks old). Good enough to read whether
|
||
// a surface is feeding taste-fitting tracks.
|
||
// $1 window in weeks.
|
||
func (q *Queries) RecommendationWeeklyTrends(ctx context.Context, weeks int32) ([]RecommendationWeeklyTrendsRow, error) {
|
||
rows, err := q.db.Query(ctx, recommendationWeeklyTrends, weeks)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var items []RecommendationWeeklyTrendsRow
|
||
for rows.Next() {
|
||
var i RecommendationWeeklyTrendsRow
|
||
if err := rows.Scan(
|
||
&i.WeekStart,
|
||
&i.Source,
|
||
&i.Plays,
|
||
&i.Skips,
|
||
&i.CompletionN,
|
||
&i.AvgCompletion,
|
||
&i.TasteHits,
|
||
); err != nil {
|
||
return nil, err
|
||
}
|
||
items = append(items, i)
|
||
}
|
||
if err := rows.Err(); err != nil {
|
||
return nil, err
|
||
}
|
||
return items, nil
|
||
}
|