Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import matplotlib | |
| matplotlib.use('Agg') | |
| import matplotlib.pyplot as plt | |
| import matplotlib.font_manager as fm | |
| from datasets import load_dataset | |
| import os | |
| import google.generativeai as genai | |
| import re | |
| # --- MUST BE FIRST --- | |
| st.set_page_config(page_title="์ธ์ฒ ์๋ฏผ ์ ์ฑ AI ์๋ฎฌ๋ ์ดํฐ", layout="wide") | |
| # --- Font and Environment Setup --- | |
| def setup_environment(): | |
| # Install Nanum Gothic font | |
| os.system('sudo apt-get install -y fonts-nanum') | |
| # Clear matplotlib cache to force rebuild | |
| os.system('rm -rf ~/.cache/matplotlib') | |
| # fm._rebuild() # Removed as it was causing issues | |
| try: | |
| plt.rc('font', family='NanumBarunGothic') | |
| except Exception as e: | |
| st.warning(f"ํฐํธ ์ค์ ์ค ์ค๋ฅ ๋ฐ์: {e}. ๊ธฐ๋ณธ ํฐํธ๋ก ํ์๋ฉ๋๋ค.") | |
| setup_environment() | |
| # --- Data Loading --- | |
| def load_incheon_data(): | |
| dataset = load_dataset('nvidia/Nemotron-Personas-Korea', split='train') | |
| df = pd.DataFrame(dataset) | |
| return df[df['persona'].str.contains('์ธ์ฒ')].copy() | |
| # Helper to extract age group from persona | |
| def get_age_group_from_persona(persona_text): | |
| match = re.search(r'(\d+)์ธ', persona_text) | |
| if match: | |
| age = int(match.group(1)) | |
| if 19 <= age <= 29: return '19-29' | |
| elif 30 <= age <= 39: return '30-39' | |
| elif 40 <= age <= 49: return '40-49' | |
| elif 50 <= age <= 59: return '50-59' | |
| elif 60 <= age <= 69: return '60-69' | |
| elif age >= 70: return '70๋ ์ด์' | |
| return '๋ถ๋ช ' # Unknown age group | |
| # --- LLM Analysis Logic --- | |
| def get_llm_score(model, persona_text, issue_or_policy): | |
| prompt = f""" | |
| ๋น์ ์ ์๋ฏผ ํ๋/์ธ์ง/๊ฐ์ ๋ถ์ ์ ๋ฌธ๊ฐ์ ๋๋ค. | |
| ๋ค์ ํ๋ฅด์๋๋ฅผ ๊ฐ์ง ์ธ์ฒ ์๋ฏผ์ด ์ฃผ์ด์ง ์ฌ์/์ ์ฑ /์ง๋ฌธ1์ ๋ํด ์ผ๋ง๋ ๊ธ์ ์ ์ธ ํ๋/์ธ์ง/๊ฐ์ ์ ๊ฐ์ง์ง ๋ถ์ํ์ธ์. | |
| [์๋ฏผ ํ๋ฅด์๋]: {persona_text} | |
| [๋์ ์ฌ์/์ ์ฑ /์ง๋ฌธ]: {issue_or_policy} | |
| ๊ฒฐ๊ณผ๋ ๋ฐ๋์ 0์์ 100 ์ฌ์ด์ ์ซ์๋ง ์ถ๋ ฅํ์ธ์. (0: ๋งค์ฐ ๋ถ์ ์ , 100: ๋งค์ฐ ๊ธ์ ์ ) | |
| ๋ต๋ณ์๋ ์ซ์ ์ธ์ ์๋ฌด๋ฐ ํ ์คํธ๋ ํฌํจํ์ง ๋ง์ธ์. | |
| """ | |
| try: | |
| response = model.generate_content(prompt) | |
| result = re.findall(r'\d+', response.text) | |
| if result: | |
| score = float(result[0]) | |
| return min(max(score, 0), 100) | |
| return 50.0 # Default to neutral if parsing fails | |
| except Exception as e: | |
| return str(e) | |
| st.title("๐๏ธ ์ธ์ฒ ์๋ฏผ AI ์๋ฎฌ๋ ์ดํฐ") | |
| st.sidebar.header("์ค์ ") | |
| # 1. Gemini API Key - hardcoded | |
| genai.configure(api_key="์ ๋ ฅํ์") | |
| model = genai.GenerativeModel('gemini-2.5-flash') | |
| incheon_df = load_incheon_data() | |
| st.header("์ง๋ฌธ ์ ๋ ฅ") | |
| policy_input = st.text_input("๋ฌผ์ด๋ณด๊ณ ์ถ์ ์ฌ์์ ์ ๋ ฅํ์ธ์. ์์ธํ ์๋ก ๋ต๋ณ์ด ์ ํํฉ๋๋ค (์: ์์ฐํฌํจ) :", "์ธ์ฒ ์ ๊ณตํญ ๊ฑด์ค ๋ฐ ๋ฌผ๋ฅ๋จ์ง ์กฐ์ฑ") | |
| # Sample size slider | |
| sample_size = st.slider("๋ถ์ํ ํ๋ฅด์๋ ์ด ์ํ ์", 1, 100, 20) # Increased max sample size for more flexibility | |
| st.markdown("---") | |
| st.subheader("ํ๋ฅด์๋ ์ฐ๋ น๋๋ณ ๋ถ๋ฅ") | |
| use_age_classification = st.checkbox("์ฐ๋ น๋๋ณ๋ก ๋ถ๋ฅํ์ฌ ์ํ์ ์ ํํ์๊ฒ ์ต๋๊น?", value=False) | |
| age_group_counts = {} | |
| if use_age_classification: | |
| st.write("๊ฐ ์ฐ๋ น๋๋ณ ์ํ ์๋ฅผ ์ ๋ ฅํ์ธ์:") | |
| age_groups = ['19-29', '30-39', '40-49', '50-59', '60-69', '70๋ ์ด์'] | |
| total_age_samples = 0 | |
| for group in age_groups: | |
| age_group_counts[group] = st.number_input(f"{group}์ธ", min_value=0, max_value=sample_size, value=0, key=f"age_{group}") | |
| total_age_samples += age_group_counts[group] | |
| if total_age_samples != sample_size: | |
| st.error(f"์ฐ๋ น๋๋ณ ์ํ ์์ ํฉ์ด ์ด ์ํ ์ ({sample_size})์ ์ผ์นํด์ผ ํฉ๋๋ค. ํ์ฌ ํฉ๊ณ: {total_age_samples}") | |
| st.stop() # Stop execution if counts don't match | |
| if st.button("AI ์๋ฎฌ๋ ์ด์ ์์"): | |
| if incheon_df.empty: | |
| st.warning("์ธ์ฒ ๊ด๋ จ ํ๋ฅด์๋ ๋ฐ์ดํฐ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค. ๋ฐ์ดํฐ์ ์ ํ์ธํด์ฃผ์ธ์.") | |
| st.stop() | |
| with st.spinner(f'{sample_size}๋ช ์ ํ๋ฅด์๋๋ฅผ AI๊ฐ ๋ถ์ ์ค์ ๋๋ค...'): | |
| sample_df = pd.DataFrame() # Initialize empty dataframe | |
| if use_age_classification: | |
| incheon_df['age_group'] = incheon_df['persona'].apply(get_age_group_from_persona) | |
| for group, count in age_group_counts.items(): | |
| if count > 0: | |
| age_filtered_df = incheon_df[incheon_df['age_group'] == group] | |
| if len(age_filtered_df) < count: | |
| st.warning(f"๊ฒฝ๊ณ : {group}์ธ ์ฐ๋ น๋์์ ์์ฒญ๋ {count}๊ฐ ํ๋ฅด์๋๋ฅผ ์ํ๋งํ ์ ์์ต๋๋ค. ์ฌ์ฉ ๊ฐ๋ฅํ ํ๋ฅด์๋ ์: {len(age_filtered_df)}๊ฐ. ๊ฐ๋ฅํ ๋งํผ ์ํ๋งํฉ๋๋ค.") | |
| sample_df = pd.concat([sample_df, age_filtered_df.sample(len(age_filtered_df), random_state=42)]) | |
| else: | |
| sample_df = pd.concat([sample_df, age_filtered_df.sample(count, random_state=42)]) | |
| # If total_age_samples is 0 and use_age_classification is true, sample_df might be empty, so handle that | |
| if sample_df.empty and sample_size > 0: | |
| st.error("์ฐ๋ น๋๋ณ ์ํ๋ง ํ, ์ ํ๋ ํ๋ฅด์๋๊ฐ ์์ต๋๋ค. ์ฐ๋ น๋ ์ ๋ ฅ์ด ์ฌ๋ฐ๋ฅธ์ง ํ์ธํ์ธ์.") | |
| st.stop() | |
| else: | |
| sample_df = incheon_df.sample(sample_size, random_state=42).copy() | |
| # Ensure that sample_df is not empty before proceeding with scoring | |
| if sample_df.empty: | |
| st.error("์ ํ๋ ํ๋ฅด์๋๊ฐ ์์ต๋๋ค. ์ํ ์ ๋๋ ์ฐ๋ น๋๋ณ ์ํ๋ง ์ค์ ์ ํ์ธํ์ธ์.") | |
| st.stop() | |
| scores = [] | |
| for persona in sample_df['persona']: | |
| score = get_llm_score(model, persona, policy_input) | |
| scores.append(score) | |
| if any(isinstance(s, str) for s in scores): | |
| st.error(f"AI ๋ถ์ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: {next(s for s in scores if isinstance(s, str))}") | |
| st.stop() | |
| sample_df['acceptance_score'] = scores | |
| def get_reaction_korean(score): | |
| if score > 80: return 'Very positive' | |
| if score > 60: return 'Positive' | |
| if score > 40: return 'Neutral' | |
| if score > 20: return 'Negative' | |
| return 'Very Negative' | |
| sample_df['reaction'] = sample_df['acceptance_score'].apply(get_reaction_korean) | |
| avg_score = sample_df['acceptance_score'].mean() | |
| st.subheader("์๋ฎฌ๋ ์ด์ ๊ฒฐ๊ณผ") | |
| st.metric("AI-simulated result", f"{avg_score:.2f} / 100") | |
| # Reaction distribution graph | |
| fig, ax = plt.subplots(figsize=(10, 6)) | |
| counts = sample_df['reaction'].value_counts().reindex(['Very positive', 'positive', 'Neutral', 'Negative', 'Very negatie']).fillna(0) | |
| counts.plot(kind='bar', ax=ax, color=['#4CAF50', '#8BC34A', '#FFEB3B', '#FF9800', '#F44336']) | |
| ax.set_title(f"Distribution:", fontsize=14) | |
| ax.set_xlabel("Attitude", fontsize=12) | |
| ax.set_ylabel("Number", fontsize=12) | |
| ax.tick_params(axis='x', rotation=45) | |
| st.pyplot(fig) | |
| st.subheader("AI ์์ธ ๋ถ์ ์ํ (5๋ช )") | |
| for idx, row in sample_df.head(5).iterrows(): # Display 5 samples | |
| with st.expander(f"ํ๋ฅด์๋ ์์ฝ: {row['persona'][:30]}..."): | |
| st.write(f"**์ ์:** {row['acceptance_score']}") | |
| st.write(f"**๋ฐ์:** {row['reaction']}") | |
| st.write(f"**์ ์ฒด ํ๋ฅด์๋:** {row['persona']}") | |
| st.subheader("์ ์ฒด ํ๋ฅด์๋ ๋ถ์ ๊ฒฐ๊ณผ") | |
| st.dataframe(sample_df[['persona', 'acceptance_score', 'reaction']]) | |
| if use_age_classification and not sample_df.empty: | |
| st.subheader("์ฐ๋ น๋๋ณ ๋ถ์") | |
| age_analysis = sample_df.groupby('age_group')['acceptance_score'].mean().reindex(age_groups).fillna(0).reset_index() | |
| age_analysis.columns = ['์ฐ๋ น๋', 'ํ๊ท ์์ฉ๋'] | |
| st.dataframe(age_analysis) | |
| fig_age, ax_age = plt.subplots(figsize=(10, 6)) | |
| import seaborn as sns # Import seaborn here if not already at the top | |
| sns.barplot(x='์ฐ๋ น๋', y='ํ๊ท ์์ฉ๋', data=age_analysis, ax=ax_age, palette='coolwarm') | |
| ax_age.set_title(f"์ฐ๋ น๋๋ณ ํ๊ท ์์ฉ๋: {policy_input}", fontsize=14) | |
| ax_age.set_xlabel("์ฐ๋ น๋", fontsize=12) | |
| ax_age.set_ylabel("ํ๊ท ์์ฉ๋", fontsize=12) | |
| ax_age.tick_params(axis='x', rotation=45) | |
| st.pyplot(fig_age) | |
| # Optional: Detailed reaction distribution per age group | |
| st.subheader("์ฐ๋ น๋๋ณ ๋ฐ์ ๋ถํฌ") | |
| for group in age_groups: | |
| age_group_df = sample_df[sample_df['age_group'] == group] | |
| if not age_group_df.empty: | |
| st.markdown(f"#### {group}์ธ ์ฐ๋ น๋") | |
| fig_detail, ax_detail = plt.subplots(figsize=(8, 4)) | |
| detail_counts = age_group_df['reaction'].value_counts().reindex(['๋งค์ฐ ๊ธ์ ', '๊ธ์ ', '์ค๋ฆฝ', '๋ถ์ ์ /์ฐ๋ ค', '๋งค์ฐ ๋ถ์ ์ ']).fillna(0) | |
| detail_counts.plot(kind='bar', ax=ax_detail, color=['#4CAF50', '#8BC34A', '#FFEB3B', '#FF9800', '#F44336']) | |
| ax_detail.set_title(f"{group}์ธ ๋ฐ์ ๋ถํฌ") | |
| ax_detail.set_xlabel("๋ฐ์") | |
| ax_detail.set_ylabel("์ธ์ ์") | |
| ax_detail.tick_params(axis='x', rotation=45) | |
| st.pyplot(fig_detail) | |