Skip to content

Commit 5955e4a

Browse files
committed
feat(staffml): complete feedback pipeline with tests and CI
Fix the feedback data round-trip end-to-end: - QuestionFeedback: dedup guard, aria-pressed, hydrate previous feedback on mount, wire Report/Suggest to analytics events - analytics.ts: computeSummary() aggregates thumbs and difficulty with last-write-wins dedup per question+session - dashboard: new thumbs ratio and difficulty distribution panels - gauntlet: add QuestionFeedback to per-question review - progress.ts: include analytics in export/import - worker.js: server-side summary aggregates feedback with dedup Add Vitest test infrastructure (34 journey tests across 2 files) and embed type-check + test steps in both CI deploy workflows so tests gate every build before deployment.
1 parent d5113ef commit 5955e4a

14 files changed

Lines changed: 2774 additions & 61 deletions

File tree

.github/workflows/staffml-preview-dev.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ jobs:
6262
working-directory: interviews/staffml
6363
run: npm ci
6464

65+
- name: 🔍 Type check
66+
working-directory: interviews/staffml
67+
run: npx tsc --noEmit
68+
69+
- name: 🧪 Run tests
70+
working-directory: interviews/staffml
71+
run: npm test
72+
6573
- name: 🔨 Build StaffML
6674
working-directory: interviews/staffml
6775
env:

.github/workflows/staffml-publish-live.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ jobs:
5858
working-directory: interviews/staffml
5959
run: npm ci
6060

61+
- name: 🔍 Type check
62+
working-directory: interviews/staffml
63+
run: npx tsc --noEmit
64+
65+
- name: 🧪 Run tests
66+
working-directory: interviews/staffml
67+
run: npm test
68+
6169
- name: 🔄 Sync vault data to StaffML
6270
run: python3 interviews/staffml/scripts/sync-vault.py
6371

interviews/staffml/analytics-worker/worker.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,11 @@ async function handleSummary(env, corsHeaders) {
180180
let questionsScored = 0;
181181
let gauntletsCompleted = 0;
182182
let questionsReported = 0;
183+
let improvementsSuggested = 0;
183184
const scoresByLevel = {};
185+
// Dedup feedback: last-write-wins per (questionId, sessionId)
186+
const latestThumbs = new Map();
187+
const latestDifficulty = new Map();
184188

185189
for (const event of recentEvents) {
186190
if (event._sid) sessions.add(event._sid);
@@ -200,9 +204,32 @@ async function handleSummary(env, corsHeaders) {
200204
case 'question_reported':
201205
questionsReported++;
202206
break;
207+
case 'improvement_suggested':
208+
improvementsSuggested++;
209+
break;
210+
case 'question_thumbs':
211+
if (event.questionId && event._sid) {
212+
latestThumbs.set(`${event.questionId}:${event._sid}`, event.value);
213+
}
214+
break;
215+
case 'question_difficulty_feedback':
216+
if (event.questionId && event._sid) {
217+
latestDifficulty.set(`${event.questionId}:${event._sid}`, event.perceived);
218+
}
219+
break;
203220
}
204221
}
205222

223+
// Aggregate deduplicated feedback
224+
let thumbsUp = 0, thumbsDown = 0;
225+
for (const v of latestThumbs.values()) {
226+
if (v === 'up') thumbsUp++; else thumbsDown++;
227+
}
228+
const difficultyDistribution = { too_easy: 0, about_right: 0, too_hard: 0 };
229+
for (const v of latestDifficulty.values()) {
230+
if (difficultyDistribution[v] !== undefined) difficultyDistribution[v]++;
231+
}
232+
206233
// Compute averages
207234
for (const v of Object.values(scoresByLevel)) {
208235
v.avg = v.count > 0 ? (v.total / v.count).toFixed(2) : 0;
@@ -215,6 +242,10 @@ async function handleSummary(env, corsHeaders) {
215242
questionsScored,
216243
gauntletsCompleted,
217244
questionsReported,
245+
improvementsSuggested,
246+
thumbsUp,
247+
thumbsDown,
248+
difficultyDistribution,
218249
eventsByDay,
219250
scoresByLevel,
220251
},

0 commit comments

Comments
 (0)