What a Number Means
Compare a measure with an earlier period or a target, say which way is good, choose how the change shows, and run a table calculation such as a running total or a share of the total, through Semaphor MCP.
An agent can set what each number on a card means, in the same words the editor uses: what a measure is compared with, which direction is good, how the change shows, and a table calculation such as a running total or a share of the total. It reads each one in the outline and changes it through semaphor_propose_change and semaphor_apply_change like every other change (see Making Changes).
{
"dashboardId": "d_8f2k",
"changes": [
{
"op": "card.update",
"cardId": "c_purchases_trend",
"query": { "measures": [{ "name": "net_purchase_value", "comparison": { "kind": "previous_year" } }] },
"comparisonDisplay": { "mode": "percent_change" }
}
]
}The proposal reads it back in plain words:
Compares Net Purchase Value in "Net purchase value by month" with the same period last year, and shows the comparison as a percent change.
Read What a Card Computes
semaphor_get_dashboard_details with fields: ["outline"] shows each measure's comparison and table calculation inside the card's query, and the card's comparison settings beside its columns:
{
"cardId": "c_purchases_trend",
"title": "Net purchase value by month",
"type": "line",
"query": {
"measures": [
{ "name": "net_purchase_value", "comparison": { "kind": "previous_year" } },
{ "name": "net_tons", "transform": { "kind": "moving_average", "orderBy": { "field": "purchase_date", "direction": "asc" }, "resetBy": [], "window": 3, "minPeriods": 1 } }
],
"dateField": "purchase_date",
"timeGrain": "month"
},
"comparisonDisplay": { "mode": "percent_change", "precision": 1 },
"columns": {
"Net Purchase Value": { "field": "net_purchase_value", "role": "measure", "polarity": "lower_is_better", "inherits": ["label", "format"] }
}
}A measure with no comparison follows the dashboard's date comparison. A table calculation shows every field it runs with, defaults included. Send what you read back and nothing changes, so an agent can copy a card's query, change one measure, and send it back.
Comparisons and Targets
A measure in query.measures takes a comparison:
kind | What the measure is compared with |
|---|---|
inherit | The dashboard's date comparison, when a viewer picks one. This is the default. |
off | Nothing, even when the dashboard compares |
previous_period | The period before. offsetPeriods goes further back; alignment: "weekday" matches weekdays; toDate: true compares the elapsed part of an incomplete period |
previous_year | The same period last year, with offsetYears, alignment and toDate |
target | A fixed number, targetValue |
{ "op": "card.update", "cardId": "c_total", "query": { "measures": [{ "name": "net_purchase_value", "comparison": { "kind": "target", "targetValue": 1000000 } }] } }- Comparisons show on KPIs, tables and bar, line and area charts; targets on KPIs and tables.
- A period or year comparison needs a date to compare over: the card's date grouping (on a KPI, its Trend By:
dateFieldwithtimeGrain), or the dataset's primary date. - It shows while a date range applies to that date: the card's own
timeWindow, such as{ "kind": "periods", "unit": "year", "from": 0, "to": 0, "toDate": true, "anchor": "now" }for year to date, or a dashboard date filter that reaches the card. A proposal says so in its warnings when none does yet. - A measure keeps its comparison when the change doesn't state one, even when the rest of the query changes.
Which Way Is Good
A measure's polarity in columns says whether a rise is good (higher_is_better, the default), bad (lower_is_better) or neither (neutral). It colors the change on that card.
{ "op": "card.update", "cardId": "c_returns", "columns": { "Returns": { "polarity": "lower_is_better" } } }When the semantic model sets a measure's direction, every card follows it: the outline lists polarity in the column's inherits, and a card built through the MCP takes it too.
How the Change Shows
| Setting | Values |
|---|---|
comparisonDisplay | { "show": false } hides the comparison; mode is difference, percent_change, both or ratio; precision sets the decimals of a percent or ratio, 0 to 6, and null returns it to the default |
inheritComparison | false: the card's measures without a comparison of their own ignore the dashboard's date comparison. true follows it again. |
A KPI, its tab header and its tile in a document show the change the same way.
Table Calculations
A measure in query.measures takes a transform, calculated over the rows the card returns:
kind | What the measure shows | Its own fields |
|---|---|---|
running_total | The total so far | |
moving_average | The average of the last window points | window (7), minPeriods (1) |
difference_from_previous | The change from the previous row | mode: difference or percent_change |
percent_of_total | Each row's share of the total | scope: grand_total or partition_total; output: percent or fraction |
orderBynames the grouping it runs along, with an optionaldirection. Left out, it runs along the card's date grouping; a card without one names the grouping to run along.resetBylists the groupings it starts over for, named as the query names them (a field of another dataset as{ "name", "datasetName" }). Left out, a running total, moving average or difference starts over for every other grouping. A share of a partition total (scope: "partition_total") must name the groupings each share is a part of; without them it's refused, listing the card's groupings.- A share of the total shows as a percent unless the column has a format of its own.
transform: nullremoves it. A measure's own comparison and a table calculation replace each other, and the sentence says so.
{
"op": "card.update",
"cardId": "c_monthly_by_facility",
"query": {
"measures": [{ "name": "net_purchase_value", "transform": { "kind": "running_total", "orderBy": { "field": "purchase_date" }, "resetBy": ["facility_id"] } }]
}
}Table calculations run on tables and bar, line, area and pie charts.
In semaphor_analyze
semaphor_analyze takes the same comparison and transform on its measures. A comparison applies to the whole analysis, so state it once (top level or on the measures), with kind and targetValue. A table calculation runs on the result rows:
{
"domainId": "dom_ops",
"datasetName": "purchase_line",
"measures": [{ "name": "net_tons", "transform": { "kind": "percent_of_total" } }],
"dimensions": ["facility_id"]
}A KPI's Trend By
A KPI's Trend By is its query's dateField and timeGrain. It is the date a comparison runs over, and it sets where a to-date comparison cuts the current period.
{ "op": "card.update", "cardId": "c_total", "query": { "dateField": "purchase_date", "timeGrain": "month" } }What Needs a Yes
A comparison, a target, a table calculation and inheritComparison change what a card computes, so their proposal returns confirm: "needed". A polarity or a comparisonDisplay only changes how a card reads, so on its own it returns confirm: "not_needed".
How Cards Read and Show
Change a card's column headers, number and date formats, hidden columns, widths, total row, data labels, legend, axes, chart variant and highlights, and a dashboard's title, through Semaphor MCP.
Improve a Semantic Model
Let an agent add synonyms, example questions, instructions, descriptions, formats and calculations to a semantic domain through Semaphor MCP, with every change previewed and nothing typed by hand lost.