UX: Use stacked line chart for post sentiment (#737)
This commit is contained in:
parent
acc3f817e1
commit
2ff3fe3a9f
|
@ -1,8 +1,4 @@
|
||||||
.dashboard.dashboard-sentiment {
|
.dashboard.dashboard-sentiment {
|
||||||
.navigation-item.sentiment {
|
|
||||||
border-bottom: 0.4em solid var(--tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.charts {
|
.charts {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(12, 1fr);
|
grid-template-columns: repeat(12, 1fr);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
.dashboard.dashboard-sentiment .charts {
|
.dashboard.dashboard-sentiment .charts {
|
||||||
.overall-sentiment {
|
.overall-sentiment {
|
||||||
grid-column: span 8;
|
grid-column: span 6;
|
||||||
}
|
}
|
||||||
.post-emotion {
|
.post-emotion {
|
||||||
grid-column: span 4;
|
grid-column: span 6;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -295,15 +295,13 @@ en:
|
||||||
positive: "Positive"
|
positive: "Positive"
|
||||||
negative: "Negative"
|
negative: "Negative"
|
||||||
post_emotion:
|
post_emotion:
|
||||||
tl_01: "Trust levels 0-1"
|
sadness: "Sadness 😢"
|
||||||
tl_234: "Trust levels 2+"
|
surprise: "Surprise 😱"
|
||||||
sadness: "Sadness"
|
neutral: "Neutral 😐"
|
||||||
surprise: "Surprise"
|
fear: "Fear 😨"
|
||||||
neutral: "Neutral"
|
anger: "Anger 😡"
|
||||||
fear: "Fear"
|
joy: "Joy 😀"
|
||||||
anger: "Anger"
|
disgust: "Disgust 🤢"
|
||||||
joy: "Joy"
|
|
||||||
disgust: "Disgust"
|
|
||||||
|
|
||||||
llm:
|
llm:
|
||||||
configuration:
|
configuration:
|
||||||
|
|
|
@ -67,39 +67,39 @@ module DiscourseAi
|
||||||
end
|
end
|
||||||
|
|
||||||
plugin.add_report("post_emotion") do |report|
|
plugin.add_report("post_emotion") do |report|
|
||||||
report.modes = [:radar]
|
report.modes = [:stacked_line_chart]
|
||||||
threshold = 30
|
threshold = 30
|
||||||
|
|
||||||
emotion_count_clause = Proc.new { |emotion| <<~SQL }
|
emotion_count_clause = Proc.new { |emotion| <<~SQL }
|
||||||
COUNT(
|
COUNT(
|
||||||
CASE WHEN (cr.classification::jsonb->'#{emotion}')::integer > :threshold THEN 1 ELSE NULL END
|
CASE WHEN (cr.classification::jsonb->'#{emotion}')::integer > :threshold THEN 1 ELSE NULL END
|
||||||
) AS #{emotion}_count
|
) AS #{emotion}_count
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
grouped_emotions =
|
grouped_emotions =
|
||||||
DB.query(
|
DB.query(
|
||||||
<<~SQL,
|
<<~SQL,
|
||||||
SELECT
|
SELECT
|
||||||
u.trust_level AS trust_level,
|
DATE_TRUNC('day', p.created_at)::DATE AS posted_at,
|
||||||
#{emotion_count_clause.call("sadness")},
|
#{emotion_count_clause.call("sadness")},
|
||||||
#{emotion_count_clause.call("surprise")},
|
#{emotion_count_clause.call("surprise")},
|
||||||
#{emotion_count_clause.call("fear")},
|
#{emotion_count_clause.call("fear")},
|
||||||
#{emotion_count_clause.call("anger")},
|
#{emotion_count_clause.call("anger")},
|
||||||
#{emotion_count_clause.call("joy")},
|
#{emotion_count_clause.call("joy")},
|
||||||
#{emotion_count_clause.call("disgust")}
|
#{emotion_count_clause.call("disgust")}
|
||||||
FROM
|
FROM
|
||||||
classification_results AS cr
|
classification_results AS cr
|
||||||
INNER JOIN posts p ON p.id = cr.target_id AND cr.target_type = 'Post'
|
INNER JOIN posts p ON p.id = cr.target_id AND cr.target_type = 'Post'
|
||||||
INNER JOIN users u ON p.user_id = u.id
|
INNER JOIN users u ON p.user_id = u.id
|
||||||
INNER JOIN topics t ON t.id = p.topic_id
|
INNER JOIN topics t ON t.id = p.topic_id
|
||||||
INNER JOIN categories c ON c.id = t.category_id
|
INNER JOIN categories c ON c.id = t.category_id
|
||||||
WHERE
|
WHERE
|
||||||
t.archetype = 'regular' AND
|
t.archetype = 'regular' AND
|
||||||
p.user_id > 0 AND
|
p.user_id > 0 AND
|
||||||
cr.model_used = 'emotion' AND
|
cr.model_used = 'emotion' AND
|
||||||
(p.created_at > :report_start AND p.created_at < :report_end)
|
(p.created_at > :report_start AND p.created_at < :report_end)
|
||||||
GROUP BY u.trust_level
|
GROUP BY DATE_TRUNC('day', p.created_at)
|
||||||
SQL
|
SQL
|
||||||
report_start: report.start_date,
|
report_start: report.start_date,
|
||||||
report_end: report.end_date,
|
report_end: report.end_date,
|
||||||
threshold: threshold,
|
threshold: threshold,
|
||||||
|
@ -107,27 +107,24 @@ module DiscourseAi
|
||||||
|
|
||||||
return report if grouped_emotions.empty?
|
return report if grouped_emotions.empty?
|
||||||
|
|
||||||
emotions = %w[sadness disgust fear anger joy surprise]
|
emotions = [
|
||||||
level_groups = [[0, 1], [2, 3, 4]]
|
{ name: "sadness", color: report.colors[:turquoise] },
|
||||||
|
{ name: "disgust", color: report.colors[:lime] },
|
||||||
|
{ name: "fear", color: report.colors[:purple] },
|
||||||
|
{ name: "anger", color: report.colors[:magenta] },
|
||||||
|
{ name: "joy", color: report.colors[:yellow] },
|
||||||
|
{ name: "surprise", color: report.colors[:brown] },
|
||||||
|
]
|
||||||
|
|
||||||
report.data =
|
report.data =
|
||||||
level_groups.each_with_index.map do |lg, idx|
|
emotions.map do |emotion|
|
||||||
color = idx == 0 ? :turquoise : :lime
|
|
||||||
tl_emotion_avgs = grouped_emotions.select { |ge| lg.include?(ge.trust_level) }
|
|
||||||
|
|
||||||
{
|
{
|
||||||
req: "emotion_tl_#{lg.join}",
|
req: "emotion_#{emotion[:name]}",
|
||||||
color: report.colors[color],
|
color: emotion[:color],
|
||||||
label: I18n.t("discourse_ai.sentiment.reports.post_emotion.tl_#{lg.join}"),
|
label: I18n.t("discourse_ai.sentiment.reports.post_emotion.#{emotion[:name]}"),
|
||||||
data:
|
data:
|
||||||
emotions.map do |e|
|
grouped_emotions.map do |ge|
|
||||||
{
|
{ x: ge.posted_at, y: ge.public_send("#{emotion[:name]}_count") }
|
||||||
x: I18n.t("discourse_ai.sentiment.reports.post_emotion.#{e}"),
|
|
||||||
y:
|
|
||||||
tl_emotion_avgs.sum do |tl_emotion_avg|
|
|
||||||
tl_emotion_avg.public_send("#{e}_count").to_i
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -100,10 +100,12 @@ RSpec.describe DiscourseAi::Sentiment::EntryPoint do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def strip_emoji_and_downcase(str)
|
||||||
|
stripped_str = str.gsub(/[^\p{L}\p{N}]+/, "") # remove any non-alphanumeric characters
|
||||||
|
stripped_str.downcase
|
||||||
|
end
|
||||||
|
|
||||||
it "calculate averages using only public posts" do
|
it "calculate averages using only public posts" do
|
||||||
post_1.user.update!(trust_level: TrustLevel[0])
|
|
||||||
post_2.user.update!(trust_level: TrustLevel[3])
|
|
||||||
pm.user.update!(trust_level: TrustLevel[0])
|
|
||||||
threshold = 30
|
threshold = 30
|
||||||
|
|
||||||
emotion_classification(post_1, emotion_1)
|
emotion_classification(post_1, emotion_1)
|
||||||
|
@ -111,31 +113,16 @@ RSpec.describe DiscourseAi::Sentiment::EntryPoint do
|
||||||
emotion_classification(pm, emotion_2)
|
emotion_classification(pm, emotion_2)
|
||||||
|
|
||||||
report = Report.find("post_emotion")
|
report = Report.find("post_emotion")
|
||||||
tl_01_point = report.data[0][:data]
|
|
||||||
tl_234_point = report.data[1][:data]
|
|
||||||
|
|
||||||
tl_01_point.each do |point|
|
data_point = report.data
|
||||||
expected = emotion_1[point[:x].downcase.to_sym] > threshold ? 1 : 0
|
|
||||||
expect(point[:y]).to eq(expected)
|
data_point.each do |point|
|
||||||
|
emotion = strip_emoji_and_downcase(point[:label])
|
||||||
|
expected =
|
||||||
|
(emotion_1[emotion.to_sym] > threshold ? 1 : 0) +
|
||||||
|
(emotion_2[emotion.to_sym] > threshold ? 1 : 0)
|
||||||
|
expect(point[:data][0][:y]).to eq(expected)
|
||||||
end
|
end
|
||||||
|
|
||||||
tl_234_point.each do |point|
|
|
||||||
expected = emotion_2[point[:x].downcase.to_sym] > threshold ? 1 : 0
|
|
||||||
expect(point[:y]).to eq(expected)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it "doesn't try to divide by zero if there are no data in a TL group" do
|
|
||||||
post_1.user.update!(trust_level: TrustLevel[3])
|
|
||||||
post_2.user.update!(trust_level: TrustLevel[3])
|
|
||||||
|
|
||||||
emotion_classification(post_1, emotion_1)
|
|
||||||
emotion_classification(post_2, emotion_2)
|
|
||||||
|
|
||||||
report = Report.find("post_emotion")
|
|
||||||
tl_01_point = report.data[0][:data].first
|
|
||||||
|
|
||||||
expect(tl_01_point[:y]).to be_zero
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue