sec_query 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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