diff --git a/questions/migrations/0038_question_latest_aggregate_forecast.py b/questions/migrations/0038_question_latest_aggregate_forecast.py new file mode 100644 index 0000000000..e31a39d774 --- /dev/null +++ b/questions/migrations/0038_question_latest_aggregate_forecast.py @@ -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 + + +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), + ] diff --git a/questions/models.py b/questions/models.py index e167a8be94..4d9de06509 100644 --- a/questions/models.py +++ b/questions/models.py @@ -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) diff --git a/questions/services/forecasts.py b/questions/services/forecasts.py index ee6f4616aa..1c4cf2947c 100644 --- a/questions/services/forecasts.py +++ b/questions/services/forecasts.py @@ -1,3 +1,4 @@ +from bisect import bisect_left import logging from collections import defaultdict from datetime import datetime, timedelta, timezone as dt_timezone @@ -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 @@ -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 + ) + else: + question.latest_aggregate_forecast = None + question.save(update_fields=["latest_aggregate_forecast"])