Class ::xowf::test_item::grading::Grading (public)

 ::nx::Class ::xowf::test_item::grading::Grading[i]

Defined in packages/xowf/tcl/grading-procs.tcl

Superclass representing a generic grading

Testcases:
No testcase defined.
Source code:
    :property {precision ""}
    :property {title ""}
    #
    # The following two properties are specified by the sub-classes
    # and ensure that no grading is defined accidentally from the
    # base class.
    #
    :property {percentage_boundaries:required}
    :property {csv:required}

    :method init {} {
      #
      # Provide a default, self-descriptive title
      #
      if {${:title} eq ""} {
        set roundingClass [namespace tail [:info class]]
        if {$roundingClass ne "GradingRoundNone" && [string match *Round* $roundingClass]} {
          set round_string "#xowf.Rounding_scheme#: #xowf.Rounding_scheme-$roundingClass#,"
        } else {
          set round_string ""
        }
        if {$roundingClass ne "GradingRoundNone" && ${:precision} ne ""} {
          set precision "#xowf.Rounding_precision#: ${:precision},"
        } else {
          set precision ""
        }
        set :title "[namespace tail [self]]: $round_string $precision #xowf.Grade_boundaries#: ${:percentage_boundaries}"
        ns_log notice "[self] initialized with title ${:title}"
      }
      next
    }

    :method calc_grade {-percentage -points -achievable_points} {
      #
      # Return a numeric grade for an exam submission based on
      # percentage and the property "percentage_mapping". On
      # invalid data, return 0.
      #
      # When "-percentage" is provided, use this for calculation
      # Otherwise calculate percentage based on "-points" (which might
      # be custom rounded) and "-achievable_points".
      #

      if {![info exists percentage] && $achievable_points > 0} {
        set percentage  [format %.2f [expr {($points*100/$achievable_points) + 0.00001}]]
      }
      if {[info exists percentage]} {
        set grade 1
        set nrGrades [expr {[llength ${:percentage_boundaries}]+1}]
        if {$nrGrades ne 5} {
          ns_log warning "grading [self]: unexpected number of grades: $nrGrades"
        }
        set gradePos 0
        foreach boundary ${:percentage_boundaries} {
          if {$percentage < $boundary} {
            set grade [expr {$nrGrades - $gradePos}]
            break
          }
          incr gradePos
        }
      } else {
        set grade 0
      }
      return $grade
    }

    :public method grading_dict {achieved_points} {

      # Important dict members of "achieved_points":
      #  - achievedPoints: points that the student has achieved in her exam
      #  - achievablePoints: points that the student could have achieved so far
      #  - totalPoints: points that the student can achieve when finishing the exam
      #
      #     achieved_points:    {achievedPoints 4.0 achievablePoints 4 totalPoints 4}
      #     percentage_mapping: {50.0 60.0 70.0 80.0}
      #
      # While "achievedPoints" and "achievablePoints" are calculated by
      # iterating over the submitted values, "totalPoints" contains
      # the sum of points of all questions of the exam, no matter if
      # these were answered or not.
      #
      if {![dict exists $achieved_points achievablePoints] && [dict exists $achieved_points totalPoints]} {
        ns_log warning "test_item::grading legacy call, use 'achievablePoints' instead of 'totalPoints'"
        dict set achieved_points achievablePoints [dict get $achieved_points totalPoints]
      }
      #
      # When the "achievedPoints" member is set to empty, and "details" are
      # provided, the caller can request a new calculation based on
      # the "details" member.
      #
      if {[dict get $achieved_points achievedPoints] eq ""
          && [dict exists $achieved_points details]
        } {
        set achievablePoints 0
        set achievedPoints 0
        #ns_log notice "RECALC in grading_dict "
        foreach detail [dict get $achieved_points details] {
          #ns_log notice "RECALC in grading_dict '$detail'"
          set achievedPoints   [expr {$achievedPoints   + [dict get $detail achieved]}]
          set achievablePoints [expr {$achievablePoints + [dict get $detail achievable]}]
        }
        dict set achieved_points achievedPoints $achievedPoints
        dict set achieved_points achievablePoints $achievablePoints
      }

      foreach key {
        achievedPoints
        achievablePoints
        totalPoints
      } {
        if {![dict exists $achieved_points $key]} {
          ns_log warning "test_item::grading dict without $key: $achieved_points"
          ::xo::show_stack
          dict set achieved_points $key 0
        }
      }
      #
      # Format all values with two comma precision. The values
      # achievedPointsRounded and "percentageRounded" are rounded to
      # the custom precision.
      #
      dict with achieved_points {
        dict set achieved_points achievedPointsRounded [format %.${:precision}$achievedPoints]
        set achievedPoints [format %.2f $achievedPoints]
        set percentage  [format %.2f [expr {$totalPoints > 0 ? ($achievedPoints*100.0/$totalPoints) : 0}]]
        dict set achieved_points percentage $percentage
        dict set achieved_points percentageRounded [format %.${:precision}$percentage]
      }
      #ns_log notice "R=$achieved_points"
      return $achieved_points
    }

    :public method print {-achieved_points:required} {
      #
      # Return a dict containing the members "panel" and "csv"
      # depending on the type of rounding options
      #
      set achieved_points  [:grading_dict $achieved_points]
      set grade            [:grade -achieved_points $achieved_points]
      dict with achieved_points {
        return [list panel [_ xowf.panel_[namespace tail [:info class]]] csv [subst ${:csv}]]
      }
    }
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: