> ## Documentation Index
> Fetch the complete documentation index at: https://docs.azalt.co/llms.txt
> Use this file to discover all available pages before exploring further.

# ActivityDefinition

> Reusable calculation blueprints with custom logic and UI

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:

```ts theme={null}
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:

```tsx theme={null}
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):

```ts theme={null}
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`.

```ts theme={null}
// 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.

```tsx theme={null}
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 { value, metadata }” → 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 `await`s 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/$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.
