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

# How to create measures

> How to define cube measures with the Atoti Configuration API using `IMeasuresConfigurator`, with Copper, `CopperNext` UDAFs or `IMeasureFactory`, and rename them in YAML.

Measures define the calculations performed on cube data. All measures are defined as `CopperMeasure` objects.

There are two approaches:

1. Copper API for simple measures
2. Measure factories for parameterized or reusable patterns

## Prerequisites

Before creating measures, make sure:

* A cube is created (see [configure cubes](./overview))
* Copper API basics for measure definitions are understood

## How to add a measure

The starting point for creating measures is the `IMeasuresConfigurator` interface from the cube configurator.

There are two main approaches:

1. `IMeasuresConfigurator.addMeasure(String, CopperMeasure)` - Add a measure defined with Copper API
2. `IMeasuresConfigurator.addMeasure(String, IMeasureFactoryInput, IMetadataConfigurer)` - Add a measure using a factory

### How to add a measure with Copper

Create measures directly with the Copper API for simple calculations:

```java theme={"languages":{"custom":["/engine/python-sdk/6.2/languages/pycon.tmLanguage.json"]}}
ICubeConfigurator cube = managerConfigurator.cube("MyCube");
IMeasuresConfigurator measures = cube.measures();
measures.addMeasure(
    "RevenuePercentage",
    Copper.measure("Revenue")
        .divide(Copper.measure("TotalRevenue"))
        .multiply(Copper.constant(100))
        .withinFolder("myFolder"));
```

> [!NOTE]
>
> The measure name is provided as the first argument of `addMeasure`; the methods `copperMeasure.as("name")` and `copperMeasure.withName("name")` are not supported.
> Intermediate measures that depend on each other must each be added separately via `addMeasure`.

### How to use measure factories?

For complex scenarios, use a measure factory. This approach is useful when:

* Measure creation requires parameterization
* The Python API needs to modify elements used to create the measure

First, define the factory input class:

```java theme={"languages":{"custom":["/engine/python-sdk/6.2/languages/pycon.tmLanguage.json"]}}
public record MyInput(double scalingFactor, String filterValue) implements IMeasureFactoryInput {
  @Override
  public String getPluginKey() {
    return MyMeasureFactory.PLUGIN_KEY;
  }
}
```

Then, implement the measure factory:

```java theme={"languages":{"custom":["/engine/python-sdk/6.2/languages/pycon.tmLanguage.json"]}}
@AtotiPluginValue(intf = IMeasureFactory.class)
public static class MyMeasureFactory implements IMeasureFactory<MyInput> {
  public static final String PLUGIN_KEY = "MyKey";
  @Override
  public CopperMeasure create(final MyInput input, final IMeasureFactoryContext ctx) {
    // Create the measure using the input
    return Copper.measure("BaseRevenue")
        .multiply(Copper.constant(input.scalingFactor()))
        .filter(
            Copper.level("Category", "Category", "Category")
                .eq(Copper.constant(input.filterValue())));
  }
  @Override
  public String key() {
    return PLUGIN_KEY;
  }
}
```

Finally, add the measure in the cube configuration:

```java theme={"languages":{"custom":["/engine/python-sdk/6.2/languages/pycon.tmLanguage.json"]}}
MyInput input = new MyInput(1.2, "Electronics");
measuresConfigurator.addMeasure(
    "ScaledRevenue", input, metadata -> metadata.folder("myFolder"));
```

### How to register a custom measure factory

Measure factories can be registered in two ways.

**Static registration** uses the `@AtotiPluginValue` annotation. The factory is discovered by the Registry:

```java theme={"languages":{"custom":["/engine/python-sdk/6.2/languages/pycon.tmLanguage.json"]}}
@AtotiPluginValue(intf = IMeasureFactory.class)
public static class MyStaticFactory implements IMeasureFactory<MyInput> {
```

**Dynamic registration** uses `MeasureFactoryRegistration` beans. Use this approach when the factory needs access to external objects, such as Spring-managed beans:

```java theme={"languages":{"custom":["/engine/python-sdk/6.2/languages/pycon.tmLanguage.json"]}}
@Bean
public MeasureFactoryRegistration measureFactoryRegistration(PricingService pricingService) {
  return new MeasureFactoryRegistration(new MyMeasureFactory(pricingService));
}
```

The factory does not need the `@AtotiPluginValue` annotation when using dynamic registration. Multiple factories can be registered in a single `MeasureFactoryRegistration` bean.

## How to create aggregated measures

Use `CopperNext` to define aggregated measures and user-defined aggregation functions (UDAFs).

> [!NOTE]
>
> `CopperNext` is a temporary class to expose API related to Copper and the Atoti Configuration API. This class will be merged into `Copper` once the Atoti Configuration API is no longer experimental.

Reference the aggregated field with a `FieldPath` relative to a table:

```java theme={"languages":{"custom":["/engine/python-sdk/6.2/languages/pycon.tmLanguage.json"]}}
ICubeConfigurator cubeConfigurator = managerConfigurator.cube("cubeName");
IMeasuresConfigurator measuresConfigurator = cubeConfigurator.measures();
measuresConfigurator.addMeasure("MyMeasure", CopperNext.sum(FieldPath.of("value")));
```

Other available methods in `CopperNext`:

* `avg` - Average aggregation
* `min` - Minimum aggregation
* `max` - Maximum aggregation
* `agg` - Custom aggregation functions

### How to create a UDAF

Use `CopperNext.userDefinedAgg` for user-defined aggregation functions:

```java theme={"languages":{"custom":["/engine/python-sdk/6.2/languages/pycon.tmLanguage.json"]}}
measuresConfigurator.addMeasure(
    "MyUDAF",
    CopperNext.userDefinedAgg(
        List.of(FieldPath.of("value"), FieldPath.of("toCountry", "country")),
        new int[] {Types.DOUBLE},
        (fact, buffer) -> {
          double value = fact.readDouble(0);
          String country = (String) fact.read(1);
          double scaledValue = country.equals("France") ? value * 1.1 : value;
          if (buffer.isNull(0)) {
            buffer.writeDouble(0, scaledValue);
          } else {
            buffer.addDouble(0, scaledValue);
          }
        },
        (in, out) -> out.addDouble(0, in.readDouble(0)),
        (fact, buffer) -> {
          double value = fact.readDouble(0);
          String country = (String) fact.read(1);
          double scaledValue = country.equals("France") ? value * 1.1 : value;
          buffer.addDouble(0, -scaledValue);
        },
        buffer -> buffer.readDouble(0),
        Types.DOUBLE));
```

The fluent API for UDAFs will be added in a future release.

## How to rename measures

Predefined cubes may include measures that can be renamed.

Add 'name overrides' in the application configuration file:

```yaml theme={"languages":{"custom":["/engine/python-sdk/6.2/languages/pycon.tmLanguage.json"]}}
semantic-model:
  name-overrides:
    cubes:
      CubeName:
        measures:
          Measure1: MeasureA
          '[Measure with spaces]': MeasureB
```

This configuration renames `Measure1` to `MeasureA` and `Measure with spaces` to `MeasureB`.

Use square brackets for measure names containing spaces.

## Related pages

* [Create dimensions and hierarchies](./structure): dimension configuration
* [Speed up queries](./query-performance): aggregate providers and the measure cache
