module TestTemplate; from Questions import Class; from Questions import LastUsed; from Questions import Time; from Questions import Type; from Questions import Question; from Questions import Author; from Questions import QKeyword; from Questions import Difficulty; from Questions import QuestionName; from Database import LocalDatabase; from Database import Database; from Tests import Test; from Tests import QuestionGroup; object TestTemplate is components: TestTemplateItem*; description: (* A TestTemplate any number of Groups and Questions in an ordered List *); end TestTemplate; object TestTemplateItem is components: g:Group or q:Question; description: (* A test template item is either a Group or a specific Queestion *); end TestTemplateItem; object Group is components: c:criteria*, t:GroupTime, q:NumberOfQuestions, d:GroupDifficulty; description: (* A Group is comprise of a list of criteria, a time that the Group should take to complete, the number of questions in the Group, and a specification of the variability of the difficulty of the questions in the Group *); end Group; object criteria is components: a:QuestionAttribute, v:QuestionAttributeValue; description: (* A criteria is a QuestionAttribute and the value that the given attribute must assume to vailidate the criteria *); end criteria; object GroupTime is integer; object NumberOfQuestions is integer; object GroupDifficulty is components: one:all1 or two:all2 or three:all3 or four:all4 or five:all5 or inc:Increasing or random:Random; description: (* The specification of of the difficulty variability in a group *); end GroupDifficulty; object Text is string; object Keywords is string; object all1 is boolean; object all2 is boolean; object all3 is boolean; object all4 is boolean; object all5 is boolean; object Increasing is boolean; object Random is boolean; object QuestionAttribute is components: c:Class or t:Type or k:Keywords or a:Author; description: (* The possible attributes of a question, NOTICE, this is the name of the attribute, NOT its value. *); end QuestionAttribute; object QuestionAttributeValue is components: string*; description: (* The desired value(s) for Class, Type, Keywork, or Author. *); end QuestionAttributeValue; operation CreateGroup inputs: c:criteria*, t:GroupTime, q:NumberOfQuestions, d:GroupDifficulty; outputs: g:Group; precondition: (* * The criteria list is not empty. The time is non-negative. * The number of questions is non-negative. The GroupDifficulty is defined. *) (c != nil) and (t > 0) and (q > 0) and ( (d.one = true) or (d.two = true) or (d.three = true) or (d.four = true) or (d.five = true) or (d.random = true) or (d.inc = true) ); postcondition:(* * The inputs become the contents of the output *) (g.c = c) and (g.d = d) and (g.t = t) and (g.q = q); description: (* Creates a group given criteria, time, number of questions, and difficulty *); end; operation CreateTemplate inputs: l:TestTemplateItem*; outputs: t:TestTemplate; precondition: (* * The list of TestTemplateItems is not empty. *) (l != nil); postcondition:(* * The TestTemplate is now equal to the list of TestTemplateItems. *) (t = l); description: (* Creates a template given a list of TestTemplateItems *); end; operation SelectQuestions inputs: i:TestTemplateItem, l:Database; outputs: qg:QuestionGroup; precondition: (* * The Database has enough questions of the given criteria. *) (if (i.g != nil) then AreEnough(l, i.g.c, i.g.q); ) and (if (i.q != nil) then (i.q in l)); postcondition:(* * The questions satsify the criteria. * The questions add up to within 10% of the required time. * The questions have approximately the required difficulty distribution. *) (if (i.g != nil) then ( (forall (q in qg.qs) (QuestionFits(q,i.g.c))) and ((TotalTime(qg.qs) - i.g.t) < (i.g.t * 0.1)) and ((i.g.t - TotalTime(qg.qs)) > (i.g.t * 0.1)) and DifficultyMatch(l, qg, i.g.d, i.g.c) )) and (if (i.q != nil) then ((#qg.qs = 1) and (forall (q in qg.qs) q = i.q))); description: (* Creates a QuestionGroup populated with questions satisfying the conditions of TestTemplateItem. This QuestionGroup can then be added to a test using InsertQuestionGroup, found in tests.fmsl *); end; operation ReplacQuestion inputs: q:Question, t:Test, l:Database; outputs: t':Test; precondition: (* * q is in T, there exists a question similar to q in l *) (exists (qg in t.questions) q in qg.qs) and (exists (q' in l) (q'.ty = q.ty) and (q'.cl = q.cl) and (q'.d <= q.d + 1) and (q'.d >= q.d -1)); postcondition:(* * t' is identical to t except t' has q' in place of q *) (forall (i:integer| (i>=1) and (i<=#t.questions)) (if (not(q in t.questions[i].qs)) then (t.questions[i] = t'.questions[i]) else (forall (index:integer|(index>=1)and(index<=#t'.questions[i].qs)) (if (q != t'.questions[i].qs[index]) then (t'.questions[i].qs[index] = t.questions[1].qs[index]) else ( (t'.questions[i].qs[index].ty = q.ty) and (t'.questions[i].qs[index].cl = q.cl) and (t'.questions[i].qs[index].d <= q.d + 1) and (t'.questions[i].qs[index].d >= q.d -1) ) ) ))); description: (* Replaces a question in a test with a similar question*); end; function AreEnough(l:Database, cl:criteria*, n:NumberOfQuestions) = (* * Are there n questions in l that satisfy c? *) (forall (c in cl) (HowMany(l, c) >= n)); function HowMany(l:Database, c: criteria) = (* * How many questions in the database satisfy c? Notice that the condition is equal to "QuestionFits". *) (if (QuestionFits(l[1], [c])) then (HowMany(l[2:#l],c) + 1) else (if (#l != 0 ) then (HowMany(l[2:#l],c)) else 0)); function QuestionFits(q:Question, cl:criteria*) -> boolean = (* * Does q satsify c? *) (forall (c in cl) (if (c.a.t != nil) then (exists (v in c.v) v = q.ty)) or (if (c.a.c != nil) then (exists (v in c.v) v in q.cl)) or (if (c.a.a != nil) then (exists (v in c.v) v = q.a)) or (if (c.a.k != nil) then (exists (v in c.v) v in q.k))); function TotalTime(ql: Question*) = (* * The sum of the time of each question in a list of question. *) (if (#ql = 0) then 0 else (ql[1].t + TotalTime(ql[2:#ql]))); function DifficultyMatch(l: Database, qg:QuestionGroup, d:GroupDifficulty, cl:criteria*) = (* * Are the questions in the group of the right Difficulty? *) (if (d.one) then ((forall (q in qg.qs) q.d = 1) or ((forall (q in l) (if ((q.d = 1) and QuestionFits(q,cl)) then (q in qg.qs))) and (forall (q in qg.qs) ((q.d =1) or (q.d=2))) ) )) and (if (d.two) then ((forall (q in qg.qs) q.d = 2) or ((forall (q in l) (if ((q.d = 2) and QuestionFits(q,cl)) then (q in qg.qs))) and (forall (q in qg.qs) ((q.d =1) or (q.d=2) or (q.d=3))) ) )) and (if (d.three) then ((forall (q in qg.qs) q.d = 3) or ((forall (q in l) (if ((q.d = 3) and QuestionFits(q,cl)) then (q in qg.qs))) and (forall (q in qg.qs) ((q.d =2) or (q.d=3) or (q.d=4))) ) )) and (if (d.four) then ((forall (q in qg.qs) q.d = 4) or ((forall (q in l) (if ((q.d = 4) and QuestionFits(q,cl)) then (q in qg.qs))) and (forall (q in qg.qs) ((q.d =3) or (q.d=4) or (q.d=5))) ) )) and (if (d.five) then ((forall (q in qg.qs) q.d = 5) or ((forall (q in l) (if ((q.d = 5) and QuestionFits(q,cl)) then (q in qg.qs))) and (forall (q in qg.qs) ((q.d =4) or (q.d=5))) ) )) (if ((d.random or d.inc) and (#qg.qs >=5)) then (forall (i:integer| (i >= 1) and (i <= 5)) (if (exists (q in l) ((q.d = i) and QuestionFits(q,cl))) then (exists (q in qg.qs) q.d = i)))) and (if ((d.random or d.inc) and (#qg.qs <= 4)) then (forall (q in qg.qs) (forall (q2 in qg.qs) (if (q.d = q2.d) then ((q = q2) or (not (exists (q3 in l) (QuestionFits(q,cl) and (q3.d != q.d) and (not (q3 in qg.qs)))))))))) and (if (d.inc) then (forall (i:integer| (i >=0) and (i < #qg.qs)) qg.qs[i].d <= qg.qs[i+1].d)); end TestTemplate;