use chrono::{DateTime, NaiveDate, TimeZone, Utc};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SecFiling {
pub date: DateTime<Utc>,
pub filing_type: String,
pub title: String,
pub url: String,
pub exhibits: Vec<SecExhibit>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SecExhibit {
pub exhibit_type: String,
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SecFilingsResponse {
pub symbol: String,
pub filings: Vec<SecFiling>,
}
impl SecFilingsResponse {
pub(crate) fn from_yahoo_response(
symbol: String,
response: YahooSecFilingsResponse,
) -> Result<Self, crate::client::YahooError> {
let result = response.quote_summary.result.first().ok_or_else(|| {
crate::client::YahooError::ParseError("No SEC filings data in response".to_string())
})?;
let filings = result
.sec_filings
.filings
.iter()
.filter_map(|f| {
let date = parse_sec_date(&f.date)?;
let exhibits = f
.exhibits
.as_ref()
.map(|exs| {
exs.iter()
.map(|e| SecExhibit {
exhibit_type: e.exhibit_type.clone().unwrap_or_default(),
url: e.url.clone().unwrap_or_default(),
})
.collect()
})
.unwrap_or_default();
Some(SecFiling {
date,
filing_type: f.filing_type.clone().unwrap_or_default(),
title: f.title.clone().unwrap_or_default(),
url: f.edgar_url.clone().unwrap_or_default(),
exhibits,
})
})
.collect();
Ok(Self { symbol, filings })
}
}
fn parse_sec_date(date_value: &SecDateValue) -> Option<DateTime<Utc>> {
match date_value {
SecDateValue::String(s) => {
NaiveDate::parse_from_str(s, "%Y-%m-%d")
.ok()
.and_then(|d| d.and_hms_opt(0, 0, 0))
.and_then(|dt| dt.and_local_timezone(Utc).single())
}
SecDateValue::Timestamp(ts) => Utc.timestamp_opt(*ts, 0).single(),
SecDateValue::Object { raw, .. } => raw.and_then(|ts| Utc.timestamp_opt(ts, 0).single()),
}
}
#[derive(Debug, Deserialize)]
pub(crate) struct YahooSecFilingsResponse {
#[serde(rename = "quoteSummary")]
pub quote_summary: SecQuoteSummaryData,
}
#[derive(Debug, Deserialize)]
pub(crate) struct SecQuoteSummaryData {
pub result: Vec<SecQuoteSummaryResult>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct SecQuoteSummaryResult {
#[serde(rename = "secFilings")]
pub sec_filings: SecFilingsData,
}
#[derive(Debug, Deserialize)]
pub(crate) struct SecFilingsData {
pub filings: Vec<YahooSecFiling>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct YahooSecFiling {
pub date: SecDateValue,
#[serde(rename = "type")]
pub filing_type: Option<String>,
pub title: Option<String>,
#[serde(rename = "edgarUrl")]
pub edgar_url: Option<String>,
pub exhibits: Option<Vec<YahooExhibit>>,
}
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub(crate) enum SecDateValue {
String(String),
Timestamp(i64),
Object {
raw: Option<i64>,
#[allow(dead_code)]
fmt: Option<String>,
},
}
#[derive(Debug, Deserialize)]
pub(crate) struct YahooExhibit {
#[serde(rename = "type")]
pub exhibit_type: Option<String>,
pub url: Option<String>,
}