Skip to content

fix(score): compute the CR per program instead of blending degrees - #103

Open
ForceTower wants to merge 2 commits into
mainfrom
feat/score-per-program
Open

ForceTower wants to merge 2 commits into
mainfrom
feat/score-per-program

Conversation

@ForceTower

@ForceTower ForceTower commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Fixes the CR blending reported in #55 (2019): a student who finishes a graduação and starts a mestrado keeps studying under the same login, so both degrees' disciplines fell into one lifetime CR. The number that comes out describes neither degree — it can sit a full point above or below the real one, in either direction, depending on how the second program is going.

How programs are told apart

By the semester track: the suffix upstream stamps on a semester code (22.1RUE, 19.1PGM, 2023M), absent on the regular undergrad calendar. Postgrad, EAD and the year-long health cycles all run their own calendars, so this separates them cleanly — and it is the only per-discipline program signal that exists, since nothing upstream ties a turma to a course.

The API already sends track on both semester endpoints and KMP already stored it in SemesterEntity; no call site read it. iOS dropped it at decode, so this adds the column (SemesterRecord, migration v10) and carries it through both semester DTOs.

What changed

Shared (KMP)

  • CalculateOverallScoreUseCase returns an OverallScore: one ProgramScore per program, ordered by recency, plus the track the student is currently in. current gives the surfaces that have room for one number the current program, falling back to the newest program with closed grades so a freshly started mestrado doesn't blank the hero.
  • checkpoints(track:) replaces the old capSemesterId sparkline, which re-ran the whole CR query once per semester. One pass now.
  • The delta follows the same rule: a mestrado semester closing between two graduação semesters no longer moves the graduação's delta.
  • ObserveGradeTileUseCase (Overview) scopes to the active semester's program.
  • SemesterDisciplines / PendingSemester carry the track.

Android

  • Histórico gets a chip row when there is more than one program; the semester list, "cursadas" and "aprovação" all scope to the pick.
  • Me and the onboarding "tudo pronto" card read the current program.
  • Students with a single program — nearly all of them — see exactly what they saw before: the picker only renders for a choice of more than one.

iOS

  • CoefficientHistory walks one program at a time. summary() keeps its shape, so Home and the Watch complication needed no change; the Retrospectiva passes the recapped semester's own track.

Eu tab (both platforms)

  • The hero's Score stat takes the program's own name — Graduação, RUE — in place of the generic label, and tapping it walks to the next program. Value, delta and the iOS sparkline all follow the pick.
  • A student with a single program sees no change at all: the label stays "Score" and the stat is not tappable, so nobody gains a dead tap target.

Tests

  • New CalculateOverallScoreTest (KMP): programs stay separate, the current program is the newest enrollment's, a program with no closed grades falls back, per-program delta, multi-group dedup.
  • New cases in CoefficientHistoryTests (iOS): per-program CR and the fallback.
  • New cases in MeFeatureTests (iOS): the hero walks and wraps through the programs, and a single-program student's stat stays inert.
  • ./gradlew testDebugUnitTest jvmTest lintDebug and the full UNESKit suite (377 tests) pass.

Not covered

  • Transfers within graduação. Two undergrad courses share the same calendar, so their semesters are indistinguishable in the data — no track, no course on the offer. That case still needs the manual hide.
  • Program names. Upstream gives us no name for a program, only the code it stamps on semesters, so the chips read Graduação / RUE / PGM. A code→name mapping would be a follow-up.
  • The other half of Cálculo de score errado.  #55 (documents not downloading for an aluno especial with graduação + mestrado) is a separate path and is untouched.

A student who finishes a graduação and starts a mestrado keeps studying
under the same login, so both degrees' disciplines fell into one lifetime
CR. The resulting number describes neither degree — depending on how the
second program is going it can sit a full point above or below the CR the
student actually has.

Programs are told apart by the semester track: the suffix upstream stamps
on a semester code (22.1RUE, 19.1PGM), absent on the regular undergrad
calendar. The API already sends it and KMP already stored it; nothing read
it. iOS dropped it at decode, so `SemesterRecord` gains the column
(migration v10) and both semester DTOs carry it through.

`CalculateOverallScoreUseCase` now returns an `OverallScore` — one
`ProgramScore` per program, ordered by recency, plus the track the student
is currently in — and `CoefficientHistory` groups its walk the same way.
Both keep a single-number entry point for the surfaces that have room for
one: the current program, falling back to the newest program with closed
grades so a freshly started mestrado doesn't blank the hero.

The delta and the sparkline follow the same rule. Previously a mestrado
semester closing in between two graduação semesters moved the graduação's
delta; now each program only compares against itself.

On the Histórico tab, a student with more than one program gets a chip row
to pick between them, and the semester list, disciplines taken and approval
rate scope to the pick. Students with a single program — nearly all of them
— see exactly what they saw before, since the picker only renders for a
choice of more than one.

Refs #55
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 16, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
unes-landing 29e5618 Commit Preview URL

Branch Preview URL
Aug 16 2026, 06:47 PM

The hero already showed the CR of the program the student is in, but
nothing said which program that was, and a student with a mestrado on top
of their graduação had no way to reach the other number without opening
Disciplinas.

The Score stat now takes the program's own name — "Graduação", "RUE" — in
place of the generic label, and tapping it walks to the next program. The
value, delta, and (on iOS) the sparkline all follow the pick. Nothing
changes for a student with a single program: the label stays "Score" and
the stat is not tappable, so no one gains a dead tap target.

Both platforms keep the same order, newest program first, so the hero opens
on the degree the student is actually studying.

Refs #55
@ForceTower
ForceTower marked this pull request as ready for review August 16, 2026 19:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant