| 10 |
10 |
|
|
| 11 |
11 |
|
use crate::heuristics::DELOAD_RATIO;
|
| 12 |
12 |
|
use crate::sets::SessionSet;
|
|
13 |
+ |
use crate::values::{Load, Reps};
|
| 13 |
14 |
|
|
| 14 |
15 |
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
| 15 |
16 |
|
pub enum State {
|
| 73 |
74 |
|
|
| 74 |
75 |
|
/// The prescription for the next session of a given exercise.
|
| 75 |
76 |
|
///
|
| 76 |
|
- |
/// Not stored anywhere; recomputed on demand from history.
|
|
77 |
+ |
/// Not stored anywhere; recomputed on demand from history. Note that
|
|
78 |
+ |
/// `sets` here is a *count* of sets to program, not a `Set` — the
|
|
79 |
+ |
/// primitive `i32` is fine.
|
| 77 |
80 |
|
#[derive(Debug, Clone, PartialEq)]
|
| 78 |
81 |
|
pub struct Prescription {
|
| 79 |
82 |
|
pub state: State,
|
| 81 |
84 |
|
/// user who typically does 3 sets keeps doing 3 sets.
|
| 82 |
85 |
|
pub sets: i32,
|
| 83 |
86 |
|
/// Target rep count. Same as the last session's target.
|
| 84 |
|
- |
pub reps: i32,
|
| 85 |
|
- |
/// Load in the exercise's `load_unit`.
|
| 86 |
|
- |
pub load: f64,
|
|
87 |
+ |
pub reps: Reps,
|
|
88 |
+ |
/// Load in the exercise's [`crate::LoadUnit`].
|
|
89 |
+ |
pub load: Load,
|
| 87 |
90 |
|
}
|
| 88 |
91 |
|
|
| 89 |
92 |
|
/// Result of walking history + computing what comes next.
|
| 269 |
272 |
|
PrescriptionResult::Prescribed(Prescription {
|
| 270 |
273 |
|
state: walk.state,
|
| 271 |
274 |
|
sets: walk.last_set_count,
|
| 272 |
|
- |
reps: walk.last_target_reps,
|
| 273 |
|
- |
load: walk.next_load,
|
|
275 |
+ |
// last_target_reps and next_load came out of a session that was
|
|
276 |
+ |
// just validated at decode time, so unwrap is fine — a bad row
|
|
277 |
+ |
// would have failed to load in the first place.
|
|
278 |
+ |
reps: Reps::new(walk.last_target_reps).expect("target reps from validated history"),
|
|
279 |
+ |
load: Load::new(walk.next_load).expect("next load from validated history"),
|
| 274 |
280 |
|
})
|
| 275 |
281 |
|
}
|
| 276 |
282 |
|
|
| 307 |
313 |
|
if matches!(result, PrescriptionResult::NoHistory)
|
| 308 |
314 |
|
&& let Some(seed) = self.diagnostic_seed(exercise_id)?
|
| 309 |
315 |
|
{
|
| 310 |
|
- |
let load = round_to_increment(
|
|
316 |
+ |
let load_raw = round_to_increment(
|
| 311 |
317 |
|
seed * crate::heuristics::DIAGNOSTIC_WORKING_WEIGHT_RATIO,
|
| 312 |
318 |
|
increment,
|
| 313 |
319 |
|
);
|
| 314 |
320 |
|
result = PrescriptionResult::Prescribed(Prescription {
|
| 315 |
321 |
|
state: State::Progressing,
|
| 316 |
322 |
|
sets: 3,
|
| 317 |
|
- |
reps: 5,
|
| 318 |
|
- |
load,
|
|
323 |
+ |
reps: Reps::new(5).unwrap(),
|
|
324 |
+ |
load: Load::new(load_raw)
|
|
325 |
+ |
.expect("diagnostic seed produces non-negative load"),
|
| 319 |
326 |
|
});
|
| 320 |
327 |
|
}
|
| 321 |
328 |
|
|
| 390 |
397 |
|
};
|
| 391 |
398 |
|
assert_eq!(pres.state, State::Progressing);
|
| 392 |
399 |
|
assert_eq!(pres.sets, 3);
|
| 393 |
|
- |
assert_eq!(pres.reps, 5);
|
| 394 |
|
- |
assert_eq!(pres.load, 90.0);
|
|
400 |
+ |
assert_eq!(pres.reps.get(), 5);
|
|
401 |
+ |
assert_eq!(pres.load.get(), 90.0);
|
| 395 |
402 |
|
}
|
| 396 |
403 |
|
|
| 397 |
404 |
|
#[test]
|
| 405 |
412 |
|
panic!("expected prescription");
|
| 406 |
413 |
|
};
|
| 407 |
414 |
|
// Working set at 60 with a clean first-session hit -> next 62.5.
|
| 408 |
|
- |
assert_eq!(pres.load, 62.5);
|
|
415 |
+ |
assert_eq!(pres.load.get(), 62.5);
|
| 409 |
416 |
|
}
|
| 410 |
417 |
|
|
| 411 |
418 |
|
#[test]
|
| 422 |
429 |
|
panic!("expected prescription");
|
| 423 |
430 |
|
};
|
| 424 |
431 |
|
assert_eq!(pres.state, State::Progressing);
|
| 425 |
|
- |
assert_eq!(pres.load, 102.5, "hold keeps load flat");
|
|
432 |
+ |
assert_eq!(pres.load.get(), 102.5, "hold keeps load flat");
|
| 426 |
433 |
|
assert_eq!(
|
| 427 |
434 |
|
db.read_progression_state(id).unwrap(),
|
| 428 |
435 |
|
Some(State::Progressing)
|
| 509 |
516 |
|
};
|
| 510 |
517 |
|
assert_eq!(pres.state, State::Progressing);
|
| 511 |
518 |
|
assert_eq!(pres.sets, 3);
|
| 512 |
|
- |
assert_eq!(pres.reps, 5);
|
| 513 |
|
- |
assert_eq!(pres.load, 102.5);
|
|
519 |
+ |
assert_eq!(pres.reps.get(), 5);
|
|
520 |
+ |
assert_eq!(pres.load.get(), 102.5);
|
| 514 |
521 |
|
}
|
| 515 |
522 |
|
|
| 516 |
523 |
|
#[test]
|
| 523 |
530 |
|
panic!()
|
| 524 |
531 |
|
};
|
| 525 |
532 |
|
assert_eq!(pres.state, State::Progressing);
|
| 526 |
|
- |
assert_eq!(pres.load, 102.5);
|
|
533 |
+ |
assert_eq!(pres.load.get(), 102.5);
|
| 527 |
534 |
|
}
|
| 528 |
535 |
|
|
| 529 |
536 |
|
#[test]
|
| 540 |
547 |
|
panic!()
|
| 541 |
548 |
|
};
|
| 542 |
549 |
|
assert_eq!(pres.state, State::Probation);
|
| 543 |
|
- |
assert_eq!(pres.load, 102.5);
|
|
550 |
+ |
assert_eq!(pres.load.get(), 102.5);
|
| 544 |
551 |
|
}
|
| 545 |
552 |
|
|
| 546 |
553 |
|
#[test]
|
| 558 |
565 |
|
panic!()
|
| 559 |
566 |
|
};
|
| 560 |
567 |
|
assert_eq!(pres.state, State::Progressing);
|
| 561 |
|
- |
assert_eq!(pres.load, 105.0);
|
|
568 |
+ |
assert_eq!(pres.load.get(), 105.0);
|
| 562 |
569 |
|
}
|
| 563 |
570 |
|
|
| 564 |
571 |
|
#[test]
|
| 581 |
588 |
|
};
|
| 582 |
589 |
|
assert_eq!(pres.state, State::Deloading);
|
| 583 |
590 |
|
// 100 * 0.9 = 90, rounded to nearest 2.5 -> 90.
|
| 584 |
|
- |
assert_eq!(pres.load, 90.0);
|
|
591 |
+ |
assert_eq!(pres.load.get(), 90.0);
|
| 585 |
592 |
|
}
|
| 586 |
593 |
|
|
| 587 |
594 |
|
#[test]
|
| 610 |
617 |
|
panic!()
|
| 611 |
618 |
|
};
|
| 612 |
619 |
|
assert_eq!(pres.state, State::Progressing);
|
| 613 |
|
- |
assert_eq!(pres.load, 102.5);
|
|
620 |
+ |
assert_eq!(pres.load.get(), 102.5);
|
| 614 |
621 |
|
}
|
| 615 |
622 |
|
|
| 616 |
623 |
|
#[test]
|
| 633 |
640 |
|
panic!()
|
| 634 |
641 |
|
};
|
| 635 |
642 |
|
assert_eq!(pres.state, State::Rebuilding);
|
| 636 |
|
- |
assert_eq!(pres.load, 92.5);
|
|
643 |
+ |
assert_eq!(pres.load.get(), 92.5);
|
| 637 |
644 |
|
}
|
| 638 |
645 |
|
|
| 639 |
646 |
|
#[test]
|
| 642 |
649 |
|
let PrescriptionResult::Prescribed(pres) = compute_prescription(&sessions, 0.0) else {
|
| 643 |
650 |
|
panic!()
|
| 644 |
651 |
|
};
|
| 645 |
|
- |
assert_eq!(pres.load, 0.0);
|
|
652 |
+ |
assert_eq!(pres.load.get(), 0.0);
|
| 646 |
653 |
|
}
|
| 647 |
654 |
|
|
| 648 |
655 |
|
#[test]
|
| 664 |
671 |
|
panic!()
|
| 665 |
672 |
|
};
|
| 666 |
673 |
|
// 82.5 * 0.9 = 74.25, nearest 2.5 = 75.
|
| 667 |
|
- |
assert_eq!(pres.load, 75.0);
|
|
674 |
+ |
assert_eq!(pres.load.get(), 75.0);
|
| 668 |
675 |
|
}
|
| 669 |
676 |
|
}
|