fix: catch metaculus errors
Current code isn't particularly resilient to API changes.
This commit is contained in:
		
							parent
							
								
									83a01e6156
								
							
						
					
					
						commit
						fc9c222a44
					
				|  | @ -211,15 +211,17 @@ const fetchAndValidate = async <T = unknown>( | |||
|   url: string, | ||||
|   validator: ValidateFunction<T> | ||||
| ): Promise<T> => { | ||||
|   console.log(url); | ||||
|   // console.log(url);
 | ||||
|   const data = await fetchWithRetries<object>(url); | ||||
|   if (validator(data)) { | ||||
|     return data; | ||||
|   } | ||||
|   }else{ | ||||
|     console.log(data) | ||||
|     throw new Error( | ||||
|       `Response validation for url ${url} failed: ` + | ||||
|       JSON.stringify(validator.errors) | ||||
|         JSON.stringify(validator.errors, null, 4) | ||||
|     ); | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| export async function fetchApiQuestions( | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| import Error from "next/error"; | ||||
| import {FetchedQuestion, Platform} from ".."; | ||||
| import {average} from "../../../utils"; | ||||
| import {sleep} from "../../utils/sleep"; | ||||
|  | @ -7,16 +8,14 @@ import { | |||
|   ApiPredictable, | ||||
|   ApiQuestion, | ||||
|   fetchApiQuestions, | ||||
|   fetchSingleApiQuestion, | ||||
|   fetchSingleApiQuestion | ||||
| } from "./api"; | ||||
| 
 | ||||
| const platformName = "metaculus"; | ||||
| const now = new Date().toISOString(); | ||||
| const SLEEP_TIME = 1000; | ||||
| 
 | ||||
| async function apiQuestionToFetchedQuestions( | ||||
|   apiQuestion: ApiQuestion | ||||
| ): Promise<FetchedQuestion[]> { | ||||
| async function apiQuestionToFetchedQuestions(apiQuestion: ApiQuestion): Promise<FetchedQuestion[]> { | ||||
|   // one item can expand:
 | ||||
|   // - to 0 questions if we don't want it;
 | ||||
|   // - to 1 question if it's a simple forecast
 | ||||
|  | @ -32,9 +31,8 @@ async function apiQuestionToFetchedQuestions( | |||
|     return false; | ||||
|   }; | ||||
| 
 | ||||
|   const buildFetchedQuestion = ( | ||||
|     q: ApiPredictable & ApiCommon | ||||
|   ): Omit<FetchedQuestion, "url" | "description" | "title"> => { | ||||
|   const buildFetchedQuestion = (q : ApiPredictable & ApiCommon) : Omit < FetchedQuestion, | ||||
|     "url" | "description" | "title" > => { | ||||
|       const isBinary = q.possibilities.type === "binary"; | ||||
|       let options: FetchedQuestion["options"] = []; | ||||
|       if (isBinary) { | ||||
|  | @ -44,30 +42,31 @@ async function apiQuestionToFetchedQuestions( | |||
|             { | ||||
|               name: "Yes", | ||||
|               probability: probability, | ||||
|             type: "PROBABILITY", | ||||
|           }, | ||||
|           { | ||||
|               type: "PROBABILITY" | ||||
|             }, { | ||||
|               name: "No", | ||||
|               probability: 1 - probability, | ||||
|             type: "PROBABILITY", | ||||
|               type: "PROBABILITY" | ||||
|             }, | ||||
|           ]; | ||||
|         } | ||||
|       } | ||||
|       return { | ||||
|       id: `${platformName}-${q.id}`, | ||||
|           id: `${platformName}-${ | ||||
|           q.id | ||||
|         }`,
 | ||||
|         options, | ||||
|         qualityindicators: { | ||||
|         numforecasts: q.number_of_predictions, | ||||
|           numforecasts: q.number_of_predictions | ||||
|         }, | ||||
|         extra: { | ||||
|           resolution_data: { | ||||
|             publish_time: apiQuestion.publish_time, | ||||
|             resolution: apiQuestion.resolution, | ||||
|             close_time: apiQuestion.close_time, | ||||
|           resolve_time: apiQuestion.resolve_time, | ||||
|         }, | ||||
|       }, | ||||
|             resolve_time: apiQuestion.resolve_time | ||||
|           } | ||||
|         } | ||||
|       }; | ||||
|     }; | ||||
| 
 | ||||
|  | @ -77,44 +76,64 @@ async function apiQuestionToFetchedQuestions( | |||
|     if (apiQuestionDetails.type !== "group") { | ||||
|       throw new Error("Expected `group` type"); // shouldn't happen, this is mostly for typescript
 | ||||
|     } | ||||
|     return (apiQuestionDetails.sub_questions || []) | ||||
|       .filter((q) => !skip(q)) | ||||
|       .map((sq) => { | ||||
|       try{ | ||||
|         let result = (apiQuestionDetails.sub_questions || []).filter((q) => ! skip(q)).map((sq) => { | ||||
|           const tmp = buildFetchedQuestion(sq); | ||||
|           return { | ||||
|             ... tmp, | ||||
|           title: `${apiQuestion.title} (${sq.title})`, | ||||
|             title: `${ | ||||
|               apiQuestion.title | ||||
|             } (${ | ||||
|               sq.title | ||||
|             })`,
 | ||||
|             description: apiQuestionDetails.description || "", | ||||
|           url: `https://www.metaculus.com${apiQuestion.page_url}?sub-question=${sq.id}`, | ||||
|             url: `https://www.metaculus.com${ | ||||
|               apiQuestion.page_url | ||||
|             }?sub-question=${ | ||||
|               sq.id | ||||
|             }` | ||||
|           }; | ||||
|         }); | ||||
|         return result | ||||
|       }catch(error){ | ||||
|         console.log(error) | ||||
|         return [] | ||||
|       } | ||||
|   } else if (apiQuestion.type === "forecast") { | ||||
|     if (apiQuestion.group) { | ||||
|       return []; // sub-question, should be handled on the group level
 | ||||
|     } | ||||
|     if (skip(apiQuestion)) { | ||||
|       console.log(`- [Skipping]: ${ | ||||
|         apiQuestion.title | ||||
|       }`)
 | ||||
|       /*console.log(`Close time: ${ | ||||
|         apiQuestion.close_time | ||||
|       }, resolve time: ${ | ||||
|         apiQuestion.resolve_time | ||||
|       }`)*/
 | ||||
|       return []; | ||||
|     } | ||||
| 
 | ||||
|     await sleep(SLEEP_TIME); | ||||
|     const apiQuestionDetails = await fetchSingleApiQuestion(apiQuestion.id); | ||||
|     try{ | ||||
|       const tmp = buildFetchedQuestion(apiQuestion); | ||||
|     return [ | ||||
|       { | ||||
|       return [{ | ||||
|           ... tmp, | ||||
|           title: apiQuestion.title, | ||||
|           description: apiQuestionDetails.description || "", | ||||
|         url: "https://www.metaculus.com" + apiQuestion.page_url, | ||||
|       }, | ||||
|     ]; | ||||
|           url: "https://www.metaculus.com" + apiQuestion.page_url | ||||
|         },]; | ||||
|     }catch(error){ | ||||
|       console.log(error) | ||||
|       return [] | ||||
|     } | ||||
|   } else { | ||||
|     if (apiQuestion.type !== "claim") { | ||||
|       // should never happen, since `discriminator` in JTD schema causes a strict runtime check
 | ||||
|       console.log( | ||||
|         `Unknown metaculus question type: ${ | ||||
|     if (apiQuestion.type !== "claim") { // should never happen, since `discriminator` in JTD schema causes a strict runtime check
 | ||||
|       console.log(`Unknown metaculus question type: ${ | ||||
|         (apiQuestion as any).type | ||||
|         }, skipping` | ||||
|       ); | ||||
|       }, skipping`);
 | ||||
|     } | ||||
|     return []; | ||||
|   } | ||||
|  | @ -125,19 +144,19 @@ export const metaculus: Platform<"id" | "debug"> = { | |||
|   label: "Metaculus", | ||||
|   color: "#006669", | ||||
|   version: "v2", | ||||
|   fetcherArgs: ["id", "debug"], | ||||
|   fetcherArgs: [ | ||||
|     "id", "debug" | ||||
|   ], | ||||
|   async fetcher(opts) { | ||||
|     let allQuestions: FetchedQuestion[] = []; | ||||
| 
 | ||||
|     if (opts.args ?. id) { | ||||
|       console.log("Using optional id arg.") | ||||
|       const id = Number(opts.args.id); | ||||
|       const apiQuestion = await fetchSingleApiQuestion(id); | ||||
|       const questions = await apiQuestionToFetchedQuestions(apiQuestion); | ||||
|       console.log(questions); | ||||
|       return { | ||||
|         questions, | ||||
|         partial: true, | ||||
|       }; | ||||
|       return {questions, partial: true}; | ||||
|     } | ||||
| 
 | ||||
|     let next: string |null = "https://www.metaculus.com/api2/questions/"; | ||||
|  | @ -148,13 +167,16 @@ export const metaculus: Platform<"id" | "debug"> = { | |||
|       await sleep(SLEEP_TIME); | ||||
|       const apiQuestions: ApiMultipleQuestions = await fetchApiQuestions(next); | ||||
|       const results = apiQuestions.results; | ||||
| 
 | ||||
|       // console.log(results)
 | ||||
|       let j = false; | ||||
| 
 | ||||
|       for (const result of results) { | ||||
|         const questions = await apiQuestionToFetchedQuestions(result); | ||||
|         // console.log(questions)
 | ||||
|         for (const question of questions) { | ||||
|           console.log(`- ${question.title}`); | ||||
|           console.log(`- ${ | ||||
|             question.title | ||||
|           }`);
 | ||||
|           if ((! j && i % 20 === 0) || opts.args ?. debug) { | ||||
|             console.log(question); | ||||
|             j = true; | ||||
|  | @ -167,20 +189,16 @@ export const metaculus: Platform<"id" | "debug"> = { | |||
|       i += 1; | ||||
|     } | ||||
| 
 | ||||
|     return { | ||||
|       questions: allQuestions, | ||||
|       partial: false, | ||||
|     }; | ||||
|     return {questions: allQuestions, partial: false}; | ||||
|   }, | ||||
| 
 | ||||
|   calculateStars(data) { | ||||
|     const {numforecasts} = data.qualityindicators; | ||||
|     const nuno = () => | ||||
|       (numforecasts || 0) > 300 ? 4 : (numforecasts || 0) > 100 ? 3 : 2; | ||||
|     const nuno = () => (numforecasts || 0) > 300 ? 4 : (numforecasts || 0) > 100 ? 3 : 2; | ||||
|     const eli = () => 3; | ||||
|     const misha = () => 3; | ||||
|     const starsDecimal = average([nuno(), eli(), misha()]); | ||||
|     const starsInteger = Math.round(starsDecimal); | ||||
|     return starsInteger; | ||||
|   }, | ||||
|   } | ||||
| }; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user