Documentation
¶
Overview ¶
Package reports implements design/039: a report is a pure function of (spec, variables, identity). The JSON spec describes sections — KPI panels, charts, tables, narrative text — each fed by its own GraphQL query + jq transform over the shared variable set; controls describe the filter panel as a separate layer over the variables. Nothing is stored: the spec travels with every call. This PR ships the MCP surface only — the HTTP endpoint and stored specifications come later with the stored-reports line.
Index ¶
Constants ¶
const ( // MaxSections bounds a report: a document, not a dashboard farm. MaxSections = 24 // DefaultTimeout is the shared deadline for one report run — every // section query, options query and jq transform together. pkg/jq has no // execution budget of its own yet, so the report-level deadline is what // keeps a fan-out of N sections × M aliases bounded. DefaultTimeout = 60 * time.Second )
const ( // OptionsCap bounds one control's option list: a dropdown, not a table. // Exceeding it is an error asking to narrow the query — silently cutting // the list would offer the user a choice that quietly is not all of them. OptionsCap = 500 )
Variables ¶
var ( SectionKinds = []string{"kpi", "chart", "table", "text"} // ControlKinds mirror hugr's filter operators one to one: eq → select/ // number/date/toggle, in → multiselect, gte+lte → numrange/daterange, // ilike → search (wrapped by the control's template). ControlKinds = []string{"select", "multiselect", "search", "number", "numrange", "date", "daterange", "toggle"} )
Functions ¶
This section is empty.
Types ¶
type Bind ¶
Bind is a control's target: a scalar target (a variable name, or a dotted path into an input-typed variable, "flt.period.from") or a {from, to} pair for range controls. In JSON it is a string or an object — never both.
func (Bind) MarshalJSON ¶
func (*Bind) UnmarshalJSON ¶
type ChartSpec ¶
The chart mapping and the column list are the 038 contracts verbatim; the aliases let a spec be built without importing pkg/mcp/viz.
type ColumnSpec ¶
type ColumnSpec = viz.ColumnSpec
The chart mapping and the column list are the 038 contracts verbatim; the aliases let a spec be built without importing pkg/mcp/viz.
type Control ¶
type Control struct {
Label string `json:"label"`
// Kind is one of ControlKinds; empty means inferred from the bound
// leaf's GraphQL type where it is known.
Kind string `json:"control,omitempty"`
Bind Bind `json:"bind"`
Required bool `json:"required,omitempty"`
// Template wraps the raw input for the QUERIES — the placeholder is
// {value}, so "%{value}%" turns a search box into an ilike argument.
// Applied server-side at bind time; the echoed variables stay raw, so
// the panel always shows what the user typed.
Template string `json:"template,omitempty"`
Min *float64 `json:"min,omitempty"`
Max *float64 `json:"max,omitempty"`
// Options (static) and OptionsQuery (resolved by a data query) are
// mutually exclusive; an enum-typed bind needs neither — its options
// come from the schema.
Options []Option `json:"options,omitempty"`
OptionsQuery *OptionsQuery `json:"options_query,omitempty"`
}
Control is one element of the filter panel — a separate layer OVER the variables. One control may fill several targets (a date range fills two); a variable bound by no control is simply fixed by its default.
type ControlOptions ¶
type ControlOptions struct {
Label string `json:"label"`
Options []Option `json:"options,omitempty"`
Error string `json:"error,omitempty"`
}
ControlOptions is the resolved option list of one control; empty for controls with static options or none.
type Option ¶
Option is one static choice: a bare scalar in JSON, or {value, label}.
func (*Option) UnmarshalJSON ¶
type OptionsQuery ¶
OptionsQuery resolves a control's choices from data: the query runs with the CURRENT variable values (so dependent lists come for free), jq shapes the result into a list of scalars or {value, label} objects.
type ReportData ¶
type ReportData struct {
Variables map[string]any `json:"variables"`
Controls []ControlOptions `json:"controls,omitempty"`
Sections []SectionData `json:"sections,omitempty"`
}
ReportData is one run's outcome: the variable values actually used, the resolved option lists (index-aligned with spec.Controls) and the section results (index-aligned with spec.Sections). A section that failed carries its error INSIDE — one broken query must not take the document down.
func Run ¶
func Run(ctx context.Context, q viz.Querier, spec *Spec, vars map[string]any, opts RunOptions) (*ReportData, error)
Run executes the report: bind and check the submitted variables, resolve option lists in dependency order, then run every section concurrently under one shared deadline. The spec is re-validated — a run is a pure function of (spec, variables, identity) and trusts nothing it was handed.
type RunOptions ¶
type RunOptions struct {
// QueryTTL is the cache hint for every query this run executes
// (MCP_QUERY_TTL) — a re-render with unchanged variables becomes cache
// reads.
QueryTTL time.Duration
// MaxTimeout caps the spec's requested deadline; 0 means the built-in
// 5-minute ceiling.
MaxTimeout time.Duration
// OptionsOnly resolves the control option lists and skips the sections —
// the light mode a changed parent variable needs to refresh dependent
// lists without re-running the whole report.
OptionsOnly bool
}
RunOptions is what the runner takes from the deployment, not the caller.
type Section ¶
type Section struct {
Kind string `json:"kind"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
// Width is the human layout word (full | two_thirds | half | third |
// quarter); Span is the fine-grained 1..12 alternative. At most one.
Width string `json:"width,omitempty"`
Span int `json:"span,omitempty"`
// PageBreak "before" forces a page break in print.
PageBreak string `json:"page_break,omitempty"`
Query string `json:"query,omitempty"`
JQ string `json:"jq,omitempty"`
Chart *ChartSpec `json:"chart,omitempty"`
Columns []ColumnSpec `json:"columns,omitempty"`
Markdown string `json:"markdown,omitempty"`
}
Section is one block of the document. kpi/chart/table sections are fed by their own query + jq producing the 038 canonical shape; text sections are markdown. A chart with its companion table is deliberately two sections side by side, not a composite.
type SectionData ¶
type SectionData struct {
Kind string `json:"kind"`
Rows []map[string]any `json:"rows,omitempty"`
Kpis []viz.KPI `json:"kpis,omitempty"`
RowCount int `json:"row_count,omitempty"`
Truncated bool `json:"truncated,omitempty"`
AtLimit bool `json:"at_limit,omitempty"`
// RowsSampled is set only on the wire copy the model (and the first
// widget render) receives; the view pulls the full rows itself.
RowsSampled bool `json:"rows_sampled,omitempty"`
Error string `json:"error,omitempty"`
}
SectionData is one section's canonical result — rows for charts and tables, cards for KPI panels, nothing for text.
type Spec ¶
type Spec struct {
Title string `json:"title"`
Description string `json:"description,omitempty"`
Variables []Variable `json:"variables,omitempty"`
Controls []Control `json:"controls,omitempty"`
Sections []Section `json:"sections"`
// TimeoutSec overrides the shared run deadline (default 60), capped by
// the server's own limit at run time.
TimeoutSec int `json:"timeout_seconds,omitempty"`
}
Spec is the report definition. It is canonical JSON with 038-grade validation: precise errors naming the section, control and field.
func Parse ¶
Parse decodes a spec strictly — unknown fields are rejected, so a typo like "colums" or an invented control field fails loudly instead of silently dropping the intent — and validates it. Strictness is a hand-rolled walk rather than DisallowUnknownFields because the stdlib error carries no path and no vocabulary: `json: unknown field "name"` sends the caller guessing, while `controls[0] has unknown field "name" — allowed: …, bind, …` is a one-round-trip fix.
func (*Spec) Validate ¶
Validate checks the spec fail-fast with errors that name the section, control and field — the 038 discipline: a vague error costs the caller a whole round trip. It is spec-internal on purpose: whether a named type exists, and whether a value coerces into it, is the engine schema's business at run time.
type Variable ¶
type Variable struct {
Name string `json:"name"`
Type string `json:"type"`
// Default applies when no value is submitted at all; an explicit null
// stays null (that is how a filter is cleared).
Default any `json:"default,omitempty"`
Required bool `json:"required,omitempty"`
}
Variable is a plain, typed query parameter. Nothing about UI here — the panel is the controls' business. Type is a hugr GraphQL type reference in query syntax (String, Int, Date, [String!], an ENUM or INPUT type name); values are coerced by the engine's own schema machinery at run time, so the spec carries no second type system.