Skip to main content
Glama
alekm

R1 Dash Master

by alekm
README.md
# R1 Dash Master

An MCP server that builds **importable RUCKUS One Data Studio dashboards** from a
simple declarative spec. Output is a `.zip` you import via **Data Studio โ†’ Settings
โ†’ Import Dashboard**. Pure offline generation โ€” no R1 API credentials needed.

๐Ÿ“บ **Setup & usage in Claude Desktop:** https://youtu.be/-gU7yu6liOw

Data Studio is Apache Superset on an Apache Druid backend (`deployment: ALTO`). This
tool encodes the dataset catalog and the chart/query grammar so you
(or an agent) can build valid dashboards without learning Superset internals or guessing
field names.

## Tools

- **`list_datasets()`** โ€” all 18 R1 datasets (internal name, cube name, id, counts).
- **`describe_dataset(name)`** โ€” exact metric + dimension names for one dataset.
- **`validate_spec(spec)`** โ€” check a spec against the catalog before building.
- **`build_dashboard(spec, filename?)`** โ€” emit an importable `.zip` (written to `out/`).

## Spec format

```jsonc
{
  "title": "Network Intelligence",      // generic โ€” NEVER tenant-specific (bundles are portable across ECs)
  // tenant_id: OPTIONAL โ€” omit it. Import auto-rescopes to the target EC (tenant). Only include to hard-pin a tenant.
  "time_range": "Last week",            // default for all charts (Last day/week/month/quarter, previous calendar week/month, or explicit range)
  "grain": "day",                       // OPTIONAL trend time grain: 30 second/minute/3ยท5ยท10ยท15ยท30 minute/hour/day/week/month/quarter (default hour); charts can override
  "rows": [                              // each row = list of charts; widths in a row sum to <= 12
    [ {chart}, {chart} ]
  ]
}
```

Chart:
```jsonc
{
  "type": "bignum" | "bignum_trend" | "line" | "bar" | "area" | "scatter" | "pie" | "table"
         | "gauge" | "heatmap" | "funnel" | "pivot" | "mixed" | "tree" | "bubble",
  "stacked": true,                       // bar/area only: stack the series
  "x": "apMac",                          // line/bar/area/scatter: optional DIMENSION x-axis (default __time)
  // pivot:  "rows": ["zoneName"], "columns": ["radio"], "metrics": [...]
  // mixed:  "metrics": [...] (bars) + "metrics_b": [...] (line) + optional "groupby"/"groupby_b","format_b"
  // tree:   "id": "apName", "parent": "apModel", "name": "apName", "metric": "..."
  // bubble: "entity": "apName", "x": <metric>, "y": <metric>, "size": <metric>  (x/y/size are METRICS here)
  // funnel/gauge/heatmap: "metric" (singular) + "groupby" ([dim]; heatmap uses first dim as Y)
  "dataset": "binnedSessions",          // internal name from list_datasets
  "title": "...", "width": 1-12,
  "metric":  "User Traffic (Total)"     // bignum/pie; string = saved metric
           | {"sql": "1.0*SUM(a)/SUM(b)", "label": "Rate"},  // or custom-SQL (ratios/%)
  "metrics": [ ... ],                    // line/table (list of the same forms)
  "groupby": ["radio"],
  "filter":  ["radio","5"]  | [["radio","5"],["zoneName","X"]],
  "time_range": "Last day",              // optional per-chart override
  "format": ".1%",                       // d3 number format (rates -> ".1%")
  "percent_of_total": ["Traffic (Total)"], // table: share-of-column-total column
  "row_limit": 25
}
```

## Layout & cross-filtering (design convention)

Data Studio dashboards are **cross-filterable**: clicking a value in any chart (e.g. a
venue in a venue table) filters the *entire* dashboard to that value; clearing it up top
removes the filter. So **put venue and AP tables/charts near the TOP** โ€” they double as
interactive filter controls. Recommended order: KPI row โ†’ venue (and AP) table โ†’ detail
charts below. The builder preserves row order from the spec, so order your `rows` that way.

## Grammar notes baked in (gotchas)

- **Field names are exact & dataset-specific.** `radio` not `Radio`; `Unique Client MAC Count`
  not "Unique Client Count"; `User Traffic(Total)` (no space) in `sessionsSummary` vs
  `User Traffic (Total)` (space) in `binnedSessions`. `validate_spec` catches saved-metric/dim typos.
- **Custom-SQL metrics reference RAW columns** (e.g. `successCount`), not display metric names,
  and integer division floors โ€” always `1.0 *` (or `100.0 *`). See `raw_columns` in the catalog.
- **Rate vs share:** a true rate = SQL metric + `.1%` format. `percent_of_total` (table
  `percent_metrics`) means "% of the column total" (contribution), not "format as %".
- **Band values are tri-band:** the `radio`/`band` dimensions take `"2.4"`, `"5"`, and
  `"6(5)"` โ€” the 6 GHz band's literal value is the string `"6(5)"`, NOT `"6"` (confirmed from
  the per-band metric SQL). Per-band metrics label it `6(5) GHz`. Don't hardcode just 2.4/5.
- Dashboards are **transmutable across ECs** โ€” keep titles generic, swap `tenant_id`.

## Why a panel imports empty (troubleshooting)

Bundles are **tenant-less**: there is no `datasets/` folder. Charts bind to a dataset
**by UUID** and reference metrics **by name string**, and both must already exist in the
**target EC**. (Multi-dataset dashboards are fully supported โ€” see
`examples/network_intelligence.json`; if a board looks limited to one dataset, that's not
a tool limitation.) A panel that imports but renders empty almost always traces to one of:

1. **Saved-metric name not present on the target cube.** A metric like `"Client Throughput"`
   only resolves if that EC's cube defines it. **Fix:** use a self-contained custom-SQL metric
   (`{"sql": "...", "label": "..."}`) instead of a saved-metric name โ€” it carries its own
   definition and doesn't depend on the target.
2. **Dataset not provisioned in that EC.** The UUID resolves to nothing. Confirm the dataset
   exists in the target Data Studio before importing.
3. **Overwrite-orphaning on re-import.** Re-importing over an existing published dashboard can
   orphan charts whose position or title changed (chart identity is keyed on title + row/col).
   **Fix:** import to a **fresh dashboard title** rather than overwriting.

### Re-importing while you iterate on the same dashboard

This is the most common self-inflicted cause of the empty/duplicate panels above, and it
bites hardest **mid-session when you're iterating on one idea โ€” adding, removing, renaming,
or reordering charts on the same board and re-importing after each change.** Chart identity
is derived from `(dashboard title, row, column, chart title)`, and the dashboard from its
title alone, so:

- Re-import is **idempotent only when the spec is unchanged** (same titles, same layout) โ€” it
  cleanly overwrites the same objects in place.
- The moment a chart is **added, removed, renamed, or moved**, its identity changes: Superset
  creates the new version and **leaves the old one behind as an orphan** (on no dashboard).
  Enough churn accumulates a pile of stale, sometimes-empty tiles that look like a data problem.

Pick one discipline and stick to it for the session:

- **Overwrite in place** โ€” keep the dashboard title, chart titles, and layout stable across
  imports so every re-import updates the same board.
- **Fresh each pass** โ€” bump the dashboard title each iteration and delete the previous board,
  so every import is a clean set with nothing orphaned.

Avoid the middle ground: repeatedly reshaping a same-named board and re-importing. If you've
already accumulated orphans, clean them in the UI โ€” delete charts that belong to no dashboard,
remove duplicate boards, then re-import your current spec once.

## CLI (without MCP)

```bash
python3 builder.py examples/network_intelligence.json out/network_intelligence_IMPORT.zip
```

## Run as MCP

```bash
pip install -r requirements.txt
python3 server.py
```

**Easiest:** just ask Claude to set it up โ€” point it at this repo and it'll wire the
MCP server into your client for you (that's what the [video](https://youtu.be/-gU7yu6liOw) shows).

**Manual:** register it yourself. For **Claude Desktop** (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "r1-dash-master": {
      "command": "python3",
      "args": ["/path/to/r1_dash_master/server.py"]
    }
  }
}
```

## Examples vs. Gallery

- **`examples/*.json`** โ€” source specs, for driving the MCP/builder and learning the spec format.
- **`gallery/*.zip`** โ€” prebuilt, **ready-to-import** dashboards. Since bundles are tenant-less,
  they auto-rescope to whatever EC you import them into. Grab one โ†’ Data Studio โ†’ Settings โ†’
  Import Dashboard. Current set: executive_overview, capacity_rf, connection_health,
  network_intelligence, switch_health, chart_gallery, delivered_throughput.
- Regenerate the gallery from specs anytime: **`./build_gallery.sh`** (keeps zips in sync).

## Status

Catalog: 18/19 datasets mapped (AP Alarms & Controller Inventory are SmartZone-only, N/A in R1).
Viz (15): bignum, bignum_trend, line, bar, area, scatter, pie, table, gauge, heatmap, funnel, pivot, mixed, tree, bubble. Query grammar: saved +
custom-SQL metrics, percent-of-total, dimension + time filters, d3 formats. Cross-filtering is
built in (click a chart value to filter the dashboard). Not yet: explicit dashboard-level native
filter bar; remaining viz (treemap, sunburst, box plot, radar, waterfall, graph, histogram,
calendar heatmap, sankey, smooth/stepped line); auto-import (needs an analytics-backend API โ€” import the zip via UI).