Help us shape the future of Go! Take the 2026 Go Developer Survey

reports

package
v0.3.45 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 13 Imported by: 0

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

View Source
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
)
View Source
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

View Source
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

type Bind struct {
	Target string
	From   string
	To     string
}

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) IsRange

func (b Bind) IsRange() bool

IsRange reports whether the bind is the {from, to} form.

func (Bind) MarshalJSON

func (b Bind) MarshalJSON() ([]byte, error)

func (Bind) Targets

func (b Bind) Targets() []string

Targets lists the bound paths — one for a scalar bind, two for a range.

func (*Bind) UnmarshalJSON

func (b *Bind) UnmarshalJSON(data []byte) error

type ChartSpec

type ChartSpec = viz.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

type Option struct {
	Value any    `json:"value"`
	Label string `json:"label,omitempty"`
}

Option is one static choice: a bare scalar in JSON, or {value, label}.

func (*Option) UnmarshalJSON

func (o *Option) UnmarshalJSON(data []byte) error

type OptionsQuery

type OptionsQuery struct {
	Query string `json:"query"`
	JQ    string `json:"jq,omitempty"`
}

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.

func (*Section) GridSpan

func (s *Section) GridSpan() int

GridSpan resolves the section's grid width in columns (12 = full row).

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

func Parse(data []byte) (*Spec, error)

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) Timeout

func (s *Spec) Timeout() time.Duration

Timeout is the run deadline the spec asks for; the runner caps it.

func (*Spec) Validate

func (s *Spec) Validate() error

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL