We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Challenge runtime statistics are not correct
Looking at the Tiger statistics, for Segmentation and Detection (Final) phase:

Average algorithm job duration for Segmentation and Detection (Final):	25 minutes
Total algorithm job duration for Segmentation and Detection (Final):	4 days

There has been 1 submission to this phase:

![MicrosoftTeams-image (3)](https://user-images.githubusercontent.com/12661555/172356728-3c3656ca-cbf9-48e6-a886-29435e11e660.png)

Looking at a couple of those results they seem to take ~30 seconds each, and maybe 50 jobs. I think what is happening is that the algorithm was used for multiple phases, and there are different versions, which are inflating the runtime statistics.



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `app/grandchallenge/pages/views.py`
Content:
```
1 from django.contrib import messages
2 from django.contrib.auth.mixins import UserPassesTestMixin
3 from django.db.models import Q
4 from django.http import Http404
5 from django.views.generic import (
6     CreateView,
7     DeleteView,
8     DetailView,
9     ListView,
10     TemplateView,
11     UpdateView,
12 )
13 from guardian.mixins import LoginRequiredMixin
14 from guardian.mixins import (
15     PermissionRequiredMixin as ObjectPermissionRequiredMixin,
16 )
17 
18 from grandchallenge.algorithms.models import Job
19 from grandchallenge.core.mixins import UserIsStaffMixin
20 from grandchallenge.evaluation.models import Submission
21 from grandchallenge.evaluation.utils import SubmissionKindChoices
22 from grandchallenge.pages.forms import PageCreateForm, PageUpdateForm
23 from grandchallenge.pages.models import Page
24 from grandchallenge.subdomains.utils import reverse, reverse_lazy
25 
26 
27 class ChallengeFilteredQuerysetMixin:
28     def get_queryset(self):
29         queryset = super().get_queryset()
30         return queryset.filter(Q(challenge=self.request.challenge))
31 
32 
33 class ChallengeFormKwargsMixin:
34     def get_form_kwargs(self):
35         kwargs = super().get_form_kwargs()
36         kwargs.update({"challenge": self.request.challenge})
37         return kwargs
38 
39 
40 class PageCreate(
41     LoginRequiredMixin,
42     ObjectPermissionRequiredMixin,
43     ChallengeFormKwargsMixin,
44     CreateView,
45 ):
46     model = Page
47     form_class = PageCreateForm
48     permission_required = "change_challenge"
49     raise_exception = True
50     login_url = reverse_lazy("account_login")
51 
52     def get_permission_object(self):
53         return self.request.challenge
54 
55     def form_valid(self, form):
56         form.instance.challenge = self.request.challenge
57         return super().form_valid(form)
58 
59 
60 class PageList(
61     LoginRequiredMixin,
62     ObjectPermissionRequiredMixin,
63     ChallengeFilteredQuerysetMixin,
64     ListView,
65 ):
66     model = Page
67     permission_required = "change_challenge"
68     raise_exception = True
69     login_url = reverse_lazy("account_login")
70 
71     def get_permission_object(self):
72         return self.request.challenge
73 
74 
75 class PageDetail(
76     UserPassesTestMixin, ChallengeFilteredQuerysetMixin, DetailView
77 ):
78     model = Page
79     raise_exception = True
80     login_url = reverse_lazy("account_login")
81 
82     def test_func(self):
83         user = self.request.user
84         page = self.get_object()
85         return page.can_be_viewed_by(user=user)
86 
87     def get_context_object_name(self, obj):
88         return "currentpage"
89 
90     def get_context_data(self, **kwargs):
91         context = super().get_context_data(**kwargs)
92         context.update(self.object.detail_context)
93         return context
94 
95 
96 class ChallengeHome(PageDetail):
97     def get_object(self, queryset=None):
98         page = self.request.challenge.page_set.first()
99 
100         if page is None:
101             raise Http404("No pages found for this challenge")
102 
103         return page
104 
105 
106 class PageUpdate(
107     LoginRequiredMixin,
108     ObjectPermissionRequiredMixin,
109     ChallengeFilteredQuerysetMixin,
110     ChallengeFormKwargsMixin,
111     UpdateView,
112 ):
113     model = Page
114     form_class = PageUpdateForm
115     permission_required = "change_challenge"
116     raise_exception = True
117     login_url = reverse_lazy("account_login")
118 
119     def get_permission_object(self):
120         return self.request.challenge
121 
122     def form_valid(self, form):
123         response = super().form_valid(form)
124         self.object.move(form.cleaned_data["move"])
125         return response
126 
127 
128 class PageDelete(
129     LoginRequiredMixin,
130     ObjectPermissionRequiredMixin,
131     ChallengeFilteredQuerysetMixin,
132     DeleteView,
133 ):
134     model = Page
135     success_message = "Page was successfully deleted"
136     permission_required = "change_challenge"
137     raise_exception = True
138     login_url = reverse_lazy("account_login")
139 
140     def get_permission_object(self):
141         return self.request.challenge
142 
143     def get_success_url(self):
144         return reverse(
145             "pages:list",
146             kwargs={"challenge_short_name": self.request.challenge.short_name},
147         )
148 
149     def delete(self, request, *args, **kwargs):
150         messages.success(self.request, self.success_message)
151         return super().delete(request, *args, **kwargs)
152 
153 
154 def get_average_job_duration_for_phase(phase):
155     algorithm_images = Submission.objects.filter(
156         phase__pk=phase.pk
157     ).values_list("algorithm_image__pk")
158     jobs = Job.objects.filter(
159         algorithm_image__pk__in=algorithm_images,
160         status=Job.SUCCESS,
161         creator=None,
162     )
163     duration_dict = {
164         "average_duration": jobs.average_duration(),
165         "total_duration": jobs.total_duration(),
166     }
167     return duration_dict
168 
169 
170 class ChallengeStatistics(LoginRequiredMixin, UserIsStaffMixin, TemplateView):
171     template_name = "pages/challenge_statistics.html"
172 
173     def get_context_data(self, **kwargs):
174         context = super().get_context_data()
175         phases = (
176             self.request.challenge.phase_set.filter(
177                 submission_kind=SubmissionKindChoices.ALGORITHM
178             )
179             .select_related("archive")
180             .prefetch_related("archive__items__values")
181             .all()
182         )
183         duration_dict = {}
184         for phase in phases:
185             duration_dict[phase.title] = get_average_job_duration_for_phase(
186                 phase=phase
187             )
188 
189         context.update(
190             {
191                 "average_job_durations": duration_dict,
192             }
193         )
194 
195         return context
196
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/app/grandchallenge/pages/views.py b/app/grandchallenge/pages/views.py
--- a/app/grandchallenge/pages/views.py
+++ b/app/grandchallenge/pages/views.py
@@ -17,7 +17,6 @@
 
 from grandchallenge.algorithms.models import Job
 from grandchallenge.core.mixins import UserIsStaffMixin
-from grandchallenge.evaluation.models import Submission
 from grandchallenge.evaluation.utils import SubmissionKindChoices
 from grandchallenge.pages.forms import PageCreateForm, PageUpdateForm
 from grandchallenge.pages.models import Page
@@ -152,14 +151,9 @@
 
 
 def get_average_job_duration_for_phase(phase):
-    algorithm_images = Submission.objects.filter(
-        phase__pk=phase.pk
-    ).values_list("algorithm_image__pk")
     jobs = Job.objects.filter(
-        algorithm_image__pk__in=algorithm_images,
-        status=Job.SUCCESS,
-        creator=None,
-    )
+        outputs__evaluation_evaluations_as_input__submission__phase=phase,
+    ).distinct()
     duration_dict = {
         "average_duration": jobs.average_duration(),
         "total_duration": jobs.total_duration(),
@@ -172,14 +166,9 @@
 
     def get_context_data(self, **kwargs):
         context = super().get_context_data()
-        phases = (
-            self.request.challenge.phase_set.filter(
-                submission_kind=SubmissionKindChoices.ALGORITHM
-            )
-            .select_related("archive")
-            .prefetch_related("archive__items__values")
-            .all()
-        )
+        phases = self.request.challenge.phase_set.filter(
+            submission_kind=SubmissionKindChoices.ALGORITHM
+        ).all()
         duration_dict = {}
         for phase in phases:
             duration_dict[phase.title] = get_average_job_duration_for_phase(

{"golden_diff": "diff --git a/app/grandchallenge/pages/views.py b/app/grandchallenge/pages/views.py\n--- a/app/grandchallenge/pages/views.py\n+++ b/app/grandchallenge/pages/views.py\n@@ -17,7 +17,6 @@\n \n from grandchallenge.algorithms.models import Job\n from grandchallenge.core.mixins import UserIsStaffMixin\n-from grandchallenge.evaluation.models import Submission\n from grandchallenge.evaluation.utils import SubmissionKindChoices\n from grandchallenge.pages.forms import PageCreateForm, PageUpdateForm\n from grandchallenge.pages.models import Page\n@@ -152,14 +151,9 @@\n \n \n def get_average_job_duration_for_phase(phase):\n-    algorithm_images = Submission.objects.filter(\n-        phase__pk=phase.pk\n-    ).values_list(\"algorithm_image__pk\")\n     jobs = Job.objects.filter(\n-        algorithm_image__pk__in=algorithm_images,\n-        status=Job.SUCCESS,\n-        creator=None,\n-    )\n+        outputs__evaluation_evaluations_as_input__submission__phase=phase,\n+    ).distinct()\n     duration_dict = {\n         \"average_duration\": jobs.average_duration(),\n         \"total_duration\": jobs.total_duration(),\n@@ -172,14 +166,9 @@\n \n     def get_context_data(self, **kwargs):\n         context = super().get_context_data()\n-        phases = (\n-            self.request.challenge.phase_set.filter(\n-                submission_kind=SubmissionKindChoices.ALGORITHM\n-            )\n-            .select_related(\"archive\")\n-            .prefetch_related(\"archive__items__values\")\n-            .all()\n-        )\n+        phases = self.request.challenge.phase_set.filter(\n+            submission_kind=SubmissionKindChoices.ALGORITHM\n+        ).all()\n         duration_dict = {}\n         for phase in phases:\n             duration_dict[phase.title] = get_average_job_duration_for_phase(\n", "issue": "Challenge runtime statistics are not correct\nLooking at the Tiger statistics, for Segmentation and Detection (Final) phase:\r\n\r\nAverage algorithm job duration for Segmentation and Detection (Final):\t25 minutes\r\nTotal algorithm job duration for Segmentation and Detection (Final):\t4 days\r\n\r\nThere has been 1 submission to this phase:\r\n\r\n![MicrosoftTeams-image (3)](https://user-images.githubusercontent.com/12661555/172356728-3c3656ca-cbf9-48e6-a886-29435e11e660.png)\r\n\r\nLooking at a couple of those results they seem to take ~30 seconds each, and maybe 50 jobs. I think what is happening is that the algorithm was used for multiple phases, and there are different versions, which are inflating the runtime statistics.\r\n\r\n\n", "before_files": [{"content": "from django.contrib import messages\nfrom django.contrib.auth.mixins import UserPassesTestMixin\nfrom django.db.models import Q\nfrom django.http import Http404\nfrom django.views.generic import (\n    CreateView,\n    DeleteView,\n    DetailView,\n    ListView,\n    TemplateView,\n    UpdateView,\n)\nfrom guardian.mixins import LoginRequiredMixin\nfrom guardian.mixins import (\n    PermissionRequiredMixin as ObjectPermissionRequiredMixin,\n)\n\nfrom grandchallenge.algorithms.models import Job\nfrom grandchallenge.core.mixins import UserIsStaffMixin\nfrom grandchallenge.evaluation.models import Submission\nfrom grandchallenge.evaluation.utils import SubmissionKindChoices\nfrom grandchallenge.pages.forms import PageCreateForm, PageUpdateForm\nfrom grandchallenge.pages.models import Page\nfrom grandchallenge.subdomains.utils import reverse, reverse_lazy\n\n\nclass ChallengeFilteredQuerysetMixin:\n    def get_queryset(self):\n        queryset = super().get_queryset()\n        return queryset.filter(Q(challenge=self.request.challenge))\n\n\nclass ChallengeFormKwargsMixin:\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs.update({\"challenge\": self.request.challenge})\n        return kwargs\n\n\nclass PageCreate(\n    LoginRequiredMixin,\n    ObjectPermissionRequiredMixin,\n    ChallengeFormKwargsMixin,\n    CreateView,\n):\n    model = Page\n    form_class = PageCreateForm\n    permission_required = \"change_challenge\"\n    raise_exception = True\n    login_url = reverse_lazy(\"account_login\")\n\n    def get_permission_object(self):\n        return self.request.challenge\n\n    def form_valid(self, form):\n        form.instance.challenge = self.request.challenge\n        return super().form_valid(form)\n\n\nclass PageList(\n    LoginRequiredMixin,\n    ObjectPermissionRequiredMixin,\n    ChallengeFilteredQuerysetMixin,\n    ListView,\n):\n    model = Page\n    permission_required = \"change_challenge\"\n    raise_exception = True\n    login_url = reverse_lazy(\"account_login\")\n\n    def get_permission_object(self):\n        return self.request.challenge\n\n\nclass PageDetail(\n    UserPassesTestMixin, ChallengeFilteredQuerysetMixin, DetailView\n):\n    model = Page\n    raise_exception = True\n    login_url = reverse_lazy(\"account_login\")\n\n    def test_func(self):\n        user = self.request.user\n        page = self.get_object()\n        return page.can_be_viewed_by(user=user)\n\n    def get_context_object_name(self, obj):\n        return \"currentpage\"\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context.update(self.object.detail_context)\n        return context\n\n\nclass ChallengeHome(PageDetail):\n    def get_object(self, queryset=None):\n        page = self.request.challenge.page_set.first()\n\n        if page is None:\n            raise Http404(\"No pages found for this challenge\")\n\n        return page\n\n\nclass PageUpdate(\n    LoginRequiredMixin,\n    ObjectPermissionRequiredMixin,\n    ChallengeFilteredQuerysetMixin,\n    ChallengeFormKwargsMixin,\n    UpdateView,\n):\n    model = Page\n    form_class = PageUpdateForm\n    permission_required = \"change_challenge\"\n    raise_exception = True\n    login_url = reverse_lazy(\"account_login\")\n\n    def get_permission_object(self):\n        return self.request.challenge\n\n    def form_valid(self, form):\n        response = super().form_valid(form)\n        self.object.move(form.cleaned_data[\"move\"])\n        return response\n\n\nclass PageDelete(\n    LoginRequiredMixin,\n    ObjectPermissionRequiredMixin,\n    ChallengeFilteredQuerysetMixin,\n    DeleteView,\n):\n    model = Page\n    success_message = \"Page was successfully deleted\"\n    permission_required = \"change_challenge\"\n    raise_exception = True\n    login_url = reverse_lazy(\"account_login\")\n\n    def get_permission_object(self):\n        return self.request.challenge\n\n    def get_success_url(self):\n        return reverse(\n            \"pages:list\",\n            kwargs={\"challenge_short_name\": self.request.challenge.short_name},\n        )\n\n    def delete(self, request, *args, **kwargs):\n        messages.success(self.request, self.success_message)\n        return super().delete(request, *args, **kwargs)\n\n\ndef get_average_job_duration_for_phase(phase):\n    algorithm_images = Submission.objects.filter(\n        phase__pk=phase.pk\n    ).values_list(\"algorithm_image__pk\")\n    jobs = Job.objects.filter(\n        algorithm_image__pk__in=algorithm_images,\n        status=Job.SUCCESS,\n        creator=None,\n    )\n    duration_dict = {\n        \"average_duration\": jobs.average_duration(),\n        \"total_duration\": jobs.total_duration(),\n    }\n    return duration_dict\n\n\nclass ChallengeStatistics(LoginRequiredMixin, UserIsStaffMixin, TemplateView):\n    template_name = \"pages/challenge_statistics.html\"\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data()\n        phases = (\n            self.request.challenge.phase_set.filter(\n                submission_kind=SubmissionKindChoices.ALGORITHM\n            )\n            .select_related(\"archive\")\n            .prefetch_related(\"archive__items__values\")\n            .all()\n        )\n        duration_dict = {}\n        for phase in phases:\n            duration_dict[phase.title] = get_average_job_duration_for_phase(\n                phase=phase\n            )\n\n        context.update(\n            {\n                \"average_job_durations\": duration_dict,\n            }\n        )\n\n        return context\n", "path": "app/grandchallenge/pages/views.py"}], "after_files": [{"content": "from django.contrib import messages\nfrom django.contrib.auth.mixins import UserPassesTestMixin\nfrom django.db.models import Q\nfrom django.http import Http404\nfrom django.views.generic import (\n    CreateView,\n    DeleteView,\n    DetailView,\n    ListView,\n    TemplateView,\n    UpdateView,\n)\nfrom guardian.mixins import LoginRequiredMixin\nfrom guardian.mixins import (\n    PermissionRequiredMixin as ObjectPermissionRequiredMixin,\n)\n\nfrom grandchallenge.algorithms.models import Job\nfrom grandchallenge.core.mixins import UserIsStaffMixin\nfrom grandchallenge.evaluation.utils import SubmissionKindChoices\nfrom grandchallenge.pages.forms import PageCreateForm, PageUpdateForm\nfrom grandchallenge.pages.models import Page\nfrom grandchallenge.subdomains.utils import reverse, reverse_lazy\n\n\nclass ChallengeFilteredQuerysetMixin:\n    def get_queryset(self):\n        queryset = super().get_queryset()\n        return queryset.filter(Q(challenge=self.request.challenge))\n\n\nclass ChallengeFormKwargsMixin:\n    def get_form_kwargs(self):\n        kwargs = super().get_form_kwargs()\n        kwargs.update({\"challenge\": self.request.challenge})\n        return kwargs\n\n\nclass PageCreate(\n    LoginRequiredMixin,\n    ObjectPermissionRequiredMixin,\n    ChallengeFormKwargsMixin,\n    CreateView,\n):\n    model = Page\n    form_class = PageCreateForm\n    permission_required = \"change_challenge\"\n    raise_exception = True\n    login_url = reverse_lazy(\"account_login\")\n\n    def get_permission_object(self):\n        return self.request.challenge\n\n    def form_valid(self, form):\n        form.instance.challenge = self.request.challenge\n        return super().form_valid(form)\n\n\nclass PageList(\n    LoginRequiredMixin,\n    ObjectPermissionRequiredMixin,\n    ChallengeFilteredQuerysetMixin,\n    ListView,\n):\n    model = Page\n    permission_required = \"change_challenge\"\n    raise_exception = True\n    login_url = reverse_lazy(\"account_login\")\n\n    def get_permission_object(self):\n        return self.request.challenge\n\n\nclass PageDetail(\n    UserPassesTestMixin, ChallengeFilteredQuerysetMixin, DetailView\n):\n    model = Page\n    raise_exception = True\n    login_url = reverse_lazy(\"account_login\")\n\n    def test_func(self):\n        user = self.request.user\n        page = self.get_object()\n        return page.can_be_viewed_by(user=user)\n\n    def get_context_object_name(self, obj):\n        return \"currentpage\"\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context.update(self.object.detail_context)\n        return context\n\n\nclass ChallengeHome(PageDetail):\n    def get_object(self, queryset=None):\n        page = self.request.challenge.page_set.first()\n\n        if page is None:\n            raise Http404(\"No pages found for this challenge\")\n\n        return page\n\n\nclass PageUpdate(\n    LoginRequiredMixin,\n    ObjectPermissionRequiredMixin,\n    ChallengeFilteredQuerysetMixin,\n    ChallengeFormKwargsMixin,\n    UpdateView,\n):\n    model = Page\n    form_class = PageUpdateForm\n    permission_required = \"change_challenge\"\n    raise_exception = True\n    login_url = reverse_lazy(\"account_login\")\n\n    def get_permission_object(self):\n        return self.request.challenge\n\n    def form_valid(self, form):\n        response = super().form_valid(form)\n        self.object.move(form.cleaned_data[\"move\"])\n        return response\n\n\nclass PageDelete(\n    LoginRequiredMixin,\n    ObjectPermissionRequiredMixin,\n    ChallengeFilteredQuerysetMixin,\n    DeleteView,\n):\n    model = Page\n    success_message = \"Page was successfully deleted\"\n    permission_required = \"change_challenge\"\n    raise_exception = True\n    login_url = reverse_lazy(\"account_login\")\n\n    def get_permission_object(self):\n        return self.request.challenge\n\n    def get_success_url(self):\n        return reverse(\n            \"pages:list\",\n            kwargs={\"challenge_short_name\": self.request.challenge.short_name},\n        )\n\n    def delete(self, request, *args, **kwargs):\n        messages.success(self.request, self.success_message)\n        return super().delete(request, *args, **kwargs)\n\n\ndef get_average_job_duration_for_phase(phase):\n    jobs = Job.objects.filter(\n        outputs__evaluation_evaluations_as_input__submission__phase=phase,\n    ).distinct()\n    duration_dict = {\n        \"average_duration\": jobs.average_duration(),\n        \"total_duration\": jobs.total_duration(),\n    }\n    return duration_dict\n\n\nclass ChallengeStatistics(LoginRequiredMixin, UserIsStaffMixin, TemplateView):\n    template_name = \"pages/challenge_statistics.html\"\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data()\n        phases = self.request.challenge.phase_set.filter(\n            submission_kind=SubmissionKindChoices.ALGORITHM\n        ).all()\n        duration_dict = {}\n        for phase in phases:\n            duration_dict[phase.title] = get_average_job_duration_for_phase(\n                phase=phase\n            )\n\n        context.update(\n            {\n                \"average_job_durations\": duration_dict,\n            }\n        )\n\n        return context\n", "path": "app/grandchallenge/pages/views.py"}]}