crate_activity/
summary.rs

1crate::ix!();
2
3#[derive(Builder,Debug)]
4#[builder(setter(into))]
5pub struct CrateActivitySummary {
6    date_interval_1d:              NaiveDate,
7    date_interval_3d:              NaiveDate,
8    date_interval_full_start:      NaiveDate,
9    date_interval_full_end:        NaiveDate,
10    total_downloads:               i64,
11    avg_daily_downloads:           f64,
12    avg_daily_downloads_per_crate: f64,
13    median_daily_downloads:        i64,
14    crates_analyzed:               usize,
15    top_crates_1d:                 Vec<(String, i64)>,
16    top_crates_3d:                 Vec<(String, i64)>,
17    top_crates_7d:                 Vec<(String, i64)>,
18}
19
20impl fmt::Display for CrateActivitySummary {
21
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23
24        writeln!(f, "Crate Activity Summary:")?;
25        writeln!(f, "  Full Data Range:             {} to {}", self.date_interval_full_start, self.date_interval_full_end)?;
26        writeln!(f, "  Date Interval (Last 1 Day):  {}", self.date_interval_1d)?;
27        writeln!(f, "  Date Interval (Last 3 Days): {}", self.date_interval_3d)?;
28
29        writeln!(f, "  Total Downloads:                   {}", self.total_downloads)?;
30        writeln!(f, "  Average Daily Downloads:           {:.2}", self.avg_daily_downloads)?;
31        writeln!(f, "  Average Daily Downloads per Crate: {:.2}", self.avg_daily_downloads_per_crate)?;
32        writeln!(f, "  Median Daily Downloads:            {}", self.median_daily_downloads)?;
33        writeln!(f, "  Crates Analyzed:                   {}", self.crates_analyzed)?;
34
35        let mut total_1d_downloads = 0;
36        writeln!(f, "\nTop Crates (Last 1 Day):")?;
37        for (crate_name, downloads) in &self.top_crates_1d {
38            total_1d_downloads += downloads;
39            writeln!(f, "  {:<30} {:>10} downloads", crate_name, downloads)?;
40        }
41        writeln!(f, "  {:<30} total downloads (Last 1 Day)", total_1d_downloads)?;
42
43        let mut total_3d_downloads = 0;
44        writeln!(f, "\nTop Crates (Last 3 Days):")?;
45        for (crate_name, downloads) in &self.top_crates_3d {
46            total_3d_downloads += downloads;
47            writeln!(f, "  {:<30} {:>10} downloads", crate_name, downloads)?;
48        }
49        writeln!(f, "  {:<30} total downloads (Last 3 Days)", total_3d_downloads)?;
50
51        let mut total_7d_downloads = 0;
52        writeln!(f, "\nTop Crates (Last 7 Days):")?;
53        for (crate_name, downloads) in &self.top_crates_7d {
54            total_7d_downloads += downloads;
55            writeln!(f, "  {:<30} {:>10} downloads", crate_name, downloads)?;
56        }
57        writeln!(f, "  {:<30} total downloads (Last 7 Days)", total_7d_downloads)?;
58
59        Ok(())
60    }
61}
62
63impl CrateActivitySummary {
64
65    pub fn new(
66        summaries: &[CrateUsageSummary],
67        interval_downloads_1d: HashMap<String, i64>,
68        interval_downloads_3d: HashMap<String, i64>,
69        interval_downloads_7d: HashMap<String, i64>,
70        one_day_ago: NaiveDate,
71        three_days_ago: NaiveDate,
72        seven_days_ago: NaiveDate,
73    ) -> Self {
74
75        // Compute the full date range
76        let (full_start, full_end) = summaries
77            .iter()
78            .flat_map(|s| s.version_downloads())
79            .map(|d| d.date())
80            .minmax()
81            .into_option()
82            .unwrap_or((&one_day_ago, &one_day_ago));
83
84        // Overall stats
85        let total_downloads: i64 = summaries.iter().map(|s| s.total_downloads()).sum();
86        let avg_daily_downloads: f64 = summaries.iter().map(|s| s.average_daily_downloads()).sum::<f64>();
87        let avg_daily_downloads_per_crate = avg_daily_downloads / summaries.len() as f64;
88
89        // Median daily downloads
90        let mut daily_downloads: Vec<i64> = summaries.iter().map(|s| *s.total_downloads()).collect();
91        daily_downloads.sort();
92        let median_daily_downloads = if daily_downloads.is_empty() {
93            0
94        } else if daily_downloads.len() % 2 == 0 {
95            let mid = daily_downloads.len() / 2;
96            (daily_downloads[mid - 1] + daily_downloads[mid]) / 2
97        } else {
98            daily_downloads[daily_downloads.len() / 2]
99        };
100
101        // Top crates for the last 1 and 3 days
102        let mut top_crates_1d: Vec<_> = interval_downloads_1d.into_iter().collect();
103        let mut top_crates_3d: Vec<_> = interval_downloads_3d.into_iter().collect();
104        let mut top_crates_7d: Vec<_> = interval_downloads_7d.into_iter().collect();
105
106        top_crates_1d.sort_by_key(|&(_, downloads)| std::cmp::Reverse(downloads));
107        top_crates_3d.sort_by_key(|&(_, downloads)| std::cmp::Reverse(downloads));
108        top_crates_7d.sort_by_key(|&(_, downloads)| std::cmp::Reverse(downloads));
109
110        CrateActivitySummary {
111            date_interval_1d:         one_day_ago,
112            date_interval_3d:         three_days_ago,
113            date_interval_full_end:   *full_end,
114            date_interval_full_start: *full_start,
115            total_downloads,
116            avg_daily_downloads,
117            avg_daily_downloads_per_crate,
118            median_daily_downloads,
119            crates_analyzed: summaries.len(),
120            top_crates_1d,
121            top_crates_3d,
122            top_crates_7d,
123        }
124    }
125}