use std::collections::HashMap;
use std::sync::Arc;
use crate::client::{block, row_to_event};
use crate::error::Result;
use crate::record::{DivEvent, DividendSnapshot};
use crate::Divkit;
struct CacheInner {
by_ticker: HashMap<String, DividendSnapshot>,
by_cik: HashMap<u32, DividendSnapshot>,
}
#[derive(Clone)]
pub struct DividendCache {
inner: Arc<CacheInner>,
}
impl DividendCache {
pub async fn hydrate() -> Result<Self> {
Self::hydrate_with(&Divkit::new()).await
}
pub async fn hydrate_with(client: &Divkit) -> Result<Self> {
let rows = client.load_all_rows().await?;
let mut ticker_cik_rows: HashMap<(String, u32), Vec<usize>> = HashMap::new();
for (i, row) in rows.iter().enumerate() {
if let Some(t) = row.ticker.as_deref() {
ticker_cik_rows
.entry((t.to_uppercase(), row.cik))
.or_default()
.push(i);
}
}
let mut ticker_winner: HashMap<String, (u32, chrono::NaiveDate)> = HashMap::new();
for (ticker_upper, cik) in ticker_cik_rows.keys() {
let indices = &ticker_cik_rows[&(ticker_upper.clone(), *cik)];
let latest = indices
.iter()
.map(|&i| rows[i].period_end)
.max()
.unwrap_or(chrono::NaiveDate::from_ymd_opt(1970, 1, 1).unwrap());
ticker_winner
.entry(ticker_upper.clone())
.and_modify(|(incumbent_cik, incumbent_latest)| {
if latest > *incumbent_latest
|| (latest == *incumbent_latest && cik > incumbent_cik)
{
*incumbent_cik = *cik;
*incumbent_latest = latest;
}
})
.or_insert((*cik, latest));
}
let mut by_ticker: HashMap<String, DividendSnapshot> = HashMap::new();
for (ticker_upper, (winning_cik, _)) in &ticker_winner {
let indices = &ticker_cik_rows[&(ticker_upper.clone(), *winning_cik)];
let events: Vec<DivEvent> = indices.iter().map(|&i| row_to_event(&rows[i])).collect();
let snap = DividendSnapshot::from_events(ticker_upper.clone(), *winning_cik, events);
by_ticker.insert(ticker_upper.clone(), snap);
}
let mut by_cik_rows: HashMap<u32, Vec<usize>> = HashMap::new();
for (i, row) in rows.iter().enumerate() {
by_cik_rows.entry(row.cik).or_default().push(i);
}
let mut by_cik: HashMap<u32, DividendSnapshot> = HashMap::new();
for (cik, indices) in &by_cik_rows {
let ticker_str = indices
.iter()
.filter_map(|&i| {
rows[i]
.ticker
.as_deref()
.map(|t| (t.to_uppercase(), rows[i].period_end))
})
.max_by(|(at, ae), (bt, be)| ae.cmp(be).then(at.cmp(bt)))
.map(|(t, _)| t)
.unwrap_or_default();
let events: Vec<DivEvent> = indices.iter().map(|&i| row_to_event(&rows[i])).collect();
let snap = DividendSnapshot::from_events(ticker_str, *cik, events);
by_cik.insert(*cik, snap);
}
Ok(Self {
inner: Arc::new(CacheInner { by_ticker, by_cik }),
})
}
pub fn hydrate_blocking() -> Result<Self> {
block(Self::hydrate())
}
pub fn snapshot(&self, ticker: &str) -> Option<&DividendSnapshot> {
self.inner.by_ticker.get(&ticker.to_uppercase())
}
pub fn annual_dividend(&self, ticker: &str) -> Option<f64> {
self.snapshot(ticker).map(|s| s.annual_amount())
}
pub fn dividends(&self, ticker: &str) -> &[DivEvent] {
self.snapshot(ticker)
.map(|s| s.history.as_slice())
.unwrap_or(&[])
}
pub fn snapshot_by_cik(&self, cik: u32) -> Option<&DividendSnapshot> {
self.inner.by_cik.get(&cik)
}
pub fn tickers(&self) -> impl Iterator<Item = &str> {
self.inner.by_ticker.keys().map(String::as_str)
}
pub fn len(&self) -> usize {
self.inner.by_cik.len()
}
pub fn is_empty(&self) -> bool {
self.inner.by_cik.is_empty()
}
pub async fn reload(&self) -> Result<Self> {
Self::hydrate().await
}
}