lexivo/app/models/review.rb
Fibe Agent e9f5d8ece2
Some checks failed
CI / Lint & Test (push) Has been cancelled
Deploy Status Page / Build & Deploy (push) Has been cancelled
Lexivo: vocabulary learning app with SM-2 spaced repetition
- Word import (multi-format: word, word-def, word:def, word|def)
- Flashcard filter UI (swipe + keyboard arrows + Space to flip)
- SM-2 spaced repetition review queue
- Stimulus flashcard controller with 3D flip animation
2026-04-22 15:02:45 +00:00

79 lines
1.8 KiB
Ruby

# frozen_string_literal: true
class Review < ApplicationRecord
belongs_to :word
EASE_MIN = 1.3
EASE_START = 2.5
# status: new | known | learning | skipped
scope :due, -> { where(status: "learning").where("due_at <= ?", Time.current).order(:due_at) }
scope :upcoming, -> { where(status: "learning").order(:due_at) }
def mark_known!
update!(status: "known", last_reviewed_at: Time.current, due_at: nil)
end
def mark_learning!
update!(
status: "learning",
interval: 1,
repetitions: 0,
ease_factor: EASE_START,
due_at: 1.day.from_now,
last_reviewed_at: Time.current
)
end
def mark_skipped!
update!(status: "skipped", last_reviewed_at: Time.current)
end
# SM-2 algorithm: quality 0-5
# 0-2 = forgot/hard 3-5 = remembered
def sm2_update!(quality)
quality = quality.to_i.clamp(0, 5)
new_ease = [EASE_MIN, ease_factor + 0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02)].max.round(3)
if quality < 3
update!(
status: "learning",
repetitions: 0,
interval: 1,
ease_factor: new_ease,
due_at: 1.day.from_now,
last_reviewed_at: Time.current
)
else
new_reps = repetitions + 1
new_interval = case repetitions
when 0 then 1
when 1 then 6
else [ (interval * new_ease).ceil, 1 ].max
end
update!(
status: "learning",
repetitions: new_reps,
interval: new_interval,
ease_factor: new_ease,
due_at: new_interval.days.from_now,
last_reviewed_at: Time.current
)
end
end
def due_label
return "Now" unless due_at
return "Now" if due_at <= Time.current
days = ((due_at - Time.current) / 1.day).ceil
case days
when 0 then "Today"
when 1 then "Tomorrow"
else "#{days} days"
end
end
end