"""SEC filings tab rendered as a client-side HTML surface.""" from datetime import date as _date from datetime import datetime as _dt from html import escape as _esc import streamlit as st import streamlit.components.v1 as components from services.data_service import get_company_info, get_latest_price, get_sec_filings _XMAP = {"NYQ": "NYSE", "NMS": "NASDAQ", "NGM": "NASDAQ", "NCM": "NASDAQ", "ASE": "AMEX"} _FORM_DESCRIPTIONS = { "10-K": "Annual report", "10-Q": "Quarterly report", "8-K": "Material event disclosure", "DEF 14A": "Proxy statement", "S-1": "IPO registration", "S-3": "Securities registration", "4": "Insider ownership change", "SC 13G": "Beneficial ownership (passive)", "SC 13D": "Beneficial ownership (active)", } def _normalize_date(raw) -> str | None: if raw is None: return None if isinstance(raw, _dt): return raw.strftime("%Y-%m-%d") if isinstance(raw, _date): return raw.strftime("%Y-%m-%d") text = str(raw).strip() if not text: return None if " " in text: text = text.split(" ", 1)[0] if "T" in text: text = text.split("T", 1)[0] for fmt in ("%Y-%m-%d", "%m/%d/%Y", "%Y/%m/%d"): try: return _dt.strptime(text, fmt).strftime("%Y-%m-%d") except Exception: pass if len(text) >= 10: cand = text[:10] try: return _dt.strptime(cand, "%Y-%m-%d").strftime("%Y-%m-%d") except Exception: pass return None def render_filings(ticker: str): import json as _json info = get_company_info(ticker) or {} price = get_latest_price(ticker) filings = get_sec_filings(ticker) if not filings: st.info("No SEC filing data available for this ticker.") return rows = [] for filing in filings: filing = filing or {} form_raw = filing.get("type") form = str(form_raw).strip() if form_raw is not None else "" if not form: form = "Other" date_str = _normalize_date(filing.get("date")) month_str = date_str[:7] if date_str else None title_raw = filing.get("title") title = str(title_raw).strip() if title_raw is not None else "" if not title: title = _FORM_DESCRIPTIONS.get(form) exhibits = filing.get("exhibits") url = None if isinstance(exhibits, dict): direct = exhibits.get(form) if direct: url = str(direct).strip() or None if url is None: edgar = filing.get("edgarUrl") if edgar: url = str(edgar).strip() or None rows.append( { "date": date_str, "month": month_str, "form": form, "title": title or None, "url": url, "has_exhibits": bool(isinstance(exhibits, dict) and len(exhibits) > 0), } ) rows.sort(key=lambda r: (r.get("date") is not None, r.get("date") or ""), reverse=True) if not rows: st.info("No SEC filing data available for this ticker.") return n_rows = max(len(rows), 20) height = 1280 + n_rows * 30 def _safe_float(value): try: return float(value) except (TypeError, ValueError): return None current_price = info.get("currentPrice") or info.get("regularMarketPrice") or price prev_close = info.get("previousClose") cur_num = _safe_float(current_price) prev_num = _safe_float(prev_close) if cur_num is not None and prev_num is not None and prev_num > 0: chg_pct = (cur_num - prev_num) / prev_num * 100.0 chg_sign = "+" if chg_pct >= 0 else "" chg_arrow = "▲" if chg_pct >= 0 else "▼" chg_str = chg_arrow + " " + chg_sign + "{:.2f}%".format(chg_pct) chg_cls = "chg-pos" if chg_pct >= 0 else "chg-neg" else: chg_str = "—" chg_cls = "" exchange_raw = str(info.get("exchange") or "") exchange = _XMAP.get(exchange_raw, exchange_raw) or "—" co_name = _esc(info.get("longName") or info.get("shortName") or ticker.upper()) price_str = "${:,.2f}".format(cur_num) if cur_num is not None else "—" rows_js = "const FILINGS=" + _json.dumps(rows) + ";" meta_js = "const FILINGS_META=" + _json.dumps({"ticker": ticker.upper(), "rows": len(rows)}) + ";" plotly_cdn = "" _ROOT = ( "" ) fonts_link = ( "" "" ) _FI_CSS = """""" ctx_html = ( '
' + '' + _esc(ticker.upper()) + "" + '' + co_name + "" + 'Filings' + '
' + "" + _esc(exchange) + "" + '' + price_str + "" + '' + chg_str + "" + "
" ) lede_html = ( '
' + '
' + 'Regulatory' + '
How the company is reporting
' + '

SEC filing flow for ' + _esc(ticker.upper()) + ' across annual, quarterly, and event-driven forms. Data is sourced from Yahoo Finance via yfinance and links route to EDGAR documents when available.

' + "
" + '
' + '
Sourceyfinance
' + '
FeedSEC filings
' + '
Filings' + str(len(rows)) + "
" + "
" ) controls_html = ( '
' + '
' + 'Range' + '' + '' + '' + "
" + '
' + 'Form' + "
" + "
" ) foot_html = ( '
' + "SEC filing data provided by Yahoo Finance via yfinance · Filing links route to EDGAR documents when available · Form labels and counts reflect the current feed returned for this ticker" + "
" ) js = ( "" ) doc = ( "" + plotly_cdn + fonts_link + _ROOT + _FI_CSS + "
" + ctx_html + '
' + lede_html + controls_html + '
' + '
' + '
Monthly filing cadence
' + '
Form mix
' + "
" + '
' + '
' + foot_html + "
" + js + "" ) components.html(doc, height=height, scrolling=False)