From 9f8a52beca373119f7779caf4a1c5805e088fa9d Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 9 Oct 2022 20:59:33 -0600 Subject: [PATCH 01/17] CLRG Scoring Analysis pre-draft --- .../2021-12-23-drwho-S01E27-mexico/index.md | 9 +- content/blog/2022-09-06-truck-bling/index.md | 5 +- .../2022-10-09-CLRG-Scoring/awardPoints.mjs | 122 ++++++++++++ .../blog/2022-10-09-CLRG-Scoring/chart.png | Bin 0 -> 13438 bytes content/blog/2022-10-09-CLRG-Scoring/index.md | 175 ++++++++++++++++++ .../2022-10-09-CLRG-Scoring/scorecard.mjs | 54 ++++++ .../2022-10-09-CLRG-Scoring/speculator.mjs | 104 +++++++++++ content/blog/2022-10-09-CLRG-Scoring/toys.css | 36 ++++ layouts/_default/baseof.html | 11 +- layouts/shortcodes/figure.html | 4 + layouts/shortcodes/video.html | 4 +- run.sh | 3 + static/assets/css/default.css | 42 ++++- 13 files changed, 550 insertions(+), 19 deletions(-) create mode 100644 content/blog/2022-10-09-CLRG-Scoring/awardPoints.mjs create mode 100644 content/blog/2022-10-09-CLRG-Scoring/chart.png create mode 100644 content/blog/2022-10-09-CLRG-Scoring/index.md create mode 100644 content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs create mode 100644 content/blog/2022-10-09-CLRG-Scoring/speculator.mjs create mode 100644 content/blog/2022-10-09-CLRG-Scoring/toys.css create mode 100644 layouts/shortcodes/figure.html diff --git a/content/blog/2021-12-23-drwho-S01E27-mexico/index.md b/content/blog/2021-12-23-drwho-S01E27-mexico/index.md index 591972c..41b05e5 100644 --- a/content/blog/2021-12-23-drwho-S01E27-mexico/index.md +++ b/content/blog/2021-12-23-drwho-S01E27-mexico/index.md @@ -1,9 +1,6 @@ --- -date: "2021-12-23T00:00:00Z" -tags: -- drwho -title: 'Doctor Who S01E27-30: Doctor Who goes to Mexico' -url: blog/2021-12-23-drwho-S01E27-mexico/ +title: "Doctor Who S01E27-30: Doctor Who goes to Mexico" +date: 2021-12-23 --- A bunch of white people pretend to be Aztecs, and explore their moral @@ -42,7 +39,7 @@ Barbara abusing her god status to outlaw human sacrifice. The Doctor gets married or engaged or something. Susan finally expresses an emotion other than terror: -{{< video "susan-well-hello-there.mp4" "Well, hello there." >}} +{{< video src="susan-well-hello-there.mp4" text="Well, hello there." >}} I'm back to being uncomfortable with the cultural framing here. I mean, maybe this is useful for viewing 1960s British culture, but that's still diff --git a/content/blog/2022-09-06-truck-bling/index.md b/content/blog/2022-09-06-truck-bling/index.md index 701f5cf..38ab566 100644 --- a/content/blog/2022-09-06-truck-bling/index.md +++ b/content/blog/2022-09-06-truck-bling/index.md @@ -1,7 +1,6 @@ --- -date: "2022-09-06T12:11:00-0600" title: Truck bling -url: blog/2022-09-06-truck-bling/ +date: "2022-09-06T12:11:00-0600" --- Yesterday, @@ -18,7 +17,7 @@ the friend soldered a bunch of stuff together, and we plugged it in. It friggin' worked! -{{< video "truck-bling.m4v" "Pickup truck with color-changing ground effects">}} +{{< video src="truck-bling.m4v" text="Pickup truck with color-changing ground effects">}} The really cool part, at least for me, is that now she can hang out with her laptop in the cabin, diff --git a/content/blog/2022-10-09-CLRG-Scoring/awardPoints.mjs b/content/blog/2022-10-09-CLRG-Scoring/awardPoints.mjs new file mode 100644 index 0000000..40a3372 --- /dev/null +++ b/content/blog/2022-10-09-CLRG-Scoring/awardPoints.mjs @@ -0,0 +1,122 @@ +let awardPoints = [ + 100, // 1 + 75, // 2 + 65, // 3 + 60, // 4 + 56, // 5 + 53, // 6 + 50, // 7 + 47, // 8 + 45, // 9 + 43, // 10 + 41, // 11 + 39, // 12 + 38, // 13 + 37, // 14 + 36, // 15 + 35, // 16 + 34, // 17 + 33, // 18 + 32, // 19 + 31, // 20 + 30, // 21 + 29, // 22 + 28, // 23 + 27, // 24 + 26, // 25 + 25, // 26 + 24, // 27 + 23, // 28 + 22, // 29 + 21, // 30 + 20, // 31 + 19, // 32 + 18, // 33 + 17, // 34 + 16, // 35 + 15, // 36 + 14, // 37 + 13, // 38 + 12, // 39 + 11, // 40 + 10, // 41 + 9, // 42 + 8, // 43 + 7, // 44 + 6, // 45 + 5, // 46 + 4, // 47 + 3, // 48 + 2, // 49 + 1, // 50 + 0.75, // 51 + 0.65, // 52 + 0.60, // 53 + 0.56, // 54 + 0.53, // 55 + 0.50, // 56 + 0.47, // 57 + 0.45, // 58 + 0.43, // 59 + 0.41, // 60 + 0.39, // 61 + 0.38, // 62 + 0.37, // 63 + 0.36, // 64 + 0.35, // 65 + 0.34, // 66 + 0.33, // 67 + 0.32, // 68 + 0.31, // 69 + 0.30, // 70 + 0.29, // 71 + 0.28, // 72 + 0.27, // 73 + 0.26, // 74 + 0.25, // 75 + 0.24, // 76 + 0.23, // 77 + 0.22, // 78 + 0.21, // 79 + 0.20, // 80 + 0.19, // 81 + 0.18, // 82 + 0.17, // 83 + 0.16, // 84 + 0.15, // 85 + 0.14, // 86 + 0.13, // 87 + 0.12, // 88 + 0.11, // 89 + 0.10, // 90 + 0.09, // 91 + 0.08, // 92 + 0.07, // 93 + 0.06, // 94 + 0.05, // 95 + 0.04, // 96 + 0.03, // 97 + 0.02, // 98 + 0.01, // 99 + 0.00, // 100 +] + +function init() { + for (let tbody of document.querySelectorAll(".awardPoints tbody")) { + for (let i = 0; i < awardPoints.length; i++) { + let tr = tbody.appendChild(document.createElement("tr")) + tr.appendChild(document.createElement("td")).textContent = i + 1 + tr.appendChild(document.createElement("td")).textContent = awardPoints[i].toFixed(2) + } + } +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init) +} else { + init() +} + +export { + awardPoints, +} diff --git a/content/blog/2022-10-09-CLRG-Scoring/chart.png b/content/blog/2022-10-09-CLRG-Scoring/chart.png new file mode 100644 index 0000000000000000000000000000000000000000..c7e2714cbdea1fa56c512ad0839abfbb78d5aeb9 GIT binary patch literal 13438 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 P@tiU+{xkcy!;Sw3i3??} literal 0 HcmV?d00001 diff --git a/content/blog/2022-10-09-CLRG-Scoring/index.md b/content/blog/2022-10-09-CLRG-Scoring/index.md new file mode 100644 index 0000000..9ede31e --- /dev/null +++ b/content/blog/2022-10-09-CLRG-Scoring/index.md @@ -0,0 +1,175 @@ +--- +title: CLRG Scoring Analyzed +date: 2022-10-09 +stylesheets: + - toys.css +scripts: + - speculator.mjs + - scorecard.mjs +--- + +Let's take a look how how CLRG does its scoring! +*With math!* + +I'm going to only view this mathematically. +I know there's a lot of other stuff that goes on, like line-ups. +I'm only going to look at this with numbers. + + +## How CLRG Scoring Works + +As I am given to understand, the scoring works like so: + +1. Adjudicators give you a "raw score": a real number between 0 and 100 +2. The scoring system ranks each dancer per adjudicator, based on raw scores +3. These rankings are mapped into "award points" +4. All of a dancer's award points are summed +5. Final ranking is determined by comparing total award points + +## Raw Scoring + +The way raw scores translate into rankings and award points is a little +confusing, so I've made a little tool you can play with to get a feel for how it +works. Essentially, it's a way of normalizing places to an adjudicator: score +weights are only relative to the judge that assigns them. + +Adjudicator A can assign scores between 80 and 100; +adjudicator B can assign scores between 1 and 40; +and they'll both have a first, second, third, fourth place, etc. +These places then get translated into award points. + + +## Award Points + +Award points are handed out based on ranking against other dancers for that +adjudicator. I obtained these values from a FeisWorx results page for my kid: + +
+ + + + + + + +
RankingAward Points
+
+ +If there's a 2-way, 3-way, or n-way tie, +all tied dancers get the average of the next 2, 3, or n award points, +and the next 2, 3, or n rankings are skipped. + +### Award Points artifacts + +One quirk of awards points is that for any given overall +score, there are only a handful of possible judge rankings that could have led +to it. That means you can make some guesses about how each judge ranked an +individual dancer, based on only their total award points. + +Here's a handy calculator! +It (currently) doesn't consider the possibility of a tie. + +
+
+ CLRG Award Points Speculator +
+ Points: + +
+ + + + + + +
Possible Rankings
Computing: this could take a while!
+
+
+ + +## What's with these values? + +At first glance, the award points look like the output of an exponential function. + +{{
}} + +In an effort to figure out where these numbers came from, +I ran some curve fitting against the data. +Here's the best I could come up with: + +| Ranking range | Award Points Function | Type of function | +| --: | --: | --- | +| 1 - 11 | 100 * x^-0.358 | Exponential | +| 12 - 50 | 51 - x | Linear | +| 51 - 60 | 14.2 - 0.46x + 0.00385x | Polynomial | +| 61 - 100 | 1 - x/100 | Linear | + +If you, dear reader, are a mathematician, +I would love to hear your thoughts on why they went with this algorithm. + +There are a few points to note here: + +* 1st place is a *huge deal*. Disproportionately huge. +* Places 2-10 are similarly big deals compared to places 3-11. +* Places 12-50 operate the way most people probably assume ranking works: linearly. +* Places 51-60 are a second degree polynomial, but it doesn't matter much for so few points. +* Places 61-100 are all less than 1 point. If you're a judge trying to tank a top dancer, anywhere in this range is equivalent to anywhere else. + + +## Consequences of Exponential Award Points + +Playing around with this, +I've found a few interesting consequences +of the exponential growth in the top 11 places. + + +### 1st place is super important + +1st place is weighted so heavily that it's almost impossible to overcome without +your own 1st. + +Take for example this scenario, +in which Adjudicator 1 has promised to give 1st place to Alice: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AliceBob
Adj. 1
Adj. 2
Adj. 3
Award Points
Ranking
+ + + + diff --git a/content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs b/content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs new file mode 100644 index 0000000..0603bbd --- /dev/null +++ b/content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs @@ -0,0 +1,54 @@ +import {awardPoints} from "./awardPoints.mjs" + +function scorecardUpdate(scorecard) { + let scores = [] + let points = [] + + let firstRow = scorecard.querySelector("tbody tr") + for (let input of firstRow.querySelectorAll("input")) { + scores.push(0) + points.push(0) + } + + for (let row of scorecard.querySelectorAll("tbody tr")) { + let i = 0 + for (let input of row.querySelectorAll("input")) { + let ranking = Number(input.value) + scores[i] += ranking + points[i] += awardPoints[ranking] + i += 1 + } + } + + { + let i = 0 + for (let out of scorecard.querySelectorAll("tfoot output[name='points']")) { + out.value = points[i] + i += 1 + } + } + + { + let i = 0 + for (let out of scorecard.querySelectorAll("tfoot output[name='ranking']")) { + out.value = scores[i] + i += 1 + } + } +} + +function init() { + for (let scorecard of document.querySelectorAll(".scorecard")) { + for (let input of scorecard.querySelectorAll("input")) { + input.addEventListener("input", () => scorecardUpdate(scorecard)) + } + scorecardUpdate(scorecard) + } +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init) +} else { + init() +} + \ No newline at end of file diff --git a/content/blog/2022-10-09-CLRG-Scoring/speculator.mjs b/content/blog/2022-10-09-CLRG-Scoring/speculator.mjs new file mode 100644 index 0000000..e9eaca1 --- /dev/null +++ b/content/blog/2022-10-09-CLRG-Scoring/speculator.mjs @@ -0,0 +1,104 @@ +import { awardPoints } from "./awardPoints.mjs" + +function arraysEqual(a, b) { + if (a === b) return true; + if (a == null || b == null) return false; + if (a.length != b.length) return false; + + for (let i = 0; i < a.length; ++i) { + if (a[i] !== b[i]) return false; + } + return true; +} +function awardPossibilities(total=0, depth=1) { + if (depth == 1) { + if (awardPoints.includes(total)) { + return [[total]] + } else { + return [] + } + } + + let possibilities = [] + for (let p of awardPoints) { + if (p <= total) { + for (let subPossibility of awardPossibilities(total - p, depth - 1)) { + let v = [p].concat(subPossibility) + v.sort((a,b) => b-a) + possibilities.push(v) + } + } + } + + possibilities.sort((a,b) => b.reduce((a,b) => a*100+b) - a.reduce((a,b) => a*100+b)) + let uniquePossibilities = [] + for (let p of possibilities) { + if (uniquePossibilities.length == 0 || !arraysEqual(p, uniquePossibilities[uniquePossibilities.length - 1])) { + uniquePossibilities.push(p) + } + } + return uniquePossibilities +} + +function speculate(calc) { + let points = calc.querySelector("[name=points]").value || 0 + let adjudicators = calc.querySelector("[name=adjudicators]").value || 3 + let results = calc.querySelector(".results tbody") + while (results.firstChild) { + results.removeChild(results.firstChild) + } + + for (let warning of calc.querySelectorAll(".warning")) { + if (adjudicators >3) { + warning.classList.add("visible") + setTimeout(() => asyncSpeculate(calc, points, adjudicators), 0) + } else { + warning.classList.remove("visible") + asyncSpeculate(calc, points, adjudicators) + } + } + +} + +async function asyncSpeculate(calc, points, adjudicators) { + let results = calc.querySelector(".results tbody") + let possibilites = awardPossibilities(points, adjudicators) + + if (possibilites.length == 0) { + let row = results.appendChild(document.createElement("tr")) + let cell = row.appendChild(document.createElement("th")) + cell.textContent = "No possible combinations" + } else { + let row = results.appendChild(document.createElement("tr")) + for (let i = 1; i <= adjudicators; ++i) { + let cell = row.appendChild(document.createElement("th")) + cell.textContent = "Adj. " + i + } + for (let possibility of possibilites) { + let row = results.appendChild(document.createElement("tr")) + for (let p of possibility) { + let cell = row.appendChild(document.createElement("td")) + cell.textContent = p + } + } + } + for (let warning of calc.querySelectorAll(".warning")) { + warning.classList.remove("visible") + } +} + +function init() { + for (let calc of document.querySelectorAll(".speculator")) { + for (let input of calc.querySelectorAll("input")) { + input.addEventListener("input", () => speculate(calc)) + } + speculate(calc) + } +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init) +} else { + init() +} + \ No newline at end of file diff --git a/content/blog/2022-10-09-CLRG-Scoring/toys.css b/content/blog/2022-10-09-CLRG-Scoring/toys.css new file mode 100644 index 0000000..8f7d521 --- /dev/null +++ b/content/blog/2022-10-09-CLRG-Scoring/toys.css @@ -0,0 +1,36 @@ +.winner { + color:cornsilk; +} + +figure img { + max-width: 100%; +} + +.warning { + color: #e64; + display: none; +} +.warning.visible { + display: initial; +} + +.awardPoints { + display: inline-block; + max-height: 60vh; + overflow-y: auto; + margin: 1em; +} +.awardPoints table { + margin: initial; +} +.awardPoints thead { + position: sticky; + top: 0; +} +.awardPoints tbody { + max-height: 60vh; + overflow-y: auto; +} +.awardPoints td { + text-align: right; +} \ No newline at end of file diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 196ff59..2b60966 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -5,13 +5,20 @@ - + {{range .AlternativeOutputFormats}} {{end}} + {{range .Params.stylesheets}} + + {{end}} {{range .Params.scripts}} - + {{end}} + {{range .Params.scripts}} + {{end}} {{range .Params.headers}} {{. | safeHTML}} diff --git a/layouts/shortcodes/figure.html b/layouts/shortcodes/figure.html new file mode 100644 index 0000000..1105d69 --- /dev/null +++ b/layouts/shortcodes/figure.html @@ -0,0 +1,4 @@ +
+ {{- $img := $.Page.Resources.GetMatch (.Get "src")}} + {{.Get +
\ No newline at end of file diff --git a/layouts/shortcodes/video.html b/layouts/shortcodes/video.html index 8d10820..551cfe5 100644 --- a/layouts/shortcodes/video.html +++ b/layouts/shortcodes/video.html @@ -1,4 +1,4 @@ diff --git a/run.sh b/run.sh index 5d78714..2e16e09 100755 --- a/run.sh +++ b/run.sh @@ -4,6 +4,9 @@ case "$(hostname)" in sweetums) baseURL=http://sweetums.lan:1313/ ;; + penguin) + baseURL=http://penguin.linux.test:1313/ + ;; *) baseURL=http://$(hostname --fqdn):1313/ ;; diff --git a/static/assets/css/default.css b/static/assets/css/default.css index 7f370d7..a889c96 100644 --- a/static/assets/css/default.css +++ b/static/assets/css/default.css @@ -19,12 +19,12 @@ body { } input { - font-family: "Lato", "Roboto", sans-serif; - font-size: 13pt; - border: 0; - outline: 0; - background: transparent; - border-bottom: 1px solid black; + font-family: inherit; + font-size: inherit; + border: thin solid #ccc; +} +input:read-only { + background-color: #eee; } .title, td.main { @@ -146,6 +146,36 @@ button.big { background: inherit; } +legend { + background-color: #e0e4cc; +} + +table { + margin: 1em; + border-collapse: collapse; +} +thead, tfoot { + color: #e64; + background-color: rgba(224, 228, 204, 0.8); +} +tbody tr:nth-of-type(even) { + background-color: rgba(238, 102, 68, 0.05); +} + +td, th { + padding: 0.2em 0.5em; +} +caption { + caption-side: bottom; + font-size: small; +} +.justify-right, input[type="number"] { + text-align: right; +} +.justify-left { + text-align: left; +} + .tags { font-size: small; } From 7e2c37cce4355af67e71668c3e328d97f7433501 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 9 Oct 2022 21:42:30 -0600 Subject: [PATCH 02/17] Okay, that's enough for today. --- content/blog/2022-10-09-CLRG-Scoring/index.md | 73 ++++++++++++++++--- .../2022-10-09-CLRG-Scoring/scorecard.mjs | 3 +- content/blog/2022-10-09-CLRG-Scoring/toys.css | 8 -- static/assets/css/default.css | 7 +- 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/content/blog/2022-10-09-CLRG-Scoring/index.md b/content/blog/2022-10-09-CLRG-Scoring/index.md index 9ede31e..d45e3f7 100644 --- a/content/blog/2022-10-09-CLRG-Scoring/index.md +++ b/content/blog/2022-10-09-CLRG-Scoring/index.md @@ -125,11 +125,7 @@ of the exponential growth in the top 11 places. ### 1st place is super important -1st place is weighted so heavily that it's almost impossible to overcome without -your own 1st. - -Take for example this scenario, -in which Adjudicator 1 has promised to give 1st place to Alice: +1st place is weighted so heavily that one judge could move a 5th place dancer into 2nd. @@ -137,23 +133,27 @@ in which Adjudicator 1 has promised to give 1st place to Alice: + - - + + + - - + + + - - + + + @@ -161,15 +161,66 @@ in which Adjudicator 1 has promised to give 1st place to Alice: + +
Alice BobCarol
Adj. 1
Adj. 2
Adj. 3
Award Points
Ranking
+You can adjust these values to get a better feel for how scoring works. +### Tanking a high-ranked dancer is another way to cheat +Because of that exponential curve, +a low ranking from a single judge can carry a lot of weight. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AliceBobCarol
Adj. 1
Adj. 2
Adj. 3
Award Points
Ranking
diff --git a/content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs b/content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs index 0603bbd..8a75ef8 100644 --- a/content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs +++ b/content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs @@ -29,9 +29,10 @@ function scorecardUpdate(scorecard) { } { + let rankedPoints = [...points].sort((a, b) => b - a) let i = 0 for (let out of scorecard.querySelectorAll("tfoot output[name='ranking']")) { - out.value = scores[i] + out.value = rankedPoints.indexOf(points[i]) + 1 i += 1 } } diff --git a/content/blog/2022-10-09-CLRG-Scoring/toys.css b/content/blog/2022-10-09-CLRG-Scoring/toys.css index 8f7d521..f52481e 100644 --- a/content/blog/2022-10-09-CLRG-Scoring/toys.css +++ b/content/blog/2022-10-09-CLRG-Scoring/toys.css @@ -1,11 +1,3 @@ -.winner { - color:cornsilk; -} - -figure img { - max-width: 100%; -} - .warning { color: #e64; display: none; diff --git a/static/assets/css/default.css b/static/assets/css/default.css index a889c96..4d84d1d 100644 --- a/static/assets/css/default.css +++ b/static/assets/css/default.css @@ -24,6 +24,7 @@ input { border: thin solid #ccc; } input:read-only { + color: #444; background-color: #eee; } @@ -151,7 +152,7 @@ legend { } table { - margin: 1em; + margin: 1em 0; border-collapse: collapse; } thead, tfoot { @@ -189,6 +190,10 @@ caption { img, video { max-width: 60%; } + + figure img { + max-width: 100%; + } } @media (prefers-color-scheme: dark) { html { From 1eea1a3447461a1ab0887d6f2aad944ff4ba9455 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 9 Oct 2022 21:44:24 -0600 Subject: [PATCH 03/17] Remove dumb disclaimer --- content/blog/2022-10-09-CLRG-Scoring/index.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/content/blog/2022-10-09-CLRG-Scoring/index.md b/content/blog/2022-10-09-CLRG-Scoring/index.md index d45e3f7..44ca021 100644 --- a/content/blog/2022-10-09-CLRG-Scoring/index.md +++ b/content/blog/2022-10-09-CLRG-Scoring/index.md @@ -11,10 +11,6 @@ scripts: Let's take a look how how CLRG does its scoring! *With math!* -I'm going to only view this mathematically. -I know there's a lot of other stuff that goes on, like line-ups. -I'm only going to look at this with numbers. - ## How CLRG Scoring Works From ac4f025633852051e8710341610cc66eba2a79dc Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 9 Oct 2022 21:47:37 -0600 Subject: [PATCH 04/17] Comment on weird section --- content/blog/2022-10-09-CLRG-Scoring/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2022-10-09-CLRG-Scoring/index.md b/content/blog/2022-10-09-CLRG-Scoring/index.md index 44ca021..9a4b988 100644 --- a/content/blog/2022-10-09-CLRG-Scoring/index.md +++ b/content/blog/2022-10-09-CLRG-Scoring/index.md @@ -108,7 +108,7 @@ There are a few points to note here: * 1st place is a *huge deal*. Disproportionately huge. * Places 2-10 are similarly big deals compared to places 3-11. * Places 12-50 operate the way most people probably assume ranking works: linearly. -* Places 51-60 are a second degree polynomial, but it doesn't matter much for so few points. +* Places 51-60 fit best to a second degree polynomial, but it doesn't matter much for differences of hundreths of a point. This section is *really weird*, mathematically. * Places 61-100 are all less than 1 point. If you're a judge trying to tank a top dancer, anywhere in this range is equivalent to anywhere else. From 29eda9672477bea5682854ab70a31cf242ea2c60 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 9 Oct 2022 22:34:44 -0600 Subject: [PATCH 05/17] Adjust ranking offsets --- content/blog/2022-10-09-CLRG-Scoring/index.md | 65 +++++++++++++++++++ .../2022-10-09-CLRG-Scoring/scorecard.mjs | 13 +++- content/blog/2022-10-09-CLRG-Scoring/toys.css | 5 ++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/content/blog/2022-10-09-CLRG-Scoring/index.md b/content/blog/2022-10-09-CLRG-Scoring/index.md index 9a4b988..62e829c 100644 --- a/content/blog/2022-10-09-CLRG-Scoring/index.md +++ b/content/blog/2022-10-09-CLRG-Scoring/index.md @@ -220,3 +220,68 @@ a low ranking from a single judge can carry a lot of weight. + + +### Being in 1st provides a nice buffer + +Try playing around with Alice's rankings with Adjudicators 2 and 3 here. +She has to get ranked a lot lower before her overall ranking starts going down. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AliceBobCarolDaveErin
Adj. 1
Adj. 2
Adj. 3
Award Points
Ranking
+
\ No newline at end of file diff --git a/content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs b/content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs index 8a75ef8..cdacf31 100644 --- a/content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs +++ b/content/blog/2022-10-09-CLRG-Scoring/scorecard.mjs @@ -3,6 +3,7 @@ import {awardPoints} from "./awardPoints.mjs" function scorecardUpdate(scorecard) { let scores = [] let points = [] + let highestRank = [] let firstRow = scorecard.querySelector("tbody tr") for (let input of firstRow.querySelectorAll("input")) { @@ -16,6 +17,7 @@ function scorecardUpdate(scorecard) { let ranking = Number(input.value) scores[i] += ranking points[i] += awardPoints[ranking] + highestRank[i] = Math.min(highestRank[i] || 100, ranking) i += 1 } } @@ -29,10 +31,19 @@ function scorecardUpdate(scorecard) { } { + let rankOffset = 0 + let overallRanking = [] let rankedPoints = [...points].sort((a, b) => b - a) + for (let i = 0; i < points.length; i++) { + overallRanking[i] = rankedPoints.indexOf(points[i]) + 1 + if (overallRanking[i] == 1) { + rankOffset = highestRank[i] + } + } + let i = 0 for (let out of scorecard.querySelectorAll("tfoot output[name='ranking']")) { - out.value = rankedPoints.indexOf(points[i]) + 1 + out.value = rankedPoints.indexOf(points[i]) + rankOffset i += 1 } } diff --git a/content/blog/2022-10-09-CLRG-Scoring/toys.css b/content/blog/2022-10-09-CLRG-Scoring/toys.css index f52481e..7cf3861 100644 --- a/content/blog/2022-10-09-CLRG-Scoring/toys.css +++ b/content/blog/2022-10-09-CLRG-Scoring/toys.css @@ -6,6 +6,11 @@ display: initial; } +.scrolly { + max-width: 100%; + overflow-x: auto; +} + .awardPoints { display: inline-block; max-height: 60vh; From 86f467aebbe3b8b4ccbd57e693b2d41b22650b3a Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 9 Oct 2022 22:49:58 -0600 Subject: [PATCH 06/17] can I sleep yet --- content/blog/2022-10-09-CLRG-Scoring/index.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/content/blog/2022-10-09-CLRG-Scoring/index.md b/content/blog/2022-10-09-CLRG-Scoring/index.md index 62e829c..965b495 100644 --- a/content/blog/2022-10-09-CLRG-Scoring/index.md +++ b/content/blog/2022-10-09-CLRG-Scoring/index.md @@ -143,7 +143,7 @@ of the exponential growth in the top 11 places. Adj. 2 - + Adj. 3 @@ -258,11 +258,11 @@ She has to get ranked a lot lower before her overall ranking starts going down. Adj. 3 - + - + From d9709b8f8a0c2a359289b8c3ffbd7632be566dd7 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Sun, 9 Oct 2022 22:54:56 -0600 Subject: [PATCH 07/17] now? --- content/blog/2022-10-09-CLRG-Scoring/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/blog/2022-10-09-CLRG-Scoring/index.md b/content/blog/2022-10-09-CLRG-Scoring/index.md index 965b495..8a57818 100644 --- a/content/blog/2022-10-09-CLRG-Scoring/index.md +++ b/content/blog/2022-10-09-CLRG-Scoring/index.md @@ -202,7 +202,7 @@ a low ranking from a single judge can carry a lot of weight. Adj. 3 - + From 7b6408a9c63a35a5bf25f9d2965398384dee423b Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Mon, 10 Oct 2022 07:21:53 -0600 Subject: [PATCH 08/17] Split yesterday's post into today's --- content/blog/2022-10-04-CLRG-cheating.md | 1 + .../blog/2022-10-09-CLRG-Scoring/chart.png | Bin 13438 -> 10490 bytes content/blog/2022-10-09-CLRG-Scoring/index.md | 30 +---- .../awardPoints.mjs | 122 ++++++++++++++++++ .../index.md | 35 +++++ .../speculator.mjs | 0 .../toys.css | 33 +++++ 7 files changed, 193 insertions(+), 28 deletions(-) create mode 100644 content/blog/2022-10-10-CLRG-Scoring-Artifacts/awardPoints.mjs create mode 100644 content/blog/2022-10-10-CLRG-Scoring-Artifacts/index.md rename content/blog/{2022-10-09-CLRG-Scoring => 2022-10-10-CLRG-Scoring-Artifacts}/speculator.mjs (100%) create mode 100644 content/blog/2022-10-10-CLRG-Scoring-Artifacts/toys.css diff --git a/content/blog/2022-10-04-CLRG-cheating.md b/content/blog/2022-10-04-CLRG-cheating.md index c72b9a2..dfbfa32 100644 --- a/content/blog/2022-10-04-CLRG-cheating.md +++ b/content/blog/2022-10-04-CLRG-cheating.md @@ -1,6 +1,7 @@ --- title: CLRG's Cheating Scandal date: 2022-10-04 +tags: clrg --- $SPOUSE just stumbled across a PowerPoint file with a bunch of text messages, diff --git a/content/blog/2022-10-09-CLRG-Scoring/chart.png b/content/blog/2022-10-09-CLRG-Scoring/chart.png index c7e2714cbdea1fa56c512ad0839abfbb78d5aeb9..df2e4ef1cdd15a0c6f4794a51ebf6b7a060c52f4 100644 GIT binary patch literal 10490 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 literal 13438 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 P@tiU+{xkcy!;Sw3i3??} diff --git a/content/blog/2022-10-09-CLRG-Scoring/index.md b/content/blog/2022-10-09-CLRG-Scoring/index.md index 8a57818..4e3ebc8 100644 --- a/content/blog/2022-10-09-CLRG-Scoring/index.md +++ b/content/blog/2022-10-09-CLRG-Scoring/index.md @@ -1,10 +1,11 @@ --- title: CLRG Scoring Analyzed date: 2022-10-09 +tags: + - clrg stylesheets: - toys.css scripts: - - speculator.mjs - scorecard.mjs --- @@ -55,33 +56,6 @@ If there's a 2-way, 3-way, or n-way tie, all tied dancers get the average of the next 2, 3, or n award points, and the next 2, 3, or n rankings are skipped. -### Award Points artifacts - -One quirk of awards points is that for any given overall -score, there are only a handful of possible judge rankings that could have led -to it. That means you can make some guesses about how each judge ranked an -individual dancer, based on only their total award points. - -Here's a handy calculator! -It (currently) doesn't consider the possibility of a tie. - -
-
- CLRG Award Points Speculator -
- Points: - -
- - - - - - -
Possible Rankings
Computing: this could take a while!
-
-
- ## What's with these values? diff --git a/content/blog/2022-10-10-CLRG-Scoring-Artifacts/awardPoints.mjs b/content/blog/2022-10-10-CLRG-Scoring-Artifacts/awardPoints.mjs new file mode 100644 index 0000000..40a3372 --- /dev/null +++ b/content/blog/2022-10-10-CLRG-Scoring-Artifacts/awardPoints.mjs @@ -0,0 +1,122 @@ +let awardPoints = [ + 100, // 1 + 75, // 2 + 65, // 3 + 60, // 4 + 56, // 5 + 53, // 6 + 50, // 7 + 47, // 8 + 45, // 9 + 43, // 10 + 41, // 11 + 39, // 12 + 38, // 13 + 37, // 14 + 36, // 15 + 35, // 16 + 34, // 17 + 33, // 18 + 32, // 19 + 31, // 20 + 30, // 21 + 29, // 22 + 28, // 23 + 27, // 24 + 26, // 25 + 25, // 26 + 24, // 27 + 23, // 28 + 22, // 29 + 21, // 30 + 20, // 31 + 19, // 32 + 18, // 33 + 17, // 34 + 16, // 35 + 15, // 36 + 14, // 37 + 13, // 38 + 12, // 39 + 11, // 40 + 10, // 41 + 9, // 42 + 8, // 43 + 7, // 44 + 6, // 45 + 5, // 46 + 4, // 47 + 3, // 48 + 2, // 49 + 1, // 50 + 0.75, // 51 + 0.65, // 52 + 0.60, // 53 + 0.56, // 54 + 0.53, // 55 + 0.50, // 56 + 0.47, // 57 + 0.45, // 58 + 0.43, // 59 + 0.41, // 60 + 0.39, // 61 + 0.38, // 62 + 0.37, // 63 + 0.36, // 64 + 0.35, // 65 + 0.34, // 66 + 0.33, // 67 + 0.32, // 68 + 0.31, // 69 + 0.30, // 70 + 0.29, // 71 + 0.28, // 72 + 0.27, // 73 + 0.26, // 74 + 0.25, // 75 + 0.24, // 76 + 0.23, // 77 + 0.22, // 78 + 0.21, // 79 + 0.20, // 80 + 0.19, // 81 + 0.18, // 82 + 0.17, // 83 + 0.16, // 84 + 0.15, // 85 + 0.14, // 86 + 0.13, // 87 + 0.12, // 88 + 0.11, // 89 + 0.10, // 90 + 0.09, // 91 + 0.08, // 92 + 0.07, // 93 + 0.06, // 94 + 0.05, // 95 + 0.04, // 96 + 0.03, // 97 + 0.02, // 98 + 0.01, // 99 + 0.00, // 100 +] + +function init() { + for (let tbody of document.querySelectorAll(".awardPoints tbody")) { + for (let i = 0; i < awardPoints.length; i++) { + let tr = tbody.appendChild(document.createElement("tr")) + tr.appendChild(document.createElement("td")).textContent = i + 1 + tr.appendChild(document.createElement("td")).textContent = awardPoints[i].toFixed(2) + } + } +} + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init) +} else { + init() +} + +export { + awardPoints, +} diff --git a/content/blog/2022-10-10-CLRG-Scoring-Artifacts/index.md b/content/blog/2022-10-10-CLRG-Scoring-Artifacts/index.md new file mode 100644 index 0000000..a6c1354 --- /dev/null +++ b/content/blog/2022-10-10-CLRG-Scoring-Artifacts/index.md @@ -0,0 +1,35 @@ +--- +title: CLRG Award Points Artifacts +date: 2022-10-09 +tags: + - clrg +stylesheets: + - toys.css +scripts: + - speculator.mjs +--- + +One quirk of awards points is that for any given overall +score, there are only a handful of possible judge rankings that could have led +to it. That means you can make some guesses about how each judge ranked an +individual dancer, based on only their total award points. + +Here's a handy calculator! +It (currently) doesn't consider the possibility of a tie. + +
+
+ CLRG Award Points Speculator +
+ Points: + +
+ + + + + + +
Possible Rankings
Computing: this could take a while!
+
+
diff --git a/content/blog/2022-10-09-CLRG-Scoring/speculator.mjs b/content/blog/2022-10-10-CLRG-Scoring-Artifacts/speculator.mjs similarity index 100% rename from content/blog/2022-10-09-CLRG-Scoring/speculator.mjs rename to content/blog/2022-10-10-CLRG-Scoring-Artifacts/speculator.mjs diff --git a/content/blog/2022-10-10-CLRG-Scoring-Artifacts/toys.css b/content/blog/2022-10-10-CLRG-Scoring-Artifacts/toys.css new file mode 100644 index 0000000..7cf3861 --- /dev/null +++ b/content/blog/2022-10-10-CLRG-Scoring-Artifacts/toys.css @@ -0,0 +1,33 @@ +.warning { + color: #e64; + display: none; +} +.warning.visible { + display: initial; +} + +.scrolly { + max-width: 100%; + overflow-x: auto; +} + +.awardPoints { + display: inline-block; + max-height: 60vh; + overflow-y: auto; + margin: 1em; +} +.awardPoints table { + margin: initial; +} +.awardPoints thead { + position: sticky; + top: 0; +} +.awardPoints tbody { + max-height: 60vh; + overflow-y: auto; +} +.awardPoints td { + text-align: right; +} \ No newline at end of file From 924c4732719710efd2e34b28b47ef5273ff7165e Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Mon, 10 Oct 2022 13:19:19 -0600 Subject: [PATCH 09/17] Working parser for one dataset --- .../_index.md | 7 + .../results.mjs | 178 ++++++++++++++++++ .../2022-10-10-CLRG-Results-Analysis/wat.html | 9 + .../index.md | 2 +- layouts/_default/baseof.html | 8 +- run.sh | 2 + 6 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 content/blog/2022-10-10-CLRG-Results-Analysis/_index.md create mode 100644 content/blog/2022-10-10-CLRG-Results-Analysis/results.mjs create mode 100644 content/blog/2022-10-10-CLRG-Results-Analysis/wat.html diff --git a/content/blog/2022-10-10-CLRG-Results-Analysis/_index.md b/content/blog/2022-10-10-CLRG-Results-Analysis/_index.md new file mode 100644 index 0000000..de873cf --- /dev/null +++ b/content/blog/2022-10-10-CLRG-Results-Analysis/_index.md @@ -0,0 +1,7 @@ +--- +title: CLRG Results Analysis +date: 2022-10-10T10:13:00-06:00 +draft: true +--- + +Hello there. diff --git a/content/blog/2022-10-10-CLRG-Results-Analysis/results.mjs b/content/blog/2022-10-10-CLRG-Results-Analysis/results.mjs new file mode 100644 index 0000000..9ccf4e7 --- /dev/null +++ b/content/blog/2022-10-10-CLRG-Results-Analysis/results.mjs @@ -0,0 +1,178 @@ +class Results { + /** + * + * @param {string} url URL to load + */ + constructor(url) { + if (url) { + this.loadData(url) + } + } + async loadData(url) { + let resp = await fetch(url) + let contentType = resp.headers.get("Content-Type") + if (! contentType.includes("/xml")) { + console.error(`Cannot load data with content-type ${contentType}`) + return + } + let text = await resp.text() + this.doc = new DOMParser().parseFromString(text, "text/xml") + this.rawData = this.parseXMLDocument(this.doc) + this.data = this.parseRawData(this.rawData) + } + + parseXMLDocument(doc) { + let table = doc.querySelector("Table") + let rawData = [] + + for (let dataRow of table.children) { + if (! ["tr"].includes(dataRow.tagName.toLowerCase())) { + console.warn(`Warning: unexpected XML tag ${dataRow.tagName}, expecting tr`) + continue + } + + let row = [] + for (let dataCell of dataRow.children) { + if (! ["th", "td"].includes(dataCell.tagName.toLowerCase())) { + console.warn(`Warning: unexpected XML tag ${dataRow.tagName}, expecting th/td`) + continue + } + row.push(dataCell.textContent) + } + + rawData.push(row) + } + return rawData + } + + parseRawData(rawData) { + let cellA1 = rawData[0][0].trim().toLowerCase() + switch (cellA1) { + case "place awd pts": + return this.parseFeisWorx2017(rawData) + break + } + console.error("Cell A1 doesn't resemble anything I can cope with", rawData[0]) + } + + /** + * + * @param {Array.>} rawData + */ + parseFeisWorx2017(rawData) { + let adjudicators = [] + let data = [] + for (let rowIndex = 0; rowIndex < rawData.length; rowIndex++) { + let cells = rawData[rowIndex] + switch (rowIndex) { + case 0: // Column headers + break + case 1: // Adjudicators + adjudicators = cells + break + default: + if ((cells.length == 11) && (cells[0].trim().toLowerCase().startsWith("place"))) { + // Page heading + continue + } + + if (cells.length >= adjudicators.length) { + // Is this just a list of adjudicators again? + let lenDiff = cells.length - adjudicators.length + let same = true + for (let i = adjudicators.length-1; i >= 0; i--) { + if (adjudicators[i] != cells[i+lenDiff]) { + same = false + break + } + } + if (same) { + continue + } + } + + let row = {} + + { + let parts = cells[0].trim().split(/\s+/) + row.overallRank = Number(parts[0]) + row.overallPoints = Number(parts[1]) + } + + { + let parts = cells[1].trim().split(/ - /) + row.competitorNumber = Number(parts[0]) + row.competitorName = parts[1] + row.qualifier = parts[2] + } + + row.adjudication = {} // XXX: I don't like this name + for (let cellIndex = 2; cellIndex < cells.length; cellIndex++) { + let cell = cells[cellIndex] + let vote = {} + let parts = cell.trim().split(/ - ?|\s/) + + if ((parts.length == 5) && (parts[3] == "AP")) { + parts.splice(3, 0, "NaN") + } + + if ((parts.length == 7) && (parts[4] == "T")) { + vote.tie = true + parts.splice(4, 1) + } else { + vote.tie = false + } + + if (parts.length != 6) { + console.error(`Wrong number of fields in row ${rowIndex} cell ${cellIndex}:`, parts, cells) + } else { + for (let i = 0; i < parts.length; i += 2) { + let key = parts[i] + let val = Number(parts[i+1]) + switch (key) { + case "Raw": + vote.raw = val + break + case "Plc": + vote.placing = val + break + case "AP": + vote.points = val + break + default: + console.error(`Unknown key ${key} in row ${rowIndex} cell ${cellIndex}:`, cell) + break + } + } + + let adjudicator = adjudicators[cellIndex - 2] + row.adjudication[adjudicator] = vote + } + } + data.push(row) + break + } + } + return data + } +} + + +async function init() { + for (let div of document.querySelectorAll(".crlg-dataset")) { + let results = new Results() + await results.loadData(div.dataset.url) + console.log(results.data) + } +} + + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init) +} else { + init() +} + +export { + Results, +} \ No newline at end of file diff --git a/content/blog/2022-10-10-CLRG-Results-Analysis/wat.html b/content/blog/2022-10-10-CLRG-Results-Analysis/wat.html new file mode 100644 index 0000000..20b19b2 --- /dev/null +++ b/content/blog/2022-10-10-CLRG-Results-Analysis/wat.html @@ -0,0 +1,9 @@ +--- +title: CLRG Data Analyzer +scripts: + - results.mjs +--- + +wat? + +
\ No newline at end of file diff --git a/content/blog/2022-10-10-CLRG-Scoring-Artifacts/index.md b/content/blog/2022-10-10-CLRG-Scoring-Artifacts/index.md index a6c1354..52da72a 100644 --- a/content/blog/2022-10-10-CLRG-Scoring-Artifacts/index.md +++ b/content/blog/2022-10-10-CLRG-Scoring-Artifacts/index.md @@ -1,6 +1,6 @@ --- title: CLRG Award Points Artifacts -date: 2022-10-09 +date: 2022-10-10T08:00:00-06:00 tags: - clrg stylesheets: diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 2b60966..17fc81f 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -16,9 +16,13 @@ {{range .Params.scripts}} {{end}} {{range .Params.scripts}} - + > {{end}} {{range .Params.headers}} {{. | safeHTML}} diff --git a/run.sh b/run.sh index 2e16e09..20bd01f 100755 --- a/run.sh +++ b/run.sh @@ -18,4 +18,6 @@ docker run \ -u $(id -u):$(id -g) \ -p 1313:1313 \ klakegg/hugo:ext server \ + --buildFuture \ + --buildDrafts \ --baseURL "$baseURL" From 184a89edbeb215a0e0521780afe5f5745aff70c8 Mon Sep 17 00:00:00 2001 From: Neale Pickett Date: Mon, 10 Oct 2022 15:53:55 -0600 Subject: [PATCH 10/17] Polish up FeisWorx parser --- .../dataset.css | 7 + .../dataset.mjs | 328 ++++++++++++++++++ .../results.mjs | 178 ---------- .../2022-10-10-CLRG-Results-Analysis/wat.html | 7 +- layouts/_default/baseof.html | 8 +- 5 files changed, 346 insertions(+), 182 deletions(-) create mode 100644 content/blog/2022-10-10-CLRG-Results-Analysis/dataset.css create mode 100644 content/blog/2022-10-10-CLRG-Results-Analysis/dataset.mjs delete mode 100644 content/blog/2022-10-10-CLRG-Results-Analysis/results.mjs diff --git a/content/blog/2022-10-10-CLRG-Results-Analysis/dataset.css b/content/blog/2022-10-10-CLRG-Results-Analysis/dataset.css new file mode 100644 index 0000000..5fb846f --- /dev/null +++ b/content/blog/2022-10-10-CLRG-Results-Analysis/dataset.css @@ -0,0 +1,7 @@ +.clrg-dataset tbody *:nth-child(3n) { + border-right: thin solid black; +} + +.clrg-dataset tbody .new-round { + border-left: thick solid black; +} \ No newline at end of file diff --git a/content/blog/2022-10-10-CLRG-Results-Analysis/dataset.mjs b/content/blog/2022-10-10-CLRG-Results-Analysis/dataset.mjs new file mode 100644 index 0000000..a24974c --- /dev/null +++ b/content/blog/2022-10-10-CLRG-Results-Analysis/dataset.mjs @@ -0,0 +1,328 @@ +/** + * @typedef Result + * @type {object} + * @param {String} name Competitor's name + * @param {Number} number Competitor's bib number + * @param {String} school Competitor's school + * @param {Number} overallPoints Overall award points for this competitor + * @param {Number} overallRank Overall ranking for this competitor + * @param {String} qualifier Any qualifiers this ranking earned + * @param {Array.} rounds How this competitor was judged in each round + */ + +/** + * @typedef Round + * @type {Array.} + */ + +/** + * @typedef Adjudication + * @type {object} + * @param {String} adjudicator Adjudicator who recorded this score + * @param {Number} raw Raw score + * @param {Number} placing Placing relative to this adjudicator's other scores + * @param {Number} points Award points + */ + +/** + * Creates a new element and appends it to parent + * + * @param {Element} parent + * @param {String} type + * @returns {Element} + */ +function newElement(parent, type) { + return parent.appendChild(document.createElement(type)) +} + +class Dataset { + /** + * + * @param {String} url URL to load + */ + constructor(url) { + if (url) { + this.loadData(url) + } + } + async loadData(url) { + let resp = await fetch(url) + let contentType = resp.headers.get("Content-Type") + if (! contentType.includes("/xml")) { + console.error(`Cannot load data with content-type ${contentType}`) + return + } + let text = await resp.text() + this.doc = new DOMParser().parseFromString(text, "text/xml") + this.rawData = this.parseXMLDocument(this.doc) + this.results = this.parseRawData(this.rawData) + } + + parseXMLDocument(doc) { + let table = doc.querySelector("Table") + let rawData = [] + + for (let dataRow of table.children) { + if (! ["tr"].includes(dataRow.tagName.toLowerCase())) { + console.warn(`Warning: unexpected XML tag ${dataRow.tagName}, expecting tr`) + continue + } + + let row = [] + for (let dataCell of dataRow.children) { + if (! ["th", "td"].includes(dataCell.tagName.toLowerCase())) { + console.warn(`Warning: unexpected XML tag ${dataRow.tagName}, expecting th/td`) + continue + } + row.push(dataCell.textContent) + } + + rawData.push(row) + } + return rawData + } + + + /** + * @typedef ParsedData + * @type {object} + * @property {Array.} adjudicators List of adjudicators + * @property {Array.} results List of results + */ + + /** + * Parse raw data into a list of adjudicators and results + * + * @param {Array.>} rawData Raw data + * @returns {Array.} + */ + parseRawData(rawData) { + let cellA1 = rawData[0][0].trim().toLowerCase() + switch (cellA1) { + case "place awd pts": + return this.parseFeisWorx2017(rawData) + } + console.error("Cell A1 doesn't resemble anything I can cope with", rawData[0]) + } + + /** + * Parse FeisWorx 2017 data + * + * This is the output of Adobe Reader saving the PDF as XML. + * + * @param {Array.>} rawData Raw data + * @returns {Array.} + */ + parseFeisWorx2017(rawData) { + let adjudicators = [] + let results = [] + let numRounds = 0 + let adjudicatorsPerRound = 0 + + for (let rowIndex = 0; rowIndex < rawData.length; rowIndex++) { + let cells = rawData[rowIndex] + + // Is it a page heading? + if ((cells.length >= 11) && (cells[0].trim().toLowerCase().startsWith("place"))) { + if (numRounds == 0) { + for (let cell of cells) { + if (cell.toLowerCase().startsWith("round")) { + numRounds++ + } + } + } + continue + } + + if (adjudicators.length == 0) { + let fishy = false + for (let adjudicator of cells) { + if (Number(adjudicator) > 0) { + fishy = true + } + adjudicators.push(adjudicator.trim()) + } + if (fishy) { + console.warn("Adjudicators row doesn't look right", cells) + } + adjudicatorsPerRound = adjudicators.length / numRounds + if (! Number.isSafeInteger(adjudicatorsPerRound)) { + console.error(`Irrational number of adjudicators for number of rounds: (${adjudicators.length}/${numRounds})`) + } + continue + } + + // Is this just a list of adjudicators again? + if (cells.length >= adjudicators.length) { + let lenDiff = cells.length - adjudicators.length + let same = true + for (let i = adjudicators.length-1; i >= 0; i--) { + if (adjudicators[i] != cells[i+lenDiff].trim()) { + same = false + break + } + } + if (same) { + continue + } + } + + let row = {} + + { + let parts = cells[0].trim().split(/\s+/) + row.overallRank = Number(parts[0]) + row.overallPoints = Number(parts[1]) + } + + { + let match = cells[1].trim().match(/(\d+) - (.+) \((.+) *\)[ -]*(.+)?/) + if (match) { + row.number = Number(match[1]) + row.name = match[2] + row.school = match[3] + row.qualifier = match[4] + } + } + + row.rounds = [] + let round = [] + for (let cellIndex = 2; cellIndex < cells.length; cellIndex++) { + let cell = cells[cellIndex] + let adjudication = {} + let parts = cell.trim().split(/ - ?|\s/) + + adjudication.adjudicator = adjudicators[cellIndex - 2] + + if ((parts.length == 5) && (parts[3] == "AP")) { + parts.splice(3, 0, "NaN") + } + + if ((parts.length == 7) && (parts[4] == "T")) { + adjudication.tie = true + parts.splice(4, 1) + } else { + adjudication.tie = false + } + + if (parts.length != 6) { + console.error(`Wrong number of fields in row ${rowIndex} cell ${cellIndex}:`, parts, cells) + break + } + + for (let i = 0; i < parts.length; i += 2) { + let key = parts[i] + let val = Number(parts[i+1]) + switch (key) { + case "Raw": + adjudication.raw = val + break + case "Plc": + adjudication.placing = val + break + case "AP": + adjudication.points = val + break + default: + console.error(`Unknown key ${key} in row ${rowIndex} cell ${cellIndex}:`, cell) + break + } + } + + round.push(adjudication) + if (round.length == adjudicatorsPerRound) { + row.rounds.push(round) + round = [] + } + } + results.push(row) + } + + return results + } +} + +/** + * + * Fills a table element with some results + * + * @param {Element} table Table to fill in + * @param {Array.} results Results to fill with + */ +function fillTable(table, results) { + let head = newElement(table, "thead") + let row0 = newElement(head, "tr") + let row1 = newElement(head, "tr") + let row2 = newElement(head, "tr") + + newElement(row0, "th").colSpan = 3 + newElement(row1, "th").colSpan = 3 + newElement(row2, "th").textContent = "Name" + newElement(row2, "th").textContent = "Points" + newElement(row2, "th").textContent = "Rank" + + let roundNumber = 0 + for (let round of results[0].rounds) { + let roundCell = newElement(row0, "th") + roundCell.textContent = `Round ${++roundNumber}` + roundCell.colSpan = 3*round.length + for (let adjudication of round) { + let adjudicator = adjudication.adjudicator + let cell = newElement(row1, "th") + cell.textContent = adjudicator + cell.colSpan = 3 + + newElement(row2, "th").textContent = "Raw" + newElement(row2, "th").textContent = "Placing" + newElement(row2, "th").textContent = "Points" + } + } + + let body = newElement(table, "tbody") + for (let result of results) { + let row = newElement(body, "tr") + + newElement(row, "th").textContent = result.name + newElement(row, "th").textContent = result.overallPoints + newElement(row, "th").textContent = result.overallRank + + let i = 0 + for (let round of result.rounds) { + let first = true + for (let adjudication of round) { + let raw = newElement(row, "td") + raw.textContent = adjudication.raw + if (first) { + raw.classList.add("new-round") + first = false + } + + newElement(row, "td").textContent = adjudication.placing + newElement(row, "td").textContent = adjudication.points + i++ + } + } + } +} + +async function init() { + for (let div of document.querySelectorAll(".clrg-dataset")) { + let dataset = new Dataset() + await dataset.loadData(div.dataset.url) + + let table = newElement(div, "table") + fillTable(table, dataset.results) + console.log(dataset) + } +} + + +if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init) +} else { + init() +} + +export { + Dataset, +} diff --git a/content/blog/2022-10-10-CLRG-Results-Analysis/results.mjs b/content/blog/2022-10-10-CLRG-Results-Analysis/results.mjs deleted file mode 100644 index 9ccf4e7..0000000 --- a/content/blog/2022-10-10-CLRG-Results-Analysis/results.mjs +++ /dev/null @@ -1,178 +0,0 @@ -class Results { - /** - * - * @param {string} url URL to load - */ - constructor(url) { - if (url) { - this.loadData(url) - } - } - async loadData(url) { - let resp = await fetch(url) - let contentType = resp.headers.get("Content-Type") - if (! contentType.includes("/xml")) { - console.error(`Cannot load data with content-type ${contentType}`) - return - } - let text = await resp.text() - this.doc = new DOMParser().parseFromString(text, "text/xml") - this.rawData = this.parseXMLDocument(this.doc) - this.data = this.parseRawData(this.rawData) - } - - parseXMLDocument(doc) { - let table = doc.querySelector("Table") - let rawData = [] - - for (let dataRow of table.children) { - if (! ["tr"].includes(dataRow.tagName.toLowerCase())) { - console.warn(`Warning: unexpected XML tag ${dataRow.tagName}, expecting tr`) - continue - } - - let row = [] - for (let dataCell of dataRow.children) { - if (! ["th", "td"].includes(dataCell.tagName.toLowerCase())) { - console.warn(`Warning: unexpected XML tag ${dataRow.tagName}, expecting th/td`) - continue - } - row.push(dataCell.textContent) - } - - rawData.push(row) - } - return rawData - } - - parseRawData(rawData) { - let cellA1 = rawData[0][0].trim().toLowerCase() - switch (cellA1) { - case "place awd pts": - return this.parseFeisWorx2017(rawData) - break - } - console.error("Cell A1 doesn't resemble anything I can cope with", rawData[0]) - } - - /** - * - * @param {Array.>} rawData - */ - parseFeisWorx2017(rawData) { - let adjudicators = [] - let data = [] - for (let rowIndex = 0; rowIndex < rawData.length; rowIndex++) { - let cells = rawData[rowIndex] - switch (rowIndex) { - case 0: // Column headers - break - case 1: // Adjudicators - adjudicators = cells - break - default: - if ((cells.length == 11) && (cells[0].trim().toLowerCase().startsWith("place"))) { - // Page heading - continue - } - - if (cells.length >= adjudicators.length) { - // Is this just a list of adjudicators again? - let lenDiff = cells.length - adjudicators.length - let same = true - for (let i = adjudicators.length-1; i >= 0; i--) { - if (adjudicators[i] != cells[i+lenDiff]) { - same = false - break - } - } - if (same) { - continue - } - } - - let row = {} - - { - let parts = cells[0].trim().split(/\s+/) - row.overallRank = Number(parts[0]) - row.overallPoints = Number(parts[1]) - } - - { - let parts = cells[1].trim().split(/ - /) - row.competitorNumber = Number(parts[0]) - row.competitorName = parts[1] - row.qualifier = parts[2] - } - - row.adjudication = {} // XXX: I don't like this name - for (let cellIndex = 2; cellIndex < cells.length; cellIndex++) { - let cell = cells[cellIndex] - let vote = {} - let parts = cell.trim().split(/ - ?|\s/) - - if ((parts.length == 5) && (parts[3] == "AP")) { - parts.splice(3, 0, "NaN") - } - - if ((parts.length == 7) && (parts[4] == "T")) { - vote.tie = true - parts.splice(4, 1) - } else { - vote.tie = false - } - - if (parts.length != 6) { - console.error(`Wrong number of fields in row ${rowIndex} cell ${cellIndex}:`, parts, cells) - } else { - for (let i = 0; i < parts.length; i += 2) { - let key = parts[i] - let val = Number(parts[i+1]) - switch (key) { - case "Raw": - vote.raw = val - break - case "Plc": - vote.placing = val - break - case "AP": - vote.points = val - break - default: - console.error(`Unknown key ${key} in row ${rowIndex} cell ${cellIndex}:`, cell) - break - } - } - - let adjudicator = adjudicators[cellIndex - 2] - row.adjudication[adjudicator] = vote - } - } - data.push(row) - break - } - } - return data - } -} - - -async function init() { - for (let div of document.querySelectorAll(".crlg-dataset")) { - let results = new Results() - await results.loadData(div.dataset.url) - console.log(results.data) - } -} - - -if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", init) -} else { - init() -} - -export { - Results, -} \ No newline at end of file diff --git a/content/blog/2022-10-10-CLRG-Results-Analysis/wat.html b/content/blog/2022-10-10-CLRG-Results-Analysis/wat.html index 20b19b2..ffaa047 100644 --- a/content/blog/2022-10-10-CLRG-Results-Analysis/wat.html +++ b/content/blog/2022-10-10-CLRG-Results-Analysis/wat.html @@ -1,9 +1,12 @@ --- title: CLRG Data Analyzer +stylesheets: + - dataset.css scripts: - - results.mjs + - dataset.mjs --- wat? -
\ No newline at end of file +
+
\ No newline at end of file diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html index 17fc81f..2473ab5 100644 --- a/layouts/_default/baseof.html +++ b/layouts/_default/baseof.html @@ -11,14 +11,18 @@ {{end}} {{range .Params.stylesheets}} - + {{$url := .}} + {{with $.Page.Resources.GetMatch .}} + {{$url = .RelPermalink}} + {{end}} + {{end}} {{range .Params.scripts}} {{end}} {{range .Params.scripts}} {{$url := .}} {{with $.Page.Resources.GetMatch .}} - {{$url = .RelPermaLink}} + {{$url = .RelPermalink}} {{end}}