Skip to content

Commit

Permalink
feat: loop with except (#727)
Browse files Browse the repository at this point in the history
* refactor: remove unused component filter in loop

* fix: adapt DDI loop items mapping for 'except'

The 'except' in Pogues UI results in a filter within a loop in DDI

* test: tests for loop with 'except'

* chore: bump version
  • Loading branch information
nsenave authored Sep 26, 2023
1 parent 9d7b8a7 commit c6854f8
Show file tree
Hide file tree
Showing 8 changed files with 1,743 additions and 20 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {

allprojects {
group = 'fr.insee.eno'
version = '3.10.1-SNAPSHOT'
version = '3.10.2-SNAPSHOT'
sourceCompatibility = '17'
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package fr.insee.eno.core.model.navigation;

import datacollection33.LoopType;
import datacollection33.SequenceType;
import fr.insee.eno.core.annotations.Contexts.Context;
import fr.insee.eno.core.annotations.DDI;
import fr.insee.eno.core.annotations.Lunatic;
import fr.insee.eno.core.exceptions.technical.MappingException;
import fr.insee.eno.core.model.EnoIdentifiableObject;
import fr.insee.eno.core.model.sequence.ItemReference;
import fr.insee.eno.core.model.sequence.StructureItemReference;
import fr.insee.eno.core.parameter.Format;
import fr.insee.eno.core.reference.DDIIndex;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import reusable33.ReferenceType;

import java.util.ArrayList;
import java.util.List;
Expand All @@ -34,19 +38,28 @@ public abstract class Loop extends EnoIdentifiableObject {
private String name;

/** Same principle as sequence items list in sequence objects. */
@DDI("#index.get(#this.getControlConstructReference().getIDArray(0).getStringValue())" +
".getControlConstructReferenceList()")
@DDI("T(fr.insee.eno.core.model.navigation.Loop).mapLoopItemReferences(#this, #index)")
private final List<ItemReference> loopItems = new ArrayList<>();

/** References of sequences or subsequences that are in the scope of the loop.
* Note: in Pogues a loop can only be defined on sequence or subsequences.
* (In other formats, nothing makes it formally impossible to have loops defined directly on questions.)
* In DDI, this property is filled by a processing using the "loopItems" property. */
private final List<StructureItemReference> loopScope = new ArrayList<>();

/** A loop can be in the scope of a filter.
* In DDI, filters are mapped at questionnaire level and inserted through a processing step. */
@Lunatic("setConditionFilter(#param)")
private ComponentFilter filter;

public static List<ReferenceType> mapLoopItemReferences(LoopType ddiLoop, DDIIndex ddiIndex) {
ReferenceType controlConstructReference = ddiLoop.getControlConstructReference();
String referencedControlConstructType = controlConstructReference.getTypeOfObject().toString();
if ("Sequence".equals(referencedControlConstructType)) {
SequenceType ddiSequence = (SequenceType) ddiIndex.get(controlConstructReference.getIDArray(0).getStringValue());
return ddiSequence.getControlConstructReferenceList();
}
if ("IfThenElse".equals(referencedControlConstructType)) {
return List.of(ddiLoop.getControlConstructReference());
}
throw new MappingException(String.format(
"DDI loop '%s' references an object of unexpected type: '%s'.",
ddiLoop.getIDArray(0).getStringValue(), referencedControlConstructType));
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package fr.insee.eno.core.processing.common.steps;

import fr.insee.eno.core.exceptions.business.DDIParsingException;
import fr.insee.eno.core.mappers.DDIMapper;
import fr.insee.eno.core.model.EnoQuestionnaire;
import fr.insee.eno.core.model.calculated.CalculatedExpression;
import fr.insee.eno.core.model.navigation.Filter;
Expand All @@ -8,8 +10,11 @@
import fr.insee.eno.core.model.sequence.StructureItemReference.StructureItemType;
import fr.insee.eno.core.processing.common.steps.EnoInsertComponentFilters;
import fr.insee.eno.core.reference.EnoIndex;
import fr.insee.eno.core.serialize.DDIDeserializer;
import org.junit.jupiter.api.Test;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;

Expand Down Expand Up @@ -49,4 +54,20 @@ void unitTest_sequence() {
enoQuestionnaire.getSequences().get(0).getComponentFilter().getValue());
}

@Test
void loopWithExcept() throws DDIParsingException {
//
EnoQuestionnaire enoQuestionnaire = new EnoQuestionnaire();
DDIMapper ddiMapper = new DDIMapper();
ddiMapper.mapDDI(
DDIDeserializer.deserialize(
this.getClass().getClassLoader().getResourceAsStream(
"integration/ddi/ddi-loop-except.xml")),
enoQuestionnaire);
//
new EnoInsertComponentFilters().apply(enoQuestionnaire);
//
assertNotNull(enoQuestionnaire.getSequences().get(2).getComponentFilter());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
import fr.insee.eno.core.exceptions.business.DDIParsingException;
import fr.insee.eno.core.mappers.DDIMapper;
import fr.insee.eno.core.model.EnoQuestionnaire;
import fr.insee.eno.core.model.navigation.LinkedLoop;
import fr.insee.eno.core.model.navigation.Loop;
import fr.insee.eno.core.model.sequence.StructureItemReference;
import fr.insee.eno.core.model.sequence.StructureItemReference.StructureItemType;
import fr.insee.eno.core.serialize.DDIDeserializer;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;

public class DDIResolveLoopsScopeTest {
Expand Down Expand Up @@ -38,25 +42,25 @@ static void mapQuestionnaire() throws DDIParsingException {
void loopScope() {
//
assertEquals(1, enoQuestionnaire.getLoops().get(0).getLoopScope().size());
assertEquals(StructureItemReference.StructureItemType.SEQUENCE,
assertEquals(StructureItemType.SEQUENCE,
enoQuestionnaire.getLoops().get(0).getLoopScope().get(0).getType());
assertEquals(enoQuestionnaire.getSequences().get(0).getId(),
enoQuestionnaire.getLoops().get(0).getLoopScope().get(0).getId());
//
assertEquals(1, enoQuestionnaire.getLoops().get(1).getLoopScope().size());
assertEquals(StructureItemReference.StructureItemType.SEQUENCE,
assertEquals(StructureItemType.SEQUENCE,
enoQuestionnaire.getLoops().get(1).getLoopScope().get(0).getType());
assertEquals(enoQuestionnaire.getSequences().get(1).getId(),
enoQuestionnaire.getLoops().get(1).getLoopScope().get(0).getId());
//
assertEquals(1, enoQuestionnaire.getLoops().get(2).getLoopScope().size());
assertEquals(StructureItemReference.StructureItemType.SEQUENCE,
assertEquals(StructureItemType.SEQUENCE,
enoQuestionnaire.getLoops().get(2).getLoopScope().get(0).getType());
assertEquals(enoQuestionnaire.getSequences().get(3).getId(),
enoQuestionnaire.getLoops().get(2).getLoopScope().get(0).getId());
//
assertEquals(1, enoQuestionnaire.getLoops().get(3).getLoopScope().size());
assertEquals(StructureItemReference.StructureItemType.SEQUENCE,
assertEquals(StructureItemType.SEQUENCE,
enoQuestionnaire.getLoops().get(3).getLoopScope().get(0).getType());
assertEquals(enoQuestionnaire.getSequences().get(4).getId(),
enoQuestionnaire.getLoops().get(3).getLoopScope().get(0).getId());
Expand Down Expand Up @@ -88,25 +92,25 @@ static void mapQuestionnaire() throws DDIParsingException {
void loopScope() {
//
assertEquals(1, enoQuestionnaire.getLoops().get(0).getLoopScope().size());
assertEquals(StructureItemReference.StructureItemType.SUBSEQUENCE,
assertEquals(StructureItemType.SUBSEQUENCE,
enoQuestionnaire.getLoops().get(0).getLoopScope().get(0).getType());
assertEquals(enoQuestionnaire.getSubsequences().get(0).getId(),
enoQuestionnaire.getLoops().get(0).getLoopScope().get(0).getId());
//
assertEquals(1, enoQuestionnaire.getLoops().get(1).getLoopScope().size());
assertEquals(StructureItemReference.StructureItemType.SUBSEQUENCE,
assertEquals(StructureItemType.SUBSEQUENCE,
enoQuestionnaire.getLoops().get(1).getLoopScope().get(0).getType());
assertEquals(enoQuestionnaire.getSubsequences().get(1).getId(),
enoQuestionnaire.getLoops().get(1).getLoopScope().get(0).getId());
//
assertEquals(1, enoQuestionnaire.getLoops().get(2).getLoopScope().size());
assertEquals(StructureItemReference.StructureItemType.SUBSEQUENCE,
assertEquals(StructureItemType.SUBSEQUENCE,
enoQuestionnaire.getLoops().get(2).getLoopScope().get(0).getType());
assertEquals(enoQuestionnaire.getSubsequences().get(2).getId(),
enoQuestionnaire.getLoops().get(2).getLoopScope().get(0).getId());
//
assertEquals(1, enoQuestionnaire.getLoops().get(3).getLoopScope().size());
assertEquals(StructureItemReference.StructureItemType.SUBSEQUENCE,
assertEquals(StructureItemType.SUBSEQUENCE,
enoQuestionnaire.getLoops().get(3).getLoopScope().get(0).getType());
assertEquals(enoQuestionnaire.getSubsequences().get(3).getId(),
enoQuestionnaire.getLoops().get(3).getLoopScope().get(0).getId());
Expand Down Expand Up @@ -141,7 +145,7 @@ void loopScopeOfMainLoop() {
assertEquals(3, loop.getLoopScope().size());
//
loop.getLoopScope().forEach(structureItemReference ->
assertEquals(StructureItemReference.StructureItemType.SEQUENCE, structureItemReference.getType()));
assertEquals(StructureItemType.SEQUENCE, structureItemReference.getType()));
//
assertEquals(enoQuestionnaire.getSequences().get(0).getId(), loop.getLoopScope().get(0).getId());
assertEquals(enoQuestionnaire.getSequences().get(1).getId(), loop.getLoopScope().get(1).getId());
Expand All @@ -155,7 +159,7 @@ void loopScopeOfLinkedLoop() {
assertEquals(3, loop.getLoopScope().size());
//
loop.getLoopScope().forEach(structureItemReference ->
assertEquals(StructureItemReference.StructureItemType.SEQUENCE, structureItemReference.getType()));
assertEquals(StructureItemType.SEQUENCE, structureItemReference.getType()));
//
assertEquals(enoQuestionnaire.getSequences().get(3).getId(), loop.getLoopScope().get(0).getId());
assertEquals(enoQuestionnaire.getSequences().get(4).getId(), loop.getLoopScope().get(1).getId());
Expand Down Expand Up @@ -191,7 +195,7 @@ void loopScopeOfMainLoop() {
assertEquals(3, loop.getLoopScope().size());
//
loop.getLoopScope().forEach(structureItemReference ->
assertEquals(StructureItemReference.StructureItemType.SUBSEQUENCE, structureItemReference.getType()));
assertEquals(StructureItemType.SUBSEQUENCE, structureItemReference.getType()));
//
assertEquals(enoQuestionnaire.getSubsequences().get(0).getId(), loop.getLoopScope().get(0).getId());
assertEquals(enoQuestionnaire.getSubsequences().get(1).getId(), loop.getLoopScope().get(1).getId());
Expand All @@ -205,13 +209,38 @@ void loopScopeOfLinkedLoop() {
assertEquals(3, loop.getLoopScope().size());
//
loop.getLoopScope().forEach(structureItemReference ->
assertEquals(StructureItemReference.StructureItemType.SUBSEQUENCE, structureItemReference.getType()));
assertEquals(StructureItemType.SUBSEQUENCE, structureItemReference.getType()));
//
assertEquals(enoQuestionnaire.getSubsequences().get(3).getId(), loop.getLoopScope().get(0).getId());
assertEquals(enoQuestionnaire.getSubsequences().get(4).getId(), loop.getLoopScope().get(1).getId());
assertEquals(enoQuestionnaire.getSubsequences().get(5).getId(), loop.getLoopScope().get(2).getId());
}

}

@Nested
class LoopWithExceptTest {

@Test
void scopeOfLoopContainingFilter() throws DDIParsingException {
// Given
EnoQuestionnaire enoQuestionnaire = new EnoQuestionnaire();
DDIMapper ddiMapper = new DDIMapper();
ddiMapper.mapDDI(
DDIDeserializer.deserialize(DDIResolveLoopsScopeTest.class.getClassLoader().getResourceAsStream(
"integration/ddi/ddi-loop-except.xml")),
enoQuestionnaire);
// When
new DDIResolveLoopsScope().apply(enoQuestionnaire);
// Then
Optional<LinkedLoop> linkedLoop = enoQuestionnaire.getLoops().stream()
.filter(LinkedLoop.class::isInstance).map(LinkedLoop.class::cast).findAny();
assert linkedLoop.isPresent(); // (not the purpose of this test)
assertEquals(1, linkedLoop.get().getLoopScope().size());
assertEquals(StructureItemType.SEQUENCE, linkedLoop.get().getLoopScope().get(0).getType());
assertEquals(enoQuestionnaire.getSequences().get(2).getId(),
linkedLoop.get().getLoopScope().get(0).getId());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -459,4 +459,37 @@ void largeCoverageQuestionnaire() throws DDIParsingException {

}

@Nested
class LoopWithExcept {

@Test
void componentsInLinkedLoopShouldHaveFilter() throws DDIParsingException {
// Given
EnoQuestionnaire enoQuestionnaire = DDIToEno.transform(
this.getClass().getClassLoader().getResourceAsStream("integration/ddi/ddi-loop-except.xml"),
EnoParameters.of(Context.DEFAULT, ModeParameter.CAWI, Format.LUNATIC));
Questionnaire lunaticQuestionnaire = new Questionnaire();
LunaticMapper lunaticMapper = new LunaticMapper();
lunaticMapper.mapQuestionnaire(enoQuestionnaire, lunaticQuestionnaire);
LunaticSortComponents lunaticSortComponents = new LunaticSortComponents(enoQuestionnaire);
lunaticSortComponents.apply(lunaticQuestionnaire);

// When
LunaticLoopResolution lunaticLoopResolution = new LunaticLoopResolution(enoQuestionnaire);
lunaticLoopResolution.apply(lunaticQuestionnaire);

// Then
assertEquals(ComponentTypeEnum.LOOP, lunaticQuestionnaire.getComponents().get(3).getComponentType());
Loop lunaticLinkedLoop = (Loop) lunaticQuestionnaire.getComponents().get(3);
assertEquals(ComponentTypeEnum.SEQUENCE, lunaticLinkedLoop.getComponents().get(0).getComponentType());
assertEquals(ComponentTypeEnum.CHECKBOX_BOOLEAN, lunaticLinkedLoop.getComponents().get(1).getComponentType());
//
lunaticLinkedLoop.getComponents().forEach(component -> {
assertEquals("(not(nvl(AGE, 0) < 18))", component.getConditionFilter().getValue());
assertEquals(Constant.LUNATIC_LABEL_VTL, component.getConditionFilter().getType());
});
}

}

}
Loading

0 comments on commit c6854f8

Please sign in to comment.