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.
Sample value of branchingLogic is as below:
{ "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/parent": ["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 we need to pass additional metadata branchingLogic as well. The metadata should be passed as part of nodesModified.
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 always support the replace operation for “branchingLogic”. So any partial update shouldn’t happen.
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:
the do_id should exist as children within the node the metadata is getting inserted.
If it has targets, those id’s entry should be present in branchingLogic as well as children array.
Platform won’t validate the preCondition data.
UpdateHierarchyManager.validateRequest() function will be modified and branchingLogic will be validated there.
Sample Request for adding dependent question (q2 depends on q1):
{ "request": { "data": { "nodesModified": { "section1": { "isNew": false, "root": false, "metadata": { "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
delete the entry from branching logic
<update all the targets> - using the source
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
If a node gets deleted from hierarchy through this api, platform will first check if its parent has branchingLogic meta, if yes, further check the node entry in branchingLogic, if entry found, there are two possible behaviour:
1st Behaviour:
if target of the requested node is not empty, delete all target ids from hierarchy as well as from branchingLogic first and then delete the requested node.
if target is empty, scan the identifier in other entries of branchingLogic and update the target, then delete the requested node from hierarchy and remove the entry from branchingLogic
2nd Behaviour:
If the target is not empty, throw client error with target ids because it has a dependent question. So in this case, the user should remove the dependent question first and update the branchingLogic of the parent question and then delete the parent one.
If the target is empty but preCondition is not empty, it will delete the node from the hierarchy and remove the entry from branchingLogic.
Note: Ideally platform should go with 1st behaviour because a child question can have only one parent.
HierarchyManager.removeLeafNodesFromHierarchy() function will be modified.
addNodeToHierachy:
The api won’t be used for dependent question feature. All children added through this api will be treated as independent object. So no code change is required.
input:
identifier of the question
branching for this node
source & pre-condition: source must already exist within the question set
TODO: add support for atomic operations to add/remove dependent questions
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 ] }
"operator": eq, ne, lt, gt, exists, not_exists, and, or "var": "<identifier>.<responseVariableKey>.value" or "SCORE" or "numAttempts" "type": "responseDeclaration" or "outcomeDeclaration"
ResponseDeclaration
{ "<operator>": [ { "var": "<identifier>.<responseVariableKey>.value", "type": "responseDeclaration" }, "<VALUE>" // expected response value ] }
Example:
{ "and": [ { "exist": [ { "var": "do_2.response1.value", "type": "responseDeclaration" }, [ 0 ] ] } ] }
OutcomeDeclaration:
{ "<operator>": [ { "var": "<outcomeVariable>", "type": "outcomeDeclaration" }, "<VALUE>" ] }
Example:
{ "and": [ { "gt": [ { "var": "SCORE", "type": "outcomeDeclaration", }, 50 ] } ] }
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
{ "name": "optionSelect", "option": { "label": "<p>RCB</p>", "value": 0, "selected": true }, "cardinality": "single", "solutions": [] }
The above object needs to be enhanced to be able to keep track of the questions taken by the user.
{ "do_1": { "response1": { "name": "optionSelect", "value": 0, "cardinality": "single" } }, "do_2": { "response1": { "name": "optionSelect", "value": [ 0, 1 ], "cardinality": "single" } } }
Add Comment