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 = """ """ HERO_HTML = """

Semantic Strategy Engine

Describe your investment thesis (e.g., "Aggressive tech growth but with debt risks"). The AI performs Semantic Search to find grounded historical cases and generates a top pick.

Transformer Models
Contextual Grounding
Real-time Inference
""" # ========================= # 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 = "
" 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"""
{row.get('company_name', 'UNK')}
{badge}
{row.get('sector', '')} • Rec: {row.get('recommendation', '')} • Score: {row['score']:.3f}
"{note_snippet}"
""" cards_html += "
" # 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""" Strategic Rationale:
Our semantic algorithm identified {best.get('company_name')} as the optimal historical precedent. Operating within the {sector} sector, this entity demonstrates a classic {rec} signal configuration, aligning with your request for specific risk/reward parameters.

Key Financial Indicators:
Revenue Trajectory: The company reported ${rev_abs}M with a growth rate of {rev_growth:.1f}%, suggesting significant market movement.
Analyst Consensus: The underlying sentiment extraction highlights correlation to your query keywords.

Contextual Grounding (Source Note):
"{best['analyst_note']}" """ pick_html = f"""
{best.get('company_name', 'UNK')}
🏆 AI TOP PICK
Strategy: {rec} • Confidence Score: {best['score']:.4f}
{analysis_text}
""" 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("

Investment Query

Describe what you are looking for
") 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("

Market Intelligence

Similar Historical Cases & AI Pick
") # ORDER FIXED: TABS (Visual Cards) FIRST, THEN AI PICK with gr.Tabs(elem_classes=["tabs"]): with gr.TabItem("Visual Cards"): out_cards = gr.HTML("
Results will appear here...
") 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()