Conversation
📝 WalkthroughWalkthroughAdds a "pre-prediction" flow: new localization keys, UI shows "Pre‑Predict" for upcoming questions with open_time, prediction eligibility extended to UPCOMING/APPROVED states, forecast components accept a dynamic predictLabel, forecast-availability types refined, and backend validation now requires open_time existence rather than future timing. Changes
Sequence DiagramsequenceDiagram
participant UI as User Interface
participant Button as Predict Button
participant FM as ForecastMaker
participant Utils as Prediction Utils
participant API as Backend API
participant DB as Database
UI->>Button: View UPCOMING question
Button->>Utils: isPostPrePrediction(post)?
Utils-->>Button: true (open_time present & future)
Button->>UI: Render "Pre‑Predict"
UI->>Button: Click "Pre‑Predict"
Button->>FM: Open ForecastMaker(predictLabel)
FM->>UI: Show modal with predictLabel
UI->>FM: Submit pre-prediction
FM->>API: POST forecast
API->>Utils: canPredictQuestion(...)
Utils-->>API: validation OK (UPCOMING with open_time)
API->>DB: Save forecast
DB-->>API: Confirm
API-->>FM: Success
FM-->>UI: Show "This prediction is not scored yet..."
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🚀 Preview EnvironmentYour preview environment is ready!
Details
ℹ️ Preview Environment InfoIsolation:
Limitations:
Cleanup:
|
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
front_end/src/components/forecast_maker/prediction_status_message.tsx (1)
15-20:⚠️ Potential issue | 🟠 MajorUse pre-prediction copy for UPCOMING pre-predictable posts too.
Line 15 still returns
predictionUpcomingMessage, so upcoming questions in the pre-prediction flow can show the old message instead of the required pre-prediction message.🔧 Suggested fix
+import { isPostPrePrediction } from "@/utils/questions/predictions"; + const PredictionStatusMessage: FC<Props> = ({ post }) => { const t = useTranslations(); + + if (isPostPrePrediction(post)) { + return <>{t("prePredictionMessage")}</>; + } switch (post.status) { case PostStatus.UPCOMING: { return <>{t("predictionUpcomingMessage")}</>; } case PostStatus.APPROVED: { - if (Date.parse(post.open_time) > Date.now()) { - return <>{t("prePredictionMessage")}</>; - } return null; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@front_end/src/components/forecast_maker/prediction_status_message.tsx` around lines 15 - 20, The UPCOMING branch in prediction_status_message.tsx currently always returns t("predictionUpcomingMessage") but should use the pre-prediction copy for posts that are still in the pre-prediction window (same condition used in the APPROVED branch). Update the PostStatus.UPCOMING case to check the post.open_time (Date.parse(post.open_time) > Date.now()) and return t("prePredictionMessage") when that condition is true, otherwise fall back to t("predictionUpcomingMessage"); mirror the logic used in the PostStatus.APPROVED branch so both statuses show the pre-prediction copy for pre-predictable posts.front_end/src/components/forecast_maker/forecast_maker_group/forecast_maker_group_binary.tsx (1)
228-239:⚠️ Potential issue | 🔴 CriticalUPCOMING group rows are still dropped from the submit payload.
Line 231 only keeps
OPENquestions, even though Lines 390-392 now let users editUPCOMINGrows. Any pre-prediction entered in a group will be silently discarded on submit.🐛 Minimal fix
return questionOptions.filter((option) => { - if (option.status !== QuestionStatus.OPEN) return false; + if ( + option.status !== QuestionStatus.OPEN && + option.status !== QuestionStatus.UPCOMING + ) { + return false; + } if (option.isDirty) return true;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@front_end/src/components/forecast_maker/forecast_maker_group/forecast_maker_group_binary.tsx` around lines 228 - 239, The submit filter in questionsToSubmit currently drops any UPCOMING rows because it only allows QuestionStatus.OPEN; update the predicate to include UPCOMING so pre-predictions entered in group rows are preserved. Specifically, in the useMemo that defines questionsToSubmit (variable questionOptions filter), change the initial status check to allow option.status === QuestionStatus.OPEN || option.status === QuestionStatus.UPCOMING (or invert to return false only when status is neither OPEN nor UPCOMING), keeping the rest of the dirty/predicted logic intact; this ensures entries editable by the picker (see isPickerDirty, hasSomeActiveUserForecasts, and isOpenQuestionPredicted) are included in the submit payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@front_end/messages/es.json`:
- Line 19: The "prePredict" translation value uses "Pre-Predecir" which sounds
unnatural; update the ES string value for the "prePredict" key to a concise,
natural Spanish CTA such as "Predecir" (replace the value for the "prePredict"
key with "Predecir").
In
`@front_end/src/components/detailed_question_card/detailed_question_card/index.tsx`:
- Around line 57-58: Remove the debug console.log calls that print
"forecastAvailability" and "status" from the DetailedQuestionCard component
(index.tsx) — locate the two console.log("forecastAvailability",
forecastAvailability) and console.log("status", status) statements and delete
them so no debug logging remains in production; if you need runtime inspection
keep a guarded logger or use React DevTools instead.
In
`@front_end/src/components/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_continuous.tsx`:
- Around line 791-792: questions for upcoming conditional branches are being
filtered out because questionsToSubmit only includes QuestionStatus.OPEN, and
handlePredictSubmit returns early for upcoming branches; update the logic so
upcoming questions are treated as submittable: modify the questionsToSubmit
construction to include both QuestionStatus.OPEN and the upcoming status (e.g.,
QuestionStatus.UPCOMING) and/or adjust handlePredictSubmit to not return early
for upcoming branch submissions (ensure it validates and includes upcoming
questions in the submission path); reference the variables/functions
questionsToSubmit and handlePredictSubmit and the constant QuestionStatus.OPEN
(and QuestionStatus.UPCOMING) as the places to change so both the inline predict
button and modal will submit for upcoming branches.
In
`@front_end/src/components/forecast_maker/forecast_maker_group/continuous_input_wrapper.tsx`:
- Around line 282-285: The current render condition uses the group-level
canPredict to enable controls for UPCOMING rows; change it to also require the
row's own open_time and that it has passed. Specifically, update the conditional
that references QuestionStatus.UPCOMING (and the similar check at the other
location) to only allow editing when option.question.open_time is present and
Date.now() (or new Date()) is >= new Date(option.question.open_time). Keep
existing canPredict but AND it with the per-row check on
option.question.open_time so UPCOMING rows without a valid/opened open_time
remain gated.
In `@front_end/src/components/forecast_maker/index.tsx`:
- Around line 28-29: The pre-predict label should be derived from the same
predictability predicate used elsewhere instead of calling isPostPrePrediction;
replace the isPrePrediction assignment to use the canonical predictability
function (e.g., isPredictable, canPredictPost, or whatever predicate your
codebase uses for "forecastable" checks) and base predictLabel on that result
(keep predictLabel = isPrePrediction ? t("prePredict") : t("predict")), removing
the isPostPrePrediction usage so open_time > now gating no longer causes stale
UPCOMING cases to show the wrong CTA.
In `@front_end/src/utils/questions/predictions.ts`:
- Around line 94-97: The nil-check for open_time is too narrow: replace the
current strict "!== undefined" check on conditional.condition_child.open_time
with the same null/undefined check used elsewhere (e.g., use != null or an
explicit !== undefined && !== null) so that null values are treated as missing;
update the return expression that uses conditionClosedOrResolved and
conditional.condition_child.open_time accordingly to match the other branches'
behavior.
- Around line 24-35: isPostPrePrediction's group branch is using
QuestionStatus.UPCOMING instead of checking each question's open_time, causing
stale pre-prediction labels; update the group_of_questions branch in
isPostPrePrediction to mirror the single-question logic by comparing each
question.open_time to now (use isNil and parseISO like the post.question branch)
— i.e., return true only if every question has a non-nil open_time and
parseISO(open_time) > now for all entries in post.group_of_questions.questions.
In `@questions/views.py`:
- Around line 120-126: The guard change lets pre-open forecasts be created but
create_forecast_bulk() -> create_forecast() currently persists forecasts with
start_time=now, causing them to be picked up by the aggregation (which filters
by start_time__lte=timezone.now()); modify create_forecast() so when given a
Question whose open_time is in the future it sets the persisted forecast's
start_time to question.open_time (instead of timezone.now()), or alternatively
update the aggregation query to explicitly exclude forecasts with start_time >
question.open_time; make the change in the create_forecast() implementation
called by create_forecast_bulk() and ensure the same logic covers the other
creation path referenced around the 199-203 area.
---
Outside diff comments:
In
`@front_end/src/components/forecast_maker/forecast_maker_group/forecast_maker_group_binary.tsx`:
- Around line 228-239: The submit filter in questionsToSubmit currently drops
any UPCOMING rows because it only allows QuestionStatus.OPEN; update the
predicate to include UPCOMING so pre-predictions entered in group rows are
preserved. Specifically, in the useMemo that defines questionsToSubmit (variable
questionOptions filter), change the initial status check to allow option.status
=== QuestionStatus.OPEN || option.status === QuestionStatus.UPCOMING (or invert
to return false only when status is neither OPEN nor UPCOMING), keeping the rest
of the dirty/predicted logic intact; this ensures entries editable by the picker
(see isPickerDirty, hasSomeActiveUserForecasts, and isOpenQuestionPredicted) are
included in the submit payload.
In `@front_end/src/components/forecast_maker/prediction_status_message.tsx`:
- Around line 15-20: The UPCOMING branch in prediction_status_message.tsx
currently always returns t("predictionUpcomingMessage") but should use the
pre-prediction copy for posts that are still in the pre-prediction window (same
condition used in the APPROVED branch). Update the PostStatus.UPCOMING case to
check the post.open_time (Date.parse(post.open_time) > Date.now()) and return
t("prePredictionMessage") when that condition is true, otherwise fall back to
t("predictionUpcomingMessage"); mirror the logic used in the PostStatus.APPROVED
branch so both statuses show the pre-prediction copy for pre-predictable posts.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e50cc15d-f48c-46f8-85d7-4c76c7b579c1
📒 Files selected for processing (28)
front_end/messages/cs.jsonfront_end/messages/en.jsonfront_end/messages/es.jsonfront_end/messages/pt.jsonfront_end/messages/zh-TW.jsonfront_end/messages/zh.jsonfront_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/action_buttons/index.tsxfront_end/src/app/(main)/questions/[id]/components/question_view/consumer_question_view/action_buttons/question_predict_button.tsxfront_end/src/components/detailed_question_card/detailed_question_card/index.tsxfront_end/src/components/forecast_maker/continuous_group_accordion/group_forecast_accordion.tsxfront_end/src/components/forecast_maker/forecast_expiration.tsxfront_end/src/components/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_binary.tsxfront_end/src/components/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_continuous.tsxfront_end/src/components/forecast_maker/forecast_maker_conditional/index.tsxfront_end/src/components/forecast_maker/forecast_maker_group/continuous_input_wrapper.tsxfront_end/src/components/forecast_maker/forecast_maker_group/forecast_maker_group_binary.tsxfront_end/src/components/forecast_maker/forecast_maker_group/forecast_maker_group_continuous.tsxfront_end/src/components/forecast_maker/forecast_maker_group/index.tsxfront_end/src/components/forecast_maker/forecast_maker_question/forecast_maker_binary.tsxfront_end/src/components/forecast_maker/forecast_maker_question/forecast_maker_continuous.tsxfront_end/src/components/forecast_maker/forecast_maker_question/forecast_maker_multiple_choice.tsxfront_end/src/components/forecast_maker/forecast_maker_question/index.tsxfront_end/src/components/forecast_maker/index.tsxfront_end/src/components/forecast_maker/prediction_status_message.tsxfront_end/src/types/question.tsfront_end/src/utils/questions/forecastAvailability.tsfront_end/src/utils/questions/predictions.tsquestions/views.py
front_end/src/components/detailed_question_card/detailed_question_card/index.tsx
Outdated
Show resolved
Hide resolved
...mponents/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_continuous.tsx
Show resolved
Hide resolved
| if ( | ||
| (option.question.status === QuestionStatus.OPEN || | ||
| option.question.status === QuestionStatus.UPCOMING) && | ||
| canPredict |
There was a problem hiding this comment.
Gate UPCOMING rows on the row’s own open_time.
canPredict here is group-level, so another question can make it true for this wrapper. With the new status checks, an individual UPCOMING row still renders controls and becomes editable even when option.question.open_time is missing, but the backend still rejects that submission.
Suggested fix
const predictButtonIsDisabled =
forecastInputMode === ContinuousForecastInputType.Slider
? option.userSliderForecast === null &&
option.question.status !== QuestionStatus.OPEN
: validateAllQuantileInputs({
question: option.question,
components: option.userQuantileForecast,
t,
}).length !== 0 || !isNil(submitError);
+ const isQuestionForecastable =
+ !isNil(option.question.open_time) &&
+ (option.question.status === QuestionStatus.OPEN ||
+ option.question.status === QuestionStatus.UPCOMING);
- if (
- (option.question.status === QuestionStatus.OPEN ||
- option.question.status === QuestionStatus.UPCOMING) &&
- canPredict
- ) {
+ if (canPredict && isQuestionForecastable) {
SubmitControls = (
<>
<FormError
@@
disabled={
- !canPredict ||
- (option.question.status !== QuestionStatus.OPEN &&
- option.question.status !== QuestionStatus.UPCOMING)
+ !canPredict || !isQuestionForecastable
}Also applies to: 421-423
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@front_end/src/components/forecast_maker/forecast_maker_group/continuous_input_wrapper.tsx`
around lines 282 - 285, The current render condition uses the group-level
canPredict to enable controls for UPCOMING rows; change it to also require the
row's own open_time and that it has passed. Specifically, update the conditional
that references QuestionStatus.UPCOMING (and the similar check at the other
location) to only allow editing when option.question.open_time is present and
Date.now() (or new Date()) is >= new Date(option.question.open_time). Keep
existing canPredict but AND it with the per-row check on
option.question.open_time so UPCOMING rows without a valid/opened open_time
remain gated.
| const isPrePrediction = isPostPrePrediction(post); | ||
| const predictLabel = isPrePrediction ? t("prePredict") : t("predict"); |
There was a problem hiding this comment.
Derive the pre-predict label from the same predicate as predictability.
Line 28 reintroduces the old open_time > now gate via isPostPrePrediction(). The rest of this flow now admits stale UPCOMING + open_time cases so open-date edits stay forecastable; in that state the user can still enter the maker, but this component drops back to a plain Predict CTA.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@front_end/src/components/forecast_maker/index.tsx` around lines 28 - 29, The
pre-predict label should be derived from the same predictability predicate used
elsewhere instead of calling isPostPrePrediction; replace the isPrePrediction
assignment to use the canonical predictability function (e.g., isPredictable,
canPredictPost, or whatever predicate your codebase uses for "forecastable"
checks) and base predictLabel on that result (keep predictLabel =
isPrePrediction ? t("prePredict") : t("predict")), removing the
isPostPrePrediction usage so open_time > now gating no longer causes stale
UPCOMING cases to show the wrong CTA.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
front_end/src/components/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_continuous.tsx (1)
839-853: Minor inconsistency inpredictLabelhandling between modal and inline button.The inline
PredictButton(line 793) conditionally omitspredictLabelwhen apreviousForecastexists, whileForecastExpirationModalalways receivespredictLabel. This means the modal's submit button will show "Pre-Predict" even when the user is updating an existing forecast on an upcoming question.If this is intentional, no action needed. If you want consistent behavior, consider applying the same conditional logic:
Optional alignment
<ForecastExpirationModal savedState={modalSavedState} setSavedState={setModalSavedState} isOpen={isForecastExpirationModalOpen} onClose={() => { setIsForecastExpirationModalOpen(false); }} onSubmit={handlePredictSubmit} isUserForecastActive={hasUserActiveForecast} isDirty={predictButtonIsDirty} hasUserForecast={hasUserForecast} isSubmissionDisabled={predictButtonIsDisabled} questionDuration={questionDuration} - predictLabel={predictLabel} + predictLabel={previousForecast ? undefined : predictLabel} />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@front_end/src/components/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_continuous.tsx` around lines 839 - 853, ForecastExpirationModal always receives predictLabel while the inline PredictButton omits predictLabel when previousForecast exists, causing inconsistent submit labels; make behavior consistent by passing predictLabel to ForecastExpirationModal conditionally the same way as in the inline button (use the same previousForecast check used when rendering PredictButton), or update PredictButton to always follow the modal's logic; locate ForecastExpirationModal and PredictButton usages and apply the same conditional expression on predictLabel (refer to predictLabel, previousForecast, ForecastExpirationModal, and PredictButton) so both UI paths show the same label for new vs. existing forecasts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@front_end/src/components/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_continuous.tsx`:
- Around line 839-853: ForecastExpirationModal always receives predictLabel
while the inline PredictButton omits predictLabel when previousForecast exists,
causing inconsistent submit labels; make behavior consistent by passing
predictLabel to ForecastExpirationModal conditionally the same way as in the
inline button (use the same previousForecast check used when rendering
PredictButton), or update PredictButton to always follow the modal's logic;
locate ForecastExpirationModal and PredictButton usages and apply the same
conditional expression on predictLabel (refer to predictLabel, previousForecast,
ForecastExpirationModal, and PredictButton) so both UI paths show the same label
for new vs. existing forecasts.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: eef1f18d-1efc-4a4f-8e57-2697e4c058f9
📒 Files selected for processing (12)
front_end/messages/cs.jsonfront_end/messages/en.jsonfront_end/messages/es.jsonfront_end/messages/pt.jsonfront_end/messages/zh-TW.jsonfront_end/messages/zh.jsonfront_end/src/components/detailed_question_card/detailed_question_card/index.tsxfront_end/src/components/forecast_maker/forecast_maker_conditional/forecast_maker_conditional_continuous.tsxfront_end/src/components/forecast_maker/forecast_maker_group/continuous_input_wrapper.tsxfront_end/src/components/forecast_maker/forecast_maker_group/forecast_maker_group_binary.tsxfront_end/src/components/forecast_maker/prediction_status_message.tsxfront_end/src/utils/questions/predictions.ts
✅ Files skipped from review due to trivial changes (5)
- front_end/messages/cs.json
- front_end/messages/pt.json
- front_end/messages/en.json
- front_end/messages/zh-TW.json
- front_end/messages/zh.json
🚧 Files skipped from review as they are similar to previous changes (5)
- front_end/src/components/forecast_maker/prediction_status_message.tsx
- front_end/src/components/detailed_question_card/detailed_question_card/index.tsx
- front_end/messages/es.json
- front_end/src/components/forecast_maker/forecast_maker_group/continuous_input_wrapper.tsx
- front_end/src/utils/questions/predictions.ts
closes #3563
Summary by CodeRabbit
New Features
Internationalization