tuitbot_core/storage/analytics/
summary.rs1use super::super::accounts::DEFAULT_ACCOUNT_ID;
2use super::super::DbPool;
3use super::content_scores::{
4 get_avg_reply_engagement_for, get_avg_tweet_engagement_for, get_performance_counts_for,
5 get_top_topics_for, ContentScore,
6};
7use super::snapshots::get_follower_snapshots_for;
8use crate::error::StorageError;
9use chrono::{NaiveDate, Utc};
10
11#[derive(Debug, Clone, serde::Serialize)]
13pub struct FollowerSummary {
14 pub current: i64,
15 pub change_7d: i64,
16 pub change_30d: i64,
17}
18
19#[derive(Debug, Clone, serde::Serialize)]
21pub struct ActionsSummary {
22 pub replies: i64,
23 pub tweets: i64,
24 pub threads: i64,
25}
26
27#[derive(Debug, Clone, serde::Serialize)]
29pub struct EngagementSummary {
30 pub avg_reply_score: f64,
31 pub avg_tweet_score: f64,
32 pub total_replies_sent: i64,
33 pub total_tweets_posted: i64,
34}
35
36#[derive(Debug, Clone, serde::Serialize)]
38pub struct AnalyticsSummary {
39 pub followers: FollowerSummary,
40 pub actions_today: ActionsSummary,
41 pub engagement: EngagementSummary,
42 pub top_topics: Vec<ContentScore>,
43}
44
45pub async fn get_analytics_summary_for(
50 pool: &DbPool,
51 account_id: &str,
52) -> Result<AnalyticsSummary, StorageError> {
53 let snapshots = get_follower_snapshots_for(pool, account_id, 90).await?;
55 let current = snapshots.first().map_or(0, |s| s.follower_count);
56
57 let today = Utc::now().date_naive();
60 let follower_at_or_before = |days: i64| -> i64 {
61 snapshots
62 .iter()
63 .find(|s| {
64 NaiveDate::parse_from_str(&s.snapshot_date, "%Y-%m-%d")
65 .map(|d| (today - d).num_days() >= days)
66 .unwrap_or(false)
67 })
68 .map_or(current, |s| s.follower_count)
69 };
70
71 let change_7d = if snapshots.len() >= 2 {
72 current - follower_at_or_before(7)
73 } else {
74 0
75 };
76 let change_30d = if snapshots.len() >= 2 {
77 current - follower_at_or_before(30)
78 } else {
79 0
80 };
81
82 let today = Utc::now().format("%Y-%m-%dT00:00:00Z").to_string();
84 let counts = super::super::action_log::get_action_counts_since(pool, &today).await?;
85 let actions_today = ActionsSummary {
86 replies: *counts.get("reply").unwrap_or(&0),
87 tweets: *counts.get("tweet").unwrap_or(&0),
88 threads: *counts.get("thread").unwrap_or(&0),
89 };
90
91 let avg_reply_score = get_avg_reply_engagement_for(pool, account_id).await?;
93 let avg_tweet_score = get_avg_tweet_engagement_for(pool, account_id).await?;
94 let (total_replies_sent, total_tweets_posted) =
95 get_performance_counts_for(pool, account_id).await?;
96
97 let top_topics = get_top_topics_for(pool, account_id, 5).await?;
99
100 Ok(AnalyticsSummary {
101 followers: FollowerSummary {
102 current,
103 change_7d,
104 change_30d,
105 },
106 actions_today,
107 engagement: EngagementSummary {
108 avg_reply_score,
109 avg_tweet_score,
110 total_replies_sent,
111 total_tweets_posted,
112 },
113 top_topics,
114 })
115}
116
117pub async fn get_analytics_summary(pool: &DbPool) -> Result<AnalyticsSummary, StorageError> {
122 get_analytics_summary_for(pool, DEFAULT_ACCOUNT_ID).await
123}