global: cost basis -> urpd

This commit is contained in:
nym21
2026-04-22 22:23:52 +02:00
parent 84e924b77e
commit 3faa989691
30 changed files with 950 additions and 507 deletions
-98
View File
@@ -1,98 +0,0 @@
use std::{fs, path::PathBuf};
use brk_error::{Error, Result};
use brk_types::{
CostBasisBucket, CostBasisDistribution, CostBasisFormatted, CostBasisValue, Date, Day1,
};
use vecdb::ReadableOptionVec;
use crate::Query;
impl Query {
/// List available cohorts for cost basis distribution.
pub fn cost_basis_cohorts(&self) -> Result<Vec<String>> {
let states_path = &self.computer().distribution.states_path;
let mut cohorts: Vec<String> = fs::read_dir(states_path)?
.filter_map(|entry| {
let name = entry.ok()?.file_name().into_string().ok()?;
let cohort = name.strip_prefix("utxo_")?.strip_suffix("_cost_basis")?;
states_path
.join(&name)
.join("by_date")
.exists()
.then(|| cohort.to_string())
})
.collect();
cohorts.sort();
Ok(cohorts)
}
fn cost_basis_dir(&self, cohort: &str) -> Result<PathBuf> {
let dir = self
.computer()
.distribution
.states_path
.join(format!("utxo_{cohort}_cost_basis/by_date"));
if !dir.exists() {
let valid = self.cost_basis_cohorts().unwrap_or_default().join(", ");
return Err(Error::NotFound(format!(
"Unknown cohort '{cohort}'. Available: {valid}"
)));
}
Ok(dir)
}
/// Get the cost basis distribution for a cohort on a specific date.
pub fn cost_basis_distribution(
&self,
cohort: &str,
date: Date,
) -> Result<CostBasisDistribution> {
let path = self.cost_basis_dir(cohort)?.join(date.to_string());
if !path.exists() {
return Err(Error::NotFound(format!(
"No data for cohort '{cohort}' on {date}"
)));
}
CostBasisDistribution::deserialize(&fs::read(&path)?)
}
/// List available dates for a cohort's cost basis distribution.
pub fn cost_basis_dates(&self, cohort: &str) -> Result<Vec<Date>> {
let dir = self.cost_basis_dir(cohort)?;
let mut dates: Vec<Date> = fs::read_dir(&dir)?
.filter_map(|entry| entry.ok()?.file_name().to_str()?.parse().ok())
.collect();
dates.sort();
Ok(dates)
}
/// Get the formatted cost basis distribution.
pub fn cost_basis_formatted(
&self,
cohort: &str,
date: Date,
bucket: CostBasisBucket,
value: CostBasisValue,
) -> Result<CostBasisFormatted> {
let distribution = self.cost_basis_distribution(cohort, date)?;
let day1 = Day1::try_from(date).map_err(|e| Error::Parse(e.to_string()))?;
let price = &self.computer().prices;
let spot = price
.split
.close
.cents
.day1
.collect_one_flat(day1)
.ok_or_else(|| Error::NotFound(format!("No price data for {date}")))?;
Ok(distribution.format(bucket, value, spot))
}
}
+1 -1
View File
@@ -1,11 +1,11 @@
mod addr;
mod block;
mod cost_basis;
mod mempool;
mod mining;
mod price;
mod series;
mod tx;
mod urpd;
pub use block::BLOCK_TXS_PAGE_SIZE;
pub use series::ResolvedQuery;
+107
View File
@@ -0,0 +1,107 @@
use std::{fs, path::PathBuf};
use brk_error::{Error, Result};
use brk_types::{Cohort, Date, Day1, Urpd, UrpdAggregation, UrpdRaw};
use vecdb::ReadableOptionVec;
use crate::Query;
impl Query {
/// Available cohorts for URPD.
pub fn urpd_cohorts(&self) -> Result<Vec<Cohort>> {
let states_path = &self.computer().distribution.states_path;
let mut cohorts: Vec<Cohort> = fs::read_dir(states_path)?
.filter_map(|entry| {
let name = entry.ok()?.file_name().into_string().ok()?;
states_path
.join(&name)
.join("urpd")
.exists()
.then(|| Cohort::from(name))
})
.collect();
cohorts.sort_by(|a, b| a.to_string().cmp(&b.to_string()));
Ok(cohorts)
}
pub(crate) fn urpd_dir(&self, cohort: &str) -> Result<PathBuf> {
let dir = self
.computer()
.distribution
.states_path
.join(cohort)
.join("urpd");
if !dir.exists() {
let valid = self
.urpd_cohorts()
.unwrap_or_default()
.iter()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join(", ");
return Err(Error::NotFound(format!(
"Unknown cohort '{cohort}'. Available: {valid}"
)));
}
Ok(dir)
}
/// Available dates for a cohort.
pub fn urpd_dates(&self, cohort: &Cohort) -> Result<Vec<Date>> {
let dir = self.urpd_dir(cohort)?;
let mut dates: Vec<Date> = fs::read_dir(&dir)?
.filter_map(|entry| entry.ok()?.file_name().to_str()?.parse().ok())
.collect();
dates.sort();
Ok(dates)
}
/// Raw URPD data for a cohort on a specific date.
pub fn urpd_raw(&self, cohort: &Cohort, date: Date) -> Result<UrpdRaw> {
let path = self.urpd_dir(cohort)?.join(date.to_string());
if !path.exists() {
return Err(Error::NotFound(format!(
"No URPD for cohort '{cohort}' on {date}"
)));
}
UrpdRaw::deserialize(&fs::read(&path)?)
}
/// URPD for a cohort on a specific date.
pub fn urpd_at(
&self,
cohort: &Cohort,
date: Date,
agg: UrpdAggregation,
) -> Result<Urpd> {
let raw = self.urpd_raw(cohort, date)?;
let day1 = Day1::try_from(date).map_err(|e| Error::Parse(e.to_string()))?;
let close = self
.computer()
.prices
.split
.close
.cents
.day1
.collect_one_flat(day1)
.ok_or_else(|| Error::NotFound(format!("No price data for {date}")))?;
Ok(Urpd::build(cohort.clone(), date, close, &raw, agg))
}
/// URPD for the most recently available date in a cohort.
pub fn urpd_latest(&self, cohort: &Cohort, agg: UrpdAggregation) -> Result<Urpd> {
let dates = self.urpd_dates(cohort)?;
let date = *dates.last().ok_or_else(|| {
Error::NotFound(format!("No URPD available for cohort '{cohort}'"))
})?;
self.urpd_at(cohort, date, agg)
}
}