{
  "openapi": "3.1.1",
  "info": {
    "title": "Fieldday 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://fieldday.usefulweb.app"
    }
  ],
  "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": [
                "padel-pickleball-scheduler",
                "tournament-bracket-maker",
                "elo-glicko-rating-lab",
                "sports-what-if-simulator",
                "sports-travel-fatigue-estimator",
                "running-pace-calculator",
                "one-rep-max-calculator",
                "random-generator",
                "jet-lag-planner"
              ]
            }
          }
        ],
        "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/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/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/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/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/sports-travel-fatigue-estimator/calculate": {
      "post": {
        "operationId": "calculateSportsTravelFatigueEstimator",
        "summary": "Travel distance, time-zone jumps, rest and back-to-backs per game as a fatigue score.",
        "description": "Enter a team's schedule (date, city or coordinates). For every game the estimator computes the great-circle travel distance from the previous venue, the time-zone shift and its direction, rest days, back-to-backs and schedule density, and combines them into a 0–100 fatigue score with openly documented weights. It is a heuristic for comparing schedules, not a physiological measurement.\n\nMethodology:\n- Travel distance: great-circle (haversine) distance between the previous venue (or the starting city) and this venue, with a mean Earth radius of 6371 km. Real road or flight routes are longer.\n- Time-zone shift = UTC offset at the venue − UTC offset at the previous venue, on the game date (daylight saving time included via the IANA time-zone database). Positive = eastward (clocks ahead).\n- Fatigue score (0–100, heuristic): travel 1 point per 100 km (max 30); time zones: hours of shift not yet recovered × 5 (eastward) or × 3.5 (westward), max 25, assuming recovery of 1 h per full day between games eastward and 1.5 h westward; rest: same-day game 30, back-to-back (next day) 20, one rest day 8, two rest days 3; density: 5 points for each game beyond 3 in the 7 days ending on the game day (max 15).\n- Eastward travel is weighted more because research on Major League Baseball (Song, Severini & Allada, PNAS 2017) found jet-lag effects mainly after eastward travel. The exact weights are editorial choices to make schedules comparable, not fitted to data.\n- Bands: below 25 low, 25–49 moderate, 50–74 high, 75 and above very high.\n\nA transparent heuristic for comparing schedules. It does not measure an athlete's actual fatigue, sleep or readiness.",
        "tags": [
          "calculators"
        ],
        "x-mcp-tool": "sports_travel_fatigue_estimator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SportsTravelFatigueEstimatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "East-coast basketball team on a western road trip",
                  "value": {
                    "homeBase": "Boston",
                    "games": [
                      {
                        "date": "2026-11-02",
                        "time": "19:30",
                        "location": "Boston",
                        "label": "Home"
                      },
                      {
                        "date": "2026-11-04",
                        "time": "19:00",
                        "location": "Denver"
                      },
                      {
                        "date": "2026-11-05",
                        "time": "19:00",
                        "location": "Phoenix"
                      },
                      {
                        "date": "2026-11-07",
                        "time": "19:30",
                        "location": "Los Angeles"
                      },
                      {
                        "date": "2026-11-09",
                        "time": "19:00",
                        "location": "Portland"
                      },
                      {
                        "date": "2026-11-10",
                        "time": "19:00",
                        "location": "Seattle"
                      },
                      {
                        "date": "2026-11-12",
                        "time": "19:30",
                        "location": "Boston",
                        "label": "Home"
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "European club: league and Champions League week",
                  "value": {
                    "homeBase": "Munich",
                    "games": [
                      {
                        "date": "2026-09-19",
                        "time": "15:30",
                        "location": "Munich"
                      },
                      {
                        "date": "2026-09-23",
                        "time": "21:00",
                        "location": "Lisbon"
                      },
                      {
                        "date": "2026-09-26",
                        "time": "18:30",
                        "location": "Hamburg"
                      },
                      {
                        "date": "2026-09-30",
                        "time": "21:00",
                        "location": "Istanbul"
                      },
                      {
                        "date": "2026-10-03",
                        "time": "15:30",
                        "location": "Munich"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SportsTravelFatigueEstimatorResult"
                }
              }
            }
          },
          "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/running-pace-calculator/calculate": {
      "post": {
        "operationId": "calculateRunningPaceCalculator",
        "summary": "Pace, time or distance in km or miles, splits, race predictions, treadmill speed, laps.",
        "description": "Enter two of distance, time and pace to get the third, in kilometres or miles. Get an even, negative or positive split table, race time predictions with the Riegel formula, the treadmill speed in km/h and mph, and track lap times.\n\nMethodology:\n- Pace = time ÷ distance; time = pace × distance; distance = time ÷ pace. 1 mile = 1609.344 m exactly; a half marathon is 21.0975 km and a marathon 42.195 km.\n- Speed (treadmill) = 3600 ÷ pace in seconds per km (km/h) or per mile (mph).\n- Splits: for an even pace every split takes pace × split length. For a negative (positive) split the pace changes linearly along the course by the chosen percentage from start to finish, so the split times still add up to the target time.\n- Race predictions use Riegel's formula T2 = T1 × (D2 ÷ D1)^1.06, which he described for efforts of roughly 3.5 to 230 minutes. Predictions far from the input distance, and marathon times for runners without long-run training, are usually too optimistic.\n- Track laps: lap time = pace × lap length. Lane 1 of a standard track is 400 m; outer lanes are longer.",
        "tags": [
          "calculators"
        ],
        "x-mcp-tool": "running_pace_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunningPaceCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "10K in 50:00: pace and splits",
                  "value": {
                    "solveFor": "pace",
                    "preset": "10k",
                    "time": "50:00",
                    "splitUnit": "km",
                    "splitStrategy": "even"
                  }
                },
                "example2": {
                  "summary": "Marathon at 5:40/km with a 3% negative split",
                  "value": {
                    "solveFor": "time",
                    "preset": "marathon",
                    "pace": "5:40",
                    "paceUnit": "km",
                    "splitUnit": "km",
                    "splitStrategy": "negative",
                    "splitPercent": 3
                  }
                },
                "example3": {
                  "summary": "How far in 1 hour at 8:30/mile?",
                  "value": {
                    "solveFor": "distance",
                    "time": "1:00:00",
                    "pace": "8:30",
                    "paceUnit": "mi",
                    "splitUnit": "mi"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RunningPaceCalculatorResult"
                }
              }
            }
          },
          "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/one-rep-max-calculator/calculate": {
      "post": {
        "operationId": "calculateOneRepMaxCalculator",
        "summary": "1RM from any set (7 formulas + average), % and RPE tables, and which plates to load.",
        "description": "Estimates your one-repetition maximum from the weight and reps of a set to failure (or with an RPE) with the Epley, Brzycki, Lander, Lombardi, Mayhew, O'Conner and Wathan equations and their average, prints a percentage table and an RPE table, and tells you which plates to put on each side of a kg or lb bar using only the plates you actually have.\n\nMethodology:\n- Seven published equations estimate the 1RM from the weight w and repetitions r to failure, as tabulated by LeSuer et al. (1997): Epley w(1 + 0.0333r); Brzycki 100w/(102.78 − 2.78r); Lander 100w/(101.3 − 2.67123r); Lombardi w·r^0.10; Mayhew 100w/(52.2 + 41.9e^(−0.055r)); O'Conner w(1 + 0.025r); Wathan 100w/(48.8 + 53.8e^(−0.075r)). The average of all seven is the headline estimate.\n- With 1 rep (and no RPE) the 1RM is the weight lifted.\n- RPE: reps in reserve = 10 − RPE are added to the reps (e.g. 5 reps at RPE 8 count as 7 reps to failure). The RPE table uses the same idea with the Epley relation %1RM = 1 ÷ (1 + 0.0333 × (reps + 10 − RPE)); it is derived, not a copy of a published RPE chart.\n- Percentage table: weight = % × average 1RM; the approximate reps at each percentage invert the Epley equation: reps = (1/% − 1) ÷ 0.0333.\n- Plate loader: per side = (target − bar − collars) ÷ 2. Plates are taken heaviest first (greedy) from the pairs you have; if that misses but an exact combination exists (possible with unusual plate sets), an exact subset-sum search finds the combination with the fewest plates. If the weight cannot be loaded exactly, the nearest loadable weights below and above are shown.",
        "tags": [
          "calculators"
        ],
        "x-mcp-tool": "one_rep_max_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OneRepMaxCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "100 kg × 5 reps, load 85%",
                  "value": {
                    "weight": 100,
                    "reps": 5,
                    "unit": "kg",
                    "targetPercent": 85
                  }
                },
                "example2": {
                  "summary": "225 lb × 8 at RPE 8, load 275 lb",
                  "value": {
                    "weight": 225,
                    "reps": 8,
                    "unit": "lb",
                    "rpe": 8,
                    "targetWeight": 275
                  }
                },
                "example3": {
                  "summary": "Home gym: 62.5 kg with a 15 kg bar and few plates",
                  "value": {
                    "weight": 50,
                    "reps": 10,
                    "unit": "kg",
                    "targetWeight": 62.5,
                    "barWeight": 15,
                    "plates": [
                      {
                        "weight": 10,
                        "count": 2
                      },
                      {
                        "weight": 5,
                        "count": 4
                      },
                      {
                        "weight": 2.5,
                        "count": 2
                      },
                      {
                        "weight": 1.25,
                        "count": 2
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OneRepMaxCalculatorResult"
                }
              }
            }
          },
          "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/random-generator/calculate": {
      "post": {
        "operationId": "calculateRandomGenerator",
        "summary": "Random numbers, dice, coins, list picks, shuffles, fair teams, dates, strings and UUIDs.",
        "description": "Generates random whole or decimal numbers (optionally unique), dice rolls such as 3d6+2, coin flips, picks from a list, shuffles, balanced teams (by optional skill), random dates, strings and UUID v4/v7. Uses cryptographic randomness by default; with a seed the output is reproducible so others can verify a draw.\n\nMethodology:\n- Without a seed, bytes come from the server's cryptographically secure generator (Node.js crypto.randomBytes); nothing is stored. With a seed, bytes come from SHA-256 in counter mode over the seed, so anyone can reproduce the draw with the same seed and settings.\n- Whole numbers in a range use rejection sampling (no modulo bias). Decimals use 53 random bits scaled into [min, max) and rounded to the chosen places. Unique numbers use a partial Fisher–Yates shuffle or Floyd's algorithm for huge ranges.\n- Shuffles and picks use Fisher–Yates. Teams: without skills, a random round-robin; with skills, a snake draft over a random order followed by swaps that shrink the gap between the strongest and weakest team.\n- UUID v4 has 122 random bits; UUID v7 has a 48-bit Unix millisecond timestamp and 74 random bits (RFC 9562).\n\nSeeded output is predictable by design. For passwords or keys, leave the seed empty (or use a dedicated password generator on your own device).",
        "tags": [
          "calculators"
        ],
        "x-mcp-tool": "random_generator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RandomGeneratorInput"
              },
              "examples": {
                "example1": {
                  "summary": "Lottery 6 of 49 (seeded)",
                  "value": {
                    "mode": "integers",
                    "count": 6,
                    "min": 1,
                    "max": 49,
                    "unique": true,
                    "seed": "draw-2026-10-18"
                  }
                },
                "example2": {
                  "summary": "Roll 4d6+2 five times (seeded)",
                  "value": {
                    "mode": "dice",
                    "count": 5,
                    "dice": "4d6+2",
                    "seed": "demo"
                  }
                },
                "example3": {
                  "summary": "Balanced teams by skill (seeded)",
                  "value": {
                    "mode": "teams",
                    "teams": 2,
                    "items": [
                      "Ana, 8",
                      "Ben, 5",
                      "Cleo, 9",
                      "Dev, 4",
                      "Eli, 6",
                      "Fay, 7"
                    ],
                    "seed": "friday"
                  }
                },
                "example4": {
                  "summary": "Three UUID v4 (seeded)",
                  "value": {
                    "mode": "uuid4",
                    "count": 3,
                    "seed": "example"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RandomGeneratorResult"
                }
              }
            }
          },
          "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/jet-lag-planner/calculate": {
      "post": {
        "operationId": "calculateJetLagPlanner",
        "summary": "A day-by-day sleep, light and caffeine plan to beat jet lag, with calendar export.",
        "description": "Pick your home and destination time zones, travel date and usual sleep times. The planner computes the real time difference for that date (including daylight saving), shifts your sleep before departure, and lists when to seek or avoid bright light and when to stop caffeine until you are adapted. Download it as a calendar file.\n\nMethodology:\n- Time difference = destination UTC offset (at noon UTC on the arrival date) − home UTC offset (at noon UTC on the departure date), via the Intl time-zone database, so daylight saving is included. It is normalised to −12…+12 h; positive = eastward (clock must advance), negative = westward (delay).\n- Pre-flight: sleep and wake move 1 hour per day toward destination time (earlier for east, later for west), as suggested in the CDC Yellow Book jet-lag chapter for the 2–3 days before travel; days beyond what is needed are skipped.\n- After arrival you sleep on local time; the body clock is assumed to catch up 1 h/day after eastward and 1.5 h/day after westward travel (common planning rule of thumb, not an individual prediction).\n- Core body temperature minimum (CBTmin) is estimated at 2.5 h before the body’s habitual wake time (2 h for early birds, 3 h for night owls).\n- Light rule (phase-response curve): to advance (east) seek bright light in the 3 h after CBTmin and avoid it in the 3 h before; to delay (west) seek light in the 3 h before CBTmin and avoid it in the 3 h after. If a window falls while you sleep, use only its waking part.\n- Caffeine cut-off = 6 h before planned bedtime (CDC Yellow Book example).\n- The .ics file contains bedtime, light and caffeine events in UTC with UIDs derived from a hash of your inputs, CRLF line endings and folded lines.\n\nGeneral planning guidance, not medical advice. Ask a clinician before using melatonin or sleep medication.",
        "tags": [
          "home-travel"
        ],
        "x-mcp-tool": "jet_lag_planner",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JetLagPlannerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Berlin → New York (westward, 6 h)",
                  "value": {
                    "homeZone": "Europe/Berlin",
                    "destinationZone": "America/New_York",
                    "departureDate": "2026-11-10",
                    "bedtime": "23:00",
                    "wakeTime": "07:00",
                    "daysBefore": 3,
                    "chronotype": "intermediate"
                  }
                },
                "example2": {
                  "summary": "London → Tokyo (eastward, 9 h)",
                  "value": {
                    "homeZone": "Europe/London",
                    "destinationZone": "Asia/Tokyo",
                    "departureDate": "2027-04-02",
                    "arrivalDate": "2027-04-03",
                    "bedtime": "23:30",
                    "wakeTime": "07:30",
                    "daysBefore": 3,
                    "chronotype": "late"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JetLagPlannerResult"
                }
              }
            }
          },
          "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"
                }
              }
            }
          }
        }
      },
      "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"
              }
            }
          }
        }
      },
      "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"
              }
            }
          }
        }
      },
      "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"
              }
            }
          }
        }
      },
      "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"
              }
            }
          }
        }
      },
      "SportsTravelFatigueEstimatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "homeBase": {
            "title": "Starting city",
            "description": "A city from the bundled list (118 major sports cities) or coordinates like 42.36,-71.06.",
            "type": "string",
            "maxLength": 80,
            "default": "Boston"
          },
          "homeTimeZone": {
            "title": "Starting time zone",
            "description": "Only needed for coordinates: an IANA zone like America/New_York or an offset like -5.",
            "type": "string",
            "maxLength": 40
          },
          "games": {
            "title": "Games",
            "description": "In date order. Location: a listed city or latitude,longitude. Time zone is only needed for coordinates.",
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "date": {
                  "title": "Date",
                  "type": "string",
                  "format": "date"
                },
                "time": {
                  "title": "Start time (local)",
                  "type": "string",
                  "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                },
                "location": {
                  "title": "City or lat,lon",
                  "type": "string",
                  "maxLength": 80
                },
                "timeZone": {
                  "title": "Time zone",
                  "type": "string",
                  "maxLength": 40
                },
                "label": {
                  "title": "Opponent / note",
                  "type": "string",
                  "maxLength": 60
                }
              },
              "required": [
                "date",
                "location"
              ]
            }
          }
        },
        "required": [
          "homeBase",
          "games"
        ]
      },
      "SportsTravelFatigueEstimatorResult": {
        "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": [
              "games",
              "totals",
              "weights",
              "cityListVersion"
            ],
            "properties": {
              "games": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "n",
                    "date",
                    "location",
                    "label",
                    "from",
                    "km",
                    "timeZone",
                    "utcOffsetHours",
                    "shiftHours",
                    "direction",
                    "daysSincePrevious",
                    "restDays",
                    "backToBack",
                    "gamesLast7Days",
                    "components",
                    "score",
                    "band"
                  ],
                  "properties": {
                    "n": {
                      "type": "integer"
                    },
                    "date": {
                      "type": "string"
                    },
                    "location": {
                      "type": "string"
                    },
                    "label": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "from": {
                      "type": "string"
                    },
                    "km": {
                      "type": "number"
                    },
                    "timeZone": {
                      "type": "string"
                    },
                    "utcOffsetHours": {
                      "type": "number"
                    },
                    "shiftHours": {
                      "type": "number",
                      "description": "Positive = clocks ahead (eastward), negative = westward."
                    },
                    "direction": {
                      "type": "string",
                      "enum": [
                        "east",
                        "west",
                        "none"
                      ]
                    },
                    "daysSincePrevious": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "restDays": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "backToBack": {
                      "type": "boolean"
                    },
                    "gamesLast7Days": {
                      "type": "integer"
                    },
                    "components": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "travel",
                        "timezone",
                        "residualHours",
                        "rest",
                        "density"
                      ],
                      "properties": {
                        "travel": {
                          "type": "number"
                        },
                        "timezone": {
                          "type": "number"
                        },
                        "residualHours": {
                          "type": "number"
                        },
                        "rest": {
                          "type": "number"
                        },
                        "density": {
                          "type": "number"
                        }
                      }
                    },
                    "score": {
                      "type": "number"
                    },
                    "band": {
                      "type": "string",
                      "enum": [
                        "low",
                        "moderate",
                        "high",
                        "very high"
                      ]
                    }
                  }
                }
              },
              "totals": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "km",
                  "timeZoneHoursCrossed",
                  "backToBacks",
                  "averageScore",
                  "maxScore",
                  "hardestGame"
                ],
                "properties": {
                  "km": {
                    "type": "number"
                  },
                  "timeZoneHoursCrossed": {
                    "type": "number"
                  },
                  "backToBacks": {
                    "type": "integer"
                  },
                  "averageScore": {
                    "type": "number"
                  },
                  "maxScore": {
                    "type": "number"
                  },
                  "hardestGame": {
                    "type": "integer"
                  }
                }
              },
              "weights": {
                "type": "object"
              },
              "cityListVersion": {
                "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"
              }
            }
          }
        }
      },
      "RunningPaceCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "solveFor": {
            "title": "Calculate",
            "type": "string",
            "enum": [
              "pace",
              "time",
              "distance"
            ],
            "default": "pace"
          },
          "preset": {
            "title": "Race distance",
            "type": "string",
            "enum": [
              "custom",
              "1k",
              "mile",
              "5k",
              "10k",
              "15k",
              "10mi",
              "half",
              "marathon",
              "50k"
            ],
            "default": "10k"
          },
          "distance": {
            "title": "Custom distance",
            "description": "Used when the race distance is \"Custom distance\".",
            "type": "number",
            "minimum": 0.001,
            "maximum": 1000
          },
          "distanceUnit": {
            "title": "Distance unit",
            "type": "string",
            "enum": [
              "km",
              "mi",
              "m"
            ],
            "default": "km"
          },
          "time": {
            "title": "Time",
            "description": "h:mm:ss or mm:ss, e.g. 1:45:00 or 24:30.",
            "type": "string",
            "maxLength": 20
          },
          "pace": {
            "title": "Pace",
            "description": "mm:ss per km or per mile, e.g. 5:30.",
            "type": "string",
            "maxLength": 20
          },
          "paceUnit": {
            "title": "Pace per",
            "type": "string",
            "enum": [
              "km",
              "mi"
            ],
            "default": "km"
          },
          "splitUnit": {
            "title": "Splits every",
            "type": "string",
            "enum": [
              "km",
              "mi"
            ],
            "default": "km"
          },
          "splitStrategy": {
            "title": "Split strategy",
            "type": "string",
            "enum": [
              "even",
              "negative",
              "positive"
            ],
            "default": "even"
          },
          "splitPercent": {
            "title": "Pace change start → finish",
            "description": "For negative/positive splits: how much the pace changes from the start to the finish, spread evenly.",
            "type": "number",
            "minimum": 0,
            "maximum": 20,
            "default": 3,
            "x-unit": "%"
          },
          "lapMeters": {
            "title": "Track lap length",
            "type": "number",
            "minimum": 50,
            "maximum": 1000,
            "default": 400,
            "x-unit": "m"
          }
        },
        "required": []
      },
      "RunningPaceCalculatorResult": {
        "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": [
              "distanceMeters",
              "distanceKm",
              "distanceMiles",
              "timeSeconds",
              "time",
              "paceSecPerKm",
              "paceSecPerMile",
              "pacePerKm",
              "pacePerMile",
              "speedKmh",
              "speedMph",
              "splits",
              "halves",
              "predictions",
              "laps"
            ],
            "properties": {
              "distanceMeters": {
                "type": "number"
              },
              "distanceKm": {
                "type": "number"
              },
              "distanceMiles": {
                "type": "number"
              },
              "timeSeconds": {
                "type": "number"
              },
              "time": {
                "type": "string"
              },
              "paceSecPerKm": {
                "type": "number"
              },
              "paceSecPerMile": {
                "type": "number"
              },
              "pacePerKm": {
                "type": "string"
              },
              "pacePerMile": {
                "type": "string"
              },
              "speedKmh": {
                "type": "number"
              },
              "speedMph": {
                "type": "number"
              },
              "splits": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "split",
                    "atDistance",
                    "splitTime",
                    "splitSeconds",
                    "cumulative",
                    "cumulativeSeconds",
                    "pace"
                  ],
                  "properties": {
                    "split": {
                      "type": "integer"
                    },
                    "atDistance": {
                      "type": "number"
                    },
                    "splitTime": {
                      "type": "string"
                    },
                    "splitSeconds": {
                      "type": "number"
                    },
                    "cumulative": {
                      "type": "string"
                    },
                    "cumulativeSeconds": {
                      "type": "number"
                    },
                    "pace": {
                      "type": "string"
                    }
                  }
                }
              },
              "halves": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "first",
                  "second"
                ],
                "properties": {
                  "first": {
                    "type": "string"
                  },
                  "second": {
                    "type": "string"
                  }
                }
              },
              "predictions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "race",
                    "meters",
                    "seconds",
                    "time",
                    "pacePerKm",
                    "pacePerMile",
                    "inRange"
                  ],
                  "properties": {
                    "race": {
                      "type": "string"
                    },
                    "meters": {
                      "type": "number"
                    },
                    "seconds": {
                      "type": "number"
                    },
                    "time": {
                      "type": "string"
                    },
                    "pacePerKm": {
                      "type": "string"
                    },
                    "pacePerMile": {
                      "type": "string"
                    },
                    "inRange": {
                      "type": "boolean",
                      "description": "Both times (your input effort and this prediction) within Riegel's 3.5–230 minute endurance range."
                    },
                    "predictionInRange": {
                      "type": "boolean",
                      "description": "This predicted time on its own is within Riegel's 3.5–230 minute range (shown in the table; the input effort is checked separately and warned about)."
                    }
                  }
                }
              },
              "laps": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "lapMeters",
                  "lapTime",
                  "lapSeconds",
                  "halfLapTime",
                  "lapsForDistance"
                ],
                "properties": {
                  "lapMeters": {
                    "type": "number"
                  },
                  "lapTime": {
                    "type": "string"
                  },
                  "lapSeconds": {
                    "type": "number"
                  },
                  "halfLapTime": {
                    "type": "string"
                  },
                  "lapsForDistance": {
                    "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"
              }
            }
          }
        }
      },
      "OneRepMaxCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "weight": {
            "title": "Weight lifted",
            "type": "number",
            "minimum": 0.5,
            "maximum": 2000,
            "default": 100
          },
          "reps": {
            "title": "Repetitions",
            "description": "Reps completed in the set. Estimates are most reliable up to about 10 reps.",
            "type": "integer",
            "minimum": 1,
            "maximum": 30,
            "default": 5
          },
          "unit": {
            "title": "Unit",
            "type": "string",
            "enum": [
              "kg",
              "lb"
            ],
            "default": "kg"
          },
          "rpe": {
            "title": "RPE of the set",
            "description": "Optional (6–10). 10 = to failure; 8 = two more reps were possible. Reps in reserve are added before estimating.",
            "type": "number",
            "minimum": 6,
            "maximum": 10
          },
          "targetWeight": {
            "title": "Weight to load",
            "description": "Optional total bar weight for the plate loader.",
            "type": "number",
            "minimum": 0,
            "maximum": 2000
          },
          "targetPercent": {
            "title": "Or: percentage of 1RM to load",
            "description": "Used when no weight to load is given; default 100% of the average estimate.",
            "type": "number",
            "minimum": 1,
            "maximum": 110,
            "x-unit": "%"
          },
          "barWeight": {
            "title": "Bar weight",
            "description": "Default 20 kg or 45 lb.",
            "type": "number",
            "minimum": 0,
            "maximum": 100
          },
          "collarWeight": {
            "title": "Collars (both together)",
            "type": "number",
            "minimum": 0,
            "maximum": 20,
            "default": 0
          },
          "plates": {
            "title": "Plates available",
            "description": "Plate weight and how many you have in total (both sides). Empty = a standard set (kg: 25–1.25, lb: 45–2.5).",
            "type": "array",
            "maxItems": 15,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "weight": {
                  "title": "Plate weight",
                  "type": "number",
                  "minimum": 0.05,
                  "maximum": 100
                },
                "count": {
                  "title": "Count",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 100
                }
              },
              "required": [
                "weight",
                "count"
              ]
            }
          }
        },
        "required": [
          "weight",
          "reps"
        ]
      },
      "OneRepMaxCalculatorResult": {
        "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": [
              "unit",
              "weight",
              "reps",
              "effectiveReps",
              "estimates",
              "average",
              "percentTable",
              "rpeTable",
              "loader"
            ],
            "properties": {
              "unit": {
                "type": "string",
                "enum": [
                  "kg",
                  "lb"
                ]
              },
              "weight": {
                "type": "number"
              },
              "reps": {
                "type": "integer"
              },
              "effectiveReps": {
                "type": "number",
                "description": "Reps + reps in reserve (10 − RPE)."
              },
              "estimates": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "name",
                    "formula",
                    "oneRepMax"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "formula": {
                      "type": "string"
                    },
                    "oneRepMax": {
                      "type": "number"
                    }
                  }
                }
              },
              "average": {
                "type": "number"
              },
              "percentTable": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "percent",
                    "weight",
                    "approxReps"
                  ],
                  "properties": {
                    "percent": {
                      "type": "number"
                    },
                    "weight": {
                      "type": "number"
                    },
                    "approxReps": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  }
                }
              },
              "rpeTable": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "reps",
                    "rpe",
                    "percent",
                    "weight"
                  ],
                  "properties": {
                    "reps": {
                      "type": "integer"
                    },
                    "rpe": {
                      "type": "number"
                    },
                    "percent": {
                      "type": "number"
                    },
                    "weight": {
                      "type": "number"
                    }
                  }
                }
              },
              "loader": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "target",
                  "bar",
                  "collars",
                  "exact",
                  "perSide",
                  "loaded",
                  "method",
                  "nearestBelow",
                  "nearestAbove"
                ],
                "properties": {
                  "target": {
                    "type": "number"
                  },
                  "bar": {
                    "type": "number"
                  },
                  "collars": {
                    "type": "number"
                  },
                  "exact": {
                    "type": "boolean"
                  },
                  "perSide": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "loaded": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "method": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "nearestBelow": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": false,
                    "properties": {
                      "total": {
                        "type": "number"
                      },
                      "perSide": {
                        "type": "array",
                        "items": {
                          "type": "number"
                        }
                      }
                    }
                  },
                  "nearestAbove": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": false,
                    "properties": {
                      "total": {
                        "type": "number"
                      },
                      "perSide": {
                        "type": "array",
                        "items": {
                          "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"
              }
            }
          }
        }
      },
      "RandomGeneratorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "mode": {
            "title": "Generate",
            "type": "string",
            "enum": [
              "integers",
              "floats",
              "dice",
              "coins",
              "pick",
              "shuffle",
              "teams",
              "dates",
              "strings",
              "uuid4",
              "uuid7"
            ],
            "default": "integers"
          },
          "count": {
            "title": "How many",
            "description": "Numbers, rolls, flips, picks, dates, strings or UUIDs.",
            "type": "integer",
            "minimum": 1,
            "maximum": 1000,
            "default": 10
          },
          "min": {
            "title": "Minimum",
            "type": "number",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "default": 1
          },
          "max": {
            "title": "Maximum",
            "type": "number",
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "default": 100
          },
          "unique": {
            "title": "No repeats (numbers and picks)",
            "type": "boolean",
            "default": false
          },
          "decimals": {
            "title": "Decimal places",
            "type": "integer",
            "minimum": 0,
            "maximum": 15,
            "default": 2
          },
          "dice": {
            "title": "Dice notation",
            "description": "NdS±M, e.g. 2d20, 4d6+2, d100.",
            "type": "string",
            "maxLength": 40,
            "default": "3d6"
          },
          "items": {
            "title": "List (one per line; \"name, skill\" for teams)",
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 1000
          },
          "teams": {
            "title": "Number of teams",
            "type": "integer",
            "minimum": 2,
            "maximum": 50,
            "default": 2
          },
          "dateFrom": {
            "title": "Earliest date",
            "type": "string",
            "format": "date",
            "default": "2026-01-01"
          },
          "dateTo": {
            "title": "Latest date",
            "type": "string",
            "format": "date",
            "default": "2026-12-31"
          },
          "length": {
            "title": "String length",
            "type": "integer",
            "minimum": 1,
            "maximum": 256,
            "default": 16
          },
          "charset": {
            "title": "Characters",
            "type": "string",
            "enum": [
              "alnum",
              "unambiguous",
              "lower",
              "upper",
              "digits",
              "hex",
              "symbols"
            ],
            "default": "alnum"
          },
          "seed": {
            "title": "Seed (optional, for reproducible results)",
            "description": "Empty = cryptographic randomness, different every time. With a seed, the same settings always give the same result — never use seeded output for passwords or keys.",
            "type": "string",
            "maxLength": 200
          },
          "uuidTime": {
            "title": "UUID v7 timestamp (optional)",
            "description": "Default: now (UTC). Set it together with a seed for reproducible v7 UUIDs. Local date-time YYYY-MM-DDTHH:mm",
            "type": "string"
          }
        },
        "required": []
      },
      "RandomGeneratorResult": {
        "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": [
              "mode",
              "seeded",
              "values"
            ],
            "properties": {
              "mode": {
                "type": "string"
              },
              "seeded": {
                "type": "boolean"
              },
              "seed": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "values": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Generated values as text, in order."
              },
              "numbers": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "dice": {
                "type": "object",
                "properties": {
                  "notation": {
                    "type": "string"
                  },
                  "rolls": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "dice": {
                          "type": "array",
                          "items": {
                            "type": "integer"
                          }
                        },
                        "total": {
                          "type": "integer"
                        }
                      }
                    }
                  },
                  "min": {
                    "type": "integer"
                  },
                  "max": {
                    "type": "integer"
                  },
                  "mean": {
                    "type": "number"
                  }
                }
              },
              "heads": {
                "type": "integer"
              },
              "teams": {
                "type": "array",
                "items": {
                  "type": "object",
                  "required": [
                    "team",
                    "members"
                  ],
                  "properties": {
                    "team": {
                      "type": "integer"
                    },
                    "members": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "totalSkill": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  }
                }
              },
              "stats": {
                "type": "object",
                "properties": {
                  "sum": {
                    "type": "number"
                  },
                  "mean": {
                    "type": "number"
                  },
                  "min": {
                    "type": "number"
                  },
                  "max": {
                    "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"
              }
            }
          }
        }
      },
      "JetLagPlannerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "homeZone": {
            "title": "Home time zone",
            "type": "string",
            "enum": [
              "Pacific/Honolulu",
              "America/Anchorage",
              "America/Los_Angeles",
              "America/Denver",
              "America/Phoenix",
              "America/Chicago",
              "America/Mexico_City",
              "America/New_York",
              "America/Toronto",
              "America/Bogota",
              "America/Lima",
              "America/Santiago",
              "America/Sao_Paulo",
              "America/Argentina/Buenos_Aires",
              "Atlantic/Reykjavik",
              "Europe/London",
              "Europe/Lisbon",
              "Europe/Dublin",
              "Europe/Paris",
              "Europe/Berlin",
              "Europe/Madrid",
              "Europe/Rome",
              "Europe/Zurich",
              "Europe/Vienna",
              "Europe/Amsterdam",
              "Europe/Stockholm",
              "Europe/Warsaw",
              "Europe/Athens",
              "Europe/Istanbul",
              "Europe/Moscow",
              "Africa/Cairo",
              "Africa/Johannesburg",
              "Africa/Lagos",
              "Africa/Nairobi",
              "Asia/Dubai",
              "Asia/Karachi",
              "Asia/Kolkata",
              "Asia/Bangkok",
              "Asia/Singapore",
              "Asia/Shanghai",
              "Asia/Hong_Kong",
              "Asia/Seoul",
              "Asia/Tokyo",
              "Australia/Perth",
              "Australia/Sydney",
              "Pacific/Auckland"
            ],
            "default": "Europe/Berlin"
          },
          "destinationZone": {
            "title": "Destination time zone",
            "type": "string",
            "enum": [
              "Pacific/Honolulu",
              "America/Anchorage",
              "America/Los_Angeles",
              "America/Denver",
              "America/Phoenix",
              "America/Chicago",
              "America/Mexico_City",
              "America/New_York",
              "America/Toronto",
              "America/Bogota",
              "America/Lima",
              "America/Santiago",
              "America/Sao_Paulo",
              "America/Argentina/Buenos_Aires",
              "Atlantic/Reykjavik",
              "Europe/London",
              "Europe/Lisbon",
              "Europe/Dublin",
              "Europe/Paris",
              "Europe/Berlin",
              "Europe/Madrid",
              "Europe/Rome",
              "Europe/Zurich",
              "Europe/Vienna",
              "Europe/Amsterdam",
              "Europe/Stockholm",
              "Europe/Warsaw",
              "Europe/Athens",
              "Europe/Istanbul",
              "Europe/Moscow",
              "Africa/Cairo",
              "Africa/Johannesburg",
              "Africa/Lagos",
              "Africa/Nairobi",
              "Asia/Dubai",
              "Asia/Karachi",
              "Asia/Kolkata",
              "Asia/Bangkok",
              "Asia/Singapore",
              "Asia/Shanghai",
              "Asia/Hong_Kong",
              "Asia/Seoul",
              "Asia/Tokyo",
              "Australia/Perth",
              "Australia/Sydney",
              "Pacific/Auckland"
            ],
            "default": "America/New_York"
          },
          "homeZoneCustom": {
            "title": "Other home zone (IANA name)",
            "description": "Overrides the list if filled in.",
            "type": "string",
            "maxLength": 64
          },
          "destinationZoneCustom": {
            "title": "Other destination zone (IANA name)",
            "description": "Overrides the list if filled in.",
            "type": "string",
            "maxLength": 64
          },
          "departureDate": {
            "title": "Departure date",
            "type": "string",
            "format": "date"
          },
          "arrivalDate": {
            "title": "Arrival date (local)",
            "description": "Defaults to the departure date.",
            "type": "string",
            "format": "date"
          },
          "bedtime": {
            "title": "Usual bedtime",
            "type": "string",
            "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
            "default": "23:00"
          },
          "wakeTime": {
            "title": "Usual wake time",
            "type": "string",
            "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$",
            "default": "07:00"
          },
          "daysBefore": {
            "title": "Days to pre-adjust before departure",
            "type": "integer",
            "minimum": 0,
            "maximum": 7,
            "default": 3
          },
          "chronotype": {
            "title": "Chronotype",
            "type": "string",
            "enum": [
              "early",
              "intermediate",
              "late"
            ],
            "default": "intermediate"
          }
        },
        "required": [
          "departureDate"
        ]
      },
      "JetLagPlannerResult": {
        "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": [
              "homeZone",
              "destinationZone",
              "offsetMinutes",
              "direction",
              "preShiftMinutes",
              "daysToAdapt",
              "schedule"
            ],
            "properties": {
              "homeZone": {
                "type": "string"
              },
              "destinationZone": {
                "type": "string"
              },
              "offsetMinutes": {
                "type": "integer",
                "description": "Destination UTC offset − home UTC offset on the travel dates, normalised to (−720, 720]."
              },
              "direction": {
                "type": "string",
                "enum": [
                  "east",
                  "west",
                  "none"
                ]
              },
              "preShiftMinutes": {
                "type": "integer",
                "minimum": 0
              },
              "daysToAdapt": {
                "type": "integer",
                "minimum": 0,
                "description": "Days after arrival until the body clock is estimated to match local time."
              },
              "schedule": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "date",
                    "phase",
                    "clock",
                    "bedtime",
                    "wakeTime",
                    "bodyTempMinimum",
                    "seekLight",
                    "avoidLight",
                    "caffeineCutoff",
                    "remainingShiftHours"
                  ],
                  "properties": {
                    "date": {
                      "type": "string",
                      "format": "date"
                    },
                    "phase": {
                      "type": "string",
                      "enum": [
                        "pre-flight",
                        "travel-day",
                        "post-arrival"
                      ]
                    },
                    "clock": {
                      "type": "string",
                      "enum": [
                        "home",
                        "destination"
                      ],
                      "description": "Which local time the times are expressed in."
                    },
                    "bedtime": {
                      "type": "string",
                      "pattern": "^\\d{2}:\\d{2}$"
                    },
                    "wakeTime": {
                      "type": "string",
                      "pattern": "^\\d{2}:\\d{2}$"
                    },
                    "bodyTempMinimum": {
                      "type": "string",
                      "pattern": "^\\d{2}:\\d{2}$"
                    },
                    "seekLight": {
                      "type": "string"
                    },
                    "avoidLight": {
                      "type": "string"
                    },
                    "caffeineCutoff": {
                      "type": "string",
                      "pattern": "^\\d{2}:\\d{2}$"
                    },
                    "remainingShiftHours": {
                      "type": "number",
                      "minimum": 0
                    }
                  }
                }
              }
            }
          },
          "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"
              }
            }
          }
        }
      }
    }
  }
}