GitHub Action
Sync from GitHub
ef78361
Raw
History Blame Contribute Delete
6.57 kB
"""ํ‚ค์›Œ๋“œ ๋ถ„์„ ์˜ค๋ฒ„๋ทฐ โ€” ์š”์•ฝ ์นด๋“œ, ํ…Œ์ด๋ธ”, ์ฐจํŠธ."""
import html
import pandas as pd
import plotly.graph_objects as go
import streamlit as st
from core.charts import POLARITY_COLORS
def render_keyword_overview(keywords_list: list[dict]):
"""ํ‚ค์›Œ๋“œ ๋ถ„์„ ์˜ค๋ฒ„๋ทฐ ์นด๋“œ."""
total_keywords = len(keywords_list)
total_sentences = sum(kw.get("total_sentences", 0) for kw in keywords_list)
total_brand_mentions = sum(kw.get("brand_mentioned_count", 0) for kw in keywords_list)
total_no_brand = total_sentences - total_brand_mentions
brand_mention_rate = (total_brand_mentions / total_sentences * 100) if total_sentences > 0 else 0
# Brand sentiment aggregation
brand_pos = sum((kw.get("brand_sentiment") or {}).get("positive", 0) for kw in keywords_list)
brand_neu = sum((kw.get("brand_sentiment") or {}).get("neutral", 0) for kw in keywords_list)
brand_neg = sum((kw.get("brand_sentiment") or {}).get("negative", 0) for kw in keywords_list)
brand_total = brand_pos + brand_neu + brand_neg
brand_pos_pct = (brand_pos / brand_total * 100) if brand_total > 0 else 0
brand_neg_pct = (brand_neg / brand_total * 100) if brand_total > 0 else 0
# Top keyword-brand associations
top_associations = []
for kw in sorted(keywords_list, key=lambda x: x.get("brand_mentioned_count", 0), reverse=True)[:3]:
keyword = kw.get("keyword", "")
total = kw.get("total_sentences", 0)
brand_count = kw.get("brand_mentioned_count", 0)
if total > 0 and brand_count > 0:
rate = brand_count / total * 100
top_associations.append(f'"{keyword}" {brand_count:,}๊ฑด ({rate:.0f}%)')
assoc_text = " | ".join(top_associations) if top_associations else "๋ฐ์ดํ„ฐ ์—†์Œ"
# Brand sentiment bar
brand_sent_bar = ""
if brand_total > 0:
bp = brand_pos / brand_total * 100
bn = brand_neg / brand_total * 100
bne = 100 - bp - bn
brand_sent_bar = f"""
<div style="display:flex;height:6px;border-radius:3px;overflow:hidden;margin-top:4px;max-width:300px;">
<div style="width:{bp}%;background:#10B981;"></div>
<div style="width:{bne}%;background:#D1D5DB;"></div>
<div style="width:{bn}%;background:#EF4444;"></div>
</div>"""
st.markdown(f"""
<div style="background: linear-gradient(135deg, #F8FAFC 0%, #ECFDF5 100%); border: 1px solid #A7F3D0;
border-radius: 12px; padding: 16px; margin-bottom: 16px;">
<div style="display: flex; gap: 24px; flex-wrap: wrap; font-size: 13px; color: #374151;">
<span>๋ถ„์„ ํ‚ค์›Œ๋“œ: <strong>{total_keywords}๊ฐœ</strong></span>
<span>์ „์ฒด ๋ฌธ์žฅ: <strong>{total_sentences:,}๊ฑด</strong></span>
</div>
<div style="display:flex;gap:20px;flex-wrap:wrap;font-size:12px;color:#374151;margin-top:8px;">
<span>๐Ÿ  ๋ธŒ๋žœ๋“œ ์–ธ๊ธ‰: <strong>{total_brand_mentions:,}๊ฑด</strong> ({brand_mention_rate:.1f}%)
&mdash; <span style="color:#10B981;">๊ธ์ • {brand_pos_pct:.0f}%</span>
/ <span style="color:#EF4444;">๋ถ€์ • {brand_neg_pct:.0f}%</span>
</span>
<span style="color:#6B7280;">๋น„๋ธŒ๋žœ๋“œ: <strong>{total_no_brand:,}๊ฑด</strong> ({100 - brand_mention_rate:.1f}%)</span>
</div>{brand_sent_bar}
<div style="font-size: 11px; color: #9CA3AF; margin-top: 6px;">
ํ‚ค์›Œ๋“œ-๋ธŒ๋žœ๋“œ ์—ฐ๊ด€ ์ƒ์œ„: {html.escape(assoc_text)}
</div>
</div>
""", unsafe_allow_html=True)
def render_summary_table(keywords_list: list[dict]):
"""ํ‚ค์›Œ๋“œ๋ณ„ ๊ฐ์„ฑ ์š”์•ฝ ํ…Œ์ด๋ธ”."""
st.markdown("**ํ‚ค์›Œ๋“œ๋ณ„ ๊ฐ์„ฑ ์š”์•ฝ**")
rows = []
for kw in keywords_list:
keyword = kw.get("keyword", "")
total = kw.get("total_sentences", 0)
ks = kw.get("keyword_sentiment", {})
pos = ks.get("positive", 0)
neu = ks.get("neutral", 0)
neg = ks.get("negative", 0)
brand_count = kw.get("brand_mentioned_count", 0)
neg_rate = (neg / total * 100) if total > 0 else 0
pos_rate = (pos / total * 100) if total > 0 else 0
# Brand sentiment breakdown
brand_sent = kw.get("brand_sentiment") or {}
brand_pos = brand_sent.get("positive", 0)
brand_neg = brand_sent.get("negative", 0)
brand_neg_rate = (brand_neg / brand_count * 100) if brand_count > 0 else 0
rows.append({
"ํ‚ค์›Œ๋“œ": keyword,
"์ด ๋ฌธ์žฅ": total,
"๊ธ์ •": pos,
"์ค‘๋ฆฝ": neu,
"๋ถ€์ •": neg,
"๋ถ€์ •๋ฅ ": f"{neg_rate:.1f}%",
"๊ธ์ •๋ฅ ": f"{pos_rate:.1f}%",
"๋ธŒ๋žœ๋“œ ๋ฉ˜์…˜": brand_count,
"๋ธŒ๋žœ๋“œ ๊ธ์ •": brand_pos if brand_count > 0 else "-",
"๋ธŒ๋žœ๋“œ ๋ถ€์ •": brand_neg if brand_count > 0 else "-",
"๋ธŒ๋žœ๋“œ ๋ถ€์ •๋ฅ ": f"{brand_neg_rate:.1f}%" if brand_count > 0 else "-",
})
if rows:
df = pd.DataFrame(rows)
df = df.sort_values("๋ถ€์ •", ascending=False)
st.dataframe(df, use_container_width=True, hide_index=True)
else:
st.info("๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค")
def render_sentiment_chart(keywords_list: list[dict]):
"""ํ‚ค์›Œ๋“œ ๊ฐ์„ฑ ๋น„๊ต bar chart (๋ถ€์ • ๋น„์œจ ์ˆœ)."""
st.markdown("**ํ‚ค์›Œ๋“œ ๊ฐ์„ฑ ๋น„๊ต ์ฐจํŠธ**")
# Sort by negative count descending
sorted_kws = sorted(
keywords_list,
key=lambda x: x.get("keyword_sentiment", {}).get("negative", 0),
reverse=True,
)[:20] # Top 20
keywords = [kw.get("keyword", "") for kw in sorted_kws]
positives = [kw.get("keyword_sentiment", {}).get("positive", 0) for kw in sorted_kws]
neutrals = [kw.get("keyword_sentiment", {}).get("neutral", 0) for kw in sorted_kws]
negatives = [kw.get("keyword_sentiment", {}).get("negative", 0) for kw in sorted_kws]
fig = go.Figure()
fig.add_trace(go.Bar(
name="๋ถ€์ •", x=keywords, y=negatives,
marker_color=POLARITY_COLORS["negative"],
))
fig.add_trace(go.Bar(
name="์ค‘๋ฆฝ", x=keywords, y=neutrals,
marker_color=POLARITY_COLORS["neutral"],
))
fig.add_trace(go.Bar(
name="๊ธ์ •", x=keywords, y=positives,
marker_color=POLARITY_COLORS["positive"],
))
fig.update_layout(
barmode="stack",
height=400,
margin=dict(l=20, r=20, t=30, b=80),
legend=dict(orientation="h", yanchor="bottom", y=1.02),
xaxis_tickangle=-45,
)
st.plotly_chart(fig, use_container_width=True, config={"displayModeBar": False})