GitHub Action
Sync from GitHub
ef78361
Raw
History Blame Contribute Delete
7.09 kB
"""๊ฐ์„ฑ๋ถ„์„ Feature ์š”์•ฝ ์นด๋“œ.
๊ธฐ์กด sections/executive_summary.py + quick_overview.py + KPI ํ†ตํ•ฉ.
"""
import streamlit as st
from core.charts import (
create_confidence_tier_pie_chart,
create_nudge_by_cej_bar_chart,
create_platform_bar_chart,
)
def get_risk_grade(high_nudges: int) -> tuple[str, str, str]:
"""Get risk grade based on HIGH tier nudge count."""
if high_nudges == 0:
return "A", "grade-a", "์šฐ์ˆ˜ (๋ถ€์ • ์–ธ๊ธ‰ ์—†์Œ)"
elif high_nudges <= 10:
return "B", "grade-b", "์–‘ํ˜ธ"
elif high_nudges <= 30:
return "C", "grade-c", "์ฃผ์˜ ํ•„์š”"
else:
return "D", "grade-d", "์ฆ‰์‹œ ๋Œ€์‘"
def render_summary(data: dict):
"""๊ฐ์„ฑ๋ถ„์„ ์š”์•ฝ ์นด๋“œ ๋ Œ๋”๋ง."""
total_nudge = data.get("total_nudge", 0)
high_count = data.get("high_count", 0)
medium_count = data.get("medium_count", 0)
risk_score = data.get("risk_score", 0.0)
tier_stats = data.get("tier_stats") or {}
platform_stats = data.get("platform_stats") or {}
cej_stats = data.get("cej_stats") or {}
candidates = data.get("candidates") or []
campaign_overview = data.get("campaign_overview") or {}
# --- Sentiment Summary Card ---
grade, grade_class, grade_desc = get_risk_grade(high_count)
if total_nudge == 0:
nudge_insight = "๋ถ€์ • ์–ธ๊ธ‰ ์—†์Œ"
elif high_count == 0:
nudge_insight = f"์ž ์žฌ ๋ฆฌ์Šคํฌ {total_nudge}๊ฑด (ํ™•์‹ ๋„ ๋‚ฎ์Œ)"
else:
nudge_insight = f"HIGH {high_count}๊ฑด / ์ด {total_nudge}๊ฑด"
col1, col2, col3, col4 = st.columns(4)
with col1:
st.markdown(f"""
<div style="background: linear-gradient(135deg, #EEF2FF 0%, #E0E7FF 100%);
padding: 16px; border-radius: 12px; min-height: 120px;">
<div style="font-size: 13px; color: #6B7280;">๊ฑด๊ฐ• ๋“ฑ๊ธ‰</div>
<div style="font-size: 32px; font-weight: bold; color: #4338CA;">{grade}</div>
<div style="font-size: 12px; color: #4B5563;">{grade_desc}</div>
</div>
""", unsafe_allow_html=True)
with col2:
st.metric("๐Ÿ”ด HIGH", f"{high_count}๊ฑด", help="โ‰ฅ85% ํ™•์‹ ๋„ - ์ฆ‰์‹œ ๋Œ€์‘ ๊ถŒ์žฅ")
with col3:
st.metric("๋ฆฌ์Šคํฌ ์ ์ˆ˜", f"{risk_score:.1f}", help="๊ฐ€์ค‘ ํ‰๊ท  ์ ์ˆ˜")
with col4:
citation_total = sum(c.get("citation_count", 0) or 0 for c in candidates)
st.metric("์ด ์ธ์šฉ ์†Œ์Šค", f"{citation_total}๊ฐœ")
# --- Pipeline Overview (collapsible) ---
with st.expander("๐Ÿ“Š ๋ฐ์ดํ„ฐ ํŒŒ์ดํ”„๋ผ์ธ ์ƒ์„ธ", expanded=False):
_render_pipeline_overview(campaign_overview)
# --- Quick Charts ---
st.markdown("---")
chart_col1, chart_col2, chart_col3, chart_col4 = st.columns(4)
with chart_col1:
st.markdown("##### Confidence Tier ๋ถ„ํฌ")
if tier_stats and sum(tier_stats.values()) > 0:
fig = create_confidence_tier_pie_chart(tier_stats)
st.plotly_chart(fig, use_container_width=True, config={"displayModeBar": False})
else:
st.info("๋ถ€์ • ์–ธ๊ธ‰์ด ์—†์Šต๋‹ˆ๋‹ค")
with chart_col2:
st.markdown("##### ํ”Œ๋žซํผ๋ณ„ ๋ถ„ํฌ")
if platform_stats and sum(platform_stats.values()) > 0:
fig = create_platform_bar_chart(platform_stats)
st.plotly_chart(fig, use_container_width=True, config={"displayModeBar": False})
else:
st.info("ํ”Œ๋žซํผ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค")
with chart_col3:
st.markdown("##### CEJ ๋‹จ๊ณ„๋ณ„ ๋ถ„ํฌ")
if cej_stats and sum(cej_stats.values()) > 0:
fig = create_nudge_by_cej_bar_chart(cej_stats)
st.plotly_chart(fig, use_container_width=True, config={"displayModeBar": False})
else:
st.info("CEJ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค")
with chart_col4:
_render_llm_verification_summary(campaign_overview)
def _render_pipeline_overview(campaign_overview: dict):
"""๋ฐ์ดํ„ฐ ํŒŒ์ดํ”„๋ผ์ธ ํ˜„ํ™ฉ."""
overview_total = campaign_overview.get("total_answers", 0)
overview_ih_neg = campaign_overview.get("in_house_negative_count", 0)
overview_llm_done = campaign_overview.get("llm_verified_in_house", 0)
overview_llm_pending = campaign_overview.get("llm_pending", 0)
overview_llm_confirmed = campaign_overview.get("llm_confirmed_negative", 0)
pipe1, pipe2, pipe3, pipe4, pipe5 = st.columns(5)
with pipe1:
st.metric(
label="์ „์ฒด AI ๋‹ต๋ณ€",
value=f"{overview_total:,}๊ฑด",
help="๊ฐ์„ฑ ๋ถ„์„์ด ์™„๋ฃŒ๋œ ์ „์ฒด AI ๋‹ต๋ณ€ ์ˆ˜",
)
with pipe2:
ih_rate = (overview_ih_neg / overview_total * 100) if overview_total > 0 else 0
st.metric(
label="1์ฐจ ๋ถ€์ • ๊ฐ์ง€ (DeBERTa)",
value=f"{overview_ih_neg:,}๊ฑด",
delta=f"{ih_rate:.1f}%",
delta_color="inverse",
help="์ž์‚ฌ ๋ธŒ๋žœ๋“œ์— ๋Œ€ํ•œ ๋ถ€์ • ๊ฐ์„ฑ์ด ๊ฐ์ง€๋œ ๋‹ต๋ณ€ (ABSA ๊ธฐ๋ฐ˜)",
)
with pipe3:
verify_rate = (overview_llm_done / overview_ih_neg * 100) if overview_ih_neg > 0 else 0
st.metric(
label="2์ฐจ ๊ฒ€์ฆ ์™„๋ฃŒ (LLM)",
value=f"{overview_llm_done:,}๊ฑด",
delta=f"{verify_rate:.0f}% ์™„๋ฃŒ",
delta_color="normal" if verify_rate >= 90 else "off",
help="LLM 2์ฐจ ๊ฒ€์ฆ์ด ์™„๋ฃŒ๋œ ๊ฑด์ˆ˜",
)
with pipe4:
st.metric(
label="2์ฐจ ๊ฒ€์ฆ ๋Œ€๊ธฐ",
value=f"{overview_llm_pending:,}๊ฑด",
help="์•„์ง LLM 2์ฐจ ๊ฒ€์ฆ์ด ์•ˆ ๋œ ๊ฑด์ˆ˜",
)
with pipe5:
confirm_rate = (overview_llm_confirmed / overview_llm_done * 100) if overview_llm_done > 0 else 0
st.metric(
label="์ตœ์ข… ์ •ํƒ",
value=f"{overview_llm_confirmed:,}๊ฑด",
delta=f"์ •ํƒ๋ฅ  {confirm_rate:.1f}%",
help="1์ฐจ + 2์ฐจ ๊ฒ€์ฆ ๋ชจ๋‘์—์„œ ๋ถ€์ •์œผ๋กœ ํ™•์ •๋œ ๊ฑด์ˆ˜",
)
def _render_llm_verification_summary(campaign_overview: dict):
"""LLM 2์ฐจ ๊ฒ€์ฆ ์š”์•ฝ."""
st.markdown("##### ๐Ÿค– LLM 2์ฐจ ๊ฒ€์ฆ")
overview_llm_done = campaign_overview.get("llm_verified_in_house", 0)
overview_llm_pending = campaign_overview.get("llm_pending", 0)
overview_llm_confirmed = campaign_overview.get("llm_confirmed_negative", 0)
if overview_llm_done > 0:
fp_count = overview_llm_done - overview_llm_confirmed
fp_rate = (fp_count / overview_llm_done * 100) if overview_llm_done > 0 else 0
st.metric(
label="๊ฒ€์ฆ ์™„๋ฃŒ",
value=f"{overview_llm_done}๊ฑด",
delta=f"์˜คํƒ {fp_count}๊ฑด ({fp_rate:.0f}%)",
delta_color="inverse",
)
st.caption(f"โœ… ์ •ํƒ: {overview_llm_confirmed}๊ฑด | โŒ ์˜คํƒ: {fp_count}๊ฑด")
if overview_llm_pending > 0:
st.caption(f"โณ ๋Œ€๊ธฐ: {overview_llm_pending}๊ฑด")
elif overview_llm_pending > 0:
st.info(f"โณ {overview_llm_pending}๊ฑด ๊ฒ€์ฆ ๋Œ€๊ธฐ ์ค‘")
else:
st.info("๊ฒ€์ฆ ๋ฐ์ดํ„ฐ ์—†์Œ")