"""Dashboard card components."""
import html
import streamlit as st
from core.charts import CONFIDENCE_TIER_COLORS
from core.styles import TIER_BORDER_COLORS
from core.utils import get_confidence_tier, truncate_text, format_brands_list
def render_nudge_card(
item: dict,
tier: str,
emoji: str,
tier_desc: str,
confidence: float,
) -> None:
"""Render nudge candidate card with summary info.
Args:
item: Nudge candidate data dict
tier: Confidence tier (HIGH/MEDIUM/LOW)
emoji: Tier emoji
tier_desc: Tier description
confidence: Confidence score (0-1)
"""
cej_stage = item.get("cej_depth2") or item.get("cej_depth1") or "N/A"
platform = item.get("platform", "N/A")
in_house = item.get("in_house_brands", [])
mentioned = item.get("mentioned_brands", [])
question = item.get("question_content", "")
answer = item.get("answer_preview", "")
tier_color = CONFIDENCE_TIER_COLORS.get(tier, "#6B7280")
border_color = TIER_BORDER_COLORS.get(tier, "#6B7280")
question_display = html.escape(truncate_text(question, 200))
answer_short = html.escape(truncate_text(answer, 150))
in_house_display = html.escape(format_brands_list(in_house))
mentioned_display = html.escape(format_brands_list(mentioned))
header_html = f"""
๐ CEJ: {cej_stage}
{emoji} ๋ต๋ณ ์ ์ฒด: {tier} ({confidence:.0%})
๐ฌ ์ง๋ฌธ
{question_display}
{answer_short}...
๐ท๏ธ {in_house_display}
๐ข {mentioned_display}
๐ฅ๏ธ {platform}
"""
st.markdown(header_html, unsafe_allow_html=True)
def render_brand_card(
brand_name: str,
brand_type: str,
sentiment_data: dict,
) -> None:
"""Render brand mention card.
Args:
brand_name: Brand name
brand_type: 'in_house' or 'competitor'
sentiment_data: Dict with sentiment, confidence, count
"""
sentiment = sentiment_data.get("sentiment", "neutral")
confidence = sentiment_data.get("confidence", 0)
mention_count = sentiment_data.get("count", 0)
type_badge = "๐ ์์ฌ" if brand_type == "in_house" else "๐ข ๊ฒฝ์์ฌ"
type_bg = "#DBEAFE" if brand_type == "in_house" else "#FEE2E2"
sentiment_colors = {
"positive": "#10B981",
"negative": "#EF4444",
"neutral": "#6B7280",
}
sent_color = sentiment_colors.get(sentiment, "#6B7280")
sentiment_ko = {"positive": "๊ธ์ ", "negative": "๋ถ์ ", "neutral": "์ค๋ฆฝ"}.get(sentiment, sentiment)
card_html = f"""
{html.escape(brand_name)}
{type_badge}
{sentiment_ko}
์ ๋ขฐ๋: {confidence:.0%}
์ธ๊ธ: {mention_count}ํ
"""
st.markdown(card_html, unsafe_allow_html=True)
def render_verification_item(item: dict, is_false_positive: bool = True) -> None:
"""Render LLM verification item info (used inside expander).
Args:
item: Verification result dict
is_false_positive: True for FP, False for TN
"""
st.markdown(f"**์ง๋ฌธ**: {item.get('question_content', 'N/A')}")
st.markdown(f"**๋ต๋ณ ๋ฏธ๋ฆฌ๋ณด๊ธฐ**: {item.get('answer_preview', 'N/A')}")
st.markdown("---")
info_col1, info_col2, info_col3 = st.columns(3)
with info_col1:
st.markdown(f"**ํ๋ซํผ**: {item.get('platform', 'N/A')}")
st.markdown(f"**CEJ**: {item.get('cej_depth1', 'N/A')} / {item.get('cej_depth2', 'N/A')}")
with info_col2:
st.markdown(f"**1์ฐจ ํ์ **: {item.get('routing_tier', 'N/A')}")
st.markdown(f"**1์ฐจ ๊ฐ์ฑ**: {item.get('overall_polarity', 'N/A')}")
with info_col3:
llm_conf = item.get('llm_confidence', 0) or 0
st.markdown(f"**LLM ์ ๋ขฐ๋**: {llm_conf:.1%}")
st.markdown(f"**LLM ์กฐ์ Tier**: {item.get('llm_adjusted_tier', 'N/A')}")
# LLM reasoning
if item.get('llm_reasoning'):
st.markdown("**LLM ํ๋จ ๊ทผ๊ฑฐ**:")
if is_false_positive:
st.info(item.get('llm_reasoning'))
else:
st.warning(item.get('llm_reasoning'))
# Evidence spans
if item.get('llm_evidence_spans'):
label = "**๊ทผ๊ฑฐ ๋ฌธ์ฅ**:" if is_false_positive else "**๋ถ์ ๊ทผ๊ฑฐ ๋ฌธ์ฅ**:"
st.markdown(label)
for span in (item.get('llm_evidence_spans') or []):
st.markdown(f"- _{span}_")
# Brands
in_house = item.get('in_house_brands', []) or []
mentioned = item.get('mentioned_brands', []) or []
if in_house or mentioned:
st.markdown(f"**์์ฌ ๋ธ๋๋**: {', '.join(in_house) if in_house else 'N/A'}")
st.markdown(f"**์ธ๊ธ ๋ธ๋๋**: {', '.join(mentioned) if mentioned else 'N/A'}")
def render_polarity_item(item: dict) -> None:
"""Render polarity (sentiment) item info (used inside expander).
Args:
item: Sentiment summary dict
"""
confidence = item.get('overall_confidence', 0) or 0
st.markdown(f"**์ง๋ฌธ**: {item.get('question_content', 'N/A')}")
st.markdown(f"**๋ต๋ณ ๋ฏธ๋ฆฌ๋ณด๊ธฐ**: {item.get('answer_preview', 'N/A')}")
st.markdown("---")
info_col1, info_col2, info_col3 = st.columns(3)
with info_col1:
st.markdown(f"**๊ฐ์ฑ**: {item.get('overall_polarity', 'N/A')}")
st.markdown(f"**์ ๋ขฐ๋**: {confidence:.1%}")
with info_col2:
st.markdown(f"**ํ๋ซํผ**: {item.get('platform', 'N/A')}")
st.markdown(f"**CEJ**: {item.get('cej_depth1', 'N/A')} / {item.get('cej_depth2', 'N/A')}")
with info_col3:
tier = item.get('routing_tier', 'N/A')
st.markdown(f"**๋ผ์ฐํ
Tier**: {tier}")
emotion = item.get('dominant_emotion', 'N/A')
st.markdown(f"**๊ฐ์ **: {emotion}")
# Brands
in_house = item.get('in_house_brands', []) or []
mentioned = item.get('mentioned_brands', []) or []
if in_house or mentioned:
st.markdown(f"**์์ฌ ๋ธ๋๋**: {', '.join(in_house) if in_house else 'N/A'}")
st.markdown(f"**์ธ๊ธ ๋ธ๋๋**: {', '.join(mentioned) if mentioned else 'N/A'}")