import os import json import re import faiss import numpy as np import gradio as gr import plotly.graph_objects as go import plotly.express as px import pandas as pd import tempfile from dotenv import load_dotenv from sentence_transformers import SentenceTransformer from groq import Groq # ========================== # OPTIONAL LIBRARIES # ========================== try: from fpdf import FPDF import kaleido PDF_AVAILABLE = True except ImportError: PDF_AVAILABLE = False try: from sklearn.decomposition import PCA SKLEARN_AVAILABLE = True except ImportError: SKLEARN_AVAILABLE = False def random_projection(embeddings, n_components=3): np.random.seed(42) projection = np.random.randn(embeddings.shape[1], n_components) return np.dot(embeddings, projection) # ========================== # LOAD ENV & CONFIG # ========================== load_dotenv() groq_client = Groq(api_key=os.getenv("GROQ_API_KEY")) BRAND_NAME = os.getenv("BRAND_NAME", "IdeaIQ") LOGO_PATH = os.getenv("LOGO_PATH", "") # ========================== # LOAD DATASET # ========================== DATA_PATH = "cleaned_market_trends.json" # adjust if needed with open(DATA_PATH, "r", encoding="utf-8") as f: documents = json.load(f) # ========================== # EMBEDDINGS + FAISS # ========================== embedding_model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2") texts = [doc["content"] for doc in documents] embeddings = embedding_model.encode(texts) embeddings = np.array(embeddings).astype("float32") faiss.normalize_L2(embeddings) dimension = embeddings.shape[1] faiss_index = faiss.IndexFlatIP(dimension) faiss_index.add(embeddings) # ========================== # RETRIEVAL # ========================== def retrieve_context(user_idea, top_k=4): query_embedding = embedding_model.encode([user_idea]) query_embedding = np.array(query_embedding).astype("float32") faiss.normalize_L2(query_embedding) scores, indices = faiss_index.search(query_embedding, top_k) retrieved_docs = [] similarity_scores = [] for score, idx in zip(scores[0], indices[0]): retrieved_docs.append(documents[idx]) similarity_scores.append(float(score)) avg_similarity = round(float(np.mean(similarity_scores)), 3) return retrieved_docs, avg_similarity # ========================== # JSON CLEANER # ========================== def extract_json(text): text = re.sub(r"```json", "", text) text = re.sub(r"```", "", text) match = re.search(r"\{.*\}", text, re.DOTALL) if match: return json.loads(match.group()) raise ValueError("Invalid JSON response") # ========================== # LLM ANALYSIS # ========================== SYSTEM_PROMPT = """ You are an AI Product Strategy Analyst. Return ONLY valid JSON. Do not wrap in markdown. Strictly follow provided structure. """ def analyze_idea(user_idea): retrieved_docs, avg_similarity = retrieve_context(user_idea) context_text = "\n\n".join( [f"{doc['domain']} | {doc['year']} | {doc['content']}" for doc in retrieved_docs] ) user_prompt = f""" User Idea: {user_idea} Market Context: {context_text} Return strictly a JSON object with the following structure. The "market_analysis" list should contain one object per retrieved document (exactly {len(retrieved_docs)} items), with estimated numeric scores based on the document content and your market knowledge. {{ "idea": "{user_idea}", "scope": "Low | Medium | High", "feasibility": "Low | Medium | High", "estimated_timeline": "X months", "market_trend_summary": "...", "uniqueness_analysis": "...", "risks": ["Risk 1", "Risk 2"], "actionable_steps": ["Step 1", "Step 2"], "project_score": 0-10, "market_analysis": [ {{ "domain": "from document", "year": "from document", "category": "e.g., AI, Healthcare, Fintech", "region": "Global | North America | etc.", "growth_rate": 0-100, "innovation_score": 0-100, "market_potential": 0-100, "competition_level": 0-100 }}, ... ] }} """ response = groq_client.chat.completions.create( model="llama-3.3-70b-versatile", messages=[ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_prompt} ], temperature=0 ) result = extract_json(response.choices[0].message.content) result.setdefault("scope", "Medium") result.setdefault("feasibility", "Medium") result.setdefault("estimated_timeline", "6 months") result.setdefault("market_trend_summary", "No summary") result.setdefault("uniqueness_analysis", "No analysis") result.setdefault("risks", ["Unknown"]) result.setdefault("actionable_steps", ["Unknown"]) result.setdefault("project_score", 5) result.setdefault("market_analysis", []) if not result["market_analysis"] and retrieved_docs: for doc in retrieved_docs: result["market_analysis"].append({ "domain": doc["domain"], "year": doc["year"], "category": "Unknown", "region": "Global", "growth_rate": np.random.randint(30, 90), "innovation_score": np.random.randint(30, 90), "market_potential": np.random.randint(30, 90), "competition_level": np.random.randint(30, 90) }) result["similarity_score"] = avg_similarity result["retrieved_docs"] = retrieved_docs return result # ========================== # VISUALIZATION FUNCTIONS # ========================== def create_gauge(result): project_score = result["project_score"] fig = go.Figure(go.Indicator( mode="gauge+number", value=project_score, number={'suffix': "/10", 'font': {'size': 30}}, title={'text': "Project Score", 'font': {'size': 20}}, gauge={ 'axis': {'range': [0, 10], 'tickwidth': 1}, 'bar': {'color': "darkblue", 'thickness': 0.3}, 'bgcolor': "white", 'borderwidth': 2, 'steps': [ {'range': [0, 3], 'color': '#ffcccc'}, {'range': [3, 7], 'color': '#ffffcc'}, {'range': [7, 10], 'color': '#ccffcc'} ], 'threshold': { 'line': {'color': "red", 'width': 4}, 'thickness': 0.75, 'value': 7 } } )) fig.update_layout(height=250, margin=dict(l=20, r=20, t=40, b=20), paper_bgcolor='rgba(0,0,0,0)') return fig def create_radial_graph(result, visible_nodes=8): idea = result["idea"] project_score = result["project_score"] feasibility = result["feasibility"] similarity = result["similarity_score"] scope = result["scope"] timeline = result["estimated_timeline"] score_color = f"rgb({255-int(project_score*25)}, {int(project_score*25)}, 0)" feasibility_color = {"High": "#00cc96", "Medium": "#ffa15e", "Low": "#ef553b"}.get(feasibility, "#636efa") nodes = [ idea, f"Scope: {scope}", f"Feasibility: {feasibility}", f"Timeline: {timeline}", "Risks", "Actionable Steps", "Uniqueness", f"Similarity: {similarity}" ] angle = np.linspace(0, 2*np.pi, len(nodes)) radius = 2.5 x = [0] + list(radius * np.cos(angle[1:])) y = [0] + list(radius * np.sin(angle[1:])) colors = [ score_color, "#1f77b4", feasibility_color, "#ff7f0e", "#d62728", "#9467bd", "#17becf", "#bcbd22" ] sizes = [60, 35, 35, 35, 40, 40, 35, 35] hover_texts = [ f"Idea
{idea}
Score: {project_score}/10", f"Scope
{scope}", f"Feasibility
{feasibility}", f"Timeline
{timeline}", f"Risks
" + "
".join(result["risks"]), f"Actionable Steps
" + "
".join(result["actionable_steps"]), f"Uniqueness
{result['uniqueness_analysis']}", f"Similarity Score
{similarity}" ] visible = min(visible_nodes, len(nodes)) visible_x = x[:visible] visible_y = y[:visible] visible_text = nodes[:visible] visible_hover = hover_texts[:visible] visible_colors = colors[:visible] visible_sizes = sizes[:visible] edge_x, edge_y = [], [] for i in range(1, visible): edge_x += [x[0], x[i], None] edge_y += [y[0], y[i], None] fig = go.Figure() fig.add_trace(go.Scatter( x=edge_x, y=edge_y, mode='lines', line=dict(width=1.5, color='#888'), hoverinfo='none' )) fig.add_trace(go.Scatter( x=visible_x, y=visible_y, mode='markers+text', text=visible_text, textposition='bottom center', hovertext=visible_hover, hoverinfo='text', marker=dict( size=visible_sizes, color=visible_colors, line=dict(width=2, color='white') ), textfont=dict(size=12, color='black') )) fig.update_layout( title=dict(text="🚀 Strategic Opportunity Map", font=dict(size=20, family='Arial Black')), showlegend=False, xaxis=dict(visible=False), yaxis=dict(visible=False), height=550, plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)', margin=dict(l=20, r=20, t=80, b=20) ) return fig def create_3d_market(result): retrieved_docs = result.get("retrieved_docs", []) idea = result["idea"] if not retrieved_docs: return go.Figure() doc_texts = [doc["content"] for doc in retrieved_docs] all_texts = [idea] + doc_texts all_embeddings = embedding_model.encode(all_texts) if SKLEARN_AVAILABLE: pca = PCA(n_components=3) coords = pca.fit_transform(all_embeddings) else: coords = random_projection(all_embeddings, 3) df = pd.DataFrame({ 'x': coords[:,0], 'y': coords[:,1], 'z': coords[:,2], 'label': ['Your Idea'] + [f"{doc['domain']} ({doc['year']})" for doc in retrieved_docs], 'size': [20] + [15]*len(retrieved_docs), 'color': ['red'] + ['blue']*len(retrieved_docs), 'hover': [f"Your Idea
{idea}"] + [f"{doc['domain']} ({doc['year']})
{doc['content'][:100]}..." for doc in retrieved_docs] }) fig = px.scatter_3d(df, x='x', y='y', z='z', text='label', size='size', color='color', hover_data={'hover': True, 'x': False, 'y': False, 'z': False, 'color': False, 'size': False}, title="🧠 Market Landscape (3D Similarity Space)") fig.update_traces(marker=dict(line=dict(width=2, color='white')), textposition='top center') fig.update_layout(height=550, showlegend=False) return fig def create_kpi_panel(df): fig = go.Figure() metrics = { "Avg Growth": df["growth_rate"].mean(), "Innovation Strength": df["innovation_score"].mean(), "Market Potential": df["market_potential"].mean(), "Strategic Advantage": (df["innovation_score"] + df["market_potential"] - df["competition_level"]).mean() } fig.add_trace(go.Indicator( mode="number", value=round(metrics["Avg Growth"], 2), title={"text": "Avg Growth"}, domain={'x': [0, 0.45], 'y': [0.55, 1]}, number={"font": {"size": 36}} )) fig.add_trace(go.Indicator( mode="number", value=round(metrics["Innovation Strength"], 2), title={"text": "Innovation Strength"}, domain={'x': [0.55, 1], 'y': [0.55, 1]}, number={"font": {"size": 36}} )) fig.add_trace(go.Indicator( mode="number", value=round(metrics["Market Potential"], 2), title={"text": "Market Potential"}, domain={'x': [0, 0.45], 'y': [0, 0.45]}, number={"font": {"size": 36}} )) fig.add_trace(go.Indicator( mode="number", value=round(metrics["Strategic Advantage"], 2), title={"text": "Strategic Advantage"}, domain={'x': [0.55, 1], 'y': [0, 0.45]}, number={"font": {"size": 36}} )) fig.update_layout( template="plotly_dark", height=320, margin=dict(t=20, b=20, l=20, r=20) ) return fig def create_positioning_matrix(df): fig = px.scatter( df, x="competition_level", y="innovation_score", size="market_potential", color="growth_rate", hover_data=["domain","category","region","year"], title="Strategic Positioning Matrix", size_max=50, color_continuous_scale="Turbo" ) fig.update_traces( marker=dict(line=dict(width=1, color='white')), hovertemplate="%{customdata[0]}
Category: %{customdata[1]}
Region: %{customdata[2]}
Year: %{customdata[3]}
Growth: %{color:.1f}" ) fig.update_layout( template="plotly_dark", height=400, hovermode="closest", transition={"duration": 500} ) return fig def create_growth_by_year(df, selected_year): if "year" not in df.columns or df.empty: fig = go.Figure() fig.add_annotation(text="No data available", showarrow=False) fig.update_layout(template="plotly_dark", height=400) return fig # Convert year column to string to match slider value df = df.copy() df['year'] = df['year'].astype(str) df_filtered = df[df["year"] == selected_year].copy() if df_filtered.empty: fig = go.Figure() fig.add_annotation(text=f"No data for year {selected_year}", showarrow=False) fig.update_layout(template="plotly_dark", height=400) return fig fig = px.bar( df_filtered, x="domain", y="growth_rate", color="growth_rate", title=f"Domain Growth - {selected_year}", color_continuous_scale="Viridis", range_y=[0, 100], hover_data=["category", "region"] ) fig.update_traces( hovertemplate="%{x}
Growth: %{y:.1f}
Category: %{customdata[0]}
Region: %{customdata[1]}" ) fig.update_layout( template="plotly_dark", height=400, xaxis_title="Domain", yaxis_title="Growth Rate (%)" ) return fig def create_heatmap(df): if "year" not in df.columns: return go.Figure() pivot = df.pivot_table(values="market_potential", index="domain", columns="year") fig = px.imshow( pivot, color_continuous_scale="Magma", title="Market Potential Heatmap", aspect="auto", labels=dict(x="Year", y="Domain", color="Potential") ) fig.update_layout(template="plotly_dark", height=400, transition={"duration": 500}) fig.update_traces(hovertemplate="Year: %{x}
Domain: %{y}
Potential: %{z:.1f}") return fig def create_opportunity_radar(df): if df.empty: return go.Figure() df["advantage"] = df["innovation_score"] + df["market_potential"] - df["competition_level"] top = df.sort_values("advantage", ascending=False).iloc[0] categories = ["Growth", "Innovation", "Market", "Competitive Advantage"] values = [ top["growth_rate"], top["innovation_score"], top["market_potential"], 100 - top["competition_level"] ] fig = go.Figure() fig.add_trace(go.Scatterpolar( r=values, theta=categories, fill="toself", marker=dict(color='rgba(102, 126, 234, 0.8)'), hovertemplate="%{theta}: %{r:.1f}" )) fig.update_layout( template="plotly_dark", polar=dict(radialaxis=dict(range=[0,100], visible=True)), title=f"Top Opportunity: {top['domain']}", height=400, transition={"duration": 500} ) return fig # ========================== # PDF GENERATION (FIXED) # ========================== REPORTS_DIR = "reports" os.makedirs(REPORTS_DIR, exist_ok=True) def generate_pdf(result, radial_fig): if not PDF_AVAILABLE: error_path = os.path.join(REPORTS_DIR, "pdf_error.txt") with open(error_path, "w") as f: f.write("PDF generation is not available because required libraries (fpdf2, kaleido) are missing.\nPlease install them.") return error_path try: # Test if kaleido can export (will fail on HF if chromium missing) try: _ = radial_fig.to_image(format="png") except Exception as e: error_path = os.path.join(REPORTS_DIR, "export_error.txt") with open(error_path, "w") as f: f.write(f"Failed to export plot image: {str(e)}\nThis may be due to missing Chromium.") return error_path pdf = FPDF() pdf.add_page() if LOGO_PATH and os.path.exists(LOGO_PATH): pdf.image(LOGO_PATH, x=10, y=8, w=30) pdf.set_font("Arial", 'B', 16) pdf.cell(0, 10, BRAND_NAME, ln=1, align='C') pdf.ln(10) pdf.set_font("Arial", 'B', 12) pdf.cell(0, 10, "Executive Summary", ln=1) pdf.set_font("Arial", '', 10) summary = f""" Project Score: {result['project_score']}/10 Feasibility: {result['feasibility']} Timeline: {result['estimated_timeline']} Market Similarity: {result['similarity_score']} Market Trend Summary: {result['market_trend_summary']} Uniqueness Analysis: {result['uniqueness_analysis']} Top Risks: {chr(10).join(['- ' + r for r in result['risks']])} Actionable Steps: {chr(10).join(['- ' + s for s in result['actionable_steps']])} """ pdf.multi_cell(0, 5, summary) pdf.ln(5) # Add radial graph image img_bytes = radial_fig.to_image(format="png") with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_img: tmp_img.write(img_bytes) img_path = tmp_img.name pdf.image(img_path, w=180) os.unlink(img_path) pdf.ln(5) pdf.set_font("Arial", 'B', 12) pdf.cell(0, 10, "Structured Report (JSON)", ln=1) pdf.set_font("Courier", '', 8) pdf.multi_cell(0, 4, json.dumps(result, indent=2)) # Save PDF with timestamp pdf_filename = f"report_{pd.Timestamp.now().strftime('%Y%m%d_%H%M%S')}.pdf" pdf_path = os.path.join(REPORTS_DIR, pdf_filename) pdf.output(pdf_path) if os.path.exists(pdf_path) and os.path.getsize(pdf_path) > 0: return pdf_path else: error_path = os.path.join(REPORTS_DIR, "empty_pdf.txt") with open(error_path, "w") as f: f.write("PDF file was created but appears empty.") return error_path except Exception as e: print(f"PDF generation error: {e}") error_path = os.path.join(REPORTS_DIR, "pdf_error.txt") with open(error_path, "w") as f: f.write(f"An error occurred during PDF generation: {str(e)}") return error_path def prepare_pdf(result, radial_fig): if not result or not radial_fig: error_path = os.path.join(REPORTS_DIR, "no_data.txt") with open(error_path, "w") as f: f.write("No data available for PDF generation.") return error_path return generate_pdf(result, radial_fig) # ========================== # UPDATE FUNCTIONS # ========================== def update_radial_and_state(result, step): if not result: return go.Figure(), go.Figure() new_fig = create_radial_graph(result, step) return new_fig, new_fig def update_live_score(result_state): if result_state: new_score = result_state.get("similarity_score", 0.5) variation = np.random.uniform(-0.05, 0.05) new_score = round(max(0, min(1, new_score + variation)), 3) result_state["similarity_score"] = new_score return f"Live Similarity: {new_score}" return "Live Similarity: N/A" def increment_step(current_step): return min(current_step + 1, 8) def update_growth_by_year(result_state, selected_year): if not result_state: return go.Figure() df = pd.DataFrame(result_state.get("market_analysis", [])) for col in ["growth_rate","innovation_score","market_potential","competition_level"]: if col in df.columns: df[col] = pd.to_numeric(df[col], errors="coerce") df.fillna(0, inplace=True) return create_growth_by_year(df, selected_year) # ========================== # FULL PIPELINE # ========================== def full_pipeline(user_input): try: result = analyze_idea(user_input) fig_radial = create_radial_graph(result, visible_nodes=1) fig_3d = create_3d_market(result) fig_gauge = create_gauge(result) df = pd.DataFrame(result["market_analysis"]) for col in ["growth_rate","innovation_score","market_potential","competition_level"]: if col in df.columns: df[col] = pd.to_numeric(df[col], errors="coerce") df.fillna(0, inplace=True) years = [] if "year" in df.columns: years = sorted(df["year"].unique()) years = [str(y) for y in years] if df.empty: fig_kpi = go.Figure() fig_matrix = go.Figure() fig_growth = create_growth_by_year(df, None) fig_heat = go.Figure() fig_radar_opp = go.Figure() else: fig_kpi = create_kpi_panel(df) fig_matrix = create_positioning_matrix(df) initial_year = str(df["year"].iloc[0]) if "year" in df.columns else None fig_growth = create_growth_by_year(df, initial_year) fig_heat = create_heatmap(df) fig_radar_opp = create_opportunity_radar(df) summary_md = f""" ### 📊 Key Metrics - **Project Score**: **{result['project_score']}/10** - **Feasibility**: **{result['feasibility']}** - **Timeline**: **{result['estimated_timeline']}** - **Market Similarity**: **{result['similarity_score']}** ### 📈 Market Trend Summary {result['market_trend_summary']} ### 💡 Uniqueness Analysis {result['uniqueness_analysis']} ### ⚠️ Top Risks {chr(10).join(['- ' + r for r in result['risks']])} ### ✅ Actionable Steps {chr(10).join(['- ' + s for s in result['actionable_steps']])} """ confetti_html = "" if result['project_score'] > 8: confetti_html = """ """ return (summary_md, json.dumps(result, indent=4), fig_radial, fig_3d, fig_gauge, fig_kpi, fig_matrix, fig_growth, fig_heat, fig_radar_opp, result, fig_radial, confetti_html, years) except Exception as e: error_msg = f"❌ Error: {str(e)}" empty_fig = go.Figure() return (error_msg, "{}", empty_fig, empty_fig, empty_fig, empty_fig, empty_fig, empty_fig, empty_fig, empty_fig, {}, empty_fig, "", []) # ========================== # ULTRA‑PREMIUM RESPONSIVE UI # ========================== with gr.Blocks(theme=gr.themes.Soft(), css="") as app: gr.HTML(""" """) # ========================== # HEADER # ========================== gr.HTML(f"""

🚀 {BRAND_NAME}

AI-Powered Market Intelligence • Real-Time Analysis • Strategic Insights

""") # ========================== # INPUT ROW (NO VOICE) # ========================== with gr.Row(elem_classes="gradio-row"): with gr.Column(scale=4, elem_classes="gradio-column"): idea_input = gr.Textbox( placeholder="Enter your startup idea... e.g., AI platform for personalized mental health coaching", lines=2, container=False, elem_classes="premium-input" ) with gr.Column(scale=1, min_width=120, elem_classes="gradio-column"): analyze_btn = gr.Button("Analyze", elem_classes="btn-primary") # ========================== # STATES # ========================== result_state = gr.State() radial_fig_state = gr.State() years_state = gr.State() # Live similarity badge live_display = gr.HTML('Live Similarity: N/A') # ========================== # SUMMARY & JSON # ========================== with gr.Row(elem_classes="gradio-row"): with gr.Column(scale=1, elem_classes="glass-card fade-in gradio-column"): summary_output = gr.Markdown() with gr.Column(scale=1, elem_classes="glass-card fade-in gradio-column"): json_output = gr.Code(label="📄 Structured Report", language="json", lines=10) # ========================== # PLOT ROWS # ========================== # Row 1 with gr.Row(elem_classes="gradio-row"): with gr.Column(elem_classes="plot-card gradio-column"): radial_output = gr.Plot(label="🗺️ Strategic Opportunity Map") with gr.Column(elem_classes="plot-card gradio-column"): market_output = gr.Plot(label="🧠 3D Market Landscape") with gr.Column(elem_classes="plot-card gradio-column"): gauge_output = gr.Plot(label="🎯 Project Score") # Row 2 with gr.Row(elem_classes="gradio-row"): with gr.Column(elem_classes="plot-card gradio-column"): kpi_output = gr.Plot(label="📊 KPI Panel") with gr.Column(elem_classes="plot-card gradio-column"): matrix_output = gr.Plot(label="🎯 Positioning Matrix") # Row 3 (Growth with year slider) with gr.Row(elem_classes="gradio-row"): with gr.Column(elem_classes="plot-card gradio-column"): year_slider = gr.Slider( minimum=0, maximum=0, step=1, value=0, label="Select Year", visible=False, elem_classes="premium-slider" ) growth_output = gr.Plot(label="📈 Growth by Year") # Row 4 with gr.Row(elem_classes="gradio-row"): with gr.Column(elem_classes="plot-card gradio-column"): heat_output = gr.Plot(label="🔥 Market Heatmap") with gr.Column(elem_classes="plot-card gradio-column"): radar_opp_output = gr.Plot(label="⭐ Opportunity Radar") # ========================== # CONTROLS # ========================== with gr.Row(elem_classes="glass-card fade-in gradio-row"): step_slider = gr.Slider(minimum=1, maximum=8, step=1, value=1, label="Reveal Steps", elem_classes="premium-slider") reveal_btn = gr.Button("Reveal Next Step", elem_classes="btn-secondary") live_btn = gr.Button("Refresh Live Similarity", elem_classes="btn-secondary") pdf_btn = gr.Button("📥 Download PDF Report", elem_classes="btn-primary") pdf_output = gr.File(label="Download PDF", visible=True) confetti_html = gr.HTML() # ========================== # EVENT HANDLERS # ========================== def update_after_analysis(summary, json, radial, market, gauge, kpi, matrix, growth, heat, radar_opp, result, radial_fig, confetti, years): if years and len(years) > 0: return (summary, json, radial, market, gauge, kpi, matrix, growth, heat, radar_opp, result, radial_fig, confetti, gr.update(minimum=0, maximum=len(years)-1, step=1, value=0, visible=True, label=f"Year: {years[0]}"), years) else: return (summary, json, radial, market, gauge, kpi, matrix, growth, heat, radar_opp, result, radial_fig, confetti, gr.update(visible=False), years) analyze_btn.click( fn=full_pipeline, inputs=idea_input, outputs=[summary_output, json_output, radial_output, market_output, gauge_output, kpi_output, matrix_output, growth_output, heat_output, radar_opp_output, result_state, radial_fig_state, confetti_html, years_state] ).then( fn=update_after_analysis, inputs=[summary_output, json_output, radial_output, market_output, gauge_output, kpi_output, matrix_output, growth_output, heat_output, radar_opp_output, result_state, radial_fig_state, confetti_html, years_state], outputs=[summary_output, json_output, radial_output, market_output, gauge_output, kpi_output, matrix_output, growth_output, heat_output, radar_opp_output, result_state, radial_fig_state, confetti_html, year_slider, years_state] ).then( fn=lambda rs: f"Live Similarity: {rs.get('similarity_score', 'N/A')}", inputs=result_state, outputs=live_display ) def on_year_change(result_state, slider_val, years_state): if years_state and slider_val < len(years_state): selected_year = years_state[slider_val] return update_growth_by_year(result_state, selected_year) return go.Figure() year_slider.change( fn=on_year_change, inputs=[result_state, year_slider, years_state], outputs=growth_output ) def update_slider_label(slider_val, years_state): if years_state and slider_val < len(years_state): return gr.update(label=f"Year: {years_state[slider_val]}") return gr.update(label="Select Year") year_slider.change( fn=update_slider_label, inputs=[year_slider, years_state], outputs=year_slider ) step_slider.change( fn=update_radial_and_state, inputs=[result_state, step_slider], outputs=[radial_output, radial_fig_state] ) reveal_btn.click( fn=increment_step, inputs=step_slider, outputs=step_slider ).then( fn=update_radial_and_state, inputs=[result_state, step_slider], outputs=[radial_output, radial_fig_state] ) live_btn.click( fn=update_live_score, inputs=result_state, outputs=live_display ) pdf_btn.click( fn=prepare_pdf, inputs=[result_state, radial_fig_state], outputs=pdf_output ) # ========================== # FOOTER # ========================== gr.HTML("""

Powered by Groq LLama 3.3 • FAISS • Sentence‑Transformers

""") if __name__ == "__main__": app.launch(server_name="0.0.0.0", server_port=7860)