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

# Measures

> The measures feature. A building block for the grid builder and graph builder

# Background

Measures are the fundamental building block of the grid builder. More specifically a measure is a datapoint or set of datapoints that relates to a serial number either at the process entry level or component instance level:

* **Process Entry Level:** data related to a serial number via \*\*\*\*a process entry
* **Component Instance Level:** data that’s inherently tied to the serial number via the `unique_identifiers` table

Measures can be any of the following types (note the difference between component instance level measures and process entry level measures)

```tsx
export enum MeasureType {
  // Component Instance Level Measures
  Identifier = "IDENTIFIER",
  Status = "STATUS",
  LinkStatus = "LINK_STATUS",
  PartNumber = "PART_NUMBER",
  WorkOrder = "WORK_ORDER",
  WorkOrderPhase = "WORK_ORDER_PHASE",
  // Process Entry Level Measures
  Station = "STATION",
  Operator = "OPERATOR",
  CycleTime = "CYCLE_TIME",
  Timestamp = "TIMESTAMP",
  ProcessResult = "PROCESS_RESULT",
  ParametricQualitative = "PARAMETRIC_QUALITATIVE",
  ParametricQuantitative = "PARAMETRIC_QUANTITATIVE",
  Checkbox = "CHECKBOX",
  PassFail = "PASSFAIL",
  Image = "IMAGE",
  File = "FILE",
  Link = "LINK",
  Datetime = "DATETIME",
}
```

# Measures Context

To utilize measures anywhere in the app you can wrap components in a `<MeasuresProvider>`

```tsx
<MeasuresProvider componentId={selectedComponentId}>
  {/* child components go here */}
</MeasuresProvider>
```

In doing so you’ll be able to invoke the `useMeasures` hook to access the context

<aside>
  ⚠️ Important Note: As you can see the measures provider requires a component ID to be specified. Measures is built on an assumption that we always start from one component and begin by analyzing all of it’s serial number. We can add data to that set of serial numbers and we can also add child links to those serial numbers but we can’t look up the tree and we must always specify a component to start with
</aside>

# Identifying Measures

Since measures currently aren’t stored in any table they don’t have a primary key. Instead we identify measures either through a `MeasureKey` object or a hash of that object:

```tsx
export interface MeasureKey {
  type: MeasureType;
  component_id: string; // snake_case used here since MeasureKeys may sometimes be stored in the database
  process_id?: string;
  dataset_id?: string;
  aggregation_operator?: MeasureAggregationOperator;
  formatting?: MeasureKeyFormatting;
}

export const hashMeasureKey = (measureKey: MeasureKey): string => {
  return `${measureKey.type}-${measureKey.component_id}-${measureKey.process_id}-${measureKey.dataset_id}`;
};
```

# Loading Measures

## Interface

The `handleAddSelectedKey` method from `useMeasures` adds a new measure key to the list of selected measure keys and triggers loading in the data. the `<MeasureKeySelector>` component can also be used to tho this and comes with a pre-built UI

```
const { handleAddSelectedKey } = useMeasures();

<MeasureKeySelector onSelect={handleAddSelectedKey} />
```

## Under the Hood

### `UniqueIdentifierWithMeasures`

When a new measure key gets added to the `selectedMeasureKeys` list, this triggers a fetches which load data into `uniqueIdentifiersWithMeasures`, the list of component instances of the selected component, which is of type `UniqueIdentifierWithMeasures`

```tsx
export interface UniqueIdentifierWithMeasures extends UniqueIdentifier {
  genealogy: GenealogyByLinkedComponent;
  measures: {
    key: MeasureKey;
    valuesByIdentifier: {
      [uniqueIdentifierId: string]: MeasureValuesWithAggregation;
    };
  }[];
}

export interface GenealogyByLinkedComponent {
  uniqueIdentifiers: UniqueIdentifier[];
  uniqueIdentifierLinks: UniqueIdentifierLink[];
}
```

Let’s pause and fully understand the type definitions above. Note the following:

* When the component ID is provided to the measures provider, it loads the component instances of that component as well as a genealogy object containing component instances and links (basically the nodes and edges in the tree). This information exists before any measure keys are selected
* After a measure key is added, the measures provider fetches all the data for that measure and then inserts the data into the `measures` arrays in `uniqueIdentifiersWithMeasures`. Note that these `measures` arrays are build of objects containing the measure key and `valuesByIdentifier` - a map of `uniqueIdentifierId` to `MeasureValuesWithAggregation`. The reason for this is any given measure might apply to one or more members or the root component instance’s genealogy. For example a Drone might have 4 motors. If we want to load motor ***Max RPM*** onto the done, we’d be loading a single measure onto 4 members of the drone. In this case each drone’s `valuesByIdentifier` object for the ***Max RPM*** measure key would have 4 key value pairs:

### `MeasureValuesWithAggregation`

As you may have noted, `valuesByIdentifier` is not as simple as mapping a UUID to a string / number. Here we’re mapping a UUID to yet another object of type `MeasureValuesWithAggregation`. There are two reasons for needing this additional complexity:

1. A particular measure might have several retests even for a particular component instance. In the case of the drone motors, the process measuring ***Max RPM*** may have been run multiple times.
2. The data being loaded is often more complex than just a string or a number, it often has additional metadata like pass/fail, USL, LSL, unit, a linked UUID, etc.

Below is the type definition for `MeasureValuesWithAggregation`:

```tsx
export interface MeasureValuesWithAggregation {
  values: MeasureValue[];
  aggregation: MeasureValueSummary;
}

export interface MeasureValueSummary {
  value: string;
  formattedValue: string;
  tag: string | null;
  fileId?: string;
  bucketName?: string;
  href?: string;
}

// Note: the MeasureValue interface extends MeasureValueBase and has a bunch
// of different variants for each type of measure key
interface MeasureValueBase {
  uniqueIdentifierId: string;
  timestamp: TimestamptzString;
  data: any; // is is specified explicity in the extended versions of this interface
  processEntry?: ProcessEntry;
}
```

The aggregated measure value is calculated based on which aggregation operator is set in the `MeasureKey`. Aggregations can be one of the following:

```tsx
export enum MeasureAggregationOperator {
  Latest = "LATEST",
  First = "FIRST",
  Count = "COUNT",
  Sum = "SUM",
  Average = "AVERAGE",
  Min = "MIN",
  Max = "MAX",
}
```

# Filtering and Sorting Measures

The measures system provides robust filtering and sorting capabilities to help users analyze data effectively.

## Filtering

Filtering is implemented using the `filterBy` array in the measures context. Each filter condition is represented by a `MeasureFilterCondition` object:

```tsx
export interface MeasureFilterCondition {
  key: MeasureKey;
  operator: FilterConditionOperator;
  value: string;
}
```

Filters can be applied using the `setFilterBy` function from the `useMeasures` hook. The `testFilter` function in `filters.ts` evaluates these conditions against the measure values.

## Sorting

Sorting is handled through the `sortBy` array in the measures context. Each sort condition is defined by a `MeasureSortCondition`:

```tsx
export interface MeasureSortCondition {
  key: MeasureKey;
  direction: "asc" | "desc";
}
```

The `setSortBy` function from `useMeasures` can be used to update the sort order.

# Displaying Measures

## Grid View

The `MeasuresGrid` component provides a tabular view of measures data. It utilizes AG Grid for efficient rendering and supports features like:

* Custom cell rendering based on measure type
* Handling of blank data with explanations
* Support for retests and aggregations

## Graph View

For visualizing measure data, the system includes `MeasuresGraphScatter` and `MeasuresGraphLine` components. These leverage charting libraries to display data trends and relationships between different measures.

# Advanced Features

## Time Operators

Time operators allow for temporal analysis of measures. They can be set using the `MeasureTimeOperator` interface:

```tsx
export interface MeasureTimeOperator {
  since?: MeasureKey | Date;
  until?: MeasureKey | Date;
}
```

The `MeasureKeyTimeOperatorModal` component provides a UI for configuring these operators.

## Aggregations

Besides the `MeasureAggregationOperator` used for individual measures, the system also supports group aggregations through the `GroupAggregationOperator`:

```tsx
export enum GroupAggregationOperator {
  Count = "COUNT",
  Average = "AVERAGE",
  Max = "MAX",
  Min = "MIN",
  Sum = "SUM",
}
```

This allows for complex data analysis across groups of measures.
