Skip to content
Conduit

mcp server

meridian

Embeddable .NET framework for longitudinal reporting: metrics → composable transforms → chart-agnostic views, with pluggable caching and an MCP agent surface.

gyandal/meridiancommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 22, 2026

Meridian

CI NuGet License: MIT

Embeddable, time-correct reporting for .NET systems that already own their data.

Meridian turns points over time — sensor readings, training loads, trips, orders, usage events — into finished, chart-ready series. You define your metrics once and point Meridian at the store you already have (a SQL database, Parquet files, an API); it handles bucketing in the right time zone, gap filling, rolling windows, comparisons, caching and invalidation, and hands back chart-agnostic JSON that any front end, API client or AI agent can use.

It's a set of NuGet packages, not a service: no new infrastructure, no data copy.

var chart = await meridian.Engine.RunAsync(
    PipelineSpec.Create("my-club", trainingLoad, athletes, lastSixMonths,
        new ViewSpec(ChartKind.Line, AxisSource.Time, SeriesBy: athlete),
        Transform.Resample(Period.Week, Aggregators.Mean, GapPolicy.LeaveMissing)),
    ProjectionOptions.Default with { Calendar = CalendarContext.For("Europe/London") });

Why Meridian

  • Time done right. Instants and calendar dates are different kinds of time, and Meridian keeps them apart: readings are bucketed into days and weeks in the report's time zone (DST included), dates of birth and match days never shift, and legacy wall-clock columns are read with explicit DST rules. How time works →
  • Fast on data you already have. Bucketing is pushed down into the database when it can be done exactly, so the first run costs what hand-written SQL costs; repeats come from cache in microseconds. Several metrics for the same entities are fetched in one query.
  • Caching that doesn't serve stale data. Per-entity slices (adding one entity to a report fetches only that entity), finished-view caching, and invalidation driven by writes — versioned keys mean a missed event can't serve stale data.
  • One metric, many views. Group the same metric by player in one chart, by venue in another and by month in a third: every attribute the data carries is a dimension, and charts that slice the same metric share one fetch — so a dashboard can go from the whole squad to one player without new queries.
  • Derived metrics, computed correctly. Define goals per 90 once, as goals ÷ minutes × 90; every chart divides totals for each bucket and group (never averages per-match ratios), treats no goals as zero, and fetches both inputs in one query. Sums and differences (goal involvements = goals + assists) and shares of a total (each player's % of the squad's goals) work the same way.
  • Compare with last season, correctly. Any report can be drawn against an earlier period (last season on this season's axis) or turned into the change since then. Comparisons line up bucket for bucket — a weekly one steps back 52 weeks, so Mondays meet Mondays — and "so far this season" meets the same point last season.
  • Projections that say they're projections. Forecast any bucketed series — the pace so far, a trend, last season's shape — to a number of buckets or the end of the season. "On pace for 14 goals" is a forecast then a running total, with its range — "14, between 11 and 17" — computed for the total, not summed from monthly ranges; every value built on a projection is marked, so charts can draw it differently.
  • Dashboards as data. A dashboard is JSON a product can store and let users edit; charts share one context (players, timeframe), load together, and focusing on one player is served from cache.
  • One definition, every surface. The same metric catalog drives charts, a REST API and a typed agent tool surface (describe the metrics, query a bounded report) — safer than letting an agent write SQL.
  • Chart-agnostic output. ChartView is plain JSON: series, marks, typed axes, server-side labels and status. Map it to Highcharts, Recharts, D3, or a table.

Use cases

  • Athlete and team performance — training load per athlete per week, acute:chronic workload ratios, wellness scores by date, this season vs last.
  • Fleet, IoT and infrastructure telemetry — per-device minute and hour rollups over billions of readings (the TSBS workload).
  • Operations and mobility — orders, trips or tickets per site per week in each site's local time (see the NYC taxi benchmark).
  • Per-customer product analytics — usage per account per week, embedded in your own product.
  • Agent-facing analytics — let an assistant answer "how did load change for these players this month?" through bounded, typed queries.

Meridian is not a BI tool or a query language: it's the reporting layer inside your application.

Install

dotnet add package Meridian.Engine --prerelease
dotnet add package Meridian.Sources.DuckDb --prerelease   # or .PostgreSql, .SqlServer, .MySql, .ClickHouse, or your own IPointSource
dotnet add package Meridian.Views.Json --prerelease       # the JSON contract for ChartView

Runs on .NET 8, 9 and 10. Each package ships a build per runtime, so a .NET 10 app gets the build that uses .NET 10's newer APIs, and the full test suite runs on all three.

Quick start

From samples/Meridian.Sample.QuickStart — runnable with dotnet run --project samples/Meridian.Sample.QuickStart:

var athlete = new DimensionId("athlete");
var trainingLoad = new MetricId("training-load");

// 1. Define the metric once.
var catalog = new InMemoryMetricCatalog(
[
    new MetricDefinition(trainingLoad, "Training load", new Unit("au"), "mean", [athlete], TimeGrain.Instant),
]);

// 2. Point Meridian at your data where it already lives: here, Parquet files queried in place by DuckDB.
var source = new DuckDbPointSource(
    new DuckDbSourceOptions("Data Source=:memory:", Relation: "read_parquet('data/load/*.parquet')"),
    athlete);

var meridian = MeridianRuntime.InMemory(catalog, source);

// 3. Ask for a report: weekly mean load per athlete, in London weeks.
var report = PipelineSpec.Create(
    tenant: "my-club",
    metric: trainingLoad,
    entities: [new EntityRef(athlete, 7), new EntityRef(athlete, 9)],
    timeframe: new DateInterval(Instant.FromUtc(new DateTime(2026, 1, 1)), Instant.FromUtc(new DateTime(2026, 7, 1))),
    view: new ViewSpec(ChartKind.Line, AxisSource.Time, SeriesBy: athlete),
    Transform.Resample(Period.Week, Aggregators.Mean, GapPolicy.LeaveMissing));

var chart = await meridian.Engine.RunAsync(report,
    ProjectionOptions.Default with { Calendar = CalendarContext.For("Europe/London") });

// 4. A chart-agnostic view: hand the JSON to any charting library, API client or agent.
Console.WriteLine(ChartViewJson.Serialize(chart));

When your data changes, tell Meridian and only the affected cache entries are dropped:

await meridian.Invalidator.InvalidateAsync(new ChangeScope("my-club", "training-load", new EntityRef(athlete, 7)));

A longer worked example (goals per athlete, this season vs last) is in docs/USAGE.md.

Benchmarks

Measured on a 6-core desktop; full method, tables and reproduction steps in docs/BENCHMARKS.md.

WorkloadHand-written SQLMeridian, first runMeridian, repeat
Weekly report, 25 entities, 1.03 billion rows of Parquet91 ms78 ms0.01 ms
Weekly trips per zone in New York time, 47.5M real NYC taxi trips717 ms735 ms0.01 ms
TSBS cpu-max-all-8 (10 metrics, 8 hosts), 259M values27 ms33 ms0.03 ms
TSBS double-groupby-all (130,000-point result)262 ms552 ms2.35 ms

TSBS rows read TSBS's native wide schema (a column per metric) on both sides.

The dashboard (dotnet run --project src/Meridian.Hosts.Http → http://localhost:5731) charts every committed benchmark run, with history.

Packages

PackageWhat it is
Meridian.Enginethe report engine: catalog → cache → pushdown/batching → transforms → view
Meridian.Core, Meridian.Time, Meridian.Transformsthe point model, time handling, the transform algebra
Meridian.Caching, Meridian.Semanticscache and invalidation; the metric catalog
Meridian.Views, Meridian.Views.Jsonchart-agnostic views and their JSON contract
Meridian.Sources.DuckDb, Meridian.Sources.PostgreSql, Meridian.Sources.SqlServer, Meridian.Sources.MySql, Meridian.Sources.ClickHousedata sources (Meridian.Sources.Sql is the engine the database ones share)
Meridian.Hosts.Mcpthe agent tool surface

Documentation

Roadmap

In preview (0.1.0-preview.x); APIs may change before 1.0. Next up: round-aligned comparisons, a Redis cache backend and an MCP server. Later: scenario modelling. See ROADMAP.md.

Contributing

Issues and pull requests are welcome — see CONTRIBUTING.md for building, testing and the few rules (warnings are errors; time and pushdown changes need parity tests). Please follow the code of conduct, and report security issues privately as described in SECURITY.md.

License

MIT © 2026 Stephen Wood

↑↓ navigate openesc close