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.
- checksums.yaml +7 -0
- data/.devcontainer/Dockerfile +54 -0
- data/.devcontainer/README.md +178 -0
- data/.devcontainer/devcontainer.json +46 -0
- data/.devcontainer/docker-compose.yml +28 -0
- data/.devcontainer/post-create.sh +51 -0
- data/.devcontainer/post-start.sh +44 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +0 -0
- data/LICENSE.txt +21 -0
- data/MIGRATION.md +274 -0
- data/README.md +370 -0
- data/Rakefile +10 -0
- data/config/secapi.yml.example +57 -0
- data/docs/development-guide.md +291 -0
- data/docs/enumerator_pattern_design.md +483 -0
- data/docs/examples/README.md +58 -0
- data/docs/examples/backfill_filings.rb +419 -0
- data/docs/examples/instrumentation.rb +583 -0
- data/docs/examples/query_builder.rb +308 -0
- data/docs/examples/streaming_notifications.rb +491 -0
- data/docs/index.md +244 -0
- data/docs/migration-guide-v1.md +1091 -0
- data/docs/pre-review-checklist.md +145 -0
- data/docs/project-overview.md +90 -0
- data/docs/project-scan-report.json +60 -0
- data/docs/source-tree-analysis.md +190 -0
- data/lib/sec_api/callback_helper.rb +49 -0
- data/lib/sec_api/client.rb +606 -0
- data/lib/sec_api/collections/filings.rb +267 -0
- data/lib/sec_api/collections/fulltext_results.rb +86 -0
- data/lib/sec_api/config.rb +590 -0
- data/lib/sec_api/deep_freezable.rb +42 -0
- data/lib/sec_api/errors/authentication_error.rb +24 -0
- data/lib/sec_api/errors/configuration_error.rb +5 -0
- data/lib/sec_api/errors/error.rb +75 -0
- data/lib/sec_api/errors/network_error.rb +26 -0
- data/lib/sec_api/errors/not_found_error.rb +23 -0
- data/lib/sec_api/errors/pagination_error.rb +28 -0
- data/lib/sec_api/errors/permanent_error.rb +29 -0
- data/lib/sec_api/errors/rate_limit_error.rb +57 -0
- data/lib/sec_api/errors/reconnection_error.rb +34 -0
- data/lib/sec_api/errors/server_error.rb +25 -0
- data/lib/sec_api/errors/transient_error.rb +28 -0
- data/lib/sec_api/errors/validation_error.rb +23 -0
- data/lib/sec_api/extractor.rb +122 -0
- data/lib/sec_api/filing_journey.rb +477 -0
- data/lib/sec_api/mapping.rb +125 -0
- data/lib/sec_api/metrics_collector.rb +411 -0
- data/lib/sec_api/middleware/error_handler.rb +250 -0
- data/lib/sec_api/middleware/instrumentation.rb +186 -0
- data/lib/sec_api/middleware/rate_limiter.rb +541 -0
- data/lib/sec_api/objects/data_file.rb +34 -0
- data/lib/sec_api/objects/document_format_file.rb +45 -0
- data/lib/sec_api/objects/entity.rb +92 -0
- data/lib/sec_api/objects/extracted_data.rb +118 -0
- data/lib/sec_api/objects/fact.rb +147 -0
- data/lib/sec_api/objects/filing.rb +197 -0
- data/lib/sec_api/objects/fulltext_result.rb +66 -0
- data/lib/sec_api/objects/period.rb +96 -0
- data/lib/sec_api/objects/stream_filing.rb +194 -0
- data/lib/sec_api/objects/xbrl_data.rb +356 -0
- data/lib/sec_api/query.rb +423 -0
- data/lib/sec_api/rate_limit_state.rb +130 -0
- data/lib/sec_api/rate_limit_tracker.rb +154 -0
- data/lib/sec_api/stream.rb +841 -0
- data/lib/sec_api/structured_logger.rb +199 -0
- data/lib/sec_api/types.rb +32 -0
- data/lib/sec_api/version.rb +42 -0
- data/lib/sec_api/xbrl.rb +220 -0
- data/lib/sec_api.rb +137 -0
- data/sig/sec_api.rbs +4 -0
- metadata +217 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Pre-Review Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist before submitting code for review. These items were identified through Epic 3 and Epic 4 retrospectives as common issues caught in code review.
|
|
4
|
+
|
|
5
|
+
## Thread Safety
|
|
6
|
+
|
|
7
|
+
- [ ] **Immutable objects are frozen** - All Dry::Struct value objects should be deeply frozen
|
|
8
|
+
- [ ] **String attributes frozen** - Use `attribute&.freeze` for string attributes in value objects
|
|
9
|
+
- [ ] **Shared state protected** - Any state shared across requests uses Mutex or is immutable
|
|
10
|
+
- [ ] **No instance variable mutation** - Value objects don't mutate after construction
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
# Good: Deep freeze in from_api
|
|
14
|
+
def self.from_api(data)
|
|
15
|
+
new(
|
|
16
|
+
statements_of_income: parse_statement(data["StatementsOfIncome"]).freeze,
|
|
17
|
+
# ...
|
|
18
|
+
).freeze
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Good: String attribute frozen
|
|
22
|
+
attribute? :text, Types::String.optional
|
|
23
|
+
# In from_api:
|
|
24
|
+
text: data["text"]&.freeze
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Test Coverage
|
|
28
|
+
|
|
29
|
+
- [ ] **Unit tests for new value objects** - Every new Dry::Struct class needs dedicated tests
|
|
30
|
+
- [ ] **Edge case tests included**:
|
|
31
|
+
- [ ] `nil` input handling
|
|
32
|
+
- [ ] Empty string/hash/array input
|
|
33
|
+
- [ ] Whitespace-only strings
|
|
34
|
+
- [ ] Invalid type input
|
|
35
|
+
- [ ] **Error message verification** - Exception tests verify message content, not just exception type
|
|
36
|
+
- [ ] **`stubs.verify_stubbed_calls`** - All HTTP stub tests verify stubs were called
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
# Good: Verify error message content
|
|
40
|
+
expect { method_call }.to raise_error(SecApi::ValidationError) do |error|
|
|
41
|
+
expect(error.message).to include("missing required field")
|
|
42
|
+
expect(error.message).to include("period")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Good: Verify stubs in after block or inline
|
|
46
|
+
after { stubs.verify_stubbed_calls }
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Input Validation
|
|
50
|
+
|
|
51
|
+
- [ ] **Validate required parameters** - Raise ArgumentError or ValidationError for missing required input
|
|
52
|
+
- [ ] **URL format validation** - SEC URLs follow predictable patterns
|
|
53
|
+
- [ ] **CIK format handling** - Normalize to 10-digit with leading zeros
|
|
54
|
+
- [ ] **Accession number format** - Support both dashed and undashed formats
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
# Good: Input validation with helpful error
|
|
58
|
+
def ticker(symbol)
|
|
59
|
+
raise ArgumentError, "ticker symbol required" if symbol.nil? || symbol.empty?
|
|
60
|
+
# ...
|
|
61
|
+
end
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Documentation
|
|
65
|
+
|
|
66
|
+
- [ ] **YARD docs on public methods** - `@param`, `@return`, `@raise`, `@example` tags
|
|
67
|
+
- [ ] **Examples match implementation** - YARD examples actually work with current code
|
|
68
|
+
- [ ] **@note for non-obvious behavior** - Document gotchas, limitations, or surprising behavior
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# Good: Complete YARD documentation
|
|
72
|
+
# Extracts XBRL data from a filing
|
|
73
|
+
#
|
|
74
|
+
# @param filing_url [String] URL to the SEC filing
|
|
75
|
+
# @return [XbrlData] Typed XBRL data object
|
|
76
|
+
# @raise [NotFoundError] when filing URL is invalid
|
|
77
|
+
# @raise [ValidationError] when response structure is invalid
|
|
78
|
+
#
|
|
79
|
+
# @example Extract from URL
|
|
80
|
+
# data = client.xbrl.to_json("https://sec.gov/...")
|
|
81
|
+
# data.statements_of_income["Revenue"]
|
|
82
|
+
#
|
|
83
|
+
# @note Element names follow US-GAAP taxonomy exactly
|
|
84
|
+
#
|
|
85
|
+
def to_json(filing_url)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Code Quality
|
|
89
|
+
|
|
90
|
+
- [ ] **DRY - No duplicate code** - Extract shared logic to modules or helpers
|
|
91
|
+
- [ ] **Consistent patterns** - Follow established `from_api` factory method pattern
|
|
92
|
+
- [ ] **No dead code** - Remove commented-out code, unused variables, unreachable branches
|
|
93
|
+
- [ ] **StandardRB passes** - Run `bundle exec standardrb` before review
|
|
94
|
+
|
|
95
|
+
## Error Handling
|
|
96
|
+
|
|
97
|
+
- [ ] **Correct exception types**:
|
|
98
|
+
- `ValidationError` - Malformed data, failed validation
|
|
99
|
+
- `NotFoundError` - Resource not found (404), invalid URL
|
|
100
|
+
- `AuthenticationError` - Invalid API key (401, 403)
|
|
101
|
+
- `NetworkError` - Connection failures, timeouts
|
|
102
|
+
- `ServerError` - API server errors (500-504)
|
|
103
|
+
- [ ] **Actionable error messages** - Include what failed, why, and received data context
|
|
104
|
+
- [ ] **No silent failures** - All errors raised or logged, never swallowed
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
# Good: Actionable error message
|
|
108
|
+
raise ValidationError, "XBRL fact missing required 'period' field. " \
|
|
109
|
+
"Received: #{data.inspect}"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## HTTP Testing Patterns
|
|
113
|
+
|
|
114
|
+
- [ ] **Use stub_connection/build_connection helpers** - See `spec/support/test_helpers.rb`
|
|
115
|
+
- [ ] **Include ErrorHandler middleware** - Required for error scenario tests
|
|
116
|
+
- [ ] **JSON content type in responses** - `{"Content-Type" => "application/json"}`
|
|
117
|
+
- [ ] **Response body as JSON string** - Use `.to_json` on response hashes
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
# Good: Complete stub setup
|
|
121
|
+
stubs = Faraday::Adapter::Test::Stubs.new
|
|
122
|
+
stubs.get("/endpoint") do
|
|
123
|
+
[200, {"Content-Type" => "application/json"}, {data: "value"}.to_json]
|
|
124
|
+
end
|
|
125
|
+
stub_connection(stubs)
|
|
126
|
+
|
|
127
|
+
# ... test code ...
|
|
128
|
+
|
|
129
|
+
stubs.verify_stubbed_calls
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Quick Self-Review
|
|
133
|
+
|
|
134
|
+
Before requesting review, ask yourself:
|
|
135
|
+
|
|
136
|
+
1. **Would this break in a multi-threaded environment?** (Sidekiq, concurrent requests)
|
|
137
|
+
2. **What happens if the API returns unexpected data?** (nil, wrong type, missing fields)
|
|
138
|
+
3. **Can someone understand this code without context?** (clear names, comments where needed)
|
|
139
|
+
4. **Did I test the failure paths, not just success?**
|
|
140
|
+
5. **Does the documentation match what the code actually does?**
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
*Checklist created from Epic 3 & 4 retrospective learnings*
|
|
145
|
+
*Last updated: 2026-01-07*
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# sec_api - Project Overview
|
|
2
|
+
|
|
3
|
+
**Generated:** 2026-01-05
|
|
4
|
+
**Scan Level:** Quick
|
|
5
|
+
**Project Type:** Ruby Library (Gem)
|
|
6
|
+
|
|
7
|
+
## Executive Summary
|
|
8
|
+
|
|
9
|
+
sec_api is a production-grade Ruby client library for accessing SEC EDGAR filings through the sec-api.io API. The project is evolving from a basic v0.1.0 API wrapper into enterprise-level financial data infrastructure for commercial financial analysis platforms.
|
|
10
|
+
|
|
11
|
+
**Current State:** Active development, transitioning from v0.1.0 to v1.0.0
|
|
12
|
+
**Purpose:** Provide Ruby developers with reliable, resilient access to SEC filing data for financial analysis applications
|
|
13
|
+
|
|
14
|
+
## Project Classification
|
|
15
|
+
|
|
16
|
+
| Attribute | Value |
|
|
17
|
+
|-----------|-------|
|
|
18
|
+
| **Repository Type** | Monolith |
|
|
19
|
+
| **Project Type** | Library (Ruby Gem) |
|
|
20
|
+
| **Primary Language** | Ruby 3.2.3 |
|
|
21
|
+
| **Minimum Ruby** | 3.1.0+ |
|
|
22
|
+
| **Distribution** | RubyGems (rubygems.org) |
|
|
23
|
+
| **Architecture Pattern** | Client → Proxy pattern with middleware stack |
|
|
24
|
+
|
|
25
|
+
## Technology Stack
|
|
26
|
+
|
|
27
|
+
### Core Dependencies
|
|
28
|
+
- **faraday** - HTTP client with middleware capabilities
|
|
29
|
+
- **faraday-retry** - Automatic retry middleware
|
|
30
|
+
- **anyway_config** - Configuration management (YAML + environment variables)
|
|
31
|
+
- **dry-struct** - Immutable value objects for type-safe responses
|
|
32
|
+
|
|
33
|
+
### Development Dependencies
|
|
34
|
+
- **rspec** ~> 3.0 - Testing framework
|
|
35
|
+
- **standard** ~> 1.3 - Ruby linter (Standard Ruby style guide)
|
|
36
|
+
- **async-http-faraday** - Async HTTP support for concurrent requests
|
|
37
|
+
|
|
38
|
+
### Key Features (v1.0.0 Target)
|
|
39
|
+
- ✅ Configuration management with validation
|
|
40
|
+
- ✅ Exception hierarchy (TransientError/PermanentError)
|
|
41
|
+
- 🚧 Query builder DSL with fluent interface
|
|
42
|
+
- 🚧 Automatic pagination for large result sets
|
|
43
|
+
- 🚧 XBRL data extraction with validation
|
|
44
|
+
- 🚧 Real-time filing notifications (WebSocket streaming)
|
|
45
|
+
- 🚧 Rate limiting intelligence with proactive throttling
|
|
46
|
+
- 🚧 Production observability hooks
|
|
47
|
+
|
|
48
|
+
## Project Structure
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
sec_api/
|
|
52
|
+
├── lib/sec_api/ # Main library code
|
|
53
|
+
│ ├── client.rb # Client entry point
|
|
54
|
+
│ ├── config.rb # Configuration (anyway_config)
|
|
55
|
+
│ ├── errors/ # Exception hierarchy
|
|
56
|
+
│ ├── middleware/ # Faraday middleware (retry, rate limiting)
|
|
57
|
+
│ ├── collections/ # Collection objects (Filings, etc.)
|
|
58
|
+
│ └── objects/ # Value objects (Filing, Entity, etc.)
|
|
59
|
+
├── spec/ # RSpec tests
|
|
60
|
+
├── config/ # Configuration files
|
|
61
|
+
└── _bmad-output/ # Planning artifacts (PRD, Architecture, Epics)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Development Status
|
|
65
|
+
|
|
66
|
+
### Completed (v0.1.0)
|
|
67
|
+
- Basic query, search, mapping, and extractor endpoints
|
|
68
|
+
- Configuration via anyway_config
|
|
69
|
+
- Immutable value objects with Dry::Struct
|
|
70
|
+
|
|
71
|
+
### In Progress (v1.0.0)
|
|
72
|
+
- Production-grade error handling and retry logic
|
|
73
|
+
- Query builder DSL
|
|
74
|
+
- XBRL extraction with validation
|
|
75
|
+
- Real-time streaming API
|
|
76
|
+
- Rate limiting middleware
|
|
77
|
+
- Observability and instrumentation
|
|
78
|
+
|
|
79
|
+
## Links to Planning Documents
|
|
80
|
+
|
|
81
|
+
- **[PRD](../_bmad-output/planning-artifacts/prd.md)** - Complete product requirements
|
|
82
|
+
- **[Architecture](../_bmad-output/planning-artifacts/architecture.md)** - Architectural decisions and patterns
|
|
83
|
+
- **[Epics & Stories](../_bmad-output/planning-artifacts/epics.md)** - Implementation breakdown
|
|
84
|
+
|
|
85
|
+
## Links to Documentation
|
|
86
|
+
|
|
87
|
+
- [README](../README.md) - Project overview and usage
|
|
88
|
+
- [CHANGELOG](../CHANGELOG.md) - Version history
|
|
89
|
+
- [Source Tree Analysis](./source-tree-analysis.md) - Code organization
|
|
90
|
+
- [Development Guide](./development-guide.md) - Setup and development workflow
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"workflow_version": "1.2.0",
|
|
3
|
+
"timestamps": {
|
|
4
|
+
"started": "2026-01-05T03:28:45Z",
|
|
5
|
+
"last_updated": "2026-01-05T03:42:00Z",
|
|
6
|
+
"completed": "2026-01-05T03:42:00Z"
|
|
7
|
+
},
|
|
8
|
+
"mode": "initial_scan",
|
|
9
|
+
"scan_level": "quick",
|
|
10
|
+
"project_root": "/Users/ljuti/Code/projects/metalsmoney/ruby/sec_api",
|
|
11
|
+
"output_folder": "/Users/ljuti/Code/projects/metalsmoney/ruby/sec_api/docs",
|
|
12
|
+
"completed_steps": [
|
|
13
|
+
{
|
|
14
|
+
"step": "step_1",
|
|
15
|
+
"status": "completed",
|
|
16
|
+
"timestamp": "2026-01-05T03:36:30Z",
|
|
17
|
+
"summary": "Classified as monolith with 1 part"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"step": "step_2",
|
|
21
|
+
"status": "completed",
|
|
22
|
+
"timestamp": "2026-01-05T03:38:00Z",
|
|
23
|
+
"summary": "Found 5 existing docs (README, CHANGELOG, PRD, Architecture, Epics)"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"step": "step_3",
|
|
27
|
+
"status": "completed",
|
|
28
|
+
"timestamp": "2026-01-05T03:40:00Z",
|
|
29
|
+
"summary": "Tech stack: Ruby 3.2.3, Faraday, anyway_config, dry-struct"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"step": "step_complete",
|
|
33
|
+
"status": "completed",
|
|
34
|
+
"timestamp": "2026-01-05T03:42:00Z",
|
|
35
|
+
"summary": "Documentation generation complete, 4 files written"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"current_step": "completed",
|
|
39
|
+
"findings": {
|
|
40
|
+
"project_classification": "Monolith, 1 part, Ruby library",
|
|
41
|
+
"existing_docs_count": 5,
|
|
42
|
+
"technology_stack": "Ruby 3.2.3, Faraday, anyway_config, dry-struct, RSpec",
|
|
43
|
+
"architecture_pattern": "Client → Proxy pattern with Faraday middleware"
|
|
44
|
+
},
|
|
45
|
+
"project_types": [
|
|
46
|
+
{
|
|
47
|
+
"part_id": "main",
|
|
48
|
+
"project_type_id": "library",
|
|
49
|
+
"display_name": "sec_api (Ruby Gem)"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"outputs_generated": [
|
|
53
|
+
"project-scan-report.json",
|
|
54
|
+
"index.md",
|
|
55
|
+
"project-overview.md",
|
|
56
|
+
"source-tree-analysis.md",
|
|
57
|
+
"development-guide.md"
|
|
58
|
+
],
|
|
59
|
+
"resume_instructions": "Workflow complete"
|
|
60
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# sec_api - Source Tree Analysis
|
|
2
|
+
|
|
3
|
+
**Generated:** 2026-01-05
|
|
4
|
+
**Project Root:** `/Users/ljuti/Code/projects/metalsmoney/ruby/sec_api`
|
|
5
|
+
|
|
6
|
+
## Directory Structure with Annotations
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
sec_api/
|
|
10
|
+
├── .github/
|
|
11
|
+
│ └── workflows/ # CI/CD workflows (planned for v1.0)
|
|
12
|
+
├── .git/ # Git repository
|
|
13
|
+
├── .ruby-lsp/ # Ruby LSP cache
|
|
14
|
+
├── _bmad/ # BMAD framework installation
|
|
15
|
+
│ ├── core/ # Core BMAD modules
|
|
16
|
+
│ └── bmm/ # BMM (BMAD Methodology Manager) modules
|
|
17
|
+
├── _bmad-output/ # BMAD planning artifacts
|
|
18
|
+
│ ├── planning-artifacts/ # PRD, Architecture, Epics documents
|
|
19
|
+
│ └── implementation-artifacts/ # (future: sprint status, stories)
|
|
20
|
+
├── bin/
|
|
21
|
+
│ ├── console # Interactive IRB console for development
|
|
22
|
+
│ └── setup # Setup script for dependencies
|
|
23
|
+
├── config/
|
|
24
|
+
│ └── secapi.yml # Configuration file (API key, settings)
|
|
25
|
+
├── docs/ # PROJECT DOCUMENTATION (this directory)
|
|
26
|
+
│ ├── project-overview.md # Project overview (generated)
|
|
27
|
+
│ ├── source-tree-analysis.md # This file
|
|
28
|
+
│ └── project-scan-report.json # Workflow state tracking
|
|
29
|
+
├── lib/ # MAIN LIBRARY CODE
|
|
30
|
+
│ ├── sec_api.rb # Main entry point (requires all files)
|
|
31
|
+
│ └── sec_api/ # Library modules
|
|
32
|
+
│ ├── client.rb # 🔑 Client entry point (delegates to proxies)
|
|
33
|
+
│ ├── config.rb # Configuration management (anyway_config)
|
|
34
|
+
│ ├── version.rb # Gem version constant
|
|
35
|
+
│ ├── collections/ # Collection objects (Enumerable wrappers)
|
|
36
|
+
│ │ ├── filings.rb # Filings collection
|
|
37
|
+
│ │ └── fulltext_results.rb # Full-text search results
|
|
38
|
+
│ ├── errors/ # 🔑 Exception hierarchy
|
|
39
|
+
│ │ ├── error.rb # Base SecApi::Error
|
|
40
|
+
│ │ ├── transient_error.rb # Retryable errors
|
|
41
|
+
│ │ ├── permanent_error.rb # Non-retryable errors
|
|
42
|
+
│ │ ├── rate_limit_error.rb
|
|
43
|
+
│ │ ├── server_error.rb
|
|
44
|
+
│ │ ├── network_error.rb
|
|
45
|
+
│ │ ├── authentication_error.rb
|
|
46
|
+
│ │ ├── not_found_error.rb
|
|
47
|
+
│ │ ├── validation_error.rb
|
|
48
|
+
│ │ └── configuration_error.rb
|
|
49
|
+
│ ├── middleware/ # 🔑 Faraday middleware stack
|
|
50
|
+
│ │ └── error_handler.rb # HTTP status → exception mapping
|
|
51
|
+
│ ├── objects/ # Value objects (Dry::Struct)
|
|
52
|
+
│ │ ├── filing.rb # Filing metadata
|
|
53
|
+
│ │ ├── entity.rb # Company/entity information
|
|
54
|
+
│ │ ├── fulltext_result.rb
|
|
55
|
+
│ │ ├── data_file.rb
|
|
56
|
+
│ │ └── document_format_file.rb
|
|
57
|
+
│ ├── query.rb # Query API proxy
|
|
58
|
+
│ ├── mapping.rb # Mapping API proxy (ticker/CIK resolution)
|
|
59
|
+
│ ├── extractor.rb # Extractor API proxy
|
|
60
|
+
│ └── xbrl.rb # XBRL API proxy
|
|
61
|
+
├── sig/ # RBS type signatures (optional)
|
|
62
|
+
├── spec/ # 🔑 RSPEC TESTS
|
|
63
|
+
│ ├── spec_helper.rb # Test configuration
|
|
64
|
+
│ └── sec_api/ # Test files mirroring lib/ structure
|
|
65
|
+
├── .gitignore
|
|
66
|
+
├── .node-version # Node version (for tooling)
|
|
67
|
+
├── .rspec # RSpec configuration
|
|
68
|
+
├── .rspec_status # Test run status
|
|
69
|
+
├── .standard.yml # Standard Ruby linter configuration
|
|
70
|
+
├── CHANGELOG.md # Version history
|
|
71
|
+
├── CLAUDE.md # Claude session notes
|
|
72
|
+
├── Gemfile # Gem dependencies
|
|
73
|
+
├── Gemfile.lock # Locked dependency versions
|
|
74
|
+
├── LICENSE.txt # MIT License
|
|
75
|
+
├── README.md # Project README
|
|
76
|
+
├── Rakefile # Rake tasks
|
|
77
|
+
└── sec_api.gemspec # Gem specification
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Critical Directories Explained
|
|
81
|
+
|
|
82
|
+
### `/lib/sec_api/` - Main Library Code
|
|
83
|
+
|
|
84
|
+
**Purpose:** Core gem functionality organized by technical layer
|
|
85
|
+
|
|
86
|
+
**Architecture Pattern:** Client → Proxy pattern with middleware stack
|
|
87
|
+
|
|
88
|
+
**Key Components:**
|
|
89
|
+
- **client.rb** - Main entry point, delegates to proxies
|
|
90
|
+
- **errors/** - Complete exception hierarchy (TransientError/PermanentError)
|
|
91
|
+
- **middleware/** - Faraday middleware (retry, rate limiting, error handling)
|
|
92
|
+
- **objects/** - Immutable value objects (Dry::Struct)
|
|
93
|
+
- **collections/** - Collection wrappers with Enumerable interface
|
|
94
|
+
- **Proxies:** query.rb, mapping.rb, extractor.rb, xbrl.rb
|
|
95
|
+
|
|
96
|
+
### `/spec/` - Test Suite
|
|
97
|
+
|
|
98
|
+
**Purpose:** RSpec tests mirroring lib/ structure
|
|
99
|
+
|
|
100
|
+
**Coverage:** >90% target for v1.0.0
|
|
101
|
+
|
|
102
|
+
**Testing Strategy:**
|
|
103
|
+
- VCR/WebMock cassettes for API integration tests
|
|
104
|
+
- Shared examples for cross-cutting behavior (retry, pagination, rate limiting)
|
|
105
|
+
- Unit tests for pure logic
|
|
106
|
+
|
|
107
|
+
### `/_bmad-output/planning-artifacts/` - Planning Documents
|
|
108
|
+
|
|
109
|
+
**Purpose:** Product requirements, architecture decisions, and epic breakdown
|
|
110
|
+
|
|
111
|
+
**Key Files:**
|
|
112
|
+
- **prd.md** - Product Requirements Document
|
|
113
|
+
- **architecture.md** - Architectural decisions and patterns
|
|
114
|
+
- **epics.md** - Epic and story breakdown for implementation
|
|
115
|
+
|
|
116
|
+
### `/config/` - Configuration Files
|
|
117
|
+
|
|
118
|
+
**Purpose:** YAML configuration and local overrides
|
|
119
|
+
|
|
120
|
+
**Files:**
|
|
121
|
+
- **secapi.yml** - Default configuration (API key, retry settings, etc.)
|
|
122
|
+
- **secapi.local.yml** (gitignored) - Local environment overrides
|
|
123
|
+
|
|
124
|
+
## Entry Points
|
|
125
|
+
|
|
126
|
+
### Main Entry Point
|
|
127
|
+
**File:** `lib/sec_api.rb`
|
|
128
|
+
**Purpose:** Requires all library files, provides top-level namespace
|
|
129
|
+
|
|
130
|
+
### Client Initialization
|
|
131
|
+
**File:** `lib/sec_api/client.rb`
|
|
132
|
+
**Usage:**
|
|
133
|
+
```ruby
|
|
134
|
+
client = SecApi::Client.new # Auto-loads config from YAML
|
|
135
|
+
client.query # Query API proxy
|
|
136
|
+
client.mapping # Mapping API proxy
|
|
137
|
+
client.extractor # Extractor API proxy
|
|
138
|
+
client.xbrl # XBRL API proxy
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Development Console
|
|
142
|
+
**File:** `bin/console`
|
|
143
|
+
**Purpose:** Interactive IRB session with gem loaded for manual testing
|
|
144
|
+
|
|
145
|
+
## Code Organization Patterns
|
|
146
|
+
|
|
147
|
+
### Naming Conventions
|
|
148
|
+
- **Modules/Classes:** `SecApi::` namespace, PascalCase
|
|
149
|
+
- **Files:** snake_case matching class names
|
|
150
|
+
- **Methods:** snake_case (Ruby standard)
|
|
151
|
+
- **Constants:** SCREAMING_SNAKE_CASE
|
|
152
|
+
|
|
153
|
+
### File-to-Class Mapping
|
|
154
|
+
- `lib/sec_api/client.rb` → `SecApi::Client`
|
|
155
|
+
- `lib/sec_api/errors/rate_limit_error.rb` → `SecApi::RateLimitError`
|
|
156
|
+
- `lib/sec_api/objects/filing.rb` → `SecApi::Filing`
|
|
157
|
+
|
|
158
|
+
### Dependencies
|
|
159
|
+
- **External:** Faraday, anyway_config, dry-struct
|
|
160
|
+
- **Internal:** Client → Proxies → Middleware → HTTP API
|
|
161
|
+
|
|
162
|
+
## Integration Points
|
|
163
|
+
|
|
164
|
+
### External API
|
|
165
|
+
- **sec-api.io REST API** - All HTTP requests via Faraday
|
|
166
|
+
- **Base URL:** `https://api.sec-api.io` (configurable)
|
|
167
|
+
- **Authentication:** API key in headers
|
|
168
|
+
|
|
169
|
+
### Configuration
|
|
170
|
+
- **YAML files:** `config/secapi.yml`
|
|
171
|
+
- **Environment variables:** `SECAPI_*` prefix
|
|
172
|
+
- **Managed by:** anyway_config gem
|
|
173
|
+
|
|
174
|
+
### Testing
|
|
175
|
+
- **Framework:** RSpec
|
|
176
|
+
- **Mocking:** VCR/WebMock for HTTP requests
|
|
177
|
+
- **Linting:** Standard Ruby (standardrb)
|
|
178
|
+
|
|
179
|
+
## Future Directories (Planned for v1.0.0)
|
|
180
|
+
|
|
181
|
+
Based on the Architecture document, these directories will be added:
|
|
182
|
+
|
|
183
|
+
- `lib/sec_api/proxies/` - Organized proxy objects
|
|
184
|
+
- `lib/sec_api/middleware/retry_config.rb` - Enhanced retry configuration
|
|
185
|
+
- `lib/sec_api/middleware/rate_limiter.rb` - Rate limiting middleware
|
|
186
|
+
- `lib/sec_api/middleware/instrumentation.rb` - Observability hooks
|
|
187
|
+
- `spec/support/shared_examples/` - Shared test behavior
|
|
188
|
+
- `spec/fixtures/vcr_cassettes/` - VCR cassettes organized by proxy
|
|
189
|
+
- `docs/examples/` - Usage examples (query, backfill, streaming)
|
|
190
|
+
- `docs/migration-guide-v1.md` - v0.1.0 → v1.0.0 migration guide
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module SecApi
|
|
6
|
+
# Shared helper methods for callback invocation and error handling.
|
|
7
|
+
#
|
|
8
|
+
# This module provides consistent callback error logging across all middleware
|
|
9
|
+
# and client code. All callback invocations should use this module's helpers
|
|
10
|
+
# to ensure consistent structured logging when callbacks fail.
|
|
11
|
+
#
|
|
12
|
+
# @example Including in a middleware class
|
|
13
|
+
# class MyMiddleware < Faraday::Middleware
|
|
14
|
+
# include SecApi::CallbackHelper
|
|
15
|
+
#
|
|
16
|
+
# def call(env)
|
|
17
|
+
# invoke_callback_safely("my_callback") do
|
|
18
|
+
# @config.my_callback&.call(data: "value")
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
module CallbackHelper
|
|
24
|
+
# Logs callback errors to the configured logger with structured JSON format.
|
|
25
|
+
#
|
|
26
|
+
# @param callback_name [String] Name of the callback that failed
|
|
27
|
+
# @param error [Exception] The exception that was raised
|
|
28
|
+
# @param config [SecApi::Config, nil] Config object with logger (optional)
|
|
29
|
+
# @return [void]
|
|
30
|
+
def log_callback_error(callback_name, error, config = nil)
|
|
31
|
+
# Use instance variable @config if config not passed
|
|
32
|
+
cfg = config || (defined?(@config) ? @config : nil) || (defined?(@_config) ? @_config : nil)
|
|
33
|
+
return unless cfg&.logger
|
|
34
|
+
|
|
35
|
+
begin
|
|
36
|
+
cfg.logger.error do
|
|
37
|
+
{
|
|
38
|
+
event: "secapi.callback_error",
|
|
39
|
+
callback: callback_name,
|
|
40
|
+
error_class: error.class.name,
|
|
41
|
+
error_message: error.message
|
|
42
|
+
}.to_json
|
|
43
|
+
end
|
|
44
|
+
rescue
|
|
45
|
+
# Don't let logging errors break anything
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|