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,419 +0,0 @@
1
- /* === HedgeBoard Dashboard Styles === */
2
- /* Used by viz/dashboard.py — loaded into every generated dashboard */
3
-
4
- /* Fonts */
5
- @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
6
-
7
- /* ---------------------------------------------------------------------------
8
- Grid Layout
9
- --------------------------------------------------------------------------- */
10
- .hb-grid {
11
- display: grid;
12
- gap: 16px;
13
- margin-bottom: 24px;
14
- }
15
-
16
- .hb-grid-2 {
17
- grid-template-columns: 1fr 1fr;
18
- }
19
-
20
- .hb-grid-3 {
21
- grid-template-columns: 1fr 1fr 1fr;
22
- }
23
-
24
- .hb-grid-cell {
25
- min-width: 0;
26
- /* prevent overflow */
27
- }
28
-
29
- /* ---------------------------------------------------------------------------
30
- KPI Row
31
- --------------------------------------------------------------------------- */
32
- .hb-kpi-row {
33
- display: grid;
34
- grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
35
- gap: 1px;
36
- background: var(--color-surface, #2A2A33);
37
- border: 1px solid var(--color-surface, #2A2A33);
38
- border-radius: 2px;
39
- margin-bottom: 24px;
40
- overflow: hidden;
41
- }
42
-
43
- .hb-kpi {
44
- background: var(--color-bg, #0C0C10);
45
- padding: 20px 24px;
46
- display: flex;
47
- flex-direction: column;
48
- gap: 4px;
49
- }
50
-
51
- .hb-kpi-label {
52
- font-family: 'IBM Plex Mono', monospace;
53
- font-size: 11px;
54
- font-weight: 500;
55
- text-transform: uppercase;
56
- letter-spacing: 1.5px;
57
- color: var(--color-muted, #6B6B78);
58
- }
59
-
60
- .hb-kpi-value {
61
- font-family: 'IBM Plex Mono', monospace;
62
- font-size: 28px;
63
- font-weight: 700;
64
- color: var(--color-text, #EAEAEA);
65
- letter-spacing: -0.5px;
66
- line-height: 1.2;
67
- }
68
-
69
- .hb-kpi-delta {
70
- font-family: 'IBM Plex Mono', monospace;
71
- font-size: 12px;
72
- font-weight: 500;
73
- }
74
-
75
- .hb-delta-up,
76
- .hb-delta-badge.hb-delta-up {
77
- color: #4ADE80;
78
- }
79
-
80
- .hb-delta-down,
81
- .hb-delta-badge.hb-delta-down {
82
- color: #F87171;
83
- }
84
-
85
- .hb-delta-flat,
86
- .hb-delta-badge.hb-delta-flat {
87
- color: #FBBF24;
88
- }
89
-
90
- /* ---------------------------------------------------------------------------
91
- Delta Badge (inline)
92
- --------------------------------------------------------------------------- */
93
- .hb-delta-badge {
94
- display: inline-flex;
95
- font-family: 'IBM Plex Mono', monospace;
96
- font-size: 12px;
97
- font-weight: 600;
98
- padding: 2px 8px;
99
- border-radius: 2px;
100
- border: 1px solid currentColor;
101
- line-height: 1.4;
102
- }
103
-
104
- /* ---------------------------------------------------------------------------
105
- Callout Boxes
106
- --------------------------------------------------------------------------- */
107
- .hb-callout {
108
- display: flex;
109
- gap: 12px;
110
- align-items: flex-start;
111
- padding: 16px 20px;
112
- border-radius: 2px;
113
- border-left: 3px solid;
114
- margin-bottom: 16px;
115
- font-size: 14px;
116
- line-height: 1.6;
117
- color: var(--color-text, #EAEAEA);
118
- }
119
-
120
- .hb-callout-icon {
121
- font-size: 16px;
122
- flex-shrink: 0;
123
- margin-top: 1px;
124
- }
125
-
126
- .hb-callout-info {
127
- background: rgba(96, 165, 250, 0.08);
128
- border-color: #60A5FA;
129
- }
130
-
131
- .hb-callout-success {
132
- background: rgba(74, 222, 128, 0.08);
133
- border-color: #4ADE80;
134
- }
135
-
136
- .hb-callout-warning {
137
- background: rgba(251, 191, 36, 0.08);
138
- border-color: #FBBF24;
139
- }
140
-
141
- .hb-callout-danger {
142
- background: rgba(248, 113, 113, 0.08);
143
- border-color: #F87171;
144
- }
145
-
146
- /* ---------------------------------------------------------------------------
147
- Scorecard
148
- --------------------------------------------------------------------------- */
149
- .hb-scorecard {
150
- background: var(--color-surface, #151519);
151
- border: 1px solid var(--color-surface, #2A2A33);
152
- border-radius: 2px;
153
- padding: 24px;
154
- margin-bottom: 24px;
155
- }
156
-
157
- .hb-scorecard-verdict {
158
- font-family: 'Space Grotesk', sans-serif;
159
- font-size: 28px;
160
- font-weight: 700;
161
- margin: 8px 0 12px;
162
- }
163
-
164
- .hb-scorecard-reason {
165
- font-size: 14px;
166
- color: var(--color-muted, #6B6B78);
167
- line-height: 1.6;
168
- }
169
-
170
- /* ---------------------------------------------------------------------------
171
- Comparison & Heatmap Tables
172
- --------------------------------------------------------------------------- */
173
- .hb-table-title {
174
- font-family: 'IBM Plex Mono', monospace;
175
- font-size: 11px;
176
- font-weight: 500;
177
- text-transform: uppercase;
178
- letter-spacing: 1.5px;
179
- color: var(--color-muted, #6B6B78);
180
- margin-bottom: 12px;
181
- }
182
-
183
- .hb-table-wrap {
184
- overflow-x: auto;
185
- margin-bottom: 24px;
186
- }
187
-
188
- .hb-comp-table {
189
- width: 100%;
190
- border-collapse: collapse;
191
- font-family: 'IBM Plex Mono', monospace;
192
- font-size: 13px;
193
- }
194
-
195
- .hb-comp-table th {
196
- text-align: left;
197
- padding: 10px 16px;
198
- font-size: 11px;
199
- font-weight: 500;
200
- text-transform: uppercase;
201
- letter-spacing: 1px;
202
- color: var(--color-muted, #6B6B78);
203
- border-bottom: 1px solid var(--color-surface, #2A2A33);
204
- background: var(--color-surface, #151519);
205
- }
206
-
207
- .hb-comp-table th:not(:first-child) {
208
- text-align: right;
209
- }
210
-
211
- .hb-comp-table td {
212
- padding: 10px 16px;
213
- color: var(--color-text, #B0B0B8);
214
- border-bottom: 1px solid var(--color-surface, #2A2A33);
215
- font-variant-numeric: tabular-nums;
216
- }
217
-
218
- .hb-comp-table td:not(:first-child) {
219
- text-align: right;
220
- }
221
-
222
- .hb-cell-label {
223
- color: var(--color-text, #EAEAEA) !important;
224
- font-weight: 600;
225
- }
226
-
227
- .hb-cell-best {
228
- color: #4ADE80 !important;
229
- font-weight: 600;
230
- }
231
-
232
- .hb-cell-worst {
233
- color: #F87171 !important;
234
- opacity: 0.8;
235
- }
236
-
237
- .hb-comp-table tbody tr:hover {
238
- background: rgba(234, 234, 234, 0.03);
239
- }
240
-
241
- /* ---------------------------------------------------------------------------
242
- Section Headers
243
- --------------------------------------------------------------------------- */
244
- .hb-section-header {
245
- margin: 32px 0 20px;
246
- padding-top: 24px;
247
- border-top: 1px solid var(--color-surface, #2A2A33);
248
- }
249
-
250
- .hb-section-lbl {
251
- display: block;
252
- font-family: 'IBM Plex Mono', monospace;
253
- font-size: 11px;
254
- font-weight: 500;
255
- text-transform: uppercase;
256
- letter-spacing: 2px;
257
- color: var(--color-muted, #6B6B78);
258
- margin-bottom: 8px;
259
- }
260
-
261
- .hb-section-title {
262
- font-family: 'Space Grotesk', sans-serif;
263
- font-size: 22px;
264
- font-weight: 700;
265
- color: var(--color-text, #EAEAEA);
266
- margin-bottom: 8px;
267
- }
268
-
269
- .hb-section-desc {
270
- font-size: 14px;
271
- color: var(--color-muted, #B0B0B8);
272
- line-height: 1.6;
273
- max-width: 600px;
274
- }
275
-
276
- /* ---------------------------------------------------------------------------
277
- Source Citation Panel
278
- --------------------------------------------------------------------------- */
279
- .hb-sources {
280
- margin-top: 32px;
281
- border-top: 1px solid var(--color-surface, #2A2A33);
282
- padding-top: 16px;
283
- }
284
-
285
- .hb-sources-toggle {
286
- font-family: 'IBM Plex Mono', monospace;
287
- font-size: 11px;
288
- font-weight: 500;
289
- text-transform: uppercase;
290
- letter-spacing: 1.5px;
291
- color: var(--color-muted, #6B6B78);
292
- cursor: pointer;
293
- padding: 4px 0;
294
- list-style: none;
295
- }
296
-
297
- .hb-sources-toggle::marker,
298
- .hb-sources-toggle::-webkit-details-marker {
299
- display: none;
300
- }
301
-
302
- .hb-sources-toggle::before {
303
- content: '▸ ';
304
- }
305
-
306
- details[open] .hb-sources-toggle::before {
307
- content: '▾ ';
308
- }
309
-
310
- .hb-sources-list {
311
- list-style: none;
312
- margin-top: 12px;
313
- display: flex;
314
- flex-direction: column;
315
- gap: 6px;
316
- }
317
-
318
- .hb-sources-list li {
319
- font-family: 'IBM Plex Mono', monospace;
320
- font-size: 11px;
321
- color: var(--color-muted, #6B6B78);
322
- padding-left: 16px;
323
- position: relative;
324
- }
325
-
326
- .hb-sources-list li::before {
327
- content: '—';
328
- position: absolute;
329
- left: 0;
330
- }
331
-
332
- /* ---------------------------------------------------------------------------
333
- Timeline
334
- --------------------------------------------------------------------------- */
335
- .hb-timeline {
336
- display: flex;
337
- gap: 0;
338
- overflow-x: auto;
339
- padding: 16px 0;
340
- margin-bottom: 24px;
341
- }
342
-
343
- .hb-timeline-item {
344
- display: flex;
345
- flex-direction: column;
346
- align-items: center;
347
- gap: 8px;
348
- min-width: 120px;
349
- flex: 1;
350
- position: relative;
351
- }
352
-
353
- .hb-timeline-item:not(:last-child)::after {
354
- content: '';
355
- position: absolute;
356
- top: 32px;
357
- left: 50%;
358
- width: 100%;
359
- height: 1px;
360
- background: var(--color-surface, #2A2A33);
361
- }
362
-
363
- .hb-timeline-date {
364
- font-family: 'IBM Plex Mono', monospace;
365
- font-size: 11px;
366
- color: var(--color-muted, #6B6B78);
367
- }
368
-
369
- .hb-timeline-dot {
370
- width: 8px;
371
- height: 8px;
372
- border-radius: 50%;
373
- background: var(--color-text, #EAEAEA);
374
- border: 2px solid var(--color-bg, #0C0C10);
375
- z-index: 1;
376
- }
377
-
378
- .hb-timeline-label {
379
- font-family: 'Space Grotesk', sans-serif;
380
- font-size: 12px;
381
- font-weight: 600;
382
- color: var(--color-text, #EAEAEA);
383
- text-align: center;
384
- }
385
-
386
- .hb-timeline-detail {
387
- font-size: 11px;
388
- color: var(--color-muted, #6B6B78);
389
- text-align: center;
390
- }
391
-
392
- /* ---------------------------------------------------------------------------
393
- Sparklines
394
- --------------------------------------------------------------------------- */
395
- .hb-sparkline {
396
- display: block;
397
- margin-top: 6px;
398
- }
399
-
400
- /* ---------------------------------------------------------------------------
401
- Responsive
402
- --------------------------------------------------------------------------- */
403
- @media (max-width: 768px) {
404
- .hb-grid-2 {
405
- grid-template-columns: 1fr;
406
- }
407
-
408
- .hb-grid-3 {
409
- grid-template-columns: 1fr;
410
- }
411
-
412
- .hb-kpi-row {
413
- grid-template-columns: repeat(2, 1fr);
414
- }
415
-
416
- .hb-kpi-value {
417
- font-size: 22px;
418
- }
419
- }
@@ -1,25 +0,0 @@
1
- {
2
- "name": "HedgeBoard",
3
- "logo": "",
4
- "colors": {
5
- "primary": "#EAEAEA",
6
- "accent": "#CACACA",
7
- "accent_dim": "#888888",
8
- "positive": "#4ADE80",
9
- "negative": "#F87171",
10
- "warning": "#FBBF24",
11
- "background": "#0C0C10",
12
- "surface": "#151519",
13
- "hover": "#1E1E24",
14
- "text": "#EAEAEA",
15
- "text_secondary": "#B0B0B8",
16
- "text_muted": "#6B6B78",
17
- "border": "#2A2A33"
18
- },
19
- "font": {
20
- "family": "Space Grotesk",
21
- "headings": "Space Grotesk",
22
- "mono": "IBM Plex Mono"
23
- },
24
- "mode": "dark"
25
- }
@@ -1 +0,0 @@
1
- """HedgeBoard data access layer."""
@@ -1,218 +0,0 @@
1
- """HedgeBoard SEC data client.
2
-
3
- Provides access to SEC filings, XBRL financial facts, and company
4
- metadata through the HedgeBoard API.
5
-
6
- Usage:
7
- from data.sec import HedgeBoardClient
8
-
9
- hb = HedgeBoardClient()
10
- company = hb.get_company("AAPL")
11
- filings = hb.get_filings("AAPL", form_type="10-K")
12
- facts = hb.get_xbrl("AAPL", concept="Revenues")
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- import json
18
- import os
19
- import sys
20
- from pathlib import Path
21
- from typing import Optional
22
-
23
- import requests
24
- from dotenv import load_dotenv
25
-
26
- # Load .env from the hedgeboard root
27
- _root = Path(__file__).resolve().parent.parent
28
- load_dotenv(_root / ".env")
29
-
30
- API_BASE = os.environ.get("HEDGEBOARD_API_URL", "https://3oy6d0glul.execute-api.us-east-1.amazonaws.com/v1")
31
- API_KEY = os.environ.get("HEDGEBOARD_API_KEY", "")
32
-
33
- # Local cache to avoid re-fetching in the same session
34
- _cache: dict[str, any] = {}
35
-
36
-
37
- class HedgeBoardClient:
38
- """Client for the HedgeBoard data API."""
39
-
40
- def __init__(self, api_key: str | None = None, base_url: str | None = None):
41
- self.api_key = api_key or API_KEY
42
- self.base_url = (base_url or API_BASE).rstrip("/")
43
-
44
- if not self.api_key:
45
- print(
46
- "⚠️ No API key found. Set HEDGEBOARD_API_KEY in hedgeboard/.env",
47
- file=sys.stderr,
48
- )
49
-
50
- # ------------------------------------------------------------------
51
- # Internal helpers
52
- # ------------------------------------------------------------------
53
-
54
- def _get(self, path: str, params: dict | None = None) -> dict | list | str:
55
- """Make an authenticated GET request to the HedgeBoard API."""
56
- cache_key = f"{path}|{json.dumps(params or {}, sort_keys=True)}"
57
- if cache_key in _cache:
58
- return _cache[cache_key]
59
-
60
- url = f"{self.base_url}{path}"
61
- headers = {"x-api-key": self.api_key}
62
- resp = requests.get(url, headers=headers, params=params, timeout=30)
63
-
64
- if resp.status_code == 403:
65
- raise PermissionError("Invalid API key. Check HEDGEBOARD_API_KEY in .env")
66
- if resp.status_code == 404:
67
- return None
68
- if resp.status_code == 429:
69
- raise RuntimeError("Rate limit exceeded. Wait a moment and try again.")
70
- resp.raise_for_status()
71
-
72
- # Filing HTML comes as text, everything else as JSON
73
- content_type = resp.headers.get("content-type", "")
74
- if "text/html" in content_type:
75
- data = resp.text
76
- else:
77
- data = resp.json()
78
-
79
- _cache[cache_key] = data
80
- return data
81
-
82
- # ------------------------------------------------------------------
83
- # Companies
84
- # ------------------------------------------------------------------
85
-
86
- def get_company(self, ticker: str) -> dict | None:
87
- """Get company metadata by ticker.
88
-
89
- Returns:
90
- Dict with keys: cik, name, ticker, exchange, sic, sic_description,
91
- category, entity_type, state, etc. None if not found.
92
- """
93
- return self._get("/v1/companies", params={"ticker": ticker.upper()})
94
-
95
- def search_companies(self, query: str, limit: int = 20) -> list[dict]:
96
- """Search companies by name, ticker, or sector.
97
-
98
- Returns:
99
- List of matching company dicts.
100
- """
101
- result = self._get(
102
- "/v1/companies/search",
103
- params={"q": query, "limit": limit},
104
- )
105
- return result if result else []
106
-
107
- # ------------------------------------------------------------------
108
- # Filings
109
- # ------------------------------------------------------------------
110
-
111
- def get_filings(
112
- self,
113
- ticker: str,
114
- form_type: str | None = None,
115
- limit: int = 10,
116
- ) -> list[dict]:
117
- """Get SEC filing metadata for a company.
118
-
119
- Args:
120
- ticker: Company ticker symbol.
121
- form_type: Filter by form type (e.g., "10-K", "10-Q").
122
- limit: Maximum number of filings to return.
123
-
124
- Returns:
125
- List of filing dicts with keys: filing_date, form_type,
126
- accession_number, s3_key, primary_doc_url, etc.
127
- Sorted by filing_date descending (newest first).
128
- """
129
- params = {"ticker": ticker.upper(), "limit": limit}
130
- if form_type:
131
- params["type"] = form_type
132
- result = self._get("/v1/filings", params=params)
133
- return result if result else []
134
-
135
- def get_filing_html(self, s3_key: str) -> str | None:
136
- """Get the raw cleaned HTML of a filing document.
137
-
138
- Args:
139
- s3_key: The S3 key from a filing record (e.g., "raw/...").
140
-
141
- Returns:
142
- HTML string, or None if not found.
143
- """
144
- return self._get("/v1/filings/html", params={"key": s3_key})
145
-
146
- # ------------------------------------------------------------------
147
- # XBRL Financial Facts
148
- # ------------------------------------------------------------------
149
-
150
- def get_xbrl(
151
- self,
152
- ticker: str,
153
- concept: str | None = None,
154
- form_type: str | None = None,
155
- limit: int = 40,
156
- ) -> list[dict]:
157
- """Get structured XBRL financial facts for a company.
158
-
159
- Args:
160
- ticker: Company ticker symbol.
161
- concept: XBRL concept name to filter (e.g., "Revenues",
162
- "NetIncomeLoss", "EarningsPerShareBasic").
163
- If None, returns all concepts.
164
- form_type: Filter by form type (e.g., "10-K" for annual only).
165
- limit: Maximum number of facts to return.
166
-
167
- Returns:
168
- List of fact dicts with keys: concept, label, value, unit,
169
- period_start, period_end, form, filed, taxonomy.
170
- Sorted by period_end descending (newest first).
171
- """
172
- params = {"ticker": ticker.upper(), "limit": limit}
173
- if concept:
174
- params["concept"] = concept
175
- if form_type:
176
- params["form"] = form_type
177
- result = self._get("/v1/xbrl", params=params)
178
- return result if result else []
179
-
180
- def get_key_financials(
181
- self,
182
- ticker: str,
183
- form_type: str = "10-K",
184
- periods: int = 5,
185
- ) -> dict[str, list[dict]]:
186
- """Convenience: get key financial metrics for a company.
187
-
188
- Returns a dict mapping concept name to list of fact records:
189
- {
190
- "Revenues": [{period_end, value, ...}, ...],
191
- "NetIncomeLoss": [...],
192
- "EarningsPerShareBasic": [...],
193
- "Assets": [...],
194
- "StockholdersEquity": [...],
195
- }
196
- """
197
- key_concepts = [
198
- "Revenues",
199
- "RevenueFromContractWithCustomerExcludingAssessedTax",
200
- "NetIncomeLoss",
201
- "EarningsPerShareBasic",
202
- "EarningsPerShareDiluted",
203
- "Assets",
204
- "Liabilities",
205
- "StockholdersEquity",
206
- "OperatingIncomeLoss",
207
- "CashAndCashEquivalentsAtCarryingValue",
208
- ]
209
-
210
- results: dict[str, list[dict]] = {}
211
- for concept in key_concepts:
212
- facts = self.get_xbrl(
213
- ticker, concept=concept, form_type=form_type, limit=periods
214
- )
215
- if facts:
216
- results[concept] = facts
217
-
218
- return results
@@ -1 +0,0 @@
1
- """HedgeBoard analysis modules."""