From 3ac59bddea4b210c0dcc6da6b37e70c92d3a94ce Mon Sep 17 00:00:00 2001 From: Rupert Westenthaler Date: Thu, 7 May 2026 15:03:12 +0200 Subject: [PATCH 1/2] #497: The QuestionObservationUtils now support SingleAnswerObservation validation for all data types expect OBJECT --- .../utils/QuestionObservationUtils.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/utils/QuestionObservationUtils.java b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/utils/QuestionObservationUtils.java index a73c5cb6..2c04e75a 100644 --- a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/utils/QuestionObservationUtils.java +++ b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/utils/QuestionObservationUtils.java @@ -196,9 +196,22 @@ public static ObservationValidationResult validateSingleAnswerObservation( .filter(it -> fieldId.equals(it.getMeasurement().getId())) .findFirst() .orElse(null); - boolean hasAnswers = answerMeasurementSummary != null - && answerMeasurementSummary.getStringResult() != null - && answerMeasurementSummary.getStringResult().values().stream().noneMatch(it -> it.value() == null); + boolean hasAnswers = false; + if (answerMeasurementSummary != null) { + hasAnswers = switch (answerMeasurementSummary.getMeasurement().getType()){ + case STRING -> answerMeasurementSummary.getStringResult() != null && + answerMeasurementSummary.getStringResult().values().stream().noneMatch(it -> it.value() == null); + case BOOLEAN -> answerMeasurementSummary.getBooleanResult() != null && + answerMeasurementSummary.getBooleanResult().values().stream().noneMatch(it -> it.value() == null); + case INTEGER, LONG, DOUBLE -> answerMeasurementSummary.getNumericResult() != null && + answerMeasurementSummary.getNumericResult().missing() == 0; + case DATE -> answerMeasurementSummary.getDateResult() != null && + answerMeasurementSummary.getDateResult().missing() == 0; + case ARRAY -> answerMeasurementSummary.getArrayResult() != null && + !answerMeasurementSummary.getArrayResult().values().value().isEmpty(); + case OBJECT -> throw new IllegalStateException("Checking for answers is not supported for Measurements of type OBJECT"); + }; + } //else no data -> no answers return new ObservationValidationResult(!hasAnswers, hasAnswers ? ObservationDataState.COMPLETE : ObservationDataState.MISSING); } } From b1caa42a43f9efe40d87c47af4b7121c21b7d886 Mon Sep 17 00:00:00 2001 From: Rupert Westenthaler Date: Thu, 7 May 2026 15:04:55 +0200 Subject: [PATCH 2/2] #497: Limesurvey Observation data health validation now uses the `lastpage` property instead of `seed`. Updated the implementation so that the validation of INTEGER type properties is now supported by the `QuestionObservationUtils`. Adapted the UnitTest to provide Mock data for `lastpage` instead of `seed` --- .../lime/LimeSurveyObservation.java | 37 ++++++++----------- .../lime/LimeSurveyObservationFactory.java | 4 +- .../lime/LimeSurveyObservationTest.java | 20 +++++----- 3 files changed, 28 insertions(+), 33 deletions(-) diff --git a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservation.java b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservation.java index 7045e21f..92c04274 100644 --- a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservation.java +++ b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservation.java @@ -211,32 +211,27 @@ public ObservationValidationResult validateData(Instant start, Instant end, Obse return new ObservationValidationResult(false, ObservationDataState.MISSING); } //(1) Use the default single Answer utility method - var validationResult = QuestionObservationUtils.validateSingleAnswerObservation( + var lastPageResult = QuestionObservationUtils.validateSingleAnswerObservation( observationDataSummary, - LimeSurveyObservationFactory.MEASUREMENT_SEED //NOTE: This utility method only works with STRING fields!! + LimeSurveyObservationFactory.MEASUREMENT_LASTPAGE //NOTE: This utility method only works with STRING fields!! ); //(2) Check of the ID property is present - MeasurementSummary answerMeasurementSummary = observationDataSummary.measurements().stream() - .filter(it -> LimeSurveyObservationFactory.MEASUREMENT_ID.equals(it.getMeasurement().getId())) - .findFirst() - .orElse(null); - //check that the field is present on all documents - boolean hasId = answerMeasurementSummary != null - && answerMeasurementSummary.getNumericResult() != null - && answerMeasurementSummary.getNumericResult().missing() == 0; + var idResult = QuestionObservationUtils.validateSingleAnswerObservation( + observationDataSummary, + LimeSurveyObservationFactory.MEASUREMENT_ID + ); //(3) Adapt the validation result where necessary - if(!validationResult.invalid() && validationResult.state() == ObservationDataState.COMPLETE && !hasId) { - //The required field seed is missing in the results! - return new ObservationValidationResult( - true, - ObservationDataState.INCOMPLETE - ); - } else if(validationResult.state() == ObservationDataState.MISSING && hasId){ - //if seed is missing, but ID is present ... return INCOMPLETE instead of MISSING as result - return new ObservationValidationResult(validationResult.invalid(), ObservationDataState.INCOMPLETE); - } else { //just return the original validation format - return validationResult; + boolean invalid = idResult.invalid() || lastPageResult.invalid(); + if(idResult.invalid() && lastPageResult.invalid()) { + return idResult; + } else if(invalid){ + return new ObservationValidationResult(invalid, ObservationDataState.INCOMPLETE); + } else { + ObservationDataState state = ObservationDataState.values()[Math.min( + idResult.state().ordinal(), + lastPageResult.state().ordinal())]; + return new ObservationValidationResult(invalid, state); } } diff --git a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationFactory.java b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationFactory.java index b27b9b97..089a420c 100644 --- a/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationFactory.java +++ b/studymanager-observation/src/main/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationFactory.java @@ -39,7 +39,7 @@ public class LimeSurveyObservationFactory, P ); public static String MEASUREMENT_ID = "id"; - public static String MEASUREMENT_SEED = "seed"; + public static String MEASUREMENT_LASTPAGE = "lastpage"; /** * This measurementSet includes the id and seed of the LimeSurvey. All * survey specific information are stored under the survey keys. Those keys @@ -48,7 +48,7 @@ public class LimeSurveyObservationFactory, P private static MeasurementSet LIMESURVEY_METADATA = new MeasurementSet( "LIMESURVEY", Set.of( new Measurement(MEASUREMENT_ID, Measurement.Type.INTEGER), - new Measurement(MEASUREMENT_SEED, Measurement.Type.STRING)) + new Measurement(MEASUREMENT_LASTPAGE, Measurement.Type.INTEGER)) ); diff --git a/studymanager-observation/src/test/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationTest.java b/studymanager-observation/src/test/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationTest.java index fada767f..4713432f 100644 --- a/studymanager-observation/src/test/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationTest.java +++ b/studymanager-observation/src/test/java/io/redlink/more/studymanager/component/observation/lime/LimeSurveyObservationTest.java @@ -154,16 +154,16 @@ public void testValidation() { LimeSurveyObservation questionObservation = new LimeSurveyObservation(sdk, properties, null); - var seedSummary = new MeasurementSummary( - new Measurement(LimeSurveyObservationFactory.MEASUREMENT_SEED, Measurement.Type.STRING)); - seedSummary.setStringResult(new StringMeasurementSummary(List.of(new FieldValue<>("seed_12345", 1)))); + var lastPageSummary = new MeasurementSummary( + new Measurement(LimeSurveyObservationFactory.MEASUREMENT_LASTPAGE, Measurement.Type.INTEGER)); + lastPageSummary.setNumericResult(new NumericMeasurementSummary(1.0,1.0, 1.0, 1.0, 0)); var idMs = new MeasurementSummary( new Measurement(LimeSurveyObservationFactory.MEASUREMENT_ID, Measurement.Type.INTEGER)); idMs.setNumericResult(new NumericMeasurementSummary(1.0,1.0, 1.0, 1.0, 0)); ObservationDataSummary validSummary = new ObservationDataSummary( 1, new DateMeasurementSummary(stored, stored, 0L), - List.of(idMs, seedSummary) + List.of(idMs, lastPageSummary) ); var result = questionObservation.validateData(start, end, validSummary); Assertions.assertFalse(result.invalid()); @@ -172,7 +172,7 @@ public void testValidation() { ObservationDataSummary invalidMultipleAnswersSummary = new ObservationDataSummary( 2, new DateMeasurementSummary(stored, stored, 0L), - List.of(idMs, seedSummary) + List.of(idMs, lastPageSummary) ); result = questionObservation.validateData(start, end, invalidMultipleAnswersSummary); Assertions.assertTrue(result.invalid()); @@ -187,13 +187,13 @@ public void testValidation() { Assertions.assertFalse(result.invalid()); Assertions.assertEquals(ObservationDataState.MISSING, result.state()); - var nullSeedMs = new MeasurementSummary( - new Measurement(LimeSurveyObservationFactory.MEASUREMENT_SEED, Measurement.Type.STRING)); - nullSeedMs.setStringResult(new StringMeasurementSummary(List.of(new FieldValue(null, 1)))); + var missingLastPageMs = new MeasurementSummary( + new Measurement(LimeSurveyObservationFactory.MEASUREMENT_LASTPAGE, Measurement.Type.INTEGER)); + missingLastPageMs.setNumericResult(new NumericMeasurementSummary(1.0,1.0, 1.0, 1.0, 1)); ObservationDataSummary nullSeedSummary = new ObservationDataSummary( 1, new DateMeasurementSummary(stored, stored, 0L), - List.of(idMs, nullSeedMs) + List.of(idMs, missingLastPageMs) ); result = questionObservation.validateData(start, end, nullSeedSummary); Assertions.assertTrue(result.invalid()); @@ -205,7 +205,7 @@ public void testValidation() { ObservationDataSummary missingIdSummary = new ObservationDataSummary( 1, new DateMeasurementSummary(stored, stored, 0L), - List.of(missingIdMs, seedSummary) + List.of(missingIdMs, lastPageSummary) ); result = questionObservation.validateData(start, end, missingIdSummary); Assertions.assertTrue(result.invalid());