File size: 2,263 Bytes
d0b8e8f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

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]
}