{
  "openapi": "3.1.1",
  "info": {
    "title": "Chore Roulette API",
    "version": "1.0.0",
    "summary": "Spin a fair chore rota: random but balanced by effort, with a “not again” rule.",
    "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://choreroulette.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Spin a fair chore rota: random but balanced by effort, with a “not again” rule.",
        "description": "Enter the people (with an optional capacity, e.g. 0.5 for a child) and the chores with effort points. Chore Roulette draws a reproducible random assignment that balances everyone’s total effort, can avoid giving anyone the same chore as last week, and shows each person’s load against their fair share.\n\nMethodology:\n- Each chore occurrence is a task with effort points. A person’s fair share is total effort × their capacity ÷ the sum of capacities.\n- The seeded random generator (sfc32 seeded with SHA-256 of the seed) shuffles the tasks and the people; tasks are then handed out largest first to whoever would end up with the lowest load relative to their capacity (ties go to the seeded order).\n- A local search then moves single tasks and swaps pairs whenever that lowers the sum of squared differences between each person’s effort and their fair share, until nothing improves. Moves never give someone a chore they did last week when the rule is on.\n- Fairness = 100 × (1 − Σ|effort − fair share| ÷ (2 × total effort)): the share of the work that sits with the right person. Spread = highest minus lowest effort per full share of capacity.\n- If everyone did a chore last week, a repeat is unavoidable and is reported. Without a seed, the seed is a hash of the inputs, so the same rota appears on the page, in the API and in MCP; change the seed to re-spin.",
        "tags": [
          "fun"
        ],
        "x-mcp-tool": "chore_roulette",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChoreRouletteInput"
              },
              "examples": {
                "example1": {
                  "summary": "Flat share of three",
                  "value": {
                    "people": [
                      {
                        "name": "Alex"
                      },
                      {
                        "name": "Bea"
                      },
                      {
                        "name": "Chris"
                      }
                    ],
                    "chores": [
                      {
                        "chore": "Bathroom",
                        "effort": 4
                      },
                      {
                        "chore": "Kitchen",
                        "effort": 3
                      },
                      {
                        "chore": "Vacuum",
                        "effort": 3
                      },
                      {
                        "chore": "Bins",
                        "effort": 1
                      },
                      {
                        "chore": "Groceries",
                        "effort": 2
                      },
                      {
                        "chore": "Dishes",
                        "effort": 1,
                        "count": 4
                      }
                    ],
                    "lastWeek": [
                      {
                        "chore": "Bathroom",
                        "person": "Alex"
                      },
                      {
                        "chore": "Kitchen",
                        "person": "Bea"
                      }
                    ],
                    "avoidRepeats": true,
                    "seed": "week-42"
                  }
                },
                "example2": {
                  "summary": "Family with a younger child",
                  "value": {
                    "people": [
                      {
                        "name": "Mum"
                      },
                      {
                        "name": "Dad"
                      },
                      {
                        "name": "Mia",
                        "capacity": 1
                      },
                      {
                        "name": "Leo",
                        "capacity": 0.5
                      }
                    ],
                    "chores": [
                      {
                        "chore": "Cook dinner",
                        "effort": 3,
                        "count": 3
                      },
                      {
                        "chore": "Laundry",
                        "effort": 4
                      },
                      {
                        "chore": "Feed the cat",
                        "effort": 0.5,
                        "count": 7
                      },
                      {
                        "chore": "Tidy living room",
                        "effort": 2
                      },
                      {
                        "chore": "Water plants",
                        "effort": 1
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChoreRouletteResult"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON body.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Invalid input (see errors[]).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (see Retry-After).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tool": {
      "get": {
        "operationId": "getToolDefinition",
        "summary": "Inputs, output schema and examples for this tool",
        "responses": {
          "200": {
            "description": "Tool definition.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. 422 responses include per-field `errors`.",
        "required": [
          "type",
          "title",
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "field",
                "message"
              ],
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ChoreRouletteInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "people": {
            "title": "People",
            "type": "array",
            "minItems": 1,
            "maxItems": 20,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Name",
                  "type": "string",
                  "maxLength": 40
                },
                "capacity": {
                  "title": "Capacity",
                  "description": "1 = a full share; 0.5 = half a share (e.g. a younger child).",
                  "type": "number",
                  "minimum": 0.1,
                  "maximum": 10,
                  "default": 1
                }
              },
              "required": [
                "name"
              ]
            }
          },
          "chores": {
            "title": "Chores",
            "type": "array",
            "minItems": 1,
            "maxItems": 60,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "chore": {
                  "title": "Chore",
                  "type": "string",
                  "maxLength": 60
                },
                "effort": {
                  "title": "Effort points",
                  "description": "E.g. minutes per week, or 1–5 for how unpleasant it is.",
                  "type": "number",
                  "minimum": 0.1,
                  "maximum": 100,
                  "default": 1
                },
                "count": {
                  "title": "Times per week",
                  "description": "Each occurrence is assigned separately (e.g. dishes ×7 shares out the days).",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 14,
                  "default": 1
                }
              },
              "required": [
                "chore"
              ]
            }
          },
          "lastWeek": {
            "title": "Last week (optional)",
            "description": "Who did which chore last week, for the “not again” rule.",
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "chore": {
                  "title": "Chore",
                  "type": "string",
                  "maxLength": 60
                },
                "person": {
                  "title": "Person",
                  "type": "string",
                  "maxLength": 40
                }
              },
              "required": [
                "chore",
                "person"
              ]
            }
          },
          "avoidRepeats": {
            "title": "Avoid giving anyone the same chore as last week",
            "type": "boolean",
            "default": true
          },
          "seed": {
            "title": "Seed (optional)",
            "description": "Same seed + same inputs = same draw. Leave empty to derive it from the inputs; change it (e.g. to the week number) to re-spin.",
            "type": "string",
            "maxLength": 60
          }
        },
        "required": [
          "people",
          "chores"
        ]
      },
      "ChoreRouletteResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schemaVersion",
          "tool",
          "computedAt",
          "inputs",
          "summary",
          "result",
          "units",
          "assumptions",
          "sources",
          "warnings",
          "presentation",
          "links"
        ],
        "properties": {
          "schemaVersion": {
            "type": "string",
            "const": "1.0"
          },
          "tool": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "id",
              "name",
              "version"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "version": {
                "type": "string"
              }
            }
          },
          "computedAt": {
            "type": "string",
            "format": "date-time"
          },
          "inputs": {
            "type": "object",
            "description": "Normalised inputs actually used, including applied defaults."
          },
          "summary": {
            "type": "string"
          },
          "result": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "seed",
              "seedDerived",
              "assignments",
              "loads",
              "totalEffort",
              "spread",
              "fairnessPercent",
              "repeats",
              "unavoidableRepeats"
            ],
            "properties": {
              "seed": {
                "type": "string"
              },
              "seedDerived": {
                "type": "boolean"
              },
              "assignments": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "chore",
                    "effort",
                    "person",
                    "repeatOfLastWeek"
                  ],
                  "properties": {
                    "chore": {
                      "type": "string"
                    },
                    "effort": {
                      "type": "number"
                    },
                    "person": {
                      "type": "string"
                    },
                    "repeatOfLastWeek": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "loads": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "person",
                    "capacity",
                    "effort",
                    "target",
                    "share",
                    "chores",
                    "deviation"
                  ],
                  "properties": {
                    "person": {
                      "type": "string"
                    },
                    "capacity": {
                      "type": "number"
                    },
                    "effort": {
                      "type": "number"
                    },
                    "target": {
                      "type": "number"
                    },
                    "share": {
                      "type": "number"
                    },
                    "chores": {
                      "type": "integer"
                    },
                    "deviation": {
                      "type": "number"
                    }
                  }
                }
              },
              "totalEffort": {
                "type": "number"
              },
              "spread": {
                "type": "number",
                "description": "Max − min of effort ÷ capacity, i.e. points per full share (0 = perfectly even)."
              },
              "fairnessPercent": {
                "type": "number",
                "description": "100 × (1 − Σ|effort − fair share| ÷ (2 × total effort)); 100 = everyone exactly at their fair share."
              },
              "repeats": {
                "type": "integer"
              },
              "unavoidableRepeats": {
                "type": "integer"
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}