kempelen 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +22 -0
- data/Gemfile +5 -0
- data/Guardfile +42 -0
- data/LICENSE.txt +21 -0
- data/README.md +59 -0
- data/Rakefile +27 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/examples/approve_assignment.rb +16 -0
- data/examples/create_hit.rb +20 -0
- data/examples/force_expire_hit.rb +16 -0
- data/examples/get_account_balance.rb +16 -0
- data/examples/get_assignments_for_hit.rb +16 -0
- data/examples/get_hit.rb +16 -0
- data/examples/get_reviewable_hits.rb +16 -0
- data/examples/reject_assignment.rb +16 -0
- data/examples/set_hit_as_reviewing.rb +16 -0
- data/kempelen.gemspec +35 -0
- data/lib/kempelen.rb +35 -0
- data/lib/kempelen/API/client.rb +45 -0
- data/lib/kempelen/API/common/answers.rb +34 -0
- data/lib/kempelen/API/common/assignment.rb +37 -0
- data/lib/kempelen/API/common/price.rb +26 -0
- data/lib/kempelen/API/common/question_form_answers.rb +57 -0
- data/lib/kempelen/API/operations/approve_assignment.rb +17 -0
- data/lib/kempelen/API/operations/assignment_operation.rb +36 -0
- data/lib/kempelen/API/operations/base.rb +71 -0
- data/lib/kempelen/API/operations/create_hit.rb +55 -0
- data/lib/kempelen/API/operations/force_expire_hit.rb +25 -0
- data/lib/kempelen/API/operations/get_account_balance.rb +30 -0
- data/lib/kempelen/API/operations/get_assignments_for_hit.rb +42 -0
- data/lib/kempelen/API/operations/get_hit.rb +25 -0
- data/lib/kempelen/API/operations/get_reviewable_hits.rb +64 -0
- data/lib/kempelen/API/operations/hit_operation.rb +23 -0
- data/lib/kempelen/API/operations/parameters.rb +40 -0
- data/lib/kempelen/API/operations/register_hit_type.rb +47 -0
- data/lib/kempelen/API/operations/reject_assignment.rb +17 -0
- data/lib/kempelen/API/operations/set_hit_as_reviewing.rb +34 -0
- data/lib/kempelen/API/responses/account_balance_response.rb +27 -0
- data/lib/kempelen/API/responses/base.rb +32 -0
- data/lib/kempelen/API/responses/empty_response.rb +17 -0
- data/lib/kempelen/API/responses/error_response.rb +32 -0
- data/lib/kempelen/API/responses/get_assignments_response.rb +31 -0
- data/lib/kempelen/API/responses/hit_response.rb +66 -0
- data/lib/kempelen/API/responses/reviewable_hits_response.rb +38 -0
- data/lib/kempelen/version.rb +3 -0
- metadata +260 -0
data/lib/kempelen.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require "kempelen/version"
|
2
|
+
|
3
|
+
require "kempelen/API/client"
|
4
|
+
|
5
|
+
require "kempelen/API/common/price"
|
6
|
+
require "kempelen/API/common/assignment"
|
7
|
+
require "kempelen/API/common/answers"
|
8
|
+
require "kempelen/API/common/question_form_answers"
|
9
|
+
|
10
|
+
require "kempelen/API/responses/base"
|
11
|
+
require "kempelen/API/responses/error_response"
|
12
|
+
require "kempelen/API/responses/empty_response"
|
13
|
+
require "kempelen/API/responses/hit_response"
|
14
|
+
require "kempelen/API/responses/account_balance_response"
|
15
|
+
require "kempelen/API/responses/reviewable_hits_response"
|
16
|
+
require "kempelen/API/responses/get_assignments_response"
|
17
|
+
|
18
|
+
require "kempelen/API/operations/base"
|
19
|
+
require "kempelen/API/operations/parameters"
|
20
|
+
require "kempelen/API/operations/hit_operation"
|
21
|
+
require "kempelen/API/operations/assignment_operation"
|
22
|
+
require "kempelen/API/operations/create_hit"
|
23
|
+
require "kempelen/API/operations/get_hit"
|
24
|
+
require "kempelen/API/operations/get_reviewable_hits"
|
25
|
+
require "kempelen/API/operations/get_account_balance"
|
26
|
+
require "kempelen/API/operations/force_expire_hit"
|
27
|
+
require "kempelen/API/operations/approve_assignment"
|
28
|
+
require "kempelen/API/operations/reject_assignment"
|
29
|
+
require "kempelen/API/operations/get_assignments_for_hit"
|
30
|
+
require "kempelen/API/operations/set_hit_as_reviewing"
|
31
|
+
require "kempelen/API/operations/register_hit_type"
|
32
|
+
|
33
|
+
module Kempelen
|
34
|
+
# Your code goes here...
|
35
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Kempelen
|
2
|
+
module API
|
3
|
+
class Client
|
4
|
+
# Amazon AWS access key.
|
5
|
+
attr_reader :access_key
|
6
|
+
|
7
|
+
# Secret key for AWS access key. Used to sign the requests.
|
8
|
+
attr_reader :secret_key
|
9
|
+
|
10
|
+
# Mechanical Turk environment to make API calls to - defaults to :production.
|
11
|
+
attr_reader :environment
|
12
|
+
|
13
|
+
# URLs for Mechanical Turk APIs, indexed by environment.
|
14
|
+
SERVICE_URLS = {
|
15
|
+
sandbox: 'https://mechanicalturk.sandbox.amazonaws.com'.freeze,
|
16
|
+
production: 'https://mechanicalturk.amazonaws.com'.freeze
|
17
|
+
}
|
18
|
+
|
19
|
+
# Service name of the Mechanical Turk API.
|
20
|
+
SERVICE_NAME = "AWSMechanicalTurkRequester".freeze
|
21
|
+
|
22
|
+
def initialize(access_key, secret_key, environment = :production)
|
23
|
+
@access_key = access_key
|
24
|
+
@secret_key = secret_key
|
25
|
+
|
26
|
+
@environment = environment.to_sym
|
27
|
+
|
28
|
+
raise ArgumentError.new("Unknown environment".freeze) if SERVICE_URLS[@environment].nil?
|
29
|
+
end
|
30
|
+
|
31
|
+
def service_name
|
32
|
+
SERVICE_NAME
|
33
|
+
end
|
34
|
+
|
35
|
+
def service_url
|
36
|
+
SERVICE_URLS[@environment]
|
37
|
+
end
|
38
|
+
|
39
|
+
def perform_request(query_string, response_object)
|
40
|
+
response = HTTParty.get(query_string)
|
41
|
+
response.parsed_response[response_object]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Kempelen
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
class Answer
|
5
|
+
attr_accessor :question_identifier
|
6
|
+
|
7
|
+
def initialize(question_identifier)
|
8
|
+
@question_identifier = question_identifier
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class FreeTextAnswer < Answer
|
13
|
+
attr_accessor :free_text
|
14
|
+
|
15
|
+
def initialize(question_identifier, free_text)
|
16
|
+
super(question_identifier)
|
17
|
+
|
18
|
+
@free_text = free_text
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class MultipleChoiceAnswer < Answer
|
23
|
+
attr_accessor :selection_identifiers
|
24
|
+
attr_accessor :other_selection_text
|
25
|
+
|
26
|
+
def initialize(question_identifier)
|
27
|
+
super(question_identifier)
|
28
|
+
|
29
|
+
@selection_identifiers = []
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Kempelen
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
class Assignment
|
5
|
+
attr_accessor :assignment_id
|
6
|
+
attr_accessor :worker_id
|
7
|
+
attr_accessor :hit_id
|
8
|
+
attr_accessor :status
|
9
|
+
attr_accessor :auto_approval_time
|
10
|
+
attr_accessor :accept_time
|
11
|
+
attr_accessor :submit_time
|
12
|
+
attr_accessor :answer
|
13
|
+
|
14
|
+
def self.create_from_response(response)
|
15
|
+
return nil if response.nil?
|
16
|
+
|
17
|
+
assignment = Assignment.new
|
18
|
+
|
19
|
+
assignment.assignment_id = response["AssignmentId"]
|
20
|
+
assignment.worker_id = response["WorkerId"]
|
21
|
+
assignment.hit_id = response["HITId"]
|
22
|
+
assignment.status = response["AssignmentStatus"]
|
23
|
+
assignment.auto_approval_time = DateTime.parse(response["AutoApprovalTime"]) rescue nil
|
24
|
+
assignment.accept_time = DateTime.parse(response["AcceptTime"]) rescue nil
|
25
|
+
assignment.submit_time = DateTime.parse(response["SubmitTime"]) rescue nil
|
26
|
+
assignment.answer = QuestionFormAnswers.new(response["Answer"])
|
27
|
+
|
28
|
+
assignment
|
29
|
+
end
|
30
|
+
|
31
|
+
def answer_question(question_identifier, index = 0)
|
32
|
+
@answer.get_answer(question_identifier, index)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Kempelen
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
class Price
|
5
|
+
attr_accessor :amount
|
6
|
+
attr_accessor :currency_code
|
7
|
+
|
8
|
+
def initialize(amount = 0.0, currency_code = "USD")
|
9
|
+
@amount = amount
|
10
|
+
@currency_code = currency_code
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.create_from_response(response)
|
14
|
+
return nil if response.nil?
|
15
|
+
|
16
|
+
price = Price.new
|
17
|
+
|
18
|
+
price.amount = response["Amount"].to_f
|
19
|
+
price.currency_code = response["CurrencyCode"]
|
20
|
+
|
21
|
+
price
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'nori'
|
2
|
+
|
3
|
+
module Kempelen
|
4
|
+
module API
|
5
|
+
module Common
|
6
|
+
class QuestionFormAnswers
|
7
|
+
attr_accessor :answers
|
8
|
+
|
9
|
+
def initialize(answer = nil)
|
10
|
+
@answers = []
|
11
|
+
|
12
|
+
load_from_answer(answer) unless answer.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
# <?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
16
|
+
#<QuestionFormAnswers xmlns="http://mechanicalturk.amazonaws.com/AWSMechanicalTurkDataSchemas/2005-10-01/QuestionFormAnswers.xsd">
|
17
|
+
# <Answer>
|
18
|
+
# <QuestionIdentifier>TranscriptionTexts</QuestionIdentifier>
|
19
|
+
# <FreeText>This is the second test.</FreeText>
|
20
|
+
# </Answer>
|
21
|
+
#</QuestionFormAnswers>
|
22
|
+
|
23
|
+
def load_from_answer(answer)
|
24
|
+
parser = Nori.new(:parser => :rexml)
|
25
|
+
parsed_data = parser.parse(answer)
|
26
|
+
answer_list = parsed_data["QuestionFormAnswers"]["Answer"]
|
27
|
+
|
28
|
+
unless answer_list.is_a?(Array)
|
29
|
+
answer_list = [answer_list]
|
30
|
+
end
|
31
|
+
|
32
|
+
answer_list.each do |answer_data|
|
33
|
+
if answer_data.has_key?("FreeText")
|
34
|
+
@answers << FreeTextAnswer.new(answer_data["QuestionIdentifier"].to_s, answer_data["FreeText"].to_s)
|
35
|
+
elsif answer_data.has_key?("SelectionIdentifier") or answer_data.has_key?("OtherSelectionText")
|
36
|
+
new_answer = MultipleChoiceAnswer.new(answer_data["QuestionIdentifier"].to_s)
|
37
|
+
|
38
|
+
selection = answer_data["SelectionIdentifier"]
|
39
|
+
if selection.is_a?(Array)
|
40
|
+
new_answer.selection_identifiers += selection
|
41
|
+
else
|
42
|
+
new_answer.selection_identifiers << selection
|
43
|
+
end
|
44
|
+
new_answer.other_selection_text = answer_data["OtherSelectionText"]
|
45
|
+
|
46
|
+
@answers << new_answer
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_answer(question_identifier, index = 0)
|
52
|
+
@answers.select {|a| a.question_identifier == question_identifier}[index] rescue nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Kempelen
|
2
|
+
module API
|
3
|
+
module Operations
|
4
|
+
class ApproveAssignment < AssignmentOperation
|
5
|
+
AWS_OPERATION_NAME = "ApproveAssignment".freeze
|
6
|
+
AWS_RESPONSE_OBJECT = "ApproveAssignmentResponse".freeze
|
7
|
+
|
8
|
+
def initialize(client, assignment_id, requester_feedback = nil)
|
9
|
+
super(client, assignment_id, requester_feedback)
|
10
|
+
|
11
|
+
@operation_name = AWS_OPERATION_NAME
|
12
|
+
@response_object = AWS_RESPONSE_OBJECT
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Kempelen
|
2
|
+
module API
|
3
|
+
module Operations
|
4
|
+
class AssignmentOperation < Base
|
5
|
+
attr_accessor :assignment_id
|
6
|
+
attr_accessor :requester_feedback
|
7
|
+
attr_accessor :operation_name
|
8
|
+
|
9
|
+
FEEDBACK_MAX_SIZE = 1024
|
10
|
+
|
11
|
+
def initialize(client, assignment_id, requester_feedback = nil)
|
12
|
+
super(client)
|
13
|
+
|
14
|
+
@assignment_id = assignment_id
|
15
|
+
@requester_feedback = requester_feedback
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_parameters
|
19
|
+
@parameters[:operation] = @operation_name
|
20
|
+
@parameters[:assignment_id] = @assignment_id
|
21
|
+
@parameters[:requester_feedback] = @requester_feedback.slice(0, FEEDBACK_MAX_SIZE) unless @requester_feedback.nil?
|
22
|
+
|
23
|
+
super
|
24
|
+
end
|
25
|
+
|
26
|
+
def perform_operation
|
27
|
+
create_request_string
|
28
|
+
|
29
|
+
super
|
30
|
+
|
31
|
+
Kempelen::API::Responses::EmptyResponse.new(@raw_response)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'hmac-sha1'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
module Kempelen
|
7
|
+
module API
|
8
|
+
module Operations
|
9
|
+
class Base
|
10
|
+
attr_accessor :client
|
11
|
+
attr_accessor :parameters
|
12
|
+
attr_accessor :timestamp
|
13
|
+
attr_accessor :response_object
|
14
|
+
attr_accessor :query_string
|
15
|
+
attr_accessor :raw_response
|
16
|
+
|
17
|
+
def initialize(client)
|
18
|
+
@client = client
|
19
|
+
@parameters = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_parameters
|
23
|
+
@parameters[:service] = client.service_name
|
24
|
+
@parameters[:access_key] = client.access_key
|
25
|
+
@parameters[:timestamp] = DateTime.now
|
26
|
+
|
27
|
+
string_to_sign = [@parameters[:service], @parameters[:operation], @parameters[:timestamp]].join
|
28
|
+
@parameters[:signature] = (Base64.encode64 HMAC::SHA1.digest(client.secret_key, string_to_sign)).strip
|
29
|
+
end
|
30
|
+
|
31
|
+
# TODO: Reduce the complexity of this method.
|
32
|
+
def create_request_string
|
33
|
+
create_parameters
|
34
|
+
|
35
|
+
request_str = ""
|
36
|
+
|
37
|
+
@parameters.each do |name, value|
|
38
|
+
aws_name = Kempelen::API::Operations::PARAMETERS[name]
|
39
|
+
if !aws_name.nil?
|
40
|
+
unless value.nil?
|
41
|
+
request_str += "&" unless request_str.empty?
|
42
|
+
request_str += "#{aws_name}=#{CGI.escape(value.to_s)}"
|
43
|
+
end
|
44
|
+
else
|
45
|
+
aws_name = Kempelen::API::Operations::ARRAY_PARAMETERS[name]
|
46
|
+
unless aws_name.nil?
|
47
|
+
value.each_index do |idx|
|
48
|
+
hash_value = value[idx]
|
49
|
+
|
50
|
+
if hash_value.respond_to?(:each)
|
51
|
+
hash_value.each do |k, v|
|
52
|
+
request_str += "&" unless request_str.empty?
|
53
|
+
request_str += "#{aws_name}.#{idx + 1}.#{k}=#{CGI.escape(v.to_s)}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
@query_string = "#{client.service_url}?#{request_str}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def perform_operation
|
65
|
+
@raw_response = @client.perform_request(@query_string, @response_object)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Kempelen
|
2
|
+
module API
|
3
|
+
module Operations
|
4
|
+
class CreateHit < Base
|
5
|
+
attr_accessor :hit_type_id
|
6
|
+
attr_accessor :layout_id
|
7
|
+
attr_accessor :layout_parameters
|
8
|
+
attr_accessor :lifetime_in_seconds
|
9
|
+
attr_accessor :max_assignments
|
10
|
+
attr_accessor :requester_annotation
|
11
|
+
attr_accessor :unique_request_token
|
12
|
+
|
13
|
+
AWS_OPERATION_NAME = "CreateHIT".freeze
|
14
|
+
AWS_RESPONSE_OBJECT = "CreateHITResponse".freeze
|
15
|
+
|
16
|
+
def initialize(client, hit_type_id, lifetime_in_seconds, &block)
|
17
|
+
super(client)
|
18
|
+
|
19
|
+
@hit_type_id = hit_type_id
|
20
|
+
@lifetime_in_seconds = lifetime_in_seconds.to_i
|
21
|
+
@layout_parameters = []
|
22
|
+
@max_assignments = 1
|
23
|
+
@response_object = AWS_RESPONSE_OBJECT
|
24
|
+
|
25
|
+
yield self unless block == nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_layout_parameter(name, value)
|
29
|
+
@layout_parameters << {Name: name, Value: value}
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_parameters
|
33
|
+
@parameters[:operation] = AWS_OPERATION_NAME
|
34
|
+
@parameters[:hit_type_id] = @hit_type_id
|
35
|
+
@parameters[:lifetime_in_seconds] = @lifetime_in_seconds
|
36
|
+
@parameters[:max_assignments] = @max_assignments
|
37
|
+
@parameters[:layout_id] = @layout_id unless @layout_id.nil?
|
38
|
+
@parameters[:layout_parameters] = @layout_parameters unless @layout_parameters.empty?
|
39
|
+
@parameters[:requester_annotation] = @requester_annotation unless @requester_annotation.nil?
|
40
|
+
@parameters[:unique_request_token] = @unique_request_token unless @unique_request_token.nil?
|
41
|
+
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
45
|
+
def perform_operation
|
46
|
+
create_request_string
|
47
|
+
|
48
|
+
super
|
49
|
+
|
50
|
+
Kempelen::API::Responses::HitResponse.new(@raw_response)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Kempelen
|
2
|
+
module API
|
3
|
+
module Operations
|
4
|
+
class ForceExpireHit < HitOperation
|
5
|
+
AWS_OPERATION_NAME = "ForceExpireHIT".freeze
|
6
|
+
AWS_RESPONSE_OBJECT = "ForceExpireHITResponse".freeze
|
7
|
+
|
8
|
+
def initialize(client, hit_id)
|
9
|
+
super(client, hit_id)
|
10
|
+
|
11
|
+
@operation_name = AWS_OPERATION_NAME
|
12
|
+
@response_object = AWS_RESPONSE_OBJECT
|
13
|
+
end
|
14
|
+
|
15
|
+
def perform_operation
|
16
|
+
create_request_string
|
17
|
+
|
18
|
+
super
|
19
|
+
|
20
|
+
Kempelen::API::Responses::EmptyResponse.new(@raw_response)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|