sec_api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.devcontainer/Dockerfile +54 -0
  3. data/.devcontainer/README.md +178 -0
  4. data/.devcontainer/devcontainer.json +46 -0
  5. data/.devcontainer/docker-compose.yml +28 -0
  6. data/.devcontainer/post-create.sh +51 -0
  7. data/.devcontainer/post-start.sh +44 -0
  8. data/.rspec +3 -0
  9. data/.standard.yml +3 -0
  10. data/CHANGELOG.md +5 -0
  11. data/CLAUDE.md +0 -0
  12. data/LICENSE.txt +21 -0
  13. data/MIGRATION.md +274 -0
  14. data/README.md +370 -0
  15. data/Rakefile +10 -0
  16. data/config/secapi.yml.example +57 -0
  17. data/docs/development-guide.md +291 -0
  18. data/docs/enumerator_pattern_design.md +483 -0
  19. data/docs/examples/README.md +58 -0
  20. data/docs/examples/backfill_filings.rb +419 -0
  21. data/docs/examples/instrumentation.rb +583 -0
  22. data/docs/examples/query_builder.rb +308 -0
  23. data/docs/examples/streaming_notifications.rb +491 -0
  24. data/docs/index.md +244 -0
  25. data/docs/migration-guide-v1.md +1091 -0
  26. data/docs/pre-review-checklist.md +145 -0
  27. data/docs/project-overview.md +90 -0
  28. data/docs/project-scan-report.json +60 -0
  29. data/docs/source-tree-analysis.md +190 -0
  30. data/lib/sec_api/callback_helper.rb +49 -0
  31. data/lib/sec_api/client.rb +606 -0
  32. data/lib/sec_api/collections/filings.rb +267 -0
  33. data/lib/sec_api/collections/fulltext_results.rb +86 -0
  34. data/lib/sec_api/config.rb +590 -0
  35. data/lib/sec_api/deep_freezable.rb +42 -0
  36. data/lib/sec_api/errors/authentication_error.rb +24 -0
  37. data/lib/sec_api/errors/configuration_error.rb +5 -0
  38. data/lib/sec_api/errors/error.rb +75 -0
  39. data/lib/sec_api/errors/network_error.rb +26 -0
  40. data/lib/sec_api/errors/not_found_error.rb +23 -0
  41. data/lib/sec_api/errors/pagination_error.rb +28 -0
  42. data/lib/sec_api/errors/permanent_error.rb +29 -0
  43. data/lib/sec_api/errors/rate_limit_error.rb +57 -0
  44. data/lib/sec_api/errors/reconnection_error.rb +34 -0
  45. data/lib/sec_api/errors/server_error.rb +25 -0
  46. data/lib/sec_api/errors/transient_error.rb +28 -0
  47. data/lib/sec_api/errors/validation_error.rb +23 -0
  48. data/lib/sec_api/extractor.rb +122 -0
  49. data/lib/sec_api/filing_journey.rb +477 -0
  50. data/lib/sec_api/mapping.rb +125 -0
  51. data/lib/sec_api/metrics_collector.rb +411 -0
  52. data/lib/sec_api/middleware/error_handler.rb +250 -0
  53. data/lib/sec_api/middleware/instrumentation.rb +186 -0
  54. data/lib/sec_api/middleware/rate_limiter.rb +541 -0
  55. data/lib/sec_api/objects/data_file.rb +34 -0
  56. data/lib/sec_api/objects/document_format_file.rb +45 -0
  57. data/lib/sec_api/objects/entity.rb +92 -0
  58. data/lib/sec_api/objects/extracted_data.rb +118 -0
  59. data/lib/sec_api/objects/fact.rb +147 -0
  60. data/lib/sec_api/objects/filing.rb +197 -0
  61. data/lib/sec_api/objects/fulltext_result.rb +66 -0
  62. data/lib/sec_api/objects/period.rb +96 -0
  63. data/lib/sec_api/objects/stream_filing.rb +194 -0
  64. data/lib/sec_api/objects/xbrl_data.rb +356 -0
  65. data/lib/sec_api/query.rb +423 -0
  66. data/lib/sec_api/rate_limit_state.rb +130 -0
  67. data/lib/sec_api/rate_limit_tracker.rb +154 -0
  68. data/lib/sec_api/stream.rb +841 -0
  69. data/lib/sec_api/structured_logger.rb +199 -0
  70. data/lib/sec_api/types.rb +32 -0
  71. data/lib/sec_api/version.rb +42 -0
  72. data/lib/sec_api/xbrl.rb +220 -0
  73. data/lib/sec_api.rb +137 -0
  74. data/sig/sec_api.rbs +4 -0
  75. metadata +217 -0
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example: Query Builder DSL
5
+ #
6
+ # This example demonstrates:
7
+ # - Querying filings by ticker symbol(s)
8
+ # - Querying by CIK number
9
+ # - Filtering by form type (domestic and international)
10
+ # - Filtering by date range
11
+ # - Full-text search
12
+ # - Combining multiple filters
13
+ # - Limiting results
14
+ # - Manual pagination with fetch_next_page
15
+ #
16
+ # Prerequisites:
17
+ # - gem install sec_api
18
+ # - Set SECAPI_API_KEY environment variable
19
+ #
20
+ # Usage:
21
+ # ruby docs/examples/query_builder.rb
22
+
23
+ require "sec_api"
24
+
25
+ # Initialize client with API key from environment
26
+ client = SecApi::Client.new(
27
+ api_key: ENV.fetch("SECAPI_API_KEY")
28
+ )
29
+
30
+ # =============================================================================
31
+ # SECTION 1: Basic Ticker Queries
32
+ # =============================================================================
33
+
34
+ puts "=" * 60
35
+ puts "SECTION 1: Basic Ticker Queries"
36
+ puts "=" * 60
37
+
38
+ # Single ticker query - returns most recent filings for Apple
39
+ filings = client.query.ticker("AAPL").search
40
+ puts "\nApple filings: #{filings.count} total"
41
+ puts "First filing: #{filings.first.form_type} filed on #{filings.first.filed_at}" if filings.any?
42
+
43
+ # Multiple tickers - query filings for multiple companies at once
44
+ filings = client.query.ticker("AAPL", "TSLA", "GOOGL").search
45
+ puts "\nMultiple tickers: #{filings.count} total filings"
46
+
47
+ # Tickers can also be passed as an array
48
+ tickers = %w[MSFT AMZN META]
49
+ filings = client.query.ticker(*tickers).search
50
+ puts "Tech companies: #{filings.count} total filings"
51
+
52
+ # =============================================================================
53
+ # SECTION 2: CIK Queries
54
+ # =============================================================================
55
+
56
+ puts "\n" + "=" * 60
57
+ puts "SECTION 2: CIK Queries"
58
+ puts "=" * 60
59
+
60
+ # CIK with leading zeros (automatically normalized)
61
+ # Apple's CIK is 0000320193
62
+ filings = client.query.cik("0000320193").search
63
+ puts "\nApple by CIK (with zeros): #{filings.count} filings"
64
+
65
+ # CIK without leading zeros (also works)
66
+ filings = client.query.cik("320193").search
67
+ puts "Apple by CIK (without zeros): #{filings.count} filings"
68
+
69
+ # =============================================================================
70
+ # SECTION 3: Form Type Filtering
71
+ # =============================================================================
72
+
73
+ puts "\n" + "=" * 60
74
+ puts "SECTION 3: Form Type Filtering"
75
+ puts "=" * 60
76
+
77
+ # Single form type - Annual reports only
78
+ filings = client.query.ticker("AAPL").form_type("10-K").search
79
+ puts "\nApple 10-K filings: #{filings.count}"
80
+
81
+ # Multiple form types - Annual and quarterly reports
82
+ filings = client.query.ticker("AAPL").form_type("10-K", "10-Q").search
83
+ puts "Apple 10-K and 10-Q filings: #{filings.count}"
84
+
85
+ # Common SEC forms
86
+ # - 10-K: Annual report
87
+ # - 10-Q: Quarterly report
88
+ # - 8-K: Current report (material events)
89
+ # - 4: Insider trading
90
+ # - 13F: Institutional holdings
91
+ # - DEF 14A: Proxy statement
92
+
93
+ # Material events (8-K filings)
94
+ filings = client.query.ticker("TSLA").form_type("8-K").search
95
+ puts "Tesla 8-K filings: #{filings.count}"
96
+
97
+ # International form types - Foreign private issuers
98
+ # - 20-F: Foreign annual report (like 10-K for non-US companies)
99
+ # - 40-F: Canadian annual report (MJDS program)
100
+ # - 6-K: Foreign current report (like 8-K for non-US companies)
101
+
102
+ # Example: Nomura Holdings (Japanese company)
103
+ filings = client.query.ticker("NMR").form_type("20-F").search
104
+ puts "Nomura 20-F filings: #{filings.count}"
105
+
106
+ # Example: Barrick Gold (Canadian company)
107
+ filings = client.query.ticker("ABX").form_type("40-F").search
108
+ puts "Barrick 40-F filings: #{filings.count}"
109
+
110
+ # Mix domestic and international annual reports
111
+ filings = client.query.form_type("10-K", "20-F", "40-F").limit(100).search
112
+ puts "All annual reports (10-K, 20-F, 40-F): #{filings.count}"
113
+
114
+ # =============================================================================
115
+ # SECTION 4: Date Range Filtering
116
+ # =============================================================================
117
+
118
+ puts "\n" + "=" * 60
119
+ puts "SECTION 4: Date Range Filtering"
120
+ puts "=" * 60
121
+
122
+ # Using string dates (ISO 8601 format: YYYY-MM-DD)
123
+ filings = client.query
124
+ .ticker("AAPL")
125
+ .date_range(from: "2023-01-01", to: "2023-12-31")
126
+ .search
127
+ puts "\nApple 2023 filings: #{filings.count}"
128
+
129
+ # Using Date objects
130
+ require "date"
131
+ filings = client.query
132
+ .ticker("AAPL")
133
+ .date_range(from: Date.new(2022, 1, 1), to: Date.new(2022, 12, 31))
134
+ .search
135
+ puts "Apple 2022 filings: #{filings.count}"
136
+
137
+ # Using Time objects (time portion is ignored, uses date only)
138
+ filings = client.query
139
+ .ticker("AAPL")
140
+ .date_range(from: Time.now - (365 * 24 * 60 * 60), to: Time.now)
141
+ .search
142
+ puts "Apple filings in last year: #{filings.count}"
143
+
144
+ # Using Date.today for dynamic queries
145
+ filings = client.query
146
+ .ticker("AAPL")
147
+ .form_type("10-Q")
148
+ .date_range(from: Date.today - 365, to: Date.today)
149
+ .search
150
+ puts "Apple quarterly reports in last year: #{filings.count}"
151
+
152
+ # =============================================================================
153
+ # SECTION 5: Full-Text Search
154
+ # =============================================================================
155
+
156
+ puts "\n" + "=" * 60
157
+ puts "SECTION 5: Full-Text Search"
158
+ puts "=" * 60
159
+
160
+ # Search for specific keywords in filing content
161
+ filings = client.query.search_text("merger acquisition").search
162
+ puts "\nFilings mentioning 'merger acquisition': #{filings.count}"
163
+
164
+ # Combined with ticker filter
165
+ filings = client.query
166
+ .ticker("AAPL")
167
+ .form_type("8-K")
168
+ .search_text("acquisition")
169
+ .search
170
+ puts "Apple 8-K filings mentioning 'acquisition': #{filings.count}"
171
+
172
+ # Search for specific company mentions across all filings
173
+ filings = client.query
174
+ .search_text("artificial intelligence")
175
+ .form_type("10-K")
176
+ .limit(50)
177
+ .search
178
+ puts "10-K filings mentioning 'artificial intelligence': #{filings.count}"
179
+
180
+ # =============================================================================
181
+ # SECTION 6: Combining Multiple Filters
182
+ # =============================================================================
183
+
184
+ puts "\n" + "=" * 60
185
+ puts "SECTION 6: Combining Multiple Filters"
186
+ puts "=" * 60
187
+
188
+ # Complex query: Apple's annual reports from 2020-2023
189
+ filings = client.query
190
+ .ticker("AAPL")
191
+ .form_type("10-K")
192
+ .date_range(from: "2020-01-01", to: "2023-12-31")
193
+ .search
194
+ puts "\nApple 10-K filings 2020-2023: #{filings.count}"
195
+
196
+ # All major tech companies, annual reports, recent years
197
+ filings = client.query
198
+ .ticker("AAPL", "MSFT", "GOOGL", "AMZN", "META")
199
+ .form_type("10-K", "10-Q")
200
+ .date_range(from: "2022-01-01", to: Date.today.to_s)
201
+ .search
202
+ puts "Big Tech quarterly/annual reports since 2022: #{filings.count}"
203
+
204
+ # International + domestic, with search, limited results
205
+ filings = client.query
206
+ .form_type("10-K", "20-F")
207
+ .search_text("climate risk")
208
+ .date_range(from: "2023-01-01", to: "2023-12-31")
209
+ .limit(100)
210
+ .search
211
+ puts "Annual reports mentioning 'climate risk' in 2023: #{filings.count}"
212
+
213
+ # =============================================================================
214
+ # SECTION 7: Limiting Results
215
+ # =============================================================================
216
+
217
+ puts "\n" + "=" * 60
218
+ puts "SECTION 7: Limiting Results"
219
+ puts "=" * 60
220
+
221
+ # Default is 50 results per page
222
+ filings = client.query.ticker("AAPL").search
223
+ puts "\nDefault limit (50): #{filings.to_a.size} filings returned"
224
+
225
+ # Custom limit - get exactly what you need
226
+ filings = client.query.ticker("AAPL").limit(10).search
227
+ puts "Limited to 10: #{filings.to_a.size} filings returned"
228
+
229
+ # Limit 1 - get just the most recent filing
230
+ filing = client.query
231
+ .ticker("TSLA")
232
+ .form_type("10-K")
233
+ .limit(1)
234
+ .search
235
+ .first
236
+ puts "Tesla's latest 10-K: #{filing.filed_at}" if filing
237
+
238
+ # =============================================================================
239
+ # SECTION 8: Manual Pagination with fetch_next_page
240
+ # =============================================================================
241
+
242
+ puts "\n" + "=" * 60
243
+ puts "SECTION 8: Manual Pagination"
244
+ puts "=" * 60
245
+
246
+ # First page of results
247
+ filings = client.query
248
+ .ticker("AAPL")
249
+ .form_type("10-K", "10-Q", "8-K")
250
+ .limit(10)
251
+ .search
252
+
253
+ puts "\nPage 1: #{filings.to_a.size} filings (total: #{filings.count})"
254
+ puts "Has more pages? #{filings.has_more?}"
255
+
256
+ # Fetch subsequent pages manually
257
+ page_number = 1
258
+ while filings.has_more? && page_number < 3 # Limit to 3 pages for demo
259
+ filings = filings.fetch_next_page
260
+ page_number += 1
261
+ puts "Page #{page_number}: #{filings.to_a.size} filings"
262
+ end
263
+
264
+ # =============================================================================
265
+ # SECTION 9: Working with Filing Objects
266
+ # =============================================================================
267
+
268
+ puts "\n" + "=" * 60
269
+ puts "SECTION 9: Working with Filing Objects"
270
+ puts "=" * 60
271
+
272
+ # Get a filing and explore its attributes
273
+ filing = client.query
274
+ .ticker("AAPL")
275
+ .form_type("10-K")
276
+ .limit(1)
277
+ .search
278
+ .first
279
+
280
+ if filing
281
+ puts "\nFiling Details:"
282
+ puts " Ticker: #{filing.ticker}"
283
+ puts " Company: #{filing.company_name}"
284
+ puts " CIK: #{filing.cik}"
285
+ puts " Form Type: #{filing.form_type}"
286
+ puts " Filed At: #{filing.filed_at}"
287
+ puts " Accession Number: #{filing.accession_number}"
288
+ puts " Link to SEC: #{filing.filing_details_url}"
289
+ end
290
+
291
+ # Use Enumerable methods on the collection
292
+ filings = client.query.ticker("AAPL").limit(20).search
293
+
294
+ # Filter to specific form types
295
+ ten_ks = filings.select { |f| f.form_type == "10-K" }
296
+ puts "\nFiltered to 10-K: #{ten_ks.size} filings"
297
+
298
+ # Map to get just tickers
299
+ form_types = filings.map(&:form_type).uniq
300
+ puts "Unique form types: #{form_types.join(", ")}"
301
+
302
+ # Find first matching a condition
303
+ first_8k = filings.find { |f| f.form_type == "8-K" }
304
+ puts "First 8-K: #{first_8k.filed_at}" if first_8k
305
+
306
+ puts "\n" + "=" * 60
307
+ puts "Examples completed successfully!"
308
+ puts "=" * 60