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: 3 additions & 0 deletions app/grandchallenge/challenges/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ class Meta:
"algorithm_selectable_gpu_type_choices_for_tasks",
"algorithm_maximum_settable_memory_gb_for_tasks",
"average_size_test_case_mb_for_tasks",
"average_size_job_output_mb_for_tasks",
"inference_time_average_minutes_for_tasks",
"task_id_for_phases",
"number_of_teams_for_phases",
Expand Down Expand Up @@ -436,6 +437,7 @@ def __init__(self, *args, **kwargs):
"algorithm_selectable_gpu_type_choices_for_tasks",
"algorithm_maximum_settable_memory_gb_for_tasks",
"average_size_test_case_mb_for_tasks",
"average_size_job_output_mb_for_tasks",
"inference_time_average_minutes_for_tasks",
css_class="border rounded px-2 my-4",
),
Expand Down Expand Up @@ -477,6 +479,7 @@ def _clean_task_lists_equal_length(self, cleaned_data):
"algorithm_selectable_gpu_type_choices_for_tasks",
"algorithm_maximum_settable_memory_gb_for_tasks",
"average_size_test_case_mb_for_tasks",
"average_size_job_output_mb_for_tasks",
"inference_time_average_minutes_for_tasks",
):
field_value = cleaned_data.get(field_name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generated by Django 5.2.13 on 2026-04-21 11:04

from django.db import migrations, models

import grandchallenge.core.validators


class Migration(migrations.Migration):

dependencies = [
("challenges", "0071_alter_challengerequest_status_and_more"),
]

operations = [
migrations.AddField(
model_name="challengerequest",
name="average_size_job_output_mb_for_tasks",
field=models.JSONField(
default=list,
help_text="Average size of the output per job in MB, for each task.",
validators=[
grandchallenge.core.validators.JSONValidator(
schema={
"$schema": "http://json-schema.org/draft-07/schema",
"items": {
"maximum": 10000,
"minimum": 0,
"type": "integer",
},
"type": "array",
}
)
],
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from django.db import migrations


def set_average_size_job_output_mb_for_tasks(apps, schema_editor):
ChallengeRequest = apps.get_model( # noqa: N806
"challenges", "ChallengeRequest"
)

for challenge_request in ChallengeRequest.objects.all():
challenge_request.average_size_job_output_mb_for_tasks = [
0 for _ in challenge_request.task_ids
]
challenge_request.save()


class Migration(migrations.Migration):

dependencies = [
(
"challenges",
"0072_challengerequest_average_size_output_mb_for_tasks",
),
]

operations = [
migrations.RunPython(
set_average_size_job_output_mb_for_tasks, elidable=True
),
]
59 changes: 55 additions & 4 deletions app/grandchallenge/challenges/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,7 @@ class ChallengeRequestStatusChoices(models.TextChoices):
"algorithm_selectable_gpu_type_choices_for_tasks",
"algorithm_maximum_settable_memory_gb_for_tasks",
"average_size_test_case_mb_for_tasks",
"average_size_job_output_mb_for_tasks",
"inference_time_average_minutes_for_tasks",
"task_id_for_phases",
"number_of_teams_for_phases",
Expand Down Expand Up @@ -1183,6 +1184,23 @@ class ChallengeRequest(UUIDModel, ChallengeBase):
)
],
)
average_size_job_output_mb_for_tasks = models.JSONField(
help_text="Average size of the output per job in MB, for each task.",
default=list,
validators=[
JSONValidator(
schema={
"$schema": "http://json-schema.org/draft-07/schema",
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 10000,
},
}
)
],
)
number_of_submissions_per_team_for_phases = models.JSONField(
help_text="Number of submissions per team for each phase",
default=list,
Expand Down Expand Up @@ -1545,6 +1563,13 @@ def average_size_test_case_mb_for_phases(self):
for task_index in self.task_index_for_phases
]

@property
def average_size_job_output_mb_for_phases(self):
return [
self.average_size_job_output_mb_for_tasks[task_index]
for task_index in self.task_index_for_phases
]

@cached_property
def number_of_submissions_for_phases(self):
return [
Expand Down Expand Up @@ -1601,15 +1626,35 @@ def total_compute_time(self):

@cached_property
def data_storage_size_gb_for_phases(self):
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if it was better to split this into input/output. Opted to keep this in a single property for now.

return [
round(n_images * image_mb * settings.MEGABYTE / settings.GIGABYTE)
for n_images, image_mb in zip(
input_size_mb_for_phases = [
n_images * input_mb
for n_images, input_mb in zip(
self.number_of_test_cases_for_phases,
self.average_size_test_case_mb_for_phases,
strict=True,
)
]

output_size_mb_for_phases = [
n_jobs * output_mb
for n_jobs, output_mb in zip(
self.number_of_algorithm_jobs_for_phases,
self.average_size_job_output_mb_for_phases,
strict=True,
)
]
return [
round(
(input_size_mb + output_size_mb)
* (settings.MEGABYTE / settings.GIGABYTE)
)
for input_size_mb, output_size_mb in zip(
input_size_mb_for_phases,
output_size_mb_for_phases,
strict=True,
)
]

@property
def number_of_docker_images_per_team_for_tasks(self):
# A docker image for a later phase should also be submitted to an earlier one.
Expand Down Expand Up @@ -1770,7 +1815,7 @@ def compute_costs_euros_for_tasks(self):
for task_id in self.task_ids
]

@cached_property
@property
def data_storage_costs_euros_for_tasks(self):
return [
sum(
Expand Down Expand Up @@ -1892,6 +1937,9 @@ def costs_for_tasks(self):
"average_size_test_case_mb": self.average_size_test_case_mb_for_tasks[
task_index
],
"average_size_job_output_mb": self.average_size_job_output_mb_for_tasks[
task_index
],
"compute_costs_euros": self.compute_costs_euros_for_tasks[
task_index
],
Expand Down Expand Up @@ -1928,6 +1976,9 @@ def get_costs_for_phases_in_task(self, task_id):
"data_storage_size_gb": self.data_storage_size_gb_for_phases[
phase_index
],
"number_of_jobs": self.number_of_algorithm_jobs_for_phases[
phase_index
],
"compute_costs_euros": self.compute_costs_euros_for_phases[
phase_index
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ <h5>Task {{ task.id }}</h5>
<tr>
<td>Data storage phase {{ forloop.counter }}</td>
<td>{{ task.average_size_test_case_mb }} MB average size per case,
{{ phase.number_of_test_cases }} case{{ phase.number_of_test_cases|pluralize }},
{{ phase.number_of_test_cases }} case{{ phase.number_of_test_cases|pluralize }},
{{ task.average_size_job_output_mb }} MB average output per job,
{{ phase.number_of_jobs }} job{{ phase.number_of_jobs|pluralize }},
for {{ num_support_years }} year{{ num_support_years|pluralize }}</td>
<td class="text-right">{{ object.storage_costs_euros_per_gb|euro }}</td>
<td>{{ phase.data_storage_size_gb }}</td>
Expand Down
1 change: 1 addition & 0 deletions app/grandchallenge/challenges/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,7 @@ def get_initial(self):
],
"algorithm_maximum_settable_memory_gb_for_tasks": [32, 32],
"average_size_test_case_mb_for_tasks": [10, 40],
"average_size_job_output_mb_for_tasks": [1, 1],
"inference_time_average_minutes_for_tasks": [5, 10],
"task_ids": [1, 2],
"task_id_for_phases": [1, 1, 2, 2],
Expand Down
7 changes: 7 additions & 0 deletions app/tests/challenges_tests/test_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def test_budget_update_form():
"algorithm_maximum_settable_memory_gb_for_tasks": "[32, 32]",
"algorithm_selectable_gpu_type_choices_for_tasks": '[["", "T4"],["", "A10G", "T4"]]',
"average_size_test_case_mb_for_tasks": "[10, 100]",
"average_size_job_output_mb_for_tasks": "[1, 1]",
"task_id_for_phases": "[1, 1, 2, 2]",
"number_of_teams_for_phases": "[500, 500, 500, 500]",
"number_of_submissions_per_team_for_phases": "[10, 1, 10, 1]",
Expand All @@ -121,6 +122,7 @@ def test_budget_update_form():
"algorithm_maximum_settable_memory_gb_for_tasks": "[32, 32]",
"algorithm_selectable_gpu_type_choices_for_tasks": '[["", "T4"],["", "A10G", "T4"]]',
"average_size_test_case_mb_for_tasks": "[10, 100]",
"average_size_job_output_mb_for_tasks": "[1, 1]",
"inference_time_average_minutes_for_tasks": "[5, 10]",
"task_id_for_phases": "[1, 1, 2, 2]",
"number_of_teams_for_phases": "[500, 500, 500, 500]",
Expand Down Expand Up @@ -150,6 +152,10 @@ def test_budget_update_form():
{"average_size_test_case_mb_for_tasks": "[10]"},
"not all tasks defined",
),
(
{"average_size_job_output_mb_for_tasks": "[1]"},
"not all tasks defined",
),
(
{"inference_time_average_minutes_for_tasks": "[10]"},
"not all tasks defined",
Expand Down Expand Up @@ -186,6 +192,7 @@ def test_budget_update_form_invalid(invalid_data, reason_invalid):
"algorithm_maximum_settable_memory_gb_for_tasks": "[32, 32]",
"algorithm_selectable_gpu_type_choices_for_tasks": '[["", "T4"],["", "A10G", "T4"]]',
"average_size_test_case_mb_for_tasks": "[10, 100]",
"average_size_job_output_mb_for_tasks": "[1, 1]",
"inference_time_average_minutes_for_tasks": "[5, 10]",
"task_id_for_phases": "[1, 1, 2, 2]",
"number_of_teams_for_phases": "[10, 10, 10, 10]",
Expand Down
38 changes: 35 additions & 3 deletions app/tests/challenges_tests/test_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@

from tests.factories import ChallengeRequestFactory

_migration = importlib.import_module(
_migration_0070 = importlib.import_module(
"grandchallenge.challenges.migrations"
".0070_assign_review_perm_to_reviewers_group"
)
_migration_0073 = importlib.import_module(
"grandchallenge.challenges.migrations"
".0073_set_average_size_job_output_for_tasks"
)


@pytest.mark.django_db
Expand All @@ -30,7 +34,9 @@ def test_assign_review_perm_to_reviewers_group(settings):
reviewers_group, challenge_request
)

_migration.assign_review_perm_to_reviewers_group(apps, schema_editor=None)
_migration_0070.assign_review_perm_to_reviewers_group(
apps, schema_editor=None
)

assert "review_challengerequest" in get_group_perms(
reviewers_group, challenge_request
Expand All @@ -53,8 +59,34 @@ def test_assign_change_perm_to_creators():
challenge_request.creator, challenge_request
)

_migration.assign_change_perm_to_creators(apps, schema_editor=None)
_migration_0070.assign_change_perm_to_creators(apps, schema_editor=None)

assert "change_challengerequest" in get_user_perms(
challenge_request.creator, challenge_request
)


@pytest.mark.django_db
@pytest.mark.parametrize(
"task_ids, expected",
[
([], []),
([1], [0]),
([1, 2, 3], [0, 0, 0]),
],
)
def test_set_average_size_job_output_mb_for_tasks(task_ids, expected):
challenge_request = ChallengeRequestFactory(
task_ids=task_ids,
average_size_job_output_mb_for_tasks=[],
)

# The field is empty before the migration.
assert challenge_request.average_size_job_output_mb_for_tasks == []

_migration_0073.set_average_size_job_output_mb_for_tasks(
apps, schema_editor=None
)

challenge_request.refresh_from_db()
assert challenge_request.average_size_job_output_mb_for_tasks == expected
Loading
Loading