diff --git a/efsity-cli/build.gradle.kts b/efsity-cli/build.gradle.kts index cdf9545..85e00b3 100644 --- a/efsity-cli/build.gradle.kts +++ b/efsity-cli/build.gradle.kts @@ -18,7 +18,7 @@ repositories { group = "org.smartregister" -version = "2.3.12-SNAPSHOT" +version = "2.3.13-SNAPSHOT" description = "fhircore-tooling (efsity)" diff --git a/efsity-cli/src/main/java/org/smartregister/command/QuestionnaireResponseGeneratorCommand.java b/efsity-cli/src/main/java/org/smartregister/command/QuestionnaireResponseGeneratorCommand.java index 91a4756..8619060 100644 --- a/efsity-cli/src/main/java/org/smartregister/command/QuestionnaireResponseGeneratorCommand.java +++ b/efsity-cli/src/main/java/org/smartregister/command/QuestionnaireResponseGeneratorCommand.java @@ -83,6 +83,12 @@ public class QuestionnaireResponseGeneratorCommand implements Runnable { defaultValue = ".") private String outputFilePath; + @CommandLine.Option( + names = {"-ih", "--ignore-hidden"}, + description = "Ignore hidden questions when generating responses", + defaultValue = "true") + private boolean ignoreHiddenQuestions; + private static final Random random = new Random(); private static final Faker faker = new Faker(); @@ -99,7 +105,8 @@ public void run() { fhir_base_url, apiKey, aiModel, - maxTokens); + maxTokens, + ignoreHiddenQuestions); } catch (IOException e) { throw new RuntimeException(e); } @@ -127,7 +134,8 @@ public static void generateResponse( String fhir_base_url, String apiKey, String aiModel, - String maxTokens) + String maxTokens, + boolean ignoreHiddenQuestions) throws IOException { long start = System.currentTimeMillis(); @@ -141,7 +149,7 @@ public static void generateResponse( String questionnaireResponseString = (Objects.equals(mode, "populate")) - ? populateMode(questionnaireData, fhir_base_url, extrasPath) + ? populateMode(questionnaireData, fhir_base_url, extrasPath, ignoreHiddenQuestions) : aiMode(questionnaireData, apiKey, aiModel, maxTokens); // Write response to file @@ -321,9 +329,15 @@ static JSONObject generateAnswer( } } - static JSONArray getAnswers(JSONArray questions, JSONArray responses, JSONObject extras) { + static JSONArray getAnswers( + JSONArray questions, JSONArray responses, JSONObject extras, boolean ignoreHiddenQuestions) { for (int i = 0; i < questions.length(); i++) { JSONObject current_question = questions.getJSONObject(i); + + if (ignoreHiddenQuestions && isHiddenQuestion(current_question)) { + continue; + } + String question_type = current_question.getString("type"); String link_id = current_question.getString("linkId"); @@ -335,7 +349,7 @@ static JSONArray getAnswers(JSONArray questions, JSONArray responses, JSONObject if (current_question.has("item")) { JSONArray group_questions = current_question.getJSONArray("item"); JSONArray group_responses = responses.getJSONObject(i).getJSONArray("item"); - getAnswers(group_questions, group_responses, extras); + getAnswers(group_questions, group_responses, extras, ignoreHiddenQuestions); } } responses.getJSONObject(i).put("answer", answer_arr); @@ -343,7 +357,30 @@ static JSONArray getAnswers(JSONArray questions, JSONArray responses, JSONObject return responses; } - static String populateMode(String questionnaireData, String fhir_base_url, String extrasPath) + static boolean isHiddenQuestion(JSONObject question) { + boolean isHidden = false; + + if (question.has("extension")) { + JSONArray extensions = question.getJSONArray("extension"); + for (int i = 0; i < extensions.length(); i++) { + JSONObject extension = extensions.getJSONObject(i); + if (extension + .getString("url") + .equals("http://hl7.org/fhir/StructureDefinition/questionnaire-hidden")) { + isHidden = extension.optBoolean("valueBoolean", true); + break; + } + } + } + + return isHidden; + } + + static String populateMode( + String questionnaireData, + String fhir_base_url, + String extrasPath, + boolean ignoreHiddenQuestions) throws IOException { JSONObject resource = new JSONObject(questionnaireData); String questionnaire_id = resource.getString("id"); @@ -377,7 +414,6 @@ static String populateMode(String questionnaireData, String fhir_base_url, Strin String populate_endpoint = String.join("/", fhir_base_url, resourceType, questionnaire_id, "$populate"); List result = HttpClient.postRequest(params.toString(), populate_endpoint, null); - JSONObject questionnaire_response = new JSONObject(result.get(1)); FctUtils.printError("Debug: response from questionnaireResponse: " + questionnaire_response); @@ -387,7 +423,8 @@ static String populateMode(String questionnaireData, String fhir_base_url, Strin if (questionnaire_response.has("item")) { JSONArray response = (JSONArray) questionnaire_response.get("item"); JSONArray questions = resource.getJSONArray("item"); - JSONArray response_with_answers = getAnswers(questions, response, extras); + JSONArray response_with_answers = + getAnswers(questions, response, extras, ignoreHiddenQuestions); questionnaire_response.put("item", response_with_answers); } return String.valueOf(questionnaire_response); diff --git a/efsity-cli/src/main/java/org/smartregister/command/TranslateCommand.java b/efsity-cli/src/main/java/org/smartregister/command/TranslateCommand.java index da2a7a3..1acaaa7 100644 --- a/efsity-cli/src/main/java/org/smartregister/command/TranslateCommand.java +++ b/efsity-cli/src/main/java/org/smartregister/command/TranslateCommand.java @@ -235,12 +235,16 @@ public void run() { Objects.requireNonNull(inputFilePath, "Input file path cannot be null"); if (inputFilePath.endsWith("configs") || inputFilePath.endsWith("fhir_content")) { + return inputFilePath.resolve("translation"); + } if (inputFilePath.toString().endsWith(".json")) { Path parent = inputFilePath.getParent(); if (parent == null || parent.getParent() == null) { + throw new IllegalArgumentException("Invalid file path for: " + inputFilePath); + } return parent.getParent().resolve("translation"); } diff --git a/efsity-cli/src/main/java/org/smartregister/command/ValidateStructureMapCommand.java b/efsity-cli/src/main/java/org/smartregister/command/ValidateStructureMapCommand.java index d10c85a..d168de2 100644 --- a/efsity-cli/src/main/java/org/smartregister/command/ValidateStructureMapCommand.java +++ b/efsity-cli/src/main/java/org/smartregister/command/ValidateStructureMapCommand.java @@ -127,7 +127,8 @@ void validateStructureMap(String inputFilePath, String structureMapFilePath, boo "http://localhost:8080/fhir", "", "", - ""); + "", + true); // Extract Resources using the StructureMap and the generated QuestionnaireResponse String generatedQuestionnaireResponsePath = diff --git a/efsity-cli/src/test/java/org/smartregister/command/QuestionnaireResponseGeneratorCommandTest.java b/efsity-cli/src/test/java/org/smartregister/command/QuestionnaireResponseGeneratorCommandTest.java index ed053ef..5cc9d40 100644 --- a/efsity-cli/src/test/java/org/smartregister/command/QuestionnaireResponseGeneratorCommandTest.java +++ b/efsity-cli/src/test/java/org/smartregister/command/QuestionnaireResponseGeneratorCommandTest.java @@ -5,6 +5,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import org.junit.jupiter.api.Test; @@ -299,4 +300,232 @@ void testGenerateDefaultAnswer() { assertNotNull(answer); assertTrue(answer.isEmpty()); } + + @Test + void testIsHiddenQuestion_hiddenExtensionTrue() { + JSONObject question = new JSONObject(); + JSONArray extensions = new JSONArray(); + JSONObject hiddenExtension = new JSONObject(); + hiddenExtension.put("url", "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden"); + hiddenExtension.put("valueBoolean", true); + extensions.put(hiddenExtension); + question.put("extension", extensions); + + assertTrue(QuestionnaireResponseGeneratorCommand.isHiddenQuestion(question)); + } + + @Test + void testIsHiddenQuestion_hiddenExtensionFalse() { + JSONObject question = new JSONObject(); + JSONArray extensions = new JSONArray(); + JSONObject hiddenExtension = new JSONObject(); + hiddenExtension.put("url", "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden"); + hiddenExtension.put("valueBoolean", false); + extensions.put(hiddenExtension); + question.put("extension", extensions); + + assertFalse(QuestionnaireResponseGeneratorCommand.isHiddenQuestion(question)); + } + + @Test + void testIsHiddenQuestion_noHiddenExtension() { + JSONObject question = new JSONObject(); + JSONArray extensions = new JSONArray(); + JSONObject otherExtension = new JSONObject(); + otherExtension.put("url", "http://example.com/other-extension"); + otherExtension.put("valueBoolean", true); + extensions.put(otherExtension); + question.put("extension", extensions); + + assertFalse(QuestionnaireResponseGeneratorCommand.isHiddenQuestion(question)); + } + + @Test + void testIsHiddenQuestion_noExtensions() { + JSONObject question = new JSONObject(); + assertFalse(QuestionnaireResponseGeneratorCommand.isHiddenQuestion(question)); + } + + @Test + void testIsHiddenQuestion_hiddenExtensionNoValueBoolean() { + JSONObject question = new JSONObject(); + JSONArray extensions = new JSONArray(); + JSONObject hiddenExtension = new JSONObject(); + hiddenExtension.put("url", "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden"); + extensions.put(hiddenExtension); + question.put("extension", extensions); + + assertTrue(QuestionnaireResponseGeneratorCommand.isHiddenQuestion(question)); + } + + @Test + void testGetAnswers_simpleQuestion() { + JSONArray questions = new JSONArray(); + JSONObject question = new JSONObject(); + question.put("type", "string"); + question.put("linkId", "exampleLinkId"); + questions.put(question); + + JSONArray responses = new JSONArray(); + JSONObject response = new JSONObject(); + responses.put(response); + + JSONObject extras = new JSONObject(); + + JSONArray updatedResponses = + QuestionnaireResponseGeneratorCommand.getAnswers(questions, responses, extras, false); + + assertNotNull(updatedResponses); + assertTrue(updatedResponses.getJSONObject(0).has("answer")); + assertTrue( + updatedResponses + .getJSONObject(0) + .getJSONArray("answer") + .getJSONObject(0) + .has("valueString")); + } + + @Test + void testGetAnswers_groupQuestion() { + JSONArray questions = new JSONArray(); + JSONObject groupQuestion = new JSONObject(); + groupQuestion.put("type", "group"); + groupQuestion.put("linkId", "group1"); + JSONArray nestedQuestions = new JSONArray(); + JSONObject nestedQuestion = new JSONObject(); + nestedQuestion.put("type", "integer"); + nestedQuestion.put("linkId", "nested1"); + nestedQuestions.put(nestedQuestion); + groupQuestion.put("item", nestedQuestions); + questions.put(groupQuestion); + + JSONArray responses = new JSONArray(); + JSONObject groupResponse = new JSONObject(); + groupResponse.put("item", new JSONArray().put(new JSONObject())); + responses.put(groupResponse); + + JSONObject extras = new JSONObject(); + + JSONArray updatedResponses = + QuestionnaireResponseGeneratorCommand.getAnswers(questions, responses, extras, false); + + assertNotNull(updatedResponses); + JSONObject nestedResponse = + updatedResponses.getJSONObject(0).getJSONArray("item").getJSONObject(0); + assertTrue(nestedResponse.has("answer")); + assertTrue(nestedResponse.getJSONArray("answer").getJSONObject(0).has("valueInteger")); + } + + @Test + void testGetAnswers_ignoreHiddenQuestions() { + JSONArray questions = new JSONArray(); + JSONObject hiddenQuestion = new JSONObject(); + hiddenQuestion.put("type", "boolean"); + hiddenQuestion.put("linkId", "hidden1"); + JSONArray extensions = new JSONArray(); + JSONObject hiddenExtension = new JSONObject(); + hiddenExtension.put("url", "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden"); + hiddenExtension.put("valueBoolean", true); + extensions.put(hiddenExtension); + hiddenQuestion.put("extension", extensions); + questions.put(hiddenQuestion); + + JSONArray responses = new JSONArray(); + responses.put(new JSONObject()); + + JSONObject extras = new JSONObject(); + + JSONArray updatedResponses = + QuestionnaireResponseGeneratorCommand.getAnswers(questions, responses, extras, true); + + assertNotNull(updatedResponses); + assertFalse(updatedResponses.getJSONObject(0).has("answer")); + } + + @Test + void testGetAnswers_includeHiddenQuestions() { + JSONArray questions = new JSONArray(); + JSONObject hiddenQuestion = new JSONObject(); + hiddenQuestion.put("type", "boolean"); + hiddenQuestion.put("linkId", "hidden1"); + JSONArray extensions = new JSONArray(); + JSONObject hiddenExtension = new JSONObject(); + hiddenExtension.put("url", "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden"); + hiddenExtension.put("valueBoolean", true); + extensions.put(hiddenExtension); + hiddenQuestion.put("extension", extensions); + questions.put(hiddenQuestion); + + JSONArray responses = new JSONArray(); + responses.put(new JSONObject()); + + JSONObject extras = new JSONObject(); + + JSONArray updatedResponses = + QuestionnaireResponseGeneratorCommand.getAnswers(questions, responses, extras, false); + + assertNotNull(updatedResponses); + assertTrue(updatedResponses.getJSONObject(0).has("answer")); + assertTrue( + updatedResponses + .getJSONObject(0) + .getJSONArray("answer") + .getJSONObject(0) + .has("valueBoolean")); + } + + @Test + void testGetAnswers_noExtras() { + JSONArray questions = new JSONArray(); + JSONObject question = new JSONObject(); + question.put("type", "decimal"); + question.put("linkId", "decimal1"); + questions.put(question); + + JSONArray responses = new JSONArray(); + responses.put(new JSONObject()); + + JSONArray updatedResponses = + QuestionnaireResponseGeneratorCommand.getAnswers(questions, responses, null, false); + + assertNotNull(updatedResponses); + assertTrue(updatedResponses.getJSONObject(0).has("answer")); + assertTrue( + updatedResponses + .getJSONObject(0) + .getJSONArray("answer") + .getJSONObject(0) + .has("valueDecimal")); + } + + @Test + void testGetAnswers_emptyQuestionnaire() { + JSONArray questions = new JSONArray(); + JSONArray responses = new JSONArray(); + JSONObject extras = new JSONObject(); + + JSONArray updatedResponses = + QuestionnaireResponseGeneratorCommand.getAnswers(questions, responses, extras, false); + + assertNotNull(updatedResponses); + assertEquals(0, updatedResponses.length()); + } + + @Test + void testGetAnswers_mismatchedQuestionsAndResponses() { + JSONArray questions = new JSONArray(); + JSONObject question = new JSONObject(); + question.put("type", "text"); + question.put("linkId", "text1"); + questions.put(question); + + JSONArray responses = new JSONArray(); + + JSONObject extras = new JSONObject(); + + assertThrows( + JSONException.class, + () -> + QuestionnaireResponseGeneratorCommand.getAnswers(questions, responses, extras, false)); + } }