AgentSkillsCN

Dashboard

创建、编辑并迭代数据仪表板。当用户希望获得带有图表、表格、指标和筛选器的持久化可视化分析——并以文件形式保存于/dashboard/{name}/时使用。

SKILL.md
--- frontmatter
name: Dashboard
description: Create, edit, and iterate on data dashboards. Use when the user wants a persistent visual analysis with charts, tables, metrics, and filters — saved as files at /dashboard/{name}/.

Dashboard Creation

When to create a dashboard

By the time you're here, you should either have explored the data in conversation already, or have a request specific enough to build from directly. If neither is true, go back to the conversation and explore first.

Check prior work

Read /dashboard/index.md before creating anything new. If related dashboards exist, tell the user what's there and how it relates to their request. Ask whether to extend or start fresh.

Working notes

Create notes.md in the dashboard folder to record context that isn't visible in the dashboard itself: analytical decisions, data caveats, why you chose one table over another, filter logic, or anything useful for understanding the dashboard later. This file is not rendered in the app.

Dashboard structure

Create dashboards at /dashboard/{name}/ with three required files:

code
dashboard/{name}/
├── queries/         # One .sql file per query
│   ├── regions.sql
│   └── trend.sql
├── outputs.py       # @output functions → Metric, DataFrame, or Plotly figure
└── dashboard.md     # Layout with containers and component tags

queries/*.sql

One file per query. Filename (without extension) = query name.

Use :param for filter binding. The framework extracts params via regex — only params found in the query are bound, others get NULL.

For optional filters, use the IS NULL OR pattern:

sql
SELECT
    tid, indhold as population
FROM fact.folk1a
WHERE kon = 'TOT'
  AND (:region IS NULL OR omrade = :region)
  AND (:period_from IS NULL OR tid >= :period_from)
  AND (:period_to IS NULL OR tid <= :period_to)
ORDER BY tid

Avoid :: PostgreSQL casts in the same position as :param — the regex uses negative lookbehind to distinguish them, but keep casts away from param names to be safe.

Queries referenced by options="query:..." in a filter tag are options queries — run at page load to populate dropdowns. All others are data queries — run when outputs lazy-load.

outputs.py

Functions decorated with @output that produce dashboard content. The names output, Metric, px, go, and pd are pre-injected — no imports needed for these, though explicit imports also work.

python
@output
def population_trend(trend, filters):
    return px.line(trend, x="tid", y="population", title="Population Over Time")

@output
def total_population(trend, filters):
    current = trend["population"].iloc[-1]
    return Metric(value=current, label="Total Population", format="number")

@output
def region_table(by_region, filters):
    return by_region

Dependency injection: parameter names matching a query name receive that query's result as a DataFrame. The filters parameter receives all current filter values as a dict.

Return types:

Return typeRendered asMarkdown tag
Plotly FigureInteractive chart<fig />
pd.DataFrameDaisyUI table<df />
MetricMetric card<metric />

Metric model:

python
Metric(
    value=1234567,           # float, int, or str
    label="Total Revenue",
    format="number",         # "number" (K/M/B), "currency" (kr.), "percent" (%)
    change=0.12,             # optional ratio; renders as +12.0% ((current - prev) / prev)
    change_label="vs last year",
)

change=0.2 renders as +20.0%, not +0.2 units.

dashboard.md

Extended markdown with ::: container syntax and self-closing component tags. Regular markdown (headers, paragraphs, lists) renders normally between components.

Containers

ContainerAttributesPurpose
filtersWraps filter components in a form
gridcols (default: 2)CSS grid layout
tabsTab container
tabnameIndividual tab panel

Nesting: one level supported (e.g. grid inside tab). ::: always closes the most recently opened container.

Component tags

Output tags — reference an @output function by exact name:

markdown
<fig name="trend_chart" />
<df name="detail_table" />
<metric name="total_count" />

Filter tags:

markdown
<filter-select name="region" label="Region" options="query:regions" default="all" />
<filter-date name="period" label="Period" default="all" />
<filter-date name="period" label="Period" default_from="2020-01-01" default_to="2025-12-31" />
<filter-checkbox name="include_pending" label="Include Pending" default=false />
  • filter-select: options="query:{query_name}" supports:
    • single-column query: value and display label are the same.
    • two-column query: first column = value sent in URL/SQL param, second column = display label. default="all" means no filter (NULL in SQL). If your options query is SELECT kode, titel ..., filter with d.kode::text = :region.
  • filter-date: produces {name}_from and {name}_to params. default="all" = no date filter. Can set default_from/default_to independently.
  • filter-checkbox: default=true or default=false. URL format: ?name=true or ?name=false.

Example dashboard.md

markdown
# Det Danske Boligmarked

::: grid cols=4
<metric name="total_boliger" />
<metric name="andel_parcelhuse" />
<metric name="andel_etageboliger" />
<metric name="prisindeks_seneste" />
:::

::: tabs

::: tab name="Boligbestand"
Udvikling i boligbestanden over tid.
<fig name="boligbestand_chart" />
<df name="boligbestand_tabel" />
:::

::: tab name="Prisudvikling"
Prisindeks for forskellige ejendomstyper (2015=100).
<fig name="prisindeks_chart" />
<df name="prisudvikling_tabel" />
:::

::: tab name="Salgsaktivitet"
<fig name="salg_antal_chart" />
<fig name="salg_priser_chart" />
:::

:::

Workflow

  1. Check prior work — read /dashboard/index.md. If related dashboards exist, tell the user and ask whether to extend or start fresh. Reuse queries and patterns where they fit.
  2. Plan structure — decide which queries, outputs, and filters the dashboard needs. If you explored data in conversation, reuse those queries as starting points.
  3. Write queries — one .sql per query in queries/. Use :param and IS NULL OR for optional filters.
  4. Write outputs.py — one @output function per visual element.
  5. Write dashboard.md — layout with containers and component tags. Add markdown commentary to explain the data.
  6. Validate — Use ValidateDashboard(url?) to run full end-to-end check of the dashboard.
  7. Snapshot and inspectSnapshot() to materialize outputs. Read the PNGs and JSON selectively to verify the dashboard looks right.
  8. Iterate — fix issues, re-validate, re-snapshot.
  9. NavigateUpdateUrl(path="/dashboard/{name}") to open it.

Validation

Use ValidateDashboard to execute all SQL queries and output functions before snapshotting:

code
ValidateDashboard(url="/dashboard/boligmarked")

If validation fails, fix the listed query/output errors first. If validation passes with warnings (for example empty results for filtered views), decide whether to continue to snapshot.

Snapshot

Use the Snapshot tool to capture the current dashboard state to disk. When already viewing a dashboard, call with no arguments:

code
Snapshot()

Or specify a URL with filters:

code
Snapshot(url="/dashboard/boligmarked?region=Hovedstaden")

Outputs are written to /dashboard/{name}/snapshots/{query}/:

  • dashboard.png — full page screenshot
  • figures/{output_name}.png — each chart as PNG
  • tables/{output_name}.parquet — each table as parquet
  • metrics.json — all metrics as JSON

Inspect results with Read on the png and json files, or check parquet files to verify data.