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

# Overview

export const MintTable = ({columns = [], rows = [], columnWidths = []}) => {
  const pushTextWithLineBreaks = (parts, text, keyBase) => {
    const segments = String(text).split(/\\n|\n/);
    segments.forEach((segment, idx) => {
      if (segment) {
        parts.push(<span key={`${keyBase}-text-${idx}`}>{segment}</span>);
      }
      if (idx < segments.length - 1) {
        parts.push(<br key={`${keyBase}-br-${idx}`} />);
      }
    });
  };
  const parseMarkdown = text => {
    if (text === null || text === undefined) return "";
    const str = String(text);
    const parts = [];
    let lastIndex = 0;
    const pattern = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|\[([^\]]+)\]\(([^)]+)\))/g;
    let match;
    while (true) {
      match = pattern.exec(str);
      if (match === null) {
        break;
      }
      if (match.index > lastIndex) {
        pushTextWithLineBreaks(parts, str.substring(lastIndex, match.index), `before-${lastIndex}`);
      }
      const fullMatch = match[0];
      if (fullMatch.startsWith("`") && fullMatch.endsWith("`")) {
        parts.push(<code key={match.index}>{fullMatch.slice(1, -1)}</code>);
      } else if (fullMatch.startsWith("**") && fullMatch.endsWith("**")) {
        parts.push(<strong key={match.index}>{fullMatch.slice(2, -2)}</strong>);
      } else if (fullMatch.startsWith("*") && fullMatch.endsWith("*")) {
        parts.push(<em key={match.index}>{fullMatch.slice(1, -1)}</em>);
      } else if (fullMatch.startsWith("[")) {
        const linkText = match[2];
        const linkUrl = match[3];
        parts.push(<a key={match.index} href={linkUrl} className="text-black-600 dark:text-black-400">
            {linkText}
          </a>);
      }
      lastIndex = pattern.lastIndex;
    }
    if (lastIndex < str.length) {
      pushTextWithLineBreaks(parts, str.substring(lastIndex), `tail-${lastIndex}`);
    }
    if (parts.length > 0) {
      return parts;
    }
    const plainParts = [];
    pushTextWithLineBreaks(plainParts, str, "plain");
    return plainParts.length ? plainParts : str;
  };
  const safeColumns = Array.isArray(columns) ? columns : [];
  const safeRows = Array.isArray(rows) ? rows : [];
  const safeColumnWidths = Array.isArray(columnWidths) ? columnWidths : [];
  const hasColumnWidths = safeColumnWidths.some(w => w !== null && w !== undefined && w !== "");
  const toCssWidth = width => typeof width === "number" ? `${width}px` : String(width);
  const getColumnStyle = idx => {
    const rawWidth = safeColumnWidths[idx];
    if (rawWidth === null || rawWidth === undefined || rawWidth === "") {
      return undefined;
    }
    const width = toCssWidth(rawWidth);
    return {
      width,
      minWidth: width
    };
  };
  const containerStyle = hasColumnWidths ? undefined : {
    overflowX: "auto"
  };
  const tableStyle = hasColumnWidths ? {
    tableLayout: "fixed",
    width: "100%"
  } : {
    width: "max-content",
    minWidth: "100%"
  };
  if (!Array.isArray(columns) || !Array.isArray(rows) || !Array.isArray(columnWidths)) {
    console.warn("MintTable received invalid props:", {
      columns,
      rows,
      columnWidths
    });
  }
  if (!safeColumns.length && !safeRows.length) {
    return null;
  }
  return <div className="mint-table-container" style={containerStyle}>
      <table style={tableStyle}>
        {hasColumnWidths && <colgroup>
            {safeColumns.map((_, idx) => {
    const style = getColumnStyle(idx);
    return <col key={idx} style={style} />;
  })}
          </colgroup>}
        <thead>
          <tr>
            {safeColumns.map((col, idx) => <th key={idx} className="text-left" style={getColumnStyle(idx)}>
                <b>{parseMarkdown(col)}</b>
              </th>)}
          </tr>
        </thead>
        <tbody>
          {safeRows.map((row, rIdx) => {
    const safeRow = Array.isArray(row) ? row : [];
    return <tr key={rIdx}>
                {safeRow.map((cell, cIdx) => <td key={cIdx} style={getColumnStyle(cIdx)}>
                    {parseMarkdown(cell)}
                  </td>)}
              </tr>;
  })}
        </tbody>
      </table>
    </div>;
};

<Icon icon="layer-group" /> **Families** in Sift are named groups of related Runs used for comparison, analysis, and historical baselines.

For example, group all known-good acceptance test Runs for an engine configuration into a Family, then evaluate each new Run against the Family's chamber pressure mean and standard deviation to flag deviations from the baseline.

Once a Family is defined, you can overlay members on a shared time axis, compute aggregate statistics, and build Rules that flag when a new Run deviates from the baseline. See [When to use Families](#when-to-use-families) for more example workflows.

## Key concepts

Families are built from four core elements:

<MintTable
  columns={['Concept', 'Description']}
  rows={[
['**Members**', 'Runs in the Family that serve as the reference dataset. You can include or exclude individual Runs from the calculation of aggregate statistics while preserving their visibility for reference. For example, exclude a known anomalous Run from aggregate calculations but keep it in the Family for context.'],
['**Alignments**', 'Configurations that time-synchronize Runs for comparison. Instead of using an absolute clock, alignments define a shared T-0 reference point, such as engine ignition or an Annotation, so signal shapes can be overlaid even when Runs occurred at different times.'],
['**Family statistics**', 'Statistical aggregates across all included Runs that produce a representative time series for each Channel, such as mean, standard deviation, min, and max. Statistics are available in Explore and Family rule evaluation.'],
['**Rules**', 'Rules that use Family statistics as dynamic thresholds. Instead of comparing a Channel against a fixed limit, a Family rule checks whether a new Run stays within the statistical envelope of the reference dataset. For example, within 3 standard deviations of the Family mean.']
]}
/>

## When to use Families

Use Families when historical data defines standard behavior and you want to compare new Runs against that baseline.

<MintTable
  columns={['Workflow', 'How Families help', 'Example']}
  rows={[
['**Acceptance testing**', 'Group known-good qualification Runs into a Family and evaluate new Runs against the expected statistical envelope.', 'All nominal engine acceptance tests are grouped into a Family. A new test Run is evaluated against the mean ± 3σ of chamber pressure and thrust.'],
['**Regression detection**', 'Use a Family of baseline Runs to detect regressions in simulation or CI pipelines where signal behavior should remain consistent across builds.', 'A Family of reference simulation Runs flags a build whose output diverges from the expected signal shape.'],
['**Visual comparison**', 'Overlay Runs on a shared time axis to inspect variance, identify outliers, and understand the spread of normal behavior.', 'Twenty acceptance test Runs are overlaid in Explore, aligned to engine ignition, to inspect the consistency of a pressure transient.']
]}
/>

## Alignments

During hardware tests or operations, events occur at different absolute times and sequences vary in terms of duration, so it can be hard to make direct comparisons. Alignments solve this by defining a shared T-0 reference point for each Run, enabling direct comparison.

A Family can have multiple alignments, and you can switch between them when visualizing Runs in Explore or configuring a Rule.

You can align Runs to the following:

* **Run start or end**: T-0 is set to the beginning or end of each Run. This works well for Runs with consistent structure.
* **A specific timestamp**: T-0 is a fixed absolute time, useful when all Runs pass through a known moment.
* **An Annotation**: T-0 is set when a named event occurs within each Run (such as an engine ignition or valve opening). This helps when the event happens at different absolute times across Runs. Annotations can be created manually or generated by Rules. You can align on these events, including derived events such as state transitions.

## Family statistics

Family statistics compute aggregate time series across all included members. When you save a Family statistic, Sift fetches data from each included Run in the configured time window, converts it to relative time using the alignment, and computes the aggregate at each time step.

Supported aggregation types:

<MintTable
  columns={['Type', 'Description']}
  rows={[
['`avg`', 'Mean value across all included Runs at each time step'],
['`median`', 'Median value across all included Runs at each time step'],
['`min`', 'Minimum value across all included Runs at each time step'],
['`max`', 'Maximum value across all included Runs at each time step'],
['`stdev`', 'Standard deviation across all included Runs at each time step'],
['`sum`', 'Sum across all included Runs at each time step'],
['`input_count`', 'Number of included Runs or dynamic window occurrences with a value at each time step']
]}
/>

Statistics can be scoped to specific time windows within a Run. For example, scoping for the 30 seconds after engine ignition only, using data ranges configured on the statistic.

## Family rules

Family rules use computed statistics as dynamic thresholds in rule expressions. A common pattern compares a Channel from a Run under test against a sigma band derived from the Family's `avg` and `stdev` statistics.

When evaluating a Family rule against a new Run, Sift prompts you to map the Run's alignment configuration to the Family's alignment so the comparison is time-synchronized correctly. Results appear in reports that show the Family statistic overlay alongside the Run's Channel data in relative time.

<Info>
  Family Rules do not support live (real-time) evaluation. They are intended for retrospective analysis after a Run completes.
</Info>

## Version history

Every change to a Family, including adding or removing members and updating alignments or statistics, creates a new version with a record of what changed and when. Version history is visible on the Family overview page and provides an audit trail of how the reference dataset changed over time.

## How-to guides

* [Group Runs into a Family](/documentation/analyze/families/group-runs-into-a-family)
* [Compare Runs visually against a Family baseline](/documentation/analyze/families/compare-runs-visually-against-a-family-baseline)
* [Detect statistical deviations using Family Rules](/documentation/analyze/families/detect-deviations-from-a-historical-baseline)
* [Manage Families](/documentation/analyze/families/keep-a-family-current)
