File size: 11,699 Bytes
c14ceee | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | # In-product SKILL: the ANALYTICS TRANSFORM LIBRARY (v1, 2026-07-18) — the Tableau-class
# table-calc + analytics capability set, mapped onto the ONE governed transform_result tool.
# Grounding: .claude/wiki/research/analytics-tools.md. Every op here EXISTS in harness/transforms.py
# (OPS) and is unit-proven in _demo_analytics.py — recipes and code version together.
#
# HOW IT WORKS: run_semantic_query -> transform_result([{op, ...}, ...]) -> make_chart|make_table.
# Ops CHAIN (each takes the previous derived result). The model picks op names + params; the
# platform computes deterministically over the exact governed rows and every derived result still
# drills to its numbers. NEVER do arithmetic in your head — there is an op for it.
key: analytics
label: Analytics transforms (table calcs, stats, business templates)
# ========================================================================= THE ADDITIVITY LAW
# The ONE rule that must never be broken: revenue, units, margin, cogs, orders are ADDITIVE — you
# may sum/accumulate them. customers (a DISTINCT count) and margin_pct / aov (RATIOS) are NOT
# additive — summing them over months or groups OVER-COUNTS and is forbidden. The accumulating ops
# (running_total, share_of_total, cum_share, moving_sum, abc_classify, concentration) REFUSE a
# non-additive measure with an error. For a CUMULATIVE or TRAILING distinct count / ratio, use the
# re-query ops ytd / rolling (they re-run the governed query per window — correct by construction),
# never running_total/moving_sum.
# ========================================================================= THE OP CATALOG (families)
ops:
ordering_rank: >
sort{by,direction} · head{n} · bottom_n{by,n} · rank{by,direction} · rank_pct{by,direction}
(percentile rank 0-100) · ntile{by,tiles,direction} (quartile=4/decile=10 buckets) ·
top_n{by,n,other} (N largest + honest Other) · add_total{label} (grand-total row).
part_to_whole: "share_of_total{of} (adds <of>_share_pct) · cum_share{of} (pareto prep, sorted). ADDITIVE-only."
running_moving: >
running_total{of} (ADDITIVE-only) · running_avg{of} · running_max{of} · running_min{of} ·
running_count{} (cumulative # of rows) · moving_average{of,window} · moving_sum{of,window}
(ADDITIVE-only) · moving_median{of,window} · rolling_std{of,window} (trailing volatility).
running_avg/max/min and the moving averages/medians/std are smoothings — valid on any measure.
period_over_period: >
diff{of} (vs previous row, one column) · pct_change{of} · lag{of,k} · lead{of,k} ·
diff_from_first{of} · index_to_100{of} (rebase a series to 100) · percent_of_max{of} ·
compare{a,b,how:diff|pct|ratio} (between TWO existing columns, e.g. revenue vs revenue_ly).
All row-wise — safe on any measure (a period-over-period change of a ratio is fine).
distribution_stats: >
bin{of,bins} (histogram buckets) · describe{of} (count/mean/median/std/min/p25/p75/max[/sum]) ·
zscore{of} · outliers{of,method:zscore|iqr,k} · winsorize{of,p} · clip{of,lo,hi} ·
normalize{of} (0-1) · correlate{x,y} (Pearson r) · weighted_average{of,weight} ·
safe_ratio{numerator,denominator} (row-wise, guarded) · product{a,b}.
business_templates: >
abc_classify{of,a=80,b=95} (Pareto A/B/C tiers, ADDITIVE-only) · concentration{of}
(HHI+Gini+top1/5/10 share, ADDITIVE-only) · contribution_to_change{of} (needs <of>_delta from
yoy — who drove the change) · rfm{recency,frequency,monetary,tiles=5} · funnel_rates{of}
(stage step% + overall%).
modeling: >
trend_line{of} (adds <of>_trend fitted line to draw over the series) · regression{x,y}
(slope/intercept/r2) · cagr{of} (compound per-period growth) · growth_rate{of} (total first->last).
Transparent least-squares only — no black-box models.
reference_annotations: >
reference_line{of,stat:mean|median|min|max} (adds <of>_ref constant) · reference_band{of,
method:stddev|percentile,k} (adds <of>_band_lo/_hi) · target_line{value} · xmr_limits{of}
(Wheeler control chart: adds <of>_center/_ucl/_lcl + <of>_signal bool — the business-native
way to flag a REAL signal vs noise in a monthly series). Draw the constant columns as extra
series on a combo/line chart (the Analytics-pane average/reference/target/control line).
reshape: >
pivot{index,column,value} (long->wide crosstab; RAISES on collision — regroup the query, never
a hidden sum) · unpivot{keep,columns} (wide->long melt) · filter_rows{col,cmp,value}
(cmp: >,>=,<,<=,==,!=,contains — a 'having' clause) · dedupe{by} · resample{grain} (fill a
GAPPY date spine with None rows — run BEFORE window ops on sparse/filtered time series).
requery_windows: >
yoy{} (adds <m>_ly/_delta/_yoy_pct; needs date_from/date_to) · ytd{of} (CORRECT cumulative
year-to-date, incl. distinct counts) · rolling{of,window} (CORRECT trailing-N-period value,
incl. distinct counts — T12M revenue, trailing-3-mo customers) · forecast{of,periods,method:
linear|seasonal_naive} (appends future rows with <of>_forecast). These re-run the governed
query per window — the ONLY correct path for cumulative/trailing NON-additive measures.
# ========================================================================= RECIPES (compose ops)
recipes:
- ask: "80/20 — which [customers|SKUs] carry the book? / ABC classification / concentration"
plan: >
run_semantic_query(group_by=[dim], measures=[revenue], sort=-revenue, limit=1000) ->
transform_result([{op: abc_classify, of: revenue}]) -> make_table (A/B/C tiers + cum %).
For a single concentration read: transform_result([{op: concentration, of: revenue}]) ->
make_kpi/table (HHI, Gini, top-5 share). For the picture: make_chart(kind=pareto).
guard: "abc_classify / concentration are ADDITIVE-only (revenue/units/margin) — never on customers."
- ask: "cumulative distinct customers YTD / running unique customers this year"
plan: >
run_semantic_query(topic=sales_lines, measures=[customers], grain=month, date_from, date_to)
-> transform_result([{op: ytd, of: customers}]) -> make_chart(kind=line, x=period,
y=customers_ytd)
guard: >
NEVER running_total on customers (distinct counts don't sum). ytd re-runs the query
Jan1->each month end — the correct cumulative distinct count. Same law: rolling for trailing.
- ask: "trailing-12-month / LTM [revenue|customers] (smooth out seasonality)"
plan: >
run_semantic_query(measures=[revenue], grain=month, date_from, date_to) ->
transform_result([{op: rolling, of: revenue, window: 12}]) -> make_chart(kind=line,
x=period, y=revenue_roll12)
guard: "rolling re-queries the trailing window — correct for customers (distinct) too, unlike moving_sum."
- ask: "forecast / project the next [3] months of [revenue]"
plan: >
run_semantic_query(measures=[revenue], grain=month, date_from, date_to) ->
transform_result([{op: forecast, of: revenue, periods: 3}]) -> make_chart(kind=line,
x=period, y=revenue, y2=revenue_forecast) or combo
guard: >
transparent linear/seasonal-naive projection, NOT a statistical model — say "a simple trend
projection, not a forecast model". Use method=seasonal_naive when the series is seasonal and
you have >= one full cycle of history.
- ask: "add an average line / target line / ±1 std band to a chart"
plan: >
transform_result([{op: reference_line, of: revenue, stat: mean}]) then make_chart(kind=combo,
y=revenue, y2=revenue_ref). Target: [{op: target_line, value: 500000}] -> make_chart(
kind=bullet, y=revenue, y2=target). Band: [{op: reference_band, of: revenue, method: stddev,
k: 1}] -> table/chart with revenue_band_lo/_hi.
guard: "the reference is drawn as an extra series/column — never annotate by editing the answer text."
- ask: "who DECLINED the most YoY / biggest movers, and who drove the total change"
plan: >
run_semantic_query(group_by=[order_partner], measures=[revenue], team_id?, date_from, date_to,
limit=1000) -> transform_result([{op: yoy}, {op: contribution_to_change, of: revenue},
{op: sort, by: revenue_delta, direction: asc}, {op: head, n: 20}]) ->
make_table(columns=[order_partner, revenue, revenue_ly, revenue_delta, revenue_delta_share_pct])
guard: "yoy needs date_from/date_to; contribution_to_change needs the <of>_delta yoy adds first."
- ask: "distribution / outliers of [order value|customer size]"
plan: >
run_semantic_query(group_by=[entity], measures=[revenue], limit=1000) ->
transform_result([{op: describe, of: revenue}]) -> make_table (or {op: outliers, of: revenue,
method: iqr} then filter_rows revenue_outlier == true, or {op: bin} -> histogram)
guard: "describe/outliers operate on per-entity values — group by the entity first, not a trend."
- ask: "crosstab / pivot [revenue] by [month] x [business unit]"
plan: >
run_semantic_query(measures=[revenue], grain=month, group_by=[team]) ->
transform_result([{op: pivot, index: period, column: team, value: revenue}]) -> make_table
guard: >
pivot is a pure reshape — if it RAISES a collision, the query grain is too coarse (two rows
per cell); regroup the query. It never hides a sum.
- ask: "is [X] correlated with [Y]? / relationship between two measures"
plan: >
run_semantic_query(group_by=[entity], measures=[X, Y], limit=1000) ->
transform_result([{op: correlate, x: X, y: Y}]) -> report r; for the picture make_chart(
kind=scatter, x=X, y=Y). regression{x,y} adds slope/intercept/r2.
guard: "correlation is not causation — say so; name the outliers rather than hiding them."
- ask: "is [this month's revenue] a REAL signal or just noise? / control chart / XmR"
plan: >
run_semantic_query(measures=[revenue], grain=month, date_from, date_to) ->
transform_result([{op: xmr_limits, of: revenue}]) -> make_chart(kind=combo, x=period,
y=revenue, y2=revenue_ucl) and report the <of>_signal points as the real signals
guard: >
XmR (mean ± 2.66·mean-moving-range) is how this business separates signal from noise
(warehouse/expenses use it). A point is only a SIGNAL if <of>_signal is true — don't call a
within-limits wiggle a trend.
- ask: "[any trend/cumulative question on a filtered or sparse monthly series]"
plan: >
run_semantic_query(measures=[...], grain=month, ...) -> transform_result([{op: resample,
grain: month}, {op: running_total, of: revenue}]) # resample FIRST
guard: >
if a month has no rows (filtered scope, a quiet SKU), the window ops (running_total,
moving_average, diff, ytd) would silently skip the gap and misalign. resample inserts the
missing months as None first so the series is honest.
context: >
This library is the analytics half of the tool registry (charting.skill.yml is the viz half).
The additivity law is absolute: additive = revenue/units/margin/cogs/orders; NOT additive =
customers (distinct) and margin_pct/aov (ratios). When an accumulating op errors on additivity,
switch to the re-query op (ytd/rolling) — do not work around it. rfm needs recency (days since
last order), which is not yet a semantic metric — frequency=orders and monetary=revenue exist,
so if asked for RFM today, deliver F+M scoring and report recency as the missing input (a gap).
Every derived result keeps its result_id and drills to rows; saved views replay the whole chain.
|