Wecode supports flexible late submission penalties using a custom expression language that allows you to define time-based scoring rules.
Overview
The late submission system:
- Expression-Based: Use mathematical expressions to define penalties
- Time-Aware: Access delay time and extra time in formulas
- Automatic Application: Penalties calculated and applied automatically
- Custom Grading: Different rules for different assignments
Late Rule Field
Late submission rules are stored in the Assignment model:
protected $fillable = [
"name",
"total_submits",
"open",
"score_board",
"javaexceptions",
"start_time",
"finish_time",
"extra_time",
"late_rule", // Expression for late penalties
"participants",
"description",
"user_id",
"language_ids",
];
Expression Language
Wecode uses Symfony’s ExpressionLanguage with custom math functions:
class my_expression_language_provider implements ExpressionFunctionProviderInterface
{
public function getFunctions(): array
{
$func_list = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2',
'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh',
'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1',
'fdiv', 'floor', 'fmod', 'hexdec', 'hypot', 'intdiv',
'is_finite', 'is_infinite', 'is_nan', 'log', 'log10',
'log1p', 'max', 'min', 'octdec', 'pi', 'pow', 'rad2deg',
'round', 'sin', 'sinh', 'sqrt', 'tan', 'tanh'];
return array_map(array('Symfony\Component\ExpressionLanguage\ExpressionFunction','fromPhp'), $func_list);
}
}
Available Variables
In your late rule expressions, you have access to:
delay: Seconds after the deadline (finish_time)
extra_time: Extra time allowed for submission in seconds
Available Functions
All standard math functions are available:
- Basic:
abs(), ceil(), floor(), round(), min(), max()
- Exponential:
exp(), log(), log10(), pow(), sqrt()
- Trigonometric:
sin(), cos(), tan(), asin(), acos(), atan()
- Hyperbolic:
sinh(), cosh(), tanh(), asinh(), acosh(), atanh()
- Conversion:
deg2rad(), rad2deg()
- And more: See the full list in the code above
Coefficient Evaluation
The coefficient is calculated when a submission is made:
private function _eval_coefficient($delay = 0, $submit_time = 0)
{
try {
$extra_time = $this->extra_time;
$expressionLanguage = new ExpressionLanguage();
$expressionLanguage->registerProvider(new my_expression_language_provider());
$coefficient = $expressionLanguage->evaluate($this->late_rule, [
'delay' => $delay,
'extra_time' => $extra_time,
]);
$coefficient = round($coefficient, 1);
if ($coefficient < 0)
$coefficient = max(-10000, $coefficient);
else
$coefficient = min(10000, $coefficient);
} catch (\Throwable $e) {
$coefficient = "error";
}
if (!isset($coefficient) || !is_numeric($coefficient)) {
$coefficient = "error";
}
return $coefficient;
}
Important Notes:
- Coefficient is rounded to 1 decimal place
- Clamped between -10000 and 10000
- Returns “error” if expression is invalid or throws exception
- When coefficient is “error”, the submission receives 0 score
Using the Coefficient
The coefficient is stored as a percentage (100 = 100%, 50 = 50%, etc.):
public function eval_coefficient()
{
$delay = $this->finish_time->diffInSeconds(Carbon::now());
$submit_time = $this->start_time->diffInSeconds(Carbon::now());
return $this->_eval_coefficient($delay, $submit_time);
}
The final score calculation uses the coefficient:
$final_score = ceil(
($final->pre_score * ($problem_score[$final->problem_id] ?? 0)) / 10000,
);
$final_score = ceil(
$final_score * ($final->coefficient === "error" ? 0 : $final->coefficient / 100),
);
Example Late Rules
No Penalty
Allow late submissions without penalty:
Simple Linear Decay
Decrease by 1% per hour (3600 seconds):
No Late Submissions
Reject all late submissions:
Exponential Decay
Rapidly decreasing penalty:
"100 * exp(-delay / 86400)" // Decay based on days
Step Function
Different penalties for different time periods:
"delay < 3600 ? 100 : (delay < 86400 ? 80 : 50)"
This gives:
- 100% if within 1 hour late
- 80% if within 1 day late
- 50% after 1 day late
Penalize only after extra time expires:
"delay < extra_time ? 100 : max(0, 100 - ((delay - extra_time) / 3600))"
Grace Period with Linear Decay
24-hour grace period, then 10% penalty per day:
"delay < 86400 ? 100 : max(0, 100 - ((delay - 86400) / 86400 * 10))"
Logarithmic Decay
Slower initial decay that accelerates:
"max(0, 100 - log(delay + 1) * 10)"
Updating Coefficients
When you change a late rule, update existing submissions:
public function update_submissions_coefficient()
{
foreach ($this->submissions as $sub) {
$delay = $this->finish_time->diffInSeconds($sub->created_at);
$submit_time = $this->start_time->diffInSeconds($sub->created_at);
$sub->coefficient = $this->_eval_coefficient($delay, $submit_time);
$sub->save();
}
}
Call this method after modifying the late_rule to recalculate all submission coefficients.
How Delay is Calculated
Delay is measured from the finish_time:
$delay = $this->finish_time->diffInSeconds($sub->created_at);
- Before Deadline:
delay will be negative
- At Deadline:
delay is 0
- After Deadline:
delay is positive (seconds late)
Final Submission Selection
When determining the final (best) submission, both raw score and coefficient are considered:
$final_sub = Submission::where([
'user_id' => $submission->user_id,
'assignment_id' => $submission->assignment_id,
'problem_id' => $submission->problem_id,
'is_final' => 1
])->lockForUpdate()->first();
if (
$final_sub == NULL
||
($final_sub->pre_score < $submission['pre_score']
|| $final_sub->pre_score * intval($final_sub->coefficient) < $submission['pre_score'] * intval($submission['coefficient'])
)
){
if ($final_sub){
$final_sub->is_final = 0;
$final_sub->save();
}
$submission->is_final = 1;
}
A new submission becomes final if:
- No final submission exists, OR
- New raw score is better, OR
- New score × coefficient is better than old score × coefficient
Best Practices
- Test Your Formula: Verify the coefficient values for different delay times
- Use Bounds: Always ensure coefficient stays between 0-100
- Consider Edge Cases: Test with negative delays (early submissions)
- Document Rules: Comment complex expressions for future reference
- Update Carefully: Changing late rules affects all submissions when recalculated
Expression Errors: If your late rule expression is invalid or throws an error, the coefficient will be set to “error” and the submission will receive a score of 0. Always test expressions before deploying.
Common Patterns
Percentage Penalty Per Time Unit
// 5% off per hour late
"max(0, 100 - (delay / 3600) * 5)"
// 1% off per 10 minutes late
"max(0, 100 - (delay / 600))"
Tiered Penalties
// Full credit within 1 hour, 90% within 6 hours, 80% within 24 hours, 0% after
"delay < 3600 ? 100 : (delay < 21600 ? 90 : (delay < 86400 ? 80 : 0))"
Soft Deadlines
// Use extra_time as a grace period
"delay <= extra_time ? 100 : max(0, 100 - ((delay - extra_time) / 3600) * 2)"
Troubleshooting
Coefficient Shows “error”
- Check expression syntax
- Verify all variables are spelled correctly
- Ensure functions are in the allowed list
- Test with simple expressions first
- Check server logs for PHP errors
Unexpected Coefficient Values
- Verify
delay calculation matches expectations
- Check if
extra_time is set correctly
- Test expression with known delay values
- Remember: coefficient is clamped to [-10000, 10000]
Submissions Not Using New Rule
- Call
update_submissions_coefficient() after changing the rule
- Verify the assignment’s
late_rule field was actually saved
- Check that submissions are being recalculated