Cross Cohort Performance metrics - #88
Conversation
| `${c.cohortType}_S${c.seasonNumber}`, | ||
| new CrossCohortPerformanceEntryDto({ | ||
| scoreReceived: c.totalScore, | ||
| totalScore: c.maxTotalScore, |
There was a problem hiding this comment.
totalScore here means the opposite of everywhere else in the module.
In this DTO, totalScore is being set to the max possible score (c.maxTotalScore), while the earned total goes into scoreReceived. But everywhere else in this file — GetCohortScoresResponseDto, LeaderboardEntryDto, WeeklyScore — totalScore means points earned and maxTotalScore is the ceiling.
A consumer reading entry.totalScore will almost certainly treat it as the earned score and render the ceiling instead — a silent data bug that's hard to spot.
Can we rename the field to match the rest of the module (e.g. scoreReceived / maxScore) so the same name doesn't mean two different things? If the API contract is already fixed externally, let's at least document it explicitly on the DTO.
| } | ||
| } | ||
|
|
||
| const totalWeeks = cohort.weeks.length; |
There was a problem hiding this comment.
totalWeeks counts non-scoring weeks, which deflates attendancePercent and avgScore.
totalWeeks is every week on the cohort — including ORIENTATION / GRADUATION and any week the user has no attendance record for. But attendedWeeks is derived from weeklyScores, which are only pushed when an attendance record exists (if (attendance)), and scorePercent's denominator (cohortMaxTotalScore) is likewise summed only over weeks with records.
So within the same object we're mixing two denominators: scorePercent is over attended/recorded weeks, while attendancePercent and avgScore are over all calendar weeks. This also differs from LeaderboardEntryDto, which uses user.attendances.length for maxAttendance.
Is counting orientation/graduation weeks in the denominator intended? If not, filtering cohort.weeks to scoreable week types (or using the count of weeks with attendance records) would keep the three metrics consistent.
| ApiExtraModels(CrossCohortPerformanceEntryDto), | ||
| ApiOkResponse({ | ||
| description: | ||
| 'Map of "<cohortType>_S<season>" to { scoreReceived, totalScore }', |
There was a problem hiding this comment.
Response description is out of date — the entry now also returns attendedWeeks and totalWeeks, but the description still lists only two fields. Quick fix:
| 'Map of "<cohortType>_S<season>" to { scoreReceived, totalScore }', | |
| 'Map of "<cohortType>_S<season>" to { scoreReceived, totalScore, attendedWeeks, totalWeeks }', |
902e154 to
dcf50a9
Compare
Co-authored-by: Cursor <cursoragent@cursor.com>
1759ca1 to
3c86b21
Compare
Summary
attendedWeeks,totalWeeks,scorePercent,attendancePercent,avgScoretoGetCohortScoresResponseDtoScoresService.getUserScores(backsGET /scores/meandGET /scores/user/:userId), no schema/migration changesTest plan
tsc --noEmitpassesGET /scores/mefor a user in multiple cohorts and confirm the new fields, including zero-denominator cohorts (no weeks / no scored weeks yet)🤖 Generated with Claude Code