diff --git a/apps/labrinth/src/queue/payout_run/estimate.rs b/apps/labrinth/src/queue/payout_run/estimate.rs index 6139037102..ca9a2e18e2 100644 --- a/apps/labrinth/src/queue/payout_run/estimate.rs +++ b/apps/labrinth/src/queue/payout_run/estimate.rs @@ -2,10 +2,11 @@ use std::collections::HashMap; -use chrono::{Datelike, Months}; +use chrono::{Datelike, Months, NaiveDate}; use dashmap::DashMap; use eyre::{Result, eyre}; use rust_decimal::Decimal; +use serde::{Deserialize, Serialize}; use xredis::RedisPool; use crate::{ @@ -15,19 +16,20 @@ use crate::{ const REDIS_KEY: &str = "aditude_month_estimate_v1"; -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct PeriodEstimate { pub period: YearMonth, pub days: Vec, } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct DayEstimate { - pub day: u32, + pub date: NaiveDate, pub raw_estimated_revenue_usd: Decimal, pub impressions: u128, } +/// Get per-month and per-day estimated ad provider info. pub async fn estimate( aditude: &aditude::Client, redis: &RedisPool, @@ -93,7 +95,7 @@ async fn fetch_estimates( let day = date.day(); days.push(DayEstimate { - day, + date, raw_estimated_revenue_usd: row .revenue .wrap_err_with(|| eyre!("no revenue data for day {day}"))?, @@ -107,7 +109,7 @@ async fn fetch_estimates( // we have no clue if the Aditude row return order is stable, // so for safety sort the days here for period in map.values_mut() { - period.days.sort_unstable_by_key(|day| day.day); + period.days.sort_unstable_by_key(|day| day.date); } Ok(map.into_iter().collect::>()) diff --git a/apps/labrinth/src/queue/payout_run/mod.rs b/apps/labrinth/src/queue/payout_run/mod.rs index d0c37c5c2b..4a4fafa2b2 100644 --- a/apps/labrinth/src/queue/payout_run/mod.rs +++ b/apps/labrinth/src/queue/payout_run/mod.rs @@ -62,4 +62,84 @@ //! epoch) //! - the decimal fraction of variance to apply +use chrono::NaiveDate; +use rust_decimal::{Decimal, dec}; +use serde::{Deserialize, Serialize}; + mod estimate; + +/// Fraction defining much of the net revenue goes to the platform. +const PLATFORM_REVENUE_SPLIT: Decimal = dec!(0.25); + +/// How input revenue is distributed for a specific day. +/// +/// This may refer to either estimated or actual revenue. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DayDistribution { + pub raw_revenue_usd: Decimal, + pub fees_usd: Decimal, + pub variance_usd: Decimal, + pub sum_adjustments_usd: Decimal, + pub net_revenue_usd: Decimal, + pub platform_net_revenue_usd: Decimal, + pub creator_net_revenue_usd: Decimal, +} + +#[derive(Debug)] +pub struct PayoutVariance { + pub starts_at: NaiveDate, + pub frac: Decimal, +} + +#[derive(Debug)] +pub struct PayoutVariances { + pub fracs: Vec, + pub default_frac: Decimal, +} + +impl PayoutVariances { + pub const ZERO: Self = Self { + fracs: Vec::new(), + default_frac: Decimal::ZERO, + }; +} + +/// Compute the [`DayDistribution`] for an input revenue and impressions amount. +/// +/// This logic may be used on both estimated and actual revenue. For actual +/// revenue, [`PayoutVariances::ZERO`] should be used, since variance only +/// applies to estimated days. +pub fn distribution_for_day( + date: NaiveDate, + raw_revenue_usd: Decimal, + impressions: u128, + sum_adjustments_usd: Decimal, + variances: &PayoutVariances, +) -> DayDistribution { + let fees_usd = { + let clean_io_cpm = Decimal::from(8) / Decimal::from(1000); + clean_io_cpm * Decimal::from(impressions) / Decimal::from(1000) + }; + let variance_frac = variances + .fracs + .iter() + .find(|v| v.starts_at >= date) + .map(|v| v.frac) + .unwrap_or(variances.default_frac); + let variance_usd = raw_revenue_usd * variance_frac; + + let net_estimated_revenue_usd = + raw_revenue_usd - fees_usd - variance_usd + sum_adjustments_usd; + + DayDistribution { + raw_revenue_usd, + fees_usd, + variance_usd, + sum_adjustments_usd, + net_revenue_usd: net_estimated_revenue_usd, + platform_net_revenue_usd: net_estimated_revenue_usd + * PLATFORM_REVENUE_SPLIT, + creator_net_revenue_usd: net_estimated_revenue_usd + * (dec!(1) - PLATFORM_REVENUE_SPLIT), + } +}