|
|
| import gleam/int |
| import gleam/float |
| import gleam/string |
| import gleam/order |
|
|
| pub type ReviewGrade { |
| Complete |
| Incorrect |
| Difficult |
| Acceptable |
| Easy |
| Perfect |
| } |
|
|
| pub fn grade_to_int(grade: ReviewGrade) -> Int { |
| case grade { |
| Incorrect -> 0 |
| Difficult -> 1 |
| Acceptable -> 3 |
| Easy -> 4 |
| Perfect -> 5 |
| Complete -> 2 |
| } |
| } |
|
|
| pub type SpacedRepetitionState { |
| SpacedRepetitionState(ease_factor: Float, interval: Int, repetitions: Int, next_review_date: String) |
| } |
|
|
| pub fn initial_state() -> SpacedRepetitionState { |
| SpacedRepetitionState(ease_factor: 2.5, interval: 1, repetitions: 0, next_review_date: get_future_date(1)) |
| } |
|
|
| pub fn calculate_next_review(state: SpacedRepetitionState, grade: ReviewGrade) -> SpacedRepetitionState { |
| let grade_num = grade_to_int(grade) |
| let new_ease_factor = case grade_num { |
| n if n < 3 -> 1.3 |
| n -> { |
| let base = state.ease_factor +. {0.1 -. {5.0 -. int.to_float(n)} *. {0.08 +. {5.0 -. int.to_float(n)} *. 0.02}} |
| case base <. 1.3 { |
| True -> 1.3 |
| False -> base |
| } |
| } |
| } |
|
|
| let #(new_interval, new_reps) = case grade_num { |
| n if n < 3 -> #(1, 0) |
| _ -> case state.repetitions { |
| 0 -> #(1, 1) |
| 1 -> #(3, 2) |
| _ -> { |
| let new_int = int.to_float(state.interval) *. new_ease_factor |> float.round |
| #(int.max(new_int, 1), state.repetitions + 1) |
| } |
| } |
| } |
|
|
| SpacedRepetitionState(ease_factor: new_ease_factor, interval: new_interval, repetitions: new_reps, next_review_date: get_future_date(new_interval)) |
| } |
|
|
| pub fn calculate_mastery_percentage(state: SpacedRepetitionState) -> Float { |
| let base = int.to_float(state.repetitions) /. 5.0 *. 100.0 |
| case base >. 100.0 { |
| True -> 100.0 |
| False -> base |
| } |
| } |
|
|
| pub fn should_review(current: String, next: String) -> Bool { |
| case string.compare(current, next) { |
| order.Lt -> False |
| _ -> True |
| } |
| } |
|
|
| pub fn get_future_date(days: Int) -> String { |
| "2026-03-" <> int.to_string(int.min(days + 19, 31)) |
| } |
|
|
| pub fn estimate_total_reviews(target: Float) -> Int { |
| case target <. 2.0 { |
| True -> 8 |
| False -> case target <. 2.5 { |
| True -> 7 |
| False -> 5 |
| } |
| } |
| } |
|
|
| pub fn calculate_optimal_schedule(_total: Int) -> List(Int) { |
| [1, 3, 7, 14, 30, 60, 120, 365] |
| } |
|
|
|
|