mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-29 11:48:12 -07:00
global: snapshot
This commit is contained in:
@@ -16,7 +16,7 @@ pub struct AddressGroups<T> {
|
||||
impl<T> AddressGroups<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
Self {
|
||||
ge_amount: ByGreatEqualAmount::new(&mut create),
|
||||
@@ -25,6 +25,17 @@ impl<T> AddressGroups<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(create: &F) -> Result<Self, E>
|
||||
where
|
||||
F: Fn(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
Ok(Self {
|
||||
ge_amount: ByGreatEqualAmount::try_new(create)?,
|
||||
amount_range: ByAmountRange::try_new(create)?,
|
||||
lt_amount: ByLowerThanAmount::try_new(create)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
self.ge_amount
|
||||
.iter()
|
||||
|
||||
@@ -33,40 +33,4 @@ impl AmountFilter {
|
||||
AmountFilter::Range(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_name_suffix(&self) -> String {
|
||||
match self {
|
||||
AmountFilter::LowerThan(s) if *s == Sats::_1 => "with_0sats".to_string(),
|
||||
AmountFilter::LowerThan(s) => format!("under_{}", format_sats(*s)),
|
||||
AmountFilter::GreaterOrEqual(s) => format!("above_{}", format_sats(*s)),
|
||||
AmountFilter::Range(r) => {
|
||||
format!(
|
||||
"above_{}_under_{}",
|
||||
format_sats(r.start),
|
||||
format_sats(r.end)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_sats(sats: Sats) -> String {
|
||||
match sats {
|
||||
s if s == Sats::ZERO => "0sats".to_string(),
|
||||
s if s == Sats::_1 => "1sat".to_string(),
|
||||
s if s == Sats::_10 => "10sats".to_string(),
|
||||
s if s == Sats::_100 => "100sats".to_string(),
|
||||
s if s == Sats::_1K => "1k_sats".to_string(),
|
||||
s if s == Sats::_10K => "10k_sats".to_string(),
|
||||
s if s == Sats::_100K => "100k_sats".to_string(),
|
||||
s if s == Sats::_1M => "1m_sats".to_string(),
|
||||
s if s == Sats::_10M => "10m_sats".to_string(),
|
||||
s if s == Sats::_1BTC => "1btc".to_string(),
|
||||
s if s == Sats::_10BTC => "10btc".to_string(),
|
||||
s if s == Sats::_100BTC => "100btc".to_string(),
|
||||
s if s == Sats::_1K_BTC => "1k_btc".to_string(),
|
||||
s if s == Sats::_10K_BTC => "10k_btc".to_string(),
|
||||
s if s == Sats::_100K_BTC => "100k_btc".to_string(),
|
||||
_ => format!("{}sats", u64::from(sats)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use brk_traversable::Traversable;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{Filter, TimeFilter};
|
||||
use super::{CohortName, Filter, TimeFilter};
|
||||
|
||||
// Age boundary constants in days
|
||||
pub const DAYS_1D: usize = 1;
|
||||
@@ -31,7 +34,85 @@ pub const AGE_BOUNDARIES: [usize; 19] = [
|
||||
DAYS_3Y, DAYS_4Y, DAYS_5Y, DAYS_6Y, DAYS_7Y, DAYS_8Y, DAYS_10Y, DAYS_12Y, DAYS_15Y,
|
||||
];
|
||||
|
||||
#[derive(Default, Clone, Traversable)]
|
||||
/// Age range bounds (end = usize::MAX means unbounded)
|
||||
pub const AGE_RANGE_BOUNDS: ByAgeRange<Range<usize>> = ByAgeRange {
|
||||
up_to_1d: 0..DAYS_1D,
|
||||
_1d_to_1w: DAYS_1D..DAYS_1W,
|
||||
_1w_to_1m: DAYS_1W..DAYS_1M,
|
||||
_1m_to_2m: DAYS_1M..DAYS_2M,
|
||||
_2m_to_3m: DAYS_2M..DAYS_3M,
|
||||
_3m_to_4m: DAYS_3M..DAYS_4M,
|
||||
_4m_to_5m: DAYS_4M..DAYS_5M,
|
||||
_5m_to_6m: DAYS_5M..DAYS_6M,
|
||||
_6m_to_1y: DAYS_6M..DAYS_1Y,
|
||||
_1y_to_2y: DAYS_1Y..DAYS_2Y,
|
||||
_2y_to_3y: DAYS_2Y..DAYS_3Y,
|
||||
_3y_to_4y: DAYS_3Y..DAYS_4Y,
|
||||
_4y_to_5y: DAYS_4Y..DAYS_5Y,
|
||||
_5y_to_6y: DAYS_5Y..DAYS_6Y,
|
||||
_6y_to_7y: DAYS_6Y..DAYS_7Y,
|
||||
_7y_to_8y: DAYS_7Y..DAYS_8Y,
|
||||
_8y_to_10y: DAYS_8Y..DAYS_10Y,
|
||||
_10y_to_12y: DAYS_10Y..DAYS_12Y,
|
||||
_12y_to_15y: DAYS_12Y..DAYS_15Y,
|
||||
from_15y: DAYS_15Y..usize::MAX,
|
||||
};
|
||||
|
||||
/// Age range filters
|
||||
pub const AGE_RANGE_FILTERS: ByAgeRange<Filter> = ByAgeRange {
|
||||
up_to_1d: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS.up_to_1d)),
|
||||
_1d_to_1w: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._1d_to_1w)),
|
||||
_1w_to_1m: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._1w_to_1m)),
|
||||
_1m_to_2m: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._1m_to_2m)),
|
||||
_2m_to_3m: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._2m_to_3m)),
|
||||
_3m_to_4m: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._3m_to_4m)),
|
||||
_4m_to_5m: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._4m_to_5m)),
|
||||
_5m_to_6m: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._5m_to_6m)),
|
||||
_6m_to_1y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._6m_to_1y)),
|
||||
_1y_to_2y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._1y_to_2y)),
|
||||
_2y_to_3y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._2y_to_3y)),
|
||||
_3y_to_4y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._3y_to_4y)),
|
||||
_4y_to_5y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._4y_to_5y)),
|
||||
_5y_to_6y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._5y_to_6y)),
|
||||
_6y_to_7y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._6y_to_7y)),
|
||||
_7y_to_8y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._7y_to_8y)),
|
||||
_8y_to_10y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._8y_to_10y)),
|
||||
_10y_to_12y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._10y_to_12y)),
|
||||
_12y_to_15y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS._12y_to_15y)),
|
||||
from_15y: Filter::Time(TimeFilter::Range(AGE_RANGE_BOUNDS.from_15y)),
|
||||
};
|
||||
|
||||
/// Age range names
|
||||
pub const AGE_RANGE_NAMES: ByAgeRange<CohortName> = ByAgeRange {
|
||||
up_to_1d: CohortName::new("up_to_1d_old", "<1d", "Up to 1 Day Old"),
|
||||
_1d_to_1w: CohortName::new("at_least_1d_up_to_1w_old", "1d-1w", "1 Day to 1 Week Old"),
|
||||
_1w_to_1m: CohortName::new("at_least_1w_up_to_1m_old", "1w-1m", "1 Week to 1 Month Old"),
|
||||
_1m_to_2m: CohortName::new("at_least_1m_up_to_2m_old", "1m-2m", "1 to 2 Months Old"),
|
||||
_2m_to_3m: CohortName::new("at_least_2m_up_to_3m_old", "2m-3m", "2 to 3 Months Old"),
|
||||
_3m_to_4m: CohortName::new("at_least_3m_up_to_4m_old", "3m-4m", "3 to 4 Months Old"),
|
||||
_4m_to_5m: CohortName::new("at_least_4m_up_to_5m_old", "4m-5m", "4 to 5 Months Old"),
|
||||
_5m_to_6m: CohortName::new("at_least_5m_up_to_6m_old", "5m-6m", "5 to 6 Months Old"),
|
||||
_6m_to_1y: CohortName::new("at_least_6m_up_to_1y_old", "6m-1y", "6 Months to 1 Year Old"),
|
||||
_1y_to_2y: CohortName::new("at_least_1y_up_to_2y_old", "1y-2y", "1 to 2 Years Old"),
|
||||
_2y_to_3y: CohortName::new("at_least_2y_up_to_3y_old", "2y-3y", "2 to 3 Years Old"),
|
||||
_3y_to_4y: CohortName::new("at_least_3y_up_to_4y_old", "3y-4y", "3 to 4 Years Old"),
|
||||
_4y_to_5y: CohortName::new("at_least_4y_up_to_5y_old", "4y-5y", "4 to 5 Years Old"),
|
||||
_5y_to_6y: CohortName::new("at_least_5y_up_to_6y_old", "5y-6y", "5 to 6 Years Old"),
|
||||
_6y_to_7y: CohortName::new("at_least_6y_up_to_7y_old", "6y-7y", "6 to 7 Years Old"),
|
||||
_7y_to_8y: CohortName::new("at_least_7y_up_to_8y_old", "7y-8y", "7 to 8 Years Old"),
|
||||
_8y_to_10y: CohortName::new("at_least_8y_up_to_10y_old", "8y-10y", "8 to 10 Years Old"),
|
||||
_10y_to_12y: CohortName::new("at_least_10y_up_to_12y_old", "10y-12y", "10 to 12 Years Old"),
|
||||
_12y_to_15y: CohortName::new("at_least_12y_up_to_15y_old", "12y-15y", "12 to 15 Years Old"),
|
||||
from_15y: CohortName::new("at_least_15y_old", "15y+", "15+ Years Old"),
|
||||
};
|
||||
|
||||
impl ByAgeRange<CohortName> {
|
||||
pub const fn names() -> &'static Self {
|
||||
&AGE_RANGE_NAMES
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Traversable, Serialize)]
|
||||
pub struct ByAgeRange<T> {
|
||||
pub up_to_1d: T,
|
||||
pub _1d_to_1w: T,
|
||||
@@ -85,32 +166,64 @@ impl<T> ByAgeRange<T> {
|
||||
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
let f = AGE_RANGE_FILTERS;
|
||||
let n = AGE_RANGE_NAMES;
|
||||
Self {
|
||||
up_to_1d: create(Filter::Time(TimeFilter::Range(0..DAYS_1D))),
|
||||
_1d_to_1w: create(Filter::Time(TimeFilter::Range(DAYS_1D..DAYS_1W))),
|
||||
_1w_to_1m: create(Filter::Time(TimeFilter::Range(DAYS_1W..DAYS_1M))),
|
||||
_1m_to_2m: create(Filter::Time(TimeFilter::Range(DAYS_1M..DAYS_2M))),
|
||||
_2m_to_3m: create(Filter::Time(TimeFilter::Range(DAYS_2M..DAYS_3M))),
|
||||
_3m_to_4m: create(Filter::Time(TimeFilter::Range(DAYS_3M..DAYS_4M))),
|
||||
_4m_to_5m: create(Filter::Time(TimeFilter::Range(DAYS_4M..DAYS_5M))),
|
||||
_5m_to_6m: create(Filter::Time(TimeFilter::Range(DAYS_5M..DAYS_6M))),
|
||||
_6m_to_1y: create(Filter::Time(TimeFilter::Range(DAYS_6M..DAYS_1Y))),
|
||||
_1y_to_2y: create(Filter::Time(TimeFilter::Range(DAYS_1Y..DAYS_2Y))),
|
||||
_2y_to_3y: create(Filter::Time(TimeFilter::Range(DAYS_2Y..DAYS_3Y))),
|
||||
_3y_to_4y: create(Filter::Time(TimeFilter::Range(DAYS_3Y..DAYS_4Y))),
|
||||
_4y_to_5y: create(Filter::Time(TimeFilter::Range(DAYS_4Y..DAYS_5Y))),
|
||||
_5y_to_6y: create(Filter::Time(TimeFilter::Range(DAYS_5Y..DAYS_6Y))),
|
||||
_6y_to_7y: create(Filter::Time(TimeFilter::Range(DAYS_6Y..DAYS_7Y))),
|
||||
_7y_to_8y: create(Filter::Time(TimeFilter::Range(DAYS_7Y..DAYS_8Y))),
|
||||
_8y_to_10y: create(Filter::Time(TimeFilter::Range(DAYS_8Y..DAYS_10Y))),
|
||||
_10y_to_12y: create(Filter::Time(TimeFilter::Range(DAYS_10Y..DAYS_12Y))),
|
||||
_12y_to_15y: create(Filter::Time(TimeFilter::Range(DAYS_12Y..DAYS_15Y))),
|
||||
from_15y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_15Y))),
|
||||
up_to_1d: create(f.up_to_1d.clone(), n.up_to_1d.id),
|
||||
_1d_to_1w: create(f._1d_to_1w.clone(), n._1d_to_1w.id),
|
||||
_1w_to_1m: create(f._1w_to_1m.clone(), n._1w_to_1m.id),
|
||||
_1m_to_2m: create(f._1m_to_2m.clone(), n._1m_to_2m.id),
|
||||
_2m_to_3m: create(f._2m_to_3m.clone(), n._2m_to_3m.id),
|
||||
_3m_to_4m: create(f._3m_to_4m.clone(), n._3m_to_4m.id),
|
||||
_4m_to_5m: create(f._4m_to_5m.clone(), n._4m_to_5m.id),
|
||||
_5m_to_6m: create(f._5m_to_6m.clone(), n._5m_to_6m.id),
|
||||
_6m_to_1y: create(f._6m_to_1y.clone(), n._6m_to_1y.id),
|
||||
_1y_to_2y: create(f._1y_to_2y.clone(), n._1y_to_2y.id),
|
||||
_2y_to_3y: create(f._2y_to_3y.clone(), n._2y_to_3y.id),
|
||||
_3y_to_4y: create(f._3y_to_4y.clone(), n._3y_to_4y.id),
|
||||
_4y_to_5y: create(f._4y_to_5y.clone(), n._4y_to_5y.id),
|
||||
_5y_to_6y: create(f._5y_to_6y.clone(), n._5y_to_6y.id),
|
||||
_6y_to_7y: create(f._6y_to_7y.clone(), n._6y_to_7y.id),
|
||||
_7y_to_8y: create(f._7y_to_8y.clone(), n._7y_to_8y.id),
|
||||
_8y_to_10y: create(f._8y_to_10y.clone(), n._8y_to_10y.id),
|
||||
_10y_to_12y: create(f._10y_to_12y.clone(), n._10y_to_12y.id),
|
||||
_12y_to_15y: create(f._12y_to_15y.clone(), n._12y_to_15y.id),
|
||||
from_15y: create(f.from_15y.clone(), n.from_15y.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
let f = AGE_RANGE_FILTERS;
|
||||
let n = AGE_RANGE_NAMES;
|
||||
Ok(Self {
|
||||
up_to_1d: create(f.up_to_1d.clone(), n.up_to_1d.id)?,
|
||||
_1d_to_1w: create(f._1d_to_1w.clone(), n._1d_to_1w.id)?,
|
||||
_1w_to_1m: create(f._1w_to_1m.clone(), n._1w_to_1m.id)?,
|
||||
_1m_to_2m: create(f._1m_to_2m.clone(), n._1m_to_2m.id)?,
|
||||
_2m_to_3m: create(f._2m_to_3m.clone(), n._2m_to_3m.id)?,
|
||||
_3m_to_4m: create(f._3m_to_4m.clone(), n._3m_to_4m.id)?,
|
||||
_4m_to_5m: create(f._4m_to_5m.clone(), n._4m_to_5m.id)?,
|
||||
_5m_to_6m: create(f._5m_to_6m.clone(), n._5m_to_6m.id)?,
|
||||
_6m_to_1y: create(f._6m_to_1y.clone(), n._6m_to_1y.id)?,
|
||||
_1y_to_2y: create(f._1y_to_2y.clone(), n._1y_to_2y.id)?,
|
||||
_2y_to_3y: create(f._2y_to_3y.clone(), n._2y_to_3y.id)?,
|
||||
_3y_to_4y: create(f._3y_to_4y.clone(), n._3y_to_4y.id)?,
|
||||
_4y_to_5y: create(f._4y_to_5y.clone(), n._4y_to_5y.id)?,
|
||||
_5y_to_6y: create(f._5y_to_6y.clone(), n._5y_to_6y.id)?,
|
||||
_6y_to_7y: create(f._6y_to_7y.clone(), n._6y_to_7y.id)?,
|
||||
_7y_to_8y: create(f._7y_to_8y.clone(), n._7y_to_8y.id)?,
|
||||
_8y_to_10y: create(f._8y_to_10y.clone(), n._8y_to_10y.id)?,
|
||||
_10y_to_12y: create(f._10y_to_12y.clone(), n._10y_to_12y.id)?,
|
||||
_12y_to_15y: create(f._12y_to_15y.clone(), n._12y_to_15y.id)?,
|
||||
from_15y: create(f.from_15y.clone(), n.from_15y.id)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
[
|
||||
&self.up_to_1d,
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
use std::ops::{Add, AddAssign};
|
||||
use std::ops::{Add, AddAssign, Range};
|
||||
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::Sats;
|
||||
use rayon::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{AmountFilter, Filter};
|
||||
use super::{AmountFilter, CohortName, Filter};
|
||||
|
||||
/// Bucket index for amount ranges. Use for cheap comparisons and direct lookups.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -57,7 +58,108 @@ pub fn amounts_in_different_buckets(a: Sats, b: Sats) -> bool {
|
||||
AmountBucket::from(a) != AmountBucket::from(b)
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Traversable)]
|
||||
/// Amount range bounds
|
||||
pub const AMOUNT_RANGE_BOUNDS: ByAmountRange<Range<Sats>> = ByAmountRange {
|
||||
_0sats: Sats::ZERO..Sats::_1,
|
||||
_1sat_to_10sats: Sats::_1..Sats::_10,
|
||||
_10sats_to_100sats: Sats::_10..Sats::_100,
|
||||
_100sats_to_1k_sats: Sats::_100..Sats::_1K,
|
||||
_1k_sats_to_10k_sats: Sats::_1K..Sats::_10K,
|
||||
_10k_sats_to_100k_sats: Sats::_10K..Sats::_100K,
|
||||
_100k_sats_to_1m_sats: Sats::_100K..Sats::_1M,
|
||||
_1m_sats_to_10m_sats: Sats::_1M..Sats::_10M,
|
||||
_10m_sats_to_1btc: Sats::_10M..Sats::_1BTC,
|
||||
_1btc_to_10btc: Sats::_1BTC..Sats::_10BTC,
|
||||
_10btc_to_100btc: Sats::_10BTC..Sats::_100BTC,
|
||||
_100btc_to_1k_btc: Sats::_100BTC..Sats::_1K_BTC,
|
||||
_1k_btc_to_10k_btc: Sats::_1K_BTC..Sats::_10K_BTC,
|
||||
_10k_btc_to_100k_btc: Sats::_10K_BTC..Sats::_100K_BTC,
|
||||
_100k_btc_or_more: Sats::_100K_BTC..Sats::MAX,
|
||||
};
|
||||
|
||||
/// Amount range names
|
||||
pub const AMOUNT_RANGE_NAMES: ByAmountRange<CohortName> = ByAmountRange {
|
||||
_0sats: CohortName::new("with_0sats", "0 sats", "0 Sats"),
|
||||
_1sat_to_10sats: CohortName::new("above_1sat_under_10sats", "1-10 sats", "1 to 10 Sats"),
|
||||
_10sats_to_100sats: CohortName::new(
|
||||
"above_10sats_under_100sats",
|
||||
"10-100 sats",
|
||||
"10 to 100 Sats",
|
||||
),
|
||||
_100sats_to_1k_sats: CohortName::new(
|
||||
"above_100sats_under_1k_sats",
|
||||
"100-1k sats",
|
||||
"100 to 1K Sats",
|
||||
),
|
||||
_1k_sats_to_10k_sats: CohortName::new(
|
||||
"above_1k_sats_under_10k_sats",
|
||||
"1k-10k sats",
|
||||
"1K to 10K Sats",
|
||||
),
|
||||
_10k_sats_to_100k_sats: CohortName::new(
|
||||
"above_10k_sats_under_100k_sats",
|
||||
"10k-100k sats",
|
||||
"10K to 100K Sats",
|
||||
),
|
||||
_100k_sats_to_1m_sats: CohortName::new(
|
||||
"above_100k_sats_under_1m_sats",
|
||||
"100k-1M sats",
|
||||
"100K to 1M Sats",
|
||||
),
|
||||
_1m_sats_to_10m_sats: CohortName::new(
|
||||
"above_1m_sats_under_10m_sats",
|
||||
"1M-10M sats",
|
||||
"1M to 10M Sats",
|
||||
),
|
||||
_10m_sats_to_1btc: CohortName::new("above_10m_sats_under_1btc", "0.1-1 BTC", "0.1 to 1 BTC"),
|
||||
_1btc_to_10btc: CohortName::new("above_1btc_under_10btc", "1-10 BTC", "1 to 10 BTC"),
|
||||
_10btc_to_100btc: CohortName::new("above_10btc_under_100btc", "10-100 BTC", "10 to 100 BTC"),
|
||||
_100btc_to_1k_btc: CohortName::new("above_100btc_under_1k_btc", "100-1k BTC", "100 to 1K BTC"),
|
||||
_1k_btc_to_10k_btc: CohortName::new(
|
||||
"above_1k_btc_under_10k_btc",
|
||||
"1k-10k BTC",
|
||||
"1K to 10K BTC",
|
||||
),
|
||||
_10k_btc_to_100k_btc: CohortName::new(
|
||||
"above_10k_btc_under_100k_btc",
|
||||
"10k-100k BTC",
|
||||
"10K to 100K BTC",
|
||||
),
|
||||
_100k_btc_or_more: CohortName::new("above_100k_btc", "100k+ BTC", "100K+ BTC"),
|
||||
};
|
||||
|
||||
/// Amount range filters
|
||||
pub const AMOUNT_RANGE_FILTERS: ByAmountRange<Filter> = ByAmountRange {
|
||||
_0sats: Filter::Amount(AmountFilter::Range(AMOUNT_RANGE_BOUNDS._0sats)),
|
||||
_1sat_to_10sats: Filter::Amount(AmountFilter::Range(AMOUNT_RANGE_BOUNDS._1sat_to_10sats)),
|
||||
_10sats_to_100sats: Filter::Amount(AmountFilter::Range(AMOUNT_RANGE_BOUNDS._10sats_to_100sats)),
|
||||
_100sats_to_1k_sats: Filter::Amount(AmountFilter::Range(
|
||||
AMOUNT_RANGE_BOUNDS._100sats_to_1k_sats,
|
||||
)),
|
||||
_1k_sats_to_10k_sats: Filter::Amount(AmountFilter::Range(
|
||||
AMOUNT_RANGE_BOUNDS._1k_sats_to_10k_sats,
|
||||
)),
|
||||
_10k_sats_to_100k_sats: Filter::Amount(AmountFilter::Range(
|
||||
AMOUNT_RANGE_BOUNDS._10k_sats_to_100k_sats,
|
||||
)),
|
||||
_100k_sats_to_1m_sats: Filter::Amount(AmountFilter::Range(
|
||||
AMOUNT_RANGE_BOUNDS._100k_sats_to_1m_sats,
|
||||
)),
|
||||
_1m_sats_to_10m_sats: Filter::Amount(AmountFilter::Range(
|
||||
AMOUNT_RANGE_BOUNDS._1m_sats_to_10m_sats,
|
||||
)),
|
||||
_10m_sats_to_1btc: Filter::Amount(AmountFilter::Range(AMOUNT_RANGE_BOUNDS._10m_sats_to_1btc)),
|
||||
_1btc_to_10btc: Filter::Amount(AmountFilter::Range(AMOUNT_RANGE_BOUNDS._1btc_to_10btc)),
|
||||
_10btc_to_100btc: Filter::Amount(AmountFilter::Range(AMOUNT_RANGE_BOUNDS._10btc_to_100btc)),
|
||||
_100btc_to_1k_btc: Filter::Amount(AmountFilter::Range(AMOUNT_RANGE_BOUNDS._100btc_to_1k_btc)),
|
||||
_1k_btc_to_10k_btc: Filter::Amount(AmountFilter::Range(AMOUNT_RANGE_BOUNDS._1k_btc_to_10k_btc)),
|
||||
_10k_btc_to_100k_btc: Filter::Amount(AmountFilter::Range(
|
||||
AMOUNT_RANGE_BOUNDS._10k_btc_to_100k_btc,
|
||||
)),
|
||||
_100k_btc_or_more: Filter::Amount(AmountFilter::Range(AMOUNT_RANGE_BOUNDS._100k_btc_or_more)),
|
||||
};
|
||||
|
||||
#[derive(Debug, Default, Clone, Traversable, Serialize)]
|
||||
pub struct ByAmountRange<T> {
|
||||
pub _0sats: T,
|
||||
pub _1sat_to_10sats: T,
|
||||
@@ -76,50 +178,84 @@ pub struct ByAmountRange<T> {
|
||||
pub _100k_btc_or_more: T,
|
||||
}
|
||||
|
||||
impl ByAmountRange<CohortName> {
|
||||
pub const fn names() -> &'static Self {
|
||||
&AMOUNT_RANGE_NAMES
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ByAmountRange<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
let f = AMOUNT_RANGE_FILTERS;
|
||||
let n = AMOUNT_RANGE_NAMES;
|
||||
Self {
|
||||
_0sats: create(Filter::Amount(AmountFilter::Range(Sats::ZERO..Sats::_1))),
|
||||
_1sat_to_10sats: create(Filter::Amount(AmountFilter::Range(Sats::_1..Sats::_10))),
|
||||
_10sats_to_100sats: create(Filter::Amount(AmountFilter::Range(Sats::_10..Sats::_100))),
|
||||
_100sats_to_1k_sats: create(Filter::Amount(AmountFilter::Range(Sats::_100..Sats::_1K))),
|
||||
_1k_sats_to_10k_sats: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_1K..Sats::_10K,
|
||||
))),
|
||||
_10k_sats_to_100k_sats: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_10K..Sats::_100K,
|
||||
))),
|
||||
_100k_sats_to_1m_sats: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_100K..Sats::_1M,
|
||||
))),
|
||||
_1m_sats_to_10m_sats: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_1M..Sats::_10M,
|
||||
))),
|
||||
_10m_sats_to_1btc: create(Filter::Amount(AmountFilter::Range(Sats::_10M..Sats::_1BTC))),
|
||||
_1btc_to_10btc: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_1BTC..Sats::_10BTC,
|
||||
))),
|
||||
_10btc_to_100btc: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_10BTC..Sats::_100BTC,
|
||||
))),
|
||||
_100btc_to_1k_btc: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_100BTC..Sats::_1K_BTC,
|
||||
))),
|
||||
_1k_btc_to_10k_btc: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_1K_BTC..Sats::_10K_BTC,
|
||||
))),
|
||||
_10k_btc_to_100k_btc: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_10K_BTC..Sats::_100K_BTC,
|
||||
))),
|
||||
_100k_btc_or_more: create(Filter::Amount(AmountFilter::Range(
|
||||
Sats::_100K_BTC..Sats::MAX,
|
||||
))),
|
||||
_0sats: create(f._0sats.clone(), n._0sats.id),
|
||||
_1sat_to_10sats: create(f._1sat_to_10sats.clone(), n._1sat_to_10sats.id),
|
||||
_10sats_to_100sats: create(f._10sats_to_100sats.clone(), n._10sats_to_100sats.id),
|
||||
_100sats_to_1k_sats: create(f._100sats_to_1k_sats.clone(), n._100sats_to_1k_sats.id),
|
||||
_1k_sats_to_10k_sats: create(f._1k_sats_to_10k_sats.clone(), n._1k_sats_to_10k_sats.id),
|
||||
_10k_sats_to_100k_sats: create(
|
||||
f._10k_sats_to_100k_sats.clone(),
|
||||
n._10k_sats_to_100k_sats.id,
|
||||
),
|
||||
_100k_sats_to_1m_sats: create(
|
||||
f._100k_sats_to_1m_sats.clone(),
|
||||
n._100k_sats_to_1m_sats.id,
|
||||
),
|
||||
_1m_sats_to_10m_sats: create(f._1m_sats_to_10m_sats.clone(), n._1m_sats_to_10m_sats.id),
|
||||
_10m_sats_to_1btc: create(f._10m_sats_to_1btc.clone(), n._10m_sats_to_1btc.id),
|
||||
_1btc_to_10btc: create(f._1btc_to_10btc.clone(), n._1btc_to_10btc.id),
|
||||
_10btc_to_100btc: create(f._10btc_to_100btc.clone(), n._10btc_to_100btc.id),
|
||||
_100btc_to_1k_btc: create(f._100btc_to_1k_btc.clone(), n._100btc_to_1k_btc.id),
|
||||
_1k_btc_to_10k_btc: create(f._1k_btc_to_10k_btc.clone(), n._1k_btc_to_10k_btc.id),
|
||||
_10k_btc_to_100k_btc: create(f._10k_btc_to_100k_btc.clone(), n._10k_btc_to_100k_btc.id),
|
||||
_100k_btc_or_more: create(f._100k_btc_or_more.clone(), n._100k_btc_or_more.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
let f = AMOUNT_RANGE_FILTERS;
|
||||
let n = AMOUNT_RANGE_NAMES;
|
||||
Ok(Self {
|
||||
_0sats: create(f._0sats.clone(), n._0sats.id)?,
|
||||
_1sat_to_10sats: create(f._1sat_to_10sats.clone(), n._1sat_to_10sats.id)?,
|
||||
_10sats_to_100sats: create(f._10sats_to_100sats.clone(), n._10sats_to_100sats.id)?,
|
||||
_100sats_to_1k_sats: create(f._100sats_to_1k_sats.clone(), n._100sats_to_1k_sats.id)?,
|
||||
_1k_sats_to_10k_sats: create(
|
||||
f._1k_sats_to_10k_sats.clone(),
|
||||
n._1k_sats_to_10k_sats.id,
|
||||
)?,
|
||||
_10k_sats_to_100k_sats: create(
|
||||
f._10k_sats_to_100k_sats.clone(),
|
||||
n._10k_sats_to_100k_sats.id,
|
||||
)?,
|
||||
_100k_sats_to_1m_sats: create(
|
||||
f._100k_sats_to_1m_sats.clone(),
|
||||
n._100k_sats_to_1m_sats.id,
|
||||
)?,
|
||||
_1m_sats_to_10m_sats: create(
|
||||
f._1m_sats_to_10m_sats.clone(),
|
||||
n._1m_sats_to_10m_sats.id,
|
||||
)?,
|
||||
_10m_sats_to_1btc: create(f._10m_sats_to_1btc.clone(), n._10m_sats_to_1btc.id)?,
|
||||
_1btc_to_10btc: create(f._1btc_to_10btc.clone(), n._1btc_to_10btc.id)?,
|
||||
_10btc_to_100btc: create(f._10btc_to_100btc.clone(), n._10btc_to_100btc.id)?,
|
||||
_100btc_to_1k_btc: create(f._100btc_to_1k_btc.clone(), n._100btc_to_1k_btc.id)?,
|
||||
_1k_btc_to_10k_btc: create(f._1k_btc_to_10k_btc.clone(), n._1k_btc_to_10k_btc.id)?,
|
||||
_10k_btc_to_100k_btc: create(
|
||||
f._10k_btc_to_100k_btc.clone(),
|
||||
n._10k_btc_to_100k_btc.id,
|
||||
)?,
|
||||
_100k_btc_or_more: create(f._100k_btc_or_more.clone(), n._100k_btc_or_more.id)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get(&self, value: Sats) -> &T {
|
||||
match AmountBucket::from(value).0 {
|
||||
|
||||
@@ -1,10 +1,38 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{HalvingEpoch, Height};
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::Filter;
|
||||
use super::{CohortName, Filter};
|
||||
|
||||
#[derive(Default, Clone, Traversable)]
|
||||
/// Epoch values
|
||||
pub const EPOCH_VALUES: ByEpoch<HalvingEpoch> = ByEpoch {
|
||||
_0: HalvingEpoch::new(0),
|
||||
_1: HalvingEpoch::new(1),
|
||||
_2: HalvingEpoch::new(2),
|
||||
_3: HalvingEpoch::new(3),
|
||||
_4: HalvingEpoch::new(4),
|
||||
};
|
||||
|
||||
/// Epoch filters
|
||||
pub const EPOCH_FILTERS: ByEpoch<Filter> = ByEpoch {
|
||||
_0: Filter::Epoch(EPOCH_VALUES._0),
|
||||
_1: Filter::Epoch(EPOCH_VALUES._1),
|
||||
_2: Filter::Epoch(EPOCH_VALUES._2),
|
||||
_3: Filter::Epoch(EPOCH_VALUES._3),
|
||||
_4: Filter::Epoch(EPOCH_VALUES._4),
|
||||
};
|
||||
|
||||
/// Epoch names
|
||||
pub const EPOCH_NAMES: ByEpoch<CohortName> = ByEpoch {
|
||||
_0: CohortName::new("epoch_0", "Epoch 0", "Epoch 0"),
|
||||
_1: CohortName::new("epoch_1", "Epoch 1", "Epoch 1"),
|
||||
_2: CohortName::new("epoch_2", "Epoch 2", "Epoch 2"),
|
||||
_3: CohortName::new("epoch_3", "Epoch 3", "Epoch 3"),
|
||||
_4: CohortName::new("epoch_4", "Epoch 4", "Epoch 4"),
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Traversable, Serialize)]
|
||||
pub struct ByEpoch<T> {
|
||||
pub _0: T,
|
||||
pub _1: T,
|
||||
@@ -13,20 +41,43 @@ pub struct ByEpoch<T> {
|
||||
pub _4: T,
|
||||
}
|
||||
|
||||
impl ByEpoch<CohortName> {
|
||||
pub const fn names() -> &'static Self {
|
||||
&EPOCH_NAMES
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ByEpoch<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
let f = EPOCH_FILTERS;
|
||||
let n = EPOCH_NAMES;
|
||||
Self {
|
||||
_0: create(Filter::Epoch(HalvingEpoch::new(0))),
|
||||
_1: create(Filter::Epoch(HalvingEpoch::new(1))),
|
||||
_2: create(Filter::Epoch(HalvingEpoch::new(2))),
|
||||
_3: create(Filter::Epoch(HalvingEpoch::new(3))),
|
||||
_4: create(Filter::Epoch(HalvingEpoch::new(4))),
|
||||
_0: create(f._0, n._0.id),
|
||||
_1: create(f._1, n._1.id),
|
||||
_2: create(f._2, n._2.id),
|
||||
_3: create(f._3, n._3.id),
|
||||
_4: create(f._4, n._4.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
let f = EPOCH_FILTERS;
|
||||
let n = EPOCH_NAMES;
|
||||
Ok(Self {
|
||||
_0: create(f._0, n._0.id)?,
|
||||
_1: create(f._1, n._1.id)?,
|
||||
_2: create(f._2, n._2.id)?,
|
||||
_3: create(f._3, n._3.id)?,
|
||||
_4: create(f._4, n._4.id)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
[&self._0, &self._1, &self._2, &self._3, &self._4].into_iter()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,64 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::Sats;
|
||||
use rayon::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{AmountFilter, Filter};
|
||||
use super::{AmountFilter, CohortName, Filter};
|
||||
|
||||
#[derive(Default, Clone, Traversable)]
|
||||
/// Greater-or-equal amount thresholds
|
||||
pub const GE_AMOUNT_THRESHOLDS: ByGreatEqualAmount<Sats> = ByGreatEqualAmount {
|
||||
_1sat: Sats::_1,
|
||||
_10sats: Sats::_10,
|
||||
_100sats: Sats::_100,
|
||||
_1k_sats: Sats::_1K,
|
||||
_10k_sats: Sats::_10K,
|
||||
_100k_sats: Sats::_100K,
|
||||
_1m_sats: Sats::_1M,
|
||||
_10m_sats: Sats::_10M,
|
||||
_1btc: Sats::_1BTC,
|
||||
_10btc: Sats::_10BTC,
|
||||
_100btc: Sats::_100BTC,
|
||||
_1k_btc: Sats::_1K_BTC,
|
||||
_10k_btc: Sats::_10K_BTC,
|
||||
};
|
||||
|
||||
/// Greater-or-equal amount names
|
||||
pub const GE_AMOUNT_NAMES: ByGreatEqualAmount<CohortName> = ByGreatEqualAmount {
|
||||
_1sat: CohortName::new("above_1sat", "1+ sats", "Above 1 Sat"),
|
||||
_10sats: CohortName::new("above_10sats", "10+ sats", "Above 10 Sats"),
|
||||
_100sats: CohortName::new("above_100sats", "100+ sats", "Above 100 Sats"),
|
||||
_1k_sats: CohortName::new("above_1k_sats", "1k+ sats", "Above 1K Sats"),
|
||||
_10k_sats: CohortName::new("above_10k_sats", "10k+ sats", "Above 10K Sats"),
|
||||
_100k_sats: CohortName::new("above_100k_sats", "100k+ sats", "Above 100K Sats"),
|
||||
_1m_sats: CohortName::new("above_1m_sats", "1M+ sats", "Above 1M Sats"),
|
||||
_10m_sats: CohortName::new("above_10m_sats", "0.1+ BTC", "Above 0.1 BTC"),
|
||||
_1btc: CohortName::new("above_1btc", "1+ BTC", "Above 1 BTC"),
|
||||
_10btc: CohortName::new("above_10btc", "10+ BTC", "Above 10 BTC"),
|
||||
_100btc: CohortName::new("above_100btc", "100+ BTC", "Above 100 BTC"),
|
||||
_1k_btc: CohortName::new("above_1k_btc", "1k+ BTC", "Above 1K BTC"),
|
||||
_10k_btc: CohortName::new("above_10k_btc", "10k+ BTC", "Above 10K BTC"),
|
||||
};
|
||||
|
||||
/// Greater-or-equal amount filters
|
||||
pub const GE_AMOUNT_FILTERS: ByGreatEqualAmount<Filter> = ByGreatEqualAmount {
|
||||
_1sat: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._1sat)),
|
||||
_10sats: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._10sats)),
|
||||
_100sats: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._100sats)),
|
||||
_1k_sats: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._1k_sats)),
|
||||
_10k_sats: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._10k_sats)),
|
||||
_100k_sats: Filter::Amount(AmountFilter::GreaterOrEqual(
|
||||
GE_AMOUNT_THRESHOLDS._100k_sats,
|
||||
)),
|
||||
_1m_sats: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._1m_sats)),
|
||||
_10m_sats: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._10m_sats)),
|
||||
_1btc: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._1btc)),
|
||||
_10btc: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._10btc)),
|
||||
_100btc: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._100btc)),
|
||||
_1k_btc: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._1k_btc)),
|
||||
_10k_btc: Filter::Amount(AmountFilter::GreaterOrEqual(GE_AMOUNT_THRESHOLDS._10k_btc)),
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Traversable, Serialize)]
|
||||
pub struct ByGreatEqualAmount<T> {
|
||||
pub _1sat: T,
|
||||
pub _10sats: T,
|
||||
@@ -21,28 +75,59 @@ pub struct ByGreatEqualAmount<T> {
|
||||
pub _10k_btc: T,
|
||||
}
|
||||
|
||||
impl ByGreatEqualAmount<CohortName> {
|
||||
pub const fn names() -> &'static Self {
|
||||
&GE_AMOUNT_NAMES
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ByGreatEqualAmount<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
let f = GE_AMOUNT_FILTERS;
|
||||
let n = GE_AMOUNT_NAMES;
|
||||
Self {
|
||||
_1sat: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1))),
|
||||
_10sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10))),
|
||||
_100sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100))),
|
||||
_1k_sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K))),
|
||||
_10k_sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K))),
|
||||
_100k_sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100K))),
|
||||
_1m_sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1M))),
|
||||
_10m_sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10M))),
|
||||
_1btc: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1BTC))),
|
||||
_10btc: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10BTC))),
|
||||
_100btc: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100BTC))),
|
||||
_1k_btc: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K_BTC))),
|
||||
_10k_btc: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K_BTC))),
|
||||
_1sat: create(f._1sat.clone(), n._1sat.id),
|
||||
_10sats: create(f._10sats.clone(), n._10sats.id),
|
||||
_100sats: create(f._100sats.clone(), n._100sats.id),
|
||||
_1k_sats: create(f._1k_sats.clone(), n._1k_sats.id),
|
||||
_10k_sats: create(f._10k_sats.clone(), n._10k_sats.id),
|
||||
_100k_sats: create(f._100k_sats.clone(), n._100k_sats.id),
|
||||
_1m_sats: create(f._1m_sats.clone(), n._1m_sats.id),
|
||||
_10m_sats: create(f._10m_sats.clone(), n._10m_sats.id),
|
||||
_1btc: create(f._1btc.clone(), n._1btc.id),
|
||||
_10btc: create(f._10btc.clone(), n._10btc.id),
|
||||
_100btc: create(f._100btc.clone(), n._100btc.id),
|
||||
_1k_btc: create(f._1k_btc.clone(), n._1k_btc.id),
|
||||
_10k_btc: create(f._10k_btc.clone(), n._10k_btc.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
let f = GE_AMOUNT_FILTERS;
|
||||
let n = GE_AMOUNT_NAMES;
|
||||
Ok(Self {
|
||||
_1sat: create(f._1sat.clone(), n._1sat.id)?,
|
||||
_10sats: create(f._10sats.clone(), n._10sats.id)?,
|
||||
_100sats: create(f._100sats.clone(), n._100sats.id)?,
|
||||
_1k_sats: create(f._1k_sats.clone(), n._1k_sats.id)?,
|
||||
_10k_sats: create(f._10k_sats.clone(), n._10k_sats.id)?,
|
||||
_100k_sats: create(f._100k_sats.clone(), n._100k_sats.id)?,
|
||||
_1m_sats: create(f._1m_sats.clone(), n._1m_sats.id)?,
|
||||
_10m_sats: create(f._10m_sats.clone(), n._10m_sats.id)?,
|
||||
_1btc: create(f._1btc.clone(), n._1btc.id)?,
|
||||
_10btc: create(f._10btc.clone(), n._10btc.id)?,
|
||||
_100btc: create(f._100btc.clone(), n._100btc.id)?,
|
||||
_1k_btc: create(f._1k_btc.clone(), n._1k_btc.id)?,
|
||||
_10k_btc: create(f._10k_btc.clone(), n._10k_btc.id)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
[
|
||||
&self._1sat,
|
||||
|
||||
@@ -1,10 +1,62 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::Sats;
|
||||
use rayon::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{AmountFilter, Filter};
|
||||
use super::{AmountFilter, CohortName, Filter};
|
||||
|
||||
#[derive(Default, Clone, Traversable)]
|
||||
/// Lower-than amount thresholds
|
||||
pub const LT_AMOUNT_THRESHOLDS: ByLowerThanAmount<Sats> = ByLowerThanAmount {
|
||||
_10sats: Sats::_10,
|
||||
_100sats: Sats::_100,
|
||||
_1k_sats: Sats::_1K,
|
||||
_10k_sats: Sats::_10K,
|
||||
_100k_sats: Sats::_100K,
|
||||
_1m_sats: Sats::_1M,
|
||||
_10m_sats: Sats::_10M,
|
||||
_1btc: Sats::_1BTC,
|
||||
_10btc: Sats::_10BTC,
|
||||
_100btc: Sats::_100BTC,
|
||||
_1k_btc: Sats::_1K_BTC,
|
||||
_10k_btc: Sats::_10K_BTC,
|
||||
_100k_btc: Sats::_100K_BTC,
|
||||
};
|
||||
|
||||
/// Lower-than amount names
|
||||
pub const LT_AMOUNT_NAMES: ByLowerThanAmount<CohortName> = ByLowerThanAmount {
|
||||
_10sats: CohortName::new("under_10sats", "<10 sats", "Under 10 Sats"),
|
||||
_100sats: CohortName::new("under_100sats", "<100 sats", "Under 100 Sats"),
|
||||
_1k_sats: CohortName::new("under_1k_sats", "<1k sats", "Under 1K Sats"),
|
||||
_10k_sats: CohortName::new("under_10k_sats", "<10k sats", "Under 10K Sats"),
|
||||
_100k_sats: CohortName::new("under_100k_sats", "<100k sats", "Under 100K Sats"),
|
||||
_1m_sats: CohortName::new("under_1m_sats", "<1M sats", "Under 1M Sats"),
|
||||
_10m_sats: CohortName::new("under_10m_sats", "<0.1 BTC", "Under 0.1 BTC"),
|
||||
_1btc: CohortName::new("under_1btc", "<1 BTC", "Under 1 BTC"),
|
||||
_10btc: CohortName::new("under_10btc", "<10 BTC", "Under 10 BTC"),
|
||||
_100btc: CohortName::new("under_100btc", "<100 BTC", "Under 100 BTC"),
|
||||
_1k_btc: CohortName::new("under_1k_btc", "<1k BTC", "Under 1K BTC"),
|
||||
_10k_btc: CohortName::new("under_10k_btc", "<10k BTC", "Under 10K BTC"),
|
||||
_100k_btc: CohortName::new("under_100k_btc", "<100k BTC", "Under 100K BTC"),
|
||||
};
|
||||
|
||||
/// Lower-than amount filters
|
||||
pub const LT_AMOUNT_FILTERS: ByLowerThanAmount<Filter> = ByLowerThanAmount {
|
||||
_10sats: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._10sats)),
|
||||
_100sats: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._100sats)),
|
||||
_1k_sats: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._1k_sats)),
|
||||
_10k_sats: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._10k_sats)),
|
||||
_100k_sats: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._100k_sats)),
|
||||
_1m_sats: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._1m_sats)),
|
||||
_10m_sats: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._10m_sats)),
|
||||
_1btc: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._1btc)),
|
||||
_10btc: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._10btc)),
|
||||
_100btc: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._100btc)),
|
||||
_1k_btc: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._1k_btc)),
|
||||
_10k_btc: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._10k_btc)),
|
||||
_100k_btc: Filter::Amount(AmountFilter::LowerThan(LT_AMOUNT_THRESHOLDS._100k_btc)),
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Traversable, Serialize)]
|
||||
pub struct ByLowerThanAmount<T> {
|
||||
pub _10sats: T,
|
||||
pub _100sats: T,
|
||||
@@ -21,28 +73,59 @@ pub struct ByLowerThanAmount<T> {
|
||||
pub _100k_btc: T,
|
||||
}
|
||||
|
||||
impl ByLowerThanAmount<CohortName> {
|
||||
pub const fn names() -> &'static Self {
|
||||
<_AMOUNT_NAMES
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ByLowerThanAmount<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
let f = LT_AMOUNT_FILTERS;
|
||||
let n = LT_AMOUNT_NAMES;
|
||||
Self {
|
||||
_10sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_10))),
|
||||
_100sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_100))),
|
||||
_1k_sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_1K))),
|
||||
_10k_sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_10K))),
|
||||
_100k_sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_100K))),
|
||||
_1m_sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_1M))),
|
||||
_10m_sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_10M))),
|
||||
_1btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_1BTC))),
|
||||
_10btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_10BTC))),
|
||||
_100btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_100BTC))),
|
||||
_1k_btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_1K_BTC))),
|
||||
_10k_btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_10K_BTC))),
|
||||
_100k_btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_100K_BTC))),
|
||||
_10sats: create(f._10sats.clone(), n._10sats.id),
|
||||
_100sats: create(f._100sats.clone(), n._100sats.id),
|
||||
_1k_sats: create(f._1k_sats.clone(), n._1k_sats.id),
|
||||
_10k_sats: create(f._10k_sats.clone(), n._10k_sats.id),
|
||||
_100k_sats: create(f._100k_sats.clone(), n._100k_sats.id),
|
||||
_1m_sats: create(f._1m_sats.clone(), n._1m_sats.id),
|
||||
_10m_sats: create(f._10m_sats.clone(), n._10m_sats.id),
|
||||
_1btc: create(f._1btc.clone(), n._1btc.id),
|
||||
_10btc: create(f._10btc.clone(), n._10btc.id),
|
||||
_100btc: create(f._100btc.clone(), n._100btc.id),
|
||||
_1k_btc: create(f._1k_btc.clone(), n._1k_btc.id),
|
||||
_10k_btc: create(f._10k_btc.clone(), n._10k_btc.id),
|
||||
_100k_btc: create(f._100k_btc.clone(), n._100k_btc.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
let f = LT_AMOUNT_FILTERS;
|
||||
let n = LT_AMOUNT_NAMES;
|
||||
Ok(Self {
|
||||
_10sats: create(f._10sats.clone(), n._10sats.id)?,
|
||||
_100sats: create(f._100sats.clone(), n._100sats.id)?,
|
||||
_1k_sats: create(f._1k_sats.clone(), n._1k_sats.id)?,
|
||||
_10k_sats: create(f._10k_sats.clone(), n._10k_sats.id)?,
|
||||
_100k_sats: create(f._100k_sats.clone(), n._100k_sats.id)?,
|
||||
_1m_sats: create(f._1m_sats.clone(), n._1m_sats.id)?,
|
||||
_10m_sats: create(f._10m_sats.clone(), n._10m_sats.id)?,
|
||||
_1btc: create(f._1btc.clone(), n._1btc.id)?,
|
||||
_10btc: create(f._10btc.clone(), n._10btc.id)?,
|
||||
_100btc: create(f._100btc.clone(), n._100btc.id)?,
|
||||
_1k_btc: create(f._1k_btc.clone(), n._1k_btc.id)?,
|
||||
_10k_btc: create(f._10k_btc.clone(), n._10k_btc.id)?,
|
||||
_100k_btc: create(f._100k_btc.clone(), n._100k_btc.id)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
[
|
||||
&self._10sats,
|
||||
|
||||
@@ -1,12 +1,80 @@
|
||||
use brk_traversable::Traversable;
|
||||
use rayon::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{
|
||||
Filter, TimeFilter, DAYS_10Y, DAYS_12Y, DAYS_15Y, DAYS_1M, DAYS_1W, DAYS_1Y, DAYS_2M, DAYS_2Y,
|
||||
DAYS_3M, DAYS_3Y, DAYS_4M, DAYS_4Y, DAYS_5M, DAYS_5Y, DAYS_6M, DAYS_6Y, DAYS_7Y, DAYS_8Y,
|
||||
CohortName, Filter, TimeFilter, DAYS_10Y, DAYS_12Y, DAYS_15Y, DAYS_1M, DAYS_1W, DAYS_1Y,
|
||||
DAYS_2M, DAYS_2Y, DAYS_3M, DAYS_3Y, DAYS_4M, DAYS_4Y, DAYS_5M, DAYS_5Y, DAYS_6M, DAYS_6Y,
|
||||
DAYS_7Y, DAYS_8Y,
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Traversable)]
|
||||
/// Max age thresholds in days
|
||||
pub const MAX_AGE_DAYS: ByMaxAge<usize> = ByMaxAge {
|
||||
_1w: DAYS_1W,
|
||||
_1m: DAYS_1M,
|
||||
_2m: DAYS_2M,
|
||||
_3m: DAYS_3M,
|
||||
_4m: DAYS_4M,
|
||||
_5m: DAYS_5M,
|
||||
_6m: DAYS_6M,
|
||||
_1y: DAYS_1Y,
|
||||
_2y: DAYS_2Y,
|
||||
_3y: DAYS_3Y,
|
||||
_4y: DAYS_4Y,
|
||||
_5y: DAYS_5Y,
|
||||
_6y: DAYS_6Y,
|
||||
_7y: DAYS_7Y,
|
||||
_8y: DAYS_8Y,
|
||||
_10y: DAYS_10Y,
|
||||
_12y: DAYS_12Y,
|
||||
_15y: DAYS_15Y,
|
||||
};
|
||||
|
||||
/// Max age filters (LowerThan threshold)
|
||||
pub const MAX_AGE_FILTERS: ByMaxAge<Filter> = ByMaxAge {
|
||||
_1w: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._1w)),
|
||||
_1m: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._1m)),
|
||||
_2m: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._2m)),
|
||||
_3m: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._3m)),
|
||||
_4m: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._4m)),
|
||||
_5m: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._5m)),
|
||||
_6m: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._6m)),
|
||||
_1y: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._1y)),
|
||||
_2y: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._2y)),
|
||||
_3y: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._3y)),
|
||||
_4y: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._4y)),
|
||||
_5y: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._5y)),
|
||||
_6y: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._6y)),
|
||||
_7y: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._7y)),
|
||||
_8y: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._8y)),
|
||||
_10y: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._10y)),
|
||||
_12y: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._12y)),
|
||||
_15y: Filter::Time(TimeFilter::LowerThan(MAX_AGE_DAYS._15y)),
|
||||
};
|
||||
|
||||
/// Max age names
|
||||
pub const MAX_AGE_NAMES: ByMaxAge<CohortName> = ByMaxAge {
|
||||
_1w: CohortName::new("up_to_1w_old", "<1w", "Up to 1 Week Old"),
|
||||
_1m: CohortName::new("up_to_1m_old", "<1m", "Up to 1 Month Old"),
|
||||
_2m: CohortName::new("up_to_2m_old", "<2m", "Up to 2 Months Old"),
|
||||
_3m: CohortName::new("up_to_3m_old", "<3m", "Up to 3 Months Old"),
|
||||
_4m: CohortName::new("up_to_4m_old", "<4m", "Up to 4 Months Old"),
|
||||
_5m: CohortName::new("up_to_5m_old", "<5m", "Up to 5 Months Old"),
|
||||
_6m: CohortName::new("up_to_6m_old", "<6m", "Up to 6 Months Old"),
|
||||
_1y: CohortName::new("up_to_1y_old", "<1y", "Up to 1 Year Old"),
|
||||
_2y: CohortName::new("up_to_2y_old", "<2y", "Up to 2 Years Old"),
|
||||
_3y: CohortName::new("up_to_3y_old", "<3y", "Up to 3 Years Old"),
|
||||
_4y: CohortName::new("up_to_4y_old", "<4y", "Up to 4 Years Old"),
|
||||
_5y: CohortName::new("up_to_5y_old", "<5y", "Up to 5 Years Old"),
|
||||
_6y: CohortName::new("up_to_6y_old", "<6y", "Up to 6 Years Old"),
|
||||
_7y: CohortName::new("up_to_7y_old", "<7y", "Up to 7 Years Old"),
|
||||
_8y: CohortName::new("up_to_8y_old", "<8y", "Up to 8 Years Old"),
|
||||
_10y: CohortName::new("up_to_10y_old", "<10y", "Up to 10 Years Old"),
|
||||
_12y: CohortName::new("up_to_12y_old", "<12y", "Up to 12 Years Old"),
|
||||
_15y: CohortName::new("up_to_15y_old", "<15y", "Up to 15 Years Old"),
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Traversable, Serialize)]
|
||||
pub struct ByMaxAge<T> {
|
||||
pub _1w: T,
|
||||
pub _1m: T,
|
||||
@@ -28,33 +96,69 @@ pub struct ByMaxAge<T> {
|
||||
pub _15y: T,
|
||||
}
|
||||
|
||||
impl ByMaxAge<CohortName> {
|
||||
pub const fn names() -> &'static Self {
|
||||
&MAX_AGE_NAMES
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ByMaxAge<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
let f = MAX_AGE_FILTERS;
|
||||
let n = MAX_AGE_NAMES;
|
||||
Self {
|
||||
_1w: create(Filter::Time(TimeFilter::LowerThan(DAYS_1W))),
|
||||
_1m: create(Filter::Time(TimeFilter::LowerThan(DAYS_1M))),
|
||||
_2m: create(Filter::Time(TimeFilter::LowerThan(DAYS_2M))),
|
||||
_3m: create(Filter::Time(TimeFilter::LowerThan(DAYS_3M))),
|
||||
_4m: create(Filter::Time(TimeFilter::LowerThan(DAYS_4M))),
|
||||
_5m: create(Filter::Time(TimeFilter::LowerThan(DAYS_5M))),
|
||||
_6m: create(Filter::Time(TimeFilter::LowerThan(DAYS_6M))),
|
||||
_1y: create(Filter::Time(TimeFilter::LowerThan(DAYS_1Y))),
|
||||
_2y: create(Filter::Time(TimeFilter::LowerThan(DAYS_2Y))),
|
||||
_3y: create(Filter::Time(TimeFilter::LowerThan(DAYS_3Y))),
|
||||
_4y: create(Filter::Time(TimeFilter::LowerThan(DAYS_4Y))),
|
||||
_5y: create(Filter::Time(TimeFilter::LowerThan(DAYS_5Y))),
|
||||
_6y: create(Filter::Time(TimeFilter::LowerThan(DAYS_6Y))),
|
||||
_7y: create(Filter::Time(TimeFilter::LowerThan(DAYS_7Y))),
|
||||
_8y: create(Filter::Time(TimeFilter::LowerThan(DAYS_8Y))),
|
||||
_10y: create(Filter::Time(TimeFilter::LowerThan(DAYS_10Y))),
|
||||
_12y: create(Filter::Time(TimeFilter::LowerThan(DAYS_12Y))),
|
||||
_15y: create(Filter::Time(TimeFilter::LowerThan(DAYS_15Y))),
|
||||
_1w: create(f._1w.clone(), n._1w.id),
|
||||
_1m: create(f._1m.clone(), n._1m.id),
|
||||
_2m: create(f._2m.clone(), n._2m.id),
|
||||
_3m: create(f._3m.clone(), n._3m.id),
|
||||
_4m: create(f._4m.clone(), n._4m.id),
|
||||
_5m: create(f._5m.clone(), n._5m.id),
|
||||
_6m: create(f._6m.clone(), n._6m.id),
|
||||
_1y: create(f._1y.clone(), n._1y.id),
|
||||
_2y: create(f._2y.clone(), n._2y.id),
|
||||
_3y: create(f._3y.clone(), n._3y.id),
|
||||
_4y: create(f._4y.clone(), n._4y.id),
|
||||
_5y: create(f._5y.clone(), n._5y.id),
|
||||
_6y: create(f._6y.clone(), n._6y.id),
|
||||
_7y: create(f._7y.clone(), n._7y.id),
|
||||
_8y: create(f._8y.clone(), n._8y.id),
|
||||
_10y: create(f._10y.clone(), n._10y.id),
|
||||
_12y: create(f._12y.clone(), n._12y.id),
|
||||
_15y: create(f._15y.clone(), n._15y.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
let f = MAX_AGE_FILTERS;
|
||||
let n = MAX_AGE_NAMES;
|
||||
Ok(Self {
|
||||
_1w: create(f._1w.clone(), n._1w.id)?,
|
||||
_1m: create(f._1m.clone(), n._1m.id)?,
|
||||
_2m: create(f._2m.clone(), n._2m.id)?,
|
||||
_3m: create(f._3m.clone(), n._3m.id)?,
|
||||
_4m: create(f._4m.clone(), n._4m.id)?,
|
||||
_5m: create(f._5m.clone(), n._5m.id)?,
|
||||
_6m: create(f._6m.clone(), n._6m.id)?,
|
||||
_1y: create(f._1y.clone(), n._1y.id)?,
|
||||
_2y: create(f._2y.clone(), n._2y.id)?,
|
||||
_3y: create(f._3y.clone(), n._3y.id)?,
|
||||
_4y: create(f._4y.clone(), n._4y.id)?,
|
||||
_5y: create(f._5y.clone(), n._5y.id)?,
|
||||
_6y: create(f._6y.clone(), n._6y.id)?,
|
||||
_7y: create(f._7y.clone(), n._7y.id)?,
|
||||
_8y: create(f._8y.clone(), n._8y.id)?,
|
||||
_10y: create(f._10y.clone(), n._10y.id)?,
|
||||
_12y: create(f._12y.clone(), n._12y.id)?,
|
||||
_15y: create(f._15y.clone(), n._15y.id)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
[
|
||||
&self._1w, &self._1m, &self._2m, &self._3m, &self._4m, &self._5m, &self._6m, &self._1y,
|
||||
|
||||
@@ -1,12 +1,80 @@
|
||||
use brk_traversable::Traversable;
|
||||
use rayon::prelude::*;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{
|
||||
Filter, TimeFilter, DAYS_10Y, DAYS_12Y, DAYS_1D, DAYS_1M, DAYS_1W, DAYS_1Y, DAYS_2M, DAYS_2Y,
|
||||
DAYS_3M, DAYS_3Y, DAYS_4M, DAYS_4Y, DAYS_5M, DAYS_5Y, DAYS_6M, DAYS_6Y, DAYS_7Y, DAYS_8Y,
|
||||
CohortName, Filter, TimeFilter, DAYS_10Y, DAYS_12Y, DAYS_1D, DAYS_1M, DAYS_1W, DAYS_1Y,
|
||||
DAYS_2M, DAYS_2Y, DAYS_3M, DAYS_3Y, DAYS_4M, DAYS_4Y, DAYS_5M, DAYS_5Y, DAYS_6M, DAYS_6Y,
|
||||
DAYS_7Y, DAYS_8Y,
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Traversable)]
|
||||
/// Min age thresholds in days
|
||||
pub const MIN_AGE_DAYS: ByMinAge<usize> = ByMinAge {
|
||||
_1d: DAYS_1D,
|
||||
_1w: DAYS_1W,
|
||||
_1m: DAYS_1M,
|
||||
_2m: DAYS_2M,
|
||||
_3m: DAYS_3M,
|
||||
_4m: DAYS_4M,
|
||||
_5m: DAYS_5M,
|
||||
_6m: DAYS_6M,
|
||||
_1y: DAYS_1Y,
|
||||
_2y: DAYS_2Y,
|
||||
_3y: DAYS_3Y,
|
||||
_4y: DAYS_4Y,
|
||||
_5y: DAYS_5Y,
|
||||
_6y: DAYS_6Y,
|
||||
_7y: DAYS_7Y,
|
||||
_8y: DAYS_8Y,
|
||||
_10y: DAYS_10Y,
|
||||
_12y: DAYS_12Y,
|
||||
};
|
||||
|
||||
/// Min age filters (GreaterOrEqual threshold)
|
||||
pub const MIN_AGE_FILTERS: ByMinAge<Filter> = ByMinAge {
|
||||
_1d: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._1d)),
|
||||
_1w: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._1w)),
|
||||
_1m: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._1m)),
|
||||
_2m: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._2m)),
|
||||
_3m: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._3m)),
|
||||
_4m: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._4m)),
|
||||
_5m: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._5m)),
|
||||
_6m: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._6m)),
|
||||
_1y: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._1y)),
|
||||
_2y: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._2y)),
|
||||
_3y: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._3y)),
|
||||
_4y: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._4y)),
|
||||
_5y: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._5y)),
|
||||
_6y: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._6y)),
|
||||
_7y: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._7y)),
|
||||
_8y: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._8y)),
|
||||
_10y: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._10y)),
|
||||
_12y: Filter::Time(TimeFilter::GreaterOrEqual(MIN_AGE_DAYS._12y)),
|
||||
};
|
||||
|
||||
/// Min age names
|
||||
pub const MIN_AGE_NAMES: ByMinAge<CohortName> = ByMinAge {
|
||||
_1d: CohortName::new("at_least_1d_old", "1d+", "At Least 1 Day Old"),
|
||||
_1w: CohortName::new("at_least_1w_old", "1w+", "At Least 1 Week Old"),
|
||||
_1m: CohortName::new("at_least_1m_old", "1m+", "At Least 1 Month Old"),
|
||||
_2m: CohortName::new("at_least_2m_old", "2m+", "At Least 2 Months Old"),
|
||||
_3m: CohortName::new("at_least_3m_old", "3m+", "At Least 3 Months Old"),
|
||||
_4m: CohortName::new("at_least_4m_old", "4m+", "At Least 4 Months Old"),
|
||||
_5m: CohortName::new("at_least_5m_old", "5m+", "At Least 5 Months Old"),
|
||||
_6m: CohortName::new("at_least_6m_old", "6m+", "At Least 6 Months Old"),
|
||||
_1y: CohortName::new("at_least_1y_old", "1y+", "At Least 1 Year Old"),
|
||||
_2y: CohortName::new("at_least_2y_old", "2y+", "At Least 2 Years Old"),
|
||||
_3y: CohortName::new("at_least_3y_old", "3y+", "At Least 3 Years Old"),
|
||||
_4y: CohortName::new("at_least_4y_old", "4y+", "At Least 4 Years Old"),
|
||||
_5y: CohortName::new("at_least_5y_old", "5y+", "At Least 5 Years Old"),
|
||||
_6y: CohortName::new("at_least_6y_old", "6y+", "At Least 6 Years Old"),
|
||||
_7y: CohortName::new("at_least_7y_old", "7y+", "At Least 7 Years Old"),
|
||||
_8y: CohortName::new("at_least_8y_old", "8y+", "At Least 8 Years Old"),
|
||||
_10y: CohortName::new("at_least_10y_old", "10y+", "At Least 10 Years Old"),
|
||||
_12y: CohortName::new("at_least_12y_old", "12y+", "At Least 12 Years Old"),
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Traversable, Serialize)]
|
||||
pub struct ByMinAge<T> {
|
||||
pub _1d: T,
|
||||
pub _1w: T,
|
||||
@@ -28,33 +96,69 @@ pub struct ByMinAge<T> {
|
||||
pub _12y: T,
|
||||
}
|
||||
|
||||
impl ByMinAge<CohortName> {
|
||||
pub const fn names() -> &'static Self {
|
||||
&MIN_AGE_NAMES
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ByMinAge<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
let f = MIN_AGE_FILTERS;
|
||||
let n = MIN_AGE_NAMES;
|
||||
Self {
|
||||
_1d: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1D))),
|
||||
_1w: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1W))),
|
||||
_1m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1M))),
|
||||
_2m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2M))),
|
||||
_3m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3M))),
|
||||
_4m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4M))),
|
||||
_5m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5M))),
|
||||
_6m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6M))),
|
||||
_1y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1Y))),
|
||||
_2y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2Y))),
|
||||
_3y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3Y))),
|
||||
_4y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4Y))),
|
||||
_5y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5Y))),
|
||||
_6y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6Y))),
|
||||
_7y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_7Y))),
|
||||
_8y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_8Y))),
|
||||
_10y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_10Y))),
|
||||
_12y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_12Y))),
|
||||
_1d: create(f._1d.clone(), n._1d.id),
|
||||
_1w: create(f._1w.clone(), n._1w.id),
|
||||
_1m: create(f._1m.clone(), n._1m.id),
|
||||
_2m: create(f._2m.clone(), n._2m.id),
|
||||
_3m: create(f._3m.clone(), n._3m.id),
|
||||
_4m: create(f._4m.clone(), n._4m.id),
|
||||
_5m: create(f._5m.clone(), n._5m.id),
|
||||
_6m: create(f._6m.clone(), n._6m.id),
|
||||
_1y: create(f._1y.clone(), n._1y.id),
|
||||
_2y: create(f._2y.clone(), n._2y.id),
|
||||
_3y: create(f._3y.clone(), n._3y.id),
|
||||
_4y: create(f._4y.clone(), n._4y.id),
|
||||
_5y: create(f._5y.clone(), n._5y.id),
|
||||
_6y: create(f._6y.clone(), n._6y.id),
|
||||
_7y: create(f._7y.clone(), n._7y.id),
|
||||
_8y: create(f._8y.clone(), n._8y.id),
|
||||
_10y: create(f._10y.clone(), n._10y.id),
|
||||
_12y: create(f._12y.clone(), n._12y.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
let f = MIN_AGE_FILTERS;
|
||||
let n = MIN_AGE_NAMES;
|
||||
Ok(Self {
|
||||
_1d: create(f._1d.clone(), n._1d.id)?,
|
||||
_1w: create(f._1w.clone(), n._1w.id)?,
|
||||
_1m: create(f._1m.clone(), n._1m.id)?,
|
||||
_2m: create(f._2m.clone(), n._2m.id)?,
|
||||
_3m: create(f._3m.clone(), n._3m.id)?,
|
||||
_4m: create(f._4m.clone(), n._4m.id)?,
|
||||
_5m: create(f._5m.clone(), n._5m.id)?,
|
||||
_6m: create(f._6m.clone(), n._6m.id)?,
|
||||
_1y: create(f._1y.clone(), n._1y.id)?,
|
||||
_2y: create(f._2y.clone(), n._2y.id)?,
|
||||
_3y: create(f._3y.clone(), n._3y.id)?,
|
||||
_4y: create(f._4y.clone(), n._4y.id)?,
|
||||
_5y: create(f._5y.clone(), n._5y.id)?,
|
||||
_6y: create(f._6y.clone(), n._6y.id)?,
|
||||
_7y: create(f._7y.clone(), n._7y.id)?,
|
||||
_8y: create(f._8y.clone(), n._8y.id)?,
|
||||
_10y: create(f._10y.clone(), n._10y.id)?,
|
||||
_12y: create(f._12y.clone(), n._12y.id)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
[
|
||||
&self._1d, &self._1w, &self._1m, &self._2m, &self._3m, &self._4m, &self._5m, &self._6m,
|
||||
|
||||
@@ -3,10 +3,56 @@ use std::ops::{Add, AddAssign};
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::OutputType;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::Filter;
|
||||
use super::{CohortName, Filter};
|
||||
|
||||
#[derive(Default, Clone, Debug, Traversable)]
|
||||
/// Spendable type values
|
||||
pub const SPENDABLE_TYPE_VALUES: BySpendableType<OutputType> = BySpendableType {
|
||||
p2pk65: OutputType::P2PK65,
|
||||
p2pk33: OutputType::P2PK33,
|
||||
p2pkh: OutputType::P2PKH,
|
||||
p2ms: OutputType::P2MS,
|
||||
p2sh: OutputType::P2SH,
|
||||
p2wpkh: OutputType::P2WPKH,
|
||||
p2wsh: OutputType::P2WSH,
|
||||
p2tr: OutputType::P2TR,
|
||||
p2a: OutputType::P2A,
|
||||
unknown: OutputType::Unknown,
|
||||
empty: OutputType::Empty,
|
||||
};
|
||||
|
||||
/// Spendable type filters
|
||||
pub const SPENDABLE_TYPE_FILTERS: BySpendableType<Filter> = BySpendableType {
|
||||
p2pk65: Filter::Type(SPENDABLE_TYPE_VALUES.p2pk65),
|
||||
p2pk33: Filter::Type(SPENDABLE_TYPE_VALUES.p2pk33),
|
||||
p2pkh: Filter::Type(SPENDABLE_TYPE_VALUES.p2pkh),
|
||||
p2ms: Filter::Type(SPENDABLE_TYPE_VALUES.p2ms),
|
||||
p2sh: Filter::Type(SPENDABLE_TYPE_VALUES.p2sh),
|
||||
p2wpkh: Filter::Type(SPENDABLE_TYPE_VALUES.p2wpkh),
|
||||
p2wsh: Filter::Type(SPENDABLE_TYPE_VALUES.p2wsh),
|
||||
p2tr: Filter::Type(SPENDABLE_TYPE_VALUES.p2tr),
|
||||
p2a: Filter::Type(SPENDABLE_TYPE_VALUES.p2a),
|
||||
unknown: Filter::Type(SPENDABLE_TYPE_VALUES.unknown),
|
||||
empty: Filter::Type(SPENDABLE_TYPE_VALUES.empty),
|
||||
};
|
||||
|
||||
/// Spendable type names
|
||||
pub const SPENDABLE_TYPE_NAMES: BySpendableType<CohortName> = BySpendableType {
|
||||
p2pk65: CohortName::new("p2pk65", "P2PK65", "Pay to Public Key (65 bytes)"),
|
||||
p2pk33: CohortName::new("p2pk33", "P2PK33", "Pay to Public Key (33 bytes)"),
|
||||
p2pkh: CohortName::new("p2pkh", "P2PKH", "Pay to Public Key Hash"),
|
||||
p2ms: CohortName::new("p2ms", "P2MS", "Pay to Multisig"),
|
||||
p2sh: CohortName::new("p2sh", "P2SH", "Pay to Script Hash"),
|
||||
p2wpkh: CohortName::new("p2wpkh", "P2WPKH", "Pay to Witness Public Key Hash"),
|
||||
p2wsh: CohortName::new("p2wsh", "P2WSH", "Pay to Witness Script Hash"),
|
||||
p2tr: CohortName::new("p2tr", "P2TR", "Pay to Taproot"),
|
||||
p2a: CohortName::new("p2a", "P2A", "Pay to Anchor"),
|
||||
unknown: CohortName::new("unknown_outputs", "Unknown", "Unknown Output Type"),
|
||||
empty: CohortName::new("empty_outputs", "Empty", "Empty Output"),
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Debug, Traversable, Serialize)]
|
||||
pub struct BySpendableType<T> {
|
||||
pub p2pk65: T,
|
||||
pub p2pk33: T,
|
||||
@@ -21,26 +67,55 @@ pub struct BySpendableType<T> {
|
||||
pub empty: T,
|
||||
}
|
||||
|
||||
impl BySpendableType<CohortName> {
|
||||
pub const fn names() -> &'static Self {
|
||||
&SPENDABLE_TYPE_NAMES
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> BySpendableType<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
let f = SPENDABLE_TYPE_FILTERS;
|
||||
let n = SPENDABLE_TYPE_NAMES;
|
||||
Self {
|
||||
p2pk65: create(Filter::Type(OutputType::P2PK65)),
|
||||
p2pk33: create(Filter::Type(OutputType::P2PK33)),
|
||||
p2pkh: create(Filter::Type(OutputType::P2PKH)),
|
||||
p2ms: create(Filter::Type(OutputType::P2MS)),
|
||||
p2sh: create(Filter::Type(OutputType::P2SH)),
|
||||
p2wpkh: create(Filter::Type(OutputType::P2WPKH)),
|
||||
p2wsh: create(Filter::Type(OutputType::P2WSH)),
|
||||
p2tr: create(Filter::Type(OutputType::P2TR)),
|
||||
p2a: create(Filter::Type(OutputType::P2A)),
|
||||
unknown: create(Filter::Type(OutputType::Unknown)),
|
||||
empty: create(Filter::Type(OutputType::Empty)),
|
||||
p2pk65: create(f.p2pk65, n.p2pk65.id),
|
||||
p2pk33: create(f.p2pk33, n.p2pk33.id),
|
||||
p2pkh: create(f.p2pkh, n.p2pkh.id),
|
||||
p2ms: create(f.p2ms, n.p2ms.id),
|
||||
p2sh: create(f.p2sh, n.p2sh.id),
|
||||
p2wpkh: create(f.p2wpkh, n.p2wpkh.id),
|
||||
p2wsh: create(f.p2wsh, n.p2wsh.id),
|
||||
p2tr: create(f.p2tr, n.p2tr.id),
|
||||
p2a: create(f.p2a, n.p2a.id),
|
||||
unknown: create(f.unknown, n.unknown.id),
|
||||
empty: create(f.empty, n.empty.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
let f = SPENDABLE_TYPE_FILTERS;
|
||||
let n = SPENDABLE_TYPE_NAMES;
|
||||
Ok(Self {
|
||||
p2pk65: create(f.p2pk65, n.p2pk65.id)?,
|
||||
p2pk33: create(f.p2pk33, n.p2pk33.id)?,
|
||||
p2pkh: create(f.p2pkh, n.p2pkh.id)?,
|
||||
p2ms: create(f.p2ms, n.p2ms.id)?,
|
||||
p2sh: create(f.p2sh, n.p2sh.id)?,
|
||||
p2wpkh: create(f.p2wpkh, n.p2wpkh.id)?,
|
||||
p2wsh: create(f.p2wsh, n.p2wsh.id)?,
|
||||
p2tr: create(f.p2tr, n.p2tr.id)?,
|
||||
p2a: create(f.p2a, n.p2a.id)?,
|
||||
unknown: create(f.unknown, n.unknown.id)?,
|
||||
empty: create(f.empty, n.empty.id)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, output_type: OutputType) -> &mut T {
|
||||
match output_type {
|
||||
OutputType::P2PK65 => &mut self.p2pk65,
|
||||
|
||||
@@ -1,25 +1,64 @@
|
||||
use brk_traversable::Traversable;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{Filter, Term};
|
||||
use super::{CohortName, Filter, Term};
|
||||
|
||||
#[derive(Default, Clone, Traversable)]
|
||||
/// Term values
|
||||
pub const TERM_VALUES: ByTerm<Term> = ByTerm {
|
||||
short: Term::Sth,
|
||||
long: Term::Lth,
|
||||
};
|
||||
|
||||
/// Term filters
|
||||
pub const TERM_FILTERS: ByTerm<Filter> = ByTerm {
|
||||
short: Filter::Term(TERM_VALUES.short),
|
||||
long: Filter::Term(TERM_VALUES.long),
|
||||
};
|
||||
|
||||
/// Term names
|
||||
pub const TERM_NAMES: ByTerm<CohortName> = ByTerm {
|
||||
short: CohortName::new("sth", "STH", "Short Term Holders"),
|
||||
long: CohortName::new("lth", "LTH", "Long Term Holders"),
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Traversable, Serialize)]
|
||||
pub struct ByTerm<T> {
|
||||
pub short: T,
|
||||
pub long: T,
|
||||
}
|
||||
|
||||
impl ByTerm<CohortName> {
|
||||
pub const fn names() -> &'static Self {
|
||||
&TERM_NAMES
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ByTerm<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
let f = TERM_FILTERS;
|
||||
let n = TERM_NAMES;
|
||||
Self {
|
||||
short: create(Filter::Term(Term::Sth)),
|
||||
long: create(Filter::Term(Term::Lth)),
|
||||
short: create(f.short, n.short.id),
|
||||
long: create(f.long, n.long.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
let f = TERM_FILTERS;
|
||||
let n = TERM_NAMES;
|
||||
Ok(Self {
|
||||
short: create(f.short, n.short.id)?,
|
||||
long: create(f.long, n.long.id)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
[&self.short, &self.long].into_iter()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,77 @@
|
||||
use brk_traversable::Traversable;
|
||||
use brk_types::{Timestamp, Year};
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::Filter;
|
||||
use super::{CohortName, Filter};
|
||||
|
||||
#[derive(Default, Clone, Traversable)]
|
||||
/// Year values
|
||||
pub const YEAR_VALUES: ByYear<Year> = ByYear {
|
||||
_2009: Year::new(2009),
|
||||
_2010: Year::new(2010),
|
||||
_2011: Year::new(2011),
|
||||
_2012: Year::new(2012),
|
||||
_2013: Year::new(2013),
|
||||
_2014: Year::new(2014),
|
||||
_2015: Year::new(2015),
|
||||
_2016: Year::new(2016),
|
||||
_2017: Year::new(2017),
|
||||
_2018: Year::new(2018),
|
||||
_2019: Year::new(2019),
|
||||
_2020: Year::new(2020),
|
||||
_2021: Year::new(2021),
|
||||
_2022: Year::new(2022),
|
||||
_2023: Year::new(2023),
|
||||
_2024: Year::new(2024),
|
||||
_2025: Year::new(2025),
|
||||
_2026: Year::new(2026),
|
||||
};
|
||||
|
||||
/// Year filters
|
||||
pub const YEAR_FILTERS: ByYear<Filter> = ByYear {
|
||||
_2009: Filter::Year(YEAR_VALUES._2009),
|
||||
_2010: Filter::Year(YEAR_VALUES._2010),
|
||||
_2011: Filter::Year(YEAR_VALUES._2011),
|
||||
_2012: Filter::Year(YEAR_VALUES._2012),
|
||||
_2013: Filter::Year(YEAR_VALUES._2013),
|
||||
_2014: Filter::Year(YEAR_VALUES._2014),
|
||||
_2015: Filter::Year(YEAR_VALUES._2015),
|
||||
_2016: Filter::Year(YEAR_VALUES._2016),
|
||||
_2017: Filter::Year(YEAR_VALUES._2017),
|
||||
_2018: Filter::Year(YEAR_VALUES._2018),
|
||||
_2019: Filter::Year(YEAR_VALUES._2019),
|
||||
_2020: Filter::Year(YEAR_VALUES._2020),
|
||||
_2021: Filter::Year(YEAR_VALUES._2021),
|
||||
_2022: Filter::Year(YEAR_VALUES._2022),
|
||||
_2023: Filter::Year(YEAR_VALUES._2023),
|
||||
_2024: Filter::Year(YEAR_VALUES._2024),
|
||||
_2025: Filter::Year(YEAR_VALUES._2025),
|
||||
_2026: Filter::Year(YEAR_VALUES._2026),
|
||||
};
|
||||
|
||||
/// Year names
|
||||
pub const YEAR_NAMES: ByYear<CohortName> = ByYear {
|
||||
_2009: CohortName::new("year_2009", "2009", "Year 2009"),
|
||||
_2010: CohortName::new("year_2010", "2010", "Year 2010"),
|
||||
_2011: CohortName::new("year_2011", "2011", "Year 2011"),
|
||||
_2012: CohortName::new("year_2012", "2012", "Year 2012"),
|
||||
_2013: CohortName::new("year_2013", "2013", "Year 2013"),
|
||||
_2014: CohortName::new("year_2014", "2014", "Year 2014"),
|
||||
_2015: CohortName::new("year_2015", "2015", "Year 2015"),
|
||||
_2016: CohortName::new("year_2016", "2016", "Year 2016"),
|
||||
_2017: CohortName::new("year_2017", "2017", "Year 2017"),
|
||||
_2018: CohortName::new("year_2018", "2018", "Year 2018"),
|
||||
_2019: CohortName::new("year_2019", "2019", "Year 2019"),
|
||||
_2020: CohortName::new("year_2020", "2020", "Year 2020"),
|
||||
_2021: CohortName::new("year_2021", "2021", "Year 2021"),
|
||||
_2022: CohortName::new("year_2022", "2022", "Year 2022"),
|
||||
_2023: CohortName::new("year_2023", "2023", "Year 2023"),
|
||||
_2024: CohortName::new("year_2024", "2024", "Year 2024"),
|
||||
_2025: CohortName::new("year_2025", "2025", "Year 2025"),
|
||||
_2026: CohortName::new("year_2026", "2026", "Year 2026"),
|
||||
};
|
||||
|
||||
#[derive(Default, Clone, Traversable, Serialize)]
|
||||
pub struct ByYear<T> {
|
||||
pub _2009: T,
|
||||
pub _2010: T,
|
||||
@@ -26,33 +93,69 @@ pub struct ByYear<T> {
|
||||
pub _2026: T,
|
||||
}
|
||||
|
||||
impl ByYear<CohortName> {
|
||||
pub const fn names() -> &'static Self {
|
||||
&YEAR_NAMES
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ByYear<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
let f = YEAR_FILTERS;
|
||||
let n = YEAR_NAMES;
|
||||
Self {
|
||||
_2009: create(Filter::Year(Year::new(2009))),
|
||||
_2010: create(Filter::Year(Year::new(2010))),
|
||||
_2011: create(Filter::Year(Year::new(2011))),
|
||||
_2012: create(Filter::Year(Year::new(2012))),
|
||||
_2013: create(Filter::Year(Year::new(2013))),
|
||||
_2014: create(Filter::Year(Year::new(2014))),
|
||||
_2015: create(Filter::Year(Year::new(2015))),
|
||||
_2016: create(Filter::Year(Year::new(2016))),
|
||||
_2017: create(Filter::Year(Year::new(2017))),
|
||||
_2018: create(Filter::Year(Year::new(2018))),
|
||||
_2019: create(Filter::Year(Year::new(2019))),
|
||||
_2020: create(Filter::Year(Year::new(2020))),
|
||||
_2021: create(Filter::Year(Year::new(2021))),
|
||||
_2022: create(Filter::Year(Year::new(2022))),
|
||||
_2023: create(Filter::Year(Year::new(2023))),
|
||||
_2024: create(Filter::Year(Year::new(2024))),
|
||||
_2025: create(Filter::Year(Year::new(2025))),
|
||||
_2026: create(Filter::Year(Year::new(2026))),
|
||||
_2009: create(f._2009, n._2009.id),
|
||||
_2010: create(f._2010, n._2010.id),
|
||||
_2011: create(f._2011, n._2011.id),
|
||||
_2012: create(f._2012, n._2012.id),
|
||||
_2013: create(f._2013, n._2013.id),
|
||||
_2014: create(f._2014, n._2014.id),
|
||||
_2015: create(f._2015, n._2015.id),
|
||||
_2016: create(f._2016, n._2016.id),
|
||||
_2017: create(f._2017, n._2017.id),
|
||||
_2018: create(f._2018, n._2018.id),
|
||||
_2019: create(f._2019, n._2019.id),
|
||||
_2020: create(f._2020, n._2020.id),
|
||||
_2021: create(f._2021, n._2021.id),
|
||||
_2022: create(f._2022, n._2022.id),
|
||||
_2023: create(f._2023, n._2023.id),
|
||||
_2024: create(f._2024, n._2024.id),
|
||||
_2025: create(f._2025, n._2025.id),
|
||||
_2026: create(f._2026, n._2026.id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn try_new<F, E>(mut create: F) -> Result<Self, E>
|
||||
where
|
||||
F: FnMut(Filter, &'static str) -> Result<T, E>,
|
||||
{
|
||||
let f = YEAR_FILTERS;
|
||||
let n = YEAR_NAMES;
|
||||
Ok(Self {
|
||||
_2009: create(f._2009, n._2009.id)?,
|
||||
_2010: create(f._2010, n._2010.id)?,
|
||||
_2011: create(f._2011, n._2011.id)?,
|
||||
_2012: create(f._2012, n._2012.id)?,
|
||||
_2013: create(f._2013, n._2013.id)?,
|
||||
_2014: create(f._2014, n._2014.id)?,
|
||||
_2015: create(f._2015, n._2015.id)?,
|
||||
_2016: create(f._2016, n._2016.id)?,
|
||||
_2017: create(f._2017, n._2017.id)?,
|
||||
_2018: create(f._2018, n._2018.id)?,
|
||||
_2019: create(f._2019, n._2019.id)?,
|
||||
_2020: create(f._2020, n._2020.id)?,
|
||||
_2021: create(f._2021, n._2021.id)?,
|
||||
_2022: create(f._2022, n._2022.id)?,
|
||||
_2023: create(f._2023, n._2023.id)?,
|
||||
_2024: create(f._2024, n._2024.id)?,
|
||||
_2025: create(f._2025, n._2025.id)?,
|
||||
_2026: create(f._2026, n._2026.id)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
[
|
||||
&self._2009,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use super::Filter;
|
||||
|
||||
/// Context for cohort naming - determines whether a prefix is needed.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum CohortContext {
|
||||
@@ -14,4 +16,22 @@ impl CohortContext {
|
||||
CohortContext::Address => "addrs",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prefixed(&self, name: &str) -> String {
|
||||
format!("{}_{}", self.prefix(), name)
|
||||
}
|
||||
|
||||
/// Build full name for a filter, adding prefix only for Time/Amount filters.
|
||||
///
|
||||
/// Prefix rules:
|
||||
/// - No prefix: `All`, `Term`, `Epoch`, `Year`, `Type`
|
||||
/// - Context prefix: `Time`, `Amount`
|
||||
pub fn full_name(&self, filter: &Filter, name: &str) -> String {
|
||||
match filter {
|
||||
Filter::All | Filter::Term(_) | Filter::Epoch(_) | Filter::Year(_) | Filter::Type(_) => {
|
||||
name.to_string()
|
||||
}
|
||||
Filter::Time(_) | Filter::Amount(_) => self.prefixed(name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
use serde::Serialize;
|
||||
|
||||
/// Display names for a cohort with id (for storage/API), short (for charts), and long (for tooltips/labels)
|
||||
#[derive(Clone, Copy, Serialize)]
|
||||
pub struct CohortName {
|
||||
pub id: &'static str,
|
||||
pub short: &'static str,
|
||||
pub long: &'static str,
|
||||
}
|
||||
|
||||
impl CohortName {
|
||||
pub const fn new(id: &'static str, short: &'static str, long: &'static str) -> Self {
|
||||
Self { id, short, long }
|
||||
}
|
||||
}
|
||||
@@ -29,47 +29,6 @@ impl Filter {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_name_suffix(&self) -> String {
|
||||
match self {
|
||||
Filter::All => String::new(),
|
||||
Filter::Term(t) => t.to_name().to_string(),
|
||||
Filter::Time(t) => t.to_name_suffix(),
|
||||
Filter::Amount(a) => a.to_name_suffix(),
|
||||
Filter::Epoch(e) => format!("epoch_{}", usize::from(*e)),
|
||||
Filter::Year(y) => format!("year_{}", u16::from(*y)),
|
||||
Filter::Type(t) => match t {
|
||||
OutputType::P2MS => "p2ms_outputs".to_string(),
|
||||
OutputType::Empty => "empty_outputs".to_string(),
|
||||
OutputType::Unknown => "unknown_outputs".to_string(),
|
||||
_ => format!("{:?}", t).to_lowercase(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the full name for this filter, including context-based prefix.
|
||||
///
|
||||
/// Prefix rules:
|
||||
/// - No prefix: `All`, `Term`, `Epoch`, `Type`
|
||||
/// - `utxos_` prefix: `Time` or `Amount` with `CohortContext::Utxo`
|
||||
/// - `addrs_` prefix: `Amount` with `CohortContext::Address`
|
||||
pub fn to_full_name(&self, context: CohortContext) -> String {
|
||||
let suffix = self.to_name_suffix();
|
||||
if suffix.is_empty() {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
let needs_prefix = match self {
|
||||
Filter::All | Filter::Term(_) | Filter::Epoch(_) | Filter::Year(_) | Filter::Type(_) => false,
|
||||
Filter::Time(_) | Filter::Amount(_) => true,
|
||||
};
|
||||
|
||||
if needs_prefix {
|
||||
format!("{}_{}", context.prefix(), suffix)
|
||||
} else {
|
||||
suffix
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a time value (days) is contained by this filter
|
||||
pub fn contains_time(&self, days: usize) -> bool {
|
||||
match self {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::{CohortContext, Filter};
|
||||
use super::Filter;
|
||||
|
||||
pub trait Filtered {
|
||||
fn filter(&self) -> &Filter;
|
||||
@@ -10,12 +10,4 @@ pub trait Filtered {
|
||||
fn includes_first_day(&self) -> bool {
|
||||
self.filter().includes_first_day()
|
||||
}
|
||||
|
||||
fn name_suffix(&self) -> String {
|
||||
self.filter().to_name_suffix()
|
||||
}
|
||||
|
||||
fn full_name(&self, context: CohortContext) -> String {
|
||||
self.filter().to_full_name(context)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ mod by_term;
|
||||
mod by_type;
|
||||
mod by_unspendable_type;
|
||||
mod cohort_context;
|
||||
mod cohort_name;
|
||||
mod filter;
|
||||
mod filtered;
|
||||
mod state_level;
|
||||
@@ -41,6 +42,7 @@ pub use by_term::*;
|
||||
pub use by_type::*;
|
||||
pub use by_unspendable_type::*;
|
||||
pub use cohort_context::*;
|
||||
pub use cohort_name::*;
|
||||
pub use filter::*;
|
||||
pub use filtered::*;
|
||||
pub use state_level::*;
|
||||
|
||||
@@ -12,11 +12,4 @@ pub enum Term {
|
||||
|
||||
impl Term {
|
||||
pub const THRESHOLD_DAYS: usize = DAYS_5M;
|
||||
|
||||
pub fn to_name(&self) -> &'static str {
|
||||
match self {
|
||||
Term::Sth => "sth",
|
||||
Term::Lth => "lth",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,75 +40,4 @@ impl TimeFilter {
|
||||
TimeFilter::GreaterOrEqual(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_name_suffix(&self) -> String {
|
||||
match self {
|
||||
// Special cases for common filters
|
||||
TimeFilter::LowerThan(1) => "up_to_1d_old".to_string(),
|
||||
TimeFilter::LowerThan(7) => "up_to_1w_old".to_string(),
|
||||
TimeFilter::LowerThan(30) => "up_to_1m_old".to_string(),
|
||||
TimeFilter::LowerThan(60) => "up_to_2m_old".to_string(),
|
||||
TimeFilter::LowerThan(90) => "up_to_3m_old".to_string(),
|
||||
TimeFilter::LowerThan(120) => "up_to_4m_old".to_string(),
|
||||
TimeFilter::LowerThan(150) => "sth".to_string(),
|
||||
TimeFilter::LowerThan(180) => "up_to_6m_old".to_string(),
|
||||
TimeFilter::LowerThan(365) => "up_to_1y_old".to_string(),
|
||||
TimeFilter::LowerThan(730) => "up_to_2y_old".to_string(),
|
||||
TimeFilter::LowerThan(1095) => "up_to_3y_old".to_string(),
|
||||
TimeFilter::LowerThan(1460) => "up_to_4y_old".to_string(),
|
||||
TimeFilter::LowerThan(1825) => "up_to_5y_old".to_string(),
|
||||
TimeFilter::LowerThan(2190) => "up_to_6y_old".to_string(),
|
||||
TimeFilter::LowerThan(2555) => "up_to_7y_old".to_string(),
|
||||
TimeFilter::LowerThan(2920) => "up_to_8y_old".to_string(),
|
||||
TimeFilter::LowerThan(3650) => "up_to_10y_old".to_string(),
|
||||
TimeFilter::LowerThan(4380) => "up_to_12y_old".to_string(),
|
||||
TimeFilter::LowerThan(5475) => "up_to_15y_old".to_string(),
|
||||
|
||||
TimeFilter::GreaterOrEqual(1) => "at_least_1d_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(7) => "at_least_1w_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(30) => "at_least_1m_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(60) => "at_least_2m_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(90) => "at_least_3m_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(120) => "at_least_4m_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(150) => "lth".to_string(),
|
||||
TimeFilter::GreaterOrEqual(180) => "at_least_6m_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(365) => "at_least_1y_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(730) => "at_least_2y_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(1095) => "at_least_3y_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(1460) => "at_least_4y_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(1825) => "at_least_5y_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(2190) => "at_least_6y_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(2555) => "at_least_7y_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(2920) => "at_least_8y_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(3650) => "at_least_10y_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(4380) => "at_least_12y_old".to_string(),
|
||||
TimeFilter::GreaterOrEqual(5475) => "at_least_15y_old".to_string(),
|
||||
|
||||
// Range special cases
|
||||
TimeFilter::Range(r) if *r == (0..1) => "up_to_1d".to_string(),
|
||||
TimeFilter::Range(r) if *r == (1..7) => "at_least_1d_up_to_1w_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (7..30) => "at_least_1w_up_to_1m_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (30..60) => "at_least_1m_up_to_2m_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (60..90) => "at_least_2m_up_to_3m_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (90..120) => "at_least_3m_up_to_4m_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (120..150) => "at_least_4m_up_to_5m_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (150..180) => "at_least_5m_up_to_6m_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (180..365) => "at_least_6m_up_to_1y_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (365..730) => "at_least_1y_up_to_2y_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (730..1095) => "at_least_2y_up_to_3y_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (1095..1460) => "at_least_3y_up_to_4y_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (1460..1825) => "at_least_4y_up_to_5y_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (1825..2190) => "at_least_5y_up_to_6y_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (2190..2555) => "at_least_6y_up_to_7y_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (2555..2920) => "at_least_7y_up_to_8y_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (2920..3650) => "at_least_8y_up_to_10y_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (3650..4380) => "at_least_10y_up_to_12y_old".to_string(),
|
||||
TimeFilter::Range(r) if *r == (4380..5475) => "at_least_12y_up_to_15y_old".to_string(),
|
||||
|
||||
// Fallback generic names
|
||||
TimeFilter::LowerThan(d) => format!("up_to_{}d", d),
|
||||
TimeFilter::GreaterOrEqual(d) => format!("at_least_{}d", d),
|
||||
TimeFilter::Range(r) => format!("{}d_to_{}d", r.start, r.end),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ pub struct UTXOGroups<T> {
|
||||
impl<T> UTXOGroups<T> {
|
||||
pub fn new<F>(mut create: F) -> Self
|
||||
where
|
||||
F: FnMut(Filter) -> T,
|
||||
F: FnMut(Filter, &'static str) -> T,
|
||||
{
|
||||
Self {
|
||||
all: create(Filter::All),
|
||||
all: create(Filter::All, ""),
|
||||
age_range: ByAgeRange::new(&mut create),
|
||||
epoch: ByEpoch::new(&mut create),
|
||||
year: ByYear::new(&mut create),
|
||||
|
||||
Reference in New Issue
Block a user