Skip to content

fix: aggregate repeated exercises consistently in history and stats - #212

Merged
DuarteSantos8 merged 2 commits into
DuarteSantos8:mainfrom
Space-Hermes:contrib/github-history-aggregation-20260914
Sep 28, 2026
Merged

DuarteSantos8 merged 2 commits into
DuarteSantos8:mainfrom
Space-Hermes:contrib/github-history-aggregation-20260914

Conversation

@Space-Hermes

@Space-Hermes Space-Hermes commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

When a combined workout contains the same exercise more than once, progress readers currently stop at the first occurrence. A session with 60 kg followed by 80 kg can therefore report 60 kg. This change reads all matching completed occurrences while keeping one progress point per workout.

The focused remainder of GitLab !50 is refreshed on v1.3.8 main (f91cde15) following #140. Exercise history, estimated 1RM, Stats and Strength now agree on repeated occurrences. Warm-up and mode boundaries remain respected; per-side metrics count only completed limbs. Legacy top-weight records remain usable, and deleted-exercise display metadata comes from the newest dated workout. Strength retention and 1RM formulas are unchanged. Repeated assisted exercises retain lower-assistance ranking and remain excluded from estimated 1RM.

Validation: all 1,598 frontend tests pass across 126 files; the production build passes. Regressions cover later stronger occurrences, partially completed sides, unsorted history, legacy top weights and mixed legacy/modern entries. Earlier browser review verified both bench occurrences in one dated Stats row, all three completed set labels, estimated 1RM and the Strength view.

@kurktchiev

Copy link
Copy Markdown
Contributor

One gap worth naming, since this PR is what makes it visible: the progression side still reads the first occurrence.

bestWeightForEntry, bestSetOf and the e1RM series now answer with the best across every occurrence of an exercise in a workout, but lastEntryFor in history.js and sessionsFor in progression.js both still do w.entries.find(e => e.id === exId). So for a day that holds one exercise twice, Stats and the estimated 1RM report one block while the "Last time" card, the rows the next session seeds, the freestyle config and the next prescription report the other.

A workout can hold the same exercise twice whenever two routines in one session share it, or when a routine is added to a session already under way. Before this PR everything read the first occurrence, so the app was consistent and sometimes low. Now the two halves can disagree about the same day.

I have taken the following in a fork, if it is useful here. One helper picks the block that speaks for the exercise in a workout and both readers go through it: most completed work sets wins, then whichever of load and the mode's own measure is the thing being trained, so seconds rank ahead of load for a hold and minutes for cardio. Set count first matters, because a heavy single logged after a working block should not become what the next session is prescribed from.

Three things only turned up under review, and they may save someone else the trouble:

A block logged in another mode has to be excluded, and the mode taken from the first block that is actually in the running. nextPrescription filters sessions by mode, so letting a timed 4x30s block win on set count does not merely answer oddly, it drops the day and reports "nothing logged yet" for an exercise that was pressed that morning. Taking the mode from the first block stored is worse again, because an excluded rehab block ahead of the real work leaves the workout with no answer at all.

A block with no target of its own has to stay a candidate for any mode. readSession judges those against the fallback, and both importers write entries with no target, so classifying them by the exercise id drops every imported hold out of a timed progression.

The tie-break has to be read off the rows that are handed back rather than through bestWeightForEntry, which counts a per-side row with one limb ticked and falls back to a legacy topW that no row carries. Either one can hand the win to a block whose rows are lighter than the loser's.

The helper also applies the existing exclusion rule, so a rehab or deload block is not a candidate, and it still yields exactly one session per workout, which keeps the stall and deload counting intact.

Happy to open it as a separate PR on top of this one if you would rather keep this change to what it already covers.

@Space-Hermes

Copy link
Copy Markdown
Contributor Author

Thanks @kurktchiev — I checked both readers and agree that the first-occurrence behaviour needs a follow-up. Please open the separate PR on top of this one, as you offered; I'd keep #212 focused on history/Stats and review the progression selection rule separately.

The working-block versus heavy-single distinction makes sense. The cases you identified would be useful regression tests: mixed modes, target-less imports, excluded rehab/deload blocks, and per-side completion, while retaining one progression session per workout. Stats can aggregate completed work without requiring the next prescription to use the strongest single set. Please link the follow-up here when it is ready.

@DuarteSantos8
DuarteSantos8 merged commit e637a9e into DuarteSantos8:main Sep 28, 2026
4 checks passed
DuarteSantos8 added a commit that referenced this pull request Sep 28, 2026
…ines' occurrences of a combined day while each routine still progresses from its own
@DuarteSantos8

Copy link
Copy Markdown
Owner

Thanks @Space-Hermes, repeated exercises now add up the same way in history and Stats.

@kurktchiev's point about progression still reading the first occurrence is addressed from the other side in this release: progression now reads per routine slot (#216), so on a combined day each routine progresses from its own block. There's a test pinning that the history sheet shows both occurrences while each routine keeps its own line. The same exercise twice inside one routine still shares one slot.

Released in v1.3.9: https://github.com/DuarteSantos8/openGym/releases/tag/v1.3.9

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.

3 participants