govkit 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Carl Tashian
1
+ Copyright (c) 2010 Participatory Politics Foundation
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.markdown CHANGED
@@ -1,6 +1,11 @@
1
1
  # Govkit
2
2
 
3
- Govkit is a Ruby gem that provides simple access to open government APIs around the web.
3
+ Govkit is a Ruby gem that provides simple access to open government APIs around the web, including:
4
+
5
+ * [OpenCongress](http://www.opencongress.org/api), which has an API for federal bills, votes, people, and news and blog coverage
6
+ * [The Fifty States project](http://fiftystates-dev.sunlightlabs.com/), which has a RESTful API for accessing data about state legislators, bills, votes, etc.
7
+ * [Project Vote Smart](http://www.votesmart.org/services_api.php), which has an API with congressional addresses, etc.
8
+ * [Follow The Money](http://www.followthemoney.org/), whose API reveals campaign contribution data for state officials.
4
9
 
5
10
  # Installation
6
11
 
@@ -14,14 +19,11 @@ Add govkit to your environment.rb or Gemfile
14
19
 
15
20
  Run <code>./script/generate govkit</code> to copy a config file into <code>config/initializers/govkit.rb</code>. You will need to add your API keys to this config file.
16
21
 
17
- # Example
18
-
19
- [http://fiftystates-dev.sunlightlabs.com/](The Fifty States project) has a RESTful API for accessing data about state legislators, bills, votes, etc.
22
+ # Examples
20
23
 
21
24
  >> Govkit::FiftyStates::State.find_by_abbreviation('CA')
22
- >> Govkit::VoteSmart::Address.find(legislator_id)
23
-
24
- (TODO: add usage examples...)
25
+ >> Govkit::VoteSmart::Address.find(votesmart_candidate_id)
26
+ >> GovKit::OpenCongress::Bill.find(:number => 5479, :type => 'h', :congress => '111')
25
27
 
26
28
  # Bugs? Questions?
27
29
 
data/Rakefile CHANGED
@@ -2,6 +2,7 @@ require 'rubygems'
2
2
  require 'rake'
3
3
  require 'rake/rdoctask'
4
4
 
5
+
5
6
  begin
6
7
  require 'spec/rake/spectask'
7
8
  rescue LoadError
@@ -23,7 +24,8 @@ begin
23
24
  gem.homepage = "http://github.com/opengovernment/govkit"
24
25
  gem.authors = ["Participatory Politics Foundation", "Srinivas Aki", "Carl Tashian"]
25
26
  gem.add_dependency('httparty', '>= 0.5.2')
26
- gem.add_dependency('json', '>= 1.2.4')
27
+ gem.add_dependency('json', '>= 1.4.3')
28
+ gem.add_dependency('hpricot', '>= 0.8.2')
27
29
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
28
30
  end
29
31
  Jeweler::GemcutterTasks.new
data/USAGE CHANGED
@@ -0,0 +1 @@
1
+ See README.markdown for full usage details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.1.0
@@ -1,9 +1,19 @@
1
1
  if defined? GovKit
2
-
3
2
  GovKit.configure do |config|
4
3
  # Get an API key for Sunlight's Fifty States project here:
5
4
  # http://services.sunlightlabs.com/accounts/register/
6
5
  config.fiftystates_apikey = 'YOUR_FIFTYSTATES_API_KEY'
7
- end
8
6
 
7
+ ##API key for Votesmart
8
+ # http://votesmart.org/services_api.php
9
+ config.votesmart_apikey = 'YOUR_VOTESMART_API_KEY'
10
+
11
+ # API key for NIMSP. Request one here:
12
+ # http://www.followthemoney.org/membership/settings.phtml
13
+ config.ftm_apikey = 'YOUR_FTM_API_KEY'
14
+
15
+ # Api key for OpenCongress
16
+ # http://www.opencongress.org/api
17
+ config.opencongress_apikey = 'YOUR_OPENCONGRESS_API_KEY'
18
+ end
9
19
  end
data/govkit.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{govkit}
8
- s.version = "0.0.2"
8
+ s.version = "0.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Participatory Politics Foundation", "Srinivas Aki", "Carl Tashian"]
12
- s.date = %q{2010-05-14}
12
+ s.date = %q{2010-06-10}
13
13
  s.description = %q{Govkit lets you quickly get encapsulated Ruby objects for common open government APIs. We're starting with Sunlight's Fifty States API and the Project Vote Smart API.}
14
14
  s.email = %q{carl@ppolitics.org}
15
15
  s.extra_rdoc_files = [
@@ -28,14 +28,26 @@ Gem::Specification.new do |s|
28
28
  "generators/govkit/templates/govkit.rb",
29
29
  "govkit.gemspec",
30
30
  "lib/gov_kit.rb",
31
+ "lib/gov_kit/acts_as_citeable.rb",
31
32
  "lib/gov_kit/configuration.rb",
32
33
  "lib/gov_kit/fifty_states.rb",
34
+ "lib/gov_kit/follow_the_money.rb",
35
+ "lib/gov_kit/open_congress.rb",
36
+ "lib/gov_kit/open_congress/bill.rb",
37
+ "lib/gov_kit/open_congress/blog_post.rb",
38
+ "lib/gov_kit/open_congress/news_post.rb",
39
+ "lib/gov_kit/open_congress/person.rb",
40
+ "lib/gov_kit/open_congress/person_stat.rb",
41
+ "lib/gov_kit/open_congress/roll_call.rb",
42
+ "lib/gov_kit/open_congress/roll_call_comparison.rb",
43
+ "lib/gov_kit/open_congress/voting_comparison.rb",
33
44
  "lib/gov_kit/resource.rb",
45
+ "lib/gov_kit/search_engines.rb",
46
+ "lib/gov_kit/search_engines/google_blog.rb",
47
+ "lib/gov_kit/search_engines/google_news.rb",
48
+ "lib/gov_kit/search_engines/technorati.rb",
34
49
  "lib/gov_kit/vote_smart.rb",
35
50
  "lib/govkit.rb",
36
- "lib/govkit/configuration.rb",
37
- "lib/govkit/fifty_states.rb",
38
- "lib/govkit/vote_smart.rb",
39
51
  "rails/init.rb",
40
52
  "spec/fifty_states_spec.rb",
41
53
  "spec/spec.opts",
@@ -44,7 +56,7 @@ Gem::Specification.new do |s|
44
56
  s.homepage = %q{http://github.com/opengovernment/govkit}
45
57
  s.rdoc_options = ["--charset=UTF-8"]
46
58
  s.require_paths = ["lib"]
47
- s.rubygems_version = %q{1.3.6}
59
+ s.rubygems_version = %q{1.3.7}
48
60
  s.summary = %q{Simple access to open government APIs around the web}
49
61
  s.test_files = [
50
62
  "spec/fifty_states_spec.rb",
@@ -55,16 +67,19 @@ Gem::Specification.new do |s|
55
67
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
68
  s.specification_version = 3
57
69
 
58
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
70
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
59
71
  s.add_runtime_dependency(%q<httparty>, [">= 0.5.2"])
60
- s.add_runtime_dependency(%q<json>, [">= 1.2.4"])
72
+ s.add_runtime_dependency(%q<json>, [">= 1.4.3"])
73
+ s.add_runtime_dependency(%q<hpricot>, [">= 0.8.2"])
61
74
  else
62
75
  s.add_dependency(%q<httparty>, [">= 0.5.2"])
63
- s.add_dependency(%q<json>, [">= 1.2.4"])
76
+ s.add_dependency(%q<json>, [">= 1.4.3"])
77
+ s.add_dependency(%q<hpricot>, [">= 0.8.2"])
64
78
  end
65
79
  else
66
80
  s.add_dependency(%q<httparty>, [">= 0.5.2"])
67
- s.add_dependency(%q<json>, [">= 1.2.4"])
81
+ s.add_dependency(%q<json>, [">= 1.4.3"])
82
+ s.add_dependency(%q<hpricot>, [">= 0.8.2"])
68
83
  end
69
84
  end
70
85
 
@@ -0,0 +1,40 @@
1
+ module GovKit::ActsAsCiteable
2
+
3
+ def self.included(base)
4
+ base.extend ActMethods
5
+ end
6
+
7
+ module ActMethods
8
+ def acts_as_citeable(options={})
9
+ options[:keywords] ||= []
10
+
11
+ class_inheritable_accessor :options
12
+ self.options = options
13
+
14
+ unless included_modules.include? InstanceMethods
15
+ extend ClassMethods
16
+ include InstanceMethods
17
+ end
18
+ end
19
+ end
20
+
21
+ module ClassMethods
22
+ end
23
+
24
+ module InstanceMethods
25
+
26
+ def raw_citations
27
+ params = self.options[:keywords].clone
28
+ attributes = self.options[:with].clone
29
+
30
+ attributes.each do |attr|
31
+ params << self.instance_eval("#{attr}")
32
+ end
33
+ {
34
+ :google_news => GovKit::SearchEngines::GoogleNews.search(params),
35
+ :google_blogs => GovKit::SearchEngines::GoogleBlog.search(params),
36
+ :technorati => GovKit::SearchEngines::Technorati.search(params)
37
+ }
38
+ end
39
+ end
40
+ end
@@ -2,13 +2,15 @@ module GovKit
2
2
  class Configuration
3
3
  attr_accessor :fiftystates_apikey, :fiftystates_base_url
4
4
  attr_accessor :votesmart_apikey, :votesmart_base_url
5
+ attr_accessor :ftm_apikey, :ftm_base_url
6
+ attr_accessor :opencongress_apikey, :opencongress_base_url
5
7
 
6
8
  def initialize
7
- @fiftystates_apikey = ''
9
+ @fiftystates_apikey = @votesmart_apikey = @ftm_apikey = ''
8
10
  @fiftystates_base_url = 'fiftystates-dev.sunlightlabs.com/api'
9
-
10
- @votesmart_apikey = ''
11
11
  @votesmart_base_url = 'api.votesmart.org/'
12
+ @ftm_base_url = 'api.followthemoney.org/'
13
+ @opencongress_base_url = 'www.opencongress.org/'
12
14
  end
13
15
  end
14
16
 
@@ -0,0 +1,100 @@
1
+ module GovKit
2
+ class FollowTheMoneyResource < Resource
3
+ default_params :key => GovKit::configuration.ftm_apikey
4
+ base_uri GovKit::configuration.ftm_base_url
5
+ end
6
+
7
+ module FollowTheMoney
8
+ class Business < FollowTheMoneyResource
9
+ def self.list
10
+ next_page, result, page_num = "yes", [], 0
11
+
12
+ until next_page == "no"
13
+ puts "Getting batch number #{page_num}"
14
+
15
+ response = get("/base_level.industries.list.php", :query => {:page => page_num})
16
+
17
+ doc = Hpricot::XML(response)
18
+
19
+ next_page = doc.search("/").first.attributes['next_page']
20
+
21
+ page_num += 1
22
+
23
+ result += doc.search('//business_detail').collect do |business|
24
+ business.attributes.to_hash
25
+ end
26
+ end
27
+
28
+ instantiate_collection(result)
29
+ end
30
+ end
31
+
32
+ class Contribution < FollowTheMoneyResource
33
+ def self.find(nimsp_id)
34
+ next_page, result, page_num = "yes", [], 0
35
+
36
+ until next_page == "no"
37
+ response = get("/candidates.contributions.php", :query => {"imsp_candidate_id" => nimsp_id, :page => page_num})
38
+ doc = Hpricot::XML(response)
39
+
40
+ next_page = doc.search("/").first.attributes['next_page']
41
+
42
+ page_num += 1
43
+
44
+ result = doc.search('//contribution').collect do |contribution|
45
+ contribution.attributes.to_hash
46
+ end
47
+ end
48
+ instantiate_collection(result)
49
+ end
50
+
51
+ def self.top(nimsp_id)
52
+ response = get("/candidates.top_contributor.php", :query => {"imsp_candidate_id" => nimsp_id})
53
+ doc = Hpricot::XML(response)
54
+ result = doc.search('//top_contributor').collect do |contribution|
55
+ contribution.attributes.to_hash
56
+ end
57
+
58
+ instantiate_collection(result)
59
+ end
60
+ end
61
+
62
+ class IndustryContribution < Contribution
63
+ def self.find(nimsp_id)
64
+ response = get("/candidates.industries.php", :query => {"imsp_candidate_id" => nimsp_id})
65
+ doc = Hpricot::XML(response)
66
+
67
+ result = doc.search('//candidate_industry').collect do |contribution|
68
+ contribution.attributes.to_hash
69
+ end
70
+
71
+ instantiate_collection(result)
72
+ end
73
+ end
74
+
75
+ class SectorContribution < Contribution
76
+ def self.find(nimsp_id)
77
+ response = get("/candidates.sectors.php", :query => {"imsp_candidate_id" => nimsp_id})
78
+ doc = Hpricot::XML(response)
79
+
80
+ result = doc.search('//candidate_sector').collect do |contribution|
81
+ contribution.attributes.to_hash
82
+ end
83
+
84
+ instantiate_collection(result)
85
+ end
86
+ end
87
+
88
+ class BusinessContribution < Contribution
89
+ def self.find(nimsp_id)
90
+ response = get("/candidates.businesses.php", :query => {"imsp_candidate_id" => nimsp_id})
91
+ doc = Hpricot::XML(response)
92
+ result = doc.search('//candidate_business').collect do |contribution|
93
+ contribution.attributes.to_hash
94
+ end
95
+
96
+ instantiate_collection(result)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,173 @@
1
+ module GovKit
2
+ module OpenCongress
3
+ class Bill < OpenCongressObject
4
+ attr_accessor :bill_type, :id, :introduced, :last_speech, :last_vote_date, :last_vote_roll, :last_vote_where, :last_action, :number, :plain_language_summary, :session, :sponsor, :co_sponsors, :title_full_common, :status, :most_recent_actions, :bill_titles, :recent_blogs, :recent_news, :ident
5
+
6
+ def initialize(params)
7
+ params.each do |key, value|
8
+ instance_variable_set("@#{key}", value) if Bill.instance_methods.include? key
9
+ end
10
+ end
11
+
12
+ def ident
13
+ "#{session}-#{bill_type}#{number}"
14
+ end
15
+
16
+ def self.find(params)
17
+
18
+ url = construct_url("bills", params)
19
+
20
+ if (result = make_call(url))
21
+ parse_results(result)
22
+ else
23
+ nil
24
+ end
25
+
26
+ end
27
+
28
+ def self.most_blogged_bills_this_week
29
+ url = construct_url("most_blogged_bills_this_week", {})
30
+ if (result = make_call(url))
31
+ bills = parse_results(result)
32
+ return bills
33
+ else
34
+ nil
35
+ end
36
+ end
37
+
38
+ def self.bills_in_the_news_this_week
39
+ url = construct_url("bills_in_the_news_this_week", {})
40
+ if (result = make_call(url))
41
+ bills = parse_results(result)
42
+ return bills
43
+ else
44
+ nil
45
+ end
46
+ end
47
+
48
+ def self.most_tracked_bills_this_week
49
+ url = construct_url("most_tracked_bills_this_week", {})
50
+ if (result = make_call(url))
51
+ bills = parse_results(result)
52
+ return bills
53
+ else
54
+ nil
55
+ end
56
+ end
57
+
58
+ def self.most_supported_bills_this_week
59
+ url = construct_url("most_supported_bills_this_week", {})
60
+ if (result = make_call(url))
61
+ bills = parse_results(result)
62
+ return bills
63
+ else
64
+ nil
65
+ end
66
+ end
67
+
68
+ def self.most_opposed_bills_this_week
69
+ url = construct_url("most_opposed_bills_this_week", {})
70
+ if (result = make_call(url))
71
+ bills = parse_results(result)
72
+ return bills
73
+ else
74
+ nil
75
+ end
76
+ end
77
+
78
+ def self.find_by_query(q)
79
+ url = Bill.construct_url("bills_by_query", {:q => q})
80
+
81
+ if (result = make_call(url))
82
+ bills = parse_results(result)
83
+ else
84
+ nil
85
+ end
86
+ end
87
+
88
+ def self.find_by_idents(idents)
89
+ q = []
90
+ if idents.class.to_s == "Array"
91
+ q = idents
92
+ else
93
+ q = idents.split(',')
94
+ end
95
+
96
+ url = Bill.construct_url("bills_by_ident", {:ident => q.join(',')})
97
+
98
+ if (result = make_call(url))
99
+ bills = parse_results(result)
100
+ else
101
+ nil
102
+ end
103
+ end
104
+
105
+ def opencongress_users_supporting_bill_are_also
106
+ url = Bill.construct_url("opencongress_users_supporting_bill_are_also/#{ident}", {})
107
+ if (result = Bill.make_call(url))
108
+ bills = Bill.parse_supporting_results(result)
109
+ return bills
110
+ else
111
+ nil
112
+ end
113
+ end
114
+
115
+ def opencongress_users_opposing_bill_are_also
116
+ url = Bill.construct_url("opencongress_users_opposing_bill_are_also/#{ident}", {})
117
+ if (result = Bill.make_call(url))
118
+ bills = Bill.parse_supporting_results(result)
119
+ return bills
120
+ else
121
+ nil
122
+ end
123
+ end
124
+
125
+ def self.parse_results(result)
126
+
127
+ bills = []
128
+ result["bills"].each do |bill|
129
+
130
+ these_recent_blogs = bill["recent_blogs"]
131
+ blogs = []
132
+
133
+ if these_recent_blogs
134
+ these_recent_blogs.each do |trb|
135
+ blogs << BlogPost.new(trb)
136
+ end
137
+ end
138
+
139
+ bill["recent_blogs"] = blogs
140
+
141
+
142
+ these_recent_news = bill["recent_news"]
143
+ news = []
144
+ if these_recent_news
145
+ these_recent_news.each do |trb|
146
+ news << NewsPost.new(trb)
147
+ end
148
+ end
149
+
150
+ bill["recent_news"] = news
151
+
152
+ these_co_sponsors = bill["co_sponsors"]
153
+ co_sponsors = []
154
+ if these_co_sponsors
155
+ these_co_sponsors.each do |tcs|
156
+ co_sponsors << Person.new(tcs)
157
+ end
158
+ end
159
+
160
+ bill["co_sponsors"] = co_sponsors
161
+
162
+
163
+ bill["sponsor"] = Person.new(bill["sponsor"]) if bill["sponsor"]
164
+
165
+
166
+ bills << Bill.new(bill)
167
+ end
168
+ bills
169
+ end
170
+
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,19 @@
1
+ module GovKit
2
+ module OpenCongress
3
+
4
+ class BlogPost < OpenCongressObject
5
+
6
+ attr_accessor :title, :date, :url, :source_url, :excerpt, :source, :average_rating
7
+
8
+
9
+ def initialize(params)
10
+ params.each do |key, value|
11
+ instance_variable_set("@#{key}", value) if BlogPost.instance_methods.include? key
12
+ end
13
+ end
14
+
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ module GovKit
2
+ module OpenCongress
3
+
4
+ class NewsPost < OpenCongressObject
5
+
6
+ attr_accessor :title, :date, :url, :source_url, :excerpt, :source, :average_rating
7
+
8
+
9
+ def initialize(params)
10
+ params.each do |key, value|
11
+ instance_variable_set("@#{key}", value) if NewsPost.instance_methods.include? key
12
+ end
13
+ end
14
+
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,143 @@
1
+ module GovKit
2
+ module OpenCongress
3
+
4
+ class Person < OpenCongressObject
5
+
6
+ attr_accessor :firstname, :lastname, :bioguideid, :birthday, :district, :email, :gender, :id, :metavid_id, :middlename,
7
+ :name, :nickname, :osid, :party, :religion, :state, :title, :unaccented_name, :url, :user_approval,
8
+ :youtube_id, :oc_user_comments, :oc_users_tracking, :abstains_percentage, :with_party_percentage, :recent_news,
9
+ :recent_blogs, :person_stats
10
+
11
+ def initialize(params)
12
+ params.each do |key, value|
13
+ instance_variable_set("@#{key}", value) if Person.instance_methods.include? key
14
+ end
15
+ end
16
+
17
+ def self.find(params)
18
+
19
+ url = construct_url("people", params)
20
+ if (result = make_call(url))
21
+ people = parse_results(result)
22
+ return people
23
+ else
24
+ nil
25
+ end
26
+
27
+ end
28
+
29
+ def self.compare(person1, person2)
30
+ url = "#{GovKit::configuration.opencongress_base_url}person/compare.json?person1=#{person1.id}&person2=#{person2.id}"
31
+ if (result = make_call(url))
32
+ comparison = VotingComparison.new(result["comparison"])
33
+ else
34
+ nil
35
+ end
36
+
37
+ end
38
+
39
+ def self.senators_most_in_the_news_this_week
40
+
41
+ url = construct_url("senators_most_in_the_news_this_week", {})
42
+ if (result = make_call(url))
43
+ people = parse_results(result)
44
+ return people
45
+ else
46
+ nil
47
+ end
48
+
49
+ end
50
+
51
+ def self.representatives_most_in_the_news_this_week
52
+
53
+ url = construct_url("representatives_most_in_the_news_this_week", {})
54
+ if (result = make_call(url))
55
+ people = parse_results(result)
56
+ return people
57
+ else
58
+ nil
59
+ end
60
+
61
+ end
62
+
63
+ def self.most_blogged_senators_this_week
64
+
65
+ url = construct_url("most_blogged_senators_this_week", {})
66
+ if (result = make_call(url))
67
+ people = parse_results(result)
68
+ return people
69
+ else
70
+ nil
71
+ end
72
+
73
+ end
74
+
75
+ def self.most_blogged_representatives_this_week
76
+
77
+ url = construct_url("most_blogged_representatives_this_week", {})
78
+ if (result = make_call(url))
79
+ people = parse_results(result)
80
+ return people
81
+ else
82
+ nil
83
+ end
84
+
85
+ end
86
+
87
+ def opencongress_users_supporting_person_are_also
88
+ url = Person.construct_url("opencongress_users_supporting_person_are_also/#{id}", {})
89
+ if (result = Person.make_call(url))
90
+ people = Person.parse_supporting_results(result)
91
+ return people
92
+ else
93
+ nil
94
+ end
95
+ end
96
+
97
+ def opencongress_users_opposing_person_are_also
98
+ url = Person.construct_url("opencongress_users_opposing_person_are_also/#{id}", {})
99
+ if (result = Person.make_call(url))
100
+ people = Person.parse_supporting_results(result)
101
+ return people
102
+ else
103
+ nil
104
+ end
105
+ end
106
+
107
+ def self.parse_results(result)
108
+
109
+ people = []
110
+ result["people"].each do |person|
111
+
112
+ these_recent_blogs = person["recent_blogs"]
113
+ blogs = []
114
+ these_recent_blogs.each do |trb|
115
+ blogs << BlogPost.new(trb)
116
+ end
117
+
118
+ person["recent_blogs"] = blogs
119
+
120
+
121
+ these_recent_news = person["recent_news"]
122
+ news = []
123
+ these_recent_news.each do |trb|
124
+ news << NewsPost.new(trb)
125
+ end
126
+
127
+ person["person_stats"] = PersonStat.new(person["person_stats"]) if person["person_stats"]
128
+
129
+ person["recent_news"] = news
130
+
131
+ people << Person.new(person)
132
+ end
133
+
134
+ people
135
+
136
+ end
137
+
138
+
139
+ end
140
+
141
+
142
+ end
143
+ end