danadvash's picture
Update app.py
7bbbc26 verified
import os
import numpy as np
import pandas as pd
import gradio as gr
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
# =========================
# 1. ASSETS & CONFIG
# =========================
MODEL_ID = "sentence-transformers/all-MiniLM-L6-v2"
DATA_FILE = "synthetic_analyst_notes_10k.csv"
EMBED_FILE = "embeddings.npy"
# =========================
# 2. WALL STREET STYLING (CSS) - FINAL POLISH
# =========================
CSS = r"""
:root{
--bg:#0b1220;
--panel:#0f1a2f;
--text:#e7eefc;
--muted:#9db0d0;
--accent:#2b76ff;
--accent2:#00c2ff;
--good:#18d38a;
--bad:#ff4d5e;
--border:rgba(255,255,255,.10);
--shadow:0 22px 70px rgba(0,0,0,.55);
}
*{box-sizing:border-box;}
html,body{background:var(--bg); color:var(--text);}
.gradio-container{max-width:1200px !important; background:var(--bg) !important; color:var(--text) !important;}
h1,h2,h3,h4, label, .label, .wrap, .prose, .markdown, .md{color:var(--text) !important;}
/* --- FINAL NAV BAR FIX (ALL WHITE) --- */
.nav{
position:sticky; top:0; z-index:50;
display:flex; align-items:center; gap:16px;
padding:14px 18px;
background:rgba(11,18,32,.95);
backdrop-filter: blur(10px);
border-bottom:1px solid rgba(255,255,255,.15);
}
/* Force all text inside nav to be white */
.nav * { color: #ffffff !important; }
.brand{display:flex; align-items:center; gap:10px; font-weight:900; letter-spacing:.4px;}
.brand .logo{width:22px; height:22px; border-radius:7px; background:linear-gradient(135deg, var(--accent), var(--accent2)); box-shadow:0 16px 55px rgba(43,118,255,.28);}
.links{display:flex; gap:14px; font-size:13px; font-weight: 500;}
.links div { opacity: 0.9; cursor: pointer; }
.links div:hover { opacity: 1; text-decoration: underline; }
.spacer{flex:1;}
.pill{border:1px solid rgba(255,255,255,.30); border-radius:999px; padding:7px 10px; font-size:12px; background:rgba(255,255,255,.15);}
.kpi{display:flex; align-items:center; gap:10px; font-size:12px;}
.dot{width:7px; height:7px; border-radius:999px; background:rgba(24,211,138,.95); box-shadow:0 0 18px rgba(24,211,138,.30);}
/* HERO & SECTIONS */
.hero{margin:18px; border-radius:22px; overflow:hidden; position:relative; min-height:260px; background:radial-gradient(1100px 420px at 20% 10%, rgba(43,118,255,.34), transparent 62%), radial-gradient(900px 380px at 80% 30%, rgba(0,194,255,.16), transparent 60%), linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,0)); border:1px solid rgba(255,255,255,.08); box-shadow:var(--shadow);}
.hero-inner{position:relative; padding:30px 32px 22px 32px; display:grid; grid-template-columns: 1.25fr .75fr; gap:18px; align-items:end;}
.hero h1{margin:0; font-size:38px; line-height:1.06;}
.hero p{margin:10px 0 0 0; color:var(--muted); max-width:62ch; font-size:14px;}
.tagrow{display:flex; gap:10px; flex-wrap:wrap; justify-content:flex-end; padding-bottom:6px;}
.tag{font-size:12px; color:var(--muted); border:1px solid rgba(255,255,255,.10); background:rgba(255,255,255,.04); padding:7px 10px; border-radius:999px;}
.section{margin:18px; border:1px solid rgba(255,255,255,.08); background:rgba(255,255,255,.03); border-radius:18px; box-shadow:0 18px 55px rgba(0,0,0,.35); overflow:hidden;}
.section-head{padding:14px 16px; border-bottom:1px solid rgba(255,255,255,.06); display:flex; align-items:baseline; justify-content:space-between; gap:10px;}
.pad{padding:14px 16px;}
.whitepanel{background:#ffffff !important; color:#111111 !important; border-radius:16px !important; border:1px solid rgba(0,0,0,.08) !important;}
.whitepanel *{color:#111111 !important;}
.gr-textbox textarea{background:#ffffff !important; color:#111111 !important; border:1px solid rgba(0,0,0,.10) !important; border-radius:16px !important;}
/* BUTTONS UNIFORMITY */
button { height: 100% !important; min-height: 50px !important; }
.runbtn{border:1px solid rgba(43,118,255,.55) !important; background:linear-gradient(135deg, rgba(43,118,255,.95), rgba(0,194,255,.70)) !important; color:#ffffff !important; box-shadow:0 18px 45px rgba(43,118,255,.22) !important; font-weight: bold !important;}
.quick{border:1px solid rgba(0,0,0,.12) !important; background:#f0f2f5 !important; color:#111111 !important;}
.quick:hover{border-color:rgba(43,118,255,.35) !important; background:#ffffff !important;}
/* CARDS */
.cards{display:grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap:12px;}
@media (max-width: 980px){.cards{grid-template-columns: 1fr;}}
.card{border-radius:16px; border:1px solid rgba(0,0,0,.10); background:#ffffff; padding:12px; box-shadow:0 14px 40px rgba(0,0,0,.10);}
.card .top{display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:8px;}
.badge{font-size:11px; font-weight:800; padding:6px 8px; border-radius:999px; background:rgba(43,118,255,.10); border:1px solid rgba(43,118,255,.35); color:#111111;}
.sym{font-weight:900; letter-spacing:.2px; font-size:18px;}
.meta{color:#333; font-size:12px; display:flex; gap:10px; flex-wrap:wrap; margin-bottom:10px;}
.note{font-size:13px; line-height:1.35; color:#111; white-space:pre-wrap;}
"""
NAV_HTML = """
<div class="nav">
<div class="brand"><div class="logo"></div><div>WALLSTREET AI</div></div>
<div class="links">
<div>Semantic Search</div><div>Signals</div><div>Grounding</div><div>Terminal</div>
</div>
<div class="spacer"></div>
<div class="kpi"><div class="dot"></div><div>System Live</div></div>
<div class="pill">10k Synthetic Data</div>
</div>
"""
HERO_HTML = """
<div class="hero">
<div class="hero-inner">
<div>
<h1>Semantic Strategy Engine</h1>
<p>
Describe your investment thesis (e.g., "Aggressive tech growth but with debt risks").
The AI performs <b>Semantic Search</b> to find grounded historical cases and generates a top pick.
</p>
</div>
<div class="tagrow">
<div class="tag">Transformer Models</div>
<div class="tag">Contextual Grounding</div>
<div class="tag">Real-time Inference</div>
</div>
</div>
</div>
"""
# =========================
# 3. BACKEND LOGIC
# =========================
print("⏳ Loading Models & Data...")
# Load Model
model = SentenceTransformer(MODEL_ID)
# Load Data
try:
df = pd.read_csv(DATA_FILE)
df['analyst_note'] = df['analyst_note'].astype(str)
embeddings = np.load(EMBED_FILE)
print("✅ Assets Loaded.")
except Exception as e:
print(f"❌ Error loading assets: {e}")
df = pd.DataFrame()
embeddings = np.array([])
def search(query):
if not query or df.empty:
return pd.DataFrame(), "", ""
# 1. Embed Query
query_vec = model.encode([query])
# 2. Similarity Search
sims = cosine_similarity(query_vec, embeddings)[0]
# 3. Get Top 3
top_indices = sims.argsort()[-3:][::-1]
results = df.iloc[top_indices].copy()
results['score'] = sims[top_indices]
# Prepare Data Table
display_cols = ['company_name', 'sector', 'recommendation', 'revenue_musd', 'revenue_growth', 'score']
table_data = results[[c for c in display_cols if c in results.columns]]
# 4. Generate "Cards" HTML (For Top 3)
cards_html = "<div class='cards'>"
for i, (_, row) in enumerate(results.iterrows()):
badge = "TOP MATCH" if i == 0 else f"#{i+1}"
note_snippet = row['analyst_note'][:180] + "..."
cards_html += f"""
<div class="card">
<div class="top">
<div class="sym">{row.get('company_name', 'UNK')}</div>
<div class="badge">{badge}</div>
</div>
<div class="meta">
{row.get('sector', '')} • Rec: <b>{row.get('recommendation', '')}</b> • Score: {row['score']:.3f}
</div>
<div class="note">"{note_snippet}"</div>
</div>
"""
cards_html += "</div>"
# 5. Generate "AI PICK" HTML (Enriched/Faker Analysis)
best = results.iloc[0]
# Constructing a "Smart" Fake Rationale based on real data
rev_growth = float(best.get('revenue_growth', 0)) * 100
rev_abs = best.get('revenue_musd', 0)
sector = best.get('sector', 'Unknown')
rec = best.get('recommendation', 'HOLD')
# Richer text generation (template)
analysis_text = f"""
<b>Strategic Rationale:</b><br>
Our semantic algorithm identified <b>{best.get('company_name')}</b> as the optimal historical precedent.
Operating within the <b>{sector}</b> sector, this entity demonstrates a classic <b>{rec}</b> signal configuration,
aligning with your request for specific risk/reward parameters.
<br><br>
<b>Key Financial Indicators:</b><br>
• <b>Revenue Trajectory:</b> The company reported ${rev_abs}M with a growth rate of <b>{rev_growth:.1f}%</b>, suggesting significant market movement.<br>
• <b>Analyst Consensus:</b> The underlying sentiment extraction highlights correlation to your query keywords.
<br><br>
<b>Contextual Grounding (Source Note):</b><br>
<i>"{best['analyst_note']}"</i>
"""
pick_html = f"""
<div class='card' style='margin-top:12px; border:2px solid #4f8cff; background:#f0f7ff'>
<div class='top'>
<div class='sym'>{best.get('company_name', 'UNK')}</div>
<div class='badge' style='background:#2b76ff; color:white'>🏆 AI TOP PICK</div>
</div>
<div class='meta'>
<b>Strategy: {rec}</b> • Confidence Score: {best['score']:.4f}
</div>
<div class='note'>
{analysis_text}
</div>
</div>
"""
return table_data, cards_html, pick_html
# =========================
# 4. QUICK STARTERS (STRATEGY TYPES)
# =========================
def q1(): return "Companies with aggressive revenue growth >20% despite high burn rate and volatility"
def q2(): return "Distressed assets with high debt-to-equity ratios and liquidity concerns"
def q3(): return "Established firms with compressing gross margins and operational inefficiencies"
# =========================
# 5. UI CONSTRUCTION
# =========================
with gr.Blocks(css=CSS, theme=gr.themes.Base()) as demo:
gr.HTML(NAV_HTML)
gr.HTML(HERO_HTML)
with gr.Group(elem_classes=["section", "whitepanel"]):
gr.HTML("<div class='section-head'><h2>Investment Query</h2><div class='hint'>Describe what you are looking for</div></div>")
with gr.Row(elem_classes=["pad"]):
query_in = gr.Textbox(label="Your Thesis", placeholder="e.g. 'Aggressive growth but burning cash...'", lines=2)
# BUTTONS ROW - All equal size now
with gr.Row(elem_classes=["pad"]):
run_btn = gr.Button("🔍 SCAN MARKET", elem_classes=["runbtn"])
b1 = gr.Button("🔥 High Growth", elem_classes=["quick"])
b2 = gr.Button("⚠️ High Risk", elem_classes=["quick"])
b3 = gr.Button("📉 Low Margins", elem_classes=["quick"])
gr.HTML("<div class='section-head'><h2>Market Intelligence</h2><div class='hint'>Similar Historical Cases & AI Pick</div></div>")
# ORDER FIXED: TABS (Visual Cards) FIRST, THEN AI PICK
with gr.Tabs(elem_classes=["tabs"]):
with gr.TabItem("Visual Cards"):
out_cards = gr.HTML("<div class='smallmuted'>Results will appear here...</div>")
with gr.TabItem("Data Table"):
out_table = gr.Dataframe()
# AI Pick is now below the tabs
out_pick = gr.HTML()
# Interactions
run_btn.click(fn=search, inputs=query_in, outputs=[out_table, out_cards, out_pick])
b1.click(fn=q1, outputs=query_in).then(fn=search, inputs=query_in, outputs=[out_table, out_cards, out_pick])
b2.click(fn=q2, outputs=query_in).then(fn=search, inputs=query_in, outputs=[out_table, out_cards, out_pick])
b3.click(fn=q3, outputs=query_in).then(fn=search, inputs=query_in, outputs=[out_table, out_cards, out_pick])
if __name__ == "__main__":
demo.launch()