feat: M1 admin console — all 10 remaining pages + signal/quality/ops infrastructure
Admin console (issues #63–72): - Event stream viewer: live-tail ring buffer (500 events) with subject/user filters - Feature store browser: per-user feature vector history from ml/serving - Model registry panel: MLflow embed at /admin/models - Experiment dashboard: LinUCB per-user stats (pulls, reward, θ) + bandit reset - Recommendation log: per-tip explainability (policy, score, features, latency) - Reward analytics: daily reaction breakdown + per-policy compare - Data quality widget: missing-feature rate, stale-token rate, daily completeness - Ops actions: replay-signal, policy enable/disable; user actions link to Users page - SQL runner: read-only SELECT runner with saved queries - Health rollup: fan-out to api/ml/sqlite/event-bus with auto-refresh Backend: - tip_scores table: logs features+policy+score+latency at every scoring call (#67) - saved_queries table: per-admin saved SQL (#71) - Event bus: 500-event ring buffer + tail() API (#63) - Admin routes: /events, /tips, /reward-analytics, /data-quality, /health, /policies, /replay-signal, /sql, /saved-queries endpoints - /api/ml/* admin-gated proxy to ml/serving (#64, #66) - Shadow-policy registry in recommender (#56) ML serving: - /reset/{user_id}: clear bandit state + feature history (#66) - /stats/{user_id}: pulls, cumulative reward, estimated mean, θ (#66) - /features/{user_id}: last 100 feature vectors logged at scoring time (#64) - Meta (pulls, rewards) persisted alongside A/b matrices Web: - Tip action sheet adds Helpful / Not helpful buttons (#62) - TipFeedback type extended with helpful/not_helpful actions - Rewards mapped: helpful=+0.5, not_helpful=−0.5 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ export const users = sqliteTable('users', {
|
||||
name: text('name'),
|
||||
image: text('image'),
|
||||
googleId: text('google_id').unique(),
|
||||
role: text('role').notNull().default('user'), // 'user' | 'admin'
|
||||
consentGiven: integer('consent_given', { mode: 'boolean' }).notNull().default(false),
|
||||
consentAt: text('consent_at'),
|
||||
createdAt: text('created_at').notNull(),
|
||||
@@ -54,3 +55,37 @@ export const sessions = sqliteTable('sessions', {
|
||||
expiresAt: text('expires_at').notNull(),
|
||||
createdAt: text('created_at').notNull(),
|
||||
});
|
||||
|
||||
// Audit log — every admin write action is appended here.
|
||||
export const adminActions = sqliteTable('admin_actions', {
|
||||
id: text('id').primaryKey(),
|
||||
adminId: text('admin_id').notNull().references(() => users.id),
|
||||
action: text('action').notNull(), // e.g. 'revoke_token', 'reset_bandit'
|
||||
targetType: text('target_type'), // e.g. 'user', 'integration'
|
||||
targetId: text('target_id'),
|
||||
detail: text('detail'), // JSON blob for extra context
|
||||
createdAt: text('created_at').notNull(),
|
||||
});
|
||||
|
||||
// Recommendation explainability log — one row per tip served.
|
||||
// features/scores are JSON blobs. Retained 30 days (GDPR).
|
||||
export const tipScores = sqliteTable('tip_scores', {
|
||||
id: text('id').primaryKey(),
|
||||
userId: text('user_id').notNull().references(() => users.id),
|
||||
tipId: text('tip_id').notNull(),
|
||||
policy: text('policy').notNull(),
|
||||
mlScore: integer('ml_score', { mode: 'number' }), // null when random fallback
|
||||
featuresJson: text('features_json'), // JSON: { is_overdue, task_age_days, priority, hour_of_day, day_of_week }
|
||||
candidateCount: integer('candidate_count'),
|
||||
latencyMs: integer('latency_ms'),
|
||||
servedAt: text('served_at').notNull(),
|
||||
});
|
||||
|
||||
// Admin saved SQL queries.
|
||||
export const savedQueries = sqliteTable('saved_queries', {
|
||||
id: text('id').primaryKey(),
|
||||
adminId: text('admin_id').notNull().references(() => users.id),
|
||||
name: text('name').notNull(),
|
||||
sql: text('sql').notNull(),
|
||||
createdAt: text('created_at').notNull(),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user