{
  "openapi": "3.1.1",
  "info": {
    "title": "Ledgerly 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://ledgerly.usefulweb.site"
    }
  ],
  "paths": {
    "/api/v1/tools": {
      "get": {
        "operationId": "listTools",
        "summary": "Catalog of all tools",
        "responses": {
          "200": {
            "description": "Tool list.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/tools/{id}": {
      "get": {
        "operationId": "getTool",
        "summary": "Definition of one tool",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "subscription-stack",
                "cancellation-navigator",
                "bill-detective",
                "hidden-fee-finder",
                "loan-amortization-calculator",
                "deadline-decoder",
                "life-admin-generator",
                "digital-estate-organizer",
                "how-long-will-it-last",
                "repair-or-replace",
                "warranty-value-calculator",
                "true-cost-calculator",
                "is-it-worth-it",
                "bulk-buy-brain",
                "price-per-everything",
                "break-even-explorer",
                "decision-simulator",
                "salary-reality-check",
                "tip-bill-split-calculator",
                "percentage-ratio-calculator",
                "inflation-time-machine",
                "currency-converter",
                "historical-fx-chart",
                "multi-currency-invoice-calculator",
                "travel-exchange-fee-calculator"
              ]
            }
          }
        ],
        "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/subscription-stack/calculate": {
      "post": {
        "operationId": "calculateSubscriptionStack",
        "summary": "Add up every subscription, spot overlaps and see which ones to cancel and what you save.",
        "description": "List your subscriptions with price, billing cycle, category, how often you use them and how many people share them. Subscription Stack converts everything to monthly and yearly totals, groups by category, flags overlapping services, computes cost per use and lists cancellation candidates with the savings.\n\nMethodology:\n- Monthly price = weekly × (365.25/12)/7 (≈ 4.348), monthly × 1, quarterly ÷ 3, yearly ÷ 12.\n- Your share = monthly price ÷ number of people sharing; annual = your monthly share × 12.\n- Cost per use = your monthly share ÷ uses per month (not computed when uses are unknown or zero).\n- Overlap = two or more subscriptions in the same category (excluding “other”).\n- Cancel candidate if uses per month < your threshold, or cost per use > your threshold, or it is the most expensive per use (or least used) service within an overlapping category.\n- Savings if cancelled = sum of your monthly shares of all candidates; annual = × 12.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "subscription_stack",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubscriptionStackInput"
              },
              "examples": {
                "example1": {
                  "summary": "Typical household stack",
                  "value": {
                    "currency": "EUR",
                    "minUsesPerMonth": 2,
                    "maxCostPerUse": 5,
                    "subscriptions": [
                      {
                        "name": "Netflix",
                        "price": 13.99,
                        "billing": "monthly",
                        "category": "video",
                        "usesPerMonth": 12,
                        "sharedBy": 2
                      },
                      {
                        "name": "Disney+",
                        "price": 89.9,
                        "billing": "yearly",
                        "category": "video",
                        "usesPerMonth": 2
                      },
                      {
                        "name": "Spotify",
                        "price": 10.99,
                        "billing": "monthly",
                        "category": "music",
                        "usesPerMonth": 40
                      },
                      {
                        "name": "Gym",
                        "price": 34.9,
                        "billing": "monthly",
                        "category": "fitness",
                        "usesPerMonth": 3
                      },
                      {
                        "name": "Cloud 2 TB",
                        "price": 9.99,
                        "billing": "monthly",
                        "category": "cloud-storage",
                        "usesPerMonth": 30
                      },
                      {
                        "name": "Newspaper",
                        "price": 29.97,
                        "billing": "quarterly",
                        "category": "news",
                        "usesPerMonth": 1
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Freelancer software stack",
                  "value": {
                    "currency": "USD",
                    "minUsesPerMonth": 4,
                    "maxCostPerUse": 3,
                    "subscriptions": [
                      {
                        "name": "Design suite",
                        "price": 59.99,
                        "billing": "monthly",
                        "category": "software",
                        "usesPerMonth": 20
                      },
                      {
                        "name": "Second design app",
                        "price": 99,
                        "billing": "yearly",
                        "category": "software",
                        "usesPerMonth": 1
                      },
                      {
                        "name": "Cloud backup",
                        "price": 6,
                        "billing": "monthly",
                        "category": "cloud-storage",
                        "usesPerMonth": 30
                      },
                      {
                        "name": "Extra cloud drive",
                        "price": 2.99,
                        "billing": "monthly",
                        "category": "cloud-storage"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubscriptionStackResult"
                }
              }
            }
          },
          "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/cancellation-navigator/calculate": {
      "post": {
        "operationId": "calculateCancellationNavigator",
        "summary": "Verified steps to cancel Netflix, Spotify, Prime & more, plus a cancellation letter.",
        "description": "Choose a subscription and how you signed up. Cancellation Navigator shows the steps from the official help page (with the date we last checked it), what to do if you are billed through the App Store, Google Play or a carrier, your German online cancellation-button rights (§ 312k BGB), a ready-to-send cancellation letter and a proof-keeping checklist.\n\nMethodology:\n- Service steps were condensed from each service's official help page on the date shown (verifiedAt); nothing is added that the page does not say. If a page could not be read reliably, only the official link is given (steps not verified).\n- App Store and Google Play subscriptions are billed by Apple/Google, so the store's own cancellation steps (Apple Support, Google Play Help) are shown first for those channels.\n- Carrier/partner billing: cancel with the company that bills you (e.g. Spotify's help states partner plans are cancelled with the partner).\n- Germany: § 312k BGB (Kündigungsbutton) summary from gesetze-im-internet.de. Rights for other countries are not asserted because they were not verified.\n- Unknown services get a generic method and letter, clearly marked as unverified.\n\nNot legal advice. Service flows change; the official page linked is authoritative.",
        "tags": [
          "everyday"
        ],
        "x-mcp-tool": "cancellation_navigator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancellationNavigatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "Netflix signed up on the web, Germany",
                  "value": {
                    "service": "netflix",
                    "country": "DE",
                    "channel": "web"
                  }
                },
                "example2": {
                  "summary": "ChatGPT Plus bought in the iPhone app, US",
                  "value": {
                    "service": "chatgpt-plus",
                    "country": "US",
                    "channel": "app-store"
                  }
                },
                "example3": {
                  "summary": "Unknown local gym, Germany",
                  "value": {
                    "service": "other",
                    "serviceName": "FitClub Musterstadt",
                    "country": "DE",
                    "channel": "other",
                    "yourName": "Max Mustermann"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancellationNavigatorResult"
                }
              }
            }
          },
          "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/bill-detective/calculate": {
      "post": {
        "operationId": "calculateBillDetective",
        "summary": "Spot spikes, price changes and calculation errors across your utility bills.",
        "description": "Enter several billing periods of an electricity, gas, water or data bill. Bill Detective normalises consumption per day, flags unusual periods (outliers), unit-price and standing-charge changes, and totals that do not add up, each with a plain-English explanation.\n\nMethodology:\n- Days = end date − start date + 1 (both dates count). Consumption per day = consumption ÷ days. Periods are sorted by start date.\n- Computed total = consumption × unit price + fixed charge + other charges. A mismatch is flagged when |total billed − computed| > tolerance % × computed (minimum 0.01).\n- Consumption change: per-day consumption vs. the previous period; flagged when the absolute change exceeds the threshold (default 25%).\n- Above average: per-day consumption more than the threshold above the average of all periods.\n- Outliers (IQR rule, only with ≥ 4 periods): per-day value above Q3 + 1.5 × IQR or below Q1 − 1.5 × IQR, quartiles by linear interpolation.\n- Outliers (median rule): per-day value more than X% (default 30%) above the median per-day value.\n- Unit price change: any change vs. the previous period. Fixed charge change: both the fixed charge per period and the fixed charge per day differ from the previous period by more than 1% (so a flat monthly fee over months of different length, or a fixed daily rate, is not flagged).\n- Gaps and overlaps between consecutive periods are reported as information.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "bill_detective",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillDetectiveInput"
              },
              "examples": {
                "example1": {
                  "summary": "Monthly electricity with a May spike and a spring price rise",
                  "value": {
                    "unit": "kWh",
                    "currency": "EUR",
                    "periods": [
                      {
                        "label": "Jan",
                        "startDate": "2026-01-01",
                        "endDate": "2026-01-31",
                        "consumption": 310,
                        "unitPrice": 0.32,
                        "fixedCharge": 12,
                        "totalBilled": 111.2
                      },
                      {
                        "label": "Feb",
                        "startDate": "2026-02-01",
                        "endDate": "2026-02-28",
                        "consumption": 280,
                        "unitPrice": 0.32,
                        "fixedCharge": 12,
                        "totalBilled": 101.6
                      },
                      {
                        "label": "Mar",
                        "startDate": "2026-03-01",
                        "endDate": "2026-03-31",
                        "consumption": 300,
                        "unitPrice": 0.32,
                        "fixedCharge": 12,
                        "totalBilled": 108
                      },
                      {
                        "label": "Apr",
                        "startDate": "2026-04-01",
                        "endDate": "2026-04-30",
                        "consumption": 290,
                        "unitPrice": 0.35,
                        "fixedCharge": 12,
                        "totalBilled": 113.5
                      },
                      {
                        "label": "May",
                        "startDate": "2026-05-01",
                        "endDate": "2026-05-31",
                        "consumption": 520,
                        "unitPrice": 0.35,
                        "fixedCharge": 14,
                        "totalBilled": 198
                      },
                      {
                        "label": "Jun",
                        "startDate": "2026-06-01",
                        "endDate": "2026-06-30",
                        "consumption": 295,
                        "unitPrice": 0.35,
                        "fixedCharge": 14,
                        "totalBilled": 117.25
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Quarterly water bills",
                  "value": {
                    "unit": "m3",
                    "currency": "GBP",
                    "periods": [
                      {
                        "label": "Q1",
                        "startDate": "2025-01-01",
                        "endDate": "2025-03-31",
                        "consumption": 24,
                        "unitPrice": 2.1,
                        "fixedCharge": 18,
                        "totalBilled": 68.4
                      },
                      {
                        "label": "Q2",
                        "startDate": "2025-04-01",
                        "endDate": "2025-06-30",
                        "consumption": 26,
                        "unitPrice": 2.1,
                        "fixedCharge": 18,
                        "totalBilled": 72.6
                      },
                      {
                        "label": "Q3",
                        "startDate": "2025-07-01",
                        "endDate": "2025-09-30",
                        "consumption": 31,
                        "unitPrice": 2.25,
                        "fixedCharge": 18,
                        "totalBilled": 87.75
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillDetectiveResult"
                }
              }
            }
          },
          "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/hidden-fee-finder/calculate": {
      "post": {
        "operationId": "calculateHiddenFeeFinder",
        "summary": "Paste a quote, contract or invoice and see every fee, tax, penalty and add-on.",
        "description": "Paste the lines of a quote, rental contract, booking or invoice (English or German). Hidden Fee Finder extracts the amounts, classifies each line with transparent keyword rules (base price, fee, tax, surcharge, penalty, deposit, discount, insurance/add-on, recurring), flags conditional penalties and optional add-ons, and shows how much of the total is not the base price.\n\nMethodology:\n- Each non-empty line is scanned for amounts. A number counts as money if it has a currency marker (€, $, £, EUR, USD, GBP, CHF…), exactly two decimals, or ends the line; the last such amount on a line is used. A number followed by % is a percentage.\n- Number formats: with both separators the last one is the decimal mark; \"1,234\" (comma + 3 digits) is read as a thousands separator in auto mode, \"12,50\" as a decimal; choose a fixed format to override.\n- Categories are assigned by ordered keyword rules (total → penalty → deposit → discount → tax → insurance/add-on → surcharge → fee → recurring → base). The first matching keyword is shown. Lines without any keyword default to base price.\n- Flags: conditional (late, cancellation, early termination, if, per day, wenn, falls, Verzug, pro Tag…), recurring (monthly, per year, subscription, monatlich…), optional/hidden (optional, pre-selected, recommended, auto-renew, empfohlen, voreingestellt…), included tax (incl./inkl./included).\n- Payable total = base + fees + taxes + surcharges + insurance/add-ons + recurring + discounts. Excluded: deposits (refundable), conditional lines (only if triggered), total lines, and taxes marked as included.\n- Percent-only lines: taxes are applied to the sum of all other included lines; other percentages are applied to the base total.\n- Non-base share = (payable total − base − discounts) ÷ payable total.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "hidden_fee_finder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HiddenFeeFinderInput"
              },
              "examples": {
                "example1": {
                  "summary": "Hotel booking with resort fee and conditional charges",
                  "value": {
                    "currency": "USD",
                    "text": "Room rate, 3 nights: $420.00\nResort fee (mandatory): $105.00\nCity tax: 6%\nTravel insurance (pre-selected, optional): $29.99\nLate checkout fee if after 12:00: $50.00\nSecurity deposit (refundable): $200.00\nMember discount: -$21.00\nTotal: $566.03"
                  }
                },
                "example2": {
                  "summary": "German rental quote",
                  "value": {
                    "currency": "EUR",
                    "numberFormat": "de",
                    "text": "Grundpreis Miete Transporter 1 Tag 89,00 €\nBearbeitungsgebühr 15,00 €\nWochenendzuschlag 20,00 €\nVollkaskoversicherung optional 24,90 €\nKaution 300,00 €\nVertragsstrafe bei verspäteter Rückgabe pro Tag 75,00 €\nGesamtbetrag inkl. 19% MwSt. 148,90 €"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HiddenFeeFinderResult"
                }
              }
            }
          },
          "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/loan-amortization-calculator/calculate": {
      "post": {
        "operationId": "calculateLoanAmortizationCalculator",
        "summary": "Annuity, linear or interest-only loans: schedule, extra payments, fixed-rate balance, APR.",
        "description": "Calculates the payment and the complete amortization schedule of an annuity, linear (constant principal) or interest-only loan, including one-off and recurring extra payments, the remaining balance at the end of a fixed-rate period, the effective annual rate and the APR with up-front fees. Download the schedule as CSV.\n\nMethodology:\n- Rate per period i = nominal annual rate ÷ payments per year (the usual convention for German and most European loans).\n- Annuity payment A = L·i ÷ (1 − (1 + i)^−n), rounded to the cent; with an initial repayment rate t instead: A = L × (rate + t) ÷ payments per year, and the term follows from A.\n- Each period: interest = balance × i (rounded to the cent); principal = A − interest (annuity), L ÷ n (linear) or 0 until the last payment (interest-only). The last scheduled payment clears any remaining cents.\n- Extra payments reduce the balance on top of the regular payment. Annuity loans keep the payment and finish earlier; linear and interest-only loans keep their principal plan and pay less interest.\n- Remaining balance at the end of the fixed-rate period = balance after the last payment inside that period.\n- Effective annual rate (no fees) = (1 + i)^m − 1. APR: the rate X solving Σ payment_l × (1 + X)^(−t_l) = loan − up-front fees, with t_l measured in years of 12 standard months, as in the equation of the PAngV annex (EU Consumer Credit Directive). This is an approximation: real offers count exact days, may add account fees or insurance, and lenders round to two decimals.",
        "tags": [
          "calculators"
        ],
        "x-mcp-tool": "loan_amortization_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoanAmortizationCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "Mortgage 300,000 at 3.8%, 2% initial repayment, 10-year fixed rate",
                  "value": {
                    "principal": 300000,
                    "currency": "EUR",
                    "ratePercent": 3.8,
                    "loanType": "annuity",
                    "initialRepaymentPercent": 2,
                    "frequency": "monthly",
                    "startDate": "2026-11-01",
                    "fixedRateYears": 10,
                    "upfrontFees": 0,
                    "recurringExtra": 5000,
                    "recurringEvery": 12,
                    "recurringFrom": 12
                  }
                },
                "example2": {
                  "summary": "Car loan 20,000, 5 years at 6.9% with 400 fees",
                  "value": {
                    "principal": 20000,
                    "currency": "USD",
                    "ratePercent": 6.9,
                    "loanType": "annuity",
                    "termYears": 5,
                    "frequency": "monthly",
                    "upfrontFees": 400
                  }
                },
                "example3": {
                  "summary": "Linear loan 120,000 over 10 years, quarterly",
                  "value": {
                    "principal": 120000,
                    "ratePercent": 4,
                    "loanType": "linear",
                    "termYears": 10,
                    "frequency": "quarterly",
                    "startDate": "2027-01-01",
                    "extraPayments": [
                      {
                        "period": 8,
                        "amount": 10000
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LoanAmortizationCalculatorResult"
                }
              }
            }
          },
          "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/deadline-decoder/calculate": {
      "post": {
        "operationId": "calculateDeadlineDecoder",
        "summary": "Find every deadline, required action and consequence in a letter or notice.",
        "description": "Paste or upload an official letter, invoice, reminder or notice (English or German). Deadline Decoder extracts absolute dates and relative deadlines such as \"within 14 days\" or \"binnen eines Monats\", resolves them against the letter or receipt date, lists what you must do and what happens otherwise, and exports the deadlines as a calendar (.ics) file.\n\nMethodology:\n- The text is split into sentences (paragraph-, bullet- and abbreviation-aware; German ordinal dates like \"12. Oktober\" are not treated as sentence ends).\n- Absolute dates are matched in the formats 2026-10-12, 12.10.2026, 12/10/2026 (day/month or month/day as selected; an unambiguous day > 12 overrides), \"October 12, 2026\", \"12 Oct 2026\" and \"12. Oktober 2026\". Impossible dates (31.02.) are discarded.\n- A date counts as a deadline when its sentence contains a deadline cue (by, until, no later than, due, bis, spätestens, fällig, Frist …) or a required-action pattern; other dates are listed as \"mentioned\".\n- Relative deadlines (\"within 14 days\", \"no later than 30 days after receipt\", \"innerhalb von 2 Wochen\", \"binnen eines Monats\") are added to the reference date: days and weeks as calendar days, business days skip Saturdays and Sundays (public holidays are not considered), months/years as calendar months (clamped to the month end).\n- Required actions are sentences matching modal or request patterns (must, shall, required, please pay/submit, bitte, müssen, \"ist … zu zahlen\", \"überweisen Sie\" …). Consequences are sentences with otherwise/failure-to/fee/penalty/escalation patterns (andernfalls, sonst, Mahngebühr, Säumniszuschlag, Inkasso, Vollstreckung …).\n- Every finding quotes the exact sentence and names the rule that fired. Confidence is \"pattern-match\": nothing is guessed by an AI model.\n\nNot legal advice. Rule-based extraction can miss deadlines or misread dates, and legal deadline rules (e.g. when a letter counts as received, weekends and public holidays) differ by country and authority. Always check the original document and, if in doubt, ask the sender or an adviser.",
        "tags": [
          "everyday"
        ],
        "x-mcp-tool": "deadline_decoder",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeadlineDecoderInput"
              },
              "examples": {
                "example1": {
                  "summary": "German payment reminder (Mahnung)",
                  "value": {
                    "text": "Zahlungserinnerung\nDatum: 05.10.2026\n\nSehr geehrte Frau Muster,\nleider konnten wir bis heute keinen Zahlungseingang für die Rechnung Nr. 4711 vom 01.09.2026 feststellen. Bitte überweisen Sie den offenen Betrag von 89,90 EUR bis spätestens 20. Oktober 2026 auf das unten genannte Konto. Andernfalls müssen wir eine Mahngebühr von 5,00 EUR berechnen und die Forderung an ein Inkassobüro übergeben. Gegen diesen Bescheid kann innerhalb eines Monats nach Bekanntgabe Widerspruch eingelegt werden.",
                    "referenceDate": "2026-10-07",
                    "referenceType": "received",
                    "dateOrder": "DMY"
                  }
                },
                "example2": {
                  "summary": "US insurance notice",
                  "value": {
                    "text": "Notice date: October 1, 2026\n\nWe need additional information to process your claim. You must submit the enclosed form and a copy of your medical bills within 30 days after receipt of this notice. If we do not receive the documents, your claim will be closed. Your premium payment of $142.50 is due 10/25/2026; failure to pay by the due date may result in a late fee of $15 and cancellation of your policy.",
                    "referenceDate": "2026-10-03",
                    "referenceType": "received",
                    "dateOrder": "MDY"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeadlineDecoderResult"
                }
              }
            }
          },
          "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/life-admin-generator/calculate": {
      "post": {
        "operationId": "calculateLifeAdminGenerator",
        "summary": "A dated to-do list for moving, marriage, a baby, a new job and other life events.",
        "description": "Choose a life event, your country, the date and your circumstances. You get a personalised before / on-the-day / after checklist with due dates. Legal deadlines are only stated where verified against official sources (e.g. German Anmeldung within two weeks); everything else is clearly marked as a recommendation.\n\nMethodology:\n- A rule catalog maps each life event to tasks with an offset in days relative to the event date (negative = before). Rules can be limited to countries and to circumstances (e.g. lease notice only when renting).\n- Due date = event date + offset (calendar days). Items are grouped into before, on the day and after, sorted by due date then id.\n- Country-specific legal deadlines are only included where verified against an official source (Germany: BMG § 17, PStG §§ 18 and 28; Austria: oesterreich.gv.at; Switzerland: ch.ch; UK: GOV.UK birth and death registration). Only those are marked mandatory and cited.\n- For other countries and topics the item is phrased as \"check local requirements\"; offsets for recommendations are editorial planning suggestions.\n\nOrganisational help, not legal advice. Rules change and have exceptions — confirm deadlines with the responsible authority.",
        "tags": [
          "everyday"
        ],
        "x-mcp-tool": "life_admin_generator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LifeAdminGeneratorInput"
              },
              "examples": {
                "example1": {
                  "summary": "Moving home in Germany (renting, car)",
                  "value": {
                    "event": "moving",
                    "country": "de",
                    "eventDate": "2026-12-01",
                    "circumstances": [
                      "renting",
                      "has-car",
                      "employed"
                    ]
                  }
                },
                "example2": {
                  "summary": "Having a baby in the UK",
                  "value": {
                    "event": "baby",
                    "country": "uk",
                    "eventDate": "2027-03-20",
                    "circumstances": [
                      "employed",
                      "has-car"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LifeAdminGeneratorResult"
                }
              }
            }
          },
          "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/digital-estate-organizer/calculate": {
      "post": {
        "operationId": "calculateDigitalEstateOrganizer",
        "summary": "List your online accounts and get a prioritised plan for what happens to them – no passwords needed.",
        "description": "Inventory email, cloud, social, financial, crypto and subscription accounts, choose what should happen to each (transfer, download then delete, memorialise, delete, keep running), and get a prioritised plan that uses the providers' official legacy features (Google Inactive Account Manager, Apple Legacy Contact, Facebook legacy contact, Bitwarden Emergency Access…) plus a printable handover document. Passwords are refused and nothing is stored.\n\nMethodology:\n- Priority order: password manager → email (it resets everything else) → banking/crypto → domains → cloud data → subscriptions/work → social → shopping → other; ties keep your order.\n- Services are matched against a small, sourced list of official legacy features (Google, Apple, Meta, Bitwarden); unmatched services get category-based steps.\n- Input that looks like a password, PIN or seed phrase is rejected; nothing is stored on the server.\n\nThis is an organisational checklist, not legal advice and not a will. Inheritance law differs by country; store the generated document with your will and tell your executor where it is.",
        "tags": [
          "high-value"
        ],
        "x-mcp-tool": "digital_estate_organizer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DigitalEstateOrganizerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Typical household",
                  "value": {
                    "ownerName": "Alex Example",
                    "trustedContact": "Sam Example",
                    "hasPasswordManager": true,
                    "hasWrittenWill": false,
                    "accounts": [
                      {
                        "service": "Gmail",
                        "category": "email",
                        "wish": "transfer",
                        "recoveryEmail": "alex.backup@example.org"
                      },
                      {
                        "service": "iCloud Photos",
                        "category": "cloud-photos",
                        "wish": "download-then-delete",
                        "monthlyCost": 2.99
                      },
                      {
                        "service": "Facebook",
                        "category": "social",
                        "wish": "memorialize"
                      },
                      {
                        "service": "Bitwarden",
                        "category": "password-manager",
                        "wish": "transfer"
                      },
                      {
                        "service": "Netflix",
                        "category": "subscription",
                        "wish": "delete",
                        "monthlyCost": 13.99
                      },
                      {
                        "service": "example-family.org",
                        "category": "domain-website",
                        "wish": "keep-running",
                        "monthlyCost": 1.5
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DigitalEstateOrganizerResult"
                }
              }
            }
          },
          "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/how-long-will-it-last/calculate": {
      "post": {
        "operationId": "calculateHowLongWillItLast",
        "summary": "Estimate remaining life of appliances and home components and plan the replacement.",
        "description": "Pick an appliance or home component, enter its age, how hard it is used and how well it is maintained. The tool uses the life-expectancy ranges published in InterNACHI's Standard Estimated Life Expectancy Chart for Homes to estimate remaining years, the share of life used, and a replacement planning window.\n\nMethodology:\n- Base range: the YEARS value for the item from InterNACHI's Standard Estimated Life Expectancy Chart for Homes (e.g. '9 to 13'). Single values use the same min and max; 'N+' values are open-ended (only a minimum).\n- Adjusted range = base range × usage multiplier (light 1.15, normal 1.0, heavy 0.8) × maintenance multiplier (regular 1.0, occasional 0.9, neglected 0.75). These multipliers are this tool's own assumptions, not published figures; InterNACHI's values already assume regular recommended maintenance and normal wear and tear.\n- Remaining years = max(0, adjusted min − age) to max(0, adjusted max − age).\n- Life used = age ÷ midpoint of the adjusted range × 100 (open-ended: ÷ adjusted minimum).\n- Status: < 50% early life, 50–80% mid-life, ≥ 80% but below adjusted minimum approaching end, between adjusted min and max within end-of-life range, beyond adjusted max beyond expected life.\n- Replacement window = current year + remaining min … current year + remaining max (rounded to whole years).\n\nLife expectancies are guidelines only, not guarantees or warranties (InterNACHI).",
        "tags": [
          "everyday"
        ],
        "x-mcp-tool": "how_long_will_it_last",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HowLongWillItLastInput"
              },
              "examples": {
                "example1": {
                  "summary": "7-year-old refrigerator, normal use",
                  "value": {
                    "item": "refrigerator",
                    "ageYears": 7,
                    "usage": "normal",
                    "maintenance": "good"
                  }
                },
                "example2": {
                  "summary": "10-year-old water heater, heavy use, occasional maintenance",
                  "value": {
                    "item": "water-heater",
                    "ageYears": 10,
                    "usage": "heavy",
                    "maintenance": "average"
                  }
                },
                "example3": {
                  "summary": "4-year-old washing machine, light use",
                  "value": {
                    "item": "washing-machine",
                    "ageYears": 4,
                    "usage": "light",
                    "maintenance": "good"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HowLongWillItLastResult"
                }
              }
            }
          },
          "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/repair-or-replace/calculate": {
      "post": {
        "operationId": "calculateRepairOrReplace",
        "summary": "Fix it or buy new? The 50% rule plus a cost-per-year check, with a clear verdict.",
        "description": "Enter the age and expected lifespan of a broken item, the repair quote, the replacement price and running costs. The tool applies the common 50% rule of thumb and compares the cost per remaining year of repairing versus replacing, including the risk of another failure, and gives a reasoned verdict.\n\nMethodology:\n- 50% rule of thumb: if the repair quote is more than the threshold (default 50%) of the replacement price, replace; otherwise repair. This is a common heuristic, not a law or standard.\n- Expected extra repair cost = P(another failure) × next repair cost (defaults to the current quote).\n- Repair cost per year = (repair quote + expected extra repair cost) ÷ expected extra life + running cost of the old item per year.\n- Replace cost per year = replacement price ÷ lifespan of the new item + running cost of the new item per year.\n- Break-even repair quote = (replace cost per year − old running cost) × extra life − expected extra repair cost (floored at 0). When the next repair cost is left empty it moves with the quote, so the break-even quote is (replace cost per year − old running cost) × extra life ÷ (1 + P(failure)).\n- Final verdict follows the cost-per-year comparison, because it accounts for remaining life, failure risk and running costs; the 50% rule is shown as a cross-check.",
        "tags": [
          "everyday"
        ],
        "x-mcp-tool": "repair_or_replace",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RepairOrReplaceInput"
              },
              "examples": {
                "example1": {
                  "summary": "Washing machine with a failed drum bearing",
                  "value": {
                    "itemName": "Washing machine",
                    "currency": "EUR",
                    "itemAgeYears": 8,
                    "expectedLifespanYears": 11,
                    "replacementPrice": 550,
                    "repairQuote": 320,
                    "lifeAfterRepairYears": 3,
                    "runningCostOldPerYear": 75,
                    "runningCostNewPerYear": 55,
                    "failureProbabilityPercent": 35,
                    "ruleThresholdPercent": 50
                  }
                },
                "example2": {
                  "summary": "Laptop with a dead battery",
                  "value": {
                    "itemName": "Laptop",
                    "currency": "USD",
                    "itemAgeYears": 3,
                    "expectedLifespanYears": 6,
                    "replacementPrice": 1200,
                    "repairQuote": 150,
                    "lifeAfterRepairYears": 3,
                    "runningCostOldPerYear": 0,
                    "runningCostNewPerYear": 0,
                    "failureProbabilityPercent": 15,
                    "ruleThresholdPercent": 50
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RepairOrReplaceResult"
                }
              }
            }
          },
          "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/warranty-value-calculator/calculate": {
      "post": {
        "operationId": "calculateWarrantyValueCalculator",
        "summary": "Is the extended warranty worth it? Expected payout vs. price and break-even failure rate.",
        "description": "Enter the warranty price, how many years it covers, what is already covered by the legal or manufacturer warranty, a failure probability, repair cost, deductible and the chance a claim is approved. The calculator shows the expected payout, the expected net value and the failure rate at which the warranty would break even.\n\nMethodology:\n- Effective years = coverage years − years already covered by the legal/manufacturer warranty (floored at 0); during the overlap the extended warranty adds nothing for defects.\n- Payout per claim = min(average repair cost, item price) − deductible, floored at 0 (a repair costing more than the item is replaced instead).\n- Expected approved claims = effective years × annual failure probability × approval probability (at most one failure per year, years independent).\n- Expected payout = expected approved claims × payout per claim; expected net value = expected payout − warranty price.\n- Probability of at least one approved claim = 1 − (1 − p × a)^years, with p = annual failure probability and a = approval probability.\n- Break-even failure probability = warranty price ÷ (effective years × approval probability × payout per claim).\n- Verdict: worth it if net value > +10% of the warranty price, not worth it if < −10%, otherwise marginal.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "warranty_value_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WarrantyValueCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "TV: 3-year plan over a 2-year legal warranty",
                  "value": {
                    "currency": "EUR",
                    "itemPrice": 900,
                    "warrantyPrice": 99,
                    "coverageYears": 3,
                    "overlapYears": 2,
                    "annualFailureProbabilityPercent": 5,
                    "averageRepairCost": 300,
                    "deductible": 0,
                    "claimApprovalPercent": 80
                  }
                },
                "example2": {
                  "summary": "Phone: 2-year protection with deductible",
                  "value": {
                    "currency": "USD",
                    "itemPrice": 1000,
                    "warrantyPrice": 199,
                    "coverageYears": 2,
                    "overlapYears": 1,
                    "annualFailureProbabilityPercent": 15,
                    "averageRepairCost": 400,
                    "deductible": 99,
                    "claimApprovalPercent": 90
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WarrantyValueCalculatorResult"
                }
              }
            }
          },
          "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/true-cost-calculator/calculate": {
      "post": {
        "operationId": "calculateTrueCostCalculator",
        "summary": "What a car, pet, appliance, home or gadget really costs per year, month and day.",
        "description": "Add the purchase price, financing, expected resale value, recurring costs, maintenance, insurance and energy use. The calculator returns the true total cost of ownership, the cost per year, month and day, the share of each cost bucket, and how many times the sticker price you really pay.\n\nMethodology:\n- Depreciation = purchase price − expected resale value (the money you will not get back).\n- Loan interest is computed from an exact monthly annuity schedule: payment = L·i ÷ (1 − (1 + i)^−n) with i = APR ÷ 12 and n = term in months; only interest falling within the ownership period (years × 12 months) is counted.\n- Recurring costs are annualised: weekly × 365.25/7, monthly × 12, quarterly × 4, yearly × 1; then multiplied by years owned.\n- Energy, fuel or food per year = consumption ÷ (1 or 100) × usage per year × price per unit.\n- True total = depreciation + loan interest + years × (recurring + maintenance or vet care + insurance + energy, fuel or food per year).\n- Per year = total ÷ years; per month = total ÷ (12 × years); per day = total ÷ (365.25 × years). Sticker multiplier = total ÷ purchase price.",
        "tags": [
          "everyday"
        ],
        "x-mcp-tool": "true_cost_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TrueCostCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "Compact car, financed, 6 years",
                  "value": {
                    "itemType": "car",
                    "itemName": "Compact car",
                    "currency": "EUR",
                    "purchasePrice": 24000,
                    "yearsOwned": 6,
                    "resaleValue": 9000,
                    "loanAmount": 15000,
                    "aprPercent": 5.9,
                    "loanTermMonths": 48,
                    "maintenancePerYear": 600,
                    "insurancePerYear": 750,
                    "energyConsumption": 6,
                    "consumptionPer": "per-100",
                    "energyUnitPrice": 1.75,
                    "usagePerYear": 12000,
                    "recurring": [
                      {
                        "name": "Vehicle tax",
                        "amount": 120,
                        "frequency": "yearly"
                      },
                      {
                        "name": "Parking permit",
                        "amount": 30,
                        "frequency": "monthly"
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Medium-sized dog, 12 years",
                  "value": {
                    "itemType": "pet",
                    "itemName": "Dog",
                    "currency": "EUR",
                    "purchasePrice": 900,
                    "yearsOwned": 12,
                    "resaleValue": 0,
                    "loanAmount": 0,
                    "aprPercent": 0,
                    "loanTermMonths": 0,
                    "insurancePerYear": 240,
                    "maintenancePerYear": 250,
                    "energyConsumption": 0.35,
                    "consumptionPer": "per-1",
                    "energyUnitPrice": 6,
                    "usagePerYear": 365,
                    "recurring": [
                      {
                        "name": "Dog tax",
                        "amount": 100,
                        "frequency": "yearly"
                      },
                      {
                        "name": "Toys & treats",
                        "amount": 10,
                        "frequency": "monthly"
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrueCostCalculatorResult"
                }
              }
            }
          },
          "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/is-it-worth-it/calculate": {
      "post": {
        "operationId": "calculateIsItWorthIt",
        "summary": "Buy, rent, repair or subscribe? Compare total cost and NPV over the years you need it.",
        "description": "Enter the options you are considering (buy new, rent, repair what you have, subscribe) with upfront, monthly and one-off costs, resale value and a repair allowance. The tool computes total cost of ownership, net present value of costs, cost per month, a ranking and when the best option overtakes the runner-up.\n\nMethodology:\n- Annual running cost = recurring per month × 12 + repair allowance per year.\n- Total cost (nominal) = upfront + one-off costs + years × annual running cost − resale value.\n- NPV cost = upfront + one-off ÷ (1 + r)^y + Σ(t = 1..N) annual running cost ÷ (1 + r)^t − resale ÷ (1 + r)^N, where r = discount rate and y = the one-off year (running costs are discounted at each year end).\n- Cost per month = total cost ÷ (12 × years); NPV cost per month = NPV cost ÷ (12 × years).\n- Options are ranked by NPV cost (lowest first); ties keep input order.\n- Break-even: nominal cumulative costs of the best and runner-up options are compared at each year end (resale counted only at the end, one-off costs from their year) and the crossing point is interpolated linearly.",
        "tags": [
          "everyday"
        ],
        "x-mcp-tool": "is_it_worth_it",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IsItWorthItInput"
              },
              "examples": {
                "example1": {
                  "summary": "Washing machine: buy, rent or repair the old one (5 years)",
                  "value": {
                    "years": 5,
                    "discountRatePercent": 3,
                    "currency": "EUR",
                    "options": [
                      {
                        "name": "Buy new",
                        "kind": "buy",
                        "upfront": 650,
                        "resale": 100,
                        "repairPerYear": 20
                      },
                      {
                        "name": "Rent",
                        "kind": "rent",
                        "monthly": 15,
                        "oneOff": 30
                      },
                      {
                        "name": "Repair old",
                        "kind": "repair",
                        "upfront": 220,
                        "repairPerYear": 60,
                        "oneOff": 650,
                        "oneOffYear": 3
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Design software: perpetual licence vs subscription (4 years)",
                  "value": {
                    "years": 4,
                    "discountRatePercent": 4,
                    "currency": "USD",
                    "options": [
                      {
                        "name": "Perpetual licence + 1 upgrade",
                        "kind": "buy",
                        "upfront": 399,
                        "oneOff": 149,
                        "oneOffYear": 2
                      },
                      {
                        "name": "Monthly subscription",
                        "kind": "subscribe",
                        "monthly": 12.99
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IsItWorthItResult"
                }
              }
            }
          },
          "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/bulk-buy-brain/calculate": {
      "post": {
        "operationId": "calculateBulkBuyBrain",
        "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/tools/price-per-everything/calculate": {
      "post": {
        "operationId": "calculatePricePerEverything",
        "summary": "Compare products by price per gram, use, serving, hour or year of life.",
        "description": "Enter two or more products with their price and quantity (grams, uses, servings, hours or years). Price Per Everything normalises them to the same basis, accounts for waste, and ranks them from cheapest to most expensive per unit.\n\nMethodology:\n- Usable quantity = quantity × (1 − waste %).\n- Price per unit = price ÷ usable quantity, unrounded internally; displayed with 4 significant digits.\n- Products are ranked by price per unit; ties keep input order.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "price_per_everything",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PricePerEverythingInput"
              },
              "examples": {
                "example1": {
                  "summary": "Coffee: small vs. large bag",
                  "value": {
                    "basis": "unit",
                    "currency": "EUR",
                    "products": [
                      {
                        "name": "Coffee 250 g",
                        "price": 4.99,
                        "quantity": 250,
                        "unit": "g"
                      },
                      {
                        "name": "Coffee 1 kg",
                        "price": 15.49,
                        "quantity": 1000,
                        "unit": "g",
                        "wastePercent": 10
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Shoes: cost per wear",
                  "value": {
                    "basis": "use",
                    "currency": "USD",
                    "products": [
                      {
                        "name": "Budget sneakers",
                        "price": 40,
                        "quantity": 120
                      },
                      {
                        "name": "Leather boots",
                        "price": 180,
                        "quantity": 900
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PricePerEverythingResult"
                }
              }
            }
          },
          "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/break-even-explorer/calculate": {
      "post": {
        "operationId": "calculateBreakEvenExplorer",
        "summary": "When does an investment pay for itself? Payback, NPV and IRR for solar, EVs, heat pumps…",
        "description": "Enter an upfront investment, subsidies and the yearly savings it brings, with optional price escalation, performance degradation and maintenance. The explorer returns simple and discounted payback (fractional years), NPV, IRR and a year-by-year cumulative cash-flow table.\n\nMethodology:\n- Net investment (year 0 outflow) = upfront investment − subsidies.\n- Savings in year t = year-1 savings × (1 + escalation)^(t−1) × (1 − degradation)^(t−1), unless overridden for that year.\n- Net cash flow in year t = savings − maintenance (maintenance is constant in nominal terms).\n- Simple payback = the year in which cumulative cash flow turns non-negative, interpolated linearly within that year (savings assumed to accrue evenly).\n- Discounted payback uses cash flows discounted by (1 + r)^t; NPV = −net investment + Σ net cash flow ÷ (1 + r)^t.\n- IRR = the rate at which NPV = 0, found by bisection between −99% and 1000% (null if NPV does not change sign in that range).\n- Presets only supply defaults for escalation, degradation and horizon when those fields are left empty; the values used are always listed.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "break_even_explorer",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BreakEvenExplorerInput"
              },
              "examples": {
                "example1": {
                  "summary": "Rooftop solar with a grant",
                  "value": {
                    "preset": "solar",
                    "currency": "EUR",
                    "upfrontInvestment": 12000,
                    "subsidies": 1500,
                    "annualSavings": 1100,
                    "maintenancePerYear": 100,
                    "discountRatePercent": 3
                  }
                },
                "example2": {
                  "summary": "Insulation project, flat savings, 15 years",
                  "value": {
                    "preset": "custom",
                    "currency": "EUR",
                    "upfrontInvestment": 6000,
                    "subsidies": 0,
                    "annualSavings": 700,
                    "savingsEscalationPercent": 0,
                    "degradationPercent": 0,
                    "maintenancePerYear": 0,
                    "discountRatePercent": 4,
                    "horizonYears": 15
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BreakEvenExplorerResult"
                }
              }
            }
          },
          "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/decision-simulator/calculate": {
      "post": {
        "operationId": "calculateDecisionSimulator",
        "summary": "Compare 2–6 choices by expected value, worst case, best case and minimax regret.",
        "description": "Give each choice a worst, most likely and best outcome (money, hours, points…). The simulator computes expected values with PERT or triangular weights (or your own probabilities), then applies maximin, maximax, minimax regret and Laplace rules and explains which rule picks which choice.\n\nMethodology:\n- Each choice has three scenario outcomes: worst (W), most likely (M) and best (B).\n- PERT expected value = (W + 4M + B) ÷ 6, standard deviation ≈ (B − W) ÷ 6.\n- Triangular expected value = (W + M + B) ÷ 3, standard deviation = √((W² + M² + B² − WM − WB − MB) ÷ 18).\n- Custom: probabilities are normalised to sum to 1; EV = Σ pᵢ·xᵢ, standard deviation = √(Σ pᵢ·(xᵢ − EV)²).\n- Maximin picks the best worst case; maximax the best best case; Laplace the best unweighted average (W + M + B) ÷ 3.\n- Minimax regret treats worst / likely / best as three shared states of the world: regret = (best outcome of any choice in that state) − (this choice’s outcome); the choice with the smallest maximum regret wins.\n- When lower values are better, “best” means lowest everywhere. Ties go to the choice listed first. The recommendation is the expected-value pick.",
        "tags": [
          "everyday"
        ],
        "x-mcp-tool": "decision_simulator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecisionSimulatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "Job offers: first-year net gain",
                  "value": {
                    "goal": "maximize",
                    "method": "pert",
                    "unit": "EUR",
                    "choices": [
                      {
                        "name": "Stay in current job",
                        "worst": 48000,
                        "likely": 50000,
                        "best": 52000
                      },
                      {
                        "name": "Startup offer",
                        "worst": 30000,
                        "likely": 58000,
                        "best": 90000
                      },
                      {
                        "name": "Corporate offer",
                        "worst": 50000,
                        "likely": 55000,
                        "best": 60000
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Commute: travel minutes per day",
                  "value": {
                    "goal": "minimize",
                    "method": "custom",
                    "unit": "minutes",
                    "choices": [
                      {
                        "name": "Car",
                        "worst": 90,
                        "likely": 45,
                        "best": 35,
                        "pWorst": 10,
                        "pLikely": 70,
                        "pBest": 20
                      },
                      {
                        "name": "Train",
                        "worst": 70,
                        "likely": 55,
                        "best": 50,
                        "pWorst": 15,
                        "pLikely": 75,
                        "pBest": 10
                      },
                      {
                        "name": "Bike",
                        "worst": 60,
                        "likely": 58,
                        "best": 55,
                        "pWorst": 20,
                        "pLikely": 60,
                        "pBest": 20
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DecisionSimulatorResult"
                }
              }
            }
          },
          "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/salary-reality-check/calculate": {
      "post": {
        "operationId": "calculateSalaryRealityCheck",
        "summary": "Compare job offers by real net pay per hour, including commute time and living costs.",
        "description": "Enter 2–4 job offers with salary, your own effective tax rate, bonus, benefits, commute and cost-of-living differences. The tool ranks them by net income per hour of working plus commuting time, so a higher salary with a long commute or an expensive city is put into perspective.\n\nMethodology:\n- Gross total = gross salary + annual bonus. Net annual = gross total × (1 − effective tax+social rate) + benefits value (benefits are treated as already net).\n- The effective tax+social rate is supplied by you; no national tax rules are applied.\n- Commute days per year = working days × (1 − remote days per week ÷ 5). Commute hours = commute days × 2 × one-way minutes ÷ 60.\n- Work hours per year = weekly hours ÷ 5 × working days (a 5-day week is assumed).\n- Net after costs = net annual − 12 × monthly commute cost − 12 × monthly living cost difference.\n- Net per hour incl. commute = net after costs ÷ (work hours + commute hours). Offers are ranked by this value; ties keep input order.\n- Value of commute time = commute hours × (net annual ÷ work hours).\n- Difference to the top-ranked offer per year = net after costs of the top-ranked offer − net after costs of this offer. A negative value means this offer keeps more money per year than the top-ranked one, but needs more hours for it.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "salary_reality_check",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SalaryRealityCheckInput"
              },
              "examples": {
                "example1": {
                  "summary": "City job vs. remote-friendly job",
                  "value": {
                    "currency": "EUR",
                    "offers": [
                      {
                        "name": "Big-city bank",
                        "grossAnnual": 72000,
                        "taxRatePercent": 42,
                        "bonus": 6000,
                        "benefitsValue": 1200,
                        "commuteMinutesOneWay": 55,
                        "commuteCostMonthly": 90,
                        "workingDays": 220,
                        "weeklyHours": 42,
                        "livingCostDiffMonthly": 450,
                        "remoteDaysPerWeek": 1
                      },
                      {
                        "name": "Local software firm",
                        "grossAnnual": 62000,
                        "taxRatePercent": 39,
                        "bonus": 0,
                        "benefitsValue": 800,
                        "commuteMinutesOneWay": 20,
                        "commuteCostMonthly": 40,
                        "workingDays": 220,
                        "weeklyHours": 40,
                        "livingCostDiffMonthly": 0,
                        "remoteDaysPerWeek": 3
                      }
                    ]
                  }
                },
                "example2": {
                  "summary": "Three offers with different hours",
                  "value": {
                    "currency": "USD",
                    "offers": [
                      {
                        "name": "Startup",
                        "grossAnnual": 95000,
                        "taxRatePercent": 30,
                        "benefitsValue": 3000,
                        "commuteMinutesOneWay": 30,
                        "commuteCostMonthly": 120,
                        "weeklyHours": 50
                      },
                      {
                        "name": "Corporate",
                        "grossAnnual": 105000,
                        "taxRatePercent": 32,
                        "bonus": 10000,
                        "benefitsValue": 8000,
                        "commuteMinutesOneWay": 60,
                        "commuteCostMonthly": 250,
                        "weeklyHours": 45
                      },
                      {
                        "name": "Fully remote",
                        "grossAnnual": 88000,
                        "taxRatePercent": 29,
                        "benefitsValue": 2000,
                        "remoteDaysPerWeek": 5,
                        "weeklyHours": 40
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SalaryRealityCheckResult"
                }
              }
            }
          },
          "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/tip-bill-split-calculator/calculate": {
      "post": {
        "operationId": "calculateTipBillSplitCalculator",
        "summary": "Tip, tax and who pays what: split a bill equally, by shares or by items, rounded up.",
        "description": "Enter the bill, the tax rule (included as in most of Europe, or added at the till as in the US) and a tip as a percentage or an amount, before or after tax. Split equally, by shares or by what each person ordered, round each share up to 0.50 or 1, and get an exact who-pays-what table that adds up to the cent.\n\nMethodology:\n- Tax included (VAT): subtotal before tax = bill ÷ (1 + rate); tax = bill − subtotal. Tax added (sales tax): tax = bill × rate, rounded to the cent; bill total = bill + tax.\n- Tip = tip % × tip base, where the base is the subtotal before tax or the total with tax (your choice), rounded to the cent; or a fixed tip amount.\n- Weights: equal = 1 per person; shares = the share you enter; items = each person's ordered amount plus an equal part of anything not assigned to anyone.\n- Bill, tax and tip are each split by weight with the largest-remainder method in whole cents, so the shares add up exactly to the totals.\n- Rounding is applied per person to their exact total; the difference between what is collected and the exact total goes to the tip and is shown separately.",
        "tags": [
          "calculators"
        ],
        "x-mcp-tool": "tip_bill_split_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TipBillSplitCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "Dinner for 4 in Germany, 84.50 incl. VAT, 10% tip, round up to 1",
                  "value": {
                    "symbol": "€",
                    "billAmount": 84.5,
                    "taxMode": "included",
                    "taxPercent": 19,
                    "tipMode": "percent",
                    "tipPercent": 10,
                    "tipBase": "post-tax",
                    "splitMode": "equal",
                    "people": 4,
                    "rounding": "up-1"
                  }
                },
                "example2": {
                  "summary": "US restaurant, 120 + 8.875% sales tax, 20% tip on pre-tax, by items",
                  "value": {
                    "symbol": "$",
                    "billAmount": 120,
                    "taxMode": "added",
                    "taxPercent": 8.875,
                    "tipMode": "percent",
                    "tipPercent": 20,
                    "tipBase": "pre-tax",
                    "splitMode": "items",
                    "persons": [
                      {
                        "name": "Ana",
                        "items": 34
                      },
                      {
                        "name": "Ben",
                        "items": 46
                      },
                      {
                        "name": "Chloe",
                        "items": 28
                      }
                    ],
                    "rounding": "none"
                  }
                },
                "example3": {
                  "summary": "Couple + 2 singles by shares, fixed tip 15",
                  "value": {
                    "symbol": "£",
                    "billAmount": 150,
                    "taxMode": "none",
                    "taxPercent": 0,
                    "tipMode": "amount",
                    "tipAmount": 15,
                    "splitMode": "shares",
                    "persons": [
                      {
                        "name": "Sam & Alex",
                        "shares": 2
                      },
                      {
                        "name": "Jo",
                        "shares": 1
                      },
                      {
                        "name": "Kim",
                        "shares": 1
                      }
                    ],
                    "rounding": "up-0.5"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TipBillSplitCalculatorResult"
                }
              }
            }
          },
          "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/percentage-ratio-calculator/calculate": {
      "post": {
        "operationId": "calculatePercentageRatioCalculator",
        "summary": "X% of Y, % change, reverse %, percentage points, ratios and aspect ratios, with formulas.",
        "description": "Answers every everyday percentage question (X% of Y, X is what percent of Y, percentage change, increase or decrease by a percentage, reverse percentage, percentage points versus percent) and every ratio task (simplify, scale, split an amount, aspect ratios). Each answer shows the formula and the steps.\n\nMethodology:\n- X% of Y = X ÷ 100 × Y. X as a percent of Y = X ÷ Y × 100.\n- Percentage change from X to Y = (Y − X) ÷ |X| × 100; positive is an increase.\n- Increase Y by X% = Y × (1 + X/100); decrease Y by X% = Y × (1 − X/100).\n- Reverse percentage: if Y is the value after a change of X%, the original is Y ÷ (1 + X/100) (use a negative X for a decrease, e.g. −20 for 20% off).\n- Percentage points measure the plain difference between two percentages (3% − 2% = 1 percentage point); percent measures the relative change ((3 − 2) ÷ 2 = 50%).\n- Ratios are simplified exactly: decimal terms are first multiplied by a power of 10 to make whole numbers, then divided by their greatest common divisor.\n- Splitting an amount by a ratio uses the largest-remainder method at the chosen decimal places, so the parts always add up to exactly the amount.\n- Aspect ratios: width:height simplified by the GCD, the decimal width ÷ height, and the nearest well-known ratio within 1%. Resizing keeps the ratio: new height = new width × height ÷ width.",
        "tags": [
          "calculators"
        ],
        "x-mcp-tool": "percentage_ratio_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PercentageRatioCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "What is 15% of 80?",
                  "value": {
                    "mode": "percent-of",
                    "x": 15,
                    "y": 80
                  }
                },
                "example2": {
                  "summary": "Price rose from 40 to 50: percentage change",
                  "value": {
                    "mode": "change",
                    "x": 40,
                    "y": 50
                  }
                },
                "example3": {
                  "summary": "Price after 19% VAT is 119: price before tax",
                  "value": {
                    "mode": "reverse",
                    "x": 19,
                    "y": 119
                  }
                },
                "example4": {
                  "summary": "Interest rate from 2% to 3%: points vs percent",
                  "value": {
                    "mode": "points",
                    "x": 2,
                    "y": 3
                  }
                },
                "example5": {
                  "summary": "Split 1,000 by 2:3:5",
                  "value": {
                    "mode": "ratio-split",
                    "ratio": "2:3:5",
                    "amount": 1000
                  }
                },
                "example6": {
                  "summary": "1920 × 1080 aspect ratio, resize to 1280 wide",
                  "value": {
                    "mode": "aspect",
                    "x": 1920,
                    "y": 1080,
                    "newWidth": 1280
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PercentageRatioCalculatorResult"
                }
              }
            }
          },
          "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/inflation-time-machine/calculate": {
      "post": {
        "operationId": "calculateInflationTimeMachine",
        "summary": "What was money from one year worth in another? CPI-based, 20 countries, World Bank data.",
        "description": "Convert an amount between years with the consumer price index (2010 = 100) published by the World Bank. Shows the equivalent value, cumulative inflation, average annual inflation and lost purchasing power, with a year-by-year table.\n\nMethodology:\n- Data: World Bank WDI indicator FP.CPI.TOTL, consumer price index (2010 = 100), annual averages, snapshot stored with retrieval date.\n- Equivalent value = amount × CPI(to year) ÷ CPI(from year).\n- Cumulative inflation = (CPI(later) ÷ CPI(earlier) − 1) × 100.\n- Average annual inflation (CAGR) = ((CPI(later) ÷ CPI(earlier))^(1 ÷ years) − 1) × 100.\n- Purchasing power loss = (1 − CPI(earlier) ÷ CPI(later)) × 100: how much less the same nominal amount buys in the later year.\n- Because CPI values are annual averages, results compare average price levels of whole years, not specific dates.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "inflation_time_machine",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InflationTimeMachineInput"
              },
              "examples": {
                "example1": {
                  "summary": "100 EUR in Germany, 2000 → 2024",
                  "value": {
                    "amount": 100,
                    "currency": "EUR",
                    "fromYear": 2000,
                    "toYear": 2024,
                    "country": "DE"
                  }
                },
                "example2": {
                  "summary": "1,000 USD in the US, 1980 → 2024",
                  "value": {
                    "amount": 1000,
                    "currency": "USD",
                    "fromYear": 1980,
                    "toYear": 2024,
                    "country": "US"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InflationTimeMachineResult"
                }
              }
            }
          },
          "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/currency-converter/calculate": {
      "post": {
        "operationId": "calculateCurrencyConverter",
        "summary": "Convert an amount into several currencies at ECB euro reference rates, with rate date and source.",
        "description": "Converts an amount between the 30 currencies of the European Central Bank’s daily euro foreign exchange reference rates – into several target currencies at once, at the latest rates or those of a past date. Cross rates are calculated via the euro; every result shows the rate, the inverse rate, the reference date and the source.\n\nMethodology:\n- Rates come from the ECB’s daily reference-rate file (eurofxref-daily.xml) or, for past dates, from the ECB Data Portal (dataset EXR, daily series D.<currency>.EUR.SP00.A). They are cached per day; the daily file is re-checked at most hourly until the day’s rates (around 16:00 CET) are available.\n- All ECB rates are quoted as units of currency per 1 euro, so a cross rate is rate(to) ÷ rate(from); converting X from A to B = X × rate(B) ÷ rate(A). Rates are shown with 6 significant digits; amounts are rounded half away from zero to the currency’s minor units (0 for JPY, KRW and ISK).\n- For a date without a publication (weekends, TARGET holidays) the last earlier publication is used and named.\n- Reference rates are published for information only. Card schemes, banks and exchange offices use their own rates and add fees, so the amount you actually pay or receive will differ.\n\nECB reference rates are not transaction rates. For a real payment, check your provider’s rate and fees.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "currency_converter",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CurrencyConverterInput"
              },
              "examples": {
                "example1": {
                  "summary": "100 EUR into USD, GBP, CHF and JPY (latest)",
                  "value": {
                    "amount": 100,
                    "from": "EUR",
                    "to": "USD, GBP, CHF, JPY"
                  }
                },
                "example2": {
                  "summary": "250 USD into EUR and GBP on 2024-01-02",
                  "value": {
                    "amount": 250,
                    "from": "USD",
                    "to": "EUR, GBP",
                    "date": "2024-01-02"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CurrencyConverterResult"
                }
              }
            }
          },
          "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/historical-fx-chart/calculate": {
      "post": {
        "operationId": "calculateHistoricalFxChart",
        "summary": "Chart exchange rates over time from ECB data: min, max, average, change, volatility and CSV export.",
        "description": "Pick a base currency, one or more quote currencies and a date range (back to 1999). The tool loads the ECB’s daily euro reference rates, builds the cross rates, draws a line chart (indexed to 100 to compare several pairs) and reports start, end, minimum, maximum, average, change and annualised volatility, with a CSV download.\n\nMethodology:\n- Daily reference rates (units per euro) are loaded from the ECB Data Portal SDMX API as CSV (series D.<currency>.EUR.SP00.A, only the requested currencies and dates) and cached; ranges ending more than a week ago are cached permanently, recent ranges per day.\n- Cross rate base/quote on each publication day = rate(quote) ÷ rate(base); days where either leg is missing are skipped. EUR legs are 1.\n- Statistics: first and last observation, minimum and maximum with dates, arithmetic mean, change = last ÷ first − 1, annualised volatility = standard deviation of daily log returns × √252.\n- Indexed view: every pair divided by its first value × 100, so pairs with very different levels (e.g. JPY and GBP) can be compared.\n\nReference rates for information; they are not the rates at which you could have traded.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "historical_fx_chart",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HistoricalFxChartInput"
              },
              "examples": {
                "example1": {
                  "summary": "EUR/USD and EUR/GBP over 2024 (indexed)",
                  "value": {
                    "base": "EUR",
                    "quotes": "USD, GBP",
                    "start": "2024-01-01",
                    "end": "2024-12-31"
                  }
                },
                "example2": {
                  "summary": "USD/JPY in 2023",
                  "value": {
                    "base": "USD",
                    "quotes": "JPY",
                    "start": "2023-01-01",
                    "end": "2023-12-31",
                    "png": false
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HistoricalFxChartResult"
                }
              }
            }
          },
          "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/multi-currency-invoice-calculator/calculate": {
      "post": {
        "operationId": "calculateMultiCurrencyInvoiceCalculator",
        "summary": "Convert invoice lines in different currencies at ECB reference rates, with VAT, rounding and totals.",
        "description": "Enter invoice lines in different currencies, the invoice currency and a rate date. The calculator converts every line at the ECB euro reference rates of that date (or your own rates), applies VAT per line, shows the VAT summary and totals with either per-line or per-document rounding (and the difference between both), and writes the “converted at the ECB reference rate of …” statement. Download as CSV or an HTML table.\n\nMethodology:\n- Each line’s net amount = quantity × unit price, rounded to the minor units of its own currency, then converted with the cross rate rate(invoice currency) ÷ rate(line currency), both quoted per euro by the ECB.\n- Per-line rounding: each converted net amount and its VAT are rounded to the invoice currency’s minor units and then added up. Per-document rounding: exact converted amounts are added up per VAT rate, the net subtotal is rounded and VAT is calculated once per rate. Both totals are shown, because tax authorities and accounting systems differ in which they expect.\n- Rounding is half away from zero (commercial rounding). Lines without a currency use the invoice currency; lines without a VAT rate have 0 %.\n- ECB rates for a date are the last publication on or before it (none on weekends and TARGET holidays). Manual rates are entered per euro, exactly like the ECB quotes.\n\nNot tax advice. Check which exchange rate and rounding your tax authority requires for invoices and VAT returns.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "multi_currency_invoice_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MultiCurrencyInvoiceCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "EUR invoice with USD and GBP costs (manual ECB-style rates)",
                  "value": {
                    "invoiceCurrency": "EUR",
                    "rateSource": "manual",
                    "rateDate": "2026-10-16",
                    "manualRates": "USD 1.1253\nGBP 0.84385",
                    "lines": [
                      {
                        "description": "Consulting (days)",
                        "quantity": 3,
                        "unitPrice": 850,
                        "currency": "EUR",
                        "vatRate": 19
                      },
                      {
                        "description": "Cloud hosting",
                        "quantity": 1,
                        "unitPrice": 412.37,
                        "currency": "USD",
                        "vatRate": 19
                      },
                      {
                        "description": "Conference ticket",
                        "quantity": 2,
                        "unitPrice": 299.99,
                        "currency": "GBP",
                        "vatRate": 7
                      },
                      {
                        "description": "Travel expenses (no VAT)",
                        "quantity": 1,
                        "unitPrice": 123.45,
                        "currency": "USD",
                        "vatRate": 0
                      }
                    ],
                    "rounding": "line"
                  }
                },
                "example2": {
                  "summary": "CHF invoice, document rounding (cross rates via EUR)",
                  "value": {
                    "invoiceCurrency": "CHF",
                    "rateSource": "manual",
                    "manualRates": "CHF 0.9272\nUSD 1.1253\nJPY 177.82",
                    "rounding": "document",
                    "lines": [
                      {
                        "description": "Licences",
                        "quantity": 7,
                        "unitPrice": 19.99,
                        "currency": "USD",
                        "vatRate": 8.1
                      },
                      {
                        "description": "Support",
                        "quantity": 1,
                        "unitPrice": 15000,
                        "currency": "JPY",
                        "vatRate": 8.1
                      },
                      {
                        "description": "Setup",
                        "quantity": 1,
                        "unitPrice": 250,
                        "currency": "CHF",
                        "vatRate": 8.1
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MultiCurrencyInvoiceCalculatorResult"
                }
              }
            }
          },
          "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/travel-exchange-fee-calculator/calculate": {
      "post": {
        "operationId": "calculateTravelExchangeFeeCalculator",
        "summary": "Compare what cards, ATMs, DCC and exchange bureaus really cost abroad vs the ECB reference rate.",
        "description": "Enter what you spend abroad and the terms of your payment options – foreign transaction fee, exchange-rate mark-up, ATM fees, “pay in your currency” (dynamic currency conversion) and exchange-bureau spreads. The calculator converts at the ECB reference rate (or your own rate) and shows the effective rate, total cost and extra cost of each option, cheapest first.\n\nMethodology:\n- Reference cost = amount ÷ reference rate (the ECB euro reference rate, cross-calculated via EUR for non-euro pairs, or your manual rate).\n- Each option: cost at its own rate = reference cost × (1 + mark-up %); plus the percentage fee on that amount; plus fixed fees (ATM fixed fees × number of withdrawals). Effective rate = amount ÷ total cost; extra cost = total − reference cost.\n- Dynamic currency conversion (DCC, “pay in your currency”) means the shop’s or ATM operator’s provider converts instead of your card issuer; its mark-up is entered as a rate mark-up. Under Regulation (EU) 2019/518 DCC providers and card issuers in the EU must express their conversion charges as a mark-up over the latest ECB reference rate before you pay, which is the comparison this tool makes.\n- The default rows are illustrative example values, not market data. Replace them with your card’s published terms and the mark-up shown on the terminal or at the exchange counter.\n\nAn estimate: the rate your card network applies on the booking day and any fees not entered here change the final amount.",
        "tags": [
          "money"
        ],
        "x-mcp-tool": "travel_exchange_fee_calculator",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TravelExchangeFeeCalculatorInput"
              },
              "examples": {
                "example1": {
                  "summary": "500 USD with the example options (manual rate 1 EUR = 1.10 USD)",
                  "value": {
                    "amount": 500,
                    "spendCurrency": "USD",
                    "homeCurrency": "EUR",
                    "rateSource": "manual",
                    "manualRate": 1.1
                  }
                },
                "example2": {
                  "summary": "20,000 JPY, 2 ATM withdrawals (manual rate 1 GBP = 190 JPY)",
                  "value": {
                    "amount": 20000,
                    "spendCurrency": "JPY",
                    "homeCurrency": "GBP",
                    "rateSource": "manual",
                    "manualRate": 190,
                    "atmWithdrawals": 2,
                    "options": [
                      {
                        "name": "Travel card",
                        "method": "card",
                        "feePercent": 0,
                        "markupPercent": 0.4,
                        "fixedFee": 0
                      },
                      {
                        "name": "High-street bank ATM",
                        "method": "atm",
                        "feePercent": 2.99,
                        "markupPercent": 0,
                        "fixedFee": 1.5
                      },
                      {
                        "name": "DCC offer at the ATM",
                        "method": "dcc",
                        "feePercent": 0,
                        "markupPercent": 8,
                        "fixedFee": 1.5
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Calculation result envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TravelExchangeFeeCalculatorResult"
                }
              }
            }
          },
          "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"
                }
              }
            }
          }
        }
      },
      "SubscriptionStackInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "minUsesPerMonth": {
            "title": "Flag if used fewer than",
            "type": "number",
            "minimum": 0,
            "maximum": 1000,
            "default": 2,
            "x-unit": "times/month"
          },
          "maxCostPerUse": {
            "title": "Flag if cost per use is above",
            "description": "In your currency, for your share.",
            "type": "number",
            "minimum": 0,
            "default": 5
          },
          "subscriptions": {
            "title": "Subscriptions",
            "description": "Price is the full price per billing period; “Shared by” splits it (you pay price ÷ people). Leave uses empty if unknown.",
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Service",
                  "type": "string"
                },
                "price": {
                  "title": "Price",
                  "type": "number",
                  "minimum": 0
                },
                "billing": {
                  "title": "Billed",
                  "type": "string",
                  "enum": [
                    "weekly",
                    "monthly",
                    "quarterly",
                    "yearly"
                  ],
                  "default": "monthly"
                },
                "category": {
                  "title": "Category",
                  "type": "string",
                  "enum": [
                    "video",
                    "music",
                    "news",
                    "software",
                    "cloud-storage",
                    "fitness",
                    "gaming",
                    "food",
                    "other"
                  ],
                  "default": "other"
                },
                "usesPerMonth": {
                  "title": "Uses / month",
                  "type": "number",
                  "minimum": 0
                },
                "sharedBy": {
                  "title": "Shared by",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 20,
                  "default": 1,
                  "x-unit": "people"
                }
              },
              "required": [
                "name",
                "price"
              ]
            }
          }
        },
        "required": [
          "subscriptions"
        ]
      },
      "SubscriptionStackResult": {
        "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",
              "monthlyTotal",
              "annualTotal",
              "fullPriceMonthlyTotal",
              "subscriptions",
              "categories",
              "overlaps",
              "cancelCandidates",
              "savingsIfCancelled"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "monthlyTotal": {
                "type": "number",
                "description": "Your share, per month."
              },
              "annualTotal": {
                "type": "number"
              },
              "fullPriceMonthlyTotal": {
                "type": "number",
                "description": "Before splitting shared subscriptions."
              },
              "subscriptions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "name",
                    "category",
                    "billing",
                    "monthlyFullPrice",
                    "monthlyYourShare",
                    "annualYourShare",
                    "usesPerMonth",
                    "costPerUse",
                    "shareOfTotalPercent",
                    "cancelCandidate",
                    "reasons"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "video",
                        "music",
                        "news",
                        "software",
                        "cloud-storage",
                        "fitness",
                        "gaming",
                        "food",
                        "other"
                      ]
                    },
                    "billing": {
                      "type": "string",
                      "enum": [
                        "weekly",
                        "monthly",
                        "quarterly",
                        "yearly"
                      ]
                    },
                    "monthlyFullPrice": {
                      "type": "number"
                    },
                    "monthlyYourShare": {
                      "type": "number"
                    },
                    "annualYourShare": {
                      "type": "number"
                    },
                    "usesPerMonth": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "costPerUse": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Your monthly share ÷ uses per month; null if uses unknown or 0."
                    },
                    "shareOfTotalPercent": {
                      "type": "number"
                    },
                    "cancelCandidate": {
                      "type": "boolean"
                    },
                    "reasons": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              },
              "categories": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "category",
                    "label",
                    "count",
                    "monthly",
                    "annual"
                  ],
                  "properties": {
                    "category": {
                      "type": "string",
                      "enum": [
                        "video",
                        "music",
                        "news",
                        "software",
                        "cloud-storage",
                        "fitness",
                        "gaming",
                        "food",
                        "other"
                      ]
                    },
                    "label": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "monthly": {
                      "type": "number"
                    },
                    "annual": {
                      "type": "number"
                    }
                  }
                }
              },
              "overlaps": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "category",
                    "services",
                    "monthly"
                  ],
                  "properties": {
                    "category": {
                      "type": "string",
                      "enum": [
                        "video",
                        "music",
                        "news",
                        "software",
                        "cloud-storage",
                        "fitness",
                        "gaming",
                        "food",
                        "other"
                      ]
                    },
                    "services": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "monthly": {
                      "type": "number"
                    }
                  }
                }
              },
              "cancelCandidates": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "savingsIfCancelled": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "monthly",
                  "annual"
                ],
                "properties": {
                  "monthly": {
                    "type": "number"
                  },
                  "annual": {
                    "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"
              }
            }
          }
        }
      },
      "CancellationNavigatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "service": {
            "title": "Service",
            "type": "string",
            "enum": [
              "netflix",
              "spotify",
              "amazon-prime",
              "kindle-unlimited",
              "disney-plus",
              "hulu",
              "paramount-plus",
              "youtube-premium",
              "google-one",
              "microsoft-365",
              "xbox",
              "playstation-plus",
              "adobe",
              "audible",
              "dazn",
              "linkedin-premium",
              "dropbox",
              "chatgpt-plus",
              "duolingo",
              "other"
            ],
            "default": "netflix"
          },
          "serviceName": {
            "title": "Service name (if Other)",
            "type": "string",
            "maxLength": 120
          },
          "country": {
            "title": "Your country",
            "type": "string",
            "enum": [
              "DE",
              "AT",
              "EU-other",
              "UK",
              "US",
              "other"
            ],
            "default": "DE"
          },
          "channel": {
            "title": "Where did you sign up / who bills you?",
            "type": "string",
            "enum": [
              "web",
              "app-store",
              "google-play",
              "carrier",
              "other"
            ],
            "default": "web"
          },
          "yourName": {
            "title": "Your name (for the letter, optional)",
            "type": "string",
            "maxLength": 120
          },
          "customerNumber": {
            "title": "Customer / account number (optional)",
            "type": "string",
            "maxLength": 80
          }
        },
        "required": []
      },
      "CancellationNavigatorResult": {
        "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": [
              "service",
              "verified",
              "officialUrl",
              "verifiedAt",
              "stepsSource",
              "steps",
              "serviceNotes",
              "channelNotes",
              "rights",
              "letter"
            ],
            "properties": {
              "service": {
                "type": "string"
              },
              "verified": {
                "type": "boolean",
                "description": "True if the steps come from an official help page checked on verifiedAt (false = generic method)."
              },
              "officialUrl": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "verifiedAt": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date"
              },
              "stepsSource": {
                "type": "string",
                "enum": [
                  "service-help-page",
                  "apple-support",
                  "google-play-help",
                  "generic"
                ]
              },
              "steps": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "serviceNotes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "channelNotes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "rights": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "title",
                    "url",
                    "text"
                  ],
                  "properties": {
                    "title": {
                      "type": "string"
                    },
                    "url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "text": {
                      "type": "string"
                    }
                  }
                }
              },
              "letter": {
                "type": "string",
                "description": "Plain-text cancellation letter template."
              }
            }
          },
          "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"
              }
            }
          }
        }
      },
      "BillDetectiveInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "unit": {
            "title": "Consumption unit",
            "type": "string",
            "enum": [
              "kWh",
              "m3",
              "liters",
              "GB"
            ],
            "default": "kWh"
          },
          "currency": {
            "title": "Currency",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "changeThresholdPercent": {
            "title": "Flag per-day change vs. previous period above",
            "type": "number",
            "minimum": 1,
            "maximum": 1000,
            "default": 25,
            "x-unit": "%"
          },
          "outlierPercent": {
            "title": "Flag periods above median by more than",
            "type": "number",
            "minimum": 1,
            "maximum": 1000,
            "default": 30,
            "x-unit": "%"
          },
          "tolerancePercent": {
            "title": "Arithmetic tolerance",
            "description": "Billed total may differ from consumption × unit price + charges by this much (rounding, taxes included in prices) before it is flagged.",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "default": 1,
            "x-unit": "%"
          },
          "periods": {
            "title": "Billing periods",
            "description": "Start and end dates are both counted as billing days. Fixed charge = total standing/base charge for the period. Leave Total billed empty to skip the arithmetic check for a row.",
            "type": "array",
            "minItems": 2,
            "maxItems": 36,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "label": {
                  "title": "Period",
                  "type": "string"
                },
                "startDate": {
                  "title": "Start date",
                  "type": "string",
                  "format": "date"
                },
                "endDate": {
                  "title": "End date",
                  "type": "string",
                  "format": "date"
                },
                "consumption": {
                  "title": "Consumption",
                  "type": "number",
                  "minimum": 0
                },
                "unitPrice": {
                  "title": "Unit price",
                  "type": "number",
                  "minimum": 0
                },
                "fixedCharge": {
                  "title": "Fixed / standing charge",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "otherCharges": {
                  "title": "Other charges",
                  "type": "number",
                  "default": 0
                },
                "totalBilled": {
                  "title": "Total billed",
                  "type": "number",
                  "minimum": 0
                }
              },
              "required": [
                "label",
                "startDate",
                "endDate",
                "consumption",
                "unitPrice"
              ]
            }
          }
        },
        "required": [
          "periods"
        ]
      },
      "BillDetectiveResult": {
        "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",
              "currency",
              "averagePerDay",
              "medianPerDay",
              "periods",
              "anomalies",
              "anomalyCount"
            ],
            "properties": {
              "unit": {
                "type": "string"
              },
              "currency": {
                "type": "string"
              },
              "averagePerDay": {
                "type": "number"
              },
              "medianPerDay": {
                "type": "number"
              },
              "iqr": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "q1",
                  "q3",
                  "lowerFence",
                  "upperFence"
                ],
                "properties": {
                  "q1": {
                    "type": "number"
                  },
                  "q3": {
                    "type": "number"
                  },
                  "lowerFence": {
                    "type": "number"
                  },
                  "upperFence": {
                    "type": "number"
                  }
                }
              },
              "anomalyCount": {
                "type": "integer"
              },
              "periods": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "label",
                    "startDate",
                    "endDate",
                    "days",
                    "consumption",
                    "perDay",
                    "changeVsPreviousPercent",
                    "vsAveragePercent",
                    "unitPrice",
                    "fixedCharge",
                    "fixedChargePerDay",
                    "computedTotal",
                    "totalBilled",
                    "difference",
                    "costPerDay"
                  ],
                  "properties": {
                    "label": {
                      "type": "string"
                    },
                    "startDate": {
                      "type": "string",
                      "format": "date"
                    },
                    "endDate": {
                      "type": "string",
                      "format": "date"
                    },
                    "days": {
                      "type": "integer"
                    },
                    "consumption": {
                      "type": "number"
                    },
                    "perDay": {
                      "type": "number"
                    },
                    "changeVsPreviousPercent": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "vsAveragePercent": {
                      "type": "number"
                    },
                    "unitPrice": {
                      "type": "number"
                    },
                    "fixedCharge": {
                      "type": "number"
                    },
                    "fixedChargePerDay": {
                      "type": "number"
                    },
                    "computedTotal": {
                      "type": "number"
                    },
                    "totalBilled": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "difference": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Total billed − computed total."
                    },
                    "costPerDay": {
                      "type": "number",
                      "description": "Billed total (or computed total if not given) ÷ days."
                    }
                  }
                }
              },
              "anomalies": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "period",
                    "type",
                    "severity",
                    "explanation"
                  ],
                  "properties": {
                    "period": {
                      "type": "string"
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "consumption-change",
                        "above-average",
                        "outlier-iqr",
                        "outlier-median",
                        "unit-price-change",
                        "fixed-charge-change",
                        "arithmetic-mismatch",
                        "period-overlap",
                        "period-gap"
                      ]
                    },
                    "severity": {
                      "type": "string",
                      "enum": [
                        "info",
                        "warning",
                        "alert"
                      ]
                    },
                    "explanation": {
                      "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"
              }
            }
          }
        }
      },
      "HiddenFeeFinderInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "text": {
            "title": "Quote / contract / invoice lines",
            "description": "One cost per line. Supported amount formats: 1,234.56 / 1.234,56 / €12 / 12 EUR / 12,50 € and percentages like 19% or 2.5 %.",
            "type": "string",
            "maxLength": 50000
          },
          "items": {
            "title": "Or: line items",
            "description": "Optional alternative to pasting text. Rows are appended after the pasted lines.",
            "type": "array",
            "maxItems": 100,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "description": {
                  "title": "Description",
                  "type": "string"
                },
                "amount": {
                  "title": "Amount",
                  "type": "number"
                }
              },
              "required": [
                "description",
                "amount"
              ]
            }
          },
          "currency": {
            "title": "Currency",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "numberFormat": {
            "title": "Number format",
            "type": "string",
            "enum": [
              "auto",
              "en",
              "de"
            ],
            "default": "auto"
          }
        },
        "required": []
      },
      "HiddenFeeFinderResult": {
        "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",
              "lines",
              "totals",
              "statedTotal",
              "statedTotalDifference",
              "hiddenOrOptional",
              "conditionalCharges"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "statedTotal": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Amount on the last line recognised as a total, if any."
              },
              "statedTotalDifference": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Stated total − computed payable total."
              },
              "totals": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "base",
                  "fees",
                  "taxes",
                  "surcharges",
                  "insuranceAddons",
                  "recurring",
                  "discounts",
                  "payableTotal",
                  "nonBaseCosts",
                  "nonBaseSharePercent",
                  "deposits",
                  "conditionalPotential",
                  "upfrontCashInclDeposits"
                ],
                "properties": {
                  "base": {
                    "type": "number"
                  },
                  "fees": {
                    "type": "number"
                  },
                  "taxes": {
                    "type": "number"
                  },
                  "surcharges": {
                    "type": "number"
                  },
                  "insuranceAddons": {
                    "type": "number"
                  },
                  "recurring": {
                    "type": "number"
                  },
                  "discounts": {
                    "type": "number",
                    "description": "Negative number."
                  },
                  "payableTotal": {
                    "type": "number",
                    "description": "Sum of all lines included in the total (excludes deposits, conditional charges, totals and taxes stated as included)."
                  },
                  "nonBaseCosts": {
                    "type": "number",
                    "description": "fees + taxes + surcharges + insurance/add-ons + recurring (non-base lines)."
                  },
                  "nonBaseSharePercent": {
                    "type": "number",
                    "description": "nonBaseCosts ÷ payableTotal × 100."
                  },
                  "deposits": {
                    "type": "number"
                  },
                  "conditionalPotential": {
                    "type": "number",
                    "description": "Sum of conditional charges (only due if triggered)."
                  },
                  "upfrontCashInclDeposits": {
                    "type": "number"
                  }
                }
              },
              "lines": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "lineNumber",
                    "text",
                    "amount",
                    "percent",
                    "amountSource",
                    "category",
                    "matchedKeyword",
                    "recurring",
                    "recurringKeyword",
                    "conditional",
                    "conditionalKeyword",
                    "optional",
                    "optionalKeyword",
                    "includedTax",
                    "includedInTotal"
                  ],
                  "properties": {
                    "lineNumber": {
                      "type": "integer"
                    },
                    "text": {
                      "type": "string"
                    },
                    "amount": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "percent": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "amountSource": {
                      "type": "string",
                      "enum": [
                        "stated",
                        "derived-from-percent",
                        "none"
                      ]
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "base",
                        "fee",
                        "tax",
                        "surcharge",
                        "penalty",
                        "deposit",
                        "discount",
                        "insurance-addon",
                        "recurring",
                        "total",
                        "info"
                      ]
                    },
                    "matchedKeyword": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Keyword that triggered the category (null = default base price)."
                    },
                    "recurring": {
                      "type": "boolean"
                    },
                    "recurringKeyword": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "conditional": {
                      "type": "boolean"
                    },
                    "conditionalKeyword": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "optional": {
                      "type": "boolean"
                    },
                    "optionalKeyword": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "includedTax": {
                      "type": "boolean",
                      "description": "Tax stated as already included (e.g. \"incl. VAT\"); not added again."
                    },
                    "includedInTotal": {
                      "type": "boolean"
                    }
                  }
                }
              },
              "hiddenOrOptional": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "conditionalCharges": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "LoanAmortizationCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "principal": {
            "title": "Loan amount",
            "type": "number",
            "minimum": 1,
            "maximum": 10000000000,
            "default": 200000
          },
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "ratePercent": {
            "title": "Nominal interest rate",
            "description": "Borrowing rate (Sollzins). The rate per period is this ÷ payments per year.",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 3.5,
            "x-unit": "% per year"
          },
          "loanType": {
            "title": "Repayment type",
            "type": "string",
            "enum": [
              "annuity",
              "linear",
              "interest-only"
            ],
            "default": "annuity"
          },
          "termYears": {
            "title": "Term",
            "type": "number",
            "minimum": 0.25,
            "maximum": 100,
            "default": 25,
            "x-unit": "years"
          },
          "initialRepaymentPercent": {
            "title": "Initial repayment rate (annuity only)",
            "description": "Optional, common in Germany (anfängliche Tilgung): payment per year = loan × (interest rate + this rate). The term then follows from the payment and the Term field is ignored.",
            "type": "number",
            "minimum": 0.01,
            "maximum": 100,
            "x-unit": "% per year"
          },
          "frequency": {
            "title": "Payments",
            "type": "string",
            "enum": [
              "monthly",
              "quarterly",
              "annually"
            ],
            "default": "monthly"
          },
          "startDate": {
            "title": "Payout date",
            "description": "Optional. The first payment is due one period later; dates are added to the schedule.",
            "type": "string",
            "format": "date"
          },
          "fixedRateYears": {
            "title": "Fixed-rate period",
            "description": "Optional (Zinsbindung): shows the balance still owed when the fixed rate ends.",
            "type": "number",
            "minimum": 0.25,
            "maximum": 100,
            "x-unit": "years"
          },
          "upfrontFees": {
            "title": "Up-front fees",
            "description": "Processing or broker fees paid at payout. They raise the APR, not the nominal rate.",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "extraPayments": {
            "title": "One-off extra payments",
            "description": "Paid together with the regular payment number you enter (e.g. 12 = end of the first year for monthly payments).",
            "type": "array",
            "maxItems": 30,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "period": {
                  "title": "Payment number",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 1200
                },
                "amount": {
                  "title": "Extra amount",
                  "type": "number",
                  "minimum": 0
                }
              },
              "required": [
                "period",
                "amount"
              ]
            }
          },
          "recurringExtra": {
            "title": "Recurring extra payment",
            "description": "Optional, e.g. an annual special repayment (Sondertilgung).",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "recurringEvery": {
            "title": "Recurring extra: every",
            "type": "integer",
            "minimum": 1,
            "maximum": 120,
            "default": 12,
            "x-unit": "payments"
          },
          "recurringFrom": {
            "title": "Recurring extra: first at payment number",
            "type": "integer",
            "minimum": 1,
            "maximum": 1200,
            "default": 12
          },
          "recurringUntil": {
            "title": "Recurring extra: last at payment number",
            "description": "Optional; empty = until the loan is repaid.",
            "type": "integer",
            "minimum": 1,
            "maximum": 1200
          }
        },
        "required": [
          "principal"
        ]
      },
      "LoanAmortizationCalculatorResult": {
        "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",
              "loanType",
              "periodsPerYear",
              "principal",
              "nominalRatePercent",
              "periodicRatePercent",
              "regularPayment",
              "numberOfPayments",
              "termYears",
              "totalInterest",
              "totalExtra",
              "totalPaid",
              "payoffDate",
              "effectiveRatePercent",
              "aprPercent",
              "fixedRate",
              "extrasSavings",
              "schedule"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "loanType": {
                "type": "string",
                "enum": [
                  "annuity",
                  "linear",
                  "interest-only"
                ]
              },
              "periodsPerYear": {
                "type": "integer"
              },
              "principal": {
                "type": "number"
              },
              "nominalRatePercent": {
                "type": "number"
              },
              "periodicRatePercent": {
                "type": "number"
              },
              "regularPayment": {
                "type": "number",
                "description": "Annuity: the constant payment; linear and interest-only: the first payment."
              },
              "numberOfPayments": {
                "type": "integer"
              },
              "termYears": {
                "type": "number"
              },
              "totalInterest": {
                "type": "number"
              },
              "totalExtra": {
                "type": "number"
              },
              "totalPaid": {
                "type": "number"
              },
              "payoffDate": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "effectiveRatePercent": {
                "type": "number",
                "description": "(1 + nominal/m)^m − 1, without fees."
              },
              "aprPercent": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Annual rate equating the net payout (loan − fees) with all payments (PAngV/CCD equation, standard months)."
              },
              "fixedRate": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "years",
                  "payments",
                  "balanceAtEnd",
                  "interestPaid",
                  "principalRepaid",
                  "endDate"
                ],
                "properties": {
                  "years": {
                    "type": "number"
                  },
                  "payments": {
                    "type": "integer"
                  },
                  "balanceAtEnd": {
                    "type": "number"
                  },
                  "interestPaid": {
                    "type": "number"
                  },
                  "principalRepaid": {
                    "type": "number"
                  },
                  "endDate": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "extrasSavings": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "required": [
                  "interestSaved",
                  "paymentsSaved"
                ],
                "properties": {
                  "interestSaved": {
                    "type": "number"
                  },
                  "paymentsSaved": {
                    "type": "integer"
                  }
                }
              },
              "schedule": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "period",
                    "date",
                    "payment",
                    "interest",
                    "principal",
                    "extra",
                    "balance"
                  ],
                  "properties": {
                    "period": {
                      "type": "integer"
                    },
                    "date": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "payment": {
                      "type": "number"
                    },
                    "interest": {
                      "type": "number"
                    },
                    "principal": {
                      "type": "number"
                    },
                    "extra": {
                      "type": "number"
                    },
                    "balance": {
                      "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"
              }
            }
          }
        }
      },
      "DeadlineDecoderInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "text": {
            "title": "Letter or notice text",
            "description": "Paste the text here, or use one of the other options below (exactly one).",
            "type": "string",
            "maxLength": 200000
          },
          "file": {
            "title": "Or upload a file",
            "description": "TXT, PDF, DOCX, HTML or a photo/scan (PNG/JPG, read with OCR). Max 5 MiB. Upload as {filename, contentType?, contentBase64}. Max 5120 KiB. Accepts .txt,.pdf,.docx,.png,.jpg,.jpeg,.html.",
            "type": "object",
            "additionalProperties": false,
            "required": [
              "filename",
              "contentBase64"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "contentType": {
                "type": "string"
              },
              "contentBase64": {
                "type": "string"
              }
            }
          },
          "referenceDate": {
            "title": "Reference date",
            "description": "Date of the letter or the day you received it. Relative deadlines (\"within 14 days\") are counted from here. Defaults to today.",
            "type": "string",
            "format": "date"
          },
          "referenceType": {
            "title": "The reference date is…",
            "type": "string",
            "enum": [
              "received",
              "letter"
            ],
            "default": "received"
          },
          "dateOrder": {
            "title": "Numeric dates like 03/04/2026 are",
            "type": "string",
            "enum": [
              "DMY",
              "MDY"
            ],
            "default": "DMY"
          }
        },
        "required": []
      },
      "DeadlineDecoderResult": {
        "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": [
              "referenceDate",
              "referenceType",
              "today",
              "deadlines",
              "datesMentioned",
              "actions",
              "consequences",
              "nextDeadline",
              "confidence"
            ],
            "properties": {
              "referenceDate": {
                "type": "string",
                "format": "date"
              },
              "referenceType": {
                "type": "string",
                "enum": [
                  "letter",
                  "received"
                ]
              },
              "today": {
                "type": "string",
                "format": "date"
              },
              "confidence": {
                "type": "string",
                "const": "pattern-match",
                "description": "All findings come from fixed text patterns, not from a statistical model."
              },
              "nextDeadline": {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Earliest deadline that is today or upcoming, or null."
              },
              "deadlines": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "date",
                    "daysRemaining",
                    "status",
                    "kind",
                    "rule",
                    "matchedText",
                    "evidence",
                    "action",
                    "calculation"
                  ],
                  "properties": {
                    "date": {
                      "type": "string",
                      "format": "date"
                    },
                    "daysRemaining": {
                      "type": "integer",
                      "description": "Calendar days from today (negative = overdue)."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "overdue",
                        "today",
                        "upcoming"
                      ]
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "absolute",
                        "relative"
                      ]
                    },
                    "rule": {
                      "type": "string"
                    },
                    "matchedText": {
                      "type": "string"
                    },
                    "evidence": {
                      "type": "string",
                      "description": "Exact sentence the deadline was found in."
                    },
                    "action": {
                      "type": "string"
                    },
                    "calculation": {
                      "type": "string"
                    }
                  }
                }
              },
              "datesMentioned": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "date",
                    "matchedText",
                    "rule",
                    "evidence"
                  ],
                  "properties": {
                    "date": {
                      "type": "string",
                      "format": "date"
                    },
                    "matchedText": {
                      "type": "string"
                    },
                    "rule": {
                      "type": "string"
                    },
                    "evidence": {
                      "type": "string"
                    }
                  }
                }
              },
              "actions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "action",
                    "rule",
                    "evidence"
                  ],
                  "properties": {
                    "action": {
                      "type": "string"
                    },
                    "rule": {
                      "type": "string"
                    },
                    "evidence": {
                      "type": "string"
                    }
                  }
                }
              },
              "consequences": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "rules",
                    "amounts",
                    "evidence"
                  ],
                  "properties": {
                    "rules": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "amounts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "evidence": {
                      "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"
              }
            }
          }
        }
      },
      "LifeAdminGeneratorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "event": {
            "title": "Life event",
            "type": "string",
            "enum": [
              "moving",
              "marriage",
              "new-job",
              "baby",
              "university",
              "retiring",
              "death",
              "car",
              "pet",
              "abroad"
            ]
          },
          "country": {
            "title": "Country",
            "type": "string",
            "enum": [
              "de",
              "at",
              "ch",
              "uk",
              "us",
              "other"
            ],
            "default": "de"
          },
          "eventDate": {
            "title": "Date of the event",
            "description": "Moving day, wedding, first working day, due date, date of death…",
            "type": "string",
            "format": "date"
          },
          "circumstances": {
            "title": "Your situation",
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "renting",
                "owning",
                "has-car",
                "has-children",
                "employed",
                "self-employed",
                "has-pets"
              ]
            }
          }
        },
        "required": [
          "event",
          "eventDate"
        ]
      },
      "LifeAdminGeneratorResult": {
        "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": [
              "event",
              "country",
              "eventDate",
              "items",
              "mandatoryCount"
            ],
            "properties": {
              "event": {
                "type": "string"
              },
              "country": {
                "type": "string"
              },
              "eventDate": {
                "type": "string",
                "format": "date"
              },
              "mandatoryCount": {
                "type": "integer",
                "minimum": 0
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "title",
                    "due",
                    "phase",
                    "daysFromEvent",
                    "category",
                    "mandatory"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "title": {
                      "type": "string"
                    },
                    "due": {
                      "type": "string",
                      "format": "date"
                    },
                    "phase": {
                      "type": "string",
                      "enum": [
                        "before",
                        "on-the-day",
                        "after"
                      ]
                    },
                    "daysFromEvent": {
                      "type": "integer"
                    },
                    "category": {
                      "type": "string"
                    },
                    "mandatory": {
                      "type": "boolean",
                      "description": "True only for legal duties verified against the cited official source."
                    },
                    "source": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "title",
                        "url"
                      ],
                      "properties": {
                        "title": {
                          "type": "string"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri"
                        }
                      }
                    },
                    "note": {
                      "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"
              }
            }
          }
        }
      },
      "DigitalEstateOrganizerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "ownerName": {
            "title": "Your name (optional, for the document)",
            "type": "string",
            "maxLength": 120
          },
          "trustedContact": {
            "title": "Trusted contact / executor",
            "type": "string",
            "maxLength": 120
          },
          "accounts": {
            "title": "Accounts",
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "service": {
                  "title": "Service",
                  "type": "string",
                  "maxLength": 80
                },
                "category": {
                  "title": "Category",
                  "type": "string",
                  "enum": [
                    "email",
                    "cloud-photos",
                    "social",
                    "password-manager",
                    "banking",
                    "crypto",
                    "subscription",
                    "shopping",
                    "work",
                    "domain-website",
                    "other"
                  ],
                  "default": "other"
                },
                "wish": {
                  "title": "What should happen?",
                  "type": "string",
                  "enum": [
                    "transfer",
                    "download-then-delete",
                    "memorialize",
                    "delete",
                    "keep-running"
                  ],
                  "default": "download-then-delete"
                },
                "monthlyCost": {
                  "title": "Monthly cost",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100000
                },
                "recoveryEmail": {
                  "title": "Recovery email (not password)",
                  "type": "string",
                  "maxLength": 120
                }
              },
              "required": [
                "service",
                "category",
                "wish"
              ]
            }
          },
          "hasPasswordManager": {
            "title": "I use a password manager",
            "type": "boolean",
            "default": false
          },
          "hasWrittenWill": {
            "title": "I have a written will",
            "type": "boolean",
            "default": false
          }
        },
        "required": [
          "trustedContact"
        ]
      },
      "DigitalEstateOrganizerResult": {
        "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": [
              "trustedContact",
              "accounts",
              "recurringMonthlyCost",
              "generalActions",
              "document"
            ],
            "properties": {
              "trustedContact": {
                "type": "string"
              },
              "recurringMonthlyCost": {
                "type": "number"
              },
              "generalActions": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "document": {
                "type": "string",
                "description": "Markdown handover document"
              },
              "accounts": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "priority",
                    "service",
                    "category",
                    "wish",
                    "legacyFeature",
                    "steps",
                    "monthlyCost"
                  ],
                  "properties": {
                    "priority": {
                      "type": "integer"
                    },
                    "service": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string"
                    },
                    "wish": {
                      "type": "string"
                    },
                    "monthlyCost": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "legacyFeature": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": false,
                      "required": [
                        "name",
                        "how",
                        "url"
                      ],
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "how": {
                          "type": "string"
                        },
                        "url": {
                          "type": "string"
                        }
                      }
                    },
                    "steps": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "HowLongWillItLastInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "item": {
            "title": "Item",
            "type": "string",
            "enum": [
              "air-conditioner-window",
              "trash-compactor",
              "dehumidifier",
              "dishwasher",
              "food-waste-disposal",
              "dryer-vent-plastic",
              "dryer-vent-steel",
              "clothes-dryer",
              "exhaust-fans",
              "freezer",
              "gas-oven",
              "hand-dryer",
              "humidifier-portable",
              "microwave-oven",
              "range-hood",
              "electric-range",
              "gas-range",
              "refrigerator",
              "swamp-cooler",
              "washing-machine",
              "whole-house-vacuum",
              "caulking",
              "paint-exterior",
              "paint-interior",
              "stains",
              "entertainment-center",
              "kitchen-cabinets",
              "countertop-laminate",
              "countertop-cultured-marble",
              "deck-planks",
              "deck-composite",
              "door-vinyl-exterior",
              "door-hollow-core",
              "afci",
              "light-fixtures",
              "propane-backup-generator",
              "service-panel",
              "solar-panels",
              "solar-batteries",
              "electrical-accessories",
              "carpet",
              "laminate-flooring",
              "vinyl-flooring",
              "linoleum",
              "garage-door",
              "garage-door-opener",
              "co-detector",
              "security-system",
              "central-ac",
              "boiler",
              "ceiling-fan",
              "condenser",
              "furnace",
              "gas-fireplace",
              "heat-pump",
              "whole-house-humidifier",
              "thermostat",
              "burner",
              "faucets",
              "fiberglass-tub-shower",
              "tankless-water-heater",
              "shower-doors",
              "sump-pump",
              "toilet-tank-components",
              "water-heater",
              "water-softener",
              "well-pump",
              "roof-asphalt-architectural",
              "roof-asphalt-3-tab",
              "roof-metal",
              "roof-epdm",
              "vinyl-siding",
              "asphalt-driveway",
              "wood-fencing",
              "pool-filter-pump",
              "pool-water-heater",
              "pool-vinyl-liner",
              "windows-double-pane",
              "windows-vinyl",
              "skylights",
              "window-glazing"
            ],
            "default": "refrigerator"
          },
          "ageYears": {
            "title": "Current age",
            "type": "number",
            "minimum": 0,
            "maximum": 200,
            "default": 5,
            "x-unit": "years"
          },
          "usage": {
            "title": "Usage intensity",
            "type": "string",
            "enum": [
              "light",
              "normal",
              "heavy"
            ],
            "default": "normal"
          },
          "maintenance": {
            "title": "Maintenance quality",
            "type": "string",
            "enum": [
              "good",
              "average",
              "poor"
            ],
            "default": "good"
          }
        },
        "required": [
          "ageYears"
        ]
      },
      "HowLongWillItLastResult": {
        "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": [
              "item",
              "category",
              "publishedYears",
              "publishedMin",
              "publishedMax",
              "openEnded",
              "factor",
              "adjustedMin",
              "adjustedMax",
              "ageYears",
              "remainingMin",
              "remainingMax",
              "lifeUsedPercent",
              "status",
              "replacementWindowStart",
              "replacementWindowEnd"
            ],
            "properties": {
              "item": {
                "type": "string"
              },
              "category": {
                "type": "string"
              },
              "publishedYears": {
                "type": "string",
                "description": "Verbatim value from the InterNACHI chart."
              },
              "publishedMin": {
                "type": "number"
              },
              "publishedMax": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "null for open-ended values like \"20+\"."
              },
              "openEnded": {
                "type": "boolean"
              },
              "factor": {
                "type": "number",
                "description": "Usage multiplier × maintenance multiplier (tool assumption)."
              },
              "adjustedMin": {
                "type": "number"
              },
              "adjustedMax": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "ageYears": {
                "type": "number"
              },
              "remainingMin": {
                "type": "number"
              },
              "remainingMax": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "lifeUsedPercent": {
                "type": "number",
                "description": "Age ÷ midpoint of the adjusted range (÷ adjusted minimum if open-ended) × 100."
              },
              "status": {
                "type": "string",
                "enum": [
                  "early-life",
                  "mid-life",
                  "approaching-end",
                  "within-end-of-life-range",
                  "beyond-expected-life"
                ]
              },
              "replacementWindowStart": {
                "type": "integer",
                "description": "Calendar year when the adjusted minimum life is reached (start budgeting)."
              },
              "replacementWindowEnd": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Calendar year when the adjusted maximum life is reached."
              }
            }
          },
          "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"
              }
            }
          }
        }
      },
      "RepairOrReplaceInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "itemName": {
            "title": "Item",
            "type": "string",
            "maxLength": 80,
            "default": "Appliance"
          },
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "itemAgeYears": {
            "title": "Age of the item",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 6,
            "x-unit": "years"
          },
          "expectedLifespanYears": {
            "title": "Typical lifespan",
            "description": "Typical total life of this kind of item; also used for the new one unless set below.",
            "type": "number",
            "minimum": 0.5,
            "maximum": 100,
            "default": 10,
            "x-unit": "years"
          },
          "replacementPrice": {
            "title": "Price of a new replacement",
            "type": "number",
            "minimum": 0.01,
            "default": 600
          },
          "repairQuote": {
            "title": "Repair quote",
            "type": "number",
            "minimum": 0,
            "default": 200
          },
          "lifeAfterRepairYears": {
            "title": "Expected extra life after repair",
            "type": "number",
            "minimum": 0.1,
            "maximum": 100,
            "default": 3,
            "x-unit": "years"
          },
          "newItemLifespanYears": {
            "title": "Lifespan of the new item (optional)",
            "type": "number",
            "minimum": 0.5,
            "maximum": 100,
            "x-unit": "years"
          },
          "runningCostOldPerYear": {
            "title": "Running cost of the old item",
            "description": "Energy, water, consumables.",
            "type": "number",
            "minimum": 0,
            "default": 0,
            "x-unit": "per year"
          },
          "runningCostNewPerYear": {
            "title": "Running cost of the new item",
            "type": "number",
            "minimum": 0,
            "default": 0,
            "x-unit": "per year"
          },
          "failureProbabilityPercent": {
            "title": "Chance of another failure after repair",
            "description": "Your estimate for the extra life period; an assumption, not a statistic.",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 30,
            "x-unit": "%"
          },
          "nextRepairCost": {
            "title": "Cost of that next repair (optional)",
            "description": "Defaults to the current repair quote.",
            "type": "number",
            "minimum": 0
          },
          "ruleThresholdPercent": {
            "title": "Rule-of-thumb threshold",
            "type": "number",
            "minimum": 1,
            "maximum": 100,
            "default": 50,
            "x-unit": "%"
          }
        },
        "required": [
          "replacementPrice",
          "repairQuote"
        ]
      },
      "RepairOrReplaceResult": {
        "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": [
              "itemName",
              "currency",
              "verdict",
              "rule50",
              "costPerYear",
              "breakEvenRepairQuote",
              "lifespanUsedPercent",
              "rulesAgree",
              "reasoning"
            ],
            "properties": {
              "itemName": {
                "type": "string"
              },
              "currency": {
                "type": "string"
              },
              "verdict": {
                "type": "string",
                "enum": [
                  "repair",
                  "replace"
                ]
              },
              "rule50": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "thresholdPercent",
                  "repairShareOfReplacementPercent",
                  "verdict"
                ],
                "properties": {
                  "thresholdPercent": {
                    "type": "number"
                  },
                  "repairShareOfReplacementPercent": {
                    "type": "number"
                  },
                  "verdict": {
                    "type": "string",
                    "enum": [
                      "repair",
                      "replace"
                    ]
                  }
                }
              },
              "costPerYear": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "repair",
                  "replace",
                  "difference",
                  "verdict",
                  "expectedExtraRepairCost",
                  "newItemLifespanYears"
                ],
                "properties": {
                  "repair": {
                    "type": "number",
                    "description": "(repair quote + P(failure) × next repair cost) ÷ extra life + old running cost per year."
                  },
                  "replace": {
                    "type": "number",
                    "description": "replacement price ÷ new lifespan + new running cost per year."
                  },
                  "difference": {
                    "type": "number",
                    "description": "replace − repair (positive = repairing is cheaper per year)."
                  },
                  "verdict": {
                    "type": "string",
                    "enum": [
                      "repair",
                      "replace"
                    ]
                  },
                  "expectedExtraRepairCost": {
                    "type": "number"
                  },
                  "newItemLifespanYears": {
                    "type": "number"
                  }
                }
              },
              "breakEvenRepairQuote": {
                "type": "number",
                "description": "Highest repair quote at which repairing still costs no more per year than replacing (0 if repairing never pays)."
              },
              "lifespanUsedPercent": {
                "type": "number"
              },
              "rulesAgree": {
                "type": "boolean"
              },
              "reasoning": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "WarrantyValueCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "itemPrice": {
            "title": "Item price",
            "type": "number",
            "minimum": 0.01,
            "default": 800
          },
          "warrantyPrice": {
            "title": "Warranty price",
            "type": "number",
            "minimum": 0,
            "default": 120
          },
          "coverageYears": {
            "title": "Warranty covers (from purchase)",
            "type": "number",
            "minimum": 0.1,
            "maximum": 30,
            "default": 3,
            "x-unit": "years"
          },
          "overlapYears": {
            "title": "Of which already covered by legal / manufacturer warranty",
            "description": "E.g. 2 years of statutory warranty in the EU; set 0 if the warranty starts after it.",
            "type": "number",
            "minimum": 0,
            "maximum": 30,
            "default": 2,
            "x-unit": "years"
          },
          "annualFailureProbabilityPercent": {
            "title": "Chance of a failure per year",
            "description": "Your estimate; reliability differs a lot by product and brand.",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 5,
            "x-unit": "%/year"
          },
          "averageRepairCost": {
            "title": "Typical repair cost",
            "type": "number",
            "minimum": 0,
            "default": 250
          },
          "deductible": {
            "title": "Deductible per claim",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "claimApprovalPercent": {
            "title": "Chance a claim is approved",
            "description": "Exclusions (accidental damage, wear) often reduce real coverage.",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 80,
            "x-unit": "%"
          }
        },
        "required": [
          "itemPrice",
          "warrantyPrice"
        ]
      },
      "WarrantyValueCalculatorResult": {
        "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",
              "verdict",
              "effectiveYears",
              "payoutPerClaim",
              "expectedClaims",
              "probabilityOfAnyClaim",
              "expectedPayout",
              "expectedNetValue",
              "returnPerCurrencyUnit",
              "breakEvenAnnualFailurePercent",
              "warrantyShareOfItemPricePercent"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "verdict": {
                "type": "string",
                "enum": [
                  "worth-it",
                  "marginal",
                  "not-worth-it"
                ]
              },
              "effectiveYears": {
                "type": "number",
                "description": "Coverage years not already covered by the legal/manufacturer warranty."
              },
              "payoutPerClaim": {
                "type": "number",
                "description": "min(repair cost, item price) − deductible, floored at 0."
              },
              "expectedClaims": {
                "type": "number",
                "description": "Expected number of approved claims."
              },
              "probabilityOfAnyClaim": {
                "type": "number",
                "minimum": 0,
                "maximum": 100,
                "description": "Percent chance of at least one approved claim."
              },
              "expectedPayout": {
                "type": "number"
              },
              "expectedNetValue": {
                "type": "number",
                "description": "expectedPayout − warranty price."
              },
              "returnPerCurrencyUnit": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Expected payout per unit of warranty price; null if the warranty is free."
              },
              "breakEvenAnnualFailurePercent": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Annual failure probability at which expected payout equals price; null if unreachable (above 100% or no payout possible)."
              },
              "warrantyShareOfItemPricePercent": {
                "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"
              }
            }
          }
        }
      },
      "TrueCostCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "itemType": {
            "title": "What is it?",
            "type": "string",
            "enum": [
              "car",
              "pet",
              "appliance",
              "home",
              "gadget",
              "other"
            ],
            "default": "car"
          },
          "itemName": {
            "title": "Name",
            "type": "string",
            "maxLength": 80,
            "default": "My purchase"
          },
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "purchasePrice": {
            "title": "Purchase (sticker) price",
            "description": "Pet: purchase or adoption fee.",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "yearsOwned": {
            "title": "Years you will own it",
            "type": "number",
            "minimum": 0.1,
            "maximum": 60,
            "default": 5,
            "x-unit": "years"
          },
          "resaleValue": {
            "title": "Expected resale value at the end",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "loanAmount": {
            "title": "Loan amount",
            "description": "Leave 0 if you pay cash.",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "aprPercent": {
            "title": "Loan APR",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 0,
            "x-unit": "%/year"
          },
          "loanTermMonths": {
            "title": "Loan term",
            "type": "integer",
            "minimum": 0,
            "maximum": 600,
            "default": 0,
            "x-unit": "months"
          },
          "maintenancePerYear": {
            "title": "Maintenance, repairs or vet care per year",
            "description": "Car or appliance: servicing and repairs. Pet: vet visits, vaccinations, medication and other health care.",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "insurancePerYear": {
            "title": "Insurance per year",
            "description": "Pet: health or liability insurance.",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "energyConsumption": {
            "title": "Energy, fuel or food consumption",
            "description": "Car: e.g. 6 (litres per 100 km). Appliance: e.g. 1.2 (kWh per wash cycle). Pet: e.g. 0.35 (kg of food per day).",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "consumptionPer": {
            "title": "Consumption is per",
            "type": "string",
            "enum": [
              "per-100",
              "per-1"
            ],
            "default": "per-100"
          },
          "energyUnitPrice": {
            "title": "Price per unit (litre, kWh or kg of food)",
            "description": "E.g. price per litre of fuel, per kWh or per kg of pet food.",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "usagePerYear": {
            "title": "Usage per year",
            "description": "E.g. km per year, wash cycles per year, or 365 feeding days for a pet.",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "recurring": {
            "title": "Other recurring costs",
            "description": "Taxes, parking, subscriptions, pet supplies, grooming, fees…",
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Cost",
                  "type": "string"
                },
                "amount": {
                  "title": "Amount",
                  "type": "number",
                  "minimum": 0
                },
                "frequency": {
                  "title": "Every",
                  "type": "string",
                  "enum": [
                    "weekly",
                    "monthly",
                    "quarterly",
                    "yearly"
                  ],
                  "default": "monthly"
                }
              },
              "required": [
                "name",
                "amount"
              ]
            }
          }
        },
        "required": [
          "purchasePrice"
        ]
      },
      "TrueCostCalculatorResult": {
        "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": [
              "itemName",
              "itemType",
              "currency",
              "yearsOwned",
              "trueTotal",
              "perYear",
              "perMonth",
              "perDay",
              "stickerMultiplier",
              "loan",
              "buckets",
              "recurringItems"
            ],
            "properties": {
              "itemName": {
                "type": "string"
              },
              "itemType": {
                "type": "string",
                "enum": [
                  "car",
                  "pet",
                  "appliance",
                  "home",
                  "gadget",
                  "other"
                ]
              },
              "currency": {
                "type": "string"
              },
              "yearsOwned": {
                "type": "number"
              },
              "trueTotal": {
                "type": "number",
                "description": "Sum of all cost buckets over the ownership period."
              },
              "perYear": {
                "type": "number"
              },
              "perMonth": {
                "type": "number"
              },
              "perDay": {
                "type": "number"
              },
              "stickerMultiplier": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "trueTotal ÷ purchase price; null when the purchase price is 0."
              },
              "loan": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "monthlyPayment",
                  "totalInterestFullTerm",
                  "interestDuringOwnership",
                  "balanceAtEnd"
                ],
                "properties": {
                  "monthlyPayment": {
                    "type": "number"
                  },
                  "totalInterestFullTerm": {
                    "type": "number"
                  },
                  "interestDuringOwnership": {
                    "type": "number"
                  },
                  "balanceAtEnd": {
                    "type": "number",
                    "description": "Outstanding principal when you stop owning the item (to be repaid from the sale)."
                  }
                }
              },
              "buckets": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "bucket",
                    "label",
                    "total",
                    "perYear",
                    "sharePercent"
                  ],
                  "properties": {
                    "bucket": {
                      "type": "string",
                      "enum": [
                        "depreciation",
                        "financing",
                        "recurring",
                        "maintenance",
                        "insurance",
                        "energy"
                      ]
                    },
                    "label": {
                      "type": "string"
                    },
                    "total": {
                      "type": "number"
                    },
                    "perYear": {
                      "type": "number"
                    },
                    "sharePercent": {
                      "type": "number"
                    }
                  }
                }
              },
              "recurringItems": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "name",
                    "perYear",
                    "total"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "perYear": {
                      "type": "number"
                    },
                    "total": {
                      "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"
              }
            }
          }
        }
      },
      "IsItWorthItInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "years": {
            "title": "How long you need it",
            "type": "integer",
            "minimum": 1,
            "maximum": 50,
            "default": 5,
            "x-unit": "years"
          },
          "discountRatePercent": {
            "title": "Discount rate",
            "description": "What money is worth to you per year (e.g. savings interest). Used for NPV only.",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "default": 3,
            "x-unit": "%/year"
          },
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "options": {
            "title": "Options",
            "description": "Costs are positive numbers. Resale is what you expect to get back at the end. One-off year 0 = at the start.",
            "type": "array",
            "minItems": 2,
            "maxItems": 10,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Option",
                  "type": "string"
                },
                "kind": {
                  "title": "Type",
                  "type": "string",
                  "enum": [
                    "buy",
                    "rent",
                    "repair",
                    "subscribe",
                    "other"
                  ],
                  "default": "other"
                },
                "upfront": {
                  "title": "Upfront cost",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "monthly": {
                  "title": "Recurring / month",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "oneOff": {
                  "title": "One-off costs",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "oneOffYear": {
                  "title": "One-off in year",
                  "type": "integer",
                  "minimum": 0,
                  "maximum": 50,
                  "default": 0
                },
                "resale": {
                  "title": "Resale at end",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "repairPerYear": {
                  "title": "Repair allowance / year",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                }
              },
              "required": [
                "name"
              ]
            }
          }
        },
        "required": [
          "options"
        ]
      },
      "IsItWorthItResult": {
        "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",
              "years",
              "discountRatePercent",
              "best",
              "runnerUp",
              "breakEvenYear",
              "savingVsRunnerUpNpv",
              "ranking"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "years": {
                "type": "integer"
              },
              "discountRatePercent": {
                "type": "number"
              },
              "best": {
                "type": "string"
              },
              "runnerUp": {
                "type": "string"
              },
              "breakEvenYear": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Years from start after which the best option (by NPV) is cumulatively cheaper than the runner-up in nominal terms; 0 = cheaper from day one; null = never within the period."
              },
              "savingVsRunnerUpNpv": {
                "type": "number",
                "description": "NPV cost of the runner-up minus NPV cost of the best option."
              },
              "ranking": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "rank",
                    "name",
                    "kind",
                    "totalCost",
                    "npvCost",
                    "costPerMonth",
                    "npvCostPerMonth",
                    "annualRunningCost",
                    "differenceToBest"
                  ],
                  "properties": {
                    "rank": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "name": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "buy",
                        "rent",
                        "repair",
                        "subscribe",
                        "other"
                      ]
                    },
                    "totalCost": {
                      "type": "number",
                      "description": "Nominal total cost of ownership over the period, net of resale."
                    },
                    "npvCost": {
                      "type": "number",
                      "description": "Present value of all costs minus present value of resale."
                    },
                    "costPerMonth": {
                      "type": "number"
                    },
                    "npvCostPerMonth": {
                      "type": "number"
                    },
                    "annualRunningCost": {
                      "type": "number"
                    },
                    "differenceToBest": {
                      "type": "number",
                      "description": "npvCost minus the best option’s npvCost."
                    }
                  }
                }
              }
            }
          },
          "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"
              }
            }
          }
        }
      },
      "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"
              }
            }
          }
        }
      },
      "PricePerEverythingInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "basis": {
            "title": "Compare by",
            "type": "string",
            "enum": [
              "unit",
              "use",
              "serving",
              "hour",
              "year"
            ],
            "default": "unit"
          },
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "products": {
            "title": "Products",
            "description": "Quantity is in the chosen basis (e.g. 500 for 500 g, 30 for 30 uses). Waste % reduces the usable quantity.",
            "type": "array",
            "minItems": 2,
            "maxItems": 20,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Product",
                  "type": "string"
                },
                "price": {
                  "title": "Price",
                  "type": "number",
                  "minimum": 0
                },
                "quantity": {
                  "title": "Quantity",
                  "type": "number",
                  "minimum": 0.000001
                },
                "unit": {
                  "title": "Unit label",
                  "type": "string"
                },
                "wastePercent": {
                  "title": "Waste %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 99,
                  "default": 0
                }
              },
              "required": [
                "name",
                "price",
                "quantity"
              ]
            }
          }
        },
        "required": [
          "products"
        ]
      },
      "PricePerEverythingResult": {
        "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": [
              "basis",
              "currency",
              "ranking",
              "cheapest",
              "savingsVsMostExpensivePercent"
            ],
            "properties": {
              "basis": {
                "type": "string"
              },
              "currency": {
                "type": "string"
              },
              "cheapest": {
                "type": "string"
              },
              "savingsVsMostExpensivePercent": {
                "type": "number"
              },
              "ranking": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "rank",
                    "name",
                    "price",
                    "usableQuantity",
                    "pricePerUnit",
                    "unit",
                    "relativeToCheapest"
                  ],
                  "properties": {
                    "rank": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "price": {
                      "type": "number"
                    },
                    "usableQuantity": {
                      "type": "number"
                    },
                    "pricePerUnit": {
                      "type": "number"
                    },
                    "unit": {
                      "type": "string"
                    },
                    "relativeToCheapest": {
                      "type": "number",
                      "description": "Price per unit divided by the cheapest price per unit."
                    }
                  }
                }
              }
            }
          },
          "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"
              }
            }
          }
        }
      },
      "BreakEvenExplorerInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "preset": {
            "title": "Preset",
            "description": "Presets only fill in escalation, degradation and horizon when you leave those empty; the values are listed under assumptions.",
            "type": "string",
            "enum": [
              "custom",
              "solar",
              "ev",
              "heat-pump"
            ],
            "default": "custom"
          },
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "upfrontInvestment": {
            "title": "Upfront investment",
            "type": "number",
            "minimum": 0,
            "default": 10000
          },
          "subsidies": {
            "title": "Subsidies / grants / tax credits",
            "description": "Received at the start; reduces the net investment.",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "annualSavings": {
            "title": "Savings in year 1",
            "type": "number",
            "default": 1200,
            "x-unit": "per year"
          },
          "savingsEscalationPercent": {
            "title": "Savings escalation (e.g. energy price inflation)",
            "description": "Empty = preset default.",
            "type": "number",
            "minimum": -50,
            "maximum": 50,
            "x-unit": "%/year"
          },
          "degradationPercent": {
            "title": "Performance degradation",
            "description": "Empty = preset default.",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "x-unit": "%/year"
          },
          "maintenancePerYear": {
            "title": "Maintenance per year",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "discountRatePercent": {
            "title": "Discount rate",
            "type": "number",
            "minimum": 0,
            "maximum": 50,
            "default": 3,
            "x-unit": "%/year"
          },
          "horizonYears": {
            "title": "Horizon",
            "description": "Empty = preset default.",
            "type": "integer",
            "minimum": 1,
            "maximum": 60,
            "x-unit": "years"
          },
          "savingsByYear": {
            "title": "Override savings for specific years (optional)",
            "description": "Savings entered here replace the escalated/degraded value for that year (before maintenance).",
            "type": "array",
            "maxItems": 60,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "year": {
                  "title": "Year",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 60
                },
                "savings": {
                  "title": "Savings",
                  "type": "number"
                }
              },
              "required": [
                "year",
                "savings"
              ]
            }
          }
        },
        "required": [
          "upfrontInvestment",
          "annualSavings"
        ]
      },
      "BreakEvenExplorerResult": {
        "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": [
              "preset",
              "currency",
              "netInvestment",
              "horizonYears",
              "escalationPercent",
              "degradationPercent",
              "discountRatePercent",
              "simplePaybackYears",
              "discountedPaybackYears",
              "npv",
              "irrPercent",
              "totalNetSavings",
              "roiPercent",
              "years"
            ],
            "properties": {
              "preset": {
                "type": "string",
                "enum": [
                  "custom",
                  "solar",
                  "ev",
                  "heat-pump"
                ]
              },
              "currency": {
                "type": "string"
              },
              "netInvestment": {
                "type": "number",
                "description": "Upfront investment − subsidies."
              },
              "horizonYears": {
                "type": "integer"
              },
              "escalationPercent": {
                "type": "number"
              },
              "degradationPercent": {
                "type": "number"
              },
              "discountRatePercent": {
                "type": "number"
              },
              "simplePaybackYears": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Fractional years until cumulative nominal cash flow reaches 0; null if not within the horizon."
              },
              "discountedPaybackYears": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "npv": {
                "type": "number"
              },
              "irrPercent": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Internal rate of return in %; null if none exists in [−99%, 1000%]."
              },
              "totalNetSavings": {
                "type": "number",
                "description": "Sum of yearly net cash flows (savings − maintenance) over the horizon."
              },
              "roiPercent": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "(total net savings − net investment) ÷ net investment; null when net investment is 0."
              },
              "years": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "year",
                    "savings",
                    "maintenance",
                    "netCashFlow",
                    "cumulative",
                    "discountedCashFlow",
                    "discountedCumulative"
                  ],
                  "properties": {
                    "year": {
                      "type": "integer"
                    },
                    "savings": {
                      "type": "number"
                    },
                    "maintenance": {
                      "type": "number"
                    },
                    "netCashFlow": {
                      "type": "number"
                    },
                    "cumulative": {
                      "type": "number"
                    },
                    "discountedCashFlow": {
                      "type": "number"
                    },
                    "discountedCumulative": {
                      "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"
              }
            }
          }
        }
      },
      "DecisionSimulatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "goal": {
            "title": "Higher values are",
            "type": "string",
            "enum": [
              "maximize",
              "minimize"
            ],
            "default": "maximize"
          },
          "method": {
            "title": "Expected value weights",
            "type": "string",
            "enum": [
              "pert",
              "triangular",
              "custom"
            ],
            "default": "pert"
          },
          "unit": {
            "title": "Unit of the values",
            "type": "string",
            "maxLength": 20,
            "default": "EUR"
          },
          "choices": {
            "title": "Choices",
            "description": "Worst/likely/best outcome per choice. Probabilities (%) are only used with “My own probabilities” and are normalised to sum to 100.",
            "type": "array",
            "minItems": 2,
            "maxItems": 6,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Choice",
                  "type": "string"
                },
                "worst": {
                  "title": "Worst",
                  "type": "number"
                },
                "likely": {
                  "title": "Most likely",
                  "type": "number"
                },
                "best": {
                  "title": "Best",
                  "type": "number"
                },
                "pWorst": {
                  "title": "P(worst) %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                "pLikely": {
                  "title": "P(likely) %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                "pBest": {
                  "title": "P(best) %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                }
              },
              "required": [
                "name",
                "worst",
                "likely",
                "best"
              ]
            }
          }
        },
        "required": [
          "choices"
        ]
      },
      "DecisionSimulatorResult": {
        "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": [
              "goal",
              "method",
              "unit",
              "choices",
              "picks",
              "recommendation",
              "rulesInAgreement",
              "explanation"
            ],
            "properties": {
              "goal": {
                "type": "string",
                "enum": [
                  "maximize",
                  "minimize"
                ]
              },
              "method": {
                "type": "string",
                "enum": [
                  "pert",
                  "triangular",
                  "custom"
                ]
              },
              "unit": {
                "type": "string"
              },
              "choices": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "name",
                    "worst",
                    "likely",
                    "best",
                    "expectedValue",
                    "standardDeviation",
                    "average",
                    "maxRegret",
                    "weights",
                    "rankByExpectedValue"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "worst": {
                      "type": "number"
                    },
                    "likely": {
                      "type": "number"
                    },
                    "best": {
                      "type": "number"
                    },
                    "expectedValue": {
                      "type": "number"
                    },
                    "standardDeviation": {
                      "type": "number",
                      "description": "Analytical spread of the chosen distribution (PERT: (best − worst)/6; triangular: √((a²+b²+c²−ab−ac−bc)/18); custom: discrete)."
                    },
                    "average": {
                      "type": "number",
                      "description": "Unweighted mean of worst, likely and best (Laplace rule)."
                    },
                    "maxRegret": {
                      "type": "number",
                      "description": "Largest shortfall versus the best choice in any of the three scenarios."
                    },
                    "weights": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "additionalProperties": false,
                      "required": [
                        "worst",
                        "likely",
                        "best"
                      ],
                      "properties": {
                        "worst": {
                          "type": "number"
                        },
                        "likely": {
                          "type": "number"
                        },
                        "best": {
                          "type": "number"
                        }
                      },
                      "description": "Normalised probabilities used (null when not applicable)."
                    },
                    "rankByExpectedValue": {
                      "type": "integer",
                      "minimum": 1
                    }
                  }
                }
              },
              "picks": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "expectedValue",
                  "maximin",
                  "maximax",
                  "minimaxRegret",
                  "laplace"
                ],
                "properties": {
                  "expectedValue": {
                    "type": "string"
                  },
                  "maximin": {
                    "type": "string",
                    "description": "Best worst case (pessimist)."
                  },
                  "maximax": {
                    "type": "string",
                    "description": "Best best case (optimist)."
                  },
                  "minimaxRegret": {
                    "type": "string"
                  },
                  "laplace": {
                    "type": "string",
                    "description": "Best unweighted average."
                  }
                }
              },
              "recommendation": {
                "type": "string"
              },
              "rulesInAgreement": {
                "type": "integer",
                "minimum": 1,
                "maximum": 5,
                "description": "How many of the 5 rules pick the recommended choice."
              },
              "explanation": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "units": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "assumptions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "title"
              ],
              "properties": {
                "title": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "publisher": {
                  "type": "string"
                },
                "retrievedAt": {
                  "type": "string"
                },
                "note": {
                  "type": "string"
                }
              }
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "presentation": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Display blocks used by the human page (same data)."
          },
          "links": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "human",
              "api",
              "openapi",
              "methodology"
            ],
            "properties": {
              "human": {
                "type": "string",
                "format": "uri"
              },
              "api": {
                "type": "string",
                "format": "uri"
              },
              "openapi": {
                "type": "string",
                "format": "uri"
              },
              "methodology": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      },
      "SalaryRealityCheckInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "currency": {
            "title": "Currency",
            "description": "Shown in results only; no conversion is performed.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "offers": {
            "title": "Job offers",
            "description": "Tax rate is YOUR effective tax + social contribution rate in % of gross (look it up in a local net-pay calculator); this tool does not implement any national tax law. Living cost difference is extra rent/living cost per month for this job compared to your baseline (negative if cheaper).",
            "type": "array",
            "minItems": 2,
            "maxItems": 4,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Offer",
                  "type": "string"
                },
                "grossAnnual": {
                  "title": "Gross salary / year",
                  "type": "number",
                  "minimum": 0
                },
                "taxRatePercent": {
                  "title": "Effective tax + social %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 95,
                  "x-unit": "%"
                },
                "bonus": {
                  "title": "Bonus / year",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "benefitsValue": {
                  "title": "Benefits value / year",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "commuteMinutesOneWay": {
                  "title": "Commute one way",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 600,
                  "default": 0,
                  "x-unit": "min"
                },
                "commuteCostMonthly": {
                  "title": "Commute cost / month",
                  "type": "number",
                  "minimum": 0,
                  "default": 0
                },
                "workingDays": {
                  "title": "Working days / year",
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 366,
                  "default": 220,
                  "x-unit": "days"
                },
                "weeklyHours": {
                  "title": "Hours / week",
                  "type": "number",
                  "minimum": 1,
                  "maximum": 100,
                  "default": 40,
                  "x-unit": "h"
                },
                "livingCostDiffMonthly": {
                  "title": "Living cost difference / month",
                  "type": "number",
                  "default": 0
                },
                "remoteDaysPerWeek": {
                  "title": "Remote days / week",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 5,
                  "default": 0,
                  "x-unit": "days"
                }
              },
              "required": [
                "name",
                "grossAnnual",
                "taxRatePercent"
              ]
            }
          }
        },
        "required": [
          "offers"
        ]
      },
      "SalaryRealityCheckResult": {
        "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",
              "best",
              "ranking"
            ],
            "properties": {
              "currency": {
                "type": "string"
              },
              "best": {
                "type": "string",
                "description": "Offer with the highest net income per hour of working plus commuting, after commute and living costs."
              },
              "ranking": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "rank",
                    "name",
                    "grossTotal",
                    "netAnnual",
                    "commuteDaysPerYear",
                    "commuteHoursPerYear",
                    "commuteCostPerYear",
                    "livingCostDiffPerYear",
                    "netAfterCosts",
                    "workHoursPerYear",
                    "netPerWorkHour",
                    "netPerHourInclCommute",
                    "commuteTimeValue",
                    "differenceToBestPerYear"
                  ],
                  "properties": {
                    "rank": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "grossTotal": {
                      "type": "number",
                      "description": "Gross salary + bonus."
                    },
                    "netAnnual": {
                      "type": "number",
                      "description": "(Gross + bonus) × (1 − tax rate) + benefits."
                    },
                    "commuteDaysPerYear": {
                      "type": "number"
                    },
                    "commuteHoursPerYear": {
                      "type": "number"
                    },
                    "commuteCostPerYear": {
                      "type": "number"
                    },
                    "livingCostDiffPerYear": {
                      "type": "number"
                    },
                    "netAfterCosts": {
                      "type": "number",
                      "description": "Net annual − commute cost − living cost difference."
                    },
                    "workHoursPerYear": {
                      "type": "number"
                    },
                    "netPerWorkHour": {
                      "type": "number",
                      "description": "Net annual ÷ work hours (ignores commute)."
                    },
                    "netPerHourInclCommute": {
                      "type": "number",
                      "description": "Net after costs ÷ (work hours + commute hours)."
                    },
                    "commuteTimeValue": {
                      "type": "number",
                      "description": "Commute hours valued at the net hourly wage of this offer."
                    },
                    "differenceToBestPerYear": {
                      "type": "number",
                      "description": "Net after costs of the top-ranked offer (best per hour) minus net after costs of this offer, per year. Positive = this offer keeps less per year than the top-ranked offer; negative = this offer keeps more per year than the top-ranked offer (it ranks lower only because of the hours). 0 for the top-ranked offer."
                    },
                    "comparedToBest": {
                      "type": "string",
                      "description": "Plain-language reading of differenceToBestPerYear, e.g. \"Behind the top-ranked offer by 1,000 EUR per year\" or \"Keeps 1,820 EUR more per year than the top-ranked offer\"."
                    }
                  }
                }
              }
            }
          },
          "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"
              }
            }
          }
        }
      },
      "TipBillSplitCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "symbol": {
            "title": "Currency symbol",
            "description": "Display only, e.g. €, $, £, CHF.",
            "type": "string",
            "maxLength": 4,
            "default": "€"
          },
          "billAmount": {
            "title": "Bill amount",
            "description": "The amount printed on the bill (before tip).",
            "type": "number",
            "minimum": 0.01,
            "maximum": 10000000,
            "default": 84.5
          },
          "taxMode": {
            "title": "Tax",
            "type": "string",
            "enum": [
              "included",
              "added",
              "none"
            ],
            "default": "included"
          },
          "taxPercent": {
            "title": "Tax rate",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 19,
            "x-unit": "%"
          },
          "tipMode": {
            "title": "Tip given as",
            "type": "string",
            "enum": [
              "percent",
              "amount"
            ],
            "default": "percent"
          },
          "tipPercent": {
            "title": "Tip percentage",
            "type": "number",
            "minimum": 0,
            "maximum": 100,
            "default": 10,
            "x-unit": "%"
          },
          "tipAmount": {
            "title": "Tip amount",
            "type": "number",
            "minimum": 0,
            "default": 0
          },
          "tipBase": {
            "title": "Tip calculated on",
            "type": "string",
            "enum": [
              "pre-tax",
              "post-tax"
            ],
            "default": "post-tax"
          },
          "splitMode": {
            "title": "Split",
            "type": "string",
            "enum": [
              "equal",
              "shares",
              "items"
            ],
            "default": "equal"
          },
          "people": {
            "title": "Number of people (equal split)",
            "description": "Used when the list of people below is empty.",
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 4
          },
          "persons": {
            "title": "People",
            "description": "Optional for an equal split. Shares: weight per person (default 1). Items: the bill amount of what each person ordered, in the same terms as the bill (with tax if tax is included); anything not assigned is shared equally.",
            "type": "array",
            "maxItems": 40,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Name",
                  "type": "string",
                  "maxLength": 40
                },
                "shares": {
                  "title": "Shares",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                },
                "items": {
                  "title": "Ordered items",
                  "type": "number",
                  "minimum": 0
                }
              },
              "required": [
                "name"
              ]
            }
          },
          "rounding": {
            "title": "Rounding per person",
            "description": "Any rounding difference is added to (or taken from) the tip.",
            "type": "string",
            "enum": [
              "none",
              "up-0.1",
              "up-0.5",
              "up-1",
              "up-5",
              "nearest-0.05"
            ],
            "default": "none"
          }
        },
        "required": [
          "billAmount"
        ]
      },
      "TipBillSplitCalculatorResult": {
        "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": [
              "symbol",
              "subtotalBeforeTax",
              "tax",
              "billTotal",
              "tipBase",
              "tip",
              "tipPercentOfBill",
              "grandTotal",
              "collected",
              "roundingAdded",
              "effectiveTip",
              "effectiveTipPercent",
              "people"
            ],
            "properties": {
              "symbol": {
                "type": "string"
              },
              "subtotalBeforeTax": {
                "type": "number"
              },
              "tax": {
                "type": "number"
              },
              "billTotal": {
                "type": "number",
                "description": "Bill including tax, before tip."
              },
              "tipBase": {
                "type": "number",
                "description": "Amount the tip percentage was applied to."
              },
              "tip": {
                "type": "number",
                "description": "Tip before per-person rounding."
              },
              "tipPercentOfBill": {
                "type": "number",
                "description": "Tip as % of the bill including tax."
              },
              "grandTotal": {
                "type": "number",
                "description": "Bill including tax + tip, before rounding."
              },
              "collected": {
                "type": "number",
                "description": "Sum of what everyone pays after rounding."
              },
              "roundingAdded": {
                "type": "number",
                "description": "collected − grandTotal (goes to the tip)."
              },
              "effectiveTip": {
                "type": "number"
              },
              "effectiveTipPercent": {
                "type": "number",
                "description": "Effective tip as % of the tip base."
              },
              "people": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "name",
                    "weight",
                    "billShare",
                    "taxShare",
                    "tipShare",
                    "exact",
                    "pays"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "weight": {
                      "type": "number",
                      "description": "Relative split weight: 1 per person (equal), the entered share (shares), or the ordered amount plus an equal part of anything unassigned, in cents (items)."
                    },
                    "sharePercent": {
                      "type": "number",
                      "description": "This person's share of the bill, tax and tip in percent (weight ÷ sum of weights × 100)."
                    },
                    "billShare": {
                      "type": "number"
                    },
                    "taxShare": {
                      "type": "number"
                    },
                    "tipShare": {
                      "type": "number"
                    },
                    "exact": {
                      "type": "number"
                    },
                    "pays": {
                      "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"
              }
            }
          }
        }
      },
      "PercentageRatioCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "mode": {
            "title": "Question",
            "type": "string",
            "enum": [
              "percent-of",
              "what-percent",
              "change",
              "increase",
              "decrease",
              "reverse",
              "points",
              "ratio-simplify",
              "ratio-scale",
              "ratio-split",
              "aspect"
            ],
            "default": "percent-of"
          },
          "x": {
            "title": "Value X",
            "description": "Percentage modes: the percentage or the start value (see the question). Aspect ratio: width.",
            "type": "number"
          },
          "y": {
            "title": "Value Y",
            "description": "Percentage modes: the base, end or final value. Aspect ratio: height.",
            "type": "number"
          },
          "ratio": {
            "title": "Ratio",
            "description": "For the ratio modes: 2 to 10 positive terms separated by \":\" (e.g. 16:9, 2:3:5, 1.5:2).",
            "type": "string",
            "maxLength": 120
          },
          "amount": {
            "title": "Amount",
            "description": "Split: the total to share out. Scale: the value the chosen term should become.",
            "type": "number"
          },
          "scaleTerm": {
            "title": "Term to scale",
            "description": "Scale mode: which ratio term (1 = first) should equal the amount.",
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "default": 1
          },
          "newWidth": {
            "title": "New width",
            "description": "Aspect ratio: optional; the matching height is calculated.",
            "type": "number",
            "minimum": 0
          },
          "newHeight": {
            "title": "New height",
            "description": "Aspect ratio: optional; the matching width is calculated.",
            "type": "number",
            "minimum": 0
          },
          "decimals": {
            "title": "Decimal places",
            "description": "Rounding of the displayed answer and of split amounts (2 = cents).",
            "type": "integer",
            "minimum": 0,
            "maximum": 10,
            "default": 2
          }
        },
        "required": []
      },
      "PercentageRatioCalculatorResult": {
        "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",
              "question",
              "answer",
              "answerText",
              "formula",
              "steps",
              "ratio"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "percent-of",
                  "what-percent",
                  "change",
                  "increase",
                  "decrease",
                  "reverse",
                  "points",
                  "ratio-simplify",
                  "ratio-scale",
                  "ratio-split",
                  "aspect"
                ]
              },
              "question": {
                "type": "string"
              },
              "answer": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Main numeric answer, rounded to the chosen decimals (null for ratio-only answers)."
              },
              "answerText": {
                "type": "string"
              },
              "formula": {
                "type": "string"
              },
              "steps": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "ratio": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": false,
                "properties": {
                  "terms": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "simplified": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    }
                  },
                  "unitForm": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    },
                    "description": "Ratio divided by its first term (1 : n)."
                  },
                  "parts": {
                    "type": "array",
                    "items": {
                      "type": "number"
                    },
                    "description": "Split amounts (sum exactly to the amount) or scaled terms."
                  },
                  "decimal": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Aspect ratio as a decimal (width ÷ height)."
                  },
                  "nearestNamed": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "newWidth": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "newHeight": {
                    "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"
              }
            }
          }
        }
      },
      "InflationTimeMachineInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "amount": {
            "title": "Amount",
            "type": "number",
            "minimum": 0,
            "default": 100
          },
          "currency": {
            "title": "Currency label",
            "description": "Label only. Use the currency of the chosen country; no conversion is done.",
            "type": "string",
            "maxLength": 8,
            "default": "EUR"
          },
          "fromYear": {
            "title": "From year",
            "type": "integer",
            "minimum": 1960,
            "maximum": 2100,
            "default": 2000
          },
          "toYear": {
            "title": "To year",
            "type": "integer",
            "minimum": 1960,
            "maximum": 2100,
            "default": 2024
          },
          "country": {
            "title": "Country",
            "type": "string",
            "enum": [
              "US",
              "GB",
              "DE",
              "FR",
              "IT",
              "ES",
              "NL",
              "AT",
              "CH",
              "SE",
              "PL",
              "CA",
              "AU",
              "JP",
              "IN",
              "BR",
              "MX",
              "ZA",
              "TR",
              "CN"
            ],
            "default": "DE"
          }
        },
        "required": [
          "amount",
          "fromYear",
          "toYear"
        ]
      },
      "InflationTimeMachineResult": {
        "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": [
              "country",
              "countryName",
              "amount",
              "fromYear",
              "toYear",
              "cpiFrom",
              "cpiTo",
              "equivalentValue",
              "cumulativeInflationPercent",
              "averageAnnualInflationPercent",
              "purchasingPowerLossPercent",
              "years",
              "dataLastUpdated"
            ],
            "properties": {
              "country": {
                "type": "string"
              },
              "countryName": {
                "type": "string"
              },
              "amount": {
                "type": "number"
              },
              "fromYear": {
                "type": "integer"
              },
              "toYear": {
                "type": "integer"
              },
              "cpiFrom": {
                "type": "number",
                "description": "CPI (2010 = 100) in the from-year."
              },
              "cpiTo": {
                "type": "number",
                "description": "CPI (2010 = 100) in the to-year."
              },
              "equivalentValue": {
                "type": "number",
                "description": "amount × CPI(to) ÷ CPI(from)."
              },
              "cumulativeInflationPercent": {
                "type": "number",
                "description": "(CPI(to) ÷ CPI(from) − 1) × 100."
              },
              "averageAnnualInflationPercent": {
                "type": "number",
                "description": "Compound annual rate between the earlier and later year."
              },
              "purchasingPowerLossPercent": {
                "type": "number",
                "description": "Share of purchasing power lost from the earlier to the later year (negative = gain)."
              },
              "dataLastUpdated": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "years": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "year",
                    "cpi",
                    "value",
                    "inflationPercent"
                  ],
                  "properties": {
                    "year": {
                      "type": "integer"
                    },
                    "cpi": {
                      "type": "number"
                    },
                    "value": {
                      "type": "number",
                      "description": "Amount expressed in this year's prices."
                    },
                    "inflationPercent": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Change of CPI vs. the previous listed year."
                    }
                  }
                }
              }
            }
          },
          "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"
              }
            }
          }
        }
      },
      "CurrencyConverterInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "amount": {
            "title": "Amount",
            "type": "number",
            "minimum": 0,
            "maximum": 1000000000000000,
            "default": 100
          },
          "from": {
            "title": "From currency",
            "description": "ISO code, e.g. EUR, USD, GBP, CHF, JPY.",
            "type": "string",
            "maxLength": 3,
            "default": "EUR"
          },
          "to": {
            "title": "To currencies",
            "description": "One or more ISO codes, comma-separated, or \"all\".",
            "type": "string",
            "maxLength": 200,
            "default": "USD, GBP, CHF, JPY"
          },
          "date": {
            "title": "Rate date",
            "description": "Optional: use the reference rates of this date (the last publication on or before it). Empty: latest rates.",
            "type": "string",
            "format": "date"
          }
        },
        "required": [
          "amount",
          "from",
          "to"
        ]
      },
      "CurrencyConverterResult": {
        "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": [
              "amount",
              "from",
              "rates",
              "conversions"
            ],
            "properties": {
              "amount": {
                "type": "number"
              },
              "from": {
                "type": "string"
              },
              "rates": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "date",
                  "source",
                  "url",
                  "fetchedAt",
                  "stale"
                ],
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Reference date of the rates."
                  },
                  "requestedDate": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "ecb-daily",
                      "ecb-sdmx",
                      "manual"
                    ]
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "fetchedAt": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "stale": {
                    "type": "boolean"
                  }
                }
              },
              "conversions": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "currency",
                    "name",
                    "rate",
                    "inverseRate",
                    "amount",
                    "rounded"
                  ],
                  "properties": {
                    "currency": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "rate": {
                      "type": "number",
                      "description": "Units of the target currency per 1 unit of the source currency."
                    },
                    "inverseRate": {
                      "type": "number"
                    },
                    "amount": {
                      "type": "number"
                    },
                    "rounded": {
                      "type": "number",
                      "description": "Rounded to the currency’s minor units."
                    }
                  }
                }
              }
            }
          },
          "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"
              }
            }
          }
        }
      },
      "HistoricalFxChartInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "base": {
            "title": "Base currency",
            "description": "The rate shows how many units of the quote currency one unit of the base buys.",
            "type": "string",
            "maxLength": 3,
            "default": "EUR"
          },
          "quotes": {
            "title": "Quote currencies",
            "description": "Up to 6 ISO codes, comma-separated.",
            "type": "string",
            "maxLength": 100,
            "default": "USD, GBP"
          },
          "start": {
            "title": "From",
            "description": "Default: one year before the end date. Earliest 1999-01-04.",
            "type": "string",
            "format": "date"
          },
          "end": {
            "title": "To",
            "description": "Default: today.",
            "type": "string",
            "format": "date"
          },
          "normalise": {
            "title": "Index to 100 at the start",
            "type": "string",
            "enum": [
              "auto",
              "yes",
              "no"
            ],
            "default": "auto"
          },
          "png": {
            "title": "Also create a PNG image",
            "type": "boolean",
            "default": true
          }
        },
        "required": [
          "base",
          "quotes"
        ]
      },
      "HistoricalFxChartResult": {
        "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": [
              "base",
              "quotes",
              "start",
              "end",
              "normalised",
              "pairs",
              "svg",
              "png",
              "dataUrl"
            ],
            "properties": {
              "base": {
                "type": "string"
              },
              "quotes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "start": {
                "type": "string"
              },
              "end": {
                "type": "string"
              },
              "normalised": {
                "type": "boolean"
              },
              "pairs": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "pair",
                    "base",
                    "quote",
                    "observations",
                    "start",
                    "end",
                    "min",
                    "max",
                    "average",
                    "changePercent",
                    "annualisedVolatilityPercent"
                  ],
                  "properties": {
                    "pair": {
                      "type": "string"
                    },
                    "base": {
                      "type": "string"
                    },
                    "quote": {
                      "type": "string"
                    },
                    "observations": {
                      "type": "integer"
                    },
                    "start": {
                      "type": "object"
                    },
                    "end": {
                      "type": "object"
                    },
                    "min": {
                      "type": "object"
                    },
                    "max": {
                      "type": "object"
                    },
                    "average": {
                      "type": "number"
                    },
                    "changePercent": {
                      "type": "number"
                    },
                    "annualisedVolatilityPercent": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  }
                }
              },
              "svg": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "artifactId",
                  "filename",
                  "mime",
                  "bytes",
                  "sha256",
                  "path"
                ],
                "properties": {
                  "artifactId": {
                    "type": "string"
                  },
                  "filename": {
                    "type": "string"
                  },
                  "mime": {
                    "type": "string"
                  },
                  "bytes": {
                    "type": "integer"
                  },
                  "sha256": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Download path on the same site (valid for about 2 hours)."
                  }
                }
              },
              "png": {
                "anyOf": [
                  {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "artifactId",
                      "filename",
                      "mime",
                      "bytes",
                      "sha256",
                      "path"
                    ],
                    "properties": {
                      "artifactId": {
                        "type": "string"
                      },
                      "filename": {
                        "type": "string"
                      },
                      "mime": {
                        "type": "string"
                      },
                      "bytes": {
                        "type": "integer"
                      },
                      "sha256": {
                        "type": "string"
                      },
                      "path": {
                        "type": "string",
                        "description": "Download path on the same site (valid for about 2 hours)."
                      }
                    }
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "dataUrl": {
                "type": "string",
                "description": "ECB Data Portal request used."
              }
            }
          },
          "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"
              }
            }
          }
        }
      },
      "MultiCurrencyInvoiceCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "invoiceCurrency": {
            "title": "Invoice currency",
            "type": "string",
            "maxLength": 3,
            "default": "EUR"
          },
          "rateSource": {
            "title": "Exchange rates",
            "type": "string",
            "enum": [
              "ecb",
              "manual"
            ],
            "default": "ecb"
          },
          "rateDate": {
            "title": "Rate date",
            "description": "E.g. the invoice or supply date. Empty: latest ECB rates. Weekends/holidays use the last publication before.",
            "type": "string",
            "format": "date"
          },
          "manualRates": {
            "title": "Manual rates",
            "description": "One per line, as units per 1 EUR (ECB convention). Used with \"Enter rates manually\".",
            "type": "string",
            "maxLength": 4000
          },
          "lines": {
            "title": "Invoice lines",
            "type": "array",
            "minItems": 1,
            "maxItems": 200,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "description": {
                  "title": "Description",
                  "type": "string",
                  "maxLength": 200
                },
                "quantity": {
                  "title": "Quantity",
                  "type": "number",
                  "minimum": -1000000000,
                  "maximum": 1000000000,
                  "default": 1
                },
                "unitPrice": {
                  "title": "Unit price",
                  "type": "number",
                  "minimum": -1000000000000,
                  "maximum": 1000000000000
                },
                "currency": {
                  "title": "Currency",
                  "type": "string",
                  "maxLength": 3
                },
                "vatRate": {
                  "title": "VAT %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100,
                  "default": 0
                }
              },
              "required": [
                "unitPrice"
              ]
            }
          },
          "rounding": {
            "title": "Rounding",
            "type": "string",
            "enum": [
              "line",
              "document"
            ],
            "default": "line"
          }
        },
        "required": [
          "invoiceCurrency",
          "lines"
        ]
      },
      "MultiCurrencyInvoiceCalculatorResult": {
        "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": [
              "invoiceCurrency",
              "rounding",
              "rates",
              "ratesUsed",
              "lines",
              "vatSummary",
              "totals",
              "alternativeRounding",
              "roundingDifference",
              "statement"
            ],
            "properties": {
              "invoiceCurrency": {
                "type": "string"
              },
              "rounding": {
                "type": "string"
              },
              "rates": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "date",
                  "source",
                  "url",
                  "fetchedAt",
                  "stale"
                ],
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Reference date of the rates."
                  },
                  "requestedDate": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "ecb-daily",
                      "ecb-sdmx",
                      "manual"
                    ]
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "fetchedAt": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "stale": {
                    "type": "boolean"
                  }
                }
              },
              "ratesUsed": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "currency",
                    "perEur",
                    "toInvoice"
                  ],
                  "properties": {
                    "currency": {
                      "type": "string"
                    },
                    "perEur": {
                      "type": "number"
                    },
                    "toInvoice": {
                      "type": "number",
                      "description": "Invoice-currency units per 1 unit of this currency."
                    }
                  }
                }
              },
              "lines": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "description",
                    "quantity",
                    "unitPrice",
                    "currency",
                    "netOriginal",
                    "rate",
                    "net",
                    "vatRate",
                    "vat",
                    "gross"
                  ],
                  "properties": {
                    "description": {
                      "type": "string"
                    },
                    "quantity": {
                      "type": "number"
                    },
                    "unitPrice": {
                      "type": "number"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "netOriginal": {
                      "type": "number"
                    },
                    "rate": {
                      "type": "number"
                    },
                    "net": {
                      "type": "number"
                    },
                    "vatRate": {
                      "type": "number"
                    },
                    "vat": {
                      "type": "number"
                    },
                    "gross": {
                      "type": "number"
                    }
                  }
                }
              },
              "vatSummary": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "vatRate",
                    "net",
                    "vat",
                    "gross"
                  ],
                  "properties": {
                    "vatRate": {
                      "type": "number"
                    },
                    "net": {
                      "type": "number"
                    },
                    "vat": {
                      "type": "number"
                    },
                    "gross": {
                      "type": "number"
                    }
                  }
                }
              },
              "totals": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "net",
                  "vat",
                  "gross"
                ],
                "properties": {
                  "net": {
                    "type": "number"
                  },
                  "vat": {
                    "type": "number"
                  },
                  "gross": {
                    "type": "number"
                  }
                }
              },
              "alternativeRounding": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "method",
                  "net",
                  "vat",
                  "gross"
                ],
                "properties": {
                  "method": {
                    "type": "string"
                  },
                  "net": {
                    "type": "number"
                  },
                  "vat": {
                    "type": "number"
                  },
                  "gross": {
                    "type": "number"
                  }
                }
              },
              "roundingDifference": {
                "type": "number",
                "description": "Gross total of this method minus the other method."
              },
              "statement": {
                "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"
              }
            }
          }
        }
      },
      "TravelExchangeFeeCalculatorInput": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "amount": {
            "title": "Amount spent abroad",
            "description": "In the local (spending) currency.",
            "type": "number",
            "minimum": 0.01,
            "maximum": 1000000000,
            "default": 500
          },
          "spendCurrency": {
            "title": "Spending currency",
            "type": "string",
            "maxLength": 3,
            "default": "USD"
          },
          "homeCurrency": {
            "title": "Home (account) currency",
            "type": "string",
            "maxLength": 3,
            "default": "EUR"
          },
          "rateSource": {
            "title": "Reference rate",
            "type": "string",
            "enum": [
              "ecb",
              "manual"
            ],
            "default": "ecb"
          },
          "manualRate": {
            "title": "Manual rate: 1 home-currency unit =",
            "description": "…units of the spending currency (e.g. 1 EUR = 1.10 USD → 1.10). Used with \"Enter a rate manually\", or as fallback when the ECB is unreachable.",
            "type": "number",
            "minimum": 0.000001,
            "maximum": 1000000000
          },
          "options": {
            "title": "Payment options",
            "description": "Example values – replace them with your own card’s and provider’s terms. EU card issuers must state their conversion mark-up over the ECB rate.",
            "type": "array",
            "maxItems": 12,
            "items": {
              "type": "object",
              "additionalProperties": false,
              "properties": {
                "name": {
                  "title": "Option",
                  "type": "string",
                  "maxLength": 80
                },
                "method": {
                  "title": "Type",
                  "type": "string",
                  "enum": [
                    "card",
                    "atm",
                    "dcc",
                    "exchange"
                  ],
                  "default": "card"
                },
                "feePercent": {
                  "title": "Fee %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 50,
                  "x-unit": "%"
                },
                "markupPercent": {
                  "title": "Rate mark-up %",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 50,
                  "x-unit": "%"
                },
                "fixedFee": {
                  "title": "Fixed fee (home currency)",
                  "type": "number",
                  "minimum": 0,
                  "maximum": 10000
                }
              },
              "required": []
            },
            "default": [
              {
                "name": "Card without foreign transaction fee",
                "method": "card",
                "feePercent": 0,
                "markupPercent": 0,
                "fixedFee": 0
              },
              {
                "name": "Card with foreign transaction fee",
                "method": "card",
                "feePercent": 1.75,
                "markupPercent": 0,
                "fixedFee": 0
              },
              {
                "name": "ATM cash withdrawal",
                "method": "atm",
                "feePercent": 1,
                "markupPercent": 0,
                "fixedFee": 4
              },
              {
                "name": "\"Pay in your currency\" (DCC) at the terminal",
                "method": "dcc",
                "feePercent": 0,
                "markupPercent": 5,
                "fixedFee": 0
              },
              {
                "name": "Exchange bureau cash",
                "method": "exchange",
                "feePercent": 0,
                "markupPercent": 7,
                "fixedFee": 0
              }
            ]
          },
          "atmWithdrawals": {
            "title": "Number of ATM withdrawals",
            "description": "The fixed fee of ATM options is charged per withdrawal.",
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 1
          }
        },
        "required": [
          "amount",
          "spendCurrency",
          "homeCurrency"
        ]
      },
      "TravelExchangeFeeCalculatorResult": {
        "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": [
              "amount",
              "spendCurrency",
              "homeCurrency",
              "referenceRate",
              "referenceCost",
              "rates",
              "options",
              "cheapest",
              "mostExpensive"
            ],
            "properties": {
              "amount": {
                "type": "number"
              },
              "spendCurrency": {
                "type": "string"
              },
              "homeCurrency": {
                "type": "string"
              },
              "referenceRate": {
                "type": "number",
                "description": "Spending-currency units per 1 home-currency unit."
              },
              "referenceCost": {
                "type": "number"
              },
              "rates": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "date",
                  "source",
                  "url",
                  "fetchedAt",
                  "stale"
                ],
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Reference date of the rates."
                  },
                  "requestedDate": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "ecb-daily",
                      "ecb-sdmx",
                      "manual"
                    ]
                  },
                  "url": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "fetchedAt": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "stale": {
                    "type": "boolean"
                  }
                }
              },
              "options": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "name",
                    "method",
                    "homeCost",
                    "extraCost",
                    "extraPercent",
                    "effectiveRate",
                    "breakdown"
                  ],
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "method": {
                      "type": "string"
                    },
                    "homeCost": {
                      "type": "number"
                    },
                    "extraCost": {
                      "type": "number"
                    },
                    "extraPercent": {
                      "type": "number"
                    },
                    "effectiveRate": {
                      "type": "number"
                    },
                    "breakdown": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "atReference",
                        "markup",
                        "percentFee",
                        "fixedFees"
                      ],
                      "properties": {
                        "atReference": {
                          "type": "number"
                        },
                        "markup": {
                          "type": "number"
                        },
                        "percentFee": {
                          "type": "number"
                        },
                        "fixedFees": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              },
              "cheapest": {
                "type": "string"
              },
              "mostExpensive": {
                "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"
              }
            }
          }
        }
      }
    }
  }
}