def6870f06
Reviewed-on: #365 Co-authored-by: miteruzo <miteruzo@naver.com> Co-committed-by: miteruzo <miteruzo@naver.com>
98 行
2.6 KiB
Ruby
98 行
2.6 KiB
Ruby
class GekanatorQuestionExample < ApplicationRecord
|
|
ANSWERS = GekanatorQuestionSuggestion::ANSWERS
|
|
NON_UNKNOWN_ANSWERS = ANSWERS - ['unknown']
|
|
SOURCES = ['initial_suggestion', 'post_game_extra'].freeze
|
|
|
|
belongs_to :gekanator_question
|
|
belongs_to :post
|
|
belongs_to :user
|
|
belongs_to :gekanator_game, optional: true
|
|
|
|
validates :answer, presence: true, inclusion: { in: ANSWERS }
|
|
validates :answer_counts, presence: true
|
|
validates :sample_count,
|
|
presence: true,
|
|
numericality: {
|
|
only_integer: true,
|
|
greater_than: 0
|
|
}
|
|
validates :source, presence: true, inclusion: { in: SOURCES }
|
|
validates :weight,
|
|
presence: true,
|
|
numericality: {
|
|
greater_than: 0
|
|
}
|
|
|
|
before_validation :normalize_learning_state
|
|
|
|
def record_answer!(answer:, source:, gekanator_game: nil)
|
|
answer = answer.to_s
|
|
raise ArgumentError, 'invalid answer' unless ANSWERS.include?(answer)
|
|
|
|
counts = normalized_answer_counts
|
|
counts[answer] += 1
|
|
|
|
self.answer_counts = counts
|
|
self.sample_count = counts.values.sum
|
|
self.gekanator_game = gekanator_game if gekanator_game.present?
|
|
self.source = source if new_record?
|
|
|
|
apply_aggregated_answer!(preferred_answer: answer)
|
|
self
|
|
end
|
|
|
|
private
|
|
|
|
def normalize_learning_state
|
|
counts = normalized_answer_counts
|
|
|
|
if counts.values.sum.zero? && answer.present?
|
|
counts[answer] = 1
|
|
end
|
|
|
|
self.answer_counts = counts
|
|
self.sample_count = counts.values.sum
|
|
|
|
apply_aggregated_answer!
|
|
end
|
|
|
|
def apply_aggregated_answer!(preferred_answer: nil)
|
|
counts = normalized_answer_counts
|
|
known_counts = counts.slice(*NON_UNKNOWN_ANSWERS)
|
|
known_total = known_counts.values.sum
|
|
|
|
if known_total.zero?
|
|
self.answer = 'unknown'
|
|
self.weight = 0.1
|
|
return
|
|
else
|
|
max_count = known_counts.values.max
|
|
candidates = known_counts.select { |_answer, count| count == max_count }.keys
|
|
self.answer =
|
|
if preferred_answer.present? && candidates.include?(preferred_answer)
|
|
preferred_answer
|
|
elsif answer.present? && candidates.include?(answer)
|
|
answer
|
|
else
|
|
candidates.first
|
|
end
|
|
end
|
|
|
|
consensus = max_count.to_f / known_total
|
|
self.weight = Math.sqrt(known_total) * consensus
|
|
end
|
|
|
|
def normalized_answer_counts
|
|
base = ANSWERS.index_with(0)
|
|
|
|
answer_counts.to_h.each do |key, value|
|
|
answer_key = key.to_s
|
|
next unless ANSWERS.include?(answer_key)
|
|
|
|
base[answer_key] = value.to_i
|
|
end
|
|
|
|
base
|
|
end
|
|
end
|