Tony CHEMIT pushed to branch feature/issue-2946-2 at ultreiaio / ird-observe

Commits:

4 changed files:

Changes:

  • core/api/dto/src/main/java/fr/ird/observe/dto/referential/FormulaHelper.java
    1
    +package fr.ird.observe.dto.referential;
    
    2
    +
    
    3
    +/*-
    
    4
    + * #%L
    
    5
    + * ObServe Core :: API :: Dto
    
    6
    + * %%
    
    7
    + * Copyright (C) 2008 - 2024 IRD, Ultreia.io
    
    8
    + * %%
    
    9
    + * This program is free software: you can redistribute it and/or modify
    
    10
    + * it under the terms of the GNU General Public License as
    
    11
    + * published by the Free Software Foundation, either version 3 of the
    
    12
    + * License, or (at your option) any later version.
    
    13
    + *
    
    14
    + * This program is distributed in the hope that it will be useful,
    
    15
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    16
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    17
    + * GNU General Public License for more details.
    
    18
    + *
    
    19
    + * You should have received a copy of the GNU General Public
    
    20
    + * License along with this program.  If not, see
    
    21
    + * <http://www.gnu.org/licenses/gpl-3.0.html>.
    
    22
    + * #L%
    
    23
    + */
    
    24
    +
    
    25
    +import fr.ird.observe.dto.ObserveUtil;
    
    26
    +import io.ultreia.java4all.lang.Strings;
    
    27
    +import org.apache.logging.log4j.LogManager;
    
    28
    +import org.apache.logging.log4j.Logger;
    
    29
    +
    
    30
    +import javax.script.Bindings;
    
    31
    +import javax.script.ScriptContext;
    
    32
    +import javax.script.ScriptEngine;
    
    33
    +import javax.script.ScriptException;
    
    34
    +import java.util.Map;
    
    35
    +import java.util.TreeMap;
    
    36
    +import java.util.function.Function;
    
    37
    +import java.util.regex.Matcher;
    
    38
    +import java.util.regex.Pattern;
    
    39
    +
    
    40
    +/**
    
    41
    + * Created on 05/11/16.
    
    42
    + *
    
    43
    + * @author Tony Chemit - dev@tchemit.fr
    
    44
    + * @since 6.0
    
    45
    + */
    
    46
    +public class FormulaHelper {
    
    47
    +
    
    48
    +    public static final String VARIABLE_WEIGHT = "P";
    
    49
    +    public static final String VARIABLE_LENGTH = "L";
    
    50
    +    public static final String VARIABLE_INPUT = "I";
    
    51
    +    public static final String VARIABLE_OUTPUT = "O";
    
    52
    +    public static final String COEFFICIENT_B = "b";
    
    53
    +    private static final Logger log = LogManager.getLogger(FormulaHelper.class);
    
    54
    +    private static final Pattern COEFFICIENTS_PATTERN = Pattern.compile("(.+)=(.+)");
    
    55
    +    private static final String VARIABLE_X = "x";
    
    56
    +    private static ScriptEngine scriptEngine;
    
    57
    +
    
    58
    +    private static ScriptEngine getScriptEngine() {
    
    59
    +        if (scriptEngine == null) {
    
    60
    +            scriptEngine = ObserveUtil.getScriptEngine();
    
    61
    +        }
    
    62
    +        return scriptEngine;
    
    63
    +    }
    
    64
    +
    
    65
    +    public static Map<String, Double> getCoefficientValues(WithFormula formula) {
    
    66
    +        Map<String, Double> result = new TreeMap<>();
    
    67
    +        String coefficients = formula.getCoefficients();
    
    68
    +        if (coefficients != null) {
    
    69
    +            for (String coefficientDef : coefficients.split(":")) {
    
    70
    +                Matcher matcher = COEFFICIENTS_PATTERN.matcher(coefficientDef.trim());
    
    71
    +                log.debug("constant to test = " + coefficientDef);
    
    72
    +                if (matcher.matches()) {
    
    73
    +                    String key = matcher.group(1);
    
    74
    +                    String val = matcher.group(2);
    
    75
    +                    try {
    
    76
    +                        Double d = Double.valueOf(val);
    
    77
    +                        result.put(key, d);
    
    78
    +                        log.debug("detects coefficient " + key + '=' + val);
    
    79
    +                    } catch (NumberFormatException e) {
    
    80
    +                        // pas pu recupere le count...
    
    81
    +                        log.warn("could not parse double " + val + " for coefficient " + key, e);
    
    82
    +                    }
    
    83
    +                }
    
    84
    +            }
    
    85
    +        }
    
    86
    +        return result;
    
    87
    +    }
    
    88
    +
    
    89
    +    public static boolean validateRelation(WithFormula withFormula, String formula, String variable) {
    
    90
    +        boolean result = false;
    
    91
    +        if (Strings.isNotEmpty(formula)) {
    
    92
    +            Map<String, Double> coefficientValues = withFormula.getCoefficientValues();
    
    93
    +            ScriptEngine engine = getScriptEngine();
    
    94
    +            Bindings bindings = engine.createBindings();
    
    95
    +            addBindings(coefficientValues, bindings, variable, 1);
    
    96
    +            try {
    
    97
    +                engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
    
    98
    +                Number o = (Number) engine.eval("parseFloat(" + formula + ")");
    
    99
    +                log.debug("evaluation ok : " + formula + " (" + variable + "=1) = " + o);
    
    100
    +                result = true;
    
    101
    +            } catch (Exception e) {
    
    102
    +                log.error("evaluation ko : " + formula + ", reason : " + e.getMessage(), e);
    
    103
    +            }
    
    104
    +        }
    
    105
    +        return result;
    
    106
    +    }
    
    107
    +
    
    108
    +    public static boolean validateObjectMaterialValidation(String relation, Object value) {
    
    109
    +        boolean result = false;
    
    110
    +        if (Strings.isNotEmpty(relation)) {
    
    111
    +            ScriptEngine engine = getScriptEngine();
    
    112
    +            Bindings bindings = engine.createBindings();
    
    113
    +            bindings.put(FormulaHelper.VARIABLE_X, value);
    
    114
    +            try {
    
    115
    +                engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
    
    116
    +                Boolean o = (Boolean) engine.eval(relation);
    
    117
    +                log.debug(String.format("evaluation ok : %s (%s=%s) = %s", relation, FormulaHelper.VARIABLE_X, value, o));
    
    118
    +                result = o != null && o;
    
    119
    +            } catch (Exception e) {
    
    120
    +                log.error("evaluation ko : " + relation + ", reason : " + e.getMessage(), e);
    
    121
    +            }
    
    122
    +        }
    
    123
    +        return result;
    
    124
    +    }
    
    125
    +
    
    126
    +    public static boolean validateObjectMaterialValidationSyntax(String relation, Object value) {
    
    127
    +        if (Strings.isNotEmpty(relation)) {
    
    128
    +            ScriptEngine engine = getScriptEngine();
    
    129
    +            Bindings bindings = engine.createBindings();
    
    130
    +            bindings.put(FormulaHelper.VARIABLE_X, value);
    
    131
    +            try {
    
    132
    +                engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
    
    133
    +                Boolean o = (Boolean) engine.eval(relation);
    
    134
    +                log.debug(String.format("evaluation ok : %s (%s=%s) = %s", relation, FormulaHelper.VARIABLE_X, value, o));
    
    135
    +                return true;
    
    136
    +            } catch (Exception e) {
    
    137
    +                log.error(String.format("evaluation ko : %s, reason : %s", relation, e.getMessage()), e);
    
    138
    +                return false;
    
    139
    +            }
    
    140
    +        }
    
    141
    +        return false;
    
    142
    +    }
    
    143
    +
    
    144
    +    public static Float computeValue(WithFormula withFormula, String formula, String coefficientName, String variableName, float data, Function<Float,Float> toRoundResult) {
    
    145
    +        if (coefficientName != null) {
    
    146
    +            Double b = withFormula.getCoefficientValue(coefficientName);
    
    147
    +            if (b == 0) {
    
    148
    +                // ce cas limite ne permet pas de calculer la taille a partir du weight
    
    149
    +                return null;
    
    150
    +            }
    
    151
    +        }
    
    152
    +        Float o = computeValue(withFormula, formula, variableName, data);
    
    153
    +        if (o != null) {
    
    154
    +            o = toRoundResult.apply(o);
    
    155
    +        }
    
    156
    +        return o;
    
    157
    +    }
    
    158
    +
    
    159
    +    private static Float computeValue(WithFormula withFormula, String formula, String variable, float data) {
    
    160
    +        Map<String, Double> coefficientValues = withFormula.getCoefficientValues();
    
    161
    +        ScriptEngine engine = getScriptEngine();
    
    162
    +        Bindings bindings = engine.createBindings();
    
    163
    +        addBindings(coefficientValues, bindings, variable, data);
    
    164
    +        engine.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
    
    165
    +        Number o = null;
    
    166
    +        try {
    
    167
    +            o = (Number) engine.eval("parseFloat(" + formula + ")");
    
    168
    +        } catch (ScriptException e) {
    
    169
    +            log.error("Could not compute value from " + formula, e);
    
    170
    +        }
    
    171
    +        return o == null ? null : o.floatValue();
    
    172
    +    }
    
    173
    +
    
    174
    +    private static void addBindings(Map<String, Double> coefficientValues, Bindings bindings, String variable, float data) {
    
    175
    +        for (Map.Entry<String, Double> entry : coefficientValues.entrySet()) {
    
    176
    +            String key = entry.getKey();
    
    177
    +            Double value = entry.getValue();
    
    178
    +            bindings.put(key, value);
    
    179
    +            log.debug("add constant " + key + '=' + value);
    
    180
    +        }
    
    181
    +        bindings.put(variable, data);
    
    182
    +    }
    
    183
    +
    
    184
    +}

  • core/api/dto/src/main/java/fr/ird/observe/dto/referential/WithFormula.java
    1
    +package fr.ird.observe.dto.referential;
    
    2
    +
    
    3
    +/*-
    
    4
    + * #%L
    
    5
    + * ObServe Core :: API :: Dto
    
    6
    + * %%
    
    7
    + * Copyright (C) 2008 - 2024 IRD, Ultreia.io
    
    8
    + * %%
    
    9
    + * This program is free software: you can redistribute it and/or modify
    
    10
    + * it under the terms of the GNU General Public License as
    
    11
    + * published by the Free Software Foundation, either version 3 of the
    
    12
    + * License, or (at your option) any later version.
    
    13
    + *
    
    14
    + * This program is distributed in the hope that it will be useful,
    
    15
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    16
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    17
    + * GNU General Public License for more details.
    
    18
    + *
    
    19
    + * You should have received a copy of the GNU General Public
    
    20
    + * License along with this program.  If not, see
    
    21
    + * <http://www.gnu.org/licenses/gpl-3.0.html>.
    
    22
    + * #L%
    
    23
    + */
    
    24
    +
    
    25
    +import fr.ird.observe.dto.WithStartEndDate;
    
    26
    +
    
    27
    +import java.util.Comparator;
    
    28
    +import java.util.Date;
    
    29
    +import java.util.List;
    
    30
    +import java.util.Map;
    
    31
    +import java.util.Set;
    
    32
    +
    
    33
    +/**
    
    34
    + * Created on 22/12/16.
    
    35
    + *
    
    36
    + * @author Tony Chemit - dev@tchemit.fr
    
    37
    + * @since 6.0
    
    38
    + */
    
    39
    +public interface WithFormula extends WithStartEndDate, WithSpeciesFaoCode {
    
    40
    +
    
    41
    +    String PROPERTY_OCEAN = "ocean";
    
    42
    +    String PROPERTY_SPECIES = "species";
    
    43
    +    String PROPERTY_SEX = "sex";
    
    44
    +    String PROPERTY_START_DATE = "startDate";
    
    45
    +    String PROPERTY_END_DATE = "endDate";
    
    46
    +    String PROPERTY_COEFFICIENTS = "coefficients";
    
    47
    +    String PROPERTY_SOURCE = "source";
    
    48
    +    Comparator<WithFormula> FORMULA_SUPPORT_START_DATE_COMPARATOR = Comparator.comparing(WithStartEndDate::getStartDate, WithStartEndDate.START_DATE_COMPARATOR);
    
    49
    +    Comparator<WithFormula> FORMULA_SUPPORT_END_DATE_COMPARATOR = Comparator.comparing(WithStartEndDate::getEndDate, WithStartEndDate.END_DATE_COMPARATOR);
    
    50
    +    Comparator<WithFormula> FORMULA_SUPPORT_COMPARATOR = FORMULA_SUPPORT_START_DATE_COMPARATOR.thenComparing(FORMULA_SUPPORT_END_DATE_COMPARATOR);
    
    51
    +
    
    52
    +    static <D extends WithFormula> void sort(List<D> list) {
    
    53
    +        list.sort(FORMULA_SUPPORT_COMPARATOR);
    
    54
    +    }
    
    55
    +
    
    56
    +    String getCoefficients();
    
    57
    +
    
    58
    +    void setCoefficients(String coefficients);
    
    59
    +
    
    60
    +    Map<String, Double> getCoefficientValues();
    
    61
    +
    
    62
    +    String getFormulaOneVariableName();
    
    63
    +
    
    64
    +    String getFormulaTwoVariableName();
    
    65
    +
    
    66
    +    void setStartDate(Date startDate);
    
    67
    +
    
    68
    +    void setEndDate(Date endDate);
    
    69
    +
    
    70
    +    String getSource();
    
    71
    +
    
    72
    +    void setSource(String source);
    
    73
    +
    
    74
    +    default Set<String> getCoefficientNames() {
    
    75
    +        return getCoefficientValues().keySet();
    
    76
    +    }
    
    77
    +
    
    78
    +    String getFormulaOne();
    
    79
    +
    
    80
    +    String getFormulaTwo();
    
    81
    +
    
    82
    +    default void revalidateFormulaOne() {
    
    83
    +        boolean result = FormulaHelper.validateRelation(this, getFormulaOne(), getFormulaOneVariableName());
    
    84
    +        setFormulaOneValid(result);
    
    85
    +    }
    
    86
    +
    
    87
    +    boolean isFormulaOneValid();
    
    88
    +
    
    89
    +    void setFormulaOneValid(boolean formulaOneValid);
    
    90
    +
    
    91
    +    boolean isFormulaTwoValid();
    
    92
    +
    
    93
    +    void setFormulaTwoValid(boolean formulaTwoValid);
    
    94
    +
    
    95
    +    default void revalidateFormulaTwo() {
    
    96
    +        boolean result = FormulaHelper.validateRelation(this, getFormulaTwo(), getFormulaTwoVariableName());
    
    97
    +        setFormulaTwoValid(result);
    
    98
    +    }
    
    99
    +
    
    100
    +    default Double getCoefficientValue(String coefficientName) {
    
    101
    +        return getCoefficientValues().get(coefficientName);
    
    102
    +    }
    
    103
    +
    
    104
    +    default Float computeFromFormulaOne(float data) {
    
    105
    +        // only used for entities
    
    106
    +        return null;
    
    107
    +    }
    
    108
    +
    
    109
    +    default Float computeFromFormulaTwo(float data) {
    
    110
    +        // only used for entities
    
    111
    +        return null;
    
    112
    +    }
    
    113
    +
    
    114
    +}

  • core/api/dto/src/main/java/fr/ird/observe/dto/referential/WithSpeciesFaoCode.java
    1
    +package fr.ird.observe.dto.referential;
    
    2
    +
    
    3
    +/*-
    
    4
    + * #%L
    
    5
    + * ObServe Core :: API :: Dto
    
    6
    + * %%
    
    7
    + * Copyright (C) 2008 - 2023 IRD, Ultreia.io
    
    8
    + * %%
    
    9
    + * This program is free software: you can redistribute it and/or modify
    
    10
    + * it under the terms of the GNU General Public License as
    
    11
    + * published by the Free Software Foundation, either version 3 of the
    
    12
    + * License, or (at your option) any later version.
    
    13
    + *
    
    14
    + * This program is distributed in the hope that it will be useful,
    
    15
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    16
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    17
    + * GNU General Public License for more details.
    
    18
    + *
    
    19
    + * You should have received a copy of the GNU General Public
    
    20
    + * License along with this program.  If not, see
    
    21
    + * <http://www.gnu.org/licenses/gpl-3.0.html>.
    
    22
    + * #L%
    
    23
    + */
    
    24
    +
    
    25
    +import fr.ird.observe.dto.CommonDto;
    
    26
    +import fr.ird.observe.dto.DtoAndReferenceAware;
    
    27
    +
    
    28
    +/**
    
    29
    + * Created on 18/10/2022.
    
    30
    + *
    
    31
    + * @author Tony Chemit - dev@tchemit.fr
    
    32
    + * @since 9.0.14
    
    33
    + */
    
    34
    +public interface WithSpeciesFaoCode extends DtoAndReferenceAware {
    
    35
    +
    
    36
    +    CommonDto getSpecies();
    
    37
    +
    
    38
    +    default CommonDto getSpeciesLabel() {
    
    39
    +        return getSpecies();
    
    40
    +    }
    
    41
    +
    
    42
    +    String getSpeciesFaoCode();
    
    43
    +}

  • core/api/dto/src/test/java/fr/ird/observe/dto/referential/FormulaHelperTest.java
    1
    +package fr.ird.observe.dto.referential;
    
    2
    +
    
    3
    +/*-
    
    4
    + * #%L
    
    5
    + * ObServe Core :: API :: Dto
    
    6
    + * %%
    
    7
    + * Copyright (C) 2008 - 2024 IRD, Ultreia.io
    
    8
    + * %%
    
    9
    + * This program is free software: you can redistribute it and/or modify
    
    10
    + * it under the terms of the GNU General Public License as
    
    11
    + * published by the Free Software Foundation, either version 3 of the
    
    12
    + * License, or (at your option) any later version.
    
    13
    + *
    
    14
    + * This program is distributed in the hope that it will be useful,
    
    15
    + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    16
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    17
    + * GNU General Public License for more details.
    
    18
    + *
    
    19
    + * You should have received a copy of the GNU General Public
    
    20
    + * License along with this program.  If not, see
    
    21
    + * <http://www.gnu.org/licenses/gpl-3.0.html>.
    
    22
    + * #L%
    
    23
    + */
    
    24
    +
    
    25
    +import fr.ird.observe.dto.CommonDto;
    
    26
    +import io.ultreia.java4all.lang.Numbers;
    
    27
    +import org.junit.Assert;
    
    28
    +import org.junit.Test;
    
    29
    +
    
    30
    +import java.util.Date;
    
    31
    +import java.util.Map;
    
    32
    +
    
    33
    +/**
    
    34
    + * Created on 05/11/16.
    
    35
    + *
    
    36
    + * @author Tony Chemit - dev@tchemit.fr
    
    37
    + * @since 6.0
    
    38
    + */
    
    39
    +public class FormulaHelperTest {
    
    40
    +
    
    41
    +    @Test
    
    42
    +    public void testComputeValue() {
    
    43
    +
    
    44
    +        WithFormula formula = new WithFormula() {
    
    45
    +            @Override
    
    46
    +            public String getSpeciesFaoCode() {
    
    47
    +                return null;
    
    48
    +            }
    
    49
    +
    
    50
    +            @Override
    
    51
    +            public CommonDto getSpecies() {
    
    52
    +                return null;
    
    53
    +            }
    
    54
    +
    
    55
    +            @Override
    
    56
    +            public Date getStartDate() {
    
    57
    +                return null;
    
    58
    +            }
    
    59
    +
    
    60
    +            @Override
    
    61
    +            public Date getEndDate() {
    
    62
    +                return null;
    
    63
    +            }
    
    64
    +
    
    65
    +            @Override
    
    66
    +            public String getCoefficients() {
    
    67
    +                return "a=3.8e-5:b=2.78 ";
    
    68
    +            }
    
    69
    +
    
    70
    +            @Override
    
    71
    +            public void setCoefficients(String coefficients) {
    
    72
    +
    
    73
    +            }
    
    74
    +
    
    75
    +            @Override
    
    76
    +            public Double getCoefficientValue(String coefficientName) {
    
    77
    +                return getCoefficientValues().get(coefficientName);
    
    78
    +            }
    
    79
    +
    
    80
    +            @Override
    
    81
    +            public Map<String, Double> getCoefficientValues() {
    
    82
    +                return Map.of("a", 3.8e-5, "b", 2.78);
    
    83
    +            }
    
    84
    +
    
    85
    +            @Override
    
    86
    +            public String getFormulaOneVariableName() {
    
    87
    +                return null;
    
    88
    +            }
    
    89
    +
    
    90
    +            @Override
    
    91
    +            public String getFormulaTwoVariableName() {
    
    92
    +                return null;
    
    93
    +            }
    
    94
    +
    
    95
    +            @Override
    
    96
    +            public void setStartDate(Date startDate) {
    
    97
    +
    
    98
    +            }
    
    99
    +
    
    100
    +            @Override
    
    101
    +            public void setEndDate(Date endDate) {
    
    102
    +
    
    103
    +            }
    
    104
    +
    
    105
    +            @Override
    
    106
    +            public String getSource() {
    
    107
    +                return null;
    
    108
    +            }
    
    109
    +
    
    110
    +            @Override
    
    111
    +            public void setSource(String source) {
    
    112
    +
    
    113
    +            }
    
    114
    +
    
    115
    +            @Override
    
    116
    +            public String getFormulaOne() {
    
    117
    +                return null;
    
    118
    +            }
    
    119
    +
    
    120
    +            @Override
    
    121
    +            public String getFormulaTwo() {
    
    122
    +                return null;
    
    123
    +            }
    
    124
    +
    
    125
    +            @Override
    
    126
    +            public boolean isFormulaOneValid() {
    
    127
    +                return false;
    
    128
    +            }
    
    129
    +
    
    130
    +            @Override
    
    131
    +            public void setFormulaOneValid(boolean formulaOneValid) {
    
    132
    +
    
    133
    +            }
    
    134
    +
    
    135
    +            @Override
    
    136
    +            public boolean isFormulaTwoValid() {
    
    137
    +                return false;
    
    138
    +            }
    
    139
    +
    
    140
    +            @Override
    
    141
    +            public void setFormulaTwoValid(boolean formulaTwoValid) {
    
    142
    +
    
    143
    +            }
    
    144
    +        };
    
    145
    +
    
    146
    +        Float weight = FormulaHelper.computeValue(formula, "a * Math.pow(L, b)", null, "L", 84.0f, Numbers::roundThreeDigits);
    
    147
    +        Assert.assertNotNull(weight);
    
    148
    +
    
    149
    +        float excepted = (float) (Math.pow(84.0, 2.78) * 3.8e-5);
    
    150
    +        Assert.assertEquals(excepted, weight, 3);
    
    151
    +    }
    
    152
    +}