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
45 changes: 45 additions & 0 deletions questions/migrations/0038_question_latest_aggregate_forecast.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 5.2.14 on 2026-06-18 15:18

from django.db import migrations, models
from django.utils import timezone

from questions.serializers.aggregate_forecasts import serialize_aggregate_forecast

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid runtime serializer imports inside migrations.

On Line 6, importing questions.serializers.aggregate_forecasts.serialize_aggregate_forecast makes this migration depend on mutable application code. If that serializer changes later, migration replay can fail on clean installs. Keep serialization logic snapshot-local inside this migration (or serialize from historical model fields directly).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@questions/migrations/0038_question_latest_aggregate_forecast.py` at line 6,
Remove the import of serialize_aggregate_forecast from
questions.serializers.aggregate_forecasts at the top of the migration file.
Instead, copy the serialization logic directly into the migration file or
implement the serialization logic inline without relying on external application
code. Update any calls to serialize_aggregate_forecast within the migration to
use the local implementation instead. This ensures the migration is
self-contained and will not break if the external serializer changes in the
future.



def migrate(apps, schema_editor):
Question = apps.get_model("questions", "Question")
now = timezone.now()
for question in Question.objects.filter(
actual_close_time__isnull=True,
open_time__lte=now,
):
latest = (
question.aggregate_forecasts.filter(start_time__lte=now)
.order_by("-start_time")
.first()
)
question.latest_aggregate_forecast = (
None
if latest is None
else serialize_aggregate_forecast(latest, question.type, full=True)
)
question.save(update_fields=["latest_aggregate_forecast"])


class Migration(migrations.Migration):
dependencies = [
("questions", "0037_question_options_order"),
]

operations = [
migrations.AddField(
model_name="question",
name="latest_aggregate_forecast",
field=models.JSONField(
blank=True,
help_text="Serialized form of the latest aggregate forecast for this question.",
null=True,
),
),
migrations.RunPython(migrate, reverse_code=migrations.RunPython.noop),
]
5 changes: 5 additions & 0 deletions questions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ class QuestionType(models.TextChoices):
a better choice.
""",
)
latest_aggregate_forecast = models.JSONField(
null=True,
blank=True,
help_text="Serialized form of the latest aggregate forecast for this question.",
)

# description fields
title = models.CharField(max_length=2000)
Expand Down
15 changes: 15 additions & 0 deletions questions/services/forecasts.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from bisect import bisect_left
import logging
from collections import defaultdict
from datetime import datetime, timedelta, timezone as dt_timezone
Expand All @@ -16,6 +17,7 @@
create_subscription,
)
from posts.tasks import run_on_post_forecast
from questions.serializers.aggregate_forecasts import serialize_aggregate_forecast
from questions.services.multiple_choice_handlers import get_all_options_from_history
from scoring.models import Score
from users.constants import ApiForecastingAccess
Expand Down Expand Up @@ -577,3 +579,16 @@ def build_question_forecasts(
AggregateForecast.objects.bulk_update(overwriters, fields, batch_size=50)
AggregateForecast.objects.filter(id__in=[old.id for old in to_delete]).delete()
AggregateForecast.objects.bulk_create(to_create, batch_size=50)

# get the latest aggregation and store its serialized form on the question
latest_index = bisect_left(
aggregation_history, timezone.now(), key=lambda af: af.start_time
)
if latest_index > 0:
latest = aggregation_history[latest_index - 1]
question.latest_aggregate_forecast = serialize_aggregate_forecast(
latest, question.type, full=True
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
else:
question.latest_aggregate_forecast = None
question.save(update_fields=["latest_aggregate_forecast"])
Loading