Skip to content

feat(semantic-layer): generate one metric per measure field in build#488

Open
yustme wants to merge 1 commit into
keboola:mainfrom
yustme:feat/semantic-layer-metric-per-measure
Open

feat(semantic-layer): generate one metric per measure field in build#488
yustme wants to merge 1 commit into
keboola:mainfrom
yustme:feat/semantic-layer-metric-per-measure

Conversation

@yustme

@yustme yustme commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Problem

kbagent semantic-layer build emitted a single COUNT(*) placeholder metric per table, no matter how many measurable columns it had. On a metrics table with dozens of numeric columns you got one metric and had to add the rest by hand — whereas the semantic-layer toolkit generates one metric per measure.

Fix

Emit one metric per measure field, with the aggregate guessed from the column name (mirrors the toolkit, with two deliberate refinements):

Column name contains Aggregate
avg / average / mean / rate / pct / percent / ratio / share AVG
max / maximum / peak MAX
min / minimum MIN
anything else (default) SUM

Refinements over a naive port:

  • percent / ratio → AVG, not SUM — summing a rate is almost always wrong and would trip the existing SUM_ON_PCT validation warning.
  • count_* stays SUM — additive daily counts sum to a total; COUNT() of an already-aggregated column is meaningless.

The COUNT(*) row-count metric is kept as a baseline, metric names are total_/avg_/max_/min_-prefixed, snake_cased, and deduplicated model-wide (_2, _3, … on collision).

Result

On a 47-measure table the build now yields ~48 metrics (43 SUM + 4 AVG for the pct/rate columns + 1 row count) instead of 1.

Tests

  • TestEstimateMetricAggregation: default SUM, count_* stays SUM, rate/pct → AVG, min/max.
  • TestHeuristicMetricPerMeasure: one metric per measure + row count; dimension columns produce none; duplicate measure names across tables are deduped.
  • Updated test_heuristic_fallback for the measure-metric + row-count pair.
  • Full tests/test_semantic_layer_service.py passes; ruff check clean.

Note

Independent of #486 (field-role classifier) and #487 (alias type resolution), but composes with them: #486/#487 make more columns correctly classify as measure, which this PR turns into metrics. The generated metric SQL reuses the builder's existing Snowflake-style FQN ("KEBOOLA"."<bucket>"."<table>") — pre-existing behaviour, unchanged here.

The heuristic builder emitted a single COUNT(*) placeholder metric per
table, regardless of how many measurable columns it had. Match the
semantic-layer toolkit: emit one metric per `measure` field, with the
aggregate guessed from the column name -- SUM by default, AVG for
rate/percent/ratio names (so it does not trip the SUM_ON_PCT validation),
MIN/MAX for min/max names. A `count_*` column stays SUM (additive counts
sum to a total). The COUNT(*) row-count metric is kept as a baseline, and
metric names are deduplicated model-wide.

On a 47-measure table the build now yields ~48 metrics instead of 1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant