Skip to content

Commit

Permalink
feat(pogues mapping): unique and multiple choice questions (#1195)
Browse files Browse the repository at this point in the history
* feat(pogues mapping): unique choice questions

* feat(pogues mapping): insert code lists in questions

* feat(pogues mapping): detail response (wip)

* feat(pogues mapping): boolean multiple choice question

* fix: code list references

* feat(pogues mapping): mcq with code list response

* chore: bump version
  • Loading branch information
nsenave authored Jan 7, 2025
1 parent c08f4d5 commit 977b35c
Show file tree
Hide file tree
Showing 22 changed files with 378 additions and 141 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ java {

allprojects {
group = "fr.insee.eno"
version = "3.32.0-SNAPSHOT"
version = "3.32.0-SNAPSHOT.1"
}

subprojects {
Expand Down
4 changes: 4 additions & 0 deletions eno-core/src/main/java/fr/insee/eno/core/PoguesToEno.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import fr.insee.eno.core.model.EnoQuestionnaire;
import fr.insee.eno.core.parameter.EnoParameters;
import fr.insee.eno.core.processing.common.EnoProcessing;
import fr.insee.eno.core.processing.in.PoguesInProcessing;
import fr.insee.eno.core.serialize.PoguesDeserializer;
import fr.insee.pogues.model.Questionnaire;

Expand All @@ -28,6 +29,9 @@ public EnoQuestionnaire transform(InputStream poguesInputStream, EnoParameters e
EnoQuestionnaire enoQuestionnaire = new EnoQuestionnaire();
poguesMapper.mapPoguesQuestionnaire(poguesQuestionnaire, enoQuestionnaire);
//
PoguesInProcessing poguesInProcessing = new PoguesInProcessing();
poguesInProcessing.applyProcessing(enoQuestionnaire);
//
EnoProcessing enoProcessing = new EnoProcessing(enoParameters);
enoProcessing.applyProcessing(enoQuestionnaire);
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import fr.insee.eno.core.model.EnoObject;
import fr.insee.eno.core.model.EnoQuestionnaire;
import fr.insee.pogues.model.QuestionType;
import fr.insee.pogues.model.ResponseType;
import fr.insee.pogues.model.VariableType;

public class PoguesConverter implements InConverter {
Expand All @@ -18,6 +19,8 @@ public EnoObject convertToEno(Object poguesObject, Class<?> enoType) {
if (poguesObject instanceof QuestionType poguesQuestion
&& EnoQuestionnaire.isMultipleResponseQuestion(poguesQuestion))
return PoguesMultipleChoiceQuestionConverter.instantiateFrom(poguesQuestion);
if (poguesObject instanceof ResponseType poguesResponse)
return PoguesTableCellConversion.instantiateFrom(poguesResponse);
throw new ConversionException("Eno conversion for Pogues type " + poguesObject.getClass() + " not implemented.");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package fr.insee.eno.core.converter;

import fr.insee.eno.core.exceptions.technical.ConversionException;
import fr.insee.eno.core.model.EnoObject;
import fr.insee.eno.core.model.question.table.TextCell;
import fr.insee.eno.core.model.question.table.UniqueChoiceCell;
import fr.insee.pogues.model.DatatypeTypeEnum;
import fr.insee.pogues.model.ResponseType;

class PoguesTableCellConversion {

private PoguesTableCellConversion() {}

static EnoObject instantiateFrom(ResponseType poguesResponse) {
DatatypeTypeEnum typeName = poguesResponse.getDatatype().getTypeName();
if (DatatypeTypeEnum.TEXT.equals(typeName)) {
if (poguesResponse.getCodeListReference() != null)
return new UniqueChoiceCell();
return new TextCell();
}
throw new ConversionException("Conversion of Pogues table cell of type " + typeName + " not implemented.");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
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.annotations.Pogues;
import fr.insee.eno.core.model.code.CodeList;
import fr.insee.eno.core.model.navigation.Binding;
import fr.insee.eno.core.model.question.table.ResponseCell;
import fr.insee.eno.core.parameter.Format;
import fr.insee.lunatic.model.flat.Table;
import fr.insee.pogues.model.QuestionType;
import lombok.Getter;
import lombok.Setter;

Expand All @@ -24,6 +26,7 @@
*/
@Getter
@Setter
@Context(format = Format.POGUES, type = QuestionType.class)
@Context(format = Format.DDI, type = QuestionGridType.class)
@Context(format = Format.LUNATIC, type = Table.class)
public class ComplexMultipleChoiceQuestion extends MultipleResponseQuestion {
Expand All @@ -37,24 +40,32 @@ public class ComplexMultipleChoiceQuestion extends MultipleResponseQuestion {
@Lunatic("setPositioning(#param)")
private String positioning = "HORIZONTAL";

/** Boolean property to tag the question as mandatory or not.
* Mapped in DDI and Lunatic, but unused for now. */
@DDI("getResponseDomain()?.getResponseCardinality()?.getMinimumResponses() != null ? " +
"getResponseDomain().getResponseCardinality().getMinimumResponses().intValue() > 0 : false")
@Lunatic("setMandatory(#param)")
boolean mandatory;

@Pogues("getResponseStructure().getDimension().getFirst().getCodeListReference()")
@DDI("#this.getGridDimensionList().?[#this.getRank().intValue() == 1].get(0)" +
".getCodeDomain().getCodeListReference().getIDArray(0).getStringValue()")
String leftColumnCodeListReference;

/** In Pogues and DDI, the code list object is inserted in a processing step.
* In Lunatic the code list is used in a table processing step. */
CodeList leftColumn;

/** Considering that out parameters are sorted in the same order as GridResponseDomainInMixed objects in DDI. */
@Pogues("getResponse().![#poguesIndex.get(#this.getCollectedVariableReference()).getName()]")
@DDI("getOutParameterList().![#this.getParameterNameArray(0).getStringArray(0).getStringValue()]")
List<String> variableNames = new ArrayList<>();

/** Only used in DDI. */
@DDI("getBindingArray()")
List<Binding> bindings = new ArrayList<>();

@Pogues("getResponse()")
@DDI("getStructuredMixedGridResponseDomain().getGridResponseDomainInMixedList()")
List<ResponseCell> responseCells = new ArrayList<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@
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.annotations.Pogues;
import fr.insee.eno.core.model.navigation.Binding;
import fr.insee.eno.core.model.response.CodeResponse;
import fr.insee.eno.core.model.response.DetailResponse;
import fr.insee.eno.core.model.response.ModalityAttachment;
import fr.insee.eno.core.parameter.Format;
import fr.insee.lunatic.model.flat.CheckboxGroup;
import fr.insee.pogues.model.QuestionType;
import lombok.Getter;
import lombok.Setter;

Expand All @@ -23,11 +26,13 @@
*/
@Getter
@Setter
@Context(format = Format.POGUES, type = QuestionType.class)
@Context(format = Format.DDI, type = QuestionGridType.class)
@Context(format = Format.LUNATIC, type = CheckboxGroup.class)
public class SimpleMultipleChoiceQuestion extends MultipleResponseQuestion {

/** Reference to the code list upon which modalities are based. */
@Pogues("getResponseStructure().getDimension().getFirst().getCodeListReference()")
@DDI("getGridDimensionArray(0).getCodeDomain().getCodeListReference().getIDArray(0).getStringValue()")
String codeListReference;

Expand All @@ -39,10 +44,19 @@ public class SimpleMultipleChoiceQuestion extends MultipleResponseQuestion {
* In Lunatic, the additional field is inside the response object of the modality.
* A DDI processing does the insertion of detail responses at the right place.
*/
@Pogues("getResponse()")
@DDI("getOutParameterList()")
@Lunatic("getResponses()")
List<CodeResponse> codeResponses = new ArrayList<>();

/**
* Detail responses for modalities that have a "please specify" field.
* In DDI, this information is mapped in a processing step.
* In Lunatic, they are inserted in code response objects through a processing.
*/
@Pogues("getClarificationQuestion()")
List<DetailResponse> detailResponses = new ArrayList<>();

/** DDI bindings used to keep the link between detail responses and the code response modality they belong to. */
@DDI("getBindingList()")
List<Binding> ddiBindings = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import fr.insee.lunatic.model.flat.Dropdown;
import fr.insee.lunatic.model.flat.Radio;
import fr.insee.pogues.model.QuestionType;
import fr.insee.pogues.model.ResponseType;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -64,19 +65,21 @@ public enum DisplayFormat {RADIO, CHECKBOX, DROPDOWN}
* Property used to convert to unique choice question to the right Lunatic component.
* In DDI, there are conventional values in the "generic output format" property.
* In Lunatic, it is used by the converter to create the right object, and to set the component type property. */
@Pogues("T(fr.insee.eno.core.model.question.UniqueChoiceQuestion).convertPoguesVisualizationHint(#this)")
@Pogues("T(fr.insee.eno.core.model.question.UniqueChoiceQuestion).convertPoguesVisualizationHint(" +
"#this.getResponse().getFirst())")
@DDI("T(fr.insee.eno.core.model.question.UniqueChoiceQuestion).convertDDIOutputFormat(#this)")
@Lunatic("setComponentType(" +
"T(fr.insee.eno.core.model.question.UniqueChoiceQuestion).convertDisplayFormatToLunatic(#param))")
DisplayFormat displayFormat;

/** Reference to the code list that contain the modalities of the question. */
@Pogues("getResponse().getFirst().getCodeListReference()")
@DDI("T(fr.insee.eno.core.model.question.UniqueChoiceQuestion).mapDDICodeListReference(#this)")
String codeListReference;

/**
* List of modalities of the unique choice question.
* In DDI, these are inserted here through a processing.
* In Pogues and DDI, these are inserted here through a processing.
*/
@Lunatic("getOptions()")
List<CodeItem> codeItems = new ArrayList<>();
Expand All @@ -92,12 +95,13 @@ public enum DisplayFormat {RADIO, CHECKBOX, DROPDOWN}

/** Detail responses for modalities that have a "please specify" field.
* In DDI, these are mapped at question level.
* In Lunatic, they are inserted in option in through a processing. */
* In Lunatic, they are inserted in option through a processing. */
@Pogues("getClarificationQuestion()")
@DDI("T(fr.insee.eno.core.model.question.UniqueChoiceQuestion).mapDetailResponses(#this)")
List<DetailResponse> detailResponses = new ArrayList<>();

public static DisplayFormat convertPoguesVisualizationHint(QuestionType poguesQuestion) {
return switch (poguesQuestion.getResponse().getFirst().getDatatype().getVisualizationHint()) {
public static DisplayFormat convertPoguesVisualizationHint(ResponseType poguesResponse) {
return switch (poguesResponse.getDatatype().getVisualizationHint()) {
case CHECKBOX -> DisplayFormat.CHECKBOX;
case DROPDOWN -> DisplayFormat.DROPDOWN;
case RADIO -> DisplayFormat.RADIO;
Expand All @@ -106,7 +110,6 @@ public static DisplayFormat convertPoguesVisualizationHint(QuestionType poguesQu
};
}


/**
* From DDI question item (that correspond to a unique choice question),
* return the eno model display format for the unique choice question.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package fr.insee.eno.core.model.question.table;

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.annotations.Pogues;
import fr.insee.eno.core.model.EnoObject;
import fr.insee.eno.core.model.EnoObjectWithId;
import fr.insee.eno.core.model.response.Response;
import fr.insee.eno.core.parameter.Format;
import fr.insee.pogues.model.ResponseType;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Context(format = Format.POGUES, type = ResponseType.class)
public abstract class ResponseCell extends EnoObject implements TableCell, EnoObjectWithId {

/** Source parameter id from DDI **/
@Pogues("getId()")
@DDI("getResponseDomain().getOutParameter().getIDArray(0).getStringValue()")
@Lunatic("setId(#param)")
String id;
Expand All @@ -29,6 +35,7 @@ public abstract class ResponseCell extends EnoObject implements TableCell, EnoOb

/** Response object for Lunatic.
* In DDI, response names are mapped in the table question object and inserted here through a processing. */
@Pogues("#this")
@Lunatic("setResponse(#param)")
Response response;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
import fr.insee.ddi.lifecycle33.datacollection.GridResponseDomainInMixedType;
import fr.insee.eno.core.annotations.DDI;
import fr.insee.eno.core.annotations.Lunatic;
import fr.insee.eno.core.annotations.Pogues;
import fr.insee.eno.core.exceptions.technical.MappingException;
import fr.insee.eno.core.model.code.CodeItem;
import fr.insee.eno.core.model.question.UniqueChoiceQuestion;
import fr.insee.eno.core.parameter.Format;
import fr.insee.lunatic.model.flat.BodyCell;
import fr.insee.lunatic.model.flat.Orientation;
import fr.insee.pogues.model.ResponseType;
import lombok.Getter;
import lombok.Setter;

Expand All @@ -21,6 +23,7 @@
/** A UniqueChoiceCell object is the content of a table.
* A cell is neither part of the header nor of the left column. */
@Getter @Setter
@Context(format = Format.POGUES, type = ResponseType.class)
@Context(format = Format.DDI, type = GridResponseDomainInMixedType.class)
@Context(format = Format.LUNATIC, type = BodyCell.class)
public class UniqueChoiceCell extends ResponseCell {
Expand All @@ -30,6 +33,7 @@ public class UniqueChoiceCell extends ResponseCell {
* For DDI, an analog method defined for the GridResponseDomainInMixed object is used.
* For Lunatic, method from Eno unique choice question class is reused.
* */
@Pogues("T(fr.insee.eno.core.model.question.UniqueChoiceQuestion).convertPoguesVisualizationHint(#this)")
@DDI("T(fr.insee.eno.core.model.question.table.UniqueChoiceCell).convertDDIOutputFormat(#this)")
@Lunatic("setComponentType(" +
"T(fr.insee.eno.core.model.question.UniqueChoiceQuestion).convertDisplayFormatToLunatic(#param))")
Expand All @@ -40,6 +44,7 @@ public class UniqueChoiceCell extends ResponseCell {
@Lunatic("setOrientation(T(fr.insee.lunatic.model.flat.Orientation).valueOf(#param))")
String orientation = Orientation.HORIZONTAL.toString();

@Pogues("getCodeListReference()")
@DDI("getResponseDomain().getCodeListReference().getIDArray(0).getStringValue()")
String codeListReference;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
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.annotations.Pogues;
import fr.insee.eno.core.model.EnoIdentifiableObject;
import fr.insee.eno.core.model.label.Label;
import fr.insee.eno.core.parameter.Format;
import fr.insee.lunatic.model.flat.ResponseCheckboxGroup;
import fr.insee.pogues.model.ResponseType;
import lombok.Getter;
import lombok.Setter;

Expand All @@ -17,22 +19,27 @@
*/
@Getter
@Setter
@Context(format = Format.POGUES, type = ResponseType.class)
@Context(format = Format.DDI, type = ParameterType.class)
@Context(format = Format.LUNATIC, type = ResponseCheckboxGroup.class)
public class CodeResponse extends EnoIdentifiableObject {

/** Label of this modality.
* In DDI, it is inserted there through a processing. */
* In Pogues and DDI, it is inserted there through a processing.
* @see fr.insee.eno.core.processing.in.steps.ddi.DDIInsertMultipleChoiceLabels
*/
@Lunatic("setLabel(#param)")
private Label label;

/** Response of the modality. */
@Pogues("#this")
@DDI("#this")
@Lunatic("setResponse(#param)")
Response response;

/** Additional "please specify" field of the modality.
* In DDI, it is inserted here through a processing. */
* In DDI, it is inserted here through a processing.
* In Pogues, it is mapped at the question level and inserted here in a processing step. */
@Lunatic("setDetail(#param)")
DetailResponse detailResponse;

Expand Down
Loading

0 comments on commit 977b35c

Please sign in to comment.