Working With Activities, Exercises, ITQs and SAQs
Contents
Working With Activities, Exercises, ITQs and SAQs#
When creating active teaching and learning materials, an important learning design approach for prompting engagement is the inclusion of activities, where a call to action is provided and a learner is encouraged to perform a particular task or check their understanding in a particular way.
A large number of potentially reusable activities are defined in a structured way in OU-XML documents. If we are to reuse such activities, whether directly, with modification, or simply as a source of inspiration, we need to be able to browse or discover them in some way.
Currently, discovery is likely to arise from an academic remembering a particular activity from a particular module then sarching the VLE for that module, and searching within the VLE for the activity they remember. How much easier it would be if they could simply search over all activities, perhaps filtering down by module code, and then previewing the results in a meaningful way?
The <Activity>
, <Exercise>
, <ITQ>
and <SAQ>
elements all have a similar internal structure and only differ in the parent tag [example docs].
Each element must include a <Question>
and may include a <Heading>
and a <Timing>
element; various different response elements may be provided (one or more of <MediaContent>
, <Interaction>
, <Answer>
, <Discussion>
) either as a single response, or within a <Part>
tag inside a <Multipart>
response element..
For example, at its simplest, an ITQ might take the following form:
<ITQ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Question>
<Paragraph>In session 1, you met charged versions of atoms; what are they called? </Paragraph>
</Question>
<Answer>
<Paragraph>These are ions (positively charged ions are called cations and negatively charged ions are called anions). </Paragraph>
</Answer>
</ITQ>
ITQs may also be defined as multipart questions.
Work in Progress; the Activity
context, it would probably make sense to use a foreign key relationship to provide a reference into the media
table from an activities
table.
Preparing the Ground#
As ever, we need to set up a database connection:
from sqlite_utils import Database
import pandas as pd
# Open database connection
xml_dbname = "all_openlean_xml.db"
xml_db = Database(xml_dbname)
activity_dbname = "openlean_assets.db"
db = Database(activity_dbname)
And get a sample XML file, selecting one that we know contains structurally marked up glossary items:
from lxml import etree
import pandas as pd
from xml_utils import unpack, flatten
# Grab an OU-XML file that is known to contain activity items
activity_example_raw = pd.read_sql("SELECT xml FROM xml WHERE name='Accessibility of eLearning'",
con=xml_db.conn).loc[0, "xml"]
# Parse the XML into an xml object
root = etree.fromstring(activity_example_raw)
Decomposing Activities Into Component Parts#
Activity-like objects are made up of several “simple” and more structurd componenents. All activity types must define a <Question>
component, but there is some freedom in the choice of “response” element. In addition, multipart responses may be defined.
The strategy followed below is to split out the “simple” <Answer>
and <Discussion>
elements ito their own columns. For <Multipart>
responses, each <Part>
has its own entry in the activities
table, with a multipart
flag set.
TO DO: a “part-sort-order” number should also be included in the table.
The more complex <MediaContent>
and <Interaction>
elements are not currently handled in a meanigful way in the activity context. (Media items are handled separately in their own mined table, and a foreigh key reference should be made into that table. The interaction element is currently included in the activities table as stringified XML.)
TO DO: handle interaction
TO DO: handle media item reference.
import secrets
def parse_activity_item(activity, _path=None, key=""):
"""Parse activity element.
The key is a unit id that lets us create a unique interaction table FK."""
def _delist(x):
return x[0] if x else x
def _tidy(x):
return unpack(x).decode().strip() if len(x) else None
if _path is None:
tree = etree.ElementTree(activity)
_path = tree.getpath(activity)
key = key if key else secrets.token_hex(16)
# Need to consider Multipart
_flat = flatten(activity)
if _flat:
a_multipart=[]
a_heading = activity.xpath("Heading")
a_heading = flatten(a_heading[0]) if a_heading else None
a_timing = activity.xpath("Timing")
a_timing = flatten(a_timing[0]) if a_timing else None
# Should we perhaps parse the question to markdown?
a_question = _tidy(_delist(activity.xpath("Question")))
a_answer = _tidy(_delist(activity.xpath("Answer")))
"""
# The interaction type is a complex element that should be included
# in an interaction table with a shared reference id generated from
# the unit id and the path to the element
_interaction = _tidy(_delist(activity.xpath("Interaction")))
if _interaction:
interaction_id = create_id( (key, _path) )
a_interaction = interaction_id
else:
a_interaction = None
"""
a_discussion = _tidy(_delist(activity.xpath("Discussion")))
# TO DO - for now, explicitly capture the interaction
a_interaction = _tidy(_delist(activity.xpath("Interaction")))
# TO DO: activity.xpath("MediaElement") as a FK relation?
if activity.xpath("Multipart"):
for p in activity.xpath("Multipart/Part"):
a_multipart.extend(parse_activity_item(p))
if not a_multipart:
# Also return the multipart status and the multipart name and timing
return [(a_heading, a_timing, a_question, a_interaction, a_answer,
a_discussion, _path,
True if a_multipart else False, _path)]
else:
return [(m[0], m[1], m[2], m[3], m[4], m[5], m[6],
True if a_multipart else False, _path) for m in a_multipart if m]
return []
Let’s see how that works:
parse_activity_item( root.xpath("//Activity")[0])
[('Activity 1',
None,
'<Question xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><NumberedList><ListItem>Identify some eLearning materials you are familiar with. You may have written them, taught them, or even studied them (you can think about this course if you have no examples of your own).</ListItem><ListItem>Identify the different elements of the materials – there will almost certainly be text, but are there images, videos, forms or boxes to enter text, drag-and-drop exercises, quizzes etc.? List all of the different types of element.</ListItem><ListItem>For each item on the list try to identify where students with particular disabilities might experience difficulties, and try to suggest possible ways (if you can think of any) that these barriers may be removed. It does not matter if your list is incomplete, or if you cannot think of solutions to some of your identified issues. We will revisit this task later, after you have worked through more of the course materials.</ListItem></NumberedList><Paragraph>Use the box below to record your thoughts.</Paragraph></Question>',
'<Interaction xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><FreeResponse size="paragraph" id="fr_3"/></Interaction>',
None,
None,
'/Item/Unit/Session[1]/Section[6]/Activity',
False,
'/Item/Unit/Session[1]/Section[6]/Activity')]
TO DO - the <Interaction>
element takes several forms: <SingleChoice>
, <Matching>
, <FreeResponse>
, <VoiceRecorder>
, <MultipleChoice>
.
As such, it makes sense to record interaction components in a table of their own, referenced to the parent activity element etc. TO DO
We can also create a table that splits the activity elements out into component parts:
all_activities_tbl = db["activities"]
all_activities_tbl.drop(ignore=True)
all_activities_tbl.create({
"type": str,
"heading": str,
"timing": str,
"question": str,
"answer": int,
"discussion": str,
"interaction": str,
"multipart": bool,
"id": str, # id of unit
"_path": str
})
# For the pk, we need to be able to account for multipart elements
# Currently, each part in multipart element is an entry in this table
# Enable full text search
# This creates an extra virtual table (media_fts) to support the full text search
db[f"{all_activities_tbl.name}_fts"].drop(ignore=True)
db[all_activities_tbl.name].enable_fts(["heading", "question", "interaction",
"answer", "discussion", "id"],
create_triggers=True)
<Table activities (type, heading, timing, question, answer, discussion, interaction, multipart, id, _path)>
Create a simple function to grab all the activities associated with a particular unit:
def get_activity_items(root, typ="Activity", _id=""):
"""Extract activity items from an OU-XML XML object."""
activities = root.xpath(f'//{typ}')
activity_list_raw = []
activity_list = []
for activity in activities:
# Get path to activity within unit
tree = etree.ElementTree(activity)
_path = tree.getpath(activity)
activity_list.extend(parse_activity_item(activity, _path))
activity_list_raw.append( {"activity": unpack(activity), "id": _id,
"type":typ.lower(), "_path": _path} )
return activity_list_raw, activity_list
Create a function to scan the OpenLearn units for various type of activity:
def add_activities_to_db(typ="Activity"):
"""Add activity type elements to the database."""
for row in xml_db.query("""SELECT * FROM xml;"""):
_root = etree.fromstring(row["xml"])
raw_activity_items, activity_items = get_activity_items(_root,
typ=typ,
_id=row["id"])
# From the list of activity items,
# create a list of dict items we can add to the database
activity_item_dicts = [{"heading": a[0], "timing": a[1] , "question": a[2],
"interaction": a[3], "answer": a[4],
"discussion": a[5], "_path": a[6],
"type": typ.lower(),
"id": row["id"]} for a in activity_items if a[2] ]
# Add items to the database
db[all_activities_tbl.name].insert_all(activity_item_dicts)
We can now parse the documents for Activity
type elements:
add_activities_to_db(typ="Activity")
How does that look?
pd.read_sql("SELECT * FROM activities LIMIT 3", con=db.conn)
type | heading | timing | question | answer | discussion | interaction | multipart | id | _path | |
---|---|---|---|---|---|---|---|---|---|---|
0 | activity | Activity 1 Looking at different technologies | Allow approximately 15 minutes | <Question xmlns:xsi="http://www.w3.org/2001/XM... | None | <Discussion xmlns:xsi="http://www.w3.org/2001/... | <Interaction xmlns:xsi="http://www.w3.org/2001... | None | 1f194525072f4358f7639c471ee5289665d50a3f | /Item/Unit/Session[1]/Activity |
1 | activity | Activity 2 Types of body language | Allow approximately 20 minutes | <Question xmlns:xsi="http://www.w3.org/2001/XM... | None | <Discussion xmlns:xsi="http://www.w3.org/2001/... | None | None | 1f194525072f4358f7639c471ee5289665d50a3f | /Item/Unit/Session[5]/Activity |
2 | activity | Activity 3 Translating emoji language | Allow approximately 15 minutes | <Question xmlns:xsi="http://www.w3.org/2001/XM... | None | <Discussion xmlns:xsi="http://www.w3.org/2001/... | None | None | 1f194525072f4358f7639c471ee5289665d50a3f | /Item/Unit/Session[6]/Activity |
And for the raw table?
pd.read_sql("SELECT * FROM activities_raw LIMIT 3", con=db.conn)
type | activity | id | _path | |
---|---|---|---|---|
0 | activity | b'<Activity xmlns:xsi="http://www.w3.org/2001/... | 1f194525072f4358f7639c471ee5289665d50a3f | /Item/Unit/Session[1]/Activity |
1 | activity | b'<Activity xmlns:xsi="http://www.w3.org/2001/... | 1f194525072f4358f7639c471ee5289665d50a3f | /Item/Unit/Session[5]/Activity |
2 | activity | b'<Activity xmlns:xsi="http://www.w3.org/2001/... | 1f194525072f4358f7639c471ee5289665d50a3f | /Item/Unit/Session[6]/Activity |
Parsing Other Activity Types#
An ITQ element can be parsed in the same way as <Activity>
element, as previously described:
add_activities_to_db(typ="ITQ")
pd.read_sql("SELECT * FROM activities WHERE type='itq' LIMIT 3", con=db.conn)
type | heading | timing | question | answer | discussion | interaction | multipart | id | _path | |
---|---|---|---|---|---|---|---|---|---|---|
0 | itq | None | None | <Question xmlns:xsi="http://www.w3.org/2001/XM... | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | None | None | 6bff78840be5165329dda278418bbbd54c909047 | /Item/Unit/Session[1]/Section[2]/ITQ |
1 | itq | None | None | <Question xmlns:xsi="http://www.w3.org/2001/XM... | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | None | None | 6bff78840be5165329dda278418bbbd54c909047 | /Item/Unit/Session[1]/Section[3]/SubSection[1]... |
2 | itq | None | None | <Question xmlns:xsi="http://www.w3.org/2001/XM... | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | None | None | 6bff78840be5165329dda278418bbbd54c909047 | /Item/Unit/Session[1]/Section[3]/SubSection[6]... |
How about exercises?
add_activities_to_db(typ="Exercise")
pd.read_sql("SELECT * FROM activities WHERE type='exercise' LIMIT 3", con=db.conn)
type | heading | timing | question | answer | discussion | interaction | multipart | id | _path |
---|
Or SAQs?
add_activities_to_db(typ="SAQ")
pd.read_sql("SELECT * FROM activities WHERE type='saq' LIMIT 3", con=db.conn)
type | heading | timing | question | answer | discussion | interaction | multipart | id | _path | |
---|---|---|---|---|---|---|---|---|---|---|
0 | saq | None | None | <Question xmlns:xsi="http://www.w3.org/2001/XM... | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | None | None | 6c5ba9c60fa29f546a71ba94565a6f62f1eae0db | /Item/Unit[2]/Session[2]/Section/SAQ |
1 | saq | None | None | <Question xmlns:xsi="http://www.w3.org/2001/XM... | None | <Discussion xmlns:xsi="http://www.w3.org/2001/... | None | None | 6c5ba9c60fa29f546a71ba94565a6f62f1eae0db | /Item/Unit[2]/Session[3]/Section[2]/SAQ[1] |
2 | saq | None | None | <Question xmlns:xsi="http://www.w3.org/2001/XM... | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | None | None | 6c5ba9c60fa29f546a71ba94565a6f62f1eae0db | /Item/Unit[2]/Session[3]/Section[2]/SAQ[2] |
We should also be able to run full-text search questions over all the activity types:
fts(db, "activities", "chemical equation")
heading | question | interaction | answer | discussion | id | |
---|---|---|---|---|---|---|
0 | Part 1 | <Question>\n <P... | <Interaction>\n ... | <Answer>\n <Equ... | None | 904a100e4d41cf1a696b547eec1b2f625fc5bd78 |
1 | Question 9 | <Question xmlns:xsi="http://www.w3.org/2001/XM... | None | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | f36ae6f445e1c39925fa84d3e188af9ed7c15fdc |
2 | None | <Question xmlns:xsi="http://www.w3.org/2001/XM... | None | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | 884164a46f4066c6b26894c812484c74ab2e8531 |
3 | None | <Question xmlns:xsi="http://www.w3.org/2001/XM... | None | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | 884164a46f4066c6b26894c812484c74ab2e8531 |
4 | None | <Question xmlns:xsi="http://www.w3.org/2001/XM... | None | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | 884164a46f4066c6b26894c812484c74ab2e8531 |
5 | None | <Question xmlns:xsi="http://www.w3.org/2001/XM... | None | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | 884164a46f4066c6b26894c812484c74ab2e8531 |
6 | None | <Question xmlns:xsi="http://www.w3.org/2001/XM... | None | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | 884164a46f4066c6b26894c812484c74ab2e8531 |
7 | SAQ 5 | <Question xmlns:xsi="http://www.w3.org/2001/XM... | None | <Answer xmlns:xsi="http://www.w3.org/2001/XMLS... | None | a82aa8fe5c90c02095eefb3e7d998efbbc1949c8 |
We really need a better way to render these results…
Parsing the activity into markdown would be one way…