Merge pull request #69 from quantified-uncertainty/more-prisma

refactor: prisma everywhere, drop unused columns and tables
This commit is contained in:
Vyacheslav Matyukhin 2022-04-23 22:48:58 +03:00 committed by GitHub
commit 621af946b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 181 additions and 541 deletions

View File

@ -0,0 +1,12 @@
/*
Warnings:
- You are about to drop the column `stars` on the `history` table. All the data in the column will be lost.
- You are about to drop the column `stars` on the `questions` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "history" DROP COLUMN "stars";
-- AlterTable
ALTER TABLE "questions" DROP COLUMN "stars";

View File

@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the `frontpage` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropTable
DROP TABLE "frontpage";

View File

@ -23,14 +23,6 @@ model Dashboard {
@@map("dashboards") @@map("dashboards")
} }
model Frontpage {
id Int @id @default(autoincrement())
frontpage_full Json
frontpage_sliced Json
@@map("frontpage")
}
model History { model History {
id String id String
title String title String
@ -39,7 +31,6 @@ model History {
description String description String
options Json options Json
timestamp DateTime @db.Timestamp(6) timestamp DateTime @db.Timestamp(6)
stars Int
qualityindicators Json qualityindicators Json
extra Json extra Json
pk Int @id @default(autoincrement()) pk Int @id @default(autoincrement())
@ -49,14 +40,37 @@ model History {
} }
model Question { model Question {
/// E.g. "fantasyscotus-580"
id String @id id String @id
/// E.g. "In Wooden v. U.S., the SCOTUS will affirm the lower court's decision"
title String title String
/// E.g. "https://fantasyscotus.net/user-predictions/case/wooden-v-us/"
url String url String
/// E.g. "fantasyscotus"
platform String platform String
/// E.g. "62.50% (75 out of 120) of FantasySCOTUS players predict that the lower court's decision will be affirmed. FantasySCOTUS overall predicts an outcome of Affirm 6-3. Historically, FantasySCOTUS has chosen the correct side 50.00% of the time."
description String description String
// E.g.:
// [
// {
// "name": "Yes",
// "probability": 0.625,
// "type": "PROBABILITY"
// },
// {
// "name": "No",
// "probability": 0.375,
// "type": "PROBABILITY"
// }
// ]
options Json options Json
timestamp DateTime @db.Timestamp(6) timestamp DateTime @db.Timestamp(6)
stars Int
// {
// "numforecasts": 120,
// "stars": 2
// }
qualityindicators Json qualityindicators Json
extra Json extra Json

View File

@ -1,163 +0,0 @@
import { Pool, PoolClient } from "pg";
import { Question } from "../platforms";
import { measureTime } from "../utils/measureTime";
import { roughSizeOfObject } from "../utils/roughSize";
const questionTableNames = ["questions", "history"];
const allTableNames = [...questionTableNames, "dashboards", "frontpage"];
/* Postgres database connection code */
const databaseURL = process.env.DIGITALOCEAN_POSTGRES;
export const pool = new Pool({
connectionString: databaseURL,
ssl: process.env.POSTGRES_NO_SSL
? false
: {
rejectUnauthorized: false,
},
});
// Read
export async function pgRead({ tableName }: { tableName: string }) {
if (!allTableNames.includes(tableName)) {
throw Error(
`Table ${tableName} not in whitelist; stopping to avoid tricky sql injections`
);
}
let command = `SELECT * from ${tableName}`;
return (await pool.query(command)).rows;
}
export async function pgBulkInsert({
data,
tableName,
client,
}: {
data: Question[];
tableName: string;
client: PoolClient;
}) {
if (!questionTableNames.includes(tableName)) {
throw Error(
`Table ${tableName} not in whitelist; stopping to avoid tricky sql injections`
);
}
const generateQuery = (rows: number) => {
let text = `INSERT INTO ${tableName} VALUES`;
const cols = 10;
const parts: string[] = [];
for (let r = 0; r < rows; r++) {
const bits = [];
for (let c = 1; c <= cols; c++) {
bits.push(`$${cols * r + c}`);
}
parts.push("(" + bits.join(", ") + ")");
}
text += parts.join(", ");
return text;
};
let from = 0;
const chunkSize = 20;
while (from < data.length - 1) {
const take = Math.min(chunkSize, data.length - from);
const query = generateQuery(take);
const chunk = [];
for (let i = from; i < from + take; i++) {
const datum = data[i];
let timestamp =
datum.timestamp &&
!!datum.timestamp.slice &&
!isNaN(Date.parse(datum.timestamp))
? datum.timestamp
: new Date().toISOString();
timestamp = timestamp.slice(0, 19).replace("T", " ");
const values = [
datum.id,
datum.title,
datum.url,
datum.platform,
datum.description || "",
JSON.stringify(datum.options || []),
timestamp, // fix
datum.stars ||
(datum.qualityindicators ? datum.qualityindicators.stars : 2),
JSON.stringify(datum.qualityindicators || []),
JSON.stringify(datum.extra || []),
];
chunk.push(...values);
}
console.log(`Inserting ${from + 1}..${from + take}`);
from += take;
await client.query(query, chunk);
}
}
export async function pgUpsert({
contents,
tableName,
replacePlatform,
}: {
contents: Question[];
tableName: string;
replacePlatform?: string;
}) {
if (!questionTableNames.includes(tableName)) {
throw Error(
`Table ${tableName} not in whitelist; stopping to avoid tricky sql injections`
);
}
await measureTime(async () => {
const client = await pool.connect();
try {
await client.query("BEGIN");
if (replacePlatform) {
await client.query(`DELETE FROM ${tableName} WHERE platform = $1`, [
replacePlatform,
]);
}
console.log(
`Upserting ${contents.length} rows into postgres table ${tableName}.`
);
await pgBulkInsert({ data: contents, tableName, client });
console.log(
`Inserted ${
contents.length
} rows with approximate cummulative size ${roughSizeOfObject(
contents
)} MB into ${tableName}.`
);
console.log("Sample: ");
console.log(
JSON.stringify(
// only show the first three options
contents.slice(0, 1).map((question) => ({
...question,
options: question.options
? question.options.length > 3
? question.options.slice(0, 3).concat("...")
: question.options
: null,
})),
null,
4
)
);
await client.query("COMMIT");
} catch (e) {
await client.query("ROLLBACK");
throw e;
} finally {
client.release();
}
});
}

View File

@ -1,9 +1,8 @@
import { pgRead, pgUpsert } from "../../database/pg-wrapper"; import { prisma } from "../../database/prisma";
export async function updateHistory() { export async function updateHistory() {
let latest = await pgRead({ tableName: "questions" }); const questions = await prisma.question.findMany({});
await pgUpsert({ await prisma.history.createMany({
contents: latest, data: questions,
tableName: "history",
}); });
} }

View File

@ -2,10 +2,10 @@ import "dotenv/config";
import fs from "fs"; import fs from "fs";
import { pgRead } from "../database/pg-wrapper"; import { prisma } from "../database/prisma";
let main = async () => { let main = async () => {
let json = await pgRead({ tableName: "questions" }); let json = await prisma.question.findMany({});
let string = JSON.stringify(json, null, 2); let string = JSON.stringify(json, null, 2);
let filename = "metaforecasts.json"; let filename = "metaforecasts.json";
fs.writeFileSync(filename, string); fs.writeFileSync(filename, string);

View File

@ -1,92 +0,0 @@
import "dotenv/config";
import { pool } from "../database/pg-wrapper";
const migrate = async () => {
const client = await pool.connect();
const execQuery = async (q: string) => {
console.log(q);
await client.query(q);
};
const platformTitleToName = {
Betfair: "betfair",
FantasySCOTUS: "fantasyscotus",
Foretold: "foretold",
"GiveWell/OpenPhilanthropy": "givewellopenphil",
"Good Judgment": "goodjudgment",
"Good Judgment Open": "goodjudgmentopen",
Infer: "infer",
Kalshi: "kalshi",
"Manifold Markets": "manifold",
Metaculus: "metaculus",
"Peter Wildeford": "wildeford",
PolyMarket: "polymarket",
PredictIt: "predictit",
Rootclaim: "rootclaim",
Smarkets: "smarkets",
"X-risk estimates": "xrisk",
};
try {
await client.query("BEGIN");
const copyTable = async (from: string, to: string) => {
await execQuery(`DROP TABLE IF EXISTS ${to}`);
await execQuery(`CREATE TABLE ${to} (LIKE ${from} INCLUDING ALL)`);
await execQuery(`INSERT INTO ${to} SELECT * FROM ${from}`);
};
await copyTable("latest.dashboards", "dashboards");
await copyTable("latest.combined", "questions");
await copyTable("latest.frontpage", "frontpage");
await copyTable("history.h2022", "history");
for (const [title, name] of Object.entries(platformTitleToName)) {
console.log(`Updating ${title} -> ${name}`);
for (const table of ["questions", "history"]) {
await client.query(
`UPDATE ${table} SET platform=$1 WHERE platform=$2`,
[name, title]
);
}
}
console.log("Fixing GJOpen ids in questions and history");
for (const table of ["questions", "history"]) {
await client.query(
`UPDATE ${table} SET id=REPLACE(id, 'goodjudmentopen-', 'goodjudgmentopen-') WHERE id LIKE 'goodjudmentopen-%'`
);
}
const fixId = (id: string) =>
id.replace("goodjudmentopen-", "goodjudgmentopen-");
console.log(
"Please rebuild frontpage manually - current version includes invalid GJOpen and xrisk ids"
);
const updateDashboards = async () => {
const res = await client.query("SELECT id, contents FROM dashboards");
for (const row of res.rows) {
let { id, contents } = row;
contents = contents.map(fixId);
await client.query(
"UPDATE dashboards SET contents = $1 WHERE id = $2",
[JSON.stringify(contents), id]
);
}
};
console.log("Updating dashboards");
await updateDashboards();
await client.query("COMMIT");
} catch (e) {
await client.query("ROLLBACK");
throw e;
} finally {
client.release();
}
};
migrate();

View File

@ -1,92 +0,0 @@
import "dotenv/config";
import { pool } from "../database/pg-wrapper";
const migrate = async () => {
const client = await pool.connect();
const execQuery = async (q: string) => {
console.log(q);
await client.query(q);
};
try {
await client.query("BEGIN");
const notNullColumn = async (table: string, column: string) => {
await execQuery(
`ALTER TABLE ${table} ALTER COLUMN ${column} SET NOT NULL`
);
};
const jsonbColumn = async (table: string, column: string) => {
await execQuery(
`ALTER TABLE ${table} ALTER COLUMN ${column} SET DATA TYPE jsonb USING ${column}::jsonb`
);
};
const t2c = {
dashboards: [
"id",
"title",
"description",
"contents",
"timestamp",
"creator",
"extra",
],
frontpage: ["frontpage_sliced", "frontpage_full"],
history: [
"id",
"title",
"url",
"platform",
"description",
"options",
"timestamp",
"stars",
"qualityindicators",
"extra",
],
questions: [
"id",
"title",
"url",
"platform",
"description",
"options",
"timestamp",
"stars",
"qualityindicators",
"extra",
],
};
for (const [table, columns] of Object.entries(t2c)) {
for (const column of columns) {
await notNullColumn(table, column);
}
}
await execQuery("ALTER TABLE history ADD COLUMN pk SERIAL PRIMARY KEY");
await execQuery("ALTER TABLE dashboards ADD PRIMARY KEY (id)");
await execQuery("ALTER TABLE questions ADD PRIMARY KEY (id)");
await jsonbColumn("dashboards", "contents");
await jsonbColumn("dashboards", "extra");
for (const table of ["history", "questions"]) {
await jsonbColumn(table, "options");
await jsonbColumn(table, "qualityindicators");
await jsonbColumn(table, "extra");
}
await client.query("COMMIT");
} catch (e) {
await client.query("ROLLBACK");
throw e;
} finally {
client.release();
}
};
migrate();

View File

@ -2,7 +2,7 @@
import axios from "axios"; import axios from "axios";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import { Platform } from "./"; import { FetchedQuestion, Platform } from "./";
/* Definitions */ /* Definitions */
const platformName = "example"; const platformName = "example";
@ -24,9 +24,9 @@ async function fetchData() {
async function processPredictions(predictions) { async function processPredictions(predictions) {
let results = await predictions.map((prediction) => { let results = await predictions.map((prediction) => {
let id = `${platformName}-${prediction.id}`; const id = `${platformName}-${prediction.id}`;
let probability = prediction.probability; const probability = prediction.probability;
let options = [ const options = [
{ {
name: "Yes", name: "Yes",
probability: probability, probability: probability,
@ -38,19 +38,19 @@ async function processPredictions(predictions) {
type: "PROBABILITY", type: "PROBABILITY",
}, },
]; ];
let result = { const result: FetchedQuestion = {
id,
title: prediction.title, title: prediction.title,
url: `https://example.com`, url: `https://example.com`,
platform: platformName, platform: platformName,
description: prediction.description, description: prediction.description,
options: options, options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
stars: calculateStars(platformName, { stars: calculateStars(platformName, {
/* some: somex, factors: factors */ /* some: somex, factors: factors */
}), }),
other: prediction.otherx, // other: prediction.otherx,
indicators: prediction.indicatorx, // indicators: prediction.indicatorx,
}, },
}; };
return result; return result;

View File

@ -3,7 +3,7 @@ import axios from "axios";
import https from "https"; import https from "https";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import { Platform, Question } from "./"; import { FetchedQuestion, Platform } from "./";
const platformName = "betfair"; const platformName = "betfair";
@ -80,7 +80,7 @@ async function whipIntoShape(data) {
async function processPredictions(data) { async function processPredictions(data) {
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: Question[] = predictions.map((prediction) => { let results: FetchedQuestion[] = predictions.map((prediction) => {
/* if(Math.floor(Math.random() * 10) % 20 ==0){ /* if(Math.floor(Math.random() * 10) % 20 ==0){
console.log(JSON.stringify(prediction, null, 4)) console.log(JSON.stringify(prediction, null, 4))
} */ } */
@ -126,7 +126,6 @@ async function processPredictions(data) {
platform: platformName, platform: platformName,
description: description, description: description,
options: options, options: options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
stars: calculateStars(platformName, { stars: calculateStars(platformName, {
volume: prediction.totalMatched, volume: prediction.totalMatched,

View File

@ -2,7 +2,7 @@
import axios from "axios"; import axios from "axios";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import { Platform, Question } from "./"; import { FetchedQuestion, Platform } from "./";
const platformName = "fantasyscotus"; const platformName = "fantasyscotus";
@ -67,7 +67,7 @@ async function processData(data) {
let historicalPercentageCorrect = data.stats.pcnt_correct; let historicalPercentageCorrect = data.stats.pcnt_correct;
let historicalProbabilityCorrect = let historicalProbabilityCorrect =
Number(historicalPercentageCorrect.replace("%", "")) / 100; Number(historicalPercentageCorrect.replace("%", "")) / 100;
let results: Question[] = []; let results: FetchedQuestion[] = [];
for (let event of events) { for (let event of events) {
if (event.accuracy == "") { if (event.accuracy == "") {
let id = `${platformName}-${event.id}`; let id = `${platformName}-${event.id}`;
@ -75,7 +75,7 @@ async function processData(data) {
let predictionData = await getPredictionsData(event.docket_url); let predictionData = await getPredictionsData(event.docket_url);
let pAffirm = predictionData.proportionAffirm; let pAffirm = predictionData.proportionAffirm;
//let trackRecord = event.prediction.includes("Affirm") ? historicalProbabilityCorrect : 1-historicalProbabilityCorrect //let trackRecord = event.prediction.includes("Affirm") ? historicalProbabilityCorrect : 1-historicalProbabilityCorrect
let eventObject: Question = { let eventObject: FetchedQuestion = {
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}`,
@ -99,7 +99,6 @@ async function processData(data) {
type: "PROBABILITY", type: "PROBABILITY",
}, },
], ],
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
numforecasts: Number(predictionData.numForecasts), numforecasts: Number(predictionData.numForecasts),
stars: calculateStars(platformName, {}), stars: calculateStars(platformName, {}),

View File

@ -2,7 +2,7 @@
import axios from "axios"; import axios from "axios";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import { Platform } from "./"; import { FetchedQuestion, Platform } from "./";
/* Definitions */ /* Definitions */
@ -61,7 +61,7 @@ export const foretold: Platform = {
label: "Foretold", label: "Foretold",
color: "#62520b", color: "#62520b",
async fetcher() { async fetcher() {
let results = []; let results: FetchedQuestion[] = [];
for (let community of highQualityCommunities) { for (let community of highQualityCommunities) {
let questions = await fetchAllCommunityQuestions(community); let questions = await fetchAllCommunityQuestions(community);
questions = questions.map((question) => question.node); questions = questions.map((question) => question.node);
@ -84,14 +84,13 @@ export const foretold: Platform = {
}, },
]; ];
} }
let result = { let result: FetchedQuestion = {
id: 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, platform: platformName,
description: "", description: "",
options: options, options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
numforecasts: Math.floor(Number(question.measurementCount) / 2), numforecasts: Math.floor(Number(question.measurementCount) / 2),
stars: calculateStars(platformName, {}), stars: calculateStars(platformName, {}),

View File

@ -47,12 +47,12 @@ async function main1() {
); );
let description = "<h2 " + internalforecasts[1]; let description = "<h2 " + internalforecasts[1];
let result = { const result = {
title: title, title,
url: url, url,
platform: platformName, platform: platformName,
description: description, description,
timestamp: new Date().toISOString(), options: [],
qualityindicators: { qualityindicators: {
stars: calculateStars(platformName, {}), stars: calculateStars(platformName, {}),
}, },
@ -80,7 +80,7 @@ export const givewellopenphil: Platform = {
const dataWithDate = data.map((datum: any) => ({ const dataWithDate = data.map((datum: any) => ({
...datum, ...datum,
platform: platformName, platform: platformName,
timestamp: "2021-02-23", timestamp: new Date("2021-02-23"),
})); }));
return dataWithDate; return dataWithDate;
}, },

View File

@ -5,7 +5,7 @@ import tunnel from "tunnel";
import { hash } from "../utils/hash"; import { hash } from "../utils/hash";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import { Platform } from "./"; import { FetchedQuestion, Platform } from "./";
/* Definitions */ /* Definitions */
const platformName = "goodjudgment"; const platformName = "goodjudgment";
@ -57,7 +57,7 @@ export const goodjudgment: Platform = {
.then((query) => query.data); .then((query) => query.data);
// Processing // Processing
let results = []; let results: FetchedQuestion[] = [];
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
@ -100,14 +100,13 @@ export const goodjudgment: Platform = {
analysis = analysis ? analysis[0] : ""; analysis = analysis ? analysis[0] : "";
analysis = analysis ? analysis[0] : ""; // not a duplicate analysis = analysis ? analysis[0] : ""; // not a duplicate
// console.log(analysis) // console.log(analysis)
let standardObj = { let standardObj: FetchedQuestion = {
id: id, id,
title: title, title,
url: endpoint, url: endpoint,
platform: platformName, platform: platformName,
description: description, description,
options: options, options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
stars: calculateStars(platformName, {}), stars: calculateStars(platformName, {}),
}, },

View File

@ -114,7 +114,6 @@ async function fetchStats(questionUrl, cookie) {
let result = { let result = {
description: description, description: description,
options: options, options: options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
numforecasts: Number(numforecasts), numforecasts: Number(numforecasts),
numforecasters: Number(numforecasters), numforecasters: Number(numforecasters),

View File

@ -1,4 +1,6 @@
import { pgUpsert } from "../database/pg-wrapper"; import { Question } from "@prisma/client";
import { prisma } from "../database/prisma";
import { betfair } from "./betfair"; import { betfair } from "./betfair";
import { fantasyscotus } from "./fantasyscotus"; import { fantasyscotus } from "./fantasyscotus";
import { foretold } from "./foretold"; import { foretold } from "./foretold";
@ -28,57 +30,23 @@ export interface QualityIndicators {
tradevolume?: string; tradevolume?: string;
pool?: any; pool?: any;
createdTime?: any; createdTime?: any;
shares_volume?: any;
yes_bid?: any;
yes_ask?: any;
spread?: any;
} }
export interface Question { export type FetchedQuestion = Omit<
id: string; Question,
// "fantasyscotus-580" "extra" | "qualityindicators" | "timestamp"
> & {
title: string; timestamp?: Date;
// "In Wooden v. U.S., the SCOTUS will affirm the lower court's decision" 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
url: string; };
// "https://fantasyscotus.net/user-predictions/case/wooden-v-us/"
description: string;
// "62.50% (75 out of 120) of FantasySCOTUS players predict that the lower court's decision will be affirmed. FantasySCOTUS overall predicts an outcome of Affirm 6-3. Historically, FantasySCOTUS has chosen the correct side 50.00% of the time."
platform: string;
// "FantasySCOTUS"
options: any[];
/*
[
{
"name": "Yes",
"probability": 0.625,
"type": "PROBABILITY"
},
{
"name": "No",
"probability": 0.375,
"type": "PROBABILITY"
}
]
*/
timestamp: string;
// "2022-02-11T21:42:19.291Z"
stars?: number;
// 2
qualityindicators: QualityIndicators;
/*
{
"numforecasts": 120,
"stars": 2
}
*/
extra?: any;
}
// 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<Question[] | null>; export type PlatformFetcher = () => Promise<FetchedQuestion[] | null>;
export interface Platform { export interface Platform {
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"
@ -95,13 +63,6 @@ export interface Platform {
// export type PlatformFetcher = (options: FetchOptions) => Promise<void>; // export type PlatformFetcher = (options: FetchOptions) => Promise<void>;
// interface Platform {
// name: string;
// color?: string;
// longName: string;
// fetcher: PlatformFetcher;
// }
export const platforms: Platform[] = [ export const platforms: Platform[] = [
betfair, betfair,
fantasyscotus, fantasyscotus,
@ -126,13 +87,23 @@ export const processPlatform = async (platform: Platform) => {
console.log(`Platform ${platform.name} doesn't have a fetcher, skipping`); console.log(`Platform ${platform.name} doesn't have a fetcher, skipping`);
return; return;
} }
let results = await platform.fetcher(); const results = await platform.fetcher();
if (results && results.length) { if (results && results.length) {
await pgUpsert({ await prisma.$transaction([
contents: results, prisma.question.deleteMany({
tableName: "questions", where: {
replacePlatform: platform.name, platform: platform.name,
}); },
}),
prisma.question.createMany({
data: results.map((q) => ({
extra: {},
timestamp: new Date(),
...q,
qualityindicators: q.qualityindicators as object, // fighting typescript
})),
}),
]);
console.log("Done"); console.log("Done");
} else { } else {
console.log(`Platform ${platform.name} didn't return any results`); console.log(`Platform ${platform.name} didn't return any results`);

View File

@ -5,7 +5,7 @@ import { applyIfSecretExists } from "../utils/getSecrets";
import { measureTime } from "../utils/measureTime"; import { measureTime } from "../utils/measureTime";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import toMarkdown from "../utils/toMarkdown"; import toMarkdown from "../utils/toMarkdown";
import { Platform, Question } from "./"; import { FetchedQuestion, Platform } from "./";
/* Definitions */ /* Definitions */
const platformName = "infer"; const platformName = "infer";
@ -105,7 +105,6 @@ async function fetchStats(questionUrl, cookie) {
let result = { let result = {
description: description, description: description,
options: options, options: options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
numforecasts: Number(numforecasts), numforecasts: Number(numforecasts),
numforecasters: Number(numforecasters), numforecasters: Number(numforecasters),
@ -147,7 +146,7 @@ function sleep(ms) {
async function infer_inner(cookie: string) { async function infer_inner(cookie: string) {
let i = 1; let i = 1;
let response = await fetchPage(i, cookie); let response = await fetchPage(i, cookie);
let results: Question[] = []; let results: FetchedQuestion[] = [];
await measureTime(async () => { await measureTime(async () => {
// console.log("Downloading... This might take a couple of minutes. Results will be shown.") // console.log("Downloading... This might take a couple of minutes. Results will be shown.")
@ -178,7 +177,7 @@ async function infer_inner(cookie: string) {
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]; let questionNum = url.match(questionNumRegex)[1]; //.split("questions/")[1].split("-")[0];
let id = `${platformName}-${questionNum}`; let id = `${platformName}-${questionNum}`;
let question: Question = { let question: FetchedQuestion = {
id: id, id: id,
title: title, title: title,
description: moreinfo.description, description: moreinfo.description,

View File

@ -2,7 +2,7 @@
import axios from "axios"; import axios from "axios";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import { Platform } from "./"; import { FetchedQuestion, Platform } from "./";
/* Definitions */ /* Definitions */
const platformName = "kalshi"; const platformName = "kalshi";
@ -22,8 +22,8 @@ async function processMarkets(markets) {
// 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) => {
let probability = market.last_price / 100; const probability = market.last_price / 100;
let options = [ const options = [
{ {
name: "Yes", name: "Yes",
probability: probability, probability: probability,
@ -35,15 +35,14 @@ async function processMarkets(markets) {
type: "PROBABILITY", type: "PROBABILITY",
}, },
]; ];
let id = `${platformName}-${market.id}`; const id = `${platformName}-${market.id}`;
let result = { const result: FetchedQuestion = {
id: 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, 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, options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
stars: calculateStars(platformName, { stars: calculateStars(platformName, {
shares_volume: market.volume, shares_volume: market.volume,

View File

@ -2,7 +2,7 @@
import axios from "axios"; import axios from "axios";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import { Platform, Question } from "./"; import { FetchedQuestion, Platform } from "./";
/* Definitions */ /* Definitions */
const platformName = "manifold"; const platformName = "manifold";
@ -23,7 +23,7 @@ async function fetchData() {
return response; return response;
} }
function showStatistics(results: Question[]) { 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) => arr.reduce((tally, a) => tally + a, 0);
let num2StarsOrMore = results.filter( let num2StarsOrMore = results.filter(
@ -44,7 +44,7 @@ function showStatistics(results: Question[]) {
} }
async function processPredictions(predictions) { async function processPredictions(predictions) {
let results: Question[] = await predictions.map((prediction) => { let results: FetchedQuestion[] = await 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 = [
@ -59,14 +59,13 @@ async function processPredictions(predictions) {
type: "PROBABILITY", type: "PROBABILITY",
}, },
]; ];
const result: Question = { const result: FetchedQuestion = {
id: id, id: id,
title: prediction.question, title: prediction.question,
url: prediction.url, url: prediction.url,
platform: platformName, platform: platformName,
description: prediction.description, description: prediction.description,
options: options, options: options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
stars: calculateStars(platformName, { stars: calculateStars(platformName, {
volume7Days: prediction.volume7Days, volume7Days: prediction.volume7Days,
@ -86,7 +85,7 @@ async function processPredictions(predictions) {
}); });
const unresolvedResults = results.filter( const unresolvedResults = results.filter(
(result) => !result.extra.isResolved (result) => !(result.extra as any).isResolved
); );
return unresolvedResults; return unresolvedResults;
} }

View File

@ -3,7 +3,7 @@ import axios from "axios";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import toMarkdown from "../utils/toMarkdown"; import toMarkdown from "../utils/toMarkdown";
import { Platform } from "./"; import { FetchedQuestion, Platform } from "./";
/* Definitions */ /* Definitions */
const platformName = "metaculus"; const platformName = "metaculus";
@ -148,14 +148,13 @@ export const metaculus: Platform = {
]; ];
} }
let id = `${platformName}-${result.id}`; let id = `${platformName}-${result.id}`;
let interestingInfo = { let interestingInfo: FetchedQuestion = {
id: 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, platform: platformName,
description: description, description,
options: options, options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
numforecasts: Number(result.number_of_predictions), numforecasts: Number(result.number_of_predictions),
stars: calculateStars(platformName, { stars: calculateStars(platformName, {

View File

@ -2,7 +2,7 @@
import axios from "axios"; import axios from "axios";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import { Platform, Question } from "./"; import { FetchedQuestion, Platform } from "./";
/* Definitions */ /* Definitions */
const platformName = "polymarket"; const platformName = "polymarket";
@ -68,7 +68,7 @@ export const polymarket: Platform = {
label: "PolyMarket", label: "PolyMarket",
color: "#00314e", color: "#00314e",
async fetcher() { async fetcher() {
let results: Question[] = []; let results: FetchedQuestion[] = [];
let webpageEndpointData = await fetchAllContractInfo(); let webpageEndpointData = await fetchAllContractInfo();
for (let marketInfo of webpageEndpointData) { for (let marketInfo of webpageEndpointData) {
let address = marketInfo.marketMakerAddress; let address = marketInfo.marketMakerAddress;
@ -102,14 +102,13 @@ export const polymarket: Platform = {
}); });
} }
let result: Question = { let result: FetchedQuestion = {
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, platform: platformName,
description: marketInfo.description, description: marketInfo.description,
options: options, options: options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
numforecasts: numforecasts.toFixed(0), numforecasts: numforecasts.toFixed(0),
liquidity: liquidity.toFixed(2), liquidity: liquidity.toFixed(2),

View File

@ -2,7 +2,7 @@ import axios from "axios";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import toMarkdown from "../utils/toMarkdown"; import toMarkdown from "../utils/toMarkdown";
import { Platform } from "./"; import { FetchedQuestion, Platform } from "./";
const platformName = "predictit"; const platformName = "predictit";
@ -53,7 +53,7 @@ export const predictit: Platform = {
})); }));
// console.log(markets) // console.log(markets)
let results = []; let results: FetchedQuestion[] = [];
for (let market of markets) { for (let market of markets) {
// console.log(market.name) // console.log(market.name)
let id = `${platformName}-${market.id}`; let id = `${platformName}-${market.id}`;
@ -96,17 +96,16 @@ export const predictit: Platform = {
]; ];
} }
let obj = { const obj: FetchedQuestion = {
id: id, id,
title: market["name"], title: market["name"],
url: market.url, url: market.url,
platform: platformName, platform: platformName,
description: description, description,
options: options, options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
stars: calculateStars(platformName, {}), stars: calculateStars(platformName, {}),
shares_volume: shares_volume, shares_volume,
}, },
}; };
// console.log(obj) // console.log(obj)

View File

@ -3,7 +3,7 @@ import { JSDOM } from "jsdom";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import toMarkdown from "../utils/toMarkdown"; import toMarkdown from "../utils/toMarkdown";
import { Platform, Question } from "./"; import { FetchedQuestion, Platform } from "./";
const platformName = "rootclaim"; const platformName = "rootclaim";
const jsonEndpoint = const jsonEndpoint =
@ -50,7 +50,7 @@ export const rootclaim: Platform = {
color: "#0d1624", color: "#0d1624",
async fetcher() { async fetcher() {
const claims = await fetchAllRootclaims(); const claims = await fetchAllRootclaims();
const results: Question[] = []; const results: FetchedQuestion[] = [];
for (const claim of claims) { for (const claim of claims) {
const id = `${platformName}-${claim.slug.toLowerCase()}`; const id = `${platformName}-${claim.slug.toLowerCase()}`;
@ -71,14 +71,13 @@ export const rootclaim: Platform = {
const description = await fetchDescription(url, claim.isclaim); const description = await fetchDescription(url, claim.isclaim);
let obj: Question = { let obj: FetchedQuestion = {
id, id,
title: toMarkdown(claim.question).replace("\n", ""), title: toMarkdown(claim.question).replace("\n", ""),
url, url,
platform: platformName, platform: platformName,
description: toMarkdown(description).replace("&#39;", "'"), description: toMarkdown(description).replace("&#39;", "'"),
options: options, options: options,
timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
numforecasts: 1, numforecasts: 1,
stars: calculateStars(platformName, {}), stars: calculateStars(platformName, {}),

View File

@ -1,7 +1,7 @@
import axios from "axios"; import axios from "axios";
import { calculateStars } from "../utils/stars"; import { calculateStars } from "../utils/stars";
import { Platform, Question } from "./"; import { FetchedQuestion, Platform } from "./";
/* Definitions */ /* Definitions */
const platformName = "smarkets"; const platformName = "smarkets";
@ -159,14 +159,14 @@ export const smarkets: Platform = {
name = name+ (contractName=="Yes"?'':` (${contracts["contracts"][0].name})`) name = name+ (contractName=="Yes"?'':` (${contracts["contracts"][0].name})`)
} }
*/ */
let result: Question = { let result: FetchedQuestion = {
id: id, id: id,
title: name, title: name,
url: "https://smarkets.com/event/" + market.event_id + market.slug, url: "https://smarkets.com/event/" + market.event_id + market.slug,
platform: platformName, platform: platformName,
description: market.description, description: market.description,
options: options, options: options,
timestamp: new Date().toISOString(), timestamp: new Date(),
qualityindicators: { qualityindicators: {
stars: calculateStars(platformName, {}), stars: calculateStars(platformName, {}),
}, },

View File

@ -4,7 +4,7 @@ import { GoogleSpreadsheet } from "google-spreadsheet";
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 { calculateStars } from "../utils/stars";
import { Platform } from "./"; import { FetchedQuestion, Platform } from "./";
/* Definitions */ /* Definitions */
const platformName = "wildeford"; const platformName = "wildeford";
@ -88,16 +88,14 @@ async function processPredictions(predictions) {
type: "PROBABILITY", type: "PROBABILITY",
}, },
]; ];
let result = { let result: FetchedQuestion = {
id: id, id,
title: title, title,
url: prediction["url"], url: prediction["url"],
platform: platformName, platform: platformName,
description: prediction["Notes"] || "", description: prediction["Notes"] || "",
options: options, options,
timestamp: new Date( timestamp: new Date(Date.parse(prediction["Prediction Date"] + "Z")),
Date.parse(prediction["Prediction Date"] + "Z")
).toISOString(),
qualityindicators: { qualityindicators: {
stars: calculateStars(platformName, null), stars: calculateStars(platformName, null),
}, },

View File

@ -1,7 +1,7 @@
/* Imports */ /* Imports */
import fs from "fs"; import fs from "fs";
import { pgRead } from "../../database/pg-wrapper"; import { prisma } from "../../database/prisma";
/* Definitions */ /* Definitions */
@ -24,7 +24,7 @@ const main = async () => {
"PredictIt", "PredictIt",
"Rootclaim", "Rootclaim",
]; ];
const json = await pgRead({ tableName: "questions" }); const json = await prisma.question.findMany({});
console.log(json.length); console.log(json.length);
//let uniquePlatforms = [...new Set(json.map(forecast => forecast.platform))] //let uniquePlatforms = [...new Set(json.map(forecast => forecast.platform))]
//console.log(uniquePlatforms) //console.log(uniquePlatforms)

View File

@ -2,7 +2,7 @@
import fs from "fs"; import fs from "fs";
import { shuffleArray } from "../../../utils"; import { shuffleArray } from "../../../utils";
import { pgRead } from "../../database/pg-wrapper"; import { prisma } from "../../database/prisma";
/* Definitions */ /* Definitions */
@ -18,7 +18,7 @@ let getQualityIndicators = (question) =>
let main = async () => { let main = async () => {
let highQualityPlatforms = ["Metaculus"]; // ['CSET-foretell', 'Foretold', 'Good Judgment Open', 'Metaculus', 'PredictIt', 'Rootclaim'] let highQualityPlatforms = ["Metaculus"]; // ['CSET-foretell', 'Foretold', 'Good Judgment Open', 'Metaculus', 'PredictIt', 'Rootclaim']
let json = await pgRead({ tableName: "questions" }); let json = await prisma.question.findMany({});
console.log(json.length); console.log(json.length);
//let uniquePlatforms = [...new Set(json.map(question => question.platform))] //let uniquePlatforms = [...new Set(json.map(question => question.platform))]
//console.log(uniquePlatforms) //console.log(uniquePlatforms)

View File

@ -38,7 +38,7 @@ for (let datum of data) {
*/ */
timestamp: "2021-02-23T152137.005Z", //new Date().toISOString(), timestamp: "2021-02-23T152137.005Z", //new Date().toISOString(),
qualityindicators: { qualityindicators: {
stars: datum.qualityindicators.stars, //datum["stars"], stars: datum.qualityindicators.stars,
}, },
}; };
results.push(result); results.push(result);

View File

@ -40,7 +40,7 @@ ${datum["description"]}`
], ],
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
qualityindicators: { qualityindicators: {
stars: 2, //datum["stars"] stars: 2,
}, },
}; };
results.push(result); results.push(result);

View File

@ -23,7 +23,7 @@ for (let datum of data) {
options: datum.options, options: datum.options,
timestamp: datum.timestamps, timestamp: datum.timestamps,
qualityindicators: { qualityindicators: {
stars: 2, //datum["stars"] stars: 2,
}, },
}; };
results.push(result); results.push(result);

View File

@ -1,7 +1,8 @@
/* Imports */ /* Imports */
import fs from "fs"; import fs from "fs";
import { pgRead } from "../../database/pg-wrapper"; import { prisma } from "../../database/prisma";
import { QualityIndicators } from "../../platforms";
/* Definitions */ /* Definitions */
let locationData = "./data/"; let locationData = "./data/";
@ -9,8 +10,8 @@ let locationData = "./data/";
/* Body */ /* Body */
// 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() {
let data = await pgRead({ tableName: "questions" }); //JSON.parse(rawdata) const data = await prisma.question.findMany({});
let processDescription = (description) => { const processDescription = (description) => {
if (description == null || description == undefined || description == "") { if (description == null || description == undefined || description == "") {
return ""; return "";
} else { } else {
@ -32,14 +33,14 @@ async function main() {
}; };
let results = []; let results = [];
for (let datum of data) { for (const datum of data) {
// do something // do something
let description = processDescription(datum["description"]); const description = processDescription(datum["description"]);
let forecasts = datum["qualityindicators"] const forecasts = datum["qualityindicators"]
? datum["qualityindicators"].numforecasts ? (datum["qualityindicators"] as object as QualityIndicators).numforecasts
: "unknown"; : "unknown";
let stars = datum["qualityindicators"] const stars = datum["qualityindicators"]
? datum["qualityindicators"].stars ? (datum["qualityindicators"] as object as QualityIndicators).stars
: 2; : 2;
results.push("Title: " + datum["title"]); results.push("Title: " + datum["title"]);
results.push("URL: " + datum["url"]); results.push("URL: " + datum["url"]);

View File

@ -43,11 +43,10 @@ export default async function searchGuesstimate(
description, description,
options: [], options: [],
qualityindicators: { qualityindicators: {
stars: stars, stars,
numforecasts: 1, numforecasts: 1,
numforecasters: 1, numforecasters: 1,
}, },
stars,
extra: { extra: {
visualization: model.big_screenshot, visualization: model.big_screenshot,
}, },

View File

@ -116,7 +116,6 @@ export default async function searchWithAlgolia({
}, },
], ],
timestamp: `${new Date().toISOString().slice(0, 10)}`, timestamp: `${new Date().toISOString().slice(0, 10)}`,
stars: 5, // legacy
qualityindicators: { qualityindicators: {
numforecasts: 1, numforecasts: 1,
numforecasters: 1, numforecasters: 1,
@ -148,7 +147,6 @@ export default async function searchWithAlgolia({
}, },
], ],
timestamp: `${new Date().toISOString().slice(0, 10)}`, timestamp: `${new Date().toISOString().slice(0, 10)}`,
stars: 5, // legacy
qualityindicators: { qualityindicators: {
numforecasts: 1, numforecasts: 1,
numforecasters: 1, numforecasters: 1,
@ -183,7 +181,6 @@ export default async function searchWithAlgolia({
}, },
], ],
timestamp: `${new Date().toISOString().slice(0, 10)}`, timestamp: `${new Date().toISOString().slice(0, 10)}`,
stars: 1, // legacy
qualityindicators: { qualityindicators: {
numforecasts: 1, numforecasts: 1,
numforecasters: 1, numforecasters: 1,