diff --git a/build.gradle b/build.gradle index c361c2172..6a1450c04 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ plugins { allprojects { group = 'fr.insee.eno' - version = '3.9.3-SNAPSHOT' + version = '3.10.0-SNAPSHOT' sourceCompatibility = '17' } diff --git a/eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticLogicException.java b/eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticLogicException.java new file mode 100644 index 000000000..11cb95ca4 --- /dev/null +++ b/eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticLogicException.java @@ -0,0 +1,10 @@ +package fr.insee.eno.core.exceptions.business; + +/** Exception to be thrown if a blocking logical inconsistency is detected in the Lunatic questionnaire. */ +public class LunaticLogicException extends RuntimeException { + + public LunaticLogicException(String message) { + super(message); + } + +} diff --git a/eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticLoopResolutionException.java b/eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticLoopException.java similarity index 62% rename from eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticLoopResolutionException.java rename to eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticLoopException.java index 8141aeecf..cac1d2592 100644 --- a/eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticLoopResolutionException.java +++ b/eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticLoopException.java @@ -2,9 +2,9 @@ /** Exception to be thrown when invalid information is detected in a loop object that makes it impossible to be * resolved in Lunatic. */ -public class LunaticLoopResolutionException extends RuntimeException { +public class LunaticLoopException extends RuntimeException { - public LunaticLoopResolutionException(String message) { + public LunaticLoopException(String message) { super(message); } diff --git a/eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticProcessingException.java b/eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticProcessingException.java deleted file mode 100644 index 750572778..000000000 --- a/eno-core/src/main/java/fr/insee/eno/core/exceptions/business/LunaticProcessingException.java +++ /dev/null @@ -1,8 +0,0 @@ -package fr.insee.eno.core.exceptions.business; - -public class LunaticProcessingException extends RuntimeException { - - public LunaticProcessingException(String message) { - super(message); - } -} diff --git a/eno-core/src/main/java/fr/insee/eno/core/exceptions/technical/LunaticPairwiseException.java b/eno-core/src/main/java/fr/insee/eno/core/exceptions/technical/LunaticPairwiseException.java new file mode 100644 index 000000000..cd305d173 --- /dev/null +++ b/eno-core/src/main/java/fr/insee/eno/core/exceptions/technical/LunaticPairwiseException.java @@ -0,0 +1,11 @@ +package fr.insee.eno.core.exceptions.technical; + +/** Exception to be thrown if illegal content if found in a Lunatic pairwise object. + * This exception exists since current Lunatic pairwise modeling relies on implicit conventions. */ +public class LunaticPairwiseException extends RuntimeException { + + public LunaticPairwiseException(String message) { + super(message); + } + +} diff --git a/eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingLoopVariable.java b/eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingEntry.java similarity index 84% rename from eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingLoopVariable.java rename to eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingEntry.java index 51fcceab5..845dfa7d3 100644 --- a/eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingLoopVariable.java +++ b/eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingEntry.java @@ -7,7 +7,7 @@ @Data @AllArgsConstructor -public class LunaticResizingLoopVariable { +public class LunaticResizingEntry { private String name; private String size; private List variables; diff --git a/eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingPairWiseVariable.java b/eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingPairwiseEntry.java similarity index 84% rename from eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingPairWiseVariable.java rename to eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingPairwiseEntry.java index cd3a36d8a..072857f14 100644 --- a/eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingPairWiseVariable.java +++ b/eno-core/src/main/java/fr/insee/eno/core/model/lunatic/LunaticResizingPairwiseEntry.java @@ -7,7 +7,7 @@ @Data @AllArgsConstructor -public class LunaticResizingPairWiseVariable { +public class LunaticResizingPairwiseEntry { private String name; private List sizeForLinksVariables; private List linksVariables; diff --git a/eno-core/src/main/java/fr/insee/eno/core/processing/out/LunaticProcessing.java b/eno-core/src/main/java/fr/insee/eno/core/processing/out/LunaticProcessing.java index 66be04f98..1a2bcd86d 100644 --- a/eno-core/src/main/java/fr/insee/eno/core/processing/out/LunaticProcessing.java +++ b/eno-core/src/main/java/fr/insee/eno/core/processing/out/LunaticProcessing.java @@ -7,6 +7,7 @@ import fr.insee.eno.core.processing.out.steps.lunatic.calculatedvariable.ShapefromAttributeRetrieval; import fr.insee.eno.core.processing.out.steps.lunatic.calculatedvariable.ShapefromAttributeRetrievalFromVariableGroups; import fr.insee.eno.core.processing.out.steps.lunatic.pagination.LunaticAddPageNumbers; +import fr.insee.eno.core.processing.out.steps.lunatic.resizing.LunaticAddResizing; import fr.insee.eno.core.processing.out.steps.lunatic.table.LunaticTableProcessing; import fr.insee.eno.core.reference.EnoCatalog; import fr.insee.eno.core.reference.EnoIndex; @@ -40,7 +41,7 @@ public void applyProcessing(Questionnaire lunaticQuestionnaire, EnoQuestionnaire .then(new LunaticLoopResolution(enoQuestionnaire)) .then(new LunaticTableProcessing(enoQuestionnaire)) .then(new LunaticAddMissingVariables(enoCatalog, parameters.isMissingVariables())) - //.then(new LunaticAddResizing(enoQuestionnaire)) // TODO: re-active when work is done + .then(new LunaticAddResizing(enoQuestionnaire)) .then(new LunaticAddHierarchy()) .then(new LunaticAddPageNumbers(parameters.getLunaticPaginationMode())) .then(new LunaticAddCleaningVariables()) diff --git a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddMissingVariables.java b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddMissingVariables.java index 08bbdd268..75be6a3b2 100644 --- a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddMissingVariables.java +++ b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddMissingVariables.java @@ -1,6 +1,6 @@ package fr.insee.eno.core.processing.out.steps.lunatic; -import fr.insee.eno.core.exceptions.business.LunaticProcessingException; +import fr.insee.eno.core.exceptions.business.LunaticLoopException; import fr.insee.eno.core.model.lunatic.MissingBlock; import fr.insee.eno.core.processing.ProcessingStep; import fr.insee.eno.core.reference.EnoCatalog; @@ -170,7 +170,8 @@ private void setComponentMissingResponse(ComponentType component) { .map(ComponentSimpleResponseType::getResponse) .map(ResponseType::getName) .findFirst() - .orElseThrow(() -> new LunaticProcessingException(String.format("main loop %s does not have a simple question in his components", loop.getId()))); + .orElseThrow(() -> new LunaticLoopException(String.format( + "Main loop '%s' does not have a simple question in its components.", loop.getId()))); } // missing responses are handled on the components of pairwise diff --git a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddResizing.java b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddResizing.java deleted file mode 100644 index 62859ccd3..000000000 --- a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddResizing.java +++ /dev/null @@ -1,126 +0,0 @@ -package fr.insee.eno.core.processing.out.steps.lunatic; - -import fr.insee.eno.core.exceptions.business.LunaticSerializationException; -import fr.insee.eno.core.model.EnoQuestionnaire; -import fr.insee.eno.core.model.lunatic.LunaticResizingLoopVariable; -import fr.insee.eno.core.model.lunatic.LunaticResizingPairWiseVariable; -import fr.insee.eno.core.model.navigation.LinkedLoop; -import fr.insee.eno.core.model.question.PairwiseQuestion; -import fr.insee.eno.core.model.variable.CollectedVariable; -import fr.insee.eno.core.model.variable.Variable; -import fr.insee.eno.core.processing.ProcessingStep; -import fr.insee.eno.core.reference.EnoCatalog; -import fr.insee.lunatic.model.flat.*; -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import java.util.ArrayList; -import java.util.List; - -@Slf4j -@AllArgsConstructor -public class LunaticAddResizing implements ProcessingStep { - - private EnoQuestionnaire enoQuestionnaire; - private EnoCatalog enoCatalog; - - public LunaticAddResizing(EnoQuestionnaire enoQuestionnaire) { - this.enoQuestionnaire = enoQuestionnaire; - this.enoCatalog = new EnoCatalog(enoQuestionnaire); - } - - @Override - public void apply(Questionnaire lunaticQuestionnaire) { - ResizingType resizingType = lunaticQuestionnaire.getResizing(); - if(resizingType == null) { - resizingType = new ResizingType(); - } - - List resizings = resizingType.getAny(); - - for(ComponentType component: lunaticQuestionnaire.getComponents()) { - if(component.getComponentType().equals(ComponentTypeEnum.LOOP)) { - resizings.addAll(buildResizingVariablesForLoop((Loop) component)); - } - - if(component.getComponentType().equals(ComponentTypeEnum.PAIRWISE_LINKS)) { - resizings.add(buildResizingVariableForPairwise((PairwiseLinks) component)); - } - } - - if(!resizings.isEmpty()) { - lunaticQuestionnaire.setResizing(resizingType); - } - } - - - /** - * Build resizing variable for a pairwise link - * @param links pairwise link - * @return resizing variable of a pairwise link - */ - private LunaticResizingPairWiseVariable buildResizingVariableForPairwise(PairwiseLinks links) { - List sizesVTLFormula = List.of(links.getXAxisIterations().getValue(), links.getYAxisIterations().getValue()); - String resizingVariable = ((PairwiseQuestion) enoCatalog.getQuestion(links.getId())).getLoopVariableName(); - List variablesNames = getLoopVariables(links.getId()); - return new LunaticResizingPairWiseVariable(resizingVariable, sizesVTLFormula, variablesNames); - } - - - /** - * Build resizing variables for a loop - * @param loop loop - * @return list of resizing variables of a loop - */ - private List buildResizingVariablesForLoop(Loop loop) { - // no need to handle linked loops, variables from main loop includes variables from linked loops - fr.insee.eno.core.model.navigation.Loop enoLoop = (fr.insee.eno.core.model.navigation.Loop) enoQuestionnaire.getIndex().get(loop.getId()); - String sizeVTLFormula; - - if(enoLoop instanceof LinkedLoop) { - sizeVTLFormula = loop.getIterations().getValue(); - } else { - sizeVTLFormula = loop.getLines().getMax().getValue(); - } - List resizingVariables = getResizingVariables(loop); - - if(resizingVariables.isEmpty()) { - return new ArrayList<>(); - } - - List loopVariables = getLoopVariables(loop.getId()); - return resizingVariables.stream() - .map(resizingVariable -> new LunaticResizingLoopVariable(resizingVariable.getName(), sizeVTLFormula, loopVariables)) - .toList(); - - } - - /** - * @param loopId/pairwise loop id - * @return all the variables in a loop/pairwise - */ - private List getLoopVariables(String loopId) { - //TODO retrieve variables from loop - return new ArrayList<>(); - } - - /** - * Get resizing variables of a loop - * @param loop loop component - * @return resizing variable list - */ - private List getResizingVariables(Loop loop) { - return loop.getLoopDependencies().stream() - .map(this::getEnoVariable) - .filter(variable -> variable.getCollectionType().equals(Variable.CollectionType.COLLECTED)) - .map(CollectedVariable.class::cast) - .toList(); - } - - private Variable getEnoVariable(String variableName) { - return enoCatalog.getVariables().stream() - .filter(variable -> variableName.equals(variable.getName())) - .findFirst() - .orElseThrow(() -> new LunaticSerializationException(String.format("Variable %s not found when trying to process resizing", variableName))); - } -} diff --git a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticLoopResolution.java b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticLoopResolution.java index 9cd54992e..a2d7012e5 100644 --- a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticLoopResolution.java +++ b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticLoopResolution.java @@ -1,7 +1,7 @@ package fr.insee.eno.core.processing.out.steps.lunatic; import fr.insee.eno.core.Constant; -import fr.insee.eno.core.exceptions.business.LunaticLoopResolutionException; +import fr.insee.eno.core.exceptions.business.LunaticLoopException; import fr.insee.eno.core.exceptions.technical.MappingException; import fr.insee.eno.core.model.EnoIdentifiableObject; import fr.insee.eno.core.model.EnoObject; @@ -60,7 +60,7 @@ private static Loop findLunaticLoop(Questionnaire lunaticQuestionnaire, fr.insee private void insertSequencesInLoop(Questionnaire lunaticQuestionnaire, Loop lunaticLoop, fr.insee.eno.core.model.navigation.Loop enoLoop) { if (enoLoop.getLoopScope().isEmpty()) - throw new LunaticLoopResolutionException("Loop '" + enoLoop.getId() + "' has an empty scope."); + throw new LunaticLoopException("Loop '" + enoLoop.getId() + "' has an empty scope."); int position = insertSequenceInLoop(lunaticQuestionnaire, lunaticLoop, enoLoop.getLoopScope().get(0).getId()); enoLoop.getLoopScope().stream().skip(1).forEachOrdered(structureItemReference -> insertSequenceInLoop(lunaticQuestionnaire, lunaticLoop, structureItemReference.getId())); @@ -151,7 +151,7 @@ private void setLinkedLoopIterations(Loop lunaticLoop, LinkedLoop enoLinkedLoop) // We "just" want to find the first variable in the scope of the reference loop EnoIdentifiableObject reference = enoIndex.get(enoLinkedLoop.getReference()); if (reference instanceof StandaloneLoop enoReferenceLoop) { - String variableName = findFirstVariableOfReference(enoLinkedLoop, enoReferenceLoop); + String variableName = findFirstVariableOfReference(enoLinkedLoop, enoReferenceLoop, enoIndex); lunaticLoop.setIterations(new LabelType()); lunaticLoop.getIterations().setValue("count("+ variableName +")"); lunaticLoop.getIterations().setType(Constant.LUNATIC_LABEL_VTL); @@ -163,24 +163,25 @@ private void setLinkedLoopIterations(Loop lunaticLoop, LinkedLoop enoLinkedLoop) "Linked loop '%s' is based on a dynamic table. This feature is not supported yet.", enoLinkedLoop.getId())); } - throw new LunaticLoopResolutionException(String.format( + throw new LunaticLoopException(String.format( "Linked loop '%s' reference object's '%s' is neither a loop nor a dynamic table.", enoLinkedLoop.getId(), reference)); } - private String findFirstVariableOfReference(LinkedLoop enoLinkedLoop, StandaloneLoop enoReferenceLoop) { + public static String findFirstVariableOfReference(LinkedLoop enoLinkedLoop, StandaloneLoop enoReferenceLoop, + EnoIndex enoIndex) { AbstractSequence startSequence = (AbstractSequence) enoIndex.get(enoReferenceLoop.getLoopScope().get(0).getId()); if (startSequence.getSequenceStructure().isEmpty()) { - throw new LunaticLoopResolutionException(String.format( + throw new LunaticLoopException(String.format( "Linked loop '%s' is based on loop '%s'. " + "This loop is defined to start at sequence '%s', which is empty. " + "Unable to find its first question to compute Lunatic \"iterations\" expression.", enoLinkedLoop.getId(), enoReferenceLoop.getId(), startSequence.getId())); } - String firstQuestionId = findFirstQuestionId(startSequence); + String firstQuestionId = findFirstQuestionId(startSequence, enoIndex); EnoObject firstQuestion = enoIndex.get(firstQuestionId); if (! (firstQuestion instanceof SingleResponseQuestion)) { - throw new LunaticLoopResolutionException(String.format( + throw new LunaticLoopException(String.format( "Linked loop '%s' is based on loop '%s' that starts at sequence '%s'. " + "This first question of the sequence is not a \"simple\" question.", enoLinkedLoop.getId(), enoReferenceLoop.getId(), startSequence.getId())); @@ -190,15 +191,16 @@ private String findFirstVariableOfReference(LinkedLoop enoLinkedLoop, Standalone /** * Return the id of the first question in given sequence or subsequence object. + * * @param sequence Eno sequence object. * @return The id of the first question within the sequence. */ - private String findFirstQuestionId(AbstractSequence sequence) { + private static String findFirstQuestionId(AbstractSequence sequence, EnoIndex enoIndex) { StructureItemReference firstSequenceItem = sequence.getSequenceStructure().get(0); if (firstSequenceItem.getType() == StructureItemType.QUESTION) return firstSequenceItem.getId(); AbstractSequence subsequence = (AbstractSequence) enoIndex.get(firstSequenceItem.getId()); - return findFirstQuestionId(subsequence); + return findFirstQuestionId(subsequence, enoIndex); } } diff --git a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/pagination/LunaticAddPageNumbersSequenceMode.java b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/pagination/LunaticAddPageNumbersSequenceMode.java index 9247e1ffa..971dbfb3b 100644 --- a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/pagination/LunaticAddPageNumbersSequenceMode.java +++ b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/pagination/LunaticAddPageNumbersSequenceMode.java @@ -1,6 +1,6 @@ package fr.insee.eno.core.processing.out.steps.lunatic.pagination; - import fr.insee.eno.core.exceptions.business.LunaticLoopResolutionException; + import fr.insee.eno.core.exceptions.business.LunaticLoopException; import fr.insee.lunatic.model.flat.ComponentType; import fr.insee.lunatic.model.flat.ComponentTypeEnum; import fr.insee.lunatic.model.flat.Loop; @@ -50,7 +50,7 @@ public void applyNumPageOnSubsequence(Subsequence subsequence, String numPagePre private boolean shouldLoopBePaginated(Loop loop) { List loopComponents = loop.getComponents(); if(loopComponents == null || loopComponents.isEmpty()) { - throw new LunaticLoopResolutionException(String.format("Loop %s should have components inside", loop.getId())); + throw new LunaticLoopException(String.format("Loop %s should have components inside", loop.getId())); } return loopComponents.get(0).getComponentType().equals(ComponentTypeEnum.SEQUENCE); diff --git a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticAddResizing.java b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticAddResizing.java new file mode 100644 index 000000000..d370fd940 --- /dev/null +++ b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticAddResizing.java @@ -0,0 +1,87 @@ +package fr.insee.eno.core.processing.out.steps.lunatic.resizing; + +import fr.insee.eno.core.exceptions.business.LunaticLogicException; +import fr.insee.eno.core.model.EnoQuestionnaire; +import fr.insee.eno.core.model.lunatic.LunaticResizingEntry; +import fr.insee.eno.core.model.lunatic.LunaticResizingPairwiseEntry; +import fr.insee.eno.core.processing.ProcessingStep; +import fr.insee.eno.core.reference.EnoIndex; +import fr.insee.lunatic.model.flat.*; +import lombok.extern.slf4j.Slf4j; + +import java.util.*; + +@Slf4j +public class LunaticAddResizing implements ProcessingStep { + + private final EnoQuestionnaire enoQuestionnaire; + private final EnoIndex enoIndex; + + public LunaticAddResizing(EnoQuestionnaire enoQuestionnaire) { + this.enoQuestionnaire = enoQuestionnaire; + this.enoIndex = enoQuestionnaire.getIndex(); + } + + @Override + public void apply(Questionnaire lunaticQuestionnaire) { + // + ResizingType resizingType = new ResizingType(); + List resizingList = resizingType.getAny(); + // + LunaticLoopResizingLogic loopResizingLogic = new LunaticLoopResizingLogic( + lunaticQuestionnaire, enoQuestionnaire, enoIndex); + LunaticPairwiseResizingLogic pairwiseResizingLogic = new LunaticPairwiseResizingLogic( + lunaticQuestionnaire, enoIndex); + LunaticDynamicTableResizingLogic dynamicTableResizingLogic = new LunaticDynamicTableResizingLogic( + lunaticQuestionnaire); + // + lunaticQuestionnaire.getComponents().forEach(component -> { + ComponentTypeEnum componentType = component.getComponentType(); + if (Objects.requireNonNull(componentType) == ComponentTypeEnum.LOOP) { + resizingList.addAll(loopResizingLogic.buildResizingEntries((Loop) component)); + } + if (componentType == ComponentTypeEnum.ROSTER_FOR_LOOP) { + log.warn("Resizing is not implemented for dynamic tables."); + resizingList.addAll(dynamicTableResizingLogic.buildResizingEntries((RosterForLoop) component)); + } + if (componentType == ComponentTypeEnum.PAIRWISE_LINKS) { + resizingList.addAll(pairwiseResizingLogic.buildPairwiseResizingEntries((PairwiseLinks) component)); + } + }); + // Check that there is no duplicate keys for resizing + // (Lunatic modeling for resizing has to be changed to be more precise, so that there would be no duplicate issue) + noDuplicatesControl(resizingList); + // Set the resizing list only if it is not empty + if(!resizingList.isEmpty()) + lunaticQuestionnaire.setResizing(resizingType); + } + + /** Throws an exception if there are duplicates in the resizing list. + * We could make something more precise than this by making controls everywhere we add resizing entries. + * Yet since the Lunatic "resizing" feature has to be reworked, we'll settle for that for now. */ + private void noDuplicatesControl(List resizingList) { + Set resizingKeys = new HashSet<>(); + Set duplicates = new HashSet<>(); + resizingList.forEach(resizingEntry -> { + // crappy code but all this will be removed when there will be a proper implementation in Lunatic-Model + String variableName = null; + if (resizingEntry instanceof LunaticResizingEntry lunaticResizingEntry) + variableName = lunaticResizingEntry.getName(); + if (resizingEntry instanceof LunaticResizingPairwiseEntry lunaticResizingPairwiseEntry) + variableName = lunaticResizingPairwiseEntry.getName(); + assert variableName != null; + // + if (resizingKeys.contains(variableName)) + duplicates.add(variableName); + resizingKeys.add(variableName); + }); + // + if (! duplicates.isEmpty()) + throw new LunaticLogicException(String.format( + "Variables '%s' is are used to define the size of different components in the questionnaire. " + + "Check loop 'max' iteration expressions, dynamic table max size expressions, " + + "and variable used for the pairwise component.", + duplicates)); + } + +} diff --git a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticDynamicTableResizingLogic.java b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticDynamicTableResizingLogic.java new file mode 100644 index 000000000..60044fe38 --- /dev/null +++ b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticDynamicTableResizingLogic.java @@ -0,0 +1,26 @@ +package fr.insee.eno.core.processing.out.steps.lunatic.resizing; + +import fr.insee.eno.core.model.lunatic.LunaticResizingEntry; +import fr.insee.lunatic.model.flat.Questionnaire; +import fr.insee.lunatic.model.flat.RosterForLoop; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; + +@Slf4j +public class LunaticDynamicTableResizingLogic { + + private final Questionnaire lunaticQuestionnaire; + + public LunaticDynamicTableResizingLogic(Questionnaire lunaticQuestionnaire) { + this.lunaticQuestionnaire = lunaticQuestionnaire; + } + + public List buildResizingEntries(RosterForLoop dynamicTable) { + log.warn("Dynamic table '{}': no resizing entries will be added.", dynamicTable.getId()); + log.debug("(Questionnaire '{}')", lunaticQuestionnaire.getId()); + return new ArrayList<>(); + } + +} diff --git a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticLoopResizingLogic.java b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticLoopResizingLogic.java new file mode 100644 index 000000000..f2e2ecc82 --- /dev/null +++ b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticLoopResizingLogic.java @@ -0,0 +1,137 @@ +package fr.insee.eno.core.processing.out.steps.lunatic.resizing; + +import fr.insee.eno.core.exceptions.business.LunaticLoopException; +import fr.insee.eno.core.exceptions.technical.MappingException; +import fr.insee.eno.core.model.EnoQuestionnaire; +import fr.insee.eno.core.model.calculated.BindingReference; +import fr.insee.eno.core.model.lunatic.LunaticResizingEntry; +import fr.insee.eno.core.model.navigation.LinkedLoop; +import fr.insee.eno.core.model.navigation.StandaloneLoop; +import fr.insee.eno.core.processing.out.steps.lunatic.LunaticLoopResolution; +import fr.insee.eno.core.reference.EnoIndex; +import fr.insee.lunatic.model.flat.*; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@Slf4j +public class LunaticLoopResizingLogic { + + private final Questionnaire lunaticQuestionnaire; + private final EnoQuestionnaire enoQuestionnaire; + private final EnoIndex enoIndex; + + public LunaticLoopResizingLogic(Questionnaire lunaticQuestionnaire, EnoQuestionnaire enoQuestionnaire, EnoIndex enoIndex) { + this.lunaticQuestionnaire = lunaticQuestionnaire; + this.enoQuestionnaire = enoQuestionnaire; + this.enoIndex = enoIndex; + } + + /** + * Build resizing entries for a loop. + * @param lunaticLoop Lunatic loop object. + * @return list of resizing entries of the loop. + */ + public List buildResizingEntries(Loop lunaticLoop) { + + // Corresponding Eno loop object + fr.insee.eno.core.model.navigation.Loop enoLoop = (fr.insee.eno.core.model.navigation.Loop) + enoIndex.get(lunaticLoop.getId()); + if (enoLoop == null) + throw new MappingException(String.format( + "Eno loop object corresponding to Lunatic loop '%s' cannot be found.", lunaticLoop.getId())); + + // Variable names that are the keys of the resizing + List resizingVariableNames = new ArrayList<>(); + // Expression that resize the concerned variables + String sizeExpression = null; + + if (enoLoop instanceof StandaloneLoop enoStandaloneLoop) { + resizingVariableNames.addAll(findResizingVariablesForLoop(enoStandaloneLoop)); + sizeExpression = lunaticLoop.getLines().getMax().getValue(); + } + if (enoLoop instanceof LinkedLoop enoLinkedLoop){ + resizingVariableNames.add(findResizingVariableForLinkedLoop(enoLinkedLoop)); + sizeExpression = lunaticLoop.getIterations().getValue(); + } + + if (resizingVariableNames.isEmpty()) + return new ArrayList<>(); + + // Concerned variables to be resized + // Note: external variables are not concerned since their values are not designed to be changed dynamically + List resizedVariableNames = getCollectedVariablesInLoop(lunaticLoop); + + List resizingLoopEntries = new ArrayList<>(); + String finalSizeExpression = sizeExpression; // (due to usage in lambda) + resizingVariableNames.forEach(variableName -> + resizingLoopEntries.add( + new LunaticResizingEntry(variableName, finalSizeExpression, resizedVariableNames))); + + return resizingLoopEntries; + } + + private List findResizingVariablesForLoop(StandaloneLoop enoLoop) { + List maxIterationDependencies = enoLoop.getMaxIteration().getBindingReferences().stream() + .map(BindingReference::getVariableName) + .toList(); + return lunaticQuestionnaire.getVariables().stream() + .filter(variable -> VariableTypeEnum.COLLECTED.equals(variable.getVariableType())) + .map(IVariableType::getName) + .filter(maxIterationDependencies::contains) + .toList(); + } + + /** For a linked loop, the resizing variable is the first variable of its reference/main loop + * (this implicit rule strikes again here...). */ + private String findResizingVariableForLinkedLoop(LinkedLoop enoLinkedLoop) { + // Find the reference/main loop of the linked loop + Optional referenceLoop = enoQuestionnaire.getLoops().stream() + .filter(StandaloneLoop.class::isInstance) + .map(StandaloneLoop.class::cast) + .filter(standaloneLoop -> enoLinkedLoop.getReference().equals(standaloneLoop.getId())) + .findAny(); + if (referenceLoop.isEmpty()) + throw new MappingException(String.format( + "Unable to find the reference loop '%s' of linked loop '%s'", + enoLinkedLoop.getReference(), enoLinkedLoop.getId())); + // Return the variable name of its first question (reusing some code from lunatic loop processing) + return LunaticLoopResolution.findFirstVariableOfReference(enoLinkedLoop, referenceLoop.get(), enoIndex); + } + + private List getCollectedVariablesInLoop(Loop loop) { + List result = new ArrayList<>(); + loop.getComponents().forEach(component -> { + switch (component.getComponentType()) { + case CHECKBOX_BOOLEAN, INPUT_NUMBER, INPUT, TEXTAREA, DATEPICKER, RADIO, CHECKBOX_ONE, DROPDOWN -> + result.add(((ComponentSimpleResponseType) component).getResponse().getName()); + case CHECKBOX_GROUP -> + ((CheckboxGroup) component).getResponses().forEach(responsesCheckboxGroup -> + result.add(responsesCheckboxGroup.getResponse().getName())); + case TABLE -> + ((Table) component).getBodyLines().forEach(bodyLine -> + bodyLine.getBodyCells().stream() + .filter(bodyCell -> bodyCell.getResponse() != null) + .forEach(bodyCell -> result.add(bodyCell.getResponse().getName()))); + case ROSTER_FOR_LOOP -> + throw new LunaticLoopException(String.format( + "Dynamic tables are forbidden in loops: loop '%s' contains a dynamic table.", + loop.getId())); + case LOOP -> + throw new LunaticLoopException(String.format( + "Nested loop are forbidden: loop '%s' contains an other loop.", + loop.getId())); + case PAIRWISE_LINKS -> + throw new LunaticLoopException(String.format( + "Pairwise components are forbidden in loops: loop '%s' contains a pairwise component.", + loop.getId())); + default -> + log.debug("(Resizing) Component of type {} has no response.", component.getComponentType()); + } + }); + return result; + } + +} diff --git a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticPairwiseResizingLogic.java b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticPairwiseResizingLogic.java new file mode 100644 index 000000000..dbf6cfee1 --- /dev/null +++ b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticPairwiseResizingLogic.java @@ -0,0 +1,100 @@ +package fr.insee.eno.core.processing.out.steps.lunatic.resizing; + +import fr.insee.eno.core.exceptions.technical.LunaticPairwiseException; +import fr.insee.eno.core.exceptions.technical.MappingException; +import fr.insee.eno.core.model.lunatic.LunaticResizingPairwiseEntry; +import fr.insee.eno.core.model.question.PairwiseQuestion; +import fr.insee.eno.core.reference.EnoIndex; +import fr.insee.lunatic.model.flat.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class LunaticPairwiseResizingLogic { + + private final Questionnaire lunaticQuestionnaire; + private final EnoIndex enoIndex; + + public LunaticPairwiseResizingLogic(Questionnaire lunaticQuestionnaire, EnoIndex enoIndex) { + this.lunaticQuestionnaire = lunaticQuestionnaire; + this.enoIndex = enoIndex; + } + + /** + * Build resizing entries for a pairwise component. + * @param pairwiseLinks Lunatic pairwise object. + * @return list of resizing entries of the loop. + */ + public List buildPairwiseResizingEntries(PairwiseLinks pairwiseLinks) { + + // Corresponding Eno object + PairwiseQuestion enoPairwiseQuestion = (PairwiseQuestion) enoIndex.get(pairwiseLinks.getId()); + if (enoPairwiseQuestion == null) + throw new MappingException(String.format( + "Eno pairwise question corresponding to Lunatic pairwise object '%s' cannot be found.", + pairwiseLinks.getId())); + + // Variable names that are the keys of the resizing + List resizingVariableNames = findResizingVariablesForPairwise(pairwiseLinks, enoPairwiseQuestion); + + if (resizingVariableNames.isEmpty()) + return new ArrayList<>(); + + // Expressions that resize the concerned variables + // (Note: pairwise variables are 'two dimensions', that's why there are two expressions) + List sizeExpressions = List.of( + pairwiseLinks.getXAxisIterations().getValue(), pairwiseLinks.getYAxisIterations().getValue()); + // Concerned variables to be resized + List resizedVariableNames = findResizedVariablesForPairwise(pairwiseLinks); + + List resizingPairwiseEntries = new ArrayList<>(); + resizingVariableNames.forEach(variableName -> + resizingPairwiseEntries.add( + new LunaticResizingPairwiseEntry(variableName, sizeExpressions, resizedVariableNames))); + + return resizingPairwiseEntries; + } + + private List findResizingVariablesForPairwise(PairwiseLinks pairwiseLinks, PairwiseQuestion enoPairwiseQuestion) { + // Source variable of the pairwise + String pairwiseSourceVariableName = enoPairwiseQuestion.getLoopVariableName(); + // Find corresponding variable object + Optional correspondingVariable = lunaticQuestionnaire.getVariables().stream() + .filter(variable -> pairwiseSourceVariableName.equals(variable.getName())) + .findAny(); + if (correspondingVariable.isEmpty()) + throw new LunaticPairwiseException(String.format( + "Source variable '%s' of pairwise component '%s' cannot be found in questionnaire variables.", + pairwiseSourceVariableName, pairwiseLinks)); + VariableType pairwiseSourceVariable = (VariableType) correspondingVariable.get(); + // If it not a calculated, simply return the variable name + if (! VariableTypeEnum.CALCULATED.equals(correspondingVariable.get().getVariableType())) + return List.of(pairwiseSourceVariableName); + // Otherwise return its binding dependencies (without the calculated variable) + return pairwiseSourceVariable.getBindingDependencies().stream() + .filter(variableName -> !pairwiseSourceVariableName.equals(variableName)) + .toList(); + } + + private List findResizedVariablesForPairwise(PairwiseLinks pairwiseLinks) { + // Some controls... + int pairwiseComponentsSize = pairwiseLinks.getComponents().size(); + if (pairwiseComponentsSize != 1) + throw new LunaticPairwiseException(String.format( + "Lunatic pairwise must contain exactly 1 component. Pairwise object '%s' contains %s.", + pairwiseLinks.getId(), pairwiseComponentsSize)); + ComponentType pairwiseComponent = pairwiseLinks.getComponents().get(0); + if (! (ComponentTypeEnum.DROPDOWN.equals(pairwiseComponent.getComponentType()) || + ComponentTypeEnum.RADIO.equals(pairwiseComponent.getComponentType()) || + ComponentTypeEnum.CHECKBOX_ONE.equals(pairwiseComponent.getComponentType()))) + throw new LunaticPairwiseException(String.format( + "Lunatic pairwise component should be a unique choice component. Pairwise object '%s' " + + "contains a component of type '%s'.", + pairwiseLinks.getId(), pairwiseComponent.getComponentType())); + // + return List.of( + ((ComponentSimpleResponseType) pairwiseComponent).getResponse().getName()); + } + +} diff --git a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/table/LunaticTableProcessing.java b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/table/LunaticTableProcessing.java index bf6e948ee..57aee37ab 100644 --- a/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/table/LunaticTableProcessing.java +++ b/eno-core/src/main/java/fr/insee/eno/core/processing/out/steps/lunatic/table/LunaticTableProcessing.java @@ -10,6 +10,7 @@ import fr.insee.lunatic.model.flat.*; import lombok.extern.slf4j.Slf4j; +import java.util.Objects; import java.util.Optional; @Slf4j @@ -27,12 +28,13 @@ public void apply(Questionnaire lunaticQuestionnaire) { } private void processTableComponent(ComponentType componentType) { - switch (componentType.getComponentType()) { - case LOOP -> ((Loop) componentType).getComponents().forEach(this::processTableComponent); - case TABLE -> processTable((Table) componentType); - case ROSTER_FOR_LOOP -> processRosterForLoop((RosterForLoop) componentType); - default -> {} - } + ComponentTypeEnum type = componentType.getComponentType(); + if (Objects.requireNonNull(type) == ComponentTypeEnum.LOOP) + ((Loop) componentType).getComponents().forEach(this::processTableComponent); + if (type == ComponentTypeEnum.TABLE) + processTable((Table) componentType); + if (type == ComponentTypeEnum.ROSTER_FOR_LOOP) + processRosterForLoop((RosterForLoop) componentType); } private void processTable(Table table) { diff --git a/eno-core/src/main/java/fr/insee/eno/core/serialize/LunaticSerializer.java b/eno-core/src/main/java/fr/insee/eno/core/serialize/LunaticSerializer.java index c98054b55..5f6c4cdc8 100644 --- a/eno-core/src/main/java/fr/insee/eno/core/serialize/LunaticSerializer.java +++ b/eno-core/src/main/java/fr/insee/eno/core/serialize/LunaticSerializer.java @@ -122,32 +122,49 @@ private static ObjectNode createCleaningObject(Questionnaire lunaticQuestionnair * @return the resizing node */ private static ObjectNode createResizingObject(Questionnaire lunaticQuestionnaire, ObjectMapper mapper) { - List resizingVariables = lunaticQuestionnaire.getResizing().getAny(); + List resizingList = lunaticQuestionnaire.getResizing().getAny(); - ObjectNode resizingsJson = mapper.createObjectNode(); - for(Object variableObject: resizingVariables) { - if(variableObject instanceof LunaticResizingPairWiseVariable variable) { - resizingsJson.set(variable.getName(), buildPairwiseResizingVariable(variable)); + ObjectNode jsonResizingNode = mapper.createObjectNode(); + for(Object resizingObject: resizingList) { + if(resizingObject instanceof LunaticResizingPairwiseEntry variable) { + jsonResizingNode.set(variable.getName(), resizingPairwiseEntryToJsonNode(variable)); } - - if(variableObject instanceof LunaticResizingLoopVariable variable) { - resizingsJson.set(variable.getName(), buildResizingLoopVariable(variable)); + if(resizingObject instanceof LunaticResizingEntry variable) { + jsonResizingNode.set(variable.getName(), resizingEntryToJsonNode(variable)); } } - return resizingsJson; + return jsonResizingNode; + } + + /** + * Build entry for resizing entry (concerns loops and dynamic tables). + * @param resizingEntry Resizing entry object. + * @return Resizing entry as a json node. + */ + private static JsonNode resizingEntryToJsonNode(LunaticResizingEntry resizingEntry) { + ArrayNode variablesArray = JsonNodeFactory.instance.arrayNode(); + resizingEntry.getVariables().forEach(variablesArray::add); + + ObjectNode loopVariableNode = JsonNodeFactory.instance.objectNode(); + + ValueNode sizeNodeValue = JsonNodeFactory.instance.textNode(resizingEntry.getSize()); + loopVariableNode.set("size", sizeNodeValue); + loopVariableNode.set("variables", variablesArray); + + return loopVariableNode; } /** - * Build variable for pairwise resize - * @param variable variable to resize - * @return variable node to resize + * Build variable for pairwise resizing entry. + * @param resizingPairwiseEntry Pairwise-case resizing entry object. + * @return Resizing entry as a json node. */ - private static JsonNode buildPairwiseResizingVariable(LunaticResizingPairWiseVariable variable) { + private static JsonNode resizingPairwiseEntryToJsonNode(LunaticResizingPairwiseEntry resizingPairwiseEntry) { ArrayNode linksVariablesBuilder = JsonNodeFactory.instance.arrayNode(); - variable.getLinksVariables().forEach(linksVariablesBuilder::add); + resizingPairwiseEntry.getLinksVariables().forEach(linksVariablesBuilder::add); ArrayNode sizeForLinksVariablesBuilder = JsonNodeFactory.instance.arrayNode(); - variable.getSizeForLinksVariables().forEach(sizeForLinksVariablesBuilder::add); + resizingPairwiseEntry.getSizeForLinksVariables().forEach(sizeForLinksVariablesBuilder::add); ObjectNode pairwiseVariableNode = JsonNodeFactory.instance.objectNode(); @@ -157,21 +174,4 @@ private static JsonNode buildPairwiseResizingVariable(LunaticResizingPairWiseVar return pairwiseVariableNode; } - /** - * Build variable for loop resize - * @param variable variable to resize - * @return variable node to resize - */ - private static JsonNode buildResizingLoopVariable(LunaticResizingLoopVariable variable) { - ArrayNode variablesArray = JsonNodeFactory.instance.arrayNode(); - variable.getVariables().forEach(variablesArray::add); - - ObjectNode loopVariableNode = JsonNodeFactory.instance.objectNode(); - - ValueNode sizeNodeValue = JsonNodeFactory.instance.textNode(variable.getSize()); - loopVariableNode.set("size", sizeNodeValue); - loopVariableNode.set("variables", variablesArray); - - return loopVariableNode; - } } diff --git a/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddResizingTest.java b/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddResizingTest.java deleted file mode 100644 index 694fd7b6b..000000000 --- a/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/LunaticAddResizingTest.java +++ /dev/null @@ -1,186 +0,0 @@ -package fr.insee.eno.core.processing.out.steps.lunatic; - -import fr.insee.eno.core.model.EnoQuestionnaire; -import fr.insee.eno.core.model.lunatic.LunaticResizingLoopVariable; -import fr.insee.eno.core.model.lunatic.LunaticResizingPairWiseVariable; -import fr.insee.eno.core.reference.EnoIndex; -import fr.insee.lunatic.model.flat.*; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -class LunaticAddResizingTest { - - private Questionnaire lunaticQuestionnaire; - private LunaticAddResizing processing; - - @BeforeEach - void init() { - lunaticQuestionnaire = buildLunaticQuestionnaire(); - processing = new LunaticAddResizing(createEnoQuestionnaire()); - } - - @Test - @Disabled("work on resizing is in progress") - void whenProcessingLoopVariableIsConsideredAsLoopVariable() { - // - processing.apply(lunaticQuestionnaire); - List resizings = lunaticQuestionnaire.getResizing().getAny(); - // - assertTrue(resizings.get(0) instanceof LunaticResizingLoopVariable); - } - - @Test - @Disabled("work on resizing is in progress") - void whenProcessingLoopVariableIsConsideredAsPairwiseVariable() { - // - processing.apply(lunaticQuestionnaire); - List resizings = lunaticQuestionnaire.getResizing().getAny(); - // - assertTrue(resizings.get(1) instanceof LunaticResizingPairWiseVariable); - } - - private EnoQuestionnaire createEnoQuestionnaire() { - EnoQuestionnaire enoQuestionnaire = new EnoQuestionnaire(); - EnoIndex enoIndex = new EnoIndex(); - // - // TODO: eno questionnaire content - // - enoQuestionnaire.setIndex(enoIndex); - return enoQuestionnaire; - } - - private Questionnaire buildLunaticQuestionnaire() { - Questionnaire questionnaire = new Questionnaire(); - List components = new ArrayList<>(); - List mainLoopComponents = new ArrayList<>(); - List linkedLoopComponents = new ArrayList<>(); - List pairwiseComponents = new ArrayList<>(); - - Sequence s1 = buildSequence("id-1"); - components.add(s1); - InputNumber n2 = buildNumber("id-2"); - components.add(n2); - InputNumber n3 = buildNumber("id-3"); - components.add(n3); - Sequence s4 = buildSequence("id-4"); - components.add(s4); - - InputNumber n5 = buildNumber("id-5"); - components.add(n5); - - Loop l6 = buildEmptyLoop("id-6"); - - // build loop 6 components - Subsequence ss6 = buildSubsequence("id-6-1"); - mainLoopComponents.add(ss6); - - Input i6 = buildInput("id-6-2"); - mainLoopComponents.add(i6); - - l6.getComponents().addAll(mainLoopComponents); - components.add(l6); - - Loop l7 = buildEmptyLoop("id-7"); - - Subsequence ss71 = buildSubsequence("id-7-1"); - linkedLoopComponents.add(ss71); - - CheckboxOne co71 = buildCheckboxOne("id-7-2"); - linkedLoopComponents.add(co71); - - InputNumber n72 = buildNumber("id-7-3"); - linkedLoopComponents.add(n72); - - PairwiseLinks p73 = buildEmptyPairWiseLinks("pairwise-links"); - CheckboxOne co73 = buildCheckboxOne("lhpyz9b73"); - InputNumber n73 = buildNumber("lhpzan73"); - - pairwiseComponents.add(co73); - pairwiseComponents.add(n73); - p73.getComponents().addAll(pairwiseComponents); - linkedLoopComponents.add(p73); - - l7.getComponents().addAll(linkedLoopComponents); - components.add(l7); - - Sequence s8 = buildSequence("li1wjpqw"); - components.add(s8); - - Sequence s9 = buildSequence("COMMENT-SEQ"); - components.add(s9); - - Textarea t10 = buildTextarea("COMMENT-QUESTION"); - components.add(t10); - - questionnaire.getComponents().addAll(components); - return questionnaire; - } - - private CheckboxOne buildCheckboxOne(String id) { - CheckboxOne co = new CheckboxOne(); - co.setComponentType(ComponentTypeEnum.CHECKBOX_ONE); - co.setId(id); - return co; - } - - private Textarea buildTextarea(String id) { - Textarea textarea = new Textarea(); - textarea.setComponentType(ComponentTypeEnum.TEXTAREA); - textarea.setId(id); - return textarea; - } - - private Input buildInput(String id) { - Input input = new Input(); - input.setComponentType(ComponentTypeEnum.INPUT); - input.setId(id); - return input; - } - - private Sequence buildSequence(String id) { - Sequence sequence = new Sequence(); - sequence.setId(id); - sequence.setComponentType(ComponentTypeEnum.SEQUENCE); - return sequence; - } - - private Subsequence buildSubsequence(String id) { - Subsequence subsequence = new Subsequence(); - subsequence.setId(id); - subsequence.setComponentType(ComponentTypeEnum.SUBSEQUENCE); - return subsequence; - } - - private InputNumber buildNumber(String id) { - InputNumber number = new InputNumber(); - number.setComponentType(ComponentTypeEnum.INPUT_NUMBER); - number.setId(id); - return number; - } - - private Loop buildEmptyLoop(String id) { - Loop loop = new Loop(); - loop.setComponentType(ComponentTypeEnum.LOOP); - loop.setId(id); - - LabelType label = new LabelType(); - label.setValue("COUNT(PRENOM)"); - LinesLoop line = new LinesLoop(); - line.setMax(label); - loop.setLines(line); - return loop; - } - - private PairwiseLinks buildEmptyPairWiseLinks(String id) { - PairwiseLinks pairwiseLinks = new PairwiseLinks(); - pairwiseLinks.setId(id); - pairwiseLinks.setComponentType(ComponentTypeEnum.PAIRWISE_LINKS); - return pairwiseLinks; - } -} diff --git a/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticAddResizingTest.java b/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticAddResizingTest.java new file mode 100644 index 000000000..60651612b --- /dev/null +++ b/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticAddResizingTest.java @@ -0,0 +1,96 @@ +package fr.insee.eno.core.processing.out.steps.lunatic.resizing; + +import fr.insee.eno.core.DDIToEno; +import fr.insee.eno.core.exceptions.business.DDIParsingException; +import fr.insee.eno.core.mappers.LunaticMapper; +import fr.insee.eno.core.model.EnoQuestionnaire; +import fr.insee.eno.core.model.lunatic.LunaticResizingEntry; +import fr.insee.eno.core.model.lunatic.LunaticResizingPairwiseEntry; +import fr.insee.eno.core.parameter.EnoParameters; +import fr.insee.eno.core.processing.out.steps.lunatic.LunaticLoopResolution; +import fr.insee.eno.core.processing.out.steps.lunatic.LunaticSortComponents; +import fr.insee.eno.core.processing.out.steps.lunatic.table.LunaticTableProcessing; +import fr.insee.lunatic.model.flat.Questionnaire; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +class LunaticAddResizingTest { + + private static List resizingList; + + @BeforeAll + static void init() throws DDIParsingException { + + // Given + EnoQuestionnaire enoQuestionnaire = DDIToEno.transform( + LunaticAddResizingTest.class.getClassLoader().getResourceAsStream( + "integration/ddi/ddi-resizing.xml"), + EnoParameters.of(EnoParameters.Context.DEFAULT, EnoParameters.ModeParameter.CAWI)); + Questionnaire lunaticQuestionnaire = new Questionnaire(); + LunaticMapper lunaticMapper = new LunaticMapper(); + lunaticMapper.mapQuestionnaire(enoQuestionnaire, lunaticQuestionnaire); + new LunaticSortComponents(enoQuestionnaire).apply(lunaticQuestionnaire); + new LunaticLoopResolution(enoQuestionnaire).apply(lunaticQuestionnaire); + new LunaticTableProcessing(enoQuestionnaire).apply(lunaticQuestionnaire); + + // When + new LunaticAddResizing(enoQuestionnaire).apply(lunaticQuestionnaire); + + // Then + resizingList = lunaticQuestionnaire.getResizing().getAny(); + // -> tests + } + + @Test + void resizingListIsNotEmpty() { + assertFalse(resizingList.isEmpty()); + } + + @Test + void loopResizing() { + List resizingEntries = resizingList.stream() + .filter(LunaticResizingEntry.class::isInstance) + .map(LunaticResizingEntry.class::cast) + .toList(); + // + assertEquals(2, resizingEntries.size()); + // + Optional loopResizingEntry = resizingEntries.stream() + .filter(resizingEntry -> "NUMBER".equals(resizingEntry.getName())) + .findAny(); + assertTrue(loopResizingEntry.isPresent()); + assertEquals("nvl(NUMBER, 1)", loopResizingEntry.get().getSize()); + assertThat(loopResizingEntry.get().getVariables()).containsExactlyInAnyOrderElementsOf( + List.of("Q2", "PAIRWISE_SOURCE")); + // + Optional linkedLoopResizingEntry = resizingEntries.stream() + .filter(resizingEntry -> "Q2".equals(resizingEntry.getName())) + .findAny(); + assertTrue(linkedLoopResizingEntry.isPresent()); + assertEquals("count(Q2)", linkedLoopResizingEntry.get().getSize()); + assertEquals(List.of("Q3"), linkedLoopResizingEntry.get().getVariables()); + } + + @Test + void pairwiseResizing() { + List resizingPairwiseEntries = resizingList.stream() + .filter(LunaticResizingPairwiseEntry.class::isInstance) + .map(LunaticResizingPairwiseEntry.class::cast) + .toList(); + // + assertEquals(1, resizingPairwiseEntries.size()); + // + LunaticResizingPairwiseEntry pairwiseResizingEntry = resizingPairwiseEntries.get(0); + assertEquals("PAIRWISE_SOURCE", pairwiseResizingEntry.getName()); + assertEquals(List.of("count(PAIRWISE_SOURCE)", "count(PAIRWISE_SOURCE)"), + pairwiseResizingEntry.getSizeForLinksVariables()); + assertEquals(List.of("LINKS"), pairwiseResizingEntry.getLinksVariables()); + } + +} diff --git a/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticLoopResizingLogicTest.java b/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticLoopResizingLogicTest.java new file mode 100644 index 000000000..ee129db50 --- /dev/null +++ b/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticLoopResizingLogicTest.java @@ -0,0 +1,320 @@ +package fr.insee.eno.core.processing.out.steps.lunatic.resizing; + +import fr.insee.eno.core.model.EnoQuestionnaire; +import fr.insee.eno.core.model.calculated.BindingReference; +import fr.insee.eno.core.model.calculated.CalculatedExpression; +import fr.insee.eno.core.model.lunatic.LunaticResizingEntry; +import fr.insee.eno.core.model.navigation.StandaloneLoop; +import fr.insee.eno.core.reference.EnoIndex; +import fr.insee.lunatic.model.flat.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class LunaticLoopResizingLogicTest { + + private Questionnaire lunaticQuestionnaire; + private EnoQuestionnaire enoQuestionnaire; + private EnoIndex enoIndex; + private Loop lunaticLoop; + private StandaloneLoop enoLoop; + + @BeforeEach + void resizingUnitTestsCanvas() { + // + lunaticQuestionnaire = new Questionnaire(); + // + lunaticLoop = new Loop(); + lunaticLoop.setId("loop-id"); + lunaticLoop.setLines(new LinesLoop()); + lunaticLoop.getLines().setMax(new LabelType()); + lunaticLoop.getLines().getMax().setValue("nvl(LOOP_SIZE_VAR, 1)"); + // + CheckboxBoolean simpleResponseComponent = new CheckboxBoolean(); + simpleResponseComponent.setComponentType(ComponentTypeEnum.CHECKBOX_BOOLEAN); + simpleResponseComponent.setResponse(new ResponseType()); + simpleResponseComponent.getResponse().setName("RESPONSE_VAR"); + lunaticLoop.getComponents().add(simpleResponseComponent); + // + VariableType numberVariable = new VariableType(); + numberVariable.setVariableType(VariableTypeEnum.COLLECTED); + numberVariable.setName("LOOP_SIZE_VAR"); + lunaticQuestionnaire.getVariables().add(numberVariable); + // + lunaticQuestionnaire.getComponents().add(lunaticLoop); + + // + enoQuestionnaire = new EnoQuestionnaire(); + enoLoop = new StandaloneLoop(); + enoLoop.setId("loop-id"); + enoLoop.setLoopIterations(new StandaloneLoop.LoopIterations()); + enoLoop.getLoopIterations().setMaxIteration(new CalculatedExpression()); + enoLoop.getLoopIterations().getMaxIteration().getBindingReferences().add( + new BindingReference("loop-size-var-ref", "LOOP_SIZE_VAR")); + + // + enoIndex = new EnoIndex(); + enoIndex.put("loop-id", enoLoop); + } + + @Test + void oneComponentInLoop_testResizingEntryNameAndSize() { + // When + LunaticLoopResizingLogic loopResizingLogic = new LunaticLoopResizingLogic( + lunaticQuestionnaire, enoQuestionnaire, enoIndex); + List resizingEntries = loopResizingLogic.buildResizingEntries(lunaticLoop); + + // Then + assertEquals(1, resizingEntries.size()); + assertEquals("LOOP_SIZE_VAR", resizingEntries.get(0).getName()); + assertEquals("nvl(LOOP_SIZE_VAR, 1)", resizingEntries.get(0).getSize()); + assertEquals(List.of("RESPONSE_VAR"), resizingEntries.get(0).getVariables()); + } + + @Test + void simpleResponseComponents_resizedVariables() { + // + lunaticLoop.getComponents().clear(); + // + CheckboxBoolean checkboxBoolean = new CheckboxBoolean(); + checkboxBoolean.setComponentType(ComponentTypeEnum.CHECKBOX_BOOLEAN); + checkboxBoolean.setResponse(new ResponseType()); + checkboxBoolean.getResponse().setName("BOOLEAN_VAR"); + lunaticLoop.getComponents().add(checkboxBoolean); + Input input = new Input(); + input.setComponentType(ComponentTypeEnum.INPUT); + input.setResponse(new ResponseType()); + input.getResponse().setName("INPUT_VAR"); + lunaticLoop.getComponents().add(input); + Textarea textarea = new Textarea(); + textarea.setComponentType(ComponentTypeEnum.TEXTAREA); + textarea.setResponse(new ResponseType()); + textarea.getResponse().setName("TEXT_VAR"); + lunaticLoop.getComponents().add(textarea); + InputNumber inputNumber = new InputNumber(); + inputNumber.setComponentType(ComponentTypeEnum.INPUT_NUMBER); + inputNumber.setResponse(new ResponseType()); + inputNumber.getResponse().setName("NUMBER_VAR"); + lunaticLoop.getComponents().add(inputNumber); + Datepicker datepicker = new Datepicker(); + datepicker.setComponentType(ComponentTypeEnum.DATEPICKER); + datepicker.setResponse(new ResponseType()); + datepicker.getResponse().setName("DATE_VAR"); + lunaticLoop.getComponents().add(datepicker); + Dropdown dropdown = new Dropdown(); + dropdown.setComponentType(ComponentTypeEnum.DROPDOWN); + dropdown.setResponse(new ResponseType()); + dropdown.getResponse().setName("DROPDOWN_VAR"); + lunaticLoop.getComponents().add(dropdown); + Radio radio = new Radio(); + radio.setComponentType(ComponentTypeEnum.RADIO); + radio.setResponse(new ResponseType()); + radio.getResponse().setName("RADIO_VAR"); + lunaticLoop.getComponents().add(radio); + CheckboxOne checkboxOne = new CheckboxOne(); + checkboxOne.setComponentType(ComponentTypeEnum.CHECKBOX_ONE); + checkboxOne.setResponse(new ResponseType()); + checkboxOne.getResponse().setName("CHECKBOX_VAR"); + lunaticLoop.getComponents().add(checkboxOne); + + // When + LunaticLoopResizingLogic loopResizingLogic = new LunaticLoopResizingLogic( + lunaticQuestionnaire, enoQuestionnaire, enoIndex); + List resizingEntries = loopResizingLogic.buildResizingEntries(lunaticLoop); + + // Then + assertThat(resizingEntries.get(0).getVariables()).containsExactlyInAnyOrderElementsOf( + List.of("BOOLEAN_VAR", "INPUT_VAR", "TEXT_VAR", "NUMBER_VAR", "DATE_VAR", + "DROPDOWN_VAR", "RADIO_VAR", "CHECKBOX_VAR")); + } + + @Test + void checkboxGroupComponent_resizedVariables() { + // + lunaticLoop.getComponents().clear(); + // + CheckboxGroup checkboxGroup = new CheckboxGroup(); + checkboxGroup.setComponentType(ComponentTypeEnum.CHECKBOX_GROUP); + checkboxGroup.getResponses().add(new ResponsesCheckboxGroup()); + checkboxGroup.getResponses().add(new ResponsesCheckboxGroup()); + checkboxGroup.getResponses().forEach(responses -> responses.setResponse(new ResponseType())); + checkboxGroup.getResponses().get(0).getResponse().setName("RESPONSE_VAR1"); + checkboxGroup.getResponses().get(1).getResponse().setName("RESPONSE_VAR2"); + lunaticLoop.getComponents().add(checkboxGroup); + + // When + LunaticLoopResizingLogic loopResizingLogic = new LunaticLoopResizingLogic( + lunaticQuestionnaire, enoQuestionnaire, enoIndex); + List resizingEntries = loopResizingLogic.buildResizingEntries(lunaticLoop); + + // Then + assertThat(resizingEntries.get(0).getVariables()).containsExactlyInAnyOrderElementsOf( + List.of("RESPONSE_VAR1", "RESPONSE_VAR2")); + } + + @Test + void tableComponent_resizedVariables() { + // + lunaticLoop.getComponents().clear(); + // + Table table = new Table(); + table.setComponentType(ComponentTypeEnum.TABLE); + table.getBodyLines().add(new BodyLine()); + table.getBodyLines().add(new BodyLine()); + table.getBodyLines().get(0).getBodyCells().add(new BodyCell()); + table.getBodyLines().get(0).getBodyCells().add(new BodyCell()); + table.getBodyLines().get(1).getBodyCells().add(new BodyCell()); + table.getBodyLines().get(1).getBodyCells().add(new BodyCell()); + table.getBodyLines().get(0).getBodyCells().forEach(bodyCell -> bodyCell.setResponse(new ResponseType())); + table.getBodyLines().get(1).getBodyCells().forEach(bodyCell -> bodyCell.setResponse(new ResponseType())); + table.getBodyLines().get(0).getBodyCells().get(0).getResponse().setName("CELL11"); + table.getBodyLines().get(0).getBodyCells().get(1).getResponse().setName("CELL12"); + table.getBodyLines().get(1).getBodyCells().get(0).getResponse().setName("CELL21"); + table.getBodyLines().get(1).getBodyCells().get(1).getResponse().setName("CELL22"); + lunaticLoop.getComponents().add(table); + + // When + LunaticLoopResizingLogic loopResizingLogic = new LunaticLoopResizingLogic( + lunaticQuestionnaire, enoQuestionnaire, enoIndex); + List resizingEntries = loopResizingLogic.buildResizingEntries(lunaticLoop); + + // Then + assertThat(resizingEntries.get(0).getVariables()).containsExactlyInAnyOrderElementsOf( + List.of("CELL11", "CELL12", "CELL21", "CELL22")); + } + + @Test + void twoVariablesInLoopExpression_twoResizingEntries() { + // Adding a second variable in max size expression + lunaticLoop.getLines().getMax().setValue("LOOP_SIZE_VAR + LOOP_SIZE_VAR2"); + // + VariableType numberVariable = new VariableType(); + numberVariable.setVariableType(VariableTypeEnum.COLLECTED); + numberVariable.setName("LOOP_SIZE_VAR2"); + lunaticQuestionnaire.getVariables().add(numberVariable); + + // + enoLoop.getLoopIterations().getMaxIteration().getBindingReferences().add( + new BindingReference("loop-size-var2-ref", "LOOP_SIZE_VAR2")); + + // When + LunaticLoopResizingLogic loopResizingLogic = new LunaticLoopResizingLogic( + lunaticQuestionnaire, enoQuestionnaire, enoIndex); + List resizingEntries = loopResizingLogic.buildResizingEntries(lunaticLoop); + + // Then + assertEquals(2, resizingEntries.size()); + // + assertThat(resizingEntries.stream().map(LunaticResizingEntry::getName).toList()) + .containsExactlyInAnyOrderElementsOf(List.of("LOOP_SIZE_VAR", "LOOP_SIZE_VAR2")); + // + resizingEntries.forEach(resizingEntry -> { + assertEquals("LOOP_SIZE_VAR + LOOP_SIZE_VAR2", resizingEntry.getSize()); + assertEquals(List.of("RESPONSE_VAR"), resizingEntry.getVariables()); + }); + } + + @Test + void loopFixedMaxSize_noResizingEntries() { + // + lunaticLoop.getLines().getMax().setValue("5"); + enoLoop.getLoopIterations().getMaxIteration().getBindingReferences().clear(); + // When + LunaticLoopResizingLogic loopResizingLogic = new LunaticLoopResizingLogic( + lunaticQuestionnaire, enoQuestionnaire, enoIndex); + List resizingEntries = loopResizingLogic.buildResizingEntries(lunaticLoop); + // Then + assertTrue(resizingEntries.isEmpty()); + } + + @Test + void loopFixedMaxSize_withExternal_noResizingEntries() { + // + lunaticLoop.getLines().getMax().setValue("count(EXTERNAL_VAR)"); + // + VariableType externalVariable = new VariableType(); + externalVariable.setVariableType(VariableTypeEnum.EXTERNAL); + externalVariable.setName("EXTERNAL_VAR"); + lunaticQuestionnaire.getVariables().add(externalVariable); + // + enoLoop.getLoopIterations().getMaxIteration().getBindingReferences().clear(); + enoLoop.getLoopIterations().getMaxIteration().getBindingReferences().add( + new BindingReference("external-ref", "EXTERNAL_VAR")); + // When + LunaticLoopResizingLogic loopResizingLogic = new LunaticLoopResizingLogic( + lunaticQuestionnaire, enoQuestionnaire, enoIndex); + List resizingEntries = loopResizingLogic.buildResizingEntries(lunaticLoop); + // Then + assertTrue(resizingEntries.isEmpty()); + } + + @Test + void externalVariableInLoopExpression_shouldHaveResizingEntriesForCollectedDependencies() { + // Adding an external variable + lunaticLoop.getLines().getMax().setValue("CALCULATED_VAR"); + // + lunaticQuestionnaire.getVariables().clear(); + VariableType calculatedVariable = new VariableType(); + calculatedVariable.setVariableType(VariableTypeEnum.CALCULATED); + calculatedVariable.setName("CALCULATED_VAR"); + lunaticQuestionnaire.getVariables().add(calculatedVariable); + VariableType collectedVariable = new VariableType(); + collectedVariable.setVariableType(VariableTypeEnum.COLLECTED); + collectedVariable.setName("COLLECTED_VAR"); + lunaticQuestionnaire.getVariables().add(collectedVariable); + VariableType externalVariable = new VariableType(); + externalVariable.setVariableType(VariableTypeEnum.EXTERNAL); + externalVariable.setName("EXTERNAL_VAR"); + lunaticQuestionnaire.getVariables().add(externalVariable); + + // + enoLoop.getLoopIterations().getMaxIteration().getBindingReferences().clear(); + enoLoop.getLoopIterations().getMaxIteration().getBindingReferences().add( + new BindingReference("collected-ref", "COLLECTED_VAR")); + enoLoop.getLoopIterations().getMaxIteration().getBindingReferences().add( + new BindingReference("external-ref", "EXTERNAL_VAR")); + + // When + LunaticLoopResizingLogic loopResizingLogic = new LunaticLoopResizingLogic( + lunaticQuestionnaire, enoQuestionnaire, enoIndex); + List resizingEntries = loopResizingLogic.buildResizingEntries(lunaticLoop); + + // Then + assertEquals(1, resizingEntries.size()); + assertEquals("COLLECTED_VAR", resizingEntries.get(0).getName()); + assertEquals("CALCULATED_VAR", resizingEntries.get(0).getSize()); + assertEquals(List.of("RESPONSE_VAR"), resizingEntries.get(0).getVariables()); + } + + @Test + void calculatedVariableInLoopExpression_shouldNotAddResizingEntries() { + // Adding an external variable + lunaticLoop.getLines().getMax().setValue("LOOP_SIZE_VAR + EXTERNAL_VAR"); + // + VariableType externalVariable = new VariableType(); + externalVariable.setVariableType(VariableTypeEnum.EXTERNAL); + externalVariable.setName("EXTERNAL_VAR"); + lunaticQuestionnaire.getVariables().add(externalVariable); + + // + enoLoop.getLoopIterations().getMaxIteration().getBindingReferences().add( + new BindingReference("external-ref", "EXTERNAL_VAR")); + + // When + LunaticLoopResizingLogic loopResizingLogic = new LunaticLoopResizingLogic( + lunaticQuestionnaire, enoQuestionnaire, enoIndex); + List resizingEntries = loopResizingLogic.buildResizingEntries(lunaticLoop); + + // Then + assertEquals(1, resizingEntries.size()); + assertEquals("LOOP_SIZE_VAR", resizingEntries.get(0).getName()); + assertEquals("LOOP_SIZE_VAR + EXTERNAL_VAR", resizingEntries.get(0).getSize()); + assertEquals(List.of("RESPONSE_VAR"), resizingEntries.get(0).getVariables()); + } + +} diff --git a/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticPairwiseResizingLogicTest.java b/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticPairwiseResizingLogicTest.java new file mode 100644 index 000000000..5ad0474c9 --- /dev/null +++ b/eno-core/src/test/java/fr/insee/eno/core/processing/out/steps/lunatic/resizing/LunaticPairwiseResizingLogicTest.java @@ -0,0 +1,60 @@ +package fr.insee.eno.core.processing.out.steps.lunatic.resizing; + +import fr.insee.eno.core.model.lunatic.LunaticResizingPairwiseEntry; +import fr.insee.eno.core.model.question.PairwiseQuestion; +import fr.insee.eno.core.reference.EnoIndex; +import fr.insee.lunatic.model.flat.*; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class LunaticPairwiseResizingLogicTest { + + @Test + void pairwiseResizingTest() { + // + Questionnaire lunaticQuestionnaire = new Questionnaire(); + // + PairwiseLinks lunaticPairwise = new PairwiseLinks(); + lunaticPairwise.setId("pairwise-id"); + lunaticPairwise.setXAxisIterations(new LabelType()); + lunaticPairwise.getXAxisIterations().setValue("count(LOOP_VAR)"); + lunaticPairwise.setYAxisIterations(new LabelType()); + lunaticPairwise.getYAxisIterations().setValue("count(LOOP_VAR)"); + Dropdown dropdown = new Dropdown(); + dropdown.setComponentType(ComponentTypeEnum.DROPDOWN); + dropdown.setResponse(new ResponseType()); + dropdown.getResponse().setName("LINKS_VAR"); + lunaticPairwise.getComponents().add(dropdown); + lunaticQuestionnaire.getComponents().add(lunaticPairwise); + // + VariableType loopVariable = new VariableType(); + loopVariable.setVariableType(VariableTypeEnum.COLLECTED); + loopVariable.setName("LOOP_VAR"); + lunaticQuestionnaire.getVariables().add(loopVariable); + + // + PairwiseQuestion enoPairwise = new PairwiseQuestion(); + enoPairwise.setId("pairwise-id"); + enoPairwise.setLoopVariableName("LOOP_VAR"); + + // + EnoIndex enoIndex = new EnoIndex(); + enoIndex.put("pairwise-id", enoPairwise); + + // When + LunaticPairwiseResizingLogic pairwiseResizingLogic = new LunaticPairwiseResizingLogic( + lunaticQuestionnaire, enoIndex); + List pairwiseResizingEntries = pairwiseResizingLogic + .buildPairwiseResizingEntries(lunaticPairwise); + + // Test + assertEquals(1, pairwiseResizingEntries.size()); + assertEquals("LOOP_VAR", pairwiseResizingEntries.get(0).getName()); + assertEquals(List.of("count(LOOP_VAR)", "count(LOOP_VAR)"), pairwiseResizingEntries.get(0).getSizeForLinksVariables()); + assertEquals(List.of("LINKS_VAR"), pairwiseResizingEntries.get(0).getLinksVariables()); + } + +} diff --git a/eno-core/src/test/java/fr/insee/eno/core/serialize/LunaticSerializerResizingTest.java b/eno-core/src/test/java/fr/insee/eno/core/serialize/LunaticSerializerResizingTest.java index c8475d16a..9c1190659 100644 --- a/eno-core/src/test/java/fr/insee/eno/core/serialize/LunaticSerializerResizingTest.java +++ b/eno-core/src/test/java/fr/insee/eno/core/serialize/LunaticSerializerResizingTest.java @@ -1,8 +1,8 @@ package fr.insee.eno.core.serialize; import fr.insee.eno.core.exceptions.business.LunaticSerializationException; -import fr.insee.eno.core.model.lunatic.LunaticResizingLoopVariable; -import fr.insee.eno.core.model.lunatic.LunaticResizingPairWiseVariable; +import fr.insee.eno.core.model.lunatic.LunaticResizingEntry; +import fr.insee.eno.core.model.lunatic.LunaticResizingPairwiseEntry; import fr.insee.lunatic.model.flat.Questionnaire; import fr.insee.lunatic.model.flat.ResizingType; import org.json.JSONException; @@ -23,10 +23,10 @@ void init() { lunaticQuestionnaire.setResizing(resizingType); List data = resizingType.getAny(); - data.add(new LunaticResizingLoopVariable("loopVariable", "count(NB)", List.of("NB"))); - data.add(new LunaticResizingLoopVariable("loopVariable1", "count(NB1,PRENOM1)", List.of("NB1", "PRENOM1"))); - data.add(new LunaticResizingPairWiseVariable("pairwise", List.of("count(NBP)", "count(PRENOMP)"), List.of("NBP", "PRENOMP"))); - data.add(new LunaticResizingPairWiseVariable("pairwise1", List.of("count(NBP1)", "count(PRENOMP1)"), List.of("NBP1", "PRENOMP1"))); + data.add(new LunaticResizingEntry("loopVariable", "count(NB)", List.of("NB"))); + data.add(new LunaticResizingEntry("loopVariable1", "count(NB1,PRENOM1)", List.of("NB1", "PRENOM1"))); + data.add(new LunaticResizingPairwiseEntry("pairwise", List.of("count(NBP)", "count(PRENOMP)"), List.of("NBP", "PRENOMP"))); + data.add(new LunaticResizingPairwiseEntry("pairwise1", List.of("count(NBP1)", "count(PRENOMP1)"), List.of("NBP1", "PRENOMP1"))); } @Test diff --git a/eno-core/src/test/resources/integration/ddi/ddi-resizing.xml b/eno-core/src/test/resources/integration/ddi/ddi-resizing.xml new file mode 100644 index 000000000..80ec4b41d --- /dev/null +++ b/eno-core/src/test/resources/integration/ddi/ddi-resizing.xml @@ -0,0 +1,1212 @@ + + + fr.insee + INSEE-lmeyzqxr + 1 + + + Eno - Resizing + + + + fr.insee + RessourcePackage-lmeyzqxr + 1 + + fr.insee + InterviewerInstructionScheme-lmeyzqxr + 1 + + A définir + + + + fr.insee + ControlConstructScheme-lmeyzqxr + 1 + + fr.insee + Sequence-lmeyzqxr + 1 + + Eno - Resizing + + template + + fr.insee + lmezfil0 + 1 + Sequence + + + fr.insee + lmezf1ds + 1 + Loop + + + fr.insee + lmezmqi8 + 1 + Loop + + + fr.insee + lmgeljoy + 1 + Sequence + + + + fr.insee + lmezfil0 + 1 + + S1 + + + "First sequence" + + module + + fr.insee + lmezc4x9-QC + 1 + QuestionConstruct + + + + fr.insee + lmez63il + 1 + + S2 + + + "Sequence with loop" + + module + + fr.insee + lmriwa1y-QC + 1 + QuestionConstruct + + + fr.insee + lmezduli-QC + 1 + QuestionConstruct + + + + fr.insee + lmezflon + 1 + + S3 + + + "Séquence avec boucle liée" + + module + + fr.insee + lmezp3nz-QC + 1 + QuestionConstruct + + + + fr.insee + lmgeljoy + 1 + + S4 + + + "Sequence pairwise links" + + module + + fr.insee + lmgen8zt-QC + 1 + QuestionConstruct + + + + fr.insee + lmezf1ds + 1 + + LOOP + + + + vtl + 1 + + + + + vtl + + fr.insee + lmezf1ds-IP-1 + 1 + + NUMBER + + + + + fr.insee + lmezc4x9-QOP-lmf3zaup + 1 + OutParameter + + + fr.insee + lmezf1ds-IP-1 + 1 + InParameter + + + nvl(lmezf1ds-IP-1, 1) + + + + + vtl + 1 + + + + fr.insee + lmezf1ds-SEQ + 1 + Sequence + + + + fr.insee + lmezf1ds-SEQ + 1 + + fr.insee + lmez63il + 1 + Sequence + + + + fr.insee + lmezmqi8 + 1 + + LINKED_LOOP + + + fr.insee + lmezmqi8-SEQ + 1 + Sequence + + + + fr.insee + lmezmqi8-SEQ + 1 + + fr.insee + lmezflon + 1 + Sequence + + + + fr.insee + lmezc4x9-QC + 1 + + NUMBER + + + fr.insee + lmezc4x9 + 1 + QuestionItem + + + + fr.insee + lmriwa1y-QC + 1 + + Q2 + + + fr.insee + lmriwa1y + 1 + QuestionItem + + + + fr.insee + lmezduli-QC + 1 + + PAIRWISE_SOURCE + + + fr.insee + lmezduli + 1 + QuestionItem + + + + fr.insee + lmezp3nz-QC + 1 + + Q3 + + + fr.insee + lmezp3nz + 1 + QuestionItem + + + + fr.insee + lmgen8zt-QC + 1 + + LINKS + + + fr.insee + lmgen8zt + 1 + QuestionItem + + + + + fr.insee + QuestionScheme-lmeyzqxr + 1 + + A définir + + + fr.insee + lmezc4x9 + 1 + + NUMBER + + + fr.insee + lmezc4x9-QOP-lmf3zaup + 1 + + NUMBER + + + + + fr.insee + lmezc4x9-RDOP-lmf3zaup + 1 + OutParameter + + + fr.insee + lmezc4x9-QOP-lmf3zaup + 1 + OutParameter + + + + + "Question that determines loops size" + + + + + 2 + 10 + + Decimal + + fr.insee + lmezc4x9-RDOP-lmf3zaup + 1 + + + + + fr.insee + lmriwa1y + 1 + + Q2 + + + fr.insee + lmriwa1y-QOP-lmrvzzlr + 1 + + Q2 + + + + + fr.insee + lmriwa1y-RDOP-lmrvzzlr + 1 + OutParameter + + + fr.insee + lmriwa1y-QOP-lmrvzzlr + 1 + OutParameter + + + + + "Question in sequence 2" + + + + + fr.insee + lmriwa1y-RDOP-lmrvzzlr + 1 + + + + + + fr.insee + lmezduli + 1 + + PAIRWISE_SOURCE + + + fr.insee + lmezduli-QOP-lmf46fuf + 1 + + PAIRWISE_SOURCE + + + + + fr.insee + lmezduli-RDOP-lmf46fuf + 1 + OutParameter + + + fr.insee + lmezduli-QOP-lmf46fuf + 1 + OutParameter + + + + + "Question that is the input of the pairwise question" + + + + + fr.insee + lmezduli-RDOP-lmf46fuf + 1 + + + + + + fr.insee + lmezp3nz + 1 + + Q3 + + + fr.insee + lmezp3nz-QOP-lmf4a46s + 1 + + Q3 + + + + + fr.insee + lmezp3nz-RDOP-lmf4a46s + 1 + OutParameter + + + fr.insee + lmezp3nz-QOP-lmf4a46s + 1 + OutParameter + + + + + "Question in sequence 3" + + + + + fr.insee + lmezp3nz-RDOP-lmf4a46s + 1 + + + + + + fr.insee + lmgen8zt + 1 + + UIComponent + HouseholdPairing + + + LINKS + + + fr.insee + lmgen8zt-IP-1 + 1 + + PAIRWISE_SOURCE + + + + fr.insee + lmgen8zt-QOP-lmgf25ji + 1 + + LINKS + + + + + fr.insee + lmgen8zt-IP-1 + 1 + OutParameter + + + fr.insee + lmezduli-QOP-lmf46fuf + 1 + InParameter + + + + + fr.insee + lmgen8zt-RDOP-lmgf25ji + 1 + OutParameter + + + fr.insee + lmgen8zt-QOP-lmgf25ji + 1 + OutParameter + + + + + "Pairwise links question" + + + + drop-down-list + + fr.insee + lmgeb1uh + 1 + CodeList + + + fr.insee + lmgen8zt-RDOP-lmgf25ji + 1 + + + fr.insee + lmgeb1uh + 1 + CodeList + + + + + + + + + fr.insee + CategoryScheme-lmgeb1uh + 1 + + LINKS_LIST + + + fr.insee + CA-lmgeb1uh-1 + 1 + + "Link of type A" + + + + fr.insee + CA-lmgeb1uh-2 + 1 + + "Link of type B" + + + + fr.insee + CA-lmgeb1uh-3 + 1 + + "Link of type C" + + + + + fr.insee + CategoryScheme-lmeyzqxr + 1 + + A définir + + + fr.insee + INSEE-COMMUN-CA-Booleen-1 + 1 + + + + + + + fr.insee + ENORESIZING-CLS + 1 + + ENORESIZING + + + fr.insee + lmgeb1uh + 1 + + LINKS_LIST + + Regular + + Ordinal + + + fr.insee + lmgeb1uh-1 + 1 + + fr.insee + CA-lmgeb1uh-1 + 1 + Category + + LINK_A + + + fr.insee + lmgeb1uh-2 + 1 + + fr.insee + CA-lmgeb1uh-2 + 1 + Category + + LINK_B + + + fr.insee + lmgeb1uh-3 + 1 + + fr.insee + CA-lmgeb1uh-3 + 1 + Category + + LINK_C + + + + fr.insee + INSEE-COMMUN-CL-Booleen + 1 + + Booleen + + Regular + + Ordinal + + + fr.insee + INSEE-COMMUN-CL-Booleen-1 + 1 + + fr.insee + INSEE-COMMUN-CA-Booleen-1 + 1 + Category + + 1 + + + + + fr.insee + VariableScheme-lmeyzqxr + 1 + + Variable Scheme for the survey + + + fr.insee + lmezvjtr + 1 + + CALCULATED1 + + + "Calculated using main loop variable" + + + fr.insee + lmezvjtr-VROP + 1 + + + + fr.insee + lmezvjtr-GI + 1 + GenerationInstruction + + + fr.insee + lmezvjtr-GOP + 1 + OutParameter + + + fr.insee + lmezvjtr-VROP + 1 + OutParameter + + + + + + + + fr.insee + lmf0pvfh + 1 + + CALCULATED2 + + + "Calculated using linked loop variable" + + + fr.insee + lmf0pvfh-VROP + 1 + + + + fr.insee + lmf0pvfh-GI + 1 + GenerationInstruction + + + fr.insee + lmf0pvfh-GOP + 1 + OutParameter + + + fr.insee + lmf0pvfh-VROP + 1 + OutParameter + + + + + + + + fr.insee + lmt9ljtn + 1 + + NUMBER + + + NUMBER label + + + fr.insee + lmezc4x9-QOP-lmf3zaup + 1 + OutParameter + + + fr.insee + lmezc4x9 + 1 + QuestionItem + + + + + 2 + 10 + + Decimal + + + + + fr.insee + lmt9waph + 1 + + Q2 + + + Q2 label + + + fr.insee + lmriwa1y-QOP-lmrvzzlr + 1 + OutParameter + + + fr.insee + lmriwa1y + 1 + QuestionItem + + + + + + + fr.insee + lmt9xzdm + 1 + + PAIRWISE_SOURCE + + + PAIRWISE_SOURCE label + + + fr.insee + lmezduli-QOP-lmf46fuf + 1 + OutParameter + + + fr.insee + lmezduli + 1 + QuestionItem + + + + + + + fr.insee + lmez8o56 + 1 + + Q3 + + + Q3 label + + + fr.insee + lmezp3nz-QOP-lmf4a46s + 1 + OutParameter + + + fr.insee + lmezp3nz + 1 + QuestionItem + + + + + + + fr.insee + lmt9r0x0 + 1 + + LINKS + + + LINKS label + + + fr.insee + lmgen8zt-QOP-lmgf25ji + 1 + OutParameter + + + fr.insee + lmgen8zt + 1 + QuestionItem + + + + + fr.insee + lmgeb1uh + 1 + CodeList + + + + + + fr.insee + lmezf1ds-vg + 1 + + + fr.insee + lmezf1ds + 1 + Loop + + + fr.insee + lmezmqi8 + 1 + Loop + + + Loop + + LOOP + + + fr.insee + lmezvjtr + 1 + Variable + + + fr.insee + lmf0pvfh + 1 + Variable + + + fr.insee + lmt9waph + 1 + Variable + + + fr.insee + lmt9xzdm + 1 + Variable + + + fr.insee + lmez8o56 + 1 + Variable + + + + fr.insee + INSEE-Instrument-lmeyzqxr-vg + 1 + + + fr.insee + Instrument-lmeyzqxr + 1 + Instrument + + + Questionnaire + + ENORESIZING + + + fr.insee + lmt9ljtn + 1 + Variable + + + fr.insee + lmt9r0x0 + 1 + Variable + + + fr.insee + lmezf1ds-vg + 1 + VariableGroup + + + + + fr.insee + INSEE-SIMPSONS-PIS-1 + 1 + + SIMPSONS + + + Processing instructions of the Simpsons questionnaire + + + fr.insee + lmezvjtr-GI + 1 + + fr.insee + lmriwa1y + 1 + QuestionItem + + + fr.insee + lmt9waph + 1 + Variable + + + + vtl + + fr.insee + lmezvjtr-IP-1 + 1 + + Q2 + + + + fr.insee + lmezvjtr-GOP + 1 + + + + fr.insee + lmriwa1y-QOP-lmrvzzlr + 1 + OutParameter + + + fr.insee + lmezvjtr-IP-1 + 1 + InParameter + + + nvl(lmezvjtr-IP-1, "") || " " || "FOO" + + + + fr.insee + lmezf1ds + 1 + Loop + + + + fr.insee + lmf0pvfh-GI + 1 + + fr.insee + lmezp3nz + 1 + QuestionItem + + + fr.insee + lmez8o56 + 1 + Variable + + + + vtl + + fr.insee + lmf0pvfh-IP-1 + 1 + + Q3 + + + + fr.insee + lmf0pvfh-GOP + 1 + + + + fr.insee + lmezp3nz-QOP-lmf4a46s + 1 + OutParameter + + + fr.insee + lmf0pvfh-IP-1 + 1 + InParameter + + + nvl(lmf0pvfh-IP-1, "") || " " || "BAR" + + + + fr.insee + lmezf1ds + 1 + Loop + + + + + fr.insee + INSEE-SIMPSONS-MRS + 1 + + Liste de formats numériques et dates de + l'enquête + Numeric and DateTime list for the survey + + + + + fr.insee + StudyUnit-lmeyzqxr + 1 + + + fr.insee + DataCollection-lmeyzqxr + 1 + + fr.insee + QuestionScheme-lmeyzqxr + 1 + QuestionScheme + + + fr.insee + ControlConstructScheme-lmeyzqxr + 1 + ControlConstructScheme + + + fr.insee + InterviewerInstructionScheme-lmeyzqxr + 1 + InterviewerInstructionScheme + + + fr.insee + InstrumentScheme-lmeyzqxr + 1 + + fr.insee + Instrument-lmeyzqxr + 1 + + ENORESIZING + + + Eno - Resizing questionnaire + + A définir + + fr.insee + Sequence-lmeyzqxr + 1 + Sequence + + + + + + diff --git a/eno-core/src/test/resources/integration/lunatic/lunatic-resizing.json b/eno-core/src/test/resources/integration/lunatic/lunatic-resizing.json new file mode 100644 index 000000000..434d67b4b --- /dev/null +++ b/eno-core/src/test/resources/integration/lunatic/lunatic-resizing.json @@ -0,0 +1,609 @@ +{ + "id": "lmeyzqxr", + "modele": "ENORESIZING", + "enoCoreVersion": "2.4.9", + "lunaticModelVersion": "2.3.4", + "generatingDate": "21-09-2023 15:57:44", + "missing": false, + "pagination": "question", + "maxPage": "6", + "label": { + "value": "Eno - Resizing", + "type": "VTL|MD" + }, + "components": [ + { + "id": "lmezfil0", + "componentType": "Sequence", + "page": "1", + "label": { + "value": "\"I - \" || \"First sequence\"", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "hierarchy": { + "sequence": { + "id": "lmezfil0", + "page": "1", + "label": { + "value": "\"I - \" || \"First sequence\"", + "type": "VTL|MD" + } + } + } + }, + { + "id": "lmezc4x9", + "componentType": "InputNumber", + "mandatory": false, + "page": "2", + "min": 2, + "max": 10, + "decimals": 0, + "label": { + "value": "\"➡ \" || \"Question that determines loops size\"", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "controls": [ + { + "id": "lmezc4x9-format-borne-inf-sup", + "typeOfControl": "FORMAT", + "criticality": "ERROR", + "control": { + "value": "not(not(isnull(NUMBER)) and (2>NUMBER or 10NUMBER)", + "type": "VTL" + }, + "errorMessage": { + "value": "\"Le nombre doit comporter au maximum 0 chiffre(s) après la virgule.\"", + "type": "VTL|MD" + } + } + ], + "hierarchy": { + "sequence": { + "id": "lmezfil0", + "page": "1", + "label": { + "value": "\"I - \" || \"First sequence\"", + "type": "VTL|MD" + } + } + }, + "bindingDependencies": [ + "NUMBER" + ], + "response": { + "name": "NUMBER" + } + }, + { + "id": "lmezf1ds", + "componentType": "Loop", + "page": "3", + "depth": 1, + "paginatedLoop": false, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "bindingDependencies": [ + "NUMBER", + "Q2", + "PAIRWISE_SOURCE" + ], + "loopDependencies": [ + "NUMBER" + ], + "lines": { + "min": { + "value": "1", + "type": "VTL" + }, + "max": { + "value": "nvl(NUMBER, 1)", + "type": "VTL" + } + }, + "components": [ + { + "id": "lmez63il", + "componentType": "Sequence", + "page": "3", + "label": { + "value": "\"II - \" || \"Sequence with loop\"", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "hierarchy": { + "sequence": { + "id": "lmez63il", + "page": "3", + "label": { + "value": "\"II - \" || \"Sequence with loop\"", + "type": "VTL|MD" + } + } + }, + "bindingDependencies": [ + "NUMBER" + ] + }, + { + "id": "lmriwa1y", + "componentType": "Input", + "mandatory": false, + "page": "3", + "maxLength": 249, + "label": { + "value": "\"➡ \" || \"Question in sequence 2\"", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "hierarchy": { + "sequence": { + "id": "lmez63il", + "page": "3", + "label": { + "value": "\"II - \" || \"Sequence with loop\"", + "type": "VTL|MD" + } + } + }, + "bindingDependencies": [ + "Q2", + "NUMBER" + ], + "response": { + "name": "Q2" + } + }, + { + "id": "lmezduli", + "componentType": "Input", + "mandatory": false, + "page": "3", + "maxLength": 20, + "label": { + "value": "\"➡ \" || \"Question that is the input of the pairwise question\"", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "hierarchy": { + "sequence": { + "id": "lmez63il", + "page": "3", + "label": { + "value": "\"II - \" || \"Sequence with loop\"", + "type": "VTL|MD" + } + } + }, + "bindingDependencies": [ + "PAIRWISE_SOURCE", + "NUMBER" + ], + "response": { + "name": "PAIRWISE_SOURCE" + } + } + ] + }, + { + "id": "lmezmqi8", + "componentType": "Loop", + "page": "4", + "maxPage": "2", + "depth": 1, + "paginatedLoop": true, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "bindingDependencies": [ + "Q3" + ], + "loopDependencies": [ + "Q2", + "PAIRWISE_SOURCE" + ], + "components": [ + { + "id": "lmezflon", + "componentType": "Sequence", + "page": "4.1", + "label": { + "value": "\"III - \" || \"Séquence avec boucle liée\"", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "hierarchy": { + "sequence": { + "id": "lmezflon", + "page": "4.1", + "label": { + "value": "\"III - \" || \"Séquence avec boucle liée\"", + "type": "VTL|MD" + } + } + }, + "bindingDependencies": [ + "Q2", + "PAIRWISE_SOURCE" + ] + }, + { + "id": "lmezp3nz", + "componentType": "Input", + "mandatory": false, + "page": "4.2", + "maxLength": 249, + "label": { + "value": "\"➡ \" || \"Question in sequence 3\"", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "hierarchy": { + "sequence": { + "id": "lmezflon", + "page": "4.1", + "label": { + "value": "\"III - \" || \"Séquence avec boucle liée\"", + "type": "VTL|MD" + } + } + }, + "bindingDependencies": [ + "Q3", + "Q2", + "PAIRWISE_SOURCE" + ], + "response": { + "name": "Q3" + } + } + ], + "iterations": { + "value": "count(Q2)", + "type": "VTL" + } + }, + { + "id": "lmgeljoy", + "componentType": "Sequence", + "page": "5", + "label": { + "value": "\"IV - \" || \"Sequence pairwise links\"", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "hierarchy": { + "sequence": { + "id": "lmgeljoy", + "page": "5", + "label": { + "value": "\"IV - \" || \"Sequence pairwise links\"", + "type": "VTL|MD" + } + } + } + }, + { + "id": "lmgen8zt", + "componentType": "PairwiseLinks", + "mandatory": false, + "page": "6", + "conditionFilter": { + "value": "true", + "type": "VTL" + }, + "hierarchy": { + "sequence": { + "id": "lmgeljoy", + "page": "5", + "label": { + "value": "\"IV - \" || \"Sequence pairwise links\"", + "type": "VTL|MD" + } + } + }, + "bindingDependencies": [ + "LINKS" + ], + "xAxisIterations": { + "value": "count(PAIRWISE_SOURCE)" + }, + "yAxisIterations": { + "value": "count(PAIRWISE_SOURCE)" + }, + "components": [ + { + "id": "lmgen8zt-pairwise-dropdown", + "componentType": "Dropdown", + "mandatory": false, + "page": "6", + "label": { + "value": "\"➡ \" || \"Pairwise links question\"", + "type": "VTL|MD" + }, + "conditionFilter": { + "value": "xAxis <> yAxis", + "type": "VTL" + }, + "bindingDependencies": [ + "LINKS" + ], + "options": [ + { + "value": "LINK_A", + "label": { + "value": "\"Link of type A\"", + "type": "VTL|MD" + } + }, + { + "value": "LINK_B", + "label": { + "value": "\"Link of type B\"", + "type": "VTL|MD" + } + }, + { + "value": "LINK_C", + "label": { + "value": "\"Link of type C\"", + "type": "VTL|MD" + } + } + ], + "response": { + "name": "LINKS" + } + } + ], + "symLinks": { + "LINKS": { + "1": "1", + "2": "3", + "3": "2", + "4": "4", + "5": "6", + "6": "5", + "7": "8", + "8": "7", + "9": "10", + "10": "9", + "11": "13", + "12": "12", + "13": "11", + "14": null, + "15": null, + "16": "16", + "17": "17", + "18": "18" + } + } + } + ], + "variables": [ + { + "variableType": "COLLECTED", + "name": "NUMBER", + "values": { + "PREVIOUS": null, + "COLLECTED": null, + "FORCED": null, + "EDITED": null, + "INPUTED": null + } + }, + { + "variableType": "COLLECTED", + "name": "Q2", + "values": { + "PREVIOUS": [ + null + ], + "COLLECTED": [ + null + ], + "FORCED": [ + null + ], + "EDITED": [ + null + ], + "INPUTED": [ + null + ] + } + }, + { + "variableType": "COLLECTED", + "name": "PAIRWISE_SOURCE", + "values": { + "PREVIOUS": [ + null + ], + "COLLECTED": [ + null + ], + "FORCED": [ + null + ], + "EDITED": [ + null + ], + "INPUTED": [ + null + ] + } + }, + { + "variableType": "COLLECTED", + "name": "Q3", + "values": { + "PREVIOUS": [ + null + ], + "COLLECTED": [ + null + ], + "FORCED": [ + null + ], + "EDITED": [ + null + ], + "INPUTED": [ + null + ] + } + }, + { + "variableType": "COLLECTED", + "name": "LINKS", + "values": { + "PREVIOUS": [ + [ + null + ] + ], + "COLLECTED": [ + [ + null + ] + ], + "FORCED": [ + [ + null + ] + ], + "EDITED": [ + [ + null + ] + ], + "INPUTED": [ + [ + null + ] + ] + } + }, + { + "variableType": "CALCULATED", + "name": "CALCULATED1", + "expression": { + "value": "nvl(Q2, \"\") || \" \" || \"FOO\"", + "type": "VTL" + }, + "bindingDependencies": [ + "Q2" + ], + "shapeFrom": "Q2", + "inFilter": "false" + }, + { + "variableType": "CALCULATED", + "name": "CALCULATED2", + "expression": { + "value": "nvl(Q3, \"\") || \" \" || \"BAR\"", + "type": "VTL" + }, + "bindingDependencies": [ + "Q3" + ], + "shapeFrom": "Q2", + "inFilter": "false" + }, + { + "variableType": "CALCULATED", + "name": "xAxis", + "expression": { + "value": "PAIRWISE_SOURCE", + "type": "VTL" + }, + "bindingDependencies": [ + "PAIRWISE_SOURCE" + ], + "shapeFrom": "PAIRWISE_SOURCE", + "inFilter": "true" + }, + { + "variableType": "CALCULATED", + "name": "yAxis", + "expression": { + "value": "PAIRWISE_SOURCE", + "type": "VTL" + }, + "bindingDependencies": [ + "PAIRWISE_SOURCE" + ], + "shapeFrom": "PAIRWISE_SOURCE", + "inFilter": "true" + } + ], + "cleaning": {}, + "resizing": { + "NUMBER": { + "size": "nvl(NUMBER, 1)", + "variables": [ + "Q2", + "PAIRWISE_SOURCE" + ] + }, + "Q2": { + "size": "count(Q2)", + "variables": [ + "Q3" + ] + }, + "PAIRWISE_SOURCE": { + "sizeForLinksVariables": [ + "count(PAIRWISE_SOURCE)", + "count(PAIRWISE_SOURCE)" + ], + "linksVariables": [ + "LINKS" + ] + } + } +} \ No newline at end of file diff --git a/eno-core/src/test/resources/integration/pogues/pogues-resizing.json b/eno-core/src/test/resources/integration/pogues/pogues-resizing.json new file mode 100644 index 000000000..ba2510179 --- /dev/null +++ b/eno-core/src/test/resources/integration/pogues/pogues-resizing.json @@ -0,0 +1,470 @@ +{ + "owner": "FAKEPERMISSION", + "FlowControl": [], + "ComponentGroup": [ + { + "MemberReference": [ + "lmezfil0", + "lmezc4x9", + "lmez63il", + "lmriwa1y", + "lmezduli", + "lmezflon", + "lmezp3nz", + "lmgeljoy", + "lmgen8zt", + "idendquest" + ], + "Label": [ + "Components for page 1" + ], + "id": "lmf42v0u", + "Name": "PAGE_1" + } + ], + "agency": "fr.insee", + "genericName": "QUESTIONNAIRE", + "Label": [ + "Eno - Resizing" + ], + "childQuestionnaireRef": [], + "Name": "ENORESIZING", + "Variables": { + "Variable": [ + { + "Formula": "nvl($Q2$, \"\") || \" \" || \"FOO\"", + "Scope": "lmezf1ds", + "Label": "\"Calculated using main loop variable\"", + "id": "lmezvjtr", + "type": "CalculatedVariableType", + "Name": "CALCULATED1", + "Datatype": { + "Pattern": "", + "typeName": "TEXT", + "type": "TextDatatypeType", + "MaxLength": 249 + } + }, + { + "Formula": "nvl($Q3$, \"\") || \" \" || \"BAR\"", + "Scope": "lmezf1ds", + "Label": "\"Calculated using linked loop variable\"", + "id": "lmf0pvfh", + "type": "CalculatedVariableType", + "Name": "CALCULATED2", + "Datatype": { + "Pattern": "", + "typeName": "TEXT", + "type": "TextDatatypeType", + "MaxLength": 249 + } + }, + { + "Label": "NUMBER label", + "id": "lmt9ljtn", + "type": "CollectedVariableType", + "Name": "NUMBER", + "Datatype": { + "Maximum": "10", + "Minimum": "2", + "typeName": "NUMERIC", + "Unit": "", + "type": "NumericDatatypeType", + "Decimals": "0" + } + }, + { + "Scope": "lmezf1ds", + "Label": "Q2 label", + "id": "lmt9waph", + "type": "CollectedVariableType", + "Name": "Q2", + "Datatype": { + "Pattern": "", + "typeName": "TEXT", + "type": "TextDatatypeType", + "MaxLength": 249 + } + }, + { + "Scope": "lmezf1ds", + "Label": "PAIRWISE_SOURCE label", + "id": "lmt9xzdm", + "type": "CollectedVariableType", + "Name": "PAIRWISE_SOURCE", + "Datatype": { + "Pattern": "", + "typeName": "TEXT", + "type": "TextDatatypeType", + "MaxLength": "20" + } + }, + { + "Scope": "lmezf1ds", + "Label": "Q3 label", + "id": "lmez8o56", + "type": "CollectedVariableType", + "Name": "Q3", + "Datatype": { + "Pattern": "", + "typeName": "TEXT", + "type": "TextDatatypeType", + "MaxLength": 249 + } + }, + { + "Label": "LINKS label", + "id": "lmt9r0x0", + "type": "CollectedVariableType", + "CodeListReference": "lmgeb1uh", + "Name": "LINKS", + "Datatype": { + "Pattern": "", + "typeName": "TEXT", + "type": "TextDatatypeType", + "MaxLength": 1 + } + } + ] + }, + "lastUpdatedDate": "Thu Sep 21 2023 17:54:10 GMT+0200 (heure d’été d’Europe centrale)", + "DataCollection": [ + { + "id": "TCM", + "uri": "http://ddi:fr.insee:DataCollection.TCM" + } + ], + "final": false, + "flowLogic": "FILTER", + "id": "lmeyzqxr", + "TargetMode": [ + "CAPI", + "CATI", + "CAWI", + "PAPI" + ], + "CodeLists": { + "CodeList": [ + { + "Label": "LINKS_LIST", + "id": "lmgeb1uh", + "Code": [ + { + "Parent": "", + "Label": "\"Link of type A\"", + "Value": "LINK_A" + }, + { + "Parent": "", + "Label": "\"Link of type B\"", + "Value": "LINK_B" + }, + { + "Parent": "", + "Label": "\"Link of type C\"", + "Value": "LINK_C" + } + ], + "Name": "" + } + ] + }, + "Iterations": { + "Iteration": [ + { + "Maximum": "nvl($NUMBER$, 1)", + "Minimum": "1", + "MemberReference": [ + "lmez63il", + "lmez63il" + ], + "id": "lmezf1ds", + "Step": "1", + "type": "DynamicIterationType", + "Name": "LOOP" + }, + { + "MemberReference": [ + "lmezflon", + "lmezflon" + ], + "id": "lmezmqi8", + "type": "DynamicIterationType", + "Name": "LINKED_LOOP", + "IterableReference": "lmezf1ds" + } + ] + }, + "formulasLanguage": "VTL", + "Child": [ + { + "Control": [], + "depth": 1, + "FlowControl": [], + "genericName": "MODULE", + "Label": [ + "\"First sequence\"" + ], + "id": "lmezfil0", + "TargetMode": [ + "CAPI", + "CATI", + "CAWI", + "PAPI" + ], + "Declaration": [], + "type": "SequenceType", + "Child": [ + { + "Response": [ + { + "CollectedVariableReference": "lmt9ljtn", + "id": "lmf3zaup", + "mandatory": false, + "Datatype": { + "Maximum": "10", + "Minimum": "2", + "typeName": "NUMERIC", + "Unit": "", + "type": "NumericDatatypeType", + "Decimals": "0" + } + } + ], + "Control": [], + "depth": 2, + "FlowControl": [], + "Label": [ + "\"Question that determines loops size\"" + ], + "id": "lmezc4x9", + "TargetMode": [ + "CAPI", + "CATI", + "CAWI", + "PAPI" + ], + "Declaration": [], + "type": "QuestionType", + "questionType": "SIMPLE", + "Name": "NUMBER" + } + ], + "Name": "S1" + }, + { + "Control": [], + "depth": 1, + "FlowControl": [], + "genericName": "MODULE", + "Label": [ + "\"Sequence with loop\"" + ], + "id": "lmez63il", + "TargetMode": [ + "CAPI", + "CATI", + "CAWI", + "PAPI" + ], + "Declaration": [], + "type": "SequenceType", + "Child": [ + { + "Response": [ + { + "CollectedVariableReference": "lmt9waph", + "id": "lmrvzzlr", + "mandatory": false, + "Datatype": { + "Pattern": "", + "typeName": "TEXT", + "type": "TextDatatypeType", + "MaxLength": 249 + } + } + ], + "Control": [], + "depth": 2, + "FlowControl": [], + "Label": [ + "\"Question in sequence 2\"" + ], + "id": "lmriwa1y", + "TargetMode": [ + "CAPI", + "CATI", + "CAWI", + "PAPI" + ], + "Declaration": [], + "type": "QuestionType", + "questionType": "SIMPLE", + "Name": "Q2" + }, + { + "Response": [ + { + "CollectedVariableReference": "lmt9xzdm", + "id": "lmf46fuf", + "mandatory": false, + "Datatype": { + "Pattern": "", + "typeName": "TEXT", + "type": "TextDatatypeType", + "MaxLength": "20" + } + } + ], + "Control": [], + "depth": 2, + "FlowControl": [], + "Label": [ + "\"Question that is the input of the pairwise question\"" + ], + "id": "lmezduli", + "TargetMode": [ + "CAPI", + "CATI", + "CAWI", + "PAPI" + ], + "Declaration": [], + "type": "QuestionType", + "questionType": "SIMPLE", + "Name": "PAIRWISE_SOURCE" + } + ], + "Name": "S2" + }, + { + "Control": [], + "depth": 1, + "FlowControl": [], + "genericName": "MODULE", + "Label": [ + "\"Séquence avec boucle liée\"" + ], + "id": "lmezflon", + "TargetMode": [ + "CAPI", + "CATI", + "CAWI", + "PAPI" + ], + "Declaration": [], + "type": "SequenceType", + "Child": [ + { + "Response": [ + { + "CollectedVariableReference": "lmez8o56", + "id": "lmf4a46s", + "mandatory": false, + "Datatype": { + "Pattern": "", + "typeName": "TEXT", + "type": "TextDatatypeType", + "MaxLength": 249 + } + } + ], + "Control": [], + "depth": 2, + "FlowControl": [], + "Label": [ + "\"Question in sequence 3\"" + ], + "id": "lmezp3nz", + "TargetMode": [ + "CAPI", + "CATI", + "CAWI", + "PAPI" + ], + "Declaration": [], + "type": "QuestionType", + "questionType": "SIMPLE", + "Name": "Q3" + } + ], + "Name": "S3" + }, + { + "Control": [], + "depth": 1, + "FlowControl": [], + "genericName": "MODULE", + "Label": [ + "\"Sequence pairwise links\"" + ], + "id": "lmgeljoy", + "TargetMode": [ + "CAPI", + "CATI", + "CAWI", + "PAPI" + ], + "Declaration": [], + "type": "SequenceType", + "Child": [ + { + "Response": [ + { + "CollectedVariableReference": "lmt9r0x0", + "id": "lmgf25ji", + "mandatory": false, + "CodeListReference": "lmgeb1uh", + "Datatype": { + "Pattern": "", + "typeName": "TEXT", + "visualizationHint": "DROPDOWN", + "type": "TextDatatypeType", + "MaxLength": 1 + } + } + ], + "Control": [], + "depth": 2, + "FlowControl": [], + "Scope": "lmt9xzdm", + "Label": [ + "\"Pairwise links question\"" + ], + "id": "lmgen8zt", + "TargetMode": [ + "CAPI", + "CATI", + "CAWI", + "PAPI" + ], + "Declaration": [], + "type": "QuestionType", + "questionType": "PAIRWISE", + "Name": "LINKS" + } + ], + "Name": "S4" + }, + { + "Control": [], + "depth": 1, + "FlowControl": [], + "genericName": "MODULE", + "Label": [ + "QUESTIONNAIRE_END" + ], + "id": "idendquest", + "TargetMode": [ + "CAPI", + "CATI", + "CAWI", + "PAPI" + ], + "Declaration": [], + "type": "SequenceType", + "Child": [], + "Name": "QUESTIONNAIRE_END" + } + ] +} \ No newline at end of file