Last active
December 30, 2022 09:13
-
-
Save double-beep/848bdcc15ad1d1d348c0231422d1c2ff to your computer and use it in GitHub Desktop.
Scraps GM applications & GM norms and finds the people who earnt at least two in less than x days
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// convert [[id1], [id2], [id3], ...] to { id1: [], id2: [], id3: [] } | |
// each empty array is filled later with norm information | |
const objectify = array => array.reduce((a, v) => ({ ...a, [v]: []}), {}); | |
const players = {}; // {id: name} | |
function sortByDiff(entries) { | |
return entries | |
.filter(([, value]) => value.length > 1) // players with >1 GM norm | |
.map(([id, info]) => { | |
// convert YYYY-MM-DD to millis | |
const times = info.map(entry => new Date(entry.endDate).getTime()); | |
const differences = times | |
.sort((a, b) => a - b) // sort by the most recent norm | |
.reduce((acc, val, index) => { | |
// all but the last element: | |
if (index === times.length - 1) return acc; | |
// find the absolute value of the difference | |
// between two dates | |
const difference = Math.abs(val - times[index + 1]); | |
acc.push(difference / (60 * 60 * 24 * 1000)); | |
return acc; | |
}, []); | |
return [id, differences]; | |
}) | |
// each player may have earnt 3+ GM norms | |
// sort by the min difference of each player | |
.sort(([, a], [, b]) => Math.min(...a) - Math.min(...b)); | |
} | |
async function getNormInformation(parsed, tdN, record = '') { | |
// an HTML table containing the tournaments a player has participated & earnt a GM norm | |
// ?record2=<id> -> for players who aren't GMs yet (Norms tab) | |
// ?record=<id> -> for players who have applied for GM (Applications tab) | |
const getLink = id => `https://ratings.fide.com/a_titles.php?record${record}=${id}`; | |
for (const id of Object.keys(parsed)) { | |
const link = getLink(id); | |
const normCall = await fetch(link); | |
const table = await normCall.text(); | |
const domParser = new DOMParser(); | |
const html = domParser.parseFromString(table, 'text/html'); | |
const children = html.body.querySelector('tbody')?.children; | |
if (!children) continue; // e.g. "No records found" | |
[...children] | |
// may contain a tr with a single column (Application file: ....) | |
.filter(child => child.childElementCount > 4) | |
.forEach(tr => { | |
const tds = [...tr.children]; | |
// NOTE: - date & event name is in the 2nd (index = 1)/5th (index = 4) column | |
// respectively if the player isn't a GM | |
// - otherwise 1st (index = 0)/4th (index = 3) | |
// only keep the date the tournament ended | |
const endDate = tds[tdN].innerText.substring(10, 20); // YYYY-MM-DD | |
const event = tds[tdN + 3].innerText; | |
// exclude duplicate entries | |
// yes, sometimes the same tournament can be shown twice | |
// although the player has earnt 1 GM norm from it | |
const isDuplicate = parsed[id].some(info => info.event === event); | |
if (isDuplicate) return; | |
// event is not needed/used, exists mostly for debugging purposes | |
parsed[id].push({ event, endDate }); | |
}); | |
} | |
} | |
async function part1() { | |
const call = await fetch('https://ratings.fide.com/a_titles.php?country=latest'); | |
const result = await call.json(); | |
// of the values provided, only keep the player's id | |
const ids = result.data.map(([, id, name]) => { | |
players[id] = name; | |
return id; | |
}); | |
const parsed = objectify(ids); | |
await getNormInformation(parsed, 1, 2); | |
return parsed; | |
} | |
async function part2() { | |
let data = []; | |
// pb = 70: 2023 1st FIDE Council | |
// update for new FIDE PB/Council meetings | |
for (let i = 1; i <= 70; i++) { | |
const url = `https://ratings.fide.com/a_titles.php?pb=${i}`; | |
const call = await fetch(url); | |
const result = await call.json(); | |
const gms = result.data | |
// grandmasters only | |
.filter(array => array.at(-1) === 'Grandmaster') | |
// keep the ids only | |
.map(([, id,,, name]) => { | |
players[id] = name; | |
return id; | |
}); | |
data.push(...gms); | |
} | |
data = objectify(data); | |
await getNormInformation(data, 0); | |
return data; | |
} | |
function announceResults(result) { | |
// find index to stop | |
// stop at 4 days | |
const stopIndex = result.findIndex(([, times]) => times.every(time => time > 4)); | |
result.forEach(([id, times], index) => { | |
if (index > stopIndex) return; | |
console.log('Player', players[id], '->', times); | |
}); | |
} | |
(async function() { | |
const p1 = Object.entries(await part1()); | |
const p2 = Object.entries(await part2()); | |
const result = sortByDiff(p1.concat(p2)); | |
announceResults(result); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment