govkit 0.0.2 → 0.1.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/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