Merge pull request #79 from quantified-uncertainty/stricter-typescript
Strict typescript & platform/cli arguments
This commit is contained in:
commit
6fbeea2267
757
package-lock.json
generated
757
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -33,13 +33,18 @@
|
||||||
"@pothos/plugin-prisma": "^3.4.0",
|
"@pothos/plugin-prisma": "^3.4.0",
|
||||||
"@pothos/plugin-relay": "^3.10.0",
|
"@pothos/plugin-relay": "^3.10.0",
|
||||||
"@prisma/client": "^3.11.1",
|
"@prisma/client": "^3.11.1",
|
||||||
|
"@quri/squiggle-lang": "^0.2.8",
|
||||||
"@tailwindcss/forms": "^0.4.0",
|
"@tailwindcss/forms": "^0.4.0",
|
||||||
"@tailwindcss/typography": "^0.5.1",
|
"@tailwindcss/typography": "^0.5.1",
|
||||||
|
"@types/chroma-js": "^2.1.3",
|
||||||
"@types/dom-to-image": "^2.6.4",
|
"@types/dom-to-image": "^2.6.4",
|
||||||
|
"@types/google-spreadsheet": "^3.2.1",
|
||||||
"@types/jsdom": "^16.2.14",
|
"@types/jsdom": "^16.2.14",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/react": "^17.0.39",
|
"@types/react": "^17.0.39",
|
||||||
"@types/react-copy-to-clipboard": "^5.0.2",
|
"@types/react-copy-to-clipboard": "^5.0.2",
|
||||||
|
"@types/textversionjs": "^1.1.1",
|
||||||
|
"@types/tunnel": "^0.0.3",
|
||||||
"airtable": "^0.11.1",
|
"airtable": "^0.11.1",
|
||||||
"algoliasearch": "^4.10.3",
|
"algoliasearch": "^4.10.3",
|
||||||
"autoprefixer": "^10.1.0",
|
"autoprefixer": "^10.1.0",
|
||||||
|
@ -86,7 +91,6 @@
|
||||||
"react-safe": "^1.3.0",
|
"react-safe": "^1.3.0",
|
||||||
"react-select": "^5.2.2",
|
"react-select": "^5.2.2",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"squiggle-experimental": "^0.1.9",
|
|
||||||
"tabletojson": "^2.0.4",
|
"tabletojson": "^2.0.4",
|
||||||
"tailwindcss": "^3.0.22",
|
"tailwindcss": "^3.0.22",
|
||||||
"textversionjs": "^1.1.3",
|
"textversionjs": "^1.1.3",
|
||||||
|
|
|
@ -3,14 +3,7 @@ import { executeJobByName } from "./jobs";
|
||||||
|
|
||||||
/* Do everything */
|
/* Do everything */
|
||||||
export async function doEverything() {
|
export async function doEverything() {
|
||||||
let jobNames = [
|
let jobNames = [...platforms.map((platform) => platform.name), "algolia"];
|
||||||
...platforms.map((platform) => platform.name),
|
|
||||||
"merge",
|
|
||||||
"algolia",
|
|
||||||
"history",
|
|
||||||
"netlify",
|
|
||||||
];
|
|
||||||
// Removed Good Judgment from the fetcher, doing it using cron instead because cloudflare blocks the utility on heroku.
|
|
||||||
|
|
||||||
console.log("");
|
console.log("");
|
||||||
console.log("");
|
console.log("");
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
import { prisma } from "../../database/prisma";
|
|
||||||
|
|
||||||
export async function updateHistory() {
|
|
||||||
const questions = await prisma.question.findMany({});
|
|
||||||
await prisma.history.createMany({
|
|
||||||
data: questions.map((q) => ({
|
|
||||||
...q,
|
|
||||||
idref: q.id,
|
|
||||||
})),
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,38 +1,29 @@
|
||||||
import { doEverything } from "../flow/doEverything";
|
import { doEverything } from "../flow/doEverything";
|
||||||
import { updateHistory } from "../flow/history/updateHistory";
|
|
||||||
import { rebuildNetlifySiteWithNewData } from "../flow/rebuildNetliftySiteWithNewData";
|
|
||||||
import { rebuildFrontpage } from "../frontpage";
|
import { rebuildFrontpage } from "../frontpage";
|
||||||
import { platforms, processPlatform } from "../platforms";
|
import { platforms, processPlatform } from "../platforms";
|
||||||
import { rebuildAlgoliaDatabase } from "../utils/algolia";
|
import { rebuildAlgoliaDatabase } from "../utils/algolia";
|
||||||
|
import { sleep } from "../utils/sleep";
|
||||||
|
|
||||||
interface Job {
|
interface Job<ArgNames extends string = ""> {
|
||||||
name: string;
|
name: string;
|
||||||
message: string;
|
message: string;
|
||||||
run: () => Promise<void>;
|
args?: ArgNames[];
|
||||||
|
run: (args?: { [k in ArgNames]: string }) => Promise<void>;
|
||||||
separate?: boolean;
|
separate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const jobs: Job[] = [
|
export const jobs: Job<string>[] = [
|
||||||
...platforms.map((platform) => ({
|
...platforms.map((platform) => ({
|
||||||
name: platform.name,
|
name: platform.name,
|
||||||
message: `Download predictions from ${platform.name}`,
|
message: `Download predictions from ${platform.name}`,
|
||||||
run: () => processPlatform(platform),
|
...(platform.version === "v2" ? { args: platform.fetcherArgs } : {}),
|
||||||
|
run: (args: any) => processPlatform(platform, args),
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
name: "algolia",
|
name: "algolia",
|
||||||
message: 'Rebuild algolia database ("index")',
|
message: 'Rebuild algolia database ("index")',
|
||||||
run: rebuildAlgoliaDatabase,
|
run: rebuildAlgoliaDatabase,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "history",
|
|
||||||
message: "Update history",
|
|
||||||
run: updateHistory,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "netlify",
|
|
||||||
message: `Rebuild netlify site with new data`,
|
|
||||||
run: rebuildNetlifySiteWithNewData,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "frontpage",
|
name: "frontpage",
|
||||||
message: "Rebuild frontpage",
|
message: "Rebuild frontpage",
|
||||||
|
@ -46,31 +37,39 @@ export const jobs: Job[] = [
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
function sleep(ms: number) {
|
async function tryCatchTryAgain<T extends object = never>(
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
fun: (args: T) => Promise<void>,
|
||||||
}
|
args: T
|
||||||
|
) {
|
||||||
async function tryCatchTryAgain(fun: () => Promise<void>) {
|
|
||||||
try {
|
try {
|
||||||
console.log("Initial try");
|
console.log("Initial try");
|
||||||
await fun();
|
await fun(args);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
sleep(10000);
|
sleep(10000);
|
||||||
console.log("Second try");
|
console.log("Second try");
|
||||||
console.log(error);
|
console.log(error);
|
||||||
try {
|
try {
|
||||||
await fun();
|
await fun(args);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const executeJobByName = async (option: string) => {
|
export const executeJobByName = async (
|
||||||
const job = jobs.find((job) => job.name === option);
|
jobName: string,
|
||||||
|
jobArgs: { [k: string]: string } = {}
|
||||||
|
) => {
|
||||||
|
const job = jobs.find((job) => job.name === jobName);
|
||||||
if (!job) {
|
if (!job) {
|
||||||
console.log(`Error, job ${option} not found`);
|
console.log(`Error, job ${jobName} not found`);
|
||||||
} else {
|
return;
|
||||||
await tryCatchTryAgain(job.run);
|
|
||||||
}
|
}
|
||||||
|
for (const key of Object.keys(jobArgs)) {
|
||||||
|
if (!job.args || job.args.indexOf(key) < 0) {
|
||||||
|
throw new Error(`Job ${jobName} doesn't accept ${key} argument`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await tryCatchTryAgain(job.run, jobArgs);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
import axios from "axios";
|
|
||||||
|
|
||||||
import { applyIfSecretExists } from "../utils/getSecrets";
|
|
||||||
|
|
||||||
async function rebuildNetlifySiteWithNewData_inner(cookie) {
|
|
||||||
let payload = {};
|
|
||||||
let response = await axios.post(cookie, payload);
|
|
||||||
let data = response.data;
|
|
||||||
console.log(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function rebuildNetlifySiteWithNewData() {
|
|
||||||
let cookie = process.env.REBUIDNETLIFYHOOKURL;
|
|
||||||
await applyIfSecretExists(cookie, rebuildNetlifySiteWithNewData_inner);
|
|
||||||
}
|
|
|
@ -2,11 +2,10 @@
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
|
|
||||||
import readline from "readline";
|
import readline from "readline";
|
||||||
import util from "util";
|
|
||||||
|
|
||||||
import { executeJobByName, jobs } from "./flow/jobs";
|
import { executeJobByName, jobs } from "./flow/jobs";
|
||||||
|
|
||||||
let generateWhatToDoMessage = () => {
|
const generateWhatToDoMessage = () => {
|
||||||
const color = "\x1b[36m";
|
const color = "\x1b[36m";
|
||||||
const resetColor = "\x1b[0m";
|
const resetColor = "\x1b[0m";
|
||||||
let completeMessages = [
|
let completeMessages = [
|
||||||
|
@ -23,27 +22,56 @@ let generateWhatToDoMessage = () => {
|
||||||
return completeMessages;
|
return completeMessages;
|
||||||
};
|
};
|
||||||
|
|
||||||
let whattodoMessage = generateWhatToDoMessage();
|
const whattodoMessage = generateWhatToDoMessage();
|
||||||
|
|
||||||
/* BODY */
|
const askForJobName = async () => {
|
||||||
let commandLineUtility = async () => {
|
const rl = readline.createInterface({
|
||||||
const pickOption = async () => {
|
input: process.stdin,
|
||||||
if (process.argv.length === 3) {
|
output: process.stdout,
|
||||||
return process.argv[2]; // e.g., npm run cli polymarket
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const rl = readline.createInterface({
|
const question = (query: string) => {
|
||||||
input: process.stdin,
|
return new Promise((resolve: (s: string) => void) => {
|
||||||
output: process.stdout,
|
rl.question(query, resolve);
|
||||||
});
|
});
|
||||||
|
|
||||||
const question = util.promisify(rl.question).bind(rl);
|
|
||||||
const answer = await question(whattodoMessage);
|
|
||||||
rl.close();
|
|
||||||
return answer;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await executeJobByName(await pickOption());
|
const answer = await question(whattodoMessage);
|
||||||
|
rl.close();
|
||||||
|
|
||||||
|
return answer;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pickJob = async (): Promise<[string, { [k: string]: string }]> => {
|
||||||
|
if (process.argv.length < 3) {
|
||||||
|
const jobName = await askForJobName();
|
||||||
|
return [jobName, {}]; // e.g., npm run cli polymarket
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobName = process.argv[2];
|
||||||
|
if ((process.argv.length - 3) % 2) {
|
||||||
|
throw new Error("Number of extra arguments must be even");
|
||||||
|
}
|
||||||
|
|
||||||
|
const args: { [k: string]: string } = {};
|
||||||
|
for (let i = 3; i < process.argv.length; i += 2) {
|
||||||
|
let argName = process.argv[i];
|
||||||
|
const argValue = process.argv[i + 1];
|
||||||
|
if (argName.slice(0, 2) !== "--") {
|
||||||
|
throw new Error(`${argName} should start with --`);
|
||||||
|
}
|
||||||
|
argName = argName.slice(2);
|
||||||
|
args[argName] = argValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [jobName, args];
|
||||||
|
};
|
||||||
|
|
||||||
|
/* BODY */
|
||||||
|
const commandLineUtility = async () => {
|
||||||
|
const [jobName, jobArgs] = await pickJob();
|
||||||
|
|
||||||
|
await executeJobByName(jobName, jobArgs);
|
||||||
process.exit();
|
process.exit();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* Imports */
|
/* Imports */
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
|
@ -22,11 +21,11 @@ async function fetchData() {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processPredictions(predictions) {
|
async function processPredictions(predictions: any[]) {
|
||||||
let results = await predictions.map((prediction) => {
|
let results = await predictions.map((prediction) => {
|
||||||
const id = `${platformName}-${prediction.id}`;
|
const id = `${platformName}-${prediction.id}`;
|
||||||
const probability = prediction.probability;
|
const probability = prediction.probability;
|
||||||
const options = [
|
const options: FetchedQuestion["options"] = [
|
||||||
{
|
{
|
||||||
name: "Yes",
|
name: "Yes",
|
||||||
probability: probability,
|
probability: probability,
|
||||||
|
@ -41,14 +40,10 @@ async function processPredictions(predictions) {
|
||||||
const result: FetchedQuestion = {
|
const result: FetchedQuestion = {
|
||||||
id,
|
id,
|
||||||
title: prediction.title,
|
title: prediction.title,
|
||||||
url: `https://example.com`,
|
url: "https://example.com",
|
||||||
platform: platformName,
|
|
||||||
description: prediction.description,
|
description: prediction.description,
|
||||||
options,
|
options,
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
stars: calculateStars(platformName, {
|
|
||||||
/* some: somex, factors: factors */
|
|
||||||
}),
|
|
||||||
// other: prediction.otherx,
|
// other: prediction.otherx,
|
||||||
// indicators: prediction.indicatorx,
|
// indicators: prediction.indicatorx,
|
||||||
},
|
},
|
||||||
|
@ -64,9 +59,13 @@ export const example: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "Example platform",
|
label: "Example platform",
|
||||||
color: "#ff0000",
|
color: "#ff0000",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
let data = await fetchData();
|
let data = await fetchData();
|
||||||
let results = await processPredictions(data); // somehow needed
|
let results = await processPredictions(data); // somehow needed
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
return 2;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,16 +2,16 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import https from "https";
|
import https from "https";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
import { average } from "../../utils";
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
const platformName = "betfair";
|
const platformName = "betfair";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
let endpoint = process.env.SECRET_BETFAIR_ENDPOINT;
|
const endpoint = process.env.SECRET_BETFAIR_ENDPOINT;
|
||||||
|
|
||||||
/* Utilities */
|
/* Utilities */
|
||||||
let arraysEqual = (a, b) => {
|
const arraysEqual = (a: string[], b: string[]) => {
|
||||||
if (a === b) return true;
|
if (a === b) return true;
|
||||||
if (a == null || b == null) return false;
|
if (a == null || b == null) return false;
|
||||||
if (a.length !== b.length) return false;
|
if (a.length !== b.length) return false;
|
||||||
|
@ -26,7 +26,8 @@ let arraysEqual = (a, b) => {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
let mergeRunners = (runnerCatalog, runnerBook) => {
|
|
||||||
|
const mergeRunners = (runnerCatalog: any, runnerBook: any) => {
|
||||||
let keys = Object.keys(runnerCatalog);
|
let keys = Object.keys(runnerCatalog);
|
||||||
let result = [];
|
let result = [];
|
||||||
for (let key of keys) {
|
for (let key of keys) {
|
||||||
|
@ -41,19 +42,16 @@ async function fetchPredictions() {
|
||||||
const agent = new https.Agent({
|
const agent = new https.Agent({
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false,
|
||||||
});
|
});
|
||||||
let response = await axios({
|
const response = await axios({
|
||||||
url: endpoint,
|
url: endpoint,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/html",
|
|
||||||
},
|
|
||||||
httpsAgent: agent,
|
httpsAgent: agent,
|
||||||
}).then((response) => response.data);
|
}).then((response) => response.data);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function whipIntoShape(data) {
|
async function whipIntoShape(data: any) {
|
||||||
let catalogues = data.market_catalogues;
|
let catalogues = data.market_catalogues;
|
||||||
let books = data.market_books;
|
let books = data.market_books;
|
||||||
let keys1 = Object.keys(catalogues).sort();
|
let keys1 = Object.keys(catalogues).sort();
|
||||||
|
@ -77,7 +75,7 @@ async function whipIntoShape(data) {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processPredictions(data) {
|
async function processPredictions(data: any) {
|
||||||
let predictions = await whipIntoShape(data);
|
let predictions = await whipIntoShape(data);
|
||||||
// console.log(JSON.stringify(predictions, null, 4))
|
// console.log(JSON.stringify(predictions, null, 4))
|
||||||
let results: FetchedQuestion[] = predictions.map((prediction) => {
|
let results: FetchedQuestion[] = predictions.map((prediction) => {
|
||||||
|
@ -86,13 +84,17 @@ async function processPredictions(data) {
|
||||||
} */
|
} */
|
||||||
let id = `${platformName}-${prediction.marketId}`;
|
let id = `${platformName}-${prediction.marketId}`;
|
||||||
let normalizationFactor = prediction.options
|
let normalizationFactor = prediction.options
|
||||||
.filter((option) => option.status == "ACTIVE" && option.totalMatched > 0)
|
.filter(
|
||||||
.map((option) => option.lastPriceTraded)
|
(option: any) => option.status == "ACTIVE" && option.totalMatched > 0
|
||||||
.map((x) => 1 / x)
|
)
|
||||||
.reduce((a, b) => a + b, 0);
|
.map((option: any) => option.lastPriceTraded)
|
||||||
|
.map((x: any) => 1 / x)
|
||||||
|
.reduce((a: any, b: any) => a + b, 0);
|
||||||
let options = prediction.options
|
let options = prediction.options
|
||||||
.filter((option) => option.status == "ACTIVE" && option.totalMatched > 0)
|
.filter(
|
||||||
.map((option) => ({
|
(option: any) => option.status == "ACTIVE" && option.totalMatched > 0
|
||||||
|
)
|
||||||
|
.map((option: any) => ({
|
||||||
name: option.runnerName,
|
name: option.runnerName,
|
||||||
probability:
|
probability:
|
||||||
option.lastPriceTraded != 0
|
option.lastPriceTraded != 0
|
||||||
|
@ -114,22 +116,19 @@ async function processPredictions(data) {
|
||||||
if (rules == undefined) {
|
if (rules == undefined) {
|
||||||
// console.log(prediction.description)
|
// console.log(prediction.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
let title = rules.split("? ")[0] + "?";
|
let title = rules.split("? ")[0] + "?";
|
||||||
let description = rules.split("? ")[1].trim();
|
let description = rules.split("? ")[1].trim();
|
||||||
if (title.includes("of the named")) {
|
if (title.includes("of the named")) {
|
||||||
title = prediction.marketName + ": " + title;
|
title = prediction.marketName + ": " + title;
|
||||||
}
|
}
|
||||||
let result = {
|
const result: FetchedQuestion = {
|
||||||
id: id,
|
id,
|
||||||
title: title,
|
title,
|
||||||
url: `https://www.betfair.com/exchange/plus/politics/market/${prediction.marketId}`,
|
url: `https://www.betfair.com/exchange/plus/politics/market/${prediction.marketId}`,
|
||||||
platform: platformName,
|
description,
|
||||||
description: description,
|
options,
|
||||||
options: options,
|
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
stars: calculateStars(platformName, {
|
|
||||||
volume: prediction.totalMatched,
|
|
||||||
}),
|
|
||||||
volume: prediction.totalMatched,
|
volume: prediction.totalMatched,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -142,9 +141,31 @@ export const betfair: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "Betfair",
|
label: "Betfair",
|
||||||
color: "#3d674a",
|
color: "#3d674a",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
const data = await fetchPredictions();
|
const data = await fetchPredictions();
|
||||||
const results = await processPredictions(data); // somehow needed
|
const results = await processPredictions(data);
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
const volume = data.qualityindicators.volume || 0;
|
||||||
|
let nuno = () => (volume > 10000 ? 4 : volume > 1000 ? 3 : 2);
|
||||||
|
let eli = () => (volume > 10000 ? null : null);
|
||||||
|
let misha = () => null;
|
||||||
|
let starsDecimal = average([nuno()]); //, eli(), misha()])
|
||||||
|
|
||||||
|
const firstOption = data.options[0];
|
||||||
|
|
||||||
|
// Substract 1 star if probability is above 90% or below 10%
|
||||||
|
if (
|
||||||
|
firstOption &&
|
||||||
|
((firstOption.probability || 0) < 0.1 ||
|
||||||
|
(firstOption.probability || 0) > 0.9)
|
||||||
|
) {
|
||||||
|
starsDecimal = starsDecimal - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
/* Imports */
|
/* Imports */
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
const platformName = "fantasyscotus";
|
const platformName = "fantasyscotus";
|
||||||
|
@ -30,7 +29,7 @@ async function fetchData() {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getPredictionsData(caseUrl) {
|
async function getPredictionsData(caseUrl: string) {
|
||||||
let newCaseUrl = `https://fantasyscotus.net/user-predictions${caseUrl}?filterscount=0&groupscount=0&sortdatafield=username&sortorder=asc&pagenum=0&pagesize=20&recordstartindex=0&recordendindex=20&_=${unixtime}`;
|
let newCaseUrl = `https://fantasyscotus.net/user-predictions${caseUrl}?filterscount=0&groupscount=0&sortdatafield=username&sortorder=asc&pagenum=0&pagesize=20&recordstartindex=0&recordendindex=20&_=${unixtime}`;
|
||||||
//console.log(newCaseUrl)
|
//console.log(newCaseUrl)
|
||||||
let predictions = await axios({
|
let predictions = await axios({
|
||||||
|
@ -50,7 +49,7 @@ async function getPredictionsData(caseUrl) {
|
||||||
}).then((res) => res.data);
|
}).then((res) => res.data);
|
||||||
|
|
||||||
let predictionsAffirm = predictions.filter(
|
let predictionsAffirm = predictions.filter(
|
||||||
(prediction) => prediction.percent_affirm > 50
|
(prediction: any) => prediction.percent_affirm > 50
|
||||||
);
|
);
|
||||||
//console.log(predictions)
|
//console.log(predictions)
|
||||||
//console.log(predictionsAffirm.length/predictions.length)
|
//console.log(predictionsAffirm.length/predictions.length)
|
||||||
|
@ -62,7 +61,7 @@ async function getPredictionsData(caseUrl) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processData(data) {
|
async function processData(data: any) {
|
||||||
let events = data.object_list;
|
let events = data.object_list;
|
||||||
let historicalPercentageCorrect = data.stats.pcnt_correct;
|
let historicalPercentageCorrect = data.stats.pcnt_correct;
|
||||||
let historicalProbabilityCorrect =
|
let historicalProbabilityCorrect =
|
||||||
|
@ -79,7 +78,6 @@ async function processData(data) {
|
||||||
id: id,
|
id: id,
|
||||||
title: `In ${event.short_name}, the SCOTUS will affirm the lower court's decision`,
|
title: `In ${event.short_name}, the SCOTUS will affirm the lower court's decision`,
|
||||||
url: `https://fantasyscotus.net/user-predictions${event.docket_url}`,
|
url: `https://fantasyscotus.net/user-predictions${event.docket_url}`,
|
||||||
platform: platformName,
|
|
||||||
description: `${(pAffirm * 100).toFixed(2)}% (${
|
description: `${(pAffirm * 100).toFixed(2)}% (${
|
||||||
predictionData.numAffirm
|
predictionData.numAffirm
|
||||||
} out of ${
|
} out of ${
|
||||||
|
@ -101,7 +99,6 @@ async function processData(data) {
|
||||||
],
|
],
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
numforecasts: Number(predictionData.numForecasts),
|
numforecasts: Number(predictionData.numForecasts),
|
||||||
stars: calculateStars(platformName, {}),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
results.push(eventObject);
|
results.push(eventObject);
|
||||||
|
@ -116,9 +113,13 @@ export const fantasyscotus: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "FantasySCOTUS",
|
label: "FantasySCOTUS",
|
||||||
color: "#231149",
|
color: "#231149",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
let rawData = await fetchData();
|
let rawData = await fetchData();
|
||||||
let results = await processData(rawData);
|
let results = await processData(rawData);
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
return 2;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* Imports */
|
/* Imports */
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
import { average } from "../../utils";
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
|
@ -18,8 +18,10 @@ let highQualityCommunities = [
|
||||||
];
|
];
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
async function fetchAllCommunityQuestions(communityId) {
|
async function fetchAllCommunityQuestions(communityId: string) {
|
||||||
let response = await axios({
|
// TODO - fetch foretold graphql schema to type the result properly?
|
||||||
|
// (should be doable with graphql-code-generator, why not)
|
||||||
|
const response = await axios({
|
||||||
url: graphQLendpoint,
|
url: graphQLendpoint,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
|
@ -30,10 +32,10 @@ async function fetchAllCommunityQuestions(communityId) {
|
||||||
channelId: "${communityId}",
|
channelId: "${communityId}",
|
||||||
states: OPEN,
|
states: OPEN,
|
||||||
first: 500
|
first: 500
|
||||||
){
|
) {
|
||||||
total
|
total
|
||||||
edges{
|
edges {
|
||||||
node{
|
node {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
valueType
|
valueType
|
||||||
|
@ -52,14 +54,15 @@ async function fetchAllCommunityQuestions(communityId) {
|
||||||
})
|
})
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.then((res) => res.data.measurables.edges);
|
.then((res) => res.data.measurables.edges);
|
||||||
//console.log(response)
|
|
||||||
return response;
|
return response as any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const foretold: Platform = {
|
export const foretold: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "Foretold",
|
label: "Foretold",
|
||||||
color: "#62520b",
|
color: "#62520b",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
let results: FetchedQuestion[] = [];
|
let results: FetchedQuestion[] = [];
|
||||||
for (let community of highQualityCommunities) {
|
for (let community of highQualityCommunities) {
|
||||||
|
@ -67,10 +70,11 @@ export const foretold: Platform = {
|
||||||
questions = questions.map((question) => question.node);
|
questions = questions.map((question) => question.node);
|
||||||
questions = questions.filter((question) => question.previousAggregate); // Questions without any predictions
|
questions = questions.filter((question) => question.previousAggregate); // Questions without any predictions
|
||||||
questions.forEach((question) => {
|
questions.forEach((question) => {
|
||||||
let id = `${platformName}-${question.id}`;
|
const id = `${platformName}-${question.id}`;
|
||||||
let options = [];
|
|
||||||
|
let options: FetchedQuestion["options"] = [];
|
||||||
if (question.valueType == "PERCENTAGE") {
|
if (question.valueType == "PERCENTAGE") {
|
||||||
let probability = question.previousAggregate.value.percentage;
|
const probability = question.previousAggregate.value.percentage;
|
||||||
options = [
|
options = [
|
||||||
{
|
{
|
||||||
name: "Yes",
|
name: "Yes",
|
||||||
|
@ -84,16 +88,15 @@ export const foretold: Platform = {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
let result: FetchedQuestion = {
|
|
||||||
|
const result: FetchedQuestion = {
|
||||||
id,
|
id,
|
||||||
title: question.name,
|
title: question.name,
|
||||||
url: `https://www.foretold.io/c/${community}/m/${question.id}`,
|
url: `https://www.foretold.io/c/${community}/m/${question.id}`,
|
||||||
platform: platformName,
|
|
||||||
description: "",
|
description: "",
|
||||||
options,
|
options,
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
numforecasts: Math.floor(Number(question.measurementCount) / 2),
|
numforecasts: Math.floor(Number(question.measurementCount) / 2),
|
||||||
stars: calculateStars(platformName, {}),
|
|
||||||
},
|
},
|
||||||
/*liquidity: liquidity.toFixed(2),
|
/*liquidity: liquidity.toFixed(2),
|
||||||
tradevolume: tradevolume.toFixed(2),
|
tradevolume: tradevolume.toFixed(2),
|
||||||
|
@ -105,4 +108,12 @@ export const foretold: Platform = {
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
let nuno = () => 2;
|
||||||
|
let eli = () => null;
|
||||||
|
let misha = () => null;
|
||||||
|
let starsDecimal = average([nuno()]); //, eli(), misha()])
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
import { average } from "../../utils";
|
||||||
import { Platform } from "./";
|
import { Platform } from "./";
|
||||||
|
|
||||||
const platformName = "givewellopenphil";
|
const platformName = "givewellopenphil";
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
async function fetchPage(url: string) {
|
async function fetchPage(url: string): Promise<string> {
|
||||||
let response = await axios({
|
const response = await axios({
|
||||||
url: url,
|
url: url,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -53,9 +53,7 @@ async function main1() {
|
||||||
platform: platformName,
|
platform: platformName,
|
||||||
description,
|
description,
|
||||||
options: [],
|
options: [],
|
||||||
qualityindicators: {
|
qualityindicators: {},
|
||||||
stars: calculateStars(platformName, {}),
|
|
||||||
},
|
|
||||||
}; // Note: This requires some processing afterwards
|
}; // Note: This requires some processing afterwards
|
||||||
// console.log(result)
|
// console.log(result)
|
||||||
results.push(result);
|
results.push(result);
|
||||||
|
@ -70,6 +68,7 @@ export const givewellopenphil: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "GiveWell/OpenPhilanthropy",
|
label: "GiveWell/OpenPhilanthropy",
|
||||||
color: "#32407e",
|
color: "#32407e",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
// main1()
|
// main1()
|
||||||
return; // not necessary to refill the DB every time
|
return; // not necessary to refill the DB every time
|
||||||
|
@ -84,4 +83,12 @@ export const givewellopenphil: Platform = {
|
||||||
}));
|
}));
|
||||||
return dataWithDate;
|
return dataWithDate;
|
||||||
},
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
let nuno = () => 2;
|
||||||
|
let eli = () => null;
|
||||||
|
let misha = () => null;
|
||||||
|
let starsDecimal = average([nuno()]); //, eli(), misha()])
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,27 +1,24 @@
|
||||||
/* Imports */
|
/* Imports */
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Tabletojson } from "tabletojson";
|
import { Tabletojson } from "tabletojson";
|
||||||
import tunnel from "tunnel";
|
|
||||||
|
|
||||||
|
import { average } from "../../utils";
|
||||||
import { hash } from "../utils/hash";
|
import { hash } from "../utils/hash";
|
||||||
import { calculateStars } from "../utils/stars";
|
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
const platformName = "goodjudgment";
|
const platformName = "goodjudgment";
|
||||||
let endpoint = "https://goodjudgment.io/superforecasts/";
|
const endpoint = "https://goodjudgment.io/superforecasts/";
|
||||||
String.prototype.replaceAll = function replaceAll(search, replace) {
|
|
||||||
return this.split(search).join(replace);
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Body */
|
/* Body */
|
||||||
export const goodjudgment: Platform = {
|
export const goodjudgment: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "Good Judgment",
|
label: "Good Judgment",
|
||||||
color: "#7d4f1b",
|
color: "#7d4f1b",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
// Proxy fuckery
|
// Proxy fuckery
|
||||||
let proxy;
|
// let proxy;
|
||||||
/*
|
/*
|
||||||
* try {
|
* try {
|
||||||
proxy = await axios
|
proxy = await axios
|
||||||
|
@ -32,19 +29,19 @@ export const goodjudgment: Platform = {
|
||||||
console.log("Proxy generation failed; using backup proxy instead");
|
console.log("Proxy generation failed; using backup proxy instead");
|
||||||
// hard-coded backup proxy
|
// hard-coded backup proxy
|
||||||
*/
|
*/
|
||||||
proxy = {
|
// proxy = {
|
||||||
ip: process.env.BACKUP_PROXY_IP,
|
// ip: process.env.BACKUP_PROXY_IP,
|
||||||
port: process.env.BACKUP_PROXY_PORT,
|
// port: process.env.BACKUP_PROXY_PORT,
|
||||||
};
|
// };
|
||||||
// }
|
// // }
|
||||||
let agent = tunnel.httpsOverHttp({
|
// let agent = tunnel.httpsOverHttp({
|
||||||
proxy: {
|
// proxy: {
|
||||||
host: proxy.ip,
|
// host: proxy.ip,
|
||||||
port: proxy.port,
|
// port: proxy.port,
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
|
|
||||||
let content = await axios
|
const content = await axios
|
||||||
.request({
|
.request({
|
||||||
url: "https://goodjudgment.io/superforecasts/",
|
url: "https://goodjudgment.io/superforecasts/",
|
||||||
method: "get",
|
method: "get",
|
||||||
|
@ -61,17 +58,16 @@ export const goodjudgment: Platform = {
|
||||||
let jsonTable = Tabletojson.convert(content, { stripHtmlFromCells: false });
|
let jsonTable = Tabletojson.convert(content, { stripHtmlFromCells: false });
|
||||||
jsonTable.shift(); // deletes first element
|
jsonTable.shift(); // deletes first element
|
||||||
jsonTable.pop(); // deletes last element
|
jsonTable.pop(); // deletes last element
|
||||||
// console.log(jsonTable)
|
|
||||||
for (let table of jsonTable) {
|
for (let table of jsonTable) {
|
||||||
// console.log(table)
|
|
||||||
let title = table[0]["0"].split("\t\t\t").splice(3)[0];
|
let title = table[0]["0"].split("\t\t\t").splice(3)[0];
|
||||||
if (title != undefined) {
|
if (title != undefined) {
|
||||||
title = title.replaceAll("</a>", "");
|
title = title.replaceAll("</a>", "");
|
||||||
let id = `${platformName}-${hash(title)}`;
|
const id = `${platformName}-${hash(title)}`;
|
||||||
let description = table
|
const description = table
|
||||||
.filter((row) => row["0"].includes("BACKGROUND:"))
|
.filter((row: any) => row["0"].includes("BACKGROUND:"))
|
||||||
.map((row) => row["0"])
|
.map((row: any) => row["0"])
|
||||||
.map((text) =>
|
.map((text: any) =>
|
||||||
text
|
text
|
||||||
.split("BACKGROUND:")[1]
|
.split("BACKGROUND:")[1]
|
||||||
.split("Examples of Superforecaster")[0]
|
.split("Examples of Superforecaster")[0]
|
||||||
|
@ -83,16 +79,16 @@ export const goodjudgment: Platform = {
|
||||||
.replaceAll(" ", "")
|
.replaceAll(" ", "")
|
||||||
.replaceAll("<br> ", "")
|
.replaceAll("<br> ", "")
|
||||||
)[0];
|
)[0];
|
||||||
let options = table
|
const options = table
|
||||||
.filter((row) => "4" in row)
|
.filter((row: any) => "4" in row)
|
||||||
.map((row) => ({
|
.map((row: any) => ({
|
||||||
name: row["2"]
|
name: row["2"]
|
||||||
.split('<span class="qTitle">')[1]
|
.split('<span class="qTitle">')[1]
|
||||||
.replace("</span>", ""),
|
.replace("</span>", ""),
|
||||||
probability: Number(row["3"].split("%")[0]) / 100,
|
probability: Number(row["3"].split("%")[0]) / 100,
|
||||||
type: "PROBABILITY",
|
type: "PROBABILITY",
|
||||||
}));
|
}));
|
||||||
let analysis = table.filter((row) =>
|
let analysis = table.filter((row: any) =>
|
||||||
row[0] ? row[0].toLowerCase().includes("commentary") : false
|
row[0] ? row[0].toLowerCase().includes("commentary") : false
|
||||||
);
|
);
|
||||||
// "Examples of Superforecaster Commentary" / Analysis
|
// "Examples of Superforecaster Commentary" / Analysis
|
||||||
|
@ -104,12 +100,9 @@ export const goodjudgment: Platform = {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
url: endpoint,
|
url: endpoint,
|
||||||
platform: platformName,
|
|
||||||
description,
|
description,
|
||||||
options,
|
options,
|
||||||
qualityindicators: {
|
qualityindicators: {},
|
||||||
stars: calculateStars(platformName, {}),
|
|
||||||
},
|
|
||||||
extra: {
|
extra: {
|
||||||
superforecastercommentary: analysis || "",
|
superforecastercommentary: analysis || "",
|
||||||
},
|
},
|
||||||
|
@ -124,4 +117,12 @@ export const goodjudgment: Platform = {
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
let nuno = () => 4;
|
||||||
|
let eli = () => 4;
|
||||||
|
let misha = () => 3.5;
|
||||||
|
let starsDecimal = average([nuno()]); //, eli(), misha()])
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,16 +2,17 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { Tabletojson } from "tabletojson";
|
import { Tabletojson } from "tabletojson";
|
||||||
|
|
||||||
|
import { average } from "../../utils";
|
||||||
import { applyIfSecretExists } from "../utils/getSecrets";
|
import { applyIfSecretExists } from "../utils/getSecrets";
|
||||||
import { calculateStars } from "../utils/stars";
|
import { sleep } from "../utils/sleep";
|
||||||
import toMarkdown from "../utils/toMarkdown";
|
import toMarkdown from "../utils/toMarkdown";
|
||||||
import { Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
const platformName = "goodjudgmentopen";
|
const platformName = "goodjudgmentopen";
|
||||||
|
|
||||||
let htmlEndPoint = "https://www.gjopen.com/questions?page=";
|
const htmlEndPoint = "https://www.gjopen.com/questions?page=";
|
||||||
let annoyingPromptUrls = [
|
const annoyingPromptUrls = [
|
||||||
"https://www.gjopen.com/questions/1933-what-forecasting-questions-should-we-ask-what-questions-would-you-like-to-forecast-on-gjopen",
|
"https://www.gjopen.com/questions/1933-what-forecasting-questions-should-we-ask-what-questions-would-you-like-to-forecast-on-gjopen",
|
||||||
"https://www.gjopen.com/questions/1779-are-there-any-forecasting-tips-tricks-and-experiences-you-would-like-to-share-and-or-discuss-with-your-fellow-forecasters",
|
"https://www.gjopen.com/questions/1779-are-there-any-forecasting-tips-tricks-and-experiences-you-would-like-to-share-and-or-discuss-with-your-fellow-forecasters",
|
||||||
"https://www.gjopen.com/questions/2246-are-there-any-forecasting-tips-tricks-and-experiences-you-would-like-to-share-and-or-discuss-with-your-fellow-forecasters-2022-thread",
|
"https://www.gjopen.com/questions/2246-are-there-any-forecasting-tips-tricks-and-experiences-you-would-like-to-share-and-or-discuss-with-your-fellow-forecasters-2022-thread",
|
||||||
|
@ -22,12 +23,11 @@ const id = () => 0;
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
|
|
||||||
async function fetchPage(page, cookie) {
|
async function fetchPage(page: number, cookie: string) {
|
||||||
let response = await axios({
|
const response: string = await axios({
|
||||||
url: htmlEndPoint + page,
|
url: htmlEndPoint + page,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "text/html",
|
|
||||||
Cookie: cookie,
|
Cookie: cookie,
|
||||||
},
|
},
|
||||||
}).then((res) => res.data);
|
}).then((res) => res.data);
|
||||||
|
@ -35,12 +35,11 @@ async function fetchPage(page, cookie) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchStats(questionUrl, cookie) {
|
async function fetchStats(questionUrl: string, cookie: string) {
|
||||||
let response = await axios({
|
let response: string = await axios({
|
||||||
url: questionUrl + "/stats",
|
url: questionUrl + "/stats",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "text/html",
|
|
||||||
Cookie: cookie,
|
Cookie: cookie,
|
||||||
Referer: questionUrl,
|
Referer: questionUrl,
|
||||||
},
|
},
|
||||||
|
@ -50,7 +49,7 @@ async function fetchStats(questionUrl, cookie) {
|
||||||
// Is binary?
|
// Is binary?
|
||||||
let isbinary = response.includes("binary?":true");
|
let isbinary = response.includes("binary?":true");
|
||||||
|
|
||||||
let options = [];
|
let options: FetchedQuestion["options"] = [];
|
||||||
if (isbinary) {
|
if (isbinary) {
|
||||||
// Crowd percentage
|
// Crowd percentage
|
||||||
let htmlElements = response.split("\n");
|
let htmlElements = response.split("\n");
|
||||||
|
@ -74,7 +73,7 @@ async function fetchStats(questionUrl, cookie) {
|
||||||
let optionsHtmlElement = "<table" + response.split("tbody")[1] + "table>";
|
let optionsHtmlElement = "<table" + response.split("tbody")[1] + "table>";
|
||||||
let tablesAsJson = Tabletojson.convert(optionsHtmlElement);
|
let tablesAsJson = Tabletojson.convert(optionsHtmlElement);
|
||||||
let firstTable = tablesAsJson[0];
|
let firstTable = tablesAsJson[0];
|
||||||
options = firstTable.map((element) => ({
|
options = firstTable.map((element: any) => ({
|
||||||
name: element["0"],
|
name: element["0"],
|
||||||
probability: Number(element["1"].replace("%", "")) / 100,
|
probability: Number(element["1"].replace("%", "")) / 100,
|
||||||
type: "PROBABILITY",
|
type: "PROBABILITY",
|
||||||
|
@ -107,21 +106,12 @@ async function fetchStats(questionUrl, cookie) {
|
||||||
.split(",")[0];
|
.split(",")[0];
|
||||||
//console.log(numpredictors)
|
//console.log(numpredictors)
|
||||||
|
|
||||||
// Calculate the stars
|
|
||||||
let minProbability = Math.min(...options.map((option) => option.probability));
|
|
||||||
let maxProbability = Math.max(...options.map((option) => option.probability));
|
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
description: description,
|
description,
|
||||||
options: options,
|
options,
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
numforecasts: Number(numforecasts),
|
numforecasts: Number(numforecasts),
|
||||||
numforecasters: Number(numforecasters),
|
numforecasters: Number(numforecasters),
|
||||||
stars: calculateStars("Good Judgment Open", {
|
|
||||||
numforecasts,
|
|
||||||
minProbability,
|
|
||||||
maxProbability,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
// this mismatches the code below, and needs to be fixed, but I'm doing typescript conversion and don't want to touch any logic for now
|
// this mismatches the code below, and needs to be fixed, but I'm doing typescript conversion and don't want to touch any logic for now
|
||||||
} as any;
|
} as any;
|
||||||
|
@ -129,7 +119,7 @@ async function fetchStats(questionUrl, cookie) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSignedIn(html) {
|
function isSignedIn(html: string) {
|
||||||
let isSignedInBool = !(
|
let isSignedInBool = !(
|
||||||
html.includes("You need to sign in or sign up before continuing") ||
|
html.includes("You need to sign in or sign up before continuing") ||
|
||||||
html.includes("Sign up")
|
html.includes("Sign up")
|
||||||
|
@ -142,7 +132,7 @@ function isSignedIn(html) {
|
||||||
return isSignedInBool;
|
return isSignedInBool;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reachedEnd(html) {
|
function reachedEnd(html: string) {
|
||||||
let reachedEndBool = html.includes("No questions match your filter");
|
let reachedEndBool = html.includes("No questions match your filter");
|
||||||
if (reachedEndBool) {
|
if (reachedEndBool) {
|
||||||
//console.log(html)
|
//console.log(html)
|
||||||
|
@ -151,13 +141,9 @@ function reachedEnd(html) {
|
||||||
return reachedEndBool;
|
return reachedEndBool;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sleep(ms: number) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Body */
|
/* Body */
|
||||||
|
|
||||||
async function goodjudgmentopen_inner(cookie) {
|
async function goodjudgmentopen_inner(cookie: string) {
|
||||||
let i = 1;
|
let i = 1;
|
||||||
let response = await fetchPage(i, cookie);
|
let response = await fetchPage(i, cookie);
|
||||||
|
|
||||||
|
@ -185,7 +171,11 @@ async function goodjudgmentopen_inner(cookie) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let questionNumRegex = new RegExp("questions/([0-9]+)");
|
let questionNumRegex = new RegExp("questions/([0-9]+)");
|
||||||
let questionNum = url.match(questionNumRegex)[1]; //.split("questions/")[1].split("-")[0];
|
const questionNumMatch = url.match(questionNumRegex);
|
||||||
|
if (!questionNumMatch) {
|
||||||
|
throw new Error(`Couldn't find question num in ${url}`);
|
||||||
|
}
|
||||||
|
let questionNum = questionNumMatch[1];
|
||||||
let id = `${platformName}-${questionNum}`;
|
let id = `${platformName}-${questionNum}`;
|
||||||
let question = {
|
let question = {
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -241,8 +231,26 @@ export const goodjudgmentopen: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "Good Judgment Open",
|
label: "Good Judgment Open",
|
||||||
color: "#002455",
|
color: "#002455",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
let cookie = process.env.GOODJUDGMENTOPENCOOKIE;
|
let cookie = process.env.GOODJUDGMENTOPENCOOKIE;
|
||||||
return await applyIfSecretExists(cookie, goodjudgmentopen_inner);
|
return (await applyIfSecretExists(cookie, goodjudgmentopen_inner)) || null;
|
||||||
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
let minProbability = Math.min(
|
||||||
|
...data.options.map((option) => option.probability || 0)
|
||||||
|
);
|
||||||
|
let maxProbability = Math.max(
|
||||||
|
...data.options.map((option) => option.probability || 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
let nuno = () => ((data.qualityindicators.numforecasts || 0) > 100 ? 3 : 2);
|
||||||
|
let eli = () => 3;
|
||||||
|
let misha = () =>
|
||||||
|
minProbability > 0.1 || maxProbability < 0.9 ? 3.1 : 2.5;
|
||||||
|
|
||||||
|
let starsDecimal = average([nuno(), eli(), misha()]);
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { parseISO } from "date-fns";
|
|
||||||
|
|
||||||
/* Imports */
|
|
||||||
import { Question } from "@prisma/client";
|
import { Question } from "@prisma/client";
|
||||||
|
|
||||||
import { AlgoliaQuestion } from "../../backend/utils/algolia";
|
|
||||||
import { prisma } from "../database/prisma";
|
import { prisma } from "../database/prisma";
|
||||||
import { Platform } from "./";
|
import { AlgoliaQuestion } from "../utils/algolia";
|
||||||
|
import { FetchedQuestion, Platform, prepareQuestion } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
const searchEndpoint =
|
const searchEndpoint =
|
||||||
|
@ -14,25 +12,20 @@ const searchEndpoint =
|
||||||
|
|
||||||
const apiEndpoint = "https://guesstimate.herokuapp.com";
|
const apiEndpoint = "https://guesstimate.herokuapp.com";
|
||||||
|
|
||||||
/* Body */
|
const modelToQuestion = (model: any): ReturnType<typeof prepareQuestion> => {
|
||||||
|
|
||||||
const modelToQuestion = (model: any): Question => {
|
|
||||||
const { description } = model;
|
const { description } = model;
|
||||||
// const description = model.description
|
// const description = model.description
|
||||||
// ? model.description.replace(/\n/g, " ").replace(/ /g, " ")
|
// ? model.description.replace(/\n/g, " ").replace(/ /g, " ")
|
||||||
// : "";
|
// : "";
|
||||||
const stars = description.length > 250 ? 2 : 1;
|
// const timestamp = parseISO(model.created_at);
|
||||||
const timestamp = parseISO(model.created_at);
|
const fq: FetchedQuestion = {
|
||||||
const q: Question = {
|
|
||||||
id: `guesstimate-${model.id}`,
|
id: `guesstimate-${model.id}`,
|
||||||
title: model.name,
|
title: model.name,
|
||||||
url: `https://www.getguesstimate.com/models/${model.id}`,
|
url: `https://www.getguesstimate.com/models/${model.id}`,
|
||||||
timestamp,
|
// timestamp,
|
||||||
platform: "guesstimate",
|
|
||||||
description,
|
description,
|
||||||
options: [],
|
options: [],
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
stars,
|
|
||||||
numforecasts: 1,
|
numforecasts: 1,
|
||||||
numforecasters: 1,
|
numforecasters: 1,
|
||||||
},
|
},
|
||||||
|
@ -41,6 +34,7 @@ const modelToQuestion = (model: any): Question => {
|
||||||
},
|
},
|
||||||
// ranking: 10 * (index + 1) - 0.5, //(model._rankingInfo - 1*index)// hack
|
// ranking: 10 * (index + 1) - 0.5, //(model._rankingInfo - 1*index)// hack
|
||||||
};
|
};
|
||||||
|
const q = prepareQuestion(fq, guesstimate);
|
||||||
return q;
|
return q;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -68,7 +62,7 @@ async function search(query: string): Promise<AlgoliaQuestion[]> {
|
||||||
});
|
});
|
||||||
|
|
||||||
// filter for duplicates. Surprisingly common.
|
// filter for duplicates. Surprisingly common.
|
||||||
let uniqueTitles = [];
|
let uniqueTitles: string[] = [];
|
||||||
let uniqueModels: AlgoliaQuestion[] = [];
|
let uniqueModels: AlgoliaQuestion[] = [];
|
||||||
for (let model of mappedModels) {
|
for (let model of mappedModels) {
|
||||||
if (!uniqueTitles.includes(model.title) && !model.title.includes("copy")) {
|
if (!uniqueTitles.includes(model.title) && !model.title.includes("copy")) {
|
||||||
|
@ -83,12 +77,11 @@ async function search(query: string): Promise<AlgoliaQuestion[]> {
|
||||||
const fetchQuestion = async (id: number): Promise<Question> => {
|
const fetchQuestion = async (id: number): Promise<Question> => {
|
||||||
const response = await axios({ url: `${apiEndpoint}/spaces/${id}` });
|
const response = await axios({ url: `${apiEndpoint}/spaces/${id}` });
|
||||||
let q = modelToQuestion(response.data);
|
let q = modelToQuestion(response.data);
|
||||||
q = await prisma.question.upsert({
|
return await prisma.question.upsert({
|
||||||
where: { id: q.id },
|
where: { id: q.id },
|
||||||
create: q,
|
create: q,
|
||||||
update: q,
|
update: q,
|
||||||
});
|
});
|
||||||
return q;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const guesstimate: Platform & {
|
export const guesstimate: Platform & {
|
||||||
|
@ -99,5 +92,7 @@ export const guesstimate: Platform & {
|
||||||
label: "Guesstimate",
|
label: "Guesstimate",
|
||||||
color: "#223900",
|
color: "#223900",
|
||||||
search,
|
search,
|
||||||
|
version: "v1",
|
||||||
fetchQuestion,
|
fetchQuestion,
|
||||||
|
calculateStars: (q) => (q.description.length > 250 ? 2 : 1),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Question } from "@prisma/client";
|
import { Question } from "@prisma/client";
|
||||||
|
|
||||||
|
import { QuestionOption } from "../../common/types";
|
||||||
import { prisma } from "../database/prisma";
|
import { prisma } from "../database/prisma";
|
||||||
import { betfair } from "./betfair";
|
import { betfair } from "./betfair";
|
||||||
import { fantasyscotus } from "./fantasyscotus";
|
import { fantasyscotus } from "./fantasyscotus";
|
||||||
|
@ -41,32 +42,51 @@ export interface QualityIndicators {
|
||||||
|
|
||||||
export type FetchedQuestion = Omit<
|
export type FetchedQuestion = Omit<
|
||||||
Question,
|
Question,
|
||||||
"extra" | "qualityindicators" | "timestamp"
|
"extra" | "qualityindicators" | "timestamp" | "platform" | "options"
|
||||||
> & {
|
> & {
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
extra?: object; // required in DB but annoying to return empty; also this is slightly stricter than Prisma's JsonValue
|
extra?: object; // required in DB but annoying to return empty; also this is slightly stricter than Prisma's JsonValue
|
||||||
qualityindicators: QualityIndicators; // slightly stronger type than Prisma's JsonValue
|
options: QuestionOption[]; // stronger type than Prisma's JsonValue
|
||||||
|
qualityindicators: Omit<QualityIndicators, "stars">; // slightly stronger type than Prisma's JsonValue
|
||||||
};
|
};
|
||||||
|
|
||||||
// fetcher should return null if platform failed to fetch questions for some reason
|
// fetcher should return null if platform failed to fetch questions for some reason
|
||||||
export type PlatformFetcher = () => Promise<FetchedQuestion[] | null>;
|
type PlatformFetcherV1 = () => Promise<FetchedQuestion[] | null>;
|
||||||
|
|
||||||
export interface Platform {
|
type PlatformFetcherV2Result = {
|
||||||
|
questions: FetchedQuestion[];
|
||||||
|
// if partial is true then we won't cleanup old questions from the database; this is useful when manually invoking a fetcher with arguments for updating a single question
|
||||||
|
partial: boolean;
|
||||||
|
} | null;
|
||||||
|
|
||||||
|
type PlatformFetcherV2<ArgNames extends string> = (opts: {
|
||||||
|
args?: { [k in ArgNames]: string };
|
||||||
|
}) => Promise<PlatformFetcherV2Result>;
|
||||||
|
|
||||||
|
export type PlatformFetcher<ArgNames extends string> =
|
||||||
|
| PlatformFetcherV1
|
||||||
|
| PlatformFetcherV2<ArgNames>;
|
||||||
|
|
||||||
|
// using "" as ArgNames default is technically incorrect, but shouldn't cause any real issues
|
||||||
|
// (I couldn't find a better solution for signifying an empty value, though there probably is one)
|
||||||
|
export type Platform<ArgNames extends string = ""> = {
|
||||||
name: string; // short name for ids and `platform` db column, e.g. "xrisk"
|
name: string; // short name for ids and `platform` db column, e.g. "xrisk"
|
||||||
label: string; // longer name for displaying on frontend etc., e.g. "X-risk estimates"
|
label: string; // longer name for displaying on frontend etc., e.g. "X-risk estimates"
|
||||||
color: string; // used on frontend
|
color: string; // used on frontend
|
||||||
fetcher?: PlatformFetcher;
|
calculateStars: (question: FetchedQuestion) => number;
|
||||||
}
|
} & (
|
||||||
|
| {
|
||||||
|
version: "v1";
|
||||||
|
fetcher?: PlatformFetcherV1;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
version: "v2";
|
||||||
|
fetcherArgs?: ArgNames[];
|
||||||
|
fetcher?: PlatformFetcherV2<ArgNames>;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// draft for the future callback-based streaming/chunking API:
|
export const platforms: Platform<string>[] = [
|
||||||
// interface FetchOptions {
|
|
||||||
// since?: string; // some kind of cursor, Date object or opaque string?
|
|
||||||
// save: (questions: Question[]) => Promise<void>;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export type PlatformFetcher = (options: FetchOptions) => Promise<void>;
|
|
||||||
|
|
||||||
export const platforms: Platform[] = [
|
|
||||||
betfair,
|
betfair,
|
||||||
fantasyscotus,
|
fantasyscotus,
|
||||||
foretold,
|
foretold,
|
||||||
|
@ -86,26 +106,59 @@ export const platforms: Platform[] = [
|
||||||
xrisk,
|
xrisk,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const processPlatform = async (platform: Platform) => {
|
// Typing notes:
|
||||||
|
// There's a difference between prisma's Question type (type returned from `find` and `findMany`) and its input types due to JsonValue vs InputJsonValue mismatch.
|
||||||
|
// On the other hand, we can't use Prisma.QuestionUpdateInput or Prisma.QuestionCreateManyInput either, because we use this question in guesstimate's code for preparing questions from guesstimate models...
|
||||||
|
// So here we build a new type which should be ok to use both in place of prisma's Question type and as an input to its update or create methods.
|
||||||
|
type PreparedQuestion = Omit<
|
||||||
|
Question,
|
||||||
|
"extra" | "qualityindicators" | "options"
|
||||||
|
> & {
|
||||||
|
extra: NonNullable<Question["extra"]>;
|
||||||
|
qualityindicators: NonNullable<Question["qualityindicators"]>;
|
||||||
|
options: NonNullable<Question["options"]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const prepareQuestion = (
|
||||||
|
q: FetchedQuestion,
|
||||||
|
platform: Platform<any>
|
||||||
|
): PreparedQuestion => {
|
||||||
|
return {
|
||||||
|
extra: {},
|
||||||
|
timestamp: new Date(),
|
||||||
|
...q,
|
||||||
|
platform: platform.name,
|
||||||
|
qualityindicators: {
|
||||||
|
...q.qualityindicators,
|
||||||
|
stars: platform.calculateStars(q),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const processPlatform = async <T extends string = "">(
|
||||||
|
platform: Platform<T>,
|
||||||
|
args?: { [k in T]: string }
|
||||||
|
) => {
|
||||||
if (!platform.fetcher) {
|
if (!platform.fetcher) {
|
||||||
console.log(`Platform ${platform.name} doesn't have a fetcher, skipping`);
|
console.log(`Platform ${platform.name} doesn't have a fetcher, skipping`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fetchedQuestions = await platform.fetcher();
|
const result =
|
||||||
if (!fetchedQuestions || !fetchedQuestions.length) {
|
platform.version === "v1"
|
||||||
|
? { questions: await platform.fetcher(), partial: false } // this is not exactly PlatformFetcherV2Result, since `questions` can be null
|
||||||
|
: await platform.fetcher({ args });
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
console.log(`Platform ${platform.name} didn't return any results`);
|
console.log(`Platform ${platform.name} didn't return any results`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prepareQuestion = (q: FetchedQuestion): Question => {
|
const { questions: fetchedQuestions, partial } = result;
|
||||||
return {
|
|
||||||
extra: {},
|
if (!fetchedQuestions || !fetchedQuestions.length) {
|
||||||
timestamp: new Date(),
|
console.log(`Platform ${platform.name} didn't return any results`);
|
||||||
...q,
|
return;
|
||||||
platform: platform.name,
|
}
|
||||||
qualityindicators: q.qualityindicators as object, // fighting typescript
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const oldQuestions = await prisma.question.findMany({
|
const oldQuestions = await prisma.question.findMany({
|
||||||
where: {
|
where: {
|
||||||
|
@ -119,11 +172,11 @@ export const processPlatform = async (platform: Platform) => {
|
||||||
const fetchedIdsSet = new Set(fetchedIds);
|
const fetchedIdsSet = new Set(fetchedIds);
|
||||||
const oldIdsSet = new Set(oldIds);
|
const oldIdsSet = new Set(oldIds);
|
||||||
|
|
||||||
const createdQuestions: Question[] = [];
|
const createdQuestions: PreparedQuestion[] = [];
|
||||||
const updatedQuestions: Question[] = [];
|
const updatedQuestions: PreparedQuestion[] = [];
|
||||||
const deletedIds = oldIds.filter((id) => !fetchedIdsSet.has(id));
|
const deletedIds = oldIds.filter((id) => !fetchedIdsSet.has(id));
|
||||||
|
|
||||||
for (const q of fetchedQuestions.map((q) => prepareQuestion(q))) {
|
for (const q of fetchedQuestions.map((q) => prepareQuestion(q, platform))) {
|
||||||
if (oldIdsSet.has(q.id)) {
|
if (oldIdsSet.has(q.id)) {
|
||||||
updatedQuestions.push(q);
|
updatedQuestions.push(q);
|
||||||
} else {
|
} else {
|
||||||
|
@ -132,27 +185,45 @@ export const processPlatform = async (platform: Platform) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const stats: { created?: number; updated?: number; deleted?: number } = {};
|
||||||
|
|
||||||
await prisma.question.createMany({
|
await prisma.question.createMany({
|
||||||
data: createdQuestions,
|
data: createdQuestions,
|
||||||
});
|
});
|
||||||
|
stats.created = createdQuestions.length;
|
||||||
|
|
||||||
for (const q of updatedQuestions) {
|
for (const q of updatedQuestions) {
|
||||||
await prisma.question.update({
|
await prisma.question.update({
|
||||||
where: { id: q.id },
|
where: { id: q.id },
|
||||||
data: q,
|
data: q,
|
||||||
});
|
});
|
||||||
|
stats.updated ??= 0;
|
||||||
|
stats.updated++;
|
||||||
}
|
}
|
||||||
|
|
||||||
await prisma.question.deleteMany({
|
if (!partial) {
|
||||||
where: {
|
await prisma.question.deleteMany({
|
||||||
id: {
|
where: {
|
||||||
in: deletedIds,
|
id: {
|
||||||
|
in: deletedIds,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
|
stats.deleted = deletedIds.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.history.createMany({
|
||||||
|
data: [...createdQuestions, ...updatedQuestions].map((q) => ({
|
||||||
|
...q,
|
||||||
|
idref: q.id,
|
||||||
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`Done, ${deletedIds.length} deleted, ${updatedQuestions.length} updated, ${createdQuestions.length} created`
|
"Done, " +
|
||||||
|
Object.entries(stats)
|
||||||
|
.map(([k, v]) => `${v} ${k}`)
|
||||||
|
.join(", ")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,38 +1,37 @@
|
||||||
/* Imports */
|
/* Imports */
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
|
import { FullQuestionOption } from "../../common/types";
|
||||||
|
import { average } from "../../utils";
|
||||||
import { applyIfSecretExists } from "../utils/getSecrets";
|
import { applyIfSecretExists } from "../utils/getSecrets";
|
||||||
import { measureTime } from "../utils/measureTime";
|
import { measureTime } from "../utils/measureTime";
|
||||||
import { calculateStars } from "../utils/stars";
|
import { sleep } from "../utils/sleep";
|
||||||
import toMarkdown from "../utils/toMarkdown";
|
import toMarkdown from "../utils/toMarkdown";
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
const platformName = "infer";
|
const platformName = "infer";
|
||||||
let htmlEndPoint = "https://www.infer-pub.com/questions";
|
const htmlEndPoint = "https://www.infer-pub.com/questions";
|
||||||
String.prototype.replaceAll = function replaceAll(search, replace) {
|
|
||||||
return this.split(search).join(replace);
|
|
||||||
};
|
|
||||||
const DEBUG_MODE: "on" | "off" = "off"; // "off"
|
const DEBUG_MODE: "on" | "off" = "off"; // "off"
|
||||||
const SLEEP_TIME_RANDOM = 7000; // miliseconds
|
const SLEEP_TIME_RANDOM = 7000; // miliseconds
|
||||||
const SLEEP_TIME_EXTRA = 2000;
|
const SLEEP_TIME_EXTRA = 2000;
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
|
|
||||||
function cleanDescription(text) {
|
function cleanDescription(text: string) {
|
||||||
let md = toMarkdown(text);
|
let md = toMarkdown(text);
|
||||||
let result = md.replaceAll("---", "-").replaceAll(" ", " ");
|
let result = md.replaceAll("---", "-").replaceAll(" ", " ");
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchPage(page, cookie) {
|
async function fetchPage(page: number, cookie: string) {
|
||||||
console.log(`Page #${page}`);
|
console.log(`Page #${page}`);
|
||||||
if (page == 1) {
|
if (page == 1) {
|
||||||
cookie = cookie.split(";")[0]; // Interesting that it otherwise doesn't work :(
|
cookie = cookie.split(";")[0]; // Interesting that it otherwise doesn't work :(
|
||||||
}
|
}
|
||||||
let urlEndpoint = `${htmlEndPoint}/?page=${page}`;
|
let urlEndpoint = `${htmlEndPoint}/?page=${page}`;
|
||||||
console.log(urlEndpoint);
|
console.log(urlEndpoint);
|
||||||
let response = await axios({
|
const response: string = await axios({
|
||||||
url: urlEndpoint,
|
url: urlEndpoint,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -44,8 +43,8 @@ async function fetchPage(page, cookie) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchStats(questionUrl, cookie) {
|
async function fetchStats(questionUrl: string, cookie: string) {
|
||||||
let response = await axios({
|
let response: string = await axios({
|
||||||
url: questionUrl + "/stats",
|
url: questionUrl + "/stats",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -59,7 +58,7 @@ async function fetchStats(questionUrl, cookie) {
|
||||||
throw Error("Not logged in");
|
throw Error("Not logged in");
|
||||||
}
|
}
|
||||||
// Init
|
// Init
|
||||||
let options = [];
|
let options: FullQuestionOption[] = [];
|
||||||
|
|
||||||
// Parse the embedded json
|
// Parse the embedded json
|
||||||
let htmlElements = response.split("\n");
|
let htmlElements = response.split("\n");
|
||||||
|
@ -84,7 +83,7 @@ async function fetchStats(questionUrl, cookie) {
|
||||||
questionType.includes("Forecast::Question") ||
|
questionType.includes("Forecast::Question") ||
|
||||||
!questionType.includes("Forecast::MultiTimePeriodQuestion")
|
!questionType.includes("Forecast::MultiTimePeriodQuestion")
|
||||||
) {
|
) {
|
||||||
options = firstEmbeddedJson.question.answers.map((answer) => ({
|
options = firstEmbeddedJson.question.answers.map((answer: any) => ({
|
||||||
name: answer.name,
|
name: answer.name,
|
||||||
probability: answer.normalized_probability,
|
probability: answer.normalized_probability,
|
||||||
type: "PROBABILITY",
|
type: "PROBABILITY",
|
||||||
|
@ -94,12 +93,11 @@ async function fetchStats(questionUrl, cookie) {
|
||||||
options[0].probability > 1
|
options[0].probability > 1
|
||||||
? 1 - options[0].probability / 100
|
? 1 - options[0].probability / 100
|
||||||
: 1 - options[0].probability;
|
: 1 - options[0].probability;
|
||||||
let optionNo = {
|
options.push({
|
||||||
name: "No",
|
name: "No",
|
||||||
probability: probabilityNo,
|
probability: probabilityNo,
|
||||||
type: "PROBABILITY",
|
type: "PROBABILITY",
|
||||||
};
|
});
|
||||||
options.push(optionNo);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let result = {
|
let result = {
|
||||||
|
@ -109,14 +107,13 @@ async function fetchStats(questionUrl, cookie) {
|
||||||
numforecasts: Number(numforecasts),
|
numforecasts: Number(numforecasts),
|
||||||
numforecasters: Number(numforecasters),
|
numforecasters: Number(numforecasters),
|
||||||
comments_count: Number(comments_count),
|
comments_count: Number(comments_count),
|
||||||
stars: calculateStars(platformName, { numforecasts }),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
// console.log(JSON.stringify(result, null, 4));
|
// console.log(JSON.stringify(result, null, 4));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSignedIn(html) {
|
function isSignedIn(html: string) {
|
||||||
let isSignedInBool = !(
|
let isSignedInBool = !(
|
||||||
html.includes("You need to sign in or sign up before continuing") ||
|
html.includes("You need to sign in or sign up before continuing") ||
|
||||||
html.includes("Sign up")
|
html.includes("Sign up")
|
||||||
|
@ -128,7 +125,7 @@ function isSignedIn(html) {
|
||||||
return isSignedInBool;
|
return isSignedInBool;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reachedEnd(html) {
|
function reachedEnd(html: string) {
|
||||||
let reachedEndBool = html.includes("No questions match your filter");
|
let reachedEndBool = html.includes("No questions match your filter");
|
||||||
if (reachedEndBool) {
|
if (reachedEndBool) {
|
||||||
//console.log(html)
|
//console.log(html)
|
||||||
|
@ -137,10 +134,6 @@ function reachedEnd(html) {
|
||||||
return reachedEndBool;
|
return reachedEndBool;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sleep(ms) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Body */
|
/* Body */
|
||||||
|
|
||||||
async function infer_inner(cookie: string) {
|
async function infer_inner(cookie: string) {
|
||||||
|
@ -173,17 +166,18 @@ async function infer_inner(cookie: string) {
|
||||||
await sleep(Math.random() * SLEEP_TIME_RANDOM + SLEEP_TIME_EXTRA); // don't be as noticeable
|
await sleep(Math.random() * SLEEP_TIME_RANDOM + SLEEP_TIME_EXTRA); // don't be as noticeable
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let moreinfo = await fetchStats(url, cookie);
|
const moreinfo = await fetchStats(url, cookie);
|
||||||
let questionNumRegex = new RegExp("questions/([0-9]+)");
|
const questionNumRegex = new RegExp("questions/([0-9]+)");
|
||||||
let questionNum = url.match(questionNumRegex)[1]; //.split("questions/")[1].split("-")[0];
|
const questionNumMatch = url.match(questionNumRegex);
|
||||||
let id = `${platformName}-${questionNum}`;
|
if (!questionNumMatch) {
|
||||||
|
throw new Error(`Couldn't find question num in ${url}`);
|
||||||
|
}
|
||||||
|
let questionNum = questionNumMatch[1];
|
||||||
|
const id = `${platformName}-${questionNum}`;
|
||||||
let question: FetchedQuestion = {
|
let question: FetchedQuestion = {
|
||||||
id: id,
|
id,
|
||||||
title: title,
|
title,
|
||||||
description: moreinfo.description,
|
url,
|
||||||
url: url,
|
|
||||||
platform: platformName,
|
|
||||||
options: moreinfo.options,
|
|
||||||
...moreinfo,
|
...moreinfo,
|
||||||
};
|
};
|
||||||
console.log(JSON.stringify(question, null, 4));
|
console.log(JSON.stringify(question, null, 4));
|
||||||
|
@ -236,8 +230,17 @@ export const infer: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "Infer",
|
label: "Infer",
|
||||||
color: "#223900",
|
color: "#223900",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
let cookie = process.env.INFER_COOKIE;
|
let cookie = process.env.INFER_COOKIE;
|
||||||
return await applyIfSecretExists(cookie, infer_inner);
|
return (await applyIfSecretExists(cookie, infer_inner)) || null;
|
||||||
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
let nuno = () => 2;
|
||||||
|
let eli = () => null;
|
||||||
|
let misha = () => null;
|
||||||
|
let starsDecimal = average([nuno()]); //, eli(), misha()])
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,29 +1,28 @@
|
||||||
/* Imports */
|
/* Imports */
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
import { average } from "../../utils";
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
const platformName = "kalshi";
|
const platformName = "kalshi";
|
||||||
let jsonEndpoint = "https://trading-api.kalshi.com/v1/cached/markets/"; //"https://subgraph-matic.poly.market/subgraphs/name/TokenUnion/polymarket"//"https://subgraph-backup.poly.market/subgraphs/name/TokenUnion/polymarket"//'https://subgraph-matic.poly.market/subgraphs/name/TokenUnion/polymarket3'
|
let jsonEndpoint = "https://trading-api.kalshi.com/v1/cached/markets/";
|
||||||
|
|
||||||
async function fetchAllMarkets() {
|
async function fetchAllMarkets() {
|
||||||
// for info which the polymarket graphql API
|
|
||||||
let response = await axios
|
let response = await axios
|
||||||
.get(jsonEndpoint)
|
.get(jsonEndpoint)
|
||||||
.then((response) => response.data.markets);
|
.then((response) => response.data.markets);
|
||||||
// console.log(response)
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processMarkets(markets) {
|
async function processMarkets(markets: any[]) {
|
||||||
let dateNow = new Date().toISOString();
|
let dateNow = new Date().toISOString();
|
||||||
// console.log(markets)
|
// console.log(markets)
|
||||||
markets = markets.filter((market) => market.close_date > dateNow);
|
markets = markets.filter((market) => market.close_date > dateNow);
|
||||||
let results = await markets.map((market) => {
|
let results = await markets.map((market) => {
|
||||||
const probability = market.last_price / 100;
|
const probability = market.last_price / 100;
|
||||||
const options = [
|
const options: FetchedQuestion["options"] = [
|
||||||
{
|
{
|
||||||
name: "Yes",
|
name: "Yes",
|
||||||
probability: probability,
|
probability: probability,
|
||||||
|
@ -40,41 +39,63 @@ async function processMarkets(markets) {
|
||||||
id,
|
id,
|
||||||
title: market.title.replaceAll("*", ""),
|
title: market.title.replaceAll("*", ""),
|
||||||
url: `https://kalshi.com/markets/${market.ticker_name}`,
|
url: `https://kalshi.com/markets/${market.ticker_name}`,
|
||||||
platform: platformName,
|
|
||||||
description: `${market.settle_details}. The resolution source is: ${market.ranged_group_name} (${market.settle_source_url})`,
|
description: `${market.settle_details}. The resolution source is: ${market.ranged_group_name} (${market.settle_source_url})`,
|
||||||
options,
|
options,
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
stars: calculateStars(platformName, {
|
|
||||||
shares_volume: market.volume,
|
|
||||||
interest: market.open_interest,
|
|
||||||
}),
|
|
||||||
yes_bid: market.yes_bid,
|
yes_bid: market.yes_bid,
|
||||||
yes_ask: market.yes_ask,
|
yes_ask: market.yes_ask,
|
||||||
spread: Math.abs(market.yes_bid - market.yes_ask),
|
spread: Math.abs(market.yes_bid - market.yes_ask),
|
||||||
shares_volume: market.volume, // Assuming that half of all buys are for yes and half for no, which is a big if.
|
shares_volume: market.volume, // Assuming that half of all buys are for yes and half for no, which is a big if.
|
||||||
// "open_interest": market.open_interest, also in shares
|
// "open_interest": market.open_interest, also in shares
|
||||||
},
|
},
|
||||||
|
extra: {
|
||||||
|
open_interest: market.open_interest,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
//console.log(results.length)
|
|
||||||
// console.log(results.map(result => result.title))
|
|
||||||
// console.log(results.map(result => result.title).length)
|
|
||||||
console.log([...new Set(results.map((result) => result.title))]);
|
console.log([...new Set(results.map((result) => result.title))]);
|
||||||
console.log(
|
console.log(
|
||||||
"Number of unique questions: ",
|
"Number of unique questions: ",
|
||||||
[...new Set(results.map((result) => result.title))].length
|
[...new Set(results.map((result) => result.title))].length
|
||||||
);
|
);
|
||||||
// console.log([...new Set(results.map(result => result.title))].length)
|
|
||||||
return results; //resultsProcessed
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const kalshi: Platform = {
|
export const kalshi: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "Kalshi",
|
label: "Kalshi",
|
||||||
color: "#615691",
|
color: "#615691",
|
||||||
|
version: "v1",
|
||||||
fetcher: async function () {
|
fetcher: async function () {
|
||||||
let markets = await fetchAllMarkets();
|
let markets = await fetchAllMarkets();
|
||||||
return await processMarkets(markets);
|
return await processMarkets(markets);
|
||||||
},
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
let nuno = () =>
|
||||||
|
((data.extra as any)?.open_interest || 0) > 500 &&
|
||||||
|
data.qualityindicators.shares_volume > 10000
|
||||||
|
? 4
|
||||||
|
: data.qualityindicators.shares_volume > 2000
|
||||||
|
? 3
|
||||||
|
: 2;
|
||||||
|
// let eli = (data) => data.interest > 10000 ? 5 : 4
|
||||||
|
// let misha = (data) => 4
|
||||||
|
let starsDecimal = average([nuno()]); //, eli(data), misha(data)])
|
||||||
|
|
||||||
|
// Substract 1 star if probability is above 90% or below 10%
|
||||||
|
if (
|
||||||
|
data.options instanceof Array &&
|
||||||
|
data.options[0] &&
|
||||||
|
((data.options[0].probability || 0) < 0.1 ||
|
||||||
|
(data.options[0].probability || 0) > 0.9)
|
||||||
|
) {
|
||||||
|
starsDecimal = starsDecimal - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/* Imports */
|
/* Imports */
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
import { average } from "../../utils";
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
const platformName = "manifold";
|
const platformName = "manifold";
|
||||||
let endpoint = "https://manifold.markets/api/v0/markets";
|
const endpoint = "https://manifold.markets/api/v0/markets";
|
||||||
// See https://manifoldmarkets.notion.site/Manifold-Markets-API-5e7d0aef4dcf452bb04b319e178fabc5
|
// See https://manifoldmarkets.notion.site/Manifold-Markets-API-5e7d0aef4dcf452bb04b319e178fabc5
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
|
@ -25,16 +25,16 @@ async function fetchData() {
|
||||||
|
|
||||||
function showStatistics(results: FetchedQuestion[]) {
|
function showStatistics(results: FetchedQuestion[]) {
|
||||||
console.log(`Num unresolved markets: ${results.length}`);
|
console.log(`Num unresolved markets: ${results.length}`);
|
||||||
let sum = (arr) => arr.reduce((tally, a) => tally + a, 0);
|
let sum = (arr: number[]) => arr.reduce((tally, a) => tally + a, 0);
|
||||||
let num2StarsOrMore = results.filter(
|
let num2StarsOrMore = results.filter(
|
||||||
(result) => result.qualityindicators.stars >= 2
|
(result) => manifold.calculateStars(result) >= 2
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
`Manifold has ${num2StarsOrMore.length} markets with 2 stars or more`
|
`Manifold has ${num2StarsOrMore.length} markets with 2 stars or more`
|
||||||
);
|
);
|
||||||
console.log(
|
console.log(
|
||||||
`Mean volume: ${
|
`Mean volume: ${
|
||||||
sum(results.map((result) => result.qualityindicators.volume7Days)) /
|
sum(results.map((result) => result.qualityindicators.volume7Days || 0)) /
|
||||||
results.length
|
results.length
|
||||||
}; mean pool: ${
|
}; mean pool: ${
|
||||||
sum(results.map((result) => result.qualityindicators.pool)) /
|
sum(results.map((result) => result.qualityindicators.pool)) /
|
||||||
|
@ -43,11 +43,11 @@ function showStatistics(results: FetchedQuestion[]) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processPredictions(predictions) {
|
function processPredictions(predictions: any[]): FetchedQuestion[] {
|
||||||
let results: FetchedQuestion[] = await predictions.map((prediction) => {
|
let results: FetchedQuestion[] = predictions.map((prediction) => {
|
||||||
let id = `${platformName}-${prediction.id}`; // oops, doesn't match platform name
|
let id = `${platformName}-${prediction.id}`; // oops, doesn't match platform name
|
||||||
let probability = prediction.probability;
|
let probability = prediction.probability;
|
||||||
let options = [
|
let options: FetchedQuestion["options"] = [
|
||||||
{
|
{
|
||||||
name: "Yes",
|
name: "Yes",
|
||||||
probability: probability,
|
probability: probability,
|
||||||
|
@ -63,15 +63,9 @@ async function processPredictions(predictions) {
|
||||||
id: id,
|
id: id,
|
||||||
title: prediction.question,
|
title: prediction.question,
|
||||||
url: prediction.url,
|
url: prediction.url,
|
||||||
platform: platformName,
|
|
||||||
description: prediction.description,
|
description: prediction.description,
|
||||||
options: options,
|
options,
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
stars: calculateStars(platformName, {
|
|
||||||
volume7Days: prediction.volume7Days,
|
|
||||||
volume24Hours: prediction.volume24Hours,
|
|
||||||
pool: prediction.pool,
|
|
||||||
}),
|
|
||||||
createdTime: prediction.createdTime,
|
createdTime: prediction.createdTime,
|
||||||
volume7Days: prediction.volume7Days,
|
volume7Days: prediction.volume7Days,
|
||||||
volume24Hours: prediction.volume24Hours,
|
volume24Hours: prediction.volume24Hours,
|
||||||
|
@ -94,10 +88,24 @@ export const manifold: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "Manifold Markets",
|
label: "Manifold Markets",
|
||||||
color: "#793466",
|
color: "#793466",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
let data = await fetchData();
|
let data = await fetchData();
|
||||||
let results = await processPredictions(data); // somehow needed
|
let results = processPredictions(data); // somehow needed
|
||||||
showStatistics(results);
|
showStatistics(results);
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
let nuno = () =>
|
||||||
|
(data.qualityindicators.volume7Days || 0) > 250 ||
|
||||||
|
((data.qualityindicators.pool || 0) > 500 &&
|
||||||
|
(data.qualityindicators.volume7Days || 0) > 100)
|
||||||
|
? 2
|
||||||
|
: 1;
|
||||||
|
let eli = () => null;
|
||||||
|
let misha = () => null;
|
||||||
|
let starsDecimal = average([nuno()]); //, eli(data), misha(data)])
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
/* Imports */
|
/* Imports */
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
import { average } from "../../utils";
|
||||||
|
import { sleep } from "../utils/sleep";
|
||||||
import toMarkdown from "../utils/toMarkdown";
|
import toMarkdown from "../utils/toMarkdown";
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
const platformName = "metaculus";
|
const platformName = "metaculus";
|
||||||
let jsonEndPoint = "https://www.metaculus.com/api2/questions/?page=";
|
|
||||||
let now = new Date().toISOString();
|
let now = new Date().toISOString();
|
||||||
let DEBUG_MODE = "off";
|
let DEBUG_MODE = "off";
|
||||||
let SLEEP_TIME = 5000;
|
let SLEEP_TIME = 5000;
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
async function fetchMetaculusQuestions(next) {
|
async function fetchMetaculusQuestions(next: string) {
|
||||||
// Numbers about a given address: how many, how much, at what price, etc.
|
// Numbers about a given address: how many, how much, at what price, etc.
|
||||||
let response;
|
let response;
|
||||||
let data;
|
let data;
|
||||||
|
@ -24,15 +25,17 @@ async function fetchMetaculusQuestions(next) {
|
||||||
});
|
});
|
||||||
data = response.data;
|
data = response.data;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Error in async function fetchMetaculusQuestions(next)`);
|
console.log(`Error in async function fetchMetaculusQuestions(next)`);
|
||||||
if (!!error.response.headers["retry-after"]) {
|
|
||||||
let timeout = error.response.headers["retry-after"];
|
|
||||||
console.log(`Timeout: ${timeout}`);
|
|
||||||
await sleep(Number(timeout) * 1000 + SLEEP_TIME);
|
|
||||||
} else {
|
|
||||||
await sleep(SLEEP_TIME);
|
|
||||||
}
|
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
if (error.response?.headers["retry-after"]) {
|
||||||
|
const timeout = error.response.headers["retry-after"];
|
||||||
|
console.log(`Timeout: ${timeout}`);
|
||||||
|
await sleep(Number(timeout) * 1000 + SLEEP_TIME);
|
||||||
|
} else {
|
||||||
|
await sleep(SLEEP_TIME);
|
||||||
|
}
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
response = await axios({
|
response = await axios({
|
||||||
|
@ -50,11 +53,7 @@ async function fetchMetaculusQuestions(next) {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sleep(ms: number) {
|
async function fetchMetaculusQuestionDescription(slug: string) {
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchMetaculusQuestionDescription(slug) {
|
|
||||||
try {
|
try {
|
||||||
let response = await axios({
|
let response = await axios({
|
||||||
method: "get",
|
method: "get",
|
||||||
|
@ -67,11 +66,12 @@ async function fetchMetaculusQuestionDescription(slug) {
|
||||||
`We encountered some error when attempting to fetch a metaculus page. Trying again`
|
`We encountered some error when attempting to fetch a metaculus page. Trying again`
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
|
axios.isAxiosError(error) &&
|
||||||
typeof error.response != "undefined" &&
|
typeof error.response != "undefined" &&
|
||||||
typeof error.response.headers != "undefined" &&
|
typeof error.response.headers != "undefined" &&
|
||||||
typeof error.response.headers["retry-after"] != "undefined"
|
typeof error.response.headers["retry-after"] != "undefined"
|
||||||
) {
|
) {
|
||||||
let timeout = error.response.headers["retry-after"];
|
const timeout = error.response.headers["retry-after"];
|
||||||
console.log(`Timeout: ${timeout}`);
|
console.log(`Timeout: ${timeout}`);
|
||||||
await sleep(Number(timeout) * 1000 + SLEEP_TIME);
|
await sleep(Number(timeout) * 1000 + SLEEP_TIME);
|
||||||
} else {
|
} else {
|
||||||
|
@ -98,6 +98,7 @@ export const metaculus: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "Metaculus",
|
label: "Metaculus",
|
||||||
color: "#006669",
|
color: "#006669",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
// let metaculusQuestionsInit = await fetchMetaculusQuestions(1)
|
// let metaculusQuestionsInit = await fetchMetaculusQuestions(1)
|
||||||
// let numQueries = Math.round(Number(metaculusQuestionsInit.count) / 20)
|
// let numQueries = Math.round(Number(metaculusQuestionsInit.count) / 20)
|
||||||
|
@ -131,7 +132,7 @@ export const metaculus: Platform = {
|
||||||
let description = descriptionprocessed2;
|
let description = descriptionprocessed2;
|
||||||
|
|
||||||
let isbinary = result.possibilities.type == "binary";
|
let isbinary = result.possibilities.type == "binary";
|
||||||
let options = [];
|
let options: FetchedQuestion["options"] = [];
|
||||||
if (isbinary) {
|
if (isbinary) {
|
||||||
let probability = Number(result.community_prediction.full.q2);
|
let probability = Number(result.community_prediction.full.q2);
|
||||||
options = [
|
options = [
|
||||||
|
@ -152,14 +153,10 @@ export const metaculus: Platform = {
|
||||||
id,
|
id,
|
||||||
title: result.title,
|
title: result.title,
|
||||||
url: "https://www.metaculus.com" + result.page_url,
|
url: "https://www.metaculus.com" + result.page_url,
|
||||||
platform: platformName,
|
|
||||||
description,
|
description,
|
||||||
options,
|
options,
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
numforecasts: Number(result.number_of_predictions),
|
numforecasts: Number(result.number_of_predictions),
|
||||||
stars: calculateStars(platformName, {
|
|
||||||
numforecasts: result.number_of_predictions,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
extra: {
|
extra: {
|
||||||
resolution_data: {
|
resolution_data: {
|
||||||
|
@ -194,4 +191,15 @@ export const metaculus: Platform = {
|
||||||
|
|
||||||
return all_questions;
|
return all_questions;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
calculateStars(data) {
|
||||||
|
const { numforecasts } = data.qualityindicators;
|
||||||
|
let nuno = () =>
|
||||||
|
(numforecasts || 0) > 300 ? 4 : (numforecasts || 0) > 100 ? 3 : 2;
|
||||||
|
let eli = () => 3;
|
||||||
|
let misha = () => 3;
|
||||||
|
let starsDecimal = average([nuno(), eli(), misha()]);
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/* Imports */
|
/* Imports */
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
import { average } from "../../utils";
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
const platformName = "polymarket";
|
const platformName = "polymarket";
|
||||||
let graphQLendpoint =
|
const graphQLendpoint =
|
||||||
"https://api.thegraph.com/subgraphs/name/polymarket/matic-markets-5"; // "https://api.thegraph.com/subgraphs/name/polymarket/matic-markets-4"// "https://api.thegraph.com/subgraphs/name/tokenunion/polymarket-matic"//"https://subgraph-matic.poly.market/subgraphs/name/TokenUnion/polymarket"//"https://subgraph-backup.poly.market/subgraphs/name/TokenUnion/polymarket"//'https://subgraph-matic.poly.market/subgraphs/name/TokenUnion/polymarket3'
|
"https://api.thegraph.com/subgraphs/name/polymarket/matic-markets-5";
|
||||||
let units = 10 ** 6;
|
let units = 10 ** 6;
|
||||||
|
|
||||||
async function fetchAllContractInfo() {
|
async function fetchAllContractInfo() {
|
||||||
|
@ -18,11 +18,11 @@ async function fetchAllContractInfo() {
|
||||||
// "https://strapi-matic.poly.market/markets?active=true&_sort=volume:desc&_limit=-1" to get all markets, including closed ones
|
// "https://strapi-matic.poly.market/markets?active=true&_sort=volume:desc&_limit=-1" to get all markets, including closed ones
|
||||||
)
|
)
|
||||||
.then((query) => query.data);
|
.then((query) => query.data);
|
||||||
response = response.filter((res) => res.closed != true);
|
response = response.filter((res: any) => res.closed != true);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchIndividualContractData(marketMakerAddress) {
|
async function fetchIndividualContractData(marketMakerAddress: string) {
|
||||||
let daysSinceEra = Math.round(Date.now() / (1000 * 24 * 60 * 60)) - 7; // last week
|
let daysSinceEra = Math.round(Date.now() / (1000 * 24 * 60 * 60)) - 7; // last week
|
||||||
let response = await axios({
|
let response = await axios({
|
||||||
url: graphQLendpoint,
|
url: graphQLendpoint,
|
||||||
|
@ -59,7 +59,7 @@ async function fetchIndividualContractData(marketMakerAddress) {
|
||||||
})
|
})
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.then((res) => res.data.fixedProductMarketMakers);
|
.then((res) => res.data.fixedProductMarketMakers);
|
||||||
// console.log(response)
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +67,7 @@ export const polymarket: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "PolyMarket",
|
label: "PolyMarket",
|
||||||
color: "#00314e",
|
color: "#00314e",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
let results: FetchedQuestion[] = [];
|
let results: FetchedQuestion[] = [];
|
||||||
let webpageEndpointData = await fetchAllContractInfo();
|
let webpageEndpointData = await fetchAllContractInfo();
|
||||||
|
@ -93,11 +94,11 @@ export const polymarket: Platform = {
|
||||||
// let isbinary = Number(moreMarketInfo.conditions[0].outcomeSlotCount) == 2
|
// let isbinary = Number(moreMarketInfo.conditions[0].outcomeSlotCount) == 2
|
||||||
// let percentage = Number(moreMarketInfo.outcomeTokenPrices[0]) * 100
|
// let percentage = Number(moreMarketInfo.outcomeTokenPrices[0]) * 100
|
||||||
// let percentageFormatted = isbinary ? (percentage.toFixed(0) + "%") : "none"
|
// let percentageFormatted = isbinary ? (percentage.toFixed(0) + "%") : "none"
|
||||||
let options = [];
|
let options: FetchedQuestion["options"] = [];
|
||||||
for (let outcome in moreMarketInfo.outcomeTokenPrices) {
|
for (let outcome in moreMarketInfo.outcomeTokenPrices) {
|
||||||
options.push({
|
options.push({
|
||||||
name: marketInfo.outcomes[outcome],
|
name: String(marketInfo.outcomes[outcome]),
|
||||||
probability: moreMarketInfo.outcomeTokenPrices[outcome],
|
probability: Number(moreMarketInfo.outcomeTokenPrices[outcome]),
|
||||||
type: "PROBABILITY",
|
type: "PROBABILITY",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -106,18 +107,12 @@ export const polymarket: Platform = {
|
||||||
id: id,
|
id: id,
|
||||||
title: marketInfo.question,
|
title: marketInfo.question,
|
||||||
url: "https://polymarket.com/market/" + marketInfo.slug,
|
url: "https://polymarket.com/market/" + marketInfo.slug,
|
||||||
platform: platformName,
|
|
||||||
description: marketInfo.description,
|
description: marketInfo.description,
|
||||||
options: options,
|
options,
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
numforecasts: numforecasts.toFixed(0),
|
numforecasts: numforecasts.toFixed(0),
|
||||||
liquidity: liquidity.toFixed(2),
|
liquidity: liquidity.toFixed(2),
|
||||||
tradevolume: tradevolume.toFixed(2),
|
tradevolume: tradevolume.toFixed(2),
|
||||||
stars: calculateStars(platformName, {
|
|
||||||
liquidity,
|
|
||||||
option: options[0],
|
|
||||||
volume: tradevolume,
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
extra: {
|
extra: {
|
||||||
address: marketInfo.address,
|
address: marketInfo.address,
|
||||||
|
@ -133,4 +128,33 @@ export const polymarket: Platform = {
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
// let nuno = (data) => (data.volume > 10000 ? 4 : data.volume > 1000 ? 3 : 2);
|
||||||
|
// let eli = (data) => data.liquidity > 10000 ? 5 : 4
|
||||||
|
// let misha = (data) => 4
|
||||||
|
|
||||||
|
const liquidity = data.qualityindicators.liquidity || 0;
|
||||||
|
const volume = data.qualityindicators.tradevolume || 0;
|
||||||
|
|
||||||
|
let nuno = () =>
|
||||||
|
liquidity > 1000 && volume > 10000
|
||||||
|
? 4
|
||||||
|
: liquidity > 500 && volume > 1000
|
||||||
|
? 3
|
||||||
|
: 2;
|
||||||
|
let starsDecimal = average([nuno()]); //, eli(data), misha(data)])
|
||||||
|
|
||||||
|
// Substract 1 star if probability is above 90% or below 10%
|
||||||
|
if (
|
||||||
|
data.options instanceof Array &&
|
||||||
|
data.options[0] &&
|
||||||
|
((data.options[0].probability || 0) < 0.1 ||
|
||||||
|
(data.options[0].probability || 0) > 0.9)
|
||||||
|
) {
|
||||||
|
starsDecimal = starsDecimal - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,24 +1,25 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
import { average } from "../../utils";
|
||||||
|
import { sleep } from "../utils/sleep";
|
||||||
import toMarkdown from "../utils/toMarkdown";
|
import toMarkdown from "../utils/toMarkdown";
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
const platformName = "predictit";
|
const platformName = "predictit";
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
async function fetchmarkets() {
|
async function fetchmarkets(): Promise<any[]> {
|
||||||
let response = await axios({
|
const response = await axios({
|
||||||
method: "get",
|
method: "get",
|
||||||
url: "https://www.predictit.org/api/marketdata/all/",
|
url: "https://www.predictit.org/api/marketdata/all/",
|
||||||
});
|
});
|
||||||
let openMarkets = response.data.markets.filter(
|
const openMarkets = response.data.markets.filter(
|
||||||
(market) => market.status == "Open"
|
(market: any) => market.status == "Open"
|
||||||
);
|
);
|
||||||
return openMarkets;
|
return openMarkets;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchmarketrules(market_id) {
|
async function fetchmarketrules(market_id: string | number) {
|
||||||
let response = await axios({
|
let response = await axios({
|
||||||
method: "get",
|
method: "get",
|
||||||
url: "https://www.predictit.org/api/Market/" + market_id,
|
url: "https://www.predictit.org/api/Market/" + market_id,
|
||||||
|
@ -34,15 +35,12 @@ async function fetchmarketvolumes() {
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sleep(ms: number) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Body */
|
/* Body */
|
||||||
export const predictit: Platform = {
|
export const predictit: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "PredictIt",
|
label: "PredictIt",
|
||||||
color: "#460c00",
|
color: "#460c00",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
let markets = await fetchmarkets();
|
let markets = await fetchmarkets();
|
||||||
let marketVolumes = await fetchmarketvolumes();
|
let marketVolumes = await fetchmarketvolumes();
|
||||||
|
@ -65,13 +63,15 @@ export const predictit: Platform = {
|
||||||
let shares_volume = market["TotalSharesTraded"];
|
let shares_volume = market["TotalSharesTraded"];
|
||||||
// let percentageFormatted = isbinary ? Number(Number(market.contracts[0].lastTradePrice) * 100).toFixed(0) + "%" : "none"
|
// let percentageFormatted = isbinary ? Number(Number(market.contracts[0].lastTradePrice) * 100).toFixed(0) + "%" : "none"
|
||||||
|
|
||||||
let options = market.contracts.map((contract) => ({
|
let options: FetchedQuestion["options"] = (market.contracts as any[]).map(
|
||||||
name: contract.name,
|
(contract) => ({
|
||||||
probability: contract.lastTradePrice,
|
name: String(contract.name),
|
||||||
type: "PROBABILITY",
|
probability: Number(contract.lastTradePrice),
|
||||||
}));
|
type: "PROBABILITY",
|
||||||
|
})
|
||||||
|
);
|
||||||
let totalValue = options
|
let totalValue = options
|
||||||
.map((element) => Number(element.probability))
|
.map((element: any) => Number(element.probability))
|
||||||
.reduce((a, b) => a + b, 0);
|
.reduce((a, b) => a + b, 0);
|
||||||
|
|
||||||
if (options.length != 1 && totalValue > 1) {
|
if (options.length != 1 && totalValue > 1) {
|
||||||
|
@ -81,7 +81,7 @@ export const predictit: Platform = {
|
||||||
}));
|
}));
|
||||||
} else if (options.length == 1) {
|
} else if (options.length == 1) {
|
||||||
let option = options[0];
|
let option = options[0];
|
||||||
let probability = option["probability"];
|
let probability = option.probability;
|
||||||
options = [
|
options = [
|
||||||
{
|
{
|
||||||
name: "Yes",
|
name: "Yes",
|
||||||
|
@ -90,7 +90,7 @@ export const predictit: Platform = {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "No",
|
name: "No",
|
||||||
probability: 1 - probability,
|
probability: 1 - (probability || 0),
|
||||||
type: "PROBABILITY",
|
type: "PROBABILITY",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -100,11 +100,9 @@ export const predictit: Platform = {
|
||||||
id,
|
id,
|
||||||
title: market["name"],
|
title: market["name"],
|
||||||
url: market.url,
|
url: market.url,
|
||||||
platform: platformName,
|
|
||||||
description,
|
description,
|
||||||
options,
|
options,
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
stars: calculateStars(platformName, {}),
|
|
||||||
shares_volume,
|
shares_volume,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -114,4 +112,12 @@ export const predictit: Platform = {
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
let nuno = () => 3;
|
||||||
|
let eli = () => 3.5;
|
||||||
|
let misha = () => 2.5;
|
||||||
|
let starsDecimal = average([nuno(), eli(), misha()]);
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
import { average } from "../../utils";
|
||||||
import toMarkdown from "../utils/toMarkdown";
|
import toMarkdown from "../utils/toMarkdown";
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ export const rootclaim: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "Rootclaim",
|
label: "Rootclaim",
|
||||||
color: "#0d1624",
|
color: "#0d1624",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
const claims = await fetchAllRootclaims();
|
const claims = await fetchAllRootclaims();
|
||||||
const results: FetchedQuestion[] = [];
|
const results: FetchedQuestion[] = [];
|
||||||
|
@ -55,7 +56,7 @@ export const rootclaim: Platform = {
|
||||||
for (const claim of claims) {
|
for (const claim of claims) {
|
||||||
const id = `${platformName}-${claim.slug.toLowerCase()}`;
|
const id = `${platformName}-${claim.slug.toLowerCase()}`;
|
||||||
|
|
||||||
let options = [];
|
let options: FetchedQuestion["options"] = [];
|
||||||
for (let scenario of claim.scenarios) {
|
for (let scenario of claim.scenarios) {
|
||||||
options.push({
|
options.push({
|
||||||
name: toMarkdown(scenario.name || scenario.text)
|
name: toMarkdown(scenario.name || scenario.text)
|
||||||
|
@ -75,16 +76,22 @@ export const rootclaim: Platform = {
|
||||||
id,
|
id,
|
||||||
title: toMarkdown(claim.question).replace("\n", ""),
|
title: toMarkdown(claim.question).replace("\n", ""),
|
||||||
url,
|
url,
|
||||||
platform: platformName,
|
|
||||||
description: toMarkdown(description).replace("'", "'"),
|
description: toMarkdown(description).replace("'", "'"),
|
||||||
options: options,
|
options,
|
||||||
qualityindicators: {
|
qualityindicators: {
|
||||||
numforecasts: 1,
|
numforecasts: 1,
|
||||||
stars: calculateStars(platformName, {}),
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
results.push(obj);
|
results.push(obj);
|
||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
let nuno = () => 4;
|
||||||
|
let eli = () => null;
|
||||||
|
let misha = () => null;
|
||||||
|
let starsDecimal = average([nuno() /*, eli(data), misha(data)*/]);
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,180 +1,212 @@
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
import { calculateStars } from "../utils/stars";
|
import { QuestionOption } from "../../common/types";
|
||||||
|
import { average } from "../../utils";
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
const platformName = "smarkets";
|
const platformName = "smarkets";
|
||||||
let htmlEndPointEntrance = "https://api.smarkets.com/v3/events/";
|
const apiEndpoint = "https://api.smarkets.com/v3"; // documented at https://docs.smarkets.com/
|
||||||
let VERBOSE = false;
|
|
||||||
let empty = () => 0;
|
type Context = {
|
||||||
|
verbose: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
|
|
||||||
async function fetchEvents(url) {
|
async function fetchEvents(ctx: Context) {
|
||||||
let response = await axios({
|
let queryString =
|
||||||
url: htmlEndPointEntrance + url,
|
"?state=new&state=upcoming&state=live&type_domain=politics&type_scope=single_event&with_new_type=true&sort=id&limit=50";
|
||||||
method: "GET",
|
|
||||||
headers: {
|
let events = [];
|
||||||
"Content-Type": "text/html",
|
while (queryString) {
|
||||||
},
|
const data = await axios({
|
||||||
}).then((res) => res.data);
|
url: `${apiEndpoint}/events/${queryString}`,
|
||||||
VERBOSE ? console.log(response) : empty();
|
method: "GET",
|
||||||
return response;
|
}).then((res) => res.data);
|
||||||
|
|
||||||
|
events.push(...data.events);
|
||||||
|
queryString = data.pagination.next_page;
|
||||||
|
}
|
||||||
|
ctx.verbose && console.log(events);
|
||||||
|
|
||||||
|
return events;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchMarkets(eventid) {
|
async function fetchSingleEvent(id: string, ctx: Context) {
|
||||||
let response = await axios({
|
const events = await fetchEvents(ctx);
|
||||||
url: `https://api.smarkets.com/v3/events/${eventid}/markets/`,
|
const event = events.find((event) => event.id === id);
|
||||||
|
if (!event) {
|
||||||
|
throw new Error(`Event ${id} not found`);
|
||||||
|
}
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchMarkets(eventId: string) {
|
||||||
|
const response = await axios({
|
||||||
|
url: `${apiEndpoint}/events/${eventId}/markets/`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/json",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.then((res) => res.markets);
|
.then((res) => res.markets);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchContracts(marketid) {
|
async function fetchContracts(marketId: string, ctx: Context) {
|
||||||
let response = await axios({
|
const response = await axios({
|
||||||
url: `https://api.smarkets.com/v3/markets/${marketid}/contracts/`,
|
url: `${apiEndpoint}/markets/${marketId}/contracts/?include_hidden=true`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/html",
|
|
||||||
},
|
|
||||||
}).then((res) => res.data);
|
}).then((res) => res.data);
|
||||||
VERBOSE ? console.log(response) : empty();
|
ctx.verbose && console.log(response);
|
||||||
return response;
|
|
||||||
|
if (!(response.contracts instanceof Array)) {
|
||||||
|
throw new Error("Invalid response while fetching contracts");
|
||||||
|
}
|
||||||
|
return response.contracts as any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchPrices(marketid) {
|
async function fetchPrices(marketId: string, ctx: Context) {
|
||||||
let response = await axios({
|
const response = await axios({
|
||||||
url: `https://api.smarkets.com/v3/markets/${marketid}/last_executed_prices/`,
|
url: `https://api.smarkets.com/v3/markets/${marketId}/last_executed_prices/`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
|
||||||
"Content-Type": "text/html",
|
|
||||||
},
|
|
||||||
}).then((res) => res.data);
|
}).then((res) => res.data);
|
||||||
VERBOSE ? console.log(response) : empty();
|
ctx.verbose && console.log(response);
|
||||||
return response;
|
if (!response.last_executed_prices) {
|
||||||
|
throw new Error("Invalid response while fetching prices");
|
||||||
|
}
|
||||||
|
return response.last_executed_prices;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const smarkets: Platform = {
|
async function processEventMarkets(event: any, ctx: Context) {
|
||||||
name: platformName,
|
ctx.verbose && console.log(Date.now());
|
||||||
label: "Smarkets",
|
ctx.verbose && console.log(event.name);
|
||||||
color: "#6f5b41",
|
|
||||||
async fetcher() {
|
|
||||||
let htmlPath =
|
|
||||||
"?state=new&state=upcoming&state=live&type_domain=politics&type_scope=single_event&with_new_type=true&sort=id&limit=50";
|
|
||||||
|
|
||||||
let events = [];
|
let markets = await fetchMarkets(event.id);
|
||||||
while (htmlPath) {
|
markets = markets.map((market: any) => ({
|
||||||
let data = await fetchEvents(htmlPath);
|
...market,
|
||||||
events.push(...data.events);
|
// smarkets doesn't have separate urls for different markets in a single event
|
||||||
htmlPath = data.pagination.next_page;
|
// we could use anchors (e.g. https://smarkets.com/event/886716/politics/uk/uk-party-leaders/next-conservative-leader#contract-collapse-9815728-control), but it's unclear if they aren't going to change
|
||||||
}
|
slug: event.full_slug,
|
||||||
VERBOSE ? console.log(events) : empty();
|
}));
|
||||||
let markets = [];
|
ctx.verbose && console.log(`Markets for ${event.id} fetched`);
|
||||||
for (let event of events) {
|
ctx.verbose && console.log(markets);
|
||||||
VERBOSE ? console.log(Date.now()) : empty();
|
|
||||||
VERBOSE ? console.log(event.name) : empty();
|
|
||||||
let eventMarkets = await fetchMarkets(event.id);
|
|
||||||
eventMarkets = eventMarkets.map((market) => ({
|
|
||||||
...market,
|
|
||||||
slug: event.full_slug,
|
|
||||||
}));
|
|
||||||
VERBOSE ? console.log("Markets fetched") : empty();
|
|
||||||
VERBOSE ? console.log(event.id) : empty();
|
|
||||||
VERBOSE ? console.log(eventMarkets) : empty();
|
|
||||||
markets.push(...eventMarkets);
|
|
||||||
//let lastPrices = await fetchPrices(market.id)
|
|
||||||
}
|
|
||||||
VERBOSE ? console.log(markets) : empty();
|
|
||||||
|
|
||||||
let results = [];
|
let results: FetchedQuestion[] = [];
|
||||||
for (let market of markets) {
|
for (const market of markets) {
|
||||||
VERBOSE ? console.log("================") : empty();
|
ctx.verbose && console.log("================");
|
||||||
VERBOSE ? console.log("Market: ", market) : empty();
|
ctx.verbose && console.log("Market:", market);
|
||||||
let id = `${platformName}-${market.id}`;
|
|
||||||
let name = market.name;
|
|
||||||
|
|
||||||
let contracts = await fetchContracts(market.id);
|
const contracts = await fetchContracts(market.id, ctx);
|
||||||
VERBOSE ? console.log("Contracts: ", contracts) : empty();
|
ctx.verbose && console.log("Contracts:", contracts);
|
||||||
let prices = await fetchPrices(market.id);
|
const prices = await fetchPrices(market.id, ctx);
|
||||||
VERBOSE
|
ctx.verbose && console.log("Prices:", prices[market.id]);
|
||||||
? console.log("Prices: ", prices["last_executed_prices"][market.id])
|
|
||||||
: empty();
|
|
||||||
|
|
||||||
let optionsObj = {};
|
let optionsObj: {
|
||||||
for (let contract of contracts["contracts"]) {
|
[k: string]: QuestionOption;
|
||||||
optionsObj[contract.id] = { name: contract.name };
|
} = {};
|
||||||
|
|
||||||
|
const contractsById = Object.fromEntries(
|
||||||
|
contracts.map((c) => [c.id as string, c])
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const price of prices[market.id]) {
|
||||||
|
const contract = contractsById[price.contract_id];
|
||||||
|
if (!contract) {
|
||||||
|
console.warn(
|
||||||
|
`Couldn't find contract ${price.contract_id} in contracts data for ${market.id}, event ${market.event_id}, skipping`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
for (let price of prices["last_executed_prices"][market.id]) {
|
optionsObj[price.contract_id] = {
|
||||||
optionsObj[price.contract_id] = {
|
name: contract.name,
|
||||||
...optionsObj[price.contract_id],
|
probability: contract.hidden ? 0 : Number(price.last_executed_price),
|
||||||
probability: price.last_executed_price
|
type: "PROBABILITY",
|
||||||
? Number(price.last_executed_price)
|
};
|
||||||
: null,
|
}
|
||||||
type: "PROBABILITY",
|
let options: QuestionOption[] = Object.values(optionsObj);
|
||||||
};
|
ctx.verbose && console.log("Options before patching:", options);
|
||||||
}
|
|
||||||
let options: any[] = Object.values(optionsObj);
|
// monkey patch the case where there are only two options and only one has traded.
|
||||||
// monkey patch the case where there are only two options and only one has traded.
|
if (
|
||||||
if (
|
options.length === 2 &&
|
||||||
options.length == 2 &&
|
options.map((option) => option.probability).includes(0)
|
||||||
options.map((option) => option.probability).includes(null)
|
) {
|
||||||
) {
|
const nonNullPrice = options[0].probability || options[1].probability;
|
||||||
let nonNullPrice =
|
|
||||||
options[0].probability == null
|
if (nonNullPrice) {
|
||||||
? options[1].probability
|
|
||||||
: options[0].probability;
|
|
||||||
options = options.map((option) => {
|
options = options.map((option) => {
|
||||||
let probability = option.probability;
|
|
||||||
return {
|
return {
|
||||||
...option,
|
...option,
|
||||||
probability: probability == null ? 100 - nonNullPrice : probability,
|
probability: option.probability || 100 - nonNullPrice,
|
||||||
// yes, 100, because prices are not yet normalized.
|
// yes, 100, because prices are not yet normalized.
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize normally
|
|
||||||
let totalValue = options
|
|
||||||
.map((element) => Number(element.probability))
|
|
||||||
.reduce((a, b) => a + b, 0);
|
|
||||||
|
|
||||||
options = options.map((element) => ({
|
|
||||||
...element,
|
|
||||||
probability: Number(element.probability) / totalValue,
|
|
||||||
}));
|
|
||||||
VERBOSE ? console.log(options) : empty();
|
|
||||||
|
|
||||||
/*
|
|
||||||
if(contracts["contracts"].length == 2){
|
|
||||||
isBinary = true
|
|
||||||
percentage = ( Number(prices["last_executed_prices"][market.id][0].last_executed_price) + (100 - Number(prices["last_executed_prices"][market.id][1].last_executed_price)) ) / 2
|
|
||||||
percentage = Math.round(percentage)+"%"
|
|
||||||
let contractName = contracts["contracts"][0].name
|
|
||||||
name = name+ (contractName=="Yes"?'':` (${contracts["contracts"][0].name})`)
|
|
||||||
}
|
}
|
||||||
*/
|
ctx.verbose && console.log("Options after patching:", options);
|
||||||
let result: FetchedQuestion = {
|
|
||||||
id: id,
|
// Normalize normally
|
||||||
title: name,
|
const totalValue = options
|
||||||
url: "https://smarkets.com/event/" + market.event_id + market.slug,
|
.map((element) => Number(element.probability))
|
||||||
platform: platformName,
|
.reduce((a, b) => a + b, 0);
|
||||||
description: market.description,
|
|
||||||
options: options,
|
options = options.map((element) => ({
|
||||||
timestamp: new Date(),
|
...element,
|
||||||
qualityindicators: {
|
probability: Number(element.probability) / totalValue,
|
||||||
stars: calculateStars(platformName, {}),
|
}));
|
||||||
},
|
ctx.verbose && console.log("Normalized options:", options);
|
||||||
};
|
|
||||||
VERBOSE ? console.log(result) : empty();
|
const result: FetchedQuestion = {
|
||||||
results.push(result);
|
id: `${platformName}-${market.id}`,
|
||||||
|
title: market.name,
|
||||||
|
url: "https://smarkets.com/event/" + market.event_id + market.slug,
|
||||||
|
description: market.description,
|
||||||
|
options,
|
||||||
|
timestamp: new Date(),
|
||||||
|
qualityindicators: {},
|
||||||
|
};
|
||||||
|
ctx.verbose && console.log(result);
|
||||||
|
results.push(result);
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const smarkets: Platform<"eventId" | "verbose"> = {
|
||||||
|
name: platformName,
|
||||||
|
label: "Smarkets",
|
||||||
|
color: "#6f5b41",
|
||||||
|
version: "v2",
|
||||||
|
fetcherArgs: ["eventId", "verbose"],
|
||||||
|
async fetcher(opts) {
|
||||||
|
const ctx = {
|
||||||
|
verbose: Boolean(opts.args?.verbose) || false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let events: any[] = [];
|
||||||
|
let partial = true;
|
||||||
|
if (opts.args?.eventId) {
|
||||||
|
events = [await fetchSingleEvent(opts.args.eventId, ctx)];
|
||||||
|
} else {
|
||||||
|
events = await fetchEvents(ctx);
|
||||||
|
partial = false;
|
||||||
}
|
}
|
||||||
VERBOSE ? console.log(results) : empty();
|
|
||||||
return results;
|
let results: FetchedQuestion[] = [];
|
||||||
|
for (const event of events) {
|
||||||
|
const eventResults = await processEventMarkets(event, ctx);
|
||||||
|
results.push(...eventResults);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
questions: results,
|
||||||
|
partial,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
const nuno = () => 2;
|
||||||
|
const eli = () => null;
|
||||||
|
const misha = () => null;
|
||||||
|
const starsDecimal = average([nuno()]); //, eli(), misha()])
|
||||||
|
const starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/* Imports */
|
/* Imports */
|
||||||
import { GoogleSpreadsheet } from "google-spreadsheet";
|
import { GoogleSpreadsheet } from "google-spreadsheet";
|
||||||
|
|
||||||
|
import { average } from "../../utils";
|
||||||
import { applyIfSecretExists } from "../utils/getSecrets";
|
import { applyIfSecretExists } from "../utils/getSecrets";
|
||||||
import { hash } from "../utils/hash";
|
import { hash } from "../utils/hash";
|
||||||
import { calculateStars } from "../utils/stars";
|
|
||||||
import { FetchedQuestion, Platform } from "./";
|
import { FetchedQuestion, Platform } from "./";
|
||||||
|
|
||||||
/* Definitions */
|
/* Definitions */
|
||||||
|
@ -13,7 +13,7 @@ const endpoint = `https://docs.google.com/spreadsheets/d/${SHEET_ID}/edit#gid=0`
|
||||||
// https://docs.google.com/spreadsheets/d/1xcgYF7Q0D95TPHLLSgwhWBHFrWZUGJn7yTyAhDR4vi0/edit#gid=0
|
// https://docs.google.com/spreadsheets/d/1xcgYF7Q0D95TPHLLSgwhWBHFrWZUGJn7yTyAhDR4vi0/edit#gid=0
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
|
|
||||||
const formatRow = (row) => {
|
const formatRow = (row: string[]) => {
|
||||||
let colNames = [
|
let colNames = [
|
||||||
"Prediction Date",
|
"Prediction Date",
|
||||||
"Prediction",
|
"Prediction",
|
||||||
|
@ -23,15 +23,15 @@ const formatRow = (row) => {
|
||||||
"Prediction Right?",
|
"Prediction Right?",
|
||||||
"Brier Score",
|
"Brier Score",
|
||||||
"Notes",
|
"Notes",
|
||||||
];
|
] as const;
|
||||||
let result = {};
|
let result: Partial<{ [k in typeof colNames[number]]: string }> = {};
|
||||||
row.forEach((col, i) => {
|
row.forEach((col: string, i) => {
|
||||||
result[colNames[i]] = col;
|
result[colNames[i]] = col;
|
||||||
});
|
});
|
||||||
return result;
|
return result as Required<typeof result>;
|
||||||
};
|
};
|
||||||
|
|
||||||
async function fetchGoogleDoc(google_api_key) {
|
async function fetchGoogleDoc(google_api_key: string) {
|
||||||
// https://gist.github.com/micalevisk/9bc831bd4b3e5a3f62b9810330129c59
|
// https://gist.github.com/micalevisk/9bc831bd4b3e5a3f62b9810330129c59
|
||||||
let results = [];
|
let results = [];
|
||||||
const doc = new GoogleSpreadsheet(SHEET_ID);
|
const doc = new GoogleSpreadsheet(SHEET_ID);
|
||||||
|
@ -41,7 +41,7 @@ async function fetchGoogleDoc(google_api_key) {
|
||||||
console.log(">>", doc.title);
|
console.log(">>", doc.title);
|
||||||
|
|
||||||
const sheet = doc.sheetsByIndex[0];
|
const sheet = doc.sheetsByIndex[0];
|
||||||
const rows = await sheet.getRows({ offset: 0 });
|
const rows = await sheet.getRows();
|
||||||
|
|
||||||
console.log("# " + rows[0]._sheet.headerValues.join(","));
|
console.log("# " + rows[0]._sheet.headerValues.join(","));
|
||||||
let isEnd = false;
|
let isEnd = false;
|
||||||
|
@ -68,7 +68,9 @@ async function fetchGoogleDoc(google_api_key) {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processPredictions(predictions) {
|
async function processPredictions(
|
||||||
|
predictions: Awaited<ReturnType<typeof fetchGoogleDoc>>
|
||||||
|
) {
|
||||||
let currentPredictions = predictions.filter(
|
let currentPredictions = predictions.filter(
|
||||||
(prediction) => prediction["Actual"] == "Unknown"
|
(prediction) => prediction["Actual"] == "Unknown"
|
||||||
);
|
);
|
||||||
|
@ -76,7 +78,7 @@ async function processPredictions(predictions) {
|
||||||
let title = prediction["Prediction"].replace(" [update]", "");
|
let title = prediction["Prediction"].replace(" [update]", "");
|
||||||
let id = `${platformName}-${hash(title)}`;
|
let id = `${platformName}-${hash(title)}`;
|
||||||
let probability = Number(prediction["Odds"].replace("%", "")) / 100;
|
let probability = Number(prediction["Odds"].replace("%", "")) / 100;
|
||||||
let options = [
|
let options: FetchedQuestion["options"] = [
|
||||||
{
|
{
|
||||||
name: "Yes",
|
name: "Yes",
|
||||||
probability: probability,
|
probability: probability,
|
||||||
|
@ -92,20 +94,17 @@ async function processPredictions(predictions) {
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
url: prediction["url"],
|
url: prediction["url"],
|
||||||
platform: platformName,
|
|
||||||
description: prediction["Notes"] || "",
|
description: prediction["Notes"] || "",
|
||||||
options,
|
options,
|
||||||
timestamp: new Date(Date.parse(prediction["Prediction Date"] + "Z")),
|
timestamp: new Date(Date.parse(prediction["Prediction Date"] + "Z")),
|
||||||
qualityindicators: {
|
qualityindicators: {},
|
||||||
stars: calculateStars(platformName, null),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
|
|
||||||
results = results.reverse();
|
results = results.reverse();
|
||||||
let uniqueTitles = [];
|
let uniqueTitles: string[] = [];
|
||||||
let uniqueResults = [];
|
let uniqueResults: FetchedQuestion[] = [];
|
||||||
results.forEach((result) => {
|
results.forEach((result) => {
|
||||||
if (!uniqueTitles.includes(result.title)) uniqueResults.push(result);
|
if (!uniqueTitles.includes(result.title)) uniqueResults.push(result);
|
||||||
uniqueTitles.push(result.title);
|
uniqueTitles.push(result.title);
|
||||||
|
@ -113,7 +112,7 @@ async function processPredictions(predictions) {
|
||||||
return uniqueResults;
|
return uniqueResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function wildeford_inner(google_api_key) {
|
export async function wildeford_inner(google_api_key: string) {
|
||||||
let predictions = await fetchGoogleDoc(google_api_key);
|
let predictions = await fetchGoogleDoc(google_api_key);
|
||||||
return await processPredictions(predictions);
|
return await processPredictions(predictions);
|
||||||
}
|
}
|
||||||
|
@ -122,8 +121,17 @@ export const wildeford: Platform = {
|
||||||
name: platformName,
|
name: platformName,
|
||||||
label: "Peter Wildeford",
|
label: "Peter Wildeford",
|
||||||
color: "#984158",
|
color: "#984158",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY; // See: https://developers.google.com/sheets/api/guides/authorizing#APIKey
|
const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY; // See: https://developers.google.com/sheets/api/guides/authorizing#APIKey
|
||||||
return await applyIfSecretExists(GOOGLE_API_KEY, wildeford_inner);
|
return (await applyIfSecretExists(GOOGLE_API_KEY, wildeford_inner)) || null;
|
||||||
|
},
|
||||||
|
calculateStars(data) {
|
||||||
|
let nuno = () => 3;
|
||||||
|
let eli = () => null;
|
||||||
|
let misha = () => null;
|
||||||
|
let starsDecimal = average([nuno()]); //, eli(), misha()])
|
||||||
|
let starsInteger = Math.round(starsDecimal);
|
||||||
|
return starsInteger;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,21 +9,22 @@ export const xrisk: Platform = {
|
||||||
name: "xrisk",
|
name: "xrisk",
|
||||||
label: "X-risk estimates",
|
label: "X-risk estimates",
|
||||||
color: "#272600",
|
color: "#272600",
|
||||||
|
version: "v1",
|
||||||
async fetcher() {
|
async fetcher() {
|
||||||
// return; // not necessary to refill the DB every time
|
// return; // not necessary to refill the DB every time
|
||||||
let fileRaw = fs.readFileSync("./input/xrisk-questions.json", {
|
let fileRaw = fs.readFileSync("./input/xrisk-questions.json", {
|
||||||
encoding: "utf-8",
|
encoding: "utf-8",
|
||||||
});
|
});
|
||||||
let results = JSON.parse(fileRaw);
|
let parsedData = JSON.parse(fileRaw);
|
||||||
results = results.map((item) => {
|
const results = parsedData.map((item: any) => {
|
||||||
item.extra = item.moreoriginsdata;
|
item.extra = item.moreoriginsdata;
|
||||||
delete item.moreoriginsdata;
|
delete item.moreoriginsdata;
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
id: `${platformName}-${hash(item.title + " | " + item.url)}`, // some titles are non-unique, but title+url pair is always unique
|
id: `${platformName}-${hash(item.title + " | " + item.url)}`, // some titles are non-unique, but title+url pair is always unique
|
||||||
platform: platformName,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
calculateStars: () => 2,
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,13 +5,14 @@ import { Question } from "@prisma/client";
|
||||||
import { prisma } from "../database/prisma";
|
import { prisma } from "../database/prisma";
|
||||||
import { platforms } from "../platforms";
|
import { platforms } from "../platforms";
|
||||||
|
|
||||||
let cookie = process.env.ALGOLIA_MASTER_API_KEY;
|
let cookie = process.env.ALGOLIA_MASTER_API_KEY || "";
|
||||||
const algoliaAppId = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID;
|
const algoliaAppId = process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || "";
|
||||||
const client = algoliasearch(algoliaAppId, cookie);
|
const client = algoliasearch(algoliaAppId, cookie);
|
||||||
const index = client.initIndex("metaforecast");
|
const index = client.initIndex("metaforecast");
|
||||||
|
|
||||||
export type AlgoliaQuestion = Omit<Question, "timestamp"> & {
|
export type AlgoliaQuestion = Omit<Question, "timestamp"> & {
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
|
optionsstringforsearch?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getoptionsstringforsearch = (record: Question): string => {
|
const getoptionsstringforsearch = (record: Question): string => {
|
||||||
|
@ -43,7 +44,7 @@ export async function rebuildAlgoliaDatabase() {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if (index.exists()) {
|
if (await index.exists()) {
|
||||||
console.log("Index exists");
|
console.log("Index exists");
|
||||||
await index.replaceAllObjects(records, { safe: true });
|
await index.replaceAllObjects(records, { safe: true });
|
||||||
console.log(
|
console.log(
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
/* Imports */
|
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
import { prisma } from "../../database/prisma";
|
|
||||||
|
|
||||||
/* Definitions */
|
|
||||||
|
|
||||||
/* Utilities */
|
|
||||||
|
|
||||||
/* Support functions */
|
|
||||||
const getQualityIndicators = (question) =>
|
|
||||||
Object.entries(question.qualityindicators)
|
|
||||||
.map((entry) => `${entry[0]}: ${entry[1]}`)
|
|
||||||
.join("; ");
|
|
||||||
|
|
||||||
/* Body */
|
|
||||||
|
|
||||||
const main = async () => {
|
|
||||||
let highQualityPlatforms = [
|
|
||||||
"CSET-foretell",
|
|
||||||
"Foretold",
|
|
||||||
"Good Judgment Open",
|
|
||||||
"Metaculus",
|
|
||||||
"PredictIt",
|
|
||||||
"Rootclaim",
|
|
||||||
];
|
|
||||||
const json = await prisma.question.findMany({});
|
|
||||||
console.log(json.length);
|
|
||||||
//let uniquePlatforms = [...new Set(json.map(forecast => forecast.platform))]
|
|
||||||
//console.log(uniquePlatforms)
|
|
||||||
|
|
||||||
const questionsFromGoodPlatforms = json.filter((question) =>
|
|
||||||
highQualityPlatforms.includes(question.platform)
|
|
||||||
);
|
|
||||||
const tsv =
|
|
||||||
"index\ttitle\turl\tqualityindicators\n" +
|
|
||||||
questionsFromGoodPlatforms
|
|
||||||
.map((question, index) => {
|
|
||||||
let row = `${index}\t${question.title}\t${
|
|
||||||
question.url
|
|
||||||
}\t${getQualityIndicators(question)}`;
|
|
||||||
console.log(row);
|
|
||||||
return row;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
//console.log(tsv)
|
|
||||||
|
|
||||||
// let string = JSON.stringify(json, null, 2)
|
|
||||||
fs.writeFileSync("metaforecasts.tsv", tsv);
|
|
||||||
};
|
|
||||||
main();
|
|
|
@ -1,48 +0,0 @@
|
||||||
/* Imports */
|
|
||||||
import fs from "fs";
|
|
||||||
|
|
||||||
import { shuffleArray } from "../../../utils";
|
|
||||||
import { prisma } from "../../database/prisma";
|
|
||||||
|
|
||||||
/* Definitions */
|
|
||||||
|
|
||||||
/* Utilities */
|
|
||||||
|
|
||||||
/* Support functions */
|
|
||||||
let getQualityIndicators = (question) =>
|
|
||||||
Object.entries(question.qualityindicators)
|
|
||||||
.map((entry) => `${entry[0]}: ${entry[1]}`)
|
|
||||||
.join("; ");
|
|
||||||
|
|
||||||
/* Body */
|
|
||||||
|
|
||||||
let main = async () => {
|
|
||||||
let highQualityPlatforms = ["Metaculus"]; // ['CSET-foretell', 'Foretold', 'Good Judgment Open', 'Metaculus', 'PredictIt', 'Rootclaim']
|
|
||||||
let json = await prisma.question.findMany({});
|
|
||||||
console.log(json.length);
|
|
||||||
//let uniquePlatforms = [...new Set(json.map(question => question.platform))]
|
|
||||||
//console.log(uniquePlatforms)
|
|
||||||
|
|
||||||
let questionsFromGoodPlatforms = json.filter((question) =>
|
|
||||||
highQualityPlatforms.includes(question.platform)
|
|
||||||
);
|
|
||||||
let questionsFromGoodPlatformsShuffled = shuffleArray(
|
|
||||||
questionsFromGoodPlatforms
|
|
||||||
);
|
|
||||||
let tsv =
|
|
||||||
"index\ttitle\turl\tqualityindicators\n" +
|
|
||||||
questionsFromGoodPlatforms
|
|
||||||
.map((question, index) => {
|
|
||||||
let row = `${index}\t${question.title}\t${
|
|
||||||
question.url
|
|
||||||
}\t${getQualityIndicators(question)}`;
|
|
||||||
console.log(row);
|
|
||||||
return row;
|
|
||||||
})
|
|
||||||
.join("\n");
|
|
||||||
//console.log(tsv)
|
|
||||||
|
|
||||||
// let string = JSON.stringify(json, null, 2)
|
|
||||||
fs.writeFileSync("metaforecasts_metaculus_v2.tsv", tsv);
|
|
||||||
};
|
|
||||||
main();
|
|
|
@ -1,5 +1,5 @@
|
||||||
export async function applyIfSecretExists<T>(
|
export async function applyIfSecretExists<T>(
|
||||||
cookie: string,
|
cookie: string | undefined,
|
||||||
fun: (cookie: string) => T
|
fun: (cookie: string) => T
|
||||||
) {
|
) {
|
||||||
if (cookie) {
|
if (cookie) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ let locationData = "./data/";
|
||||||
// let rawdata = fs.readFileSync("./data/merged-questions.json") // run from topmost folder, not from src
|
// let rawdata = fs.readFileSync("./data/merged-questions.json") // run from topmost folder, not from src
|
||||||
async function main() {
|
async function main() {
|
||||||
const data = await prisma.question.findMany({});
|
const data = await prisma.question.findMany({});
|
||||||
const processDescription = (description) => {
|
const processDescription = (description: string | null | undefined) => {
|
||||||
if (description == null || description == undefined || description == "") {
|
if (description == null || description == undefined || description == "") {
|
||||||
return "";
|
return "";
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,7 +10,7 @@ let rawdata = fs.readFileSync("../data/merged-questions.json", {
|
||||||
});
|
});
|
||||||
let data = JSON.parse(rawdata);
|
let data = JSON.parse(rawdata);
|
||||||
|
|
||||||
let results = [];
|
let results: any[] = [];
|
||||||
for (let datum of data) {
|
for (let datum of data) {
|
||||||
// do something
|
// do something
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
export function roughSizeOfObject(object) {
|
|
||||||
var objectList = [];
|
|
||||||
var stack = [object];
|
|
||||||
var bytes = 0;
|
|
||||||
|
|
||||||
while (stack.length) {
|
|
||||||
var value = stack.pop();
|
|
||||||
if (typeof value === "boolean") {
|
|
||||||
bytes += 4;
|
|
||||||
} else if (typeof value === "string") {
|
|
||||||
bytes += value.length * 2;
|
|
||||||
} else if (typeof value === "number") {
|
|
||||||
bytes += 8;
|
|
||||||
} else if (typeof value === "object" && objectList.indexOf(value) === -1) {
|
|
||||||
objectList.push(value);
|
|
||||||
|
|
||||||
for (var i in value) {
|
|
||||||
stack.push(value[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let megaBytes = bytes / 1024 ** 2;
|
|
||||||
let megaBytesRounded = Math.round(megaBytes * 10) / 10;
|
|
||||||
return megaBytesRounded;
|
|
||||||
}
|
|
3
src/backend/utils/sleep.ts
Normal file
3
src/backend/utils/sleep.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function sleep(ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
|
@ -1,356 +0,0 @@
|
||||||
export function getStarSymbols(numstars) {
|
|
||||||
let stars = "★★☆☆☆";
|
|
||||||
switch (numstars) {
|
|
||||||
case 0:
|
|
||||||
stars = "☆☆☆☆☆";
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
stars = "★☆☆☆☆";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
stars = "★★☆☆☆";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
stars = "★★★☆☆";
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
stars = "★★★★☆";
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
stars = "★★★★★";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
stars = "★★☆☆☆";
|
|
||||||
}
|
|
||||||
return stars;
|
|
||||||
}
|
|
||||||
|
|
||||||
let average = (array) => array.reduce((a, b) => a + b, 0) / array.length;
|
|
||||||
|
|
||||||
function calculateStarsAstralCodexTen(data) {
|
|
||||||
let nuno = (data) => 3;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsBetfair(data) {
|
|
||||||
let nuno = (data) => (data.volume > 10000 ? 4 : data.volume > 1000 ? 3 : 2);
|
|
||||||
let eli = (data) => (data.volume > 10000 ? null : null);
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
// Substract 1 star if probability is above 90% or below 10%
|
|
||||||
if (
|
|
||||||
data.option &&
|
|
||||||
(data.option.probability < 0.1 || data.option.probability > 0.9)
|
|
||||||
) {
|
|
||||||
starsDecimal = starsDecimal - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsCoupCast(data) {
|
|
||||||
let nuno = (data) => 3;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsCSETForetell(data) {
|
|
||||||
let nuno = (data) => (data.numforecasts > 100 ? 3 : 2);
|
|
||||||
let eli = (data) => 3;
|
|
||||||
let misha = (data) => 2;
|
|
||||||
let starsDecimal = average([nuno(data), eli(data), misha(data)]);
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsElicit(data) {
|
|
||||||
let nuno = (data) => 1;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsEstimize(data) {
|
|
||||||
let nuno = (data) => 2;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsForetold(data) {
|
|
||||||
let nuno = (data) => 2;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsGiveWellOpenPhil(data) {
|
|
||||||
let nuno = (data) => 2;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsGoodJudgment(data) {
|
|
||||||
let nuno = (data) => 4;
|
|
||||||
let eli = (data) => 4;
|
|
||||||
let misha = (data) => 3.5;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsGoodJudgmentOpen(data) {
|
|
||||||
let nuno = (data) => (data.numforecasts > 100 ? 3 : 2);
|
|
||||||
let eli = (data) => 3;
|
|
||||||
let misha = (data) =>
|
|
||||||
data.minProbability > 0.1 || data.maxProbability < 0.9 ? 3.1 : 2.5;
|
|
||||||
let starsDecimal = average([nuno(data), eli(data), misha(data)]);
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsHypermind(data) {
|
|
||||||
let nuno = (data) => 3;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsInfer(data) {
|
|
||||||
let nuno = (data) => 2;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsKalshi(data) {
|
|
||||||
let nuno = (data) =>
|
|
||||||
data.interest > 500 && data.shares_volume > 10000
|
|
||||||
? 4
|
|
||||||
: data.shares_volume > 2000
|
|
||||||
? 3
|
|
||||||
: 2;
|
|
||||||
// let eli = (data) => data.interest > 10000 ? 5 : 4
|
|
||||||
// let misha = (data) => 4
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
// Substract 1 star if probability is above 90% or below 10%
|
|
||||||
if (
|
|
||||||
data.option &&
|
|
||||||
(data.option.probability < 0.1 || data.option.probability > 0.9)
|
|
||||||
) {
|
|
||||||
starsDecimal = starsDecimal - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsLadbrokes(data) {
|
|
||||||
let nuno = (data) => 2;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsManifold(data) {
|
|
||||||
let nuno = (data) =>
|
|
||||||
data.volume7Days > 250 || (data.pool > 500 && data.volume7Days > 100)
|
|
||||||
? 2
|
|
||||||
: 1;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
// console.log(data);
|
|
||||||
// console.log(starsInteger);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsMetaculus(data) {
|
|
||||||
let nuno = (data) =>
|
|
||||||
data.numforecasts > 300 ? 4 : data.numforecasts > 100 ? 3 : 2;
|
|
||||||
let eli = (data) => 3;
|
|
||||||
let misha = (data) => 3;
|
|
||||||
let starsDecimal = average([nuno(data), eli(data), misha(data)]);
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsOmen(data) {
|
|
||||||
let nuno = (data) => 1;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsPolymarket(data) {
|
|
||||||
// let nuno = (data) => (data.volume > 10000 ? 4 : data.volume > 1000 ? 3 : 2);
|
|
||||||
// let eli = (data) => data.liquidity > 10000 ? 5 : 4
|
|
||||||
// let misha = (data) => 4
|
|
||||||
|
|
||||||
let nuno = (data) =>
|
|
||||||
data.liquidity > 1000 && data.volume > 10000
|
|
||||||
? 4
|
|
||||||
: data.liquidity > 500 && data.volume > 1000
|
|
||||||
? 3
|
|
||||||
: 2;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
// Substract 1 star if probability is above 90% or below 10%
|
|
||||||
if (
|
|
||||||
data.option &&
|
|
||||||
(data.option.probability < 0.1 || data.option.probability > 0.9)
|
|
||||||
) {
|
|
||||||
starsDecimal = starsDecimal - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsPredictIt(data) {
|
|
||||||
let nuno = (data) => 3;
|
|
||||||
let eli = (data) => 3.5;
|
|
||||||
let misha = (data) => 2.5;
|
|
||||||
let starsDecimal = average([nuno(data), eli(data), misha(data)]);
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsRootclaim(data) {
|
|
||||||
let nuno = (data) => 4;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data) /*, eli(data), misha(data)*/]);
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsSmarkets(data) {
|
|
||||||
let nuno = (data) => 2;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsWildeford(data) {
|
|
||||||
let nuno = (data) => 3;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateStarsWilliamHill(data) {
|
|
||||||
let nuno = (data) => 2;
|
|
||||||
let eli = (data) => null;
|
|
||||||
let misha = (data) => null;
|
|
||||||
let starsDecimal = average([nuno(data)]); //, eli(data), misha(data)])
|
|
||||||
let starsInteger = Math.round(starsDecimal);
|
|
||||||
return starsInteger;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function calculateStars(platform: string, data) {
|
|
||||||
let stars = 2;
|
|
||||||
switch (platform) {
|
|
||||||
case "betfair":
|
|
||||||
stars = calculateStarsBetfair(data);
|
|
||||||
break;
|
|
||||||
case "infer":
|
|
||||||
stars = calculateStarsInfer(data);
|
|
||||||
break;
|
|
||||||
case "foretold":
|
|
||||||
stars = calculateStarsForetold(data);
|
|
||||||
break;
|
|
||||||
case "givewellopenphil":
|
|
||||||
stars = calculateStarsGiveWellOpenPhil(data);
|
|
||||||
break;
|
|
||||||
case "goodjudgment":
|
|
||||||
stars = calculateStarsGoodJudgment(data);
|
|
||||||
break;
|
|
||||||
case "goodjudgmentopen":
|
|
||||||
stars = calculateStarsGoodJudgmentOpen(data);
|
|
||||||
break;
|
|
||||||
case "kalshi":
|
|
||||||
stars = calculateStarsKalshi(data);
|
|
||||||
break;
|
|
||||||
case "manifold":
|
|
||||||
stars = calculateStarsManifold(data);
|
|
||||||
break;
|
|
||||||
case "metaculus":
|
|
||||||
stars = calculateStarsMetaculus(data);
|
|
||||||
break;
|
|
||||||
case "polymarket":
|
|
||||||
stars = calculateStarsPolymarket(data);
|
|
||||||
break;
|
|
||||||
case "predictit":
|
|
||||||
stars = calculateStarsPredictIt(data);
|
|
||||||
break;
|
|
||||||
case "rootclaim":
|
|
||||||
stars = calculateStarsRootclaim(data);
|
|
||||||
break;
|
|
||||||
case "smarkets":
|
|
||||||
stars = calculateStarsSmarkets(data);
|
|
||||||
break;
|
|
||||||
case "wildeford":
|
|
||||||
stars = calculateStarsWildeford(data);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// deprecated
|
|
||||||
case "AstralCodexTen":
|
|
||||||
stars = calculateStarsAstralCodexTen(data);
|
|
||||||
break;
|
|
||||||
case "CoupCast":
|
|
||||||
stars = calculateStarsCoupCast(data);
|
|
||||||
break;
|
|
||||||
case "CSET-foretell":
|
|
||||||
stars = calculateStarsCSETForetell(data);
|
|
||||||
break;
|
|
||||||
case "Elicit":
|
|
||||||
stars = calculateStarsElicit(data);
|
|
||||||
break;
|
|
||||||
case "Estimize":
|
|
||||||
stars = calculateStarsEstimize(data);
|
|
||||||
break;
|
|
||||||
case "Hypermind":
|
|
||||||
stars = calculateStarsHypermind(data);
|
|
||||||
break;
|
|
||||||
case "Ladbrokes":
|
|
||||||
stars = calculateStarsLadbrokes(data);
|
|
||||||
break;
|
|
||||||
case "Omen":
|
|
||||||
stars = calculateStarsOmen(data);
|
|
||||||
break;
|
|
||||||
case "WilliamHill":
|
|
||||||
stars = calculateStarsWilliamHill(data);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
stars = 2;
|
|
||||||
}
|
|
||||||
return stars;
|
|
||||||
}
|
|
|
@ -1,26 +1,16 @@
|
||||||
/* Imports */
|
|
||||||
import textVersion from "textversionjs";
|
import textVersion from "textversionjs";
|
||||||
|
|
||||||
/* Definitions */
|
export default function toMarkdown(htmlText: string) {
|
||||||
String.prototype.replaceAll = function replaceAll(search, replace) {
|
|
||||||
return this.split(search).join(replace);
|
|
||||||
};
|
|
||||||
|
|
||||||
var styleConfig = {
|
|
||||||
linkProcess: function (href, linkText) {
|
|
||||||
let newHref = href ? href.replace(/\(/g, "%28").replace(/\)/g, "%29") : "";
|
|
||||||
// Deal corretly in markdown with links that contain parenthesis
|
|
||||||
return `[${linkText}](${newHref})`;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Support functions */
|
|
||||||
|
|
||||||
/* Body */
|
|
||||||
|
|
||||||
export default function toMarkdown(htmlText) {
|
|
||||||
let html2 = htmlText.replaceAll(`='`, `="`).replaceAll(`'>`, `">`);
|
let html2 = htmlText.replaceAll(`='`, `="`).replaceAll(`'>`, `">`);
|
||||||
return textVersion(html2, styleConfig);
|
return textVersion(html2, {
|
||||||
|
linkProcess: (href, linkText) => {
|
||||||
|
let newHref = href
|
||||||
|
? href.replace(/\(/g, "%28").replace(/\)/g, "%29")
|
||||||
|
: "";
|
||||||
|
// Deal correctly in markdown with links that contain parenthesis
|
||||||
|
return `[${linkText}](${newHref})`;
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// toMarkdown()
|
// toMarkdown()
|
||||||
|
|
22
src/common/types.ts
Normal file
22
src/common/types.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { QuestionFragment } from "../web/fragments.generated";
|
||||||
|
|
||||||
|
// this type is good both for backend (e.g. FetchedQuestion["options"]) and for graphql shapes
|
||||||
|
export type QuestionOption = {
|
||||||
|
name?: string;
|
||||||
|
probability?: number;
|
||||||
|
type: "PROBABILITY";
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FullQuestionOption = Exclude<
|
||||||
|
QuestionOption,
|
||||||
|
"name" | "probability"
|
||||||
|
> & {
|
||||||
|
name: NonNullable<QuestionOption["name"]>;
|
||||||
|
probability: NonNullable<QuestionOption["probability"]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isFullQuestionOption = (
|
||||||
|
option: QuestionOption | QuestionFragment["options"][0]
|
||||||
|
): option is FullQuestionOption => {
|
||||||
|
return option.name != null && option.probability != null;
|
||||||
|
};
|
|
@ -36,6 +36,7 @@ const DashboardObj = builder.objectRef<Dashboard>("Dashboard").implement({
|
||||||
builder.queryField("dashboard", (t) =>
|
builder.queryField("dashboard", (t) =>
|
||||||
t.field({
|
t.field({
|
||||||
type: DashboardObj,
|
type: DashboardObj,
|
||||||
|
nullable: true,
|
||||||
description: "Look up a single dashboard by its id",
|
description: "Look up a single dashboard by its id",
|
||||||
args: {
|
args: {
|
||||||
id: t.arg({ type: "ID", required: true }),
|
id: t.arg({ type: "ID", required: true }),
|
||||||
|
|
|
@ -140,6 +140,7 @@ builder.queryField("questions", (t) =>
|
||||||
builder.queryField("question", (t) =>
|
builder.queryField("question", (t) =>
|
||||||
t.field({
|
t.field({
|
||||||
type: QuestionObj,
|
type: QuestionObj,
|
||||||
|
nullable: true,
|
||||||
description: "Look up a single question by its id",
|
description: "Look up a single question by its id",
|
||||||
args: {
|
args: {
|
||||||
id: t.arg({ type: "ID", required: true }),
|
id: t.arg({ type: "ID", required: true }),
|
||||||
|
@ -149,7 +150,6 @@ builder.queryField("question", (t) =>
|
||||||
const [platform, id] = [parts[0], parts.slice(1).join("-")];
|
const [platform, id] = [parts[0], parts.slice(1).join("-")];
|
||||||
if (platform === "guesstimate") {
|
if (platform === "guesstimate") {
|
||||||
const q = await guesstimate.fetchQuestion(Number(id));
|
const q = await guesstimate.fetchQuestion(Number(id));
|
||||||
console.log(q);
|
|
||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
return await prisma.question.findUnique({
|
return await prisma.question.findUnique({
|
||||||
|
|
|
@ -32,19 +32,19 @@ builder.queryField("searchQuestions", (t) =>
|
||||||
// defs
|
// defs
|
||||||
const query = input.query === undefined ? "" : input.query;
|
const query = input.query === undefined ? "" : input.query;
|
||||||
if (query === "") return [];
|
if (query === "") return [];
|
||||||
const forecastsThreshold = input.forecastsThreshold;
|
const { forecastsThreshold, starsThreshold } = input;
|
||||||
const starsThreshold = input.starsThreshold;
|
|
||||||
const platformsIncludeGuesstimate =
|
const platformsIncludeGuesstimate =
|
||||||
input.forecastingPlatforms?.includes("guesstimate") &&
|
input.forecastingPlatforms?.includes("guesstimate") &&
|
||||||
starsThreshold <= 1;
|
(!starsThreshold || starsThreshold <= 1);
|
||||||
|
|
||||||
// preparation
|
// preparation
|
||||||
const unawaitedAlgoliaResponse = searchWithAlgolia({
|
const unawaitedAlgoliaResponse = searchWithAlgolia({
|
||||||
queryString: query,
|
queryString: query,
|
||||||
hitsPerPage: input.limit + 50,
|
hitsPerPage: input.limit ?? 50,
|
||||||
starsThreshold,
|
starsThreshold: starsThreshold ?? undefined,
|
||||||
filterByPlatforms: input.forecastingPlatforms,
|
filterByPlatforms: input.forecastingPlatforms ?? undefined,
|
||||||
forecastsThreshold,
|
forecastsThreshold: forecastsThreshold ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
let results: AlgoliaQuestion[] = [];
|
let results: AlgoliaQuestion[] = [];
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { NextApiRequest, NextApiResponse } from "next/types";
|
import { NextApiRequest, NextApiResponse } from "next/types";
|
||||||
import { runMePlease } from "squiggle-experimental/dist/index.js";
|
|
||||||
|
import { run } from "@quri/squiggle-lang";
|
||||||
|
|
||||||
export default async function handler(
|
export default async function handler(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
|
@ -24,6 +25,6 @@ $ curl -X POST -H "Content-Type: application/json" -d '{"model": "1 to 4"}'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log(body.model);
|
console.log(body.model);
|
||||||
res.status(200).send(runMePlease(body.model));
|
res.status(200).send(run(body.model));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { GetServerSideProps, NextPage } from "next";
|
import { GetServerSideProps, NextPage } from "next";
|
||||||
import Error from "next/error";
|
import NextError from "next/error";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DashboardByIdDocument, DashboardFragment
|
DashboardByIdDocument, DashboardFragment
|
||||||
|
@ -19,9 +19,13 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
const dashboardId = context.query.id as string;
|
const dashboardId = context.query.id as string;
|
||||||
const numCols = Number(context.query.numCols);
|
const numCols = Number(context.query.numCols);
|
||||||
|
|
||||||
const dashboard = (
|
const response = await client
|
||||||
await client.query(DashboardByIdDocument, { id: dashboardId }).toPromise()
|
.query(DashboardByIdDocument, { id: dashboardId })
|
||||||
).data?.result;
|
.toPromise();
|
||||||
|
if (!response.data) {
|
||||||
|
throw new Error(`GraphQL query failed: ${response.error}`);
|
||||||
|
}
|
||||||
|
const dashboard = response.data.result;
|
||||||
|
|
||||||
if (!dashboard) {
|
if (!dashboard) {
|
||||||
context.res.statusCode = 404;
|
context.res.statusCode = 404;
|
||||||
|
@ -32,14 +36,14 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
// reduntant: page component doesn't do graphql requests, but it's still nice/more consistent to have data in cache
|
// reduntant: page component doesn't do graphql requests, but it's still nice/more consistent to have data in cache
|
||||||
urqlState: ssrCache.extractData(),
|
urqlState: ssrCache.extractData(),
|
||||||
dashboard,
|
dashboard,
|
||||||
numCols: !numCols ? null : numCols < 5 ? numCols : 4,
|
numCols: !numCols ? undefined : numCols < 5 ? numCols : 4,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmbedDashboardPage: NextPage<Props> = ({ dashboard, numCols }) => {
|
const EmbedDashboardPage: NextPage<Props> = ({ dashboard, numCols }) => {
|
||||||
if (!dashboard) {
|
if (!dashboard) {
|
||||||
return <Error statusCode={404} />;
|
return <NextError statusCode={404} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -45,8 +45,11 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
const defaultNumDisplay = 21;
|
const defaultNumDisplay = 21;
|
||||||
const initialNumDisplay = Number(urlQuery.numDisplay) || defaultNumDisplay;
|
const initialNumDisplay = Number(urlQuery.numDisplay) || defaultNumDisplay;
|
||||||
|
|
||||||
const defaultResults = (await client.query(FrontpageDocument).toPromise())
|
const response = await client.query(FrontpageDocument).toPromise();
|
||||||
.data.result;
|
if (!response.data) {
|
||||||
|
throw new Error(`GraphQL query failed: ${response.error}`);
|
||||||
|
}
|
||||||
|
const defaultResults = response.data.result;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!!initialQueryParameters &&
|
!!initialQueryParameters &&
|
||||||
|
@ -58,7 +61,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
.query(SearchDocument, {
|
.query(SearchDocument, {
|
||||||
input: {
|
input: {
|
||||||
...initialQueryParameters,
|
...initialQueryParameters,
|
||||||
limit: initialNumDisplay,
|
limit: initialNumDisplay + 50,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.toPromise();
|
.toPromise();
|
||||||
|
|
|
@ -29,16 +29,19 @@ export const getServerSideProps: GetServerSideProps<Props> = async (
|
||||||
|
|
||||||
let results: QuestionFragment[] = [];
|
let results: QuestionFragment[] = [];
|
||||||
if (initialQueryParameters.query !== "") {
|
if (initialQueryParameters.query !== "") {
|
||||||
results = (
|
const response = await client
|
||||||
await client
|
.query(SearchDocument, {
|
||||||
.query(SearchDocument, {
|
input: {
|
||||||
input: {
|
...initialQueryParameters,
|
||||||
...initialQueryParameters,
|
limit: 1,
|
||||||
limit: 1,
|
},
|
||||||
},
|
})
|
||||||
})
|
.toPromise();
|
||||||
.toPromise()
|
if (response.data?.result) {
|
||||||
).data.result;
|
results = response.data.result;
|
||||||
|
} else {
|
||||||
|
throw new Error("GraphQL request failed");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -6,3 +6,6 @@ export const shuffleArray = <T>(array: T[]): T[] => {
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const average = (array: number[]) =>
|
||||||
|
array.reduce((a, b) => a + b, 0) / array.length;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Head from "next/head";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import React, { ErrorInfo } from "react";
|
import React, { ErrorInfo } from "react";
|
||||||
|
|
||||||
import { Logo2 } from "../icons/index";
|
import { Logo2 } from "../icons";
|
||||||
|
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
page: string;
|
page: string;
|
||||||
|
|
|
@ -87,7 +87,7 @@ export const MultiSelectPlatform: React.FC<Props> = ({
|
||||||
|
|
||||||
const selectValue = value.map((v) => id2option[v]).filter((v) => v);
|
const selectValue = value.map((v) => id2option[v]).filter((v) => v);
|
||||||
|
|
||||||
const onSelectChange = (newValue: Option[]) => {
|
const onSelectChange = (newValue: readonly Option[]) => {
|
||||||
onChange(newValue.map((o) => o.value));
|
onChange(newValue.map((o) => o.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { EventHandler, SyntheticEvent, useState } from "react";
|
import React, { ChangeEvent, EventHandler, SyntheticEvent, useState } from "react";
|
||||||
|
|
||||||
import { Button } from "../common/Button";
|
import { Button } from "../common/Button";
|
||||||
import { InfoBox } from "../common/InfoBox";
|
import { InfoBox } from "../common/InfoBox";
|
||||||
|
@ -18,7 +18,7 @@ export const DashboardCreator: React.FC<Props> = ({ handleSubmit }) => {
|
||||||
const [value, setValue] = useState(exampleInput);
|
const [value, setValue] = useState(exampleInput);
|
||||||
const [acting, setActing] = useState(false);
|
const [acting, setActing] = useState(false);
|
||||||
|
|
||||||
const handleChange = (event) => {
|
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setValue(event.target.value);
|
setValue(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,7 +37,9 @@ export const DashboardCreator: React.FC<Props> = ({ handleSubmit }) => {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setActing(false);
|
setActing(false);
|
||||||
const substituteText = `Error: ${error.message}
|
const substituteText = `Error: ${
|
||||||
|
error instanceof Error ? error.message : "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
Try something like:
|
Try something like:
|
||||||
${exampleInput}
|
${exampleInput}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* Imports */
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Handles, Rail, Slider, Tracks } from "react-compound-slider";
|
import {
|
||||||
|
GetHandleProps, GetTrackProps, Handles, Rail, Slider, SliderItem, Tracks
|
||||||
|
} from "react-compound-slider";
|
||||||
|
|
||||||
// https://sghall.github.io/react-compound-slider/#/getting-started/tutorial
|
// https://sghall.github.io/react-compound-slider/#/getting-started/tutorial
|
||||||
|
|
||||||
|
@ -24,12 +25,11 @@ const railStyle = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Support functions */
|
/* Support functions */
|
||||||
function Handle({
|
const Handle: React.FC<{
|
||||||
handle: { id, value, percent },
|
handle: SliderItem;
|
||||||
getHandleProps,
|
getHandleProps: GetHandleProps;
|
||||||
displayFunction,
|
displayFunction: (value: number) => string;
|
||||||
handleWidth,
|
}> = ({ handle: { id, value, percent }, getHandleProps, displayFunction }) => {
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="justify-center text-center text-gray-600 text-xs">
|
<div className="justify-center text-center text-gray-600 text-xs">
|
||||||
|
@ -53,9 +53,13 @@ function Handle({
|
||||||
></div>
|
></div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
function Track({ source, target, getTrackProps }) {
|
const Track: React.FC<{
|
||||||
|
source: SliderItem;
|
||||||
|
target: SliderItem;
|
||||||
|
getTrackProps: GetTrackProps;
|
||||||
|
}> = ({ source, target, getTrackProps }) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
@ -74,16 +78,15 @@ function Track({ source, target, getTrackProps }) {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: number;
|
value: number;
|
||||||
onChange: (event: any) => void;
|
onChange: (value: number) => void;
|
||||||
displayFunction: (value: number) => string;
|
displayFunction: (value: number) => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Body */
|
/* Body */
|
||||||
// Two functions, essentially identical.
|
|
||||||
export const SliderElement: React.FC<Props> = ({
|
export const SliderElement: React.FC<Props> = ({
|
||||||
onChange,
|
onChange,
|
||||||
value,
|
value,
|
||||||
|
@ -96,21 +99,20 @@ export const SliderElement: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
domain={[0, 200]}
|
domain={[0, 200]}
|
||||||
values={[value]}
|
values={[value]}
|
||||||
onChange={onChange}
|
onChange={(values) => onChange(values[0])}
|
||||||
>
|
>
|
||||||
<Rail>
|
<Rail>
|
||||||
{({ getRailProps }) => <div style={railStyle} {...getRailProps()} />}
|
{({ getRailProps }) => <div style={railStyle} {...getRailProps()} />}
|
||||||
</Rail>
|
</Rail>
|
||||||
<Handles>
|
<Handles>
|
||||||
{({ handles, getHandleProps }) => (
|
{({ handles, getHandleProps }) => (
|
||||||
<div className="slider-handles">
|
<div>
|
||||||
{handles.map((handle) => (
|
{handles.map((handle) => (
|
||||||
<Handle
|
<Handle
|
||||||
key={handle.id}
|
key={handle.id}
|
||||||
handle={handle}
|
handle={handle}
|
||||||
getHandleProps={getHandleProps}
|
getHandleProps={getHandleProps}
|
||||||
displayFunction={displayFunction}
|
displayFunction={displayFunction}
|
||||||
handleWidth={"15em"}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
@ -118,7 +120,7 @@ export const SliderElement: React.FC<Props> = ({
|
||||||
</Handles>
|
</Handles>
|
||||||
<Tracks right={false}>
|
<Tracks right={false}>
|
||||||
{({ tracks, getTrackProps }) => (
|
{({ tracks, getTrackProps }) => (
|
||||||
<div className="slider-tracks">
|
<div>
|
||||||
{tracks.map(({ id, source, target }) => (
|
{tracks.map(({ id, source, target }) => (
|
||||||
<Track
|
<Track
|
||||||
key={id}
|
key={id}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import * as React from "react";
|
export const Favicon: React.FC<React.SVGAttributes<SVGElement>> = (props) => {
|
||||||
|
|
||||||
function SvgFavicon(props) {
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@ -11,6 +9,4 @@ function SvgFavicon(props) {
|
||||||
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm198.4-588.1a32 32 0 00-24.5.5L414.9 415 296.4 686c-3.6 8.2-3.6 17.5 0 25.7 3.4 7.8 9.7 13.9 17.7 17 3.8 1.5 7.7 2.2 11.7 2.2 4.4 0 8.7-.9 12.8-2.7l271-118.6 118.5-271a32.06 32.06 0 00-17.7-42.7zM576.8 534.4l26.2 26.2-42.4 42.4-26.2-26.2L380 644.4 447.5 490 422 464.4l42.4-42.4 25.5 25.5L644.4 380l-67.6 154.4zM464.4 422L422 464.4l25.5 25.6 86.9 86.8 26.2 26.2 42.4-42.4-26.2-26.2-86.8-86.9z" />
|
<path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm198.4-588.1a32 32 0 00-24.5.5L414.9 415 296.4 686c-3.6 8.2-3.6 17.5 0 25.7 3.4 7.8 9.7 13.9 17.7 17 3.8 1.5 7.7 2.2 11.7 2.2 4.4 0 8.7-.9 12.8-2.7l271-118.6 118.5-271a32.06 32.06 0 00-17.7-42.7zM576.8 534.4l26.2 26.2-42.4 42.4-26.2-26.2L380 644.4 447.5 490 422 464.4l42.4-42.4 25.5 25.5L644.4 380l-67.6 154.4zM464.4 422L422 464.4l25.5 25.6 86.9 86.8 26.2 26.2 42.4-42.4-26.2-26.2-86.8-86.9z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SvgFavicon;
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import * as React from "react";
|
export const Logo: React.FC<React.SVGAttributes<SVGElement>> = (props) => {
|
||||||
|
|
||||||
function SvgLogo(props) {
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width={1333.333}
|
width={1333.333}
|
||||||
|
@ -76,6 +74,4 @@ function SvgLogo(props) {
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SvgLogo;
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import * as React from "react";
|
export const Logo2: React.FC<React.SVGAttributes<SVGElement>> = (props) => {
|
||||||
|
|
||||||
function SvgLogo2(props) {
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
@ -69,6 +67,4 @@ function SvgLogo2(props) {
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SvgLogo2;
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
export { default as Favicon } from "./Favicon";
|
export { Favicon } from "./Favicon";
|
||||||
export { default as Logo } from "./Logo";
|
export { Logo } from "./Logo";
|
||||||
export { default as Logo2 } from "./Logo2";
|
export { Logo2 } from "./Logo2";
|
||||||
|
|
|
@ -34,10 +34,12 @@ const getVictoryGroup = ({
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InnerChart: React.FC<{
|
export type Props = {
|
||||||
data: ChartData;
|
data: ChartData;
|
||||||
highlight: number | undefined;
|
highlight: number | undefined;
|
||||||
}> = ({
|
};
|
||||||
|
|
||||||
|
export const InnerChart: React.FC<Props> = ({
|
||||||
data: { maxProbability, seriesList, minDate, maxDate },
|
data: { maxProbability, seriesList, minDate, maxDate },
|
||||||
highlight,
|
highlight,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -120,7 +122,7 @@ export const InnerChart: React.FC<{
|
||||||
<VictoryAxis
|
<VictoryAxis
|
||||||
tickCount={Math.min(7, differenceInDays(maxDate, minDate) + 1)}
|
tickCount={Math.min(7, differenceInDays(maxDate, minDate) + 1)}
|
||||||
style={{
|
style={{
|
||||||
grid: { stroke: null, strokeWidth: 0.5 },
|
grid: { strokeWidth: 0.5 },
|
||||||
}}
|
}}
|
||||||
tickLabelComponent={
|
tickLabelComponent={
|
||||||
<VictoryLabel
|
<VictoryLabel
|
||||||
|
|
|
@ -12,15 +12,17 @@ const LegendItem: React.FC<{ item: Item; onHighlight: () => void }> = ({
|
||||||
onHighlight,
|
onHighlight,
|
||||||
}) => {
|
}) => {
|
||||||
const { x, y, reference, floating, strategy } = useFloating({
|
const { x, y, reference, floating, strategy } = useFloating({
|
||||||
// placement: "right",
|
|
||||||
middleware: [shift()],
|
middleware: [shift()],
|
||||||
});
|
});
|
||||||
|
|
||||||
const [showTooltip, setShowTooltip] = useState(false);
|
const [showTooltip, setShowTooltip] = useState(false);
|
||||||
const textRef = useRef<HTMLDivElement>();
|
const textRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const onHover = () => {
|
const onHover = () => {
|
||||||
if (textRef.current.scrollWidth > textRef.current.clientWidth) {
|
if (
|
||||||
|
textRef.current &&
|
||||||
|
textRef.current.scrollWidth > textRef.current.clientWidth
|
||||||
|
) {
|
||||||
setShowTooltip(true);
|
setShowTooltip(true);
|
||||||
}
|
}
|
||||||
onHighlight();
|
onHighlight();
|
||||||
|
|
|
@ -2,11 +2,12 @@ import dynamic from "next/dynamic";
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
|
|
||||||
import { QuestionWithHistoryFragment } from "../../../fragments.generated";
|
import { QuestionWithHistoryFragment } from "../../../fragments.generated";
|
||||||
|
import { Props as InnerChartProps } from "./InnerChart"; // hopefully doesn't import code, just types - need to check
|
||||||
import { InnerChartPlaceholder } from "./InnerChartPlaceholder";
|
import { InnerChartPlaceholder } from "./InnerChartPlaceholder";
|
||||||
import { Legend } from "./Legend";
|
import { Legend } from "./Legend";
|
||||||
import { buildChartData, chartColors } from "./utils";
|
import { buildChartData, chartColors } from "./utils";
|
||||||
|
|
||||||
const InnerChart = dynamic(
|
const InnerChart = dynamic<InnerChartProps>(
|
||||||
() => import("./InnerChart").then((mod) => mod.InnerChart),
|
() => import("./InnerChart").then((mod) => mod.InnerChart),
|
||||||
{ ssr: false, loading: () => <InnerChartPlaceholder /> }
|
{ ssr: false, loading: () => <InnerChartPlaceholder /> }
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { addDays, startOfDay, startOfToday, startOfTomorrow } from "date-fns";
|
import { addDays, startOfDay, startOfToday, startOfTomorrow } from "date-fns";
|
||||||
|
|
||||||
|
import { isFullQuestionOption } from "../../../../common/types";
|
||||||
import { QuestionWithHistoryFragment } from "../../../fragments.generated";
|
import { QuestionWithHistoryFragment } from "../../../fragments.generated";
|
||||||
|
import { isQuestionBinary } from "../../../utils";
|
||||||
|
|
||||||
export type ChartSeries = { x: Date; y: number; name: string }[];
|
export type ChartSeries = { x: Date; y: number; name: string }[];
|
||||||
|
|
||||||
|
@ -33,6 +35,7 @@ export const buildChartData = (
|
||||||
question: QuestionWithHistoryFragment
|
question: QuestionWithHistoryFragment
|
||||||
): ChartData => {
|
): ChartData => {
|
||||||
let seriesNames = question.options
|
let seriesNames = question.options
|
||||||
|
.filter(isFullQuestionOption)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
if (a.probability > b.probability) {
|
if (a.probability > b.probability) {
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -44,9 +47,7 @@ export const buildChartData = (
|
||||||
.map((o) => o.name)
|
.map((o) => o.name)
|
||||||
.slice(0, MAX_LINES);
|
.slice(0, MAX_LINES);
|
||||||
|
|
||||||
const isBinary =
|
const isBinary = isQuestionBinary(question);
|
||||||
(seriesNames[0] === "Yes" && seriesNames[1] === "No") ||
|
|
||||||
(seriesNames[0] === "No" && seriesNames[1] === "Yes");
|
|
||||||
if (isBinary) {
|
if (isBinary) {
|
||||||
seriesNames = ["Yes"];
|
seriesNames = ["Yes"];
|
||||||
}
|
}
|
||||||
|
@ -69,6 +70,9 @@ export const buildChartData = (
|
||||||
const date = new Date(item.timestamp * 1000);
|
const date = new Date(item.timestamp * 1000);
|
||||||
|
|
||||||
for (const option of item.options) {
|
for (const option of item.options) {
|
||||||
|
if (option.name == null || option.probability == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const idx = nameToIndex[option.name];
|
const idx = nameToIndex[option.name];
|
||||||
if (idx === undefined) {
|
if (idx === undefined) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -45,18 +45,18 @@ export const IndicatorsTable: React.FC<Props> = ({ question }) => (
|
||||||
) : null}
|
) : null}
|
||||||
{Object.keys(question.qualityIndicators)
|
{Object.keys(question.qualityIndicators)
|
||||||
.filter(
|
.filter(
|
||||||
(indicator) =>
|
(indicator): indicator is UsedIndicatorName =>
|
||||||
question.qualityIndicators[indicator] != null &&
|
(question.qualityIndicators as any)[indicator] != null &&
|
||||||
!!qualityIndicatorLabels[indicator]
|
indicator in qualityIndicatorLabels
|
||||||
)
|
)
|
||||||
.map((indicator: UsedIndicatorName) => {
|
.map((indicator) => {
|
||||||
return (
|
return (
|
||||||
<TableRow
|
<TableRow
|
||||||
title={qualityIndicatorLabels[indicator]}
|
title={qualityIndicatorLabels[indicator]}
|
||||||
key={indicator}
|
key={indicator}
|
||||||
>
|
>
|
||||||
{formatIndicatorValue(
|
{formatIndicatorValue(
|
||||||
question.qualityIndicators[indicator],
|
Number(question.qualityIndicators[indicator]), // must be non-null due to former check
|
||||||
indicator,
|
indicator,
|
||||||
question.platform.id
|
question.platform.id
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -30,13 +30,17 @@ export const qualityIndicatorLabels: { [k in UsedIndicatorName]: string } = {
|
||||||
openInterest: "Interest",
|
openInterest: "Interest",
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatNumber = (num) => {
|
const isUsedIndicatorName = (name: string): name is UsedIndicatorName => {
|
||||||
if (Number(num) < 1000) {
|
return name in qualityIndicatorLabels;
|
||||||
return Number(num).toFixed(0);
|
};
|
||||||
|
|
||||||
|
const formatNumber = (num: number) => {
|
||||||
|
if (num < 1000) {
|
||||||
|
return num.toFixed(0);
|
||||||
} else if (num < 10000) {
|
} else if (num < 10000) {
|
||||||
return (Number(num) / 1000).toFixed(1) + "k";
|
return (num / 1000).toFixed(1) + "k";
|
||||||
} else {
|
} else {
|
||||||
return (Number(num) / 1000).toFixed(0) + "k";
|
return (num / 1000).toFixed(0) + "k";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,7 +104,7 @@ const FirstQualityIndicator: React.FC<{
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatIndicatorValue = (
|
export const formatIndicatorValue = (
|
||||||
value: any,
|
value: number,
|
||||||
indicator: UsedIndicatorName,
|
indicator: UsedIndicatorName,
|
||||||
platform: string
|
platform: string
|
||||||
): string => {
|
): string => {
|
||||||
|
@ -119,21 +123,26 @@ const QualityIndicatorsList: React.FC<{
|
||||||
return (
|
return (
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
<FirstQualityIndicator question={question} />
|
<FirstQualityIndicator question={question} />
|
||||||
{Object.entries(question.qualityIndicators).map((entry, i) => {
|
{Object.entries(question.qualityIndicators).map(
|
||||||
const indicatorLabel = qualityIndicatorLabels[entry[0]];
|
([indicator, value], i) => {
|
||||||
if (!indicatorLabel || entry[1] === null) return;
|
if (!isUsedIndicatorName(indicator)) return;
|
||||||
const indicator = entry[0] as UsedIndicatorName; // guaranteed by the previous line
|
const indicatorLabel = qualityIndicatorLabels[indicator];
|
||||||
const value = entry[1];
|
if (!indicatorLabel || value === null) return;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={indicator}>
|
<div key={indicator}>
|
||||||
<span>{indicatorLabel}:</span>
|
<span>{indicatorLabel}:</span>
|
||||||
<span className="font-bold">
|
<span className="font-bold">
|
||||||
{formatIndicatorValue(value, indicator, question.platform.id)}
|
{formatIndicatorValue(
|
||||||
</span>
|
Number(value),
|
||||||
</div>
|
indicator,
|
||||||
);
|
question.platform.id
|
||||||
})}
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,62 +13,25 @@ const truncateText = (length: number, text: string): string => {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
if (!!text && text.length <= length) {
|
if (text.length <= length) {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
const breakpoints = " .!?";
|
const breakpoints = " .!?";
|
||||||
let lastLetter = null;
|
let lastLetter: string | undefined = undefined;
|
||||||
let lastIndex = null;
|
let lastIndex: number | undefined = undefined;
|
||||||
for (let index = length; index > 0; index--) {
|
for (let index = length; index > 0; index--) {
|
||||||
let letter = text[index];
|
const letter = text[index];
|
||||||
if (breakpoints.includes(letter)) {
|
if (breakpoints.includes(letter)) {
|
||||||
lastLetter = letter;
|
lastLetter = letter;
|
||||||
lastIndex = index;
|
lastIndex = index;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let truncatedText = !!text.slice
|
let truncatedText =
|
||||||
? text.slice(0, lastIndex) + (lastLetter != "." ? "..." : "..")
|
text.slice(0, lastIndex) + (lastLetter != "." ? "..." : "..");
|
||||||
: "";
|
|
||||||
return truncatedText;
|
return truncatedText;
|
||||||
};
|
};
|
||||||
|
|
||||||
// replaceAll polyfill
|
|
||||||
function escapeRegExp(string) {
|
|
||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
|
|
||||||
}
|
|
||||||
|
|
||||||
function replaceAll(
|
|
||||||
originalString: string,
|
|
||||||
pattern: string | RegExp,
|
|
||||||
substitute
|
|
||||||
) {
|
|
||||||
return originalString.replace(
|
|
||||||
new RegExp(escapeRegExp(pattern), "g"),
|
|
||||||
substitute
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!String.prototype.replaceAll) {
|
|
||||||
String.prototype.replaceAll = function (
|
|
||||||
pattern: string | RegExp,
|
|
||||||
substitute
|
|
||||||
) {
|
|
||||||
let originalString = this;
|
|
||||||
|
|
||||||
// If a regex pattern
|
|
||||||
if (
|
|
||||||
Object.prototype.toString.call(pattern).toLowerCase() ===
|
|
||||||
"[object regexp]"
|
|
||||||
) {
|
|
||||||
return originalString.replace(pattern, substitute);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a string
|
|
||||||
return replaceAll(originalString, pattern, substitute);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auxiliary components
|
// Auxiliary components
|
||||||
|
|
||||||
const DisplayMarkdown: React.FC<{ description: string }> = ({
|
const DisplayMarkdown: React.FC<{ description: string }> = ({
|
||||||
|
@ -153,14 +116,14 @@ export const QuestionCard: React.FC<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
{isBinary ? (
|
{isBinary ? (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<QuestionOptions options={options} />
|
<QuestionOptions question={question} />
|
||||||
<div className={`hidden ${showTimeStamp ? "sm:block" : ""}`}>
|
<div className={`hidden ${showTimeStamp ? "sm:block" : ""}`}>
|
||||||
<LastUpdated timestamp={lastUpdated} />
|
<LastUpdated timestamp={lastUpdated} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<QuestionOptions options={options} />
|
<QuestionOptions question={question} />
|
||||||
<div className={`hidden ${showTimeStamp ? "sm:block" : ""} ml-6`}>
|
<div className={`hidden ${showTimeStamp ? "sm:block" : ""} ml-6`}>
|
||||||
<LastUpdated timestamp={lastUpdated} />
|
<LastUpdated timestamp={lastUpdated} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -173,7 +136,7 @@ export const QuestionCard: React.FC<Props> = ({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{question.platform.id === "guesstimate" && (
|
{question.platform.id === "guesstimate" && question.visualization && (
|
||||||
<img
|
<img
|
||||||
className="rounded-sm"
|
className="rounded-sm"
|
||||||
src={question.visualization}
|
src={question.visualization}
|
||||||
|
|
|
@ -1,227 +0,0 @@
|
||||||
import { QuestionFragment } from "../../fragments.generated";
|
|
||||||
|
|
||||||
type QualityIndicator = QuestionFragment["qualityIndicators"];
|
|
||||||
type IndicatorName = keyof QualityIndicator;
|
|
||||||
|
|
||||||
// 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";
|
|
||||||
|
|
||||||
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) => {
|
|
||||||
if (Number(num) < 1000) {
|
|
||||||
return Number(num).toFixed(0);
|
|
||||||
} else if (num < 10000) {
|
|
||||||
return (Number(num) / 1000).toFixed(1) + "k";
|
|
||||||
} else {
|
|
||||||
return (Number(num) / 1000).toFixed(0) + "k";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Display functions*/
|
|
||||||
|
|
||||||
const getPercentageSymbolIfNeeded = ({
|
|
||||||
indicator,
|
|
||||||
platform,
|
|
||||||
}: {
|
|
||||||
indicator: UsedIndicatorName;
|
|
||||||
platform: string;
|
|
||||||
}) => {
|
|
||||||
let indicatorsWhichNeedPercentageSymbol: IndicatorName[] = ["spread"];
|
|
||||||
if (indicatorsWhichNeedPercentageSymbol.includes(indicator)) {
|
|
||||||
return "%";
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCurrencySymbolIfNeeded = ({
|
|
||||||
indicator,
|
|
||||||
platform,
|
|
||||||
}: {
|
|
||||||
indicator: UsedIndicatorName;
|
|
||||||
platform: string;
|
|
||||||
}) => {
|
|
||||||
const indicatorsWhichNeedCurrencySymbol: IndicatorName[] = [
|
|
||||||
"volume",
|
|
||||||
"tradeVolume",
|
|
||||||
"openInterest",
|
|
||||||
"liquidity",
|
|
||||||
];
|
|
||||||
let dollarPlatforms = ["predictit", "kalshi", "polymarket"];
|
|
||||||
if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) {
|
|
||||||
if (dollarPlatforms.includes(platform)) {
|
|
||||||
return "$";
|
|
||||||
} else {
|
|
||||||
return "£";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const FirstQualityIndicator: React.FC<{
|
|
||||||
question: QuestionFragment;
|
|
||||||
}> = ({ question }) => {
|
|
||||||
if (question.qualityIndicators.numForecasts) {
|
|
||||||
return (
|
|
||||||
<div className="flex">
|
|
||||||
<span>Forecasts:</span>
|
|
||||||
<span className="font-bold">
|
|
||||||
{Number(question.qualityIndicators.numForecasts).toFixed(0)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const QualityIndicatorsList: React.FC<{
|
|
||||||
question: QuestionFragment;
|
|
||||||
}> = ({ question }) => {
|
|
||||||
return (
|
|
||||||
<div className="text-sm">
|
|
||||||
<FirstQualityIndicator question={question} />
|
|
||||||
{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 (
|
|
||||||
<div key={indicator}>
|
|
||||||
<span>{indicatorLabel}:</span>
|
|
||||||
<span className="font-bold">
|
|
||||||
{`${getCurrencySymbolIfNeeded({
|
|
||||||
indicator,
|
|
||||||
platform: question.platform.id,
|
|
||||||
})}${formatNumber(value)}${getPercentageSymbolIfNeeded({
|
|
||||||
indicator,
|
|
||||||
platform: question.platform.id,
|
|
||||||
})}`}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Database-like functions
|
|
||||||
export function getstars(numstars: number) {
|
|
||||||
let stars = "★★☆☆☆";
|
|
||||||
switch (numstars) {
|
|
||||||
case 0:
|
|
||||||
stars = "☆☆☆☆☆";
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
stars = "★☆☆☆☆";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
stars = "★★☆☆☆";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
stars = "★★★☆☆";
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
stars = "★★★★☆";
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
stars = "★★★★★";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
stars = "★★☆☆☆";
|
|
||||||
}
|
|
||||||
return stars;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStarsColor(numstars: number) {
|
|
||||||
let color = "text-yellow-400";
|
|
||||||
switch (numstars) {
|
|
||||||
case 0:
|
|
||||||
color = "text-red-400";
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
color = "text-red-400";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
color = "text-orange-400";
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
color = "text-yellow-400";
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
color = "text-green-400";
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
color = "text-blue-400";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
color = "text-yellow-400";
|
|
||||||
}
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
question: QuestionFragment;
|
|
||||||
expandFooterToFullWidth: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const QuestionFooter: React.FC<Props> = ({
|
|
||||||
question,
|
|
||||||
expandFooterToFullWidth,
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`grid grid-cols-3 ${
|
|
||||||
expandFooterToFullWidth ? "justify-between" : ""
|
|
||||||
} text-gray-500 mb-2 mt-1`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`self-center col-span-1 ${getStarsColor(
|
|
||||||
question.qualityIndicators.stars
|
|
||||||
)}`}
|
|
||||||
>
|
|
||||||
{getstars(question.qualityIndicators.stars)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
expandFooterToFullWidth ? "place-self-center" : "self-center"
|
|
||||||
} col-span-1 font-bold`}
|
|
||||||
>
|
|
||||||
{question.platform.label
|
|
||||||
.replace("Good Judgment Open", "GJOpen")
|
|
||||||
.replace(/ /g, "\u00a0")}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`${
|
|
||||||
expandFooterToFullWidth
|
|
||||||
? "justify-self-end mr-4"
|
|
||||||
: "justify-self-center"
|
|
||||||
} col-span-1`}
|
|
||||||
>
|
|
||||||
<QualityIndicatorsList question={question} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
import { FullQuestionOption, isFullQuestionOption } from "../../../common/types";
|
||||||
import { QuestionFragment } from "../../fragments.generated";
|
import { QuestionFragment } from "../../fragments.generated";
|
||||||
|
import { isQuestionBinary } from "../../utils";
|
||||||
import { formatProbability } from "../utils";
|
import { formatProbability } from "../utils";
|
||||||
|
|
||||||
type Option = QuestionFragment["options"][0];
|
|
||||||
|
|
||||||
const textColor = (probability: number) => {
|
const textColor = (probability: number) => {
|
||||||
if (probability < 0.03) {
|
if (probability < 0.03) {
|
||||||
return "text-red-600";
|
return "text-red-600";
|
||||||
|
@ -89,7 +89,7 @@ const chooseColor = (probability: number) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const OptionRow: React.FC<{ option: Option }> = ({ option }) => {
|
const OptionRow: React.FC<{ option: FullQuestionOption }> = ({ option }) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div
|
<div
|
||||||
|
@ -106,15 +106,19 @@ const OptionRow: React.FC<{ option: Option }> = ({ option }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const QuestionOptions: React.FC<{ options: Option[] }> = ({
|
export const QuestionOptions: React.FC<{ question: QuestionFragment }> = ({
|
||||||
options,
|
question,
|
||||||
}) => {
|
}) => {
|
||||||
const isBinary =
|
const isBinary = isQuestionBinary(question);
|
||||||
options.length === 2 &&
|
|
||||||
(options[0].name === "Yes" || options[0].name === "No");
|
|
||||||
|
|
||||||
if (isBinary) {
|
if (isBinary) {
|
||||||
const yesOption = options.find((o) => o.name === "Yes");
|
const yesOption = question.options.find((o) => o.name === "Yes");
|
||||||
|
if (!yesOption) {
|
||||||
|
return null; // shouldn't happen
|
||||||
|
}
|
||||||
|
if (!isFullQuestionOption(yesOption)) {
|
||||||
|
return null; // missing data
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<span
|
<span
|
||||||
|
@ -134,8 +138,11 @@ export const QuestionOptions: React.FC<{ options: Option[] }> = ({
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const optionsSorted = options.sort((a, b) => b.probability - a.probability);
|
const optionsSorted = question.options
|
||||||
const optionsMax5 = !!optionsSorted.slice ? optionsSorted.slice(0, 5) : []; // display max 5 options.
|
.filter(isFullQuestionOption)
|
||||||
|
.sort((a, b) => b.probability - a.probability);
|
||||||
|
|
||||||
|
const optionsMax5 = optionsSorted.slice(0, 5); // display max 5 options.
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
|
|
@ -74,7 +74,7 @@ const LargeQuestionCard: React.FC<{
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
{question.platform.id === "guesstimate" ? (
|
{question.platform.id === "guesstimate" && question.visualization ? (
|
||||||
<a className="no-underline" href={question.url} target="_blank">
|
<a className="no-underline" href={question.url} target="_blank">
|
||||||
<img
|
<img
|
||||||
className="rounded-sm"
|
className="rounded-sm"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { ChangeEvent } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
onChange: (v: string) => void;
|
onChange: (v: string) => void;
|
||||||
|
@ -9,7 +11,7 @@ export const QueryForm: React.FC<Props> = ({
|
||||||
onChange,
|
onChange,
|
||||||
placeholder,
|
placeholder,
|
||||||
}) => {
|
}) => {
|
||||||
const handleInputChange = (event) => {
|
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onChange(event.target.value); // In this case, the query, e.g. "COVID.19"
|
onChange(event.target.value); // In this case, the query, e.g. "COVID.19"
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React, { Fragment, useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useQuery } from "urql";
|
import { useQuery } from "urql";
|
||||||
|
|
||||||
import { PlatformConfig } from "../../../backend/platforms";
|
import { PlatformConfig } from "../../../backend/platforms";
|
||||||
|
@ -61,7 +61,7 @@ export const SearchScreen: React.FC<Props> = ({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
...queryParameters,
|
...queryParameters,
|
||||||
limit: numDisplay,
|
limit: numDisplay + 50,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pause: !isFirstRender,
|
pause: !isFirstRender,
|
||||||
|
@ -126,7 +126,8 @@ export const SearchScreen: React.FC<Props> = ({
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateRoute = () => {
|
const updateRoute = () => {
|
||||||
const stringify = (key: string, value: any) => {
|
const stringify = (key: string, obj: { [k: string]: any }) => {
|
||||||
|
const value = obj[key];
|
||||||
if (key === "forecastingPlatforms") {
|
if (key === "forecastingPlatforms") {
|
||||||
return value.join("|");
|
return value.join("|");
|
||||||
} else {
|
} else {
|
||||||
|
@ -134,15 +135,16 @@ export const SearchScreen: React.FC<Props> = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const query = {};
|
const query: { [k: string]: string } = {};
|
||||||
for (const key of Object.keys(defaultQueryParameters)) {
|
for (const key of Object.keys(defaultQueryParameters)) {
|
||||||
const value = stringify(key, queryParameters[key]);
|
const value = stringify(key, queryParameters);
|
||||||
const defaultValue = stringify(key, defaultQueryParameters[key]);
|
const defaultValue = stringify(key, defaultQueryParameters);
|
||||||
if (value === defaultValue) continue;
|
if (value === defaultValue) continue;
|
||||||
query[key] = value;
|
query[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numDisplay !== defaultNumDisplay) query["numDisplay"] = numDisplay;
|
if (numDisplay !== defaultNumDisplay)
|
||||||
|
query["numDisplay"] = String(numDisplay);
|
||||||
|
|
||||||
router.replace(
|
router.replace(
|
||||||
{
|
{
|
||||||
|
@ -191,8 +193,8 @@ export const SearchScreen: React.FC<Props> = ({
|
||||||
(Math.round(value) === 1 ? "" : "s")
|
(Math.round(value) === 1 ? "" : "s")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const onChangeSliderForNumDisplay = (event) => {
|
const onChangeSliderForNumDisplay = (value: number) => {
|
||||||
setNumDisplay(Math.round(event[0]));
|
setNumDisplay(Math.round(value));
|
||||||
setForceSearch(forceSearch + 1); // FIXME - force new search iff numDisplay is greater than last search limit
|
setForceSearch(forceSearch + 1); // FIXME - force new search iff numDisplay is greater than last search limit
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -200,10 +202,10 @@ export const SearchScreen: React.FC<Props> = ({
|
||||||
const displayFunctionNumForecasts = (value: number) => {
|
const displayFunctionNumForecasts = (value: number) => {
|
||||||
return "# Forecasts > " + Math.round(value);
|
return "# Forecasts > " + Math.round(value);
|
||||||
};
|
};
|
||||||
const onChangeSliderForNumForecasts = (event) => {
|
const onChangeSliderForNumForecasts = (value: number) => {
|
||||||
setQueryParameters({
|
setQueryParameters({
|
||||||
...queryParameters,
|
...queryParameters,
|
||||||
forecastsThreshold: Math.round(event[0]),
|
forecastsThreshold: Math.round(value),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -230,7 +232,7 @@ export const SearchScreen: React.FC<Props> = ({
|
||||||
|
|
||||||
/* Final return */
|
/* Final return */
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<>
|
||||||
<label className="mb-4 mt-4 flex flex-row justify-center items-center">
|
<label className="mb-4 mt-4 flex flex-row justify-center items-center">
|
||||||
<div className="w-10/12 mb-2">
|
<div className="w-10/12 mb-2">
|
||||||
<QueryForm
|
<QueryForm
|
||||||
|
@ -317,6 +319,6 @@ export const SearchScreen: React.FC<Props> = ({
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</Fragment>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,5 +36,8 @@ export const getUrqlClientOptions = (ssr: SSRExchange) => ({
|
||||||
export const ssrUrql = () => {
|
export const ssrUrql = () => {
|
||||||
const ssrCache = ssrExchange({ isClient: false });
|
const ssrCache = ssrExchange({ isClient: false });
|
||||||
const client = initUrqlClient(getUrqlClientOptions(ssrCache), false);
|
const client = initUrqlClient(getUrqlClientOptions(ssrCache), false);
|
||||||
|
if (!client) {
|
||||||
|
throw new Error("Expected non-null client instance from initUrqlClient");
|
||||||
|
}
|
||||||
return [ssrCache, client] as const;
|
return [ssrCache, client] as const;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { QuestionFragment } from "./fragments.generated";
|
||||||
|
|
||||||
export const getBasePath = () => {
|
export const getBasePath = () => {
|
||||||
if (process.env.NEXT_PUBLIC_VERCEL_URL) {
|
if (process.env.NEXT_PUBLIC_VERCEL_URL) {
|
||||||
return `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`;
|
return `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`;
|
||||||
|
@ -29,3 +31,12 @@ export const cleanText = (text: string): string => {
|
||||||
//console.log(textString)
|
//console.log(textString)
|
||||||
return textString;
|
return textString;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isQuestionBinary = (question: QuestionFragment): boolean => {
|
||||||
|
const { options } = question;
|
||||||
|
return (
|
||||||
|
options.length === 2 &&
|
||||||
|
((options[0].name === "Yes" && options[1].name === "No") ||
|
||||||
|
(options[0].name === "No" && options[1].name === "Yes"))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -1,18 +1,31 @@
|
||||||
import algoliasearch from "algoliasearch";
|
import algoliasearch from "algoliasearch";
|
||||||
|
|
||||||
|
import { Hit } from "@algolia/client-search";
|
||||||
|
|
||||||
import { AlgoliaQuestion } from "../../backend/utils/algolia";
|
import { AlgoliaQuestion } from "../../backend/utils/algolia";
|
||||||
|
|
||||||
const client = algoliasearch(
|
const client = algoliasearch(
|
||||||
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID,
|
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || "",
|
||||||
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY
|
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY || ""
|
||||||
);
|
);
|
||||||
const index = client.initIndex("metaforecast");
|
const index = client.initIndex("metaforecast");
|
||||||
|
|
||||||
let buildFilter = ({
|
interface SearchOpts {
|
||||||
|
queryString: string;
|
||||||
|
hitsPerPage?: number;
|
||||||
|
starsThreshold?: number;
|
||||||
|
filterByPlatforms?: string[];
|
||||||
|
forecastsThreshold?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildFilter = ({
|
||||||
starsThreshold,
|
starsThreshold,
|
||||||
filterByPlatforms,
|
filterByPlatforms,
|
||||||
forecastsThreshold,
|
forecastsThreshold,
|
||||||
}) => {
|
}: Pick<
|
||||||
|
SearchOpts,
|
||||||
|
"starsThreshold" | "filterByPlatforms" | "forecastsThreshold"
|
||||||
|
>) => {
|
||||||
const starsFilter = starsThreshold
|
const starsFilter = starsThreshold
|
||||||
? `qualityindicators.stars >= ${starsThreshold}`
|
? `qualityindicators.stars >= ${starsThreshold}`
|
||||||
: null;
|
: null;
|
||||||
|
@ -20,7 +33,7 @@ let buildFilter = ({
|
||||||
? filterByPlatforms.map((platform) => `platform:"${platform}"`).join(" OR ")
|
? filterByPlatforms.map((platform) => `platform:"${platform}"`).join(" OR ")
|
||||||
: null;
|
: null;
|
||||||
const numForecastsFilter =
|
const numForecastsFilter =
|
||||||
forecastsThreshold > 0
|
forecastsThreshold && forecastsThreshold > 0
|
||||||
? `qualityindicators.numforecasts >= ${forecastsThreshold}`
|
? `qualityindicators.numforecasts >= ${forecastsThreshold}`
|
||||||
: null;
|
: null;
|
||||||
const finalFilter = [starsFilter, platformsFilter, numForecastsFilter]
|
const finalFilter = [starsFilter, platformsFilter, numForecastsFilter]
|
||||||
|
@ -35,26 +48,15 @@ let buildFilter = ({
|
||||||
return finalFilter;
|
return finalFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
let buildFacetFilter = ({ filterByPlatforms }) => {
|
const noExactMatch = (queryString: string, result: Hit<AlgoliaQuestion>) => {
|
||||||
let platformsFilter = [];
|
|
||||||
if (filterByPlatforms.length > 0) {
|
|
||||||
platformsFilter = [
|
|
||||||
[filterByPlatforms.map((platform) => `platform:${platform}`)],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
console.log(platformsFilter);
|
|
||||||
console.log(
|
|
||||||
"searchWithAlgolia.js/searchWithAlgolia/buildFacetFilter",
|
|
||||||
platformsFilter
|
|
||||||
);
|
|
||||||
return platformsFilter;
|
|
||||||
};
|
|
||||||
|
|
||||||
let noExactMatch = (queryString, result) => {
|
|
||||||
queryString = queryString.toLowerCase();
|
queryString = queryString.toLowerCase();
|
||||||
let title = result.title.toLowerCase();
|
|
||||||
let description = result.description.toLowerCase();
|
const title = result.title.toLowerCase();
|
||||||
let optionsstringforsearch = result.optionsstringforsearch.toLowerCase();
|
const description = result.description.toLowerCase();
|
||||||
|
const optionsstringforsearch = (
|
||||||
|
result.optionsstringforsearch || ""
|
||||||
|
).toLowerCase();
|
||||||
|
|
||||||
return !(
|
return !(
|
||||||
title.includes(queryString) ||
|
title.includes(queryString) ||
|
||||||
description.includes(queryString) ||
|
description.includes(queryString) ||
|
||||||
|
@ -62,14 +64,6 @@ let noExactMatch = (queryString, result) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
interface SearchOpts {
|
|
||||||
queryString: string;
|
|
||||||
hitsPerPage?: number;
|
|
||||||
starsThreshold: number;
|
|
||||||
filterByPlatforms: string[];
|
|
||||||
forecastsThreshold: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// only query string
|
// only query string
|
||||||
export default async function searchWithAlgolia({
|
export default async function searchWithAlgolia({
|
||||||
queryString,
|
queryString,
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"dom.iterable",
|
"dom.iterable",
|
||||||
"esnext"
|
"esnext"
|
||||||
],
|
],
|
||||||
"strict": false,
|
"strict": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user