day distribution logic

This commit is contained in:
aecsocket
2026-08-17 17:20:12 +09:00
parent 7c24822a1a
commit 9cb02f3abf
2 changed files with 88 additions and 6 deletions
@@ -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<DayEstimate>,
}
#[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::<DashMap<_, _>>())
+80
View File
@@ -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<PayoutVariance>,
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),
}
}