forked from concord-consortium/sparks
-
Notifications
You must be signed in to change notification settings - Fork 0
/
README.authoring_guide.confluence
888 lines (704 loc) · 46.2 KB
/
README.authoring_guide.confluence
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
Our new breadboard activities all support authoring the entire content from an online authoring system.
The authoring database can be found here: [http://couchdb.cosmos.concord.org/_utils/database.html?sparks]
New activity files can be created quickly, and given an id. That id then instantly becomes a url where you can find the activity you have been authoring. This is designed to be a nice, quick, iterative authoring setup.
If you give an activity the id X, it can be found at [http://sparks.portal.concord.org/sparks-content/activities.html#X]. So, for example, the activity file with the id series-interpretive, for instance, which can be found here: [http://couchdb.cosmos.concord.org/_utils/document.html?sparks/series-interpretive], will instantly create an activity which can be found at [http://sparks.portal.concord.org/sparks-content/activities.html#series-interpretive]
*Note:* We now can author both "Activities" and "Sections" (or "Levels"). One activity is made up of several sections. Most of the information below is about authoring sections. Authoring activities is described at the bottom of this document.
{info:title=Table of Contents}
{toc}
{info}
h1. Section JSON documents
Activity sections (levels) are specified using a JavaScript syntax called JSON. The complete JSON specification can be found at [http://www.json.org/], but here are the basics:
A JSON object is an object that is surrounded by curly braces {tt}{ }{tt} and contains keys and values. Each key must be a string, and each value can be either
* a primitive (A string, a number or a boolean (true/false)
* another JSON object
* an array containing any number of these three things (primitives, JSON and arrays), surrounded by square brackets {tt}\[ \]{tt}.
as you can see, it can seem a little recursive, as you can have a property which is an array of arrays of JSON objects of arrays... but at the end there will always be primitives.
h2. Syntax
The syntax of JSON is simple, but is also easy to mess up.
* All strings must be surrounded by quotes. This includes property keys. If a string had quotes within it, use single quotes on the inside
* Every value or element of an array must end in a comma, *except the last element*.
This last rule may seem strange, but it is for the same reason that we would be confused if someone defined a 3-dimensional location as {tt}(1,5,6,){tt}. It would appear to be missing the last term.
A good syntax check can be found at [http://www.jsonlint.com/]
So here we have a legal JSON document (minus the comments, which are not legal):
{code}
{
"key1": "value", // a string value
"key2": 2, // a number
"key3": { // a JSON obeject
"innerkey1": "value",
"innerkey2": "value"
},
"key4": [ // an array of strings
"array value 1",
"array value 2"
],
"key5": [ // an array of JSON objects
{
"innerkey1": "value",
"innerkey2": "value"
},
{
"innerkey1": "value",
"innerkey2": "value" // note lack of trailing commas
}
]
}
{code}
h1. Starting a new section
To create a new section file in the authoring database, we first create the new database entry, copy-and-paste a bare-bones, syntactically-correct activity, and then add content.
# Click "New Document" from the top-left of [http://couchdb.cosmos.concord.org/_utils/database.html?sparks]
# Modify the id to a useful name. e.g. 'series-interpretive'
# Save the document. You will get an error. This is because CouchDB didn't notice you changed the id, and tried to show you a document with the original randomly-generated id. Ignore this annoyance, and return to the main index. You will see your activity.
# When you open the document, you will see a new property has been added called "_rev." This is the revision number, and allows CouchDB to keep track of edits. This should not be modified
# Paste the following between the \_id and the \_rev:
{code}
// {
// "_id": "my-level",
"title": "My level",
"show_multimeter": "false",
"circuit": [
],
"pages": [
{
"questions":
[
{
"prompt": "Question 1"
},
{
"prompt": "Question 2"
}
]
}
],
// "_rev": "xyz123"
// }
{code}
As you type, clicking off of the document will cause it to validate itself, which is a quick way to know if your syntax is correct.
Save the document. (Save early and often\!) Visit it at [http://sparks.portal.concord.org/sparks-content/activities.html#my-level]
h1. Level properties
The following is a list of all the top-level properties a level may have. Each property is described in more detail below:
|| Property || ||
|| circuit | The circuit model used in the level ||
|| image | An image displayed above the questions ||
|| pages | The list of pages containing questions and notes ||
|| hide_circuit | See _Showing and hiding Flash elements_ ||
|| show_multimeter | Adds a DMM to the circuit. ||
|| show_oscilloscope | Adds an oscilloscope to the circuit. Only one of the previous two properties may be set. ||
|| allow_move_yellow_probe | Allows the yellow probe that is normally stuck to the positive rail to be moved by the student ||
|| disable_multimeter_position | See _Showing and hiding Flash elements_ ||
|| referenceFrequency | Frequency, in Hz, to be used when calculating inductance or capacitance of reactive components from a desired impedance. ||
h1. Defining the circuit
The circuit is defined as an array of components. Currently we can author resistors, capacitors, inductors, and the power source
{code}
"circuit": [
{
// component 1
},
{
// component 2
}
]
{code}
Each component contains a minimum of two properties: a type and a pair of connections. The following is a list of properties that circuit components may have:
|| Component type || Property || Values ||
|| All | type (required) | wire \\
resistor \\ capacitor \\ inductor \\ battery \\ function generator ||
| | connections (required) | holeName1,holeName2 \\
e.g. a1,b20 \\
left_positive20,a23 \\
left_negative5,b5 |
| | label | A label that will be shown when the user mouses over the component. Max two characters? |
| | UID | A unique ID for referring to this component elsewhere ||
|| *Resistors* | resistance (in ohms) | If specified, the resistor will have this exact resistance. Nominal resistance (to be called rated resistance later) is calculated automatically unless otherwise specified. You can also request a random value using a 3-element array whose first element is the string "uniform". Where possible, a table of "sensible" resistor values (expanded to span the needed orders of magnitude) for the given tolerance will be used, and randomly selected from.
\\
\\ Examples:
\\ {tt}1000{tt} -- resistance should by 1000 Ohm exactly.
\\ {tt}["uniform", 700, 100000]{tt} -- impedance should be a randomly chosen, sensible value between 700 Ohm and 100,000 Ohm. (If the {tt}tolerance{tt} is 0.05, the list of "sensible values" will be 750, 820, 910, 1000, 1110, ..., 82000, 91000 Ohm.) |
| | nominalResistance | (To be called ratedResistance.) Rated resistance. If actual resistance (above) is not specified, will be randomly generated within the tolerance of the rated resistance |
| | colors | An array of band colors, e.g. {tt}["red","blue","green","gold"]{tt}. Equivalent to specifying nominalResistance |
| | tolerance | _To be added_ |
| | resistance constrained by other resistors | _to be added_ |
|| *Capacitors* | capacitance | The exact capacitance of this capacitor, in Farads. *To access this value in a question script, use {tt}.getCapacitance(){tt} rather than {tt}.capacitance{tt}* |
| | impedance | The desired impedance of this capacitor at its {tt}referenceFrequency{tt}, in Ohms. If the {tt}capacitance{tt} property is not set on this capacitor, it will be calculated from the impedance. You can specify an exact value (as a number) or request a random value using a 3-element array whose first element is the string "uniform".
\\
\\ Examples:
\\ {tt}1000{tt} -- impedance should by 1000 Ohm at the {tt}referenceFrequency{tt}
\\ {tt}["uniform", 100, 1000]{tt} -- impedance should be a randomly chosen value between 100 Ohm and 1000 Ohm, at the {tt}referenceFrequency{tt} |
| | referenceFrequency | When specifying a desired impedance instead of an exact capacitance value, this is the frequency in Hz at which the capacitor should have that impedance. This property does not need to be explicitly set on the capacitor, as the top-level {tt}referenceFrequency{tt} will be used if not overridden by defining it here. |
|| *Inductors* | inductance | The exact inductance of this inductor, in Henries. *To access this value in a question script, use {tt}.getInductance(){tt} rather than {tt}.inductance{tt}* |
| | impedance | The desired impedance of this inductor at its {tt}referenceFrequency{tt}, in Ohms. If the {tt}inductance{tt} property is not set on this inductor, it will be calculated from the impedance. You can specify an exact value (as a number) or request a random value using a 3-element array whose first element is the string "uniform".
\\
\\ Examples:
\\ {tt}1000{tt} -- impedance should by 1000 Ohm at the {tt}referenceFrequency{tt}
\\ {tt}["uniform", 100, 1000]{tt} -- impedance should be a randomly chosen value between 100 Ohm and 1000 Ohm, at the {tt}referenceFrequency{tt} |
| | referenceFrequency | When specifying a desired impedance instead of an exact inductance value, this is the frequency in Hz at which the inductor should have that impedance. This property does not need to be explicitly set on the inductor, as the top-level {tt}referenceFrequency{tt} will be used if not overridden by defining it here. |
|| *Batteries* | voltage (in volts) | Can be either a specific voltage, or a range. A range is specified as an array. E.g.: \\ 9 \\ {tt}[8.0, 9.1]{tt} _-- A random voltage selected between these 8V and 9.1V_ \\ \\ *Note:* See the section _Defining the power source in a circuit_ for usage |
|| *Function generators* | amplitude | The peak amplitude, in volts. Can be a plain number or an array representing a range of values. Examples \\ {tt}10{tt} _-- A single peak amplitude of 10V_ \\ {tt}[0, 10]{tt} _-- A range of amplitudes from 0 to 10V, initially set at 5V (halfway)_ \\ {tt}[10, 100, 20]{tt} _-- A range from 10 to 100V, initially set at 20V_|
| | frequencies | An array representing a set of possible frequencies that the generator can produce. Examples: \\ {tt}[1000]{tt} _-- A single frequency of 1KHz. Note that it must be an array_ \\ {tt}[1000, 2000, 5000]{tt} _three possible frequencies_ \\ {tt}["linear", 1000, 10000, 5]{tt} _-- A range of frequencies from 1 KHz to 10 KHz, in 5 linear increments_ \\ {tt}["logarithmic", 1000, 1e6, 20]{tt} _-- A range of frequencies from 1 KHz to 1 MHz, in 20 logarithmic increments_ \\ \\ *Note:* See the section _Defining the power source in a circuit_ for usage |
| | initialFrequency | _Optional._ The initial frequency setting of the function generator. If this value is set, the frequency generator will initially be set to that frequency, in the set of frequencies specified by {tt}frequencies{tt}, that is closest to {tt}initialFrequency{tt} |
h2. An example circuit
The following in an example circuit with three resistors in series. This example should help see how to use the list of properties defined above to create your own circuits:
{code}
"circuit": [
{
"type": "wire", // the type of this first component, in this case a wire
"connections": "left_positive20,a23" // the holes this component connects to
},
{
"type": "resistor", // a second component, this time the type is "resistor"
"UID": "r1",
"connections": "b23,b17",
"label": "R1",
"resistance": 2000 // here the author is specifying the actual resistance.
},
{
"type": "resistor",
"UID": "r2",
"connections": "c17,c11",
"label": "R2",
"nominalResistance": 1000 // here the author has chosen to specify the nominal resistance (colors)
},
{
"type": "resistor", // neither resistance nor nominalResistance has been specified here, so it will be randomized
"UID": "r3",
"connections": "d11,d5",
"label": "R3"
},
{
"type": "wire",
"connections": "left_negative3,a5"
}
]
{code}
h2. Defining the power source in a circuit
An author can set the power source of a circuit to be either a DC battery or an AC function generator using the "battery" or "function generator" components defined in the components table above. In order for the application to understand that this component is being designated as the primary power source (connected to the power rails), this component must be given the UID *"source"*.
Any circuits created without a power source with the UID "source" are given a default 9V battery as their source.
A power source is added to a circuit just like any other component. However, as it is assumed to be connected to the power rails, it does not need to specify its connections. See the components table above to see all the properties a battery or function generator may have.
Examples:
{code}
"circuit": [
{
"type": "battery", // adding a battery as the breadboard power source
"UID": "source", // we *must* define the UID as "source"
"voltage": [8.5, 9] // setting the voltage to be between 8.5 and 9 (random)
},
{
"type": "wire",
"connections": "left_positive20,a23"
},
{
"type": "resistor",
"UID": "r1",
"connections": "b23,b17",
"label": "R1",
"resistance": 2000
},
{
"type": "wire",
"connections": "left_negative3,a5"
}
]
{code}
{code}
"circuit": [
{
"type": "function generator", // adding an AC function generator as the breadboard power source
"UID": "source",
"amplitude": 10, // 10V peak amplitude
"frequencies": [1000] // the frequencies array with only a single value specified
},
{
"type": "wire",
"connections": "left_positive20,a23"
},
{
"type": "resistor",
"UID": "r1",
"connections": "b23,b17",
"label": "R1",
"resistance": 2000
},
{
"type": "wire",
"connections": "left_negative3,a5"
}
]
{code}
Note: Batteries and function generators are "first-class components" which, like resistors or capacitors, could theoretically be added to the circuit anywhere by an author. This is not recommended, however, as there is no visual representation of batteries or function generators in Flash, besides the one attached to the rails.
h2. Defining faults in a circuit
SPARKS contains a very generalizable system for creating faults in a circuit. An author can
* specify specific faults for specific components
* specify specific faults for any number of randomly-selected components
* specify specific faults for any random number of components (up to an authored max)
* specify random faults for any of the above (i.e. picking a random fault for each resistor)
Currently this system is limited to creating "open" or "shorted" faults on resistors (max resistance and min (shorted) resistance respectively). However, the system is flexible enough that as we come up with new ways to break the circuit we can add them in easily to the same system.
Faults are defined in a new "faults" array, typically defined right after the circuit definition. The application always creates the circuit first, generating appropriate resistor values etc., and then applies the faults.
{code}
"circuit": [...],
"faults": [...],
{code}
Examples in this case may be quicker to understand than a property table. The property table is below, but here are some example faults:
{code}
// creates an "open" fault on R1 and a "shorted" fault on R2
"faults": [
{
"type": "open",
"component": "r1"
},
{
"type": "shorted",
"component": "r2"
}
]
{code}
{code}
// creates an "open" fault on one random resistor
"faults": [
{
"type": "open",
"count": 1
}
]
{code}
{code}
// creates faults on two random resistors, randomly choosing "open" or "shorted" for each
"faults": [
{
"type": ["open", "shorted"],
"count": 2
}
]
{code}
{code}
// creates a shorted fault on anywhere from 1 to 5 resistors, and an
// open fault on R2
"faults": [
{
"type": "shorted",
"max": 5
},
{
"type": "open",
"component": "r2"
}
]
{code}
As you can see, you can have multiple faults defined, and each fault can affect multiple resistors.
|| Property || Meaning || Possible values ||
|| type | Type of fault | "open", "shorted" ||
|| | Array of any of the above. Randomly chooses from the array independently for each component | {tt}\[...\]{tt} ||
|| component | Specific component to apply this to | "r1" etc. ||
|| count | Number of components to apply this to. Specific components will be randomly chosen | 1\+ ||
|| max | Maximum number of components to apply this to, randomly chosen between 1 and max | 1\+ ||
h2. Showing and hiding Flash elements
The circuit as defined above represents the underlying _Javascript model_ of the circuit. Typically, this will then be rendered by Flash and displayed to the student, along with a DMM. However, there are certain section-level properties that can control what the student sees:
|| Property || Meaning || Possible values, default if not specified ||
|| hide_circuit | If *true*, Flash circuit will not appear | true, false (Default=false) ||
|| show_multimeter | If *true*, DMM is available for the student | true, false (Default=false) ||
|| show_oscilloscope | If *true*, the oscilloscope is available for the student. (Cannot be combined with the above) | true, false (Default=false) ||
|| disable_multimeter_position | Section of the DMM to be disabled | Any one of "r,dcv,acv,dca,diode,hfe,c_10a,p_9v" (Default=none) ||
h1. Images
Images can be references in two places: at the section level, and at the question level.
{code}
{
"title": "My level",
"image": "http://...", // this image will be at the top of every page
"pages": [
{
"questions":
[
{
"prompt": "Question 1"
},
{
"image": "http://...", // this image will be displayed above this question
"prompt": "Question 2"
}
]
}
],
}
{code}
h2. Attaching images to the document
Images can be attached directly to the authoring document in CouchDB, and then referenced by name only. This makes it very easy to add new images.
# Give your file an easy-to-understand name. We'll assume "series-circuit.jpg"
# From the CouchDB authoring page, click the *Upload attachment* link at the top
# Select the file and upload it.
# Refer to the image in the document by its name, e.g. "series-circuit.jpg"
h1. Pages
A single Sparks section consists of one circuit (or main image) and several internal "pages" of questions. Each page consists of a few questions, and as the student moves through each page within a section the main circuit will stay the same.
After a student completes a page, they immediately have their answers graded, and they see a mini-report of the questions on that page. At this point, they have the option of moving on to the next page (or section), or repeating the page again.
If they choose to repeat the page, the circuit will reload, and may contain new values for resistors or other components. This new circuit will also be used for any subsequent pages (unless the student chooses to repeat yet again).
h2. Defining pages
Pages are defined in an *array*, and each page is a *JSON object* containing questions, optional notes for the student, and optional points for time.
{code}
"pages": [
{ // page 1
"questions": [
{
//question 1
},
{
//question 2
}
],
"notes": "This message will appear on this page for students"
"time": { ... } // see below
},
{ // page 2
"questions": [
{
//question 1
},
{
//question 2
}
],
"notes": "This message will appear on this page for students after they turn the page"
}
]
{code}
Note that even if you only wish to have one page of questions, it is still necessary to define the array of pages - the array would just have one page in it. An example of a one-page level can be found in the section *Starting a new section* above.
h2. Notes
Anything in the "notes" property of the page will show up in a box to the right of the questions (we can play with layout later if necessary). Notes can contain any plain text, HTML, and will also perform circuit calculations, to allow authors to use variables from the current circuit. For information on using calculations, please see the section *Calculated answers with circuit variables* below.
Example note:
{code}
"note": "The value of R1 is [${r1.nominalResistance}] ohms. <br/> The value of R2 is [${r2.nominalResistance}] ohms."
{code}
h2. Time
An author can specify that a student should gain points for completing a page in a certain amount of time. The author can specify the "best" time to complete it in, in which case they score full points, and the "worst" time to complete it in, in which case they score zero bonus points. The points decline linearly between the two times.
Example:
{code}
"time": {
"best": 60,
"worst": 120,
"points": 5
}
{code}
h1. Questions
Each page contains an array of questions. All questions (at the moment) are automatically graded as soon as a student submits their answer, and are tabulated in a report at the end of each page.
Most questions have a specific "correct" answer. In multiple-choice questions, this correct answer is defined _implicitly_ by the score assigned to that response. For an open-response question, the correct answer must be defined _explicitly_ in the question.
h2. Defining questions
The questions definition is an _array_ of questions, and each question may optionally contain an _array_ of subquestions.
The example below shows the syntax using plain open-response questions (which are simpler) for clarity.
{code}
"questions": [
{
"prompt": "What is the answer to question 1?",
"correct_answer": "The answer"
},
{
"prompt": "What is the answer to",
"subquestions": [
{
"prompt": "question 2?",
"correct_answer": "Answer 2"
},
{
"prompt": "question 3?",
"correct_answer": "Answer 3"
}
]
},
{
"prompt": "What is the answer to question 4?",
"correct_answer": "The answer",
"category": "Questions about the number 4"
}
]
{code}
Each "subquestion" is actually a unique question, and is graded as if it were its own question. Visually, however, a subquestion is nested under an outer prompt, and a group of subquestions has only one submit button. So the page above would look like
{noformat}
1. What is the answer to question 1? [________] {submit}
2. What is the answer to
question 2? [________]
question 3? [________] {submit}
3. What is the answer to question 4? [________] {submit}
{noformat}
h3. Question properties
By default, all questions are open-response. That is, if the only thing specified is a prompt, the question will be styled with an input box after the prompt. The two bottom properties in this list are only used if no "options" are set, and will be ignored for multiple-choice questions.
|| Object || Property || Values ||
|| Question | prompt (required) | The question being asked ||
| | shortPrompt | An optional summary of the prompt for use in reports. Particularly useful for subquestions, where a subquestion prompt may be nothing more than "R1?," the shortPrompt could be "Resistance of R1." |
| | options | A list of options, containing the choices and the points and feedback associated with each choice. Described below. |
| | radio/checkbox/ keepOrder | *Only for multiple-choice questions*. Described below. |
| | correct_units | If specified, a units pull-down will appear. Note, only the "unit type" needs to be specified, e.g. 'V', 'A', 'ohms.' More on units below. |
| | tutorial | A link to the appropriate tutorial: a button will show in the report table if the question is answered incorrectly |
| | correct_answer | *Only for open-response questions*. If a correct answer is specified, an exact match will be scored as correct. |
| | points | *Only for open-response questions*. Max points for this question, given to student if answer is correct |
| | category | *deprecated* Marks the question as being a member of a category, for reporting back to the student and teacher. *NOTE*: Now just setting the tutorial will automatically set the category |
| | show_read_multimeter_button | Adds a "read multimeter" button, instead of a text box, that reads the multimeter into the answer box. If the circuit is an AC circuit, the frequency and amplitude of the source are additionally (but invisibly) recorded as part of the answer and are available to question scripts; see below. |
| | scoring | A script used to grade a question and provide feedback. See *Question scoring scripts* below |
| | beforeScript | A script run when the question is first enabled. See *Question before scripts* below |
| | meta | An object that may contain more information about the answer for author scripts. Not settable by author, it is instead filled when a user answers a question. See *The question meta object* below |
h3. Multi-choice questions
Multi-choice questions have the advantage of being easier to score and easier to provide feedback for. Since we make it possible to provide partial credit, each possible answer can have points specified. In a report, the answer is shown as being correct if it is the answer with the highest possible points.
Each option is a JSON object with the option, optional feedback for picking that option, and optional points for picking that option.
|| Object || Property || Values ||
|| Question | options | An array of options ||
| | radio | if "true," question will shown as radio buttons. If omitted, options will be pull-down list. |
| | checkbox | if "true," question will shown as check boxes (multichoice). If omitted, options will be pull-down list. |
| | keepOrder | the order of the options is randomized by default. If keepOrder is true, the order will not be randomized |
|| Options | option | The option the student sees ||
| | points | Points given to the student for answering that option. Zero if omitted |
| | feedback | Feedback shown to the student for answering that option. Nothing if omitted |
| | tutorial | Overrides the "tutorial" specified at the question-level for this option |
Example:
{code}
{
"prompt": "What is the answer?",
"options": [
{
"option": "A",
"points": 5, // maximum points, so this answer is considered correct
"feedback": "Good job!"
},
{
"option": "B",
"points": 1,
"feedback": "That's not quite right..."
},
{
"option": "C",
"feedback": "Did you even read the material?",
"tutorial": "finding_the_answer.html"
}
]
}
{code}
h2. Calculated answers with circuit variables
Often you want an answer to use the values of the components in the specific circuit the user is viewing. For this purpose, we have a special calculation syntax that can be used both as a multi-choice option and as a correct_answer.
Anything in square brackets {tt}\[ \]{tt} will be run through the script parser. A script can be a simple calculation. So
{code}
"option": "Ten is [5*2]"
{code}
Will be displayed as "Ten is 10." Likewise, {tt}"correct_answer": "\[(5/2) * 10\]"{tt} will score a question correct only if the student answers "25".
*Circuit variables* are accessed in the script simply by referring to the component's UID. Each component in the circuit is accessible in the script, along with all it's properties: {tt}uuid.property{tt}. Again, it must by between square brackets {tt}\[ \]{tt} to be processed be the script parse. So
{code}
"option": "[r1.resistance]"
{code}
will display the resistance of r1. Similarly, "{tt}r2.nominalResistance{tt}" would give you the nominalResistance of r2. Any numerical property from the circuit component properties defined above may be used.
Math and variables can be freely mixed: variables will be converted and treated as numbers. So "{tt}\[2 * r2.nominalResistance\]{tt}" will give you twice the nominal resistance of r2.
The code between square brackets {tt}\[ \]{tt} can, in fact, be a complete script. See more about this in the *Scripting* section below.
h3. Units
Adding a unit such as "V", "A" or "ohms" to the end of an option or correct_answer will immediately cause the value to be converted to engineering format.
So if you specify
{code}
"option": "[r1.resistance] ohms"
{code}
The value will be displayed in ohms if the number is between 0 - 1000, kiloohms if the number is between 10^3 - 10^6, etc.
Note: for now this is the default. If we have a reason that the author needs to show "10,000 ohms" in the dropdown box, we can make this an option. For now, if the author specifies 10000 ohms as an option, it will be automatically converted to 10 kiloohms.
h3. More math functions
The parsing code (which parses everything in an answer between square brackets {tt}\[ \]{tt}) has access to the entire JavaScript library, including the JavaScript Math object: [http://www.w3schools.com/jsref/jsref_obj_math.asp]. The Math object has been extended with a couple other useful functions, {tt}Math.log10(x){tt} and {tt}Math.powNdigits(x,n){tt}.
Some other useful functions include {tt}Math.max(a,b,c...){tt} to return the maximum of _n_ values, {tt}Math.round(a){tt} to round a value to the nearest integer, and {tt}Math.random(){tt} to get a random floating-point number between 0 and 1.
Some examples of this in use:
{code}
"option": "[Math.max(r1.resistance,r2.resistance)] ohms" // if r1=100 and r2=200, this statement would resolve to "200 ohms"
"option": "[Math.round(100 * Math.random())]" // returns a random integer between 0 and 100
"option": "[r1.resistance * Math.sqrt(r2.resistance)]" // returns r1 times the square root of r2 (who knows why...)
"option": "[Math.ceil(Math.log10(r1.resistance))]" // the number of digits in the resistance of r1 (Math.ceil rounds up)
{code}
Some math functions were added to Sparks that would be particularly helpful to authors:
{code}
Math.log10(x)
Math.powNdigits(x,n) -- not really sure what that does. It's equivalent to: Math.pow(10,Math.floor(Math.log(x)/Math.LN10-n+1))
// The following use the cMath (circuit Math) object for dealing with circuit variables
cMath.rSeries(x,y,z,...) // will calculate the series resistance of the named resistors. So cMath.rSeries("r1","r2") will add the resistances of r1 and r2. You can have unlimited named resistors
cMath.rParallel(x,y,z,...) // will calculate the parallel resistance of the named resistors.
cMath.rNominalSeries(x,y,z...)
cMath.rNominalParallel(x,y,z...) // will do the same for the nominal resistances
cMath.vDiv(x,y) // will calculate the proportion of the voltage across resistor x, if x and y are in series.
{code}
h2. Question categories
All questions can have categories assigned to them. The reports that are shown to a student will show the student the percentage of questions in these categories they have answered correctly. Unlike the regular scoring, these percentages take into account questions answered incorrectly, so if a student answers an "Understanding breadboards" question incorrectly on page 1, and then repeats the page and gets it right, the table will show them as having answered 50% of "Understanding breadboards" questions correctly.
Note that categories are just strings, and if two such string differ, even just by case, then they will be counted as two different categories.
Example:
{code}
"questions": [
{
"prompt": "What is the answer to question 1?",
"correct_answer": "The answer",
"category": "Intro questions"
},
{
"prompt": "What is the answer to question 2?",
"correct_answer": "The answer",
"category": "Hard questions"
}
]
{code}
h2. Question scoring scripts
All questions can use authored scripts to score answers, instead of relying on the Sparks application's inbuilt scoring system (i.e. "correct_answer" and point values for options). If a question contains a script, the script will be run when the report is generated and no other scoring or processing will be done. This means that it is up to the author to manually set the student's score, the correct answer, tutorial buttons etc, from within the script.
Scripts are written in JavaScript, and are added to the "scoring" property of a question.
*A very simple script:*
Here is a trivial script which an author would never use (as it could be done by other means), but should be illustrative of the script style.
{code}
{
"prompt": "What is 1 + 1?",
"points": 10,
"scoring": "if (question.answer == 2) {question.points_earned = 10}"
}
{code}
Here we see three things:
* The script has access to the *question* object, which is defined below. Furthermore, the question object has already had it's "answer" property (i.e. the student's answer) set by the system, so it can use it to score points.
* The script can set properties on the question object, such as the points_earned. This will be the score the student earns.
* If the points_earned is equal to (or greater than) the point-value of the question (set above the script), the question will be considered correct.
*Access to the circuit*
Along with the question object, the script also has access to the circuit, using the same r1, r2 variables defined earlier. Using this, we can modify our question above:
{code}
{
"prompt": "What is the rated resistance of R1?",
"points": 10,
"scoring": "if (question.answer == r1.nominalResistance) {question.points_earned = 10}"
}
{code}
Again, this isn't a script that an author would probably write, as there are simpler ways of scoring this simple question.
Finally, multiple-choice questions can be treated exactly the same way as the open-response questions above. question.answer will simply be set to whatever answer they picked (as a string). Using this, we can create a very simple script for a basic faulty circuit question:
{code}
{
"prompt": "One of these resistors is faulty and is allowing no current through it. Using the fewest number of measurements, can you work out which it is?",
"points": "10",
"options": [
{
"option": "R1"
},
{
"option": "R2"
},
{
"option": "R3"
},
{
"option": "R4"
}
],
"keepOrder": true,
"scoring": "if (question.answer.toLowerCase() === breadboard.getFault().UID) {question.points_earned = 10} question.correct_answer = breadboard.getFault().UID"
}
{code}
the script is, unfortunately, all on one line. To make it easier to follow, I will reproduce it with more typical line spacing:
{code}
if (question.answer.toLowerCase() === breadboard.getFault().UID) { // check if the student got the answer right
question.points_earned = 10 // if so, award full marks
}
question.correct_answer = breadboard.getFault().UID // set correct_answer, so that this shows up in the student's report
{code}
Here, we create a variable called badResistorName. We then set this variable to "R1" if the bad resistor is r1, etc. Finally, we check to see if the student's answer was the badResistorName, and, if so, award full points. (If we do not set points_earned, it will be zero. In this case, if question.answer is not equals to badResistorName, no points will be earned.) We also set question.correct_answer to be the badResistorName.
Of course, there were numerous ways to do this. Would also have made a more complicated if-statement: {tt}if (question.answer == "R1" && ${r1.resistance} > 1e12) { question.points_earned = 10 } else if ....{tt}.
h3. The question object
The question object in the Sparks application contains all the information needed to display a question and make a report: the prompt, the point-value, the correct_option (sometimes), and, after the question has been graded, the points earned, feedback, tutorials to be displayed and so on. You can set any property you want, which is, of course, dangerous: if you set question.answer, for instance, you will be changing the student's actual answer.
The following are the properties that might be relevant to a script author:
|| Property || Meaning ||
|| answer | The answer the student made - either the open response they typed in or the choice they selected, or the multimeter reading. If the multimeter reading was made in an AC circuit, {tt}answer{tt} will be an *object* with properties {tt}reading{tt}, {tt}frequency{tt}, and {tt}amplitude{tt}; see below. ||
|| points | The maximum points this question is worth ||
|| points_earned | The points the student scored for this question. If equal to 'points', the question is marked correct ||
|| feedback | The feedback to be displayed (won't show if the question is answered correctly) ||
|| tutorial | The link to the tutorial button ||
|| meta | An object containing additional information. See below ||
h4. The question meta object
Whenever a student answers a question, additional information may be saved in the question.meta object, such as the state of the circuit when the user hit "submit".
|| Property || Meaning ||
|| question.meta.frequency | The frequency of the power source when the user hit submit (AC only) ||
|| question.meta.amplitude | The amplitude of the power source when the user hit submit (AC only) ||
|| question.meta.dmmDial | The current DMM dial setting as a string ||
|| question.meta.oscopeScaleQuality | The "quality" of the current OScope scale, from 0 to 1 ||
|| question.meta.val | Used for multimeter-button readings (below) ||
|| question.meta.units | Used for multimeter-button readings (below)||
h4. Using the multimeter button
If the question used the "Read Multimeter" button (by setting the property {tt}show_read_multimeter_button{tt} of the question object to true), the user will be shown a button allowing them to directly enter the value from the multimeter. The question's answer property will be set to a string representing what was seen on the DMM (e.g. "20 mA"). However, just like parsing an answer (see Parsing written answers), the question will also save two additional properties in the meta object: question.meta.val will be the actual value of the reading at the base SI unit, and question.meta.units will be the units.
So if a student presses "Read Multimeter" when the DMM is showing "20 mA", the following properties will be saved:
|| Property || Value given input of "20 mA" ||
|| question.answer | "20 mA" ||
|| question.meta.val | 0.02 ||
|| question.meta.units | "A" ||
h3. The log object
Scripts have access to the student log, and can get the current log through the helper variable *log*. The following methods are available:
|| Method || ||
|| measurements() | total number of measurements (inc. dial spinning) ||
|| uniqueVMeasurements() | unique voltage measurements ||
|| uniqueIMeasurements() | ... current ||
|| uniqueRMeasurements() | ... resistance ||
|| connectionBreaks() | number of times student broke a connection (lifted a lead) ||
|| connectionMakes() | replaced a lead ||
|| blownFuses() | number of times student blew the fuse ||
h3. Parsing written answers
Two in-built script functions are available to help authors decipher and score hand-written answers, "parse" and "close".
*parse(question.answer)* will take the student's answer and return an object. That's object's *val* is the parsed value, and *units* is the parsed units.
*close(num1, num3, optionalPercent)* will return *true* if num1 is close to num2. By default, closeness is considered to be within 5%. You can optionally put in a third parameter, so *close(100, 120, 20)* will return true, because 120 is within 20% of 100.
Example:
{code}
var parsedAnswer = parse(question.answer);
var valueCorrect = close(parsedAnswer.val, 5010);
if (valueCorrect) {
question.points_earned = 10;
}
{code}
h3. Other useful functions for scripts
Besides the functions "parse" and "close" described above, there are a number of other specialized functions that may be useful to script authors.
|| Area || Function || What it does || Example ||
|| Parsing, measurements and units | sparks.unit.convertMeasurement(string) | Converts a measurement (as a string) to engineering notation if possible | ...convertMeasurement("0.0045 A") -> "4.5 mA" |
|| | sparks.unit.toEngineering(value, units) | Converts a measurement (as a value and a string unit) to an object in engineering notation | ...toEngineering(0.0045, "A") -> {tt}{value: 4.5, units: "mA"}{tt} |
|| Math | _See the section_ More math functions _above_ |
h2. Question before scripts
The "beforeScript" in a question is run the moment that question is enabled. So for the first question on a page, it is run immediately when the user flips to that page, and for later questions on the page it is run when the user hits "Submit" on the question above.
You can do anything in these scripts, but two common actions are showing or hiding the DMM or OScope:
{code}
{
"prompt": "Look, now the DMM has appeared! What is the rated resistance of R1?",
"beforeScript": "sparks.sectionController.setDMMVisibility(true)",
},
{
"prompt": "Awww... now the OScope has gone!",
"beforeScript": "sparks.sectionController.setOScopeVisibility(false)",
}
{code}
h1. General scripting tips and strategies
h2. Global variables
All scripts can set global variables using the sparks.vars.*X*, where x is any variable name.
Note the order that scripts are executed in: All question options are read first, one after the other, and any scripts executed. Then the Notes is read and any scripts executed. Then, after the student has subitted all the answers on a page, the scoring scripts are executed. If a variable is defined in a late-executed script, it can't be used in an earlier script.
Example:
{code}
"questions": [
{
"prompt": "What is the resistance of R1?",
"correct_answer": "[sparks.vars.a = 10/2; sparks.vars.a]",
"correct_units": "ohms"
},
{
"prompt": "What is the resistance of R1?",
"correct_answer": "[sparks.vars.a]",
"correct_units": "ohms"
}
]
{code}
h2. Finding faults
Quickly finding circuit faults is possible in any script.
|| Method || ||
|| breadboard.getFault() | returns the 1st faulty resistor in the circuit, or only fault is there is only one ||
|| breadboard.getFaults() | returns the array of all the faults. breadboard.getFaults()[0] gives you the first (as above); breadboard.getFaults()[1] gives you the second, etc.; breadboard.getFaults().length gives you the total number of faults. ||
the resistor objects returned by the functions above are exactly the same as r1, r2 above. So you can say {tt}breadboard.getFault().UID{tt} for the id, {tt}breadboard.getFault().resistance{tt}, etc.
You can now check if a resistor is open or shorted quickly. {tt}r1.open{tt} and {tt}r1.shorted{tt} return true or false. Likewise, {tt}breadboard.getFault().open{tt}, or {tt}breadboard.getFaults()\[1\].shorted{tt} will tell you what type of fault the known-bad-resistors 1 and 2 are.
h2. Logging to the console
From within any script, you can log messages to the Javascript console (visible on Chrome through View \-> Developer \-> Javascript Console) using {tt}console.log(X){tt}. Not only can you use this to log variable values (e.g. {tt}console.log("The res of R1 is " + r1.resistance){tt}), but if you log an object alone it will display itself in the console in such a way that you can open it up:
|| console.log(r1) | Print (and open up) the object representing R1 ||
|| console.log(log) | Print the current log ||
h1. Defining an activity
Sparks activities are made up of multiple sections (levels). A given section can be used in multiple activities.
To define an activity, you can create a new JSON document in the Couch Database, and give it three simple properties: *type: activity*, to distinguish it from sections, a *title*, and *sections*, an array of the ids of the sections you want to use:
{code}
{
"_id": "series-resistances",
"type": "activity",
"title": "Series Resistances",
"sections": [
"series-a",
"series-b",
"series-c",
"series-d",
"series-e",
"series-f"
]
}
{code}
Just as with levels, ff you give an activity the id X, it can be found at [http://sparks.portal.concord.org/sparks-content/activities.html#X].