File size: 27,376 Bytes
bbf243c
325cb74
bbf243c
 
 
 
 
 
 
 
 
 
de1ea72
 
 
 
 
bbf243c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325cb74
bbf243c
 
 
325cb74
bbf243c
 
 
04250b6
 
 
 
 
325cb74
bbf243c
 
 
325cb74
bbf243c
 
 
598ea92
04250b6
 
 
598ea92
 
 
04250b6
 
 
598ea92
 
 
04250b6
 
 
598ea92
 
 
04250b6
 
 
598ea92
bbf243c
325cb74
bbf243c
 
 
325cb74
bbf243c
 
 
325cb74
598ea92
 
bbf243c
325cb74
598ea92
 
bbf243c
 
 
325cb74
bbf243c
 
 
75aa22f
 
325cb74
75aa22f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bbf243c
75aa22f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325cb74
75aa22f
 
325cb74
75aa22f
 
 
 
325cb74
75aa22f
 
 
 
 
 
 
325cb74
75aa22f
 
 
 
 
bbf243c
75aa22f
325cb74
75aa22f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325cb74
75aa22f
 
 
 
 
 
 
 
 
 
 
de1ea72
75aa22f
02b42b3
 
75aa22f
 
bbf243c
325cb74
bbf243c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
02b42b3
 
75aa22f
 
 
 
 
 
 
 
325cb74
75aa22f
325cb74
75aa22f
 
 
325cb74
75aa22f
 
 
 
325cb74
75aa22f
bbf243c
75aa22f
 
325cb74
75aa22f
325cb74
bbf243c
75aa22f
 
325cb74
75aa22f
 
 
 
 
325cb74
75aa22f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325cb74
75aa22f
 
 
 
 
 
325cb74
75aa22f
 
 
 
 
 
325cb74
75aa22f
 
 
 
bbf243c
325cb74
75aa22f
 
 
325cb74
75aa22f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325cb74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75aa22f
bbf243c
325cb74
bbf243c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325cb74
bbf243c
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
"""

web_app/app.py -- Cloud-hosted Streamlit Portal for Financial Analysis.



Provides a rich dark UI dashboard to scrape, compute, analyze and download reports

for Excel, Word, and PowerPoint formats using user-supplied LLM credentials.

"""
import os
import sys
import time
import datetime
from pathlib import Path
import streamlit as st
from dotenv import load_dotenv

# Load environment variables early so keys can be hidden from UI
_ENV_PATH = Path(__file__).parent.parent / ".env"
load_dotenv(_ENV_PATH)

# Setup sys.path so local packages can be resolved from parent directory
PARENT_DIR = Path(__file__).parent.parent
sys.path.insert(0, str(PARENT_DIR))

# Import builders and pipeline
from pipeline.resolver import resolve
from pipeline.fetcher import fetch_all
from pipeline.peer_selector import select_peers
from pipeline.calculator import calculate
from pipeline.analyst import generate_narrative
from builders.build_excel import build_workbook
from builders.build_docx import build_document
from builders.build_pptx import build_presentation
from web_app.database import save_audit_log, get_audit_logs

# Streamlit Page Config
st.set_page_config(
    page_title="Financial Statement Analyzer Cloud Portal",
    page_icon="πŸ“ˆ",
    layout="wide",
    initial_sidebar_state="expanded"
)

# Dark Theme Glassmorphism Custom CSS Injection
st.markdown("""

<style>

    @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap');

    

    html, body, [class*="css"] {

        font-family: 'Inter', sans-serif;

    }

    

    .main {

        background-color: #0A0F1D;

        color: #F3F4F6;

    }

    

    /* Sidebar styling */

    [data-testid="stSidebar"] {

        background-color: #0F162A;

        border-right: 1px solid #1E293B;

    }

    

    /* Title and Subtitle */

    .dashboard-title {

        font-size: 2.5rem;

        font-weight: 700;

        background: linear-gradient(135deg, #60A5FA, #34D399);

        -webkit-background-clip: text;

        -webkit-text-fill-color: transparent;

        margin-bottom: 0.2rem;

    }

    

    .dashboard-subtitle {

        font-size: 1.1rem;

        font-weight: 300;

        color: #9CA3AF;

        margin-bottom: 2rem;

    }

    

    /* Glowing card containers */

    .glass-card {

        background: rgba(30, 41, 59, 0.45);

        border: 1px solid rgba(255, 255, 255, 0.08);

        border-radius: 16px;

        padding: 1.5rem;

        backdrop-filter: blur(12px);

        margin-bottom: 1.5rem;

        box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);

    }

    

    .status-log {

        background-color: #0D1117 !important;

        border: 1px solid #1F242C !important;

        border-radius: 8px !important;

        color: #C9D1D9 !important;

        font-family: 'Consolas', monospace !important;

        padding: 12px;

        max-height: 380px;

        overflow-y: auto;

    }

    

    /* Primary buttons styling */

    div.stButton > button:first-child {

        background: linear-gradient(135deg, #3B82F6, #10B981);

        border: none;

        color: white;

        font-weight: 600;

        padding: 0.6rem 2rem;

        border-radius: 8px;

        transition: transform 0.2s, box-shadow 0.2s;

        box-shadow: 0 0 15px rgba(59, 130, 246, 0.4);

    }

    

    div.stButton > button:first-child:hover {

        transform: translateY(-2px);

        box-shadow: 0 0 25px rgba(16, 185, 129, 0.6);

        color: white;

    }

    

    /* Checkbox layouts styling */

    .stCheckbox > label {

        color: #D1D5DB !important;

    }

</style>

""", unsafe_allow_html=True)

# -- Sidebar -- Connect Your AI ------------------------------------------
st.sidebar.markdown("""

<h2 style='color:#60A5FA;margin-top:0;'>πŸ€– Connect Your AI</h2>

<p style='font-size:0.8rem;color:#9CA3AF;'>

  Bring your own key. Keys live only in your browser session -- never stored on the server.

</p>

""", unsafe_allow_html=True)

st.sidebar.markdown("---")
st.sidebar.markdown("<h3 style='color:#34D399;margin-top:0;'>πŸ‘€ User Profile</h3>", unsafe_allow_html=True)
username = st.sidebar.text_input("Username / Email", value="", placeholder="name@company.com", help="Mandatory for audit trail")
st.sidebar.markdown("---")

# -- FREE OPTIONS -------------------------------------------------------
st.sidebar.markdown("""

<div style='background:rgba(16,185,129,0.08);border:1px solid rgba(16,185,129,0.25);

            border-radius:8px;padding:0.6rem 0.85rem;margin-bottom:0.5rem;'>

  <span style='font-size:0.75rem;font-weight:700;color:#34D399;letter-spacing:0.06em;'>πŸ†“ FREE -- NO CREDIT CARD</span>

</div>

""", unsafe_allow_html=True)

st.sidebar.markdown("""

    **NVIDIA NIM** *(Llama 3.1 405B / Nemotron)*  

    <a href='https://build.nvidia.com/explore/discover' target='_blank' style='font-size:0.75rem;color:#34D399;'>Get free key -> build.nvidia.com</a>

    """, unsafe_allow_html=True)
nvidia_key = st.sidebar.text_input("NVIDIA API Key", value=os.environ.get("NVIDIA_API_KEY", ""), type="password", placeholder="nvapi-...", key="inp_nvidia")

st.sidebar.markdown("""

    **Groq** *(Llama 3.3 70B -- fast & free)*  

    <a href='https://console.groq.com/keys' target='_blank' style='font-size:0.75rem;color:#34D399;'>Get free key -> console.groq.com</a>

    """, unsafe_allow_html=True)
groq_key = st.sidebar.text_input("Groq API Key", value=os.environ.get("GROQ_API_KEY", ""), type="password", placeholder="gsk_...", key="inp_groq")

st.sidebar.markdown("""

    **Google Gemini** *(Gemini 2.0 Flash -- free tier)*  

    <a href='https://aistudio.google.com/app/apikey' target='_blank' style='font-size:0.75rem;color:#34D399;'>Get free key -> aistudio.google.com</a>

    """, unsafe_allow_html=True)
gemini_key = st.sidebar.text_input("Gemini API Key", value=os.environ.get("GEMINI_API_KEY", ""), type="password", placeholder="AIza...", key="inp_gemini")

st.sidebar.markdown("""

    **Hugging Face** *(Serverless API)*  

    <a href='https://huggingface.co/settings/tokens' target='_blank' style='font-size:0.75rem;color:#34D399;'>Get free key -> huggingface.co</a>

    """, unsafe_allow_html=True)
hf_key = st.sidebar.text_input("Hugging Face API Key", value=os.environ.get("HF_API_KEY", ""), type="password", placeholder="hf_...", key="inp_hf")

# -- PAID OPTIONS -------------------------------------------------------
st.sidebar.markdown("""

<div style='background:rgba(99,102,241,0.08);border:1px solid rgba(99,102,241,0.25);

            border-radius:8px;padding:0.6rem 0.85rem;margin:0.5rem 0;'>

  <span style='font-size:0.75rem;font-weight:700;color:#818CF8;letter-spacing:0.06em;'>πŸ’³ OPTIONAL -- PAID APIS</span>

</div>

""", unsafe_allow_html=True)

with st.sidebar.expander("Claude (Anthropic) -- paid", expanded=False):
    st.markdown("<a href='https://console.anthropic.com/' target='_blank' style='font-size:0.75rem;color:#818CF8;'>Get key -> console.anthropic.com</a>", unsafe_allow_html=True)
    claude_key = st.text_input("Claude API Key", value=os.environ.get("CLAUDE_API_KEY", ""), type="password", placeholder="sk-ant-...", key="inp_claude")

with st.sidebar.expander("ChatGPT (OpenAI) -- paid", expanded=False):
    st.markdown("<a href='https://platform.openai.com/api-keys' target='_blank' style='font-size:0.75rem;color:#818CF8;'>Get key -> platform.openai.com</a>", unsafe_allow_html=True)
    openai_key = st.text_input("OpenAI API Key", value=os.environ.get("OPENAI_API_KEY", ""), type="password", placeholder="sk-proj-...", key="inp_openai")

st.sidebar.markdown("---")

# -- Main Dashboard Layout ----------------------------------------------
st.markdown("<h1 class='dashboard-title'>πŸ“ˆ Financial Statement Analyzer</h1>", unsafe_allow_html=True)
st.markdown("<p class='dashboard-subtitle'>Multi-model AI insights scraper and report compilation portal</p>", unsafe_allow_html=True)

st.subheader("Analysis Parameters")

# -- Ticker Search & Suggestion Logic --
search_col1, search_col2 = st.columns([3, 1])
with search_col1:
    search_query = st.text_input("Search Company Name or Ticker", placeholder="e.g. TCS, Reliance, AAPL, MSFT")
with search_col2:
    search_clicked = st.button("πŸ” Find", use_container_width=True)

ticker_input = search_query # default if no suggestions

if search_clicked and search_query:
    with st.spinner("Searching..."):
        try:
            import yahooquery as yq
            res = yq.search(search_query)
            quotes = [q for q in res.get("quotes", []) if q.get("quoteType") in ("EQUITY", "MUTUALFUND", "ETF")]
            st.session_state["ticker_suggestions"] = quotes
        except Exception:
            st.session_state["ticker_suggestions"] = []

if "ticker_suggestions" in st.session_state and st.session_state["ticker_suggestions"]:
    options = {}
    for q in st.session_state["ticker_suggestions"]:
        name = q.get('longname') or q.get('shortname') or q.get('symbol')
        label = f"{name} ({q['symbol']}) - {q.get('exchange', 'Unknown')}"
        options[label] = q['symbol']
        
    options[f"Use exact input: '{search_query}'"] = search_query
    selected_label = st.selectbox("Confirm Company:", list(options.keys()))
    ticker_input = options[selected_label]

col_years, col_refresh = st.columns([1, 1])
with col_years:
    analysis_years = st.selectbox("Analysis Horizon", options=[5, 7, 10], index=0)
with col_refresh:
    force_refresh = st.checkbox("Bypass Cache (Force Scrape)", value=False)

st.markdown("<b>Download Formats:</b>", unsafe_allow_html=True)
chk_excel = st.checkbox("Excel Analysis Workbook (.xlsx)", value=True)
chk_word  = st.checkbox("Word Summary Report (.docx)",     value=True)
chk_pptx  = st.checkbox("PowerPoint Pitch Deck (.pptx)",   value=True)

# -- AI Engine selection -- auto-disable if key not supplied
st.markdown("<b>AI Engines:</b>", unsafe_allow_html=True)

_hint = "<span style='font-size:0.72rem;color:#9CA3AF;'>(add key in sidebar ->)</span>"

engines = []

if st.checkbox(
    "πŸ†“ NVIDIA NIM -- Llama 3.1 405B",
    value=bool(nvidia_key),
    disabled=not nvidia_key,
    help="Free credits. Get key at build.nvidia.com"
):
    engines.append("nvidia")

if st.checkbox(
    "πŸ†“ Groq -- Llama 3.3 70B",
    value=bool(groq_key),
    disabled=not groq_key,
    help="Free. Get key at console.groq.com"
):
    engines.append("groq")
    
if st.checkbox(
    "πŸ†“ Hugging Face -- DeepSeek-R1",
    value=bool(hf_key),
    disabled=not hf_key,
    help="Free via Hugging Face Inference API"
):
    engines.append("huggingface")

if st.checkbox(
    "πŸ†“ Gemini 2.0 Flash (Google)",
    value=bool(gemini_key),
    disabled=not gemini_key,
    help="Free tier. Get key at aistudio.google.com"
):
    engines.append("gemini")

if st.checkbox(
    "πŸ’³ Claude Haiku (Anthropic)",
    value=False,
    disabled=not claude_key,
    help="Paid. Add key in sidebar to enable."
):
    engines.append("claude")

if st.checkbox(
    "πŸ’³ ChatGPT -- GPT-4o mini (OpenAI)",
    value=False,
    disabled=not openai_key,
    help="Paid. Add key in sidebar to enable."
):
    engines.append("openai")

if not groq_key and not gemini_key:
    st.info("πŸ’‘ Add a **free Groq key** in the sidebar to enable AI narratives.", icon="πŸ”‘")

st.markdown("<br>", unsafe_allow_html=True)
start_btn = st.button("Begin Financial Scrape & Analysis")
st.markdown("<br>", unsafe_allow_html=True)

progress_bar_container = st.empty()
status_container = st.empty()
metrics_container = st.empty()
download_container = st.empty()

# -- Analysis Runner ---------------------------------------------------
if start_btn:
    if not username or "@" not in username:
        st.error("Please enter a valid email address in the User Profile section before analyzing.")
    elif not ticker_input:
        st.error("Please enter a stock ticker or company name.")
    elif not (chk_excel or chk_word or chk_pptx):
        st.error("Please select at least one download format checkbox.")
    else:
        # Inject credentials into all modules that cache them at import time
        import config
        import pipeline.analyst
        import pipeline.key_validator
        
        config.GEMINI_API_KEY = gemini_key
        config.CLAUDE_API_KEY = claude_key
        config.OPENAI_API_KEY = openai_key
        config.GROQ_API_KEY   = groq_key
        config.NVIDIA_API_KEY = nvidia_key
        config.HF_API_KEY     = hf_key
        
        os.environ["GEMINI_API_KEY"] = gemini_key
        os.environ["CLAUDE_API_KEY"] = claude_key
        os.environ["OPENAI_API_KEY"] = openai_key
        os.environ["NVIDIA_API_KEY"] = nvidia_key
        os.environ["HF_API_KEY"]     = hf_key
        
        pipeline.analyst.GEMINI_API_KEY = gemini_key
        pipeline.analyst.CLAUDE_API_KEY = claude_key
        pipeline.analyst.OPENAI_API_KEY = openai_key
        pipeline.analyst.GROQ_API_KEY   = groq_key
        pipeline.analyst.NVIDIA_API_KEY = nvidia_key
        
        pipeline.key_validator.GEMINI_API_KEY = gemini_key
        pipeline.key_validator.CLAUDE_API_KEY = claude_key
        pipeline.key_validator.OPENAI_API_KEY = openai_key
        pipeline.key_validator.GROQ_API_KEY   = groq_key
        pipeline.key_validator.NVIDIA_API_KEY = nvidia_key

        # Push into environment for any sub-process
        for k, v in [("GEMINI_API_KEY", gemini_key), ("CLAUDE_API_KEY", claude_key),
                     ("OPENAI_API_KEY", openai_key), ("GROQ_API_KEY", groq_key), 
                     ("NVIDIA_API_KEY", nvidia_key), ("HF_API_KEY", hf_key)]:
            if v: os.environ[k] = v
        
        # Validate that selected engines have keys
        failed_validation = False
        engine_key_map = {
            "nvidia": (nvidia_key, "NVIDIA"),
            "groq":   (groq_key,   "Groq"),
            "gemini": (gemini_key, "Gemini"),
            "claude": (claude_key, "Claude"),
            "openai": (openai_key, "ChatGPT"),
            "huggingface": (hf_key, "Hugging Face"),
        }
        for engine in engines:
            key, label = engine_key_map[engine]
            if not key:
                st.error(f"Please supply a {label} API Key or uncheck the {label} model.")
                failed_validation = True
            
        if not failed_validation:
            try:
                progress_bar = progress_bar_container.progress(0, text="Initializing analysis...")
                with status_container.status("Analysis Execution Status", expanded=True) as status_box:
                    def log_fn(msg):
                        status_box.write(msg)
                    
                    log_fn(f"Starting analysis for: '{ticker_input}'")
                    log_fn(f"Time: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
                    
                    # 1. Resolve Company Info
                    progress_bar.progress(14, text="[1/7] Resolving company metadata...")
                    log_fn("\n[1/7] Resolving company metadata ...")
                    meta = resolve(ticker_input, log_fn=log_fn)
                    log_fn(f"  [OK] {meta.name} | {meta.ticker} | {meta.exchange}")
                    
                    # 2. Fetch Statements
                    progress_bar.progress(28, text="[2/7] Scrape financial statements...")
                    log_fn("\n[2/7] Scrape financial statements ...")
                    fin_data = fetch_all(meta, force_refresh=force_refresh, log_fn=log_fn)
                    
                    # 3. Peers Comparison Selection
                    progress_bar.progress(42, text="[3/7] Benchmarking peer selection...")
                    log_fn("\n[3/7] Benchmarking peer selection ...")
                    fin_data["peers"] = select_peers(meta, log_fn=log_fn)
                    
                    # 4. Math ratios calculations
                    progress_bar.progress(57, text="[4/7] Computing financial ratios...")
                    log_fn("\n[4/7] Computing financial ratios & DuPont decomposition ...")
                    fin_data = calculate(fin_data, meta.to_dict())
                    log_fn("  [OK] Completed ratio math computations")
                    
                    # 5. AI Synthesis Narrative
                    progress_bar.progress(71, text="[5/7] Requesting AI Model narrative...")
                    log_fn("\n[5/7] Requesting AI Model narrative generation ...")
                    narrative = generate_narrative(fin_data, meta.to_dict(), engines, log_fn=log_fn)
                    fin_data.update(narrative)
                    
                    # 6. Generate outputs
                    progress_bar.progress(85, text="[6/7] Building output report files...")
                    log_fn("\n[6/7] Building output report files ...")
                    fin_data["company"] = {
                        "name": meta.name, "ticker": meta.ticker, "exchange": meta.exchange,
                        "currency": fin_data.get("currency", meta.currency),
                        "units": fin_data.get("units", "millions"),
                        "fiscal_year_end": "",
                        "industry": meta.industry, "sector": meta.sector,
                        "business_description": meta.description,
                        "report_date": datetime.date.today().isoformat(),
                        "analysis_period_years": len(fin_data.get("years", [])),
                        "share_price": meta.share_price, "market_cap": meta.mkt_cap,
                    }
                    fin_data["models_used"] = engines
                    fin_data.setdefault("sources", [])
                    
                    ticker_safe = meta.ticker.replace(".", "_").replace("/", "_")
                    timestamp_suffix = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
                    
                    # Exporters path settings
                    excel_bytes = None
                    word_bytes = None
                    pptx_bytes = None
                    
                    out_dir = PARENT_DIR / "outputs"
                    os.makedirs(str(out_dir), exist_ok=True)
                    
                    # Build files in memory / disk
                    if chk_excel:
                        xlsx_path = out_dir / f"{ticker_safe}_financial_analysis_{timestamp_suffix}.xlsx"
                        build_workbook(fin_data, str(xlsx_path))
                        with open(xlsx_path, "rb") as f:
                            excel_bytes = f.read()
                        log_fn(f"  [OK] Created Excel analysis: {xlsx_path.name}")
                        
                    if chk_word:
                        docx_path = out_dir / f"{ticker_safe}_financial_analysis_{timestamp_suffix}.docx"
                        build_document(fin_data, str(docx_path))
                        with open(docx_path, "rb") as f:
                            word_bytes = f.read()
                        log_fn(f"  [OK] Created Word summary: {docx_path.name}")
                        
                    if chk_pptx:
                        pptx_path = out_dir / f"{ticker_safe}_financial_analysis_{timestamp_suffix}.pptx"
                        build_presentation(fin_data, str(pptx_path))
                        with open(pptx_path, "rb") as f:
                            pptx_bytes = f.read()
                        log_fn(f"  [OK] Created PowerPoint Presentation: {pptx_path.name}")
                    
                    progress_bar.progress(100, text="[7/7] Financial analysis process complete!")
                    log_fn(f"\n[7/7] βœ… Financial analysis process complete!")
                    status_box.update(state="complete", expanded=False)
                
                # -- Display Success Metrics
                st.toast('Analysis successfully completed!', icon='πŸŽ‰')
                with metrics_container.container():
                    st.markdown("### πŸ“Š Quick Insights", unsafe_allow_html=True)
                    mc1, mc2, mc3, mc4 = st.columns(4)
                    mc1.metric("Company", meta.name, meta.ticker)
                    
                    # Format Price
                    price_val = meta.share_price
                    price_str = f"{fin_data.get('currency', '')} {price_val:,.2f}" if price_val else "N/A"
                    mc2.metric("Share Price", price_str)
                    
                    # Format Market Cap
                    mc_val = meta.mkt_cap
                    mc_str = "N/A"
                    if mc_val:
                        if mc_val > 1e12:
                            mc_str = f"{fin_data.get('currency', '')} {mc_val/1e12:,.2f} Trillion"
                        elif mc_val > 1e9:
                            mc_str = f"{fin_data.get('currency', '')} {mc_val/1e9:,.2f} Billion"
                        else:
                            mc_str = f"{fin_data.get('currency', '')} {mc_val/1e6:,.2f} Million"
                    mc3.metric("Market Cap", mc_str)
                    
                    # Data quality badge
                    dq = fin_data.get("data_quality", 0)
                    if dq >= 80:
                        dq_color, dq_label = "#22c55e", "Excellent"
                    elif dq >= 55:
                        dq_color, dq_label = "#f59e0b", "Partial"
                    else:
                        dq_color, dq_label = "#ef4444", "Low"
                    mc4.markdown(
                        f"""<div style='background:rgba(255,255,255,0.05);border-radius:12px;

                        padding:14px 12px;text-align:center;border:1px solid rgba(255,255,255,0.1)'>

                        <div style='font-size:0.75rem;color:#9CA3AF;margin-bottom:4px'>Data Quality</div>

                        <div style='font-size:1.6rem;font-weight:700;color:{dq_color}'>{dq}%</div>

                        <div style='font-size:0.75rem;color:{dq_color}'>{dq_label}</div>

                        </div>""",
                        unsafe_allow_html=True
                    )
                    
                    # Source attribution
                    src = fin_data.get("source", "")
                    if src:
                        st.caption(f"Data sources: {src}")
                    st.markdown("<br>", unsafe_allow_html=True)
                
                # -- Generate download buttons in UI
                with download_container.container():
                    st.success("Download ready!")
                    dl_col1, dl_col2, dl_col3, dl_col4 = st.columns(4)
                    
                    download_files = []
                    
                    if excel_bytes:
                        dl_col1.download_button(
                            label="πŸ“₯ Download Excel",
                            data=excel_bytes,
                            file_name=f"{ticker_safe}_analysis.xlsx",
                            mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                            use_container_width=True
                        )
                        download_files.append((f"{ticker_safe}_analysis.xlsx", excel_bytes))
                        
                    if word_bytes:
                        dl_col2.download_button(
                            label="πŸ“„ Download Word",
                            data=word_bytes,
                            file_name=f"{ticker_safe}_report.docx",
                            mime="application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                            use_container_width=True
                        )
                        download_files.append((f"{ticker_safe}_report.docx", word_bytes))
                        
                    if pptx_bytes:
                        dl_col3.download_button(
                            label="πŸ“Š Download PPTX",
                            data=pptx_bytes,
                            file_name=f"{ticker_safe}_deck.pptx",
                            mime="application/vnd.openxmlformats-officedocument.presentationml.presentation",
                            use_container_width=True
                        )
                        download_files.append((f"{ticker_safe}_deck.pptx", pptx_bytes))

                    if len(download_files) > 1:
                        import zipfile
                        import io
                        zip_buffer = io.BytesIO()
                        with zipfile.ZipFile(zip_buffer, "a", zipfile.ZIP_DEFLATED, False) as zip_file:
                            for file_name, data in download_files:
                                zip_file.writestr(file_name, data)
                        dl_col4.download_button(
                            label="πŸ“¦ Download All (ZIP)",
                            data=zip_buffer.getvalue(),
                            file_name=f"{ticker_safe}_all_reports.zip",
                            mime="application/zip",
                            use_container_width=True
                        )
                # Write search activity log to audit table
                selected_formats = [f for f, ok in [("xlsx", chk_excel), ("docx", chk_word), ("pptx", chk_pptx)] if ok]
                save_audit_log(
                    user_email=username,
                    company_ticker=meta.ticker,
                    company_name=meta.name,
                    selected_formats=selected_formats,
                    ai_models=engines,
                    status="completed"
                )
                
            except Exception as e:
                log_fn(f"\n❌ Pipeline failed: {e}")
                import traceback
                log_fn(traceback.format_exc())
                st.error(f"Analysis failed: {e}")
                
                # Save audit log failure
                try:
                    selected_formats = [f for f, ok in [("xlsx", chk_excel), ("docx", chk_word), ("pptx", chk_pptx)] if ok]
                    save_audit_log(
                        user_email=username,
                        company_ticker=ticker_input,
                        company_name="N/A",
                        selected_formats=selected_formats,
                        ai_models=engines,
                        status="failed"
                    )
                except:
                    pass

# -- Audit History Panel Table -------------------------------------------
st.markdown("---")
st.markdown("<h3 style='color:#60A5FA;margin-top:0;'>πŸ“œ Query Audit Trail & History</h3>", unsafe_allow_html=True)
st.markdown("<p style='font-size:0.85rem;color:#9CA3AF;'>Past analysis runs requested by team members.</p>", unsafe_allow_html=True)

history_logs = get_audit_logs()
if history_logs:
    st.table(history_logs)
else:
    st.info("No queries have been run yet.")