sunlightlabs-sunlight 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGES.textile +29 -0
- data/README.textile +224 -0
- data/lib/sunlight.rb +8 -0
- data/lib/sunlight/base.rb +57 -0
- data/lib/sunlight/district.rb +108 -0
- data/lib/sunlight/filing.rb +119 -0
- data/lib/sunlight/issue.rb +15 -0
- data/lib/sunlight/legislator.rb +214 -0
- data/lib/sunlight/lobbyist.rb +63 -0
- data/sunlight.gemspec +18 -0
- metadata +81 -0
data/CHANGES.textile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
h3. 1.0 / 2009-06-25
|
2
|
+
|
3
|
+
* Move to the sunlightlabs GitHub account
|
4
|
+
* Fully support the Sunlight Labs API, notably the Committee methods
|
5
|
+
|
6
|
+
h3. 0.9.0 / 2009-03-15
|
7
|
+
|
8
|
+
* Warning: This release is not backwards-compatible!
|
9
|
+
* Change loading behavior of base functionality, works better with Rails and Merb
|
10
|
+
* Sunlight::SunlightObject is now Sunlight::Base
|
11
|
+
* For set up, Sunlight.api_key= is now Sunlight::Base.api_key=
|
12
|
+
* For set up, using "include 'Sunlight'" is no longer recommended
|
13
|
+
* Correct usage is Sunlight::Legislator.all_for(...) instead of just Legislator.all_for(...)
|
14
|
+
* Credit to Rue the Ghetto (rughetto on GitHub) and Eric Mill for inspiring the improvements above
|
15
|
+
* Add support for senate_class ("I", "II", or "III") and in_office (0 or 1) on Legislator
|
16
|
+
* Add support for Lobbyists, Filings, and Issues
|
17
|
+
* Huge credit to mindleak on GitHub for Lobbyist-related functionality
|
18
|
+
|
19
|
+
h3. 0.2.0 / 2009-03-01
|
20
|
+
|
21
|
+
* Add support for twitter_id and youtube_url on Legislator
|
22
|
+
* Add Legislator#search_by_name for fuzzy name searching
|
23
|
+
* Add Legislator#all_in_zipcode for better lookups on a five-digit zip code
|
24
|
+
* Raise exception when API Key isn't set
|
25
|
+
* Credit for various fixes goes to GitHub users pengwynn, hoverbird, and wilson
|
26
|
+
|
27
|
+
h3. 0.1.0 / 2008-08-20
|
28
|
+
|
29
|
+
* Initial version
|
data/README.textile
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
h1. Sunlight Labs API Gem
|
2
|
+
|
3
|
+
NOTE: This file is formatted in Textile and best viewed on "GitHub":http://github.com/luigi/sunlight.
|
4
|
+
|
5
|
+
h2. Description
|
6
|
+
|
7
|
+
A little gem that integrates with the Sunlight Labs API. From the "official site":http://services.sunlightlabs.com/api/:
|
8
|
+
|
9
|
+
bq. The Sunlight Labs API provides methods for obtaining basic information on Members of Congress, legislator IDs used by various websites, and lookups between places and the politicians that represent them. The primary purpose of the API is to facilitate mashups involving politicians and the various other APIs that are out there.
|
10
|
+
|
11
|
+
Full API documentation available "here":http://services.sunlightlabs.com/api/docs/.
|
12
|
+
|
13
|
+
Also, the Sunlight gem integrates with the Google Maps API for "street address geocoding":http://code.google.com/apis/maps/documentation/services.html#Geocoding. Because some zip codes overlap two or more congressional districts, passing in a latitude/longitude will give users the most accurate information. Since it's highly unlikely a user would know their exact lat/long, the Google Maps API is used to translate a street address string into a lat/long pair.
|
14
|
+
|
15
|
+
|
16
|
+
h2. Installation
|
17
|
+
|
18
|
+
The following gems are required:
|
19
|
+
|
20
|
+
* json
|
21
|
+
* ym4r
|
22
|
+
|
23
|
+
@$ sudo gem install json ym4r@
|
24
|
+
|
25
|
+
Then, install the stable Sunlight gem:
|
26
|
+
|
27
|
+
@$ sudo gem install sunlight@
|
28
|
+
|
29
|
+
Or, if you're adventurous, the latest dev version (which should be quite stable):
|
30
|
+
|
31
|
+
@$ sudo gem install sunlightlabs-ruby-sunlightapi --source=http://gems.github.com@
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
h2. Set Up
|
36
|
+
|
37
|
+
First, register for an API key "here":http://services.sunlightlabs.com/api/register/.
|
38
|
+
|
39
|
+
Then, you'll want to stick the following lines somewhere in your Ruby environment. _Note, this set up changed slightly as of version 0.9.0:_
|
40
|
+
|
41
|
+
<pre><code>
|
42
|
+
require 'rubygems'
|
43
|
+
require 'sunlight'
|
44
|
+
Sunlight::Base.api_key = 'yourapikeyfromtheurlabove'
|
45
|
+
</code></pre>
|
46
|
+
|
47
|
+
If you're testing this out in IRB, you'll run them one at a time. If you're using Rails >=2.1, stick them in a file called @sunlight.rb@ in @RAILS_ROOT/config/initializers@. They'll load on app startup.
|
48
|
+
|
49
|
+
h2. Usage
|
50
|
+
|
51
|
+
The Sunlight gem currently supports the Legislator and District objects. The Lobbyist object is coming soon.
|
52
|
+
|
53
|
+
h3. Legislator
|
54
|
+
|
55
|
+
Time to get to the good stuff. The most useful method is @Legislator#all_for@:
|
56
|
+
|
57
|
+
<pre><code>
|
58
|
+
congresspeople = Sunlight::Legislator.all_for(:address => "123 Fifth Ave New York, NY 10003")
|
59
|
+
senior_senator = congresspeople[:senior_senator]
|
60
|
+
junior_senator = congresspeople[:junior_senator]
|
61
|
+
representative = congresspeople[:representative]
|
62
|
+
|
63
|
+
junior_senator.firstname # returns "Kirsten"
|
64
|
+
junior_senator.lastname # returns "Gillibrand"
|
65
|
+
junior_senator.congress_office # returns "531 Dirksen Senate Office Building"
|
66
|
+
junior_senator.phone # returns "202-224-4451"
|
67
|
+
</code></pre>
|
68
|
+
|
69
|
+
Note that you should make the best attempt to get a full street address, as that is geocoded behind the scenes into a lat/long pair. If all you have is a five-digit zip code, you should not use @Legislator#all_for@, instead opting for @Legislator#all_in_zipcode@ (see below). If you pass in a zip+4, then go ahead and use @Legislator#all_for@.
|
70
|
+
|
71
|
+
So @Legislator#all_for@ returns a hash of @Legislator@ objects, and the keys are @:senior_senator@, @:junior_senator@, and @:representative@. Make sure to review all the available fields from the "Sunlight Labs API":http://services.sunlightlabs.com/api/docs/legislators/. You can also pass in a lat/long pair:
|
72
|
+
|
73
|
+
<pre><code>
|
74
|
+
congresspeople = Sunlight::Legislator.all_for(:latitude => 33.876145, :longitude => -84.453789)
|
75
|
+
</code></pre>
|
76
|
+
|
77
|
+
This bypasses the geocoding necessary by the Google Maps API. For social networks and other applications with a User object, it makes sense to geocode the user's address up front and save the lat/long data in the local database. Then, use the lat/long pair instead of address, which cuts a substantial bit of time from the @Legislator#all_for@ request since the Google Maps API Geocoding function doesn't have to be called.
|
78
|
+
|
79
|
+
Have a five-digit zip code only? You can use the @Legislator#all_in_zipcode@ method, but keep in mind that a single zip may have multiple U.S. Representatives, as congressional district lines frequently divide populous zip codes. Unlike @Legislator#all_for@, this method returns an array of legislators, and it'll be up to you to parse through them (there will be a senior senator, a junior senator, and one or more representatives).
|
80
|
+
|
81
|
+
<pre><code>
|
82
|
+
members_of_congress = Sunlight::Legislator.all_in_zipcode(90210)
|
83
|
+
|
84
|
+
members_of_congress.each do |member|
|
85
|
+
# do stuff
|
86
|
+
end
|
87
|
+
</code></pre>
|
88
|
+
|
89
|
+
You can also use the @Legislator#all_where@ method for searching based on available fields. Again, you'll get back an array of @Legislator@ objects:
|
90
|
+
|
91
|
+
<pre><code>
|
92
|
+
johns = Sunlight::Legislator.all_where(:firstname => "John")
|
93
|
+
floridians = Sunlight::Legislator.all_where(:state => "FL")
|
94
|
+
dudes = Sunlight::Legislator.all_where(:gender => "M")
|
95
|
+
|
96
|
+
johns.each do |john|
|
97
|
+
# do stuff
|
98
|
+
end
|
99
|
+
</code></pre>
|
100
|
+
|
101
|
+
Lastly, to provide your users with a name search functionality, use @Legislator#search_by_name@, which uses fuzzy matching to compensate for nicknames and misspellings. So "Teddy Kennedey" (real name Edward Kennedy) and "Jack Murtha" (real name John Murtha) will return the correct matches. You can specify a higher confidence threshold (default set to 0.80) if you feel that the matches being returned aren't accurate enough. This also returns an array of @Legislator@ objects:
|
102
|
+
|
103
|
+
<pre><code>
|
104
|
+
legislators = Sunlight::Legislator.search_by_name("Teddy Kennedey")
|
105
|
+
legislators = Sunlight::Legislator.search_by_name("Johnny Boy Kerry", 0.91)
|
106
|
+
</code></pre>
|
107
|
+
|
108
|
+
|
109
|
+
h3. District
|
110
|
+
|
111
|
+
There's also the @District@ object. @District#get@ takes in either lat/long or an address and does it's best to return the correct Congressional District:
|
112
|
+
|
113
|
+
<pre><code>
|
114
|
+
district = Sunlight::District.get(:latitude => 33.876145, :longitude => -84.453789)
|
115
|
+
district.state # returns "GA"
|
116
|
+
district.number # returns "6"
|
117
|
+
|
118
|
+
district = Sunlight::District.get(:address => "123 Fifth Ave New York, NY")
|
119
|
+
</code></pre>
|
120
|
+
|
121
|
+
Finally, two more methods, @District.all_from_zipcode@ and @District.zipcodes_in@, help you out when you want to get all districts in a given zip code, or if you want to get back all zip codes in a given district.
|
122
|
+
|
123
|
+
<pre><code>
|
124
|
+
districts = Sunlight::District.all_from_zipcode(90210) # returns array of District objects
|
125
|
+
zipcodes = Sunlight::District.zipcodes_in("NY", "10") # returns array of zip codes as strings ["11201", "11202", "11203",...]
|
126
|
+
</code></pre>
|
127
|
+
|
128
|
+
|
129
|
+
h3. Committees
|
130
|
+
|
131
|
+
Members of Congress sit on all-important @Committees@, the smaller bodies that hold hearings and are first to review legislation.
|
132
|
+
|
133
|
+
The @Committee@ object has three identifying fields, and an array of subcommittees, which are @Committee@ objects themselves. To get all the committees for a given chamber of Congress:
|
134
|
+
|
135
|
+
<pre><code>
|
136
|
+
committees = Sunlight::Committee.all_for_chamber("Senate") # or "House" or "Joint"
|
137
|
+
some_committee = committees.last
|
138
|
+
some_committee.name # "Senate Committee on Agriculture, Nutrition, and Forestry"
|
139
|
+
some_committee.id # "SSAF"
|
140
|
+
some_committee.chamber # "Senate"
|
141
|
+
|
142
|
+
some_committee.subcommittees.each do |subcommittee|
|
143
|
+
# do some stuff...
|
144
|
+
end
|
145
|
+
</code></pre>
|
146
|
+
|
147
|
+
The @Committee@ object also keeps a collection of members in that committee, but since that's an API-heavy call, it must be done for each Committee one at a time:
|
148
|
+
|
149
|
+
<pre><code>
|
150
|
+
committees = Sunlight::Committee.all_for_chamber("Senate") # or "House" or "Joint"
|
151
|
+
some_committee = committees.last # some_committee.members starts out as nil
|
152
|
+
some_committee.load_members # some_committee.members is now populated
|
153
|
+
some_committee.members.each do |legislator|
|
154
|
+
# do some stuff...
|
155
|
+
end
|
156
|
+
</code></pre>
|
157
|
+
|
158
|
+
Coming from the opposite direction, the @Legislator@ object has a method for getting all committees for that particular Legislator, returning an array of @Committee@ objects:
|
159
|
+
|
160
|
+
<pre><code>
|
161
|
+
legislators = Sunlight::Legislator.search_by_name("Ted Kennedy")
|
162
|
+
legislator = legislators.first
|
163
|
+
legislator.committees.each do |committee|
|
164
|
+
# do some stuff...
|
165
|
+
end
|
166
|
+
</code></pre>
|
167
|
+
|
168
|
+
h3. Lobbyists and Filings
|
169
|
+
|
170
|
+
Moving away from members of Congress, the Sunlight API also exposes data on @Lobbyists@, the organizations and companies they lobby on behalf of, and the @Issues@ they lobby on. Lobbyists must submit filings to the Senate Office of Public Records, and these are represented as @Filings@ in the API.
|
171
|
+
|
172
|
+
Like on the @Legislator@ object, the @Lobbyist@ object provides for fuzzy name search capability. However, because the universe of Lobbyists is much larger than Legislators, the confidence threshold defaults to 0.90 instead of 0.80, and also limits the search to Lobbyists who have filed in the current year. Both can be adjusted, and the method returns an array of @Lobbyist@ objects:
|
173
|
+
|
174
|
+
<pre><code>
|
175
|
+
lobbyists = Lobbyist.search("Nisha Thompsen")
|
176
|
+
lobbyists = Lobbyist.search("Michael Klein", 0.95, 2007)
|
177
|
+
</code></pre>
|
178
|
+
|
179
|
+
You can also use the @Filing@ object to get a specific filing record, where the unique identifier comes from the "Senate Office of Public Records":http://senate.gov/legislative/Public_Disclosure/LDA_reports.htm. The @Filing@ object will have an array of issues and lobbyists associated to it.
|
180
|
+
|
181
|
+
<pre><code>
|
182
|
+
filing = Sunlight::Filing.get("29D4D19E-CB7D-46D2-99F0-27FF15901A4C")
|
183
|
+
filing.issues.each { |issue| ... }
|
184
|
+
filing.lobbyists.each { |lobbyist| ... }
|
185
|
+
</code></pre>
|
186
|
+
|
187
|
+
If you have a client name (that is, the organization or company the lobbyist works for) or the registrant name (the lobbyist), then you can also find associated filings. To use the @all_where@ method, pass in a hash with @:client_name@, @:registrant_name@, and @:year@ as the keys. You must pass in @:client_name@ or @:registrant_name@.
|
188
|
+
|
189
|
+
<pre><code>
|
190
|
+
filings = Filing.all_where(:client_name => "SUNLIGHT FOUNDATION")
|
191
|
+
filings.each do |filing|
|
192
|
+
...
|
193
|
+
filing.issues.each { |issue| ... }
|
194
|
+
filing.lobbyists.each { |issue| ... }
|
195
|
+
end
|
196
|
+
</code></pre>
|
197
|
+
|
198
|
+
|
199
|
+
h2. License
|
200
|
+
|
201
|
+
See the terms of usage for the "Sunlight Labs API":http://services.sunlightlabs.com/api/ and the "Google Maps API":http://code.google.com/apis/maps/terms.html.
|
202
|
+
|
203
|
+
Copyright © 2009 by Luigi Montanez and the Sunlight Foundation under the MIT License.
|
204
|
+
|
205
|
+
Permission is hereby granted, free of charge, to any person
|
206
|
+
obtaining a copy of this software and associated documentation
|
207
|
+
files (the "Software"), to deal in the Software without
|
208
|
+
restriction, including without limitation the rights to use,
|
209
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
210
|
+
copies of the Software, and to permit persons to whom the
|
211
|
+
Software is furnished to do so, subject to the following
|
212
|
+
conditions:
|
213
|
+
|
214
|
+
The above copyright notice and this permission notice shall be
|
215
|
+
included in all copies or substantial portions of the Software.
|
216
|
+
|
217
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
218
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
219
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
220
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
221
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
222
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
223
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
224
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/lib/sunlight.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module Sunlight
|
2
|
+
|
3
|
+
# Houses general methods to work with the Sunlight and Google Maps APIs
|
4
|
+
class Base
|
5
|
+
|
6
|
+
API_URL = "http://services.sunlightlabs.com/api/"
|
7
|
+
API_FORMAT = "json"
|
8
|
+
@@api_key = ''
|
9
|
+
|
10
|
+
def self.api_key
|
11
|
+
@@api_key
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.api_key=(key)
|
15
|
+
@@api_key = key
|
16
|
+
end
|
17
|
+
|
18
|
+
# Constructs a Sunlight API-friendly URL
|
19
|
+
def self.construct_url(api_method, params)
|
20
|
+
if api_key == nil or api_key == ''
|
21
|
+
raise "Failed to provide Sunlight API Key"
|
22
|
+
else
|
23
|
+
"#{API_URL}#{api_method}.#{API_FORMAT}?apikey=#{api_key}#{hash2get(params)}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Converts a hash to a GET string
|
28
|
+
def self.hash2get(h)
|
29
|
+
|
30
|
+
get_string = ""
|
31
|
+
|
32
|
+
h.each_pair do |key, value|
|
33
|
+
get_string += "&#{key.to_s}=#{CGI::escape(value.to_s)}"
|
34
|
+
end
|
35
|
+
|
36
|
+
get_string
|
37
|
+
|
38
|
+
end # def hash2get
|
39
|
+
|
40
|
+
# Use the Net::HTTP and JSON libraries to make the API call
|
41
|
+
#
|
42
|
+
# Usage:
|
43
|
+
# Legislator::District.get_json_data("http://someurl.com") # returns Hash of data or nil
|
44
|
+
def self.get_json_data(url)
|
45
|
+
|
46
|
+
response = Net::HTTP.get_response(URI.parse(url))
|
47
|
+
if response.class == Net::HTTPOK
|
48
|
+
result = JSON.parse(response.body)
|
49
|
+
else
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
end # self.get_json_data
|
54
|
+
|
55
|
+
end # class Base
|
56
|
+
|
57
|
+
end # module Sunlight
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Sunlight
|
2
|
+
|
3
|
+
class District < Base
|
4
|
+
|
5
|
+
attr_accessor :state, :number
|
6
|
+
|
7
|
+
def initialize(state, number)
|
8
|
+
@state = state
|
9
|
+
@number = number
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
# Usage:
|
14
|
+
# Sunlight::District.get(:latitude => 33.876145, :longitude => -84.453789) # returns one District object or nil
|
15
|
+
# Sunlight::District.get(:address => "123 Fifth Ave New York, NY") # returns one District object or nil
|
16
|
+
#
|
17
|
+
def self.get(params)
|
18
|
+
|
19
|
+
if (params[:latitude] and params[:longitude])
|
20
|
+
|
21
|
+
get_from_lat_long(params[:latitude], params[:longitude])
|
22
|
+
|
23
|
+
elsif (params[:address])
|
24
|
+
|
25
|
+
# get the lat/long from Google
|
26
|
+
placemarks = Geocoding::get(params[:address])
|
27
|
+
|
28
|
+
unless placemarks.empty?
|
29
|
+
placemark = placemarks[0]
|
30
|
+
get_from_lat_long(placemark.latitude, placemark.longitude)
|
31
|
+
end
|
32
|
+
|
33
|
+
else
|
34
|
+
nil # appropriate params not found
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
|
41
|
+
# Usage:
|
42
|
+
# Sunlight::District.get_from_lat_long(-123, 123) # returns District object or nil
|
43
|
+
#
|
44
|
+
def self.get_from_lat_long(latitude, longitude)
|
45
|
+
|
46
|
+
url = construct_url("districts.getDistrictFromLatLong", {:latitude => latitude, :longitude => longitude})
|
47
|
+
|
48
|
+
if (result = get_json_data(url))
|
49
|
+
|
50
|
+
districts = []
|
51
|
+
result["response"]["districts"].each do |district|
|
52
|
+
districts << District.new(district["district"]["state"], district["district"]["number"])
|
53
|
+
end
|
54
|
+
|
55
|
+
districts.first
|
56
|
+
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end # if response.class
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
# Usage:
|
66
|
+
# Sunlight::District.all_from_zipcode(90210) # returns array of District objects
|
67
|
+
#
|
68
|
+
def self.all_from_zipcode(zipcode)
|
69
|
+
|
70
|
+
url = construct_url("districts.getDistrictsFromZip", {:zip => zipcode})
|
71
|
+
|
72
|
+
if (result = get_json_data(url))
|
73
|
+
|
74
|
+
districts = []
|
75
|
+
result["response"]["districts"].each do |district|
|
76
|
+
districts << District.new(district["district"]["state"], district["district"]["number"])
|
77
|
+
end
|
78
|
+
|
79
|
+
districts
|
80
|
+
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end # if response.class
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
# Usage:
|
90
|
+
# Sunlight::District.zipcodes_in("NY", 29) # returns ["14009", "14024", "14029", ...]
|
91
|
+
#
|
92
|
+
def self.zipcodes_in(state, number)
|
93
|
+
|
94
|
+
url = construct_url("districts.getZipsFromDistrict", {:state => state, :district => number})
|
95
|
+
|
96
|
+
if (result = get_json_data(url))
|
97
|
+
result["response"]["zips"]
|
98
|
+
else
|
99
|
+
nil
|
100
|
+
end # if response.class
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
end # class District
|
107
|
+
|
108
|
+
end # module Sunlight
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module Sunlight
|
2
|
+
|
3
|
+
class Filing < Base
|
4
|
+
attr_accessor :filing_id, :filing_period, :filing_date, :filing_amount,
|
5
|
+
:filing_year, :filing_type, :filing_pdf, :client_senate_id,
|
6
|
+
:client_name, :client_country, :client_state,
|
7
|
+
:client_ppb_country, :client_ppb_state, :client_description,
|
8
|
+
:client_contact_firstname, :client_contact_middlename,
|
9
|
+
:client_contact_lastname, :client_contact_suffix,
|
10
|
+
:registrant_senate_id, :registrant_name, :registrant_address,
|
11
|
+
:registrant_description, :registrant_country,
|
12
|
+
:registrant_ppb_country, :lobbyists, :issues
|
13
|
+
|
14
|
+
# Takes in a hash where the keys are strings (the format passed in by the JSON parser)
|
15
|
+
#
|
16
|
+
def initialize(params)
|
17
|
+
params.each do |key, value|
|
18
|
+
instance_variable_set("@#{key}", value) if Filing.instance_methods.include? key
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Get a filing based on filing ID.
|
24
|
+
#
|
25
|
+
# See the API documentation:
|
26
|
+
#
|
27
|
+
# http://wiki.sunlightlabs.com/index.php/Lobbyists.getFiling
|
28
|
+
#
|
29
|
+
# Returns:
|
30
|
+
#
|
31
|
+
# A Filing and corresponding Lobbyists and Issues matching
|
32
|
+
# the given ID, or nil if one wasn't found.
|
33
|
+
#
|
34
|
+
# Usage:
|
35
|
+
#
|
36
|
+
# filing = Sunlight::Filing.get("29D4D19E-CB7D-46D2-99F0-27FF15901A4C")
|
37
|
+
# filing.issues.each { |issue| ... }
|
38
|
+
# filing.lobbyists.each { |lobbyist| ... }
|
39
|
+
#
|
40
|
+
def self.get(id)
|
41
|
+
url = construct_url("lobbyists.getFiling", :id => id)
|
42
|
+
|
43
|
+
if (response = get_json_data(url))
|
44
|
+
if (f = response["response"]["filing"])
|
45
|
+
filing = Filing.new(f)
|
46
|
+
filing.lobbyists = filing.lobbyists.map do |lobbyist|
|
47
|
+
Lobbyist.new(lobbyist["lobbyist"])
|
48
|
+
end
|
49
|
+
filing.issues = filing.issues.map do |issue|
|
50
|
+
Issue.new(issue["issue"])
|
51
|
+
end
|
52
|
+
filing
|
53
|
+
else
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Search the filing database. At least one of client_name or
|
63
|
+
# registrant_name must be provided, along with an optional year.
|
64
|
+
# Note that year is recommended, as the full data set dating back
|
65
|
+
# to 1999 may be enormous.
|
66
|
+
#
|
67
|
+
# See the API documentation:
|
68
|
+
#
|
69
|
+
# http://wiki.sunlightlabs.com/index.php/Lobbyists.getFilingList
|
70
|
+
#
|
71
|
+
# Returns:
|
72
|
+
#
|
73
|
+
# An array of Filing objects that match the conditions
|
74
|
+
#
|
75
|
+
# Usage:
|
76
|
+
#
|
77
|
+
# filings = Filing.all_where(:client_name => "SUNLIGHT FOUNDATION")
|
78
|
+
# filings.each do |filing|
|
79
|
+
# ...
|
80
|
+
# filing.issues.each { |issue| ... }
|
81
|
+
# filing.lobbyists.each { |issue| ... }
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
def self.all_where(params)
|
85
|
+
if params[:client_name].nil? and params[:registrant_name].nil?
|
86
|
+
nil
|
87
|
+
else
|
88
|
+
url = construct_url("lobbyists.getFilingList", params)
|
89
|
+
|
90
|
+
if (response = get_json_data(url))
|
91
|
+
filings = []
|
92
|
+
|
93
|
+
response["response"]["filings"].each do |result|
|
94
|
+
filing = Filing.new(result["filing"])
|
95
|
+
|
96
|
+
filing.lobbyists = filing.lobbyists.map do |lobbyist|
|
97
|
+
Lobbyist.new(lobbyist["lobbyist"])
|
98
|
+
end
|
99
|
+
filing.issues = filing.issues.map do |issue|
|
100
|
+
Issue.new(issue["issue"])
|
101
|
+
end
|
102
|
+
|
103
|
+
filings << filing
|
104
|
+
end
|
105
|
+
|
106
|
+
if filings.empty?
|
107
|
+
nil
|
108
|
+
else
|
109
|
+
filings
|
110
|
+
end
|
111
|
+
else
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
end # if params
|
115
|
+
end # def self.all_where
|
116
|
+
|
117
|
+
end # class Filing
|
118
|
+
|
119
|
+
end # module Sunlight
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Sunlight
|
2
|
+
|
3
|
+
class Issue < Base
|
4
|
+
attr_accessor :code, :specific_issue
|
5
|
+
|
6
|
+
# Takes in a hash where the keys are strings (the format passed in by the JSON parser)
|
7
|
+
#
|
8
|
+
def initialize(params)
|
9
|
+
params.each do |key, value|
|
10
|
+
instance_variable_set("@#{key}", value) if Issue.instance_methods.include? key
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module Sunlight
|
2
|
+
|
3
|
+
class Legislator < Base
|
4
|
+
|
5
|
+
|
6
|
+
attr_accessor :title, :firstname, :middlename, :lastname, :name_suffix, :nickname,
|
7
|
+
:party, :state, :district, :gender, :phone, :fax, :website, :webform,
|
8
|
+
:email, :congress_office, :bioguide_id, :votesmart_id, :fec_id,
|
9
|
+
:govtrack_id, :crp_id, :event_id, :congresspedia_url, :youtube_url,
|
10
|
+
:twitter_id, :fuzzy_score, :in_office, :senate_class
|
11
|
+
|
12
|
+
# Takes in a hash where the keys are strings (the format passed in by the JSON parser)
|
13
|
+
#
|
14
|
+
def initialize(params)
|
15
|
+
params.each do |key, value|
|
16
|
+
instance_variable_set("@#{key}", value) if Legislator.instance_methods.include? key
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Get the committees the Legislator sits on
|
21
|
+
#
|
22
|
+
# Returns:
|
23
|
+
#
|
24
|
+
# An array of Committee objects, each possibly
|
25
|
+
# having its own subarray of subcommittees
|
26
|
+
def committees
|
27
|
+
url = Sunlight::Base.construct_url("committees.allForLegislator", {:bioguide_id => self.bioguide_id})
|
28
|
+
|
29
|
+
if (result = Sunlight::Base.get_json_data(url))
|
30
|
+
committees = []
|
31
|
+
result["response"]["committees"].each do |committee|
|
32
|
+
committees << Sunlight::Committee.new(committee["committee"])
|
33
|
+
end
|
34
|
+
else
|
35
|
+
nil # appropriate params not found
|
36
|
+
end
|
37
|
+
committees
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
#
|
42
|
+
# Useful for getting the exact Legislators for a given district.
|
43
|
+
#
|
44
|
+
# Returns:
|
45
|
+
#
|
46
|
+
# A Hash of the three Members of Congress for a given District: Two
|
47
|
+
# Senators and one Representative.
|
48
|
+
#
|
49
|
+
# You can pass in lat/long or address. The district will be
|
50
|
+
# determined for you:
|
51
|
+
#
|
52
|
+
# officials = Legislator.all_for(:latitude => 33.876145, :longitude => -84.453789)
|
53
|
+
# senior = officials[:senior_senator]
|
54
|
+
# junior = officials[:junior_senator]
|
55
|
+
# rep = officials[:representative]
|
56
|
+
#
|
57
|
+
# Sunlight::Legislator.all_for(:address => "123 Fifth Ave New York, NY 10003")
|
58
|
+
# Sunlight::Legislator.all_for(:address => "90210") # it'll work, but use all_in_zip instead
|
59
|
+
#
|
60
|
+
def self.all_for(params)
|
61
|
+
|
62
|
+
if (params[:latitude] and params[:longitude])
|
63
|
+
Legislator.all_in_district(District.get(:latitude => params[:latitude], :longitude => params[:longitude]))
|
64
|
+
elsif (params[:address])
|
65
|
+
Legislator.all_in_district(District.get(:address => params[:address]))
|
66
|
+
else
|
67
|
+
nil # appropriate params not found
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
#
|
74
|
+
# A helper method for all_for. Use that instead, unless you
|
75
|
+
# already have the district object, then use this.
|
76
|
+
#
|
77
|
+
# Usage:
|
78
|
+
#
|
79
|
+
# officials = Sunlight::Legislator.all_in_district(District.new("NJ", "7"))
|
80
|
+
#
|
81
|
+
def self.all_in_district(district)
|
82
|
+
|
83
|
+
senior_senator = Legislator.all_where(:state => district.state, :district => "Senior Seat").first
|
84
|
+
junior_senator = Legislator.all_where(:state => district.state, :district => "Junior Seat").first
|
85
|
+
representative = Legislator.all_where(:state => district.state, :district => district.number).first
|
86
|
+
|
87
|
+
{:senior_senator => senior_senator, :junior_senator => junior_senator, :representative => representative}
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
#
|
93
|
+
# A more general, open-ended search on Legislators than #all_for.
|
94
|
+
# See the Sunlight API for list of conditions and values:
|
95
|
+
#
|
96
|
+
# http://services.sunlightlabs.com/api/docs/legislators/
|
97
|
+
#
|
98
|
+
# Returns:
|
99
|
+
#
|
100
|
+
# An array of Legislator objects that matches the conditions
|
101
|
+
#
|
102
|
+
# Usage:
|
103
|
+
#
|
104
|
+
# johns = Sunlight::Legislator.all_where(:firstname => "John")
|
105
|
+
# floridians = Sunlight::Legislator.all_where(:state => "FL")
|
106
|
+
# dudes = Sunlight::Legislator.all_where(:gender => "M")
|
107
|
+
#
|
108
|
+
def self.all_where(params)
|
109
|
+
|
110
|
+
url = construct_url("legislators.getList", params)
|
111
|
+
|
112
|
+
if (result = get_json_data(url))
|
113
|
+
|
114
|
+
legislators = []
|
115
|
+
result["response"]["legislators"].each do |legislator|
|
116
|
+
legislators << Legislator.new(legislator["legislator"])
|
117
|
+
end
|
118
|
+
|
119
|
+
legislators
|
120
|
+
|
121
|
+
else
|
122
|
+
nil
|
123
|
+
end # if response.class
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# When you only have a zipcode (and could not get address from the user), use this.
|
129
|
+
# It specifically accounts for the case where more than one Representative's district
|
130
|
+
# is in a zip code.
|
131
|
+
#
|
132
|
+
# If possible, ask for full address for proper geocoding via Legislator#all_for, which
|
133
|
+
# gives you a nice hash.
|
134
|
+
#
|
135
|
+
# Returns:
|
136
|
+
#
|
137
|
+
# An array of Legislator objects
|
138
|
+
#
|
139
|
+
# Usage:
|
140
|
+
#
|
141
|
+
# legislators = Sunlight::Legislator.all_in_zipcode(90210)
|
142
|
+
#
|
143
|
+
def self.all_in_zipcode(zipcode)
|
144
|
+
|
145
|
+
url = construct_url("legislators.allForZip", {:zip => zipcode})
|
146
|
+
|
147
|
+
if (result = get_json_data(url))
|
148
|
+
|
149
|
+
legislators = []
|
150
|
+
result["response"]["legislators"].each do |legislator|
|
151
|
+
legislators << Legislator.new(legislator["legislator"])
|
152
|
+
end
|
153
|
+
|
154
|
+
legislators
|
155
|
+
|
156
|
+
else
|
157
|
+
nil
|
158
|
+
end # if response.class
|
159
|
+
|
160
|
+
end # def self.all_in_zipcode
|
161
|
+
|
162
|
+
|
163
|
+
#
|
164
|
+
# Fuzzy name searching. Returns possible matching Legislators
|
165
|
+
# along with a confidence score. Confidence scores below 0.8
|
166
|
+
# mean the Legislator should not be used.
|
167
|
+
#
|
168
|
+
# The API documentation explains it best:
|
169
|
+
#
|
170
|
+
# http://wiki.sunlightlabs.com/index.php/Legislators.search
|
171
|
+
#
|
172
|
+
# Returns:
|
173
|
+
#
|
174
|
+
# An array of Legislators, with the fuzzy_score set as an attribute
|
175
|
+
#
|
176
|
+
# Usage:
|
177
|
+
#
|
178
|
+
# legislators = Sunlight::Legislator.search_by_name("Teddy Kennedey")
|
179
|
+
# legislators = Sunlight::Legislator.search_by_name("Johnny Kerry", 0.9)
|
180
|
+
#
|
181
|
+
def self.search_by_name(name, threshold='0.8')
|
182
|
+
|
183
|
+
url = construct_url("legislators.search", {:name => name, :threshold => threshold})
|
184
|
+
|
185
|
+
if (response = get_json_data(url))
|
186
|
+
|
187
|
+
legislators = []
|
188
|
+
response["response"]["results"].each do |result|
|
189
|
+
if result
|
190
|
+
legislator = Legislator.new(result["result"]["legislator"])
|
191
|
+
fuzzy_score = result["result"]["score"]
|
192
|
+
|
193
|
+
if threshold.to_f < fuzzy_score.to_f
|
194
|
+
legislator.fuzzy_score = fuzzy_score.to_f
|
195
|
+
legislators << legislator
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
if legislators.empty?
|
201
|
+
nil
|
202
|
+
else
|
203
|
+
legislators
|
204
|
+
end
|
205
|
+
|
206
|
+
else
|
207
|
+
nil
|
208
|
+
end
|
209
|
+
|
210
|
+
end # def self.search_by_name
|
211
|
+
|
212
|
+
end # class Legislator
|
213
|
+
|
214
|
+
end # module Sunlight
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Sunlight
|
2
|
+
|
3
|
+
class Lobbyist < Base
|
4
|
+
attr_accessor :firstname, :middlename, :lastname, :suffix,
|
5
|
+
:official_position, :filings, :fuzzy_score
|
6
|
+
|
7
|
+
# Takes in a hash where the keys are strings (the format passed in by the JSON parser)
|
8
|
+
#
|
9
|
+
def initialize(params)
|
10
|
+
params.each do |key, value|
|
11
|
+
instance_variable_set("@#{key}", value) if Lobbyist.instance_methods.include? key
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Fuzzy name searching of lobbyists. Returns possible matching Lobbyists
|
17
|
+
# along with a confidence score. Confidence scores below 0.8
|
18
|
+
# mean the lobbyist should not be used.
|
19
|
+
#
|
20
|
+
# See the API documentation:
|
21
|
+
#
|
22
|
+
# http://wiki.sunlightlabs.com/index.php/Lobbyists.search
|
23
|
+
#
|
24
|
+
# Returns:
|
25
|
+
#
|
26
|
+
# An array of Lobbyists, with the fuzzy_score set as an attribute
|
27
|
+
#
|
28
|
+
# Usage:
|
29
|
+
#
|
30
|
+
# lobbyists = Lobbyist.search("Nisha Thompsen")
|
31
|
+
# lobbyists = Lobbyist.search("Michael Klein", 0.95, 2007)
|
32
|
+
#
|
33
|
+
def self.search_by_name(name, threshold=0.9, year=Time.now.year)
|
34
|
+
|
35
|
+
url = construct_url("lobbyists.search", :name => name, :threshold => threshold, :year => year)
|
36
|
+
|
37
|
+
if (results = get_json_data(url))
|
38
|
+
lobbyists = []
|
39
|
+
results["response"]["results"].each do |result|
|
40
|
+
if result
|
41
|
+
lobbyist = Lobbyist.new(result["result"]["lobbyist"])
|
42
|
+
fuzzy_score = result["result"]["score"]
|
43
|
+
|
44
|
+
if threshold.to_f < fuzzy_score.to_f
|
45
|
+
lobbyist.fuzzy_score = fuzzy_score.to_f
|
46
|
+
lobbyists << lobbyist
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if lobbyists.empty?
|
52
|
+
nil
|
53
|
+
else
|
54
|
+
lobbyists
|
55
|
+
end
|
56
|
+
|
57
|
+
else
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end # def self.search
|
61
|
+
end # class Lobbyist
|
62
|
+
|
63
|
+
end
|
data/sunlight.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "sunlight"
|
3
|
+
s.version = "1.0.0"
|
4
|
+
s.date = "2009-06-10"
|
5
|
+
s.summary = "Library for accessing the Sunlight Labs API."
|
6
|
+
s.description = "Library for accessing the Sunlight Labs API."
|
7
|
+
s.rubyforge_project = "sunlight"
|
8
|
+
s.email = "luigi@sunlightfoundation.com"
|
9
|
+
s.homepage = "http://github.com/luigi/sunlight"
|
10
|
+
s.authors = ["Luigi Montanez"]
|
11
|
+
s.files = ['sunlight.gemspec', 'lib/sunlight.rb', 'lib/sunlight/base.rb',
|
12
|
+
'lib/sunlight/district.rb', 'lib/sunlight/legislator.rb',
|
13
|
+
'lib/sunlight/filing.rb', 'lib/sunlight/issue.rb',
|
14
|
+
'lib/sunlight/lobbyist.rb','README.textile', 'CHANGES.textile']
|
15
|
+
s.add_dependency("json", [">= 1.1.3"])
|
16
|
+
s.add_dependency("ym4r", [">= 0.6.1"])
|
17
|
+
s.has_rdoc = true
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sunlightlabs-sunlight
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Luigi Montanez
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-06-10 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: json
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.1.3
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ym4r
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.6.1
|
34
|
+
version:
|
35
|
+
description: Library for accessing the Sunlight Labs API.
|
36
|
+
email: luigi@sunlightfoundation.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- sunlight.gemspec
|
45
|
+
- lib/sunlight.rb
|
46
|
+
- lib/sunlight/base.rb
|
47
|
+
- lib/sunlight/district.rb
|
48
|
+
- lib/sunlight/legislator.rb
|
49
|
+
- lib/sunlight/filing.rb
|
50
|
+
- lib/sunlight/issue.rb
|
51
|
+
- lib/sunlight/lobbyist.rb
|
52
|
+
- README.textile
|
53
|
+
- CHANGES.textile
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/luigi/sunlight
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
|
59
|
+
require_paths:
|
60
|
+
- lib
|
61
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
version:
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
version:
|
73
|
+
requirements: []
|
74
|
+
|
75
|
+
rubyforge_project: sunlight
|
76
|
+
rubygems_version: 1.2.0
|
77
|
+
signing_key:
|
78
|
+
specification_version: 2
|
79
|
+
summary: Library for accessing the Sunlight Labs API.
|
80
|
+
test_files: []
|
81
|
+
|