Skip to content
Closed
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
2 changes: 1 addition & 1 deletion front_end/messages/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -1289,7 +1289,7 @@
"zeroPointError0": "Nulový bod není podporován pro diskrétní otázky",
"zeroPointError1": "Nulový bod ({zeroPoint}) by neměl být mezi min ({min}) a max ({max})",
"stepError0": "Krok nemůže být větší než polovina rozsahu: ({halfRange})",
"stepError1": "Krok musí být alespoň 1/200 rozsahu: ({rangePortion})",
"stepError1": "Diskrétní otázky jsou omezeny na {maxOptions} možností. Zvětšete velikost kroku nebo zúžte rozsah.",
"minMaxError0": "Zadejte správné hodnoty min a max",
"minMaxError1": "Minimální hodnota by měla být menší než maximální hodnota",
"rangeWarning0": "Upozornění: Krok nedělí rozsah rovnoměrně. Max snížen na {newMax}",
Expand Down
2 changes: 1 addition & 1 deletion front_end/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1464,7 +1464,7 @@
"zeroPointError0": "Zero point is not supported for discrete questions",
"zeroPointError1": "Zero point ({zeroPoint}) should not be between min ({min}) and max ({max})",
"stepError0": "Step cannot be more than half the range: ({halfRange})",
"stepError1": "Step must be at least 1/200 of the range: ({rangePortion})",
"stepError1": "Discrete questions are limited to {maxOptions} options. Please increase the step size or tighten the bounds.",
"minMaxError0": "Provide correct min and max values",
"minMaxError1": "Minimum value should be less than maximum value",
"rangeWarning0": "Warning: Step does not divide the range evenly. Max reduced to {newMax}",
Expand Down
2 changes: 1 addition & 1 deletion front_end/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -1288,7 +1288,7 @@
"zeroPointError0": "El punto cero no está permitido para preguntas discretas",
"zeroPointError1": "El punto cero ({zeroPoint}) no debe estar entre min ({min}) y max ({max})",
"stepError0": "El paso no puede ser mayor que la mitad del rango: ({halfRange})",
"stepError1": "El paso debe ser al menos 1/200 del rango: ({rangePortion})",
"stepError1": "Las preguntas discretas están limitadas a {maxOptions} opciones. Aumente el tamaño del paso o ajuste los límites.",
"minMaxError0": "Proporcione valores correctos de mínimo y máximo",
"minMaxError1": "El valor mínimo debe ser menor que el valor máximo",
"rangeWarning0": "Advertencia: El paso no divide el rango de manera uniforme. Max reducido a {newMax}",
Expand Down
2 changes: 1 addition & 1 deletion front_end/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -1286,7 +1286,7 @@
"zeroPointError0": "Ponto zero não é suportado para perguntas discretas",
"zeroPointError1": "O ponto zero ({zeroPoint}) não deve estar entre o mínimo ({min}) e o máximo ({max})",
"stepError0": "O passo não pode ser maior que a metade do intervalo: ({halfRange})",
"stepError1": "O passo deve ser pelo menos 1/200 do intervalo: ({rangePortion})",
"stepError1": "Perguntas discretas são limitadas a {maxOptions} opções. Aumente o tamanho do passo ou ajuste os limites.",
"minMaxError0": "Forneça valores mínimos e máximos corretos",
"minMaxError1": "O valor mínimo deve ser menor que o valor máximo",
"rangeWarning0": "Aviso: O passo não divide o intervalo de forma uniforme. Máximo reduzido para {newMax}",
Expand Down
2 changes: 1 addition & 1 deletion front_end/messages/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,7 @@
"zeroPointError0": "不支持離散問題的零點",
"zeroPointError1": "零點 ({zeroPoint}) 不應位於最小值 ({min}) 和最大值 ({max}) 之間",
"stepError0": "步驟不能超過範圍的一半: ({halfRange})",
"stepError1": "步驟至少必須是範圍的1/200: ({rangePortion})",
"stepError1": "離散問題最多限制為{maxOptions}個選項。請增大步驟或縮小範圍。",
"minMaxError0": "提供正確的最小值和最大值",
"minMaxError1": "最小值應小於最大值",
"rangeWarning0": "警告:步驟無法均等地劃分範圍。最大值減至 {newMax}",
Expand Down
2 changes: 1 addition & 1 deletion front_end/messages/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,7 @@
"zeroPointError0": "不支持离散问题的零点",
"zeroPointError1": "零点 ({zeroPoint}) 不应在最小值 ({min}) 和最大值 ({max}) 之间",
"stepError0": "步长不能超过范围的一半: ({halfRange})",
"stepError1": "步长必须至少是范围的1/200: ({rangePortion})",
"stepError1": "离散问题最多限制为{maxOptions}个选项。请增大步长或缩小范围。",
"minMaxError0": "请提供正确的最小值和最大值",
"minMaxError1": "最小值应小于最大值",
"rangeWarning0": "警告: 步长不能平均划分范围。最大值减少到 {newMax}",
Expand Down
9 changes: 9 additions & 0 deletions front_end/src/app/(main)/question-writing/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,15 @@ export default function QuestionChecklist() {
enough that the true value is very unlikely to fall outside, but no
wider.
</li>
<li>
<strong>Limit discrete questions to at most 50 options.</strong>{" "}
Discrete questions are designed for outcomes with a small number of
distinct values (e.g. number of goals in a match, number of seats
won). If your question would have more than 50 possible outcomes,
increase the step size or tighten the bounds. Having too many
discrete options makes the probability mass function chart difficult
to read and undermines the forecasting experience.
</li>
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.

This might be pedantic, but this limit is about the number of in-bound outcomes, not total outcomes.
If we have 50 inbound outcomes, there is 1 more per open boundary.

<li>
<strong>Double check.</strong> Proof-read your submission,
double-check your resolution conditions and ranges, and make sure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { FormError, Input } from "@/components/ui/form_field";
import {
AggregationMethod,
DefaultInboundOutcomeCount,
MaxDiscreteOptionCount,
QuestionDraft,
QuestionWithNumericForecasts,
} from "@/types/question";
Expand Down Expand Up @@ -137,7 +138,10 @@ const NumericQuestionInput: React.FC<{
inbound_outcome_count: isNil(defaultInboundOutcomeCount)
? questionType !== QuestionType.Discrete || isNil(min) || isNil(max)
? DefaultInboundOutcomeCount
: Math.max(3, Math.min(200, Math.round((max - min) / step) + 1))
: Math.max(
3,
Math.min(MaxDiscreteOptionCount, Math.round((max - min) / step) + 1)
)
: defaultInboundOutcomeCount,
aggregations: {
recency_weighted: { history: [], latest: undefined },
Expand Down Expand Up @@ -194,9 +198,9 @@ const NumericQuestionInput: React.FC<{
t.rich("stepError0", { halfRange: (max - min) / 2 })
);
}
if (step != 0 && step < (max - min) / 200) {
if (step != 0 && step < (max - min) / MaxDiscreteOptionCount) {
current_errors.push(
t.rich("stepError1", { rangePortion: (max - min) / 2 })
t.rich("stepError1", { maxOptions: MaxDiscreteOptionCount })
);
Comment on lines +201 to 204
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

Fix off‑by‑one in discrete step validation (can allow 51 options).

Line 201 allows step === (max - min) / MaxDiscreteOptionCount, which yields MaxDiscreteOptionCount + 1 options for inclusive discrete ranges. This then gets silently clamped to 50 elsewhere, changing the effective step/spacing from what the user entered.

🛠️ Proposed fix
-        if (step != 0 && step < (max - min) / MaxDiscreteOptionCount) {
+        if (step != 0 && step < (max - min) / (MaxDiscreteOptionCount - 1)) {
           current_errors.push(
             t.rich("stepError1", { maxOptions: MaxDiscreteOptionCount })
           );
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (step != 0 && step < (max - min) / MaxDiscreteOptionCount) {
current_errors.push(
t.rich("stepError1", { rangePortion: (max - min) / 2 })
t.rich("stepError1", { maxOptions: MaxDiscreteOptionCount })
);
if (step != 0 && step < (max - min) / (MaxDiscreteOptionCount - 1)) {
current_errors.push(
t.rich("stepError1", { maxOptions: MaxDiscreteOptionCount })
);
}
🤖 Prompt for AI Agents
In `@front_end/src/app/`(main)/questions/components/numeric_question_input.tsx
around lines 201 - 204, The discrete-step validation in
numeric_question_input.tsx incorrectly permits step === (max - min) /
MaxDiscreteOptionCount, allowing MaxDiscreteOptionCount + 1 inclusive options
which are later clamped; update the conditional in the validation that builds
current_errors (the check currently using step != 0 && step < (max - min) /
MaxDiscreteOptionCount) to use a non-strict comparison (<=) so that step values
producing more than MaxDiscreteOptionCount inclusive options are rejected, and
keep the existing error message constructed via t.rich("stepError1", {
maxOptions: MaxDiscreteOptionCount }) unchanged.

}
}
Expand Down Expand Up @@ -230,7 +234,7 @@ const NumericQuestionInput: React.FC<{
if (questionType === QuestionType.Discrete) {
inboundOutcomeCount = Math.max(
3,
Math.min(200, Math.round((mx - mn) / step))
Math.min(MaxDiscreteOptionCount, Math.round((mx - mn) / step))
);
} else {
inboundOutcomeCount = DefaultInboundOutcomeCount;
Expand Down
1 change: 1 addition & 0 deletions front_end/src/types/question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ContinuousForecastInputType } from "./charts";
import { ScoreType } from "./scoring";

export const DefaultInboundOutcomeCount = 200;
export const MaxDiscreteOptionCount = 50;

export enum QuestionType {
Binary = "binary",
Expand Down
1 change: 1 addition & 0 deletions questions/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from scoring.models import Score, ArchivedScore

DEFAULT_INBOUND_OUTCOME_COUNT = 200
MAX_DISCRETE_OPTION_COUNT = 50


def validate_options_history(value):
Expand Down
11 changes: 11 additions & 0 deletions questions/serializers/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from questions.constants import UnsuccessfulResolutionType
from questions.models import (
DEFAULT_INBOUND_OUTCOME_COUNT,
MAX_DISCRETE_OPTION_COUNT,
QUESTION_CONTINUOUS_TYPES,
Question,
Conditional,
Expand Down Expand Up @@ -217,6 +218,16 @@ def validate(self, data: dict):
errors.append("Range Max is required for continuous questions")
if data.get("range_min") is None:
errors.append("Range Min is required for continuous questions")
if question_type == Question.QuestionType.DISCRETE:
inbound_outcome_count = data.get("inbound_outcome_count")
if (
inbound_outcome_count is not None
and inbound_outcome_count > MAX_DISCRETE_OPTION_COUNT
):
errors.append(
f"Discrete questions are limited to {MAX_DISCRETE_OPTION_COUNT} options. "
"Please increase the step size or tighten the bounds."
)
Comment thread
lsabor marked this conversation as resolved.

if errors:
raise serializers.ValidationError(errors)
Expand Down
Loading