[Design] QuML Dependent Questions

Problem Statement:

  • The creator should be able to add a sub-question against the options of a question.

  • Users should be able to see the relevant question based on the response of the previous question/s.

Rules/Behaviour:

  • A Question can have more than one dependent(child) question for its different option combinations.

  • Questions with a pre-condition (i.e. pre-condition is not null or empty) will not be part of the primary sequence of the question set.

  • Source question and its targets should be members of the same question set.

Current Implementation:

  • The dependent Question feature should be enabled only for observation & survey.

  • A question can't depend upon more than one question. It means multiple questions can't point to the same question as a dependent one.

  • The dependent question will not have further dependent questions.

Example:

Q1, Q2, Q3

Q4 - pre-condition:

  • if Q1. response = x or Q1.response = y

  • if Q1.response = x and Q2.response = y

    • delete Q1: delete Q1 from hierarchy and branching logic. return “Q4 is dependent on Q1” in the response.

      • hierarchy: Q2, Q3, Q4

      • branchingLogic:

        • Q2 - target = Q4

        • Q4 - source = Q1, Q2 & pre-condition: {Q1.response = x and Q2.response = y}

  • if Q1.response = x or Q2.response = y

 

Solution: Dependent Question Behaviour will be achieved using metadata (branchingLogic) with Immediate Parent ( A Section or Observation/Survey) who has a dependent question

  • The Feature will be enabled/disabled for a particular category using the flag “allowBranching“ with the value “Yes/No

  • The flag allowBranching will be stored at object level schema with default to “No“ and same can be overridden for specific category at category definition level.

  • A dependent question can be added only for questions available within the immediate parent. It means we can't target question available with other parents within observation/survey hierarchy.

  • The rule (branchingLogic) will be available with the immediate parent (section or root node)

  • The branchingLogic will be stored 

    • in the hierarchy itself, if available at section/unit level. 

    • in the hierarchy & graph, if available at root level (Observation/Survey)

  • “branchingLogic“ will be added to QuestionSet object level schema as an optional property.

  • “branchingLogic” will be accepted by the platform only if “allowBranching” is set “Yes”.

  • Sample value of branchingLogic is as below:

{ "allowBranching": "Yes", "branchingLogic": { // Renamed from render config "questionId_do_1": { // Only parent or child questions will be stored here. "target": [ "questionId_do_2" ], "preCondition": {} }, "questionId_do_2": { "target": [], "source": ["questionId_do_1"], "preCondition": { "and": [ // Use of JSON Logic library { "eq": [ // eq, ne, lt, gt, exists, not_exists, and, or { "var": "do_1.response1.value", "type": "responseDeclaration" }, "0" ] } ] } } } }
  • target : all target question id's will be stored here. 

  • preCondition: rule or set of rules for question response assertion

  • “branchingLogic” will have entries for only those objects which have a dependent question and a dependent one.

  • Dependent Question should be added only through update hierarchy api as well as addNodeToHierachy api.

    • Question with visibility Parent/Default can be added as a dependent question.

  • ES Syncing will be disabled for the field “branchingLogic” through an existing config in search-indxer job.

  • Platform will support the merge operation for “branchingLogic”. So any partial update for branchingLogic will be supported.

  • compatibilityLevel for observation & survey categories will be set as current + 1 . i.e: 6. So that user will be forced to update the app, when observation/survey consumption will go live.

  • When The player starts playing a survey/observation, It should start rendering using hierarchy index as well as branchingLogic.

    • Question Index needs to be handled explicitly at player end as hierarchy index may be different for question which becomes visible first. the same will be applicable for dependent question as well.

  • Solution of Progress tracking/score computation for dependent question will be designed and taken up later before consumption start.

API Enhancement:

updateHierarchy:

The api will simply store the metadata after a basic validation. The validation includes below:

  1. the do_id should exist as children within the node the metadata is getting inserted.

  2. If it has targets, those id’s entry should be present in branchingLogic as well as children array.

  3. Platform won’t validate the preCondition data.

  4. UpdateHierarchyManager.validateRequest() function will be modified and branchingLogic will be validated there.

https://github.com/project-sunbird/knowledge-platform/blob/8f579b5ca4a25feca628832ce2706d0ad25ab25c/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/UpdateHierarchyManager.scala#L73

  1. Sample Request for adding dependent question (q2 depends on q1):

{ "request": { "data": { "nodesModified": { "section1": { "isNew": false, "root": false, "metadata": { "allowBranching": "Yes", "branchingLogic": { "q1": { "target": [ "q2" ], "preCondition": {} // this will be empty }, "q2": { "target": [], // this will be empty "preCondition": {} // this will have required rule. } } } }, "q2": {} // metadata for question q2 }, "hierarchy": { "survey-1": { "name": "survey 1", "primaryCategory": "Survey", "children": [ "section1" ], "root": true }, "section1": { "name": "Section 1", "primaryCategory": "Survey", "children": [ "q1", "q2" ], "root": false } }, "lastUpdatedBy": "5a587cc1-e018-4859-a0a8-e842650b9d64" } } }

deleteFromHierarchy:

  • deleting a dependent question:

    • delete from hierarchy

    • <update all the targets> - using the source property

    • delete the entry of the question from branching logic

  • deleting an independent question:

    • if the question has targets, throw an error…

      • the dependent questions should be deleted first and then the delete should be triggered again

    • if there are no targets, delete the node from hierarchy, remove the entry from branchingLogic

  • HierarchyManager.removeLeafNodesFromHierarchy() function will be modified.

https://github.com/project-sunbird/knowledge-platform/blob/8f579b5ca4a25feca628832ce2706d0ad25ab25c/assessment-api/qs-hierarchy-manager/src/main/scala/org/sunbird/managers/HierarchyManager.scala#L96


addNodeToHierachy:

  • The api will support dependent question feature. “branchingLogic“ can be passed as an optional data.

  • The Request will be as below:

  • input:

    • identifier of the question

    • branching for this node

      • source & pre-condition: source must already exist within the question set

  • Platform will support for atomic operations to add/remove dependent questions

  • The api version won’t be upgraded as branchingLogic is an optional input and backward compatibility won’t break.

questionset-publish flink job:

  • The job needs to be enhanced to process “branchingLogic” meta.

  • Syncing for sections needs to be enabled (not yet implemented).

  • The job needs to validate the object by using combined schema (object + category)

  • Fix compatibility level issue(read from schema and inject)

auto-creator-v2 flink job:

  • Syncing for sections needs to be enabled (not yet implemented).

  • The job needs to validate the object by using combined schema (object + category)

DSL for branchingConfig

With reference to the third-party library JSONLogic, we will compose the logic for precondition as shown below:

{ "<operator>": [ { "var": "", "type": "" }, "<VALUE>" // expected response value ] }

ResponseDeclaration

Example:

 

OutcomeDeclaration:

Example:

 

Enhancing the existing object in the player to evaluate branchConfig’s precondition

Currently, the player is using the following object to assert the user response evaluate the score for a given MCQ question

The above object needs to be enhanced to be able to keep track of the questions taken by the user.