Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decimals (float) in avg expressions. #469

Merged
merged 3 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions app/javascript/libs/expressions/operators/avg.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { MATHEMATICAL_OPERATORS } from "../constants";

import sumOperator from "./sum";

export default expressions => ({
export default (expressions, extra) => ({
expressions,
operator: MATHEMATICAL_OPERATORS.AVG,
evaluate: data => {
const decimalPlaces = extra?.decimalPlaces;
const sum = sumOperator(expressions).evaluate(data);

const count = Object.values(expressions).reduce((prev, current) => {
if (has(data, current) && !isNil(data[current]) && data[current] !== "") {
return prev + 1;
Expand All @@ -21,6 +21,13 @@ export default expressions => ({
return prev;
}, 0);

return Math.floor(sum / (count === 0 ? 1 : count));
const res = sum / (count === 0 ? 1 : count);

if (decimalPlaces) {
return parseFloat(res.toFixed(decimalPlaces));
}

// Backwards compatible version rounds down
return Math.floor(res);
}
});
4 changes: 4 additions & 0 deletions app/javascript/libs/expressions/operators/avg.unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import avgOperator from "./avg";

describe("avgOperator", () => {
const operator = avgOperator(["a", "b", "c"]);
const decimalPlaceOperator = avgOperator(["a", "b", "c"], { decimalPlaces: 3 });

it("should return avg", () => {
expect(operator.evaluate({ a: 3, b: 4, c: 2 })).to.deep.equals(3);
Expand All @@ -22,4 +23,7 @@ describe("avgOperator", () => {
it("returns 0 when no argument passed", () => {
expect(operator.evaluate({})).to.deep.equals(0);
});
it("returns a float when decimal places are specified", () => {
expect(decimalPlaceOperator.evaluate({ a: 1, b: 4 })).to.deep.equals(2.5);
});
});
10 changes: 6 additions & 4 deletions app/javascript/libs/expressions/parse-expression.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ const parseExpression = expression => {
}

if (isMathematicalOperator(operator)) {
const mathExp = Array.isArray(value)
? value.map(nested => (isObject(nested) ? parseExpression(nested) : nested))
: parseExpression(value);
const data = value?.data ?? value;
const extra = value?.extra;
const mathExp = Array.isArray(data)
? data.map(nested => (isObject(nested) ? parseExpression(nested) : nested))
: parseExpression(data);

return buildOperator(operator, mathExp);
return buildOperator(operator, mathExp, extra);
}

return buildOperator(operator, value);
Expand Down
10 changes: 10 additions & 0 deletions app/javascript/libs/expressions/parse-expression.unit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ describe("parseExpression", () => {

context("avgOperator", () => {
const operator = parseExpression({ avg: ["a", "b", "c"] });
const decimalOperator = parseExpression({ avg: { data: ["a", "b", "c"], extra: { decimalPlaces: 3 } } });

it("should return avg", () => {
expect(operator.evaluate({ a: 3, b: 4, c: 2 })).to.deep.equals(3);
Expand All @@ -215,5 +216,14 @@ describe("parseExpression", () => {
it("returns 0 when no argument passed", () => {
expect(operator.evaluate({})).to.deep.equals(0);
});
it("works with decimalPlaces specified", () => {
expect(decimalOperator.evaluate({ a: 3, b: 2 })).to.deep.equals(2.5);
});
it("Correctly rounds to the right number of decimal places", () => {
expect(decimalOperator.evaluate({ a: 2, b: 2, c: 1 })).to.deep.equals(1.667);
});
it("works with strings", () => {
expect(decimalOperator.evaluate({ a: "2", b: "3" })).to.deep.equals(2.5);
});
});
});
4 changes: 2 additions & 2 deletions app/javascript/libs/expressions/utils/build-operator.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
avgOperator
} from "../operators";

export default (operator, value) => {
export default (operator, value, extra) => {
switch (operator) {
case COMPARISON_OPERATORS.EQ:
return eqOperator(value);
Expand All @@ -38,7 +38,7 @@ export default (operator, value) => {
case MATHEMATICAL_OPERATORS.SUM:
return sumOperator(value);
case MATHEMATICAL_OPERATORS.AVG:
return avgOperator(value);
return avgOperator(value, extra);
default:
throw Error(`Operator ${operator} is not valid.`);
}
Expand Down
2 changes: 1 addition & 1 deletion app/services/record_json_validator_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def build_schema(fields)
when Field::TALLY_FIELD
properties[field.name] = { 'type' => %w[object null], 'properties' => tally_properties(field.tally_i18n) }
when Field::CALCULATED
properties[field.name] = { 'type' => %w[integer string boolean array null],
properties[field.name] = { 'type' => %w[integer number string boolean array null],
'minimum' => -2_147_483_648,
'maximum' => 2_147_483_647 }
end
Expand Down