diff --git a/src/web/display/DisplayQuestion/QuestionFooter.tsx b/src/web/display/DisplayQuestion/QuestionFooter.tsx
index d7441ea..beebe2b 100644
--- a/src/web/display/DisplayQuestion/QuestionFooter.tsx
+++ b/src/web/display/DisplayQuestion/QuestionFooter.tsx
@@ -3,57 +3,28 @@ import { QuestionFragment } from "../../search/queries.generated";
type QualityIndicator = QuestionFragment["qualityIndicators"];
type IndicatorName = keyof QualityIndicator;
-const formatQualityIndicator = (indicator: IndicatorName) => {
- let result: string | null = null;
- switch (indicator) {
- case "numForecasts":
- result = null;
- break;
+// this duplication can probably be simplified with typescript magic, but this is good enough for now
+type UsedIndicatorName =
+ | "volume"
+ | "numForecasters"
+ | "spread"
+ | "sharesVolume"
+ | "liquidity"
+ | "tradeVolume"
+ | "openInterest";
- case "stars":
- result = null;
- break;
-
- case "volume":
- result = "Volume";
- break;
-
- case "numForecasters":
- result = "Forecasters";
- break;
-
- // case "yesBid":
- // result = null; // "Yes bid"
- // break;
-
- // case "yesAsk":
- // result = null; // "Yes ask"
- // break;
-
- case "spread":
- result = "Spread";
- break;
- case "sharesVolume":
- result = "Shares vol.";
- break;
-
- case "openInterest":
- result = "Interest";
- break;
-
- // case "resolution_data":
- // result = null;
- // break;
-
- case "liquidity":
- result = "Liquidity";
- break;
-
- case "tradeVolume":
- result = "Volume";
- break;
- }
- return result;
+const qualityIndicatorLabels: { [k in UsedIndicatorName]: string } = {
+ // numForecasts: null,
+ // stars: null,
+ // yesBid: "Yes bid",
+ // yesAsk: "Yes ask",
+ volume: "Volume",
+ numForecasters: "Forecasters",
+ spread: "Spread",
+ sharesVolume: "Shares vol.",
+ liquidity: "Liquidity",
+ tradeVolume: "Volume",
+ openInterest: "Interest",
};
const formatNumber = (num) => {
@@ -66,27 +37,16 @@ const formatNumber = (num) => {
}
};
-const formatQualityIndicators = (qualityIndicators: QualityIndicator) => {
- let newQualityIndicators: { [k: string]: string | number } = {};
- for (const key of Object.keys(qualityIndicators)) {
- const newKey = formatQualityIndicator(key as IndicatorName);
- if (newKey && qualityIndicators[key] !== null) {
- newQualityIndicators[newKey] = qualityIndicators[key];
- }
- }
- return newQualityIndicators;
-};
-
/* Display functions*/
const getPercentageSymbolIfNeeded = ({
indicator,
platform,
}: {
- indicator: string;
+ indicator: UsedIndicatorName;
platform: string;
}) => {
- let indicatorsWhichNeedPercentageSymbol = ["Spread"];
+ let indicatorsWhichNeedPercentageSymbol: IndicatorName[] = ["spread"];
if (indicatorsWhichNeedPercentageSymbol.includes(indicator)) {
return "%";
} else {
@@ -98,10 +58,15 @@ const getCurrencySymbolIfNeeded = ({
indicator,
platform,
}: {
- indicator: string;
+ indicator: UsedIndicatorName;
platform: string;
}) => {
- let indicatorsWhichNeedCurrencySymbol = ["Volume", "Interest", "Liquidity"];
+ const indicatorsWhichNeedCurrencySymbol: IndicatorName[] = [
+ "volume",
+ "tradeVolume",
+ "openInterest",
+ "liquidity",
+ ];
let dollarPlatforms = ["predictit", "kalshi", "polymarket"];
if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) {
if (dollarPlatforms.includes(platform)) {
@@ -114,66 +79,50 @@ const getCurrencySymbolIfNeeded = ({
}
};
-const showFirstQualityIndicator: React.FC<{
+const FirstQualityIndicator: React.FC<{
question: QuestionFragment;
- showTimeStamp: boolean;
-}> = ({ question, showTimeStamp }) => {
- const lastUpdated = new Date(question.timestamp * 1000);
- if (!!question.qualityIndicators.numForecasts) {
+}> = ({ question }) => {
+ if (question.qualityIndicators.numForecasts) {
return (
-
- {/*
{` ${numforecasts == 1 ? "Forecast" : "Forecasts:"}`} */}
+
Forecasts:
{Number(question.qualityIndicators.numForecasts).toFixed(0)}
);
- } else if (showTimeStamp) {
- return (
-
-
- {`Last updated: ${
- lastUpdated ? lastUpdated.toISOString().slice(0, 10) : "unknown"
- }`}
-
- );
} else {
return null;
}
};
-const displayQualityIndicators: React.FC<{
+const QualityIndicatorsList: React.FC<{
question: QuestionFragment;
- showTimeStamp: boolean;
-}> = ({ question, showTimeStamp }) => {
- const { qualityIndicators } = question;
+}> = ({ question }) => {
return (
- {showFirstQualityIndicator({
- question,
- showTimeStamp,
+
+ {Object.entries(question.qualityIndicators).map((entry, i) => {
+ const indicatorLabel = qualityIndicatorLabels[entry[0]];
+ if (!indicatorLabel || entry[1] === null) return;
+ const indicator = entry[0] as UsedIndicatorName; // guaranteed by the previous line
+ const value = entry[1];
+
+ return (
+
+ {indicatorLabel}:
+
+ {`${getCurrencySymbolIfNeeded({
+ indicator,
+ platform: question.platform.id,
+ })}${formatNumber(value)}${getPercentageSymbolIfNeeded({
+ indicator,
+ platform: question.platform.id,
+ })}`}
+
+
+ );
})}
- {Object.entries(formatQualityIndicators(question.qualityIndicators)).map(
- (entry, i) => {
- return (
-
- ${entry[0]}:
-
- {`${getCurrencySymbolIfNeeded({
- indicator: entry[0],
- platform: question.platform.id,
- })}${formatNumber(entry[1])}${getPercentageSymbolIfNeeded({
- indicator: entry[0],
- platform: question.platform.id,
- })}`}
-
-
- );
- }
- )}
);
};
@@ -235,16 +184,13 @@ function getStarsColor(numstars: number) {
interface Props {
question: QuestionFragment;
- showTimeStamp: boolean;
expandFooterToFullWidth: boolean;
}
export const QuestionFooter: React.FC
= ({
question,
- showTimeStamp,
expandFooterToFullWidth,
}) => {
- let debuggingWithBackground = false;
return (
= ({
{getstars(question.qualityIndicators.stars)}
{question.platform.label
.replace("Good Judgment Open", "GJOpen")
@@ -272,12 +218,9 @@ export const QuestionFooter: React.FC
= ({
expandFooterToFullWidth
? "justify-self-end mr-4"
: "justify-self-center"
- } col-span-1 ${debuggingWithBackground ? "bg-red-100" : ""}`}
+ } col-span-1`}
>
- {displayQualityIndicators({
- question,
- showTimeStamp,
- })}
+
);
diff --git a/src/web/display/DisplayQuestion/index.tsx b/src/web/display/DisplayQuestion/index.tsx
index 8d84a67..3bb582f 100644
--- a/src/web/display/DisplayQuestion/index.tsx
+++ b/src/web/display/DisplayQuestion/index.tsx
@@ -4,7 +4,6 @@ import ReactMarkdown from "react-markdown";
import { CopyText } from "../../common/CopyText";
import { QuestionOptions } from "../../questions/components/QuestionOptions";
-import { formatProbability } from "../../questions/utils";
import { QuestionFragment } from "../../search/queries.generated";
import { Card } from "../Card";
import { QuestionFooter } from "./QuestionFooter";
@@ -87,99 +86,12 @@ const cleanText = (text: string): string => {
return textString;
};
-const primaryForecastColor = (probability: number) => {
- if (probability < 0.03) {
- return "bg-red-600";
- } else if (probability < 0.1) {
- return "bg-red-600 opacity-80";
- } else if (probability < 0.2) {
- return "bg-red-600 opacity-70";
- } else if (probability < 0.3) {
- return "bg-red-600 opacity-60";
- } else if (probability < 0.4) {
- return "bg-red-600 opacity-50";
- } else if (probability < 0.5) {
- return "bg-gray-500";
- } else if (probability < 0.6) {
- return "bg-gray-500";
- } else if (probability < 0.7) {
- return "bg-green-600 opacity-50";
- } else if (probability < 0.8) {
- return "bg-green-600 opacity-60";
- } else if (probability < 0.9) {
- return "bg-green-600 opacity-70";
- } else if (probability < 0.97) {
- return "bg-green-600 opacity-80";
- } else {
- return "bg-green-600";
- }
-};
-
-const textColor = (probability: number) => {
- if (probability < 0.03) {
- return "text-red-600";
- } else if (probability < 0.1) {
- return "text-red-600 opacity-80";
- } else if (probability < 0.2) {
- return "text-red-600 opacity-80";
- } else if (probability < 0.3) {
- return "text-red-600 opacity-70";
- } else if (probability < 0.4) {
- return "text-red-600 opacity-70";
- } else if (probability < 0.5) {
- return "text-gray-500";
- } else if (probability < 0.6) {
- return "text-gray-500";
- } else if (probability < 0.7) {
- return "text-green-600 opacity-70";
- } else if (probability < 0.8) {
- return "text-green-600 opacity-70";
- } else if (probability < 0.9) {
- return "text-green-600 opacity-80";
- } else if (probability < 0.97) {
- return "text-green-600 opacity-80";
- } else {
- return "text-green-600";
- }
-};
-
-const primaryEstimateAsText = (probability: number) => {
- if (probability < 0.03) {
- return "Exceptionally unlikely";
- } else if (probability < 0.1) {
- return "Very unlikely";
- } else if (probability < 0.4) {
- return "Unlikely";
- } else if (probability < 0.6) {
- return "About Even";
- } else if (probability < 0.9) {
- return "Likely";
- } else if (probability < 0.97) {
- return "Very likely";
- } else {
- return "Virtually certain";
- }
-};
-
-// Logical checks
-
-const checkIfDisplayTimeStampAtBottom = (qualityIndicators: {
- [k: string]: any;
-}) => {
- let indicators = Object.keys(qualityIndicators);
- if (indicators.length == 1 && indicators[0] == "stars") {
- return true;
- } else {
- return false;
- }
-};
-
// Auxiliary components
const DisplayMarkdown: React.FC<{ description: string }> = ({
description,
}) => {
- let formatted = truncateText(250, cleanText(description));
+ const formatted = truncateText(250, cleanText(description));
// overflow-hidden overflow-ellipsis h-24
return formatted === "" ? null : (
@@ -217,19 +129,10 @@ export const DisplayQuestion: React.FC
= ({
expandFooterToFullWidth,
showIdToggle,
}) => {
- const {
- platform,
- description,
- options,
- qualityIndicators,
- timestamp,
- visualization,
- } = question;
- const lastUpdated = new Date(timestamp * 1000);
- const displayTimestampAtBottom =
- checkIfDisplayTimeStampAtBottom(qualityIndicators);
+ const { options } = question;
+ const lastUpdated = new Date(question.timestamp * 1000);
- const yesNoOptions =
+ const isBinary =
options.length === 2 &&
(options[0].name === "Yes" || options[0].name === "No");
@@ -243,7 +146,7 @@ export const DisplayQuestion: React.FC = ({
) : null}
- {yesNoOptions && (
+ {isBinary ? (
-
-
- {formatProbability(options[0].probability)}
-
-
- {primaryEstimateAsText(options[0].probability)}
-
-
-
- )}
- {!yesNoOptions && (
+ ) : (
-
@@ -303,14 +182,14 @@ export const DisplayQuestion: React.FC
= ({
{question.platform.id !== "guesstimate" && options.length < 3 && (
-
+
)}
{question.platform.id === "guesstimate" && (
)}
@@ -324,7 +203,6 @@ export const DisplayQuestion: React.FC = ({
diff --git a/src/web/questions/components/QuestionOptions.tsx b/src/web/questions/components/QuestionOptions.tsx
index 6ff1122..e200018 100644
--- a/src/web/questions/components/QuestionOptions.tsx
+++ b/src/web/questions/components/QuestionOptions.tsx
@@ -1,18 +1,95 @@
+import { QuestionFragment } from "../../search/queries.generated";
import { formatProbability } from "../utils";
-const OptionRow: React.FC<{ option: any }> = ({ option }) => {
- const chooseColor = (probability: number) => {
- if (probability < 0.1) {
- return "bg-blue-50 text-blue-500";
- } else if (probability < 0.3) {
- return "bg-blue-100 text-blue-600";
- } else if (probability < 0.7) {
- return "bg-blue-200 text-blue-700";
- } else {
- return "bg-blue-300 text-blue-800";
- }
- };
+type Option = QuestionFragment["options"][0];
+const textColor = (probability: number) => {
+ if (probability < 0.03) {
+ return "text-red-600";
+ } else if (probability < 0.1) {
+ return "text-red-600 opacity-80";
+ } else if (probability < 0.2) {
+ return "text-red-600 opacity-80";
+ } else if (probability < 0.3) {
+ return "text-red-600 opacity-70";
+ } else if (probability < 0.4) {
+ return "text-red-600 opacity-70";
+ } else if (probability < 0.5) {
+ return "text-gray-500";
+ } else if (probability < 0.6) {
+ return "text-gray-500";
+ } else if (probability < 0.7) {
+ return "text-green-600 opacity-70";
+ } else if (probability < 0.8) {
+ return "text-green-600 opacity-70";
+ } else if (probability < 0.9) {
+ return "text-green-600 opacity-80";
+ } else if (probability < 0.97) {
+ return "text-green-600 opacity-80";
+ } else {
+ return "text-green-600";
+ }
+};
+
+const primaryForecastColor = (probability: number) => {
+ if (probability < 0.03) {
+ return "bg-red-600";
+ } else if (probability < 0.1) {
+ return "bg-red-600 opacity-80";
+ } else if (probability < 0.2) {
+ return "bg-red-600 opacity-70";
+ } else if (probability < 0.3) {
+ return "bg-red-600 opacity-60";
+ } else if (probability < 0.4) {
+ return "bg-red-600 opacity-50";
+ } else if (probability < 0.5) {
+ return "bg-gray-500";
+ } else if (probability < 0.6) {
+ return "bg-gray-500";
+ } else if (probability < 0.7) {
+ return "bg-green-600 opacity-50";
+ } else if (probability < 0.8) {
+ return "bg-green-600 opacity-60";
+ } else if (probability < 0.9) {
+ return "bg-green-600 opacity-70";
+ } else if (probability < 0.97) {
+ return "bg-green-600 opacity-80";
+ } else {
+ return "bg-green-600";
+ }
+};
+
+const primaryEstimateAsText = (probability: number) => {
+ if (probability < 0.03) {
+ return "Exceptionally unlikely";
+ } else if (probability < 0.1) {
+ return "Very unlikely";
+ } else if (probability < 0.4) {
+ return "Unlikely";
+ } else if (probability < 0.6) {
+ return "About Even";
+ } else if (probability < 0.9) {
+ return "Likely";
+ } else if (probability < 0.97) {
+ return "Very likely";
+ } else {
+ return "Virtually certain";
+ }
+};
+
+const chooseColor = (probability: number) => {
+ if (probability < 0.1) {
+ return "bg-blue-50 text-blue-500";
+ } else if (probability < 0.3) {
+ return "bg-blue-100 text-blue-600";
+ } else if (probability < 0.7) {
+ return "bg-blue-200 text-blue-700";
+ } else {
+ return "bg-blue-300 text-blue-800";
+ }
+};
+
+const OptionRow: React.FC<{ option: Option }> = ({ option }) => {
return (
= ({ option }) => {
);
};
-export const QuestionOptions: React.FC<{ options: any[] }> = ({ options }) => {
+export const QuestionOptions: React.FC<{ options: Option[] }> = ({
+ options,
+}) => {
+ const isBinary =
+ options.length === 2 &&
+ (options[0].name === "Yes" || options[0].name === "No");
+
const optionsSorted = options.sort((a, b) => b.probability - a.probability);
const optionsMax5 = !!optionsSorted.slice ? optionsSorted.slice(0, 5) : []; // display max 5 options.
- return (
-
- {optionsMax5.map((option, i) => (
-
- ))}
-
- );
+
+ if (isBinary) {
+ return (
+
+
+ {formatProbability(options[0].probability)}
+
+
+ {primaryEstimateAsText(options[0].probability)}
+
+
+ );
+ } else {
+ return (
+
+ {optionsMax5.map((option, i) => (
+
+ ))}
+
+ );
+ }
};
diff --git a/src/web/questions/pages/QuestionPage.tsx b/src/web/questions/pages/QuestionPage.tsx
index aa2b083..c36ca11 100644
--- a/src/web/questions/pages/QuestionPage.tsx
+++ b/src/web/questions/pages/QuestionPage.tsx
@@ -3,6 +3,7 @@ import ReactMarkdown from "react-markdown";
import { Query } from "../../common/Query";
import { Card } from "../../display/Card";
+import { QuestionFooter } from "../../display/DisplayQuestion/QuestionFooter";
import { Layout } from "../../display/Layout";
import { QuestionFragment } from "../../search/queries.generated";
import { ssrUrql } from "../../urql";
@@ -39,8 +40,18 @@ const QuestionCardContents: React.FC<{ question: QuestionFragment }> = ({
question,
}) => (
-
{question.title}
+
+
+
{question.description}