openc_bot 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.travis.yml +8 -0
  4. data/CHANGELOG.md +2 -0
  5. data/Gemfile +8 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +253 -0
  8. data/Rakefile +14 -0
  9. data/bin/openc_bot +13 -0
  10. data/create_bot.sh +30 -0
  11. data/create_company_bot.sh +16 -0
  12. data/create_simple_licence_bot.sh +31 -0
  13. data/db/.gitkeep +0 -0
  14. data/examples/basic/.gitignore +3 -0
  15. data/examples/basic/Gemfile +7 -0
  16. data/examples/basic/config.yml +21 -0
  17. data/examples/basic/lib/basic.rb +88 -0
  18. data/examples/basic_with_proxy/Gemfile +7 -0
  19. data/examples/basic_with_proxy/config.yml +21 -0
  20. data/examples/basic_with_proxy/lib/basic_with_proxy.rb +103 -0
  21. data/examples/bot_with_simple_iterator/Gemfile +6 -0
  22. data/examples/bot_with_simple_iterator/config.yml +21 -0
  23. data/examples/bot_with_simple_iterator/lib/bot_with_simple_iterator.rb +112 -0
  24. data/examples/company_fetchers/basic.rb +49 -0
  25. data/lib/monkey_patches/mechanize.rb +53 -0
  26. data/lib/openc_bot.rb +89 -0
  27. data/lib/openc_bot/bot_data_validator.rb +18 -0
  28. data/lib/openc_bot/company_fetcher_bot.rb +40 -0
  29. data/lib/openc_bot/exceptions.rb +17 -0
  30. data/lib/openc_bot/helpers/_csv.rb +10 -0
  31. data/lib/openc_bot/helpers/alpha_search.rb +73 -0
  32. data/lib/openc_bot/helpers/dates.rb +33 -0
  33. data/lib/openc_bot/helpers/html.rb +8 -0
  34. data/lib/openc_bot/helpers/incremental_search.rb +106 -0
  35. data/lib/openc_bot/helpers/register_methods.rb +205 -0
  36. data/lib/openc_bot/helpers/text.rb +18 -0
  37. data/lib/openc_bot/incrementers.rb +2 -0
  38. data/lib/openc_bot/incrementers/base.rb +214 -0
  39. data/lib/openc_bot/incrementers/common.rb +47 -0
  40. data/lib/openc_bot/tasks.rb +385 -0
  41. data/lib/openc_bot/templates/README.md +35 -0
  42. data/lib/openc_bot/templates/bin/export_data +28 -0
  43. data/lib/openc_bot/templates/bin/fetch_data +23 -0
  44. data/lib/openc_bot/templates/bin/verify_data +1 -0
  45. data/lib/openc_bot/templates/config.yml +21 -0
  46. data/lib/openc_bot/templates/lib/bot.rb +43 -0
  47. data/lib/openc_bot/templates/lib/company_fetcher_bot.rb +95 -0
  48. data/lib/openc_bot/templates/lib/simple_bot.rb +67 -0
  49. data/lib/openc_bot/templates/spec/bot_spec.rb +11 -0
  50. data/lib/openc_bot/templates/spec/simple_bot_spec.rb +11 -0
  51. data/lib/openc_bot/templates/spec/spec_helper.rb +13 -0
  52. data/lib/openc_bot/version.rb +3 -0
  53. data/lib/simple_openc_bot.rb +289 -0
  54. data/openc_bot.gemspec +35 -0
  55. data/schemas/company-schema.json +112 -0
  56. data/schemas/includes/address.json +23 -0
  57. data/schemas/includes/base-statement.json +27 -0
  58. data/schemas/includes/company.json +14 -0
  59. data/schemas/includes/filing.json +20 -0
  60. data/schemas/includes/license-data.json +27 -0
  61. data/schemas/includes/officer.json +14 -0
  62. data/schemas/includes/previous_name.json +11 -0
  63. data/schemas/includes/share-parcel-data.json +67 -0
  64. data/schemas/includes/share-parcel.json +60 -0
  65. data/schemas/includes/subsidiary-relationship-data.json +52 -0
  66. data/schemas/includes/total-shares.json +10 -0
  67. data/schemas/licence-schema.json +21 -0
  68. data/schemas/share-parcel-schema.json +21 -0
  69. data/schemas/subsidiary-relationship-schema.json +19 -0
  70. data/spec/dummy_classes/foo_bot.rb +4 -0
  71. data/spec/lib/bot_data_validator_spec.rb +69 -0
  72. data/spec/lib/company_fetcher_bot_spec.rb +93 -0
  73. data/spec/lib/exceptions_spec.rb +25 -0
  74. data/spec/lib/helpers/alpha_search_spec.rb +173 -0
  75. data/spec/lib/helpers/dates_spec.rb +65 -0
  76. data/spec/lib/helpers/incremental_search_spec.rb +471 -0
  77. data/spec/lib/helpers/register_methods_spec.rb +558 -0
  78. data/spec/lib/helpers/text_spec.rb +50 -0
  79. data/spec/lib/openc_bot/db/.gitkeep +0 -0
  80. data/spec/lib/openc_bot/incrementers/common_spec.rb +83 -0
  81. data/spec/lib/openc_bot_spec.rb +116 -0
  82. data/spec/schemas/company-schema_spec.rb +676 -0
  83. data/spec/simple_openc_bot_spec.rb +302 -0
  84. data/spec/spec_helper.rb +19 -0
  85. metadata +300 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "TotalShares",
3
+ "description": "The total number of shares a company has issued",
4
+ "type": "object",
5
+ "properties": { "number": { "type": "integer" },
6
+ "share_class": { "type": "string", "minLength": 1 }
7
+ },
8
+ "required": [ "number" ]
9
+ }
10
+
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "Licence Schema",
4
+ "type": "object",
5
+ "allOf" : [
6
+ // The following is a basic statement with sample_date, etc
7
+ { "$ref": "includes/base-statement.json" },
8
+ // And this overrides it to provide data-type-specific information
9
+ {
10
+ "properties": {
11
+ "data": {
12
+ "items": {
13
+ "allOf": [
14
+ { "$ref": "includes/license-data.json" }
15
+ ]
16
+ }
17
+ }
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "Share Parcel Schema",
4
+ "type": "object",
5
+ "allOf" : [
6
+ // The following is a basic statement with sample_date, etc
7
+ { "$ref": "includes/base-statement.json" },
8
+ // And this overrides it to provide data-type-specific information
9
+ {
10
+ "properties": {
11
+ "data": {
12
+ "items": {
13
+ "allOf": [
14
+ { "$ref": "includes/share-parcel-data.json" }
15
+ ]
16
+ }
17
+ }
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-04/schema#",
3
+ "title": "Subsidiary Relationship Schema",
4
+ "type": "object",
5
+ "allOf" : [
6
+ { "$ref": "includes/base-statement.json" },
7
+ {
8
+ "properties": {
9
+ "data": {
10
+ "items": {
11
+ "allOf": [
12
+ { "$ref": "includes/subsidiary-relationship-data.json" }
13
+ ]
14
+ }
15
+ }
16
+ }
17
+ }
18
+ ]
19
+ }
@@ -0,0 +1,4 @@
1
+ module FooBot
2
+ extend OpencBot
3
+ # extend self
4
+ end
@@ -0,0 +1,69 @@
1
+ # encoding: UTF-8
2
+ require_relative '../spec_helper'
3
+ require 'openc_bot'
4
+
5
+ describe OpencBot::BotDataValidator do
6
+
7
+ describe '#validate' do
8
+ before do
9
+ @valid_data =
10
+ { :company => {:name => "CENTRAL BANK", :identifier => "rssd/546544", :jurisdiction => "IA"},
11
+ :data => [{ :data_type => :subsidiary_relationship,
12
+ :properties => {:foo => 'bar'}
13
+ },
14
+ { :data_type => :subsidiary_relationship,
15
+ :properties => { :foo => 'baz' }
16
+ }
17
+ ],
18
+ :source_url => "http://www.ffiec.gov/nicpubweb/nicweb/OrgHierarchySearchForm.aspx?parID_RSSD=546544&parDT_END=99991231",
19
+ :reporting_date => "2013-01-18 12:52:20"
20
+ }
21
+
22
+
23
+ end
24
+ it 'should return true if data is valid' do
25
+ OpencBot::BotDataValidator.validate(@valid_data).should be_true
26
+ end
27
+
28
+ it 'should return false if data is not a hash' do
29
+ OpencBot::BotDataValidator.validate(nil).should be_false
30
+ OpencBot::BotDataValidator.validate('foo').should be_false
31
+ OpencBot::BotDataValidator.validate(['foo']).should be_false
32
+ end
33
+
34
+ it 'should return false if company_data is blank' do
35
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:company => nil)).should be_false
36
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:company => ' ')).should be_false
37
+ end
38
+
39
+ it 'should return false if company_data is missing name' do
40
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:company => {:name => nil})).should be_false
41
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:company => {:name => ' '})).should be_false
42
+ end
43
+
44
+ it 'should return false if source_url is blank' do
45
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:source_url => nil)).should be_false
46
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:source_url => ' ')).should be_false
47
+ end
48
+
49
+ it 'should return false if data is empty' do
50
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:data => nil)).should be_false
51
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:data => [])).should be_false
52
+ end
53
+
54
+ it 'should return false if data is missing data_type' do
55
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:data => [{:data_type => nil,
56
+ :properties => {:foo => 'bar'}}])).should be_false
57
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:data => [{:data_type => ' ',
58
+ :properties => {:foo => 'bar'}}])).should be_false
59
+ end
60
+
61
+ it 'should return false if properties is blank' do
62
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:data => [{:data_type => :subsidiary_relationship,
63
+ :properties => {}}])).should be_false
64
+ OpencBot::BotDataValidator.validate(@valid_data.merge(:data => [{:data_type => :subsidiary_relationship,
65
+ :properties => nil}])).should be_false
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,93 @@
1
+ # encoding: UTF-8
2
+ require_relative '../spec_helper'
3
+ require 'openc_bot'
4
+ require 'openc_bot/company_fetcher_bot'
5
+
6
+ module TestCompaniesFetcher
7
+ extend OpencBot::CompanyFetcherBot
8
+ end
9
+
10
+ module UsXxCompaniesFetcher
11
+ extend OpencBot::CompanyFetcherBot
12
+ end
13
+
14
+ describe "A module that extends CompanyFetcherBot" do
15
+
16
+ before do
17
+ @dummy_connection = double('database_connection', :save_data => nil)
18
+ TestCompaniesFetcher.stub(:sqlite_magic_connection).and_return(@dummy_connection)
19
+ end
20
+
21
+ it "should include OpencBot methods" do
22
+ TestCompaniesFetcher.should respond_to(:save_run_report)
23
+ end
24
+
25
+ it "should include IncrementalHelper methods" do
26
+ TestCompaniesFetcher.should respond_to(:incremental_search)
27
+ end
28
+
29
+ it "should include AlphaHelper methods" do
30
+ TestCompaniesFetcher.should respond_to(:letters_and_numbers)
31
+ end
32
+
33
+ it "should set primary_key_name to :company_number" do
34
+ TestCompaniesFetcher.primary_key_name.should == :company_number
35
+ end
36
+
37
+ describe "#fetch_datum for company_number" do
38
+ before do
39
+ TestCompaniesFetcher.stub(:fetch_registry_page)
40
+ end
41
+
42
+ it "should #fetch_registry_page for company_numbers" do
43
+ TestCompaniesFetcher.should_receive(:fetch_registry_page).with('76543')
44
+ TestCompaniesFetcher.fetch_datum('76543')
45
+ end
46
+
47
+ it "should stored result of #fetch_registry_page in hash keyed to :company_page" do
48
+ TestCompaniesFetcher.stub(:fetch_registry_page).and_return(:registry_page_html)
49
+ TestCompaniesFetcher.fetch_datum('76543').should == {:company_page => :registry_page_html}
50
+ end
51
+ end
52
+
53
+ describe "#schema_name" do
54
+ context 'and no SCHEMA_NAME constant' do
55
+ it "should return 'company-schema'" do
56
+ TestCompaniesFetcher.schema_name.should == 'company-schema'
57
+ end
58
+ end
59
+
60
+ context 'and SCHEMA_NAME constant set' do
61
+ it "should return SCHEMA_NAME" do
62
+ stub_const("TestCompaniesFetcher::SCHEMA_NAME", 'foo-schema')
63
+ TestCompaniesFetcher.schema_name.should == 'foo-schema'
64
+ end
65
+ end
66
+ end
67
+
68
+ describe "#inferred_jurisdiction_code" do
69
+ it "should return jurisdiction_code inferred from class_name" do
70
+ UsXxCompaniesFetcher.inferred_jurisdiction_code.should == 'us_xx'
71
+ end
72
+
73
+ it "should return nil if jurisdiction_code not correct format" do
74
+ TestCompaniesFetcher.inferred_jurisdiction_code.should be_nil
75
+ end
76
+ end
77
+
78
+ describe "#save_entity" do
79
+ before do
80
+ TestCompaniesFetcher.stub(:inferred_jurisdiction_code).and_return('ab_cd')
81
+ end
82
+
83
+ it "should save_entity with inferred_jurisdiction_code" do
84
+ TestCompaniesFetcher.should_receive(:prepare_and_save_data).with(:name => 'Foo Corp', :company_number => '12345', :jurisdiction_code => 'ab_cd')
85
+ TestCompaniesFetcher.save_entity(:name => 'Foo Corp', :company_number => '12345')
86
+ end
87
+
88
+ it "should save_entity with given jurisdiction_code" do
89
+ TestCompaniesFetcher.should_receive(:prepare_and_save_data).with(:name => 'Foo Corp', :company_number => '12345', :jurisdiction_code => 'xx')
90
+ TestCompaniesFetcher.save_entity(:name => 'Foo Corp', :company_number => '12345', :jurisdiction_code => 'xx')
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: UTF-8
2
+ require_relative '../spec_helper'
3
+ require 'openc_bot'
4
+
5
+ describe 'OpencBot exceptions' do
6
+
7
+ describe OpencBot::OpencBotError do
8
+
9
+ it 'should have StandardError as superclass' do
10
+ OpencBot::OpencBotError.superclass.should == StandardError
11
+ end
12
+ end
13
+
14
+ describe OpencBot::RecordInvalid do
15
+
16
+ it 'should have OpencBotError as superclass' do
17
+ OpencBot::RecordInvalid.superclass.should == OpencBot::OpencBotError
18
+ end
19
+
20
+ it "should have set validation_errors accessor on instantiation" do
21
+ error = OpencBot::RecordInvalid.new(:some_validation_error)
22
+ error.validation_errors.should == :some_validation_error
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,173 @@
1
+ # encoding: UTF-8
2
+ require_relative '../../spec_helper'
3
+ require 'openc_bot'
4
+ require 'openc_bot/helpers/alpha_search'
5
+
6
+ module ModuleThatIncludesAlphaSearch
7
+ extend OpencBot
8
+ extend OpencBot::Helpers::AlphaSearch
9
+ end
10
+
11
+ describe 'a module that includes AlphaSearch' do
12
+
13
+ before do
14
+ ModuleThatIncludesAlphaSearch.stub(:sqlite_magic_connection).
15
+ and_return(test_database_connection)
16
+ end
17
+
18
+ after do
19
+ remove_test_database
20
+ end
21
+
22
+ it "should include register_methods" do
23
+ ModuleThatIncludesAlphaSearch.should respond_to(:registry_url)
24
+ end
25
+
26
+ describe "#letters_and_numbers" do
27
+ it "should return an array of all letters and numbers" do
28
+ ModuleThatIncludesAlphaSearch.letters_and_numbers.should == ('A'..'Z').to_a + ('0'..'9').to_a
29
+ end
30
+ end
31
+
32
+ describe "numbers_of_chars_in_search" do
33
+ context 'and no NUMBER_OF_CHARS_IN_SEARCH constant' do
34
+ it "should return 1" do
35
+ ModuleThatIncludesAlphaSearch.numbers_of_chars_in_search.should == 1
36
+ end
37
+ end
38
+
39
+ context 'and has NUMBER_OF_CHARS_IN_SEARCH constant' do
40
+ it "should return NUMBER_OF_CHARS_IN_SEARCH" do
41
+ stub_const("ModuleThatIncludesAlphaSearch::NUMBER_OF_CHARS_IN_SEARCH", 4)
42
+ ModuleThatIncludesAlphaSearch.numbers_of_chars_in_search.should == ModuleThatIncludesAlphaSearch::NUMBER_OF_CHARS_IN_SEARCH
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "#alpha_terms" do
48
+ before do
49
+ @letters_and_numbers = ['A','B','1','2']
50
+ ModuleThatIncludesAlphaSearch.stub(:letters_and_numbers).and_return(@letters_and_numbers)
51
+ end
52
+
53
+ it "should return array of letters_and_numbers based on numbers_of_chars_in_search" do
54
+ ModuleThatIncludesAlphaSearch.alpha_terms.should == @letters_and_numbers
55
+ ModuleThatIncludesAlphaSearch.should_receive(:numbers_of_chars_in_search).and_return(2)
56
+ ModuleThatIncludesAlphaSearch.alpha_terms.
57
+ should == @letters_and_numbers.repeated_permutation(2).collect(&:join)
58
+ end
59
+
60
+ context "and starting character given" do
61
+ it "should start array from given character" do
62
+ ModuleThatIncludesAlphaSearch.alpha_terms('B').should == @letters_and_numbers[1..-1]
63
+ ModuleThatIncludesAlphaSearch.stub(:numbers_of_chars_in_search).and_return(2)
64
+ ModuleThatIncludesAlphaSearch.alpha_terms('1B').
65
+ should == ["1B", "11", "12", "2A", "2B", "21", "22"]
66
+ end
67
+ it "should start array from beginning if no such character" do
68
+ ModuleThatIncludesAlphaSearch.alpha_terms('X').should == @letters_and_numbers
69
+ ModuleThatIncludesAlphaSearch.stub(:numbers_of_chars_in_search).and_return(2)
70
+ ModuleThatIncludesAlphaSearch.alpha_terms('C').
71
+ should == @letters_and_numbers.repeated_permutation(2).collect(&:join)
72
+ end
73
+ end
74
+ end
75
+
76
+ describe "each_search_term" do
77
+ before do
78
+ ModuleThatIncludesAlphaSearch.should_receive(:alpha_terms).with('B').and_return(['C','D'])
79
+ end
80
+
81
+ it "should iterate through alpha_terms and yield them" do
82
+ yielded_data = []
83
+ ModuleThatIncludesAlphaSearch.each_search_term('B') { |t| yielded_data << "#{t}#{t}" }
84
+ yielded_data.should == ['CC','DD']
85
+ end
86
+
87
+ context "and no block given" do
88
+ it "should return alpha_terms" do
89
+ ModuleThatIncludesAlphaSearch.each_search_term('B').should == ['C','D']
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#fetch_data_via_alpha_search' do
95
+ before do
96
+ @alpha_terms = ['A1','B2','XX','YY']
97
+ ModuleThatIncludesAlphaSearch.stub(:create_new_company)
98
+ ModuleThatIncludesAlphaSearch.stub(:save_entity)
99
+ ModuleThatIncludesAlphaSearch.stub(:alpha_terms).and_return(@alpha_terms)
100
+ ModuleThatIncludesAlphaSearch.stub(:search_for_entities_for_term).and_yield(nil)
101
+ end
102
+
103
+ it "should search_for_entities_for_term for each term in alpha_terms" do
104
+ @alpha_terms.each do |term|
105
+ ModuleThatIncludesAlphaSearch.should_receive(:search_for_entities_for_term).with(term, anything).and_yield(nil)
106
+ end
107
+ ModuleThatIncludesAlphaSearch.fetch_data_via_alpha_search
108
+ end
109
+
110
+ it "should process entity data yielded by search_for_entities_for_term" do
111
+ @alpha_terms.each do |term|
112
+ ModuleThatIncludesAlphaSearch.stub(:search_for_entities_for_term).with(term, anything).and_yield(:datum_1).and_yield(:datum_2)
113
+ end
114
+ ModuleThatIncludesAlphaSearch.should_receive(:save_entity).with(:datum_1)
115
+ ModuleThatIncludesAlphaSearch.should_receive(:save_entity).with(:datum_2)
116
+
117
+ ModuleThatIncludesAlphaSearch.fetch_data_via_alpha_search
118
+ end
119
+
120
+ it "should start from saved starting term" do
121
+ ModuleThatIncludesAlphaSearch.save_var('starting_term', 'B2')
122
+ ModuleThatIncludesAlphaSearch.should_receive(:alpha_terms).with('B2').and_return(@alpha_terms)
123
+
124
+ ModuleThatIncludesAlphaSearch.fetch_data_via_alpha_search
125
+ end
126
+
127
+ it "should pass options to search_for_entities_for_term" do
128
+ ModuleThatIncludesAlphaSearch.should_receive(:search_for_entities_for_term).with(anything, :foo => 'bar').and_yield(nil)
129
+
130
+ ModuleThatIncludesAlphaSearch.fetch_data_via_alpha_search(:foo => 'bar')
131
+ end
132
+
133
+ context "and explicit starting_term passed in as option" do
134
+ it "should start from given starting term" do
135
+ ModuleThatIncludesAlphaSearch.save_var('starting_term', 'B2')
136
+ ModuleThatIncludesAlphaSearch.should_receive(:alpha_terms).with('XX').and_return(@alpha_terms)
137
+
138
+ ModuleThatIncludesAlphaSearch.fetch_data_via_alpha_search(:starting_term => 'XX')
139
+ end
140
+ end
141
+
142
+ context "and search gets to end of alpha_terms" do
143
+ it "should flush starting_term record" do
144
+ ModuleThatIncludesAlphaSearch.save_var('starting_term', 'B2')
145
+
146
+ ModuleThatIncludesAlphaSearch.fetch_data_via_alpha_search
147
+ ModuleThatIncludesAlphaSearch.get_var('starting_term').should be_nil
148
+ end
149
+ end
150
+
151
+ context "and search finishes before getting to end of alpha_terms" do
152
+ it "should store term where it was working on where there was problem" do
153
+ ModuleThatIncludesAlphaSearch.should_receive(:search_for_entities_for_term).
154
+ with(@alpha_terms.first, anything).and_yield(nil)
155
+
156
+ ModuleThatIncludesAlphaSearch.should_receive(:search_for_entities_for_term).
157
+ with(@alpha_terms[1], anything).and_raise('Something has gone wrong')
158
+
159
+ lambda { ModuleThatIncludesAlphaSearch.fetch_data_via_alpha_search }.should raise_error
160
+ ModuleThatIncludesAlphaSearch.get_var('starting_term').should == @alpha_terms[1]
161
+ end
162
+ end
163
+ end
164
+
165
+ describe "#search_for_entities_for_term" do
166
+
167
+ it "should raise exception" do
168
+ lambda { ModuleThatIncludesAlphaSearch.search_for_entities_for_term('foo') }.should raise_error(/method has not been implemented/)
169
+ end
170
+
171
+ end
172
+
173
+ end