use crate::{
Address, Profile, YfClient, YfError, analysis,
core::client::{CacheMode, RetryConfig},
esg,
};
use serde::Serialize;
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct Info {
pub symbol: String,
pub short_name: Option<String>,
pub regular_market_price: Option<f64>,
pub regular_market_previous_close: Option<f64>,
pub currency: Option<String>,
pub exchange: Option<String>,
pub market_state: Option<String>,
pub sector: Option<String>,
pub industry: Option<String>,
pub website: Option<String>,
pub summary: Option<String>,
pub address: Option<Address>,
pub family: Option<String>,
pub fund_kind: Option<String>,
pub isin: Option<String>,
pub target_mean_price: Option<f64>,
pub target_high_price: Option<f64>,
pub target_low_price: Option<f64>,
pub number_of_analyst_opinions: Option<u32>,
pub recommendation_mean: Option<f64>,
pub recommendation_key: Option<String>,
pub total_esg_score: Option<f64>,
pub environment_score: Option<f64>,
pub social_score: Option<f64>,
pub governance_score: Option<f64>,
}
fn log_err_async<T>(res: Result<T, YfError>, name: &str, symbol: &str) -> Option<T> {
match res {
Ok(data) => Some(data),
Err(e) => {
if std::env::var("YF_DEBUG").ok().as_deref() == Some("1") {
eprintln!(
"YF_DEBUG(info): failed to fetch '{}' for {}: {}",
name, symbol, e
);
}
None
}
}
}
pub(super) async fn fetch_info(
client: &YfClient,
symbol: &str,
cache_mode: CacheMode,
retry_override: Option<&RetryConfig>,
) -> Result<Info, YfError> {
let (quote_res, profile_res, price_target_res, rec_summary_res, esg_res) = tokio::join!(
crate::ticker::quote::fetch_quote(client, symbol, cache_mode, retry_override),
Profile::load(client, symbol),
analysis::AnalysisBuilder::new(client.clone(), symbol)
.cache_mode(cache_mode)
.retry_policy(retry_override.cloned())
.analyst_price_target(),
analysis::AnalysisBuilder::new(client.clone(), symbol)
.cache_mode(cache_mode)
.retry_policy(retry_override.cloned())
.recommendations_summary(),
esg::EsgBuilder::new(client, symbol)
.cache_mode(cache_mode)
.retry_policy(retry_override.cloned())
.fetch()
);
let profile = profile_res?;
let quote = log_err_async(quote_res, "quote", symbol);
let price_target = log_err_async(price_target_res, "price target", symbol);
let rec_summary = log_err_async(rec_summary_res, "recommendation summary", symbol);
let esg_scores = log_err_async(esg_res, "esg scores", symbol);
let (sector, industry, website, summary, address, isin, family, fund_kind) = match profile {
Profile::Company(c) => (
c.sector, c.industry, c.website, c.summary, c.address, c.isin,
None, None, ),
Profile::Fund(f) => (
None, None, None, None, None, f.isin,
f.family,
Some(f.kind),
),
};
let info = Info {
symbol: quote
.as_ref()
.map_or_else(|| symbol.to_string(), |q| q.symbol.clone()),
short_name: quote.as_ref().and_then(|q| q.shortname.clone()),
regular_market_price: quote.as_ref().and_then(|q| q.regular_market_price),
regular_market_previous_close: quote.as_ref().and_then(|q| q.regular_market_previous_close),
currency: quote.as_ref().and_then(|q| q.currency.clone()),
exchange: quote.as_ref().and_then(|q| q.exchange.clone()),
market_state: quote.as_ref().and_then(|q| q.market_state.clone()),
sector,
industry,
website,
summary,
address,
isin,
family,
fund_kind,
target_mean_price: price_target.as_ref().and_then(|pt| pt.mean),
target_high_price: price_target.as_ref().and_then(|pt| pt.high),
target_low_price: price_target.as_ref().and_then(|pt| pt.low),
number_of_analyst_opinions: price_target.as_ref().and_then(|pt| pt.number_of_analysts),
recommendation_mean: rec_summary.as_ref().and_then(|rs| rs.mean),
recommendation_key: rec_summary.as_ref().and_then(|rs| rs.mean_key.clone()),
total_esg_score: esg_scores.as_ref().and_then(|esg| esg.total_esg),
environment_score: esg_scores.as_ref().and_then(|esg| esg.environment_score),
social_score: esg_scores.as_ref().and_then(|esg| esg.social_score),
governance_score: esg_scores.as_ref().and_then(|esg| esg.governance_score),
};
Ok(info)
}