Finish off the links between frontend and backend #10
@@ -9,6 +9,9 @@ type EmotionalStatsProps = {
|
|||||||
|
|
||||||
const EmotionalStats = ({contentData}: EmotionalStatsProps) => {
|
const EmotionalStats = ({contentData}: EmotionalStatsProps) => {
|
||||||
const rows = contentData.average_emotion_by_topic ?? [];
|
const rows = contentData.average_emotion_by_topic ?? [];
|
||||||
|
const overallEmotionAverage = contentData.overall_emotion_average ?? [];
|
||||||
|
const dominantEmotionDistribution = contentData.dominant_emotion_distribution ?? [];
|
||||||
|
const emotionBySource = contentData.emotion_by_source ?? [];
|
||||||
const lowSampleThreshold = 20;
|
const lowSampleThreshold = 20;
|
||||||
const stableSampleThreshold = 50;
|
const stableSampleThreshold = 50;
|
||||||
const emotionKeys = rows.length
|
const emotionKeys = rows.length
|
||||||
@@ -64,39 +67,104 @@ const EmotionalStats = ({contentData}: EmotionalStatsProps) => {
|
|||||||
return (
|
return (
|
||||||
<div style={styles.page}>
|
<div style={styles.page}>
|
||||||
<div style={{ ...styles.container, ...styles.card, marginTop: 16 }}>
|
<div style={{ ...styles.container, ...styles.card, marginTop: 16 }}>
|
||||||
<h2 style={styles.sectionTitle}>Average Emotion by Topic</h2>
|
<h2 style={styles.sectionTitle}>Topic Mood Overview</h2>
|
||||||
<p style={styles.sectionSubtitle}>Read confidence together with sample size. Topics with fewer than {lowSampleThreshold} events are usually noisy and less reliable.</p>
|
<p style={styles.sectionSubtitle}>Use the strength score together with post count. Topics with fewer than {lowSampleThreshold} events are often noisy.</p>
|
||||||
<div style={styles.emotionalSummaryRow}>
|
<div style={styles.emotionalSummaryRow}>
|
||||||
<span><strong style={{ color: "#24292f" }}>Topics:</strong> {strongestPerTopic.length}</span>
|
<span><strong style={{ color: "#24292f" }}>Topics:</strong> {strongestPerTopic.length}</span>
|
||||||
<span><strong style={{ color: "#24292f" }}>Median Sample:</strong> {medianSampleSize} events</span>
|
<span><strong style={{ color: "#24292f" }}>Median Posts:</strong> {medianSampleSize}</span>
|
||||||
<span><strong style={{ color: "#24292f" }}>Low Sample (<{lowSampleThreshold}):</strong> {lowSampleTopics}</span>
|
<span><strong style={{ color: "#24292f" }}>Small Topics (<{lowSampleThreshold}):</strong> {lowSampleTopics}</span>
|
||||||
<span><strong style={{ color: "#24292f" }}>Stable Sample ({stableSampleThreshold}+):</strong> {stableSampleTopics}</span>
|
<span><strong style={{ color: "#24292f" }}>Stable Topics ({stableSampleThreshold}+):</strong> {stableSampleTopics}</span>
|
||||||
</div>
|
</div>
|
||||||
<p style={{ ...styles.sectionSubtitle, marginTop: 10, marginBottom: 0 }}>
|
<p style={{ ...styles.sectionSubtitle, marginTop: 10, marginBottom: 0 }}>
|
||||||
Confidence reflects how strongly one emotion leads within a topic, not model accuracy. Use larger samples for stronger conclusions.
|
Strength means how far the top emotion is ahead in that topic. It does not mean model accuracy.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ ...styles.container, ...styles.grid }}>
|
<div style={{ ...styles.container, ...styles.grid }}>
|
||||||
{strongestPerTopic.map((topic) => (
|
<div style={{ ...styles.card, gridColumn: "span 4" }}>
|
||||||
<div key={topic.topic} style={{ ...styles.card, gridColumn: "span 4" }}>
|
<h2 style={styles.sectionTitle}>Mood Averages</h2>
|
||||||
<h3 style={{ ...styles.sectionTitle, marginBottom: 6 }}>{topic.topic}</h3>
|
<p style={styles.sectionSubtitle}>Average score for each emotion.</p>
|
||||||
<div style={styles.emotionalTopicLabel}>
|
{!overallEmotionAverage.length ? (
|
||||||
Top Emotion
|
<div style={styles.topUserMeta}>No overall emotion averages available.</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ ...styles.topUsersList, maxHeight: 260, overflowY: "auto" }}>
|
||||||
|
{[...overallEmotionAverage]
|
||||||
|
.sort((a, b) => b.score - a.score)
|
||||||
|
.map((row) => (
|
||||||
|
<div key={row.emotion} style={styles.topUserItem}>
|
||||||
|
<div style={styles.topUserName}>{formatEmotion(row.emotion)}</div>
|
||||||
|
<div style={styles.topUserMeta}>{row.score.toFixed(3)}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div style={styles.emotionalTopicValue}>
|
)}
|
||||||
{formatEmotion(topic.emotion)}
|
</div>
|
||||||
|
|
||||||
|
<div style={{ ...styles.card, gridColumn: "span 4" }}>
|
||||||
|
<h2 style={styles.sectionTitle}>Mood Split</h2>
|
||||||
|
<p style={styles.sectionSubtitle}>How often each emotion is dominant.</p>
|
||||||
|
{!dominantEmotionDistribution.length ? (
|
||||||
|
<div style={styles.topUserMeta}>No dominant-emotion split available.</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ ...styles.topUsersList, maxHeight: 260, overflowY: "auto" }}>
|
||||||
|
{[...dominantEmotionDistribution]
|
||||||
|
.sort((a, b) => b.ratio - a.ratio)
|
||||||
|
.map((row) => (
|
||||||
|
<div key={row.emotion} style={styles.topUserItem}>
|
||||||
|
<div style={styles.topUserName}>{formatEmotion(row.emotion)}</div>
|
||||||
|
<div style={styles.topUserMeta}>{(row.ratio * 100).toFixed(1)}% • {row.count.toLocaleString()} events</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div style={styles.emotionalMetricRow}>
|
)}
|
||||||
<span>Confidence</span>
|
</div>
|
||||||
<span style={styles.emotionalMetricValue}>{topic.value.toFixed(3)}</span>
|
|
||||||
</div>
|
<div style={{ ...styles.card, gridColumn: "span 4" }}>
|
||||||
<div style={styles.emotionalMetricRowCompact}>
|
<h2 style={styles.sectionTitle}>Mood by Source</h2>
|
||||||
<span>Sample Size</span>
|
<p style={styles.sectionSubtitle}>Leading emotion in each source.</p>
|
||||||
<span style={styles.emotionalMetricValue}>{topic.count} events</span>
|
{!emotionBySource.length ? (
|
||||||
|
<div style={styles.topUserMeta}>No source emotion profile available.</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ ...styles.topUsersList, maxHeight: 260, overflowY: "auto" }}>
|
||||||
|
{[...emotionBySource]
|
||||||
|
.sort((a, b) => b.event_count - a.event_count)
|
||||||
|
.map((row) => (
|
||||||
|
<div key={row.source} style={styles.topUserItem}>
|
||||||
|
<div style={styles.topUserName}>{row.source}</div>
|
||||||
|
<div style={styles.topUserMeta}>
|
||||||
|
{formatEmotion(row.dominant_emotion)} • {row.dominant_score.toFixed(3)} • {row.event_count.toLocaleString()} events
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ ...styles.card, gridColumn: "span 12" }}>
|
||||||
|
<h2 style={styles.sectionTitle}>Topic Snapshots</h2>
|
||||||
|
<p style={styles.sectionSubtitle}>Per-topic mood with strength and post count.</p>
|
||||||
|
<div style={{ ...styles.grid, marginTop: 10 }}>
|
||||||
|
{strongestPerTopic.map((topic) => (
|
||||||
|
<div key={topic.topic} style={{ ...styles.cardBase, gridColumn: "span 4" }}>
|
||||||
|
<h3 style={{ ...styles.sectionTitle, marginBottom: 6 }}>{topic.topic}</h3>
|
||||||
|
<div style={styles.emotionalTopicLabel}>
|
||||||
|
Likely Mood
|
||||||
|
</div>
|
||||||
|
<div style={styles.emotionalTopicValue}>
|
||||||
|
{formatEmotion(topic.emotion)}
|
||||||
|
</div>
|
||||||
|
<div style={styles.emotionalMetricRow}>
|
||||||
|
<span>Strength</span>
|
||||||
|
<span style={styles.emotionalMetricValue}>{topic.value.toFixed(3)}</span>
|
||||||
|
</div>
|
||||||
|
<div style={styles.emotionalMetricRowCompact}>
|
||||||
|
<span>Posts in Topic</span>
|
||||||
|
<span style={styles.emotionalMetricValue}>{topic.count}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user