loopable / platform /model /skills /charting.skill.yml
fsanyoto's picture
Deploy AIOS web (React glide grid + FastAPI slice)
c14ceee verified
Raw
History Blame Contribute Delete
6.05 kB
# In-product SKILL: charting + analytics recipes (v1, 2026-07-16) β€” the visualization half of the
# tool registry. Teaches the small model WHICH form answers WHICH comparison (Zelazny: the form
# follows the comparison) and HOW to reach it: run_semantic_query -> transform_result (optional,
# governed) -> make_chart | make_table. The model NEVER computes derived numbers itself and NEVER
# emits HTML/vega β€” it picks a kind; the platform owns the pixels and every chart drills to its
# exact rows (result_id).
#
# This file versions TOGETHER with harness/tools.py (CHART_KINDS + transform ops) β€” a kind or op
# named here must exist there, and vice versa.
key: charting
label: Charts, tables, and analytics transforms
kinds: [line, bar, area, scatter, map, pie, donut, stacked_bar, grouped_bar, ranked_bar,
stacked_pct, combo, yoy_bars, waterfall, pareto, histogram, heatmap, treemap, funnel,
bullet, bubble, sparkline]
transforms: "the full 60-op analytics library (table calcs, stats, business templates, re-query
windows) lives in analytics.skill.yml β€” this file covers the viz forms they feed"
recipes:
- ask: "top [N] [customers|products|states] as a chart"
plan: >
run_semantic_query(group_by=[dim], sort=-measure, limit=high) ->
transform_result([{op: top_n, by: measure, n: N, other: false}]) ->
make_chart(kind=ranked_bar, x=dim, y=measure)
guard: >
for a RANKING use other:false (a giant 'Other' bar crushes the scale β€” the platform
captions what was cut); use other:true when the chart claims a share of the whole
(donut, stacked). Never truncate without the transform.
- ask: "how concentrated is [revenue|margin]? / do a few customers carry the book?"
plan: >
run_semantic_query(group_by=[dim], sort=-measure, limit=high) ->
make_chart(kind=pareto, x=dim, y=measure) # platform draws bars + cumulative-% line
guard: "pareto reads concentration; for a plain ranking use ranked_bar instead"
- ask: "share/mix of [revenue] by [category|BU] β€” part-to-whole"
plan: >
run_semantic_query(group_by=[dim]) -> if >6 groups: transform_result top_n ->
make_chart(kind=donut|pie, x=dim, y=measure). Mix OVER TIME: group_by dim + grain ->
make_chart(kind=stacked_bar, x=period, y=measure, series=dim); if the question is about
SHARES not levels -> stacked_pct. Hierarchical share -> treemap.
guard: "pie/donut only <=6 slices; percentages must come from the rows, never estimated"
- ask: "this year vs last year [by month] β€” YoY comparison"
plan: >
run_semantic_query(grain=month, date_from/date_to = this year) ->
transform_result([{op: yoy}]) -> make_chart(kind=yoy_bars, x=period, y=revenue)
guard: >
yoy re-runs the SAME governed query shifted -1 year; needs explicit date_from/date_to.
The platform prints the YoY % on the chart β€” never hand-compute deltas.
- ask: "what drove the change / bridge [revenue] from X to Y"
plan: >
get a result whose rows are SIGNED contributions (e.g. yoy transform then compute per-group
delta is NOT available β€” query each component) -> make_chart(kind=waterfall, x=label,
y=amount). The platform appends the Total bar.
guard: "steps must sum to the change being explained; if they don't, say what's missing"
- ask: "distribution β€” how are [order values|customer sizes] spread?"
plan: >
run_semantic_query(group_by=[entity dim], limit=high) ->
make_chart(kind=histogram, x=measure) | or transform_result([{op: bin, of: measure,
bins: 12}]) -> make_chart(kind=bar, x=bucket, y=count) when you want the buckets as rows
guard: "histogram bins per-entity values β€” group by the entity first, never bin a trend"
- ask: "is [X] related to [Y]? (two measures per entity)"
plan: >
run_semantic_query(group_by=[entity], measures=[X, Y]) -> make_chart(kind=scatter, x=X,
y=Y); a third measure sizes the dots -> kind=bubble, size=Z
guard: "state that correlation is visual, not causal; outliers get named in the answer"
- ask: "level AND rate together (GM$ + GM%, revenue + orders)"
plan: >
run_semantic_query(grain=month, measures=[level, rate]) ->
make_chart(kind=combo, x=period, y=level, y2=rate)
guard: "y = the dollar level (bars, left); y2 = the rate/count (line, right)"
- ask: "smoothed trend / running total / share columns"
plan: >
transform_result ops: moving_average{of, window} (adds <of>_maN) Β· running_total{of} Β·
share_of_total{of} Β· rank{by} β€” then chart the derived result (e.g. line of the _ma3
column, or make_table with the share column)
guard: "the first window-1 moving-average points are None by design β€” say so if asked"
- ask: "pipeline/stage conversion; actual vs target"
plan: >
funnel: rows in stage order -> make_chart(kind=funnel, x=stage, y=value).
target: make_chart(kind=bullet, x=label, y=actual, y2=target)
guard: "funnels need a real stage sequence; never reorder stages by size"
- ask: "same mini-chart per [BU|category] β€” compare shapes side by side"
plan: >
run_semantic_query(grain=month, group_by=[dim]) ->
make_chart(kind=line, x=period, y=measure, facet=dim)
guard: "facet shares scales across panels so shapes compare honestly"
- ask: "show me the numbers / a list / exact figures"
plan: >
run_semantic_query(...) -> make_table(result_id, columns=[...], title=?)
guard: >
make_table renders house-formatted with a totals row β€” prefer it over pasting rows into
the answer text whenever there are more than ~5 rows or 3 columns
context: >
Every chart/table keeps its result_id and semantic query β€” the platform renders a
"Data behind" drill for each one, and saved views re-run query + transforms live. If a form
needs a column the result lacks, transform first (the error message names valid columns).
When two forms could work, pick the simpler; never decorate.