create-hedgeboard 1.0.7 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,157 +0,0 @@
1
- """Company overview module.
2
-
3
- Generates a comprehensive company profile given a ticker symbol:
4
- - Company info (name, sector, exchange)
5
- - Latest filings
6
- - Key financial metrics (revenue, net income, EPS, assets)
7
- - Revenue trend chart
8
- - Financial summary table
9
-
10
- Usage:
11
- from modules.company_overview import company_overview
12
- company_overview("TWLO")
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- from data.sec import HedgeBoardClient
18
- from viz.charts import line_chart, bar_chart
19
- from viz.tables import financial_table
20
- from viz.dashboard import render_dashboard
21
-
22
-
23
- def company_overview(ticker: str, serve: bool = True) -> None:
24
- """Generate a company overview dashboard.
25
-
26
- Args:
27
- ticker: Company ticker symbol (e.g., "TWLO", "AAPL").
28
- serve: If True, open dashboard in browser.
29
- """
30
- hb = HedgeBoardClient()
31
- ticker = ticker.upper()
32
-
33
- # 1. Company info
34
- company = hb.get_company(ticker)
35
- if not company:
36
- print(f"❌ Company not found: {ticker}")
37
- return
38
-
39
- name = company.get("name", ticker)
40
- exchange = company.get("exchange", "")
41
- sic_desc = company.get("sic_description", "")
42
-
43
- # 2. Latest filings
44
- annual_filings = hb.get_filings(ticker, form_type="10-K", limit=5)
45
- quarterly_filings = hb.get_filings(ticker, form_type="10-Q", limit=4)
46
-
47
- # 3. Key financials
48
- financials = hb.get_key_financials(ticker, form_type="10-K", periods=5)
49
-
50
- # Build sections
51
- sections = []
52
-
53
- # --- Company Header ---
54
- info_section = f"""## {name} ({ticker})
55
- **Exchange:** {exchange} · **Sector:** {sic_desc}
56
- **CIK:** {company.get('cik', 'N/A')}
57
- """
58
- sections.append(info_section)
59
-
60
- # --- Revenue Chart ---
61
- revenue_key = None
62
- for key in ["Revenues", "RevenueFromContractWithCustomerExcludingAssessedTax"]:
63
- if key in financials:
64
- revenue_key = key
65
- break
66
-
67
- if revenue_key:
68
- rev_data = sorted(financials[revenue_key], key=lambda x: x.get("period_end", ""))
69
- labels = [f.get("period_end", "")[:4] for f in rev_data]
70
- values = [float(f.get("value", 0)) / 1_000_000 for f in rev_data]
71
-
72
- sections.append(
73
- line_chart(
74
- labels=labels,
75
- datasets=[{"label": "Revenue ($M)", "data": values}],
76
- title=f"{ticker} Annual Revenue",
77
- )
78
- )
79
-
80
- # --- Financial Summary Table ---
81
- table_rows = []
82
- concept_labels = {
83
- "Revenues": "Revenue",
84
- "RevenueFromContractWithCustomerExcludingAssessedTax": "Revenue",
85
- "NetIncomeLoss": "Net Income",
86
- "EarningsPerShareDiluted": "EPS (Diluted)",
87
- "Assets": "Total Assets",
88
- "Liabilities": "Total Liabilities",
89
- "StockholdersEquity": "Stockholders' Equity",
90
- "OperatingIncomeLoss": "Operating Income",
91
- "CashAndCashEquivalentsAtCarryingValue": "Cash & Equivalents",
92
- }
93
-
94
- # Collect periods
95
- all_periods = set()
96
- for facts in financials.values():
97
- for f in facts:
98
- all_periods.add(f.get("period_end", "")[:4])
99
- periods = sorted(all_periods)[-5:] # Last 5 years
100
-
101
- if periods:
102
- headers = ["Metric"] + periods
103
- for concept, label in concept_labels.items():
104
- if concept not in financials:
105
- continue
106
- row = [label]
107
- facts_by_period = {
108
- f.get("period_end", "")[:4]: f for f in financials[concept]
109
- }
110
- for period in periods:
111
- fact = facts_by_period.get(period)
112
- if fact:
113
- val = float(fact.get("value", 0))
114
- if concept == "EarningsPerShareDiluted":
115
- row.append(f"${val:.2f}")
116
- else:
117
- row.append(val)
118
- else:
119
- row.append(None)
120
- table_rows.append(row)
121
-
122
- if table_rows:
123
- sections.append(
124
- financial_table(
125
- headers=headers,
126
- rows=table_rows,
127
- title="Key Financial Metrics (Annual)",
128
- )
129
- )
130
-
131
- # --- Filings List ---
132
- filing_lines = []
133
- for f in (annual_filings or [])[:5]:
134
- filing_lines.append(
135
- f"- **{f.get('form_type', '')}** — {f.get('filing_date', 'N/A')}"
136
- )
137
- for f in (quarterly_filings or [])[:4]:
138
- filing_lines.append(
139
- f"- **{f.get('form_type', '')}** — {f.get('filing_date', 'N/A')}"
140
- )
141
-
142
- if filing_lines:
143
- sections.append("### Recent Filings\n" + "\n".join(filing_lines))
144
-
145
- # --- Source Citation ---
146
- sections.append(
147
- f"*Source: SEC EDGAR via HedgeBoard API. "
148
- f"Data as of latest available filing.*"
149
- )
150
-
151
- # Render
152
- render_dashboard(
153
- title=f"{name} — Company Overview",
154
- sections=sections,
155
- output_name=f"{ticker.lower()}_overview",
156
- serve=serve,
157
- )
@@ -1,2 +0,0 @@
1
- requests>=2.31
2
- python-dotenv>=1.0
@@ -1 +0,0 @@
1
- """HedgeBoard visualization components."""
@@ -1,198 +0,0 @@
1
- """Chart generation using Chart.js.
2
-
3
- Generates self-contained HTML snippets with embedded Chart.js charts
4
- that use the HedgeBoard color palette for distinct multi-series contrast.
5
-
6
- Usage:
7
- from viz.charts import line_chart, bar_chart
8
-
9
- html = line_chart(
10
- labels=["Q1", "Q2", "Q3", "Q4"],
11
- datasets=[{"label": "Revenue ($M)", "data": [980, 1020, 1060, 1090]}],
12
- title="Quarterly Revenue"
13
- )
14
- """
15
-
16
- from __future__ import annotations
17
-
18
- import json
19
- import uuid
20
- from pathlib import Path
21
-
22
- _THEME_PATH = Path(__file__).resolve().parent.parent / "brand" / "theme.json"
23
-
24
- # 8 distinct chart colors — high contrast on dark backgrounds
25
- CHART_COLORS = [
26
- "#EAEAEA", # primary white
27
- "#4ADE80", # green
28
- "#60A5FA", # blue
29
- "#FBBF24", # amber
30
- "#F87171", # red
31
- "#A78BFA", # purple
32
- "#34D399", # teal
33
- "#FB923C", # orange
34
- ]
35
-
36
- CHART_COLORS_DIM = [
37
- "rgba(234,234,234,0.15)",
38
- "rgba(74,222,128,0.15)",
39
- "rgba(96,165,250,0.15)",
40
- "rgba(251,191,36,0.15)",
41
- "rgba(248,113,113,0.15)",
42
- "rgba(167,139,250,0.15)",
43
- "rgba(52,211,153,0.15)",
44
- "rgba(251,146,60,0.15)",
45
- ]
46
-
47
-
48
- def _load_theme() -> dict:
49
- """Load the brand theme, falling back to HedgeBoard defaults."""
50
- try:
51
- with open(_THEME_PATH) as f:
52
- return json.load(f)
53
- except (FileNotFoundError, json.JSONDecodeError):
54
- return {
55
- "colors": {
56
- "primary": "#EAEAEA",
57
- "accent": "#CACACA",
58
- "background": "#0C0C10",
59
- "surface": "#151519",
60
- "text": "#EAEAEA",
61
- "muted": "#6B6B78",
62
- "border": "#2A2A33",
63
- }
64
- }
65
-
66
-
67
- def line_chart(
68
- labels: list[str],
69
- datasets: list[dict],
70
- title: str = "",
71
- height: int = 320,
72
- ) -> str:
73
- """Generate a branded line chart.
74
-
75
- Args:
76
- labels: X-axis labels.
77
- datasets: List of dicts, each with "label" and "data" keys.
78
- title: Chart title.
79
- height: Chart height in pixels.
80
-
81
- Returns:
82
- HTML string with an embedded Chart.js line chart.
83
- """
84
- return _render_chart("line", labels, datasets, title, height)
85
-
86
-
87
- def bar_chart(
88
- labels: list[str],
89
- datasets: list[dict],
90
- title: str = "",
91
- height: int = 320,
92
- ) -> str:
93
- """Generate a branded bar chart.
94
-
95
- Args:
96
- labels: X-axis labels.
97
- datasets: List of dicts, each with "label" and "data" keys.
98
- title: Chart title.
99
- height: Chart height in pixels.
100
-
101
- Returns:
102
- HTML string with an embedded Chart.js bar chart.
103
- """
104
- return _render_chart("bar", labels, datasets, title, height)
105
-
106
-
107
- def _render_chart(
108
- chart_type: str,
109
- labels: list[str],
110
- datasets: list[dict],
111
- title: str,
112
- height: int,
113
- ) -> str:
114
- """Render a Chart.js chart as an HTML snippet."""
115
- theme = _load_theme()
116
- colors = theme.get("colors", {})
117
- canvas_id = f"chart-{uuid.uuid4().hex[:8]}"
118
-
119
- # Build Chart.js dataset configs — each series gets a distinct color
120
- chart_datasets = []
121
- for i, ds in enumerate(datasets):
122
- color = CHART_COLORS[i % len(CHART_COLORS)]
123
- fill_color = CHART_COLORS_DIM[i % len(CHART_COLORS_DIM)]
124
- chart_datasets.append({
125
- "label": ds.get("label", f"Series {i + 1}"),
126
- "data": ds.get("data", []),
127
- "borderColor": color,
128
- "backgroundColor": fill_color if chart_type == "line" else color,
129
- "borderWidth": 2,
130
- "tension": 0.3,
131
- "pointRadius": 3,
132
- "pointBackgroundColor": color,
133
- "fill": chart_type == "line",
134
- "barPercentage": 0.7,
135
- "categoryPercentage": 0.8,
136
- })
137
-
138
- config = {
139
- "type": chart_type,
140
- "data": {"labels": labels, "datasets": chart_datasets},
141
- "options": {
142
- "responsive": True,
143
- "maintainAspectRatio": False,
144
- "plugins": {
145
- "title": {
146
- "display": bool(title),
147
- "text": title,
148
- "color": colors.get("text", "#EAEAEA"),
149
- "font": {"size": 14, "weight": "600", "family": "'Space Grotesk', sans-serif"},
150
- "padding": {"bottom": 16},
151
- },
152
- "legend": {
153
- "position": "bottom",
154
- "labels": {
155
- "color": colors.get("muted", "#6B6B78"),
156
- "font": {"size": 11, "family": "'IBM Plex Mono', monospace"},
157
- "boxWidth": 12,
158
- "boxHeight": 2,
159
- "padding": 16,
160
- },
161
- },
162
- "tooltip": {
163
- "backgroundColor": colors.get("surface", "#151519"),
164
- "titleFont": {"family": "'Space Grotesk', sans-serif"},
165
- "bodyFont": {"family": "'IBM Plex Mono', monospace", "size": 12},
166
- "borderColor": colors.get("border", "#2A2A33"),
167
- "borderWidth": 1,
168
- "padding": 12,
169
- "cornerRadius": 2,
170
- },
171
- },
172
- "scales": {
173
- "x": {
174
- "ticks": {
175
- "color": colors.get("muted", "#6B6B78"),
176
- "font": {"size": 11, "family": "'IBM Plex Mono', monospace"},
177
- },
178
- "grid": {"color": colors.get("border", "#2A2A33"), "lineWidth": 0.5},
179
- "border": {"color": colors.get("border", "#2A2A33")},
180
- },
181
- "y": {
182
- "ticks": {
183
- "color": colors.get("muted", "#6B6B78"),
184
- "font": {"size": 11, "family": "'IBM Plex Mono', monospace"},
185
- },
186
- "grid": {"color": colors.get("border", "#2A2A33"), "lineWidth": 0.5},
187
- "border": {"display": False},
188
- },
189
- },
190
- },
191
- }
192
-
193
- return f"""<div style="position:relative;height:{height}px;width:100%;background:{colors.get('surface', '#151519')};border:1px solid {colors.get('border', '#2A2A33')};border-radius:2px;padding:20px;box-sizing:border-box;">
194
- <canvas id="{canvas_id}"></canvas>
195
- </div>
196
- <script>
197
- new Chart(document.getElementById('{canvas_id}'), {json.dumps(config)});
198
- </script>"""