data / streamlit_app.py
slownews's picture
Update streamlit_app.py
fe5bbc3 verified
Raw History Blame Contribute Delete
7.94 kB
# μ•ˆμ „ν•œ 파일 λ‘œλ”©μœΌλ‘œ input stream μ—λŸ¬ ν•΄κ²°
import streamlit as st
import pandas as pd
import numpy as np
import pickle
import json
import os
st.set_page_config(
page_title="μŠ¬λ‘œλ ˆν„° μ§€λŠ₯ν˜• 검색",
page_icon="πŸ”",
layout="wide"
)
class SafeSlowletterSearch:
def __init__(self):
self.bm25 = None
self.processed_data = None
self.metadata = {}
self.system_loaded = False
def safe_load_json(self, filepath):
"""JSON 파일 μ•ˆμ „ λ‘œλ”©"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
st.error(f"JSON λ‘œλ”© μ‹€νŒ¨ ({filepath}): {e}")
return {}
def safe_load_pickle(self, filepath):
"""Pickle 파일 μ•ˆμ „ λ‘œλ”©"""
try:
with open(filepath, 'rb') as f:
return pickle.load(f)
except Exception as e:
st.error(f"Pickle λ‘œλ”© μ‹€νŒ¨ ({filepath}): {e}")
return None
def safe_load_numpy(self, filepath):
"""NumPy 파일 μ•ˆμ „ λ‘œλ”©"""
try:
return np.load(filepath, allow_pickle=True)
except Exception as e:
st.error(f"NumPy λ‘œλ”© μ‹€νŒ¨ ({filepath}): {e}")
return None
def load_system_step_by_step(self):
"""단계별 μ‹œμŠ€ν…œ λ‘œλ”©"""
st.write("πŸ”„ μ‹œμŠ€ν…œ 파일 λ‘œλ”© μ‹œμž‘...")
# 1. 파일 쑴재 확인
required_files = {
'system_metadata.json': '메타데이터',
'processed_data.pkl': '처리된 데이터',
'bm25_index.pkl': 'BM25 인덱슀',
'embeddings.npy': 'μž„λ² λ”© 데이터'
}
missing_files = []
for file, desc in required_files.items():
if os.path.exists(file):
size = os.path.getsize(file) / 1024 / 1024
st.success(f"βœ… {desc}: {file} ({size:.1f}MB)")
else:
st.error(f"❌ {desc}: {file} (μ—†μŒ)")
missing_files.append(file)
if missing_files:
st.error(f"ν•„μˆ˜ 파일 λΆ€μ‘±: {missing_files}")
return False
# 2. JSON 파일 λ¨Όμ € λ‘œλ”© (κ°€μž₯ μ•ˆμ „)
st.write("πŸ“‹ 메타데이터 λ‘œλ”© 쀑...")
self.metadata = self.safe_load_json('system_metadata.json')
if self.metadata:
st.success("βœ… 메타데이터 λ‘œλ”© μ™„λ£Œ")
st.json(self.metadata)
# 3. 처리된 데이터 λ‘œλ”©
st.write("πŸ“Š 데이터 λ‘œλ”© 쀑...")
self.processed_data = self.safe_load_pickle('processed_data.pkl')
if self.processed_data is not None:
st.success(f"βœ… 데이터 λ‘œλ”© μ™„λ£Œ: {len(self.processed_data)}개 ν–‰")
else:
st.error("❌ 데이터 λ‘œλ”© μ‹€νŒ¨")
return False
# 4. BM25 인덱슀 λ‘œλ”©
st.write("πŸ” BM25 인덱슀 λ‘œλ”© 쀑...")
self.bm25 = self.safe_load_pickle('bm25_index.pkl')
if self.bm25 is not None:
st.success("βœ… BM25 인덱슀 λ‘œλ”© μ™„λ£Œ")
else:
st.error("❌ BM25 인덱슀 λ‘œλ”© μ‹€νŒ¨")
return False
# 5. μž„λ² λ”©μ€ 일단 μŠ€ν‚΅ (κ°€μž₯ λ¬Έμ œκ°€ 될 κ°€λŠ₯μ„± λ†’μŒ)
st.info("ℹ️ μž„λ² λ”© νŒŒμΌμ€ 일단 μŠ€ν‚΅ (BM25만 μ‚¬μš©)")
self.system_loaded = True
return True
def tokenize_korean(self, text):
import re
tokens = re.findall(r'\b\w+\b', str(text).lower())
return [token for token in tokens if len(token) > 1]
def search(self, query, top_k=10):
"""BM25 μ „μš© 검색"""
if not self.system_loaded or self.bm25 is None:
return []
try:
tokenized_query = self.tokenize_korean(query)
bm25_scores = self.bm25.get_scores(tokenized_query)
# μƒμœ„ κ²°κ³Ό 선택
top_indices = np.argsort(bm25_scores)[::-1][:top_k]
results = []
for rank, idx in enumerate(top_indices, 1):
if bm25_scores[idx] > 0:
row = self.processed_data.iloc[idx]
results.append({
'rank': rank,
'title': row.get('h3_title', ''),
'date': row.get('date', ''),
'newsletter': row.get('h1_title', ''),
'entities': row.get('entities', ''),
'events': row.get('events', ''),
'content': row.get('h3_content_text', ''),
'score': float(bm25_scores[idx])
})
return results
except Exception as e:
st.error(f"검색 쀑 였λ₯˜: {e}")
return []
def main():
st.title("πŸ” μŠ¬λ‘œλ ˆν„° μ§€λŠ₯ν˜• 검색")
st.markdown("**Input Stream μ—λŸ¬ ν•΄κ²° 버전**")
# μ‹œμŠ€ν…œ μ΄ˆκΈ°ν™”
if 'search_system' not in st.session_state:
st.session_state.search_system = SafeSlowletterSearch()
st.session_state.system_loaded = False
search_system = st.session_state.search_system
# μ‹œμŠ€ν…œ λ‘œλ”© (ν•œ 번만)
if not st.session_state.system_loaded:
with st.container():
if st.button("πŸš€ μ‹œμŠ€ν…œ λ‘œλ”© μ‹œμž‘", type="primary"):
success = search_system.load_system_step_by_step()
st.session_state.system_loaded = success
if success:
st.balloons()
# 검색 μΈν„°νŽ˜μ΄μŠ€ (μ‹œμŠ€ν…œ λ‘œλ”© ν›„)
if st.session_state.system_loaded:
st.divider()
st.subheader("πŸ” 검색")
# λΉ λ₯Έ 검색 λ²„νŠΌ
quick_queries = ["μœ€μ„μ—΄", "이재λͺ…", "λͺ…νƒœκ· ", "탄핡", "경제", "λΆν•œ"]
cols = st.columns(3)
selected_query = None
for i, query in enumerate(quick_queries):
col = cols[i % 3]
if col.button(query, key=f"quick_{i}"):
selected_query = query
# 검색창
search_query = st.text_input(
"검색어:",
value=selected_query or "",
placeholder="예: μœ€μ„μ—΄ λŒ€ν†΅λ Ή"
)
# 검색 μ‹€ν–‰
if search_query:
with st.spinner("검색 쀑..."):
results = search_system.search(search_query, top_k=10)
if results:
st.success(f"πŸŽ‰ {len(results)}개 κ²°κ³Ό 발견!")
for result in results:
with st.expander(f"πŸ† {result['rank']}. {result['title']} ({result['date']})"):
col1, col2 = st.columns([3, 1])
with col1:
if result['entities']:
entities = [e.strip() for e in result['entities'].split(';')[:3] if e.strip()]
if entities:
st.write("**πŸ‘₯ κ΄€λ ¨ 인물:**", ", ".join(entities))
if result['content']:
st.write("**πŸ“ λ‚΄μš©:**")
st.write(result['content'][:200] + "..." if len(result['content']) > 200 else result['content'])
with col2:
st.metric("점수", f"{result['score']:.2f}")
else:
st.warning("검색 κ²°κ³Όκ°€ μ—†μŠ΅λ‹ˆλ‹€.")
else:
st.info("πŸ‘† 'μ‹œμŠ€ν…œ λ‘œλ”© μ‹œμž‘' λ²„νŠΌμ„ ν΄λ¦­ν•΄μ„œ μ‹œμž‘ν•˜μ„Έμš”!")
if __name__ == "__main__":
main()