File size: 6,568 Bytes
ef78361
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""ํ‚ค์›Œ๋“œ ๋ถ„์„ ์˜ค๋ฒ„๋ทฐ โ€” ์š”์•ฝ ์นด๋“œ, ํ…Œ์ด๋ธ”, ์ฐจํŠธ."""
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})