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