Skip to main content

finance_query/domains/
filings.rs

1//! SEC filing query handle.
2//!
3//! Created via [`Providers::filings`](crate::Providers::filings). Always
4//! available — backed by EDGAR (keyless) with optional Polygon fallback.
5
6use crate::error::Result;
7
8domain_handle! {
9    /// SEC filing data backed by configured data providers.
10    ///
11    /// Created via [`Providers::filings`](crate::Providers::filings).
12    pub struct Filings { symbol, symbol }
13    cache: crate::models::filings::ProviderFilings
14}
15
16impl Filings {
17    /// Fetch SEC filings for this symbol.
18    pub async fn get(&self) -> Result<crate::models::filings::ProviderFilings> {
19        fetch_via!(
20            self,
21            symbol,
22            FILINGS,
23            as_filings,
24            Filings,
25            fetch_filings,
26            crate::models::filings::ProviderFilings
27        )
28    }
29
30    /// Fetch the sectioned text of one filing by accession number via the
31    /// FILINGS route (currently Polygon only). Not cached.
32    pub async fn sections(
33        &self,
34        accession_number: &str,
35        form: crate::models::filings::FilingSectionForm,
36    ) -> Result<Vec<crate::models::filings::FilingSection>> {
37        let accession = accession_number.to_string();
38        self.providers
39            .fetch(crate::providers::Capability::FILINGS, move |p| {
40                let accession = accession.clone();
41                let p = p.clone();
42                async move {
43                    p.as_filings()
44                        .ok_or_else(|| {
45                            p.not_supported(crate::providers::Operation::FilingSections)
46                        })?
47                        .fetch_filing_sections(&accession, form)
48                        .await
49                }
50            })
51            .await
52    }
53
54    /// Full-text search over this filer's filings via the FILINGS route
55    /// (currently EDGAR only). Not cached.
56    ///
57    /// The handle's symbol scopes the search unless `filters` already names a
58    /// filer identifier; the routed provider resolves it however its own index
59    /// requires. Use [`search_all`](Self::search_all) to search every filer.
60    pub async fn search(
61        &self,
62        query: &str,
63        filters: crate::models::filings::FilingSearchFilters,
64    ) -> Result<Vec<crate::models::filings::FilingSearchHit>> {
65        let symbol: String = self.symbol().to_string();
66        self.dispatch_search(Some(symbol), query, filters).await
67    }
68
69    /// Full-text search across every filer via the FILINGS route (currently
70    /// EDGAR only). Not cached.
71    ///
72    /// Searches filing *text*, so it answers "which filings mention this"
73    /// rather than "what has this company filed" — the query shape
74    /// [`get`](Self::get) cannot express.
75    pub async fn search_all(
76        &self,
77        query: &str,
78        filters: crate::models::filings::FilingSearchFilters,
79    ) -> Result<Vec<crate::models::filings::FilingSearchHit>> {
80        self.dispatch_search(None, query, filters).await
81    }
82
83    async fn dispatch_search(
84        &self,
85        symbol: Option<String>,
86        query: &str,
87        filters: crate::models::filings::FilingSearchFilters,
88    ) -> Result<Vec<crate::models::filings::FilingSearchHit>> {
89        let query = query.to_string();
90        let filters = std::sync::Arc::new(filters);
91        self.providers
92            .fetch(crate::providers::Capability::FILINGS, move |p| {
93                let symbol = symbol.clone();
94                let query = query.clone();
95                let filters = std::sync::Arc::clone(&filters);
96                let p = p.clone();
97                async move {
98                    p.as_filings()
99                        .ok_or_else(|| p.not_supported(crate::providers::Operation::FilingSearch))?
100                        .fetch_filing_search(symbol.as_deref(), &query, &filters)
101                        .await
102                }
103            })
104            .await
105    }
106
107    /// Insider transactions for this issuer via the FILINGS route (EDGAR
108    /// parses Forms 3/4/5 directly; Alpha Vantage and FMP are coarser
109    /// fallbacks with no accession number or form type).
110    ///
111    /// `limit` caps how many *filings* are read on EDGAR, not how many
112    /// transactions come back — one Form 4 can report several lines. Each
113    /// EDGAR filing costs two extra requests, so keep it modest. Not cached.
114    pub async fn insider_trades(
115        &self,
116        limit: u32,
117    ) -> Result<Vec<crate::models::filings::InsiderTrade>> {
118        let symbol: String = self.symbol().to_string();
119        self.providers
120            .fetch(crate::providers::Capability::FILINGS, move |p| {
121                let symbol = symbol.clone();
122                let p = p.clone();
123                async move {
124                    p.as_filings()
125                        .ok_or_else(|| p.not_supported(crate::providers::Operation::InsiderTrades))?
126                        .fetch_insider_trades(&symbol, limit)
127                        .await
128                }
129            })
130            .await
131    }
132
133    /// The latest 13F-HR information table filed by this symbol's CIK, via the
134    /// FILINGS route (currently EDGAR only).
135    ///
136    /// The handle's symbol identifies the *filer*, so this is only meaningful
137    /// for listed institutional managers (e.g. `BRK-B`) — an issuer that files
138    /// no 13F errors rather than returning an empty list. Not cached.
139    pub async fn institutional_holdings(
140        &self,
141    ) -> Result<Vec<crate::models::filings::InstitutionalHolding>> {
142        let symbol: String = self.symbol().to_string();
143        self.providers
144            .fetch(crate::providers::Capability::FILINGS, move |p| {
145                let symbol = symbol.clone();
146                let p = p.clone();
147                async move {
148                    p.as_filings()
149                        .ok_or_else(|| {
150                            p.not_supported(crate::providers::Operation::InstitutionalHoldings)
151                        })?
152                        .fetch_institutional_holdings(&symbol)
153                        .await
154                }
155            })
156            .await
157    }
158
159    /// Fetch risk factors extracted from this symbol's SEC filings via the
160    /// FILINGS route (currently Polygon only). Not cached.
161    pub async fn risk_factors(&self) -> Result<Vec<crate::models::filings::RiskFactor>> {
162        let symbol: String = self.symbol().to_string();
163        self.providers
164            .fetch(crate::providers::Capability::FILINGS, move |p| {
165                let symbol = symbol.clone();
166                let p = p.clone();
167                async move {
168                    p.as_filings()
169                        .ok_or_else(|| p.not_supported(crate::providers::Operation::RiskFactors))?
170                        .fetch_risk_factors(&symbol)
171                        .await
172                }
173            })
174            .await
175    }
176
177    /// Congressional (House and Senate) stock-trade disclosures naming this
178    /// symbol, via the FILINGS route (FMP, falling back to keyless House and
179    /// Senate PTR disclosures). Not cached.
180    pub async fn congressional_trades(
181        &self,
182    ) -> Result<Vec<crate::models::filings::CongressionalTrade>> {
183        let symbol: String = self.symbol().to_string();
184        self.providers
185            .fetch(crate::providers::Capability::FILINGS, move |p| {
186                let symbol = symbol.clone();
187                let p = p.clone();
188                async move {
189                    p.as_filings()
190                        .ok_or_else(|| {
191                            p.not_supported(crate::providers::Operation::CongressionalTrades)
192                        })?
193                        .fetch_congressional_trades(&symbol)
194                        .await
195                }
196            })
197            .await
198    }
199
200    /// SEC fails-to-deliver data for this symbol, via the FILINGS route
201    /// (FMP, or keyless EDGAR when FMP isn't routed). Not cached.
202    pub async fn fails_to_deliver(&self) -> Result<Vec<crate::models::filings::FailToDeliver>> {
203        let symbol: String = self.symbol().to_string();
204        self.providers
205            .fetch(crate::providers::Capability::FILINGS, move |p| {
206                let symbol = symbol.clone();
207                let p = p.clone();
208                async move {
209                    p.as_filings()
210                        .ok_or_else(|| {
211                            p.not_supported(crate::providers::Operation::FailsToDeliver)
212                        })?
213                        .fetch_fails_to_deliver(&symbol)
214                        .await
215                }
216            })
217            .await
218    }
219}