Measured PR state and event model
Domain boundary
Measured PRs are projections of user-authored facts. They are not estimates and they are not authoritative input rows themselves.
Authoritative facts are:
- completed
mainandtopexercise sets; - dated historical 1RM inputs; and
- undated historical 1RM inputs whose exact date is unknown.
Estimated 1RM (e1RM) remains a separate calculation, history, API field, and UI concept. A missing measured PR must never be filled with an e1RM value.
Exact-repetition policy
For weight-and-repetition exercises, measured PR dimensions are the exact repetition counts 1 through 20. A set updates only its own repetition dimension. For example, 100 kg x 10 is a 10-repetition candidate; it is not a 3RM, 5RM, or 8RM candidate.
For a given user, exercise, repetition count, and observed day, the projection keeps one deterministic highest-load candidate. A new event exists only when that candidate is strictly greater than every earlier candidate. Ties do not move the original achievement date and do not create events.
Sets outside 1 through 20 and zero-load/bodyweight repetition records remain training facts but do not enter this measured-load PR projection. Those exercise measurement types require their own record definitions.
Projection layers
user_exercise_pr_events contains only actual running-maximum transitions. An event includes the previous and new values, delta, exact repetition count, effective date, source kind, and source identifiers.
user_exercise_pr_states contains at most one current row per (user_id, exercise_id, target_reps). It points to the event that established the current value. State is a bounded read projection, not an audit log.
Both tables are service-written and user-readable through bounded screen RPCs. They must be rebuildable from canonical facts.
Undated onboarding baseline
An onboarding 1RM with no known date is ordered before every observed session and imported WodUp fact, including facts imported later with older dates. It is not assigned a fabricated date.
Every raw historical input remains an independently editable source fact. When more than one undated fact exists for an exercise, the deterministic highest value is the one effective baseline in the projection; the projector does not delete or overwrite the other source facts.
The effective baseline seeds the running maximum for target_reps = 1. It may be shown as an undated starting point and may supply previous_value for the first dated improvement. It does not count as a dated PR, a recent PR, an annual PR, or a notification-worthy achievement.
When the user later supplies a date, the source fact becomes dated and the affected exercise timeline is replayed.
Replay and corrections
Appending a recent set may be optimized, but correctness is defined by replay. An insert, update, delete, date move, or exercise move marks the earliest old/new date and every old/new exercise dirty. Under the existing per-user advisory lock, the worker rebuilds the affected event stream in deterministic chronological order and replaces current state in the same transaction.
This replay may add events, remove events, move achievement dates, or lower the current state after a historical correction. That is expected. The event table is the currently supported historical truth, not an immutable mutation audit. If mutation auditing is required, it belongs in a separate append-only audit log.
Projection generation and the PR overview snapshot are published atomically. Readers must never combine a state generation with a different event or overview generation.
Read-model limits
- Home and PR overview return measured 1RM state and a bounded recent-event feed.
- Selected-exercise detail may return all 20 current NRM state rows.
- Event history is keyset-paginated.
- The overview must not return 20 NRM rows for every catalog exercise.
- Training history and e1RM history remain separate delayed fragments.
PR counts are counts of non-baseline transition events. previous PR means the previous transition value, not the second-largest arbitrary performance.
Cutover policy
This is a pre-launch breaking cutover. Active database builders, screen RPCs, TypeScript contracts, and mappers read the state/event projection directly. There is no dual-write period and no measured-value fallback to legacy candidate rows or e1RM. Existing source facts are replayed during migration before the new read contract is published.