mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-07-27 18:47:44 +00:00
d337c63976
Architecture (3,909-line monolith → 28 focused modules, all < 300 lines):
- config/: constants.py (company lists, row maps, Damodaran baselines), theme.py (CSS/HTML)
- utils/: prefs, formatting, ticker, dcf, charts, ui_helpers
- data/: sec_parser, sec_fetcher, sec_downloader, financials, fundamentals,
valuation, ratios, scores, scores_ai, market
- ai/: gemini_core, gemini_sec, gemini_insights
- views/: sidebar, tab1_quant, tab1_ai, tab1_filings, tab2_dcf,
tab3_comps, tab4_news, tab5_markets, tab6_crypto, tab7_technical
- app.py: thin orchestrator (~118 lines)
- Strict unidirectional dependency graph (no circular imports)
- All @st.cache_data TTLs and st.session_state keys preserved identically
SEC filing viewer fix:
- Rebuilt EDGAR fetch chain: company_tickers.json → CIK → submissions API
→ filings.recent.primaryDocument[] (replaces deprecated directory.item)
- Filing type selectbox (10-K, 10-Q, 8-K, 20-F, 6-K) connected to backend
- Native HTML rendered via streamlit.components.v1.html() with CSS reset
- Errors surfaced explicitly with st.error()
- DART direct links restored for Korean-listed companies
.gitignore: data/ → data/*.json + data/*.html (preserve Python modules)
README: full rewrite for master's portfolio — 7-tab layout, architecture
diagram, modular structure tree, technical challenges, design rationale
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
83 lines
3.9 KiB
Python
83 lines
3.9 KiB
Python
import pandas as pd
|
|
import streamlit as st
|
|
from config.constants import SECTORS
|
|
from data.ratios import get_comps_data
|
|
from ai.gemini_insights import get_industry_outlook
|
|
|
|
|
|
def render_tab3(ticker):
|
|
st.subheader("Top-Down Sector Analysis")
|
|
st.markdown("Select an **industry** to load peer multiples (Forward P/E, EV/EBITDA, P/B). Green = lowest (undervalued), Red = highest. Optionally generate an **AI Industry Outlook**.")
|
|
sector_options = list(SECTORS.keys())
|
|
selected_industry = st.selectbox("Select industry", sector_options, key="sector_select")
|
|
tickers_list = list(SECTORS.get(selected_industry, []))
|
|
if not tickers_list:
|
|
st.warning("No tickers defined for this industry.")
|
|
else:
|
|
with st.spinner("Fetching market data..."):
|
|
df_comps = get_comps_data(tuple(tickers_list))
|
|
if df_comps.empty:
|
|
st.warning("Could not fetch comps from yfinance. One or more tickers may have failed; try again later.")
|
|
else:
|
|
df_display = df_comps.copy()
|
|
for col in ["Forward P/E", "EV/EBITDA", "P/B"]:
|
|
if col not in df_display.columns:
|
|
continue
|
|
df_display[col] = df_display[col].apply(
|
|
lambda x: "N/A" if (x is None or (isinstance(x, float) and pd.isna(x))) else x
|
|
)
|
|
try:
|
|
styled = df_comps.style
|
|
for col in ["Forward P/E", "EV/EBITDA", "P/B"]:
|
|
if col not in df_comps.columns:
|
|
continue
|
|
s = pd.to_numeric(df_comps[col], errors="coerce")
|
|
valid = s.dropna()
|
|
if len(valid) < 2:
|
|
continue
|
|
lo, hi = valid.min(), valid.max()
|
|
if lo == hi:
|
|
continue
|
|
def color_fn(v, lo_val=lo, hi_val=hi):
|
|
if pd.isna(v):
|
|
return ""
|
|
try:
|
|
x = float(v)
|
|
except (TypeError, ValueError):
|
|
return ""
|
|
if x <= lo_val:
|
|
return "background-color: rgba(0, 200, 83, 0.35); color: #0d5c2e"
|
|
if x >= hi_val:
|
|
return "background-color: rgba(255, 82, 82, 0.35); color: #b71c1c"
|
|
return ""
|
|
styled = styled.map(color_fn, subset=[col])
|
|
styled = styled.format(subset=["Forward P/E", "EV/EBITDA", "P/B"], formatter=lambda x: "N/A" if (pd.isna(x) or x is None) else f"{x:.2f}")
|
|
st.dataframe(styled, use_container_width=True, hide_index=True)
|
|
except Exception:
|
|
st.dataframe(df_display, use_container_width=True, hide_index=True)
|
|
st.caption("Lowest multiple in each column = green (relatively undervalued); highest = red.")
|
|
|
|
st.markdown("---")
|
|
st.markdown("#### AI Industry Outlook")
|
|
if st.button("Generate Industry Outlook", key="industry_outlook_btn"):
|
|
if not tickers_list:
|
|
st.error("Select an industry above first.")
|
|
elif not st.session_state.get("google_api_key"):
|
|
st.error("Enter your Google API Key in the sidebar.")
|
|
else:
|
|
try:
|
|
with st.spinner("Generating industry outlook with Gemini..."):
|
|
report = get_industry_outlook(
|
|
st.session_state["google_api_key"],
|
|
selected_industry,
|
|
tickers_list,
|
|
)
|
|
st.success("Done.")
|
|
st.markdown(report)
|
|
except RuntimeError as e:
|
|
st.error(str(e))
|
|
except Exception as e:
|
|
st.error("Failed to generate outlook. See details below.")
|
|
with st.expander("Error details"):
|
|
st.code(repr(e), language="text")
|