{
  "openapi": "3.1.1",
  "info": {
    "title": "Fun & games API",
    "version": "1.0.0",
    "summary": "Deterministic tools for people and AI agents.",
    "description": "Every endpoint performs the same calculation as the human page and returns inputs, result, units, assumptions, sources, warnings and computedAt. MCP endpoint: /mcp."
  },
  "servers": [
    {
      "url": "https://fun.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/tools": {
      "get": {
        "operationId": "listTools",
        "summary": "Catalog of all tools",
        "responses": {
          "200": {
            "description": "Tool list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/{id}": {
      "get": {
        "operationId": "getTool",
        "summary": "Definition of one tool",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "chore-roulette",
                "elo-glicko-rating-lab",
                "emoji-cipher-playground",
                "equation-art-studio",
                "keyboard-heatmap-visualizer",
                "runtime-marathon-planner",
                "movie-night-consensus-picker",
                "padel-pickleball-scheduler",
                "password-entropy-race",
                "regex-golf-arena",
                "sports-what-if-simulator",
                "tournament-bracket-maker"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/chore-roulette/calculate": {
      "post": {
        "operationId": "calculateChoreRoulette",
        "summary": "Spin a fair chore rota: random but balanced by effort, with a “not again” rule.",
        "description": "Enter the people (with an optional capacity, e.g. 0.5 for a child) and the chores with effort points. Chore Roulette draws a reproducible random assignment that balances everyone’s total effort, can avoid giving anyone the same chore as last week, and shows each person’s load against their fair share.\n\nMethodology:\n- Each chore occurrence is a task with effort points. A person’s fair share is total effort × their capacity ÷ the sum of capacities.\n- The seeded random generator (sfc32 seeded with SHA-256 of the seed) shuffles the tasks and the people; tasks are then handed out largest first to whoever would end up with the lowest load relative to their capacity (ties go to the seeded order).\n- A local search then moves single tasks and swaps pairs whenever that lowers the sum of squared differences between each person’s effort and their fair share, until nothing improves. Moves never give someone a chore they did last week when the rule is on.\n- Fairness = 100 × (1 − Σ|effort − fair share| ÷ (2 × total effort)): the share of the work that sits with the right person. Spread = highest minus lowest effort per full share of capacity.\n- If everyone did a chore last week, a repeat is unavoidable and is reported. Without a seed, the seed is a hash of the inputs, so the same rota appears on the page, in the API and in MCP; change the seed to re-spin.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "chore_roulette",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChoreRouletteInput"
              },
              "examples": {
                "example1": {
                  "summary": "Flat share of three",
                  "value": {
                    "people": [
                      {
                        "name": "Alex"
                      },
                      {
                        "name": "Bea"
                      },
                      {
                        "name": "Chris"
                      }
                    ],
                    "chores": [
                      {
                        "chore": "Bathroom",
                        "effort": 4
                      },
                      {
                        "chore": "Kitchen",
                        "effort": 3
                      },
                      {
                        "chore": "Vacuum",
                        "effort": 3
                      },
                      {
                        "chore": "Bins",
                        "effort": 1
                      },
                      {
                        "chore": "Groceries",
                        "effort": 2
                      },
                      {
                        "chore": "Dishes",
                        "effort": 1,
                        "count": 4
                      }
                    ],
                    "lastWeek": [
                      {
                        "chore": "Bathroom",
                        "person": "Alex"
                      },
                      {
                        "chore": "Kitchen",
                        "person": "Bea"
                      }
                    ],
                    "avoidRepeats": true,
                    "seed": "week-42"
                  }
                },
                "example2": {
                  "summary": "Family with a younger child",
                  "value": {
                    "people": [
                      {
                        "name": "Mum"
                      },
                      {
                        "name": "Dad"
                      },
                      {
                        "name": "Mia",
                        "capacity": 1
                      },
                      {
                        "name": "Leo",
                        "capacity": 0.5
                      }
                    ],
                    "chores": [
                      {
                        "chore": "Cook dinner",
                        "effort": 3,
                        "count": 3
                      },
                      {
                        "chore": "Laundry",
                        "effort": 4
                      },
                      {
                        "chore": "Feed the cat",
                        "effort": 0.5,
                        "count": 7
                      },
                      {
                        "chore": "Tidy living room",
                        "effort": 2
                      },
                      {
                        "chore": "Water plants",
                        "effort": 1
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChoreRouletteResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/elo-glicko-rating-lab/calculate": {
      "post": {
        "operationId": "calculateEloGlickoRatingLab",
        "summary": "Rate players from game results with Elo, Glicko-1 and Glicko-2; history chart and win odds.",
        "description": "Enter game results (win, draw, loss) grouped into rating periods and see the ratings under Elo (fixed K or FIDE-style K), Glicko-1 and Glicko-2, implemented exactly as in Mark Glickman's papers, with the rating deviation and volatility, a rating history chart and the win probability between any two players.\n\nMethodology:\n- Elo: expected score E = 1 / (1 + 10^((R_opp − R)/400)); after every game R ← R + K(S − E) for both players, in input order. The FIDE-style option uses K = 40 for a player's first 30 games, then 20, and 10 once the player has reached 2400 (the age rule is not modelled).\n- Glicko-1 (Glickman 1999): all games of a period are rated together with each player's pre-period values; RD shrinks with games and grows by c per idle period: RD = min(√(RD² + c²·t), 350).\n- Glicko-2 (Glickman, \"Example of the Glicko-2 system\"): steps 1–8 on the μ/φ scale (÷ 173.7178) with the Illinois algorithm (ε = 0.000001) for the new volatility; players without games only have φ increased to √(φ² + σ²).\n- Win probability: Elo uses E above; Glicko uses E = 1 / (1 + 10^(−g(√(RD_A² + RD_B²))·(R_A − R_B)/400)) with g(RD) = 1 / √(1 + 3q²RD²/π²), q = ln 10 / 400. Draws count as half a win.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "elo_glicko_rating_lab",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EloGlickoRatingLabInput"
              },
              "examples": {
                "example1": {
                  "summary": "Glickman's worked example (1500 vs 1400, 1550, 1700)",
                  "value": {
                    "games": [
                      {
                        "period": 1,
                        "player": "Player",
                        "opponent": "A",
                        "score": "1"
                      },
                      {
                        "period": 1,
                        "player": "Player",
                        "opponent": "B",
                        "score": "0"
                      },
                      {
                        "period": 1,
                        "player": "Player",
                        "opponent": "C",
                        "score": "0"
                      }
                    ],
                    "players": [
                      {
                        "name": "Player",
                        "rating": 1500,
                        "rd": 200,
                        "volatility": 0.06
                      },
                      {
                        "name": "A",
                        "rating": 1400,
                        "rd": 30
                      },
                      {
                        "name": "B",
                        "rating": 1550,
                        "rd": 100
                      },
                      {
                        "name": "C",
                        "rating": 1700,
                        "rd": 300
                      }
                    ],
                    "tau": 0.5
                  }
                },
                "example2": {
                  "summary": "Small club league over three periods",
                  "value": {
                    "games": [
                      {
                        "period": 1,
                        "player": "Ana",
                        "opponent": "Ben",
                        "score": "1"
                      },
                      {
                        "period": 1,
                        "player": "Cleo",
                        "opponent": "Dev",
                        "score": "0.5"
                      },
                      {
                        "period": 2,
                        "player": "Ana",
                        "opponent": "Cleo",
                        "score": "0"
                      },
                      {
                        "period": 2,
                        "player": "Ben",
                        "opponent": "Dev",
                        "score": "1"
                      },
                      {
                        "period": 3,
                        "player": "Ana",
                        "opponent": "Dev",
                        "score": "1"
                      },
                      {
                        "period": 3,
                        "player": "Cleo",
                        "opponent": "Ben",
                        "score": "1"
                      }
                    ],
                    "eloK": "fide"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EloGlickoRatingLabResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/emoji-cipher-playground/calculate": {
      "post": {
        "operationId": "calculateEmojiCipherPlayground",
        "summary": "Turn secret notes into emoji and back: substitution, Caesar shift and base-emoji.",
        "description": "Encode or decode text with three emoji ciphers: a keyed substitution (your key shuffles the emoji alphabet), a Caesar shift over an emoji alphabet, or base-emoji, which turns every UTF-8 byte into one of 256 emoji so any text survives. Round trips are exact — and the tool shows why none of this is real encryption.\n\nMethodology:\n- Letter schemes map a–z and 0–9 to 36 emoji (🍎 a, 🐝 b, 🐱 c … and clock faces for digits). Keyed substitution shuffles that alphabet with a generator seeded by your key; Caesar shifts letters by k (mod 26) and digits by k (mod 10) before mapping.\n- Capital letters get the marker 🔼 in front. Characters that would be ambiguous (the alphabet emoji themselves, 🔼, the escape emoji 🔣 and the invisible emoji variation selector) are written as 🔣 + character. Everything else (spaces, punctuation, accents) passes through unchanged. This makes decode(encode(text)) exact, which the tool verifies on every run.\n- Base-emoji encodes the UTF-8 bytes of the text, one emoji per byte, using a fixed table of 256 emoji (U+1F300 upwards); an optional key shuffles the table. It works for any text, including emoji, but makes messages about 4× longer in code points.\n- Security: none. Letter ciphers keep word lengths, spaces and letter frequencies (the most common emoji is probably “e”), so frequency analysis breaks them in minutes; Caesar has only 26 keys. Base-emoji is an encoding, like Base64 — with the standard table anyone can decode it. Use real encryption (e.g. age, GPG or an end-to-end encrypted messenger) for secrets.\n\nA toy for puzzles, games and party invitations. It does not protect anything.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "emoji_cipher_playground",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmojiCipherPlaygroundInput"
              },
              "examples": {
                "example1": {
                  "summary": "Secret note with a keyed substitution",
                  "value": {
                    "action": "encode",
                    "scheme": "substitution",
                    "text": "Meet me at 7 by the old oak tree!",
                    "key": "picnic"
                  }
                },
                "example2": {
                  "summary": "Caesar shift 3",
                  "value": {
                    "action": "encode",
                    "scheme": "caesar",
                    "text": "Hello World",
                    "shift": 3
                  }
                },
                "example3": {
                  "summary": "Base-emoji round trip (any text, even emoji)",
                  "value": {
                    "action": "encode",
                    "scheme": "base-emoji",
                    "text": "Grüße 👋",
                    "key": ""
                  }
                },
                "example4": {
                  "summary": "Decode a Caesar message",
                  "value": {
                    "action": "decode",
                    "scheme": "caesar",
                    "text": "🔼🐍🍋🐱🐱🐶 🐶🐋 🕐?",
                    "shift": 3
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmojiCipherPlaygroundResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/equation-art-studio/calculate": {
      "post": {
        "operationId": "calculateEquationArtStudio",
        "summary": "Turn math into art: parametric, polar, Lissajous, rose and spirograph curves.",
        "description": "Type x(t) and y(t), or r(θ), or start from a preset (Lissajous, rose, spirograph, butterfly, heart), add rotational symmetry and a colour palette, and get a crisp SVG artwork plus a PNG download. Expressions are parsed safely (no code execution) and support + − × ÷ ^, sin, cos, tan, sqrt, abs, exp, log, pi and e.\n\nMethodology:\n- Expressions are tokenised and parsed by a small recursive-descent parser into a syntax tree, which is compiled into plain arithmetic closures — no eval or Function, and only the listed functions, constants and variables are accepted.\n- Polar curves are converted with x = r(θ)·cos θ, y = r(θ)·sin θ. Presets: Lissajous x = sin(a·t + d), y = sin(b·t); rose r = cos(a/b·θ); spirograph (hypotrochoid) x = (a−b)cos t + c·cos((a−b)/b·t), y = (a−b)sin t − c·sin((a−b)/b·t); Temple Fay’s butterfly; the classic 16 sin³t heart. Roses and spirographs with whole-number a, b run exactly one full period.\n- t is sampled evenly from start to end. Undefined samples (division by zero, sqrt of a negative number) leave gaps, and very large jumps (asymptotes) are not connected.\n- The drawing is fitted into a 600 × 600 square with equal x/y scale, optionally rotated into k symmetric copies, and coloured along t with a gradient from the palette. Points closer than 0.3 px are dropped to keep the SVG small.\n- The SVG uses only plain shapes (no scripts or styles). The PNG is rendered from the same SVG with ImageMagick when it is installed; both downloads stay available for about 2 hours.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "equation_art_studio",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EquationArtStudioInput"
              },
              "examples": {
                "example1": {
                  "summary": "Spirograph, sunset palette",
                  "value": {
                    "curve": "spirograph",
                    "a": 7,
                    "b": 4,
                    "c": 3,
                    "samples": 2000,
                    "symmetry": 1,
                    "palette": "sunset",
                    "strokeWidth": 2
                  }
                },
                "example2": {
                  "summary": "Rose with 3-fold symmetry",
                  "value": {
                    "curve": "rose",
                    "a": 7,
                    "b": 3,
                    "samples": 1500,
                    "symmetry": 3,
                    "palette": "ocean",
                    "strokeWidth": 1.5,
                    "png": false
                  }
                },
                "example3": {
                  "summary": "Own parametric curve",
                  "value": {
                    "curve": "parametric",
                    "xExpr": "cos(t) + cos(6t)/2 + sin(14t)/3",
                    "yExpr": "sin(t) + sin(6t)/2 + cos(14t)/3",
                    "tMin": "0",
                    "tMax": "2*pi",
                    "samples": 2500,
                    "symmetry": 1,
                    "palette": "neon",
                    "strokeWidth": 2,
                    "png": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EquationArtStudioResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/keyboard-heatmap-visualizer/calculate": {
      "post": {
        "operationId": "calculateKeyboardHeatmapVisualizer",
        "summary": "Paste any text: see which keys and fingers do the work on QWERTY, Dvorak, Colemak & co.",
        "description": "Paste text and see a key-frequency heatmap on a QWERTY, QWERTZ, AZERTY, Dvorak or Colemak keyboard, plus finger and hand load, row usage, same-finger bigrams and hand alternation — and a table comparing how the same text would feel on each layout.\n\nMethodology:\n- Every character is looked up on the layout (unshifted, Shift or AltGr level). Characters not on the layout are decomposed when possible (é → e) and counted as approximated; the rest (e.g. emoji) are counted as unmapped and skipped. Line breaks and tabs are skipped.\n- Fingers follow standard touch typing on a row-staggered keyboard: left pinky for 1/Q/A/Z (and the ISO key left of Z), ring for 2/W/S/X, middle for 3/E/D/C, index for 4–5/R–T/F–G/V–B; mirrored on the right, with the right pinky also covering the keys beyond P/;//. The space bar counts for the thumbs.\n- Same-finger bigram = two consecutive characters typed with the same finger on different keys (a key repeated, like “ll”, is not counted). Hand alternation = share of consecutive characters typed by different hands. Both exclude spaces, and a space resets the sequence.\n- Home-row share is calculated without spaces. Shift and AltGr presses are counted as extra key presses but not attributed to a finger.\n- The heatmap colour uses the square root of the count so that rarer keys stay visible.\n\nA simplified model of typing effort: real comfort also depends on rolls, lateral stretches, keyboard geometry and habits.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "keyboard_heatmap_visualizer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KeyboardHeatmapVisualizerInput"
              },
              "examples": {
                "example1": {
                  "summary": "English pangrams on QWERTY vs the rest",
                  "value": {
                    "text": "The quick brown fox jumps over the lazy dog. Pack my box with five dozen liquor jugs! How vexingly quick daft zebras jump; sphinx of black quartz, judge my vow.",
                    "layout": "qwerty",
                    "compare": [
                      "qwerty",
                      "dvorak",
                      "colemak"
                    ]
                  }
                },
                "example2": {
                  "summary": "German text on QWERTZ",
                  "value": {
                    "text": "Zwölf Boxkämpfer jagen Viktor quer über den großen Sylter Deich. Falsches Üben von Xylophonmusik quält jeden größeren Zwerg.",
                    "layout": "qwertz",
                    "compare": [
                      "qwertz",
                      "qwerty",
                      "colemak"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KeyboardHeatmapVisualizerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/runtime-marathon-planner/calculate": {
      "post": {
        "operationId": "calculateRuntimeMarathonPlanner",
        "summary": "Plan a binge: when each movie or episode starts and ends, total days, and a calendar.",
        "description": "Add movies (runtime) and series (episodes × minutes × seasons), your breaks, when you start and how many hours a day you want to watch. The planner schedules every movie and episode, tells you how many days the marathon takes and whether you finish by a date, and exports the viewing sessions as an .ics calendar.\n\nMethodology:\n- Each movie is one item; each series becomes episodes × seasons items of the given length.\n- Items are watched in the order entered. Within a day, a break follows each item (the episode break inside a title, the title break between titles); breaks do not count towards the daily maximum.\n- If the next item would push the day over the maximum viewing time, it moves to the next day at the daily start time. With pausing allowed, an item is split instead when at least 15 minutes fit into the day.\n- An item longer than the daily maximum is scheduled in one piece (with a warning) unless pausing is allowed.\n- A viewing session belongs to the day it starts, even if it runs past midnight. Times are local wall-clock times without a time zone; the .ics file uses floating times (RFC 5545), so calendars show them at the same clock time wherever you are.\n- Finish-by check: the last item must end before midnight at the end of that date. Required hours per day = total watching time ÷ number of days from the start date to the finish-by date.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "runtime_marathon_planner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RuntimeMarathonPlannerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Lord-of-the-Rings-length trilogy over a weekend",
                  "value": {
                    "titles": [
                      {
                        "title": "Part 1",
                        "minutes": 178
                      },
                      {
                        "title": "Part 2",
                        "minutes": 179
                      },
                      {
                        "title": "Part 3",
                        "minutes": 201
                      }
                    ],
                    "start": "2026-10-23T19:00",
                    "dailyStart": "14:00",
                    "maxHoursPerDay": 6,
                    "breakMinutes": 10,
                    "titleBreakMinutes": 30,
                    "allowSplit": false,
                    "finishBy": "2026-10-25"
                  }
                },
                "example2": {
                  "summary": "A 5-season drama at 2 hours a night",
                  "value": {
                    "titles": [
                      {
                        "title": "Drama",
                        "minutes": 47,
                        "episodes": 10,
                        "seasons": 5
                      }
                    ],
                    "start": "2026-11-01T20:00",
                    "dailyStart": "20:00",
                    "maxHoursPerDay": 2,
                    "breakMinutes": 5,
                    "titleBreakMinutes": 20,
                    "allowSplit": false,
                    "finishBy": "2026-11-20"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RuntimeMarathonPlannerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/movie-night-consensus-picker/calculate": {
      "post": {
        "operationId": "calculateMovieNightConsensusPicker",
        "summary": "Everyone ranks the films, vetoes count: Borda, approval, runoff and Condorcet decide.",
        "description": "List the candidate films (or restaurants, games, holiday spots), then let each person rank them and veto the ones they cannot stand. The picker runs four voting methods — Borda count, approval, instant-runoff and Condorcet/Schulze — shows each winner and why, and breaks ties fairly with a seed.\n\nMethodology:\n- Each ballot is a ranking, best first; films a person leaves out count as tied below all films they ranked. Names are matched case-insensitively; unknown names are reported and skipped.\n- Vetoes: in “remove” mode every film vetoed by at least one person is struck before any counting (if that would remove all films, vetoes are ignored and a warning is shown).\n- Borda: with n films, 1st place earns n−1 points, 2nd n−2, … ; unranked films share the remaining points equally. Approval: each voter approves their top N films; most approvals wins. Instant-runoff (IRV): count first preferences, eliminate the film with the fewest, transfer those ballots to their next choice, repeat until one film has a majority of the ballots still in play.\n- Condorcet/Schulze: for every pair, count how many voters prefer each. A Condorcet winner beats every other film head-to-head. The Schulze method (strongest paths) always finds a winner and agrees with the Condorcet winner when one exists.\n- Ties are broken with a random order of the films drawn from the seed (sfc32 seeded with SHA-256), shown in the result, so the outcome is reproducible and nobody can claim favouritism. The recommendation is the Schulze winner.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "movie_night_consensus_picker",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MovieNightConsensusPickerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Five friends, four films, one veto",
                  "value": {
                    "candidates": [
                      "Dune: Part Two",
                      "Paddington 2",
                      "Alien",
                      "Amélie"
                    ],
                    "ballots": [
                      {
                        "voter": "Ana",
                        "ranking": "Dune: Part Two > Alien > Amélie > Paddington 2"
                      },
                      {
                        "voter": "Ben",
                        "ranking": "Paddington 2 > Amélie > Dune: Part Two",
                        "veto": "Alien"
                      },
                      {
                        "voter": "Cleo",
                        "ranking": "Amélie > Paddington 2 > Dune: Part Two > Alien"
                      },
                      {
                        "voter": "Dev",
                        "ranking": "Dune: Part Two > Paddington 2 > Amélie > Alien"
                      },
                      {
                        "voter": "Eli",
                        "ranking": "Paddington 2 > Dune: Part Two > Amélie > Alien"
                      }
                    ],
                    "vetoMode": "remove"
                  }
                },
                "example2": {
                  "summary": "Methods disagree (classic three-way split)",
                  "value": {
                    "candidates": [
                      "Action",
                      "Comedy",
                      "Drama"
                    ],
                    "ballots": [
                      {
                        "voter": "A1",
                        "ranking": "Action > Drama > Comedy"
                      },
                      {
                        "voter": "A2",
                        "ranking": "Action > Drama > Comedy"
                      },
                      {
                        "voter": "A3",
                        "ranking": "Action > Drama > Comedy"
                      },
                      {
                        "voter": "A4",
                        "ranking": "Action > Drama > Comedy"
                      },
                      {
                        "voter": "B1",
                        "ranking": "Comedy > Drama > Action"
                      },
                      {
                        "voter": "B2",
                        "ranking": "Comedy > Drama > Action"
                      },
                      {
                        "voter": "B3",
                        "ranking": "Comedy > Drama > Action"
                      },
                      {
                        "voter": "C1",
                        "ranking": "Drama > Comedy > Action"
                      },
                      {
                        "voter": "C2",
                        "ranking": "Drama > Comedy > Action"
                      }
                    ],
                    "vetoMode": "ignore",
                    "approvalCount": 1,
                    "seed": "popcorn"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MovieNightConsensusPickerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/padel-pickleball-scheduler/calculate": {
      "post": {
        "operationId": "calculatePadelPickleballScheduler",
        "summary": "Fair Americano or Mexicano doubles rounds with balanced sit-outs, scores and standings.",
        "description": "Enter the players, the number of courts and rounds. The scheduler creates doubles rounds that avoid repeating partners and opponents and spreads sit-outs evenly (Americano), or pairs players by their current standings (Mexicano: 1st + 4th against 2nd + 3rd). Enter scores to get the standings and the next Mexicano round.\n\nMethodology:\n- Each round uses min(courts, ⌊players ÷ 4⌋) courts; everyone else sits out. Sit-outs go to the players who have sat out least, then to those who sat out longest ago, then from the bottom of the list, so sit-outs differ by at most one where possible.\n- Americano: the active players are arranged on courts to minimise a penalty: 10 for every earlier round two partners already played together and 3 for every earlier meeting of two opponents. Starting from a rotated order, a deterministic local search swaps players while the penalty falls. With few players, repeats are unavoidable; the fairness table shows them.\n- Mexicano: round 1 is drawn like Americano from the list order. From round 2, the active players are ranked by the standings, and every group of four (1–4, 5–8 …) plays 1st + 4th against 2nd + 3rd. A round can only be drawn when all scores of the previous round are entered.\n- Standings: every player gets the points their team scored. Sorted by points, then wins, then point difference, then name.\n- No randomness: the same players and settings always give the same schedule.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "padel_pickleball_scheduler",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PadelPickleballSchedulerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Americano: 9 players, 2 courts, 6 rounds",
                  "value": {
                    "sport": "padel",
                    "format": "americano",
                    "players": [
                      "Ana",
                      "Ben",
                      "Carla",
                      "Dev",
                      "Eli",
                      "Fran",
                      "Gus",
                      "Hana",
                      "Ivo"
                    ],
                    "courts": 2,
                    "rounds": 6,
                    "pointsPerMatch": 24
                  }
                },
                "example2": {
                  "summary": "Mexicano: 8 players, scores for round 1",
                  "value": {
                    "sport": "padel",
                    "format": "mexicano",
                    "players": [
                      "P1",
                      "P2",
                      "P3",
                      "P4",
                      "P5",
                      "P6",
                      "P7",
                      "P8"
                    ],
                    "courts": 2,
                    "rounds": 3,
                    "pointsPerMatch": 24,
                    "scores": [
                      {
                        "round": 1,
                        "court": 1,
                        "scoreA": 15,
                        "scoreB": 9
                      },
                      {
                        "round": 1,
                        "court": 2,
                        "scoreA": 10,
                        "scoreB": 14
                      }
                    ]
                  }
                },
                "example3": {
                  "summary": "Pickleball mixer: 6 players, 1 court",
                  "value": {
                    "sport": "pickleball",
                    "format": "americano",
                    "players": [
                      "Kim",
                      "Lou",
                      "Max",
                      "Nia",
                      "Oli",
                      "Pat"
                    ],
                    "courts": 1,
                    "rounds": 6,
                    "pointsPerMatch": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PadelPickleballSchedulerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/password-entropy-race/calculate": {
      "post": {
        "operationId": "calculatePasswordEntropyRace",
        "summary": "Race 2–6 passwords or passphrases: entropy, pattern penalties and crack times.",
        "description": "Compares passwords and passphrases side by side: naive charset entropy, a pattern-aware estimate that penalises common passwords, keyboard walks, repeats, sequences, dates and l33t speak, and the average time to crack each one at four attacker speeds. With JavaScript on, everything runs in your browser and nothing is sent.\n\nMethodology:\n- Charset entropy = length × log2(pool), where the pool adds 26 for lowercase, 26 for uppercase, 10 for digits, 33 for ASCII symbols/space and 100 for any other character used. This is the optimistic \"brute force\" number most meters show.\n- The pattern-aware estimate splits the password into the cheapest sequence of guessable pieces (dynamic programming, similar to zxcvbn but simpler): common passwords and words from a bundled list of about 600 entries (log2 of the rank, plus bits for capitalisation, reversal and each l33t substitution), other pronounceable letter runs (assumed to be in a 100,000-word attacker dictionary, about 16.6 bits), keyboard walks on QWERTY (start key, length and turns), repeats, alphabetical/numeric sequences, years 1900–2039 and calendar dates. Word separators (space, dash, underscore, dot) cost 2 bits; remaining characters cost log2(pool) each; combining k pieces (separators not counted) adds log2(k!) bits.\n- The reported entropy is the smaller of the two numbers. Crack time is the average (half the search space, 2^(bits−1) guesses) divided by the attacker speed: 100 guesses/hour (online, throttled), 10 guesses/s (online), 10⁴ guesses/s (offline, slow hash such as bcrypt) and 10¹⁰ guesses/s (offline, fast unsalted hash on GPUs) — the scenario speeds popularised by zxcvbn.\n- The race winner is the highest pattern-aware entropy; results within 1 bit count as a photo finish.\n- Privacy: the same JavaScript file runs in your browser (form submission is intercepted, nothing is sent) and on the server (only when JavaScript is off, or via the API/MCP). Results show passwords masked; detected pattern pieces are listed so you can see why a password is weak.\n\nAn estimate for comparison and learning, not a guarantee. Real attackers use lists with billions of leaked passwords and smarter rules; never reuse passwords and prefer a password manager.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "password_entropy_race",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordEntropyRaceInput"
              },
              "examples": {
                "example1": {
                  "summary": "Classic password vs passphrase",
                  "value": {
                    "passwords": "P@ssw0rd2024\ncorrect horse battery staple\nTr0ub4dor&3"
                  }
                },
                "example2": {
                  "summary": "Keyboard walk vs random",
                  "value": {
                    "passwords": "qwertyuiop\n1qaz2wsx3edc\nk8#Vq2!mZr7w"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PasswordEntropyRaceResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/regex-golf-arena/calculate": {
      "post": {
        "operationId": "calculateRegexGolfArena",
        "summary": "Match the left list, avoid the right list — in as few characters as possible.",
        "description": "Regex golf with built-in puzzles: write one JavaScript regular expression that matches every string on the “match” list and none on the “don’t match” list. Shorter is better; perfect solutions can join a per-puzzle top-20 leaderboard. Use “dry run” to test without submitting.\n\nMethodology:\n- Your pattern is compiled with JavaScript’s RegExp (flags i/u optional) and tested against every string with RegExp.test, so it may match anywhere in the string unless you anchor it.\n- Score = 10 × (strings on the match list that match + strings on the don’t-match list that do not match) − regex length, as in classic regex golf. A solution is perfect when every string is classified correctly.\n- Safety: patterns are limited to 200 characters, control characters are refused, and patterns where an unbounded quantifier repeats a group that contains another variable quantifier or an alternation — the classic catastrophic-backtracking shapes such as (a+)+ or (a|aa)* — are rejected before running. The puzzle strings are short (at most 12 characters), which bounds the remaining work.\n- Leaderboard: only perfect, non-dry-run solutions are entered, ranked by length, then date. Each puzzle keeps the top 20; an identical regex only counts once, and each nickname keeps its best entry. Only nickname, length, score and date are shown — never the regex — so nobody gets spoilers.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "regex_golf_arena",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegexGolfArenaInput"
              },
              "examples": {
                "example1": {
                  "summary": "Warm-up, dry run",
                  "value": {
                    "puzzle": "warm-up",
                    "regex": "cat",
                    "flags": "none",
                    "dryRun": true
                  }
                },
                "example2": {
                  "summary": "Seeing double, almost right",
                  "value": {
                    "puzzle": "doubles",
                    "regex": "(ll|ff|tt|mm|pp|rr)",
                    "flags": "none",
                    "dryRun": true
                  }
                },
                "example3": {
                  "summary": "Show the ABBA puzzle",
                  "value": {
                    "puzzle": "abba",
                    "dryRun": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegexGolfArenaResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/sports-what-if-simulator/calculate": {
      "post": {
        "operationId": "calculateSportsWhatIfSimulator",
        "summary": "Simulate a match or a small league with a Poisson goal model — and replay “what if”.",
        "description": "Give team strengths as goals scored/conceded per game or as Elo ratings. Get exact win/draw/loss odds and a scoreline heatmap for one match, or simulate the rest of a small league thousands of times (seeded, reproducible) for title and position odds — then change any result to see how the table and the odds would have looked.\n\nMethodology:\n- Goals are modelled as independent Poisson variables. Goals model (Maher): with μ = the average goals scored per team per game, home expected goals = μ × (home scoring ÷ μ) × (away conceding ÷ μ), and the same for the away side.\n- Elo model: with d = Elo difference, home expected goals = G/2 × 10^(d/1000) and away = G/2 × 10^(−d/1000), where G is the average goals per match. With G = 2.6 this reproduces the Elo expected score (win + ½ draw) within about 2 percentage points for differences up to 400 (200 points: 76% expected score = 66% wins + 20% draws); it is a calibration, not a model fitted to real results.\n- Home advantage multiplies the home side’s expected goals (default +10%); expected goals are clamped to 0.05–8.\n- Single match: win/draw/loss and scoreline probabilities are exact sums over the Poisson grid (0–15 goals each). A seeded simulation of the same match is shown next to them as a check.\n- League: every unplayed fixture is simulated N times with a seeded sfc32 generator; the table uses 3 points per win, 1 per draw, then goal difference, goals scored and a random draw. The what-if scenario re-runs with the same random numbers (common random numbers), so differences come from the changed results, not from noise.\n\nFor fun and for “what if” arguments, not for betting: the model ignores injuries, form, motivation and the correlation between the two teams’ goals.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "sports_what_if_simulator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SportsWhatIfSimulatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "One match from goal averages",
                  "value": {
                    "mode": "match",
                    "model": "goals",
                    "teams": [
                      {
                        "name": "Rovers",
                        "goalsFor": 1.9,
                        "goalsAgainst": 0.9
                      },
                      {
                        "name": "United",
                        "goalsFor": 1.3,
                        "goalsAgainst": 1.2
                      },
                      {
                        "name": "City",
                        "goalsFor": 1.1,
                        "goalsAgainst": 1.4
                      },
                      {
                        "name": "Athletic",
                        "goalsFor": 0.9,
                        "goalsAgainst": 1.7
                      }
                    ],
                    "homeTeam": "United",
                    "awayTeam": "Rovers",
                    "homeAdvantage": 10,
                    "simulations": 2000
                  }
                },
                "example2": {
                  "summary": "Four-team league run-in with a what-if",
                  "value": {
                    "mode": "league",
                    "model": "elo",
                    "teams": [
                      {
                        "name": "Rovers",
                        "elo": 1620
                      },
                      {
                        "name": "United",
                        "elo": 1580
                      },
                      {
                        "name": "City",
                        "elo": 1500
                      },
                      {
                        "name": "Athletic",
                        "elo": 1450
                      }
                    ],
                    "avgGoals": 2.6,
                    "homeAdvantage": 10,
                    "fixtures": [
                      {
                        "home": "Rovers",
                        "away": "United",
                        "homeGoals": 1,
                        "awayGoals": 1
                      },
                      {
                        "home": "City",
                        "away": "Athletic",
                        "homeGoals": 2,
                        "awayGoals": 0
                      },
                      {
                        "home": "United",
                        "away": "City",
                        "homeGoals": 3,
                        "awayGoals": 1
                      },
                      {
                        "home": "Athletic",
                        "away": "Rovers",
                        "homeGoals": 0,
                        "awayGoals": 2
                      },
                      {
                        "home": "Rovers",
                        "away": "City"
                      },
                      {
                        "home": "United",
                        "away": "Athletic"
                      },
                      {
                        "home": "United",
                        "away": "Rovers"
                      },
                      {
                        "home": "Athletic",
                        "away": "City"
                      },
                      {
                        "home": "City",
                        "away": "United"
                      },
                      {
                        "home": "Rovers",
                        "away": "Athletic"
                      },
                      {
                        "home": "City",
                        "away": "Rovers"
                      },
                      {
                        "home": "Athletic",
                        "away": "United"
                      }
                    ],
                    "whatIf": [
                      {
                        "home": "Rovers",
                        "away": "United",
                        "homeGoals": 2,
                        "awayGoals": 1
                      }
                    ],
                    "simulations": 2000,
                    "seed": "season-2026"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SportsWhatIfSimulatorResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/tournament-bracket-maker/calculate": {
      "post": {
        "operationId": "calculateTournamentBracketMaker",
        "summary": "Single or double elimination or round robin: seeded brackets, byes, results and downloads.",
        "description": "Paste the entrants (in seed order, or let a seeded random draw decide) and pick single elimination, double elimination or round robin. Get a bracket with standard seeding and byes for the top seeds, a match list, and — as you enter results — winners advancing, standings and the champion. Download the bracket as JSON or CSV to share it.\n\nMethodology:\n- Elimination brackets are padded to the next power of two. Seeds are placed in the standard order (1 v 8, 4 v 5, 2 v 7, 3 v 6 for eight), so the top seeds can only meet late and the byes go to the highest seeds.\n- Double elimination: losers of winners-round 1 meet in losers-round 1; afterwards losers-bracket rounds alternate between taking in the losers of the next winners round (in reversed order on alternate rounds to reduce early rematches) and pairing up survivors. The winners-bracket champion meets the losers-bracket champion in the grand final; if the losers-bracket player wins it, an optional reset match decides.\n- Round robin uses the circle method (Berger tables): with an odd number of players one player rests each round. Standings: 3 points for a win, 1 for a draw, then wins, then name.\n- Results advance the winner (and, in double elimination, drop the loser into the losers bracket). Results naming someone who is not in that match, or for matches whose players are not known yet, are ignored with a warning.\n- A random draw shuffles the entrants with a seeded generator, so the same seed always gives the same bracket. To share a bracket, download the JSON (it contains the input, so it can be re-run via the API) or the CSV match list.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "tournament_bracket_maker",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TournamentBracketMakerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Six players, single elimination, two rounds played",
                  "value": {
                    "title": "Office ping-pong",
                    "entrants": [
                      "Ana",
                      "Ben",
                      "Chloe",
                      "Dev",
                      "Emil",
                      "Fatima"
                    ],
                    "format": "single",
                    "seeding": "listed",
                    "thirdPlace": true,
                    "results": [
                      {
                        "match": "W1-2",
                        "winner": "Dev",
                        "score": "11–8"
                      },
                      {
                        "match": "W1-4",
                        "winner": "Fatima",
                        "score": "11–9"
                      },
                      {
                        "match": "W2-1",
                        "winner": "Ana",
                        "score": "2–1"
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Double elimination for 8 teams",
                  "value": {
                    "title": "Pub quiz cup",
                    "entrants": [
                      "Quizzly Bears",
                      "Smarty Pints",
                      "Les Quizerables",
                      "Agatha Quiztie",
                      "The Know-Its",
                      "Trivia Newton-John",
                      "Universally Challenged",
                      "Brainy Bunch"
                    ],
                    "format": "double",
                    "seeding": "listed",
                    "results": [
                      {
                        "match": "W1-1",
                        "winner": "Quizzly Bears"
                      },
                      {
                        "match": "W1-2",
                        "winner": "The Know-Its"
                      }
                    ]
                  }
                },
                "example3": {
                  "summary": "Round robin for five",
                  "value": {
                    "title": "Chess club",
                    "entrants": [
                      "Ivy",
                      "Jon",
                      "Kai",
                      "Lea",
                      "Mo"
                    ],
                    "format": "round-robin",
                    "seeding": "random",
                    "seed": "autumn",
                    "results": [
                      {
                        "match": "R1-1",
                        "winner": "draw"
                      },
                      {
                        "match": "R1-2",
                        "winner": "Kai"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TournamentBracketMakerResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/workflows": {
      "get": {
        "operationId": "listWorkflows",
        "summary": "Multi-tool workflows",
        "responses": {
          "200": {
            "description": "Workflow list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ChoreRouletteInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "people": {
            "title": "People",
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Name",
                  "type": "string",
                  "maxLength": 40
                },
                "capacity": {
                  "title": "Capacity",
                  "description": "1 = a full share; 0.5 = half a share (e.g. a younger child).",
                  "type": "number",
                  "minimum": 0.1,
                  "maximum": 10,
                  "default": 1
                }
              },
              "required": [
                "name"
              ]
            }
          },
          "chores": {
            "title": "Chores",
            "type": "array",
            "minItems": 1,
            "maxItems": 60,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "chore": {
                  "title": "Chore",
                  "type": "string",
                  "maxLength": 60
                },
                "effort": {
                  "title": "Effort points",
                  "description": "E.g. minutes per week, or 1–5 for how unpleasant it is.",
                  "type": "number",
                  "minimum": 0.1,
                  "maximum": 100,
                  "default": 1
                },
                "count": {
                  "title": "Times per week",
                  "description": "Each occurrence is assigned separately (e.g. dishes ×7 shares out the days).",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 14,
                  "default": 1
                }
              },
              "required": [
                "chore"
              ]
            }
          },
          "lastWeek": {
            "title": "Last week (optional)",
            "description": "Who did which chore last week, for the “not again” rule.",
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "chore": {
                  "title": "Chore",
                  "type": "string",
                  "maxLength": 60
                },
                "person": {
                  "title": "Person",
                  "type": "string",
                  "maxLength": 40
                }
              },
              "required": [
                "chore",
                "person"
              ]
            }
          },
          "avoidRepeats": {
            "title": "Avoid giving anyone the same chore as last week",
            "type": "boolean",
            "default": true
          },
          "seed": {
            "title": "Seed (optional)",
            "description": "Same seed + same inputs = same draw. Leave empty to derive it from the inputs; change it (e.g. to the week number) to re-spin.",
            "type": "string",
            "maxLength": 60
          }
        },
        "required": [
          "people",
          "chores"
        ]
      },
      "ChoreRouletteResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "seed",
              "seedDerived",
              "assignments",
              "loads",
              "totalEffort",
              "spread",
              "fairnessPercent",
              "repeats",
              "unavoidableRepeats"
            ],
            "properties": {
              "seed": {
                "type": "string"
              },
              "seedDerived": {
                "type": "boolean"
              },
              "assignments": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "chore",
                    "effort",
                    "person",
                    "repeatOfLastWeek"
                  ],
                  "properties": {
                    "chore": {
                      "type": "string"
                    },
                    "effort": {
                      "type": "number"
                    },
                    "person": {
                      "type": "string"
                    },
                    "repeatOfLastWeek": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "loads": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "person",
                    "capacity",
                    "effort",
                    "target",
                    "share",
                    "chores",
                    "deviation"
                  ],
                  "properties": {
                    "person": {
                      "type": "string"
                    },
                    "capacity": {
                      "type": "number"
                    },
                    "effort": {
                      "type": "number"
                    },
                    "target": {
                      "type": "number"
                    },
                    "share": {
                      "type": "number"
                    },
                    "chores": {
                      "type": "integer"
                    },
                    "deviation": {
                      "type": "number"
                    }
                  }
                }
              },
              "totalEffort": {
                "type": "number"
              },
              "spread": {
                "type": "number",
                "description": "Max − min of effort ÷ capacity, i.e. points per full share (0 = perfectly even)."
              },
              "fairnessPercent": {
                "type": "number",
                "description": "100 × (1 − Σ|effort − fair share| ÷ (2 × total effort)); 100 = everyone exactly at their fair share."
              },
              "repeats": {
                "type": "integer"
              },
              "unavoidableRepeats": {
                "type": "integer"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "EloGlickoRatingLabInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "games": {
            "title": "Games",
            "description": "One row per game. The score is from the first player's point of view. Games with the same period number form one Glicko rating period.",
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "period": {
                  "title": "Period",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 10000,
                  "default": 1
                },
                "player": {
                  "title": "Player",
                  "type": "string",
                  "maxLength": 40
                },
                "opponent": {
                  "title": "Opponent",
                  "type": "string",
                  "maxLength": 40
                },
                "score": {
                  "title": "Result for player",
                  "type": "string",
                  "enum": [
                    "1",
                    "0.5",
                    "0"
                  ],
                  "default": "1"
                }
              },
              "required": [
                "player",
                "opponent",
                "score"
              ]
            }
          },
          "players": {
            "title": "Starting ratings (optional)",
            "description": "Players not listed start at 1500, RD 350, volatility 0.06 (Glickman's defaults for unrated players).",
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Name",
                  "type": "string",
                  "maxLength": 40
                },
                "rating": {
                  "title": "Rating",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 4000
                },
                "rd": {
                  "title": "RD",
                  "type": "number",
                  "minimum": 1,
                  "maximum": 350
                },
                "volatility": {
                  "title": "Volatility σ",
                  "type": "number",
                  "minimum": 0.0001,
                  "maximum": 1
                }
              },
              "required": [
                "name"
              ]
            }
          },
          "eloK": {
            "title": "Elo K-factor",
            "type": "string",
            "enum": [
              "10",
              "16",
              "20",
              "24",
              "32",
              "40",
              "fide"
            ],
            "default": "32"
          },
          "tau": {
            "title": "Glicko-2 τ (volatility constraint)",
            "description": "Glickman suggests 0.3–1.2; smaller values change the volatility more slowly.",
            "type": "number",
            "minimum": 0.2,
            "maximum": 1.2,
            "default": 0.5
          },
          "glickoC": {
            "title": "Glicko-1 c (RD growth per idle period)",
            "description": "34.6 lets an RD of 50 grow back to 350 in 100 periods.",
            "type": "number",
            "minimum": 0,
            "maximum": 200,
            "default": 34.6
          },
          "playerA": {
            "title": "Win probability: player A",
            "description": "Default: the two highest-rated players.",
            "type": "string",
            "maxLength": 40
          },
          "playerB": {
            "title": "Win probability: player B",
            "type": "string",
            "maxLength": 40
          },
          "chartSystem": {
            "title": "Chart",
            "type": "string",
            "enum": [
              "glicko2",
              "glicko1",
              "elo"
            ],
            "default": "glicko2"
          }
        },
        "required": [
          "games"
        ]
      },
      "EloGlickoRatingLabResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "required": [
              "players",
              "periods",
              "winProbability"
            ],
            "properties": {
              "periods": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "players": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "name",
                    "games",
                    "score",
                    "elo",
                    "glicko1",
                    "glicko2"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "games": {
                      "type": "integer"
                    },
                    "score": {
                      "type": "number"
                    },
                    "elo": {
                      "type": "number"
                    },
                    "glicko1": {
                      "type": "object",
                      "required": [
                        "rating",
                        "rd"
                      ],
                      "properties": {
                        "rating": {
                          "type": "number"
                        },
                        "rd": {
                          "type": "number"
                        }
                      }
                    },
                    "glicko2": {
                      "type": "object",
                      "required": [
                        "rating",
                        "rd",
                        "volatility"
                      ],
                      "properties": {
                        "rating": {
                          "type": "number"
                        },
                        "rd": {
                          "type": "number"
                        },
                        "volatility": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              },
              "history": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "period",
                    "player",
                    "elo",
                    "glicko1",
                    "glicko2",
                    "rd2"
                  ],
                  "properties": {
                    "period": {
                      "type": "integer"
                    },
                    "player": {
                      "type": "string"
                    },
                    "elo": {
                      "type": "number"
                    },
                    "glicko1": {
                      "type": "number"
                    },
                    "glicko2": {
                      "type": "number"
                    },
                    "rd2": {
                      "type": "number"
                    }
                  }
                }
              },
              "winProbability": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "a": {
                    "type": "string"
                  },
                  "b": {
                    "type": "string"
                  },
                  "elo": {
                    "type": "number"
                  },
                  "glicko1": {
                    "type": "number"
                  },
                  "glicko2": {
                    "type": "number"
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "EmojiCipherPlaygroundInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "action": {
            "title": "Action",
            "type": "string",
            "enum": [
              "encode",
              "decode"
            ],
            "default": "encode"
          },
          "scheme": {
            "title": "Cipher",
            "type": "string",
            "enum": [
              "substitution",
              "caesar",
              "base-emoji"
            ],
            "default": "substitution"
          },
          "text": {
            "title": "Text",
            "type": "string",
            "maxLength": 5000
          },
          "key": {
            "title": "Key",
            "description": "Substitution: shuffles the emoji alphabet. Base-emoji: optional shuffle of the byte table (empty = standard table). Not used by Caesar.",
            "type": "string",
            "maxLength": 100,
            "default": "emoji"
          },
          "shift": {
            "title": "Caesar shift",
            "description": "Letters move by this many places (mod 26), digits too (mod 10).",
            "type": "integer",
            "minimum": -100,
            "maximum": 100,
            "default": 3
          }
        },
        "required": [
          "text"
        ]
      },
      "EmojiCipherPlaygroundResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "action",
              "scheme",
              "output",
              "inputLength",
              "outputLength",
              "roundTrip",
              "alphabet",
              "topSymbols",
              "keySpaceBits"
            ],
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "encode",
                  "decode"
                ]
              },
              "scheme": {
                "type": "string",
                "enum": [
                  "substitution",
                  "caesar",
                  "base-emoji"
                ]
              },
              "output": {
                "type": "string"
              },
              "inputLength": {
                "type": "integer",
                "description": "Unicode code points."
              },
              "outputLength": {
                "type": "integer",
                "description": "Unicode code points."
              },
              "roundTrip": {
                "type": "boolean",
                "description": "Encode: decoding the output gives back the input exactly. Decode: encoding the output gives back the input (ignoring emoji variation selectors)."
              },
              "alphabet": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "plain",
                    "emoji"
                  ],
                  "properties": {
                    "plain": {
                      "type": "string"
                    },
                    "emoji": {
                      "type": "string"
                    }
                  }
                }
              },
              "topSymbols": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "symbol",
                    "count"
                  ],
                  "properties": {
                    "symbol": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    }
                  }
                }
              },
              "keySpaceBits": {
                "type": "number",
                "description": "log2 of the number of possible keys — large or not, frequency analysis breaks these ciphers."
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "EquationArtStudioInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "curve": {
            "title": "Curve",
            "type": "string",
            "enum": [
              "parametric",
              "polar",
              "lissajous",
              "rose",
              "spirograph",
              "butterfly",
              "heart"
            ],
            "default": "spirograph"
          },
          "xExpr": {
            "title": "x(t)",
            "description": "Used for “my own parametric curve”. Variables: t, a, b, c, d. Implicit multiplication works: 2t, 3sin(t).",
            "type": "string",
            "maxLength": 400,
            "default": "cos(t) + cos(6t)/2 + sin(14t)/3"
          },
          "yExpr": {
            "title": "y(t)",
            "type": "string",
            "maxLength": 400,
            "default": "sin(t) + sin(6t)/2 + cos(14t)/3"
          },
          "rExpr": {
            "title": "r(θ)",
            "description": "Used for “my own polar curve”. Write θ as theta or t.",
            "type": "string",
            "maxLength": 400,
            "default": "1 + 0.5*cos(5theta)"
          },
          "tMin": {
            "title": "t from",
            "description": "A number or constant expression such as 0 or -pi. Empty = preset default (0).",
            "type": "string",
            "maxLength": 60
          },
          "tMax": {
            "title": "t to",
            "description": "E.g. 2*pi. Empty = preset default (2π, or a full period for roses and spirographs).",
            "type": "string",
            "maxLength": 60
          },
          "a": {
            "title": "a",
            "description": "Parameter for presets and your expressions. Empty = preset default.",
            "type": "number",
            "minimum": -1000,
            "maximum": 1000
          },
          "b": {
            "title": "b",
            "type": "number",
            "minimum": -1000,
            "maximum": 1000
          },
          "c": {
            "title": "c",
            "type": "number",
            "minimum": -1000,
            "maximum": 1000
          },
          "d": {
            "title": "d",
            "type": "number",
            "minimum": -1000,
            "maximum": 1000
          },
          "samples": {
            "title": "Samples",
            "description": "More samples = smoother curves. Samples × symmetry copies is limited to 24,000.",
            "type": "integer",
            "minimum": 50,
            "maximum": 10000,
            "default": 2000
          },
          "symmetry": {
            "title": "Rotational symmetry (copies)",
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "default": 1
          },
          "palette": {
            "title": "Palette",
            "type": "string",
            "enum": [
              "sunset",
              "ocean",
              "forest",
              "neon",
              "rainbow",
              "mono"
            ],
            "default": "sunset"
          },
          "strokeWidth": {
            "title": "Line width",
            "type": "number",
            "minimum": 0.25,
            "maximum": 12,
            "default": 2,
            "x-unit": "px"
          },
          "png": {
            "title": "Also render a PNG download",
            "type": "boolean",
            "default": true
          }
        },
        "required": []
      },
      "EquationArtStudioResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "curve",
              "curveName",
              "kind",
              "x",
              "y",
              "r",
              "tMin",
              "tMax",
              "params",
              "samples",
              "symmetry",
              "palette",
              "invalidSamples",
              "drawnPoints",
              "closed",
              "arcLength",
              "bbox",
              "svgBytes",
              "svgArtifact",
              "pngArtifact"
            ],
            "properties": {
              "curve": {
                "type": "string"
              },
              "curveName": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "parametric",
                  "polar"
                ]
              },
              "x": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "y": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "r": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "tMin": {
                "type": "number"
              },
              "tMax": {
                "type": "number"
              },
              "params": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "a",
                  "b",
                  "c",
                  "d"
                ],
                "properties": {
                  "a": {
                    "type": "number"
                  },
                  "b": {
                    "type": "number"
                  },
                  "c": {
                    "type": "number"
                  },
                  "d": {
                    "type": "number"
                  }
                }
              },
              "samples": {
                "type": "integer"
              },
              "symmetry": {
                "type": "integer"
              },
              "palette": {
                "type": "string"
              },
              "invalidSamples": {
                "type": "integer"
              },
              "drawnPoints": {
                "type": "integer"
              },
              "closed": {
                "type": "boolean"
              },
              "arcLength": {
                "type": "number",
                "description": "Length of one copy of the curve in curve units."
              },
              "bbox": {
                "type": "array",
                "items": {
                  "type": "number"
                },
                "description": "[minX, minY, maxX, maxY] in curve units, including symmetry copies."
              },
              "svgBytes": {
                "type": "integer"
              },
              "svgArtifact": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "path",
                  "bytes",
                  "sha256"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  }
                }
              },
              "pngArtifact": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "path",
                  "bytes",
                  "sha256"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "KeyboardHeatmapVisualizerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "text": {
            "title": "Text",
            "type": "string",
            "maxLength": 100000
          },
          "layout": {
            "title": "Layout for the heatmap",
            "type": "string",
            "enum": [
              "qwerty",
              "qwertz",
              "azerty",
              "dvorak",
              "colemak"
            ],
            "default": "qwerty"
          },
          "compare": {
            "title": "Compare with",
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "qwerty",
                "qwertz",
                "azerty",
                "dvorak",
                "colemak"
              ]
            },
            "default": [
              "qwerty",
              "qwertz",
              "azerty",
              "dvorak",
              "colemak"
            ]
          }
        },
        "required": [
          "text"
        ]
      },
      "KeyboardHeatmapVisualizerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "characters",
              "layout",
              "topKeys",
              "comparison",
              "bestForHomeRow",
              "bestForSameFinger"
            ],
            "properties": {
              "characters": {
                "type": "integer"
              },
              "layout": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "layout",
                  "name",
                  "keystrokes",
                  "typed",
                  "unmapped",
                  "approximated",
                  "rows",
                  "fingers",
                  "leftHand",
                  "rightHand",
                  "homeRow",
                  "sameFingerBigrams",
                  "handAlternation",
                  "modifierPresses"
                ],
                "properties": {
                  "layout": {
                    "type": "string",
                    "enum": [
                      "qwerty",
                      "qwertz",
                      "azerty",
                      "dvorak",
                      "colemak"
                    ]
                  },
                  "name": {
                    "type": "string"
                  },
                  "keystrokes": {
                    "type": "integer",
                    "description": "Key presses including Shift/AltGr."
                  },
                  "typed": {
                    "type": "integer",
                    "description": "Characters typed (excluding modifier presses)."
                  },
                  "unmapped": {
                    "type": "integer"
                  },
                  "approximated": {
                    "type": "integer"
                  },
                  "rows": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    },
                    "description": "% of characters per row: number, top, home, bottom, space."
                  },
                  "fingers": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    },
                    "description": "% per finger: L-pinky, L-ring, L-middle, L-index, R-index, R-middle, R-ring, R-pinky, Thumbs."
                  },
                  "leftHand": {
                    "type": "number"
                  },
                  "rightHand": {
                    "type": "number"
                  },
                  "homeRow": {
                    "type": "number",
                    "description": "% of non-space characters typed on the home row."
                  },
                  "sameFingerBigrams": {
                    "type": "number"
                  },
                  "handAlternation": {
                    "type": "number"
                  },
                  "modifierPresses": {
                    "type": "integer"
                  }
                }
              },
              "topKeys": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "key",
                    "count",
                    "percent"
                  ],
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "percent": {
                      "type": "number"
                    }
                  }
                }
              },
              "comparison": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "layout",
                    "name",
                    "keystrokes",
                    "typed",
                    "unmapped",
                    "approximated",
                    "rows",
                    "fingers",
                    "leftHand",
                    "rightHand",
                    "homeRow",
                    "sameFingerBigrams",
                    "handAlternation",
                    "modifierPresses"
                  ],
                  "properties": {
                    "layout": {
                      "type": "string",
                      "enum": [
                        "qwerty",
                        "qwertz",
                        "azerty",
                        "dvorak",
                        "colemak"
                      ]
                    },
                    "name": {
                      "type": "string"
                    },
                    "keystrokes": {
                      "type": "integer",
                      "description": "Key presses including Shift/AltGr."
                    },
                    "typed": {
                      "type": "integer",
                      "description": "Characters typed (excluding modifier presses)."
                    },
                    "unmapped": {
                      "type": "integer"
                    },
                    "approximated": {
                      "type": "integer"
                    },
                    "rows": {
                      "type": "array",
                      "items": {
                        "type": "number"
                      },
                      "description": "% of characters per row: number, top, home, bottom, space."
                    },
                    "fingers": {
                      "type": "array",
                      "items": {
                        "type": "number"
                      },
                      "description": "% per finger: L-pinky, L-ring, L-middle, L-index, R-index, R-middle, R-ring, R-pinky, Thumbs."
                    },
                    "leftHand": {
                      "type": "number"
                    },
                    "rightHand": {
                      "type": "number"
                    },
                    "homeRow": {
                      "type": "number",
                      "description": "% of non-space characters typed on the home row."
                    },
                    "sameFingerBigrams": {
                      "type": "number"
                    },
                    "handAlternation": {
                      "type": "number"
                    },
                    "modifierPresses": {
                      "type": "integer"
                    }
                  }
                }
              },
              "bestForHomeRow": {
                "type": "string"
              },
              "bestForSameFinger": {
                "type": "string"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "RuntimeMarathonPlannerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "titles": {
            "title": "Movies and series",
            "description": "Movie: runtime in minutes. Series: minutes per episode, episodes per season and seasons.",
            "type": "array",
            "minItems": 1,
            "maxItems": 30,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "title": {
                  "title": "Title",
                  "type": "string",
                  "maxLength": 80
                },
                "minutes": {
                  "title": "Minutes (per episode)",
                  "type": "number",
                  "minimum": 1,
                  "maximum": 600
                },
                "episodes": {
                  "title": "Episodes per season",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 500,
                  "default": 1
                },
                "seasons": {
                  "title": "Seasons",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60,
                  "default": 1
                }
              },
              "required": [
                "title",
                "minutes"
              ]
            }
          },
          "start": {
            "title": "Start",
            "description": "When the marathon begins (local time). Local date-time YYYY-MM-DDTHH:mm",
            "type": "string"
          },
          "dailyStart": {
            "title": "On the following days, start at",
            "type": "string",
            "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
            "default": "18:00"
          },
          "maxHoursPerDay": {
            "title": "Maximum viewing per day",
            "description": "Watching time only; breaks come on top.",
            "type": "number",
            "minimum": 0.5,
            "maximum": 20,
            "default": 4,
            "x-unit": "h"
          },
          "breakMinutes": {
            "title": "Break between episodes",
            "type": "number",
            "minimum": 0,
            "maximum": 240,
            "default": 10,
            "x-unit": "min"
          },
          "titleBreakMinutes": {
            "title": "Break between titles",
            "type": "number",
            "minimum": 0,
            "maximum": 480,
            "default": 20,
            "x-unit": "min"
          },
          "allowSplit": {
            "title": "Allow pausing a movie or episode and continuing the next day",
            "type": "boolean",
            "default": false
          },
          "finishBy": {
            "title": "Finish by",
            "description": "Optional: checks whether you make it by the end of this day.",
            "type": "string",
            "format": "date"
          }
        },
        "required": [
          "titles",
          "start"
        ]
      },
      "RuntimeMarathonPlannerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "totalWatchMinutes",
              "totalBreakMinutes",
              "items",
              "days",
              "firstStart",
              "finish",
              "finishBy",
              "onTime",
              "requiredHoursPerDay",
              "titles",
              "perDay",
              "schedule",
              "sessions"
            ],
            "properties": {
              "totalWatchMinutes": {
                "type": "number"
              },
              "totalBreakMinutes": {
                "type": "number"
              },
              "items": {
                "type": "integer",
                "description": "Movies + episodes."
              },
              "days": {
                "type": "integer",
                "description": "Viewing days, counted from the start date to the last session."
              },
              "firstStart": {
                "type": "string"
              },
              "finish": {
                "type": "string",
                "description": "Local date-time the last item ends."
              },
              "finishBy": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "onTime": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "requiredHoursPerDay": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Viewing hours per day needed to finish by the date (watching time only)."
              },
              "titles": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "title",
                    "items",
                    "minutes",
                    "start",
                    "end"
                  ],
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "items": {
                      "type": "integer"
                    },
                    "minutes": {
                      "type": "number"
                    },
                    "start": {
                      "type": "string"
                    },
                    "end": {
                      "type": "string"
                    }
                  }
                }
              },
              "perDay": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "day",
                    "date",
                    "watchMinutes",
                    "from",
                    "to"
                  ],
                  "properties": {
                    "day": {
                      "type": "integer"
                    },
                    "date": {
                      "type": "string"
                    },
                    "watchMinutes": {
                      "type": "number"
                    },
                    "from": {
                      "type": "string"
                    },
                    "to": {
                      "type": "string"
                    }
                  }
                }
              },
              "schedule": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "title",
                    "label",
                    "part",
                    "day",
                    "date",
                    "start",
                    "end",
                    "minutes"
                  ],
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "part": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "day": {
                      "type": "integer"
                    },
                    "date": {
                      "type": "string"
                    },
                    "start": {
                      "type": "string"
                    },
                    "end": {
                      "type": "string"
                    },
                    "minutes": {
                      "type": "number"
                    }
                  }
                }
              },
              "sessions": {
                "type": "integer"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "MovieNightConsensusPickerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "candidates": {
            "title": "Candidates (one per line)",
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 20
          },
          "ballots": {
            "title": "Ballots",
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "voter": {
                  "title": "Name",
                  "type": "string",
                  "maxLength": 40
                },
                "ranking": {
                  "title": "Ranking, best first",
                  "description": "Separate with “>” or commas. Unlisted films count as tied last.",
                  "type": "string",
                  "maxLength": 1000
                },
                "veto": {
                  "title": "Veto (optional)",
                  "description": "Films this person refuses to watch, separated by commas.",
                  "type": "string",
                  "maxLength": 400
                }
              },
              "required": [
                "ranking"
              ]
            }
          },
          "vetoMode": {
            "title": "Vetoes",
            "type": "string",
            "enum": [
              "remove",
              "ignore"
            ],
            "default": "remove"
          },
          "approvalCount": {
            "title": "Approval: top N of each ranking count as approved",
            "description": "Empty = half of the candidates, rounded up.",
            "type": "integer",
            "minimum": 1,
            "maximum": 20
          },
          "seed": {
            "title": "Tie-break seed (optional)",
            "description": "Ties are broken by a seeded random order of the candidates. Empty = derived from the inputs.",
            "type": "string",
            "maxLength": 60
          }
        },
        "required": [
          "candidates",
          "ballots"
        ]
      },
      "MovieNightConsensusPickerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "seed",
              "candidates",
              "vetoed",
              "eligible",
              "voters",
              "methods",
              "condorcetWinner",
              "consensus",
              "recommendation",
              "borda",
              "approval",
              "irvRounds",
              "pairwise",
              "tieBreakOrder"
            ],
            "properties": {
              "seed": {
                "type": "string"
              },
              "candidates": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "vetoed": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "candidate",
                    "by"
                  ],
                  "properties": {
                    "candidate": {
                      "type": "string"
                    },
                    "by": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "eligible": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "voters": {
                "type": "integer"
              },
              "methods": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "method",
                    "winner",
                    "tieBroken",
                    "explanation"
                  ],
                  "properties": {
                    "method": {
                      "type": "string"
                    },
                    "winner": {
                      "type": "string"
                    },
                    "tieBroken": {
                      "type": "boolean"
                    },
                    "explanation": {
                      "type": "string"
                    }
                  }
                }
              },
              "condorcetWinner": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "consensus": {
                "type": "boolean",
                "description": "All four methods pick the same film."
              },
              "recommendation": {
                "type": "string"
              },
              "borda": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "candidate",
                    "points"
                  ],
                  "properties": {
                    "candidate": {
                      "type": "string"
                    },
                    "points": {
                      "type": "number"
                    }
                  }
                }
              },
              "approval": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "candidate",
                    "approvals"
                  ],
                  "properties": {
                    "candidate": {
                      "type": "string"
                    },
                    "approvals": {
                      "type": "integer"
                    }
                  }
                }
              },
              "irvRounds": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "round",
                    "counts",
                    "exhausted",
                    "eliminated",
                    "tieBreak"
                  ],
                  "properties": {
                    "round": {
                      "type": "integer"
                    },
                    "counts": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    },
                    "exhausted": {
                      "type": "integer"
                    },
                    "eliminated": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "tieBreak": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "pairwise": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "a",
                    "b",
                    "aOverB",
                    "bOverA"
                  ],
                  "properties": {
                    "a": {
                      "type": "string"
                    },
                    "b": {
                      "type": "string"
                    },
                    "aOverB": {
                      "type": "integer"
                    },
                    "bOverA": {
                      "type": "integer"
                    }
                  }
                }
              },
              "tieBreakOrder": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "PadelPickleballSchedulerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "sport": {
            "title": "Sport",
            "type": "string",
            "enum": [
              "padel",
              "pickleball"
            ],
            "default": "padel"
          },
          "format": {
            "title": "Format",
            "type": "string",
            "enum": [
              "americano",
              "mexicano"
            ],
            "default": "americano"
          },
          "players": {
            "title": "Players",
            "description": "One name per line, at least 4. For Mexicano, list them by strength for the first round.",
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 48
          },
          "courts": {
            "title": "Courts",
            "type": "integer",
            "minimum": 1,
            "maximum": 12,
            "default": 2
          },
          "rounds": {
            "title": "Rounds",
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "default": 6
          },
          "pointsPerMatch": {
            "title": "Points per match",
            "description": "Americano/Mexicano usually play a fixed total (e.g. 24 or 32 points); pickleball games often go to 11. Used only to check scores; 0 = no check.",
            "type": "integer",
            "minimum": 0,
            "maximum": 100,
            "default": 24
          },
          "scores": {
            "title": "Scores",
            "description": "Optional. Round, court and the points of team A (left) and team B (right) as shown in the schedule.",
            "type": "array",
            "maxItems": 200,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "round": {
                  "title": "Round",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 30
                },
                "court": {
                  "title": "Court",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 12
                },
                "scoreA": {
                  "title": "Team A points",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 999
                },
                "scoreB": {
                  "title": "Team B points",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 999
                }
              },
              "required": [
                "round",
                "court",
                "scoreA",
                "scoreB"
              ]
            }
          }
        },
        "required": [
          "players"
        ]
      },
      "PadelPickleballSchedulerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "format",
              "activeCourts",
              "roundsPlanned",
              "roundsRequested",
              "pendingRound",
              "rounds",
              "fairness",
              "standings"
            ],
            "properties": {
              "format": {
                "type": "string",
                "enum": [
                  "americano",
                  "mexicano"
                ]
              },
              "activeCourts": {
                "type": "integer"
              },
              "roundsPlanned": {
                "type": "integer"
              },
              "roundsRequested": {
                "type": "integer"
              },
              "pendingRound": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Mexicano: the first round that needs the previous round's scores before it can be drawn."
              },
              "rounds": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "round",
                    "matches",
                    "sittingOut"
                  ],
                  "properties": {
                    "round": {
                      "type": "integer"
                    },
                    "sittingOut": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "matches": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "court",
                          "teamA",
                          "teamB",
                          "scoreA",
                          "scoreB"
                        ],
                        "properties": {
                          "court": {
                            "type": "integer"
                          },
                          "teamA": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "teamB": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "scoreA": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "scoreB": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              },
              "fairness": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "player",
                    "matches",
                    "sitOuts",
                    "distinctPartners",
                    "repeatedPartners",
                    "distinctOpponents"
                  ],
                  "properties": {
                    "player": {
                      "type": "string"
                    },
                    "matches": {
                      "type": "integer"
                    },
                    "sitOuts": {
                      "type": "integer"
                    },
                    "distinctPartners": {
                      "type": "integer"
                    },
                    "repeatedPartners": {
                      "type": "integer"
                    },
                    "distinctOpponents": {
                      "type": "integer"
                    }
                  }
                }
              },
              "standings": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "rank",
                    "player",
                    "points",
                    "pointsAgainst",
                    "diff",
                    "wins",
                    "draws",
                    "losses",
                    "matches"
                  ],
                  "properties": {
                    "rank": {
                      "type": "integer"
                    },
                    "player": {
                      "type": "string"
                    },
                    "points": {
                      "type": "integer"
                    },
                    "pointsAgainst": {
                      "type": "integer"
                    },
                    "diff": {
                      "type": "integer"
                    },
                    "wins": {
                      "type": "integer"
                    },
                    "draws": {
                      "type": "integer"
                    },
                    "losses": {
                      "type": "integer"
                    },
                    "matches": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "PasswordEntropyRaceInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "passwords": {
            "title": "Passwords to compare (one per line)",
            "description": "Between 2 and 6 lines, up to 128 characters each. Do not paste passwords you really use: use look-alikes with the same structure. With JavaScript on, the comparison runs in your browser only; without JavaScript (and via the API/MCP) the text is sent to the server, used for the calculation and not stored.",
            "type": "string",
            "maxLength": 780
          }
        },
        "required": [
          "passwords"
        ]
      },
      "PasswordEntropyRaceResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "entries",
              "ranking",
              "winner",
              "tie",
              "scenarios",
              "wordlistSize"
            ],
            "properties": {
              "entries": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "label",
                    "masked",
                    "length",
                    "pool",
                    "charClasses",
                    "charsetBits",
                    "patternBits",
                    "structureBits",
                    "patterns",
                    "crackSeconds",
                    "crackTime",
                    "rating"
                  ],
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "masked": {
                      "type": "string",
                      "description": "First and last character only."
                    },
                    "length": {
                      "type": "integer"
                    },
                    "pool": {
                      "type": "integer",
                      "description": "Character pool size implied by the character classes used."
                    },
                    "charClasses": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "enum": [
                          "digit",
                          "lower",
                          "other",
                          "symbol",
                          "upper"
                        ]
                      }
                    },
                    "charsetBits": {
                      "type": "number"
                    },
                    "patternBits": {
                      "type": "number"
                    },
                    "structureBits": {
                      "type": "number"
                    },
                    "patterns": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "additionalProperties": false,
                        "required": [
                          "kind",
                          "token",
                          "bits",
                          "note"
                        ],
                        "properties": {
                          "kind": {
                            "type": "string",
                            "enum": [
                              "dictionary",
                              "reversed",
                              "l33t",
                              "sequence",
                              "repeat",
                              "keyboard",
                              "date",
                              "separator",
                              "wordlike",
                              "bruteforce"
                            ]
                          },
                          "token": {
                            "type": "string"
                          },
                          "bits": {
                            "type": "number"
                          },
                          "note": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "crackSeconds": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "number"
                      }
                    },
                    "crackTime": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string"
                      }
                    },
                    "rating": {
                      "type": "string",
                      "enum": [
                        "very weak",
                        "weak",
                        "reasonable",
                        "strong",
                        "very strong"
                      ]
                    }
                  }
                }
              },
              "ranking": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "winner": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "tie": {
                "type": "boolean"
              },
              "scenarios": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "label",
                    "guessesPerSecond"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "guessesPerSecond": {
                      "type": "number"
                    }
                  }
                }
              },
              "wordlistSize": {
                "type": "integer"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "RegexGolfArenaInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "puzzle": {
            "title": "Puzzle",
            "type": "string",
            "enum": [
              "warm-up",
              "ending",
              "hex-colours",
              "doubles",
              "abba",
              "iso-dates",
              "powers-of-two",
              "vowel-parade"
            ],
            "default": "warm-up"
          },
          "regex": {
            "title": "Your regex",
            "description": "Without slashes. Tested with JavaScript RegExp.test (a match anywhere counts unless you anchor with ^ and $). Leave empty to see the puzzle and its leaderboard.",
            "type": "string",
            "maxLength": 200
          },
          "flags": {
            "title": "Flags",
            "description": "Flags do not count towards the length.",
            "type": "string",
            "enum": [
              "none",
              "i",
              "u",
              "iu"
            ],
            "default": "none"
          },
          "nickname": {
            "title": "Nickname for the leaderboard (optional)",
            "description": "Letters, digits, spaces, dot, dash and underscore; up to 24 characters. Shown publicly with the length and date — the regex itself is never shown.",
            "type": "string",
            "maxLength": 24
          },
          "dryRun": {
            "title": "Dry run (test only, do not submit to the leaderboard)",
            "type": "boolean",
            "default": false
          }
        },
        "required": []
      },
      "RegexGolfArenaResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "puzzle",
              "title",
              "hint",
              "match",
              "dontMatch",
              "par",
              "submission",
              "leaderboard"
            ],
            "properties": {
              "puzzle": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "hint": {
                "type": "string"
              },
              "match": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "dontMatch": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "par": {
                "type": "integer",
                "description": "Length of a known (not necessarily shortest) solution."
              },
              "submission": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "regex",
                  "flags",
                  "length",
                  "perfect",
                  "score",
                  "matched",
                  "missed",
                  "rejected",
                  "falsePositives",
                  "dryRun",
                  "leaderboardRank",
                  "leaderboardNote"
                ],
                "properties": {
                  "regex": {
                    "type": "string"
                  },
                  "flags": {
                    "type": "string"
                  },
                  "length": {
                    "type": "integer"
                  },
                  "perfect": {
                    "type": "boolean"
                  },
                  "score": {
                    "type": "integer",
                    "description": "10 × (correct matches + correct rejections) − length."
                  },
                  "matched": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "missed": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "rejected": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "falsePositives": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "dryRun": {
                    "type": "boolean"
                  },
                  "leaderboardRank": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "leaderboardNote": {
                    "type": "string"
                  }
                }
              },
              "leaderboard": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "rank",
                    "nickname",
                    "length",
                    "score",
                    "date"
                  ],
                  "properties": {
                    "rank": {
                      "type": "integer"
                    },
                    "nickname": {
                      "type": "string"
                    },
                    "length": {
                      "type": "integer"
                    },
                    "score": {
                      "type": "integer"
                    },
                    "date": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "SportsWhatIfSimulatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "mode": {
            "title": "Simulate",
            "type": "string",
            "enum": [
              "match",
              "league"
            ],
            "default": "match"
          },
          "model": {
            "title": "Team strength from",
            "type": "string",
            "enum": [
              "goals",
              "elo"
            ],
            "default": "goals"
          },
          "teams": {
            "title": "Teams",
            "type": "array",
            "minItems": 2,
            "maxItems": 16,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Team",
                  "type": "string",
                  "maxLength": 40
                },
                "goalsFor": {
                  "title": "Goals scored per game",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 10
                },
                "goalsAgainst": {
                  "title": "Goals conceded per game",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 10
                },
                "elo": {
                  "title": "Elo rating",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 4000
                }
              },
              "required": [
                "name"
              ]
            }
          },
          "homeTeam": {
            "title": "Home team (one match)",
            "description": "Empty = first team in the list.",
            "type": "string",
            "maxLength": 40
          },
          "awayTeam": {
            "title": "Away team (one match)",
            "description": "Empty = second team in the list.",
            "type": "string",
            "maxLength": 40
          },
          "neutral": {
            "title": "Neutral venue (no home advantage) for the single match",
            "type": "boolean",
            "default": false
          },
          "homeAdvantage": {
            "title": "Home advantage",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 10,
            "x-unit": "% more expected goals"
          },
          "avgGoals": {
            "title": "Average goals per match (Elo model)",
            "type": "number",
            "minimum": 0.2,
            "maximum": 12,
            "default": 2.6
          },
          "fixtures": {
            "title": "League fixtures and results",
            "description": "Leave the goals empty for games not played yet. No fixtures = a double round robin with nothing played.",
            "type": "array",
            "maxItems": 240,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "home": {
                  "title": "Home",
                  "type": "string",
                  "maxLength": 40
                },
                "away": {
                  "title": "Away",
                  "type": "string",
                  "maxLength": 40
                },
                "homeGoals": {
                  "title": "Home goals",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 99
                },
                "awayGoals": {
                  "title": "Away goals",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 99
                }
              },
              "required": [
                "home",
                "away"
              ]
            }
          },
          "whatIf": {
            "title": "What if… (changed results)",
            "description": "Override the result of a fixture (played or not) to compare the table and the odds.",
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "home": {
                  "title": "Home",
                  "type": "string",
                  "maxLength": 40
                },
                "away": {
                  "title": "Away",
                  "type": "string",
                  "maxLength": 40
                },
                "homeGoals": {
                  "title": "Home goals",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 99
                },
                "awayGoals": {
                  "title": "Away goals",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 99
                }
              },
              "required": [
                "home",
                "away",
                "homeGoals",
                "awayGoals"
              ]
            }
          },
          "simulations": {
            "title": "Simulations",
            "type": "integer",
            "minimum": 100,
            "maximum": 50000,
            "default": 5000
          },
          "seed": {
            "title": "Seed (optional)",
            "description": "Same seed = same simulated numbers. Empty = derived from the inputs.",
            "type": "string",
            "maxLength": 60
          }
        },
        "required": [
          "teams"
        ]
      },
      "SportsWhatIfSimulatorResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "mode",
              "model",
              "seed",
              "seedDerived",
              "simulations",
              "match",
              "league"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "match",
                  "league"
                ]
              },
              "model": {
                "type": "string",
                "enum": [
                  "goals",
                  "elo"
                ]
              },
              "seed": {
                "type": "string"
              },
              "seedDerived": {
                "type": "boolean"
              },
              "simulations": {
                "type": "integer"
              },
              "match": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "home",
                  "away",
                  "expectedGoals",
                  "probabilities",
                  "simulated",
                  "topScores",
                  "neutral"
                ],
                "properties": {
                  "home": {
                    "type": "string"
                  },
                  "away": {
                    "type": "string"
                  },
                  "neutral": {
                    "type": "boolean"
                  },
                  "expectedGoals": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "home",
                      "away"
                    ],
                    "properties": {
                      "home": {
                        "type": "number"
                      },
                      "away": {
                        "type": "number"
                      }
                    }
                  },
                  "probabilities": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "home",
                      "draw",
                      "away"
                    ],
                    "properties": {
                      "home": {
                        "type": "number"
                      },
                      "draw": {
                        "type": "number"
                      },
                      "away": {
                        "type": "number"
                      }
                    }
                  },
                  "simulated": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "home",
                      "draw",
                      "away"
                    ],
                    "properties": {
                      "home": {
                        "type": "number"
                      },
                      "draw": {
                        "type": "number"
                      },
                      "away": {
                        "type": "number"
                      }
                    }
                  },
                  "topScores": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "score",
                        "probability"
                      ],
                      "properties": {
                        "score": {
                          "type": "string"
                        },
                        "probability": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              },
              "league": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "played",
                  "remaining",
                  "table",
                  "odds",
                  "whatIf"
                ],
                "properties": {
                  "played": {
                    "type": "integer"
                  },
                  "remaining": {
                    "type": "integer"
                  },
                  "table": {
                    "type": "array",
                    "items": {
                      "type": "object"
                    }
                  },
                  "odds": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "team",
                        "title",
                        "top2",
                        "bottom",
                        "expectedPoints",
                        "averagePosition"
                      ],
                      "properties": {
                        "team": {
                          "type": "string"
                        },
                        "title": {
                          "type": "number"
                        },
                        "top2": {
                          "type": "number"
                        },
                        "bottom": {
                          "type": "number"
                        },
                        "expectedPoints": {
                          "type": "number"
                        },
                        "averagePosition": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "whatIf": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": false,
                    "required": [
                      "changes",
                      "table",
                      "odds"
                    ],
                    "properties": {
                      "changes": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "table": {
                        "type": "array",
                        "items": {
                          "type": "object"
                        }
                      },
                      "odds": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "additionalProperties": false,
                          "required": [
                            "team",
                            "title",
                            "top2",
                            "bottom",
                            "expectedPoints",
                            "averagePosition"
                          ],
                          "properties": {
                            "team": {
                              "type": "string"
                            },
                            "title": {
                              "type": "number"
                            },
                            "top2": {
                              "type": "number"
                            },
                            "bottom": {
                              "type": "number"
                            },
                            "expectedPoints": {
                              "type": "number"
                            },
                            "averagePosition": {
                              "type": "number"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "TournamentBracketMakerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "title": {
            "title": "Tournament name",
            "type": "string",
            "maxLength": 80,
            "default": "Tournament"
          },
          "entrants": {
            "title": "Entrants (one per line, best seed first)",
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 64
          },
          "format": {
            "title": "Format",
            "type": "string",
            "enum": [
              "single",
              "double",
              "round-robin"
            ],
            "default": "single"
          },
          "seeding": {
            "title": "Seeding",
            "type": "string",
            "enum": [
              "listed",
              "random"
            ],
            "default": "listed"
          },
          "thirdPlace": {
            "title": "Third-place match (single elimination)",
            "type": "boolean",
            "default": false
          },
          "grandFinalReset": {
            "title": "Grand-final reset if the losers-bracket player wins (double elimination)",
            "type": "boolean",
            "default": true
          },
          "results": {
            "title": "Results",
            "description": "Match id from the match list (e.g. W1-2, L1-1, GF-1, R3-2) and the winner’s name. Round robin: write “draw” for a draw.",
            "type": "array",
            "maxItems": 200,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "match": {
                  "title": "Match",
                  "type": "string",
                  "maxLength": 10
                },
                "winner": {
                  "title": "Winner",
                  "type": "string",
                  "maxLength": 40
                },
                "score": {
                  "title": "Score (optional)",
                  "type": "string",
                  "maxLength": 20
                }
              },
              "required": [
                "match",
                "winner"
              ]
            }
          },
          "seed": {
            "title": "Random draw seed (optional)",
            "description": "Only for a random draw. Empty = derived from the entrant list.",
            "type": "string",
            "maxLength": 60
          }
        },
        "required": [
          "entrants"
        ]
      },
      "TournamentBracketMakerResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "title",
              "format",
              "seed",
              "entrants",
              "bracketSize",
              "byes",
              "matches",
              "nextMatches",
              "champion",
              "standings",
              "completed"
            ],
            "properties": {
              "title": {
                "type": "string"
              },
              "format": {
                "type": "string",
                "enum": [
                  "single",
                  "double",
                  "round-robin"
                ]
              },
              "seed": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "entrants": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "seed",
                    "name"
                  ],
                  "properties": {
                    "seed": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    }
                  }
                }
              },
              "bracketSize": {
                "type": "integer"
              },
              "byes": {
                "type": "integer"
              },
              "matches": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "bracket",
                    "round",
                    "roundName",
                    "a",
                    "b",
                    "aFrom",
                    "bFrom",
                    "winner",
                    "loser",
                    "score",
                    "status"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "bracket": {
                      "type": "string",
                      "enum": [
                        "W",
                        "L",
                        "GF",
                        "3P",
                        "RR"
                      ]
                    },
                    "round": {
                      "type": "integer"
                    },
                    "roundName": {
                      "type": "string"
                    },
                    "a": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "b": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "aFrom": {
                      "type": "string"
                    },
                    "bFrom": {
                      "type": "string"
                    },
                    "winner": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "loser": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "score": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "pending",
                        "ready",
                        "done",
                        "bye",
                        "not-needed",
                        "draw"
                      ]
                    }
                  }
                }
              },
              "nextMatches": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Ids of matches whose players are known but that have no result yet."
              },
              "champion": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "standings": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "rank",
                    "name",
                    "played",
                    "won",
                    "drawn",
                    "lost",
                    "points"
                  ],
                  "properties": {
                    "rank": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "played": {
                      "type": "integer"
                    },
                    "won": {
                      "type": "integer"
                    },
                    "drawn": {
                      "type": "integer"
                    },
                    "lost": {
                      "type": "integer"
                    },
                    "points": {
                      "type": "integer"
                    }
                  }
                }
              },
              "completed": {
                "type": "integer"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}