feat: richer profile stats, wrapup min-count filter, watch medium distribution
Some checks failed
CI / Check / Test (push) Has been cancelled
Some checks failed
CI / Check / Test (push) Has been cancelled
- filter wrapup top directors/actors with count < 2 - add watch_medium to wrapup report + adapters - profile trends: genre breakdown, rating histogram, watch medium dist - fix unused StarDisplay import in movie detail
This commit is contained in:
@@ -4,7 +4,7 @@ mod feed;
|
||||
mod movie;
|
||||
mod refresh_session;
|
||||
mod review;
|
||||
mod stats;
|
||||
pub mod stats;
|
||||
mod user;
|
||||
|
||||
pub mod collections;
|
||||
@@ -26,7 +26,7 @@ pub use federation::*;
|
||||
pub use feed::*;
|
||||
pub use movie::*;
|
||||
pub use review::*;
|
||||
pub use stats::*;
|
||||
pub use stats::{DirectorStat, MonthActivity, MonthlyRating, MovieStats, UserStats, UserTrends};
|
||||
pub use user::*;
|
||||
|
||||
pub use goal::{Goal, GoalWithProgress};
|
||||
|
||||
@@ -30,11 +30,26 @@ pub struct DirectorStat {
|
||||
pub count: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GenreStat {
|
||||
pub genre: String,
|
||||
pub count: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct WatchMediumStat {
|
||||
pub medium: String,
|
||||
pub count: i64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct UserTrends {
|
||||
pub monthly_ratings: Vec<MonthlyRating>,
|
||||
pub top_directors: Vec<DirectorStat>,
|
||||
pub max_director_count: i64,
|
||||
pub top_genres: Vec<GenreStat>,
|
||||
pub rating_distribution: [i64; 5],
|
||||
pub watch_medium_distribution: Vec<WatchMediumStat>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
||||
@@ -17,6 +17,7 @@ pub struct WrapUpMovieRow {
|
||||
pub runtime_minutes: Option<u32>,
|
||||
pub budget_usd: Option<i64>,
|
||||
pub original_language: Option<String>,
|
||||
pub watch_medium: Option<String>,
|
||||
pub genres: Vec<String>,
|
||||
pub keywords: Vec<String>,
|
||||
pub cast_names: Vec<(String, u32, i64)>,
|
||||
@@ -95,6 +96,12 @@ pub struct LangStat {
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct WatchMediumStat {
|
||||
pub medium: String,
|
||||
pub count: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct MonthCount {
|
||||
pub year_month: String,
|
||||
@@ -142,6 +149,7 @@ pub struct WrapUpReport {
|
||||
pub total_budget_watched: Option<i64>,
|
||||
pub avg_budget: Option<i64>,
|
||||
pub language_distribution: Vec<LangStat>,
|
||||
pub watch_medium_distribution: Vec<WatchMediumStat>,
|
||||
pub oldest_movie: Option<MovieRef>,
|
||||
pub newest_movie: Option<MovieRef>,
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ fn row(title: &str, rating: u8, ym: &str) -> WrapUpMovieRow {
|
||||
runtime_minutes: Some(100),
|
||||
budget_usd: None,
|
||||
original_language: Some("en".to_string()),
|
||||
watch_medium: None,
|
||||
genres: vec!["Action".to_string()],
|
||||
keywords: vec![],
|
||||
cast_names: vec![],
|
||||
|
||||
@@ -7,6 +7,8 @@ use crate::models::WrapUpMovieRow;
|
||||
use crate::models::wrapup::*;
|
||||
use crate::models::{ExternalPersonId, PersonId};
|
||||
|
||||
const MIN_PERSON_COUNT: u32 = 2;
|
||||
|
||||
pub fn build_report(
|
||||
scope: WrapUpScope,
|
||||
date_range: DateRange,
|
||||
@@ -53,6 +55,7 @@ pub fn build_report(
|
||||
|
||||
let (total_budget_watched, avg_budget) = compute_budget_stats(rows);
|
||||
let language_distribution = compute_language_stats(rows);
|
||||
let watch_medium_distribution = compute_watch_medium_stats(rows);
|
||||
|
||||
let (total_rewatches, most_rewatched_movie, avg_rating_change_on_rewatch) =
|
||||
compute_rewatch_stats(rows);
|
||||
@@ -91,6 +94,7 @@ pub fn build_report(
|
||||
total_budget_watched,
|
||||
avg_budget,
|
||||
language_distribution,
|
||||
watch_medium_distribution,
|
||||
oldest_movie,
|
||||
newest_movie,
|
||||
total_rewatches,
|
||||
@@ -226,6 +230,7 @@ fn compute_director_stats(rows: &[WrapUpMovieRow]) -> (Vec<PersonStat>, u32) {
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
stats.retain(|s| s.count >= MIN_PERSON_COUNT);
|
||||
stats.sort_by(|a, b| {
|
||||
b.count
|
||||
.cmp(&a.count)
|
||||
@@ -270,6 +275,7 @@ fn compute_actor_stats(rows: &[WrapUpMovieRow]) -> (Vec<PersonStat>, u32, Vec<St
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
stats.retain(|s| s.count >= MIN_PERSON_COUNT);
|
||||
stats.sort_by(|a, b| {
|
||||
b.count
|
||||
.cmp(&a.count)
|
||||
@@ -367,6 +373,21 @@ fn compute_language_stats(rows: &[WrapUpMovieRow]) -> Vec<LangStat> {
|
||||
stats
|
||||
}
|
||||
|
||||
fn compute_watch_medium_stats(rows: &[WrapUpMovieRow]) -> Vec<WatchMediumStat> {
|
||||
let mut counts: HashMap<String, u32> = HashMap::new();
|
||||
for r in rows {
|
||||
if let Some(ref medium) = r.watch_medium {
|
||||
*counts.entry(medium.clone()).or_default() += 1;
|
||||
}
|
||||
}
|
||||
let mut stats: Vec<WatchMediumStat> = counts
|
||||
.into_iter()
|
||||
.map(|(medium, count)| WatchMediumStat { medium, count })
|
||||
.collect();
|
||||
stats.sort_by_key(|s| std::cmp::Reverse(s.count));
|
||||
stats
|
||||
}
|
||||
|
||||
fn compute_rewatch_stats(rows: &[WrapUpMovieRow]) -> (u32, Option<MovieRef>, Option<f64>) {
|
||||
let mut movie_reviews: HashMap<Uuid, Vec<&WrapUpMovieRow>> = HashMap::new();
|
||||
for r in rows {
|
||||
|
||||
@@ -226,6 +226,9 @@ impl StatsRepository for FakeStatsRepository {
|
||||
monthly_ratings: vec![],
|
||||
top_directors: vec![],
|
||||
max_director_count: 0,
|
||||
top_genres: vec![],
|
||||
rating_distribution: [0; 5],
|
||||
watch_medium_distribution: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user