feat(admin): profile freshness panel in data-quality (#81 phase B.4)
Adds a per-feature freshness summary to /admin/data-quality so the admin can spot features that are systematically stale or never computed: totalEligible — distinct users with tip_views in the last 30 days missing — eligible users with no row stored for the feature stale — eligible users whose stored row is past its TTL Backend exposes summarizeProfileFreshness() in profile/builder.ts; one query per feature joins eligible users LEFT JOIN profile rows. Coverage = (eligible − missing − stale) / eligible, colored green/yellow/red via the new PctGood helper (high-is-good, opposite of the existing Pct used for missing-feature/stale-token rates). Refs #81. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -120,6 +120,58 @@ export interface ProfileFeatureView {
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface FeatureFreshnessSummary {
|
||||
feature: string;
|
||||
ttlSec: number;
|
||||
/** Distinct users with tip activity in the last 30 days. */
|
||||
totalEligible: number;
|
||||
/** Eligible users with no row stored at all for this feature. */
|
||||
missing: number;
|
||||
/** Eligible users whose stored row is past its TTL. */
|
||||
stale: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Per-feature staleness summary across all eligible users (anyone with a tip
|
||||
* served in the last 30 days). Used by `/admin/data-quality` so the admin can
|
||||
* spot features that are systematically stale or never computed.
|
||||
*
|
||||
* Hand-written SQL because this joins (eligible_users LEFT JOIN profile_rows)
|
||||
* with conditional aggregations — drizzle's query builder is more pain than
|
||||
* value here, and the column allowlist is the registry.
|
||||
*/
|
||||
export function summarizeProfileFreshness(): FeatureFreshnessSummary[] {
|
||||
return FEATURES.map((f) => {
|
||||
const row = rawSqlite
|
||||
.prepare(`
|
||||
WITH eligible AS (
|
||||
SELECT DISTINCT user_id
|
||||
FROM tip_views
|
||||
WHERE served_at >= datetime('now', '-30 days')
|
||||
)
|
||||
SELECT
|
||||
COUNT(*) AS total_eligible,
|
||||
SUM(CASE WHEN upf.user_id IS NULL THEN 1 ELSE 0 END) AS missing,
|
||||
SUM(CASE WHEN upf.user_id IS NOT NULL
|
||||
AND upf.ttl_sec > 0
|
||||
AND (julianday('now') - julianday(upf.updated_at)) * 86400.0 > upf.ttl_sec
|
||||
THEN 1 ELSE 0 END) AS stale
|
||||
FROM eligible e
|
||||
LEFT JOIN user_profile_features upf
|
||||
ON upf.user_id = e.user_id AND upf.name = ?
|
||||
`)
|
||||
.get(f.name) as { total_eligible: number; missing: number; stale: number } | undefined;
|
||||
|
||||
return {
|
||||
feature: f.name,
|
||||
ttlSec: f.ttlSec,
|
||||
totalEligible: Number(row?.total_eligible ?? 0),
|
||||
missing: Number(row?.missing ?? 0),
|
||||
stale: Number(row?.stale ?? 0),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspection helper for the admin UI: returns one row per registered feature,
|
||||
* joining stored value + metadata. No compute — surface staleness; rebuild is
|
||||
|
||||
Reference in New Issue
Block a user