def getGradeAdjustment(self, score, total, timestamp, allowNeg=True): """ Given the submission's score, assignment total, and timestamp, returns the adjustment based on this policy. Respects tamarin.GRADE_PRECISION. Normally, returns the adjustment even if this is a penalty greater than the score itself. If allowNeg is False, any computed penalty is capped at -score. Remember, if passed a non-numeric score, any $ rules will convert to % instead. This way, a numeric value can always be returned. """ import tamarin if self.span_sign == '+': assert self.deadline < timestamp timestamp = min(timestamp, self.end) timespan = (tamarin.convertTimestampToTime(timestamp) - tamarin.convertTimestampToTime(self.deadline)) else: assert timestamp <= self.deadline timestamp = max(timestamp, self.end) timespan = (tamarin.convertTimestampToTime(self.deadline) - tamarin.convertTimestampToTime(timestamp)) if not self.rule_sign: #no rule, which means do nothing to the grade return 0 if not self.rule_unit: modifier = self.rule_value elif self.rule_unit == '$' and \ (isinstance(score, int) or isinstance(score, float)): modifier = (self.rule_value * score) / 100 else: # self.rule_unit == '%', or '$' with non-numeric modifier = (self.rule_value * total) / 100 if self.rule_sign == '-': modifier *= -1 times = 1 if self.rule_repeater and not self.rule_sign == '=': days = int(self.rule_day[:-1]) if self.rule_day else 0 hours = int(self.rule_hour[:-1]) if self.rule_hour else 0 minutes = int(self.rule_minute[:-1]) if self.rule_minute else 0 repeater = datetime.timedelta(days=days, hours=hours, minutes=minutes) times = timespan / repeater if self.span_sign == '+': times = math.ceil(times) #include any partial repeater span else: times = math.floor(times) #only complete repeater span gradeAdj = round(modifier * times, tamarin.GRADE_PRECISION) if not allowNeg and score + gradeAdj < 0: gradeAdj = -score return gradeAdj
def getLateOffset(self, submittedTimestamp=None): """ Returns a string in the format of '+#d #h #m' showing the lateness of the given Tamarin timestamp. If a timestamp is not given, will use the current time instead. If the given timestamp is before this assignment's deadline, will return a negative offset showing how early the submission is. """ # convert to datetimes import tamarin submitted = tamarin.convertTimestampToTime(submittedTimestamp) deadline = tamarin.convertTimestampToTime(self.due) # to avoid dealing with negative timedeltas (which are weird) if submitted <= deadline: offset = deadline - submitted sign = '-' else: offset = submitted - deadline sign = '+' hours = offset.seconds // 3600 minutes = (offset.seconds // 60) - (hours * 60) return '{0}{1}d {2}h {3:02}m'.format(sign, offset.days, hours, minutes)
def __init__(self, policy, deadline): """ Constructs a LatePolicy according to the given policy string. The deadline should be in Tamarin timestamp format. Each policy should be a string in the form of: 'range:rule' A range has the following format: [+|-][[#d][#h][#m]|timestamp] That is, the range may optionally begin with either + or -. If the sign is omitted, + is assumed. The sign may then be followed by a span specified in days, hours, and minutes. This span is relative to the assignment deadline, either earlier (if sign is -) or later (if sign is +). A particular unit may be omitted if unused; it will be assumed to be 0. If more than 23 hours or 59 minutes are specified, the corresponding overflow into days and hours will be calculated. At least one unit must be given though: d for day, h for hours, or m for minutes. If more than one is used, they must be in that order. As an alternative to specifying a relative span, a specific timestamp may be given. The sign is ignored in these cases. (Since LatePolicies usually apply across multiple assignments, specific timestamps are rare. However, they can be handy to specify a final cutoff date for all assignments--such as the last day of the course.) It is an error to have a 0 length span, either because the relative span is of size 0 or if the timestamp given is equal to the deadline. If neither span nor timestamp is given, the end of the span is treated as year 0 for a - sign or year 9999 for a positive sign. The range is then separated from the associated rule by a colon (:). The rule is of the form: [+|-|=][#[%|$]][/[#d][#h][#m]] That is, the rule may begin with a sign: +, -, or =. If omitted, = is assumed. The sign specifies whether the grade should be increased by (+), decreased (-) by, or set equal to (=) the following value. The sign is followed by an integer value, which may be appended by either % or $. Without a modifier, the value is treated as a raw point modifier. If appended with a %, this is a percentage of the assignment's total possible score. If appended with a $, it is a percentage of the submission's actual score. (If the actual score to be modified is not numeric, will default to % instead.) Optionally, the modifier may be followed by a / and relative span. At least one value--whether day, hour, or minutes--must be given after a /. When such a span is given, the modifier is applied for each span that the assignment is late. For example, /1d means apply the modifier for each day. Such as span does nothing if applied to an = modifier. If this rule is associated with a -timespan ("bonus policy"), it is applied for each full span. Otherwise, it is applied for each partial span. The rule may be omitted. In this case, the submission is marked late (or early), but no grade change is applied. Examples: +5d:-1/1d - For up to 5 days after the deadline, submissions suffer a penalty of 1 point off for each (partial) day. +48h:-10% - For 2 days after the deadline, any submission loses 10% of the assignment total. (So, for a 5 point assignment, this would be -1 point.) Equivalent to '2d:-10%' (if relying on the assumed + sign on the timespan). -3d:+5$/1d - An example of an "early bonus" policy, this grants a cumulative bonus of 5 percent of the submission's score for each full day early. 20121221-1200:40$ - Using a timestamp to define the span, anything submitted after the deadline but before noon on 21 Dec 2012 will receive 40% of the score it would otherwise have received. (:=40$ or :-60$ would also have been equivalent rules, since = is assumed on rules without a sign.) 1d: - Anything submitted for up to 1 day past the deadline is marked as late but its grade is unaffected. Given only the policies above, any submissions later than the deadline plus the given late period timespan would be refused by Tamarin. +:10% - Anything submitted after the deadline, no matter how late or how bad, is given 10% of the total possible assignment value. : - Anything submitted after the deadline is late but still accepted. """ import tamarin self.raw = policy self.deadline = deadline assert re.match(r'\d{8}-\d{4}', self.deadline) span_re = r'(\+|-)?((\d{8}-\d{4})|((\d+d)?(\d+h)?(\d+m)?))' rule_re = r'(\+|-|=)?(\d*)([%$])?(/(\d+d)?(\d+h)?(\d+m)?)?' parsed = re.match(span_re + ':' + rule_re, policy) if not parsed: raise TamarinError('INVALID_LATE_POLICY', policy) (self.span_sign, unused_offset, self.span_timestamp, relative, self.span_day, self.span_hour, self.span_minute, self.rule_sign, self.rule_value, self.rule_unit, self.rule_repeater, self.rule_day, self.rule_hour, self.rule_minute) = parsed.groups() #apply defaults and constrains if not self.span_sign: self.span_sign = '+' if self.span_timestamp: self.end = self.span_timestamp elif relative: day = hour = minute = 0 sign = -1 if self.span_sign == '-' else 1 if self.span_day: day = int(self.span_day[:-1]) * sign if self.span_hour: hour = int(self.span_hour[:-1]) * sign if self.span_minute: minute = int(self.span_minute[:-1]) * sign endDate = tamarin.convertTimestampToTime(deadline) shift = datetime.timedelta(days=day, hours=hour, minutes=minute) self.end = tamarin.convertTimeToTimestamp(endDate + shift) else: if self.span_sign == '-': self.end = '00000101-0000' else: self.end = '99991231-2359' if self.end == self.deadline: raise TamarinError('INVALID_LATE_POLICY', "Parsable, but deadline == endpoint.") if self.rule_sign or self.rule_value or self.rule_unit or \ self.rule_repeater: #some part of a rule given, so lets see if it's all valid if not self.rule_sign: self.rule_sign = '=' if not self.rule_value: raise TamarinError('INVALID_LATE_POLICY', "No grade change value given.") self.rule_value = int(self.rule_value) if self.rule_repeater and not self.rule_day and \ not self.rule_hour and not self.rule_minute: raise TamarinError('INVALID_LATE_POLICY', "Empty /modifier given: " + self.raw)