reports

package
v0.3.44 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 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