global: snapshot

This commit is contained in:
nym21
2025-12-29 17:02:17 +01:00
parent 445959f5b9
commit e89a67b9a7
40 changed files with 4057 additions and 1829 deletions
Generated
+4
View File
@@ -576,10 +576,12 @@ dependencies = [
name = "brk_binder" name = "brk_binder"
version = "0.1.0-alpha.1" version = "0.1.0-alpha.1"
dependencies = [ dependencies = [
"brk_grouper",
"brk_query", "brk_query",
"brk_types", "brk_types",
"oas3", "oas3",
"schemars", "schemars",
"serde",
"serde_json", "serde_json",
"vecdb", "vecdb",
] ]
@@ -628,6 +630,7 @@ dependencies = [
name = "brk_client" name = "brk_client"
version = "0.1.0-alpha.1" version = "0.1.0-alpha.1"
dependencies = [ dependencies = [
"brk_grouper",
"brk_types", "brk_types",
"minreq", "minreq",
"serde", "serde",
@@ -698,6 +701,7 @@ dependencies = [
"brk_traversable", "brk_traversable",
"brk_types", "brk_types",
"rayon", "rayon",
"serde",
"vecdb", "vecdb",
] ]
+2
View File
@@ -9,9 +9,11 @@ repository.workspace = true
build = "build.rs" build = "build.rs"
[dependencies] [dependencies]
brk_grouper = { workspace = true }
brk_query = { workspace = true } brk_query = { workspace = true }
brk_types = { workspace = true } brk_types = { workspace = true }
oas3 = "0.20" oas3 = "0.20"
schemars = { workspace = true } schemars = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
vecdb = { workspace = true } vecdb = { workspace = true }
+34
View File
@@ -1,5 +1,9 @@
use std::{collections::HashSet, fmt::Write as FmtWrite, fs, io, path::Path}; use std::{collections::HashSet, fmt::Write as FmtWrite, fs, io, path::Path};
use brk_grouper::{
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, EPOCH_NAMES, GE_AMOUNT_NAMES, LT_AMOUNT_NAMES,
MAX_AGE_NAMES, MIN_AGE_NAMES, SPENDABLE_TYPE_NAMES, TERM_NAMES, YEAR_NAMES,
};
use brk_types::{pools, Index, TreeNode}; use brk_types::{pools, Index, TreeNode};
use serde_json::Value; use serde_json::Value;
@@ -60,6 +64,36 @@ fn generate_constants(output: &mut String) {
writeln!(output, " {}: \"{}\",", pool.slug(), pool.name).unwrap(); writeln!(output, " {}: \"{}\",", pool.slug(), pool.name).unwrap();
} }
writeln!(output, "}});\n").unwrap(); writeln!(output, "}});\n").unwrap();
// Cohort names - serialize from brk_grouper using serde_json
generate_cohort_names(output);
}
fn generate_cohort_names(output: &mut String) {
use serde::Serialize;
fn export_const<T: Serialize>(output: &mut String, name: &str, value: &T) {
let json = serde_json::to_string_pretty(value).unwrap();
writeln!(
output,
"export const {} = /** @type {{const}} */ ({});\n",
name, json
)
.unwrap();
}
writeln!(output, "// Cohort names\n").unwrap();
export_const(output, "TERM_NAMES", &TERM_NAMES);
export_const(output, "EPOCH_NAMES", &EPOCH_NAMES);
export_const(output, "YEAR_NAMES", &YEAR_NAMES);
export_const(output, "SPENDABLE_TYPE_NAMES", &SPENDABLE_TYPE_NAMES);
export_const(output, "AGE_RANGE_NAMES", &AGE_RANGE_NAMES);
export_const(output, "MAX_AGE_NAMES", &MAX_AGE_NAMES);
export_const(output, "MIN_AGE_NAMES", &MIN_AGE_NAMES);
export_const(output, "AMOUNT_RANGE_NAMES", &AMOUNT_RANGE_NAMES);
export_const(output, "GE_AMOUNT_NAMES", &GE_AMOUNT_NAMES);
export_const(output, "LT_AMOUNT_NAMES", &LT_AMOUNT_NAMES);
} }
fn generate_type_definitions(output: &mut String, schemas: &TypeSchemas) { fn generate_type_definitions(output: &mut String, schemas: &TypeSchemas) {
+28
View File
@@ -1,6 +1,11 @@
use std::{collections::HashSet, fmt::Write as FmtWrite, fs, io, path::Path}; use std::{collections::HashSet, fmt::Write as FmtWrite, fs, io, path::Path};
use brk_grouper::{
AGE_RANGE_NAMES, AMOUNT_RANGE_NAMES, EPOCH_NAMES, GE_AMOUNT_NAMES, LT_AMOUNT_NAMES,
MAX_AGE_NAMES, MIN_AGE_NAMES, SPENDABLE_TYPE_NAMES, TERM_NAMES, YEAR_NAMES,
};
use brk_types::{pools, Index, TreeNode}; use brk_types::{pools, Index, TreeNode};
use serde::Serialize;
use serde_json::Value; use serde_json::Value;
use crate::{ use crate::{
@@ -69,6 +74,29 @@ fn generate_constants(output: &mut String) {
writeln!(output, " \"{}\": \"{}\",", pool.slug(), pool.name).unwrap(); writeln!(output, " \"{}\": \"{}\",", pool.slug(), pool.name).unwrap();
} }
writeln!(output, "}}\n").unwrap(); writeln!(output, "}}\n").unwrap();
// Cohort names
generate_cohort_names(output);
}
fn generate_cohort_names(output: &mut String) {
fn export_const<T: Serialize>(output: &mut String, name: &str, value: &T) {
let json = serde_json::to_string_pretty(value).unwrap();
writeln!(output, "{}: Final = {}\n", name, json).unwrap();
}
writeln!(output, "# Cohort names\n").unwrap();
export_const(output, "TERM_NAMES", &TERM_NAMES);
export_const(output, "EPOCH_NAMES", &EPOCH_NAMES);
export_const(output, "YEAR_NAMES", &YEAR_NAMES);
export_const(output, "SPENDABLE_TYPE_NAMES", &SPENDABLE_TYPE_NAMES);
export_const(output, "AGE_RANGE_NAMES", &AGE_RANGE_NAMES);
export_const(output, "MAX_AGE_NAMES", &MAX_AGE_NAMES);
export_const(output, "MIN_AGE_NAMES", &MIN_AGE_NAMES);
export_const(output, "AMOUNT_RANGE_NAMES", &AMOUNT_RANGE_NAMES);
export_const(output, "GE_AMOUNT_NAMES", &GE_AMOUNT_NAMES);
export_const(output, "LT_AMOUNT_NAMES", &LT_AMOUNT_NAMES);
} }
fn generate_type_definitions(output: &mut String, schemas: &TypeSchemas) { fn generate_type_definitions(output: &mut String, schemas: &TypeSchemas) {
+5 -2
View File
@@ -21,7 +21,9 @@ pub fn generate_rust_client(
writeln!(output, "// Auto-generated BRK Rust client").unwrap(); writeln!(output, "// Auto-generated BRK Rust client").unwrap();
writeln!(output, "// Do not edit manually\n").unwrap(); writeln!(output, "// Do not edit manually\n").unwrap();
writeln!(output, "#![allow(non_camel_case_types)]").unwrap(); writeln!(output, "#![allow(non_camel_case_types)]").unwrap();
writeln!(output, "#![allow(dead_code)]\n").unwrap(); writeln!(output, "#![allow(dead_code)]").unwrap();
writeln!(output, "#![allow(unused_variables)]").unwrap();
writeln!(output, "#![allow(clippy::useless_format)]\n").unwrap();
generate_imports(&mut output); generate_imports(&mut output);
generate_base_client(&mut output); generate_base_client(&mut output);
@@ -41,7 +43,8 @@ fn generate_imports(output: &mut String) {
output, output,
r#"use std::sync::Arc; r#"use std::sync::Arc;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use brk_types::*; pub use brk_grouper::*;
pub use brk_types::*;
"# "#
) )
+2 -1
View File
@@ -14,7 +14,8 @@ pub fn to_pascal_case(s: &str) -> String {
/// Convert a string to snake_case, handling Rust keywords. /// Convert a string to snake_case, handling Rust keywords.
pub fn to_snake_case(s: &str) -> String { pub fn to_snake_case(s: &str) -> String {
let sanitized = s.replace('-', "_"); // Convert to lowercase and replace dashes with underscores
let sanitized = s.to_lowercase().replace('-', "_");
// Prefix with _ if starts with digit // Prefix with _ if starts with digit
let sanitized = if sanitized.chars().next().is_some_and(|c| c.is_ascii_digit()) { let sanitized = if sanitized.chars().next().is_some_and(|c| c.is_ascii_digit()) {
+1
View File
@@ -9,6 +9,7 @@ repository.workspace = true
build = "build.rs" build = "build.rs"
[dependencies] [dependencies]
brk_grouper = { workspace = true }
brk_types = { workspace = true } brk_types = { workspace = true }
minreq = { workspace = true } minreq = { workspace = true }
serde = { workspace = true } serde = { workspace = true }
File diff suppressed because it is too large Load Diff
+1 -16
View File
@@ -1,4 +1,4 @@
use brk_types::{Bitcoin, Close, Dollars, High, Sats, StoredF32, StoredF64, StoredU32}; use brk_types::{Bitcoin, Close, Dollars, Sats, StoredF32, StoredF64, StoredU32};
use vecdb::{BinaryTransform, UnaryTransform}; use vecdb::{BinaryTransform, UnaryTransform};
/// (Dollars, Dollars) -> Dollars addition /// (Dollars, Dollars) -> Dollars addition
@@ -320,18 +320,3 @@ impl BinaryTransform<Close<Dollars>, Dollars, StoredF32> for PercentageDiffClose
} }
} }
} }
/// (High<Dollars>, Dollars) -> StoredF32 percentage difference ((a/b - 1) × 100)
/// Used for drawdown calculation from high prices
pub struct PercentageDiffHighDollars;
impl BinaryTransform<High<Dollars>, Dollars, StoredF32> for PercentageDiffHighDollars {
#[inline(always)]
fn apply(high: High<Dollars>, base: Dollars) -> StoredF32 {
if base == Dollars::ZERO {
StoredF32::default()
} else {
StoredF32::from((**high / *base - 1.0) * 100.0)
}
}
}
@@ -50,9 +50,11 @@ impl AddressCohortVecs {
/// ///
/// `all_supply` is the supply metrics from the "all" cohort, used as global /// `all_supply` is the supply metrics from the "all" cohort, used as global
/// sources for `*_rel_to_market_cap` ratios. Pass `None` if not available. /// sources for `*_rel_to_market_cap` ratios. Pass `None` if not available.
#[allow(clippy::too_many_arguments)]
pub fn forced_import( pub fn forced_import(
db: &Database, db: &Database,
filter: Filter, filter: Filter,
name: &str,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
price: Option<&price::Vecs>, price: Option<&price::Vecs>,
@@ -60,11 +62,12 @@ impl AddressCohortVecs {
all_supply: Option<&SupplyMetrics>, all_supply: Option<&SupplyMetrics>,
) -> Result<Self> { ) -> Result<Self> {
let compute_dollars = price.is_some(); let compute_dollars = price.is_some();
let full_name = filter.to_full_name(CohortContext::Address); let full_name = CohortContext::Address.full_name(&filter, name);
let cfg = ImportConfig { let cfg = ImportConfig {
db, db,
filter, filter,
full_name: &full_name,
context: CohortContext::Address, context: CohortContext::Address,
version, version,
indexes, indexes,
@@ -2,11 +2,10 @@ use std::path::Path;
use brk_error::Result; use brk_error::Result;
use brk_grouper::{ use brk_grouper::{
AddressGroups, AmountFilter, ByAmountRange, ByGreatEqualAmount, ByLowerThanAmount, Filter, AddressGroups, ByAmountRange, ByGreatEqualAmount, ByLowerThanAmount, Filter, Filtered,
Filtered,
}; };
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Bitcoin, DateIndex, Dollars, Height, Sats, Version}; use brk_types::{Bitcoin, DateIndex, Dollars, Height, Version};
use derive_deref::{Deref, DerefMut}; use derive_deref::{Deref, DerefMut};
use rayon::prelude::*; use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, IterableVec}; use vecdb::{AnyStoredVec, Database, Exit, IterableVec};
@@ -37,90 +36,19 @@ impl AddressCohorts {
let v = version + VERSION + Version::ZERO; let v = version + VERSION + Version::ZERO;
// Helper to create a cohort - only amount_range cohorts have state // Helper to create a cohort - only amount_range cohorts have state
let create = |filter: Filter, has_state: bool| -> Result<AddressCohortVecs> { let create =
let states_path = if has_state { Some(states_path) } else { None }; |filter: Filter, name: &'static str, has_state: bool| -> Result<AddressCohortVecs> {
AddressCohortVecs::forced_import(db, filter, v, indexes, price, states_path, all_supply) let sp = if has_state { Some(states_path) } else { None };
}; AddressCohortVecs::forced_import(db, filter, name, v, indexes, price, sp, all_supply)
};
let full = |f: Filter| create(f, true); let full = |f: Filter, name: &'static str| create(f, name, true);
let none = |f: Filter| create(f, false); let none = |f: Filter, name: &'static str| create(f, name, false);
Ok(Self(AddressGroups { Ok(Self(AddressGroups {
amount_range: ByAmountRange { amount_range: ByAmountRange::try_new(&full)?,
_0sats: full(Filter::Amount(AmountFilter::LowerThan(Sats::_1)))?, lt_amount: ByLowerThanAmount::try_new(&none)?,
_1sat_to_10sats: full(Filter::Amount(AmountFilter::Range(Sats::_1..Sats::_10)))?, ge_amount: ByGreatEqualAmount::try_new(&none)?,
_10sats_to_100sats: full(Filter::Amount(AmountFilter::Range(
Sats::_10..Sats::_100,
)))?,
_100sats_to_1k_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_100..Sats::_1K,
)))?,
_1k_sats_to_10k_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_1K..Sats::_10K,
)))?,
_10k_sats_to_100k_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_10K..Sats::_100K,
)))?,
_100k_sats_to_1m_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_100K..Sats::_1M,
)))?,
_1m_sats_to_10m_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_1M..Sats::_10M,
)))?,
_10m_sats_to_1btc: full(Filter::Amount(AmountFilter::Range(
Sats::_10M..Sats::_1BTC,
)))?,
_1btc_to_10btc: full(Filter::Amount(AmountFilter::Range(
Sats::_1BTC..Sats::_10BTC,
)))?,
_10btc_to_100btc: full(Filter::Amount(AmountFilter::Range(
Sats::_10BTC..Sats::_100BTC,
)))?,
_100btc_to_1k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_100BTC..Sats::_1K_BTC,
)))?,
_1k_btc_to_10k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_1K_BTC..Sats::_10K_BTC,
)))?,
_10k_btc_to_100k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_10K_BTC..Sats::_100K_BTC,
)))?,
_100k_btc_or_more: full(Filter::Amount(AmountFilter::GreaterOrEqual(
Sats::_100K_BTC,
)))?,
},
lt_amount: ByLowerThanAmount {
_10sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10)))?,
_100sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100)))?,
_1k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K)))?,
_10k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K)))?,
_100k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K)))?,
_1m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1M)))?,
_10m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10M)))?,
_1btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1BTC)))?,
_10btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10BTC)))?,
_100btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100BTC)))?,
_1k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K_BTC)))?,
_10k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K_BTC)))?,
_100k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K_BTC)))?,
},
ge_amount: ByGreatEqualAmount {
_1sat: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1)))?,
_10sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10)))?,
_100sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100)))?,
_1k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K)))?,
_10k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K)))?,
_100k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100K)))?,
_1m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1M)))?,
_10m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10M)))?,
_1btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1BTC)))?,
_10btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10BTC)))?,
_100btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100BTC)))?,
_1k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K_BTC)))?,
_10k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K_BTC)))?,
},
})) }))
} }
@@ -38,6 +38,7 @@ impl UTXOCohortVecs {
pub fn forced_import( pub fn forced_import(
db: &Database, db: &Database,
filter: Filter, filter: Filter,
name: &str,
version: Version, version: Version,
indexes: &indexes::Vecs, indexes: &indexes::Vecs,
price: Option<&price::Vecs>, price: Option<&price::Vecs>,
@@ -46,11 +47,12 @@ impl UTXOCohortVecs {
all_supply: Option<&SupplyMetrics>, all_supply: Option<&SupplyMetrics>,
) -> Result<Self> { ) -> Result<Self> {
let compute_dollars = price.is_some(); let compute_dollars = price.is_some();
let full_name = filter.to_full_name(CohortContext::Utxo); let full_name = CohortContext::Utxo.full_name(&filter, name);
let cfg = ImportConfig { let cfg = ImportConfig {
db, db,
filter, filter,
full_name: &full_name,
context: CohortContext::Utxo, context: CohortContext::Utxo,
version, version,
indexes, indexes,
@@ -6,16 +6,11 @@ use std::path::Path;
use brk_error::Result; use brk_error::Result;
use brk_grouper::{ use brk_grouper::{
AmountFilter, ByAgeRange, ByAmountRange, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount, ByAgeRange, ByAmountRange, ByEpoch, ByGreatEqualAmount, ByLowerThanAmount, ByMaxAge, ByMinAge,
ByMaxAge, ByMinAge, BySpendableType, ByTerm, ByYear, DAYS_1D, DAYS_1M, DAYS_1W, DAYS_1Y, BySpendableType, ByTerm, ByYear, Filter, Filtered, StateLevel, UTXOGroups,
DAYS_2M, DAYS_2Y, DAYS_3M, DAYS_3Y, DAYS_4M, DAYS_4Y, DAYS_5M, DAYS_5Y, DAYS_6M, DAYS_6Y,
DAYS_7Y, DAYS_8Y, DAYS_10Y, DAYS_12Y, DAYS_15Y, Filter, Filtered, StateLevel, Term, TimeFilter,
UTXOGroups,
}; };
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{ use brk_types::{Bitcoin, DateIndex, Dollars, Height, Sats, Version};
Bitcoin, DateIndex, Dollars, HalvingEpoch, Height, OutputType, Sats, Version, Year,
};
use derive_deref::{Deref, DerefMut}; use derive_deref::{Deref, DerefMut};
use rayon::prelude::*; use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, IterableVec}; use vecdb::{AnyStoredVec, Database, Exit, IterableVec};
@@ -50,6 +45,7 @@ impl UTXOCohorts {
let all = UTXOCohortVecs::forced_import( let all = UTXOCohortVecs::forced_import(
db, db,
Filter::All, Filter::All,
"",
version + VERSION + Version::ONE, version + VERSION + Version::ONE,
indexes, indexes,
price, price,
@@ -62,227 +58,34 @@ impl UTXOCohorts {
let all_supply = Some(&all.metrics.supply); let all_supply = Some(&all.metrics.supply);
// Create all cohorts first (while borrowing all_supply), then assemble struct // Create all cohorts first (while borrowing all_supply), then assemble struct
let term = ByTerm { let price_only = |f: Filter, name: &'static str| {
short: UTXOCohortVecs::forced_import(
db,
Filter::Term(Term::Sth),
v,
indexes,
price,
states_path,
StateLevel::PriceOnly,
all_supply,
)?,
long: UTXOCohortVecs::forced_import(
db,
Filter::Term(Term::Lth),
v,
indexes,
price,
states_path,
StateLevel::PriceOnly,
all_supply,
)?,
};
let full = |f: Filter| {
UTXOCohortVecs::forced_import( UTXOCohortVecs::forced_import(
db, db, f, name, v, indexes, price, states_path, StateLevel::PriceOnly, all_supply,
f,
v,
indexes,
price,
states_path,
StateLevel::Full,
all_supply,
)
};
let none = |f: Filter| {
UTXOCohortVecs::forced_import(
db,
f,
v,
indexes,
price,
states_path,
StateLevel::None,
all_supply,
) )
}; };
let epoch = ByEpoch { let term = ByTerm::try_new(&price_only)?;
_0: full(Filter::Epoch(HalvingEpoch::new(0)))?,
_1: full(Filter::Epoch(HalvingEpoch::new(1)))?, let full = |f: Filter, name: &'static str| {
_2: full(Filter::Epoch(HalvingEpoch::new(2)))?, UTXOCohortVecs::forced_import(
_3: full(Filter::Epoch(HalvingEpoch::new(3)))?, db, f, name, v, indexes, price, states_path, StateLevel::Full, all_supply,
_4: full(Filter::Epoch(HalvingEpoch::new(4)))?, )
};
let none = |f: Filter, name: &'static str| {
UTXOCohortVecs::forced_import(
db, f, name, v, indexes, price, states_path, StateLevel::None, all_supply,
)
}; };
let year = ByYear { let epoch = ByEpoch::try_new(&full)?;
_2009: full(Filter::Year(Year::new(2009)))?, let year = ByYear::try_new(&full)?;
_2010: full(Filter::Year(Year::new(2010)))?, let type_ = BySpendableType::try_new(&full)?;
_2011: full(Filter::Year(Year::new(2011)))?, let max_age = ByMaxAge::try_new(&none)?;
_2012: full(Filter::Year(Year::new(2012)))?, let min_age = ByMinAge::try_new(&none)?;
_2013: full(Filter::Year(Year::new(2013)))?, let age_range = ByAgeRange::try_new(&full)?;
_2014: full(Filter::Year(Year::new(2014)))?, let amount_range = ByAmountRange::try_new(&full)?;
_2015: full(Filter::Year(Year::new(2015)))?, let lt_amount = ByLowerThanAmount::try_new(&none)?;
_2016: full(Filter::Year(Year::new(2016)))?, let ge_amount = ByGreatEqualAmount::try_new(&none)?;
_2017: full(Filter::Year(Year::new(2017)))?,
_2018: full(Filter::Year(Year::new(2018)))?,
_2019: full(Filter::Year(Year::new(2019)))?,
_2020: full(Filter::Year(Year::new(2020)))?,
_2021: full(Filter::Year(Year::new(2021)))?,
_2022: full(Filter::Year(Year::new(2022)))?,
_2023: full(Filter::Year(Year::new(2023)))?,
_2024: full(Filter::Year(Year::new(2024)))?,
_2025: full(Filter::Year(Year::new(2025)))?,
_2026: full(Filter::Year(Year::new(2026)))?,
};
let type_ = BySpendableType {
p2pk65: full(Filter::Type(OutputType::P2PK65))?,
p2pk33: full(Filter::Type(OutputType::P2PK33))?,
p2pkh: full(Filter::Type(OutputType::P2PKH))?,
p2sh: full(Filter::Type(OutputType::P2SH))?,
p2wpkh: full(Filter::Type(OutputType::P2WPKH))?,
p2wsh: full(Filter::Type(OutputType::P2WSH))?,
p2tr: full(Filter::Type(OutputType::P2TR))?,
p2a: full(Filter::Type(OutputType::P2A))?,
p2ms: full(Filter::Type(OutputType::P2MS))?,
empty: full(Filter::Type(OutputType::Empty))?,
unknown: full(Filter::Type(OutputType::Unknown))?,
};
let max_age = ByMaxAge {
_1w: none(Filter::Time(TimeFilter::LowerThan(DAYS_1W)))?,
_1m: none(Filter::Time(TimeFilter::LowerThan(DAYS_1M)))?,
_2m: none(Filter::Time(TimeFilter::LowerThan(DAYS_2M)))?,
_3m: none(Filter::Time(TimeFilter::LowerThan(DAYS_3M)))?,
_4m: none(Filter::Time(TimeFilter::LowerThan(DAYS_4M)))?,
_5m: none(Filter::Time(TimeFilter::LowerThan(DAYS_5M)))?,
_6m: none(Filter::Time(TimeFilter::LowerThan(DAYS_6M)))?,
_1y: none(Filter::Time(TimeFilter::LowerThan(DAYS_1Y)))?,
_2y: none(Filter::Time(TimeFilter::LowerThan(DAYS_2Y)))?,
_3y: none(Filter::Time(TimeFilter::LowerThan(DAYS_3Y)))?,
_4y: none(Filter::Time(TimeFilter::LowerThan(DAYS_4Y)))?,
_5y: none(Filter::Time(TimeFilter::LowerThan(DAYS_5Y)))?,
_6y: none(Filter::Time(TimeFilter::LowerThan(DAYS_6Y)))?,
_7y: none(Filter::Time(TimeFilter::LowerThan(DAYS_7Y)))?,
_8y: none(Filter::Time(TimeFilter::LowerThan(DAYS_8Y)))?,
_10y: none(Filter::Time(TimeFilter::LowerThan(DAYS_10Y)))?,
_12y: none(Filter::Time(TimeFilter::LowerThan(DAYS_12Y)))?,
_15y: none(Filter::Time(TimeFilter::LowerThan(DAYS_15Y)))?,
};
let min_age = ByMinAge {
_1d: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1D)))?,
_1w: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1W)))?,
_1m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1M)))?,
_2m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2M)))?,
_3m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3M)))?,
_4m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4M)))?,
_5m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5M)))?,
_6m: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6M)))?,
_1y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1Y)))?,
_2y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2Y)))?,
_3y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3Y)))?,
_4y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4Y)))?,
_5y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5Y)))?,
_6y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6Y)))?,
_7y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_7Y)))?,
_8y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_8Y)))?,
_10y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_10Y)))?,
_12y: none(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_12Y)))?,
};
let age_range = ByAgeRange {
up_to_1d: full(Filter::Time(TimeFilter::Range(0..DAYS_1D)))?,
_1d_to_1w: full(Filter::Time(TimeFilter::Range(DAYS_1D..DAYS_1W)))?,
_1w_to_1m: full(Filter::Time(TimeFilter::Range(DAYS_1W..DAYS_1M)))?,
_1m_to_2m: full(Filter::Time(TimeFilter::Range(DAYS_1M..DAYS_2M)))?,
_2m_to_3m: full(Filter::Time(TimeFilter::Range(DAYS_2M..DAYS_3M)))?,
_3m_to_4m: full(Filter::Time(TimeFilter::Range(DAYS_3M..DAYS_4M)))?,
_4m_to_5m: full(Filter::Time(TimeFilter::Range(DAYS_4M..DAYS_5M)))?,
_5m_to_6m: full(Filter::Time(TimeFilter::Range(DAYS_5M..DAYS_6M)))?,
_6m_to_1y: full(Filter::Time(TimeFilter::Range(DAYS_6M..DAYS_1Y)))?,
_1y_to_2y: full(Filter::Time(TimeFilter::Range(DAYS_1Y..DAYS_2Y)))?,
_2y_to_3y: full(Filter::Time(TimeFilter::Range(DAYS_2Y..DAYS_3Y)))?,
_3y_to_4y: full(Filter::Time(TimeFilter::Range(DAYS_3Y..DAYS_4Y)))?,
_4y_to_5y: full(Filter::Time(TimeFilter::Range(DAYS_4Y..DAYS_5Y)))?,
_5y_to_6y: full(Filter::Time(TimeFilter::Range(DAYS_5Y..DAYS_6Y)))?,
_6y_to_7y: full(Filter::Time(TimeFilter::Range(DAYS_6Y..DAYS_7Y)))?,
_7y_to_8y: full(Filter::Time(TimeFilter::Range(DAYS_7Y..DAYS_8Y)))?,
_8y_to_10y: full(Filter::Time(TimeFilter::Range(DAYS_8Y..DAYS_10Y)))?,
_10y_to_12y: full(Filter::Time(TimeFilter::Range(DAYS_10Y..DAYS_12Y)))?,
_12y_to_15y: full(Filter::Time(TimeFilter::Range(DAYS_12Y..DAYS_15Y)))?,
from_15y: full(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_15Y)))?,
};
let amount_range = ByAmountRange {
_0sats: full(Filter::Amount(AmountFilter::LowerThan(Sats::_1)))?,
_1sat_to_10sats: full(Filter::Amount(AmountFilter::Range(Sats::_1..Sats::_10)))?,
_10sats_to_100sats: full(Filter::Amount(AmountFilter::Range(Sats::_10..Sats::_100)))?,
_100sats_to_1k_sats: full(Filter::Amount(AmountFilter::Range(Sats::_100..Sats::_1K)))?,
_1k_sats_to_10k_sats: full(Filter::Amount(AmountFilter::Range(Sats::_1K..Sats::_10K)))?,
_10k_sats_to_100k_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_10K..Sats::_100K,
)))?,
_100k_sats_to_1m_sats: full(Filter::Amount(AmountFilter::Range(
Sats::_100K..Sats::_1M,
)))?,
_1m_sats_to_10m_sats: full(Filter::Amount(AmountFilter::Range(Sats::_1M..Sats::_10M)))?,
_10m_sats_to_1btc: full(Filter::Amount(AmountFilter::Range(Sats::_10M..Sats::_1BTC)))?,
_1btc_to_10btc: full(Filter::Amount(AmountFilter::Range(
Sats::_1BTC..Sats::_10BTC,
)))?,
_10btc_to_100btc: full(Filter::Amount(AmountFilter::Range(
Sats::_10BTC..Sats::_100BTC,
)))?,
_100btc_to_1k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_100BTC..Sats::_1K_BTC,
)))?,
_1k_btc_to_10k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_1K_BTC..Sats::_10K_BTC,
)))?,
_10k_btc_to_100k_btc: full(Filter::Amount(AmountFilter::Range(
Sats::_10K_BTC..Sats::_100K_BTC,
)))?,
_100k_btc_or_more: full(Filter::Amount(AmountFilter::GreaterOrEqual(
Sats::_100K_BTC,
)))?,
};
let lt_amount = ByLowerThanAmount {
_10sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10)))?,
_100sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100)))?,
_1k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K)))?,
_10k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K)))?,
_100k_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K)))?,
_1m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1M)))?,
_10m_sats: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10M)))?,
_1btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1BTC)))?,
_10btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10BTC)))?,
_100btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100BTC)))?,
_1k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_1K_BTC)))?,
_10k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_10K_BTC)))?,
_100k_btc: none(Filter::Amount(AmountFilter::LowerThan(Sats::_100K_BTC)))?,
};
let ge_amount = ByGreatEqualAmount {
_1sat: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1)))?,
_10sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10)))?,
_100sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100)))?,
_1k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K)))?,
_10k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K)))?,
_100k_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100K)))?,
_1m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1M)))?,
_10m_sats: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10M)))?,
_1btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1BTC)))?,
_10btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10BTC)))?,
_100btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100BTC)))?,
_1k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K_BTC)))?,
_10k_btc: none(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K_BTC)))?,
};
Ok(Self(UTXOGroups { Ok(Self(UTXOGroups {
all, all,
@@ -8,6 +8,7 @@ use crate::{indexes, price};
pub struct ImportConfig<'a> { pub struct ImportConfig<'a> {
pub db: &'a Database, pub db: &'a Database,
pub filter: Filter, pub filter: Filter,
pub full_name: &'a str,
pub context: CohortContext, pub context: CohortContext,
pub version: Version, pub version: Version,
pub indexes: &'a indexes::Vecs, pub indexes: &'a indexes::Vecs,
@@ -37,11 +38,10 @@ impl<'a> ImportConfig<'a> {
/// Get full metric name with filter prefix. /// Get full metric name with filter prefix.
pub fn name(&self, suffix: &str) -> String { pub fn name(&self, suffix: &str) -> String {
let prefix = self.filter.to_full_name(self.context); if self.full_name.is_empty() {
if prefix.is_empty() {
suffix.to_string() suffix.to_string()
} else { } else {
format!("{prefix}_{suffix}") format!("{}_{suffix}", self.full_name)
} }
} }
} }
+1
View File
@@ -14,3 +14,4 @@ brk_types = { workspace = true }
brk_traversable = { workspace = true } brk_traversable = { workspace = true }
vecdb = { workspace = true } vecdb = { workspace = true }
rayon = { workspace = true } rayon = { workspace = true }
serde = { workspace = true }
+3 -2
View File
@@ -38,8 +38,9 @@ let filter = Filter::Time(TimeFilter::GreaterOrEqual(155));
filter.contains_time(200); // true filter.contains_time(200); // true
filter.contains_amount(sats); filter.contains_amount(sats);
// Generate metric names // Generate metric names (via CohortContext)
filter.to_full_name(CohortContext::Utxo); // "utxos_min_age_155d" let ctx = CohortContext::Utxo;
ctx.full_name(&filter, "min_age_155d"); // "utxos_min_age_155d"
``` ```
## Built On ## Built On
+12 -1
View File
@@ -16,7 +16,7 @@ pub struct AddressGroups<T> {
impl<T> AddressGroups<T> { impl<T> AddressGroups<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
Self { Self {
ge_amount: ByGreatEqualAmount::new(&mut create), 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> { pub fn iter(&self) -> impl Iterator<Item = &T> {
self.ge_amount self.ge_amount
.iter() .iter()
-36
View File
@@ -33,40 +33,4 @@ impl AmountFilter {
AmountFilter::Range(_) => false, 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)),
}
} }
+136 -23
View File
@@ -1,7 +1,10 @@
use std::ops::Range;
use brk_traversable::Traversable; use brk_traversable::Traversable;
use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::iter::{IntoParallelIterator, ParallelIterator};
use serde::Serialize;
use super::{Filter, TimeFilter}; use super::{CohortName, Filter, TimeFilter};
// Age boundary constants in days // Age boundary constants in days
pub const DAYS_1D: usize = 1; 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, 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 struct ByAgeRange<T> {
pub up_to_1d: T, pub up_to_1d: T,
pub _1d_to_1w: T, pub _1d_to_1w: T,
@@ -85,32 +166,64 @@ impl<T> ByAgeRange<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
let f = AGE_RANGE_FILTERS;
let n = AGE_RANGE_NAMES;
Self { Self {
up_to_1d: create(Filter::Time(TimeFilter::Range(0..DAYS_1D))), up_to_1d: create(f.up_to_1d.clone(), n.up_to_1d.id),
_1d_to_1w: create(Filter::Time(TimeFilter::Range(DAYS_1D..DAYS_1W))), _1d_to_1w: create(f._1d_to_1w.clone(), n._1d_to_1w.id),
_1w_to_1m: create(Filter::Time(TimeFilter::Range(DAYS_1W..DAYS_1M))), _1w_to_1m: create(f._1w_to_1m.clone(), n._1w_to_1m.id),
_1m_to_2m: create(Filter::Time(TimeFilter::Range(DAYS_1M..DAYS_2M))), _1m_to_2m: create(f._1m_to_2m.clone(), n._1m_to_2m.id),
_2m_to_3m: create(Filter::Time(TimeFilter::Range(DAYS_2M..DAYS_3M))), _2m_to_3m: create(f._2m_to_3m.clone(), n._2m_to_3m.id),
_3m_to_4m: create(Filter::Time(TimeFilter::Range(DAYS_3M..DAYS_4M))), _3m_to_4m: create(f._3m_to_4m.clone(), n._3m_to_4m.id),
_4m_to_5m: create(Filter::Time(TimeFilter::Range(DAYS_4M..DAYS_5M))), _4m_to_5m: create(f._4m_to_5m.clone(), n._4m_to_5m.id),
_5m_to_6m: create(Filter::Time(TimeFilter::Range(DAYS_5M..DAYS_6M))), _5m_to_6m: create(f._5m_to_6m.clone(), n._5m_to_6m.id),
_6m_to_1y: create(Filter::Time(TimeFilter::Range(DAYS_6M..DAYS_1Y))), _6m_to_1y: create(f._6m_to_1y.clone(), n._6m_to_1y.id),
_1y_to_2y: create(Filter::Time(TimeFilter::Range(DAYS_1Y..DAYS_2Y))), _1y_to_2y: create(f._1y_to_2y.clone(), n._1y_to_2y.id),
_2y_to_3y: create(Filter::Time(TimeFilter::Range(DAYS_2Y..DAYS_3Y))), _2y_to_3y: create(f._2y_to_3y.clone(), n._2y_to_3y.id),
_3y_to_4y: create(Filter::Time(TimeFilter::Range(DAYS_3Y..DAYS_4Y))), _3y_to_4y: create(f._3y_to_4y.clone(), n._3y_to_4y.id),
_4y_to_5y: create(Filter::Time(TimeFilter::Range(DAYS_4Y..DAYS_5Y))), _4y_to_5y: create(f._4y_to_5y.clone(), n._4y_to_5y.id),
_5y_to_6y: create(Filter::Time(TimeFilter::Range(DAYS_5Y..DAYS_6Y))), _5y_to_6y: create(f._5y_to_6y.clone(), n._5y_to_6y.id),
_6y_to_7y: create(Filter::Time(TimeFilter::Range(DAYS_6Y..DAYS_7Y))), _6y_to_7y: create(f._6y_to_7y.clone(), n._6y_to_7y.id),
_7y_to_8y: create(Filter::Time(TimeFilter::Range(DAYS_7Y..DAYS_8Y))), _7y_to_8y: create(f._7y_to_8y.clone(), n._7y_to_8y.id),
_8y_to_10y: create(Filter::Time(TimeFilter::Range(DAYS_8Y..DAYS_10Y))), _8y_to_10y: create(f._8y_to_10y.clone(), n._8y_to_10y.id),
_10y_to_12y: create(Filter::Time(TimeFilter::Range(DAYS_10Y..DAYS_12Y))), _10y_to_12y: create(f._10y_to_12y.clone(), n._10y_to_12y.id),
_12y_to_15y: create(Filter::Time(TimeFilter::Range(DAYS_12Y..DAYS_15Y))), _12y_to_15y: create(f._12y_to_15y.clone(), n._12y_to_15y.id),
from_15y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_15Y))), 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> { pub fn iter(&self) -> impl Iterator<Item = &T> {
[ [
&self.up_to_1d, &self.up_to_1d,
+175 -39
View File
@@ -1,10 +1,11 @@
use std::ops::{Add, AddAssign}; use std::ops::{Add, AddAssign, Range};
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::Sats; use brk_types::Sats;
use rayon::prelude::*; 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. /// Bucket index for amount ranges. Use for cheap comparisons and direct lookups.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[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) 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 struct ByAmountRange<T> {
pub _0sats: T, pub _0sats: T,
pub _1sat_to_10sats: T, pub _1sat_to_10sats: T,
@@ -76,50 +178,84 @@ pub struct ByAmountRange<T> {
pub _100k_btc_or_more: T, pub _100k_btc_or_more: T,
} }
impl ByAmountRange<CohortName> {
pub const fn names() -> &'static Self {
&AMOUNT_RANGE_NAMES
}
}
impl<T> ByAmountRange<T> { impl<T> ByAmountRange<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
let f = AMOUNT_RANGE_FILTERS;
let n = AMOUNT_RANGE_NAMES;
Self { Self {
_0sats: create(Filter::Amount(AmountFilter::Range(Sats::ZERO..Sats::_1))), _0sats: create(f._0sats.clone(), n._0sats.id),
_1sat_to_10sats: create(Filter::Amount(AmountFilter::Range(Sats::_1..Sats::_10))), _1sat_to_10sats: create(f._1sat_to_10sats.clone(), n._1sat_to_10sats.id),
_10sats_to_100sats: create(Filter::Amount(AmountFilter::Range(Sats::_10..Sats::_100))), _10sats_to_100sats: create(f._10sats_to_100sats.clone(), n._10sats_to_100sats.id),
_100sats_to_1k_sats: create(Filter::Amount(AmountFilter::Range(Sats::_100..Sats::_1K))), _100sats_to_1k_sats: create(f._100sats_to_1k_sats.clone(), n._100sats_to_1k_sats.id),
_1k_sats_to_10k_sats: create(Filter::Amount(AmountFilter::Range( _1k_sats_to_10k_sats: create(f._1k_sats_to_10k_sats.clone(), n._1k_sats_to_10k_sats.id),
Sats::_1K..Sats::_10K, _10k_sats_to_100k_sats: create(
))), f._10k_sats_to_100k_sats.clone(),
_10k_sats_to_100k_sats: create(Filter::Amount(AmountFilter::Range( n._10k_sats_to_100k_sats.id,
Sats::_10K..Sats::_100K, ),
))), _100k_sats_to_1m_sats: create(
_100k_sats_to_1m_sats: create(Filter::Amount(AmountFilter::Range( f._100k_sats_to_1m_sats.clone(),
Sats::_100K..Sats::_1M, n._100k_sats_to_1m_sats.id,
))), ),
_1m_sats_to_10m_sats: create(Filter::Amount(AmountFilter::Range( _1m_sats_to_10m_sats: create(f._1m_sats_to_10m_sats.clone(), n._1m_sats_to_10m_sats.id),
Sats::_1M..Sats::_10M, _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),
_10m_sats_to_1btc: create(Filter::Amount(AmountFilter::Range(Sats::_10M..Sats::_1BTC))), _10btc_to_100btc: create(f._10btc_to_100btc.clone(), n._10btc_to_100btc.id),
_1btc_to_10btc: create(Filter::Amount(AmountFilter::Range( _100btc_to_1k_btc: create(f._100btc_to_1k_btc.clone(), n._100btc_to_1k_btc.id),
Sats::_1BTC..Sats::_10BTC, _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),
_10btc_to_100btc: create(Filter::Amount(AmountFilter::Range( _100k_btc_or_more: create(f._100k_btc_or_more.clone(), n._100k_btc_or_more.id),
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,
))),
} }
} }
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)] #[inline(always)]
pub fn get(&self, value: Sats) -> &T { pub fn get(&self, value: Sats) -> &T {
match AmountBucket::from(value).0 { match AmountBucket::from(value).0 {
+59 -8
View File
@@ -1,10 +1,38 @@
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{HalvingEpoch, Height}; use brk_types::{HalvingEpoch, Height};
use rayon::iter::{IntoParallelIterator, ParallelIterator}; 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 struct ByEpoch<T> {
pub _0: T, pub _0: T,
pub _1: T, pub _1: T,
@@ -13,20 +41,43 @@ pub struct ByEpoch<T> {
pub _4: T, pub _4: T,
} }
impl ByEpoch<CohortName> {
pub const fn names() -> &'static Self {
&EPOCH_NAMES
}
}
impl<T> ByEpoch<T> { impl<T> ByEpoch<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
let f = EPOCH_FILTERS;
let n = EPOCH_NAMES;
Self { Self {
_0: create(Filter::Epoch(HalvingEpoch::new(0))), _0: create(f._0, n._0.id),
_1: create(Filter::Epoch(HalvingEpoch::new(1))), _1: create(f._1, n._1.id),
_2: create(Filter::Epoch(HalvingEpoch::new(2))), _2: create(f._2, n._2.id),
_3: create(Filter::Epoch(HalvingEpoch::new(3))), _3: create(f._3, n._3.id),
_4: create(Filter::Epoch(HalvingEpoch::new(4))), _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> { pub fn iter(&self) -> impl Iterator<Item = &T> {
[&self._0, &self._1, &self._2, &self._3, &self._4].into_iter() [&self._0, &self._1, &self._2, &self._3, &self._4].into_iter()
} }
+101 -16
View File
@@ -1,10 +1,64 @@
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::Sats; use brk_types::Sats;
use rayon::prelude::*; 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 struct ByGreatEqualAmount<T> {
pub _1sat: T, pub _1sat: T,
pub _10sats: T, pub _10sats: T,
@@ -21,28 +75,59 @@ pub struct ByGreatEqualAmount<T> {
pub _10k_btc: T, pub _10k_btc: T,
} }
impl ByGreatEqualAmount<CohortName> {
pub const fn names() -> &'static Self {
&GE_AMOUNT_NAMES
}
}
impl<T> ByGreatEqualAmount<T> { impl<T> ByGreatEqualAmount<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
let f = GE_AMOUNT_FILTERS;
let n = GE_AMOUNT_NAMES;
Self { Self {
_1sat: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1))), _1sat: create(f._1sat.clone(), n._1sat.id),
_10sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10))), _10sats: create(f._10sats.clone(), n._10sats.id),
_100sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100))), _100sats: create(f._100sats.clone(), n._100sats.id),
_1k_sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K))), _1k_sats: create(f._1k_sats.clone(), n._1k_sats.id),
_10k_sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K))), _10k_sats: create(f._10k_sats.clone(), n._10k_sats.id),
_100k_sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100K))), _100k_sats: create(f._100k_sats.clone(), n._100k_sats.id),
_1m_sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1M))), _1m_sats: create(f._1m_sats.clone(), n._1m_sats.id),
_10m_sats: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10M))), _10m_sats: create(f._10m_sats.clone(), n._10m_sats.id),
_1btc: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1BTC))), _1btc: create(f._1btc.clone(), n._1btc.id),
_10btc: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10BTC))), _10btc: create(f._10btc.clone(), n._10btc.id),
_100btc: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_100BTC))), _100btc: create(f._100btc.clone(), n._100btc.id),
_1k_btc: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_1K_BTC))), _1k_btc: create(f._1k_btc.clone(), n._1k_btc.id),
_10k_btc: create(Filter::Amount(AmountFilter::GreaterOrEqual(Sats::_10K_BTC))), _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> { pub fn iter(&self) -> impl Iterator<Item = &T> {
[ [
&self._1sat, &self._1sat,
+99 -16
View File
@@ -1,10 +1,62 @@
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::Sats; use brk_types::Sats;
use rayon::prelude::*; 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 struct ByLowerThanAmount<T> {
pub _10sats: T, pub _10sats: T,
pub _100sats: T, pub _100sats: T,
@@ -21,28 +73,59 @@ pub struct ByLowerThanAmount<T> {
pub _100k_btc: T, pub _100k_btc: T,
} }
impl ByLowerThanAmount<CohortName> {
pub const fn names() -> &'static Self {
&LT_AMOUNT_NAMES
}
}
impl<T> ByLowerThanAmount<T> { impl<T> ByLowerThanAmount<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
let f = LT_AMOUNT_FILTERS;
let n = LT_AMOUNT_NAMES;
Self { Self {
_10sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_10))), _10sats: create(f._10sats.clone(), n._10sats.id),
_100sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_100))), _100sats: create(f._100sats.clone(), n._100sats.id),
_1k_sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_1K))), _1k_sats: create(f._1k_sats.clone(), n._1k_sats.id),
_10k_sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_10K))), _10k_sats: create(f._10k_sats.clone(), n._10k_sats.id),
_100k_sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_100K))), _100k_sats: create(f._100k_sats.clone(), n._100k_sats.id),
_1m_sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_1M))), _1m_sats: create(f._1m_sats.clone(), n._1m_sats.id),
_10m_sats: create(Filter::Amount(AmountFilter::LowerThan(Sats::_10M))), _10m_sats: create(f._10m_sats.clone(), n._10m_sats.id),
_1btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_1BTC))), _1btc: create(f._1btc.clone(), n._1btc.id),
_10btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_10BTC))), _10btc: create(f._10btc.clone(), n._10btc.id),
_100btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_100BTC))), _100btc: create(f._100btc.clone(), n._100btc.id),
_1k_btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_1K_BTC))), _1k_btc: create(f._1k_btc.clone(), n._1k_btc.id),
_10k_btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_10K_BTC))), _10k_btc: create(f._10k_btc.clone(), n._10k_btc.id),
_100k_btc: create(Filter::Amount(AmountFilter::LowerThan(Sats::_100K_BTC))), _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> { pub fn iter(&self) -> impl Iterator<Item = &T> {
[ [
&self._10sats, &self._10sats,
+126 -22
View File
@@ -1,12 +1,80 @@
use brk_traversable::Traversable; use brk_traversable::Traversable;
use rayon::prelude::*; use rayon::prelude::*;
use serde::Serialize;
use super::{ use super::{
Filter, TimeFilter, DAYS_10Y, DAYS_12Y, DAYS_15Y, DAYS_1M, DAYS_1W, DAYS_1Y, DAYS_2M, DAYS_2Y, CohortName, Filter, TimeFilter, DAYS_10Y, DAYS_12Y, DAYS_15Y, DAYS_1M, DAYS_1W, DAYS_1Y,
DAYS_3M, DAYS_3Y, DAYS_4M, DAYS_4Y, DAYS_5M, DAYS_5Y, DAYS_6M, DAYS_6Y, DAYS_7Y, DAYS_8Y, 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 struct ByMaxAge<T> {
pub _1w: T, pub _1w: T,
pub _1m: T, pub _1m: T,
@@ -28,33 +96,69 @@ pub struct ByMaxAge<T> {
pub _15y: T, pub _15y: T,
} }
impl ByMaxAge<CohortName> {
pub const fn names() -> &'static Self {
&MAX_AGE_NAMES
}
}
impl<T> ByMaxAge<T> { impl<T> ByMaxAge<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
let f = MAX_AGE_FILTERS;
let n = MAX_AGE_NAMES;
Self { Self {
_1w: create(Filter::Time(TimeFilter::LowerThan(DAYS_1W))), _1w: create(f._1w.clone(), n._1w.id),
_1m: create(Filter::Time(TimeFilter::LowerThan(DAYS_1M))), _1m: create(f._1m.clone(), n._1m.id),
_2m: create(Filter::Time(TimeFilter::LowerThan(DAYS_2M))), _2m: create(f._2m.clone(), n._2m.id),
_3m: create(Filter::Time(TimeFilter::LowerThan(DAYS_3M))), _3m: create(f._3m.clone(), n._3m.id),
_4m: create(Filter::Time(TimeFilter::LowerThan(DAYS_4M))), _4m: create(f._4m.clone(), n._4m.id),
_5m: create(Filter::Time(TimeFilter::LowerThan(DAYS_5M))), _5m: create(f._5m.clone(), n._5m.id),
_6m: create(Filter::Time(TimeFilter::LowerThan(DAYS_6M))), _6m: create(f._6m.clone(), n._6m.id),
_1y: create(Filter::Time(TimeFilter::LowerThan(DAYS_1Y))), _1y: create(f._1y.clone(), n._1y.id),
_2y: create(Filter::Time(TimeFilter::LowerThan(DAYS_2Y))), _2y: create(f._2y.clone(), n._2y.id),
_3y: create(Filter::Time(TimeFilter::LowerThan(DAYS_3Y))), _3y: create(f._3y.clone(), n._3y.id),
_4y: create(Filter::Time(TimeFilter::LowerThan(DAYS_4Y))), _4y: create(f._4y.clone(), n._4y.id),
_5y: create(Filter::Time(TimeFilter::LowerThan(DAYS_5Y))), _5y: create(f._5y.clone(), n._5y.id),
_6y: create(Filter::Time(TimeFilter::LowerThan(DAYS_6Y))), _6y: create(f._6y.clone(), n._6y.id),
_7y: create(Filter::Time(TimeFilter::LowerThan(DAYS_7Y))), _7y: create(f._7y.clone(), n._7y.id),
_8y: create(Filter::Time(TimeFilter::LowerThan(DAYS_8Y))), _8y: create(f._8y.clone(), n._8y.id),
_10y: create(Filter::Time(TimeFilter::LowerThan(DAYS_10Y))), _10y: create(f._10y.clone(), n._10y.id),
_12y: create(Filter::Time(TimeFilter::LowerThan(DAYS_12Y))), _12y: create(f._12y.clone(), n._12y.id),
_15y: create(Filter::Time(TimeFilter::LowerThan(DAYS_15Y))), _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> { pub fn iter(&self) -> impl Iterator<Item = &T> {
[ [
&self._1w, &self._1m, &self._2m, &self._3m, &self._4m, &self._5m, &self._6m, &self._1y, &self._1w, &self._1m, &self._2m, &self._3m, &self._4m, &self._5m, &self._6m, &self._1y,
+126 -22
View File
@@ -1,12 +1,80 @@
use brk_traversable::Traversable; use brk_traversable::Traversable;
use rayon::prelude::*; use rayon::prelude::*;
use serde::Serialize;
use super::{ use super::{
Filter, TimeFilter, DAYS_10Y, DAYS_12Y, DAYS_1D, DAYS_1M, DAYS_1W, DAYS_1Y, DAYS_2M, DAYS_2Y, CohortName, Filter, TimeFilter, DAYS_10Y, DAYS_12Y, DAYS_1D, DAYS_1M, DAYS_1W, DAYS_1Y,
DAYS_3M, DAYS_3Y, DAYS_4M, DAYS_4Y, DAYS_5M, DAYS_5Y, DAYS_6M, DAYS_6Y, DAYS_7Y, DAYS_8Y, 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 struct ByMinAge<T> {
pub _1d: T, pub _1d: T,
pub _1w: T, pub _1w: T,
@@ -28,33 +96,69 @@ pub struct ByMinAge<T> {
pub _12y: T, pub _12y: T,
} }
impl ByMinAge<CohortName> {
pub const fn names() -> &'static Self {
&MIN_AGE_NAMES
}
}
impl<T> ByMinAge<T> { impl<T> ByMinAge<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
let f = MIN_AGE_FILTERS;
let n = MIN_AGE_NAMES;
Self { Self {
_1d: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1D))), _1d: create(f._1d.clone(), n._1d.id),
_1w: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1W))), _1w: create(f._1w.clone(), n._1w.id),
_1m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1M))), _1m: create(f._1m.clone(), n._1m.id),
_2m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2M))), _2m: create(f._2m.clone(), n._2m.id),
_3m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3M))), _3m: create(f._3m.clone(), n._3m.id),
_4m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4M))), _4m: create(f._4m.clone(), n._4m.id),
_5m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5M))), _5m: create(f._5m.clone(), n._5m.id),
_6m: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6M))), _6m: create(f._6m.clone(), n._6m.id),
_1y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_1Y))), _1y: create(f._1y.clone(), n._1y.id),
_2y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_2Y))), _2y: create(f._2y.clone(), n._2y.id),
_3y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_3Y))), _3y: create(f._3y.clone(), n._3y.id),
_4y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_4Y))), _4y: create(f._4y.clone(), n._4y.id),
_5y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_5Y))), _5y: create(f._5y.clone(), n._5y.id),
_6y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_6Y))), _6y: create(f._6y.clone(), n._6y.id),
_7y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_7Y))), _7y: create(f._7y.clone(), n._7y.id),
_8y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_8Y))), _8y: create(f._8y.clone(), n._8y.id),
_10y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_10Y))), _10y: create(f._10y.clone(), n._10y.id),
_12y: create(Filter::Time(TimeFilter::GreaterOrEqual(DAYS_12Y))), _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> { pub fn iter(&self) -> impl Iterator<Item = &T> {
[ [
&self._1d, &self._1w, &self._1m, &self._2m, &self._3m, &self._4m, &self._5m, &self._6m, &self._1d, &self._1w, &self._1m, &self._2m, &self._3m, &self._4m, &self._5m, &self._6m,
+89 -14
View File
@@ -3,10 +3,56 @@ use std::ops::{Add, AddAssign};
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::OutputType; use brk_types::OutputType;
use rayon::iter::{IntoParallelIterator, ParallelIterator}; 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 struct BySpendableType<T> {
pub p2pk65: T, pub p2pk65: T,
pub p2pk33: T, pub p2pk33: T,
@@ -21,26 +67,55 @@ pub struct BySpendableType<T> {
pub empty: T, pub empty: T,
} }
impl BySpendableType<CohortName> {
pub const fn names() -> &'static Self {
&SPENDABLE_TYPE_NAMES
}
}
impl<T> BySpendableType<T> { impl<T> BySpendableType<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
let f = SPENDABLE_TYPE_FILTERS;
let n = SPENDABLE_TYPE_NAMES;
Self { Self {
p2pk65: create(Filter::Type(OutputType::P2PK65)), p2pk65: create(f.p2pk65, n.p2pk65.id),
p2pk33: create(Filter::Type(OutputType::P2PK33)), p2pk33: create(f.p2pk33, n.p2pk33.id),
p2pkh: create(Filter::Type(OutputType::P2PKH)), p2pkh: create(f.p2pkh, n.p2pkh.id),
p2ms: create(Filter::Type(OutputType::P2MS)), p2ms: create(f.p2ms, n.p2ms.id),
p2sh: create(Filter::Type(OutputType::P2SH)), p2sh: create(f.p2sh, n.p2sh.id),
p2wpkh: create(Filter::Type(OutputType::P2WPKH)), p2wpkh: create(f.p2wpkh, n.p2wpkh.id),
p2wsh: create(Filter::Type(OutputType::P2WSH)), p2wsh: create(f.p2wsh, n.p2wsh.id),
p2tr: create(Filter::Type(OutputType::P2TR)), p2tr: create(f.p2tr, n.p2tr.id),
p2a: create(Filter::Type(OutputType::P2A)), p2a: create(f.p2a, n.p2a.id),
unknown: create(Filter::Type(OutputType::Unknown)), unknown: create(f.unknown, n.unknown.id),
empty: create(Filter::Type(OutputType::Empty)), 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 { pub fn get_mut(&mut self, output_type: OutputType) -> &mut T {
match output_type { match output_type {
OutputType::P2PK65 => &mut self.p2pk65, OutputType::P2PK65 => &mut self.p2pk65,
+44 -5
View File
@@ -1,25 +1,64 @@
use brk_traversable::Traversable; use brk_traversable::Traversable;
use rayon::iter::{IntoParallelIterator, ParallelIterator}; 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 struct ByTerm<T> {
pub short: T, pub short: T,
pub long: T, pub long: T,
} }
impl ByTerm<CohortName> {
pub const fn names() -> &'static Self {
&TERM_NAMES
}
}
impl<T> ByTerm<T> { impl<T> ByTerm<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
let f = TERM_FILTERS;
let n = TERM_NAMES;
Self { Self {
short: create(Filter::Term(Term::Sth)), short: create(f.short, n.short.id),
long: create(Filter::Term(Term::Lth)), 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> { pub fn iter(&self) -> impl Iterator<Item = &T> {
[&self.short, &self.long].into_iter() [&self.short, &self.long].into_iter()
} }
+124 -21
View File
@@ -1,10 +1,77 @@
use brk_traversable::Traversable; use brk_traversable::Traversable;
use brk_types::{Timestamp, Year}; use brk_types::{Timestamp, Year};
use rayon::iter::{IntoParallelIterator, ParallelIterator}; 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 struct ByYear<T> {
pub _2009: T, pub _2009: T,
pub _2010: T, pub _2010: T,
@@ -26,33 +93,69 @@ pub struct ByYear<T> {
pub _2026: T, pub _2026: T,
} }
impl ByYear<CohortName> {
pub const fn names() -> &'static Self {
&YEAR_NAMES
}
}
impl<T> ByYear<T> { impl<T> ByYear<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
let f = YEAR_FILTERS;
let n = YEAR_NAMES;
Self { Self {
_2009: create(Filter::Year(Year::new(2009))), _2009: create(f._2009, n._2009.id),
_2010: create(Filter::Year(Year::new(2010))), _2010: create(f._2010, n._2010.id),
_2011: create(Filter::Year(Year::new(2011))), _2011: create(f._2011, n._2011.id),
_2012: create(Filter::Year(Year::new(2012))), _2012: create(f._2012, n._2012.id),
_2013: create(Filter::Year(Year::new(2013))), _2013: create(f._2013, n._2013.id),
_2014: create(Filter::Year(Year::new(2014))), _2014: create(f._2014, n._2014.id),
_2015: create(Filter::Year(Year::new(2015))), _2015: create(f._2015, n._2015.id),
_2016: create(Filter::Year(Year::new(2016))), _2016: create(f._2016, n._2016.id),
_2017: create(Filter::Year(Year::new(2017))), _2017: create(f._2017, n._2017.id),
_2018: create(Filter::Year(Year::new(2018))), _2018: create(f._2018, n._2018.id),
_2019: create(Filter::Year(Year::new(2019))), _2019: create(f._2019, n._2019.id),
_2020: create(Filter::Year(Year::new(2020))), _2020: create(f._2020, n._2020.id),
_2021: create(Filter::Year(Year::new(2021))), _2021: create(f._2021, n._2021.id),
_2022: create(Filter::Year(Year::new(2022))), _2022: create(f._2022, n._2022.id),
_2023: create(Filter::Year(Year::new(2023))), _2023: create(f._2023, n._2023.id),
_2024: create(Filter::Year(Year::new(2024))), _2024: create(f._2024, n._2024.id),
_2025: create(Filter::Year(Year::new(2025))), _2025: create(f._2025, n._2025.id),
_2026: create(Filter::Year(Year::new(2026))), _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> { pub fn iter(&self) -> impl Iterator<Item = &T> {
[ [
&self._2009, &self._2009,
+20
View File
@@ -1,3 +1,5 @@
use super::Filter;
/// Context for cohort naming - determines whether a prefix is needed. /// Context for cohort naming - determines whether a prefix is needed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CohortContext { pub enum CohortContext {
@@ -14,4 +16,22 @@ impl CohortContext {
CohortContext::Address => "addrs", 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),
}
}
} }
+15
View File
@@ -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 }
}
}
-41
View File
@@ -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 /// Check if a time value (days) is contained by this filter
pub fn contains_time(&self, days: usize) -> bool { pub fn contains_time(&self, days: usize) -> bool {
match self { match self {
+1 -9
View File
@@ -1,4 +1,4 @@
use super::{CohortContext, Filter}; use super::Filter;
pub trait Filtered { pub trait Filtered {
fn filter(&self) -> &Filter; fn filter(&self) -> &Filter;
@@ -10,12 +10,4 @@ pub trait Filtered {
fn includes_first_day(&self) -> bool { fn includes_first_day(&self) -> bool {
self.filter().includes_first_day() 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)
}
} }
+2
View File
@@ -17,6 +17,7 @@ mod by_term;
mod by_type; mod by_type;
mod by_unspendable_type; mod by_unspendable_type;
mod cohort_context; mod cohort_context;
mod cohort_name;
mod filter; mod filter;
mod filtered; mod filtered;
mod state_level; mod state_level;
@@ -41,6 +42,7 @@ pub use by_term::*;
pub use by_type::*; pub use by_type::*;
pub use by_unspendable_type::*; pub use by_unspendable_type::*;
pub use cohort_context::*; pub use cohort_context::*;
pub use cohort_name::*;
pub use filter::*; pub use filter::*;
pub use filtered::*; pub use filtered::*;
pub use state_level::*; pub use state_level::*;
-7
View File
@@ -12,11 +12,4 @@ pub enum Term {
impl Term { impl Term {
pub const THRESHOLD_DAYS: usize = DAYS_5M; pub const THRESHOLD_DAYS: usize = DAYS_5M;
pub fn to_name(&self) -> &'static str {
match self {
Term::Sth => "sth",
Term::Lth => "lth",
}
}
} }
-71
View File
@@ -40,75 +40,4 @@ impl TimeFilter {
TimeFilter::GreaterOrEqual(_) => false, 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),
}
}
} }
+2 -2
View File
@@ -24,10 +24,10 @@ pub struct UTXOGroups<T> {
impl<T> UTXOGroups<T> { impl<T> UTXOGroups<T> {
pub fn new<F>(mut create: F) -> Self pub fn new<F>(mut create: F) -> Self
where where
F: FnMut(Filter) -> T, F: FnMut(Filter, &'static str) -> T,
{ {
Self { Self {
all: create(Filter::All), all: create(Filter::All, ""),
age_range: ByAgeRange::new(&mut create), age_range: ByAgeRange::new(&mut create),
epoch: ByEpoch::new(&mut create), epoch: ByEpoch::new(&mut create),
year: ByYear::new(&mut create), year: ByYear::new(&mut create),
+1 -1
View File
@@ -28,7 +28,7 @@ pub const BLOCKS_PER_HALVING: u32 = 210_000;
pub struct HalvingEpoch(u16); pub struct HalvingEpoch(u16);
impl HalvingEpoch { impl HalvingEpoch {
pub fn new(value: u16) -> Self { pub const fn new(value: u16) -> Self {
Self(value) Self(value)
} }
} }
+1 -1
View File
@@ -17,7 +17,7 @@ pub struct Year(u16);
impl Year { impl Year {
pub const GENESIS: Self = Self(2009); pub const GENESIS: Self = Self(2009);
pub fn new(value: u16) -> Self { pub const fn new(value: u16) -> Self {
Self(value) Self(value)
} }
+1345 -404
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff