5.7. TestGrading

module TestGrading;
  from QuestionModule import ClassName, Answer, CorrectAnswer, ShortAnswer, Essay, Question, Type, Time,
                             ID, Notes, LastUsed, Modified, Created, AverageScore, Author, Difficulty;
  from TestGeneration import Test, PointsPossible, TotalPoints, TotalGrade, TestQuestion, Roster, QuestionNumber;
  from StudentInterface import Student;
  from TestAdminister import ClassTests, QuestionView, CurrentQuestion;
  from TestTaking import AnsweredQuestion, StudentAnswer, AQStatus;

export GradedQuestion, Mean, Median, Mode, TestGradingView, SuccessRate;

--OBJECTS
object PointsEarned is number;
object SuccessRate is number;
object GradedQuestion inherits from AnsweredQuestion
    components: pe:PointsEarned, sr:SuccessRate, comm:Comment;
        description: (* A question once the auto-grader has graded it *);
end GradedQuestion;
object Mean is integer;
object Median is integer;
object Mode is integer;
object Comment is string;
object TestGradingView
    components: qv:QuestionView*, cur:CurrentQuestion, s:Student,
                m:Mean, med:Median, md:Mode, r:Roster;

    description: (* The TestGradingView object describes what the instructor sees when
                 grading a test.  It consists of: a window displaying on
                 QuestionView at a time, below each question are buttons to
                 skip to the first, prev, next or last question.  On top are the
                 class statistics, and on the side is a list of all the student
                 in the class
                  *);
end TestGradingView;

--TEST GRADING OPERATIONS
operation AutoGradeTests
    inputs: ct:ClassTests;
    outputs: ct':ClassTests;
    pre:
       (*
        * The tests have been answered
        *)
        (forall (t in ct.t)
        (t.aq != nil) and (t.qs = nil) and (t.gq = nil));

    post:
        (*
         * The points earned are assigned
         *)
        (forall (i:integer | i <= #(ct.t))
        (forall (aq' in ct.t[i].aq)
           (if (aq'.type = "TrueFalse") or (aq'.type = "MultipleChoice") or
               (aq'.type = "Matching") or (aq'.type = "FillIn") or (aq'.type = "Programming")
            then
              (if (aq'.sa = aq'.ca)
               then (exists (gq in ct'.t[i].gq) ((FieldsMatch(aq',gq)) and (gq.pe = gq.points)))
               else (exists (gq in ct'.t[i].gq) ((FieldsMatch(aq',gq)) and (gq.pe = 0))))
            else
              (if (aq'.type = "ShortAnswer")
               then
                 (exists (gq in ct'.t[i].gq) ((FieldsMatch(aq',gq) and
                 (gq.pe = (gq.points * (NumKeywordsSA(aq',#(aq'.sa.sa)) / #(aq'.sa.sa)))))))
               else
                 (exists (gq in ct'.t[i].gq) ((FieldsMatch(aq',gq) and
                 (gq.pe = (gq.points * (NumKeywordsE(aq',#(aq'.sa.e)) / #(aq'.sa.e))))))))))

            and

            (* All questions are now graded questions *)
            (ct'.t[i].aq = nil) and (ct'.t[i].gq != nil) and (ct'.t[i].qs = nil));
    description: (* After the class's tests have been submitted they are graded by the auto-grader.
                    Each question type is graded and all answered questions are converted to
                    graded questions. *);
end AutoGradeTests;

function NumKeywordsSA(aq: AnsweredQuestion, index: integer)->(number)=
(
   (*
    * The number of keyword matches within the answer
    *)
   if (index > 0)
   then (if (aq.ca.sa[index] in  aq.sa.sa)
         then (1 + NumKeywordsSA(aq,index - 1))
         else (NumKeywordsSA(aq,index - 1)))
   else (0)
);       

function NumKeywordsE(aq: AnsweredQuestion, index: integer)->(number)=
(
   (*
    * The number of keyword matches within the answe
    *)
   if (index > 0)
   then (if (aq.ca.e[index] in  aq.sa.sa)
         then (1 + NumKeywordsE(aq,index - 1))
         else (NumKeywordsE(aq,index - 1)))
   else (0)
);       

function FieldsMatch(aq: AnsweredQuestion, gq: GradedQuestion)->(boolean)=
(
   (*
    * All fields in AnsweredQuestion are in GradedQuestion
    *)
   gq.id = aq.id and
   gq.type = aq.type and
   gq.text = aq.text and
   gq.difficulty = aq.difficulty and
   gq.time = aq.time and
   gq.ca = aq.ca and
   gq.cn = aq.cn and
   gq.topics = aq.topics and
   gq.author = aq.author and
   gq.as = aq.as and
   gq.created = aq.created and
   gq.modified = aq.modified and
   gq.lu = aq.lu and
   gq.notes = aq.notes and
   gq.qt = aq.qt and
   gq.points = aq.points and
   gq.num = aq.num and
   gq.sa = aq.sa and
   gq.aqs = aq.aqs
);

operation AddComment
    inputs: c:Comment, gq:GradedQuestion;
    outputs: gq':GradedQuestion;
    precondition:
    (*
     * comment must not empty
     *)
    c != nil;

    postcondition:
        (*
     *  The graded question is returned with the new comment added
     *)
    (gq'.comm = c);

    description: (*
        Add a given comment to an answer of a student's question.
    The comment will appear in a text box below the question.
    *);
end AddComment;

operation DeleteComment
    inputs: gq:GradedQuestion;
    outputs: gq':GradedQuestion;
    precondition:
      (*
       * There is a comment in the given graded question.
       *)
      gq.comm != nil;

    postcondition:
       (*
    * The given comment is not outputted in given graded question.
    *)
       (gq'.comm = nil);

    description: (*
        Delete the selected a question on a test by the user.    After
    user comfirms that the question is removed from that student's
    test or the entire class, the test properties are then adjusted.
    *);
end DeleteComment;

operation DeleteQuestionStudent
   inputs: gq:GradedQuestion, t:Test;
   outputs: t':Test;
   precondition:
      (*
       * The given graded question is in the given test.
       *)
      gq in t.gq;

   postcondition:
       (*
    * The given graded question is not outputted in given test.
    *)
       (forall (gq' in t.gq)
          (gq' in t'.gq) iff (gq' != gq))
     
        and

        (*
         * The student's grade is updated
         *)
        (t.tg = t.tg - gq.pe)

        and

        (*
         * The test's total points possible are update
         *)
        (t.tp = t.tp - gq.points);

    description: (*
        Delete the selected a question on a test by the user.    After
    user comfirms that the question is removed from that student's
    test, the graded test properties are then adjusted.
    *);
end DeleteQuestionStudent;

operation DeleteQuestionClass
   inputs: gq:GradedQuestion, ct:ClassTests;
   outputs: ct':ClassTests;
   precondition:
      (*
       * The given graded question is in the given tests.
       *)
      forall (t in ct.t)
        (gq in t.gq);

   postcondition:
       (*
    * The given graded question is not outputted in given test.
    *)
       (forall (t in ct'.t)
       (forall (gq' in t.gq)
          (gq' in t.gq) iff (gq' != gq)))
     
        and

        (*
         * The students' grades are updated
         *)
        (forall (t in ct'.t)
          (t.tg = t.tg - gq.pe))

        and

        (*
         * The students' total points are updated
         *)
        (forall (t in ct'.t)
          (t.tp = t.tp - gq.points));

    description: (*
        Delete the selected a question on a test by the user.    After
    user comfirms that the question is removed from that student's
    test, the graded test properties are then adjusted.
    *);
end DeleteQuestionClass;

operation ChangeCorrectAnswer
    inputs: new_ca:CorrectAnswer, gq:GradedQuestion, ct:ClassTests;
    outputs: ct':ClassTests;
    precondition:
        (*
         * Old and new correct answer are not the same
         *)
        (gq.ca != new_ca)

        and
   
    (*
         * The new correct answer is not empty.
         *)
        (new_ca != nil);
   
    postcondition:
    (*
         * A new correct answer is in this question for every student's test in the class
         *)
        (forall (t in ct'.t)
        (forall (gqs in t.gq)      
          (gqs.ca = new_ca) iff (gqs = gq)));

    description: (*
        The old correct answer turn into a new answer changin the total grade earned
     throughout the whole class. The new correct answer cannot be the same as the
    old correct answer.
    *);
end ChangeCorrectAnswer;

operation ChangePointsPossible
    inputs: new_pp:PointsPossible, gq:GradedQuestion, ct:ClassTests;
    outputs: ct':ClassTests;
    precondition:
    (*
         * The old and new points possible are not the same.
         *)
          (gq.points != new_pp)

        and
   
    (*
         * The new points possible is not empty.
         *)
        (new_pp != nil);
   
    postcondition:
    (*
         * A new points possible is in this question for every student's test in the class
         *)
        (forall (t in ct'.t)
        (forall (gqs in t.gq)      
          (gqs.points = new_pp) iff (gqs = gq)));
    description: (* The points possible for a certain question are changed.  The test's other information
                    is updated appropriately. *);
end ChangePointsPossible;

operation ChangePointsEarned
    inputs: new_pe:PointsEarned, gq:GradedQuestion, t:Test;
    outputs:  t':Test;
    precondition:
    (*
         * The old and new points earned are not the same.
         *)
        (gq.pe != new_pe)

            and

    (*
         * The new points earned is not empty.
         *)
        (new_pe != nil)

        and

        (*
         * The graded question is in the test
         *)
        (gq in t.gq);
   
    postcondition:
    (*
         * A new points earned is in the new graded question
         *)
        (forall (gqs in t.gq)      
          (gqs.pe = new_pe) iff (gqs = gq))

        and

        (*
         * The points earned are update
         *)
        (t'.tg = t.tg - gq.pe + new_pe);

    description: (*
        Change the old point value to a new point value.
    *);
end ChangePointsEarned;

operation CalculateMean
    inputs: ct: ClassTests;
    outputs: m': Mean;
    description: (*
        A formula will be used to calculate the mean of the class test scores.
    *);
end CalculateMean;

operation CalculateMedian
    inputs: ct: ClassTests;
    outputs: md': Median;
    description: (*
        A formula will be used to calculate the median of the class test scores.
    *);
end CalculateMedian;

operation CalculateMode
    inputs: ct: ClassTests;
    outputs: md': Mode;
    description: (*
    A formula will be used to calculate the mode of the class test scores.
    *);
end CalculateMode;

operation CalculateSuccessRate
    inputs: ct: ClassTests;
    outputs: sr': SuccessRate;
    description: (*
    A formula will be used to calculate the success rate of each question.
    *);
end CalculateMode;

operation CalculateTotalPoint
     inputs: t:Test;
     outputs: tp:TotalPoints;
     post: (* All points summed *)
        (forall (q in t.gq)
           (tp = tp + q.points));
     description:  (*
        The points possible of all the questions within the test summed
     *);
end CalculateTotalPoints;
      

op SelectStudent
    inputs: s:Student;
    outputs: tgv':TestGradingView;
    post: (* Displays Student's test *)
          (tgv'.s = s);
    description: (* A new student's test is chosen
                 *);
end SelectStudent;

op DisplayPrevQuestion
    inputs: tgv:TestGradingView;
    outputs: tgv':TestGradingView;
    pre: (* not at first question *)
         tgv.cur > 1;
    post: (* TestGradingView displays previous question *)
         tgv'.cur = tgv.cur-1;
    description: (* The TestGradingView will display the previous question in the test
                 *);
end DisplayPrevQuestion;

op DisplayNextQuestion
    inputs: tgv:TestGradingView;
    outputs: tgv':TestGradingView;
    pre: (* not at last question *)
         tgv.cur < #(tgv.qv);
    post: (* TestGradingView displays next question *)
         tgv'.cur = tgv.cur+1;
    description: (* The TestGradingView will display the next question in the test *);
end DisplayNextQuestion;

op DisplayFirstQuestion
    inputs: tgv:TestGradingView;
    outputs: tgv':TestGradingView;
    pre: (* not at first question *)
         tgv.cur>1;
    post: (* TestGradingView displays first question *)
         tgv'.cur=1;
    description: (* The TestGradingView will display the first question in the test *);
end DisplayFirstQuestion;

op DisplayLastQuestion
    inputs: tgv:TestGradingView;
    outputs: tgv':TestGradingView;
    pre:  (* not at last question *)
          tgv.cur < #(tgv.qv);
    post: (* TestGradingView displays last question *)
          tgv'.cur = #(tgv.qv);
    description: (* The TestGradingView will display the last question in the test*);
end DisplayLastQuestion;

op SkipToQuestion
    inputs:  tgv:TestGradingView, question:integer;
    outputs: tgv':TestGradingView;
    pre:  (* not at question already *)
          tgv.cur != question;
    post: (* TestGradingView displays specified question *)
          tgv'.cur = question;
    description: (* The TestGradingView will display the question selected *);
end SkipToQuestion;
end TestGrading;


Prev:
5.6. TestGeneration

Up:
5. Formal Specifications
Next:
5.8. TestTaking