IMPORTANT: This gem now exists at sunlightlabs/ruby-sunlight.
Sunlight Labs API Gem
NOTE: This file is formatted in Textile and best viewed on GitHub.
Description
A little gem that integrates with the Sunlight Labs API. From the official site:
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.
Full API documentation available here.
Also, the Sunlight gem integrates with the Google Maps API for street address 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.
Installation
The following gems are required:
- json
- ym4r
$ sudo gem install json ym4r
Then, install the stable Sunlight gem:
$ sudo gem install sunlight
Or, if you’re adventurous, the latest dev version (which should be quite stable):
$ sudo gem install luigi-sunlight --source=http://gems.github.com
Set Up
First, register for an API key here.
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:
require 'rubygems'
require 'sunlight'
Sunlight::Base.api_key = 'yourapikeyfromtheurlabove'
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.
Usage
The Sunlight gem currently supports the Legislator and District objects. The Lobbyist object is coming soon.
Legislator
Time to get to the good stuff. The most useful method is Legislator#all_for
:
congresspeople = Sunlight::Legislator.all_for(:address => "123 Fifth Ave New York, NY 10003")
senior_senator = congresspeople[:senior_senator]
junior_senator = congresspeople[:junior_senator]
representative = congresspeople[:representative]
junior_senator.firstname # returns "Kirsten"
junior_senator.lastname # returns "Gillibrand"
junior_senator.congress_office # returns "531 Dirksen Senate Office Building"
junior_senator.phone # returns "202-224-4451"
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
.
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. You can also pass in a lat/long pair:
congresspeople = Sunlight::Legislator.all_for(:latitude => 33.876145, :longitude => -84.453789)
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.
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).
members_of_congress = Sunlight::Legislator.all_in_zipcode(90210)
members_of_congress.each do |member|
# do stuff
end
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:
johns = Sunlight::Legislator.all_where(:firstname => "John")
floridians = Sunlight::Legislator.all_where(:state => "FL")
dudes = Sunlight::Legislator.all_where(:gender => "M")
johns.each do |john|
# do stuff
end
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:
legislators = Sunlight::Legislator.search_by_name("Teddy Kennedey")
legislators = Sunlight::Legislator.search_by_name("Johnny Boy Kerry", 0.91)
District
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:
district = Sunlight::District.get(:latitude => 33.876145, :longitude => -84.453789)
district.state # returns "GA"
district.number # returns "6"
district = Sunlight::District.get(:address => "123 Fifth Ave New York, NY")
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.
districts = Sunlight::District.all_from_zipcode(90210) # returns array of District objects
zipcodes = Sunlight::District.zipcodes_in("NY", "10") # returns array of zip codes as strings ["11201", "11202", "11203",...]
Committees
Members of Congress sit on all-important Committees
, the smaller bodies that hold hearings and are first to review legislation.
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:
committees = Sunlight::Committee.all_for_chamber("Senate") # or "House" or "Joint"
some_committee = committees.last
some_committee.name # "Senate Committee on Agriculture, Nutrition, and Forestry"
some_committee.id # "SSAF"
some_committee.chamber # "Senate"
some_committee.subcommittees.each do |subcommittee|
# do some stuff...
end
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:
committees = Sunlight::Committee.all_for_chamber("Senate") # or "House" or "Joint"
some_committee = committees.last # some_committee.members starts out as nil
some_committee.load_members # some_committee.members is now populated
some_committee.members.each do |legislator|
# do some stuff...
end
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:
legislators = Sunlight::Legislator.search_by_name("Ted Kennedy")
legislator = legislators.first
legislator.committees.each do |committee|
# do some stuff...
end
Lobbyists and Filings
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.
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:
lobbyists = Lobbyist.search("Nisha Thompsen")
lobbyists = Lobbyist.search("Michael Klein", 0.95, 2007)
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. The Filing
object will have an array of issues and lobbyists associated to it.
filing = Sunlight::Filing.get("29D4D19E-CB7D-46D2-99F0-27FF15901A4C")
filing.issues.each { |issue| ... }
filing.lobbyists.each { |lobbyist| ... }
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
.
filings = Filing.all_where(:client_name => "SUNLIGHT FOUNDATION")
filings.each do |filing|
...
filing.issues.each { |issue| ... }
filing.lobbyists.each { |issue| ... }
end
License
See the terms of usage for the Sunlight Labs API and the Google Maps API.
Copyright © 2009 by Luigi Montanez under the MIT License.
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.