Skip to content

refactor(clustermap): derive user grade server-side, shrink wire format#22

Merged
FreekBes merged 3 commits into
codam-coding-college:masterfrom
CheapFuck:refactor/clustermap-grade-server-derived
Jun 6, 2026
Merged

refactor(clustermap): derive user grade server-side, shrink wire format#22
FreekBes merged 3 commits into
codam-coding-college:masterfrom
CheapFuck:refactor/clustermap-grade-server-derived

Conversation

@CheapFuck

Copy link
Copy Markdown
Contributor

Follow-up to a1e749a (#PR-ish reference to the pisciner/advanced/alumni feature).

Moves the classification rule out of the two frontend files and into src/routes/clustermap.ts so it lives in one place, and shrinks the JSON wire format to a single grade field per user instead of the full cursus_users array.

Why

The classification logic was duplicated across static/js/clustermaps/base.js (read) and static/js/clustermaps/playback.js (relay), with the cursus ids (9, 21) and grade labels ('Transcender', 'Alumni') as magic values in the client. Every live SSE tick and every history fetch also shipped the raw cursus_users array for every user just so the client could compute one label from it.

What changes

Server (src/routes/clustermap.ts)

  • Named constants for the Codam cursus taxonomy: PISCINE_CURSUS_ID, CORE_CURSUS_ID, GRADE_TRANSCENDER, GRADE_ALUMNI.
  • New UserGrade union type ('pisciner' | 'advanced' | 'alumni' | null).
  • deriveUserGrade() encodes the rule once. The precedence (ongoing piscine wins over Transcender, which wins over Alumni) is now explicit and documented in a comment.
  • enrichUser() / enrichLocation() strip cursus_users from the response and add the derived grade. Applied at all three query sites:
    • the live SSE stream (/clustermap/locations/live)
    • /clustermap/locations/:at
    • /clustermap/locations/:from/:to
  • ClustermapUser now exposes grade: UserGrade instead of cursus_users.
  • The Prisma select is unchangedcursus_users is still loaded because the server needs it to derive grade. So DB cost is identical; only the outgoing JSON shrinks (~80–150 bytes saved per active user per SSE tick).

Frontend (static/js/clustermaps/base.js)

  • createLocation() reads location.user.grade directly.
  • Defensive fallback: if grade is missing but cursus_users is present, re-derive locally. This way a stale cached client hitting a new server, or vice versa during a rolling deploy, keeps working. Also guards against missing user and non-array cursus_users so a malformed payload doesn't throw.

Frontend (static/js/clustermaps/playback.js)

  • Forwards user.grade instead of user.cursus_users when constructing the synthetic location objects passed to createLocation/removeLocation.

Non-goals / not touched

  • The visual treatment (.pisciner / .advanced / .alumni CSS, tooltip height) is unchanged.
  • The Prisma schema is unchanged.
  • No caching layer was added — at ~200 active sessions, recomputing grade per SSE tick is negligible compared to the bandwidth saved.

Verification

  • npx tsc --noEmit -p . is clean for everything under src/ after the change.
  • Local smoke: rendered clustermap and confirmed rings/tooltips still show pisciner/advanced/alumni correctly.

Moves the pisciner / advanced / alumni classification from the frontend
into src/routes/clustermap.ts so it lives in one place and the JSON
payload only carries a single 'grade' string per user instead of the
full cursus_users array.

Server (src/routes/clustermap.ts)
- Named constants for the cursus taxonomy (PISCINE_CURSUS_ID,
  CORE_CURSUS_ID, GRADE_TRANSCENDER, GRADE_ALUMNI) and a UserGrade
  union type, replacing the magic numbers / strings previously
  duplicated in base.js.
- deriveUserGrade() encodes the rule once; precedence (ongoing piscine
  > Transcender > Alumni) is now explicit and documented.
- enrichUser() / enrichLocation() strip cursus_users from the response
  and add the derived grade. Applied at all three query sites: the live
  SSE stream, /clustermap/locations/:at and /clustermap/locations/:from/:to.
- ClustermapUser interface drops cursus_users in favour of
  grade: UserGrade. The Prisma select is unchanged (cursus_users is
  still needed to derive grade), so DB cost is identical; only the JSON
  payload shrinks (~80-150 bytes saved per active user per SSE tick).

Frontend (static/js/clustermaps/base.js)
- createLocation() now just reads location.user.grade. A defensive
  fallback re-derives locally from cursus_users when grade is absent,
  so a stale cached client served against a new server (or vice versa)
  during a rolling deploy keeps working. Also guards against
  missing user / non-array cursus_users.

Frontend (static/js/clustermaps/playback.js)
- Forwards user.grade instead of user.cursus_users when constructing
  the synthetic location objects passed to createLocation/removeLocation.

@FreekBes FreekBes left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good refactor (I should have coded it like this in the first place). One part of the logic can be removed, see the comment from the code review.

Comment thread static/js/clustermaps/base.js
@FreekBes FreekBes added the refactor Refactor of existing code label Jun 4, 2026
CheapFuck and others added 2 commits June 6, 2026 11:54
CodamHero runs a single always-up-to-date server, so the defensive
re-derivation from cursus_users in createLocation is unnecessary.
Read location.user.grade directly.
@FreekBes FreekBes merged commit dedc785 into codam-coding-college:master Jun 6, 2026
1 check passed
@Codam-IT

Codam-IT commented Jun 6, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 2.3.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor Refactor of existing code released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants