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.
- package/index.js +1 -1
- package/package.json +1 -1
- package/hedgeboard/CLAUDE.md +0 -284
- package/hedgeboard/README.md +0 -64
- package/hedgeboard/brand/base.css +0 -419
- package/hedgeboard/brand/theme.json +0 -25
- package/hedgeboard/data/__init__.py +0 -1
- package/hedgeboard/data/sec.py +0 -218
- package/hedgeboard/modules/__init__.py +0 -1
- package/hedgeboard/modules/company_overview.py +0 -157
- package/hedgeboard/requirements.txt +0 -2
- package/hedgeboard/viz/__init__.py +0 -1
- package/hedgeboard/viz/charts.py +0 -198
- package/hedgeboard/viz/components.py +0 -391
- package/hedgeboard/viz/dashboard.py +0 -330
- package/hedgeboard/viz/tables.py +0 -140
|
@@ -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 +0,0 @@
|
|
|
1
|
-
"""HedgeBoard visualization components."""
|
package/hedgeboard/viz/charts.py
DELETED
|
@@ -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>"""
|