global: snapshot

This commit is contained in:
nym21
2026-03-08 12:06:55 +01:00
parent 6bb5c63db7
commit 7f1f6044dc
8 changed files with 226 additions and 21 deletions

View File

@@ -5645,6 +5645,7 @@ pub struct MetricsTree_Distribution_UtxoCohorts {
pub lt_amount: MetricsTree_Distribution_UtxoCohorts_LtAmount,
pub type_: MetricsTree_Distribution_UtxoCohorts_Type,
pub profitability: MetricsTree_Distribution_UtxoCohorts_Profitability,
pub matured: MetricsTree_Distribution_UtxoCohorts_Matured,
}
impl MetricsTree_Distribution_UtxoCohorts {
@@ -5663,6 +5664,7 @@ impl MetricsTree_Distribution_UtxoCohorts {
lt_amount: MetricsTree_Distribution_UtxoCohorts_LtAmount::new(client.clone(), format!("{base_path}_lt_amount")),
type_: MetricsTree_Distribution_UtxoCohorts_Type::new(client.clone(), format!("{base_path}_type_")),
profitability: MetricsTree_Distribution_UtxoCohorts_Profitability::new(client.clone(), format!("{base_path}_profitability")),
matured: MetricsTree_Distribution_UtxoCohorts_Matured::new(client.clone(), format!("{base_path}_matured")),
}
}
}
@@ -6341,6 +6343,59 @@ impl MetricsTree_Distribution_UtxoCohorts_Profitability_Loss {
}
}
/// Metrics tree node.
pub struct MetricsTree_Distribution_UtxoCohorts_Matured {
pub up_to_1h: BtcCentsSatsUsdPattern,
pub _1h_to_1d: BtcCentsSatsUsdPattern,
pub _1d_to_1w: BtcCentsSatsUsdPattern,
pub _1w_to_1m: BtcCentsSatsUsdPattern,
pub _1m_to_2m: BtcCentsSatsUsdPattern,
pub _2m_to_3m: BtcCentsSatsUsdPattern,
pub _3m_to_4m: BtcCentsSatsUsdPattern,
pub _4m_to_5m: BtcCentsSatsUsdPattern,
pub _5m_to_6m: BtcCentsSatsUsdPattern,
pub _6m_to_1y: BtcCentsSatsUsdPattern,
pub _1y_to_2y: BtcCentsSatsUsdPattern,
pub _2y_to_3y: BtcCentsSatsUsdPattern,
pub _3y_to_4y: BtcCentsSatsUsdPattern,
pub _4y_to_5y: BtcCentsSatsUsdPattern,
pub _5y_to_6y: BtcCentsSatsUsdPattern,
pub _6y_to_7y: BtcCentsSatsUsdPattern,
pub _7y_to_8y: BtcCentsSatsUsdPattern,
pub _8y_to_10y: BtcCentsSatsUsdPattern,
pub _10y_to_12y: BtcCentsSatsUsdPattern,
pub _12y_to_15y: BtcCentsSatsUsdPattern,
pub from_15y: BtcCentsSatsUsdPattern,
}
impl MetricsTree_Distribution_UtxoCohorts_Matured {
pub fn new(client: Arc<BrkClientBase>, base_path: String) -> Self {
Self {
up_to_1h: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_under_1h_old_matured".to_string()),
_1h_to_1d: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_1h_to_1d_old_matured".to_string()),
_1d_to_1w: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_1d_to_1w_old_matured".to_string()),
_1w_to_1m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_1w_to_1m_old_matured".to_string()),
_1m_to_2m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_1m_to_2m_old_matured".to_string()),
_2m_to_3m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_2m_to_3m_old_matured".to_string()),
_3m_to_4m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_3m_to_4m_old_matured".to_string()),
_4m_to_5m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_4m_to_5m_old_matured".to_string()),
_5m_to_6m: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_5m_to_6m_old_matured".to_string()),
_6m_to_1y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_6m_to_1y_old_matured".to_string()),
_1y_to_2y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_1y_to_2y_old_matured".to_string()),
_2y_to_3y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_2y_to_3y_old_matured".to_string()),
_3y_to_4y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_3y_to_4y_old_matured".to_string()),
_4y_to_5y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_4y_to_5y_old_matured".to_string()),
_5y_to_6y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_5y_to_6y_old_matured".to_string()),
_6y_to_7y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_6y_to_7y_old_matured".to_string()),
_7y_to_8y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_7y_to_8y_old_matured".to_string()),
_8y_to_10y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_8y_to_10y_old_matured".to_string()),
_10y_to_12y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_10y_to_12y_old_matured".to_string()),
_12y_to_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_12y_to_15y_old_matured".to_string()),
from_15y: BtcCentsSatsUsdPattern::new(client.clone(), "utxo_over_15y_old_matured".to_string()),
}
}
}
/// Metrics tree node.
pub struct MetricsTree_Distribution_AddressCohorts {
pub ge_amount: MetricsTree_Distribution_AddressCohorts_GeAmount,

View File

@@ -200,6 +200,33 @@ impl<T> ByAgeRange<T> {
}
}
pub fn from_array(arr: [T; 21]) -> Self {
let [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20] = arr;
Self {
up_to_1h: a0,
_1h_to_1d: a1,
_1d_to_1w: a2,
_1w_to_1m: a3,
_1m_to_2m: a4,
_2m_to_3m: a5,
_3m_to_4m: a6,
_4m_to_5m: a7,
_5m_to_6m: a8,
_6m_to_1y: a9,
_1y_to_2y: a10,
_2y_to_3y: a11,
_3y_to_4y: a12,
_4y_to_5y: a13,
_5y_to_6y: a14,
_6y_to_7y: a15,
_7y_to_8y: a16,
_8y_to_10y: a17,
_10y_to_12y: a18,
_12y_to_15y: a19,
from_15y: a20,
}
}
pub fn new<F>(mut create: F) -> Self
where
F: FnMut(Filter, &'static str) -> T,

View File

@@ -6,22 +6,28 @@ use brk_cohort::{
};
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Dollars, Height, Indexes, Version};
use brk_types::{Dollars, Height, Indexes, Sats, Version};
use rayon::prelude::*;
use vecdb::{AnyStoredVec, Database, Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode};
use vecdb::{AnyStoredVec, Database, Exit, ReadOnlyClone, ReadableVec, Rw, StorageMode, WritableVec};
use crate::{blocks, distribution::DynCohortVecs, indexes, prices};
use crate::distribution::metrics::{
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig, MinimalCohortMetrics,
ProfitabilityMetrics, SupplyMetrics,
use crate::{
blocks,
distribution::{
DynCohortVecs,
metrics::{
AllCohortMetrics, BasicCohortMetrics, CohortMetricsBase, CoreCohortMetrics,
ExtendedAdjustedCohortMetrics, ExtendedCohortMetrics, ImportConfig,
MinimalCohortMetrics, ProfitabilityMetrics, SupplyMetrics,
},
state::UTXOCohortState,
},
indexes,
internal::ValueFromHeight,
prices,
};
use super::{percentiles::PercentileCache, vecs::UTXOCohortVecs};
use crate::distribution::state::UTXOCohortState;
const VERSION: Version = Version::new(0);
/// All UTXO cohorts organized by filter type.
@@ -40,6 +46,7 @@ pub struct UTXOCohorts<M: StorageMode = Rw> {
pub lt_amount: ByLowerThanAmount<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
pub type_: BySpendableType<UTXOCohortVecs<MinimalCohortMetrics<M>>>,
pub profitability: ProfitabilityMetrics<M>,
pub matured: ByAgeRange<ValueFromHeight<M>>,
#[traversable(skip)]
pub(super) percentile_cache: PercentileCache,
/// Cached partition_point positions for tick_tock boundary searches.
@@ -218,6 +225,10 @@ impl UTXOCohorts<Rw> {
let lt_amount = ByLowerThanAmount::try_new(&minimal_no_state)?;
let ge_amount = ByGreatEqualAmount::try_new(&minimal_no_state)?;
let matured = ByAgeRange::try_new(&|_f: Filter, name: &'static str| -> Result<ValueFromHeight> {
ValueFromHeight::forced_import(db, &format!("utxo_{name}_matured"), v, indexes)
})?;
Ok(Self {
all,
sth,
@@ -232,11 +243,24 @@ impl UTXOCohorts<Rw> {
lt_amount,
ge_amount,
profitability,
matured,
percentile_cache: PercentileCache::default(),
tick_tock_cached_positions: [0; 20],
})
}
/// Push maturation sats to the matured vecs for the given height.
pub(crate) fn push_maturation(
&mut self,
height: Height,
matured: &ByAgeRange<Sats>,
) -> Result<()> {
for (v, &sats) in self.matured.iter_mut().zip(matured.iter()) {
v.sats.height.truncate_push(height, sats)?;
}
Ok(())
}
pub(crate) fn par_iter_separate_mut(
&mut self,
) -> impl ParallelIterator<Item = &mut dyn DynCohortVecs> {
@@ -383,6 +407,11 @@ impl UTXOCohorts<Rw> {
.try_for_each(|v| v.compute_rest_part1(blocks, prices, starting_indexes, exit))?;
}
// Compute matured cents from sats × price
self.matured
.par_iter_mut()
.try_for_each(|v| v.compute(prices, starting_indexes.height, exit))?;
Ok(())
}
@@ -596,6 +625,11 @@ impl UTXOCohorts<Rw> {
vecs.extend(v.metrics.collect_all_vecs_mut());
}
vecs.extend(self.profitability.collect_all_vecs_mut());
for v in self.matured.iter_mut() {
let base = &mut v.base;
vecs.push(&mut base.sats.height);
vecs.push(&mut base.cents.height);
}
vecs.into_par_iter()
}
@@ -609,6 +643,7 @@ impl UTXOCohorts<Rw> {
pub(crate) fn min_separate_stateful_height_len(&self) -> Height {
self.iter_separate()
.map(|v| Height::from(v.min_stateful_height_len()))
.chain(self.matured.iter().map(|v| Height::from(v.min_stateful_len())))
.min()
.unwrap_or_default()
.min(Height::from(self.profitability.min_stateful_height_len()))

View File

@@ -1,5 +1,5 @@
use brk_cohort::AGE_BOUNDARIES;
use brk_types::{CostBasisSnapshot, ONE_HOUR_IN_SEC, Timestamp};
use brk_cohort::{AGE_BOUNDARIES, ByAgeRange};
use brk_types::{CostBasisSnapshot, ONE_HOUR_IN_SEC, Sats, Timestamp};
use vecdb::{Rw, unlikely};
use crate::distribution::state::BlockState;
@@ -15,13 +15,16 @@ impl UTXOCohorts<Rw> {
/// Uses cached positions per boundary to avoid binary search.
/// Since timestamps are monotonic, positions only advance forward.
/// Complexity: O(k * c) where k = 20 boundaries, c = ~1 (forward scan steps).
///
/// Returns how many sats matured INTO each cohort from the younger adjacent one.
/// `up_to_1h` is always zero since nothing ages into the youngest cohort.
pub(crate) fn tick_tock_next_block(
&mut self,
chain_state: &[BlockState],
timestamp: Timestamp,
) {
) -> ByAgeRange<Sats> {
if chain_state.is_empty() {
return;
return ByAgeRange::default();
}
let prev_timestamp = chain_state.last().unwrap().timestamp;
@@ -29,9 +32,11 @@ impl UTXOCohorts<Rw> {
// Skip if no time has passed
if elapsed == 0 {
return;
return ByAgeRange::default();
}
let mut matured = [Sats::ZERO; 21];
// Get age_range cohort states (indexed 0..21)
// Cohort i covers hours [BOUNDARIES[i-1], BOUNDARIES[i])
// Cohort 0 covers [0, 1) hours
@@ -87,7 +92,10 @@ impl UTXOCohorts<Rw> {
if let Some(state) = age_cohorts[boundary_idx + 1].as_mut() {
state.increment_snapshot(&snapshot);
}
matured[boundary_idx + 1] += block_state.supply.value;
}
}
ByAgeRange::from_array(matured)
}
}

View File

@@ -268,11 +268,8 @@ pub(crate) fn process_blocks(
};
// Process outputs, inputs, and tick-tock in parallel via rayon::join
let (_, oi_result) = rayon::join(
|| {
vecs.utxo_cohorts
.tick_tock_next_block(chain_state, timestamp);
},
let (matured, oi_result) = rayon::join(
|| vecs.utxo_cohorts.tick_tock_next_block(chain_state, timestamp),
|| -> Result<_> {
let (outputs_result, inputs_result) = rayon::join(
|| {
@@ -355,6 +352,9 @@ pub(crate) fn process_blocks(
timestamp,
});
// Record maturation (sats crossing age boundaries)
vecs.utxo_cohorts.push_maturation(height, &matured)?;
// Build set of addresses that received this block (for detecting "both" in sent)
// Reuse pre-allocated hashsets: clear preserves capacity, avoiding reallocation
received_addresses.values_mut().for_each(|set| set.clear());

View File

@@ -4,7 +4,7 @@ mod rolling_sum;
use brk_error::Result;
use brk_traversable::Traversable;
use brk_types::{Bitcoin, Cents, Dollars, Sats, Version};
use vecdb::{Database, ReadableCloneableVec, Rw, StorageMode};
use vecdb::{AnyVec, Database, ReadableCloneableVec, Rw, StorageMode};
use crate::{
indexes,
@@ -57,6 +57,10 @@ impl ByUnit {
usd,
})
}
pub(crate) fn min_stateful_len(&self) -> usize {
self.sats.height.len()
}
}
impl Windows<ByUnit> {

View File

@@ -4941,6 +4941,7 @@ function create_24hPattern(client, acc) {
* @property {MetricsTree_Distribution_UtxoCohorts_LtAmount} ltAmount
* @property {MetricsTree_Distribution_UtxoCohorts_Type} type
* @property {MetricsTree_Distribution_UtxoCohorts_Profitability} profitability
* @property {MetricsTree_Distribution_UtxoCohorts_Matured} matured
*/
/**
@@ -5253,6 +5254,31 @@ function create_24hPattern(client, acc) {
* @property {RealizedSupplyPattern} _90pct
*/
/**
* @typedef {Object} MetricsTree_Distribution_UtxoCohorts_Matured
* @property {BtcCentsSatsUsdPattern} upTo1h
* @property {BtcCentsSatsUsdPattern} _1hTo1d
* @property {BtcCentsSatsUsdPattern} _1dTo1w
* @property {BtcCentsSatsUsdPattern} _1wTo1m
* @property {BtcCentsSatsUsdPattern} _1mTo2m
* @property {BtcCentsSatsUsdPattern} _2mTo3m
* @property {BtcCentsSatsUsdPattern} _3mTo4m
* @property {BtcCentsSatsUsdPattern} _4mTo5m
* @property {BtcCentsSatsUsdPattern} _5mTo6m
* @property {BtcCentsSatsUsdPattern} _6mTo1y
* @property {BtcCentsSatsUsdPattern} _1yTo2y
* @property {BtcCentsSatsUsdPattern} _2yTo3y
* @property {BtcCentsSatsUsdPattern} _3yTo4y
* @property {BtcCentsSatsUsdPattern} _4yTo5y
* @property {BtcCentsSatsUsdPattern} _5yTo6y
* @property {BtcCentsSatsUsdPattern} _6yTo7y
* @property {BtcCentsSatsUsdPattern} _7yTo8y
* @property {BtcCentsSatsUsdPattern} _8yTo10y
* @property {BtcCentsSatsUsdPattern} _10yTo12y
* @property {BtcCentsSatsUsdPattern} _12yTo15y
* @property {BtcCentsSatsUsdPattern} from15y
*/
/**
* @typedef {Object} MetricsTree_Distribution_AddressCohorts
* @property {MetricsTree_Distribution_AddressCohorts_GeAmount} geAmount
@@ -7514,6 +7540,29 @@ class BrkClient extends BrkClientBase {
_90pct: createRealizedSupplyPattern(this, 'loss_ge_90pct'),
},
},
matured: {
upTo1h: createBtcCentsSatsUsdPattern(this, 'utxo_under_1h_old_matured'),
_1hTo1d: createBtcCentsSatsUsdPattern(this, 'utxo_1h_to_1d_old_matured'),
_1dTo1w: createBtcCentsSatsUsdPattern(this, 'utxo_1d_to_1w_old_matured'),
_1wTo1m: createBtcCentsSatsUsdPattern(this, 'utxo_1w_to_1m_old_matured'),
_1mTo2m: createBtcCentsSatsUsdPattern(this, 'utxo_1m_to_2m_old_matured'),
_2mTo3m: createBtcCentsSatsUsdPattern(this, 'utxo_2m_to_3m_old_matured'),
_3mTo4m: createBtcCentsSatsUsdPattern(this, 'utxo_3m_to_4m_old_matured'),
_4mTo5m: createBtcCentsSatsUsdPattern(this, 'utxo_4m_to_5m_old_matured'),
_5mTo6m: createBtcCentsSatsUsdPattern(this, 'utxo_5m_to_6m_old_matured'),
_6mTo1y: createBtcCentsSatsUsdPattern(this, 'utxo_6m_to_1y_old_matured'),
_1yTo2y: createBtcCentsSatsUsdPattern(this, 'utxo_1y_to_2y_old_matured'),
_2yTo3y: createBtcCentsSatsUsdPattern(this, 'utxo_2y_to_3y_old_matured'),
_3yTo4y: createBtcCentsSatsUsdPattern(this, 'utxo_3y_to_4y_old_matured'),
_4yTo5y: createBtcCentsSatsUsdPattern(this, 'utxo_4y_to_5y_old_matured'),
_5yTo6y: createBtcCentsSatsUsdPattern(this, 'utxo_5y_to_6y_old_matured'),
_6yTo7y: createBtcCentsSatsUsdPattern(this, 'utxo_6y_to_7y_old_matured'),
_7yTo8y: createBtcCentsSatsUsdPattern(this, 'utxo_7y_to_8y_old_matured'),
_8yTo10y: createBtcCentsSatsUsdPattern(this, 'utxo_8y_to_10y_old_matured'),
_10yTo12y: createBtcCentsSatsUsdPattern(this, 'utxo_10y_to_12y_old_matured'),
_12yTo15y: createBtcCentsSatsUsdPattern(this, 'utxo_12y_to_15y_old_matured'),
from15y: createBtcCentsSatsUsdPattern(this, 'utxo_over_15y_old_matured'),
},
},
addressCohorts: {
geAmount: {

View File

@@ -4648,6 +4648,32 @@ class MetricsTree_Distribution_UtxoCohorts_Profitability:
self.profit: MetricsTree_Distribution_UtxoCohorts_Profitability_Profit = MetricsTree_Distribution_UtxoCohorts_Profitability_Profit(client)
self.loss: MetricsTree_Distribution_UtxoCohorts_Profitability_Loss = MetricsTree_Distribution_UtxoCohorts_Profitability_Loss(client)
class MetricsTree_Distribution_UtxoCohorts_Matured:
"""Metrics tree node."""
def __init__(self, client: BrkClientBase, base_path: str = ''):
self.up_to_1h: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_under_1h_old_matured')
self._1h_to_1d: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_1h_to_1d_old_matured')
self._1d_to_1w: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_1d_to_1w_old_matured')
self._1w_to_1m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_1w_to_1m_old_matured')
self._1m_to_2m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_1m_to_2m_old_matured')
self._2m_to_3m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_2m_to_3m_old_matured')
self._3m_to_4m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_3m_to_4m_old_matured')
self._4m_to_5m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_4m_to_5m_old_matured')
self._5m_to_6m: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_5m_to_6m_old_matured')
self._6m_to_1y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_6m_to_1y_old_matured')
self._1y_to_2y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_1y_to_2y_old_matured')
self._2y_to_3y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_2y_to_3y_old_matured')
self._3y_to_4y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_3y_to_4y_old_matured')
self._4y_to_5y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_4y_to_5y_old_matured')
self._5y_to_6y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_5y_to_6y_old_matured')
self._6y_to_7y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_6y_to_7y_old_matured')
self._7y_to_8y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_7y_to_8y_old_matured')
self._8y_to_10y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_8y_to_10y_old_matured')
self._10y_to_12y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_10y_to_12y_old_matured')
self._12y_to_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_12y_to_15y_old_matured')
self.from_15y: BtcCentsSatsUsdPattern = BtcCentsSatsUsdPattern(client, 'utxo_over_15y_old_matured')
class MetricsTree_Distribution_UtxoCohorts:
"""Metrics tree node."""
@@ -4665,6 +4691,7 @@ class MetricsTree_Distribution_UtxoCohorts:
self.lt_amount: MetricsTree_Distribution_UtxoCohorts_LtAmount = MetricsTree_Distribution_UtxoCohorts_LtAmount(client)
self.type_: MetricsTree_Distribution_UtxoCohorts_Type = MetricsTree_Distribution_UtxoCohorts_Type(client)
self.profitability: MetricsTree_Distribution_UtxoCohorts_Profitability = MetricsTree_Distribution_UtxoCohorts_Profitability(client)
self.matured: MetricsTree_Distribution_UtxoCohorts_Matured = MetricsTree_Distribution_UtxoCohorts_Matured(client)
class MetricsTree_Distribution_AddressCohorts_GeAmount:
"""Metrics tree node."""