finance_query_core/models/
sec_filings.rs1use chrono::{DateTime, NaiveDate, TimeZone, Utc};
2use serde::{Deserialize, Serialize};
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub struct SecFiling {
8 pub date: DateTime<Utc>,
9 pub filing_type: String,
10 pub title: String,
11 pub url: String,
12 pub exhibits: Vec<SecExhibit>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub struct SecExhibit {
19 pub exhibit_type: String,
20 pub url: String,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct SecFilingsResponse {
27 pub symbol: String,
28 pub filings: Vec<SecFiling>,
29}
30
31impl SecFilingsResponse {
32 pub(crate) fn from_yahoo_response(
33 symbol: String,
34 response: YahooSecFilingsResponse,
35 ) -> Result<Self, crate::client::YahooError> {
36 let result = response.quote_summary.result.first().ok_or_else(|| {
37 crate::client::YahooError::ParseError("No SEC filings data in response".to_string())
38 })?;
39
40 let filings = result
41 .sec_filings
42 .filings
43 .iter()
44 .filter_map(|f| {
45 let date = parse_sec_date(&f.date)?;
47
48 let exhibits = f
49 .exhibits
50 .as_ref()
51 .map(|exs| {
52 exs.iter()
53 .map(|e| SecExhibit {
54 exhibit_type: e.exhibit_type.clone().unwrap_or_default(),
55 url: e.url.clone().unwrap_or_default(),
56 })
57 .collect()
58 })
59 .unwrap_or_default();
60
61 Some(SecFiling {
62 date,
63 filing_type: f.filing_type.clone().unwrap_or_default(),
64 title: f.title.clone().unwrap_or_default(),
65 url: f.edgar_url.clone().unwrap_or_default(),
66 exhibits,
67 })
68 })
69 .collect();
70
71 Ok(Self { symbol, filings })
72 }
73}
74
75fn parse_sec_date(date_value: &SecDateValue) -> Option<DateTime<Utc>> {
77 match date_value {
78 SecDateValue::String(s) => {
79 NaiveDate::parse_from_str(s, "%Y-%m-%d")
81 .ok()
82 .and_then(|d| d.and_hms_opt(0, 0, 0))
83 .and_then(|dt| dt.and_local_timezone(Utc).single())
84 }
85 SecDateValue::Timestamp(ts) => Utc.timestamp_opt(*ts, 0).single(),
86 SecDateValue::Object { raw, .. } => raw.and_then(|ts| Utc.timestamp_opt(ts, 0).single()),
87 }
88}
89
90#[derive(Debug, Deserialize)]
92pub(crate) struct YahooSecFilingsResponse {
93 #[serde(rename = "quoteSummary")]
94 pub quote_summary: SecQuoteSummaryData,
95}
96
97#[derive(Debug, Deserialize)]
98pub(crate) struct SecQuoteSummaryData {
99 pub result: Vec<SecQuoteSummaryResult>,
100}
101
102#[derive(Debug, Deserialize)]
103pub(crate) struct SecQuoteSummaryResult {
104 #[serde(rename = "secFilings")]
105 pub sec_filings: SecFilingsData,
106}
107
108#[derive(Debug, Deserialize)]
109pub(crate) struct SecFilingsData {
110 pub filings: Vec<YahooSecFiling>,
111}
112
113#[derive(Debug, Deserialize)]
114pub(crate) struct YahooSecFiling {
115 pub date: SecDateValue,
116 #[serde(rename = "type")]
117 pub filing_type: Option<String>,
118 pub title: Option<String>,
119 #[serde(rename = "edgarUrl")]
120 pub edgar_url: Option<String>,
121 pub exhibits: Option<Vec<YahooExhibit>>,
122}
123
124#[derive(Debug, Deserialize)]
126#[serde(untagged)]
127pub(crate) enum SecDateValue {
128 String(String),
129 Timestamp(i64),
130 Object {
131 raw: Option<i64>,
132 #[allow(dead_code)]
133 fmt: Option<String>,
134 },
135}
136
137#[derive(Debug, Deserialize)]
138pub(crate) struct YahooExhibit {
139 #[serde(rename = "type")]
140 pub exhibit_type: Option<String>,
141 pub url: Option<String>,
142}