Skip to content
Draft
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
159 changes: 157 additions & 2 deletions front_end/messages/en.json

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.

I see that we only updated the en file and also added a bunch of translation keys that are not used in this PR.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export default function AggregationGraphPanel({
choice: method.id,
label: method.label,
includeBots: method.includeBots,
onlyBots: method.onlyBots,
color: colorById.get(method.id) ?? METAC_COLORS.gray["400"],
})),
[methods, colorById]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import LoadingIndicator from "@/components/ui/loading_indicator";
import Switch from "@/components/ui/switch";
import { useAuth } from "@/contexts/auth_context";

type BotMode = "exclude" | "include" | "only";

import AggregationLabel from "./aggregation_label";
import {
AGGREGATION_EXPLORER_OPTIONS,
Expand Down Expand Up @@ -99,6 +101,7 @@ type Props = {
joinedBeforeDate?: string;
userIds?: number[];
includeBots?: boolean;
onlyBots?: boolean;
}) => void;
defaultIncludeBots?: boolean;
};
Expand All @@ -120,7 +123,9 @@ export default function AggregationMethodSelector({
AggregationMethod.recency_weighted
);
const [selectedChildId, setSelectedChildId] = useState<string | null>(null);
const [includeBots, setIncludeBots] = useState(defaultIncludeBots);
const [botMode, setBotMode] = useState<BotMode>(
defaultIncludeBots ? "include" : "exclude"
);
const [joinedBeforeDate, setJoinedBeforeDate] = useState("");
const [userFilterEnabled, setUserFilterEnabled] = useState(false);
const [userIdsText, setUserIdsText] = useState("");
Expand Down Expand Up @@ -224,11 +229,18 @@ export default function AggregationMethodSelector({
) : null}

{showBotToggle ? (
<div className="mt-3 flex items-center justify-between">
<div className="mt-3">
<label className="text-xs font-medium uppercase tracking-wide text-gray-600 dark:text-gray-400">
{t("includeBots")}
{t("bots")}
</label>
<Switch checked={includeBots} onChange={setIncludeBots} />
<StyledSelect
value={botMode}
onChange={(e) => setBotMode(e.target.value as BotMode)}
>
<option value="exclude">{t("excludeBots")}</option>
<option value="include">{t("includeBots")}</option>
<option value="only">{t("onlyBots")}</option>
</StyledSelect>
</div>
) : null}

Expand Down Expand Up @@ -269,7 +281,10 @@ export default function AggregationMethodSelector({
parsedUserIds.length
? parsedUserIds
: undefined,
includeBots: showBotToggle ? includeBots : undefined,
includeBots:
showBotToggle && botMode === "include" ? true : undefined,
onlyBots:
showBotToggle && botMode === "only" ? true : undefined,
});
}}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,12 @@ export function buildConfigId(
optionId: string,
includeBots: boolean,
joinedBeforeDate?: string,
userIds?: number[]
userIds?: number[],
onlyBots?: boolean
): string {
const parts = [optionId];
if (includeBots) parts.push("bots");
if (onlyBots) parts.push("obots");
else if (includeBots) parts.push("bots");
if (joinedBeforeDate) parts.push(joinedBeforeDate);
if (userIds?.length)
parts.push(`u${[...userIds].sort((a, b) => a - b).join(",")}`);
Expand All @@ -42,6 +44,7 @@ export type SelectedAggregationConfig = {
joinedBeforeDate?: string;
userIds?: number[];
includeBots?: boolean;
onlyBots?: boolean;
enabled?: boolean;
};

Expand All @@ -52,6 +55,7 @@ export type AggregationQueryResult = {
chips: string[]; // filter chips derived from config (for display in label component)
method: string; // option.id, used for API response lookup
includeBots: boolean;
onlyBots: boolean;
joinedBeforeDate?: string;
userIds?: number[];
isPending: boolean;
Expand Down Expand Up @@ -106,7 +110,7 @@ export function buildDisplayLabel(
option: AggregationOption,
config: Pick<
SelectedAggregationConfig,
"joinedBeforeDate" | "userIds" | "includeBots" | "optionId"
"joinedBeforeDate" | "userIds" | "includeBots" | "onlyBots" | "optionId"
>
): string {
let label =
Expand All @@ -115,7 +119,9 @@ export function buildDisplayLabel(
? t("usersJoinedBefore", { date: config.joinedBeforeDate })
: t(option.labelKey as Parameters<TranslateFunction>[0]);

if (option.supportsBotToggle && config.includeBots) {
if (option.supportsBotToggle && config.onlyBots) {
label += ` (${t("onlyBots")})`;
} else if (option.supportsBotToggle && config.includeBots) {
label += ` (${t("withBots")})`;
}

Expand All @@ -140,13 +146,14 @@ export function buildChips(
t: TranslateFunction,
config: Pick<
SelectedAggregationConfig,
"joinedBeforeDate" | "userIds" | "includeBots"
"joinedBeforeDate" | "userIds" | "includeBots" | "onlyBots"
>
): string[] {
const chips: string[] = [];
if (config.joinedBeforeDate)
chips.push(t("beforeDate", { date: config.joinedBeforeDate }));
if (config.includeBots) chips.push(t("withBots"));
if (config.onlyBots) chips.push(t("onlyBots"));
else if (config.includeBots) chips.push(t("withBots"));
if (config.userIds?.length)
chips.push(t("usersFilterLabel", { ids: config.userIds.join(", ") }));
return chips;
Expand Down Expand Up @@ -177,6 +184,7 @@ export function useAggregationData({
postId,
questionId,
includeBots: config.includeBots,
onlyBots: config.onlyBots,
aggregationMethods: option.id,
joinedBeforeDate: config.joinedBeforeDate,
userIds: config.userIds,
Expand All @@ -203,6 +211,7 @@ export function useAggregationData({
chips: buildChips(t, config),
method: option.id,
includeBots: !!config.includeBots,
onlyBots: !!config.onlyBots,
joinedBeforeDate: config.joinedBeforeDate,
userIds: config.userIds,
isPending: query.isPending,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,14 @@ export function useExplorerState(postData: PostWithForecasts) {
joinedBeforeDate?: string;
userIds?: number[];
includeBots?: boolean;
onlyBots?: boolean;
}) => {
const configId = buildConfigId(
payload.optionId,
payload.includeBots ?? false,
payload.joinedBeforeDate,
payload.userIds
payload.userIds,
payload.onlyBots
);
void setSelectedConfigs((prev) => {
if (prev.some((c) => c.id === configId)) return prev;
Expand All @@ -162,6 +164,7 @@ export function useExplorerState(postData: PostWithForecasts) {
joinedBeforeDate: payload.joinedBeforeDate,
userIds: payload.userIds,
includeBots: payload.includeBots,
onlyBots: payload.onlyBots,
enabled: true,
},
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ function parseConfigSpec(spec: string): SelectedAggregationConfig | null {
const optionId = parts[0];
if (!optionId || !VALID_AGGREGATION_METHODS.has(optionId)) return null;

const includeBots = parts.includes("bots");
const onlyBots = parts.includes("obots");
const includeBots = !onlyBots && parts.includes("bots");
const joinedBeforeDate = parts.find((p) => /^\d{4}-\d{2}-\d{2}$/.test(p));
const userIdsStr = parts.find((p) => /^u\d/.test(p));
const userIds = userIdsStr
Expand All @@ -71,6 +72,7 @@ function parseConfigSpec(spec: string): SelectedAggregationConfig | null {
id,
optionId: optionId as AggregationMethod,
includeBots: includeBots || undefined,
onlyBots: onlyBots || undefined,
joinedBeforeDate,
userIds: userIds?.length ? userIds : undefined,
enabled: disabled ? false : undefined,
Expand Down
1 change: 1 addition & 0 deletions front_end/src/app/(main)/aggregation-explorer/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,6 @@ export type AggregationTooltip = {
choice: string;
label: string;
includeBots: boolean;
onlyBots: boolean;
color: ThemeColor;
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ type AggregationExplorerParams = {
postId?: number | string | null;
questionId?: number | string | null;
includeBots?: boolean;
onlyBots?: boolean;
aggregationMethods?: string;
userIds?: number[];
joinedBeforeDate?: string;
Expand All @@ -17,6 +18,7 @@ class AggregationExplorerApi extends ApiService {
post_id: params.postId?.toString() || "",
question_id: params.questionId?.toString() || "",
include_bots: params.includeBots?.toString() || "false",
only_bots: params.onlyBots?.toString() || "false",
aggregation_methods: params.aggregationMethods || "",
joined_before_date: params.joinedBeforeDate || "",
...(params.userIds !== undefined ? { user_ids: params.userIds } : {}),
Expand Down
10 changes: 8 additions & 2 deletions utils/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class DataGetRequestSerializer(serializers.Serializer):
child=serializers.IntegerField(), required=False, allow_null=True
)
include_bots = serializers.BooleanField(required=False, allow_null=True)
only_bots = serializers.BooleanField(required=False, allow_null=True)
anonymized = serializers.BooleanField(required=False)
joined_before_date = serializers.DateTimeField(required=False)
include_key_factors = serializers.BooleanField(required=False, default=False)
Expand Down Expand Up @@ -139,6 +140,7 @@ def validate(self, attrs):
"include_user_data",
"user_ids",
"include_bots",
"only_bots",
"anonymized",
"joined_before_date",
"include_key_factors",
Expand All @@ -152,13 +154,17 @@ def validate(self, attrs):
aggregation_methods = attrs.get("aggregation_methods")
user_ids = attrs.get("user_ids")
include_bots = attrs.get("include_bots")
only_bots = attrs.get("only_bots")
minimize = attrs.get("minimize", True)

if not aggregation_methods and (
(user_ids is not None) or (include_bots is not None) or not minimize
(user_ids is not None)
or (include_bots is not None)
or (only_bots is not None)
or not minimize
):
raise serializers.ValidationError(
"If user_ids, include_bots, or minimize is set, "
"If user_ids, include_bots, only_bots, or minimize is set, "
"aggregation_methods must also be set."
)

Expand Down
22 changes: 16 additions & 6 deletions utils/the_math/aggregations.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@ def get_aggregations_at_time(
include_stats: bool = False,
histogram: bool = False,
include_bots: bool = False,
only_bots: bool = False,
joined_before: datetime | None = None,
) -> dict[AggregationMethod, AggregateForecast]:
"""set include_stats to True if you want to include num_forecasters, q1s, medians,
Expand All @@ -747,11 +748,15 @@ def get_aggregations_at_time(
)
if only_include_user_ids:
forecasts = forecasts.filter(author_id__in=only_include_user_ids)
elif only_bots:
forecasts = forecasts.filter(author__is_bot=True)
else:
# only include forecasts by non-primary bots if user ids explicitly specified
# only include forecasts by non-primary bots or blacklisted users
# if user ids explicitly specified
forecasts = forecasts.exclude_non_primary_bots()
if not include_bots:
forecasts = forecasts.exclude(author__is_bot=True)
forecasts = forecasts.exclude_blacklisted_users()
if not include_bots:
forecasts = forecasts.exclude(author__is_bot=True)
if len(forecasts) == 0:
return dict()
forecast_set = ForecastSet(
Expand Down Expand Up @@ -980,6 +985,7 @@ def get_aggregation_history(
minimize: bool | int = True,
include_stats: bool = True,
include_bots: bool = False,
only_bots: bool = False,
histogram: bool | None = None,
include_future: bool = True,
joined_before: datetime | None = None,
Expand All @@ -999,11 +1005,15 @@ def get_aggregation_history(

if only_include_user_ids:
forecasts = forecasts.filter(author_id__in=only_include_user_ids)
elif only_bots:
forecasts = forecasts.filter(author__is_bot=True)
else:
# only include forecasts by non-primary bots if user ids explicitly specified
# only include forecasts by non-primary bots or blacklisted users
# if user ids explicitly specified
forecasts = forecasts.exclude_non_primary_bots()
if not include_bots:
forecasts = forecasts.exclude(author__is_bot=True)
forecasts = forecasts.exclude_blacklisted_users()
if not include_bots:
forecasts = forecasts.exclude(author__is_bot=True)

if include_pre_predictions:
earliest_time = None
Expand Down
4 changes: 4 additions & 0 deletions utils/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def aggregation_explorer_api_view(request) -> Response:
aggregation_methods = params.get("aggregation_methods", [])
only_include_user_ids = params.get("user_ids")
include_bots = params.get("include_bots")
only_bots = params.get("only_bots") or False
minimize = params.get("minimize", True)
joined_before = params.get("joined_before_date")

Expand All @@ -88,6 +89,7 @@ def aggregation_explorer_api_view(request) -> Response:
if include_bots is not None
else question.include_bots_in_aggregates
),
only_bots=only_bots,
histogram=True,
include_future=False,
joined_before=joined_before,
Expand All @@ -107,6 +109,8 @@ def aggregation_explorer_api_view(request) -> Response:
forecasters_qs = question.get_forecasters()
if only_include_user_ids:
forecasters_qs = forecasters_qs.filter(id__in=only_include_user_ids)
elif only_bots:
forecasters_qs = forecasters_qs.filter(is_bot=True)
elif not include_bots:
forecasters_qs = forecasters_qs.filter(is_bot=False)

Expand Down
Loading