{
  "openapi": "3.1.1",
  "info": {
    "title": "Bulk Buy Brain API",
    "version": "1.0.0",
    "summary": "Is the bulk pack really cheaper once waste, storage and tied-up cash are counted?",
    "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://bulkbuybrain.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/calculate": {
      "post": {
        "operationId": "calculate",
        "summary": "Is the bulk pack really cheaper once waste, storage and tied-up cash are counted?",
        "description": "Compare a small pack with a bulk pack using how fast you actually use the product, its shelf life, storage cost and the interest your money could earn. The tool computes the effective cost per unit you actually use, your saving or loss, and the largest bulk size worth buying.\n\nMethodology:\n- Small-pack unit price ps = small price ÷ small quantity (small packs bought as needed, no waste). Bulk unit price pb = bulk price ÷ bulk quantity.\n- Usable quantity = min(bulk quantity, consumption per week × shelf life); waste = bulk quantity − usable quantity.\n- Weeks held = usable quantity ÷ consumption per week. Storage cost = storage cost per week × weeks held.\n- Opportunity cost = bulk price × interest rate × (weeks held ÷ 52.18) ÷ 2 (the prepaid amount is drawn down linearly, so on average half is tied up).\n- Effective cost per used unit = (bulk price + storage + opportunity cost) ÷ usable quantity. Savings = ps × usable quantity − bulk total cost.\n- Max worthwhile size: the largest Q for which pb·Q + storage + opportunity cost ≤ ps × usable(Q), solved in closed form for the no-waste and the waste regime.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "bulk_buy_brain",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkBuyBrainInput"
              },
              "examples": {
                "example1": {
                  "summary": "Toilet paper: 8 rolls vs 48 rolls",
                  "value": {
                    "currency": "EUR",
                    "unitLabel": "rolls",
                    "smallPrice": 3.49,
                    "smallQuantity": 8,
                    "bulkPrice": 17.99,
                    "bulkQuantity": 48,
                    "consumptionPerWeek": 3,
                    "storageCostPerWeek": 0,
                    "interestRatePercent": 3
                  }
                },
                "example2": {
                  "summary": "Greek yoghurt: 500 g vs 2 kg tub (spoils)",
                  "value": {
                    "currency": "EUR",
                    "unitLabel": "g",
                    "smallPrice": 2.29,
                    "smallQuantity": 500,
                    "bulkPrice": 6.99,
                    "bulkQuantity": 2000,
                    "consumptionPerWeek": 600,
                    "shelfLifeWeeks": 2,
                    "storageCostPerWeek": 0,
                    "interestRatePercent": 3
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkBuyBrainResult"
                }
              }
            }
          },
          "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"
                }
              }
            }
          }
        }
      },
      "BulkBuyBrainInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "unitLabel": {
            "title": "Unit",
            "type": "string",
            "maxLength": 20,
            "default": "units"
          },
          "smallPrice": {
            "title": "Small pack price",
            "type": "number",
            "minimum": 0.000001,
            "default": 3
          },
          "smallQuantity": {
            "title": "Small pack quantity",
            "type": "number",
            "minimum": 0.000001,
            "default": 1
          },
          "bulkPrice": {
            "title": "Bulk pack price",
            "type": "number",
            "minimum": 0.000001,
            "default": 20
          },
          "bulkQuantity": {
            "title": "Bulk pack quantity",
            "type": "number",
            "minimum": 0.000001,
            "default": 10
          },
          "consumptionPerWeek": {
            "title": "You use per week",
            "type": "number",
            "minimum": 0.000001,
            "default": 1
          },
          "shelfLifeWeeks": {
            "title": "Shelf life once bought",
            "description": "Leave empty for non-perishables.",
            "type": "number",
            "minimum": 0.1,
            "x-unit": "weeks"
          },
          "storageCostPerWeek": {
            "title": "Storage cost of the bulk pack",
            "description": "Optional: freezer energy, rented space…",
            "type": "number",
            "minimum": 0,
            "default": 0,
            "x-unit": "per week"
          },
          "interestRatePercent": {
            "title": "Interest your cash could earn",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "default": 3,
            "x-unit": "%/year"
          }
        },
        "required": [
          "smallPrice",
          "smallQuantity",
          "bulkPrice",
          "bulkQuantity",
          "consumptionPerWeek"
        ]
      },
      "BulkBuyBrainResult": {
        "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": [
              "currency",
              "unit",
              "verdict",
              "smallPricePerUnit",
              "bulkPricePerUnit",
              "effectiveBulkCostPerUsedUnit",
              "usedQuantity",
              "wastedQuantity",
              "wastePercent",
              "weeksToUse",
              "storageCost",
              "opportunityCost",
              "savings",
              "savingsPercent",
              "maxWorthwhileQuantity",
              "maxWorthwhileWeeksOfSupply"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "unit": {
                "type": "string"
              },
              "verdict": {
                "type": "string",
                "enum": [
                  "buy-bulk",
                  "buy-small",
                  "no-difference"
                ]
              },
              "smallPricePerUnit": {
                "type": "number"
              },
              "bulkPricePerUnit": {
                "type": "number",
                "description": "Sticker unit price of the bulk pack."
              },
              "effectiveBulkCostPerUsedUnit": {
                "type": "number",
                "description": "(bulk price + storage + opportunity cost) ÷ units actually used."
              },
              "usedQuantity": {
                "type": "number"
              },
              "wastedQuantity": {
                "type": "number"
              },
              "wastePercent": {
                "type": "number"
              },
              "weeksToUse": {
                "type": "number",
                "description": "Weeks the bulk pack is held (until used up or expired)."
              },
              "storageCost": {
                "type": "number"
              },
              "opportunityCost": {
                "type": "number"
              },
              "savings": {
                "type": "number",
                "description": "Small-pack cost of the used units − total bulk cost; negative = loss."
              },
              "savingsPercent": {
                "type": "number"
              },
              "maxWorthwhileQuantity": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Largest pack size (at the bulk unit price) that is still no more expensive than small packs; null = no limit."
              },
              "maxWorthwhileWeeksOfSupply": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "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"
              }
            }
          }
        }
      }
    }
  }
}