campaign_cash 2.3.2 → 2.4
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.
- data/LICENSE +1 -1
- data/README.rdoc +12 -1
- data/lib/campaign_cash/base.rb +100 -100
- data/lib/campaign_cash/candidate.rb +103 -103
- data/lib/campaign_cash/committee.rb +102 -99
- data/lib/campaign_cash/contribution.rb +46 -36
- data/lib/campaign_cash/electioneering_communication.rb +4 -4
- data/lib/campaign_cash/filing.rb +59 -60
- data/lib/campaign_cash/filing_summary.rb +8 -8
- data/lib/campaign_cash/form.rb +7 -7
- data/lib/campaign_cash/independent_expenditure.rb +37 -35
- data/lib/campaign_cash/individual_contribution.rb +11 -11
- data/lib/campaign_cash/late_contribution.rb +37 -0
- data/lib/campaign_cash/president.rb +49 -49
- data/lib/campaign_cash/version.rb +1 -1
- data/lib/campaign_cash.rb +1 -1
- data/test/campaign_cash/test_candidate.rb +1 -1
- data/test/campaign_cash/test_committee.rb +2 -2
- data/test/campaign_cash/test_independent_expenditure.rb +1 -1
- data/test/campaign_cash/test_late_contribution.rb +24 -0
- data/test/test_helper.rb +1 -1
- metadata +87 -49
data/LICENSE
CHANGED
data/README.rdoc
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
== DESCRIPTION:
|
|
10
10
|
|
|
11
|
-
Simple ruby wrapper for The New York Times Campaign Finance API[http://developer.nytimes.com/docs/read/campaign_finance_api]. You'll need an API key. Tested under Ruby 1.8.7, 1.9.2 and 1.9.3 and JRuby 1.6.7.
|
|
11
|
+
Simple ruby wrapper for portions of The New York Times Campaign Finance API[http://developer.nytimes.com/docs/read/campaign_finance_api]. You'll need an API key. Tested under Ruby 1.8.7, 1.9.2 and 1.9.3 and JRuby 1.6.7.
|
|
12
12
|
|
|
13
13
|
== News
|
|
14
14
|
|
|
15
|
+
* July 19, 2012: Version 2.4 released. Updated for new API responses and to return integers and floats.
|
|
15
16
|
* April 10, 2012: Version 2.3.2 released. Bugfix for Committee#find.
|
|
16
17
|
* March 22, 2012: Version 2.3.1 released. Added filing_id to Filing objects and Committee#unamended_filings method.
|
|
17
18
|
* March 15, 2012: Version 2.3 released. Added committee_type to Filing objects and offset to Candidate methods.
|
|
@@ -213,6 +214,16 @@ If you're just interested in grabbing the contributions from a certain filing or
|
|
|
213
214
|
q.date_coverage_to == "2011-12-31".to_date && q.report_title == "YEAR-END"
|
|
214
215
|
end
|
|
215
216
|
|
|
217
|
+
== Late Contributions
|
|
218
|
+
|
|
219
|
+
During the final 20 days before a primary or general election, candidate committees that receive contributions of at least $1,000 must report them in filings to the F.E.C. within 48 hours of receipt. These contributions may be from individuals or other committees.
|
|
220
|
+
|
|
221
|
+
LateContribution.latest
|
|
222
|
+
#=> [#<CampaignCash::LateContribution:0x10116c6d0 @contributor_city="Brownsville", @contributor_zip=nil, @fec_committee_id="C00466854", @contributor_fec_id=nil, @fec_candidate_id="H0TN08246", @contributor_employer="Simmco", @transaction_id="20716.C7755", @contributor_street_2=nil, @contributor_prefix=nil, @office_state="TN", @contributor_state="TN", @fec_filing_id=798678, @contributor_street_1="PO Box 545", @contribution_date="2012-07-16", @contributor_last_name="Blurton", @contributor_organization_name=nil, @contributor_middle_name=nil, @contributor_suffix=nil, @contribution_amount="2500.0", @cycle=2012, @contributor_first_name="David", @entity_type="IND", @contributor_occupation="Owner">, ...]
|
|
223
|
+
|
|
224
|
+
LateContribution.candidate("H0TN08246")
|
|
225
|
+
LateContribution.committee("C00466854") # must be a candidate committee
|
|
226
|
+
|
|
216
227
|
== Electioneering Communications
|
|
217
228
|
|
|
218
229
|
Electioneering Communications are broadcast ads funded by third party groups that mention one or more candidates, but don't specifically support or oppose one. <tt>ElectioneeringCommunication</tt> objects are available newest first, or by committee ID or date (all in groups of 20). Within each object is an array of <tt>electioneering_communication_candidates</tt> mentioned in the ad.
|
data/lib/campaign_cash/base.rb
CHANGED
|
@@ -6,105 +6,105 @@ require 'ostruct'
|
|
|
6
6
|
|
|
7
7
|
module CampaignCash
|
|
8
8
|
class Base
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
9
|
+
API_SERVER = 'api.nytimes.com'
|
|
10
|
+
API_VERSION = 'v3'
|
|
11
|
+
API_NAME = 'elections/us'
|
|
12
|
+
API_BASE = "/svc/#{API_NAME}/#{API_VERSION}/finances"
|
|
13
|
+
CURRENT_CYCLE = 2012
|
|
14
|
+
|
|
15
|
+
@@api_key = nil
|
|
16
|
+
@@copyright = nil
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# The copyright footer to be placed at the bottom of any data from the New York Times. Note this is only set after an API call.
|
|
22
|
+
def copyright
|
|
23
|
+
@@copyright
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def cycle
|
|
27
|
+
@@cycle
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def base_uri
|
|
31
|
+
@@base_uri
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Set the API key used for operations. This needs to be called before any requests against the API. To obtain an API key, go to http://developer.nytimes.com/
|
|
36
|
+
def api_key=(key)
|
|
37
|
+
@@api_key = key
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def api_key
|
|
41
|
+
@@api_key
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def date_parser(date)
|
|
45
|
+
date ? Date.strptime(date, '%Y-%m-%d') : nil
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def parse_candidate(candidate)
|
|
49
|
+
return nil if candidate.nil?
|
|
50
|
+
candidate.split('/').last.split('.').first
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def parse_committee(committee)
|
|
54
|
+
return nil if committee.nil?
|
|
55
|
+
committee.split('/').last.split('.').first
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the election cycle (even-numbered) from a date.
|
|
59
|
+
def cycle_from_date(date=Date.today)
|
|
60
|
+
date.year.even? ? date.year : date.year+1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def check_offset(offset)
|
|
64
|
+
raise "Offset must be a multiple of 20" if offset % 20 != 0
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
##
|
|
68
|
+
# Builds a request URI to call the API server
|
|
69
|
+
def build_request_url(path, params)
|
|
70
|
+
URI::HTTP.build :host => API_SERVER,
|
|
71
|
+
:path => "#{API_BASE}/#{path}.json",
|
|
72
|
+
:query => params.map {|k,v| "#{k}=#{v}"}.join('&')
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def invoke(path, params={})
|
|
76
|
+
begin
|
|
77
|
+
if @@api_key.nil?
|
|
78
|
+
raise "You must initialize the API key before you run any API queries"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
full_params = params.merge 'api-key' => @@api_key
|
|
82
|
+
full_params.delete_if {|k,v| v.nil?}
|
|
83
|
+
|
|
84
|
+
check_offset(params[:offset]) if params[:offset]
|
|
85
|
+
|
|
86
|
+
uri = build_request_url(path, full_params)
|
|
87
|
+
|
|
88
|
+
reply = uri.read
|
|
89
|
+
parsed_reply = JSON.parse reply
|
|
90
|
+
|
|
91
|
+
if parsed_reply.nil?
|
|
92
|
+
raise "Empty reply returned from API"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@@copyright = parsed_reply['copyright']
|
|
96
|
+
@@cycle = parsed_reply['cycle']
|
|
97
|
+
@@base_uri = parsed_reply['base_uri']
|
|
98
|
+
|
|
99
|
+
parsed_reply
|
|
100
|
+
rescue OpenURI::HTTPError => e
|
|
101
|
+
if e.message =~ /^404/
|
|
102
|
+
return nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
raise "Error connecting to URL #{uri} #{e}"
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
109
|
end
|
|
110
110
|
end
|
|
@@ -1,145 +1,145 @@
|
|
|
1
1
|
module CampaignCash
|
|
2
2
|
class Candidate < Base
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
# Represents a candidate object based on the FEC's candidate and candidate summary files.
|
|
5
5
|
# A candidate is a person seeking a particular office within a particular two-year election
|
|
6
6
|
# cycle. Each candidate is assigned a unique ID within a cycle.
|
|
7
7
|
attr_reader :name, :id, :state, :district, :party, :fec_uri, :committee_id,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
:mailing_city, :mailing_address, :mailing_state, :mailing_zip,
|
|
9
|
+
:total_receipts, :total_contributions, :total_from_individuals,
|
|
10
|
+
:total_from_pacs, :candidate_loans, :total_disbursements,
|
|
11
|
+
:total_refunds, :debts_owed, :begin_cash, :end_cash, :status,
|
|
12
|
+
:date_coverage_to, :date_coverage_from, :relative_uri, :office
|
|
13
|
+
|
|
14
14
|
def initialize(params={})
|
|
15
15
|
params.each_pair do |k,v|
|
|
16
16
|
instance_variable_set("@#{k}", v)
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
# Creates a new candidate object from a JSON API response.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
21
|
+
def self.create(params={})
|
|
22
|
+
self.new :name => params['name'],
|
|
23
|
+
:id => params['id'],
|
|
24
|
+
:state => parse_state(params['state']),
|
|
25
|
+
:office => parse_office(params['id']),
|
|
26
|
+
:district => parse_district(params['district']),
|
|
27
|
+
:party => params['party'],
|
|
28
|
+
:fec_uri => params['fec_uri'],
|
|
29
|
+
:committee_id => parse_committee(params['committee']),
|
|
30
|
+
:mailing_city => params['mailing_city'],
|
|
31
|
+
:mailing_address => params['mailing_address'],
|
|
32
|
+
:mailing_state => params['mailing_state'],
|
|
33
|
+
:mailing_zip => params['mailing_zip'],
|
|
34
|
+
:total_receipts => params['total_receipts'].to_f,
|
|
35
|
+
:total_contributions => params['total_contributions'].to_f,
|
|
36
|
+
:total_from_individuals => params['total_from_individuals'].to_f,
|
|
37
|
+
:total_from_pacs => params['total_from_pacs'].to_f,
|
|
38
|
+
:candidate_loans => params['candidate_loans'].to_f,
|
|
39
|
+
:total_disbursements => params['total_disbursements'].to_f,
|
|
40
|
+
:total_refunds => params['total_refunds'].to_f,
|
|
41
|
+
:debts_owed => params['debts_owed'].to_f,
|
|
42
|
+
:begin_cash => params['begin_cash'].to_f,
|
|
43
|
+
:end_cash => params['end_cash'].to_f,
|
|
44
|
+
:status => params['status'],
|
|
45
|
+
:date_coverage_from => params['date_coverage_from'],
|
|
46
|
+
:date_coverage_to => params['date_coverage_to']
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.create_from_search_results(params={})
|
|
50
|
+
self.new :name => params['candidate']['name'],
|
|
51
|
+
:id => params['candidate']['id'],
|
|
52
|
+
:state => params['candidate']['id'][2..3],
|
|
53
|
+
:office => parse_office(params['candidate']['id'][0..0]),
|
|
54
|
+
:district => parse_district(params['district']),
|
|
55
|
+
:party => params['candidate']['party'],
|
|
56
|
+
:committee_id => parse_committee(params['committee'])
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.parse_state(state)
|
|
61
|
+
state.split('/').last[0..1] if state
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.parse_office(id)
|
|
65
|
+
return nil unless id
|
|
66
|
+
if id[0..0] == "H"
|
|
67
|
+
'house'
|
|
68
|
+
elsif id[0..0] == 'S'
|
|
69
|
+
'senate'
|
|
70
|
+
else
|
|
71
|
+
'president'
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.parse_district(uri)
|
|
76
|
+
if uri and uri.split('/').last.split('.').first.to_i > 0
|
|
77
|
+
uri.split('/').last.split('.').first.to_i
|
|
78
|
+
else
|
|
79
|
+
0
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.categories
|
|
84
|
+
{
|
|
85
|
+
"individual_total" => "Contributions from individuals",
|
|
86
|
+
"contribution_total" => "Total contributions",
|
|
87
|
+
"candidate_loan" => "Loans from candidate",
|
|
88
|
+
"receipts_total" => "Total receipts",
|
|
89
|
+
"refund_total" => "Total refunds",
|
|
90
|
+
"pac_total" => "Contributions from PACs",
|
|
91
|
+
"disbursements_total" => "Total disbursements",
|
|
92
|
+
"end_cash" => "Cash on hand",
|
|
93
|
+
"debts_owed" => "Debts owed by",
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Retrieve a candidate object via its FEC candidate id within a cycle.
|
|
98
|
+
# Defaults to the current cycle.
|
|
99
99
|
def self.find(fecid, cycle=CURRENT_CYCLE)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
reply = invoke("#{cycle}/candidates/#{fecid}")
|
|
101
|
+
result = reply['results']
|
|
102
|
+
self.create(result.first) if result.first
|
|
103
103
|
end
|
|
104
|
-
|
|
104
|
+
|
|
105
105
|
# Returns leading candidates for given categories from campaign filings within a cycle.
|
|
106
106
|
# See [the API docs](http://developer.nytimes.com/docs/read/campaign_finance_api#h3-candidate-leaders) for
|
|
107
107
|
# a list of acceptable categories to pass in. Defaults to the current cycle.
|
|
108
108
|
def self.leaders(category, cycle=CURRENT_CYCLE)
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
reply = invoke("#{cycle}/candidates/leaders/#{category}",{})
|
|
110
|
+
results = reply['results']
|
|
111
111
|
results.map{|c| self.create(c)}
|
|
112
112
|
end
|
|
113
|
-
|
|
113
|
+
|
|
114
114
|
# Returns an array of candidates matching a search term within a cycle. Defaults to the
|
|
115
115
|
# current cycle.
|
|
116
116
|
def self.search(name, cycle=CURRENT_CYCLE, offset=nil)
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
reply = invoke("#{cycle}/candidates/search", {:query => name, :offset => offset})
|
|
118
|
+
results = reply['results']
|
|
119
119
|
results.map{|c| self.create_from_search_results(c)}
|
|
120
120
|
end
|
|
121
|
-
|
|
121
|
+
|
|
122
122
|
# Returns an array of newly created FEC candidates within a current cycle. Defaults to the
|
|
123
123
|
# current cycle.
|
|
124
124
|
def self.new_candidates(cycle=CURRENT_CYCLE, offset=nil)
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
reply = invoke("#{cycle}/candidates/new",{:offset => offset})
|
|
126
|
+
results = reply['results']
|
|
127
127
|
results.map{|c| self.create(c)}
|
|
128
128
|
end
|
|
129
|
-
|
|
129
|
+
|
|
130
130
|
# Returns an array of candidates for a given state within a cycle, with optional chamber and
|
|
131
131
|
# district parameters. For example, House candidates from New York. Defaults to the current cycle.
|
|
132
132
|
def self.state(state, chamber=nil, district=nil, cycle=CURRENT_CYCLE, offset=nil)
|
|
133
133
|
path = "#{cycle}/seats/#{state}"
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
if chamber
|
|
135
|
+
path += "/#{chamber}"
|
|
136
|
+
path += "/#{district}" if district
|
|
137
|
+
end
|
|
138
138
|
reply = invoke(path,{:offset => offset})
|
|
139
139
|
results = reply['results']
|
|
140
140
|
results.map{|c| self.create_from_search_results(c)}
|
|
141
141
|
end
|
|
142
|
-
|
|
142
|
+
|
|
143
143
|
instance_eval { alias :state_chamber :state }
|
|
144
144
|
end
|
|
145
145
|
end
|