Skip to Content

Tiles: the building blocks of a Flow

Available

  • The seven platform tiles
  • GET /v1/tiles, GET /v1/tiles/{id}
  • The self-describing GET /v1/tiles/{id}/spec

Planned

  • Creating and forking your own tiles (POST /v1/tiles, /fork, /test)
  • Python tiles

A Flow is a grid of tiles evaluated left to right; each tile turns inputs (sensor readings, weather, events, or the previous tile’s output) into an output series, and the last tile’s output decides whether the Flow triggers. In Growth Engine every tile, including the built-in ones, is a database record, so tiles can evolve without a release and you can add your own.

What a tile definition contains

FieldMeaning
key, name, description, version, isLatestidentity; versions are immutable; a Flow pins the version it was built with. Platform tiles gain a new version whenever Soiltech updates them; your flows keep running the pinned version until you re-save
paramsSchemaJSON Schema for the tile’s parameters; the GUI renders the tile’s configuration dialog from it
inputswhat data the tile needs (sensor metrics, weather variables, events); the Flow’s data requirements are derived from this
outputKindnumeric, boolean, probability or events
uiicon, color, palette group/order, short name, summary template, badges, help
language, scriptthe runtime logic (TypeScript/JavaScript or Python), run in the Engine’s sandbox

Built-in (platform) tiles

Built in and available to every tenant:

KeyNameDoes
gddGrowing degree daysdaily heat units above a base temperature (simple or modified with upper/lower cutoffs) over a (seasonal) date window; triggers at a threshold, optionally freezing once reached
chChill hourshours below a base temperature (default 7.2 °C) over a window; triggers at a threshold
bvSensor variablea sensor metric (air/soil temperature, humidity, matric potential, VWC, leaf wetness, rain) vs a threshold; modes: any / latest / overlap with the previous tile / rolling window, optional sustained duration
wvWeather variablethe same machinery on weather: temperature, humidity, rain (with accumulation), solar, daily ET₀, VPD (air or leaf offset), wind speed, wind direction (45° sector), dew point; day/night filter
accProbability0–100 % from the previous tile, in time mode (steps up while it stays triggered, down while it doesn’t) or value mode (scales the value between 0 % and 100 % points); the flow’s probability and colour come from the first acc
twTime windowpasses the previous tile’s triggers only inside fixed dates, a rolling window from a start date, or the last N hours/days
seScout eventsplanted / harvested / irrigated / custom events with each-time, running-total or yes/no thresholds, recency or sustained duration

Every data-consuming tile (gdd, ch, bv, wv, se) has a dataSource parameter: generate (the default today: deterministic demo data so you can build and run flows before your data is connected) or adapter (your own data via adapters; a run with adapter fails with a clear message until adapters ship).

The tile spec: build your own tile UI

Every tile is self-describing. GET /v1/tiles/{id}/spec returns everything a client needs to show the tile in a palette and to build its configuration dialog, with no tile-specific code on your side. (GET /v1/tiles/{id} returns the raw definition incl. the JSON Schema; /spec flattens it.)

{ "id": "5d3c…", "key": "gdd", "version": 1, "name": "Growing degree days", "description": "Accumulates heat units above a base temperature over a date window and triggers at a threshold.", "outputKind": "numeric", "isPlatform": true, "language": "typescript", "inputs": [ { "kind": "weather", "variables": ["tempmax", "tempmin"], "when": { "source": "weather" } }, { "kind": "series", "metrics": ["temperature.ambient"], "when": { "source": "sensor" } } ], "ui": { "icon": { "kind": "mdi", "value": "WbSunny" }, "color": { "light": "#f59e0b", "dark": "#fbbf24" }, "group": "Weather", "order": 10, "shortName": "GDD", "summaryTemplate": "GDD base {{tBase}}°C ≥ {{threshold}}", // shown on the grid cell "badges": { "numeric": true }, "help": { "markdown": "Growing degree days (GDD) measure accumulated warmth…" } }, "params": [ { "name": "dataSource", "type": "string", "title": "Data source", "widget": "select", "group": "Data", "order": 0, "default": "generate", "enum": ["generate", "adapter"], "options": [ { "value": "generate", "label": "Generated demo data" }, { "value": "adapter", "label": "Adapter fetch (not yet available)" } ] }, { "name": "tBase", "type": "number", "title": "Base temperature", "unit": "°C", "widget": "slider", "minimum": -20, "maximum": 40, "step": 0.1, "default": 10, "group": "Method", "order": 3 }, { "name": "tUpper", "type": "number", "title": "Upper cutoff", "unit": "°C", "showIf": { "isSimple": [false] }, "group": "Method", "order": 4 }, { "name": "threshold", "type": "number", "title": "Trigger at", "unit": "GDD", "required": true, "default": 250, "group": "Trigger", "order": 6 }, { "name": "startDate", "type": "string", "format": "date", "widget": "date", "title": "Start date", "group": "Window", "order": 7 } // … ], "paramsSchema": { "type": "object", "properties": { "tBase": { "type": "number", "x-ui-unit": "°C", "x-ui-widget": "slider" /* … */ } } }, "notes": [ "Seasonal windows wrap the year; …" ] }

Parameter entries carry the JSON-Schema type, title/description, required, default, the expected range (minimum/maximum/step), format (date), enum + options (value/label, optionally per-option unit), unit or unitFrom (unit follows another param, e.g. the chosen metric), widget (select, switch, slider, number, date, text, metric-picker, rules, …), group/order for layout, showIf ({param: [values]}) for dependent fields, and any further x-ui-* hints verbatim under ui. params is sorted by order. The same information is embedded in the definition’s JSON Schema as x-ui-* keywords (paramsSchema is returned too), so a generic JSON-Schema form renderer works. The Engine validates flow parameters against that schema on save and returns field-level errors (errors[].loc like tiles[0].params.threshold).

Your own tiles (planned)

Create a tile with POST /v1/tiles (scope tiles:manage), or fork a platform tile as a starting point; give it a paramsSchema, and provide the script:

// evaluate(ctx, params, inputs, upstream) → TileOutput import type { TileCtx, TileInputs, TileOutput } from "@ge/tiles"; // helpers: durationMs, compare, resolveWindow, evaluateSeries… export default function evaluate(ctx: TileCtx, params: { frostC: number }, inputs: TileInputs, upstream: TileOutput | null): TileOutput { const temps = inputs.series?.["temperature.ambient"] ?? []; // [[ts (ms), value], ...] const points = temps.map(([ts, t]) => ({ ts, value: t, isTriggered: t <= params.frostC })); return { kind: "boolean", points, isTriggered: points.at(-1)?.isTriggered ?? false, lastValue: points.at(-1)?.value }; }

Python tiles export def evaluate(ctx, params, inputs, upstream) with the same shapes as dicts. Tile scripts are pure functions (no network, and no clock beyond ctx.now), so results are reproducible. Test a tile with sample inputs via POST /v1/tiles/{id}/test, then it appears in your palette next to the platform tiles.

Endpoints

Available (user gate: GUI sessions and tenant keys)

  • GET /v1/tiles (latest versions visible to you; ?allVersions=true for all)
  • GET /v1/tiles/{id}
  • GET /v1/tiles/{id}/spec

Planned (scope tiles:manage)

  • POST /v1/tiles
  • POST /v1/tiles/{id}/versions
  • PATCH /v1/tiles/{id}
  • POST /v1/tiles/{id}/fork
  • POST /v1/tiles/{id}/test