Rate monitor: daily-budget ring around the Spotify avatar

The payload has carried daily_budget {used, limit, exhausted} forever and the
dashboard rendered none of it. The avatar disc now wears a conic progress rim
that fills as the day's real-API budget is spent — green to 70%, amber to 95%,
red after — and flips purple once the worker has bridged to Spotify Free for
the rest of the day (using_free now included in the emit payload). Tooltip
carries the exact used/limit numbers.
This commit is contained in:
BoulderBadgeDad 2026-06-06 15:43:48 -07:00
parent 09af31154e
commit 7efb5da893
3 changed files with 49 additions and 0 deletions

View file

@ -35033,6 +35033,9 @@ def _emit_rate_monitor_loop():
}
if svc_key == 'spotify' and enr.get('daily_budget'):
entry['worker']['daily_budget'] = enr['daily_budget']
# Budget ring styling: purple when the worker has
# bridged to Spotify Free after spending the budget.
entry['worker']['using_free'] = bool(enr.get('using_free'))
except Exception as e:
logger.debug("enrichment worker status build failed: %s", e)

View file

@ -383,6 +383,28 @@ function _renderEqualizerBars(grid, data) {
}
bar.classList.toggle('cooldown', cooling);
// Daily-budget ring (Spotify is the only service with a real daily
// cap): a conic rim inside the avatar disc fills as the budget is
// spent — green → amber → red, purple once the worker has bridged
// to Spotify Free for the rest of the day.
const budget = worker.daily_budget;
if (budget && budget.limit > 0) {
const bPct = Math.min(1, (budget.used || 0) / budget.limit);
const bridged = !!worker.using_free && !!budget.exhausted;
bar.classList.add('has-budget');
bar.style.setProperty('--eq-budget', String(Math.max(bPct, 0.02)));
bar.style.setProperty('--eq-budget-color',
bridged ? '#a78bfa' : bPct < 0.7 ? '#4ade80' : bPct < 0.95 ? '#fbbf24' : '#ef4444');
const avatar = bar.querySelector('.rate-eq-avatar');
if (avatar) {
avatar.title = bridged
? `Daily budget spent — running on Spotify Free (${budget.used}/${budget.limit})`
: `Daily API budget: ${budget.used}/${budget.limit}`;
}
} else {
bar.classList.remove('has-budget');
}
_eqDisplay[svc] = { value, pct, peak, peakAt, rlTotal, cooling };
}
}

View file

@ -63083,6 +63083,30 @@ body.reduce-effects .dash-card::after {
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}
/* Daily-budget ring a conic progress rim hugging the avatar disc's inner
* edge, filling as the day's API budget is spent. Color comes from JS
* (green amber red, purple when bridged to Spotify Free). Drawn inside
* the disc because the avatar clips overflow. */
.rate-eq-avatar::after {
content: '';
position: absolute;
inset: 0;
border-radius: 50%;
background: conic-gradient(
var(--eq-budget-color, #4ade80) calc(var(--eq-budget, 0) * 360deg),
transparent 0);
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 2.5px));
mask: radial-gradient(farthest-side, transparent calc(100% - 3.5px), #000 calc(100% - 2.5px));
opacity: 0;
transition: opacity 0.4s;
pointer-events: none;
z-index: 2;
}
.rate-eq.has-budget .rate-eq-avatar::after {
opacity: 0.95;
}
.rate-eq.active .rate-eq-avatar {
animation: rate-eq-avatar-halo 2.8s ease-in-out infinite;
}