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 { .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);

View File

@ -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;
} }
} }

View File

@ -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:

View File

@ -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

View File

@ -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