1use std::cmp::max;
6
7use crate::interest::{Interest, InterestVector};
8
9#[uniffi::export]
22pub fn score(interest_vector: InterestVector, content_categories: Vec<Interest>) -> f64 {
23 let n = content_categories
24 .iter()
25 .fold(0, |acc, &category| acc + interest_vector[category]);
26
27 (max(n, 1) as f64).log10().tanh()
30}
31
32#[cfg(test)]
33mod test {
34 use crate::interest::{Interest, InterestVector};
35
36 use super::*;
37
38 const EPSILON: f64 = 1e-10;
39 const SUBEPSILON: f64 = 1e-6;
40
41 #[test]
42 fn test_score_lower_bound() {
43 let s = score(InterestVector::default(), vec![Interest::Food]);
45 let delta = (s - 0_f64).abs();
46
47 assert!(delta < EPSILON);
48
49 let s = score(
51 InterestVector {
52 animals: 10,
53 ..InterestVector::default()
54 },
55 vec![Interest::Food],
56 );
57 let delta = (s - 0_f64).abs();
58
59 assert!(delta < EPSILON);
60 }
61
62 #[test]
63 fn test_score_upper_bound() {
64 let score = score(
65 InterestVector {
66 animals: 1_000_000_000,
67 ..InterestVector::default()
68 },
69 vec![Interest::Animals],
70 );
71 let delta = (score - 1.0_f64).abs();
72
73 assert!(delta < SUBEPSILON);
75 }
76
77 #[test]
78 fn test_score_monotonic() {
79 let l = score(
80 InterestVector {
81 animals: 1,
82 ..InterestVector::default()
83 },
84 vec![Interest::Animals],
85 );
86
87 let r = score(
88 InterestVector {
89 animals: 5,
90 ..InterestVector::default()
91 },
92 vec![Interest::Animals],
93 );
94
95 assert!(l < r);
96 }
97
98 #[test]
99 fn test_score_multi_categories() {
100 let l = score(
101 InterestVector {
102 animals: 100,
103 food: 100,
104 ..InterestVector::default()
105 },
106 vec![Interest::Animals, Interest::Food],
107 );
108
109 let r = score(
110 InterestVector {
111 animals: 200,
112 ..InterestVector::default()
113 },
114 vec![Interest::Animals],
115 );
116 let delta = (l - r).abs();
117
118 assert!(delta < EPSILON);
119 }
120}