caveman/gatherer/src/trends.rs

95 lines
2.4 KiB
Rust

use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::rc::Rc;
use redis::{
aio::ConnectionManager,
RedisError,
};
use crate::tag::Tag;
#[derive(Debug, Clone, PartialEq, PartialOrd)]
struct ScoreKey {
score: f64,
tag: Rc<String>,
}
impl Eq for ScoreKey {
}
impl Ord for ScoreKey {
fn cmp(&self, other: &Self) -> Ordering {
if self.score == other.score {
self.tag.as_ref().cmp(other.tag.as_ref())
} else if self.score < other.score {
Ordering::Less
} else {
Ordering::Greater
}
}
}
#[derive(Debug)]
pub struct TrendAnalyzer {
/// in hours
period: u64,
/// *now* in hours
until: u64,
size: usize,
/// key contains name to avoid collision by just score
results: BTreeMap<ScoreKey, Rc<Tag>>,
score_threshold: Option<f64>,
}
impl TrendAnalyzer {
pub async fn run(
redis_man: &mut ConnectionManager,
size: usize,
periods: &[u64],
language: Option<String>,
) -> Result<Vec<Self>, RedisError> {
let until = (chrono::offset::Utc::now().naive_utc().timestamp() / 3600) as u64;
let mut analyzers: Vec<TrendAnalyzer> = periods.iter()
.cloned()
.map(|period| TrendAnalyzer {
period,
until,
size,
results: BTreeMap::new(),
score_threshold: None,
}).collect();
Tag::for_each(redis_man, language, |tag| {
let tag = Rc::new(tag);
for analyzer in &mut analyzers {
analyzer.process_tag(&tag);
}
}).await?;
Ok(analyzers)
}
pub fn process_tag(&mut self, tag: &Rc<Tag>) {
let score = tag.score(self.period, self.until);
if score <= 1.0 {
// discard downwards trends
return;
}
if self.score_threshold.map_or(false, |score_threshold| score < score_threshold) {
// score is below self.result[..self.size].score
return;
}
self.results.insert(ScoreKey { score, tag: tag.name.clone() }, tag.clone());
let mut least = self.results.keys().cloned().next().unwrap();
if self.results.len() > self.size {
self.results.remove(&least);
least = self.results.keys().cloned().next().unwrap().clone();
}
self.score_threshold = Some(least.score);
}
}