Skip to main content

edgar_rs/
models.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Deserializer, Serialize};
4
5/// Deserializes a JSON `null` as the type's default value (e.g., `""` for String, `0` for i32).
6/// This matches Go's behavior where null JSON values become zero values.
7fn nullable<'de, D, T>(deserializer: D) -> std::result::Result<T, D::Error>
8where
9    D: Deserializer<'de>,
10    T: Deserialize<'de> + Default,
11{
12    Option::<T>::deserialize(deserializer).map(Option::unwrap_or_default)
13}
14
15// ─── Tickers ────────────────────────────────────────────
16
17/// A company entry from the SEC EDGAR company tickers list.
18#[derive(Debug, Clone, Deserialize, Serialize)]
19pub struct Ticker {
20    #[serde(rename = "cik_str")]
21    pub cik: u64,
22    pub ticker: String,
23    pub title: String,
24}
25
26// ─── Submissions ────────────────────────────────────────
27
28/// Company submission data from the SEC EDGAR submissions endpoint.
29/// Includes company metadata and filing history.
30#[derive(Debug, Clone, Deserialize, Serialize)]
31#[serde(rename_all = "camelCase")]
32pub struct Submission {
33    pub cik: String,
34    pub entity_type: String,
35    pub sic: String,
36    #[serde(rename = "sicDescription")]
37    pub sic_description: String,
38    pub insider_transaction_for_owner_exists: i32,
39    pub insider_transaction_for_issuer_exists: i32,
40    pub name: String,
41    pub tickers: Vec<String>,
42    pub exchanges: Vec<String>,
43    pub ein: String,
44    pub description: String,
45    pub website: String,
46    pub investor_website: String,
47    pub category: String,
48    pub fiscal_year_end: String,
49    pub state_of_incorporation: String,
50    pub state_of_incorporation_description: String,
51    pub addresses: Addresses,
52    pub phone: String,
53    pub flags: String,
54    pub former_names: Vec<FormerName>,
55    pub filings: FilingHistory,
56    #[serde(default)]
57    pub lei: Option<String>,
58    #[serde(default)]
59    pub owner_org: Option<String>,
60}
61
62/// Mailing and business addresses for a company.
63#[derive(Debug, Clone, Deserialize, Serialize)]
64pub struct Addresses {
65    pub mailing: Address,
66    pub business: Address,
67}
68
69/// A physical address associated with a company filing.
70#[derive(Debug, Clone, Deserialize, Serialize)]
71#[serde(rename_all = "camelCase")]
72pub struct Address {
73    #[serde(default)]
74    pub street1: Option<String>,
75    #[serde(default)]
76    pub street2: Option<String>,
77    #[serde(default)]
78    pub city: Option<String>,
79    #[serde(default)]
80    pub state_or_country: Option<String>,
81    #[serde(default)]
82    pub zip_code: Option<String>,
83    #[serde(default)]
84    pub state_or_country_description: Option<String>,
85    #[serde(default)]
86    pub country: Option<String>,
87    #[serde(default)]
88    pub country_code: Option<String>,
89    #[serde(default)]
90    pub foreign_state_territory: Option<String>,
91    #[serde(default)]
92    pub is_foreign_location: Option<i32>,
93}
94
95/// A previous name used by a company.
96#[derive(Debug, Clone, Deserialize, Serialize)]
97pub struct FormerName {
98    pub name: String,
99    pub from: String,
100    pub to: String,
101}
102
103/// Recent filing set plus references to additional historical files.
104#[derive(Debug, Clone, Deserialize, Serialize)]
105pub struct FilingHistory {
106    pub recent: FilingSet,
107    pub files: Vec<FilingFile>,
108}
109
110/// Reference to a supplemental filing history JSON file.
111#[derive(Debug, Clone, Deserialize, Serialize)]
112#[serde(rename_all = "camelCase")]
113pub struct FilingFile {
114    pub name: String,
115    pub filing_count: i32,
116    pub filing_from: String,
117    pub filing_to: String,
118}
119
120/// Parallel arrays of filing attributes. Each index `i` across all vectors
121/// represents a single filing record.
122#[derive(Debug, Clone, Deserialize, Serialize)]
123#[serde(rename_all = "camelCase")]
124pub struct FilingSet {
125    pub accession_number: Vec<String>,
126    pub filing_date: Vec<String>,
127    pub report_date: Vec<String>,
128    pub acceptance_date_time: Vec<String>,
129    pub act: Vec<String>,
130    pub form: Vec<String>,
131    pub file_number: Vec<String>,
132    pub film_number: Vec<String>,
133    pub items: Vec<String>,
134    pub size: Vec<i64>,
135    #[serde(rename = "isXBRL")]
136    pub is_xbrl: Vec<i32>,
137    #[serde(rename = "isInlineXBRL")]
138    pub is_inline_xbrl: Vec<i32>,
139    pub primary_document: Vec<String>,
140    pub primary_doc_description: Vec<String>,
141    #[serde(rename = "core_type", default)]
142    pub core_type: Vec<String>,
143}
144
145// ─── XBRL: Company Concept ─────────────────────────────
146
147/// All disclosures for a single XBRL concept from a single company,
148/// grouped by unit of measure.
149#[derive(Debug, Clone, Deserialize, Serialize)]
150#[serde(rename_all = "camelCase")]
151pub struct CompanyConcept {
152    pub cik: u64,
153    pub taxonomy: String,
154    pub tag: String,
155    #[serde(deserialize_with = "nullable")]
156    pub label: String,
157    #[serde(deserialize_with = "nullable")]
158    pub description: String,
159    pub entity_name: String,
160    pub units: HashMap<String, Vec<Fact>>,
161}
162
163// ─── XBRL: Company Facts ───────────────────────────────
164
165/// All XBRL facts for a company, organized by taxonomy then tag.
166#[derive(Debug, Clone, Deserialize, Serialize)]
167#[serde(rename_all = "camelCase")]
168pub struct CompanyFacts {
169    pub cik: u64,
170    pub entity_name: String,
171    pub facts: HashMap<String, HashMap<String, ConceptFacts>>,
172}
173
174/// Label, description, and unit-grouped facts for a single XBRL concept.
175#[derive(Debug, Clone, Deserialize, Serialize)]
176pub struct ConceptFacts {
177    #[serde(deserialize_with = "nullable")]
178    pub label: String,
179    #[serde(deserialize_with = "nullable")]
180    pub description: String,
181    pub units: HashMap<String, Vec<Fact>>,
182}
183
184/// A single XBRL fact disclosure.
185#[derive(Debug, Clone, Deserialize, Serialize)]
186pub struct Fact {
187    #[serde(default)]
188    pub start: Option<String>,
189    #[serde(deserialize_with = "nullable")]
190    pub end: String,
191    pub val: f64,
192    #[serde(deserialize_with = "nullable")]
193    pub accn: String,
194    #[serde(default)]
195    pub fy: Option<i32>,
196    #[serde(deserialize_with = "nullable")]
197    pub fp: String,
198    #[serde(deserialize_with = "nullable")]
199    pub form: String,
200    #[serde(deserialize_with = "nullable")]
201    pub filed: String,
202    #[serde(default)]
203    pub frame: Option<String>,
204}
205
206// ─── XBRL: Frames ──────────────────────────────────────
207
208/// Aggregated XBRL data across all companies for a given concept,
209/// unit, and period.
210#[derive(Debug, Clone, Deserialize, Serialize)]
211pub struct Frame {
212    pub taxonomy: String,
213    pub tag: String,
214    pub ccp: String,
215    pub uom: String,
216    pub label: String,
217    pub description: String,
218    pub pts: i32,
219    pub data: Vec<FrameData>,
220}
221
222/// A single entity's fact within a Frame.
223#[derive(Debug, Clone, Deserialize, Serialize)]
224#[serde(rename_all = "camelCase")]
225pub struct FrameData {
226    pub accn: String,
227    pub cik: u64,
228    pub entity_name: String,
229    pub loc: String,
230    #[serde(default)]
231    pub start: Option<String>,
232    pub end: String,
233    pub val: f64,
234}
235
236// ─── Full-Text Search ──────────────────────────────────
237
238/// Response from the EDGAR full-text search endpoint.
239#[derive(Debug, Clone, Deserialize, Serialize)]
240pub struct SearchResult {
241    pub total: u64,
242    pub hits: Vec<SearchHit>,
243}
244
245/// A single filing match from a full-text search.
246#[derive(Debug, Clone, Deserialize, Serialize)]
247pub struct SearchHit {
248    pub id: String,
249    pub score: f64,
250    pub ciks: Vec<String>,
251    pub display_names: Vec<String>,
252    pub form: String,
253    pub file_date: String,
254    #[serde(default)]
255    pub period_ending: Option<String>,
256    pub accession_number: String,
257    pub file_type: String,
258    pub file_description: String,
259}
260
261/// Configuration for a full-text search query.
262#[derive(Debug, Clone, Default)]
263pub struct SearchOptions {
264    /// Filter results to specific form types (e.g., "10-K", "8-K").
265    pub forms: Vec<String>,
266    /// Filter results filed on or after this date (YYYY-MM-DD).
267    pub date_start: Option<String>,
268    /// Filter results filed on or before this date (YYYY-MM-DD).
269    pub date_end: Option<String>,
270    /// Pagination offset (results are returned 100 at a time).
271    pub from: Option<u32>,
272}
273
274// ─── Internal: EFTS Elasticsearch response ─────────────
275
276#[derive(Debug, Deserialize, Serialize)]
277pub struct EftsResponse {
278    pub hits: EftsHits,
279    #[serde(rename = "_shards", default)]
280    pub shards: Option<serde_json::Value>,
281    #[serde(default)]
282    pub aggregations: Option<serde_json::Value>,
283    #[serde(default)]
284    pub query: Option<serde_json::Value>,
285    #[serde(default)]
286    pub timed_out: Option<bool>,
287    #[serde(default)]
288    pub took: Option<u64>,
289}
290
291#[derive(Debug, Deserialize, Serialize)]
292pub struct EftsHits {
293    pub total: EftsTotal,
294    pub hits: Vec<EftsHit>,
295    #[serde(default)]
296    pub max_score: Option<f64>,
297}
298
299#[derive(Debug, Deserialize, Serialize)]
300pub struct EftsTotal {
301    pub value: u64,
302    #[serde(default)]
303    pub relation: Option<String>,
304}
305
306#[derive(Debug, Deserialize, Serialize)]
307pub struct EftsHit {
308    #[serde(rename = "_id")]
309    pub id: String,
310    #[serde(rename = "_score")]
311    pub score: f64,
312    #[serde(rename = "_source")]
313    pub source: EftsSource,
314    #[serde(rename = "_index", default)]
315    pub index: Option<String>,
316}
317
318#[derive(Debug, Deserialize, Serialize)]
319pub struct EftsSource {
320    #[serde(default)]
321    pub ciks: Vec<String>,
322    #[serde(default)]
323    pub display_names: Vec<String>,
324    #[serde(default)]
325    pub form: Option<String>,
326    #[serde(default)]
327    pub file_date: Option<String>,
328    #[serde(default)]
329    pub period_ending: Option<String>,
330    #[serde(default)]
331    pub adsh: Option<String>,
332    #[serde(default)]
333    pub file_type: Option<String>,
334    #[serde(default)]
335    pub file_description: Option<String>,
336    #[serde(default)]
337    pub biz_locations: Vec<serde_json::Value>,
338    #[serde(default)]
339    pub biz_states: Vec<serde_json::Value>,
340    #[serde(default)]
341    pub file_num: Vec<serde_json::Value>,
342    #[serde(default)]
343    pub film_num: Vec<serde_json::Value>,
344    #[serde(default)]
345    pub inc_states: Vec<serde_json::Value>,
346    #[serde(default)]
347    pub items: Vec<serde_json::Value>,
348    #[serde(default)]
349    pub root_forms: Vec<serde_json::Value>,
350    #[serde(default)]
351    pub sequence: Option<serde_json::Value>,
352    #[serde(default)]
353    pub sics: Vec<serde_json::Value>,
354    #[serde(default)]
355    pub xsl: Option<serde_json::Value>,
356}