aboutsummaryrefslogtreecommitdiff
path: root/app.py
diff options
context:
space:
mode:
Diffstat (limited to 'app.py')
-rw-r--r--app.py156
1 files changed, 156 insertions, 0 deletions
diff --git a/app.py b/app.py
new file mode 100644
index 0000000..78b503f
--- /dev/null
+++ b/app.py
@@ -0,0 +1,156 @@
+"""Prism — Financial Analysis Dashboard"""
+import streamlit as st
+from dotenv import load_dotenv
+
+load_dotenv()
+
+st.set_page_config(
+ page_title="Prism",
+ page_icon="🔷",
+ layout="wide",
+ initial_sidebar_state="expanded",
+)
+
+# ── Global CSS ────────────────────────────────────────────────────────────────
+st.markdown("""
+<style>
+ /* Tighten metric cards */
+ [data-testid="metric-container"] {
+ padding: 0.4rem 0.6rem !important;
+ }
+ [data-testid="stMetricLabel"] {
+ font-size: 0.72rem !important;
+ font-weight: 500;
+ color: #9aa0b0 !important;
+ letter-spacing: 0.03em;
+ text-transform: uppercase;
+ }
+ [data-testid="stMetricValue"] {
+ font-size: 1.1rem !important;
+ font-weight: 600;
+ letter-spacing: -0.01em;
+ }
+ [data-testid="stMetricDelta"] svg { width: 0.75rem; height: 0.75rem; }
+ [data-testid="stMetricDelta"] > div { font-size: 0.78rem !important; }
+
+ /* Tighter headings */
+ h1 { font-size: 1.5rem !important; font-weight: 700 !important; }
+ h2 { font-size: 1.25rem !important; font-weight: 600 !important; }
+ h3 { font-size: 1.05rem !important; font-weight: 600 !important; }
+
+ /* Tighter sidebar text */
+ [data-testid="stSidebar"] .stCaption p {
+ font-size: 0.78rem !important;
+ line-height: 1.5 !important;
+ }
+
+ /* Slim dividers */
+ hr { margin: 0.5rem 0 !important; }
+
+ /* Tab labels */
+ [data-testid="stTab"] p { font-size: 0.85rem !important; font-weight: 500; }
+
+ /* Reduce default top padding */
+ .block-container { padding-top: 1rem !important; }
+</style>
+""", unsafe_allow_html=True)
+
+from components.market_bar import render_market_bar
+from components.overview import render_overview
+from components.financials import render_financials
+from components.valuation import render_valuation
+from components.news import render_news
+from services.data_service import get_company_info, search_tickers
+
+
+# ── Sidebar ──────────────────────────────────────────────────────────────────
+
+with st.sidebar:
+ st.markdown("### 🔷 Prism")
+ st.caption("Financial Analysis Dashboard")
+ st.divider()
+
+ # Search input
+ query = st.text_input(
+ "Search company or ticker",
+ placeholder="e.g. Apple, AAPL, MSFT…",
+ key="search_query",
+ ).strip()
+
+ # Autocomplete: show results if query looks like a search term (not a bare ticker)
+ selected_symbol = None
+ if query:
+ results = search_tickers(query)
+ if results:
+ options = {f"{r['symbol']} — {r['name']} ({r['exchange']})": r["symbol"] for r in results}
+ choice = st.selectbox(
+ "Select",
+ options=list(options.keys()),
+ label_visibility="collapsed",
+ )
+ selected_symbol = options[choice]
+ else:
+ # Treat the raw input as a direct ticker
+ selected_symbol = query.upper()
+
+ if st.button("Analyze", use_container_width=True, type="primary"):
+ if selected_symbol:
+ st.session_state["ticker"] = selected_symbol
+
+ ticker = st.session_state.get("ticker", "AAPL")
+
+ # Quick company info in sidebar
+ st.divider()
+ with st.spinner(""):
+ info = get_company_info(ticker)
+ if info:
+ st.caption(info.get("longName", ticker))
+ st.caption(f"Exchange: {info.get('exchange', '—')}")
+ st.caption(f"Currency: {info.get('currency', 'USD')}")
+ st.caption(f"Sector: {info.get('sector', '—')}")
+ employees = info.get("fullTimeEmployees")
+ st.caption(f"Employees: {employees:,}" if isinstance(employees, int) else "Employees: —")
+ website = info.get("website")
+ if website:
+ st.markdown(f"[🌐 Website]({website})")
+
+
+# ── Market Bar ────────────────────────────────────────────────────────────────
+
+with st.container():
+ render_market_bar()
+
+st.divider()
+
+# ── Main Content ──────────────────────────────────────────────────────────────
+
+tab_overview, tab_financials, tab_valuation, tab_news = st.tabs([
+ "📈 Overview",
+ "📊 Financials",
+ "💰 Valuation",
+ "📰 News",
+])
+
+with tab_overview:
+ try:
+ render_overview(ticker)
+ except Exception as e:
+ st.error(f"Overview failed to load: {e}")
+
+with tab_financials:
+ try:
+ render_financials(ticker)
+ except Exception as e:
+ st.error(f"Financials failed to load: {e}")
+
+with tab_valuation:
+ try:
+ render_valuation(ticker)
+ except Exception as e:
+ st.error(f"Valuation failed to load: {e}")
+
+with tab_news:
+ try:
+ render_news(ticker)
+ except Exception as e:
+ st.error(f"News failed to load: {e}")