aboutsummaryrefslogtreecommitdiff
path: root/components/filings.py
diff options
context:
space:
mode:
Diffstat (limited to 'components/filings.py')
-rw-r--r--components/filings.py115
1 files changed, 115 insertions, 0 deletions
diff --git a/components/filings.py b/components/filings.py
new file mode 100644
index 0000000..d366a2b
--- /dev/null
+++ b/components/filings.py
@@ -0,0 +1,115 @@
+"""SEC filings — recent 10-K, 10-Q, 8-K and other forms with direct links."""
+import pandas as pd
+import streamlit as st
+
+from services.fmp_service import get_sec_filings
+
+
+_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)",
+}
+
+_FORM_COLORS = {
+ "10-K": "rgba(79,142,247,0.15)",
+ "10-Q": "rgba(130,224,170,0.15)",
+ "8-K": "rgba(247,162,79,0.15)",
+}
+
+
+def _describe_form(form_type: str) -> str:
+ return _FORM_DESCRIPTIONS.get(form_type.strip().upper(), "")
+
+
+def render_filings(ticker: str):
+ with st.spinner("Loading SEC filings…"):
+ raw = get_sec_filings(ticker)
+
+ if not raw:
+ st.info("No SEC filing data available. Requires FMP API key.")
+ return
+
+ # Collect unique form types for filter
+ form_types = sorted({str(r.get("type") or r.get("form") or "").strip() for r in raw if r.get("type") or r.get("form")})
+ filter_options = ["All"] + [t for t in ["10-K", "10-Q", "8-K"] if t in form_types] + \
+ [t for t in form_types if t not in ("10-K", "10-Q", "8-K")]
+
+ filter_col, _ = st.columns([1, 3])
+ with filter_col:
+ selected_type = st.selectbox("Form type", options=filter_options, index=0, key="filings_filter")
+
+ # Summary counts
+ counts = {}
+ for r in raw:
+ ft = str(r.get("type") or r.get("form") or "Other").strip()
+ counts[ft] = counts.get(ft, 0) + 1
+
+ priority_forms = ["10-K", "10-Q", "8-K"]
+ summary_forms = [f for f in priority_forms if f in counts]
+ if summary_forms:
+ cols = st.columns(len(summary_forms))
+ for col, ft in zip(cols, summary_forms):
+ col.metric(ft, counts[ft])
+ st.write("")
+
+ # Filter
+ if selected_type == "All":
+ filtered = raw
+ else:
+ filtered = [
+ r for r in raw
+ if str(r.get("type") or r.get("form") or "").strip() == selected_type
+ ]
+
+ if not filtered:
+ st.info("No filings match the current filter.")
+ return
+
+ # Build display table
+ rows = []
+ for item in filtered:
+ form_type = str(item.get("type") or item.get("form") or "—").strip()
+ date = str(item.get("filingDate") or item.get("date") or "")[:10]
+ description = item.get("description") or _describe_form(form_type) or "—"
+ url = item.get("link") or item.get("finalLink") or item.get("url") or ""
+ rows.append({
+ "Date": date,
+ "Form": form_type,
+ "Description": description,
+ "Link": url,
+ })
+
+ df = pd.DataFrame(rows)
+
+ # Render as clickable table — Streamlit doesn't natively support link columns,
+ # so we render each row as a compact card for the most recent 30 entries.
+ for _, row in df.head(30).iterrows():
+ form = row["Form"]
+ color = _FORM_COLORS.get(form, "rgba(255,255,255,0.05)")
+ date = row["Date"]
+ desc = row["Description"]
+ link = row["Link"]
+
+ with st.container():
+ left, right = st.columns([5, 1])
+ with left:
+ st.markdown(
+ f"<div style='background:{color};padding:6px 10px;border-radius:4px;margin-bottom:2px'>"
+ f"<strong>{form}</strong> &nbsp;·&nbsp; <span style='color:#9aa0b0;font-size:0.82rem'>{date}</span><br>"
+ f"<span style='font-size:0.85rem'>{desc}</span>"
+ f"</div>",
+ unsafe_allow_html=True,
+ )
+ with right:
+ if link:
+ st.markdown(
+ f"<div style='padding-top:8px'><a href='{link}' target='_blank'>🔗 View</a></div>",
+ unsafe_allow_html=True,
+ )