Files
jap_learn/src/lang.rs
T

516 lines
15 KiB
Rust
Raw Normal View History

2026-08-20 18:18:14 +03:00
use crate::data_provider::card_stats::load_stats_of_deck;
use crate::data_provider::card_stats::update_stat_score;
use crate::data_provider::history::{push_note, HistoryItem};
use crate::app::AppState;
2026-04-13 22:25:58 +03:00
use chrono::{DateTime, Utc};
2026-08-20 00:42:06 +03:00
use parking_lot::Mutex;
use rand::distr::weighted::WeightedIndex;
2026-08-20 18:18:14 +03:00
use rand::distr::Distribution;
2026-05-25 12:23:32 +03:00
use rand::prelude::SliceRandom;
2026-04-14 15:19:46 +03:00
use rand::rng;
use rand::rngs::ThreadRng;
2026-08-15 21:32:24 +03:00
use rayon::iter::IndexedParallelIterator;
2026-08-15 20:16:48 +03:00
use rayon::iter::IntoParallelRefIterator;
use rayon::iter::ParallelIterator;
use rhai::{Engine, Scope};
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef};
2026-08-20 18:18:14 +03:00
use rusqlite::ToSql;
2026-08-18 12:47:06 +03:00
use std::cmp::PartialEq;
2026-04-13 22:25:58 +03:00
use std::collections::HashMap;
2026-08-18 12:47:06 +03:00
use std::fmt::{Display, Formatter};
2026-08-18 12:58:02 +03:00
use std::num::ParseIntError;
use std::str::FromStr;
2026-08-20 00:07:09 +03:00
use std::sync::Arc;
2026-07-29 13:30:49 +03:00
use std::time::Instant;
2026-08-23 22:48:00 +03:00
use log::info;
2026-04-13 22:25:58 +03:00
2026-04-20 10:13:39 +03:00
const MAX_HISTORY_LEN: usize = 20;
2026-04-17 13:06:37 +03:00
const MAX_HISTORY_LEN_PART: f32 = 0.33;
2026-07-29 12:57:46 +03:00
const MAX_SCORE: u8 = 25;
2026-04-13 22:25:58 +03:00
const FADE_PER_DAY: f32 = 0.95;
2026-08-20 16:22:38 +03:00
#[derive(Clone, PartialEq, Eq, Debug, Hash, Copy, Ord, PartialOrd)]
2026-08-18 17:45:50 +03:00
pub struct Id(u32);
2026-08-18 12:47:06 +03:00
pub const INVALID_ID: Id = Id(0);
2026-08-18 12:58:02 +03:00
impl FromStr for Id {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Id(s.parse::<u32>()?))
}
}
2026-08-18 17:47:37 +03:00
impl From<u32> for Id {
fn from(value: u32) -> Self {
Id(value)
2026-08-18 12:58:02 +03:00
}
}
2026-08-20 00:18:16 +03:00
impl From<Id> for u32 {
fn from(value: Id) -> Self {
value.0
2026-08-18 12:58:02 +03:00
}
}
impl Display for Id {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromSql for Id {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(i) => Ok(Id(i as u32)),
_ => Err(FromSqlError::InvalidType),
}
}
}
impl ToSql for Id {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.0 as i64))
}
}
2026-08-20 16:22:38 +03:00
impl From<Id> for String {
fn from(value: Id) -> Self {
value.0.to_string()
}
}
2026-08-18 17:31:19 +03:00
impl Id {
pub(crate) fn is_valid(self) -> bool {
self.0 != 0
}
}
2026-02-10 12:12:16 +03:00
2026-08-18 12:47:06 +03:00
#[derive(Clone, Debug)]
2026-05-05 12:02:59 +03:00
pub struct WordData {
2026-08-18 12:47:06 +03:00
pub id: Id,
2026-04-13 22:25:58 +03:00
pub key: String,
pub value: String,
pub tags: String,
pub additional: HashMap<String, String>,
2026-08-18 12:47:06 +03:00
pub group_id: Id,
2026-04-13 22:25:58 +03:00
}
2026-05-05 12:02:59 +03:00
impl WordData {
2026-04-13 22:25:58 +03:00
pub fn new() -> Self {
Self {
2026-08-18 12:58:02 +03:00
id: INVALID_ID,
2026-04-13 22:25:58 +03:00
key: String::new(),
value: String::new(),
tags: String::new(),
additional: Default::default(),
2026-08-18 12:47:06 +03:00
group_id: 1.into(),
2026-04-13 22:25:58 +03:00
}
}
}
2026-05-05 14:26:34 +03:00
#[derive(Clone)]
2026-05-12 17:12:03 +03:00
pub struct WordGroup {
2026-08-18 12:47:06 +03:00
pub id: Id,
2026-05-05 12:02:59 +03:00
pub name: String,
}
2026-04-14 15:19:46 +03:00
#[derive(Clone, PartialEq)]
2026-04-13 22:25:58 +03:00
pub struct CardStatistics {
2026-08-18 12:47:06 +03:00
pub id: Id,
pub word_id: Id,
pub set_id: Id,
2026-04-13 22:25:58 +03:00
pub last_open: DateTime<Utc>,
2026-07-29 12:57:46 +03:00
pub score: u8,
2026-04-13 22:25:58 +03:00
}
impl CardStatistics {
2026-04-14 15:19:46 +03:00
pub fn update(&mut self, status: WordOpenMode) {
2026-04-13 22:25:58 +03:00
match status {
WordOpenMode::Easy => {
2026-07-29 12:57:46 +03:00
self.score = (self.calculated_score() + 5.0).round() as u8;
2026-04-13 22:25:58 +03:00
}
2026-07-29 12:57:46 +03:00
WordOpenMode::Ok => self.score = (self.calculated_score() + 2.0).round() as u8,
2026-04-13 22:25:58 +03:00
WordOpenMode::Hard => {
2026-07-29 12:57:46 +03:00
self.score = (self.calculated_score() * 0.75).round() as u8;
2026-04-13 22:25:58 +03:00
}
WordOpenMode::None => {
2026-07-29 12:57:46 +03:00
self.score = (self.calculated_score() * 0.4) as u8;
2026-04-13 22:25:58 +03:00
}
}
2026-08-15 13:22:59 +03:00
self.score = self.score.clamp(1, MAX_SCORE);
2026-04-16 09:56:57 +03:00
self.last_open = Utc::now();
2026-04-13 22:25:58 +03:00
}
pub fn calculated_score(&self) -> f32 {
let time = Utc::now() - self.last_open;
let days = time.num_days();
let multiplier = FADE_PER_DAY.powi(days as i32);
2026-08-01 19:36:43 +03:00
(self.score as f32 * multiplier).max(1.0)
2026-04-13 22:25:58 +03:00
}
}
2026-05-25 12:23:32 +03:00
#[derive(Clone, Copy)]
2026-04-13 22:25:58 +03:00
pub enum WordOpenMode {
Easy,
Ok,
Hard,
None,
}
#[derive(Clone)]
2026-08-18 12:14:35 +03:00
pub struct DeckData {
2026-05-05 12:02:59 +03:00
words: Vec<WordData>,
2026-04-13 22:25:58 +03:00
set: Vec<CardStatistics>,
2026-04-14 15:19:46 +03:00
current_word_index: Option<usize>,
2026-04-17 13:06:37 +03:00
state: Arc<Mutex<AppState>>,
2026-05-25 12:23:32 +03:00
order_module: OrderModule,
2026-08-18 12:14:35 +03:00
settings: DeckSettings,
2026-04-13 22:25:58 +03:00
}
2026-08-18 12:14:35 +03:00
impl DeckData {
pub fn new(settings: &DeckSettings, state: Arc<Mutex<AppState>>) -> Self {
2026-04-14 15:19:46 +03:00
let state_for = state.clone();
2026-08-20 00:07:09 +03:00
let state_locked = state.lock();
2026-04-13 22:25:58 +03:00
2026-08-20 16:22:38 +03:00
let stats = load_stats_of_deck(settings, &state_locked.connection);
2026-08-20 16:23:22 +03:00
let words = stats
.iter()
.map(|stat| {
state_locked
.dictionary
.binary_search_by_key(&stat.word_id, |w| w.id)
.unwrap()
})
.map(|index| &state_locked.dictionary[index])
.cloned()
.collect();
2026-04-15 07:53:32 +03:00
2026-04-13 22:25:58 +03:00
Self {
2026-08-20 16:22:38 +03:00
set: stats,
words,
2026-04-14 15:19:46 +03:00
current_word_index: None,
2026-04-17 13:06:37 +03:00
state: state_for,
2026-05-25 12:23:32 +03:00
order_module: match settings.open_mode {
2026-08-15 20:16:48 +03:00
OrderMode::Default => OrderModule::SemiRandomSRS(SemiRandomSRSModule::new()),
OrderMode::TrainWorstFirst => {
2026-07-29 14:24:40 +03:00
OrderModule::WorstWordsSRS(WorstWordsSRSModule::new())
}
2026-08-15 20:16:48 +03:00
OrderMode::FullRandom => OrderModule::RandomSRS(RandomSRSModule::new()),
2026-05-25 12:23:32 +03:00
},
2026-07-29 12:57:46 +03:00
settings: settings.clone(),
2026-04-14 15:19:46 +03:00
}
}
2026-08-20 16:23:22 +03:00
2026-05-05 12:02:59 +03:00
pub fn next(&mut self) -> (WordData, CardStatistics) {
2026-05-25 12:23:32 +03:00
let index = match self.order_module.clone() {
OrderModule::SemiRandomSRS(mut module) => {
2026-08-15 13:22:59 +03:00
if !module.initialized {
2026-05-25 12:23:32 +03:00
module.init(self)
}
let index = module.next(self);
self.order_module = OrderModule::SemiRandomSRS(module);
index
}
OrderModule::RandomSRS(mut module) => {
2026-08-15 13:22:59 +03:00
if !module.initialized {
2026-05-25 12:23:32 +03:00
module.init(self)
}
let index = module.next(self);
self.order_module = OrderModule::RandomSRS(module);
index
}
2026-07-29 14:24:40 +03:00
OrderModule::WorstWordsSRS(mut module) => {
2026-08-15 13:22:59 +03:00
if !module.initialized {
2026-08-01 19:43:07 +03:00
module.init(self)
}
let index = module.next(self);
self.order_module = OrderModule::WorstWordsSRS(module);
index
}
2026-05-25 12:23:32 +03:00
};
2026-04-17 13:06:37 +03:00
2026-04-14 15:19:46 +03:00
self.current_word_index = Some(index);
2026-05-05 12:02:59 +03:00
(self.words[index].clone(), self.set[index].clone())
2026-04-14 15:19:46 +03:00
}
pub fn open(&mut self, status: WordOpenMode) {
2026-08-15 13:22:59 +03:00
if self.current_word_index.is_none() {
2026-04-14 15:19:46 +03:00
return;
}
2026-05-25 12:23:32 +03:00
let index = self.current_word_index.unwrap();
2026-04-14 15:19:46 +03:00
2026-08-24 00:29:41 +03:00
let word : &mut CardStatistics = &mut self.set[index];
2026-07-29 12:57:46 +03:00
let old_score = word.score;
2026-04-14 15:19:46 +03:00
word.update(status);
2026-07-29 12:57:46 +03:00
let new_score = word.score;
2026-05-25 12:23:32 +03:00
2026-08-24 00:29:41 +03:00
match self.order_module {
OrderModule::SemiRandomSRS(ref mut module) => {
module.open(status, index, word);
2026-05-25 12:23:32 +03:00
}
2026-08-24 00:29:41 +03:00
OrderModule::RandomSRS(ref mut module) => {
module.open(status, index, word);
2026-05-25 12:23:32 +03:00
}
2026-08-24 00:29:41 +03:00
OrderModule::WorstWordsSRS(ref mut module) => {
module.open(status, index, word);
2026-08-01 19:43:07 +03:00
}
2026-05-25 12:23:32 +03:00
}
2026-08-20 00:07:09 +03:00
update_stat_score(word, &self.state.lock().connection);
2026-08-24 01:22:00 +03:00
let _ = push_note(
2026-07-29 14:24:40 +03:00
self.settings.id,
HistoryItem {
timestamp: Utc::now(),
2026-08-19 17:39:52 +03:00
word_id: word.word_id,
2026-08-19 17:33:22 +03:00
mode: status,
2026-07-29 14:24:40 +03:00
before: old_score,
after: new_score,
},
2026-08-24 01:22:00 +03:00
);
2026-05-25 12:23:32 +03:00
}
pub fn len(&self) -> usize {
self.set.len()
}
}
#[derive(Clone, PartialEq, Copy, Eq)]
2026-08-15 20:16:48 +03:00
pub enum OrderMode {
2026-05-25 12:23:32 +03:00
Default,
2026-07-29 14:24:40 +03:00
TrainWorstFirst,
2026-05-25 12:23:32 +03:00
FullRandom,
}
2026-08-18 12:14:35 +03:00
#[derive(Clone, Copy, Eq, PartialEq, Default)]
2026-08-15 21:32:24 +03:00
pub enum AppendMode {
2026-08-15 20:16:48 +03:00
Full,
2026-08-18 12:14:35 +03:00
#[default]
2026-08-15 21:32:24 +03:00
Manual,
2026-08-15 20:16:48 +03:00
}
2026-05-25 12:23:32 +03:00
#[derive(Clone)]
enum OrderModule {
SemiRandomSRS(SemiRandomSRSModule),
RandomSRS(RandomSRSModule),
2026-07-29 14:24:40 +03:00
WorstWordsSRS(WorstWordsSRSModule),
2026-05-25 12:23:32 +03:00
}
trait SRSModule {
2026-08-18 12:14:35 +03:00
fn next(&mut self, set: &mut DeckData) -> usize;
2026-08-24 00:29:41 +03:00
fn open(&mut self, status: WordOpenMode, index: usize, updated_word: &CardStatistics);
2026-08-18 12:14:35 +03:00
fn init(&mut self, set: &mut DeckData);
2026-05-25 12:23:32 +03:00
}
#[derive(Clone)]
struct RandomSRSModule {
2026-06-15 17:06:02 +03:00
basket: Vec<usize>,
initialized: bool,
2026-05-25 12:23:32 +03:00
}
impl RandomSRSModule {
fn new() -> RandomSRSModule {
2026-07-29 14:24:40 +03:00
Self {
2026-06-15 17:06:02 +03:00
basket: vec![],
initialized: false,
2026-05-25 12:23:32 +03:00
}
}
}
impl SRSModule for RandomSRSModule {
2026-08-18 12:14:35 +03:00
fn next(&mut self, set: &mut DeckData) -> usize {
2026-08-19 19:21:21 +03:00
match self.basket.pop() {
Some(index) => index,
None => {
self.basket = (0..set.words.len()).collect::<Vec<usize>>();
self.basket.shuffle(&mut rand::rng());
self.basket.pop().expect("empty basket")
}
2026-05-25 12:23:32 +03:00
}
}
2026-08-24 00:29:41 +03:00
fn open(&mut self, _: WordOpenMode, _: usize, _: &CardStatistics) {}
2026-05-25 12:23:32 +03:00
2026-08-18 12:14:35 +03:00
fn init(&mut self, set: &mut DeckData) {
2026-06-15 17:06:02 +03:00
self.initialized = true;
2026-08-20 11:17:51 +03:00
self.basket = (0..set.set.len()).collect::<Vec<usize>>();
2026-06-15 17:06:02 +03:00
self.basket.shuffle(&mut rand::rng())
2026-05-25 12:23:32 +03:00
}
}
#[derive(Clone)]
struct SemiRandomSRSModule {
history: Vec<usize>,
last_weights: WeightedIndex<f32>,
generator: ThreadRng,
2026-06-15 17:06:02 +03:00
initialized: bool,
2026-05-25 12:23:32 +03:00
}
impl SemiRandomSRSModule {
fn new() -> SemiRandomSRSModule {
SemiRandomSRSModule {
history: vec![],
last_weights: WeightedIndex::new([1.0]).unwrap(),
generator: rng(),
2026-06-15 17:06:02 +03:00
initialized: false,
2026-05-25 12:23:32 +03:00
}
}
}
impl SRSModule for SemiRandomSRSModule {
2026-08-18 12:14:35 +03:00
fn next(&mut self, set: &mut DeckData) -> usize {
2026-05-25 12:23:32 +03:00
let index = self.last_weights.sample(&mut self.generator);
2026-08-20 18:27:05 +03:00
if self.history.contains(&index) && self.history.len() != 1 {
2026-05-25 12:23:32 +03:00
return self.next(set);
}
if self.history.len() == self.history_len(set) {
self.history.remove(0);
}
self.history.push(index);
index
}
2026-08-24 00:29:41 +03:00
fn open(&mut self, _: WordOpenMode, index: usize, word: &CardStatistics) {
2026-04-20 10:13:39 +03:00
let new_weight = (100.0 / word.calculated_score()).powf(2.0);
2026-05-12 17:12:03 +03:00
self.last_weights
2026-05-25 12:23:32 +03:00
.update_weights(&[(index, &new_weight)])
2026-08-19 19:21:21 +03:00
.expect("update weights error");
2026-04-13 22:25:58 +03:00
}
2026-08-18 12:14:35 +03:00
fn init(&mut self, set: &mut DeckData) {
2026-06-15 17:06:02 +03:00
self.initialized = true;
2026-05-25 12:23:32 +03:00
let weights = set
.set
.iter()
.map(|s| (100.0 / s.calculated_score()).powf(2.0) * 2.0)
.collect::<Vec<f32>>();
self.last_weights = WeightedIndex::new(weights).unwrap();
}
}
impl SemiRandomSRSModule {
2026-08-18 12:14:35 +03:00
fn history_len(&self, set: &DeckData) -> usize {
((set.len() as f32 * MAX_HISTORY_LEN_PART) as usize).clamp(1, MAX_HISTORY_LEN)
2026-04-17 13:06:37 +03:00
}
2026-05-25 12:23:32 +03:00
}
2026-07-29 14:24:40 +03:00
#[derive(Clone)]
struct WorstWordsSRSModule {
initialized: bool,
2026-08-01 19:36:43 +03:00
pool_size: usize,
rounds_remaining: u8,
pool: Vec<usize>,
queue: Vec<usize>,
rounds_count: u8,
2026-07-29 14:24:40 +03:00
}
impl SRSModule for WorstWordsSRSModule {
2026-08-18 12:14:35 +03:00
fn next(&mut self, set: &mut DeckData) -> usize {
2026-08-01 19:36:43 +03:00
if self.rounds_remaining == 0 {
self.fill_pool(set);
self.rounds_remaining = self.rounds_count;
}
2026-08-19 19:21:21 +03:00
match self.queue.pop() {
2026-08-19 23:51:41 +03:00
Some(index) => index,
2026-08-19 19:21:21 +03:00
None => {
self.queue.append(&mut self.pool.clone());
self.rounds_remaining -= 1;
self.queue.pop().expect("empty queue")
}
2026-08-01 19:36:43 +03:00
}
2026-07-29 14:24:40 +03:00
}
2026-08-24 00:29:41 +03:00
fn open(&mut self, _: WordOpenMode, _: usize, _: &CardStatistics) {}
2026-07-29 14:24:40 +03:00
2026-08-18 12:14:35 +03:00
fn init(&mut self, _: &mut DeckData) {
2026-07-29 14:24:40 +03:00
self.initialized = true;
}
}
impl WorstWordsSRSModule {
fn new() -> WorstWordsSRSModule {
WorstWordsSRSModule {
initialized: false,
2026-08-01 19:36:43 +03:00
pool_size: 15,
rounds_count: 2,
rounds_remaining: 0,
2026-07-29 14:24:40 +03:00
pool: vec![],
2026-08-01 19:36:43 +03:00
queue: vec![],
2026-07-29 14:24:40 +03:00
}
}
2026-08-18 12:14:35 +03:00
fn fill_pool(&mut self, set: &DeckData) {
2026-08-01 19:43:07 +03:00
let mut sorted = set
.set
.clone()
.into_iter()
.zip(0..set.set.len())
.collect::<Vec<_>>();
2026-08-01 19:36:43 +03:00
sorted.sort_by_key(|c| c.0.score);
2026-08-01 19:43:07 +03:00
let mut worst = sorted[0..self.pool_size]
.iter()
.map(|(_, index)| *index)
.collect::<Vec<usize>>();
2026-08-01 19:36:43 +03:00
worst.shuffle(&mut rand::rng());
self.pool = worst;
}
2026-07-29 14:24:40 +03:00
}
2026-08-15 20:16:48 +03:00
#[derive(Clone)]
2026-08-18 12:14:35 +03:00
pub struct DeckSettings {
2026-08-18 12:47:06 +03:00
pub id: Id,
2026-08-15 20:16:48 +03:00
pub name: String,
pub forward: String,
pub backward: String,
pub filter: String,
pub count: Option<usize>,
pub worst_words_list: Option<Vec<WordData>>,
pub open_mode: OrderMode,
}
2026-08-18 12:14:35 +03:00
impl DeckSettings {
pub(crate) fn with_name(name: String) -> DeckSettings {
DeckSettings {
2026-08-18 12:47:06 +03:00
id: 0.into(),
2026-08-15 20:16:48 +03:00
name,
forward: "".to_string(),
backward: "".to_string(),
filter: "true".to_string(),
count: None,
worst_words_list: None,
open_mode: OrderMode::Default,
}
}
2026-08-18 17:45:50 +03:00
pub fn check_filter(&self) -> bool {
let time = Instant::now();
2026-08-15 20:16:48 +03:00
let engine = Engine::new();
let ast = engine.compile(&self.filter);
2026-08-23 22:48:00 +03:00
info!("Compile time is {:?}", time.elapsed());
2026-08-15 20:16:48 +03:00
ast.is_ok()
}
2026-08-15 21:32:24 +03:00
pub fn get_word_list(&self, state: &AppState) -> Vec<usize> {
2026-08-15 20:16:48 +03:00
let time = Instant::now();
let mut list = vec![];
let engine = Engine::new();
let ast = engine.compile(&self.filter);
2026-08-20 00:42:06 +03:00
if let Ok(ast) = ast {
let groups = &state.word_groups;
list = state
.dictionary
2026-08-23 00:32:43 +03:00
.par_iter()
2026-08-20 00:42:06 +03:00
.enumerate()
.filter(|(_, word)| {
let mut more = rhai::Map::new();
for iced in &word.additional {
more.insert(iced.0.clone().into(), iced.1.clone().into());
}
let mut scope = Scope::new();
scope
2026-08-23 00:01:57 +03:00
.push_constant("id", word.id.0 as i64)
2026-08-20 00:42:06 +03:00
.push_constant("key", word.key.clone())
.push_constant("value", word.value.clone())
.push_constant("tags", word.tags.clone())
.push_constant("more", more)
.push_constant(
"group",
groups
.iter()
.find(|g| g.id == word.group_id)
.cloned()
.unwrap()
.name,
);
let result = engine.eval_ast_with_scope::<bool>(&mut scope, &ast);
result.is_ok() && result.unwrap()
})
.map(|(index, _)| index)
.collect();
2026-08-23 22:48:00 +03:00
info!("Collecting available words is {:?}", time.elapsed());
2026-08-20 00:42:06 +03:00
list
} else {
list
2026-08-15 20:16:48 +03:00
}
}
pub fn require_speech(&self) -> bool {
self.forward == "speech" || self.backward == "speech"
}
2026-08-18 17:31:19 +03:00
}