diff --git a/front_end/messages/en.json b/front_end/messages/en.json
index f73cc6e286..e8f23220c5 100644
--- a/front_end/messages/en.json
+++ b/front_end/messages/en.json
@@ -482,6 +482,8 @@
"forJournalists": "For Journalists",
"trackRecord": "Track Record",
"medals": "Medals",
+ "metaculusTrackRecord": "Metaculus Track Record",
+ "trackRecordOutdatedMessage": "This track record does not yet use the new Baseline and Peer scores introduced in November 2023. Updating this page is one of our next priorities.",
"theJournal": "The Journal",
"account": "Account",
"settings": "Settings",
@@ -713,5 +715,14 @@
"metaculusOnTwitter": "Metaculus on Twitter",
"metaculusOnDiscord": "Metaculus on Discord",
"signUpAsBot": "Sign Up as Bot",
- "failedToCopyText": "failed to copy text: "
+ "failedToCopyText": "failed to copy text: ",
+ "resolutionLabel": "Resolution:",
+ "totalQuestions": "Total questions:",
+ "averageScore": "Average score:",
+ "confidenceInterval": "confidence interval",
+ "perfectCalibration": "perfect calibration",
+ "userCalibration": "user's calibration",
+ "scatterPlotHoverMessage": "Hover over a circle to see how that question resolved.",
+ "calibrationCurve": "Calibration Curve",
+ "calibrationCurveInfo": "If the diamonds are close to the grey lines, the predictions are well-calibrated at that confidence level. If the diamonds are closer to the 50% than the diamonds, the predictions were underconfident, and vice-versa."
}
diff --git a/front_end/src/app/(main)/accounts/profile/components/track_record.tsx b/front_end/src/app/(main)/accounts/profile/components/track_record.tsx
index f07a492fb2..738a069bcc 100644
--- a/front_end/src/app/(main)/accounts/profile/components/track_record.tsx
+++ b/front_end/src/app/(main)/accounts/profile/components/track_record.tsx
@@ -3,9 +3,7 @@
import { useTranslations } from "next-intl";
import { FC } from "react";
-import CalibrationChart from "@/app/(main)/charts/calibration_chart";
-import ScatterPlot from "@/app/(main)/charts/scatter_plot";
-import UserHistogram from "@/app/(main)/charts/user_histogram";
+import TrackRecordCharts from "@/app/(main)/questions/track-record/components/track_record_charts";
import { UserProfile } from "@/types/users";
const TrackRecord: FC<{ profile: UserProfile }> = ({ profile }) => {
@@ -15,62 +13,12 @@ const TrackRecord: FC<{ profile: UserProfile }> = ({ profile }) => {
return (
-
-
- {t("scoreScatterPlot")}
-
- {profile.score_scatter_plot && (
-
- )}
-
- {t("scoreHistogram")}
-
- {profile.score_histogram && (
-
- )}
-
-
- {t("scoreHistogram")}
-
- {profile.score_histogram && (
-
- )}
-
-
- Calibration Curve
-
- {profile.calibration_curve && (
-
- )}
-
-
-
- confidence
- interval
-
-
- perfect
- calibration
-
-
-
- user's calibration
-
-
-
- If the diamonds are close to the grey lines, the predictions are
- well-calibrated at that confidence level. If the diamonds are closer
- to the 50% than the diamonds, the predictions were underconfident,
- and vice-versa.
-
-
-
+
diff --git a/front_end/src/app/(main)/accounts/profile/components/user_info.tsx b/front_end/src/app/(main)/accounts/profile/components/user_info.tsx
index b8407657f4..e6d947b6df 100644
--- a/front_end/src/app/(main)/accounts/profile/components/user_info.tsx
+++ b/front_end/src/app/(main)/accounts/profile/components/user_info.tsx
@@ -15,7 +15,7 @@ import {
UpdateProfileSchema,
updateProfileSchema,
} from "@/app/(main)/accounts/schemas";
-import CalibrationChart from "@/app/(main)/charts/calibration_chart";
+import CalibrationChart from "@/app/(main)/questions/track-record/components/charts/calibration_chart";
import Button from "@/components/ui/button";
import { FormError, Input, Textarea } from "@/components/ui/form_field";
import { useAuth } from "@/contexts/auth_context";
diff --git a/front_end/src/app/(main)/charts/scatter_plot.tsx b/front_end/src/app/(main)/charts/scatter_plot.tsx
deleted file mode 100644
index fbe36b5356..0000000000
--- a/front_end/src/app/(main)/charts/scatter_plot.tsx
+++ /dev/null
@@ -1,175 +0,0 @@
-"use client";
-
-import { useTranslations } from "next-intl";
-import React from "react";
-import {
- VictoryAxis,
- VictoryChart,
- VictoryScatter,
- VictoryLine,
-} from "victory";
-
-import ChartContainer from "@/components/charts/primitives/chart_container";
-import XTickLabel from "@/components/charts/primitives/x_tick_label";
-import { darkTheme, lightTheme } from "@/constants/chart_theme";
-import { METAC_COLORS } from "@/constants/colors";
-import useAppTheme from "@/hooks/use_app_theme";
-import useContainerSize from "@/hooks/use_container_size";
-import { TimelineChartZoomOption } from "@/types/charts";
-import {
- generateNumericDomain,
- generateTicksY,
- generateTimestampXScale,
-} from "@/utils/charts";
-
-type HistogramProps = {
- score_scatter_plot: { score: number; score_timestamp: number }[];
-};
-
-const ScatterPlot: React.FC
= ({ score_scatter_plot }) => {
- const t = useTranslations();
- const { theme, getThemeColor } = useAppTheme();
- const chartTheme = theme === "dark" ? darkTheme : lightTheme;
-
- const { ref: chartContainerRef, width: chartWidth } =
- useContainerSize();
-
- const {
- overallAverage,
- movingAverage,
- ticksY,
- ticksYFormat,
- xDomain,
- xScale,
- } = buildChartData({ score_scatter_plot, chartWidth });
-
- return (
-
-
- {
- return {
- x: point.score_timestamp,
- y: point.score,
- size: 5,
- };
- })}
- style={{
- data: {
- stroke: getThemeColor(METAC_COLORS.blue["600"]),
- fill: "none",
- strokeWidth: 1,
- },
- }}
- />
-
-
-
- }
- />
-
-
- );
-};
-
-function buildChartData({
- score_scatter_plot,
- chartWidth,
-}: {
- score_scatter_plot: { score: number; score_timestamp: number }[];
- chartWidth: number;
-}) {
- const overallAverage =
- score_scatter_plot.length === 0
- ? 0
- : score_scatter_plot.reduce((reducer, data) => {
- return reducer + data.score;
- }, 0) / score_scatter_plot.length;
-
- let scoreLocalSum = 0;
- const movingAverage = score_scatter_plot.map((data, index) => {
- scoreLocalSum += data.score;
- return {
- x: data.score_timestamp,
- y: scoreLocalSum / (index + 1),
- };
- });
-
- const { ticks: ticksY, tickFormat: ticksYFormat } = generateTicksY(
- 270,
- [-100, -50, 0, 50, 100]
- );
- const xDomain = generateNumericDomain(
- score_scatter_plot.map((data) => data.score_timestamp),
- "all" as TimelineChartZoomOption
- );
- const xScale = generateTimestampXScale(xDomain, chartWidth);
-
- return {
- overallAverage,
- movingAverage,
- ticksY,
- ticksYFormat,
- xDomain,
- xScale,
- };
-}
-
-export default ScatterPlot;
diff --git a/front_end/src/app/(main)/charts/user_histogram.tsx b/front_end/src/app/(main)/charts/user_histogram.tsx
deleted file mode 100644
index dd2e53f9cc..0000000000
--- a/front_end/src/app/(main)/charts/user_histogram.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-"use client";
-
-import { range } from "lodash";
-import { useTranslations } from "next-intl";
-import React from "react";
-import {
- VictoryAxis,
- VictoryChart,
- VictoryContainer,
- VictoryArea,
-} from "victory";
-
-import { darkTheme, lightTheme } from "@/constants/chart_theme";
-import useAppTheme from "@/hooks/use_app_theme";
-import { generateTicksY } from "@/utils/charts";
-
-type HistogramProps = {
- rawHistogramData: {
- bin_start: number;
- bin_end: number;
- pct_scores: number;
- }[];
- color: string;
-};
-
-const UserHistogram: React.FC = ({
- rawHistogramData,
- color,
-}) => {
- const histogramData = mapHistogramData(rawHistogramData);
- const t = useTranslations();
- const { theme } = useAppTheme();
- const chartTheme = theme === "dark" ? darkTheme : lightTheme;
-
- const { ticks: ticksY, tickFormat: ticksYFormat } = generateTicksY(
- 180,
- [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
- );
- return (
- }
- padding={{ top: 20, bottom: 65, left: 40, right: 20 }}
- height={180}
- >
-
-
-
-
- );
-};
-
-const mapHistogramData = (
- userHistogram: {
- bin_start: number;
- bin_end: number;
- pct_scores: number;
- }[]
-) => {
- const mappedArray = [] as { x: number; y: number }[];
- userHistogram.forEach((data, index) => {
- mappedArray.push(
- ...[
- { x: data.bin_start, y: Math.max(data.pct_scores, 0) },
- { x: data.bin_end - 1, y: Math.max(data.pct_scores, 0) },
- ]
- );
- });
- return mappedArray;
-};
-
-export default UserHistogram;
diff --git a/front_end/src/app/(main)/questions/[id]/components/histogram_drawer.tsx b/front_end/src/app/(main)/questions/[id]/components/histogram_drawer.tsx
index c61195da66..a19dec6156 100644
--- a/front_end/src/app/(main)/questions/[id]/components/histogram_drawer.tsx
+++ b/front_end/src/app/(main)/questions/[id]/components/histogram_drawer.tsx
@@ -3,7 +3,7 @@
import { useTranslations } from "next-intl";
import React from "react";
-import Histogram from "@/app/(main)/charts/histogram";
+import Histogram from "@/components/charts/histogram";
import ExpandableContent from "@/components/ui/expandable_content";
import SectionToggle from "@/components/ui/section_toggle";
import { PostWithForecasts } from "@/types/post";
diff --git a/front_end/src/app/(main)/questions/track-record/components/async_track_record.tsx b/front_end/src/app/(main)/questions/track-record/components/async_track_record.tsx
new file mode 100644
index 0000000000..c117356002
--- /dev/null
+++ b/front_end/src/app/(main)/questions/track-record/components/async_track_record.tsx
@@ -0,0 +1,19 @@
+import { FC } from "react";
+
+import TrackRecordApi from "@/services/track_record";
+
+import TrackRecordCharts from "./track_record_charts";
+
+const AsyncTrackRecord: FC = async () => {
+ const trackRecord = await TrackRecordApi.getGlobalTrackRecord();
+
+ return (
+
+ );
+};
+
+export default AsyncTrackRecord;
diff --git a/front_end/src/app/(main)/charts/calibration_chart.tsx b/front_end/src/app/(main)/questions/track-record/components/charts/calibration_chart.tsx
similarity index 93%
rename from front_end/src/app/(main)/charts/calibration_chart.tsx
rename to front_end/src/app/(main)/questions/track-record/components/charts/calibration_chart.tsx
index fff596fb85..1752df6e1b 100644
--- a/front_end/src/app/(main)/charts/calibration_chart.tsx
+++ b/front_end/src/app/(main)/questions/track-record/components/charts/calibration_chart.tsx
@@ -13,11 +13,12 @@ import {
import { darkTheme, lightTheme } from "@/constants/chart_theme";
import useAppTheme from "@/hooks/use_app_theme";
+import { TrackRecordCalibrationCurveItem } from "@/types/track_record";
-const CalibrationChart: React.FC<{ data: any; showIntervals?: boolean }> = ({
- data,
- showIntervals = true,
-}) => {
+const CalibrationChart: React.FC<{
+ data: TrackRecordCalibrationCurveItem[];
+ showIntervals?: boolean;
+}> = ({ data, showIntervals = true }) => {
const calibrationData = data;
const { theme, getThemeColor } = useAppTheme();
diff --git a/front_end/src/app/(main)/questions/track-record/components/charts/scatter_plot.tsx b/front_end/src/app/(main)/questions/track-record/components/charts/scatter_plot.tsx
new file mode 100644
index 0000000000..784661139d
--- /dev/null
+++ b/front_end/src/app/(main)/questions/track-record/components/charts/scatter_plot.tsx
@@ -0,0 +1,244 @@
+"use client";
+
+import { useTranslations } from "next-intl";
+import React, { useMemo, useState } from "react";
+import {
+ VictoryAxis,
+ VictoryChart,
+ VictoryScatter,
+ VictoryLine,
+} from "victory";
+
+import ChartContainer from "@/components/charts/primitives/chart_container";
+import XTickLabel from "@/components/charts/primitives/x_tick_label";
+import { darkTheme, lightTheme } from "@/constants/chart_theme";
+import { METAC_COLORS } from "@/constants/colors";
+import useAppTheme from "@/hooks/use_app_theme";
+import useContainerSize from "@/hooks/use_container_size";
+import { TimelineChartZoomOption } from "@/types/charts";
+import { TrackRecordScatterPlotItem } from "@/types/track_record";
+import {
+ generateNumericDomain,
+ generateTicksY,
+ generateTimestampXScale,
+} from "@/utils/charts";
+
+import TrackRecordChartHero from "../track_record_chart_hero";
+
+type HistogramProps = {
+ score_scatter_plot: TrackRecordScatterPlotItem[];
+};
+
+const ScatterPlot: React.FC = ({ score_scatter_plot }) => {
+ const t = useTranslations();
+ const { theme, getThemeColor } = useAppTheme();
+ const chartTheme = theme === "dark" ? darkTheme : lightTheme;
+
+ const { ref: chartContainerRef, width: chartWidth } =
+ useContainerSize();
+
+ const {
+ overallAverage,
+ movingAverage,
+ ticksY,
+ ticksYFormat,
+ xDomain,
+ xScale,
+ } = buildChartData({ score_scatter_plot, chartWidth });
+
+ const [activeIndex, setActiveIndex] = useState(null);
+ const hoverData = useMemo(() => {
+ if (activeIndex === null || !score_scatter_plot[activeIndex]) {
+ return null;
+ }
+
+ const point = score_scatter_plot[activeIndex];
+ if (!point) {
+ return null;
+ }
+
+ return point;
+ }, [activeIndex, score_scatter_plot]);
+
+ const averageScore = useMemo(() => {
+ const sum = score_scatter_plot.reduce((acc, { score }) => acc + score, 0);
+ return (sum / score_scatter_plot.length).toFixed(3);
+ }, [score_scatter_plot]);
+
+ return (
+ <>
+
+
+
+
+ {
+ return {
+ x: point.score_timestamp,
+ y: point.score,
+ size: index === activeIndex ? 6 : 5,
+ };
+ })}
+ style={{
+ data: {
+ stroke: getThemeColor(METAC_COLORS.blue["600"]),
+ fill: "none",
+ strokeWidth: 1,
+ },
+ }}
+ events={[
+ {
+ target: "data",
+ eventHandlers: {
+ onMouseOver: (_event, datum) => {
+ setActiveIndex(datum.index);
+ return {
+ mutation: (props) => {
+ return {
+ style: Object.assign({}, props.style, {
+ strokeWidth: 3,
+ }),
+ };
+ },
+ };
+ },
+ onMouseOut: () => {
+ setActiveIndex(null);
+ return {
+ mutation: () => null,
+ };
+ },
+ },
+ },
+ ]}
+ />
+
+
+
+ }
+ />
+
+
+
+ {hoverData ? (
+ <>
+
+ {hoverData.question_title}
+
+
+ {t("resolutionLabel")} {hoverData.question_resolution}
+
+ >
+ ) : (
+
{t("scatterPlotHoverMessage")}
+ )}
+
+ >
+ );
+};
+
+function buildChartData({
+ score_scatter_plot,
+ chartWidth,
+}: {
+ score_scatter_plot: { score: number; score_timestamp: number }[];
+ chartWidth: number;
+}) {
+ const overallAverage =
+ score_scatter_plot.length === 0
+ ? 0
+ : score_scatter_plot.reduce((reducer, data) => {
+ return reducer + data.score;
+ }, 0) / score_scatter_plot.length;
+
+ let scoreLocalSum = 0;
+ const movingAverage = score_scatter_plot.map((data, index) => {
+ scoreLocalSum += data.score;
+ return {
+ x: data.score_timestamp,
+ y: scoreLocalSum / (index + 1),
+ };
+ });
+
+ const { ticks: ticksY, tickFormat: ticksYFormat } = generateTicksY(
+ 270,
+ [-100, -50, 0, 50, 100]
+ );
+ const xDomain = generateNumericDomain(
+ score_scatter_plot.map((data) => data.score_timestamp),
+ "all" as TimelineChartZoomOption
+ );
+ const xScale = generateTimestampXScale(xDomain, chartWidth);
+
+ return {
+ overallAverage,
+ movingAverage,
+ ticksY,
+ ticksYFormat,
+ xDomain,
+ xScale,
+ };
+}
+
+export default ScatterPlot;
diff --git a/front_end/src/app/(main)/questions/track-record/components/charts/user_histogram.tsx b/front_end/src/app/(main)/questions/track-record/components/charts/user_histogram.tsx
new file mode 100644
index 0000000000..1b6f0453d8
--- /dev/null
+++ b/front_end/src/app/(main)/questions/track-record/components/charts/user_histogram.tsx
@@ -0,0 +1,141 @@
+"use client";
+
+import { range } from "lodash";
+import { useTranslations } from "next-intl";
+import React, { useMemo } from "react";
+import {
+ VictoryAxis,
+ VictoryChart,
+ VictoryContainer,
+ VictoryArea,
+} from "victory";
+
+import { darkTheme, lightTheme } from "@/constants/chart_theme";
+import useAppTheme from "@/hooks/use_app_theme";
+import { TrackRecordHistogramItem } from "@/types/track_record";
+import { generateTicksY } from "@/utils/charts";
+
+import TrackRecordChartHero from "../track_record_chart_hero";
+
+type HistogramProps = {
+ rawHistogramData: TrackRecordHistogramItem[];
+ color: string;
+};
+
+const UserHistogram: React.FC = ({
+ rawHistogramData,
+ color,
+}) => {
+ const histogramData = mapHistogramData(rawHistogramData);
+ const t = useTranslations();
+ const { theme } = useAppTheme();
+ const chartTheme = theme === "dark" ? darkTheme : lightTheme;
+
+ const { ticks: ticksY, tickFormat: ticksYFormat } = generateTicksY(
+ 180,
+ [0.0, 0.2, 0.4, 0.6, 0.8, 1.0]
+ );
+
+ const averageScore = useMemo(() => {
+ const sum = histogramData.reduce((acc, { y }) => acc + y, 0);
+
+ return Math.round((sum / histogramData.length) * 1000);
+ }, [histogramData]);
+
+ return (
+ <>
+
+
+ }
+ padding={{ top: 20, bottom: 65, left: 40, right: 20 }}
+ height={180}
+ >
+
+
+
+
+ >
+ );
+};
+
+const mapHistogramData = (
+ userHistogram: {
+ bin_start: number;
+ bin_end: number;
+ pct_scores: number;
+ }[]
+) => {
+ const mappedArray = [] as { x: number; y: number }[];
+ userHistogram.forEach((data, index) => {
+ mappedArray.push(
+ ...[
+ { x: data.bin_start, y: Math.max(data.pct_scores, 0) },
+ { x: data.bin_end - 1, y: Math.max(data.pct_scores, 0) },
+ ]
+ );
+ });
+ return mappedArray;
+};
+
+export default UserHistogram;
diff --git a/front_end/src/app/(main)/questions/track-record/components/track_record_chart_hero.tsx b/front_end/src/app/(main)/questions/track-record/components/track_record_chart_hero.tsx
new file mode 100644
index 0000000000..99b6ef00ed
--- /dev/null
+++ b/front_end/src/app/(main)/questions/track-record/components/track_record_chart_hero.tsx
@@ -0,0 +1,26 @@
+import { useTranslations } from "next-intl";
+import React, { FC } from "react";
+
+type Props = {
+ totalQuestions: string;
+ averageScore: string;
+};
+
+const TrackRecordChartHero: FC = ({ totalQuestions, averageScore }) => {
+ const t = useTranslations();
+
+ return (
+
+ {t("totalQuestions")}
+
+ {" " + totalQuestions}
+
+ {t("averageScore")}
+
+ {" " + averageScore}
+
+
+ );
+};
+
+export default TrackRecordChartHero;
diff --git a/front_end/src/app/(main)/questions/track-record/components/track_record_charts.tsx b/front_end/src/app/(main)/questions/track-record/components/track_record_charts.tsx
new file mode 100644
index 0000000000..081d2eeca4
--- /dev/null
+++ b/front_end/src/app/(main)/questions/track-record/components/track_record_charts.tsx
@@ -0,0 +1,70 @@
+import classNames from "classnames";
+import { useTranslations } from "next-intl";
+import { FC } from "react";
+
+import {
+ TrackRecordCalibrationCurveItem,
+ TrackRecordHistogramItem,
+ TrackRecordScatterPlotItem,
+} from "@/types/track_record";
+
+import CalibrationChart from "./charts/calibration_chart";
+import ScatterPlot from "./charts/scatter_plot";
+import UserHistogram from "./charts/user_histogram";
+
+type Props = {
+ scatterPlot?: TrackRecordScatterPlotItem[];
+ scoreHistogram?: TrackRecordHistogramItem[];
+ calibrationCurve?: TrackRecordCalibrationCurveItem[];
+ className?: string;
+};
+
+const TrackRecordCharts: FC = ({
+ scatterPlot,
+ scoreHistogram,
+ calibrationCurve,
+ className,
+}) => {
+ const t = useTranslations();
+
+ return (
+
+
+ {t("scoreScatterPlot")}
+
+ {scatterPlot &&
}
+
+ {t("scoreHistogram")}
+
+ {scoreHistogram && (
+
+ )}
+
+
+ {t("calibrationCurve")}
+
+ {calibrationCurve &&
}
+
+
+
+
+ {t("confidenceInterval")}
+
+
+
+ {t("perfectCalibration")}
+
+
+
+ {t("userCalibration")}
+
+
+
+ {t("calibrationCurveInfo")}
+
+
+
+ );
+};
+
+export default TrackRecordCharts;
diff --git a/front_end/src/app/(main)/questions/track-record/page.tsx b/front_end/src/app/(main)/questions/track-record/page.tsx
new file mode 100644
index 0000000000..541dd715a9
--- /dev/null
+++ b/front_end/src/app/(main)/questions/track-record/page.tsx
@@ -0,0 +1,32 @@
+import Link from "next/link";
+import { getTranslations } from "next-intl/server";
+import { Suspense } from "react";
+
+import LoadingIndicator from "@/components/ui/loading_indicator";
+
+import AsyncTrackRecord from "./components/async_track_record";
+
+export default async function TrackRecordPage() {
+ const t = await getTranslations();
+
+ return (
+
+
+ {t("metaculusTrackRecord")}
+
+
+ {t.rich("trackRecordOutdatedMessage", {
+ link: (chunks) => (
+ {chunks}
+ ),
+ })}
+
+
+
+
+ }>
+
+
+
+ );
+}
diff --git a/front_end/src/app/(main)/charts/histogram.tsx b/front_end/src/components/charts/histogram.tsx
similarity index 98%
rename from front_end/src/app/(main)/charts/histogram.tsx
rename to front_end/src/components/charts/histogram.tsx
index f6f077d158..df79f16b92 100644
--- a/front_end/src/app/(main)/charts/histogram.tsx
+++ b/front_end/src/components/charts/histogram.tsx
@@ -8,8 +8,6 @@ import {
VictoryBar,
VictoryChart,
VictoryContainer,
- VictoryLine,
- VictoryLabel,
} from "victory";
import { darkTheme, lightTheme } from "@/constants/chart_theme";
diff --git a/front_end/src/services/track_record.ts b/front_end/src/services/track_record.ts
new file mode 100644
index 0000000000..d331e4e47f
--- /dev/null
+++ b/front_end/src/services/track_record.ts
@@ -0,0 +1,10 @@
+import { GlobalTrackRecord } from "@/types/track_record";
+import { get } from "@/utils/fetch";
+
+class TrackRecordApi {
+ static async getGlobalTrackRecord() {
+ return await get("/metaculus_track_record");
+ }
+}
+
+export default TrackRecordApi;
diff --git a/front_end/src/types/track_record.ts b/front_end/src/types/track_record.ts
new file mode 100644
index 0000000000..68aa1721d0
--- /dev/null
+++ b/front_end/src/types/track_record.ts
@@ -0,0 +1,27 @@
+import { Resolution } from "@/types/post";
+
+export type TrackRecordHistogramItem = {
+ bin_start: number;
+ bin_end: number;
+ pct_scores: number;
+};
+
+export type TrackRecordScatterPlotItem = {
+ score: number;
+ score_timestamp: number;
+ question_title: string;
+ question_resolution: Resolution;
+};
+
+export type TrackRecordCalibrationCurveItem = {
+ user_lower_quartile: number;
+ user_middle_quartile: number;
+ user_upper_quartile: number;
+ perfect_calibration: number;
+};
+
+export type GlobalTrackRecord = {
+ calibration_curve: TrackRecordCalibrationCurveItem[];
+ score_histogram: TrackRecordHistogramItem[];
+ score_scatter_plot: TrackRecordScatterPlotItem[];
+};
diff --git a/front_end/src/types/users.ts b/front_end/src/types/users.ts
index a651332822..2d3ff20895 100644
--- a/front_end/src/types/users.ts
+++ b/front_end/src/types/users.ts
@@ -1,4 +1,9 @@
import { SubscriptionEmailType } from "@/types/notifications";
+import {
+ TrackRecordScatterPlotItem,
+ TrackRecordHistogramItem,
+ TrackRecordCalibrationCurveItem,
+} from "@/types/track_record";
import { ProfilePreferencesType } from "./preferences";
@@ -28,13 +33,9 @@ export type User = {
};
export type UserProfile = User & {
- calibration_curve?: any;
- score_histogram?: {
- bin_start: number;
- bin_end: number;
- pct_scores: number;
- }[];
- score_scatter_plot?: { score: number; score_timestamp: number }[];
+ calibration_curve?: TrackRecordCalibrationCurveItem[];
+ score_histogram?: TrackRecordHistogramItem[];
+ score_scatter_plot?: TrackRecordScatterPlotItem[];
nr_forecasts?: number;
nr_comments?: number;
avg_score?: number;