Signals' metrics by person and whose key paid (#278)

With the server paying for everyone's Signals, its bill had no names.
ipx_signals_judged_total, ipx_signals_hits_total and ipx_jev_tokens_total
now carry user (the account's name) and key (server or own); a call
shared on the server's key is split by each person's share of its
questions. Icon picks bill the person on the server's key, key checks on
their own. The dashboard has tokens and items by person and a Who used
what table over its range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-17 15:26:50 +00:00
parent ce78599ab4
commit 4eba746ba4
8 changed files with 107 additions and 32 deletions

View File

@@ -2596,6 +2596,8 @@ pub struct Ask {
#[derive(Debug)]
pub struct Unjudged {
pub user_id: i64,
/// Their account's name, what /metrics bills by (#278).
pub user_name: String,
/// Their own key; None when the server's is used for everyone.
pub key: Option<String>,
pub feed_id: String,
@@ -2743,7 +2745,7 @@ impl Db {
// rows of signal_verdicts. The same placeholders serve both.
let half = |items: &str| {
format!(
"SELECT g.id AS signal_id, g.name, g.keywords, g.description AS signal_description, g.user_id, u.typesafe_key,
"SELECT g.id AS signal_id, g.name, g.keywords, g.description AS signal_description, g.user_id, u.name AS user_name, u.typesafe_key,
e.feed_id, e.guid, e.title, e.description, f.title AS feed_title, coalesce(e.published, e.first_seen) AS at
FROM signals g
JOIN users u ON u.id = g.user_id
@@ -2781,6 +2783,7 @@ impl Db {
Some(u) if (u.user_id, &u.feed_id, &u.guid) == (user_id, &feed_id, &guid) => u.signals.push(signal),
_ => out.push(Unjudged {
user_id,
user_name: r.try_get("", "user_name")?,
key: r.try_get("", "typesafe_key")?,
feed_id,
guid,