sec_query 1.3.1 → 1.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7ca7e36c7306d858ca010571400b4df5c273897d
4
- data.tar.gz: b93df93f32e265f347c8fcd5b66382a764b0040a
3
+ metadata.gz: 85944bfa167ef27d49aee0beaeed632b44a44fa0
4
+ data.tar.gz: 536db81b013206c45038b8f10d697ffc4158621c
5
5
  SHA512:
6
- metadata.gz: 2067ffe3a65836341b7c37ac488d9e32c5806064a11774b611d1c69e4ae14ea6346d7facfb37600d720f6b217762bb95f23c51c89e89b7b9b218b94458db92dc
7
- data.tar.gz: 9d0e6373c249503fadd9e580f8a2b2e69789d7368790af5dd02787f714c475efdf452d27566ad0dd3c86ea6c2d81d14d7a1c51d55bb8d36710974c5d061effc8
6
+ metadata.gz: baccb44f714eb163c13aa2f5313d8e6fd34794ee4cdb92bd386dc1c0e2c9e666475983d8662de49c8f99d42daf44a7bfbd429bfd00dd585140424770e3dc8295
7
+ data.tar.gz: 26a9c698c541f28f980c7937921f761665c721ffb4c86021b42b89bfcf0e911d1adf7c925099e09919060b982a26bde7ac541b5f54343b7ffe013f9d9415b02c
@@ -0,0 +1,11 @@
1
+ sudo: false
2
+ language: ruby
3
+ bundler_args: ''
4
+ rvm:
5
+ - 2.5.1
6
+ - 2.4.4
7
+ - 2.3.4
8
+ - 2.3.7
9
+ before_install:
10
+ - gem install bundler --version '~> 2.0.1'
11
+
data/README.md CHANGED
@@ -88,7 +88,7 @@ Middle initial or name is optional, but helps when there are multiple results fo
88
88
 
89
89
  Returns a list of Sec::Filing instances for an Sec::Entity
90
90
 
91
- ### Sec::Filing
91
+ ### SecQuery::Filing
92
92
 
93
93
  SecQuery::Filing instance may contains the following attributes:
94
94
 
@@ -99,6 +99,7 @@ SecQuery::Filing instance may contains the following attributes:
99
99
  * term
100
100
  * date
101
101
  * file_id
102
+ * detail
102
103
 
103
104
  #### Class Methods
104
105
 
@@ -115,6 +116,27 @@ end
115
116
 
116
117
  Requires a block. Returns the most recent filings. Use start, count and limit to iterate through recent filings.
117
118
 
119
+ ### SecQuery::FilingDetail
120
+ Represents the detail page for a given filing.
121
+ Ex: [Filing Detail page](https://www.sec.gov/Archives/edgar/data/320193/000032019317000070/0000320193-17-000070-index.htm) of Apple's Annual Report from 2017
122
+
123
+ #### Instance Methods
124
+ * link
125
+ * filing_date
126
+ * accepted_date
127
+ * period_of_report
128
+ * sec_access_number
129
+ * document_count
130
+ * format_files
131
+ * data_files
132
+
133
+ #### Class Methods
134
+ ##### .fetch
135
+ ```
136
+ appl_10k_details_url = 'https://www.sec.gov/Archives/edgar/data/320193/000032019317000070/0000320193-17-000070-index.htm'
137
+ filing_detail = SecQuery::FilingDetail.fetch(appl_10k_details_url)
138
+ ```
139
+
118
140
  ## To Whom It May Concern at the SEC
119
141
 
120
142
  Over the last decade, I have gotten to know Edgar quite extensively and I have grown quite fond of it and the information it contains. So it is with my upmost respect that I make the following suggestions:
data/Rakefile CHANGED
@@ -1 +1,9 @@
1
- require 'bundler/gem_tasks'
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task test: :spec
7
+
8
+ task default: :test
9
+
@@ -12,5 +12,6 @@ require 'rubygems'
12
12
  # internal
13
13
  require 'sec_query/entity'
14
14
  require 'sec_query/filing'
15
+ require 'sec_query/filing_detail'
15
16
  require 'sec_query/sec_uri'
16
17
  require 'sec_query/version'
@@ -14,6 +14,10 @@ module SecQuery
14
14
  end
15
15
  end
16
16
 
17
+ def detail
18
+ @detail ||= FilingDetail.fetch(@link)
19
+ end
20
+
17
21
  def self.fetch(uri, &blk)
18
22
  open(uri) do |rss|
19
23
  parse_rss(rss, &blk)
@@ -138,6 +142,11 @@ module SecQuery
138
142
  parse(cik, document)
139
143
  end
140
144
 
145
+ def self.last(cik, args = {})
146
+ filings = find(cik, 0, 1, args)
147
+ filings.is_a?(Array) ? filings.first : nil
148
+ end
149
+
141
150
  def self.parse(cik, document)
142
151
  filings = []
143
152
  if document.xpath('//content').to_s.length > 0
@@ -0,0 +1,67 @@
1
+ # encoding: UTF-8
2
+
3
+ module SecQuery
4
+ # => SecQuery::FilingDetail
5
+ # SecQuery::FilingDetail requests and parses Filing Detail for any given SecQuery::Filing
6
+ class FilingDetail
7
+ COLUMNS = [:link, :filing_date, :accepted_date, :period_of_report, :sec_access_number, :document_count, :format_files, :data_files]
8
+
9
+ attr_accessor(*COLUMNS)
10
+
11
+ def initialize(filing_detail)
12
+ COLUMNS.each do |column|
13
+ instance_variable_set("@#{ column }", filing_detail[column])
14
+ end
15
+ end
16
+
17
+ def self.fetch(uri)
18
+ document = Nokogiri::HTML(open(uri.gsub('http:', 'https:')))
19
+ filing_date = document.xpath('//*[@id="formDiv"]/div[2]/div[1]/div[2]').text
20
+ accepted_date = document.xpath('//*[@id="formDiv"]/div[2]/div[1]/div[4]').text
21
+ period_of_report = document.xpath('//*[@id="formDiv"]/div[2]/div[2]/div[2]').text
22
+ sec_access_number = document.xpath('//*[@id="secNum"]/text()').text.strip
23
+ document_count = document.xpath('//*[@id="formDiv"]/div[2]/div[1]/div[6]').text.to_i
24
+ format_files_table = document.xpath("//table[@summary='Document Format Files']")
25
+ data_files_table = document.xpath("//table[@summary='Data Files']")
26
+
27
+ format_files = (parsed = parse_files(format_files_table)) && (parsed || [])
28
+ data_files = (parsed = parse_files(data_files_table)) && (parsed || [])
29
+
30
+ new({uri: uri,
31
+ filing_date: filing_date,
32
+ accepted_date: accepted_date,
33
+ period_of_report: period_of_report,
34
+ sec_access_number: sec_access_number,
35
+ document_count: document_count,
36
+ format_files: format_files,
37
+ data_files: data_files})
38
+ end
39
+
40
+ def self.parse_files(format_files_table)
41
+ # get table headers
42
+ headers = []
43
+ format_files_table.xpath('//th').each do |th|
44
+ headers << th.text
45
+ end
46
+
47
+ # get table rows
48
+ rows = []
49
+ format_files_table.xpath('//tr').each_with_index do |row, i|
50
+ rows[i] = {}
51
+ row.xpath('td').each_with_index do |td, j|
52
+ if td.children.first && td.children.first.name == 'a'
53
+ relative_url = td.children.first.attributes.first[1].value
54
+ rows[i][headers[j]] = {
55
+ 'link' => "https://www.sec.gov#{relative_url}",
56
+ 'text' => td.text.gsub(/\A\p{Space}*/, '')
57
+ }
58
+ else
59
+ rows[i][headers[j]] = td.text.gsub(/\A\p{Space}*/, '')
60
+ end
61
+ end
62
+ end
63
+
64
+ rows.reject(&:empty?)
65
+ end
66
+ end
67
+ end
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
2
  # => SecQuery::VERSION
3
3
  module SecQuery
4
- VERSION = "1.3.1"
4
+ VERSION = "1.4.0"
5
5
  end
@@ -19,6 +19,8 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ['lib']
21
21
 
22
+ s.add_development_dependency 'bundler', '~> 2.0.1'
23
+ s.add_development_dependency 'rake'
22
24
  s.add_development_dependency 'rspec', '~> 3.5'
23
25
  s.add_development_dependency 'vcr', '~> 3.0'
24
26
  s.add_development_dependency 'webmock', '~> 2.3'
@@ -26,6 +28,6 @@ Gem::Specification.new do |s|
26
28
  s.add_development_dependency 'byebug', '~> 9.0'
27
29
  s.add_runtime_dependency 'rest-client', '~> 2.0'
28
30
  s.add_runtime_dependency 'addressable', '~> 2.5'
29
- s.add_runtime_dependency 'nokogiri', '~> 1.7'
31
+ s.add_runtime_dependency 'nokogiri', '>= 1.8.5'
30
32
  s.add_runtime_dependency 'activesupport', '>= 0'
31
- end
33
+ end
@@ -101,5 +101,61 @@ describe SecQuery::Filing do
101
101
  expect(f.content).to match(/^(<SEC-DOCUMENT>)/)
102
102
  end
103
103
  end
104
+
105
+ describe "::last", vcr: { cassette_name: "Steve Jobs"} do
106
+ let(:cik) { "0000320193" }
107
+
108
+ context 'when querying by cik' do
109
+ let(:filing) { SecQuery::Filing.last(cik) }
110
+
111
+ it 'returns the first filing' do
112
+ expect(filing).to be_kind_of(SecQuery::Filing)
113
+ is_valid_filing?(filing)
114
+ end
115
+ end
116
+
117
+ context 'when querying cik and by type param' do
118
+ let(:filing) { SecQuery::Filing.last(cik,{ type: "10-K" }) }
119
+
120
+ describe "Filings", vcr: { cassette_name: "Steve Jobs"} do
121
+ it "should return filing of type 10-K" do
122
+ expect(filing).to be_kind_of(SecQuery::Filing)
123
+ expect(filing.term).to eq "10-K"
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ describe '#detail', vcr: { cassette_name: 'Steve Jobs'} do
131
+ let(:cik) { '0000320193' }
132
+ let(:filing) { SecQuery::Filing.find(cik, 0, 1, {type: type}).first }
133
+ subject(:filing_detail) { filing.detail }
134
+
135
+ shared_examples 'Valid SecQuery::FilingDetail' do |filing_type|
136
+ it 'valid filing detail' do
137
+ expect(filing_detail).to be_a SecQuery::FilingDetail
138
+ expect((Date.strptime(subject.filing_date, '%Y-%m-%d') rescue false)).to be_a Date
139
+ expect((DateTime.strptime(subject.accepted_date, '%Y-%m-%d %H:%M:%S') rescue false)).to be_a DateTime
140
+ expect((Date.strptime(subject.period_of_report, '%Y-%m-%d') rescue false)).to be_a Date
141
+ expect(filing_detail.sec_access_number).to match /^[0-9]{10}-[0-9]{2}-[0-9]{6}$/ # ex: 0000320193-18-000100
142
+ expect(filing_detail.document_count).to be > 0
143
+
144
+
145
+ expect(filing_detail.data_files).not_to be_empty if filing_type == '10-K'
146
+ expect(filing_detail.data_files).to be_empty if filing_type == '4'
147
+ expect(filing_detail.format_files).not_to be_empty
148
+ end
149
+ end
150
+
151
+ context '10-K' do
152
+ let(:type) { '10-K' }
153
+ it_behaves_like 'Valid SecQuery::FilingDetail', '10-K'
154
+ end
155
+
156
+ context 'Form 4' do
157
+ let(:type) { '4' }
158
+ it_behaves_like 'Valid SecQuery::FilingDetail', '4'
159
+ end
104
160
  end
105
161
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sec_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ty Rauber
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-05-20 00:00:00.000000000 Z
11
+ date: 2019-04-29 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.1
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: rspec
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -112,16 +140,16 @@ dependencies:
112
140
  name: nokogiri
113
141
  requirement: !ruby/object:Gem::Requirement
114
142
  requirements:
115
- - - "~>"
143
+ - - ">="
116
144
  - !ruby/object:Gem::Version
117
- version: '1.7'
145
+ version: 1.8.5
118
146
  type: :runtime
119
147
  prerelease: false
120
148
  version_requirements: !ruby/object:Gem::Requirement
121
149
  requirements:
122
- - - "~>"
150
+ - - ">="
123
151
  - !ruby/object:Gem::Version
124
- version: '1.7'
152
+ version: 1.8.5
125
153
  - !ruby/object:Gem::Dependency
126
154
  name: activesupport
127
155
  requirement: !ruby/object:Gem::Requirement
@@ -145,13 +173,14 @@ extensions: []
145
173
  extra_rdoc_files: []
146
174
  files:
147
175
  - ".gitignore"
176
+ - ".travis.yml"
148
177
  - Gemfile
149
- - Gemfile.lock
150
178
  - README.md
151
179
  - Rakefile
152
180
  - lib/sec_query.rb
153
181
  - lib/sec_query/entity.rb
154
182
  - lib/sec_query/filing.rb
183
+ - lib/sec_query/filing_detail.rb
155
184
  - lib/sec_query/sec_uri.rb
156
185
  - lib/sec_query/version.rb
157
186
  - sec_query.gemspec