All files / libs/amalia-lang/formula/evaluate/shared/src/functions/indices/row-marginal index.ts

91.13% Statements 144/158
75.47% Branches 40/53
81.81% Functions 9/11
96.87% Lines 124/128

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 1291x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 67x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 100x 100x 100x 100x 100x 325x 7x 318x 100x 100x 100x 1x 1x 99x 99x 99x 100x     100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 100x 1x 1x 97x 97x 97x 30x 30x 30x 30x 30x 30x 30x 46x 46x 46x 30x 30x 97x 97x 97x 33x 33x 33x 68x 68x 68x     68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 68x 1x  
import { findLastIndex } from 'lodash-es';
import { type AccessorNode, type ConstantNode } from 'mathjs';
 
import { AmaliaFunctionCategory, AmaliaFunctionKeys, type AmaliaFormula } from '@amalia/amalia-lang/formula/types';
import { type RecordContent } from '@amalia/data-capture/connectors/types';
import { type ComputeEngine2DTable } from '@amalia/payout-calculation/types';
 
import { AmaliaFunctionDefault } from '../../AmaliaFunction';
import { getValueOrFormula } from '../../utils';
import { getRowFieldAmount, getTableSlice } from '../common';
 
export const indicesRowMarginal = new AmaliaFunctionDefault<
  [RecordContent, ComputeEngine2DTable<number>, RecordContent[], string, string[] | string, number?],
  number
>({
  name: AmaliaFunctionKeys.rowMarginalIndex,
  category: AmaliaFunctionCategory.INDICES,
  nbParamsRequired: 5,
  description:
    'This function will get the index at which the `dataObject` is present ' +
    'in the `filteredRows`, and will compute a cumulative sum of the `fieldToSum` until this ' +
    'row, included. It will then apply the `table` to this cumulative sum.',
 
  exec: (row, table, rows, indexField, uniqueId, startFromRaw) => {
    const precision = 1_000_000;
 
    const startFrom = startFromRaw || 0;
 
    const indexRowToCompute = rows.findIndex((r) =>
      Array.isArray(uniqueId)
        ? uniqueId.every((currentUniqueId) => r[currentUniqueId] === row[currentUniqueId])
        : r[uniqueId] === row[uniqueId],
    );
 
    Eif (indexRowToCompute === -1) {
      return 0;
    }
 
    const rowToCompute = rows.at(indexRowToCompute);
 
    Eif (!rowToCompute) {
      return 0;
    }
 
    // Retrieve rows until row to compare
    const sumPreviousRows =
      rows.slice(0, indexRowToCompute).reduce((acc, r) => acc + getRowFieldAmount(r, indexField), 0) + startFrom;
 
    const currentRowValue = getRowFieldAmount(rowToCompute, indexField);
    const sumWithCurrentRow = sumPreviousRows + currentRowValue;
 
    const rowMarginalTable = table.slice().map((slice) => getTableSlice(slice));
 
    // Get indices of related slices inside the table
    const startAtIndexTmp = findLastIndex(rowMarginalTable, (tableSlice) => sumPreviousRows >= tableSlice.min);
    const endAtIndexTmp = findLastIndex(rowMarginalTable, (tableSlice) => sumWithCurrentRow >= tableSlice.min);
 
    // Eventually invert the index in the case where the latest commission is making us go down one step in the table.
    const reverse = startAtIndexTmp > endAtIndexTmp;
    const startAtIndex = reverse ? endAtIndexTmp : startAtIndexTmp;
    const endAtIndex = reverse ? startAtIndexTmp : endAtIndexTmp;
 
    Eif (startAtIndex === -1 || endAtIndex === -1) {
      return 0;
    }
 
    // Sum all values between slices
    const sliceValues = rowMarginalTable
      // Get only related rows
      .slice(startAtIndex, endAtIndex + 1)
      // sum all computed values for each row
      // for first slice, get the max between the slice minimum and the previous rows
      // For last slice, get the min between the slide maximum and the sum with current row
      .reduce(
        (acc, slice) =>
          acc +
          (Math.min(slice.max, reverse ? sumPreviousRows : sumWithCurrentRow) -
            Math.max(slice.min, reverse ? sumWithCurrentRow : sumPreviousRows)) *
            slice.percent,
        0,
      );
 
    return (
      ((reverse ? -1 : 1) * Math.round((currentRowValue === 0 ? 0 : sliceValues / currentRowValue) * precision)) /
      precision
    );
  },
 
  generateComputedFunctionResult: (args) => ({
    array: getValueOrFormula(args[2]),
    formula: `${(args[0] as AccessorNode).name}.${(args[3] as ConstantNode).value}` as AmaliaFormula,
  }),
 
  params: [
    { name: 'dataObject', description: 'Data object' },
    { name: 'table', description: 'Table for indices' },
    { name: 'rows', description: 'Filter of records: can be sorted' },
    { name: 'fieldToSum', description: 'Field used for cumulative sum' },
    {
      name: 'uniqueId',
      description:
        'A unique id used by Amalia calculation system to retrieve the current line. Can be a string or an array of strings',
    },
    {
      name: 'startFrom',
      description: "Row marginal index computation will start cumulative sum from this value. By default it's 0.",
      defaultValue: 0,
    },
  ],
 
  examples: [
    {
      desc: 'Performs a marginal calculation of Amount Adjusted from opportunity based on the filter closedInPeriod starting from the oldest closing date.',
      formula:
        'rowMarginalIndex(opportunity, statement.commissionTable, SORT(filter.closedInPeriod, "closingDate"), "amountAdjusted", "id")' as AmaliaFormula,
    },
    {
      desc: 'Performs a marginal calculation of Amount Adjusted from opportunity based on the filter closedInPeriod starting from the oldest closing date with two fields used for Ids.',
      formula:
        'rowMarginalIndex(opportunity, statement.commissionTable, SORT(filter.closedInPeriod, "closingDate"), "amountAdjusted", ["id", "opportunityId"])' as AmaliaFormula,
    },
    {
      desc: 'Performs a marginal calculation of Amount Adjusted from opportunity based on the filter closedInPeriod starting from the oldest closing date and from record 2000.',
      formula:
        'rowMarginalIndex(opportunity, statement.commissionTable, SORT(filter.closedInPeriod, "closingDate"), "amountAdjusted", "id", 2000)' as AmaliaFormula,
    },
  ],
});