Documentation
¶
Overview ¶
Package viz is the visualization core shared by the MCP viz tools (pkg/mcp) and the report renderer (pkg/mcp/reports): the canonical data contracts — flat rows and KPI cards — the chart/column specs, the caps, and the query+jq pipeline that feeds them (query.go). It deliberately knows nothing about MCP itself: tool surfaces, result envelopes and the HTML views stay with their owners. The package lives under pkg/mcp because that is where these contracts were born — they are the viz tools' wire format first, and the report spec reuses them verbatim.
Index ¶
- Constants
- Variables
- func CanonicalRows(v any) ([]map[string]any, error)
- func JQPathHint(query string) string
- func QueryHasLimit(query string) bool
- func QueryLimit(query string, vars map[string]any) (int, bool)
- func QueryRows(ctx context.Context, q Querier, query string, vars map[string]any, ...) ([]map[string]any, bool, error)
- func QueryValue(ctx context.Context, q Querier, query string, vars map[string]any, ...) (any, error)
- type ChartSpec
- type ColumnSpec
- type KPI
- type Querier
Constants ¶
const ( // KPICap bounds a KPI panel: it is a glance surface, not a table. The // whole panel always travels inline — no sampling, no refresh channel — // and the cap is what keeps that honest. KPICap = 32 // Above the soft limit a browser table stops being usable and the payload // stops being reasonable; the hard cap is what a view will never be asked // to hold. Between them sits a window the caller opened deliberately with // limit/offset, and that is their call to make. RowSoftLimit = 2000 RowHardCap = 10000 )
const PagingRecipe = `Three ways, best first: (1) AGGREGATE — a chart wants ` +
`<object>_bucket_aggregation, not raw rows, and an aggregate is usually what the question ` +
`actually asked for; (2) NARROW — put a condition in the query's own filter argument, which ` +
`is also how "only for X" is answered; (3) PAGE — obj(limit: 500, offset: 0) with order_by ` +
`so the order is stable, then call again with offset: 500 for the next page, each call ` +
`rendering its own view. Any limit you set is honoured, including a deliberate "first N of ` +
`the count above" — just tell the user that is what they are looking at.`
PagingRecipe is the one place that spells out how to bound a query, so every refusal points the same way.
Variables ¶
var ( ChartTypes = []string{"line", "bar", "area", "pie", "scatter"} KPIDirections = []string{"up_good", "down_good", "neutral"} KPIFormats = []string{"number", "percent"} )
Functions ¶
func CanonicalRows ¶
CanonicalRows validates the canonical-rows contract: an array of flat objects with scalar values. A single-key object chain (the usual {"module":{"table":[...]}} GraphQL shape) is unwrapped first, so most queries need no jq at all.
func JQPathHint ¶
JQPathHint recovers the first two selection levels of the query so a failed transform can show the caller the path it probably meant to write.
func QueryHasLimit ¶
QueryHasLimit reports whether the caller bounded the query themselves, either with a $limit variable or a literal limit: argument. It is a textual check on purpose: the query belongs to the caller and is not re-parsed here.
func QueryLimit ¶
QueryLimit recovers the row bound the caller set, from a literal `limit: N` or from the value bound to a $limit variable. Knowing it is what lets the result say "this page is exactly full, there is probably more" instead of leaving the caller to guess from a suspiciously round number.
func QueryRows ¶
func QueryRows(ctx context.Context, q Querier, query string, vars map[string]any, jqTransform string, maxRows int, ttl time.Duration) ([]map[string]any, bool, error)
QueryRows runs a read-only GraphQL query, optionally shapes it with jq, and canonicalizes the outcome into flat rows.
func QueryValue ¶
func QueryValue(ctx context.Context, q Querier, query string, vars map[string]any, jqTransform string, ttl time.Duration) (any, error)
QueryValue is the shared query pipeline: read-only hint, cache hint, jq fail-fast compile, execute, transform, and a JSON round-trip that normalizes engine/gojq value types into plain maps. What the value MEANS — rows or KPI cards — is the caller's canonicalizer's business.
Types ¶
type ChartSpec ¶
type ChartSpec struct {
Type string `json:"type" jsonschema_description:"line | bar | area | pie | scatter"`
X string `json:"x,omitempty" jsonschema_description:"Row field for the category/x value"`
Y []string `json:"y,omitempty" jsonschema_description:"Row fields holding values; several = one series each (wide form)"`
Series string `` /* 130-byte string literal not displayed */
Stacked bool `json:"stacked,omitempty" jsonschema_description:"Stack the series"`
}
type ColumnSpec ¶
type ColumnSpec struct {
Field string `json:"field" jsonschema_description:"Row field"`
Label string `json:"label,omitempty" jsonschema_description:"Header label (default: field name)"`
Format string `json:"format,omitempty" jsonschema_description:"number to right-align and group digits"`
Align string `json:"align,omitempty" jsonschema_description:"left | right"`
}
type KPI ¶
type KPI struct {
Label string `json:"label" jsonschema_description:"Card caption"`
Value any `json:"value" jsonschema_description:"The headline value: a number, or a short string"`
Unit string `json:"unit,omitempty" jsonschema_description:"Rendered next to the value ($, %, ms, …)"`
Format string `json:"format,omitempty" jsonschema_description:"number | percent"`
Delta *float64 `json:"delta,omitempty" jsonschema_description:"Absolute change vs the comparison period"`
DeltaPct *float64 `json:"delta_pct,omitempty" jsonschema_description:"Change in percent vs the comparison period"`
Direction string `json:"direction,omitempty" jsonschema_description:"up_good | down_good | neutral — colours the delta"`
Trend []float64 `json:"trend,omitempty" jsonschema_description:"Values drawn as a sparkline under the number"`
Subtitle string `json:"subtitle,omitempty" jsonschema_description:"Small line under the value (e.g. \"vs July\")"`
}
KPI is one card on a KPI panel. Label and value are the card; everything else decorates it.
func CanonicalKPIs ¶
CanonicalKPIs validates the canonical KPI-card contract: an array of card objects, label+value required, trend the only array field. Unknown keys are rejected by name — a typo like delta_percent must fail loudly with the allowed list, not render a bare card.