Questionnaire backend
Introduction
This document describes the backend for Orikami's questionnaires, including the G8 questionnaire. Its main purpose is to process the answers to individual questions in completed questionnaires, create a total score, and return that score and its interpretation.
Interface
The questionnaire backend algorithm is implemented in Python and runs stand-alone in a Python interpreter. It communicates with the rest of the Krane framework using Apache pulsar messages. Completed questionnaires are received after subscription to the PULSAR_TENANT/questionnaires/answers-in-TENANT topic. Example values for PULSAR_TENANT and TENANT are krane-staging and orikami, respectively.
Wherever possible, information that is known to the system should not need to be re-entered in a questionnaire, except if re-entering/reproducing that data would be part of the test or omitting that would otherwise invalidate the questionnaire. A notable example is age, which can be calculated from the combination of date of birth in the user profile and time of completion of the questionnaire. As of March 2024, no method has been implemented yet to fill in known (profile) data in questionnaires.
Processed questionnaires, containing total score, interpretation and an error message if applicable, are published to the PULSAR_TENANT/experiments/TENANT-events topic.
Input
Expected format for json-formatted input messages:
{
'id': '5a0beb60-68a8-401a-b798-0868346b5882',
'name': 'answers', 'operationType': 'insert', 'collection': 'answers',
'entity': {
'_version': 0, '_id': '6529052bed80223630597ae3',
'createdAt': '2023-10-13T08:51:55.313Z', 'updatedAt': '2023-10-13T08:51:55.313Z',
'type': 'questionnaire', 'studyId': '62a0639fe722db00135e9caa',
'result':
{
'questionnaireId': '632327809e2988cbd6eebaf4',
'answers':
[
{'questionId': '632327809e2988cbd6eebaf5', 'timestamp': '2023-10-13T08:51:50.621Z', 'value': 0, 'questionName': 'geteten', 'index': 0},
{'questionId': '632327809e2988cbd6eebaf8', 'timestamp': '2023-10-13T08:51:51.833Z', 'value': 0, 'questionName': 'gemoedstoestand', 'index': 0},
{'questionId': '632327809e2988cbd6eebaf7', 'timestamp': '2023-10-13T08:51:52.997Z', 'value': 0, 'questionName': 'pijn', 'index': 0},
{'questionId': '632327809e2988cbd6eebaf6', 'timestamp': '2023-10-13T08:51:54.092Z', 'value': 0, 'questionName': 'gelopen', 'index': 0},
{'questionId': '632327809e2988cbd6eebaf9', 'timestamp': '2023-10-13T08:51:55.206Z', 'value': 0, 'questionName': 'fit', 'index': 0}
],
'questionnaireName': {'en_US': 'hackathon'}
},
'userId': '6527a81258139c1e1494c4bc', 'status': 'completed',
'partialStatus':
{
'632327809e2988cbd6eebaf8': 'completed',
'632327809e2988cbd6eebaf9': 'completed',
'632327809e2988cbd6eebaf5': 'completed',
'632327809e2988cbd6eebaf7': 'completed',
'632327809e2988cbd6eebaf6': 'completed'
}
},
'reply': False, 'timestamp': 1697187115366
}
Output
The algorithm's result is a dictionary in json format. It is published using Apache pulsar as described above, and has the following structure:
{
name: "QuestionnaireScoreGenerated",
version,
tenant,
data: {
experimentResultId,
error_message,
warning_message?,
score?,
interpretation?
}
}
- name: defines type of data, should always be QuestionnaireScoreGenerated
- version: versioning of the questionnaire background code
- tenant: e.g., orikami
- experimentResultId: copied from _id in the questionnaire input data
- error_message: user-intelligible comma separated list of error descriptions if no result could be produced. Null if no error.
- warning_message: user-intelligible warning, e.g.
unknown questionnaire with id .... Null if no warning. - score: total score. Integer or floating point value >= 0, null or absent if an error occurred.
- interpretation: string representing a conclusion based on the score. Null or absent if an error occurred or if no interpretation is specified for this particular questionnaire.
Use
The backend starts a pulsar client and keeps on listening and processing until it is terminated:
python3 -m src.main
NOTE that the pulsar client currently does NOT work in a Windows environment, so on a Windows machine WSL should be used. The code also requires a number of environment variables to be set in a .env file. An example .env file is included in this repository, containing the following variables:
PULSAR_TOKEN=<token to be obtained from Orikami Group BV>
PULSAR_URL=pulsar+ssl://pulsar.orikami.tech:6651
PULSAR_TENANT=krane-staging
The token needs to obtained from Orikami Group BV.
Questionnaire processing
A typical questionnaire sums (weighted) numerical answers to individual questions to obtain a total score. That score may then be used to form an interpretation, often by thresholding or by mapping intervals. This type of generic behaviour is built into the component and can be configured in code dedicated to specific questionnaire types.
The src/questionnaires folder contains this configuration data and if necessary dedicated code for score and/or interpretation generation. One Python file is used for each specific questionnaire, and no other files need to be updated when adding, removing or renaming a questionnaire file. The current set of questionnaires in this folder contains simple examples like alldaycomplaints, and more elaborate ones like g8. Questionnaire data referring to a questionnaire that is not available in this folder is simply ignored.
Testing
Pytest will run a number of module tests:
python3 -m pytest
Note that these tests do not require pulsar running, since they use a (simple) mockup of a pulsar client and of the Experiment Service.