pub mod compliance;
pub mod conservation;
pub mod evaluate;
pub mod fold;
pub mod pools;
pub mod resolve;
pub mod transition;
pub use compliance::{disposal_compliance, ComplianceStatus, DisposalCompliance};
pub use conservation::{conservation_report, ConservationReport};
pub use evaluate::{evaluate_disposal, CandidateDisposal, EvaluateError, EvaluateOutcome};
pub use resolve::{PseudoDefault, PseudoKind};
use crate::event::LedgerEvent;
use crate::price::PriceProvider;
use crate::state::LedgerState;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FeeTreatment {
TreatmentC,
TreatmentB,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub enum LotMethod {
#[default]
Fifo,
Lifo,
Hifo,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ProjectionConfig {
pub self_transfer_fee: FeeTreatment,
pub pre2025_method: LotMethod,
pub pre2025_method_attested: bool,
pub pseudo_reconcile: bool,
}
impl Default for ProjectionConfig {
fn default() -> Self {
ProjectionConfig {
self_transfer_fee: FeeTreatment::TreatmentC,
pre2025_method: LotMethod::Hifo,
pre2025_method_attested: false,
pseudo_reconcile: false,
}
}
}
pub fn project(
events: &[LedgerEvent],
prices: &dyn PriceProvider,
config: &ProjectionConfig,
) -> LedgerState {
let resolution = resolve::resolve(events, prices, config);
fold::fold(resolution, prices, config)
}
pub fn pseudo_plan(
events: &[LedgerEvent],
prices: &dyn PriceProvider,
config: &ProjectionConfig,
) -> Vec<PseudoDefault> {
let mut cfg = *config;
cfg.pseudo_reconcile = true;
resolve::resolve(events, prices, &cfg).pseudo_decisions
}
pub fn would_conflict(
events: &[LedgerEvent],
prices: &dyn PriceProvider,
config: &ProjectionConfig,
incoming: &crate::event::EventPayload,
now: time::OffsetDateTime,
) -> Option<String> {
use crate::identity::EventId;
use crate::state::BlockerKind;
use std::collections::BTreeSet;
let mut cfg = *config;
cfg.pseudo_reconcile = false;
let conflicts = |evs: &[LedgerEvent]| -> Vec<(Option<EventId>, String)> {
project(evs, prices, &cfg)
.blockers
.into_iter()
.filter(|b| b.kind == BlockerKind::DecisionConflict)
.map(|b| (b.event, b.detail))
.collect()
};
let baseline: BTreeSet<(Option<EventId>, String)> = conflicts(events).into_iter().collect();
let next_seq = events
.iter()
.filter_map(|e| match &e.id {
EventId::Decision { seq } => Some(*seq),
_ => None,
})
.max()
.unwrap_or(0)
+ 1;
let candidate = LedgerEvent {
id: EventId::decision(next_seq),
utc_timestamp: now,
original_tz: time::UtcOffset::UTC,
wallet: None,
payload: incoming.clone(),
};
let mut with_candidate = events.to_vec();
with_candidate.push(candidate);
conflicts(&with_candidate)
.into_iter()
.find(|c| !baseline.contains(c))
.map(|(_, detail)| detail)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InForceMethod {
pub method: LotMethod,
pub scoped: bool,
}
pub fn in_force_methods(
events: &[LedgerEvent],
prices: &dyn PriceProvider,
config: &ProjectionConfig,
date: crate::conventions::TaxDate,
wallets: &[crate::identity::WalletId],
) -> Vec<InForceMethod> {
let res = resolve::resolve(events, prices, config);
wallets
.iter()
.map(|w| {
if date < crate::conventions::TRANSITION_DATE {
InForceMethod {
method: config.pre2025_method,
scoped: false,
}
} else {
match resolve::resolve_election(date, w, &res.elections) {
Some(e) => InForceMethod {
method: e.method,
scoped: e.wallet.is_some(),
},
None => InForceMethod {
method: LotMethod::Hifo,
scoped: false,
},
}
}
})
.collect()
}