sentext / app.py
Ani-404's picture
add metric cards and emotion distribution chart
7088c8d
Raw
History Blame Contribute Delete
6.39 kB
import os
import tempfile
# Fix Streamlit permissions issue on HF Spaces
os.environ["STREAMLIT_CONFIG_DIR"] = tempfile.mkdtemp()
import streamlit as st
from transformers import pipeline
import pandas as pd
import numpy as np
import plotly.express as px
import yfinance as yf
import warnings
warnings.filterwarnings('ignore')
# Defaults to the project's own fine-tuned models. emotion-model is public;
# finbert-model is gated, so a public deployment needs either the repo ungated
# or an authorized HF_TOKEN available to the server. Override via env vars.
EMOTION_MODEL = os.getenv("EMOTION_MODEL", "Ani-404/emotion-model")
FINANCIAL_MODEL = os.getenv("FINANCIAL_MODEL", "Ani-404/finbert-model")
# Configure page
st.set_page_config(
page_title="SentText - Advanced Analysis",
page_icon="πŸ“ˆ",
layout="wide",
initial_sidebar_state="expanded"
)
# Initialize models
@st.cache_resource
def load_models():
models = {}
errors = {}
for key, model_id in (('emotion', EMOTION_MODEL), ('financial', FINANCIAL_MODEL)):
try:
models[key] = pipeline(
"text-classification",
model=model_id,
tokenizer=model_id,
)
except Exception as exc:
errors[key] = f"{model_id}: {exc}"
return models, errors
# Prediction functions
def predict_emotions_real(text, model):
results = model(text, top_k=None)
scores_list = results[0] if results and isinstance(results[0], list) else results
top = max(scores_list, key=lambda x: x['score'])
all_scores = sorted(
({'emotion': r['label'].lower(), 'score': r['score']} for r in scores_list),
key=lambda x: x['score'],
reverse=True,
)
return top['label'].lower(), top['score'], all_scores
def analyze_financial_real(text, model):
results = model(text)
res = results[0]
label = res['label'].lower()
confidence = res['score']
if 'positive' in label:
score = confidence
signal = 'BUY' if confidence>0.7 else 'HOLD'
elif 'negative' in label:
score = -confidence
signal = 'SELL' if confidence>0.7 else 'HOLD'
else:
score = 0; signal='HOLD'
return score, confidence, signal
@st.cache_data(ttl=900, show_spinner=False)
def fetch_price_history(ticker):
"""Fetch 5-day price history; cached to reduce Yahoo rate-limiting."""
return yf.Ticker(ticker).history(period='5d')
EMOTION_EMOJI = {
'joy': 'πŸ˜„', 'happy': 'πŸ˜„', 'happiness': 'πŸ˜„',
'sadness': '😒', 'sad': '😒',
'anger': '😠', 'angry': '😠',
'fear': '😨', 'surprise': '😲', 'surprised': '😲',
'disgust': '🀒', 'love': '❀️', 'neutral': '😐',
}
def emotion_emoji(label):
"""Return an emoji for an emotion label, defaulting to a neutral face."""
return EMOTION_EMOJI.get(label.lower(), 'πŸ™‚')
def signal_color(signal):
"""Return a Streamlit metric delta color hint for a trade signal."""
return {'BUY': 'normal', 'SELL': 'inverse', 'HOLD': 'off'}.get(signal, 'off')
# Main UI
def main():
st.title("SentText Analytics")
models, errors = load_models()
if errors:
with st.sidebar:
st.warning("Some models failed to load:")
for key, msg in errors.items():
st.caption(f"{key}: {msg}")
tabs = st.tabs(["🎭 Emotion Analysis", "πŸ“ˆ Financial Analysis"])
# Emotion
with tabs[0]:
text = st.text_area("Enter text:")
if st.button("Analyze Emotion") and text:
if 'emotion' in models:
with st.spinner("Analyzing..."):
label, conf, all_scores = predict_emotions_real(text, models['emotion'])
col_a, col_b = st.columns(2)
col_a.metric("Emotion", f"{emotion_emoji(label)} {label.title()}")
col_b.metric("Confidence", f"{conf:.1%}")
scores_df = pd.DataFrame(all_scores)
fig = px.bar(
scores_df,
x='score',
y='emotion',
orientation='h',
title="Emotion confidence distribution",
labels={'score': 'Confidence', 'emotion': ''},
)
fig.update_layout(yaxis={'categoryorder': 'total ascending'})
fig.update_xaxes(tickformat='.0%', range=[0, 1])
st.plotly_chart(fig, use_container_width=True)
else:
st.error("Emotion model not loaded.")
# Financial
with tabs[1]:
col1, col2 = st.columns([1,2])
with col1:
ticker = st.text_input("Ticker:", value='AAPL')
if st.button("Fetch Chart") and ticker.strip():
try:
df = fetch_price_history(ticker.strip().upper())
except Exception as exc:
df = None
if 'ratelimit' in type(exc).__name__.lower() or 'Too Many Requests' in str(exc):
st.warning("Yahoo Finance is rate-limiting the server right now. Please try again in a minute.")
else:
st.error(f"Couldn't fetch price data: {type(exc).__name__}")
if df is not None and not df.empty:
fig = px.line(df, y='Close', title=f"{ticker.upper()} Closing Prices (5d)")
st.plotly_chart(fig)
elif df is not None:
st.error("No data for that ticker.")
with col2:
fin_text = st.text_area("Enter financial text:")
if st.button("Analyze Financial Sentiment") and fin_text:
if 'financial' in models:
with st.spinner("Analyzing..."):
score, conf, signal = analyze_financial_real(fin_text, models['financial'])
m1, m2, m3 = st.columns(3)
m1.metric("Sentiment Score", f"{score:+.2f}")
m2.metric("Confidence", f"{conf:.1%}")
m3.metric(
"Signal",
signal,
delta=signal,
delta_color=signal_color(signal),
)
else:
st.error("Financial model not loaded.")
if __name__ == '__main__':
main()