# dashboardbuilder.py — the Python engine behind dashboardbuilder.ado
# ----------------------------------------------------------------------------
# Loaded at runtime by the ado via: python script "dashboardbuilder.py"
# Uses ONLY the Python standard library (json, os) + Stata's sfi bridge.
# Nothing here talks to the network; nothing needs pip.
#
# Two entry points, called as one-liners from the ado:
# _dbb_capture() snapshot the current dataset's capture-vars to JSON
# _dbb_assemble() read all snapshots + spec globals, write the HTML file
# Both report failures through the Stata global DBB_PYERR (never raise), so
# the ado can print friendly errors.
# ----------------------------------------------------------------------------
import json
import os
from sfi import Data, Macro
def _dbb_g(name):
"""read a Stata global (empty string if unset)"""
try:
return Macro.getGlobal(name)
except Exception:
return ""
def _dbb_capture():
"""Snapshot the current dataset's capture-vars to
/panel.json.
Value columns (DBB_CAPRAW) stay numeric; every other column is decoded
through its value label so categories arrive as readable strings."""
try:
k = _dbb_g("DBB_K")
d = _dbb_g("DBB_DIR")
cols = _dbb_g("DBB_CAP").split()
raw = set(_dbb_g("DBB_CAPRAW").split())
colvals, labels = [], {}
for c in cols:
vals = Data.get(var=c, valuelabel=(c not in raw), missingval=None)
colvals.append(vals)
try:
labels[c] = Data.getVarLabel(c) or c
except Exception:
labels[c] = c
rows = [list(t) for t in zip(*colvals)] if colvals else []
with open(os.path.join(d, "panel%s.json" % k), "w", encoding="utf-8") as f:
json.dump({"columns": cols, "labels": labels, "rows": rows}, f)
Macro.setGlobal("DBB_PYERR", "")
except Exception as e:
Macro.setGlobal("DBB_PYERR", str(e))
def _dbb_esc(s):
"""escape text for direct HTML embedding"""
return s.replace("&", "&").replace("<", "<").replace(">", ">")
_DBB_THEMES = {
"simple": {
"accent": "#2563EB", "accent2": "#0F172A", "bg": "#F8FAFC",
"line": "#E2E8F0", "muted": "#64748B", "ink": "#0F172A",
"hdbg": "#FFFFFF", "hdink": "#0F172A", "hdborder": "1px solid #E2E8F0",
"palette": ["#2563EB", "#0F172A", "#0E9488", "#B45309", "#7C3AED", "#DC2626", "#64748B"],
},
"tx2036": {
"accent": "#D44500", "accent2": "#1B2D55", "bg": "#F5F7FA",
"line": "#DDE3EC", "muted": "#6C7A8D", "ink": "#1B2D55",
"hdbg": "#1B2D55", "hdink": "#FFFFFF", "hdborder": "none",
"palette": ["#D44500", "#1B2D55", "#0E9488", "#6C7A8D", "#8A4B10", "#5B4FA0", "#2B6CB0"],
},
}
def _dbb_assemble():
try:
out = _dbb_g("DBB_OUT")
d = _dbb_g("DBB_DIR")
theme = _dbb_g("DBB_THEME") or "simple"
th = _DBB_THEMES[theme]
np = int(_dbb_g("DBB_NPANELS"))
nt = int(_dbb_g("DBB_NTABS"))
sel = _dbb_g("DBB_SEL")
refval = _dbb_g("DBB_REFVAL")
tabs = []
for i in range(1, nt + 1):
tabs.append({"name": _dbb_g("DBB_TAB_NAME_%d" % i),
"label": _dbb_g("DBB_TAB_LAB_%d" % i)})
panels, todo = [], []
selvalues = set()
for i in range(1, np + 1):
ptype = _dbb_g("DBB_P_%d_TYPE" % i)
if ptype == "html":
# external-HTML panel: inline the file (e.g. a sparkta2 map) as an
#