Permalink
Browse files

Initial commit

  • Loading branch information...
kevinsawicki committed May 11, 2013
0 parents commit 6e2f228f26193a38909d7219088267c67e822f6e
@@ -0,0 +1,4 @@
+node_modules/
+*.js
+.DS_Store
+*.log
@@ -0,0 +1,4 @@
+.npmignore
+*.coffee
+.DS_Store
+*.log
@@ -0,0 +1,36 @@
+module.exports = (grunt) ->
+ grunt.initConfig
+ pkg: grunt.file.readJSON('package.json')
+
+ coffee:
+ glob_to_multiple:
+ expand: true
+ cwd: 'src'
+ src: ['*.coffee']
+ dest: 'lib'
+ ext: '.js'
+
+ coffeelint:
+ options:
+ max_line_length:
+ level: 'ignore'
+
+ src: ['src/*.coffee']
+ test: ['spec/*.coffee']
+
+ shell:
+ test:
+ command: 'npm test'
+ options:
+ stdout: true
+ stderr: true
+ failOnError: true
+
+ grunt.loadNpmTasks('grunt-coffeelint')
+ grunt.loadNpmTasks('grunt-contrib-coffee')
+ grunt.loadNpmTasks('grunt-shell')
+
+ grunt.registerTask 'clean', -> require('rimraf').sync('lib')
+ grunt.registerTask('lint', ['coffeelint:src', 'coffeelint:test'])
+ grunt.registerTask('default', ['coffeelint:src', 'coffee'])
+ grunt.registerTask('test', ['lint', 'coffee', 'shell:test'])
@@ -0,0 +1,20 @@
+Copyright (c) 2013 Kevin Sawicki
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,14 @@
+# Filings
+
+Node module for downloading and querying SEC filings.
+
+## Installing
+
+```sh
+npm install filings
+```
+
+## Building
+ * Clone this repository
+ * Run `npm install`
+ * Run `grunt` to compile the CoffeeScript code
@@ -0,0 +1,44 @@
+{
+ "name": "filings",
+ "descriptions": "SEC filings module",
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "http://github.com/kevinsawicki/filings/raw/master/LICENSE.md"
+ }
+ ],
+ "keywords": [
+ "sec",
+ "edgar",
+ "10-K"
+ ],
+ "author": {
+ "name": "Kevin Sawicki",
+ "email": "kevinsawicki@gmail.com"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/kevinsawicki/filings.git"
+ },
+ "bugs": {
+ "url": "https://github.com/kevinsawicki/filings/issues"
+ },
+ "scripts": {
+ "prepublish": "grunt",
+ "test": "jasmine-focused --captureExceptions --coffee spec/"
+ },
+ "devDependencies": {
+ "grunt": "~0.4.1",
+ "grunt-cli": "~0.1.8",
+ "grunt-contrib-coffee": "~0.7.0",
+ "grunt-coffeelint": "0.0.6",
+ "rimraf": "~2.1.4",
+ "jasmine-focused": "~0.1.0",
+ "grunt-shell": "~0.2.2"
+ },
+ "dependencies": {
+ "xpath": "0.0.5",
+ "xmldom": "~0.1.16",
+ "ls-archive": "~0.5.0"
+ }
+}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,15 @@
+path = require 'path'
+TenK = require '../lib/ten-k'
+
+describe '10-K', ->
+ describe '.getProfit(year)', ->
+ it 'returns the profit for the given year', ->
+ reportPath = path.join(__dirname, 'fixtures', 'luv-2012-10-K.gz')
+ openedReport = null
+ TenK.open reportPath, (error, report) ->
+ openedReport = report
+ waitsFor -> openedReport?
+ runs ->
+ expect(openedReport.getProfit(2012)).toBe 421000000
+ expect(openedReport.getProfit(2011)).toBe 178000000
+ expect(openedReport.getProfit(2010)).toBe 459000000
@@ -0,0 +1,37 @@
+archive = require 'ls-archive'
+{DOMParser} = require 'xmldom'
+xpath = require 'xpath'
+
+module.exports =
+class TenK
+ @open: (path, callback) ->
+ archive.readGzip path, (error, data) ->
+ if error?
+ callback(error)
+ else
+ callback(null, new TenK(data))
+
+ constructor: (@contents) ->
+ @document = new DOMParser().parseFromString(@contents)
+
+ getYear: (dateRange='') ->
+ if match = dateRange.match(/^from_([a-z]+\d{2})_(\d{4})_to_([a-z]+\d{2})_(\d{4})$/i)
+ fromDate = Date.parse("#{match[1]} #{match[2]}")
+ return -1 if isNaN(fromDate)
+ toDate = Date.parse("#{match[3]} #{match[4]}")
+ return -1 if isNaN(toDate)
+ day = 24 * 60 *(1000 * 60)
+ days = (toDate - fromDate) / day
+ return new Date(toDate).getFullYear() if 300 < days < 400
+ -1
+
+ getProfit: (year) ->
+ nodes = xpath.select("//*[local-name() = 'NetIncomeLoss']", @document)
+ netIncomeLoss = 0
+ for node in nodes
+ continue unless node.prefix is 'us-gaap'
+ nodeYear = @getYear(xpath.select("@contextRef", node)[0]?.value)
+ nodeNetIncomeLoss = parseFloat(node.firstChild.data)
+ continue if isNaN(nodeNetIncomeLoss)
+ netIncomeLoss += nodeNetIncomeLoss if year is nodeYear
+ netIncomeLoss

0 comments on commit 6e2f228

Please sign in to comment.