speki_backend/
filter.rs

1use std::time::Duration;
2
3use serde::{Deserialize, Serialize};
4
5use crate::cache::CardCache;
6use crate::categories::Category;
7use crate::{deserialize_duration, serialize_duration, Id};
8
9#[derive(Debug, Clone, Serialize, Deserialize, Default)]
10pub struct FilterUtil {
11    pub allowed_cards: Vec<Id>, // Overrides all other filters.
12    pub disallowed_cards: Vec<Id>,
13    pub allowed_categories: Vec<Category>,
14    pub excluded_categories: Vec<Category>,
15    pub contains: Option<String>,
16    pub tags: Vec<String>,
17    pub suspended: Option<bool>,
18    pub resolved: Option<bool>,
19    pub pending: Option<bool>,
20    pub finished: Option<bool>,
21    #[serde(
22        serialize_with = "serialize_duration",
23        deserialize_with = "deserialize_duration"
24    )]
25    pub max_strength: Option<Duration>,
26    #[serde(
27        serialize_with = "serialize_duration",
28        deserialize_with = "deserialize_duration"
29    )]
30    pub min_strength: Option<Duration>,
31    #[serde(
32        serialize_with = "serialize_duration",
33        deserialize_with = "deserialize_duration"
34    )]
35    pub max_stability: Option<Duration>,
36    #[serde(
37        serialize_with = "serialize_duration",
38        deserialize_with = "deserialize_duration"
39    )]
40    pub min_stability: Option<Duration>,
41    pub max_recall_rate: Option<f32>,
42    pub min_recall_rate: Option<f32>,
43    pub max_lapses: Option<u32>,
44    pub all_dependencies: Option<Box<Self>>,
45    pub any_dependencies: Option<Box<Self>>,
46    pub all_dependents: Option<Box<Self>>,
47    pub any_dependents: Option<Box<Self>>,
48}
49
50impl FilterUtil {
51    pub fn new_permissive() -> Self {
52        Self::default()
53    }
54
55    pub fn new_review() -> Self {
56        FilterUtil {
57            max_recall_rate: Some(0.9),
58            resolved: Some(true),
59            suspended: Some(false),
60            finished: Some(true),
61            ..Default::default()
62        }
63    }
64
65    pub fn evaluate_cards(&self, cards: Vec<Id>, cache: &mut CardCache) -> Vec<Id> {
66        cards
67            .into_iter()
68            .filter(|card| self.keep_card(*card, cache))
69            .collect()
70    }
71
72    pub fn keep_card(&self, card: Id, cache: &mut CardCache) -> bool {
73        let card = cache.get_ref(card);
74
75        if self.allowed_cards.contains(&card.id()) {
76            return true;
77        };
78
79        if self.disallowed_cards.contains(&card.id()) {
80            return false;
81        };
82
83        if !self.allowed_categories.is_empty()
84            && !self
85                .allowed_categories
86                .iter()
87                .any(|cat| card.category() == cat)
88        {
89            return false;
90        }
91
92        if self
93            .excluded_categories
94            .iter()
95            .any(|cat| card.category() == cat)
96        {
97            return false;
98        }
99
100        if let Some(suspended) = self.suspended {
101            if card.is_suspended() != suspended {
102                return false;
103            }
104        }
105
106        if let Some(resolved) = self.resolved {
107            if card.is_resolved(cache) != resolved {
108                return false;
109            }
110        }
111
112        if let Some(finished) = self.finished {
113            if card.is_finished() != finished {
114                return false;
115            }
116        }
117        if let Some(pending) = self.pending {
118            if card.is_pending() != pending {
119                return false;
120            }
121        }
122
123        if let Some(max_strength) = self.max_strength {
124            if !card
125                .strength()
126                .map(|card_strength| card_strength < max_strength)
127                .unwrap_or(false)
128            {
129                return false;
130            }
131        }
132
133        if let Some(min_strength) = self.min_strength {
134            if !card
135                .strength()
136                .map(|card_strength| card_strength > min_strength)
137                .unwrap_or(false)
138            {
139                return false;
140            }
141        }
142
143        if let Some(max_stability) = self.max_stability {
144            if !card
145                .stability()
146                .map(|card_stability| card_stability < max_stability)
147                .unwrap_or(false)
148            {
149                return false;
150            }
151        }
152
153        if let Some(min_stability) = self.min_stability {
154            if !card
155                .stability()
156                .map(|card_stability| card_stability > min_stability)
157                .unwrap_or(false)
158            {
159                return false;
160            }
161        }
162
163        if let Some(max_recall_rate) = self.max_recall_rate {
164            if card.recall_rate().unwrap_or_default() > max_recall_rate {
165                return false;
166            }
167        }
168
169        if let Some(min_recall_rate) = self.min_recall_rate {
170            if !card
171                .recall_rate()
172                .map(|card_recall_rate| card_recall_rate > min_recall_rate)
173                .unwrap_or(false)
174            {
175                return false;
176            }
177        }
178
179        if let Some(search) = self.contains.as_deref() {
180            if !(card
181                .front_text()
182                .to_lowercase()
183                .contains(&search.to_lowercase())
184                || card
185                    .back_text()
186                    .to_lowercase()
187                    .contains(&search.to_lowercase()))
188            {
189                return false;
190            }
191        }
192
193        if !self.tags.is_empty() && !self.tags.iter().any(|tag| card.contains_tag(tag)) {
194            return false;
195        }
196
197        // no cyclical dependencies need to hold true to avoid stack overflow.
198        if let Some(all_dependencies) = &self.all_dependencies {
199            let dependencies = cache.dependencies(card.id());
200            if !dependencies.is_empty()
201                && !dependencies
202                    .iter()
203                    .all(|card| all_dependencies.keep_card(*card, cache))
204            {
205                return false;
206            }
207        }
208
209        if let Some(all_dependents) = &self.any_dependents {
210            let dependents = cache.dependents(card.id());
211            if !dependents.is_empty()
212                && !dependents
213                    .iter()
214                    .all(|card| all_dependents.keep_card(*card, cache))
215            {
216                return false;
217            }
218        }
219
220        if let Some(any_dependents) = &self.any_dependents {
221            let dependents = cache.dependents(card.id());
222            if !dependents
223                .iter()
224                .any(|card| any_dependents.keep_card(*card, cache))
225            {
226                return false;
227            }
228        }
229
230        if let Some(any_depencies) = &self.any_dependencies {
231            let dependencies = cache.dependencies(card.id());
232            if dependencies
233                .iter()
234                .any(|card| any_depencies.keep_card(*card, cache))
235            {
236                return false;
237            }
238        }
239
240        true
241    }
242}