"Data-context reference for the alpine-data ('BDT Dashboards') project — a FILE-BASED 'data warehouse' where Python fetch_*.py scripts pull ~50 upstream APIs into JSON sidecars that two static-HTML builders (app.py = V1, v2/app.py = V2) render and serve via GitHub Pages. Use when analyzing, debugging, or extending alpine-data's data for: (1) tracing where a tab's data comes from and how fresh it is, (2) reading or deriving its metrics (Money Flow Index, MFI/CMF, ETF flows, signal scores, city pulse), (3) diagnosing empty/stale tabs and the live-vs-repo trap, or any question needing alpine-data-specific data context. NOT for a SQL warehouse — this 'warehouse' is JSON files on disk + GitHub Pages."
Resources
1Install
npx skillscat add btabiado/alpine-data/alpine-data-context Install via the SkillsCat registry.
The alpine-data-context skill maps the file‑based data warehouse where Python fetch scripts pull 50 upstream APIs into JSON sidecars and HTML builders render dashboards served on GitHub Pages. It lets analysts trace data sources and freshness, compute metrics such as MFI or ETF flows, and diagnose empty or stale tabs, making it essential for debugging or extending alpine-data.
Alpine-Data Data Context
alpine-data ("BDT Dashboards") has no SQL warehouse. Its "warehouse" is a fetch → sidecar → build pipeline of Python scripts and JSON files. This skill is the map of that data layer: where each datum comes from, how it is keyed, how it is computed, how fresh it is, and the standing gotchas an analyst must know.
- Canonical repo:
/Users/bryantabiadon/alpine-data(GitHubbtabiado/alpine-data, branchmain) - Live site: V1 at https://btabiado.github.io/alpine-data/ · V2 at https://btabiado.github.io/alpine-data/v2/ · also
/summit/and/landscape/ - UI source of truth:
app.py(V1, ~985 KB) andv2/app.py(V2, ~847 KB) — single-file builders whose inlineHTML_TEMPLATErenders to the built artifactsdashboard.html/v2/dashboard.html. Never hand-edit the built.html— edit the builder and rebuild. btabiado.github.io/root 404s — the project lives under/alpine-data/.
Architecture at a glance
upstream APIs (~50) Python fetchers JSON sidecars static builders GitHub Pages
───────────────────── → fetch_market.py → data/market.json, → app.py (V1) → / (V1)
CoinGecko, FRED, ICI, fetch_cpi.py data-*.json (root), v2/app.py (V2) /v2/ (V2)
Yahoo, OpenSky, NUFORC, fetch_metals.py data/*.json embed core data inline /summit/
Socrata, State Dept, ... fetch_money_flows.py (one per feed/tab) + lazy-load sidecars /landscape/
... (see references/sources.md)
│ │
└── scheduled by GitHub Actions ──────────────────────┘
pages.yml (hourly + on push) builds & deploys everything;
per-tab crons (city/money-flow/real-estate/aviation) refresh
individual sidecars and commit them back to the repo.Two delivery modes for data inside the dashboard:
- Inline at build time — core/critical data is embedded directly in the HTML by the builder (the page is ~5 MB, ~96% inline JS+data).
- Lazy sidecars at runtime — heavier per-tab data lives in
data-*.jsonfiles fetched on first tab selection. Look forSIDECARS/loadSidecar/SIDECAR_FOR_TABinapp.py. A tab maps to a sidecar; selecting itfetch()es that JSON from the deployed site.
There is no data.json at the site root (it 404s) — that is expected; data is either inline or in a named data-*.json.
⭐ The one rule that matters most: trust LIVE, not the repo file
Many sidecars are committed to git as cold placeholders or last-known-good fallbacks, then regenerated fresh on every Pages deploy. So the file in the repo can look empty or stale while production is fully populated and current.
data-cpi.jsonin the repo:{fred_available:false, series:[]}(151 B). Live:fred_available:true, 22 series, regenerated each deploy.data-stock-money-flow.jsonrepo:scored_count:0(90 B). Live: 13 stocks scored.data-whale.jsonrepo:sentiment:null(198 B). Live: full BTC whale series.
When a date or count looks wrong in the repo, check the deployed copy before concluding anything is broken:
curl -s https://btabiado.github.io/alpine-data/data-cpi.json | python3 -m json.tool | headThe committed file is the API contract / fallback; the live Pages copy is authoritative for current state. (See references/gotchas.md for the full list of which files are placeholders vs real.)
Access patterns (how to "query" this data)
There is no SQL. You "query" by reading JSON. Common patterns:
# 1. Read a LIVE sidecar (authoritative current state) and inspect with jq/python
curl -s https://btabiado.github.io/alpine-data/data-metals.json | jq '.generated_at, (.gold | keys)'
curl -s https://btabiado.github.io/alpine-data/data/health/api_status.json | jq '.summary'
# 2. Read the on-disk repo copy (fallback / last-known-good — may be stale; see the rule above)
jq '.generated_at' ~/alpine-data/data-metals.json
# 3. Check a feed's freshness field (varies: generated_at | as_of | fetched_at | ts)
curl -s https://btabiado.github.io/alpine-data/data-tsa.json | jq '{generated_at, as_of}'
# 4. Run a fetcher locally to regenerate a sidecar (needs the repo's venv + any API keys in .env)
cd ~/alpine-data && .venv/bin/python fetch_metals.py # writes data-metals.json (+ v2/)
cd ~/alpine-data && .venv/bin/python api_status.py # writes data/health/api_status.json
# 5. Rebuild a dashboard from source (renders the builder's HTML_TEMPLATE to the artifact)
cd ~/alpine-data && .venv/bin/python app.py --no-open # -> dashboard.html (V1)
cd ~/alpine-data && .venv/bin/python v2/app.py --no-open # -> v2/dashboard.html (V2)Freshness timestamp field is not uniform — grep the file or see the per-source column in references/sources.md.
Entity disambiguation
The word "flow" is heavily overloaded here. Clarify which one:
"Flows" can mean:
- ETF flows (crypto) — daily BTC/ETH spot-ETF net inflows/outflows. Source: Farside mirror + CoinGlass. Files:
data/btc_flows.csv,data/eth_flows.csv. Tab: ETF Flows. - Equity ETF flows — per-index (SPY/DIA/QQQ)
ΔSharesOutstanding × NAV. File:data/equity_etf_flows.csv. Feeds the Money Flow Index. - Money flows (mutual funds) — ICI weekly equity-fund + money-market-fund cash flows. Files:
data-mf-flows.json,data-mmf.json. - Money Flow Index (MFX) — the composite ±100 gauge built from the three above plus MFI/CMF. Tab: Markets → Money Flow. Built in
money_flow.py:build_money_flow_index. - Stock Flows — per-stock MFI/CMF score for index constituents. File:
data-stock-money-flow.json. Tab: Stocks → Money Flow.
Other overloaded terms:
- "Signal" — the −100…+100 composite trade signal per asset (
signals.py), NOT the same as the Money Flow Index. - "Whale" — BTC on-chain large-holder proxies (
data-whale.json), distinct from the "crowded longs/shorts" futures positioning. - "Sidecar" vs "tab" — a tab is a UI panel (
<div id="tab-X">); a sidecar is thedata-*.jsonit lazy-loads. One tab → at most one sidecar (SIDECAR_FOR_TAB). - V1 vs V2 — two independent builders/sites (
app.py→/,v2/app.py→/v2/). They share the Python data layer (V2 importsfetch_market,signals,insightsfrom the repo root) but have separately maintained frontends (~86–93% duplicated JS). A data fix in a fetcher reaches both; a UI fix usually must be applied twice.
Business terminology
| Term | Meaning | Notes / gotcha |
|---|---|---|
| Sidecar | A data-*.json file a tab lazy-loads at runtime |
Committed ones are fallbacks; trust live |
| MFI | Money Flow Index (14-bar), 0–100 | money_flow.py:mfi; >70 overbought, <30 oversold |
| CMF | Chaikin Money Flow (20-bar), −1…+1 | money_flow.py:cmf |
| MFX / headline | The ±100 Money Flow Index composite gauge | money_flow.py:build_money_flow_index; ETF leg warms up ~1 trading day |
| ΔSO×NAV | ETF net flow = (sharesOut_t − sharesOut_prev) × NAV_t | Needs ≥2 daily snapshots; data/equity_etf_flows.csv |
| Signal / score | −100…+100 weighted trade signal per asset | signals.py; weights SMA20/RSI15/MACD10/Funding10/FNG10/ETF10/DVOL5/VIX5 |
| Pulse | City health score, 50 = baseline | city/pulse.py; >50 favorable |
| Thesis | LTHCS long-term-hold confidence; also a per-asset sentiment leg | Kept neutral (50) in V1 daily pipeline (Finnhub free tier unreliable) |
| api_status / health | Reachability snapshot of all upstreams | api_status.py → data/health/api_status.json, mirror health/apis.html |
| Carve-out | A !data-X.json negation in .gitignore that commits one sidecar |
Lets CI start from a baseline on a cold checkout |
| LTHCS | Long-Term Hold Confidence Score | Separate pipeline (lthcs_daily.py); V1-only; see README_LTHCS.md |
Key metrics (summary — full detail in references/metrics.md)
| Metric | One-line formula | Where |
|---|---|---|
| MFI(14) | 100 − 100/(1 + ΣposMF/ΣnegMF), MF = typical-price × volume |
money_flow.py:113 |
| CMF(20) | Σ(MFM×V)/ΣV, MFM = ((C−L)−(H−C))/(H−L) |
money_flow.py:166 |
| Money Flow Index composite | per-index = mean(z(ETF flow), z(MFI−50)) → ±100; headline renormalizes + ICI MF + MMF cash | money_flow.py:361 |
| ETF net flow | (sharesOut_t − sharesOut_prev) × NAV_t (USD millions) |
fetch_equity_etf_flows.py |
| Asset signal | weighted blend of SMA/RSI/MACD/funding/FNG/ETF/DVOL/VIX, contrarian on funding & FNG | signals.py:compute_signal |
| City pulse | normalized trailing-12-mo trend per pillar, 50 baseline | city/pulse.py |
Data freshness
| Feed / sidecar | Cadence | Refresh mechanism | Typical lag |
|---|---|---|---|
Core crypto/market (data/market.json, signals, whale, defi, money-flows) |
Hourly + on push | pages.yml runs fetch_market.fetch_all() during V1 build |
≤1 h |
api_status.json |
Hourly | pages.yml probe step after build |
≤1 h |
City (data-city.json) |
Daily 06:00 UTC | city-daily.yml commits the file |
≤24 h |
Equity ETF flows (equity_etf_flows.csv) |
Daily 08:30 UTC | money-flow-daily.yml appends + commits |
1 trading-day warm-up |
Real estate (data/real_estate.json) |
Daily | real-estate-daily.yml commits |
≤24 h |
OpenSky (data-opensky*.json) |
Hourly | aviation-opensky.yml commits; flaky on GH IPs |
~1–2 h (self-heals) |
TSA (data-tsa.json) |
Daily 14:10 UTC | aviation-tsa.yml; else baked seed |
≤24 h |
| CPI / Metals / Supplies / Travel / MUFON / Stock-prices | Each Pages deploy | V2 build step in pages.yml |
⚠️ MUFON/stock-prices currently flaky — see references/gotchas.md |
Knowledge base navigation
| Reference | Use for |
|---|---|
references/sources.md |
Per-fetcher inventory: upstream API, output file, freshness field, schedule, error handling (the "tables") |
references/sidecars.md |
Sidecar catalog: which data-*.json feeds which tab, committed-vs-gitignored, size, populated-vs-placeholder |
references/entities.md |
Entities & primary keys (crypto symbol, fund ticker, city slug, ICAO24, FRED series id, country, …) |
references/metrics.md |
Full metric derivations & formulas with file:function anchors |
references/gotchas.md |
Standing data-hygiene quirks — read before trusting any number |
Common tasks
# Which tab is empty/broken? Compare repo vs live for its sidecar:
f=data-stock-money-flow.json
echo "repo:"; jq '{as_of, scored_count}' ~/alpine-data/$f 2>/dev/null
echo "live:"; curl -s https://btabiado.github.io/alpine-data/$f | jq '{as_of, scored_count}'
# What upstreams are degraded right now?
curl -s https://btabiado.github.io/alpine-data/data/health/api_status.json \
| jq '.sources[] | select(.verdict!="up") | {label, category, verdict, note}'
# Trace a metric to its code:
grep -n "def build_money_flow_index" ~/alpine-data/money_flow.pyTroubleshooting / common mistakes (top 5; full list in references/gotchas.md)
- Reading the repo file instead of live. Committed sidecars are placeholders/fallbacks. Always confirm against the deployed Pages copy.
- Assuming MUFON/UAP "as of" = today.
fetch_mufon.pyanchors the date range to the newest entry on file, not the current date; a timed-out scrape can show an old range. - Expecting CPI/macro without a key. CPI needs
FRED_API_KEY; without itfetch_cpi.pywrites an empty{fred_available:false}state. - Retrying Yahoo on a 429.
fetch_stock_money_flow.pydeliberately does not retry rate-limits (retrying deepens the IP ban). Empty stock flows usually = throttling, not a code bug. - Fixing a UI bug in V1 only. V1 and V2 frontends are ~86–93% duplicated; the same fix usually must land in both
app.pyandv2/app.py.