Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/api/entities/task_definition_entity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ def staff?(my_role)
expose :abbreviation
expose :name
expose :description
expose :weighting
expose :estimated_hours
expose :predicted_effort
expose :target_grade

with_options(format_with: :date_only) do
Expand Down
30 changes: 26 additions & 4 deletions app/api/task_definitions_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ class TaskDefinitionsApi < Grape::API
optional :tutorial_stream_abbr, type: String, desc: 'The abbreviation of tutorial stream'
requires :name, type: String, desc: 'The name of this task def'
requires :description, type: String, desc: 'The description of this task def'
requires :weighting, type: Integer, desc: 'The weighting of this task'
requires :estimated_hours, type: Integer, desc: 'The estimated number of hours to complete this task'
optional :predicted_effort, type: Float, desc: 'The predicted effort of the task based on task features'
requires :target_grade, type: Integer, desc: 'Minimum grade for task'
optional :group_set_id, type: Integer, desc: 'Related group set'
requires :start_date, type: Date, desc: 'The date when the task should be started'
Expand Down Expand Up @@ -57,7 +58,8 @@ class TaskDefinitionsApi < Grape::API
.permit(
:name,
:description,
:weighting,
:estimated_hours,
:predicted_effort,
:target_grade,
:start_date,
:target_date,
Expand Down Expand Up @@ -115,7 +117,8 @@ class TaskDefinitionsApi < Grape::API
optional :tutorial_stream_abbr, type: String, desc: 'The abbreviation of the tutorial stream'
optional :name, type: String, desc: 'The name of this task def'
optional :description, type: String, desc: 'The description of this task def'
optional :weighting, type: Integer, desc: 'The weighting of this task'
optional :estimated_hours, type: Integer, desc: 'The estimated number of hours to complete this task'
optional :predicted_effort, type: Float, desc: 'The predicted effort of the task based on task features'
optional :target_grade, type: Integer, desc: 'Target grade for task'
optional :group_set_id, type: Integer, desc: 'Related group set'
optional :start_date, type: Date, desc: 'The date when the task should be started'
Expand Down Expand Up @@ -171,7 +174,8 @@ class TaskDefinitionsApi < Grape::API
.permit(
:name,
:description,
:weighting,
:estimated_hours,
:predicted_effort,
:target_grade,
:start_date,
:target_date,
Expand Down Expand Up @@ -926,6 +930,24 @@ class TaskDefinitionsApi < Grape::API
present job, with: Entities::SidekiqJobEntity
end

desc 'Predict the effort required for a task description'
params do
requires :unit_id, type: Integer, desc: 'The unit that has the task definition'
requires :task_def_id, type: Integer, desc: 'The task definition to predict effort for'
end
post '/units/:unit_id/task_definitions/:task_def_id/predict_effort' do
unit = Unit.find(params[:unit_id])
unless authorise? current_user, unit, :get_students
error!({ error: "Not authorised to run prediction." }, 403)
end

td = unit.task_definitions.find(params[:task_def_id])

PredictEffortJob.perform_async(td.id)

present status: "Prediction queued"
end

# desc 'Retrieve the contents of the overseer execution script'
# params do
# requires :unit_id, type: Integer, desc: 'The unit that has the task definition'
Expand Down
4 changes: 2 additions & 2 deletions app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ def discuss_and_demonstrate_tasks
# get the weight of all tasks completed or marked as ready to assess
#
def completed_tasks_weight
ready_or_complete_tasks.empty? ? 0.0 : ready_or_complete_tasks.map { |task| task.task_definition.weighting }.inject(:+)
ready_or_complete_tasks.empty? ? 0.0 : ready_or_complete_tasks.map { |task| task.task_definition.estimated_hours }.inject(:+)
end

def convert_hash_to_pct(hash, total)
Expand Down Expand Up @@ -594,7 +594,7 @@ def assigned_task_defs
end

def total_task_weight
assigned_task_defs.map(&:weighting).inject(:+)
assigned_task_defs.map(&:estimated_hours).inject(:+)
end

def remaining_days
Expand Down
2 changes: 1 addition & 1 deletion app/models/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,7 @@ def assessed?
end

def weight
task_definition.weighting.to_f
task_definition.estimated_hours.to_f
end

def add_text_comment(user, text, reply_to_id = nil)
Expand Down
8 changes: 4 additions & 4 deletions app/models/task_definition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def self.permissions
validate :unit_must_be_same
validate :tutorial_stream_present?

validates :weighting, presence: true
validates :estimated_hours, presence: true

validate :check_existing_prerequisites

Expand Down Expand Up @@ -542,7 +542,7 @@ def to_csv_row
end

def self.csv_columns
[:name, :abbreviation, :description, :weighting, :target_grade, :restrict_status_updates, :max_quality_pts,
[:name, :abbreviation, :description, :estimated_hours, :target_grade, :restrict_status_updates, :max_quality_pts,
:is_graded, :plagiarism_warn_pct, :scorm_enabled, :scorm_allow_review, :scorm_bypass_test, :scorm_time_delay_enabled,
:scorm_attempt_limit, :group_set, :upload_requirements, :start_week, :start_day, :target_week, :target_day,
:due_week, :due_day, :tutorial_stream, :assess_in_portfolio_only, :task_prerequisites, :discussion_prompts]
Expand Down Expand Up @@ -572,7 +572,7 @@ def self.task_def_for_csv_row(unit, row)
result = TaskDefinition.find_or_create_by(unit_id: unit.id, tutorial_stream: tutorial_stream, name: name, abbreviation: abbreviation) do |td|
td.target_date = target_date
td.start_date = start_date
td.weighting = row[:weighting].to_i
td.estimated_hours = row[:estimated_hours].to_i
end
new_task = true
end
Expand All @@ -581,7 +581,7 @@ def self.task_def_for_csv_row(unit, row)
result.unit_id = unit.id
result.abbreviation = abbreviation
result.description = "#{row[:description]}".strip
result.weighting = row[:weighting].to_i
result.estimated_hours = row[:estimated_hours].to_i
result.target_grade = row[:target_grade].to_i
result.restrict_status_updates = %w(Yes y Y yes true TRUE 1).include? "#{row[:restrict_status_updates]}".strip
result.max_quality_pts = row[:max_quality_pts].to_i
Expand Down
33 changes: 33 additions & 0 deletions app/sidekiq/predict_effort_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
require "net/http"
require "json"

class PredictEffortJob
include Sidekiq::Worker

def perform(task_def_id)
td = TaskDefinition.find(task_def_id)
payload = build_payload(td)
Rails.logger.info("ML payload: #{payload.to_json}")
response = Net::HTTP.post(
URI("#{ENV.fetch('ML_SERVICE_URL')}predict"),
payload.to_json,
"Content-Type" => "application/json"
)

result = JSON.parse(response.body)
Rails.logger.info("FastAPI response: #{response.body}")
td.update(predicted_effort: result["predicted_effort"])
end

private

def build_payload(task_def)
{
estimated_hours: task_def.estimated_hours,
target_grade: task_def.target_grade,
start_date: task_def.start_date,
due_date: task_def.due_date # ,
# TODO: task sheet for TF-IDF
}
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class ChangeWeightingToEstimatedHoursInTaskDefinitions < ActiveRecord::Migration[8.0]
def change
rename_column :task_definitions, :weighting, :estimated_hours
end
end
5 changes: 5 additions & 0 deletions db/migrate/20260425165056_add_predicted_effort_to_tasks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddPredictedEffortToTasks < ActiveRecord::Migration[8.0]
def change
add_column :task_definitions, :predicted_effort, :float
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class SetDefaultPredictedEffortOnTaskDefinitions < ActiveRecord::Migration[8.0]
def up
change_column_default :task_definitions, :predicted_effort, 1.0
TaskDefinition.where(predicted_effort: nil).update_all(predicted_effort: 1.0)
end

def down
change_column_default :task_definitions, :predicted_effort, nil
end
end
5 changes: 3 additions & 2 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[8.0].define(version: 2026_03_22_230239) do
ActiveRecord::Schema[8.0].define(version: 2026_04_26_101725) do
create_table "activity_types", charset: "utf8mb4", collation: "utf8mb4_general_ci", force: :cascade do |t|
t.string "name", null: false
t.string "abbreviation", null: false
Expand Down Expand Up @@ -420,7 +420,7 @@
t.bigint "unit_id"
t.string "name"
t.string "description", limit: 4096
t.decimal "weighting", precision: 10
t.decimal "estimated_hours", precision: 10
t.datetime "target_date", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
Expand Down Expand Up @@ -450,6 +450,7 @@
t.boolean "use_resources_for_jplag_base_code", default: false, null: false
t.boolean "lock_assessments_to_tutorial_stream", default: false, null: false
t.boolean "requires_discussion", default: false, null: false
t.float "predicted_effort", default: 1.0
t.index ["abbreviation", "unit_id"], name: "index_task_definitions_on_abbreviation_and_unit_id", unique: true
t.index ["group_set_id"], name: "index_task_definitions_on_group_set_id"
t.index ["name", "unit_id"], name: "index_task_definitions_on_name_and_unit_id", unique: true
Expand Down
2 changes: 1 addition & 1 deletion lib/helpers/database_populator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ def generate_tasks_for_unit(unit, unit_details)
abbreviation: "A#{count + 1}",
unit_id: unit.id,
description: faker_random_sentence(5, 10),
weighting: BigDecimal("2"),
estimated_hours: BigDecimal("2"),
target_date: target_date,
upload_requirements: up_reqs,
start_date: start_date,
Expand Down
2 changes: 1 addition & 1 deletion lib/tasks/simulate_jplag_submissions.rake
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ namespace :db do
unit_id: unit.id,
tutorial_stream: unit.tutorial_streams.first,
description: faker_random_sentence(5, 10),
weighting: BigDecimal("2"),
estimated_hours: BigDecimal("2"),
target_date: target_date,
upload_requirements: [
{
Expand Down
8 changes: 4 additions & 4 deletions test/api/comments/comment_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ def test_student_reply_to_other_student_in_same_group
tutorial_stream: unit.tutorial_streams.first,
name: 'Task to switch from ind to group after submission',
description: 'test def',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 1.week,
target_date: Time.zone.now - 1.day,
Expand Down Expand Up @@ -484,7 +484,7 @@ def test_read_receipts_for_task_status_comments
tutorial_stream: unit.tutorial_streams.first,
name: 'test_read_receipts_for_task_status_comments',
description: 'test_read_receipts_for_task_status_comments',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now + 1.week,
Expand Down Expand Up @@ -527,7 +527,7 @@ def test_project_plan_task_comments_dont_show_in_inbox
tutorial_stream: unit.tutorial_streams.first,
name: 'test_project_plan_task_comments_dont_show_in_inbox',
description: 'test_project_plan_task_comments_dont_show_in_inbox',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now + 1.week,
Expand Down Expand Up @@ -574,7 +574,7 @@ def test_discussed_in_class_task_comments_dont_show_in_inbox
tutorial_stream: unit.tutorial_streams.first,
name: 'test_discussed_in_class_task_comments_dont_show_in_inbox',
description: 'test_discussed_in_class_task_comments_dont_show_in_inbox',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now + 1.week,
Expand Down
12 changes: 6 additions & 6 deletions test/api/comments/extension_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test_extension_application
tutorial_stream: project.tutorial_enrolments.first.tutorial.tutorial_stream,
name: 'status task change',
description: 'status task change test',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now - 1.day,
Expand Down Expand Up @@ -94,7 +94,7 @@ def test_extension_application
tutorial_stream: unit.tutorial_streams.first,
name: 'status task change',
description: 'status task change test',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now - 1.day,
Expand Down Expand Up @@ -152,7 +152,7 @@ def test_disallow_student_extensions
tutorial_stream: unit.tutorial_streams.first,
name: 'status task change',
description: 'status task change test',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now - 1.day,
Expand Down Expand Up @@ -201,7 +201,7 @@ def test_extension_on_resubmit
tutorial_stream: unit.tutorial_streams.first,
name: 'Task past due - for revert',
description: 'Task past due',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now + 1.day,
Expand Down Expand Up @@ -257,7 +257,7 @@ def test_extension_in_inbox
tutorial_stream: unit.tutorial_streams.first,
name: 'status task change',
description: 'status task change test',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now - 1.day,
Expand Down Expand Up @@ -317,7 +317,7 @@ def test_flexible_dates
tutorial_stream: unit.tutorial_streams.first,
name: 'Flexible Dates Test',
description: 'Flexible Dates Test',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now + 1.day,
Expand Down
8 changes: 4 additions & 4 deletions test/api/comments/scorm_extension_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def test_scorm_extension_request
tutorial_stream: unit.tutorial_streams.first,
name: 'Scorm extension request',
description: 'Scorm extension request',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now - 1.week,
Expand Down Expand Up @@ -82,7 +82,7 @@ def test_read_by_main_tutor
tutorial_stream: unit.tutorial_streams.first,
name: 'Scorm extension request',
description: 'Scorm extension request',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now - 1.week,
Expand Down Expand Up @@ -138,7 +138,7 @@ def test_auto_grant_for_tutor
tutorial_stream: unit.tutorial_streams.first,
name: 'Scorm extension request',
description: 'Scorm extension request',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now - 1.week,
Expand Down Expand Up @@ -188,7 +188,7 @@ def test_scorm_extension_assessment
tutorial_stream: unit.tutorial_streams.first,
name: 'Scorm extension',
description: 'Scorm extension',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now - 1.week,
Expand Down
2 changes: 1 addition & 1 deletion test/api/comments/status_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def test_status_comments
abbreviation: 'test_status_comments',
name: 'test_status_comments',
description: 'test_status_comments',
weighting: 4,
estimated_hours: 4,
target_grade: 0,
start_date: Time.zone.now - 2.weeks,
target_date: Time.zone.now - 1.week,
Expand Down
Loading
Loading