cloudfactory 0.4.4 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -61,7 +61,7 @@ module Cf
61
61
  # Checking Worker
62
62
  worker = station['station']['worker']
63
63
  if worker.class != Hash
64
- errors << "Worker is missing in Block station #{i+1}!"
64
+ errors << "Worker is missing in Block station #{i+1}!"
65
65
  return errors
66
66
  elsif worker.class == Hash
67
67
  # Checking Worker type
@@ -72,14 +72,14 @@ module Cf
72
72
  if worker_type.nil?
73
73
  errors << "Worker Type is missing!"
74
74
  else
75
- if worker_type != "human"
75
+ if worker_type != "human"
76
76
  errors << "Worker type is invalid in Block station #{i+1}!" if worker_type.split("_").last != "robot"
77
77
  if worker_type.split("_").last == "robot"
78
78
  settings = worker['settings']
79
79
  errors << "Settings for the robot worker is missing in Block station #{i+1}!" if settings.nil?
80
80
  errors << "Settings for the robot worker is invalid in Block station #{i+1}!" if settings.class != Hash
81
81
  end
82
- elsif worker_type == "human"
82
+ elsif worker_type == "human"
83
83
  # Checking number of workers if worker_type == "human"
84
84
  num_workers = worker['num_workers']
85
85
  if num_workers.nil?
@@ -91,7 +91,7 @@ module Cf
91
91
  # Checking reward of workers if worker_type == "human"
92
92
  reward = worker['reward']
93
93
  if reward.nil?
94
- errors << "Reward of workers not specified in Block station #{i+1}!"
94
+ errors << "Reward of workers not specified in Block station #{i+1}!"
95
95
  else
96
96
  errors << "Reward of workers must be greater than 0 in Block station #{i+1}!" if reward < 1
97
97
  end
@@ -106,29 +106,6 @@ module Cf
106
106
  errors << "Stat badge setting is invalid in Block station #{i+1}!" if stat_badge.class != Hash
107
107
  end
108
108
 
109
- # Checking skill_badge
110
- skill_badges = station['station']['worker']['skill_badges']
111
- if !skill_badges.nil?
112
- errors << "Skill badge settings is invalid in Block station #{i+1}!" if skill_badges.class != Array
113
- skill_badges.each_with_index do |badge, badge_index|
114
- badge_title = badge['title']
115
- badge_description = badge['description']
116
- max_badges = badge['max_badges']
117
- badge_test = badge['test']
118
- test_input = badge_test['input'] if badge_test.class == Hash
119
- expected_output = badge_test['expected_output'] if badge_test.class == Hash
120
- errors << "Skill badge title is Missing in Block #{badge_index+1}!" if badge_title.nil?
121
- errors << "Skill badge Description is Missing in Block #{badge_index+1}!" if badge_description.nil?
122
- errors << "Skill badge max_badges must be greater than 100 in Block #{badge_index+1}!" if max_badges < 100 && !max_badges.nil?
123
- errors << "Skill badge Test is Missing in Block #{badge_index+1}!" if badge_test.nil?
124
- errors << "Skill badge Test is Invalid (must be Hash) in Block #{badge_index+1}!" if badge_test.class != Hash && !badge_test.nil?
125
- errors << "Skill badge Test input is Missing in Block #{badge_index+1}!" if test_input.nil? && !badge_test.nil?
126
- errors << "Skill badge Test input is Invalid (must be Hash) in Block #{badge_index+1}!" if test_input.class != Hash && !test_input.nil? && !badge_test.nil?
127
- errors << "Skill badge Test expected_output is Missing in Block #{badge_index+1}!" if expected_output.nil? && !badge_test.nil?
128
- errors << "Skill badge Test expected_output is Invalid (must be an array) in Block #{badge_index+1}!" if expected_output.class != Array && !expected_output.nil? && !badge_test.nil?
129
- end
130
- end
131
-
132
109
  # Checking TaskForm
133
110
  if worker_type == "human"
134
111
  task_form = station['station']['task_form']
@@ -142,9 +119,9 @@ module Cf
142
119
 
143
120
  instruction = custom_task_form['instruction']
144
121
  errors << "Form Instruction is missing in Block station #{i+1}!" if instruction.nil?
145
-
122
+
146
123
  errors << "station#{i+1}.html is missing in folder #{Dir.pwd} !" if !File.exist?("station#{i+1}.html")
147
- #
124
+ #
148
125
  # if File.exist?("#{Dir.pwd}")
149
126
  # station_source = "#{Dir.pwd}/station_#{i+1}"
150
127
  # errors << "station#{i+1} is missing in folder #{Dir.pwd}/station_#{i+1} !" if !File.exist?("station#{i+1}.html")
@@ -176,7 +153,7 @@ module Cf
176
153
  errors << "Option values is required for field_type => #{field_type} in block #{index+1} of Form Field within Station #{i+1} !"
177
154
  elsif !option_values.nil?
178
155
  if option_values.class != Array
179
- errors << "Option values must be an array for field_type => #{field_type} in block #{index+1} of Form Field within Station #{i+1}!"
156
+ errors << "Option values must be an array for field_type => #{field_type} in block #{index+1} of Form Field within Station #{i+1}!"
180
157
  end
181
158
  end
182
159
  end
@@ -10,11 +10,65 @@ module Cf # :nodoc: all
10
10
  end
11
11
  end
12
12
 
13
- desc "production start <run-title>", "creates a production run with input data file at input/<run-title>.csv"
13
+ desc "production start_test <run-title> -g<number-of-goldstandrds-to-be-added>", "creates a production run with given line goldstandards as units"
14
+ method_option :gold_standards_inputs, :type => :string, :aliases => "-g", :desc => "number of goldstandard as input for the run"
15
+ method_option :live, :type => :boolean, :default => false, :desc => "specifies sandbox or live mode"
16
+ method_option :line, :type => :string, :aliases => "-l", :desc => "public line to use to do the production run. the format should be <account_name>/<line-title> e.g. millisami/brandiator"
17
+ def start_test(title=nil)
18
+ line_destination = Dir.pwd
19
+ yaml_source = "#{line_destination}/line.yml"
20
+
21
+ set_target_uri(options[:live])
22
+ set_api_key
23
+ CF.account_name = CF::Account.info['name']
24
+
25
+ if options[:line].present?
26
+ line = CF::Line.find(options[:line])
27
+ if line.nil?
28
+ say("Line named #{options[:line]} does not exist !!!", :red) and exit(1)
29
+ else
30
+ line = Hashie::Mash.new(line)
31
+ line_title = options[:line]
32
+ end
33
+ elsif File.exist?("#{yaml_source}")
34
+ line_yaml_dump = YAML::load(File.read(yaml_source).strip)
35
+ line_title = line_yaml_dump['title'].parameterize
36
+ line = CF::Line.find(line_title)
37
+ if line.nil?
38
+ say("Line named #{options[:line]} does not exist !!!", :red) and exit(1)
39
+ else
40
+ line = Hashie::Mash.new(line)
41
+ end
42
+ else
43
+ say("Looks like you're not in the Line directory or did not provide the line title to use the line", :red) and return
44
+ end
45
+
46
+ if title.nil?
47
+ if line_title =~ /\w\/\w/
48
+ run_title = "#{line_title.split("/").last}-#{Time.new.strftime('%Y%b%d-%H%M%S')}".downcase
49
+ else
50
+ run_title = "#{line_title}-#{Time.new.strftime('%Y%b%d-%H%M%S')}".downcase
51
+ end
52
+ else
53
+ run_title = "#{title.parameterize}-#{Time.new.strftime('%Y%b%d-%H%M%S')}".downcase
54
+ end
55
+
56
+ gold_standard_inputs = options[:gold_standards_inputs]
57
+ say "Creating a test production run with title #{run_title}", :green
58
+ run = CF::TestRun.new(line_title, run_title, gold_standard_inputs)
59
+ if run.errors.blank?
60
+ display_success_run(run)
61
+ else
62
+ say("Error: #{run.errors}", :red)
63
+ end
64
+
65
+ end
66
+
67
+ desc "production start <run-title>", "creates a production run with input data file at input/<run-title>.csv for more option 'cf production help start'"
14
68
  method_option :input_data, :type => :string, :aliases => "-i", :desc => "the name of the input data file"
15
69
  method_option :live, :type => :boolean, :default => false, :desc => "specifies sandbox or live mode"
16
70
  method_option :line, :type => :string, :aliases => "-l", :desc => "public line to use to do the production run. the format should be <account_name>/<line-title> e.g. millisami/brandiator"
17
- def start(title=nil)
71
+ def start(title=nil)
18
72
  line_destination = Dir.pwd
19
73
  yaml_source = "#{line_destination}/line.yml"
20
74
 
@@ -32,7 +86,7 @@ module Cf # :nodoc: all
32
86
  end
33
87
  elsif File.exist?("#{yaml_source}")
34
88
  line_yaml_dump = YAML::load(File.read(yaml_source).strip)
35
- line_title = line_yaml_dump['title'].parameterize
89
+ line_title = line_yaml_dump['title'].parameterize
36
90
  line = CF::Line.find(line_title)
37
91
  if line.nil?
38
92
  say("Line named #{options[:line]} does not exist !!!", :red) and exit(1)
@@ -162,7 +216,7 @@ module Cf # :nodoc: all
162
216
  if resp_runs.has_key?("total_runs") && resp_runs['total_runs']==0
163
217
  say("\nRun list is empty.\n", :yellow) and return
164
218
  end
165
-
219
+
166
220
  if resp_runs['total_pages']
167
221
  say("\nShowing page #{current_page} of #{resp_runs['total_pages']} (Total runs: #{resp_runs['total_runs']})")
168
222
  end
@@ -198,7 +252,7 @@ module Cf # :nodoc: all
198
252
  # if result.status == "resumed"
199
253
  say("Run with title \"#{result['title']}\" is resumed!", :green)
200
254
  # end
201
- end
255
+ end
202
256
 
203
257
  desc "production add_units", "add units to already existing production run"
204
258
  method_option :run_title, :type => :string, :required => true, :aliases => "-t", :desc => "the title of the run to resume"
@@ -223,12 +277,12 @@ module Cf # :nodoc: all
223
277
  if units.nil?
224
278
  say("Error: Invalid File!", :red) and exit(1)
225
279
  else
226
- if units['error'].present?
280
+ if units['error'].present?
227
281
  say("Error: #{units['error']['message']}", :red) and exit(1)
228
282
  end
229
283
  say("\"#{units['successfull']}\"!", :green)
230
284
  end
231
- end
285
+ end
232
286
 
233
287
  desc "production delete", "Deletes created Production Run"
234
288
  method_option :run_title, :type => :string, :required => true, :aliases => "-t", :desc => "the title of the run to resume"
@@ -237,12 +291,12 @@ module Cf # :nodoc: all
237
291
  set_api_key
238
292
  CF.account_name = CF::Account.info['name']
239
293
  run_title = options[:run_title].parameterize
240
-
294
+
241
295
  deleted_run = CF::Run.destroy(run_title)
242
296
  if deleted_run['error'].present?
243
297
  say("Error: #{deleted_run['error']['message']}", :red) and exit(1)
244
298
  end
245
299
  say("Run Deleted Successfully entitled: \"#{deleted_run['title']}\"!", :green)
246
- end
300
+ end
247
301
  end
248
302
  end
@@ -0,0 +1,74 @@
1
+ module CF
2
+ class GoldStandard
3
+ require 'httparty'
4
+ include Client
5
+
6
+ # goldstandard settings
7
+ attr_accessor :settings
8
+
9
+ #GoldStandard name
10
+ attr_accessor :name
11
+
12
+ # the input provided to the goldstandard
13
+ attr_accessor :input
14
+
15
+ # ExpectedOutput of the gold-standard with which the actual output is matched
16
+ attr_accessor :expected_output
17
+
18
+ # assignment_duration is the time period that is allocated for the badge test
19
+ attr_accessor :assignment_duration
20
+
21
+ # Contains error message
22
+ attr_accessor :errors
23
+
24
+
25
+ # ==Initializes a new gold standard
26
+ # ===Usage Example:
27
+ # CF::GoldStandard.new({ :line => line,:name => "easy",:input => [{'image_url' =>"http://onwired.com/images/portfolio/linda-stanley-business-card.jpg"}],:expected_output => [{"first_name" => {"value" => "John"}, "last_name" => {"value" => "Lennon"}, "company" => {"value" => "Sprout"}}]})
28
+ # line.gold_standards gold_standard
29
+
30
+ def initialize(options={})
31
+ @settings = options
32
+ @station = options[:station] if options[:station].present?
33
+ @line = options[:line] if options[:line].nil? ? nil : options[:line]
34
+ if !@line.nil? || @station
35
+ options.delete(:station) if @settings[:station].present?
36
+ options.delete(:line)
37
+ if options[:file] || options["file"]
38
+ options.symbolize_keys!
39
+ file_upload = File.new(options[:file], 'rb')
40
+ if @station
41
+ resp = self.class.post("/lines/#{CF.account_name}/#{@line.title.downcase}/stations/#{@station.index}/gold_standards.json", {:file => file_upload})
42
+ @station.gold_standards = self
43
+ else
44
+ resp = self.class.post("/lines/#{CF.account_name}/#{@line.title.downcase}/gold_standards.json", {:file => file_upload})
45
+ @line.gold_standards = self
46
+ end
47
+ else
48
+ request =
49
+ {
50
+ :body =>
51
+ {
52
+ :api_key => CF.api_key,
53
+ :gold_standard => options
54
+ }
55
+ }
56
+
57
+ if @station
58
+ resp = HTTParty.post("#{CF.api_url}#{CF.api_version}/lines/#{CF.account_name}/#{@line.title.downcase}/stations/#{@station.index}/gold_standards.json",request)
59
+ @station.gold_standards = self
60
+ else
61
+ resp = HTTParty.post("#{CF.api_url}#{CF.api_version}/lines/#{CF.account_name}/#{@line.title.downcase}/gold_standards.json",request)
62
+ @line.gold_standards = self
63
+ end
64
+ self.errors = resp.parsed_response['error']['message'] if resp.code != 200
65
+ end
66
+ end
67
+ end
68
+
69
+ def self.create(*args)
70
+ GoldStandard.new(args.first)
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,67 @@
1
+ Tasks:
2
+
3
+ Help:
4
+ Usage:
5
+ cf [command] help
6
+ Description:
7
+ Shows all help commands
8
+
9
+ Version
10
+ Usage:
11
+ cf version
12
+ Description:
13
+ Shows the current version of cloudfactory gem
14
+
15
+ UserInfo:
16
+ Usage:
17
+ cf whoami
18
+ Description:
19
+ to know what credential you are using
20
+
21
+ Login
22
+ Usage:
23
+ cf login
24
+ Description:
25
+ Setup the cloudfactory credentials
26
+
27
+ Form:
28
+ Usage:
29
+ cf form generate --fields=key:value --labels=LABELS --station=N # generates a custom task form at <line-title>/<form-title>.html and its associated css and js files
30
+ cf form preview -s, --station=N # generates a html file to preview the custom task form
31
+ Description:
32
+ Commands to generate custom task forms. For more info, cf form help
33
+ More:
34
+ cf form help [sub-command] #list all help related to form subcommands
35
+
36
+ Line:
37
+ Usage:
38
+ cf line create # takes the line.yml and creates a new line at http://cloudfactory.com
39
+ cf line delete # delete the current line at http://cloudfactory.com
40
+ cf line details # list the details of the line
41
+ cf line generate LINE-TITLE # generates a line template at <line-title>/line.yml
42
+ cf line list # List your lines
43
+ Description:
44
+ Commands to manage the Lines. For more info, cf line help
45
+ More:
46
+ cf line help [sub-command] #list all help related to line subcommands
47
+
48
+ Production:
49
+ Usage:
50
+ cf production add_units -i, --input-data=INPUT_DATA -t, --run-title=RUN_TITLE # add units to already existing production run
51
+ cf production delete -t, --run-title=RUN_TITLE # Deletes created Production Run
52
+ cf production list # list the production runs
53
+ cf production resume -r, --run-title=RUN_TITLE # resume a paused production run
54
+ cf production start <run-title> # creates a production run with input data file at input/<run-title>.csv
55
+ cf production start_test <run-title> -g<number-of-goldstandrds-to-be-added> # creates a production run with given line goldstandards as units
56
+ Description:
57
+ Commands to create production runs. For more info, cf production help
58
+ More:
59
+ cf production help [sub-command] #list all help related to production subcommands
60
+
61
+ Output:
62
+ Usage:
63
+ cf output <run-title> -t, --run-title=RUN_TITLE
64
+ Description:
65
+ Get the output of run. For more info, cf output help
66
+ More:
67
+ cf output help [sub-command] #list all help related to output subcommands
@@ -3,33 +3,33 @@ module CF
3
3
  include Client
4
4
  require 'httparty'
5
5
  extend ActiveSupport::Concern
6
-
6
+
7
7
  # ID of "Worker" object
8
8
  attr_accessor :id
9
-
9
+
10
10
  # Number of worker, e.g. :number => 3
11
11
  attr_accessor :number
12
-
12
+
13
13
  # Reward for worker, e.g. :reward => 10 (reward unit is in cents)
14
14
  attr_accessor :reward
15
-
15
+
16
16
  # Station attribute for "worker" object
17
17
  attr_accessor :station
18
-
18
+
19
19
  # Stat Badge for "worker" object, e.g. worker = CF::HumanWorker.new({:number => 2, :reward => 20, :stat_badge => {:approval_rating => 40, :assignment_duration => 1800, :abandonment_rate => 30}})
20
20
  attr_accessor :stat_badge
21
-
21
+
22
22
  # Skill Badge for "worker" object
23
23
  # example:
24
- # badge_settings =
24
+ # badge_settings =
25
25
  # {
26
- # :title => 'Football Fanatic',
27
- # :description => "This qualification allows you to perform work at stations which have this badge.",
28
- # :max_badges => 3,
29
- # :test =>
26
+ # :title => 'Football Fanatic',
27
+ # :description => "This qualification allows you to perform work at stations which have this badge.",
28
+ # :max_badges => 3,
29
+ # :test =>
30
30
  # {
31
31
  # :input => {:name => "Lionel Andres Messi", :country => "Argentina"},
32
- # :expected_output =>
32
+ # :expected_output =>
33
33
  # [
34
34
  # {:birthplace => "Rosario, Santa Fe, Argentina",:match_options => {:tolerance => 10, :ignore_case => true }},
35
35
  # {:position => "CF",:match_options => {:tolerance => 1 }},
@@ -39,72 +39,68 @@ module CF
39
39
  # }
40
40
  #
41
41
  # worker = CF::HumanWorker.new({:number => 2, :reward => 20, :skill_badge => badge_settings})
42
- attr_accessor :skill_badges
43
-
42
+ # attr_accessor :skill_badges
43
+
44
44
  # Contains Error messages if any for "worker" object
45
45
  attr_accessor :errors
46
-
46
+
47
47
  # Badge setting for "worker" object
48
48
  attr_accessor :badge
49
-
50
- # ==Initializes a new "worker" object
49
+
50
+ # ==Initializes a new "worker" object
51
51
  # ==Usage of HumanWorker.new(hash):
52
52
  #
53
- # ==In Block DSL way
54
- # line = CF::Line.create("human_worker", "Survey") do |l|
53
+ # ==In Block DSL way
54
+ # line = CF::Line.create("human_worker", "Survey") do |l|
55
55
  # CF::Station.create({:line => l, :type => "work"}) do |s|
56
56
  # CF::HumanWorker.new({:station => s, :number => 1, :reward => 10})
57
57
  # end
58
58
  # end
59
59
  #
60
- # ==In Plain Ruby way
60
+ # ==In Plain Ruby way
61
61
  # line = CF::Line.new("human_worker", "Digitization")
62
62
  # input_format = CF::InputFormat.new({:name => "image_url", :required => true, :valid_type => "url"})
63
63
  # line.input_formats input_format
64
- #
64
+ #
65
65
  # station = CF::Station.new({:type => "work"})
66
66
  # line.stations station
67
- #
67
+ #
68
68
  # worker = CF::HumanWorker.new({:number => 2, :reward => 20})
69
69
  # line.stations.first.worker = worker
70
-
70
+
71
71
  def initialize(options={})
72
- @skill_badges = []
73
72
  @station = options[:station]
74
73
  @number = options[:number].nil? ? 1 : options[:number]
75
74
  @reward = options[:reward]
76
- @badge = options[:skill_badge].nil? ? nil : options[:skill_badge]
77
75
  @stat_badge = options[:stat_badge].nil? ? nil : options[:stat_badge]
78
76
  if @station
79
- if options[:skill_badge].nil? && options[:stat_badge].nil?
80
- request =
77
+ if options[:stat_badge].nil?
78
+ request =
81
79
  {
82
- :body =>
80
+ :body =>
83
81
  {
84
82
  :api_key => CF.api_key,
85
83
  :worker => {:number => @number, :reward => @reward, :type => "HumanWorker"}
86
84
  }
87
85
  }
88
86
  else
89
- request =
87
+ request =
90
88
  {
91
- :body =>
89
+ :body =>
92
90
  {
93
91
  :api_key => CF.api_key,
94
92
  :worker => {:number => @number, :reward => @reward, :type => "HumanWorker"},
95
- :skill_badge => @badge,
96
93
  :stat_badge => options[:stat_badge]
97
94
  }
98
95
  }
99
96
  end
100
97
  resp = HTTParty.post("#{CF.api_url}#{CF.api_version}/lines/#{CF.account_name}/#{@station.line['title'].downcase}/stations/#{@station.index}/workers.json",request)
101
-
98
+
102
99
  self.id = resp.parsed_response['id']
103
100
  self.number = resp.parsed_response['number']
104
101
  self.reward = resp.parsed_response['reward']
105
- self.stat_badge = resp.parsed_response['stat_badge']
106
- @skill_badges << resp.parsed_response['skill_badges']
107
-
102
+ self.stat_badge = resp.parsed_response['stat_badge']
103
+
108
104
  if resp.code != 200
109
105
  self.errors = resp.parsed_response['error']['message']
110
106
  end
@@ -112,19 +108,19 @@ module CF
112
108
  self.station.worker = self
113
109
  end
114
110
  end
115
-
111
+
116
112
  # ==Creation a new "worker" object with Badge
117
113
  # ==Usage Example:
118
- # ==In Plain Ruby way
119
- # badge_settings =
114
+ # ==In Plain Ruby way
115
+ # badge_settings =
120
116
  # {
121
- # :title => 'Football Fanatic',
122
- # :description => "This qualification allows you to perform work at stations which have this badge.",
123
- # :max_badges => 3,
124
- # :test =>
117
+ # :title => 'Football Fanatic',
118
+ # :description => "This qualification allows you to perform work at stations which have this badge.",
119
+ # :max_badges => 3,
120
+ # :test =>
125
121
  # {
126
122
  # :input => {:name => "Lionel Andres Messi", :country => "Argentina"},
127
- # :expected_output =>
123
+ # :expected_output =>
128
124
  # [
129
125
  # {:birthplace => "Rosario, Santa Fe, Argentina",:match_options => {:tolerance => 10, :ignore_case => true }},
130
126
  # {:position => "CF",:match_options => {:tolerance => 1 }},
@@ -135,18 +131,18 @@ module CF
135
131
  # line = CF::Line.new("human_worker", "Digitization")
136
132
  # input_format = CF::InputFormat.new({:name => "image_url", :required => true, :valid_type => "url"})
137
133
  # line.input_formats input_format
138
- #
134
+ #
139
135
  # station = CF::Station.new({:type => "work"})
140
136
  # line.stations station
141
- #
137
+ #
142
138
  # worker = CF::HumanWorker.new({:number => 2, :reward => 20, :skill_badge => skill_badge})
143
139
  # line.stations.first.worker = worker
144
140
  #
145
141
  # line.stations.first.worker.badge = badge_settings
146
142
  def badge=(badge)
147
- request =
143
+ request =
148
144
  {
149
- :body =>
145
+ :body =>
150
146
  {
151
147
  :api_key => CF.api_key,
152
148
  :skill_badge => badge
@@ -156,9 +152,9 @@ module CF
156
152
  self.errors = resp['error']['message'] if resp.code != 200
157
153
  self.skill_badges << resp.parsed_response['skill_badges']
158
154
  end
159
-
155
+
160
156
  def to_s # :nodoc:
161
- "{:id => => #{self.id}, :number => #{self.number}, :reward => #{self.reward}, :stat_badge => #{self.stat_badge}, :skill_badges => #{self.skill_badges}, :errors => #{self.errors}}"
157
+ "{:id => => #{self.id}, :number => #{self.number}, :reward => #{self.reward}, :stat_badge => #{self.stat_badge}, :errors => #{self.errors}}"
162
158
  end
163
159
  end
164
160
  end