GitHub Action
Sync from GitHub
ef78361
Raw
History Blame Contribute Delete
10.1 kB
"""Chart components for Gen3 Nudge Detection Dashboard."""
import plotly.graph_objects as go
import pandas as pd
# =============================================================================
# Color Constants
# =============================================================================
CONFIDENCE_TIER_COLORS = {
"HIGH": "#EF4444", # Red
"MEDIUM": "#F59E0B", # Amber
"LOW": "#10B981", # Green
}
PLATFORM_COLORS = {
"CHATGPT": "#10A37F",
"GOOGLE_AI": "#4285F4",
"GOOGLE_OVERVIEW": "#34A853",
"PERPLEXITY": "#6366F1",
"GEMINI": "#8B5CF6",
"BING": "#00A4EF",
"CLAUDE": "#D97706",
}
POLARITY_COLORS = {
"positive": "#10B981",
"negative": "#EF4444",
"neutral": "#6B7280",
}
# English to Korean emotion mapping
EMOTION_KO = {
"admiration": "๊ฐํƒ„",
"amusement": "์žฌ๋ฏธ",
"anger": "๋ถ„๋…ธ",
"annoyance": "์งœ์ฆ",
"approval": "์ธ์ •",
"caring": "๋ฐฐ๋ ค",
"confusion": "ํ˜ผ๋ž€",
"curiosity": "ํ˜ธ๊ธฐ์‹ฌ",
"desire": "์š•๊ตฌ",
"disappointment": "์‹ค๋ง",
"disapproval": "๋ฐ˜๋Œ€",
"disgust": "ํ˜์˜ค",
"embarrassment": "๋‹นํ˜น",
"excitement": "ํฅ๋ถ„",
"fear": "๋‘๋ ค์›€",
"gratitude": "๊ฐ์‚ฌ",
"grief": "์Šฌํ””",
"joy": "๊ธฐ์จ",
"love": "์‚ฌ๋ž‘",
"nervousness": "๋ถˆ์•ˆ",
"optimism": "๋‚™๊ด€",
"pride": "์ž๋ถ€์‹ฌ",
"realization": "๊นจ๋‹ฌ์Œ",
"relief": "์•ˆ๋„",
"remorse": "ํ›„ํšŒ",
"sadness": "์Šฌํ””",
"surprise": "๋†€๋ผ์›€",
"neutral": "์ค‘๋ฆฝ",
"trust": "์‹ ๋ขฐ",
"anticipation": "๊ธฐ๋Œ€",
"interest": "๊ด€์‹ฌ",
"satisfaction": "๋งŒ์กฑ",
"frustration": "์ขŒ์ ˆ",
"hope": "ํฌ๋ง",
"worry": "๊ฑฑ์ •",
}
# CEJ Korean labels
CEJ_LABELS = {
"VERIFICATION": "๊ฒ€์ฆ ์งˆ๋ฌธ",
"INFORMATION_DISCOVERY": "์ •๋ณด ํƒ์ƒ‰",
"HOW_TO": "์‚ฌ์šฉ๋ฒ•",
"WHERE_TO_BUY": "๊ตฌ๋งค์ฒ˜",
"RECOMMENDATION": "์ถ”์ฒœ ์š”์ฒญ",
"SIDE_EFFECT": "๋ถ€์ž‘์šฉ",
"MARKET_TRENDS": "์‹œ์žฅ ๋™ํ–ฅ",
"RESULT_EFFECTIVENESS": "ํšจ๊ณผ/๊ฒฐ๊ณผ",
"COMPARISON": "๋น„๊ต",
"INGREDIENT": "์„ฑ๋ถ„",
"AWARENESS_COMPARISON": "์ธ์ง€/๋น„๊ต",
"PURCHASE": "๊ตฌ๋งค",
"POST_PURCHASE": "๊ตฌ๋งค ํ›„",
}
# =============================================================================
# Gen3 Nudge Charts
# =============================================================================
def create_confidence_tier_pie_chart(tier_stats: dict) -> go.Figure:
"""Create pie chart for confidence tier distribution (HIGH/MEDIUM/LOW)."""
if not tier_stats:
return go.Figure()
labels = list(tier_stats.keys())
values = list(tier_stats.values())
colors = [CONFIDENCE_TIER_COLORS.get(k, "#6B7280") for k in labels]
label_map = {
"HIGH": "๐Ÿ”ด HIGH",
"MEDIUM": "๐ŸŸก MEDIUM",
"LOW": "๐ŸŸข LOW",
}
display_labels = [label_map.get(k, k) for k in labels]
fig = go.Figure(data=[go.Pie(
labels=display_labels,
values=values,
marker=dict(colors=colors),
hole=0.4,
textinfo="percent+value",
textposition="outside",
)])
fig.update_layout(
title="",
showlegend=True,
legend=dict(orientation="h", yanchor="bottom", y=-0.2, xanchor="center", x=0.5),
margin=dict(t=20, b=60, l=20, r=20),
height=280,
)
return fig
def create_platform_bar_chart(platform_stats: dict) -> go.Figure:
"""Create horizontal bar chart for platform distribution."""
if not platform_stats:
return go.Figure()
sorted_items = sorted(platform_stats.items(), key=lambda x: x[1], reverse=True)
platforms = [item[0] for item in sorted_items]
counts = [item[1] for item in sorted_items]
colors = [PLATFORM_COLORS.get(p, "#6B7280") for p in platforms]
fig = go.Figure(data=[
go.Bar(
y=platforms,
x=counts,
orientation="h",
marker_color=colors,
text=[f"{c}๊ฑด" for c in counts],
textposition="auto",
)
])
fig.update_layout(
title="",
xaxis_title="๋„›์ง€ ํ›„๋ณด ์ˆ˜",
yaxis=dict(autorange="reversed"),
margin=dict(t=20, b=40, l=100, r=20),
height=max(200, len(platforms) * 35),
)
return fig
def create_nudge_by_cej_bar_chart(cej_counts: dict) -> go.Figure:
"""Create horizontal bar chart for nudge distribution by CEJ stage."""
if not cej_counts:
return go.Figure()
sorted_items = sorted(cej_counts.items(), key=lambda x: x[1], reverse=True)
cej_stages = [item[0] for item in sorted_items]
counts = [item[1] for item in sorted_items]
display_labels = [CEJ_LABELS.get(cej, cej) for cej in cej_stages]
fig = go.Figure(data=[
go.Bar(
y=display_labels,
x=counts,
orientation="h",
marker_color="#6366F1",
text=[f"{c}๊ฑด" for c in counts],
textposition="auto",
)
])
fig.update_layout(
title="",
xaxis_title="๋„›์ง€ ํ›„๋ณด ์ˆ˜",
yaxis=dict(autorange="reversed"),
margin=dict(t=20, b=40, l=100, r=20),
height=max(200, len(cej_stages) * 35),
)
return fig
def create_brand_sentiment_chart(brands: list[dict]) -> go.Figure:
"""Create horizontal stacked bar chart for brand sentiment comparison."""
if not brands:
return go.Figure()
# Sort by total mentions
sorted_brands = sorted(brands, key=lambda x: x.get("total_mentions", 0), reverse=True)[:10]
brand_names = []
for b in sorted_brands:
name = b.get("brand_name", "Unknown")
brand_type = b.get("brand_type", "")
prefix = "๐Ÿ  " if brand_type == "IN_HOUSE" else "๐Ÿข "
brand_names.append(f"{prefix}{name}")
positive_rates = [b.get("positive_rate", 0) for b in sorted_brands]
negative_rates = [b.get("negative_rate", 0) for b in sorted_brands]
neutral_rates = [100 - p - n for p, n in zip(positive_rates, negative_rates)]
fig = go.Figure()
fig.add_trace(go.Bar(
y=brand_names,
x=positive_rates,
name="๊ธ์ •",
orientation="h",
marker_color="#10B981",
text=[f"{v:.1f}%" for v in positive_rates],
textposition="inside",
))
fig.add_trace(go.Bar(
y=brand_names,
x=neutral_rates,
name="์ค‘๋ฆฝ",
orientation="h",
marker_color="#E5E7EB",
text=[f"{v:.1f}%" for v in neutral_rates],
textposition="inside",
))
fig.add_trace(go.Bar(
y=brand_names,
x=negative_rates,
name="๋ถ€์ •",
orientation="h",
marker_color="#EF4444",
text=[f"{v:.1f}%" for v in negative_rates],
textposition="inside",
))
fig.update_layout(
title="",
barmode="stack",
xaxis_title="๋น„์œจ (%)",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
margin=dict(t=50, b=50, l=150, r=20),
height=max(300, len(sorted_brands) * 40),
yaxis=dict(autorange="reversed"),
)
return fig
def create_domain_bar_chart(domain_counts: dict) -> go.Figure:
"""Create horizontal bar chart for citation domain distribution."""
if not domain_counts:
return go.Figure()
# Take top 15 domains
sorted_items = list(domain_counts.items())[:15]
domains = [item[0] for item in sorted_items]
counts = [item[1] for item in sorted_items]
# Truncate long domain names
display_domains = [d[:40] + "..." if len(d) > 40 else d for d in domains]
fig = go.Figure(data=[
go.Bar(
y=display_domains,
x=counts,
orientation="h",
marker_color="#3B82F6",
text=[f"{c}ํšŒ" for c in counts],
textposition="auto",
)
])
fig.update_layout(
title="",
xaxis_title="์ธ์šฉ ํšŸ์ˆ˜",
yaxis=dict(autorange="reversed"),
margin=dict(t=20, b=40, l=200, r=20),
height=max(300, len(sorted_items) * 30),
)
return fig
def create_bit_quadrant_chart(bit_stats: dict) -> go.Figure:
"""Create bar chart for BIT quadrant distribution."""
if not bit_stats:
return go.Figure()
BIT_LABELS = {
"neutral": "์ค‘๋ฆฝ",
"product_satisfaction": "์ œํ’ˆ ๋งŒ์กฑ",
"unmet_expectations": "๊ธฐ๋Œ€ ๋ฏธ์ถฉ์กฑ",
"brand_trust": "๋ธŒ๋žœ๋“œ ์‹ ๋ขฐ",
}
sorted_items = sorted(bit_stats.items(), key=lambda x: x[1], reverse=True)
quadrants = [BIT_LABELS.get(item[0], item[0]) for item in sorted_items]
counts = [item[1] for item in sorted_items]
colors = ["#6366F1", "#8B5CF6", "#A855F7", "#D946EF"]
fig = go.Figure(data=[
go.Bar(
x=quadrants,
y=counts,
marker_color=colors[:len(quadrants)],
text=[f"{c}๊ฑด" for c in counts],
textposition="outside",
)
])
fig.update_layout(
title="",
yaxis_title="๋„›์ง€ ํ›„๋ณด ์ˆ˜",
margin=dict(t=20, b=50, l=50, r=20),
height=280,
)
return fig
def create_emotion_distribution_chart(emotion_stats: dict) -> go.Figure:
"""Create bar chart for emotion distribution in nudge candidates."""
if not emotion_stats:
return go.Figure()
sorted_items = sorted(emotion_stats.items(), key=lambda x: x[1], reverse=True)[:8]
emotions = [EMOTION_KO.get(item[0], item[0]) for item in sorted_items]
counts = [item[1] for item in sorted_items]
colors = ["#6366F1", "#8B5CF6", "#A855F7", "#D946EF", "#EC4899", "#F43F5E", "#F97316", "#FBBF24"]
fig = go.Figure(data=[
go.Bar(
x=emotions,
y=counts,
marker_color=colors[:len(emotions)],
text=[f"{c}๊ฑด" for c in counts],
textposition="outside",
)
])
fig.update_layout(
title="",
yaxis_title="๋„›์ง€ ํ›„๋ณด ์ˆ˜",
xaxis_tickangle=-30,
margin=dict(t=20, b=80, l=50, r=20),
height=280,
)
return fig