/* handle ties e.g. in high jump, split points -- simply bail if sorted order is different than prescribed order? */
detailEvt = ``;
hideCountry = false;
doSort = false;
bdCol = true;
window.data ??= {};
window.cache ??= {};
(async () => {
for (const day of [1])
window.data[day]??=await (await fetch("https://lqf74cdt2jf7tktozacymhcvbe.appsync-api.eu-west-1.amazonaws.com/graphql", {
"headers": {
"x-api-key": "da2-jgtxe5f2tvcafinp5zh5kmq5oq" // note API key is intentionally public
},
"body": JSON.stringify({
"operationName": "getCalendarCompetitionResults",
"variables": {
"competitionId": 7174052,
"day": day,
"eventId": null
},
"query": `query getCalendarCompetitionResults($competitionId: Int, $day: Int, $eventId: Int) {
getCalendarCompetitionResults(competitionId: $competitionId, day: $day, eventId: $eventId) {
competition {
dateRange
endDate
name
rankingCategory
startDate
venue
__typename
}
eventTitles {
rankingCategory
eventTitle
events {
event
eventId
gender
isRelay
perResultWind
withWind
summary {
competitor {
teamMembers {
id
name
iaafId
urlSlug
__typename
}
id
name
iaafId
urlSlug
birthDate
__typename
}
mark
nationality
placeInRace
placeInRound
points
raceNumber
records
wind
__typename
}
races {
date
day
race
raceId
raceNumber
results {
competitor {
teamMembers {
id
name
iaafId
urlSlug
__typename
}
id
name
iaafId
urlSlug
birthDate
hasProfile
__typename
}
mark
nationality
place
points
qualified
records
wind
remark
details {
event
eventId
raceNumber
mark
wind
placeInRound
placeInRace
points
overallPoints
placeInRoundByPoints
overallPlaceByPoints
__typename
}
__typename
}
startList {
competitor {
birthDate
country
id
name
urlSlug
__typename
}
order
pb
sb
bib
__typename
}
wind
__typename
}
__typename
}
__typename
}
options {
days {
date
day
__typename
}
events {
gender
id
name
combined
__typename
}
__typename
}
parameters {
competitionId
day
eventId
__typename
}
__typename
}
}`
}),
"method": "POST",
})).json();
if (typeof nameFixer === 'undefined') {
const script = Object.assign(document.createElement('script'), { src: 'https://unpkg.com/name-fixer@1.0.0' });
document.body.appendChild(script);
await new Promise(res => script.addEventListener('load', res));
}
titleExists=async (name)=>{
const enLabelTitleMatch = await fetch(`https://xtools.wmcloud.org/api/page/articleinfo/en.wikipedia.org/${name.replace('|', '')}?format=json&uselang=en`);
return enLabelTitleMatch.status === 200;
}
getSuffix=async (name, evt, year) => {
const el = evt?.toLowerCase() ?? '';
const parens = evt?.includes('mH') || el.includes('metres hurdles') ? 'hurdler' : el.includes('high jump') ? 'high jumper' : el.includes('long jump') ? 'long jumper' : el.includes('pole vault') ? 'pole vaulter' : el.includes('triple jump') ? 'triple jumper' : el.includes('shot put') ? 'shot putter' : el.includes('discus') ? 'discus thrower' : el.includes('hammer') ? 'hammer thrower' : el.includes('javelin') ? 'javelin thrower' : el.includes('steeplchase') ? 'steeplechase runner' : ['60m', '60 m', '100m', '100 m', '200m', '200 m', '400m', '400 m'].some(d => el.includes(d)) ? 'sprinter' : 'runner';
name += ` (${parens})`;
if (await titleExists(name)) name = name.replace(`(${parens})`, `(${parens}, born ${year})`);
return name;
}
getTitle=async (id,name,evt,year)=>{
const words = name.split(' ');
const lnameStart = words.findIndex(w => w.toUpperCase() === w);
const fname = words.slice(0, lnameStart).join(' ');
const lname = words.slice(lnameStart).join(' ');
name = fname + ' ' + nameFixer.nameFixer(lname);
name = name.replace('LI', 'Li').replace('XI', 'Xi');
if (cache[id]) return cache[id];
const pages = await (await fetch('https://www.wikidata.org/w/api.php?' + new URLSearchParams({
action: 'query',
format: 'json',
list: 'search',
srsearch: `haswbstatement:P1146=${id}`,
}))).json();
const qid = pages.query.search[0]?.title;
if (qid) {
const entity = await (await fetch('https://www.wikidata.org/w/api.php?' + new URLSearchParams({
action: 'wbgetentities',
format: 'json',
ids: qid,
}))).json();
const sitelinks = entity.entities[qid].sitelinks;
const enTitle = sitelinks.enwiki?.title;
if (enTitle) {
cache[id] = `[[${enTitle}${enTitle.includes('(') ? '|' : ''}]]`;
return cache[id];
}
let enLabel = entity.entities[qid].labels.en?.value ?? name;
const enLabelNoParens = enLabel;
if (await titleExists(enLabel)) enLabel = await getSuffix(enLabel, evt, year);
const otherWikis = Object.keys(sitelinks).filter(key => !key.startsWith('commons') && key.endsWith('wiki'));
if (otherWikis.length) {
const positionals = otherWikis.map(ow => `|${ow.replace('wiki', '')}|${sitelinks[ow].title}`).join('');
cache[id] = `{{ill|${enLabel}${positionals}${enLabel.includes('(') ? `|lt=${enLabelNoParens}` : ''}}}`;
return cache[id];
}
cache[id] = `{{ill|${enLabel}|wd=${qid}|s=1${enLabel.includes('(') ? `|lt=${enLabelNoParens}` : ''}}}`;
return cache[id];
}
if (await titleExists(name)) name = await getSuffix(name, evt, year);
cache[id] = `[[${name}${name.includes('(') ? '|' : ''}]]`;
return cache[id];
}
mark2secs=(mark, isField = false)=>{
const parts = mark.split(':');
let ret;
if (parts.length === 1) ret = +mark;
else if (parts.length === 2) ret = +parts[0] * 60 + +parts[1];
else ret = +parts[0] * 60 * 60 + +parts[1] * 60 + +parts[2];
if (Number.isNaN(ret)) return isField ? -Infinity : Infinity;
return ret;
}
const COLS = 2;
let out = '';
const combinedData = Object.values(data).reduce((acc, dayData) => {
for (let eventTitle of dayData.data.getCalendarCompetitionResults.eventTitles) {
eventTitle = structuredClone(eventTitle);
const foundEventTitle = acc.data.getCalendarCompetitionResults.eventTitles.find(et => et.eventTitle === eventTitle.eventTitle);
if (foundEventTitle) {
const oldEvts = [...eventTitle.events];
for (const evt of oldEvts) {
const foundEvt = foundEventTitle.events.find(e2 => e2.event === evt.event);
if (foundEvt) foundEvt.races.push(...evt.races);
else foundEventTitle.events.push(evt);
}
}
else acc.data.getCalendarCompetitionResults.eventTitles.push(eventTitle);
}
return acc;
}, { data: { getCalendarCompetitionResults: { eventTitles: [], competition: Object.values(data)[0].data.getCalendarCompetitionResults.competition } } });
startDate = new Date(combinedData.data.getCalendarCompetitionResults.competition.startDate);
startDateOrig = combinedData.data.getCalendarCompetitionResults.competition.startDate;
for (const eventTitle of combinedData.data.getCalendarCompetitionResults.eventTitles) {
eventTitle.pointsTitle = eventTitle.eventTitle;
if (eventTitle.eventTitle?.startsWith('XC ')) eventTitle.pointsTitle = eventTitle.eventTitle.replace(/XC [0-9\.]+km/, 'XC');
console.log(eventTitle.pointsTitle)
if (!['World Athletics Indoor Tour', 'Indoor Meeting', 'U20 Events', 'Masters Events', 'Diamond Discipline', 'Promotional Events', 'National Events', 'U18 Events', 'Regional Races', 'Additional Events', 'XC', null].includes(eventTitle.pointsTitle)) continue;
let evtIdx = -1;
let etOutput = `===${eventTitle.eventTitle ?? eventTitle.events.find(evt => evt.event === detailEvt)?.races[0].race}===\n`
for (const evt of eventTitle.events) {
if (detailEvt && evt.event !== detailEvt) continue;
let etHasEvents = true;
const isLastEvt = eventTitle.events.indexOf(evt) === eventTitle.events.length - 1;
const isField = ['jump', 'throw', 'vault', 'discus', 'put'].some(s => evt.event.toLowerCase().includes(s));
const stages = Object.values(evt.races.reduce((acc, r) => {
acc[r.race] ??= [];
acc[r.race].push(r);
return acc;
}, {}));
for (const stage of stages) {
const isLastStage = stages.indexOf(stage) === stages.length - 1;
evtIdx++;
const isFinal = stage[0].race === 'Final';
const finalQualIds = isFinal ? [] : (stages.find(st => st[0].race === 'Final') ?? []).flatMap(race => race.results).map(res => res.competitor.urlSlug?.split('-').at(-1).replace(/^0/, ''));
// Object.assign({}, [...document.querySelector('.records-table').querySelectorAll('tr')].map(tr => tr.querySelectorAll('td')[2]?.innerText).filter(x => x?.trim()))
const hasPts = isFinal && eventTitle.pointsTitle === 'World Athletics Indoor Tour' ? {1: 10, 2: 7, 3: 5, 4: 3} : isFinal && eventTitle.pointsTitle === 'Diamond Discipline' ? {1:8,2:7,3:6,4:5,5:4,6:3,7:2,8:1} : eventTitle.pointsTitle === 'XC' ? {"1":"1240","2":"1220","3":"1200","4":"1180","5":"1160","6":"1145","7":"1130","8":"1120","9":"1110","10":"1100","11":"1090","12":"1080","13":"1070","14":"1060","15":"1055","16":"1050","17":"1045","18":"1040","19":"1035","20":"1030","21":"1025","22":"1020","23":"1015","24":"1010","25":"1005","26":"1000","27":"995","28":"990","29":"985","30":"980","31":"975","32":"970","33":"965","34":"960","35":"955","36":"950","37":"945","38":"940","39":"935","40":"930","41":"927","42":"924","43":"921","44":"918","45":"915","46":"912","47":"909","48":"906","49":"903","50":"900","51":"898","52":"896","53":"894","54":"892","55":"890","56":"888","57":"886","58":"884","59":"882","60":"880","61":"879","62":"878","63":"877","64":"876","65":"875","66":"874","67":"873","68":"872","69":"871","70":"870","71":"869","72":"868","73":"887","74":"866","75":"865","76":"864","77":"863","78":"862","79":"861","80":"860"} : null;
const isMulti = stage.length > 1;
if (evtIdx % COLS === 0) etOutput += '{{col-begin}}\n';
etOutput += `{{col-${COLS}}}\n`;
const unfilteredResults = stage.flatMap(race => race.results.map(res => ({...res, raceNumber: race.raceNumber}))).map((r, idx, arr) => ({...r, bestWindLegal: !r.competitor.teamMembers && arr.findIndex(r2 => r2.competitor.urlSlug === r.competitor.urlSlug) !== idx}));
const bestWindLegals = unfilteredResults.filter(r => r.bestWindLegal);
let results = unfilteredResults.filter(r => !r.bestWindLegal)
if (doSort) results = results.sort((a, b) => isField ? mark2secs(b.mark, true) - mark2secs(a.mark, true) : mark2secs(a.mark) - mark2secs(b.mark));
const hasWindCol = results.some(res => res.wind);
const numTableCols = 4 + hasWindCol + isMulti + hasPts + bdCol;
etOutput += `{| class="wikitable mw-datatable sortable"
|+${evt.event.replace(' indoor', '') + (isFinal ? (stage.length === 1 && stage[0].wind ? ` <small>{{nowrap|(${stage[0].wind} m/s)}}</small>` : '') : ` ${stage[0].race}`)}
! Place !! Athlete !!${bdCol ? ' Age !!' : ''}${hideCountry ? '' : ' Country !!'} ${isField ? 'Mark' : 'Time'}${hasWindCol ? ' !! Wind' : ''}${isMulti ? ' !! Heat' : ''}${hasPts ? ' !! Points' : ''}\n`;
const getResultRow = async (result, isBestWindLegal = false) => {
const pl = doSort ? ((['DNS', 'DNF', 'DQ', 'NM'].includes(result.mark) ? '' : results.indexOf(result) + 1) || '') : result.place?.replace('.', '');
const name = result.competitor.name;
const dob = new Date(result.competitor.birthDate);
const isYearOnly = result.competitor.birthDate?.split(' ').length === 1;
const id = result.competitor.urlSlug?.split('-').at(-1).replace(/^0/, '');
return `|-${!isFinal && finalQualIds.includes(id) ? 'bgcolor=#bbf3bb' : ''}\n|align=center| ${{1: '{{Gold1}}', 2: '{{Silver2}}', 3: '{{Bronze3}}'}[isFinal ? pl : 0] ?? pl} || ${id ? await getTitle(id, name, evt.event, dob.getFullYear()) : (await Promise.all(result.competitor.teamMembers.map(async tm => await getTitle(tm.id, tm.name, evt.event)))).join('<br>')} ${bdCol ? `|| ${result.competitor.birthDate ? `{{age|${result.competitor.birthDate}|${startDateOrig}}} ` : ''}` : ''}${hideCountry ? '' : `|| {{flagg|cncie|${result.nationality}}} `}|| ${isField && (pl || isBestWindLegal) ? `{{nowrap|${result.mark} m}}` : result.mark}${hasWindCol ? ` ||align=right| {{nowrap|${result.wind} m/s}}` : ''}${isMulti ? ` ||align=center| ${result.raceNumber}` : ''}${hasPts ? ` ||align=center| ${hasPts[pl] ?? ''}` : ''}\n`;
}
for (const result of results) etOutput += await getResultRow(result);
if (bestWindLegals.length) {
etOutput += `|-\n!align=center colspan=${numTableCols}| Best wind-legal performances\n`;
for (const bwl of bestWindLegals) etOutput += await getResultRow(bwl, true);
}
etOutput += '|}\n';
if (evtIdx % COLS === COLS - 1 || (isLastEvt && isLastStage)) etOutput += '{{col-end}}\n';
}
}
if (etOutput.split('\n').length > 2) out += etOutput;
}
if (!out.endsWith('{{col-end}}\n')) out += '{{col-end}}\n';
console.log(out);
return out;
})();