Skip to main content
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:
Assignment.php:26
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:
Assignment.php:12
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:
Assignment.php:181
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.):
Assignment.php:219
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:
Assignment.php:266
$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:
"100"

Simple Linear Decay

Decrease by 1% per hour (3600 seconds):
"100 - (delay / 3600)"

No Late Submissions

Reject all late submissions:
"0"

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

Using Extra Time

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:
Assignment.php:209
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:
Assignment.php:212
$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:
Queue_item.php:72
$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:
  1. No final submission exists, OR
  2. New raw score is better, OR
  3. New score × coefficient is better than old score × coefficient

Best Practices

  1. Test Your Formula: Verify the coefficient values for different delay times
  2. Use Bounds: Always ensure coefficient stays between 0-100
  3. Consider Edge Cases: Test with negative delays (early submissions)
  4. Document Rules: Comment complex expressions for future reference
  5. 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”

  1. Check expression syntax
  2. Verify all variables are spelled correctly
  3. Ensure functions are in the allowed list
  4. Test with simple expressions first
  5. Check server logs for PHP errors

Unexpected Coefficient Values

  1. Verify delay calculation matches expectations
  2. Check if extra_time is set correctly
  3. Test expression with known delay values
  4. Remember: coefficient is clamped to [-10000, 10000]

Submissions Not Using New Rule

  1. Call update_submissions_coefficient() after changing the rule
  2. Verify the assignment’s late_rule field was actually saved
  3. Check that submissions are being recalculated

Build docs developers (and LLMs) love