sec_edgar 0.0.1
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/lib/assets/1996/QTR1/master.idx +49935 -0
 - data/lib/assets/1996/QTR2/master.idx +47669 -0
 - data/lib/assets/1996/QTR3/master.idx +50651 -0
 - data/lib/assets/1996/QTR4/master.idx +54399 -0
 - data/lib/sec_edgar/address.rb +3 -0
 - data/lib/sec_edgar/basic_stats.rb +26 -0
 - data/lib/sec_edgar/derivative_transaction.rb +21 -0
 - data/lib/sec_edgar/entity.rb +74 -0
 - data/lib/sec_edgar/filing.rb +171 -0
 - data/lib/sec_edgar/filing_parser.rb +216 -0
 - data/lib/sec_edgar/filing_persister.rb +53 -0
 - data/lib/sec_edgar/filing_updater.rb +52 -0
 - data/lib/sec_edgar/footnote.rb +3 -0
 - data/lib/sec_edgar/ftp_client.rb +35 -0
 - data/lib/sec_edgar/nil_ownership_document.rb +39 -0
 - data/lib/sec_edgar/officer_title.rb +39 -0
 - data/lib/sec_edgar/ownership4Document.xsd.xml +141 -0
 - data/lib/sec_edgar/ownershipDocumentCommon.xsd.xml +890 -0
 - data/lib/sec_edgar/ownership_document.rb +121 -0
 - data/lib/sec_edgar/poll.rb +132 -0
 - data/lib/sec_edgar/sec_uri.rb +102 -0
 - data/lib/sec_edgar/transaction.rb +20 -0
 - data/lib/sec_edgar/utils.rb +28 -0
 - data/lib/sec_edgar.rb +26 -0
 - metadata +123 -0
 
| 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module SecEdgar
         
     | 
| 
      
 2 
     | 
    
         
            +
              module BasicStats
         
     | 
| 
      
 3 
     | 
    
         
            +
                extend self
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def precision(tp, fp)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  tp.fdiv(tp + fp)
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def recall(tp, fn)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  tp.fdiv(tp + fn)
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def f_beta(tp, fp, fn, beta)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  p = precision(tp, fp)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  r = recall(tp, fn)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  b2 = beta ** 2
         
     | 
| 
      
 17 
     | 
    
         
            +
                  (1 + b2) * ((p * r).fdiv((b2 * p) + r))
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def f1(tp, fp, fn)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  p = precision(tp, fp)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  r = recall(tp, fn)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  2 * ((p * r).fdiv(p + r))
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,21 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module SecEdgar
         
     | 
| 
      
 2 
     | 
    
         
            +
              DerivativeTransaction = Struct.new(
         
     | 
| 
      
 3 
     | 
    
         
            +
                :acquired_or_disposed_code,
         
     | 
| 
      
 4 
     | 
    
         
            +
                :conversion_or_exercise_price,
         
     | 
| 
      
 5 
     | 
    
         
            +
                :deemed_execution_date,
         
     | 
| 
      
 6 
     | 
    
         
            +
                :exercise_date,
         
     | 
| 
      
 7 
     | 
    
         
            +
                :expiration_date,
         
     | 
| 
      
 8 
     | 
    
         
            +
                :underlying_security_title,
         
     | 
| 
      
 9 
     | 
    
         
            +
                :underlying_security_shares,
         
     | 
| 
      
 10 
     | 
    
         
            +
                :nature_of_ownership,
         
     | 
| 
      
 11 
     | 
    
         
            +
                :code,
         
     | 
| 
      
 12 
     | 
    
         
            +
                :shares,
         
     | 
| 
      
 13 
     | 
    
         
            +
                :security_title,
         
     | 
| 
      
 14 
     | 
    
         
            +
                :direct_or_indirect_code,
         
     | 
| 
      
 15 
     | 
    
         
            +
                :form_type,
         
     | 
| 
      
 16 
     | 
    
         
            +
                :equity_swap_involved,
         
     | 
| 
      
 17 
     | 
    
         
            +
                :transaction_date,
         
     | 
| 
      
 18 
     | 
    
         
            +
                :shares_after,
         
     | 
| 
      
 19 
     | 
    
         
            +
                :price_per_share
         
     | 
| 
      
 20 
     | 
    
         
            +
              )
         
     | 
| 
      
 21 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,74 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # encoding: UTF-8
         
     | 
| 
      
 2 
     | 
    
         
            +
            module SecEdgar
         
     | 
| 
      
 3 
     | 
    
         
            +
              class Entity
         
     | 
| 
      
 4 
     | 
    
         
            +
                COLUMNS = [
         
     | 
| 
      
 5 
     | 
    
         
            +
                  :cik,
         
     | 
| 
      
 6 
     | 
    
         
            +
                  :name,
         
     | 
| 
      
 7 
     | 
    
         
            +
                  :mailing_address,
         
     | 
| 
      
 8 
     | 
    
         
            +
                  :business_address,
         
     | 
| 
      
 9 
     | 
    
         
            +
                  :assigned_sic,
         
     | 
| 
      
 10 
     | 
    
         
            +
                  :assigned_sic_desc,
         
     | 
| 
      
 11 
     | 
    
         
            +
                  :assigned_sic_href,
         
     | 
| 
      
 12 
     | 
    
         
            +
                  :assitant_director,
         
     | 
| 
      
 13 
     | 
    
         
            +
                  :cik_href,
         
     | 
| 
      
 14 
     | 
    
         
            +
                  :formerly_names,
         
     | 
| 
      
 15 
     | 
    
         
            +
                  :state_location,
         
     | 
| 
      
 16 
     | 
    
         
            +
                  :state_location_href,
         
     | 
| 
      
 17 
     | 
    
         
            +
                  :state_of_incorporation
         
     | 
| 
      
 18 
     | 
    
         
            +
                ]
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                attr_accessor(*COLUMNS)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def initialize(entity)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  COLUMNS.each do |column|
         
     | 
| 
      
 24 
     | 
    
         
            +
                    instance_variable_set("@#{ column }", entity[column.to_s])
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def filings
         
     | 
| 
      
 29 
     | 
    
         
            +
                  Filing.find(@cik)
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def transactions
         
     | 
| 
      
 33 
     | 
    
         
            +
                  Transaction.find(@cik)
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                def self.query(url)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  RestClient.get(url) do |response, request, result, &block|
         
     | 
| 
      
 38 
     | 
    
         
            +
                    case response.code
         
     | 
| 
      
 39 
     | 
    
         
            +
                    when 200
         
     | 
| 
      
 40 
     | 
    
         
            +
                      return response
         
     | 
| 
      
 41 
     | 
    
         
            +
                    else
         
     | 
| 
      
 42 
     | 
    
         
            +
                      response.return!(request, result, &block)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                # def self.find(entity_args)
         
     | 
| 
      
 48 
     | 
    
         
            +
                #   temp = {}
         
     | 
| 
      
 49 
     | 
    
         
            +
                #   temp[:url] = SecURI.browse_edgar_uri(entity_args)
         
     | 
| 
      
 50 
     | 
    
         
            +
                #   temp[:url][:action] = :getcompany
         
     | 
| 
      
 51 
     | 
    
         
            +
                #   response = query(temp[:url].output_atom.to_s)
         
     | 
| 
      
 52 
     | 
    
         
            +
                #   document = Nokogiri::HTML(response)
         
     | 
| 
      
 53 
     | 
    
         
            +
                #   xml = document.xpath("//feed/company-info")
         
     | 
| 
      
 54 
     | 
    
         
            +
                #   Entity.new(parse(xml))
         
     | 
| 
      
 55 
     | 
    
         
            +
                # end
         
     | 
| 
      
 56 
     | 
    
         
            +
                #
         
     | 
| 
      
 57 
     | 
    
         
            +
                # def self.parse(xml)
         
     | 
| 
      
 58 
     | 
    
         
            +
                #   content = Hash.from_xml(xml.to_s)
         
     | 
| 
      
 59 
     | 
    
         
            +
                #   if content['company_info'].present?
         
     | 
| 
      
 60 
     | 
    
         
            +
                #     content = content['company_info']
         
     | 
| 
      
 61 
     | 
    
         
            +
                #     content['name'] = content.delete('conformed_name')
         
     | 
| 
      
 62 
     | 
    
         
            +
                #     if content['formerly_names'].present?
         
     | 
| 
      
 63 
     | 
    
         
            +
                #       content['formerly_names'] = content.delete('formerly_names')['names']
         
     | 
| 
      
 64 
     | 
    
         
            +
                #     end
         
     | 
| 
      
 65 
     | 
    
         
            +
                #     content['addresses']['address'].each do |address|
         
     | 
| 
      
 66 
     | 
    
         
            +
                #       content["#{address['type']}_address"] = address
         
     | 
| 
      
 67 
     | 
    
         
            +
                #     end
         
     | 
| 
      
 68 
     | 
    
         
            +
                #     return content
         
     | 
| 
      
 69 
     | 
    
         
            +
                #   else
         
     | 
| 
      
 70 
     | 
    
         
            +
                #     return {}
         
     | 
| 
      
 71 
     | 
    
         
            +
                #   end
         
     | 
| 
      
 72 
     | 
    
         
            +
                # end
         
     | 
| 
      
 73 
     | 
    
         
            +
              end
         
     | 
| 
      
 74 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,171 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # encoding: UTF-8
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module SecEdgar
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Filing
         
     | 
| 
      
 5 
     | 
    
         
            +
                COLUMNS = [:cik, :title, :summary, :link, :term, :date, :file_id]
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                attr_accessor(*COLUMNS)
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(filing)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  COLUMNS.each do |column|
         
     | 
| 
      
 11 
     | 
    
         
            +
                    instance_variable_set("@#{ column }", filing[column])
         
     | 
| 
      
 12 
     | 
    
         
            +
                  end
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def self.fetch(uri, &blk)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  open(uri) do |rss|
         
     | 
| 
      
 17 
     | 
    
         
            +
                    parse_rss(rss, &blk)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def self.recent(options = {}, &blk)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  start = options.fetch(:start, 0)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  count = options.fetch(:count, 100)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  limit = options.fetch(:limit, 100)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  limited_count = [limit - start, count].min
         
     | 
| 
      
 26 
     | 
    
         
            +
                  fetch(uri_for_recent(start, limited_count), &blk)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  start += count
         
     | 
| 
      
 28 
     | 
    
         
            +
                  return if start >= limit
         
     | 
| 
      
 29 
     | 
    
         
            +
                  recent({ start: start, count: count, limit: limit }, &blk)
         
     | 
| 
      
 30 
     | 
    
         
            +
                rescue OpenURI::HTTPError => e
         
     | 
| 
      
 31 
     | 
    
         
            +
                  puts e
         
     | 
| 
      
 32 
     | 
    
         
            +
                  return
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def self.for_cik(cik, options = {}, &blk)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  start = options.fetch(:start, 0)
         
     | 
| 
      
 37 
     | 
    
         
            +
                  count = options.fetch(:count, 100)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  limit = options.fetch(:limit, 100)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  fetch(uri_for_cik(cik, start, count), &blk)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  start += count
         
     | 
| 
      
 41 
     | 
    
         
            +
                  return if start >= limit
         
     | 
| 
      
 42 
     | 
    
         
            +
                  for_cik(cik, { start: start, count: count, limit: limit }, &blk)
         
     | 
| 
      
 43 
     | 
    
         
            +
                rescue OpenURI::HTTPError
         
     | 
| 
      
 44 
     | 
    
         
            +
                  return
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def self.for_date(date, &blk)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  ftp = Net::FTP.new('ftp.sec.gov')
         
     | 
| 
      
 49 
     | 
    
         
            +
                  ftp.login
         
     | 
| 
      
 50 
     | 
    
         
            +
                  file_name = ftp.nlst("edgar/daily-index/#{ date.to_sec_uri_format }*")[0]
         
     | 
| 
      
 51 
     | 
    
         
            +
                  ftp.close
         
     | 
| 
      
 52 
     | 
    
         
            +
                  open("ftp://ftp.sec.gov/#{ file_name }") do |file|
         
     | 
| 
      
 53 
     | 
    
         
            +
                    if file_name[-2..-1] == 'gz'
         
     | 
| 
      
 54 
     | 
    
         
            +
                      gz_reader = Zlib::GzipReader.new(file)
         
     | 
| 
      
 55 
     | 
    
         
            +
                      gz_reader.rewind
         
     | 
| 
      
 56 
     | 
    
         
            +
                      filings_for_index(gz_reader).each(&blk)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    else
         
     | 
| 
      
 58 
     | 
    
         
            +
                      filings_for_index(file).each(&blk)
         
     | 
| 
      
 59 
     | 
    
         
            +
                    end
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
                rescue Net::FTPTempError
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def self.filings_for_index(index)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  [].tap do |filings|
         
     | 
| 
      
 66 
     | 
    
         
            +
                    content_section = false
         
     | 
| 
      
 67 
     | 
    
         
            +
                    index.each_line do |row|
         
     | 
| 
      
 68 
     | 
    
         
            +
                      content_section = true if row.include?('-------------')
         
     | 
| 
      
 69 
     | 
    
         
            +
                      next if !content_section || row.include?('------------')
         
     | 
| 
      
 70 
     | 
    
         
            +
                      filing = filing_for_index_row(row)
         
     | 
| 
      
 71 
     | 
    
         
            +
                      filings << filing unless filing.nil?
         
     | 
| 
      
 72 
     | 
    
         
            +
                    end
         
     | 
| 
      
 73 
     | 
    
         
            +
                  end
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                def self.filing_for_index_row(row)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  data = row.split(/   /).reject(&:blank?).map(&:strip)
         
     | 
| 
      
 78 
     | 
    
         
            +
                  data = row.split(/  /).reject(&:blank?).map(&:strip) if data.count == 4
         
     | 
| 
      
 79 
     | 
    
         
            +
                  data[1].gsub!('/ADV', '')
         
     | 
| 
      
 80 
     | 
    
         
            +
                  data.delete_at(1) if data[1][0] == '/'
         
     | 
| 
      
 81 
     | 
    
         
            +
                  return nil unless Regexp.new(/\d{8}/).match(data[3])
         
     | 
| 
      
 82 
     | 
    
         
            +
                  unless data[4][0..3] == 'http'
         
     | 
| 
      
 83 
     | 
    
         
            +
                    data[4] = "http://www.sec.gov/Archives/#{ data[4] }"
         
     | 
| 
      
 84 
     | 
    
         
            +
                  end
         
     | 
| 
      
 85 
     | 
    
         
            +
                  Filing.new(
         
     | 
| 
      
 86 
     | 
    
         
            +
                    term: data[1],
         
     | 
| 
      
 87 
     | 
    
         
            +
                    cik: data[2],
         
     | 
| 
      
 88 
     | 
    
         
            +
                    date: Date.parse(data[3]),
         
     | 
| 
      
 89 
     | 
    
         
            +
                    link: data[4]
         
     | 
| 
      
 90 
     | 
    
         
            +
                  )
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                def self.uri_for_recent(start = 0, count = 100)
         
     | 
| 
      
 94 
     | 
    
         
            +
                  SecURI.browse_edgar_uri(
         
     | 
| 
      
 95 
     | 
    
         
            +
                    action: :getcurrent,
         
     | 
| 
      
 96 
     | 
    
         
            +
                    owner: :include,
         
     | 
| 
      
 97 
     | 
    
         
            +
                    output: :atom,
         
     | 
| 
      
 98 
     | 
    
         
            +
                    start: start,
         
     | 
| 
      
 99 
     | 
    
         
            +
                    count: count
         
     | 
| 
      
 100 
     | 
    
         
            +
                  )
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                def self.uri_for_cik(cik, start = 0, count = 100)
         
     | 
| 
      
 104 
     | 
    
         
            +
                  SecURI.browse_edgar_uri(
         
     | 
| 
      
 105 
     | 
    
         
            +
                    action: :getcompany,
         
     | 
| 
      
 106 
     | 
    
         
            +
                    owner: :include,
         
     | 
| 
      
 107 
     | 
    
         
            +
                    output: :atom,
         
     | 
| 
      
 108 
     | 
    
         
            +
                    start: start,
         
     | 
| 
      
 109 
     | 
    
         
            +
                    count: count,
         
     | 
| 
      
 110 
     | 
    
         
            +
                    CIK: cik
         
     | 
| 
      
 111 
     | 
    
         
            +
                  )
         
     | 
| 
      
 112 
     | 
    
         
            +
                end
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                def self.parse_rss(rss, &blk)
         
     | 
| 
      
 115 
     | 
    
         
            +
                  feed = RSS::Parser.parse(rss, false)
         
     | 
| 
      
 116 
     | 
    
         
            +
                  feed.entries.each do |entry|
         
     | 
| 
      
 117 
     | 
    
         
            +
                    filing = Filing.new(
         
     | 
| 
      
 118 
     | 
    
         
            +
                      cik: entry.title.content.match(/\((\w{10})\)/)[1],
         
     | 
| 
      
 119 
     | 
    
         
            +
                      file_id: entry.id.content.split('=').last,
         
     | 
| 
      
 120 
     | 
    
         
            +
                      term:  entry.category.term,
         
     | 
| 
      
 121 
     | 
    
         
            +
                      title: entry.title.content,
         
     | 
| 
      
 122 
     | 
    
         
            +
                      summary: entry.summary.content,
         
     | 
| 
      
 123 
     | 
    
         
            +
                      date: DateTime.parse(entry.updated.content.to_s),
         
     | 
| 
      
 124 
     | 
    
         
            +
                      link: entry.link.href.gsub('-index.htm', '.txt')
         
     | 
| 
      
 125 
     | 
    
         
            +
                    )
         
     | 
| 
      
 126 
     | 
    
         
            +
                    blk.call(filing)
         
     | 
| 
      
 127 
     | 
    
         
            +
                  end
         
     | 
| 
      
 128 
     | 
    
         
            +
                end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                # def self.find(cik, start = 0, count = 80)
         
     | 
| 
      
 131 
     | 
    
         
            +
                #   temp = {}
         
     | 
| 
      
 132 
     | 
    
         
            +
                #   temp[:url] = SecURI.browse_edgar_uri({cik: cik})
         
     | 
| 
      
 133 
     | 
    
         
            +
                #   temp[:url][:action] = :getcompany
         
     | 
| 
      
 134 
     | 
    
         
            +
                #   temp[:url][:start] = start
         
     | 
| 
      
 135 
     | 
    
         
            +
                #   temp[:url][:count] = count
         
     | 
| 
      
 136 
     | 
    
         
            +
                #   response = Entity.query(temp[:url].output_atom.to_s)
         
     | 
| 
      
 137 
     | 
    
         
            +
                #   document = Nokogiri::HTML(response)
         
     | 
| 
      
 138 
     | 
    
         
            +
                #   parse(cik, document)
         
     | 
| 
      
 139 
     | 
    
         
            +
                # end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                def self.parse(cik, document)
         
     | 
| 
      
 142 
     | 
    
         
            +
                  filings = []
         
     | 
| 
      
 143 
     | 
    
         
            +
                  if document.xpath('//content').to_s.length > 0
         
     | 
| 
      
 144 
     | 
    
         
            +
                    document.xpath('//content').each do |e|
         
     | 
| 
      
 145 
     | 
    
         
            +
                      if e.xpath('//content/accession-nunber').to_s.length > 0
         
     | 
| 
      
 146 
     | 
    
         
            +
                        content = Hash.from_xml(e.to_s)['content']
         
     | 
| 
      
 147 
     | 
    
         
            +
                        content[:cik] = cik
         
     | 
| 
      
 148 
     | 
    
         
            +
                        content[:file_id] = content.delete('accession_nunber')
         
     | 
| 
      
 149 
     | 
    
         
            +
                        content[:date] = content.delete('filing_date')
         
     | 
| 
      
 150 
     | 
    
         
            +
                        content[:link] = content.delete('filing_href')
         
     | 
| 
      
 151 
     | 
    
         
            +
                        content[:term] = content.delete('filing_type')
         
     | 
| 
      
 152 
     | 
    
         
            +
                        content[:title] = content.delete('form_name')
         
     | 
| 
      
 153 
     | 
    
         
            +
                        filings << Filing.new(content)
         
     | 
| 
      
 154 
     | 
    
         
            +
                      end
         
     | 
| 
      
 155 
     | 
    
         
            +
                    end
         
     | 
| 
      
 156 
     | 
    
         
            +
                  end
         
     | 
| 
      
 157 
     | 
    
         
            +
                  filings
         
     | 
| 
      
 158 
     | 
    
         
            +
                end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                def content(&error_blk)
         
     | 
| 
      
 161 
     | 
    
         
            +
                  @content ||= RestClient.get(self.link)
         
     | 
| 
      
 162 
     | 
    
         
            +
                rescue RestClient::ResourceNotFound => e
         
     | 
| 
      
 163 
     | 
    
         
            +
                  puts "404 Resource Not Found: Bad link #{ self.link }"
         
     | 
| 
      
 164 
     | 
    
         
            +
                  if block_given?
         
     | 
| 
      
 165 
     | 
    
         
            +
                    error_blk.call(e, self)
         
     | 
| 
      
 166 
     | 
    
         
            +
                  else
         
     | 
| 
      
 167 
     | 
    
         
            +
                    raise e
         
     | 
| 
      
 168 
     | 
    
         
            +
                  end
         
     | 
| 
      
 169 
     | 
    
         
            +
                end
         
     | 
| 
      
 170 
     | 
    
         
            +
              end
         
     | 
| 
      
 171 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,216 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module SecEdgar
         
     | 
| 
      
 2 
     | 
    
         
            +
              class FilingParser
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :xsd_uri
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(filing)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @xsd_uri = 'lib/sec4/ownership4Document.xsd.xml'
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @filing = filing
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @content = filing.content
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def content
         
     | 
| 
      
 12 
     | 
    
         
            +
                  if @content.start_with?('-----BEGIN PRIVACY-ENHANCED MESSAGE-----')
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @content = strip_privacy_wrapper(@content)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                  @content
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def doc
         
     | 
| 
      
 19 
     | 
    
         
            +
                  @doc ||= OwnershipDocument.new
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def parse(&error_blk)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  if block_given? && !xml_valid?
         
     | 
| 
      
 24 
     | 
    
         
            +
                    error_blk.call(xml_errors)
         
     | 
| 
      
 25 
     | 
    
         
            +
                    puts "Error: returning NilObjectDocument #{ @filing.link }"
         
     | 
| 
      
 26 
     | 
    
         
            +
                    return NilOwnershipDocument.new
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  footnotes | transactions | derivative_transactions # eager init
         
     | 
| 
      
 30 
     | 
    
         
            +
                  parse_doc(xml_doc)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  parse_issuer(xml_doc.xpath('//issuer'))
         
     | 
| 
      
 32 
     | 
    
         
            +
                  parse_owner(xml_doc.xpath('//reportingOwner'))
         
     | 
| 
      
 33 
     | 
    
         
            +
                  parse_non_derivative_table(xml_doc.xpath('//nonDerivativeTable'))
         
     | 
| 
      
 34 
     | 
    
         
            +
                  parse_derivative_table(xml_doc.xpath('//derivativeTable'))
         
     | 
| 
      
 35 
     | 
    
         
            +
                  parse_footnotes(xml_doc.xpath('//footnotes'))
         
     | 
| 
      
 36 
     | 
    
         
            +
                  doc
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def footnotes
         
     | 
| 
      
 40 
     | 
    
         
            +
                  doc.footnotes ||= []
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def transactions
         
     | 
| 
      
 44 
     | 
    
         
            +
                  doc.transactions ||= []
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def derivative_transactions
         
     | 
| 
      
 48 
     | 
    
         
            +
                  doc.derivative_transactions ||= []
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def xml_filing
         
     | 
| 
      
 52 
     | 
    
         
            +
                  Nokogiri::XML(xml_doc.to_xml)
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                private
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                def strip_privacy_wrapper(raw)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  lines = raw.split("\n")
         
     | 
| 
      
 59 
     | 
    
         
            +
                  lines.shift until lines.first.start_with?('<SEC-DOCUMENT>')
         
     | 
| 
      
 60 
     | 
    
         
            +
                  lines.pop if lines.last.start_with?('-----END PRIVACY-ENHANCED MESSAGE--')
         
     | 
| 
      
 61 
     | 
    
         
            +
                  lines.join("\n")
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def parse_transaction(el)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  transaction = Transaction.new
         
     | 
| 
      
 66 
     | 
    
         
            +
                  transaction.security_title = el.xpath('securityTitle').text.strip
         
     | 
| 
      
 67 
     | 
    
         
            +
                  transaction.transaction_date = Date.parse(el.xpath('transactionDate').text)
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                  parse_transaction_amounts(transaction, el.xpath('transactionAmounts'))
         
     | 
| 
      
 70 
     | 
    
         
            +
                  parse_transaction_ownership_nature(transaction, el.xpath('ownershipNature'))
         
     | 
| 
      
 71 
     | 
    
         
            +
                  parse_transaction_coding(transaction, el.xpath('transactionCoding'))
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  # post transaction amounts
         
     | 
| 
      
 74 
     | 
    
         
            +
                  transaction.shares_after = el
         
     | 
| 
      
 75 
     | 
    
         
            +
                    .xpath('postTransactionAmounts/sharesOwnedFollowingTransaction/value')
         
     | 
| 
      
 76 
     | 
    
         
            +
                    .text
         
     | 
| 
      
 77 
     | 
    
         
            +
                    .to_f
         
     | 
| 
      
 78 
     | 
    
         
            +
                  transactions << transaction
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
                def parse_footnote(el)
         
     | 
| 
      
 82 
     | 
    
         
            +
                  footnote = Footnote.new
         
     | 
| 
      
 83 
     | 
    
         
            +
                  footnote.content = el.text.strip
         
     | 
| 
      
 84 
     | 
    
         
            +
                  footnote.id = el.attribute("id").value
         
     | 
| 
      
 85 
     | 
    
         
            +
                  footnotes << footnote
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                def parse_derivative_transaction(el)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  transaction = DerivativeTransaction.new
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
                  transaction.security_title = el.xpath('securityTitle').text.strip
         
     | 
| 
      
 92 
     | 
    
         
            +
                  transaction.transaction_date = Date.parse(el.xpath('transactionDate').text)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  transaction.conversion_or_exercise_price = el.xpath('conversionOrExercisePrice').text.to_f
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  unless (expiration_date = el.xpath('expirationDate/value').text).blank?
         
     | 
| 
      
 96 
     | 
    
         
            +
                    transaction.expiration_date = Date.parse(expiration_date)
         
     | 
| 
      
 97 
     | 
    
         
            +
                  end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                  unless (exercise_date = el.xpath('exerciseDate/value').text).blank?
         
     | 
| 
      
 100 
     | 
    
         
            +
                    transaction.exercise_date = Date.parse(exercise_date)
         
     | 
| 
      
 101 
     | 
    
         
            +
                  end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                  parse_transaction_amounts(transaction, el.xpath('transactionAmounts'))
         
     | 
| 
      
 104 
     | 
    
         
            +
                  parse_transaction_ownership_nature(transaction, el.xpath('ownershipNature'))
         
     | 
| 
      
 105 
     | 
    
         
            +
                  parse_transaction_coding(transaction, el.xpath('transactionCoding'))
         
     | 
| 
      
 106 
     | 
    
         
            +
                  parse_transaction_underlying(transaction, el.xpath('underlyingSecurity'))
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                  # post transaction amounts
         
     | 
| 
      
 109 
     | 
    
         
            +
                  transaction.shares_after = el
         
     | 
| 
      
 110 
     | 
    
         
            +
                    .xpath('postTransactionAmounts/sharesOwnedFollowingTransaction/value')
         
     | 
| 
      
 111 
     | 
    
         
            +
                    .text
         
     | 
| 
      
 112 
     | 
    
         
            +
                    .to_f
         
     | 
| 
      
 113 
     | 
    
         
            +
                  derivative_transactions << transaction
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                def parse_transaction_underlying(transaction, el)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  transaction.underlying_security_title = el.xpath('underlyingSecurityTitle/value').text
         
     | 
| 
      
 118 
     | 
    
         
            +
                  transaction.underlying_security_shares =
         
     | 
| 
      
 119 
     | 
    
         
            +
                    el.xpath('underlyingSecurityShares/value').text.to_f
         
     | 
| 
      
 120 
     | 
    
         
            +
                end
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                def parse_non_derivative_table(el)
         
     | 
| 
      
 123 
     | 
    
         
            +
                  el.xpath('//nonDerivativeTransaction').each do |transaction_el|
         
     | 
| 
      
 124 
     | 
    
         
            +
                    parse_transaction(transaction_el)
         
     | 
| 
      
 125 
     | 
    
         
            +
                  end
         
     | 
| 
      
 126 
     | 
    
         
            +
                end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                def parse_derivative_table(el)
         
     | 
| 
      
 129 
     | 
    
         
            +
                  el.xpath('//derivativeTransaction').each do |transaction_el|
         
     | 
| 
      
 130 
     | 
    
         
            +
                    parse_derivative_transaction(transaction_el)
         
     | 
| 
      
 131 
     | 
    
         
            +
                  end
         
     | 
| 
      
 132 
     | 
    
         
            +
                end
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
                def parse_footnotes(el)
         
     | 
| 
      
 135 
     | 
    
         
            +
                  el.xpath('//footnote').each do |note_el|
         
     | 
| 
      
 136 
     | 
    
         
            +
                    parse_footnote(note_el)
         
     | 
| 
      
 137 
     | 
    
         
            +
                  end
         
     | 
| 
      
 138 
     | 
    
         
            +
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                def parse_transaction_amounts(transaction, el)
         
     | 
| 
      
 141 
     | 
    
         
            +
                  transaction.acquired_or_disposed_code =
         
     | 
| 
      
 142 
     | 
    
         
            +
                    el.xpath('transactionAcquiredDisposedCode/value').text
         
     | 
| 
      
 143 
     | 
    
         
            +
                  transaction.shares =
         
     | 
| 
      
 144 
     | 
    
         
            +
                    el.xpath('transactionShares/value').text.to_f
         
     | 
| 
      
 145 
     | 
    
         
            +
                  transaction.price_per_share =
         
     | 
| 
      
 146 
     | 
    
         
            +
                    el.xpath('transactionPricePerShare/value').text.to_f
         
     | 
| 
      
 147 
     | 
    
         
            +
                end
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                def parse_transaction_coding(transaction, el)
         
     | 
| 
      
 150 
     | 
    
         
            +
                  transaction.code = el.xpath('transactionCode').text
         
     | 
| 
      
 151 
     | 
    
         
            +
                  transaction.form_type = el.xpath('transactionFormType').text
         
     | 
| 
      
 152 
     | 
    
         
            +
                  transaction.equity_swap_involved = el.xpath('equitySwapInvolved').text == '1'
         
     | 
| 
      
 153 
     | 
    
         
            +
                end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                def parse_transaction_ownership_nature(transaction, el)
         
     | 
| 
      
 156 
     | 
    
         
            +
                  transaction.nature_of_ownership = el.xpath('natureOfOwnership/value').text
         
     | 
| 
      
 157 
     | 
    
         
            +
                  transaction.direct_or_indirect_code = el.xpath('directOrIndirectOwnership/value').text
         
     | 
| 
      
 158 
     | 
    
         
            +
                end
         
     | 
| 
      
 159 
     | 
    
         
            +
             
     | 
| 
      
 160 
     | 
    
         
            +
                def parse_doc(el)
         
     | 
| 
      
 161 
     | 
    
         
            +
                  doc.schema_version = el.xpath('//schemaVersion').text
         
     | 
| 
      
 162 
     | 
    
         
            +
                  doc.document_type = el.xpath('//documentType').text
         
     | 
| 
      
 163 
     | 
    
         
            +
                  doc.not_subject_to_section_16 =
         
     | 
| 
      
 164 
     | 
    
         
            +
                    (el.xpath('//notSubjectToSection16').text == '1' ||
         
     | 
| 
      
 165 
     | 
    
         
            +
                     el.xpath('//notSubjectToSection16').text == 'true')
         
     | 
| 
      
 166 
     | 
    
         
            +
                  doc.period_of_report = Date.parse(el.xpath('//periodOfReport').text)
         
     | 
| 
      
 167 
     | 
    
         
            +
                rescue ArgumentError => e
         
     | 
| 
      
 168 
     | 
    
         
            +
                  puts "parse_doc error: #{ el.inspect }"
         
     | 
| 
      
 169 
     | 
    
         
            +
                  puts e
         
     | 
| 
      
 170 
     | 
    
         
            +
                  raise e
         
     | 
| 
      
 171 
     | 
    
         
            +
                end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                def parse_issuer(el)
         
     | 
| 
      
 174 
     | 
    
         
            +
                  doc.issuer_cik = el.xpath('//issuerCik').text
         
     | 
| 
      
 175 
     | 
    
         
            +
                  doc.issuer_name = el.xpath('//issuerName').text
         
     | 
| 
      
 176 
     | 
    
         
            +
                  doc.issuer_trading_symbol = el.xpath('//issuerTradingSymbol').text
         
     | 
| 
      
 177 
     | 
    
         
            +
                end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                def parse_owner(el)
         
     | 
| 
      
 180 
     | 
    
         
            +
                  doc.owner_cik = el.xpath('//rptOwnerCik').text
         
     | 
| 
      
 181 
     | 
    
         
            +
                  doc.owner_name = el.xpath('//rptOwnerName').text
         
     | 
| 
      
 182 
     | 
    
         
            +
                  doc.is_director = el.xpath('//isDirector').text == '1'
         
     | 
| 
      
 183 
     | 
    
         
            +
                  doc.is_ten_percent_owner = el.xpath('//isTenPercentOwner').text == '1'
         
     | 
| 
      
 184 
     | 
    
         
            +
                  doc.is_other = el.xpath('//isOther').text == '1'
         
     | 
| 
      
 185 
     | 
    
         
            +
                  doc.is_officer =
         
     | 
| 
      
 186 
     | 
    
         
            +
                    (el.xpath('//isOfficer').text == '1' ||
         
     | 
| 
      
 187 
     | 
    
         
            +
                     el.xpath('//isOfficer').text.downcase == 'true')
         
     | 
| 
      
 188 
     | 
    
         
            +
                  doc.officer_title = el.xpath('//officerTitle').text
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
      
 190 
     | 
    
         
            +
                  address = Address.new
         
     | 
| 
      
 191 
     | 
    
         
            +
                  address.street1 = el.xpath('//rptOwnerStreet1').text
         
     | 
| 
      
 192 
     | 
    
         
            +
                  address.street2 = el.xpath('//rptOwnerStreet2').text
         
     | 
| 
      
 193 
     | 
    
         
            +
                  address.city = el.xpath('//rptOwnerCity').text
         
     | 
| 
      
 194 
     | 
    
         
            +
                  address.state = el.xpath('//rptOwnerState').text
         
     | 
| 
      
 195 
     | 
    
         
            +
                  address.zip = el.xpath('//rptOwnerZipCode').text
         
     | 
| 
      
 196 
     | 
    
         
            +
                  address.state_description = el.xpath('//rptOwnerStateDescription').text
         
     | 
| 
      
 197 
     | 
    
         
            +
                  doc.owner_address = address
         
     | 
| 
      
 198 
     | 
    
         
            +
                end
         
     | 
| 
      
 199 
     | 
    
         
            +
             
     | 
| 
      
 200 
     | 
    
         
            +
                def xml_doc
         
     | 
| 
      
 201 
     | 
    
         
            +
                  Nokogiri::XML(content).xpath('//ownershipDocument')
         
     | 
| 
      
 202 
     | 
    
         
            +
                end
         
     | 
| 
      
 203 
     | 
    
         
            +
             
     | 
| 
      
 204 
     | 
    
         
            +
                def xml_schema
         
     | 
| 
      
 205 
     | 
    
         
            +
                  @schema ||= Nokogiri::XML::Schema(IO.read(xsd_uri))
         
     | 
| 
      
 206 
     | 
    
         
            +
                end
         
     | 
| 
      
 207 
     | 
    
         
            +
             
     | 
| 
      
 208 
     | 
    
         
            +
                def xml_valid?
         
     | 
| 
      
 209 
     | 
    
         
            +
                  xml_errors.empty? && !content.blank?
         
     | 
| 
      
 210 
     | 
    
         
            +
                end
         
     | 
| 
      
 211 
     | 
    
         
            +
             
     | 
| 
      
 212 
     | 
    
         
            +
                def xml_errors
         
     | 
| 
      
 213 
     | 
    
         
            +
                  @errors ||= xml_schema.validate(xml_filing)
         
     | 
| 
      
 214 
     | 
    
         
            +
                end
         
     | 
| 
      
 215 
     | 
    
         
            +
              end
         
     | 
| 
      
 216 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,53 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # module SecEdgar
         
     | 
| 
      
 2 
     | 
    
         
            +
            #   class FilingPersister
         
     | 
| 
      
 3 
     | 
    
         
            +
            #     attr_reader :filing
         
     | 
| 
      
 4 
     | 
    
         
            +
            #
         
     | 
| 
      
 5 
     | 
    
         
            +
            #     def initialize(filing)
         
     | 
| 
      
 6 
     | 
    
         
            +
            #       @filing = filing
         
     | 
| 
      
 7 
     | 
    
         
            +
            #     end
         
     | 
| 
      
 8 
     | 
    
         
            +
            #
         
     | 
| 
      
 9 
     | 
    
         
            +
            #     def doc
         
     | 
| 
      
 10 
     | 
    
         
            +
            #       @doc ||= RawFiling.for_filing(filing).parsed
         
     | 
| 
      
 11 
     | 
    
         
            +
            #     end
         
     | 
| 
      
 12 
     | 
    
         
            +
            #
         
     | 
| 
      
 13 
     | 
    
         
            +
            #     def persist!
         
     | 
| 
      
 14 
     | 
    
         
            +
            #       return form if form = Form4.find_by(link: filing.link)
         
     | 
| 
      
 15 
     | 
    
         
            +
            #       doc_json = doc.to_json
         
     | 
| 
      
 16 
     | 
    
         
            +
            #       return if doc_json == '{}'
         
     | 
| 
      
 17 
     | 
    
         
            +
            #
         
     | 
| 
      
 18 
     | 
    
         
            +
            #       form = Form4.new(
         
     | 
| 
      
 19 
     | 
    
         
            +
            #         cik: filing.cik,
         
     | 
| 
      
 20 
     | 
    
         
            +
            #         title: filing.title,
         
     | 
| 
      
 21 
     | 
    
         
            +
            #         link: filing.link,
         
     | 
| 
      
 22 
     | 
    
         
            +
            #         term: filing.term,
         
     | 
| 
      
 23 
     | 
    
         
            +
            #         date: filing.date,
         
     | 
| 
      
 24 
     | 
    
         
            +
            #         file_id: filing.file_id,
         
     | 
| 
      
 25 
     | 
    
         
            +
            #         dollar_volume: doc.dollar_volume,
         
     | 
| 
      
 26 
     | 
    
         
            +
            #         document: { d: doc_json }
         
     | 
| 
      
 27 
     | 
    
         
            +
            #       )
         
     | 
| 
      
 28 
     | 
    
         
            +
            #       form.company = Company.where(cik: doc.issuer_cik).first_or_initialize
         
     | 
| 
      
 29 
     | 
    
         
            +
            #       form.company.update_attributes(
         
     | 
| 
      
 30 
     | 
    
         
            +
            #         name: doc.issuer_name,
         
     | 
| 
      
 31 
     | 
    
         
            +
            #         ticker: doc.issuer_trading_symbol.upcase
         
     | 
| 
      
 32 
     | 
    
         
            +
            #       )
         
     | 
| 
      
 33 
     | 
    
         
            +
            #
         
     | 
| 
      
 34 
     | 
    
         
            +
            #       form.insider = Insider.where(cik: doc.owner_cik).first_or_initialize
         
     | 
| 
      
 35 
     | 
    
         
            +
            #       form.insider.update_attributes(
         
     | 
| 
      
 36 
     | 
    
         
            +
            #         name: doc.owner_name[0, 254]
         
     | 
| 
      
 37 
     | 
    
         
            +
            #       )
         
     | 
| 
      
 38 
     | 
    
         
            +
            #
         
     | 
| 
      
 39 
     | 
    
         
            +
            #       form.day_traded_price = form.company.price_on(form.date)
         
     | 
| 
      
 40 
     | 
    
         
            +
            #       form.day_traded_volume = form.company.volume_on(form.date)
         
     | 
| 
      
 41 
     | 
    
         
            +
            #       form.plus_3_months_price = form.company.price_on(form.date + 3.months)
         
     | 
| 
      
 42 
     | 
    
         
            +
            #       form.plus_6_months_price = form.company.price_on(form.date + 6.months)
         
     | 
| 
      
 43 
     | 
    
         
            +
            #       form.plus_12_months_price = form.company.price_on(form.date + 12.months)
         
     | 
| 
      
 44 
     | 
    
         
            +
            #       # need to find a more detailed data source
         
     | 
| 
      
 45 
     | 
    
         
            +
            #       # form.price_to_earnings = Company.price_to_earnings_on(form.date)
         
     | 
| 
      
 46 
     | 
    
         
            +
            #       # form.price_to_book = Company.price_to_book_on(form.date)
         
     | 
| 
      
 47 
     | 
    
         
            +
            #
         
     | 
| 
      
 48 
     | 
    
         
            +
            #       form.doc = doc
         
     | 
| 
      
 49 
     | 
    
         
            +
            #       form.save!
         
     | 
| 
      
 50 
     | 
    
         
            +
            #       form
         
     | 
| 
      
 51 
     | 
    
         
            +
            #     end
         
     | 
| 
      
 52 
     | 
    
         
            +
            #   end
         
     | 
| 
      
 53 
     | 
    
         
            +
            # end
         
     | 
| 
         @@ -0,0 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module SecEdgar
         
     | 
| 
      
 2 
     | 
    
         
            +
              class FilingUpdater
         
     | 
| 
      
 3 
     | 
    
         
            +
                attr_reader :form
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize(form)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @form = form
         
     | 
| 
      
 7 
     | 
    
         
            +
                end
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def doc
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @doc ||= RawFiling.for_form(form).parsed
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def update
         
     | 
| 
      
 14 
     | 
    
         
            +
                  doc_json = doc.to_json
         
     | 
| 
      
 15 
     | 
    
         
            +
                  return if doc_json == '{}'
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  form.update({
         
     | 
| 
      
 18 
     | 
    
         
            +
                    dollar_volume: doc.dollar_volume,
         
     | 
| 
      
 19 
     | 
    
         
            +
                    document: { d: doc_json }
         
     | 
| 
      
 20 
     | 
    
         
            +
                  })
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  unless form.company
         
     | 
| 
      
 23 
     | 
    
         
            +
                    form.company = Company.where(cik: doc.issuer_cik).first_or_initialize
         
     | 
| 
      
 24 
     | 
    
         
            +
                    form.company.update_attributes(
         
     | 
| 
      
 25 
     | 
    
         
            +
                      name: doc.issuer_name,
         
     | 
| 
      
 26 
     | 
    
         
            +
                      ticker: doc.issuer_trading_symbol.upcase
         
     | 
| 
      
 27 
     | 
    
         
            +
                    )
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  unless form.insider
         
     | 
| 
      
 31 
     | 
    
         
            +
                    form.insider = Insider.where(cik: doc.owner_cik).first_or_initialize
         
     | 
| 
      
 32 
     | 
    
         
            +
                    form.insider.update_attributes(
         
     | 
| 
      
 33 
     | 
    
         
            +
                      name: doc.owner_name[0, 254]
         
     | 
| 
      
 34 
     | 
    
         
            +
                    )
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  dt = Date.parse(form.date)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  form.day_traded_price = form.company.price_on(dt)
         
     | 
| 
      
 39 
     | 
    
         
            +
                  form.day_traded_volume = form.company.volume_on(dt)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  form.plus_3_months_price = form.company.price_on(dt + 3.months)
         
     | 
| 
      
 41 
     | 
    
         
            +
                  form.plus_6_months_price = form.company.price_on(dt + 6.months)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  form.plus_12_months_price = form.company.price_on(dt + 12.months)
         
     | 
| 
      
 43 
     | 
    
         
            +
                  # need to find a more detailed data source
         
     | 
| 
      
 44 
     | 
    
         
            +
                  # form.price_to_earnings = Company.price_to_earnings_on(form.date)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  # form.price_to_book = Company.price_to_book_on(form.date)
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  form.doc = doc
         
     | 
| 
      
 48 
     | 
    
         
            +
                  form.save!
         
     | 
| 
      
 49 
     | 
    
         
            +
                  form
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,35 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module SecEdgar
         
     | 
| 
      
 2 
     | 
    
         
            +
              class FtpClient
         
     | 
| 
      
 3 
     | 
    
         
            +
                include Singleton
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
                def initialize
         
     | 
| 
      
 6 
     | 
    
         
            +
                  @ftp = Net::FTP.new
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @ftp.passive = true
         
     | 
| 
      
 8 
     | 
    
         
            +
                  connect
         
     | 
| 
      
 9 
     | 
    
         
            +
                  login
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                  at_exit do
         
     | 
| 
      
 12 
     | 
    
         
            +
                    puts "Closing Sec4::FtpClient..."
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @ftp.close
         
     | 
| 
      
 14 
     | 
    
         
            +
                  end
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def fetch(remote_url, local_url)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @ftp.getbinaryfile(remote_url, local_url)
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def connect
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @ftp.connect('ftp.sec.gov', 21)
         
     | 
| 
      
 23 
     | 
    
         
            +
                rescue => e
         
     | 
| 
      
 24 
     | 
    
         
            +
                  puts "Sec4::FtpClient connection failed"
         
     | 
| 
      
 25 
     | 
    
         
            +
                  puts e.message
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def login
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @ftp.login
         
     | 
| 
      
 30 
     | 
    
         
            +
                rescue => e
         
     | 
| 
      
 31 
     | 
    
         
            +
                  puts "Sec4::FtpClient login failed"
         
     | 
| 
      
 32 
     | 
    
         
            +
                  puts e.message
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module Sec4
         
     | 
| 
      
 2 
     | 
    
         
            +
              class NilOwnershipDocument
         
     | 
| 
      
 3 
     | 
    
         
            +
                def document_type
         
     | 
| 
      
 4 
     | 
    
         
            +
                  '4'
         
     | 
| 
      
 5 
     | 
    
         
            +
                end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def issuer_trading_symbol
         
     | 
| 
      
 8 
     | 
    
         
            +
                  'NULL'
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                def is_director
         
     | 
| 
      
 12 
     | 
    
         
            +
                  false
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def is_officer
         
     | 
| 
      
 16 
     | 
    
         
            +
                  false
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                def is_owner
         
     | 
| 
      
 20 
     | 
    
         
            +
                  false
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                def is_ten_percent_owner
         
     | 
| 
      
 24 
     | 
    
         
            +
                  false
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def officer_title
         
     | 
| 
      
 28 
     | 
    
         
            +
                  ''
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def transactions
         
     | 
| 
      
 32 
     | 
    
         
            +
                  []
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def derivative_transactions
         
     | 
| 
      
 36 
     | 
    
         
            +
                  []
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
            end
         
     |