mirror of
https://github.com/shawnkim1997/All-in-one-Financial-Analysis.git
synced 2026-08-01 12:57:45 +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>
91 lines
4.5 KiB
Python
91 lines
4.5 KiB
Python
"""
|
|
UI helper functions for rendering analyst consensus, sensitivity tables, etc.
|
|
"""
|
|
import pandas as pd
|
|
import streamlit as st
|
|
|
|
from utils.dcf import excel_style_dcf
|
|
|
|
|
|
def _render_analyst_consensus(ticker: str):
|
|
"""Render analyst consensus rating with visual badge and target prices."""
|
|
from data.valuation import get_analyst_consensus
|
|
consensus = get_analyst_consensus(ticker)
|
|
if not consensus:
|
|
st.caption("Analyst consensus data not available.")
|
|
return
|
|
|
|
rec = (consensus.get("recommendationKey") or "N/A").upper()
|
|
target_mean = consensus.get("targetMeanPrice")
|
|
target_high = consensus.get("targetHighPrice")
|
|
target_low = consensus.get("targetLowPrice")
|
|
num_analysts = consensus.get("numberOfAnalystOpinions", "N/A")
|
|
current = consensus.get("currentPrice")
|
|
|
|
# Rating badge colors
|
|
if rec in ("BUY", "STRONG_BUY", "STRONG BUY"):
|
|
badge_bg = "rgba(52, 211, 153, 0.15)"
|
|
badge_border = "rgba(52, 211, 153, 0.4)"
|
|
badge_color = "#34D399"
|
|
elif rec in ("SELL", "STRONG_SELL", "STRONG SELL"):
|
|
badge_bg = "rgba(248, 113, 113, 0.15)"
|
|
badge_border = "rgba(248, 113, 113, 0.4)"
|
|
badge_color = "#F87171"
|
|
else:
|
|
badge_bg = "rgba(251, 191, 36, 0.15)"
|
|
badge_border = "rgba(251, 191, 36, 0.4)"
|
|
badge_color = "#FBBF24"
|
|
rec_display = rec.replace("_", " ")
|
|
|
|
upside = ""
|
|
if target_mean and current and current > 0:
|
|
upside_pct = (target_mean - current) / current * 100
|
|
upside_color = "#34D399" if upside_pct > 0 else "#F87171"
|
|
upside = f'<span style="color: {upside_color}; font-family: JetBrains Mono, monospace; font-weight: 600; margin-left: 12px;">{upside_pct:+.1f}% implied</span>'
|
|
|
|
if target_mean:
|
|
st.markdown(f"""
|
|
<div style="display: flex; align-items: center; gap: 16px; padding: 16px; background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.06); border-radius: 10px; margin-bottom: 12px;">
|
|
<div>
|
|
<div style="color: #6B7280; font-size: 0.65rem; font-weight: 600; letter-spacing: 1px; text-transform: uppercase;">ANALYST RATING</div>
|
|
<span style="display: inline-block; padding: 4px 14px; border-radius: 6px; font-weight: 700; font-family: JetBrains Mono, monospace; background: {badge_bg}; border: 1px solid {badge_border}; color: {badge_color}; font-size: 1.1rem; margin-top: 4px;">{rec_display}</span>
|
|
{upside}
|
|
</div>
|
|
<div style="margin-left: auto; display: flex; gap: 24px;">
|
|
<div style="text-align: center;">
|
|
<div style="color: #6B7280; font-size: 0.6rem; letter-spacing: 1px;">TARGET (MEAN)</div>
|
|
<div style="color: #F3F4F6; font-size: 1.1rem; font-family: JetBrains Mono, monospace; font-weight: 700;">${target_mean:,.2f}</div>
|
|
</div>
|
|
<div style="text-align: center;">
|
|
<div style="color: #6B7280; font-size: 0.6rem; letter-spacing: 1px;">HIGH</div>
|
|
<div style="color: #34D399; font-size: 0.95rem; font-family: JetBrains Mono, monospace;">${target_high:,.2f}</div>
|
|
</div>
|
|
<div style="text-align: center;">
|
|
<div style="color: #6B7280; font-size: 0.6rem; letter-spacing: 1px;">LOW</div>
|
|
<div style="color: #F87171; font-size: 0.95rem; font-family: JetBrains Mono, monospace;">${target_low:,.2f}</div>
|
|
</div>
|
|
<div style="text-align: center;">
|
|
<div style="color: #6B7280; font-size: 0.6rem; letter-spacing: 1px;">ANALYSTS</div>
|
|
<div style="color: #D1D5DB; font-size: 0.95rem; font-family: JetBrains Mono, monospace;">{num_analysts}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
else:
|
|
st.caption("Analyst targets not available.")
|
|
|
|
|
|
def _render_sensitivity_table(fcf: float, total_debt: float, cash: float, shares: float):
|
|
"""Render DCF sensitivity table: WACC vs Terminal Growth Rate."""
|
|
wacc_range = [0.065, 0.070, 0.075, 0.080, 0.085, 0.090, 0.095, 0.100]
|
|
tgr_range = [0.020, 0.025, 0.030, 0.035, 0.040]
|
|
rows = []
|
|
for tgr in tgr_range:
|
|
row = {"TGR": f"{tgr*100:.1f}%"}
|
|
for w in wacc_range:
|
|
res = excel_style_dcf(fcf, w, tgr, 0.10, total_debt, cash, shares)
|
|
val = res.get("value_per_share", 0)
|
|
row[f"{w*100:.1f}%"] = f"${val:,.0f}" if val and val > 0 else "N/A"
|
|
rows.append(row)
|
|
return pd.DataFrame(rows).set_index("TGR")
|