UX: Use stacked line chart for post sentiment (#737)

This commit is contained in:
Keegan George 2024-08-02 14:23:29 -07:00 committed by GitHub
parent acc3f817e1
commit 2ff3fe3a9f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 62 additions and 84 deletions

View File

@ -1,8 +1,4 @@
.dashboard.dashboard-sentiment {
.navigation-item.sentiment {
border-bottom: 0.4em solid var(--tertiary);
}
.charts {
display: grid;
grid-template-columns: repeat(12, 1fr);

View File

@ -1,8 +1,8 @@
.dashboard.dashboard-sentiment .charts {
.overall-sentiment {
grid-column: span 8;
grid-column: span 6;
}
.post-emotion {
grid-column: span 4;
grid-column: span 6;
}
}

View File

@ -295,15 +295,13 @@ en:
positive: "Positive"
negative: "Negative"
post_emotion:
tl_01: "Trust levels 0-1"
tl_234: "Trust levels 2+"
sadness: "Sadness"
surprise: "Surprise"
neutral: "Neutral"
fear: "Fear"
anger: "Anger"
joy: "Joy"
disgust: "Disgust"
sadness: "Sadness 😢"
surprise: "Surprise 😱"
neutral: "Neutral 😐"
fear: "Fear 😨"
anger: "Anger 😡"
joy: "Joy 😀"
disgust: "Disgust 🤢"
llm:
configuration:

View File

@ -67,39 +67,39 @@ module DiscourseAi
end
plugin.add_report("post_emotion") do |report|
report.modes = [:radar]
report.modes = [:stacked_line_chart]
threshold = 30
emotion_count_clause = Proc.new { |emotion| <<~SQL }
COUNT(
CASE WHEN (cr.classification::jsonb->'#{emotion}')::integer > :threshold THEN 1 ELSE NULL END
) AS #{emotion}_count
SQL
COUNT(
CASE WHEN (cr.classification::jsonb->'#{emotion}')::integer > :threshold THEN 1 ELSE NULL END
) AS #{emotion}_count
SQL
grouped_emotions =
DB.query(
<<~SQL,
SELECT
u.trust_level AS trust_level,
#{emotion_count_clause.call("sadness")},
#{emotion_count_clause.call("surprise")},
#{emotion_count_clause.call("fear")},
#{emotion_count_clause.call("anger")},
#{emotion_count_clause.call("joy")},
#{emotion_count_clause.call("disgust")}
FROM
classification_results AS cr
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 topics t ON t.id = p.topic_id
INNER JOIN categories c ON c.id = t.category_id
WHERE
t.archetype = 'regular' AND
p.user_id > 0 AND
cr.model_used = 'emotion' AND
(p.created_at > :report_start AND p.created_at < :report_end)
GROUP BY u.trust_level
SQL
SELECT
DATE_TRUNC('day', p.created_at)::DATE AS posted_at,
#{emotion_count_clause.call("sadness")},
#{emotion_count_clause.call("surprise")},
#{emotion_count_clause.call("fear")},
#{emotion_count_clause.call("anger")},
#{emotion_count_clause.call("joy")},
#{emotion_count_clause.call("disgust")}
FROM
classification_results AS cr
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 topics t ON t.id = p.topic_id
INNER JOIN categories c ON c.id = t.category_id
WHERE
t.archetype = 'regular' AND
p.user_id > 0 AND
cr.model_used = 'emotion' AND
(p.created_at > :report_start AND p.created_at < :report_end)
GROUP BY DATE_TRUNC('day', p.created_at)
SQL
report_start: report.start_date,
report_end: report.end_date,
threshold: threshold,
@ -107,27 +107,24 @@ module DiscourseAi
return report if grouped_emotions.empty?
emotions = %w[sadness disgust fear anger joy surprise]
level_groups = [[0, 1], [2, 3, 4]]
emotions = [
{ 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 =
level_groups.each_with_index.map do |lg, idx|
color = idx == 0 ? :turquoise : :lime
tl_emotion_avgs = grouped_emotions.select { |ge| lg.include?(ge.trust_level) }
emotions.map do |emotion|
{
req: "emotion_tl_#{lg.join}",
color: report.colors[color],
label: I18n.t("discourse_ai.sentiment.reports.post_emotion.tl_#{lg.join}"),
req: "emotion_#{emotion[:name]}",
color: emotion[:color],
label: I18n.t("discourse_ai.sentiment.reports.post_emotion.#{emotion[:name]}"),
data:
emotions.map do |e|
{
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,
}
grouped_emotions.map do |ge|
{ x: ge.posted_at, y: ge.public_send("#{emotion[:name]}_count") }
end,
}
end

View File

@ -100,10 +100,12 @@ RSpec.describe DiscourseAi::Sentiment::EntryPoint do
)
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
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
emotion_classification(post_1, emotion_1)
@ -111,31 +113,16 @@ RSpec.describe DiscourseAi::Sentiment::EntryPoint do
emotion_classification(pm, emotion_2)
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|
expected = emotion_1[point[:x].downcase.to_sym] > threshold ? 1 : 0
expect(point[:y]).to eq(expected)
data_point = report.data
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
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