feat: new db structure; platform labels
This commit is contained in:
		
							parent
							
								
									a1ba23e340
								
							
						
					
					
						commit
						72db637972
					
				
							
								
								
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -26,12 +26,6 @@ npm-debug.log* | ||||||
| yarn-debug.log* | yarn-debug.log* | ||||||
| yarn-error.log* | yarn-error.log* | ||||||
| 
 | 
 | ||||||
| # local env files |  | ||||||
| .env.local |  | ||||||
| .env.development.local |  | ||||||
| .env.test.local |  | ||||||
| .env.production.local |  | ||||||
| 
 |  | ||||||
| # vercel | # vercel | ||||||
| .vercel | .vercel | ||||||
| 
 | 
 | ||||||
|  | @ -41,5 +35,4 @@ package-lock.json ## use yarn.lock instead | ||||||
| # Local Netlify folder | # Local Netlify folder | ||||||
| .netlify | .netlify | ||||||
| 
 | 
 | ||||||
| /.env | /.env* | ||||||
| /.env.production |  | ||||||
|  |  | ||||||
|  | @ -1,13 +1,11 @@ | ||||||
| import { Pool, PoolClient } from "pg"; | import { Pool, PoolClient } from "pg"; | ||||||
| 
 | 
 | ||||||
| import { Forecast, platforms } from "../platforms"; | import { Forecast } from "../platforms"; | ||||||
| import { hash } from "../utils/hash"; | import { hash } from "../utils/hash"; | ||||||
| import { measureTime } from "../utils/measureTime"; | import { measureTime } from "../utils/measureTime"; | ||||||
| import { roughSizeOfObject } from "../utils/roughSize"; | import { roughSizeOfObject } from "../utils/roughSize"; | ||||||
| 
 | 
 | ||||||
| const platformTableNames = platforms.map((platform) => platform.name); | const forecastTableNames = ["questions", "history"]; | ||||||
| 
 |  | ||||||
| const forecastTableNames = [...platformTableNames, "combined", "history"]; |  | ||||||
| 
 | 
 | ||||||
| const allTableNames = [...forecastTableNames, "dashboards", "frontpage"]; | const allTableNames = [...forecastTableNames, "dashboards", "frontpage"]; | ||||||
| 
 | 
 | ||||||
|  | @ -111,28 +109,27 @@ let buildMetaforecastTable = (table: string) => `CREATE TABLE ${table} ( | ||||||
|     extra json |     extra json | ||||||
|   );`;
 |   );`;
 | ||||||
| 
 | 
 | ||||||
| async function pgInitializeLatest() { | async function pgInitializeQuestions() { | ||||||
|   let YOLO = false; |   let YOLO = false; | ||||||
|   if (YOLO) { |   if (YOLO) { | ||||||
|     console.log("Create tables & their indexes"); |     console.log("Create tables & their indexes"); | ||||||
|     for (const table of platformTableNames) { |     const table = "questions"; | ||||||
|       await runPgCommand({ |     await runPgCommand({ | ||||||
|         command: dropTable(table), |       command: dropTable(table), | ||||||
|         pool: readWritePool, |       pool: readWritePool, | ||||||
|       }); |     }); | ||||||
|       await runPgCommand({ |     await runPgCommand({ | ||||||
|         command: buildMetaforecastTable(table), |       command: buildMetaforecastTable(table), | ||||||
|         pool: readWritePool, |       pool: readWritePool, | ||||||
|       }); |     }); | ||||||
|       await runPgCommand({ |     await runPgCommand({ | ||||||
|         command: createUniqueIndex(table), |       command: createUniqueIndex(table), | ||||||
|         pool: readWritePool, |       pool: readWritePool, | ||||||
|       }); |     }); | ||||||
|     } |  | ||||||
|     console.log(""); |     console.log(""); | ||||||
|   } else { |   } else { | ||||||
|     console.log( |     console.log( | ||||||
|       "pgInitializeLatest: This command is dangerous, set YOLO to true in the code to invoke it" |       "pgInitializeQuestions: This command is dangerous, set YOLO to true in the code to invoke it" | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | @ -234,7 +231,7 @@ async function pgInitializeFrontpage() { | ||||||
| 
 | 
 | ||||||
| export async function pgInitialize() { | export async function pgInitialize() { | ||||||
|   await pgInitializeScaffolding(); |   await pgInitializeScaffolding(); | ||||||
|   await pgInitializeLatest(); |   await pgInitializeQuestions(); | ||||||
|   await pgInitializeHistories(); |   await pgInitializeHistories(); | ||||||
|   await pgInitializeDashboards(); |   await pgInitializeDashboards(); | ||||||
|   await pgInitializeFrontpage(); |   await pgInitializeFrontpage(); | ||||||
|  | @ -416,11 +413,11 @@ pgInsertIntoDashboard({ | ||||||
| export async function pgUpsert({ | export async function pgUpsert({ | ||||||
|   contents, |   contents, | ||||||
|   tableName, |   tableName, | ||||||
|   replace, |   replacePlatform, | ||||||
| }: { | }: { | ||||||
|   contents: Forecast[]; |   contents: Forecast[]; | ||||||
|   tableName: string; |   tableName: string; | ||||||
|   replace: boolean; |   replacePlatform?: string; | ||||||
| }) { | }) { | ||||||
|   if (!forecastTableNames.includes(tableName)) { |   if (!forecastTableNames.includes(tableName)) { | ||||||
|     throw Error( |     throw Error( | ||||||
|  | @ -432,8 +429,10 @@ export async function pgUpsert({ | ||||||
|     const client = await readWritePool.connect(); |     const client = await readWritePool.connect(); | ||||||
|     try { |     try { | ||||||
|       await client.query("BEGIN"); |       await client.query("BEGIN"); | ||||||
|       if (replace) { |       if (replacePlatform) { | ||||||
|         client.query(`DELETE FROM ${tableName}`); |         await client.query(`DELETE FROM ${tableName} WHERE platform = $1`, [ | ||||||
|  |           replacePlatform, | ||||||
|  |         ]); | ||||||
|       } |       } | ||||||
|       console.log( |       console.log( | ||||||
|         `Upserting ${contents.length} rows into postgres table ${tableName}.` |         `Upserting ${contents.length} rows into postgres table ${tableName}.` | ||||||
|  |  | ||||||
|  | @ -1,10 +1,9 @@ | ||||||
| import { pgReadWithReadCredentials, pgUpsert } from "../../database/pg-wrapper"; | import { pgReadWithReadCredentials, pgUpsert } from "../../database/pg-wrapper"; | ||||||
| 
 | 
 | ||||||
| export async function updateHistory() { | export async function updateHistory() { | ||||||
|   let latest = await pgReadWithReadCredentials({ tableName: "combined" }); |   let latest = await pgReadWithReadCredentials({ tableName: "questions" }); | ||||||
|   await pgUpsert({ |   await pgUpsert({ | ||||||
|     contents: latest, |     contents: latest, | ||||||
|     tableName: "history", |     tableName: "history", | ||||||
|     replace: false, |  | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| import { pgInitialize } from "../database/pg-wrapper"; | import { pgInitialize } from "../database/pg-wrapper"; | ||||||
| import { doEverything } from "../flow/doEverything"; | import { doEverything } from "../flow/doEverything"; | ||||||
| import { updateHistory } from "../flow/history/updateHistory"; | import { updateHistory } from "../flow/history/updateHistory"; | ||||||
| import { mergeEverything } from "../flow/mergeEverything"; |  | ||||||
| import { rebuildNetlifySiteWithNewData } from "../flow/rebuildNetliftySiteWithNewData"; | import { rebuildNetlifySiteWithNewData } from "../flow/rebuildNetliftySiteWithNewData"; | ||||||
| import { rebuildFrontpage } from "../frontpage"; | import { rebuildFrontpage } from "../frontpage"; | ||||||
| import { platforms, processPlatform } from "../platforms"; | import { platforms, processPlatform } from "../platforms"; | ||||||
|  | @ -20,13 +19,6 @@ export const jobs: Job[] = [ | ||||||
|     message: `Download predictions from ${platform.name}`, |     message: `Download predictions from ${platform.name}`, | ||||||
|     run: () => processPlatform(platform), |     run: () => processPlatform(platform), | ||||||
|   })), |   })), | ||||||
|   { |  | ||||||
|     name: "merge", |  | ||||||
|     message: |  | ||||||
|       "Merge tables into one big table (and push the result to a pg database)", |  | ||||||
|     run: mergeEverything, |  | ||||||
|     separate: true, |  | ||||||
|   }, |  | ||||||
|   { |   { | ||||||
|     name: "algolia", |     name: "algolia", | ||||||
|     message: 'Rebuild algolia database ("index")', |     message: 'Rebuild algolia database ("index")', | ||||||
|  |  | ||||||
|  | @ -1,28 +0,0 @@ | ||||||
| import { pgRead, pgUpsert } from "../database/pg-wrapper"; |  | ||||||
| import { platforms } from "../platforms"; |  | ||||||
| 
 |  | ||||||
| /* Merge everything */ |  | ||||||
| 
 |  | ||||||
| export async function mergeEverythingInner() { |  | ||||||
|   let merged = []; |  | ||||||
|   for (let platform of platforms) { |  | ||||||
|     const platformName = platform.name; |  | ||||||
|     let json = await pgRead({ tableName: platformName }); |  | ||||||
|     console.log(`${platformName} has ${json.length} questions\n`); |  | ||||||
|     merged = merged.concat(json); |  | ||||||
|   } |  | ||||||
|   let mergedprocessed = merged.map((element) => ({ |  | ||||||
|     ...element, |  | ||||||
|     optionsstringforsearch: element.options |  | ||||||
|       .map((option) => option.name) |  | ||||||
|       .join(", "), |  | ||||||
|   })); |  | ||||||
|   console.log(`In total, there are ${mergedprocessed.length} questions`); |  | ||||||
|   return mergedprocessed; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export async function mergeEverything() { |  | ||||||
|   let merged = await mergeEverythingInner(); |  | ||||||
|   await pgUpsert({ contents: merged, tableName: "combined", replace: true }); |  | ||||||
|   console.log("Done"); |  | ||||||
| } |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import { pgRead, readWritePool } from "./database/pg-wrapper"; | import { pgRead, readWritePool } from "./database/pg-wrapper"; | ||||||
|  | import { Forecast } from "./platforms"; | ||||||
| 
 | 
 | ||||||
| export async function getFrontpageRaw() { | export async function getFrontpage(): Promise<Forecast[]> { | ||||||
|   const client = await readWritePool.connect(); |   const client = await readWritePool.connect(); | ||||||
|   const res = await client.query( |   const res = await client.query( | ||||||
|     "SELECT frontpage_sliced FROM frontpage ORDER BY id DESC LIMIT 1" |     "SELECT frontpage_sliced FROM frontpage ORDER BY id DESC LIMIT 1" | ||||||
|  | @ -10,7 +11,7 @@ export async function getFrontpageRaw() { | ||||||
|   return res.rows[0].frontpage_sliced; |   return res.rows[0].frontpage_sliced; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getFrontpageFullRaw() { | export async function getFrontpageFull(): Promise<Forecast[]> { | ||||||
|   const client = await readWritePool.connect(); |   const client = await readWritePool.connect(); | ||||||
|   const res = await client.query( |   const res = await client.query( | ||||||
|     "SELECT frontpage_full FROM frontpage ORDER BY id DESC LIMIT 1" |     "SELECT frontpage_full FROM frontpage ORDER BY id DESC LIMIT 1" | ||||||
|  | @ -20,31 +21,15 @@ export async function getFrontpageFullRaw() { | ||||||
|   return res.rows[0].frontpage_full; |   return res.rows[0].frontpage_full; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getFrontpage() { |  | ||||||
|   let frontPageForecastsCompatibleWithFuse = []; |  | ||||||
|   try { |  | ||||||
|     let data = await getFrontpageRaw(); |  | ||||||
|     frontPageForecastsCompatibleWithFuse = data.map((result) => ({ |  | ||||||
|       item: result, |  | ||||||
|       score: 0, |  | ||||||
|     })); |  | ||||||
|     return frontPageForecastsCompatibleWithFuse; |  | ||||||
|   } catch (error) { |  | ||||||
|     console.log(error); |  | ||||||
|   } finally { |  | ||||||
|     return frontPageForecastsCompatibleWithFuse; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export async function rebuildFrontpage() { | export async function rebuildFrontpage() { | ||||||
|   const frontpageFull = await pgRead({ |   const frontpageFull = await pgRead({ | ||||||
|     tableName: "combined", |     tableName: "questions", | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   const client = await readWritePool.connect(); |   const client = await readWritePool.connect(); | ||||||
|   const frontpageSliced = ( |   const frontpageSliced = ( | ||||||
|     await client.query(` |     await client.query(` | ||||||
|     SELECT * FROM combined |     SELECT * FROM questions | ||||||
|     WHERE |     WHERE | ||||||
|       (qualityindicators->>'stars')::int >= 3 |       (qualityindicators->>'stars')::int >= 3 | ||||||
|       AND description != '' |       AND description != '' | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import fs from "fs"; | ||||||
| import { pgReadWithReadCredentials } from "../database/pg-wrapper"; | import { pgReadWithReadCredentials } from "../database/pg-wrapper"; | ||||||
| 
 | 
 | ||||||
| let main = async () => { | let main = async () => { | ||||||
|   let json = await pgReadWithReadCredentials({ tableName: "combined" }); |   let json = await pgReadWithReadCredentials({ tableName: "questions" }); | ||||||
|   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); | ||||||
|  |  | ||||||
|  | @ -1,7 +1,6 @@ | ||||||
| import "dotenv/config"; | import "dotenv/config"; | ||||||
| 
 | 
 | ||||||
| import { readWritePool } from "../database/pg-wrapper"; | import { readWritePool } from "../database/pg-wrapper"; | ||||||
| import { platforms } from "../platforms"; |  | ||||||
| 
 | 
 | ||||||
| const migrate = async () => { | const migrate = async () => { | ||||||
|   const client = await readWritePool.connect(); |   const client = await readWritePool.connect(); | ||||||
|  | @ -11,6 +10,25 @@ const migrate = async () => { | ||||||
|     await client.query(q); |     await client.query(q); | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   const platformTitleToName = { | ||||||
|  |     Betfair: "betfair", | ||||||
|  |     FantasySCOTUS: "fantasyscotus", | ||||||
|  |     Foretold: "foretold", | ||||||
|  |     "GiveWell/OpenPhilanthropy": "givewellopenphil", | ||||||
|  |     "Good Judgment": "goodjudgement", | ||||||
|  |     "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 { |   try { | ||||||
|     await client.query("BEGIN"); |     await client.query("BEGIN"); | ||||||
|     const copyTable = async (from: string, to: string) => { |     const copyTable = async (from: string, to: string) => { | ||||||
|  | @ -19,13 +37,49 @@ const migrate = async () => { | ||||||
|       await execQuery(`INSERT INTO ${to} SELECT * FROM ${from}`); |       await execQuery(`INSERT INTO ${to} SELECT * FROM ${from}`); | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     for (const platform of platforms) { |  | ||||||
|       await copyTable(`latest.${platform.name}`, platform.name); |  | ||||||
|     } |  | ||||||
|     await copyTable("latest.dashboards", "dashboards"); |     await copyTable("latest.dashboards", "dashboards"); | ||||||
|     await copyTable("latest.combined", "combined"); |     await copyTable("latest.combined", "questions"); | ||||||
|     await copyTable("latest.frontpage", "frontpage"); |     await copyTable("latest.frontpage", "frontpage"); | ||||||
|     await copyTable("history.h2022", "history"); |     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"); |     await client.query("COMMIT"); | ||||||
|   } catch (e) { |   } catch (e) { | ||||||
|     await client.query("ROLLBACK"); |     await client.query("ROLLBACK"); | ||||||
|  |  | ||||||
|  | @ -5,6 +5,8 @@ import https from "https"; | ||||||
| import { calculateStars } from "../utils/stars"; | import { calculateStars } from "../utils/stars"; | ||||||
| import { Forecast, Platform } from "./"; | import { Forecast, Platform } from "./"; | ||||||
| 
 | 
 | ||||||
|  | const platformName = "betfair"; | ||||||
|  | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
| let endpoint = process.env.SECRET_BETFAIR_ENDPOINT; | let endpoint = process.env.SECRET_BETFAIR_ENDPOINT; | ||||||
| 
 | 
 | ||||||
|  | @ -121,12 +123,14 @@ async function processPredictions(data) { | ||||||
|       id: id, |       id: id, | ||||||
|       title: 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: "Betfair", |       platform: platformName, | ||||||
|       description: description, |       description: description, | ||||||
|       options: options, |       options: options, | ||||||
|       timestamp: new Date().toISOString(), |       timestamp: new Date().toISOString(), | ||||||
|       qualityindicators: { |       qualityindicators: { | ||||||
|         stars: calculateStars("Betfair", { volume: prediction.totalMatched }), |         stars: calculateStars(platformName, { | ||||||
|  |           volume: prediction.totalMatched, | ||||||
|  |         }), | ||||||
|         volume: prediction.totalMatched, |         volume: prediction.totalMatched, | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|  | @ -136,7 +140,9 @@ async function processPredictions(data) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const betfair: Platform = { | export const betfair: Platform = { | ||||||
|   name: "betfair", |   name: platformName, | ||||||
|  |   label: "Betfair", | ||||||
|  |   color: "#3d674a", | ||||||
|   async fetcher() { |   async fetcher() { | ||||||
|     const data = await fetchPredictions(); |     const data = await fetchPredictions(); | ||||||
|     const results = await processPredictions(data); // somehow needed
 |     const results = await processPredictions(data); // somehow needed
 | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ 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 = `platform-${prediction.id}`; |     let id = `example-${prediction.id}`; | ||||||
|     let probability = prediction.probability; |     let probability = prediction.probability; | ||||||
|     let options = [ |     let options = [ | ||||||
|       { |       { | ||||||
|  | @ -61,6 +61,8 @@ async function processPredictions(predictions) { | ||||||
| 
 | 
 | ||||||
| export const example: Platform = { | export const example: Platform = { | ||||||
|   name: "example", |   name: "example", | ||||||
|  |   label: "Example platform", | ||||||
|  |   color: "#ff0000", | ||||||
|   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
 | ||||||
|  |  | ||||||
|  | @ -2,7 +2,9 @@ | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
| 
 | 
 | ||||||
| import { calculateStars } from "../utils/stars"; | import { calculateStars } from "../utils/stars"; | ||||||
| import { Platform } from "./"; | import { Forecast, Platform } from "./"; | ||||||
|  | 
 | ||||||
|  | const platformName = "fantasyscotus"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
| let unixtime = new Date().getTime(); | let unixtime = new Date().getTime(); | ||||||
|  | @ -65,19 +67,19 @@ 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 = []; |   let results: Forecast[] = []; | ||||||
|   for (let event of events) { |   for (let event of events) { | ||||||
|     if (event.accuracy == "") { |     if (event.accuracy == "") { | ||||||
|       let id = `fantasyscotus-${event.id}`; |       let id = `${platformName}-${event.id}`; | ||||||
|       // if the thing hasn't already resolved
 |       // if the thing hasn't already resolved
 | ||||||
|       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 = { |       let eventObject: Forecast = { | ||||||
|         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: "FantasySCOTUS", |         platform: platformName, | ||||||
|         description: `${(pAffirm * 100).toFixed(2)}% (${ |         description: `${(pAffirm * 100).toFixed(2)}% (${ | ||||||
|           predictionData.numAffirm |           predictionData.numAffirm | ||||||
|         } out of ${ |         } out of ${ | ||||||
|  | @ -100,7 +102,7 @@ async function processData(data) { | ||||||
|         timestamp: new Date().toISOString(), |         timestamp: new Date().toISOString(), | ||||||
|         qualityindicators: { |         qualityindicators: { | ||||||
|           numforecasts: Number(predictionData.numForecasts), |           numforecasts: Number(predictionData.numForecasts), | ||||||
|           stars: calculateStars("FantasySCOTUS", {}), |           stars: calculateStars(platformName, {}), | ||||||
|         }, |         }, | ||||||
|       }; |       }; | ||||||
|       results.push(eventObject); |       results.push(eventObject); | ||||||
|  | @ -112,7 +114,9 @@ async function processData(data) { | ||||||
| 
 | 
 | ||||||
| /* Body */ | /* Body */ | ||||||
| export const fantasyscotus: Platform = { | export const fantasyscotus: Platform = { | ||||||
|   name: "fantasyscotus", |   name: platformName, | ||||||
|  |   label: "FantasySCOTUS", | ||||||
|  |   color: "#231149", | ||||||
|   async fetcher() { |   async fetcher() { | ||||||
|     let rawData = await fetchData(); |     let rawData = await fetchData(); | ||||||
|     let results = await processData(rawData); |     let results = await processData(rawData); | ||||||
|  |  | ||||||
|  | @ -5,6 +5,9 @@ import { calculateStars } from "../utils/stars"; | ||||||
| import { Platform } from "./"; | import { Platform } from "./"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
|  | 
 | ||||||
|  | const platformName = "foretold"; | ||||||
|  | 
 | ||||||
| let graphQLendpoint = "https://api.foretold.io/graphql"; | let graphQLendpoint = "https://api.foretold.io/graphql"; | ||||||
| let highQualityCommunities = [ | let highQualityCommunities = [ | ||||||
|   "0104d8e8-07e4-464b-8b32-74ef22b49f21", |   "0104d8e8-07e4-464b-8b32-74ef22b49f21", | ||||||
|  | @ -54,7 +57,9 @@ async function fetchAllCommunityQuestions(communityId) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const foretold: Platform = { | export const foretold: Platform = { | ||||||
|   name: "foretold", |   name: platformName, | ||||||
|  |   label: "Foretold", | ||||||
|  |   color: "#62520b", | ||||||
|   async fetcher() { |   async fetcher() { | ||||||
|     let results = []; |     let results = []; | ||||||
|     for (let community of highQualityCommunities) { |     for (let community of highQualityCommunities) { | ||||||
|  | @ -62,7 +67,7 @@ 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 = `foretold-${question.id}`; |         let id = `${platformName}-${question.id}`; | ||||||
|         let options = []; |         let options = []; | ||||||
|         if (question.valueType == "PERCENTAGE") { |         if (question.valueType == "PERCENTAGE") { | ||||||
|           let probability = question.previousAggregate.value.percentage; |           let probability = question.previousAggregate.value.percentage; | ||||||
|  | @ -83,13 +88,13 @@ export const foretold: Platform = { | ||||||
|           id: 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: "Foretold", |           platform: platformName, | ||||||
|           description: "", |           description: "", | ||||||
|           options: options, |           options: options, | ||||||
|           timestamp: new Date().toISOString(), |           timestamp: new Date().toISOString(), | ||||||
|           qualityindicators: { |           qualityindicators: { | ||||||
|             numforecasts: Math.floor(Number(question.measurementCount) / 2), |             numforecasts: Math.floor(Number(question.measurementCount) / 2), | ||||||
|             stars: calculateStars("Foretold", {}), |             stars: calculateStars(platformName, {}), | ||||||
|           }, |           }, | ||||||
|           /*liquidity: liquidity.toFixed(2), |           /*liquidity: liquidity.toFixed(2), | ||||||
|           tradevolume: tradevolume.toFixed(2), |           tradevolume: tradevolume.toFixed(2), | ||||||
|  |  | ||||||
|  | @ -5,6 +5,8 @@ import fs from "fs"; | ||||||
| import { calculateStars } from "../utils/stars"; | import { calculateStars } from "../utils/stars"; | ||||||
| import { Platform } from "./"; | import { Platform } from "./"; | ||||||
| 
 | 
 | ||||||
|  | const platformName = "givewellopenphil"; | ||||||
|  | 
 | ||||||
| /* Support functions */ | /* Support functions */ | ||||||
| async function fetchPage(url: string) { | async function fetchPage(url: string) { | ||||||
|   let response = await axios({ |   let response = await axios({ | ||||||
|  | @ -48,11 +50,11 @@ async function main1() { | ||||||
|     let result = { |     let result = { | ||||||
|       title: title, |       title: title, | ||||||
|       url: url, |       url: url, | ||||||
|       platform: "GiveWell", |       platform: platformName, | ||||||
|       description: description, |       description: description, | ||||||
|       timestamp: new Date().toISOString(), |       timestamp: new Date().toISOString(), | ||||||
|       qualityindicators: { |       qualityindicators: { | ||||||
|         stars: calculateStars("GiveWell/OpenPhilanthropy", {}), |         stars: calculateStars(platformName, {}), | ||||||
|       }, |       }, | ||||||
|     }; // Note: This requires some processing afterwards
 |     }; // Note: This requires some processing afterwards
 | ||||||
|     // console.log(result)
 |     // console.log(result)
 | ||||||
|  | @ -65,7 +67,9 @@ async function main1() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const givewellopenphil: Platform = { | export const givewellopenphil: Platform = { | ||||||
|   name: "givewellopenphil", |   name: platformName, | ||||||
|  |   label: "GiveWell/OpenPhilanthropy", | ||||||
|  |   color: "#32407e", | ||||||
|   async fetcher() { |   async fetcher() { | ||||||
|     // main1()
 |     // main1()
 | ||||||
|     return; // not necessary to refill the DB every time
 |     return; // not necessary to refill the DB every time
 | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import { calculateStars } from "../utils/stars"; | ||||||
| import { Platform } from "./"; | import { Platform } from "./"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
|  | const platformName = "goodjudgment"; | ||||||
| let endpoint = "https://goodjudgment.io/superforecasts/"; | let endpoint = "https://goodjudgment.io/superforecasts/"; | ||||||
| String.prototype.replaceAll = function replaceAll(search, replace) { | String.prototype.replaceAll = function replaceAll(search, replace) { | ||||||
|   return this.split(search).join(replace); |   return this.split(search).join(replace); | ||||||
|  | @ -15,7 +16,9 @@ String.prototype.replaceAll = function replaceAll(search, replace) { | ||||||
| 
 | 
 | ||||||
| /* Body */ | /* Body */ | ||||||
| export const goodjudgment: Platform = { | export const goodjudgment: Platform = { | ||||||
|   name: "goodjudgment", |   name: platformName, | ||||||
|  |   label: "Good Judgment", | ||||||
|  |   color: "#7d4f1b", | ||||||
|   async fetcher() { |   async fetcher() { | ||||||
|     // Proxy fuckery
 |     // Proxy fuckery
 | ||||||
|     let proxy; |     let proxy; | ||||||
|  | @ -64,7 +67,7 @@ export const goodjudgment: Platform = { | ||||||
|       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 = `goodjudgment-${hash(title)}`; |         let id = `${platformName}-${hash(title)}`; | ||||||
|         let description = table |         let description = table | ||||||
|           .filter((row) => row["0"].includes("BACKGROUND:")) |           .filter((row) => row["0"].includes("BACKGROUND:")) | ||||||
|           .map((row) => row["0"]) |           .map((row) => row["0"]) | ||||||
|  | @ -101,12 +104,12 @@ export const goodjudgment: Platform = { | ||||||
|           id: id, |           id: id, | ||||||
|           title: title, |           title: title, | ||||||
|           url: endpoint, |           url: endpoint, | ||||||
|           platform: "Good Judgment", |           platform: platformName, | ||||||
|           description: description, |           description: description, | ||||||
|           options: options, |           options: options, | ||||||
|           timestamp: new Date().toISOString(), |           timestamp: new Date().toISOString(), | ||||||
|           qualityindicators: { |           qualityindicators: { | ||||||
|             stars: calculateStars("Good Judgment", {}), |             stars: calculateStars(platformName, {}), | ||||||
|           }, |           }, | ||||||
|           extra: { |           extra: { | ||||||
|             superforecastercommentary: analysis || "", |             superforecastercommentary: analysis || "", | ||||||
|  |  | ||||||
|  | @ -8,6 +8,8 @@ import toMarkdown from "../utils/toMarkdown"; | ||||||
| import { Platform } from "./"; | import { Platform } from "./"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
|  | const platformName = "goodjudgmentopen"; | ||||||
|  | 
 | ||||||
| let htmlEndPoint = "https://www.gjopen.com/questions?page="; | let htmlEndPoint = "https://www.gjopen.com/questions?page="; | ||||||
| let annoyingPromptUrls = [ | let 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", | ||||||
|  | @ -185,12 +187,12 @@ 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];
 |           let questionNum = url.match(questionNumRegex)[1]; //.split("questions/")[1].split("-")[0];
 | ||||||
|           let id = `goodjudmentopen-${questionNum}`; |           let id = `${platformName}-${questionNum}`; | ||||||
|           let question = { |           let question = { | ||||||
|             id: id, |             id: id, | ||||||
|             title: title, |             title: title, | ||||||
|             url: url, |             url: url, | ||||||
|             platform: "Good Judgment Open", |             platform: platformName, | ||||||
|             ...moreinfo, |             ...moreinfo, | ||||||
|           }; |           }; | ||||||
|           if (j % 30 == 0 || DEBUG_MODE == "on") { |           if (j % 30 == 0 || DEBUG_MODE == "on") { | ||||||
|  | @ -236,8 +238,10 @@ async function goodjudgmentopen_inner(cookie) { | ||||||
|   return results; |   return results; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const goodjudmentopen: Platform = { | export const goodjudgmentopen: Platform = { | ||||||
|   name: "goodjudmentopen", // note the typo! current table name is without `g`, `goodjudmentopen`
 |   name: platformName, | ||||||
|  |   label: "Good Judgment Open", | ||||||
|  |   color: "#002455", | ||||||
|   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); | ||||||
|  | @ -4,10 +4,10 @@ import { fantasyscotus } from "./fantasyscotus"; | ||||||
| import { foretold } from "./foretold"; | import { foretold } from "./foretold"; | ||||||
| import { givewellopenphil } from "./givewellopenphil"; | import { givewellopenphil } from "./givewellopenphil"; | ||||||
| import { goodjudgment } from "./goodjudgment"; | import { goodjudgment } from "./goodjudgment"; | ||||||
| import { goodjudmentopen } from "./goodjudmentopen"; | import { goodjudgmentopen } from "./goodjudgmentopen"; | ||||||
| import { infer } from "./infer"; | import { infer } from "./infer"; | ||||||
| import { kalshi } from "./kalshi"; | import { kalshi } from "./kalshi"; | ||||||
| import { manifoldmarkets } from "./manifoldmarkets"; | import { manifold } from "./manifold"; | ||||||
| import { metaculus } from "./metaculus"; | import { metaculus } from "./metaculus"; | ||||||
| import { polymarket } from "./polymarket"; | import { polymarket } from "./polymarket"; | ||||||
| import { predictit } from "./predictit"; | import { predictit } from "./predictit"; | ||||||
|  | @ -67,7 +67,9 @@ export interface Forecast { | ||||||
| export type PlatformFetcher = () => Promise<Forecast[] | null>; | export type PlatformFetcher = () => Promise<Forecast[] | null>; | ||||||
| 
 | 
 | ||||||
| export interface Platform { | export interface Platform { | ||||||
|   name: string; |   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"
 | ||||||
|  |   color: string; // used on frontend
 | ||||||
|   fetcher?: PlatformFetcher; |   fetcher?: PlatformFetcher; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -92,10 +94,10 @@ export const platforms: Platform[] = [ | ||||||
|   foretold, |   foretold, | ||||||
|   givewellopenphil, |   givewellopenphil, | ||||||
|   goodjudgment, |   goodjudgment, | ||||||
|   goodjudmentopen, |   goodjudgmentopen, | ||||||
|   infer, |   infer, | ||||||
|   kalshi, |   kalshi, | ||||||
|   manifoldmarkets, |   manifold, | ||||||
|   metaculus, |   metaculus, | ||||||
|   polymarket, |   polymarket, | ||||||
|   predictit, |   predictit, | ||||||
|  | @ -114,8 +116,8 @@ export const processPlatform = async (platform: Platform) => { | ||||||
|   if (results && results.length) { |   if (results && results.length) { | ||||||
|     await pgUpsert({ |     await pgUpsert({ | ||||||
|       contents: results, |       contents: results, | ||||||
|       tableName: platform.name, |       tableName: "questions", | ||||||
|       replace: true, |       replacePlatform: platform.name, | ||||||
|     }); |     }); | ||||||
|     console.log("Done"); |     console.log("Done"); | ||||||
|   } else { |   } else { | ||||||
|  |  | ||||||
|  | @ -8,6 +8,7 @@ import toMarkdown from "../utils/toMarkdown"; | ||||||
| import { Forecast, Platform } from "./"; | import { Forecast, Platform } from "./"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
|  | const platformName = "infer"; | ||||||
| let htmlEndPoint = "https://www.infer-pub.com/questions"; | let htmlEndPoint = "https://www.infer-pub.com/questions"; | ||||||
| String.prototype.replaceAll = function replaceAll(search, replace) { | String.prototype.replaceAll = function replaceAll(search, replace) { | ||||||
|   return this.split(search).join(replace); |   return this.split(search).join(replace); | ||||||
|  | @ -145,7 +146,7 @@ async function fetchStats(questionUrl, cookie) { | ||||||
|     qualityindicators: { |     qualityindicators: { | ||||||
|       numforecasts: Number(numforecasts), |       numforecasts: Number(numforecasts), | ||||||
|       numforecasters: Number(numforecasters), |       numforecasters: Number(numforecasters), | ||||||
|       stars: calculateStars("Infer", { numforecasts }), |       stars: calculateStars(platformName, { numforecasts }), | ||||||
|     }, |     }, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  | @ -218,7 +219,7 @@ async function infer_inner(cookie) { | ||||||
|         let moreinfo = await fetchStats(url, cookie); |         let moreinfo = await fetchStats(url, 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];
 |         let questionNum = url.match(questionNumRegex)[1]; //.split("questions/")[1].split("-")[0];
 | ||||||
|         let id = `infer-${questionNum}`; |         let id = `${platformName}-${questionNum}`; | ||||||
|         let question = { |         let question = { | ||||||
|           id: id, |           id: id, | ||||||
|           title: title, |           title: title, | ||||||
|  | @ -278,7 +279,9 @@ async function infer_inner(cookie) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const infer: Platform = { | export const infer: Platform = { | ||||||
|   name: "infer", |   name: platformName, | ||||||
|  |   label: "Infer", | ||||||
|  |   color: "#223900", | ||||||
|   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); | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import { calculateStars } from "../utils/stars"; | ||||||
| import { Platform } from "./"; | import { Platform } from "./"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
|  | 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/"; //"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'
 | ||||||
| 
 | 
 | ||||||
| async function fetchAllMarkets() { | async function fetchAllMarkets() { | ||||||
|  | @ -34,17 +35,17 @@ async function processMarkets(markets) { | ||||||
|         type: "PROBABILITY", |         type: "PROBABILITY", | ||||||
|       }, |       }, | ||||||
|     ]; |     ]; | ||||||
|     let id = `kalshi-${market.id}`; |     let id = `${platformName}-${market.id}`; | ||||||
|     let result = { |     let result = { | ||||||
|       id: 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: "Kalshi", |       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: options, | ||||||
|       timestamp: new Date().toISOString(), |       timestamp: new Date().toISOString(), | ||||||
|       qualityindicators: { |       qualityindicators: { | ||||||
|         stars: calculateStars("Kalshi", { |         stars: calculateStars(platformName, { | ||||||
|           shares_volume: market.volume, |           shares_volume: market.volume, | ||||||
|           interest: market.open_interest, |           interest: market.open_interest, | ||||||
|         }), |         }), | ||||||
|  | @ -70,7 +71,9 @@ async function processMarkets(markets) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const kalshi: Platform = { | export const kalshi: Platform = { | ||||||
|   name: "kalshi", |   name: platformName, | ||||||
|  |   label: "Kalshi", | ||||||
|  |   color: "#615691", | ||||||
|   fetcher: async function () { |   fetcher: async function () { | ||||||
|     let markets = await fetchAllMarkets(); |     let markets = await fetchAllMarkets(); | ||||||
|     return await processMarkets(markets); |     return await processMarkets(markets); | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import { calculateStars } from "../utils/stars"; | ||||||
| import { Platform } from "./"; | import { Platform } from "./"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
|  | const platformName = "manifold"; | ||||||
| let endpoint = "https://manifold.markets/api/v0/markets"; | let 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
 | ||||||
| 
 | 
 | ||||||
|  | @ -44,7 +45,7 @@ function showStatistics(results) { | ||||||
| 
 | 
 | ||||||
| async function processPredictions(predictions) { | async function processPredictions(predictions) { | ||||||
|   let results = await predictions.map((prediction) => { |   let results = await predictions.map((prediction) => { | ||||||
|     let id = `manifold-${prediction.id}`; |     let id = `manifold-${prediction.id}`; // oops, doesn't match platform name
 | ||||||
|     let probability = prediction.probability; |     let probability = prediction.probability; | ||||||
|     let options = [ |     let options = [ | ||||||
|       { |       { | ||||||
|  | @ -67,7 +68,7 @@ async function processPredictions(predictions) { | ||||||
|       options: options, |       options: options, | ||||||
|       timestamp: new Date().toISOString(), |       timestamp: new Date().toISOString(), | ||||||
|       qualityindicators: { |       qualityindicators: { | ||||||
|         stars: calculateStars("Manifold Markets", { |         stars: calculateStars(platformName, { | ||||||
|           volume7Days: prediction.volume7Days, |           volume7Days: prediction.volume7Days, | ||||||
|           volume24Hours: prediction.volume24Hours, |           volume24Hours: prediction.volume24Hours, | ||||||
|           pool: prediction.pool, |           pool: prediction.pool, | ||||||
|  | @ -88,8 +89,10 @@ async function processPredictions(predictions) { | ||||||
|   return unresolvedResults; //resultsProcessed
 |   return unresolvedResults; //resultsProcessed
 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const manifoldmarkets: Platform = { | export const manifold: Platform = { | ||||||
|   name: "manifoldmarkets", |   name: platformName, | ||||||
|  |   label: "Manifold Markets", | ||||||
|  |   color: "#793466", | ||||||
|   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
 | ||||||
|  | @ -6,6 +6,7 @@ import toMarkdown from "../utils/toMarkdown"; | ||||||
| import { Platform } from "./"; | import { Platform } from "./"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
|  | const platformName = "metaculus"; | ||||||
| let jsonEndPoint = "https://www.metaculus.com/api2/questions/?page="; | 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"; | ||||||
|  | @ -94,7 +95,9 @@ async function fetchMetaculusQuestionDescription(slug) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const metaculus: Platform = { | export const metaculus: Platform = { | ||||||
|   name: "metaculus", |   name: platformName, | ||||||
|  |   label: "Metaculus", | ||||||
|  |   color: "#006669", | ||||||
|   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)
 | ||||||
|  | @ -144,18 +147,18 @@ export const metaculus: Platform = { | ||||||
|                 }, |                 }, | ||||||
|               ]; |               ]; | ||||||
|             } |             } | ||||||
|             let id = `metaculus-${result.id}`; |             let id = `${platformName}-${result.id}`; | ||||||
|             let interestingInfo = { |             let interestingInfo = { | ||||||
|               id: 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: "Metaculus", |               platform: platformName, | ||||||
|               description: description, |               description: description, | ||||||
|               options: options, |               options: options, | ||||||
|               timestamp: new Date().toISOString(), |               timestamp: new Date().toISOString(), | ||||||
|               qualityindicators: { |               qualityindicators: { | ||||||
|                 numforecasts: Number(result.number_of_predictions), |                 numforecasts: Number(result.number_of_predictions), | ||||||
|                 stars: calculateStars("Metaculus", { |                 stars: calculateStars(platformName, { | ||||||
|                   numforecasts: result.number_of_predictions, |                   numforecasts: result.number_of_predictions, | ||||||
|                 }), |                 }), | ||||||
|               }, |               }, | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import { calculateStars } from "../utils/stars"; | ||||||
| import { Forecast, Platform } from "./"; | import { Forecast, Platform } from "./"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
|  | const platformName = "polymarket"; | ||||||
| let graphQLendpoint = | let 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"; // "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'
 | ||||||
| let units = 10 ** 6; | let units = 10 ** 6; | ||||||
|  | @ -63,7 +64,9 @@ async function fetchIndividualContractData(marketMakerAddress) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const polymarket: Platform = { | export const polymarket: Platform = { | ||||||
|   name: "polymarket", |   name: platformName, | ||||||
|  |   label: "PolyMarket", | ||||||
|  |   color: "#00314e", | ||||||
|   async fetcher() { |   async fetcher() { | ||||||
|     let results: Forecast[] = []; |     let results: Forecast[] = []; | ||||||
|     let webpageEndpointData = await fetchAllContractInfo(); |     let webpageEndpointData = await fetchAllContractInfo(); | ||||||
|  | @ -79,7 +82,7 @@ export const polymarket: Platform = { | ||||||
|         ); |         ); | ||||||
|         if (moreMarketAnswer.length > 0) { |         if (moreMarketAnswer.length > 0) { | ||||||
|           let moreMarketInfo = moreMarketAnswer[0]; |           let moreMarketInfo = moreMarketAnswer[0]; | ||||||
|           let id = `polymarket-${addressLowerCase.slice(0, 10)}`; |           let id = `${platformName}-${addressLowerCase.slice(0, 10)}`; | ||||||
|           // console.log(id);
 |           // console.log(id);
 | ||||||
|           let numforecasts = Number(moreMarketInfo.tradesQuantity); |           let numforecasts = Number(moreMarketInfo.tradesQuantity); | ||||||
|           let tradevolume = |           let tradevolume = | ||||||
|  | @ -103,7 +106,7 @@ 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: "PolyMarket", |             platform: platformName, | ||||||
|             description: marketInfo.description, |             description: marketInfo.description, | ||||||
|             options: options, |             options: options, | ||||||
|             timestamp: new Date().toISOString(), |             timestamp: new Date().toISOString(), | ||||||
|  | @ -111,7 +114,7 @@ export const polymarket: Platform = { | ||||||
|               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("Polymarket", { |               stars: calculateStars(platformName, { | ||||||
|                 liquidity, |                 liquidity, | ||||||
|                 option: options[0], |                 option: options[0], | ||||||
|                 volume: tradevolume, |                 volume: tradevolume, | ||||||
|  |  | ||||||
|  | @ -1,10 +1,11 @@ | ||||||
| /* Imports */ |  | ||||||
| import axios from "axios"; | 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 { Platform } from "./"; | ||||||
| 
 | 
 | ||||||
|  | const platformName = "predictit"; | ||||||
|  | 
 | ||||||
| /* Support functions */ | /* Support functions */ | ||||||
| async function fetchmarkets() { | async function fetchmarkets() { | ||||||
|   let response = await axios({ |   let response = await axios({ | ||||||
|  | @ -39,7 +40,9 @@ function sleep(ms: number) { | ||||||
| 
 | 
 | ||||||
| /* Body */ | /* Body */ | ||||||
| export const predictit: Platform = { | export const predictit: Platform = { | ||||||
|   name: "predictit", |   name: platformName, | ||||||
|  |   label: "PredictIt", | ||||||
|  |   color: "#460c00", | ||||||
|   async fetcher() { |   async fetcher() { | ||||||
|     let markets = await fetchmarkets(); |     let markets = await fetchmarkets(); | ||||||
|     let marketVolumes = await fetchmarketvolumes(); |     let marketVolumes = await fetchmarketvolumes(); | ||||||
|  | @ -53,7 +56,7 @@ export const predictit: Platform = { | ||||||
|     let results = []; |     let results = []; | ||||||
|     for (let market of markets) { |     for (let market of markets) { | ||||||
|       // console.log(market.name)
 |       // console.log(market.name)
 | ||||||
|       let id = `predictit-${market.id}`; |       let id = `${platformName}-${market.id}`; | ||||||
|       let isbinary = market.contracts.length == 1; |       let isbinary = market.contracts.length == 1; | ||||||
|       await sleep(3000 * (1 + Math.random())); |       await sleep(3000 * (1 + Math.random())); | ||||||
|       let descriptionraw = await fetchmarketrules(market.id); |       let descriptionraw = await fetchmarketrules(market.id); | ||||||
|  | @ -97,12 +100,12 @@ export const predictit: Platform = { | ||||||
|         id: id, |         id: id, | ||||||
|         title: market["name"], |         title: market["name"], | ||||||
|         url: market.url, |         url: market.url, | ||||||
|         platform: "PredictIt", |         platform: platformName, | ||||||
|         description: description, |         description: description, | ||||||
|         options: options, |         options: options, | ||||||
|         timestamp: new Date().toISOString(), |         timestamp: new Date().toISOString(), | ||||||
|         qualityindicators: { |         qualityindicators: { | ||||||
|           stars: calculateStars("PredictIt", {}), |           stars: calculateStars(platformName, {}), | ||||||
|           shares_volume: shares_volume, |           shares_volume: shares_volume, | ||||||
|         }, |         }, | ||||||
|       }; |       }; | ||||||
|  |  | ||||||
|  | @ -1,11 +1,11 @@ | ||||||
| /* Imports */ |  | ||||||
| import axios from "axios"; | 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 { Forecast, Platform } from "./"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
|  | const platformName = "rootclaim"; | ||||||
| let jsonEndpoint = | let jsonEndpoint = | ||||||
|   "https://www.rootclaim.com/main_page_stories?number=100&offset=0"; //"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://www.rootclaim.com/main_page_stories?number=100&offset=0"; //"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'
 | ||||||
| 
 | 
 | ||||||
|  | @ -24,12 +24,14 @@ async function fetchAllRootclaims() { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const rootclaim: Platform = { | export const rootclaim: Platform = { | ||||||
|   name: "rootclaim", |   name: platformName, | ||||||
|  |   label: "Rootclaim", | ||||||
|  |   color: "#0d1624", | ||||||
|   async fetcher() { |   async fetcher() { | ||||||
|     let claims = await fetchAllRootclaims(); |     let claims = await fetchAllRootclaims(); | ||||||
|     let results = []; |     let results: Forecast[] = []; | ||||||
|     for (let claim of claims) { |     for (let claim of claims) { | ||||||
|       let id = `rootclaim-${claim.slug.toLowerCase()}`; |       let id = `${platformName}-${claim.slug.toLowerCase()}`; | ||||||
|       let options = []; |       let options = []; | ||||||
|       for (let scenario of claim.scenarios) { |       for (let scenario of claim.scenarios) { | ||||||
|         //console.log(scenario)
 |         //console.log(scenario)
 | ||||||
|  | @ -42,17 +44,17 @@ export const rootclaim: Platform = { | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|       let claimUrlPath = claim.created_at < "2020" ? "claims" : "analysis"; |       let claimUrlPath = claim.created_at < "2020" ? "claims" : "analysis"; | ||||||
|       let obj = { |       let obj: Forecast = { | ||||||
|         id: id, |         id: id, | ||||||
|         title: toMarkdown(claim.question).replace("\n", ""), |         title: toMarkdown(claim.question).replace("\n", ""), | ||||||
|         url: `https://www.rootclaim.com/${claimUrlPath}/${claim.slug}`, |         url: `https://www.rootclaim.com/${claimUrlPath}/${claim.slug}`, | ||||||
|         platform: "Rootclaim", |         platform: platformName, | ||||||
|         description: toMarkdown(claim.background).replace("'", "'"), |         description: toMarkdown(claim.background).replace("'", "'"), | ||||||
|         options: options, |         options: options, | ||||||
|         timestamp: new Date().toISOString(), |         timestamp: new Date().toISOString(), | ||||||
|         qualityindicators: { |         qualityindicators: { | ||||||
|           numforecasts: 1, |           numforecasts: 1, | ||||||
|           stars: calculateStars("Rootclaim", {}), |           stars: calculateStars(platformName, {}), | ||||||
|         }, |         }, | ||||||
|       }; |       }; | ||||||
|       results.push(obj); |       results.push(obj); | ||||||
|  |  | ||||||
|  | @ -1,13 +1,14 @@ | ||||||
| /* Imports */ |  | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
| 
 | 
 | ||||||
| import { calculateStars } from "../utils/stars"; | import { calculateStars } from "../utils/stars"; | ||||||
| import { Platform } from "./"; | import { Platform } from "./"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
|  | const platformName = "smarkets"; | ||||||
| let htmlEndPointEntrance = "https://api.smarkets.com/v3/events/"; | let htmlEndPointEntrance = "https://api.smarkets.com/v3/events/"; | ||||||
| let VERBOSE = false; | let VERBOSE = false; | ||||||
| let empty = () => 0; | let empty = () => 0; | ||||||
|  | 
 | ||||||
| /* Support functions */ | /* Support functions */ | ||||||
| 
 | 
 | ||||||
| async function fetchEvents(url) { | async function fetchEvents(url) { | ||||||
|  | @ -60,7 +61,9 @@ async function fetchPrices(marketid) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const smarkets: Platform = { | export const smarkets: Platform = { | ||||||
|   name: "smarkets", |   name: platformName, | ||||||
|  |   label: "Smarkets", | ||||||
|  |   color: "#6f5b41", | ||||||
|   async fetcher() { |   async fetcher() { | ||||||
|     let htmlPath = |     let htmlPath = | ||||||
|       "?state=new&state=upcoming&state=live&type_domain=politics&type_scope=single_event&with_new_type=true&sort=id&limit=50"; |       "?state=new&state=upcoming&state=live&type_domain=politics&type_scope=single_event&with_new_type=true&sort=id&limit=50"; | ||||||
|  | @ -93,7 +96,7 @@ export const smarkets: Platform = { | ||||||
|     for (let market of markets) { |     for (let market of markets) { | ||||||
|       VERBOSE ? console.log("================") : empty(); |       VERBOSE ? console.log("================") : empty(); | ||||||
|       VERBOSE ? console.log("Market: ", market) : empty(); |       VERBOSE ? console.log("Market: ", market) : empty(); | ||||||
|       let id = `smarkets-${market.id}`; |       let id = `${platformName}-${market.id}`; | ||||||
|       let name = market.name; |       let name = market.name; | ||||||
| 
 | 
 | ||||||
|       let contracts = await fetchContracts(market.id); |       let contracts = await fetchContracts(market.id); | ||||||
|  | @ -160,12 +163,12 @@ export const smarkets: Platform = { | ||||||
|         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: "Smarkets", |         platform: platformName, | ||||||
|         description: market.description, |         description: market.description, | ||||||
|         options: options, |         options: options, | ||||||
|         timestamp: new Date().toISOString(), |         timestamp: new Date().toISOString(), | ||||||
|         qualityindicators: { |         qualityindicators: { | ||||||
|           stars: calculateStars("Smarkets", {}), |           stars: calculateStars(platformName, {}), | ||||||
|         }, |         }, | ||||||
|       }; |       }; | ||||||
|       VERBOSE ? console.log(result) : empty(); |       VERBOSE ? console.log(result) : empty(); | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import { calculateStars } from "../utils/stars"; | ||||||
| import { Platform } from "./"; | import { Platform } from "./"; | ||||||
| 
 | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
|  | const platformName = "wildeford"; | ||||||
| const SHEET_ID = "1xcgYF7Q0D95TPHLLSgwhWBHFrWZUGJn7yTyAhDR4vi0"; // spreadsheet key is the long id in the sheets URL
 | const SHEET_ID = "1xcgYF7Q0D95TPHLLSgwhWBHFrWZUGJn7yTyAhDR4vi0"; // spreadsheet key is the long id in the sheets URL
 | ||||||
| const endpoint = `https://docs.google.com/spreadsheets/d/${SHEET_ID}/edit#gid=0`; | 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
 | ||||||
|  | @ -73,7 +74,7 @@ async function processPredictions(predictions) { | ||||||
|   ); |   ); | ||||||
|   let results = currentPredictions.map((prediction) => { |   let results = currentPredictions.map((prediction) => { | ||||||
|     let title = prediction["Prediction"].replace(" [update]", ""); |     let title = prediction["Prediction"].replace(" [update]", ""); | ||||||
|     let id = `wildeford-${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 = [ | ||||||
|       { |       { | ||||||
|  | @ -91,14 +92,14 @@ async function processPredictions(predictions) { | ||||||
|       id: id, |       id: id, | ||||||
|       title: title, |       title: title, | ||||||
|       url: prediction["url"], |       url: prediction["url"], | ||||||
|       platform: "Peter Wildeford", |       platform: platformName, | ||||||
|       description: prediction["Notes"] || "", |       description: prediction["Notes"] || "", | ||||||
|       options: options, |       options: options, | ||||||
|       timestamp: new Date( |       timestamp: new Date( | ||||||
|         Date.parse(prediction["Prediction Date"] + "Z") |         Date.parse(prediction["Prediction Date"] + "Z") | ||||||
|       ).toISOString(), |       ).toISOString(), | ||||||
|       qualityindicators: { |       qualityindicators: { | ||||||
|         stars: calculateStars("Peter Wildeford", null), |         stars: calculateStars(platformName, null), | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
|     return result; |     return result; | ||||||
|  | @ -120,7 +121,9 @@ export async function wildeford_inner(google_api_key) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const wildeford: Platform = { | export const wildeford: Platform = { | ||||||
|   name: "wildeford", |   name: platformName, | ||||||
|  |   label: "Peter Wildeford", | ||||||
|  |   color: "#984158", | ||||||
|   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); | ||||||
|  |  | ||||||
|  | @ -1,15 +1,25 @@ | ||||||
| import fs from "fs"; | import fs from "fs"; | ||||||
| 
 | 
 | ||||||
|  | import { hash } from "../utils/hash"; | ||||||
| import { Platform } from "./"; | import { Platform } from "./"; | ||||||
| 
 | 
 | ||||||
|  | const platformName = "xrisk"; | ||||||
|  | 
 | ||||||
| export const xrisk: Platform = { | export const xrisk: Platform = { | ||||||
|   name: "xrisk", |   name: "xrisk", | ||||||
|  |   label: "X-risk estimates", | ||||||
|  |   color: "#272600", | ||||||
|   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", | ||||||
|     }); |     }); | ||||||
|     const results = JSON.parse(fileRaw); |     let results = JSON.parse(fileRaw); | ||||||
|  |     results = results.map((item) => ({ | ||||||
|  |       ...item, | ||||||
|  |       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; | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,34 +1,13 @@ | ||||||
| import algoliasearch from "algoliasearch"; | import algoliasearch from "algoliasearch"; | ||||||
| 
 | 
 | ||||||
| import { pgReadWithReadCredentials } from "../database/pg-wrapper"; | import { pgReadWithReadCredentials } from "../database/pg-wrapper"; | ||||||
| import { mergeEverythingInner } from "../flow/mergeEverything"; | 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 async function rebuildAlgoliaDatabaseTheHardWay() { |  | ||||||
|   console.log("Doing this the hard way"); |  | ||||||
|   let records = await mergeEverythingInner(); |  | ||||||
|   records = records.map((record, index: number) => ({ |  | ||||||
|     ...record, |  | ||||||
|     has_numforecasts: record.numforecasts ? true : false, |  | ||||||
|     objectID: index, |  | ||||||
|   })); |  | ||||||
|   // this is necessary to filter by missing attributes https://www.algolia.com/doc/guides/managing-results/refine-results/filtering/how-to/filter-by-null-or-missing-attributes/
 |  | ||||||
| 
 |  | ||||||
|   if (index.exists()) { |  | ||||||
|     console.log("Index exists"); |  | ||||||
|     index |  | ||||||
|       .replaceAllObjects(records, { safe: true }) |  | ||||||
|       .catch((error) => console.log(error)); |  | ||||||
|     console.log( |  | ||||||
|       `Pushed ${records.length} records. Algolia will update asynchronously` |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| let getoptionsstringforsearch = (record: any) => { | let getoptionsstringforsearch = (record: any) => { | ||||||
|   let result = ""; |   let result = ""; | ||||||
|   if (!!record.options && record.options.length > 0) { |   if (!!record.options && record.options.length > 0) { | ||||||
|  | @ -42,11 +21,16 @@ let getoptionsstringforsearch = (record: any) => { | ||||||
| 
 | 
 | ||||||
| export async function rebuildAlgoliaDatabaseTheEasyWay() { | export async function rebuildAlgoliaDatabaseTheEasyWay() { | ||||||
|   let records: any[] = await pgReadWithReadCredentials({ |   let records: any[] = await pgReadWithReadCredentials({ | ||||||
|     tableName: "combined", |     tableName: "questions", | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|  |   const platformNameToLabel = Object.fromEntries( | ||||||
|  |     platforms.map((platform) => [platform.name, platform.label]) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|   records = records.map((record, index: number) => ({ |   records = records.map((record, index: number) => ({ | ||||||
|     ...record, |     ...record, | ||||||
|  |     platformLabel: platformNameToLabel[record.platform] || record.platform, | ||||||
|     has_numforecasts: record.numforecasts ? true : false, |     has_numforecasts: record.numforecasts ? true : false, | ||||||
|     objectID: index, |     objectID: index, | ||||||
|     optionsstringforsearch: getoptionsstringforsearch(record), |     optionsstringforsearch: getoptionsstringforsearch(record), | ||||||
|  | @ -62,4 +46,4 @@ export async function rebuildAlgoliaDatabaseTheEasyWay() { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const rebuildAlgoliaDatabase = rebuildAlgoliaDatabaseTheEasyWay; //rebuildAlgoliaDatabaseTheHardWay
 | export const rebuildAlgoliaDatabase = rebuildAlgoliaDatabaseTheEasyWay; | ||||||
|  |  | ||||||
|  | @ -24,7 +24,7 @@ let main = async () => { | ||||||
|     "PredictIt", |     "PredictIt", | ||||||
|     "Rootclaim", |     "Rootclaim", | ||||||
|   ]; |   ]; | ||||||
|   let json = await pgReadWithReadCredentials({ tableName: "combined" }); |   let json = await pgReadWithReadCredentials({ tableName: "questions" }); | ||||||
|   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)
 | ||||||
|  |  | ||||||
|  | @ -26,7 +26,7 @@ let shuffleArray = (array) => { | ||||||
| 
 | 
 | ||||||
| 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 pgReadWithReadCredentials({ tableName: "combined" }); |   let json = await pgReadWithReadCredentials({ tableName: "questions" }); | ||||||
|   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)
 | ||||||
|  |  | ||||||
|  | @ -9,7 +9,7 @@ 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 pgReadWithReadCredentials({ tableName: "combined" }); //JSON.parse(rawdata)
 |   let data = await pgReadWithReadCredentials({ tableName: "questions" }); //JSON.parse(rawdata)
 | ||||||
|   let processDescription = (description) => { |   let processDescription = (description) => { | ||||||
|     if (description == null || description == undefined || description == "") { |     if (description == null || description == undefined || description == "") { | ||||||
|       return ""; |       return ""; | ||||||
|  |  | ||||||
|  | @ -105,7 +105,7 @@ function calculateStarsGiveWellOpenPhil(data) { | ||||||
|   return starsInteger; |   return starsInteger; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function calculateStarsGoodJudment(data) { | function calculateStarsGoodJudgment(data) { | ||||||
|   let nuno = (data) => 4; |   let nuno = (data) => 4; | ||||||
|   let eli = (data) => 4; |   let eli = (data) => 4; | ||||||
|   let misha = (data) => 3.5; |   let misha = (data) => 3.5; | ||||||
|  | @ -114,7 +114,7 @@ function calculateStarsGoodJudment(data) { | ||||||
|   return starsInteger; |   return starsInteger; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function calculateStarsGoodJudmentOpen(data) { | function calculateStarsGoodJudgmentOpen(data) { | ||||||
|   let nuno = (data) => (data.numforecasts > 100 ? 3 : 2); |   let nuno = (data) => (data.numforecasts > 100 ? 3 : 2); | ||||||
|   let eli = (data) => 3; |   let eli = (data) => 3; | ||||||
|   let misha = (data) => |   let misha = (data) => | ||||||
|  | @ -173,7 +173,7 @@ function calculateStarsLadbrokes(data) { | ||||||
|   return starsInteger; |   return starsInteger; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function calculateStarsManifoldMarkets(data) { | function calculateStarsManifold(data) { | ||||||
|   let nuno = (data) => |   let nuno = (data) => | ||||||
|     data.volume7Days > 250 || (data.pool > 500 && data.volume7Days > 100) |     data.volume7Days > 250 || (data.pool > 500 && data.volume7Days > 100) | ||||||
|       ? 2 |       ? 2 | ||||||
|  | @ -268,15 +268,56 @@ function calculateStarsWilliamHill(data) { | ||||||
|   return starsInteger; |   return starsInteger; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function calculateStars(platform, data) { | export function calculateStars(platform: string, data) { | ||||||
|   let stars = 2; |   let stars = 2; | ||||||
|   switch (platform) { |   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": |     case "AstralCodexTen": | ||||||
|       stars = calculateStarsAstralCodexTen(data); |       stars = calculateStarsAstralCodexTen(data); | ||||||
|       break; |       break; | ||||||
|     case "Betfair": |  | ||||||
|       stars = calculateStarsBetfair(data); |  | ||||||
|       break; |  | ||||||
|     case "CoupCast": |     case "CoupCast": | ||||||
|       stars = calculateStarsCoupCast(data); |       stars = calculateStarsCoupCast(data); | ||||||
|       break; |       break; | ||||||
|  | @ -289,54 +330,15 @@ export function calculateStars(platform, data) { | ||||||
|     case "Estimize": |     case "Estimize": | ||||||
|       stars = calculateStarsEstimize(data); |       stars = calculateStarsEstimize(data); | ||||||
|       break; |       break; | ||||||
|     case "Foretold": |  | ||||||
|       stars = calculateStarsForetold(data); |  | ||||||
|       break; |  | ||||||
|     case "GiveWell/OpenPhilanthropy": |  | ||||||
|       stars = calculateStarsGiveWellOpenPhil(data); |  | ||||||
|       break; |  | ||||||
|     case "Good Judgment": |  | ||||||
|       stars = calculateStarsGoodJudment(data); |  | ||||||
|       break; |  | ||||||
|     case "Good Judgment Open": |  | ||||||
|       stars = calculateStarsGoodJudmentOpen(data); |  | ||||||
|       break; |  | ||||||
|     case "Hypermind": |     case "Hypermind": | ||||||
|       stars = calculateStarsHypermind(data); |       stars = calculateStarsHypermind(data); | ||||||
|       break; |       break; | ||||||
|     case "Infer": |  | ||||||
|       stars = calculateStarsInfer(data); |  | ||||||
|       break; |  | ||||||
|     case "Kalshi": |  | ||||||
|       stars = calculateStarsKalshi(data); |  | ||||||
|       break; |  | ||||||
|     case "Ladbrokes": |     case "Ladbrokes": | ||||||
|       stars = calculateStarsLadbrokes(data); |       stars = calculateStarsLadbrokes(data); | ||||||
|       break; |       break; | ||||||
|     case "Manifold Markets": |  | ||||||
|       stars = calculateStarsManifoldMarkets(data); |  | ||||||
|       break; |  | ||||||
|     case "Metaculus": |  | ||||||
|       stars = calculateStarsMetaculus(data); |  | ||||||
|       break; |  | ||||||
|     case "Omen": |     case "Omen": | ||||||
|       stars = calculateStarsOmen(data); |       stars = calculateStarsOmen(data); | ||||||
|       break; |       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 "Peter Wildeford": |  | ||||||
|       stars = calculateStarsWildeford(data); |  | ||||||
|       break; |  | ||||||
|     case "WilliamHill": |     case "WilliamHill": | ||||||
|       stars = calculateStarsWilliamHill(data); |       stars = calculateStarsWilliamHill(data); | ||||||
|       break; |       break; | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| import { NextApiRequest, NextApiResponse } from "next/types"; | import { NextApiRequest, NextApiResponse } from "next/types"; | ||||||
| 
 | 
 | ||||||
| import { getFrontpageFullRaw } from "../../backend/frontpage"; | import { getFrontpageFull } from "../../backend/frontpage"; | ||||||
| 
 | 
 | ||||||
| export default async function handler( | export default async function handler( | ||||||
|   req: NextApiRequest, |   req: NextApiRequest, | ||||||
|   res: NextApiResponse |   res: NextApiResponse | ||||||
| ) { | ) { | ||||||
|   let frontpageFull = await getFrontpageFullRaw(); |   let frontpageFull = await getFrontpageFull(); | ||||||
|   console.log(frontpageFull.map((element) => element.title).slice(0, 5)); |   console.log(frontpageFull.map((element) => element.title).slice(0, 5)); | ||||||
|   console.log("..."); |   console.log("..."); | ||||||
|   res.status(200).json(frontpageFull); |   res.status(200).json(frontpageFull); | ||||||
|  |  | ||||||
|  | @ -1,7 +1,7 @@ | ||||||
| import crypto from "crypto"; |  | ||||||
| import { NextApiRequest, NextApiResponse } from "next/types"; | import { NextApiRequest, NextApiResponse } from "next/types"; | ||||||
| 
 | 
 | ||||||
| import { pgInsertIntoDashboard } from "../../backend/database/pg-wrapper"; | import { pgInsertIntoDashboard } from "../../backend/database/pg-wrapper"; | ||||||
|  | import { hash } from "../../backend/utils/hash"; | ||||||
| 
 | 
 | ||||||
| export default async function handler( | export default async function handler( | ||||||
|   req: NextApiRequest, |   req: NextApiRequest, | ||||||
|  | @ -14,8 +14,6 @@ export default async function handler( | ||||||
| 
 | 
 | ||||||
|   let body = req.body; |   let body = req.body; | ||||||
|   console.log(body); |   console.log(body); | ||||||
|   const hash = (s: string) => |  | ||||||
|     crypto.createHash("sha256").update(s).digest("hex").slice(0, 10); |  | ||||||
|   try { |   try { | ||||||
|     let id = hash(JSON.stringify(body.ids)); |     let id = hash(JSON.stringify(body.ids)); | ||||||
|     let pgResponse = await pgInsertIntoDashboard({ |     let pgResponse = await pgInsertIntoDashboard({ | ||||||
|  |  | ||||||
|  | @ -23,7 +23,7 @@ export default async function handler( | ||||||
|     console.log(dashboardItem); |     console.log(dashboardItem); | ||||||
|     let dashboardContents = await pgGetByIds({ |     let dashboardContents = await pgGetByIds({ | ||||||
|       ids: dashboardItem.contents, |       ids: dashboardItem.contents, | ||||||
|       table: "combined", |       table: "questions", | ||||||
|     }); |     }); | ||||||
|     res.status(200).send({ |     res.status(200).send({ | ||||||
|       dashboardContents, |       dashboardContents, | ||||||
|  |  | ||||||
|  | @ -1,12 +1,12 @@ | ||||||
| import { NextApiRequest, NextApiResponse } from "next/types"; | import { NextApiRequest, NextApiResponse } from "next/types"; | ||||||
| 
 | 
 | ||||||
| import { getFrontpageRaw } from "../../backend/frontpage"; | import { getFrontpage } from "../../backend/frontpage"; | ||||||
| 
 | 
 | ||||||
| export default async function handler( | export default async function handler( | ||||||
|   req: NextApiRequest, |   req: NextApiRequest, | ||||||
|   res: NextApiResponse |   res: NextApiResponse | ||||||
| ) { | ) { | ||||||
|   let frontpageElements = await getFrontpageRaw(); |   let frontpageElements = await getFrontpage(); | ||||||
|   console.log(frontpageElements.map((element) => element.title).slice(0, 5)); |   console.log(frontpageElements.map((element) => element.title).slice(0, 5)); | ||||||
|   console.log("..."); |   console.log("..."); | ||||||
|   res.status(200).json(frontpageElements); |   res.status(200).json(frontpageElements); | ||||||
|  |  | ||||||
|  | @ -6,7 +6,7 @@ export default async function handler( | ||||||
|   req: NextApiRequest, |   req: NextApiRequest, | ||||||
|   res: NextApiResponse |   res: NextApiResponse | ||||||
| ) { | ) { | ||||||
|   let allQuestions = await pgRead({ tableName: "combined" }); |   let allQuestions = await pgRead({ tableName: "questions" }); | ||||||
|   console.log(allQuestions.map((element) => element.title).slice(0, 5)); |   console.log(allQuestions.map((element) => element.title).slice(0, 5)); | ||||||
|   console.log("..."); |   console.log("..."); | ||||||
|   res.status(200).json(allQuestions); |   res.status(200).json(allQuestions); | ||||||
|  |  | ||||||
|  | @ -8,17 +8,11 @@ import CommonDisplay from "../web/search/CommonDisplay"; | ||||||
| 
 | 
 | ||||||
| export { getServerSideProps } from "../web/search/anySearchPage"; | export { getServerSideProps } from "../web/search/anySearchPage"; | ||||||
| 
 | 
 | ||||||
| const CapturePage: NextPage<Props> = ({ | const CapturePage: NextPage<Props> = (props) => { | ||||||
|   defaultResults, |  | ||||||
|   initialResults, |  | ||||||
|   initialQueryParameters, |  | ||||||
| }) => { |  | ||||||
|   return ( |   return ( | ||||||
|     <Layout page={"capture"}> |     <Layout page={"capture"}> | ||||||
|       <CommonDisplay |       <CommonDisplay | ||||||
|         defaultResults={defaultResults} |         {...props} | ||||||
|         initialResults={initialResults} |  | ||||||
|         initialQueryParameters={initialQueryParameters} |  | ||||||
|         hasSearchbar={true} |         hasSearchbar={true} | ||||||
|         hasCapture={true} |         hasCapture={true} | ||||||
|         hasAdvancedOptions={false} |         hasAdvancedOptions={false} | ||||||
|  |  | ||||||
|  | @ -8,17 +8,11 @@ import CommonDisplay from "../web/search/CommonDisplay"; | ||||||
| 
 | 
 | ||||||
| export { getServerSideProps } from "../web/search/anySearchPage"; | export { getServerSideProps } from "../web/search/anySearchPage"; | ||||||
| 
 | 
 | ||||||
| const IndexPage: NextPage<Props> = ({ | const IndexPage: NextPage<Props> = (props) => { | ||||||
|   defaultResults, |  | ||||||
|   initialResults, |  | ||||||
|   initialQueryParameters, |  | ||||||
| }) => { |  | ||||||
|   return ( |   return ( | ||||||
|     <Layout page={"search"}> |     <Layout page={"search"}> | ||||||
|       <CommonDisplay |       <CommonDisplay | ||||||
|         defaultResults={defaultResults} |         {...props} | ||||||
|         initialResults={initialResults} |  | ||||||
|         initialQueryParameters={initialQueryParameters} |  | ||||||
|         hasSearchbar={true} |         hasSearchbar={true} | ||||||
|         hasCapture={false} |         hasCapture={false} | ||||||
|         hasAdvancedOptions={true} |         hasAdvancedOptions={true} | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ | ||||||
| import React from "react"; | import React from "react"; | ||||||
| 
 | 
 | ||||||
| import { displayForecast } from "../web/display/displayForecasts"; | import { displayForecast } from "../web/display/displayForecasts"; | ||||||
| import { platformsWithLabels } from "../web/platforms"; | import { FrontendForecast } from "../web/platforms"; | ||||||
| import searchAccordingToQueryData from "../web/worker/searchAccordingToQueryData"; | import searchAccordingToQueryData from "../web/worker/searchAccordingToQueryData"; | ||||||
| 
 | 
 | ||||||
| /* Helper functions */ | /* Helper functions */ | ||||||
|  | @ -15,18 +15,12 @@ export async function getServerSideProps(context) { | ||||||
|     query: "", |     query: "", | ||||||
|     starsThreshold: 2, |     starsThreshold: 2, | ||||||
|     forecastsThreshold: 0, |     forecastsThreshold: 0, | ||||||
|     forecastingPlatforms: platformsWithLabels, // weird key value format,
 |  | ||||||
|     ...urlQuery, |     ...urlQuery, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   let results; |   let results: FrontendForecast[] = []; | ||||||
|   switch (initialQueryParameters.query != "") { |   if (initialQueryParameters.query != "") { | ||||||
|     case true: |     results = await searchAccordingToQueryData(initialQueryParameters, 1); | ||||||
|       results = await searchAccordingToQueryData(initialQueryParameters); |  | ||||||
|       break; |  | ||||||
|     default: |  | ||||||
|       results = []; |  | ||||||
|       break; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return { |   return { | ||||||
|  | @ -49,8 +43,7 @@ export default function Home({ results }) { | ||||||
|             <div id="secretEmbed"> |             <div id="secretEmbed"> | ||||||
|               {result |               {result | ||||||
|                 ? displayForecast({ |                 ? displayForecast({ | ||||||
|                     ...result.item, |                     ...result, | ||||||
|                     score: result.score, |  | ||||||
|                     showTimeStamp: true, |                     showTimeStamp: true, | ||||||
|                     expandFooterToFullWidth: true, |                     expandFooterToFullWidth: true, | ||||||
|                   }) |                   }) | ||||||
|  |  | ||||||
|  | @ -3,7 +3,7 @@ import React, { useState } from "react"; | ||||||
| let exampleInput = `{
 | let exampleInput = `{
 | ||||||
|   "title": "Random example", |   "title": "Random example", | ||||||
|   "description": "Just a random description of a random example", |   "description": "Just a random description of a random example", | ||||||
|   "ids": [ "metaculus-372", "goodjudmentopen-2244", "metaculus-7550", "kalshi-09d060ee-b184-4167-b86b-d773e56b4162", "wildeford-5d1a04e1a8", "metaculus-2817" ], |   "ids": [ "metaculus-372", "goodjudgmentopen-2244", "metaculus-7550", "kalshi-09d060ee-b184-4167-b86b-d773e56b4162", "wildeford-5d1a04e1a8", "metaculus-2817" ], | ||||||
|   "creator": "Peter Parker" |   "creator": "Peter Parker" | ||||||
| }`;
 | }`;
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ import React from "react"; | ||||||
| import { FaRegClipboard } from "react-icons/fa"; | import { FaRegClipboard } from "react-icons/fa"; | ||||||
| import ReactMarkdown from "react-markdown"; | import ReactMarkdown from "react-markdown"; | ||||||
| 
 | 
 | ||||||
|  | import { FrontendForecast } from "../platforms"; | ||||||
|  | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
| 
 | 
 | ||||||
| /* Support functions */ | /* Support functions */ | ||||||
|  | @ -350,9 +352,15 @@ let checkIfDisplayTimeStampAtBottom = (qualityIndicators) => { | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| let getCurrencySymbolIfNeeded = ({ indicator, platform }) => { | let getCurrencySymbolIfNeeded = ({ | ||||||
|  |   indicator, | ||||||
|  |   platform, | ||||||
|  | }: { | ||||||
|  |   indicator: any; | ||||||
|  |   platform: string; | ||||||
|  | }) => { | ||||||
|   let indicatorsWhichNeedCurrencySymbol = ["Volume", "Interest", "Liquidity"]; |   let indicatorsWhichNeedCurrencySymbol = ["Volume", "Interest", "Liquidity"]; | ||||||
|   let dollarPlatforms = ["PredictIt", "Kalshi", "PolyMarket"]; |   let dollarPlatforms = ["predictit", "kalshi", "polymarket"]; | ||||||
|   if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) { |   if (indicatorsWhichNeedCurrencySymbol.includes(indicator)) { | ||||||
|     if (dollarPlatforms.includes(platform)) { |     if (dollarPlatforms.includes(platform)) { | ||||||
|       return "$"; |       return "$"; | ||||||
|  | @ -461,7 +469,13 @@ let showFirstQualityIndicator = ({ | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| let displayQualityIndicators = ({ | const displayQualityIndicators: React.FC<{ | ||||||
|  |   numforecasts: number; | ||||||
|  |   timestamp: number; | ||||||
|  |   showTimeStamp: boolean; | ||||||
|  |   qualityindicators: any; | ||||||
|  |   platform: string; // id string - e.g. "goodjudgment", not "Good Judgment"
 | ||||||
|  | }> = ({ | ||||||
|   numforecasts, |   numforecasts, | ||||||
|   timestamp, |   timestamp, | ||||||
|   showTimeStamp, |   showTimeStamp, | ||||||
|  | @ -504,6 +518,7 @@ let displayQualityIndicators = ({ | ||||||
| let forecastFooter = ({ | let forecastFooter = ({ | ||||||
|   stars, |   stars, | ||||||
|   platform, |   platform, | ||||||
|  |   platformLabel, | ||||||
|   numforecasts, |   numforecasts, | ||||||
|   qualityindicators, |   qualityindicators, | ||||||
|   timestamp, |   timestamp, | ||||||
|  | @ -532,7 +547,7 @@ let forecastFooter = ({ | ||||||
|           expandFooterToFullWidth ? "place-self-center" : "self-center" |           expandFooterToFullWidth ? "place-self-center" : "self-center" | ||||||
|         }  col-span-1 font-bold ${debuggingWithBackground ? "bg-red-100" : ""}`}
 |         }  col-span-1 font-bold ${debuggingWithBackground ? "bg-red-100" : ""}`}
 | ||||||
|       > |       > | ||||||
|         {platform |         {platformLabel | ||||||
|           .replace("Good Judgment Open", "GJOpen") |           .replace("Good Judgment Open", "GJOpen") | ||||||
|           .replace("OpenPhilanthropy", "OpenPhil") |           .replace("OpenPhilanthropy", "OpenPhil") | ||||||
|           .replace("AstralCodexTen", "ACX") |           .replace("AstralCodexTen", "ACX") | ||||||
|  | @ -559,22 +574,30 @@ let forecastFooter = ({ | ||||||
| 
 | 
 | ||||||
| /* Body */ | /* Body */ | ||||||
| 
 | 
 | ||||||
| export function displayForecast({ | interface SingleProps { | ||||||
|   id, |   forecast: FrontendForecast; | ||||||
|   title, |   showTimeStamp: boolean; | ||||||
|   url, |   expandFooterToFullWidth: boolean; | ||||||
|   platform, |   showIdToggle?: boolean; | ||||||
|   author, | } | ||||||
|   description, | 
 | ||||||
|   options, | export const displayForecast: React.FC<SingleProps> = ({ | ||||||
|   qualityindicators, |   forecast: { | ||||||
|   timestamp, |     id, | ||||||
|   visualization, |     title, | ||||||
|   score, |     url, | ||||||
|  |     platform, | ||||||
|  |     platformLabel, | ||||||
|  |     description, | ||||||
|  |     options, | ||||||
|  |     qualityindicators, | ||||||
|  |     timestamp, | ||||||
|  |     visualization, | ||||||
|  |   }, | ||||||
|   showTimeStamp, |   showTimeStamp, | ||||||
|   expandFooterToFullWidth, |   expandFooterToFullWidth, | ||||||
|   showIdToggle, |   showIdToggle, | ||||||
| }) { | }) => { | ||||||
|   // const [isJustCopiedSignalVisible, setIsJustCopiedSignalVisible] = useState(false)
 |   // const [isJustCopiedSignalVisible, setIsJustCopiedSignalVisible] = useState(false)
 | ||||||
|   const isJustCopiedSignalVisible = false; |   const isJustCopiedSignalVisible = false; | ||||||
| 
 | 
 | ||||||
|  | @ -588,7 +611,7 @@ export function displayForecast({ | ||||||
|       <div className="flex-grow"> |       <div className="flex-grow"> | ||||||
|         <div |         <div | ||||||
|           className={`text-gray-800 ${opacityFromScore( |           className={`text-gray-800 ${opacityFromScore( | ||||||
|             score |             0 | ||||||
|           )} text-lg mb-2 font-medium justify-self-start`}
 |           )} text-lg mb-2 font-medium justify-self-start`}
 | ||||||
|         > |         > | ||||||
|           <div |           <div | ||||||
|  | @ -641,7 +664,7 @@ export function displayForecast({ | ||||||
|                       ? "flex" |                       ? "flex" | ||||||
|                       : "hidden" |                       : "hidden" | ||||||
|                   } ${opacityFromScore( |                   } ${opacityFromScore( | ||||||
|                     score |                     0 | ||||||
|                   )} row-end-2 col-start-2 col-end-2 row-start-1 row-end-1 col-span-1 items-center justify-center text-gray-600 ml-3 mr-2 `}
 |                   )} row-end-2 col-start-2 col-end-2 row-start-1 row-end-1 col-span-1 items-center justify-center text-gray-600 ml-3 mr-2 `}
 | ||||||
|                 > |                 > | ||||||
|                   <svg className="mt-1" height="10" width="16"> |                   <svg className="mt-1" height="10" width="16"> | ||||||
|  | @ -657,7 +680,7 @@ export function displayForecast({ | ||||||
|         {(options.length != 2 || |         {(options.length != 2 || | ||||||
|           (options[0].name != "Yes" && options[0].name != "No")) && ( |           (options[0].name != "Yes" && options[0].name != "No")) && ( | ||||||
|           <> |           <> | ||||||
|             <div className={`mb-2 mt-2 ${opacityFromScore(score)}`}> |             <div className={`mb-2 mt-2 ${opacityFromScore(0)}`}> | ||||||
|               {formatForecastOptions(options)} |               {formatForecastOptions(options)} | ||||||
|             </div> |             </div> | ||||||
|             <div |             <div | ||||||
|  | @ -667,7 +690,7 @@ export function displayForecast({ | ||||||
|                   ? "flex" |                   ? "flex" | ||||||
|                   : "hidden" |                   : "hidden" | ||||||
|               } ${opacityFromScore( |               } ${opacityFromScore( | ||||||
|                 score |                 0 | ||||||
|               )} col-start-2 col-end-2 row-start-1 row-end-1 text-gray-600 mt-3 mb-3`}
 |               )} col-start-2 col-end-2 row-start-1 row-end-1 text-gray-600 mt-3 mb-3`}
 | ||||||
|             > |             > | ||||||
|               <svg className="ml-6 mr-1 mt-2" height="10" width="16"> |               <svg className="ml-6 mr-1 mt-2" height="10" width="16"> | ||||||
|  | @ -680,13 +703,13 @@ export function displayForecast({ | ||||||
|           </> |           </> | ||||||
|         )} |         )} | ||||||
| 
 | 
 | ||||||
|         {platform !== "Guesstimate" && options.length < 3 && ( |         {platform !== "guesstimate" && options.length < 3 && ( | ||||||
|           <div className={`text-gray-500 ${opacityFromScore(score)} mt-4`}> |           <div className={`text-gray-500 ${opacityFromScore(0)} mt-4`}> | ||||||
|             {displayMarkdown(description)} |             {displayMarkdown(description)} | ||||||
|           </div> |           </div> | ||||||
|         )} |         )} | ||||||
| 
 | 
 | ||||||
|         {platform === "Guesstimate" && ( |         {platform === "guesstimate" && ( | ||||||
|           <img |           <img | ||||||
|             className="rounded-sm mb-1" |             className="rounded-sm mb-1" | ||||||
|             src={visualization} |             src={visualization} | ||||||
|  | @ -705,10 +728,11 @@ export function displayForecast({ | ||||||
|         </svg> |         </svg> | ||||||
|         {`Last updated: ${timestamp ? timestamp.slice(0, 10) : "unknown"}`} |         {`Last updated: ${timestamp ? timestamp.slice(0, 10) : "unknown"}`} | ||||||
|       </div> |       </div> | ||||||
|       <div className={`${opacityFromScore(score)} w-full`}> |       <div className={`${opacityFromScore(0)} w-full`}> | ||||||
|         {forecastFooter({ |         {forecastFooter({ | ||||||
|           stars: qualityindicators.stars, |           stars: qualityindicators.stars, | ||||||
|           platform: author || platform, |           platform: platform, | ||||||
|  |           platformLabel: platformLabel || platform, // author || platformLabel,
 | ||||||
|           numforecasts: qualityindicators.numforecasts, |           numforecasts: qualityindicators.numforecasts, | ||||||
|           qualityindicators, |           qualityindicators, | ||||||
|           timestamp, |           timestamp, | ||||||
|  | @ -718,30 +742,40 @@ export function displayForecast({ | ||||||
|       </div> |       </div> | ||||||
|     </a> |     </a> | ||||||
|   ); |   ); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | interface Props { | ||||||
|  |   results: FrontendForecast[]; | ||||||
|  |   numDisplay: number; | ||||||
|  |   showIdToggle: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export default function displayForecasts({ | const DisplayForecasts: React.FC<Props> = ({ | ||||||
|   results, |   results, | ||||||
|   numDisplay, |   numDisplay, | ||||||
|   showIdToggle, |   showIdToggle, | ||||||
| }) { | }) => { | ||||||
|   return !!results && !!results.slice ? ( |   if (!results) { | ||||||
|     results.slice(0, numDisplay).map((fuseSearchResult) => { |     return <></>; | ||||||
|       /*let displayWithMetaculusCapture = |   } | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       {results.slice(0, numDisplay).map((result) => { | ||||||
|  |         /*let displayWithMetaculusCapture = | ||||||
|           fuseSearchResult.item.platform == "Metaculus" |           fuseSearchResult.item.platform == "Metaculus" | ||||||
|             ? metaculusEmbed(fuseSearchResult.item) |             ? metaculusEmbed(fuseSearchResult.item) | ||||||
|             : displayForecast({ ...fuseSearchResult.item }); |             : displayForecast({ ...fuseSearchResult.item }); | ||||||
|         */ |         */ | ||||||
|       let display = displayForecast({ |         const display = displayForecast({ | ||||||
|         ...fuseSearchResult.item, |           forecast: result, | ||||||
|         score: fuseSearchResult.score, |           showTimeStamp: false, | ||||||
|         showTimeStamp: false, |           expandFooterToFullWidth: false, | ||||||
|         expandFooterToFullWidth: false, |           showIdToggle, | ||||||
|         showIdToggle, |         }); | ||||||
|       }); |         return display; | ||||||
|       return display; |       })} | ||||||
|     }) |     </> | ||||||
|   ) : ( |  | ||||||
|     <></> |  | ||||||
|   ); |   ); | ||||||
| } | }; | ||||||
|  | 
 | ||||||
|  | export default DisplayForecasts; | ||||||
|  |  | ||||||
|  | @ -2,16 +2,16 @@ import domtoimage from "dom-to-image"; // https://github.com/tsayen/dom-to-image | ||||||
| import { useEffect, useRef, useState } from "react"; | import { useEffect, useRef, useState } from "react"; | ||||||
| import { CopyToClipboard } from "react-copy-to-clipboard"; | import { CopyToClipboard } from "react-copy-to-clipboard"; | ||||||
| 
 | 
 | ||||||
|  | import { FrontendForecast } from "../platforms"; | ||||||
| import { uploadToImgur } from "../worker/uploadToImgur"; | import { uploadToImgur } from "../worker/uploadToImgur"; | ||||||
| import { displayForecast } from "./displayForecasts"; | import { displayForecast } from "./displayForecasts"; | ||||||
| 
 | 
 | ||||||
| function displayOneForecastInner(result, containerRef) { | function displayOneForecastInner(result: FrontendForecast, containerRef) { | ||||||
|   return ( |   return ( | ||||||
|     <div ref={containerRef}> |     <div ref={containerRef}> | ||||||
|       {result |       {result | ||||||
|         ? displayForecast({ |         ? displayForecast({ | ||||||
|             ...result.item, |             forecast: result, | ||||||
|             score: result.score, |  | ||||||
|             showTimeStamp: true, |             showTimeStamp: true, | ||||||
|             expandFooterToFullWidth: true, |             expandFooterToFullWidth: true, | ||||||
|           }) |           }) | ||||||
|  | @ -170,7 +170,7 @@ let generateMetaculusSource = (result, hasDisplayBeenCaptured) => { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| interface Props { | interface Props { | ||||||
|   result: any; |   result: FrontendForecast; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const DisplayOneForecast: React.FC<Props> = ({ result }) => { | const DisplayOneForecast: React.FC<Props> = ({ result }) => { | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ import chroma from "chroma-js"; | ||||||
| import React from "react"; | import React from "react"; | ||||||
| import Select from "react-select"; | import Select from "react-select"; | ||||||
| 
 | 
 | ||||||
| import { platformsWithLabels } from "../platforms"; | import { PlatformConfig } from "../platforms"; | ||||||
| 
 | 
 | ||||||
| const colourStyles = { | const colourStyles = { | ||||||
|   control: (styles) => ({ ...styles, backgroundColor: "white" }), |   control: (styles) => ({ ...styles, backgroundColor: "white" }), | ||||||
|  | @ -59,17 +59,48 @@ const colourStyles = { | ||||||
|   }), |   }), | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default function MultiSelectPlatform({ onChange, value }) { | interface Props { | ||||||
|  |   onChange: (platforms: string[]) => void; | ||||||
|  |   value: string[]; | ||||||
|  |   platformsConfig: PlatformConfig[]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export const MultiSelectPlatform: React.FC<Props> = ({ | ||||||
|  |   onChange, | ||||||
|  |   value, | ||||||
|  |   platformsConfig, | ||||||
|  | }) => { | ||||||
|  |   type Option = { | ||||||
|  |     value: string; | ||||||
|  |     label: string; | ||||||
|  |     color: string; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const options: Option[] = platformsConfig.map((platform) => ({ | ||||||
|  |     value: platform.name, | ||||||
|  |     label: platform.label, | ||||||
|  |     color: platform.color, | ||||||
|  |   })); | ||||||
|  | 
 | ||||||
|  |   const id2option: { [k: string]: Option } = {}; | ||||||
|  |   for (const option of options) id2option[option.value] = option; | ||||||
|  | 
 | ||||||
|  |   const selectValue = value.map((v) => id2option[v]).filter((v) => v); | ||||||
|  | 
 | ||||||
|  |   const onSelectChange = (newValue: Option[]) => { | ||||||
|  |     onChange(newValue.map((o) => o.value)); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   return ( |   return ( | ||||||
|     <Select |     <Select | ||||||
|       defaultValue={platformsWithLabels} |       defaultValue={options} | ||||||
|       isMulti |       isMulti | ||||||
|       className="basic-multi-select w-full text-gray-700" |       className="basic-multi-select w-full text-gray-700" | ||||||
|       onChange={onChange} |       onChange={onSelectChange} | ||||||
|       closeMenuOnSelect={false} |       closeMenuOnSelect={false} | ||||||
|       options={platformsWithLabels} |       options={options} | ||||||
|       value={value} |       value={selectValue} | ||||||
|       styles={colourStyles} |       styles={colourStyles} | ||||||
|     /> |     /> | ||||||
|   ); |   ); | ||||||
| } | }; | ||||||
|  |  | ||||||
|  | @ -1,61 +1,12 @@ | ||||||
| export const distinctColors = [ | import { Forecast } from "../backend/platforms"; | ||||||
|   "#3d674a", |  | ||||||
|   "#231149", |  | ||||||
|   "#62520b", |  | ||||||
|   "#32407e", |  | ||||||
|   "#7d4f1b", |  | ||||||
|   "#002455", |  | ||||||
|   "#223900", |  | ||||||
|   "#615691", |  | ||||||
|   "#003419", |  | ||||||
|   "#793466", |  | ||||||
|   "#006669", |  | ||||||
|   "#984158", |  | ||||||
|   "#00314e", |  | ||||||
|   "#460c00", |  | ||||||
|   "#0d1624", |  | ||||||
|   "#6f5b41", |  | ||||||
|   "#240d23", |  | ||||||
|   "#272600", |  | ||||||
|   "#755469", |  | ||||||
|   "#2a323d", |  | ||||||
| ]; |  | ||||||
| 
 | 
 | ||||||
| // https://medialab.github.io/iwanthue/ fancy light background
 | export interface PlatformConfig { | ||||||
| export const platformNames = [ |   name: string; | ||||||
|   "Betfair", |  | ||||||
|   "FantasySCOTUS", |  | ||||||
|   "Foretold", |  | ||||||
|   "GiveWell/OpenPhilanthropy", |  | ||||||
|   "Good Judgment", |  | ||||||
|   "Good Judgment Open", |  | ||||||
|   "Guesstimate", |  | ||||||
|   "Infer", |  | ||||||
|   "Kalshi", |  | ||||||
|   "Manifold Markets", |  | ||||||
|   "Metaculus", |  | ||||||
|   "Peter Wildeford", |  | ||||||
|   "PolyMarket", |  | ||||||
|   "PredictIt", |  | ||||||
|   "Rootclaim", |  | ||||||
|   "Smarkets", |  | ||||||
|   "X-risk estimates", |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| export interface PlatformWithLabel { |  | ||||||
|   value: string; |  | ||||||
|   label: string; |   label: string; | ||||||
|   color: string; |   color: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const platformsWithLabels: PlatformWithLabel[] = platformNames.map( | export type FrontendForecast = Forecast & { | ||||||
|   (name, i) => ({ |   platformLabel: string; | ||||||
|     value: name, |   visualization?: any; | ||||||
|     label: name, | }; | ||||||
|     color: distinctColors[i], |  | ||||||
|   }) |  | ||||||
| ); |  | ||||||
| 
 |  | ||||||
| export const platforms = platformsWithLabels; |  | ||||||
| 
 |  | ||||||
| // Deprecated: AstralCodexTen, CoupCast, CSET-foretell, Estimize, Elicit, Hypermind, Omen, WilliamHill
 |  | ||||||
|  |  | ||||||
|  | @ -1,54 +1,13 @@ | ||||||
| import { useRouter } from "next/router"; | import { useRouter } from "next/router"; | ||||||
| import React, { | import React, { DependencyList, EffectCallback, Fragment, useEffect, useState } from "react"; | ||||||
|   DependencyList, |  | ||||||
|   EffectCallback, |  | ||||||
|   Fragment, |  | ||||||
|   useEffect, |  | ||||||
|   useState, |  | ||||||
| } from "react"; |  | ||||||
| 
 | 
 | ||||||
| import ButtonsForStars from "../display/buttonsForStars"; | import ButtonsForStars from "../display/buttonsForStars"; | ||||||
| import Form from "../display/form"; | import Form from "../display/form"; | ||||||
| import MultiSelectPlatform from "../display/multiSelectPlatforms"; | import { MultiSelectPlatform } from "../display/multiSelectPlatforms"; | ||||||
| import { SliderElement } from "../display/slider"; | import { SliderElement } from "../display/slider"; | ||||||
| import { platformsWithLabels, PlatformWithLabel } from "../platforms"; | import { FrontendForecast } from "../platforms"; | ||||||
| import searchAccordingToQueryData from "../worker/searchAccordingToQueryData"; | import searchAccordingToQueryData from "../worker/searchAccordingToQueryData"; | ||||||
| 
 | import { Props as AnySearchPageProps, QueryParameters } from "./anySearchPage"; | ||||||
| interface QueryParametersWithoutNum { |  | ||||||
|   query: string; |  | ||||||
|   starsThreshold: number; |  | ||||||
|   forecastsThreshold: number; |  | ||||||
|   forecastingPlatforms: PlatformWithLabel[]; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export interface QueryParameters extends QueryParametersWithoutNum { |  | ||||||
|   numDisplay: number; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| interface Props { |  | ||||||
|   defaultResults: any; |  | ||||||
|   initialResults: any; |  | ||||||
|   initialQueryParameters: QueryParameters; |  | ||||||
|   hasSearchbar: boolean; |  | ||||||
|   hasCapture: boolean; |  | ||||||
|   hasAdvancedOptions: boolean; |  | ||||||
|   placeholder: string; |  | ||||||
|   displaySeeMoreHint: boolean; |  | ||||||
|   displayForecastsWrapper: (opts: { |  | ||||||
|     results: any; |  | ||||||
|     numDisplay: number; |  | ||||||
|     whichResultToDisplayAndCapture: number; |  | ||||||
|     showIdToggle: boolean; |  | ||||||
|   }) => React.ReactNode; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| export const defaultQueryParameters: QueryParametersWithoutNum = { |  | ||||||
|   query: "", |  | ||||||
|   starsThreshold: 2, |  | ||||||
|   forecastsThreshold: 0, |  | ||||||
|   forecastingPlatforms: platformsWithLabels, // weird key value format,
 |  | ||||||
| }; |  | ||||||
| export const defaultNumDisplay = 21; |  | ||||||
| 
 | 
 | ||||||
| const useNoInitialEffect = (effect: EffectCallback, deps: DependencyList) => { | const useNoInitialEffect = (effect: EffectCallback, deps: DependencyList) => { | ||||||
|   const initial = React.useRef(true); |   const initial = React.useRef(true); | ||||||
|  | @ -61,11 +20,29 @@ const useNoInitialEffect = (effect: EffectCallback, deps: DependencyList) => { | ||||||
|   }, deps); |   }, deps); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | interface Props extends AnySearchPageProps { | ||||||
|  |   hasSearchbar: boolean; | ||||||
|  |   hasCapture: boolean; | ||||||
|  |   hasAdvancedOptions: boolean; | ||||||
|  |   placeholder: string; | ||||||
|  |   displaySeeMoreHint: boolean; | ||||||
|  |   displayForecastsWrapper: (opts: { | ||||||
|  |     results: FrontendForecast[]; | ||||||
|  |     numDisplay: number; | ||||||
|  |     whichResultToDisplayAndCapture: number; | ||||||
|  |     showIdToggle: boolean; | ||||||
|  |   }) => React.ReactNode; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /* Body */ | /* Body */ | ||||||
| const CommonDisplay: React.FC<Props> = ({ | const CommonDisplay: React.FC<Props> = ({ | ||||||
|   defaultResults, |   defaultResults, | ||||||
|   initialResults, |   initialResults, | ||||||
|   initialQueryParameters, |   initialQueryParameters, | ||||||
|  |   defaultQueryParameters, | ||||||
|  |   initialNumDisplay, | ||||||
|  |   defaultNumDisplay, | ||||||
|  |   platformsConfig, | ||||||
|   hasSearchbar, |   hasSearchbar, | ||||||
|   hasCapture, |   hasCapture, | ||||||
|   hasAdvancedOptions, |   hasAdvancedOptions, | ||||||
|  | @ -76,13 +53,12 @@ const CommonDisplay: React.FC<Props> = ({ | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|   /* States */ |   /* States */ | ||||||
| 
 | 
 | ||||||
|   const [queryParameters, setQueryParameters] = |   const [queryParameters, setQueryParameters] = useState<QueryParameters>( | ||||||
|     useState<QueryParametersWithoutNum>(initialQueryParameters); |     initialQueryParameters | ||||||
| 
 |  | ||||||
|   const [numDisplay, setNumDisplay] = useState( |  | ||||||
|     initialQueryParameters.numDisplay ?? defaultNumDisplay |  | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|  |   const [numDisplay, setNumDisplay] = useState(initialNumDisplay); | ||||||
|  | 
 | ||||||
|   // used to distinguish numDisplay updates which force search and don't force search, see effects below
 |   // used to distinguish numDisplay updates which force search and don't force search, see effects below
 | ||||||
|   const [forceSearch, setForceSearch] = useState(0); |   const [forceSearch, setForceSearch] = useState(0); | ||||||
| 
 | 
 | ||||||
|  | @ -100,25 +76,25 @@ const CommonDisplay: React.FC<Props> = ({ | ||||||
|       numDisplay, |       numDisplay, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     let filterManually = (queryData: QueryParameters, results) => { |     let filterManually = ( | ||||||
|  |       queryData: QueryParameters, | ||||||
|  |       results: FrontendForecast[] | ||||||
|  |     ) => { | ||||||
|       if ( |       if ( | ||||||
|         queryData.forecastingPlatforms && |         queryData.forecastingPlatforms && | ||||||
|         queryData.forecastingPlatforms.length > 0 |         queryData.forecastingPlatforms.length > 0 | ||||||
|       ) { |       ) { | ||||||
|         let forecastingPlatforms = queryData.forecastingPlatforms.map( |  | ||||||
|           (platformObj) => platformObj.value |  | ||||||
|         ); |  | ||||||
|         results = results.filter((result) => |         results = results.filter((result) => | ||||||
|           forecastingPlatforms.includes(result.item.platform) |           queryData.forecastingPlatforms.includes(result.platform) | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|       if (queryData.starsThreshold === 4) { |       if (queryData.starsThreshold === 4) { | ||||||
|         results = results.filter( |         results = results.filter( | ||||||
|           (result) => result.item.qualityindicators.stars >= 4 |           (result) => result.qualityindicators.stars >= 4 | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|       if (queryData.forecastsThreshold) { |       if (queryData.forecastsThreshold) { | ||||||
|         // results = results.filter(result => (result.qualityindicators && result.item.qualityindicators.numforecasts > forecastsThreshold))
 |         // results = results.filter(result => (result.qualityindicators && result.qualityindicators.numforecasts > forecastsThreshold))
 | ||||||
|       } |       } | ||||||
|       return results; |       return results; | ||||||
|     }; |     }; | ||||||
|  | @ -128,17 +104,13 @@ const CommonDisplay: React.FC<Props> = ({ | ||||||
| 
 | 
 | ||||||
|     let results = queryIsEmpty |     let results = queryIsEmpty | ||||||
|       ? filterManually(queryData, defaultResults) |       ? filterManually(queryData, defaultResults) | ||||||
|       : await searchAccordingToQueryData(queryData); |       : await searchAccordingToQueryData(queryData, numDisplay); | ||||||
| 
 | 
 | ||||||
|     setResults(results); |     setResults(results); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   // I don't want the function which display forecasts (displayForecasts) to change with a change in queryParameters. But I want it to have access to the queryParameters, and in particular access to queryParameters.numDisplay. Hence why this function lives inside Home.
 |   // I don't want the function which display forecasts (displayForecasts) to change with a change in queryParameters. But I want it to have access to the queryParameters, and in particular access to queryParameters.numDisplay. Hence why this function lives inside Home.
 | ||||||
|   let getInfoToDisplayForecastsFunction = ({ |   let getInfoToDisplayForecastsFunction = () => { | ||||||
|     results, |  | ||||||
|     whichResultToDisplayAndCapture, |  | ||||||
|     showIdToggle, |  | ||||||
|   }) => { |  | ||||||
|     let numDisplayRounded = |     let numDisplayRounded = | ||||||
|       numDisplay % 3 != 0 |       numDisplay % 3 != 0 | ||||||
|         ? numDisplay + (3 - (Math.round(numDisplay) % 3)) |         ? numDisplay + (3 - (Math.round(numDisplay) % 3)) | ||||||
|  | @ -154,7 +126,7 @@ const CommonDisplay: React.FC<Props> = ({ | ||||||
|   const updateRoute = () => { |   const updateRoute = () => { | ||||||
|     const stringify = (key: string, value: any) => { |     const stringify = (key: string, value: any) => { | ||||||
|       if (key === "forecastingPlatforms") { |       if (key === "forecastingPlatforms") { | ||||||
|         return value.map((x) => x.value).join("|"); |         return value.join("|"); | ||||||
|       } else { |       } else { | ||||||
|         return String(value); |         return String(value); | ||||||
|       } |       } | ||||||
|  | @ -330,6 +302,7 @@ const CommonDisplay: React.FC<Props> = ({ | ||||||
|             </div> |             </div> | ||||||
|             <div className="flex col-span-3 items-center justify-center"> |             <div className="flex col-span-3 items-center justify-center"> | ||||||
|               <MultiSelectPlatform |               <MultiSelectPlatform | ||||||
|  |                 platformsConfig={platformsConfig} | ||||||
|                 value={queryParameters.forecastingPlatforms} |                 value={queryParameters.forecastingPlatforms} | ||||||
|                 onChange={onChangeSelectedPlatforms} |                 onChange={onChangeSelectedPlatforms} | ||||||
|               /> |               /> | ||||||
|  | @ -344,13 +317,7 @@ const CommonDisplay: React.FC<Props> = ({ | ||||||
|         </div> |         </div> | ||||||
|       ) : null} |       ) : null} | ||||||
| 
 | 
 | ||||||
|       <div> |       <div>{getInfoToDisplayForecastsFunction()}</div> | ||||||
|         {getInfoToDisplayForecastsFunction({ |  | ||||||
|           results, |  | ||||||
|           whichResultToDisplayAndCapture, |  | ||||||
|           showIdToggle, |  | ||||||
|         })} |  | ||||||
|       </div> |  | ||||||
| 
 | 
 | ||||||
|       {displaySeeMoreHint && |       {displaySeeMoreHint && | ||||||
|       (!results || (results.length != 0 && numDisplay < results.length)) ? ( |       (!results || (results.length != 0 && numDisplay < results.length)) ? ( | ||||||
|  |  | ||||||
|  | @ -1,46 +1,103 @@ | ||||||
| import { GetServerSideProps } from "next"; | import { GetServerSideProps } from "next"; | ||||||
| 
 | 
 | ||||||
| import { getFrontpage } from "../../backend/frontpage"; | import { getFrontpage } from "../../backend/frontpage"; | ||||||
|  | import { platforms } from "../../backend/platforms"; | ||||||
|  | import { FrontendForecast, PlatformConfig } from "../platforms"; | ||||||
| import searchAccordingToQueryData from "../worker/searchAccordingToQueryData"; | import searchAccordingToQueryData from "../worker/searchAccordingToQueryData"; | ||||||
| import { |  | ||||||
|   defaultNumDisplay, |  | ||||||
|   defaultQueryParameters, |  | ||||||
|   QueryParameters, |  | ||||||
| } from "./CommonDisplay"; |  | ||||||
| 
 | 
 | ||||||
| /* Common code for / and /capture */ | /* Common code for / and /capture */ | ||||||
| 
 | 
 | ||||||
|  | export interface QueryParameters { | ||||||
|  |   query: string; | ||||||
|  |   starsThreshold: number; | ||||||
|  |   forecastsThreshold: number; | ||||||
|  |   forecastingPlatforms: string[]; // platform names
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export interface Props { | export interface Props { | ||||||
|   defaultResults: any; |   defaultResults: FrontendForecast[]; | ||||||
|   initialResults: any; |   initialResults: FrontendForecast[]; | ||||||
|   initialQueryParameters: QueryParameters; |   initialQueryParameters: QueryParameters; | ||||||
|  |   defaultQueryParameters: QueryParameters; | ||||||
|  |   initialNumDisplay: number; | ||||||
|  |   defaultNumDisplay: number; | ||||||
|  |   platformsConfig: PlatformConfig[]; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const getServerSideProps: GetServerSideProps<Props> = async ( | export const getServerSideProps: GetServerSideProps<Props> = async ( | ||||||
|   context |   context | ||||||
| ) => { | ) => { | ||||||
|   let urlQuery = context.query; |   const urlQuery = context.query; | ||||||
| 
 | 
 | ||||||
|   let initialQueryParameters: QueryParameters = { |   const platformsConfig = platforms.map((platform) => ({ | ||||||
|     ...defaultQueryParameters, |     name: platform.name, | ||||||
|     numDisplay: defaultNumDisplay, |     label: platform.label, | ||||||
|     ...urlQuery, // FIXME - parse numerical fields
 |     color: platform.color, | ||||||
|  |   })); | ||||||
|  |   platformsConfig.push({ | ||||||
|  |     name: "guesstimate", | ||||||
|  |     label: "Guesstimate", | ||||||
|  |     color: "223900", | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   const defaultQueryParameters: QueryParameters = { | ||||||
|  |     query: "", | ||||||
|  |     starsThreshold: 2, | ||||||
|  |     forecastsThreshold: 0, | ||||||
|  |     forecastingPlatforms: platforms.map((platform) => platform.name), | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   let defaultResults = await getFrontpage(); |   const initialQueryParameters: QueryParameters = { | ||||||
|  |     ...defaultQueryParameters, | ||||||
|  |   }; | ||||||
|  |   if (urlQuery.query) { | ||||||
|  |     initialQueryParameters.query = String(urlQuery.query); | ||||||
|  |   } | ||||||
|  |   if (urlQuery.starsThreshold) { | ||||||
|  |     initialQueryParameters.starsThreshold = Number(urlQuery.starsThreshold); | ||||||
|  |   } | ||||||
|  |   if (urlQuery.forecastsThreshold !== undefined) { | ||||||
|  |     initialQueryParameters.forecastsThreshold = Number( | ||||||
|  |       urlQuery.forecastsThreshold | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |   if (urlQuery.forecastingPlatforms !== undefined) { | ||||||
|  |     initialQueryParameters.forecastingPlatforms = String( | ||||||
|  |       urlQuery.forecastingPlatforms | ||||||
|  |     ).split("|"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const platformNameToLabel = Object.fromEntries( | ||||||
|  |     platforms.map((platform) => [platform.name, platform.label]) | ||||||
|  |   ); | ||||||
|  | 
 | ||||||
|  |   const defaultNumDisplay = 21; | ||||||
|  |   const initialNumDisplay = Number(urlQuery.numDisplay) || defaultNumDisplay; | ||||||
|  | 
 | ||||||
|  |   const defaultResults = (await getFrontpage()).map((result) => ({ | ||||||
|  |     ...result, | ||||||
|  |     platformLabel: platformNameToLabel[result.platform] || result.platform, | ||||||
|  |   })); | ||||||
| 
 | 
 | ||||||
|   const initialResults = |   const initialResults = | ||||||
|     !!initialQueryParameters && |     !!initialQueryParameters && | ||||||
|     initialQueryParameters.query != "" && |     initialQueryParameters.query != "" && | ||||||
|     initialQueryParameters.query != undefined |     initialQueryParameters.query != undefined | ||||||
|       ? await searchAccordingToQueryData(initialQueryParameters) |       ? await searchAccordingToQueryData( | ||||||
|  |           initialQueryParameters, | ||||||
|  |           initialNumDisplay | ||||||
|  |         ) | ||||||
|       : defaultResults; |       : defaultResults; | ||||||
| 
 | 
 | ||||||
|   return { |   return { | ||||||
|     props: { |     props: { | ||||||
|       initialQueryParameters, |       initialQueryParameters, | ||||||
|  |       defaultQueryParameters, | ||||||
|  |       initialNumDisplay, | ||||||
|  |       defaultNumDisplay, | ||||||
|       initialResults, |       initialResults, | ||||||
|       defaultResults, |       defaultResults, | ||||||
|  |       platformsConfig, | ||||||
|     }, |     }, | ||||||
|   }; |   }; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,56 +1,48 @@ | ||||||
|  | import { FrontendForecast } from "../platforms"; | ||||||
|  | import { QueryParameters } from "../search/anySearchPage"; | ||||||
| import searchGuesstimate from "./searchGuesstimate"; | import searchGuesstimate from "./searchGuesstimate"; | ||||||
| import searchWithAlgolia from "./searchWithAlgolia"; | import searchWithAlgolia from "./searchWithAlgolia"; | ||||||
| 
 | 
 | ||||||
| export default async function searchAccordingToQueryData(queryData) { | export default async function searchAccordingToQueryData( | ||||||
|   let results = []; |   queryData: QueryParameters, | ||||||
|  |   limit: number | ||||||
|  | ): Promise<FrontendForecast[]> { | ||||||
|  |   let results: FrontendForecast[] = []; | ||||||
| 
 | 
 | ||||||
|   try { |   try { | ||||||
|     // defs
 |     // defs
 | ||||||
|     let query = queryData.query == undefined ? "" : queryData.query; |     let query = queryData.query == undefined ? "" : queryData.query; | ||||||
|     if (query == "") return -1; |     if (query == "") return []; | ||||||
|     let forecastsThreshold = queryData.forecastsThreshold; |     let forecastsThreshold = queryData.forecastsThreshold; | ||||||
|     let starsThreshold = queryData.starsThreshold; |     let starsThreshold = queryData.starsThreshold; | ||||||
|     let forecastingPlatforms = queryData.forecastingPlatforms.map( |  | ||||||
|       (x) => x.value |  | ||||||
|     ); |  | ||||||
|     let platformsIncludeGuesstimate = |     let platformsIncludeGuesstimate = | ||||||
|       forecastingPlatforms.includes("Guesstimate") && starsThreshold <= 1; |       queryData.forecastingPlatforms.includes("guesstimate") && | ||||||
|  |       starsThreshold <= 1; | ||||||
| 
 | 
 | ||||||
|     // preparation
 |     // preparation
 | ||||||
|     let unawaitedAlgoliaResponse = searchWithAlgolia({ |     let unawaitedAlgoliaResponse = searchWithAlgolia({ | ||||||
|       queryString: query, |       queryString: query, | ||||||
|       hitsPerPage: queryData.numDisplay + 50, |       hitsPerPage: limit + 50, | ||||||
|       starsThreshold, |       starsThreshold, | ||||||
|       filterByPlatforms: forecastingPlatforms, |       filterByPlatforms: queryData.forecastingPlatforms, | ||||||
|       forecastsThreshold, |       forecastsThreshold, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     // consider the guesstimate and the non-guesstimate cases separately.
 |     // consider the guesstimate and the non-guesstimate cases separately.
 | ||||||
|     switch (platformsIncludeGuesstimate) { |     if (platformsIncludeGuesstimate) { | ||||||
|       case false: |       let responses = await Promise.all([ | ||||||
|         results = await unawaitedAlgoliaResponse; |         unawaitedAlgoliaResponse, | ||||||
|         break; |         searchGuesstimate(query), | ||||||
|       case true: |       ]); // faster than two separate requests
 | ||||||
|         let responses = await Promise.all([ |       let responsesNotGuesstimate = responses[0]; | ||||||
|           unawaitedAlgoliaResponse, |       let responsesGuesstimate = responses[1]; | ||||||
|           searchGuesstimate(query), |       results = [...responsesNotGuesstimate, ...responsesGuesstimate]; | ||||||
|         ]); // faster than two separate requests
 |       //results.sort((x,y)=> x.ranking < y.ranking ? -1: 1)
 | ||||||
|         let responsesNotGuesstimate = responses[0]; |     } else { | ||||||
|         let responsesGuesstimate = responses[1]; |       results = await unawaitedAlgoliaResponse; | ||||||
|         results = [...responsesNotGuesstimate, ...responsesGuesstimate]; |  | ||||||
|         //results.sort((x,y)=> x.ranking < y.ranking ? -1: 1)
 |  | ||||||
|         break; |  | ||||||
|       default: |  | ||||||
|         return -1; |  | ||||||
|     } |     } | ||||||
|     // Maintain compatibility with fuse
 |  | ||||||
|     let makeCompatibleWithFuse = (results) => |  | ||||||
|       results.map((result, index) => ({ |  | ||||||
|         item: result, |  | ||||||
|         score: 0, // 0.4 - 0.4 / (index + 1),
 |  | ||||||
|       })); |  | ||||||
| 
 | 
 | ||||||
|     results = makeCompatibleWithFuse(results); |     return results; | ||||||
|   } catch (error) { |   } catch (error) { | ||||||
|     console.log(error); |     console.log(error); | ||||||
|   } finally { |   } finally { | ||||||
|  |  | ||||||
|  | @ -1,14 +1,18 @@ | ||||||
| /* Imports */ | /* Imports */ | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
| 
 | 
 | ||||||
|  | import { FrontendForecast } from "../platforms"; | ||||||
|  | 
 | ||||||
| /* Definitions */ | /* Definitions */ | ||||||
| let urlEndPoint = | let urlEndPoint = | ||||||
|   "https://m629r9ugsg-dsn.algolia.net/1/indexes/Space_production/query?x-algolia-agent=Algolia%20for%20vanilla%20JavaScript%203.32.1&x-algolia-application-id=M629R9UGSG&x-algolia-api-key=4e893740a2bd467a96c8bfcf95b2809c"; |   "https://m629r9ugsg-dsn.algolia.net/1/indexes/Space_production/query?x-algolia-agent=Algolia%20for%20vanilla%20JavaScript%203.32.1&x-algolia-application-id=M629R9UGSG&x-algolia-api-key=4e893740a2bd467a96c8bfcf95b2809c"; | ||||||
| 
 | 
 | ||||||
| /* Body */ | /* Body */ | ||||||
| 
 | 
 | ||||||
| export default function searchGuesstimate(query) { | export default async function searchGuesstimate( | ||||||
|   let response = axios({ |   query | ||||||
|  | ): Promise<FrontendForecast[]> { | ||||||
|  |   let response = await axios({ | ||||||
|     url: urlEndPoint, |     url: urlEndPoint, | ||||||
|     // credentials: "omit",
 |     // credentials: "omit",
 | ||||||
|     headers: { |     headers: { | ||||||
|  | @ -24,48 +28,45 @@ export default function searchGuesstimate(query) { | ||||||
|       "%20" |       "%20" | ||||||
|     )}&hitsPerPage=20&page=0&getRankingInfo=true\"}`,
 |     )}&hitsPerPage=20&page=0&getRankingInfo=true\"}`,
 | ||||||
|     method: "POST", |     method: "POST", | ||||||
|   }) |   }); | ||||||
|     .then((res) => res.data.hits) |  | ||||||
|     .then((models) => |  | ||||||
|       models.map((model, index) => { |  | ||||||
|         let description = model.description |  | ||||||
|           ? model.description.replace(/\n/g, " ").replace(/  /g, " ") |  | ||||||
|           : ""; |  | ||||||
|         let stars = description.length > 250 ? 2 : 1; |  | ||||||
|         return { |  | ||||||
|           title: model.name, |  | ||||||
|           url: `https://www.getguesstimate.com/models/${model.id}`, |  | ||||||
|           platform: "Guesstimate", |  | ||||||
|           description: description, |  | ||||||
|           options: [], |  | ||||||
|           qualityindicators: { |  | ||||||
|             stars: stars, |  | ||||||
|             numforecasts: 1, |  | ||||||
|             numforecasters: 1, |  | ||||||
|           }, |  | ||||||
|           visualization: model.big_screenshot, |  | ||||||
|           ranking: 10 * (index + 1) - 0.5, //(model._rankingInfo - 1*index)// hack
 |  | ||||||
|         }; |  | ||||||
|       }) |  | ||||||
|     ) |  | ||||||
|     .then((models) => { |  | ||||||
|       // filter for duplicates. Surprisingly common.
 |  | ||||||
|       let uniqueTitles = []; |  | ||||||
|       let uniqueModels = []; |  | ||||||
|       for (let model of models) { |  | ||||||
|         if ( |  | ||||||
|           !uniqueTitles.includes(model.title) && |  | ||||||
|           !model.title.includes("copy") |  | ||||||
|         ) { |  | ||||||
|           uniqueModels.push(model); |  | ||||||
|           uniqueTitles.push(model.title); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       return uniqueModels; |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|   console.log(response); |   const models: any[] = response.data.hits; | ||||||
|   return response; // This is a promise. Usable with either async/await (a mess in React) or with .then(guesstimateModels => doSomething(guesstimateModels))
 |   const mappedModels: FrontendForecast[] = models.map((model, index) => { | ||||||
|  |     let description = model.description | ||||||
|  |       ? model.description.replace(/\n/g, " ").replace(/  /g, " ") | ||||||
|  |       : ""; | ||||||
|  |     let stars = description.length > 250 ? 2 : 1; | ||||||
|  |     return { | ||||||
|  |       id: `guesstimate-${model.id}`, | ||||||
|  |       title: model.name, | ||||||
|  |       url: `https://www.getguesstimate.com/models/${model.id}`, | ||||||
|  |       timestamp: model.created_at, // TODO - check that format matches
 | ||||||
|  |       platform: "guesstimate", | ||||||
|  |       platformLabel: "Guesstimate", | ||||||
|  |       description: description, | ||||||
|  |       options: [], | ||||||
|  |       qualityindicators: { | ||||||
|  |         stars: stars, | ||||||
|  |         numforecasts: 1, | ||||||
|  |         numforecasters: 1, | ||||||
|  |       }, | ||||||
|  |       visualization: model.big_screenshot, | ||||||
|  |       ranking: 10 * (index + 1) - 0.5, //(model._rankingInfo - 1*index)// hack
 | ||||||
|  |     }; | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   // filter for duplicates. Surprisingly common.
 | ||||||
|  |   let uniqueTitles = []; | ||||||
|  |   let uniqueModels: FrontendForecast[] = []; | ||||||
|  |   for (let model of mappedModels) { | ||||||
|  |     if (!uniqueTitles.includes(model.title) && !model.title.includes("copy")) { | ||||||
|  |       uniqueModels.push(model); | ||||||
|  |       uniqueTitles.push(model.title); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   console.log(uniqueModels); | ||||||
|  |   return uniqueModels; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // searchGuesstimate("COVID-19").then(guesstimateModels => console.log(guesstimateModels))
 | // searchGuesstimate("COVID-19").then(guesstimateModels => console.log(guesstimateModels))
 | ||||||
|  |  | ||||||
|  | @ -1,5 +1,7 @@ | ||||||
| import algoliasearch from "algoliasearch"; | import algoliasearch from "algoliasearch"; | ||||||
| 
 | 
 | ||||||
|  | import { FrontendForecast } from "../platforms"; | ||||||
|  | 
 | ||||||
| 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 | ||||||
|  | @ -76,13 +78,12 @@ let noExactMatch = (queryString, result) => { | ||||||
| // only query string
 | // only query string
 | ||||||
| export default async function searchWithAlgolia({ | export default async function searchWithAlgolia({ | ||||||
|   queryString, |   queryString, | ||||||
|   hitsPerPage, |   hitsPerPage = 5, | ||||||
|   starsThreshold, |   starsThreshold, | ||||||
|   filterByPlatforms, |   filterByPlatforms, | ||||||
|   forecastsThreshold, |   forecastsThreshold, | ||||||
| }) { | }): Promise<FrontendForecast[]> { | ||||||
|   hitsPerPage = hitsPerPage || 5; |   let response = await index.search<FrontendForecast>(queryString, { | ||||||
|   let response = await index.search(queryString, { |  | ||||||
|     hitsPerPage, |     hitsPerPage, | ||||||
|     filters: buildFilter({ |     filters: buildFilter({ | ||||||
|       starsThreshold, |       starsThreshold, | ||||||
|  | @ -92,12 +93,7 @@ export default async function searchWithAlgolia({ | ||||||
|     //facetFilters: buildFacetFilter({filterByPlatforms}),
 |     //facetFilters: buildFacetFilter({filterByPlatforms}),
 | ||||||
|     getRankingInfo: true, |     getRankingInfo: true, | ||||||
|   }); |   }); | ||||||
|   let results: any[] = response.hits; |   let results: FrontendForecast[] = response.hits; | ||||||
|   console.log( |  | ||||||
|     "searchWithAlgolia.js/searchWithAlgolia/queryString", |  | ||||||
|     queryString |  | ||||||
|   ); |  | ||||||
|   console.log("searchWithAlgolia.js/searchWithAlgolia/results", results); |  | ||||||
| 
 | 
 | ||||||
|   let recursionError = ["metaforecast", "metaforecasts", "metaforecasting"]; |   let recursionError = ["metaforecast", "metaforecasts", "metaforecasting"]; | ||||||
|   if ( |   if ( | ||||||
|  | @ -106,9 +102,11 @@ export default async function searchWithAlgolia({ | ||||||
|   ) { |   ) { | ||||||
|     results = [ |     results = [ | ||||||
|       { |       { | ||||||
|  |         id: "not-found", | ||||||
|         title: "No search results match your query", |         title: "No search results match your query", | ||||||
|         url: "https://metaforecast.org", |         url: "https://metaforecast.org", | ||||||
|         platform: "Metaforecast", |         platform: "metaforecast", | ||||||
|  |         platformLabel: "Metaforecast", | ||||||
|         description: "Maybe try a broader query?", |         description: "Maybe try a broader query?", | ||||||
|         options: [ |         options: [ | ||||||
|           { |           { | ||||||
|  | @ -128,17 +126,19 @@ export default async function searchWithAlgolia({ | ||||||
|           numforecasters: 1, |           numforecasters: 1, | ||||||
|           stars: 5, |           stars: 5, | ||||||
|         }, |         }, | ||||||
|         noExactSearchResults: true, |         // noExactSearchResults: true,
 | ||||||
|         optionsstringforsearch: "Yes, No", |         // optionsstringforsearch: "Yes, No",
 | ||||||
|         has_numforecasts: true, |         // has_numforecasts: true,
 | ||||||
|       }, |       }, | ||||||
|     ]; |     ]; | ||||||
|   } else if (recursionError.includes(queryString.toLowerCase())) { |   } else if (recursionError.includes(queryString.toLowerCase())) { | ||||||
|     results = [ |     results = [ | ||||||
|       { |       { | ||||||
|  |         id: "recursion-error", | ||||||
|         title: `Did you mean: ${queryString}?`, |         title: `Did you mean: ${queryString}?`, | ||||||
|         url: "https://metaforecast.org/recursion?bypassEasterEgg=true", |         url: "https://metaforecast.org/recursion?bypassEasterEgg=true", | ||||||
|         platform: "Metaforecast", |         platform: "metaforecast", | ||||||
|  |         platformLabel: "Metaforecast", | ||||||
|         description: |         description: | ||||||
|           "Fatal error: Too much recursion. Click to proceed anyways", |           "Fatal error: Too much recursion. Click to proceed anyways", | ||||||
|         options: [ |         options: [ | ||||||
|  | @ -159,9 +159,9 @@ export default async function searchWithAlgolia({ | ||||||
|           numforecasters: 1, |           numforecasters: 1, | ||||||
|           stars: 5, |           stars: 5, | ||||||
|         }, |         }, | ||||||
|         noExactSearchResults: true, |         // noExactSearchResults: true,
 | ||||||
|         optionsstringforsearch: "Yes, No", |         // optionsstringforsearch: "Yes, No",
 | ||||||
|         has_numforecasts: true, |         // has_numforecasts: true,
 | ||||||
|       }, |       }, | ||||||
|       ...results, |       ...results, | ||||||
|     ]; |     ]; | ||||||
|  | @ -171,9 +171,11 @@ export default async function searchWithAlgolia({ | ||||||
|     noExactMatch(queryString, results[0]) |     noExactMatch(queryString, results[0]) | ||||||
|   ) { |   ) { | ||||||
|     results.unshift({ |     results.unshift({ | ||||||
|  |       id: "not-found-2", | ||||||
|       title: "No search results appear to match your query", |       title: "No search results appear to match your query", | ||||||
|       url: "https://metaforecast.org", |       url: "https://metaforecast.org", | ||||||
|       platform: "Metaforecast", |       platform: "metaforecast", | ||||||
|  |       platformLabel: "Metaforecast", | ||||||
|       description: "Maybe try a broader query? That said, we could be wrong.", |       description: "Maybe try a broader query? That said, we could be wrong.", | ||||||
|       options: [ |       options: [ | ||||||
|         { |         { | ||||||
|  | @ -193,12 +195,12 @@ export default async function searchWithAlgolia({ | ||||||
|         numforecasters: 1, |         numforecasters: 1, | ||||||
|         stars: 1, |         stars: 1, | ||||||
|       }, |       }, | ||||||
|       noExactSearchResults: true, |       // noExactSearchResults: true,
 | ||||||
|       optionsstringforsearch: "Yes, No", |       // optionsstringforsearch: "Yes, No",
 | ||||||
|       has_numforecasts: true, |       // has_numforecasts: true,
 | ||||||
|     }); |     }); | ||||||
|   } else { |   } else { | ||||||
|     results[0].noExactSearchResults = false; |     // results[0].noExactSearchResults = false;
 | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   return results; |   return results; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user