Skip to main content
ActivityDefinitions define how complex activities are collected and calculated. Each definition bundles an input schema, a calculation function, and a React UI component, and can be reused across forms and sites.

Quick Start

  1. Create a new ActivityDefinition (Customization → Activity Definitions → New)
  2. Define inputs (manually or via Smart CSV)
  3. Write calculation code (async function returning { value, metadata })
  4. Build the UI (ActivityUI calling onAddActivity(values))
  5. Preview with test context → Save
Minimal calculation template:
async function calculate(inputValues) {
  const { electricityKwh = 0, gridRegion } = inputValues;
  const ef =
    (await $datasets.getCoefficient(
      "grid-emission-factors",
      String(gridRegion || "").toLowerCase(),
      "kgCO2ePerKWh",
      $year,
    )) ?? 0;
  return { value: electricityKwh * ef, metadata: { gridRegion, ef, year: $year } };
}
Minimal UI template:
function ActivityUI({ onAddActivity }) {
  const [values, setValues] = React.useState({ electricityKwh: 0, gridRegion: "" });
  const handleAddActivity = () => onAddActivity(values);
  return (
    <div className="space-y-3">
      <NumberInput label="Electricity (kWh)" value={values.electricityKwh}
        onChange={(v) => setValues((s) => ({ ...s, electricityKwh: Number(v ?? 0) }))} min={0} />
      <DatasetItemSelector datasetName="grid-emission-factors" value={values.gridRegion || null}
        onChange={(opt) => setValues((s) => ({ ...s, gridRegion: opt?.name ?? "" }))}
        placeholder="Search grid region…" />
      <Button onClick={handleAddActivity}>Add Activity</Button>
    </div>
  );
}

What An ActivityDefinition Includes

  • Input schema: strict, typed inputs your UI must collect
  • Calculation code: async JS function that produces a value and metadata
  • UI code: a React component users interact with to provide inputs

Input Schema

Inputs are defined with a compact schema used throughout the app (validation, UI scaffolding, CSV import hints):
const inputs = [
  { name: "electricityKwh", type: "number", required: true, example: 1200 },
  { name: "gridRegion", type: "string", required: true, example: "Türkiye" },
  { name: "notes", type: "string", required: false, example: "meter 3" },
];
Allowed types: "number" | "string". Each input has name, type, required, and an optional example.

Input Naming Guide

  • Use camelCase keys (e.g., fuelType, electricityKwh).
  • Avoid spaces/special characters. CSV Smart mode auto-cleans headers to camelCase.
  • Keep names stable; export columns use these names.
  • Provide example values to improve CSV detection and preview fidelity.

Calculation Code

Store the calculation as a single async function declaration. It must return an object with a numeric value and an object metadata.
// Required format (stored in the definition):
// async function calculate(inputValues) { /* ... */ }
async function calculate(inputValues) {
  const { electricityKwh, gridRegion } = inputValues;

  if (electricityKwh == null || electricityKwh < 0) {
    throw new Error("electricityKwh must be a non-negative number");
  }

  // Context helpers available in calculations:
  // $datasets, $kpis, $sites, $year, $period (YEARLY|QUARTERLY|MONTHLY), $periodUnit, $siteId
  const factor = await $datasets.getCoefficient(
    "grid-emission-factors",
    gridRegion,
    "kgCO2ePerKWh",
    $year,
  );

  const ef = factor ?? 0; // Fall back to 0 if missing
  const value = electricityKwh * ef;

  return {
    value,
    metadata: { gridRegion, factor: ef, year: $year },
  };
}
Returned value and any numeric metadata are sanitized server-side (NaN/Infinity → 0). Throw user-friendly Error messages to surface validation issues in the UI.

UI Component

The UI is a React component named ActivityUI that accepts a single prop onAddActivity(values) and calls it when the user submits.
function ActivityUI({ onAddActivity }: { onAddActivity: (values: any) => void }) {
  const [values, setValues] = React.useState({ electricityKwh: 0, gridRegion: "" });

  return (
    <div className="space-y-3">
      <NumberInput
        label="Electricity (kWh)"
        value={values.electricityKwh}
        onChange={(v) => setValues((s) => ({ ...s, electricityKwh: Number(v ?? 0) }))}
        min={0}
      />
      <DatasetItemSelector
        datasetName="grid-emission-factors"
        value={values.gridRegion || null}
        onChange={(opt) => setValues((s) => ({ ...s, gridRegion: opt?.name ?? "" }))}
        placeholder="Search grid region…"
      />
      <Button onClick={() => onAddActivity(values)}>Add Activity</Button>
    </div>
  );
}
Notes
  • Use DatasetItemSelector when an input corresponds to a known dataset (ensure datasetName matches your calculation code).
  • The system provides an AI assistant to draft or refine the UI and calculation code; you can edit the code directly at any time.

UI Code Contract

  • Define function ActivityUI({ onAddActivity }) { ... } in the snippet.
  • Use a click handler: onClick={handleAddActivity}; avoid default form submit flows.
  • No imports; only use components available in scope (Mantine UI and DatasetItemSelector).
  • Keep the component self-contained for the live runtime (no external variables).

Smart CSV Input Detection

When creating a new definition, you can bootstrap inputs from a CSV sample:
  • Upload a CSV file in the “Smart” tab of the New Activity Definition page.
  • The system parses headers and sample rows to infer input names/types and optional examples.
  • It also generates “AI notes” that can seed UI generation. You can then switch to the Manual tab to refine.
This flow fills the same input schema shown above; only name, type, required, and optional example are supported.

Live Preview & Test Context

In the Calculation and UI tabs, you can preview with real data:
  • Calculation preview executes your code with real dataset/KPI/site access through safe server APIs.
  • Configure test context: year, period (YEARLY/QUARTERLY/MONTHLY), periodUnit, and siteId.
  • Any async dataset/KPI/site lookups must be awaited in your calculation.

Public vs. Private Definitions

  • Private (default): visible only to your organization.
  • Public (isCommon): available to all organizations. Creating public definitions requires an ADMIN user.

Calculation Context & Helpers

Inside calculate, these helpers are available:
  • $datasets
    • getItem(datasetName, itemName, year?)
    • getDataset(datasetName, year?)
    • getCoefficient(datasetName, itemName, coefficientKey, year?)
    • getCoefficients(datasetName, year?)
  • $kpis
    • getIndicator(name)
    • getIndicatorById(id)
    • getAllIndicators()
    • getIndicatorsByTag(tag)
    • getIndicatorVariables(indicatorId)
  • $sites
    • getSite(id), getSiteByName(name), getAllSites()
    • getChildSites(parentId), getSitesByCity(city), getSitesByTag(tag)
    • getTotalFloorArea(siteIds), getTotalEmployees(siteIds)
  • $year, $period, $periodUnit, $siteId
All dataset/KPI/site access is enforced with row-level security for the current organization.

Using ActivityDefinitions In Forms

  • Add an ACTIVITY form element and link it to an ActivityDefinition.
  • The element’s key is used by the Activities API to add/import/recalculate data for that specific definition.
  • The UI code is rendered inline and calls onAddActivity(values) which creates a new Activity after running your calculation.
Workflow
  • If a workflow requires approval, submissions in the current period are marked COMPLETED; otherwise they are APPROVED.

When to Recalculate

  • After updating calculation logic, use bulk recalc to align existing activities: activities.recalculateActivities with filters by site/year/period.
  • If datasets used by your calc changed, recalc affected activities for the relevant periods.

Best Practices

  • Validate inputs early and throw user-friendly errors.
  • Always return { value: number, metadata: Record<string, any> }.
  • Keep dataset names consistent between UI and calc code.
  • Prefer numeric primitives in metadata when possible (non-numeric values are fine, but numbers are easier to chart/filter).
  • Use examples in the input schema to guide UI/CSV detection and previews.

Troubleshooting

  • “Function parse failed” → Ensure you saved a full async function calculate(inputValues) { ... } declaration.
  • “Must return ” → Return { value: number, metadata: object } from the function.
  • “Missing dataset item” → Use fallbacks: const ef = (await $datasets.getCoefficient(...)) ?? 0.
  • NaN/Infinity in results → Validate numeric inputs; server normalizes to 0 but fix root cause.
  • Forbidden site during add/import → Check site access/filters.

Performance Tips

  • Prefer fetching all coefficients at once with $datasets.getCoefficients(dataset, $year) if you need many lookups.
  • Avoid multiple awaits in tight loops; compute once and reuse.
  • Keep metadata concise; store detailed objects only when needed.

Metadata Conventions

  • Include units and sources where relevant (e.g., emissionFactorKgCO2ePerKWh, factorSource: "EPA").
  • Keep numbers numeric for charting; use strings for labels/IDs.

See Also

  • Activity Calculation Variables (context helpers and examples)
  • Dataset Access Examples (patterns for datasets/datasets/kpis/$sites usage)

Operational Notes

  • Reusability: a single definition can be used across multiple forms and sites.
  • Consistency: the same calculation logic ensures identical results.
  • Transparency: the calculation function and metadata make assumptions explicit.