{
  "$comment": "Machine-readable companion to https://valem.run/calculators/ireland/holiday-entitlement. Formula, inputs, outputs, engine-verified test vectors, cited sources and the re-runnable Valem ModelSpec behind the page.",
  "url": "https://valem.run/calculators/ireland/holiday-entitlement",
  "dataUrl": "https://valem.run/calculators/ireland/holiday-entitlement.json",
  "license": {
    "name": "CC BY 4.0",
    "url": "https://creativecommons.org/licenses/by/4.0/",
    "attribution": "Ireland annual leave model by Valem (https://valem.run/calculators/ireland/holiday-entitlement), CC BY 4.0.",
    "note": "Covers the model — the expression of each rule and the arrangement of the catalog. The rates and thresholds themselves are law and are not licensed by anyone; the cited sources are where they come from."
  },
  "verificationUrl": "https://valem.run/calculators/ireland/holiday-entitlement/verify",
  "country": "ireland",
  "countryName": "Ireland",
  "formula": "holiday-entitlement",
  "formulaName": "Annual leave",
  "title": "Annual Leave Entitlement Ireland 2026 — All Three Methods",
  "answer": "Irish law does not have one annual-leave formula. Section 19 of the Organisation of Working Time Act 1997 gives three, and the employee is entitled to whichever produces the greater period: 4 working weeks once you pass 1,365 hours in the leave year, one-third of a working week for every month you work at least 117 hours, or 8% of the hours you worked, capped at 4 working weeks. A full-year five-day employee gets 20 days.",
  "lang": "en",
  "currency": "EUR",
  "taxYear": "2026",
  "verification": {
    "claim": "internal-consistency",
    "statement": "The Valem engine reproduces this model's stated formula through 10 self-test cases (below, and re-runnable against the spec). That asserts internal consistency, NOT that the formula matches current law — the cited sources are the evidence for that, and carry the date each was last checked.",
    "selfTestCases": 10,
    "ratesAreIllustrative": false,
    "sourcesCheckedOn": "2026-10-11"
  },
  "sources": [
    {
      "label": "Organisation of Working Time Act 1997, s.19 — revised (the three methods, their thresholds, the greater-of proviso, and what a working week is)",
      "url": "https://revisedacts.lawreform.ie/eli/1997/act/20/section/19/revised/en/html",
      "checked": "2026-10-11"
    },
    {
      "label": "Workplace Relations Commission — Annual Leave (the same three methods, the greater-of rule, and the four-working-week ceiling)",
      "url": "https://www.workplacerelations.ie/en/what_you_should_know/leave/annual-leave/",
      "checked": "2026-10-11"
    },
    {
      "label": "Organisation of Working Time Act 1997, s.2(1) — revised (“leave year” begins on a 1st day of April)",
      "url": "https://revisedacts.lawreform.ie/eli/1997/act/20/section/2/revised/en/html",
      "checked": "2026-10-11"
    }
  ],
  "terminalOutput": "daysRemaining",
  "headlineOutput": "daysEntitled",
  "inputs": [
    {
      "name": "daysPerWeek",
      "type": "number",
      "label": "Days you usually work per week",
      "minimum": 1,
      "maximum": 7
    },
    {
      "name": "hoursPerWeek",
      "type": "number",
      "label": "Hours you usually work per week",
      "minimum": 0,
      "maximum": 168
    },
    {
      "name": "monthsWorked",
      "type": "number",
      "label": "Months worked in the leave year (0–12)",
      "minimum": 0,
      "maximum": 12
    },
    {
      "name": "changedEmployment",
      "type": "boolean",
      "label": "I changed employment during this leave year"
    },
    {
      "name": "daysTaken",
      "type": "number",
      "label": "Days already taken",
      "minimum": 0
    }
  ],
  "outputs": [
    {
      "name": "hoursPerDay",
      "type": "number",
      "formula": "($d := $n(daysPerWeek); $d > 0 ? $n(hoursPerWeek) / $d : 0)"
    },
    {
      "name": "hoursPerMonth",
      "type": "number",
      "formula": "$n(hoursPerWeek) * $const.weeksPerYear / $const.monthsPerYear"
    },
    {
      "name": "hoursWorked",
      "type": "number",
      "formula": "hoursPerMonth * $n(monthsWorked)"
    },
    {
      "name": "fullWorkingWeeksDays",
      "type": "number",
      "formula": "$const.workingWeeks * $n(daysPerWeek)"
    },
    {
      "name": "methodADays",
      "type": "number",
      "formula": "(changedEmployment = true or hoursWorked < $const.annualHoursThreshold ? 0 : fullWorkingWeeksDays)"
    },
    {
      "name": "qualifyingMonths",
      "type": "number",
      "formula": "(hoursPerMonth >= $const.monthlyHoursThreshold ? $floor($n(monthsWorked)) : 0)"
    },
    {
      "name": "methodBDays",
      "type": "number",
      "formula": "qualifyingMonths * $n(daysPerWeek) / $const.monthWeekDivisor"
    },
    {
      "name": "methodCHours",
      "type": "number",
      "formula": "hoursWorked * $const.hoursPercentage"
    },
    {
      "name": "methodCDays",
      "type": "number",
      "formula": "(hoursPerDay > 0 ? $clampFloorCap(methodCHours / hoursPerDay, null, fullWorkingWeeksDays) : 0)"
    },
    {
      "name": "daysEntitled",
      "type": "number",
      "formula": "($a := $n(methodADays); $b := $n(methodBDays); $c := $n(methodCDays); $ab := $a > $b ? $a : $b; $g := $ab > $c ? $ab : $c; $round($clampFloorCap($g, null, fullWorkingWeeksDays), 1))"
    },
    {
      "name": "daysRemaining",
      "type": "number",
      "formula": "($r := daysEntitled - $n(daysTaken); $r < 0 ? 0 : $round($r, 1))"
    },
    {
      "name": "methodUsed",
      "type": "string",
      "formula": "($m := daysEntitled; $a := $round($n(methodADays), 1) = $m; $b := $round($n(methodBDays), 1) = $m; $c := $round($n(methodCDays), 1) = $m; $m <= 0 ? 'none yet — no qualifying hours in this leave year' : ($a and $b and $c ? 'all three methods give the same answer' : ($a and $b ? 'methods (a) and (b)' : ($a and $c ? 'methods (a) and (c)' : ($b and $c ? 'methods (b) and (c)' : ($a ? 'method (a) — you passed the 1,365-hour threshold' : ($b ? 'method (b) — one-third of a working week per qualifying month' : 'method (c) — 8% of the hours you worked')))))))"
    }
  ],
  "testCases": [
    {
      "description": "D1 a full-year five-day week is 20 days, and all three methods agree on it",
      "given": {
        "daysPerWeek": 5,
        "hoursPerWeek": 39,
        "monthsWorked": 12,
        "changedEmployment": false,
        "daysTaken": 0
      },
      "expect": {
        "methodADays": 20,
        "methodBDays": 20,
        "methodCDays": 20,
        "daysEntitled": 20,
        "methodUsed": "all three methods give the same answer"
      }
    },
    {
      "description": "D2 a three-day part-timer fails both hour tests, so only method (c) carries them",
      "given": {
        "daysPerWeek": 3,
        "hoursPerWeek": 24,
        "monthsWorked": 12,
        "changedEmployment": false,
        "daysTaken": 0
      },
      "expect": {
        "methodADays": 0,
        "methodBDays": 0,
        "methodCDays": 12,
        "daysEntitled": 12,
        "methodUsed": "method (c) — 8% of the hours you worked"
      }
    },
    {
      "description": "D3 half a leave year: method (c)'s 10.4 beats method (b)'s 10, which is what the proviso is for",
      "given": {
        "daysPerWeek": 5,
        "hoursPerWeek": 39,
        "monthsWorked": 6,
        "changedEmployment": false,
        "daysTaken": 0
      },
      "expect": {
        "hoursWorked": 1014,
        "methodADays": 0,
        "methodBDays": 10,
        "methodCDays": 10.4,
        "daysEntitled": 10.4
      }
    },
    {
      "description": "D4 eleven months on a six-day week clears 1,365 hours, so method (a) pays the full 4 weeks",
      "given": {
        "daysPerWeek": 6,
        "hoursPerWeek": 45,
        "monthsWorked": 11,
        "changedEmployment": false,
        "daysTaken": 0
      },
      "expect": {
        "methodADays": 24,
        "methodBDays": 22,
        "daysEntitled": 24,
        "methodUsed": "method (a) — you passed the 1,365-hour threshold"
      }
    },
    {
      "description": "D5 the same year, but employment changed: s.19(1)(a) is unavailable and (c) wins at 22.9",
      "given": {
        "daysPerWeek": 6,
        "hoursPerWeek": 45,
        "monthsWorked": 11,
        "changedEmployment": true,
        "daysTaken": 0
      },
      "expect": {
        "methodADays": 0,
        "daysEntitled": 22.9,
        "methodUsed": "method (c) — 8% of the hours you worked"
      }
    },
    {
      "description": "D6 the four-working-week ceiling holds against a 100-hour week",
      "given": {
        "daysPerWeek": 5,
        "hoursPerWeek": 100,
        "monthsWorked": 12,
        "changedEmployment": false,
        "daysTaken": 0
      },
      "expect": {
        "fullWorkingWeeksDays": 20,
        "methodCDays": 20,
        "daysEntitled": 20
      }
    },
    {
      "description": "D7 no months worked is no entitlement, and the method label says so rather than naming one",
      "given": {
        "daysPerWeek": 5,
        "hoursPerWeek": 39,
        "monthsWorked": 0,
        "changedEmployment": false,
        "daysTaken": 0
      },
      "expect": {
        "daysEntitled": 0,
        "methodUsed": "none yet — no qualifying hours in this leave year"
      }
    },
    {
      "description": "D8 a two-day week is 8 days, the (c) cap biting on 8.32",
      "given": {
        "daysPerWeek": 2,
        "hoursPerWeek": 16,
        "monthsWorked": 12,
        "changedEmployment": false,
        "daysTaken": 0
      },
      "expect": {
        "methodCDays": 8,
        "daysEntitled": 8
      }
    },
    {
      "description": "D9 days already taken come off the remainder and it never goes negative",
      "given": {
        "daysPerWeek": 5,
        "hoursPerWeek": 39,
        "monthsWorked": 12,
        "changedEmployment": false,
        "daysTaken": 25
      },
      "expect": {
        "daysEntitled": 20,
        "daysRemaining": 0
      }
    },
    {
      "description": "D10 days taken below the entitlement leave the balance",
      "given": {
        "daysPerWeek": 5,
        "hoursPerWeek": 39,
        "monthsWorked": 12,
        "changedEmployment": false,
        "daysTaken": 8
      },
      "expect": {
        "daysEntitled": 20,
        "daysRemaining": 12
      }
    }
  ],
  "spec": {
    "id": "holiday-entitlement-ireland",
    "version": "1.0.0",
    "schema": {
      "type": "object",
      "properties": {
        "daysPerWeek": {
          "type": "number",
          "maximum": 7,
          "minimum": 1
        },
        "hoursPerWeek": {
          "type": "number",
          "maximum": 168,
          "minimum": 0
        },
        "monthsWorked": {
          "type": "number",
          "maximum": 12,
          "minimum": 0
        },
        "changedEmployment": {
          "type": "boolean"
        },
        "daysTaken": {
          "type": "number",
          "minimum": 0
        },
        "hoursPerDay": {
          "type": "number",
          "readOnly": true
        },
        "hoursPerMonth": {
          "type": "number",
          "readOnly": true
        },
        "hoursWorked": {
          "type": "number",
          "readOnly": true
        },
        "fullWorkingWeeksDays": {
          "type": "number",
          "readOnly": true
        },
        "methodADays": {
          "type": "number",
          "readOnly": true
        },
        "qualifyingMonths": {
          "type": "number",
          "readOnly": true
        },
        "methodBDays": {
          "type": "number",
          "readOnly": true
        },
        "methodCHours": {
          "type": "number",
          "readOnly": true
        },
        "methodCDays": {
          "type": "number",
          "readOnly": true
        },
        "daysEntitled": {
          "type": "number",
          "readOnly": true
        },
        "daysRemaining": {
          "type": "number",
          "readOnly": true
        },
        "methodUsed": {
          "type": "string",
          "readOnly": true
        }
      }
    },
    "constants": {
      "workingWeeks": 4,
      "annualHoursThreshold": 1365,
      "monthlyHoursThreshold": 117,
      "monthWeekDivisor": 3,
      "hoursPercentage": 0.08,
      "weeksPerYear": 52,
      "monthsPerYear": 12
    },
    "library": {
      "layers": [
        {
          "id": "statutory",
          "define": "(\n  /* Shared statutory helpers — the canonical copy (docs/sandbox/d8-statutory-families.md §5.4).\n     DO NOT EDIT A SPEC'S INLINED COPY. Edit this file and run `npm run sync:helpers`; a vitest\n     assertion and a Java assertion both require every inlined copy to be byte-identical to this one.\n\n     Why inlined rather than referenced: the `library` section's `ref` form — a library shared\n     between models — is still proposed, gated on the model-references design (see\n     ../../../../docs/design/library/jsonata-library.md). Inlining keeps each spec self-contained,\n     which is what lets `#e=` embedding, `#share=` import and MCP forking work with no resolver.\n\n     TWO TRAPS, both of which fail SILENTLY and both of which bit during the debt-payoff build:\n\n       1. A library function sees ONLY its arguments. It cannot read the consuming document — not\n          through a bare field name, not through `$`, not through `$$`. Everything these helpers\n          need is passed in. (`$const` is late-bound and so would resolve, but relying on it would\n          couple a shared helper to one spec's constant names.)\n       2. ONE null member wrecks a whole aggregate, and the two engines disagree about how. The JS\n          reference implementation THROWS (\"Argument 1 of function sum must be an array of\n          numbers\"); the Java port can instead yield null for the entire aggregate. Neither gives a\n          partial total, and the null-yielding form is the dangerous one because it is silent. So\n          callers coerce with `$n()` below before aggregating, which makes the behaviour identical\n          in both engines — measured, not assumed: see helpers.test.ts.\n\n       3. A PATH STEP DIRECTLY ON A CALL RESULT RETURNS NOTHING in the Java engine. This matters for\n          every caller of $amortize, which is the one helper here returning an object:\n\n              $amortize(p, r, n, 0).months            -> empty in Java, 360 in JS.  WRONG\n              ($a := $amortize(p, r, n, 0); $a.months) -> 360 in both.               RIGHT\n\n          Bind the result to a variable, then step into it. The JS reference accepts both forms, so\n          this is invisible until the page is served by the real engine, and it fails as a blank\n          field rather than an error. Measured with a probe, not inferred — the field simply does\n          not appear. */\n\n  /* Number coercion with an explicit zero default. The guard against trap 2. */\n  $n := function($v) { $v = null or $not($exists($v)) ? 0 : $number($v) };\n\n  /* ISO date -> its year / month / day parts. String slicing rather than $fromMillis formatting:\n     these are statutory dates (\"2026-04-06\"), and a timezone-sensitive round-trip through millis\n     can shift the day, which silently moves a service anniversary across a band boundary. */\n  $isoY := function($d) { $number($substring($d, 0, 4)) };\n  $isoM := function($d) { $number($substring($d, 5, 2)) };\n  $isoD := function($d) { $number($substring($d, 8, 2)) };\n\n  /* Whole years of completed service between two ISO dates. Counts an anniversary as reached only\n     when the month-and-day has arrived, which is what \"completed years of service\" means in every\n     severance statute — not a 365.25-day division, whose drift crosses a 5-year band boundary in\n     the wrong direction roughly one year in four. */\n  $yearsBetween := function($from, $to) { (\n    $y := $isoY($to) - $isoY($from);\n    $beforeAnniversary := $isoM($to) < $isoM($from)\n      or ($isoM($to) = $isoM($from) and $isoD($to) < $isoD($from));\n    $beforeAnniversary ? $y - 1 : $y\n  ) };\n\n  /* Whole completed months between two ISO dates, same convention. */\n  $monthsBetween := function($from, $to) { (\n    $m := ($isoY($to) - $isoY($from)) * 12 + ($isoM($to) - $isoM($from));\n    $isoD($to) < $isoD($from) ? $m - 1 : $m\n  ) };\n\n  /* Inclusive count of days between two ISO dates — both endpoints counted, because a leave year\n     runs from its first day to its last and an employee present on both is present for both.\n     Pro-rata accrual is then $proRataDays(a, b) / $proRataDays(yearStart, yearEnd). */\n  $proRataDays := function($from, $to) {\n    ($toMillis($to) - $toMillis($from)) / 86400000 + 1\n  };\n\n  /* Progressive charge across bands. `$bands` is ordered lowest-first, each {\"upTo\": n, \"rate\": r};\n     the final band's `upTo` is omitted or null to mean \"no ceiling\". Each band's rate applies ONLY\n     to the slice of the amount inside it — the single most common error in a hand-written bracket\n     walk is applying the top rate to the whole amount. Returns 0 for a non-positive amount. */\n  $bandedCharge := function($amount, $bands) { (\n    $a := $n($amount);\n    $a <= 0 ? 0 : $reduce($bands, function($acc, $band) { (\n      $ceiling := $exists($band.upTo) and $band.upTo != null ? $band.upTo : $a;\n      $top := $ceiling < $a ? $ceiling : $a;\n      $slice := $top - $acc.floor;\n      {\n        \"floor\": $top > $acc.floor ? $top : $acc.floor,\n        \"charge\": $acc.charge + ($slice > 0 ? $slice * $n($band.rate) : 0)\n      }\n    ) }, {\"floor\": 0, \"charge\": 0}).charge\n  ) };\n\n  /* Clamp into [floor, cap]. Either bound may be omitted (null) to leave that side open — a benefit\n     with a cap and no floor is as common as one with both. */\n  $clampFloorCap := function($value, $floor, $cap) { (\n    $v := $n($value);\n    $withFloor := $exists($floor) and $floor != null and $v < $floor ? $floor : $v;\n    $exists($cap) and $cap != null and $withFloor > $cap ? $cap : $withFloor\n  ) };\n\n  /* Level annuity payment for a principal over `$months` at `$annualRatePct` nominal, compounded\n     monthly. Falls back to straight-line division at a zero rate, where the standard formula\n     divides by zero. */\n  $annuityPayment := function($principal, $annualRatePct, $months) { (\n    $p := $n($principal);\n    $m := $n($months);\n    $i := $n($annualRatePct) / 1200;\n    $m <= 0 ? 0 : ($i = 0 ? $p / $m : $p * $i * $power(1 + $i, $m) / ($power(1 + $i, $m) - 1))\n  ) };\n\n  /* Amortisation schedule: one row per month until the balance clears or `$months` is reached.\n     Returns {\"payment\", \"months\", \"totalInterest\", \"schedule\": [{month, interest, principal,\n     balance}]}. `$extra` is an optional monthly overpayment.\n\n     A real month-by-month fold rather than a closed form, because an overpayment changes every\n     later row — the same reason debt-payoff simulates. Bounded by `$months`, so it always\n     terminates. */\n  $amortize := function($principal, $annualRatePct, $months, $extra) { (\n    $i := $n($annualRatePct) / 1200;\n    $pay := $annuityPayment($principal, $annualRatePct, $months) + $n($extra);\n    $r := $reduce([1..$n($months)], function($acc, $month) {\n      $acc.balance <= 0 ? $acc : (\n        /* The running balance is kept UNROUNDED and only the emitted row is rounded. Rounding the\n           balance each month instead accumulates drift: at 200,000 over 360 months it left 2.83\n           still owing in the final row, which would break the \"final balance is zero\" invariant\n           (§7.3) and look to a reader like the schedule does not pay the loan off. Lenders compute\n           on the exact balance and adjust the last payment, which is what this does. */\n        $interest := $acc.balance * $i;\n        $due := $acc.balance + $interest < $pay ? $acc.balance + $interest : $pay;\n        $principalPart := $due - $interest;\n        $balance := $acc.balance - $principalPart;\n        {\n          \"balance\": $balance,\n          \"totalInterest\": $acc.totalInterest + $interest,\n          \"schedule\": $append($acc.schedule, [{\n            \"month\": $month,\n            \"interest\": $round($interest, 2),\n            \"principal\": $round($principalPart, 2),\n            \"balance\": $round($balance, 2)\n          }])\n        }\n      )\n    }, {\"balance\": $n($principal), \"totalInterest\": 0, \"schedule\": []});\n    {\n      \"payment\": $round($pay, 2),\n      \"months\": $count($r.schedule),\n      \"totalInterest\": $round($r.totalInterest, 2),\n      \"schedule\": [$r.schedule]\n    }\n  ) };\n\n  [\"n\", \"isoY\", \"isoM\", \"isoD\", \"yearsBetween\", \"monthsBetween\", \"proRataDays\",\n   \"bandedCharge\", \"clampFloorCap\", \"annuityPayment\", \"amortize\"]\n)\n"
        }
      ]
    },
    "defaultValues": [
      {
        "path": "$",
        "expr": "{ 'daysPerWeek': 5, 'hoursPerWeek': 39, 'monthsWorked': 12, 'changedEmployment': false, 'daysTaken': 0 }"
      }
    ],
    "derivations": [
      {
        "path": "$.hoursPerDay",
        "expr": "($d := $n(daysPerWeek); $d > 0 ? $n(hoursPerWeek) / $d : 0)"
      },
      {
        "path": "$.hoursPerMonth",
        "expr": "$n(hoursPerWeek) * $const.weeksPerYear / $const.monthsPerYear"
      },
      {
        "path": "$.hoursWorked",
        "expr": "hoursPerMonth * $n(monthsWorked)"
      },
      {
        "path": "$.fullWorkingWeeksDays",
        "expr": "$const.workingWeeks * $n(daysPerWeek)"
      },
      {
        "path": "$.methodADays",
        "expr": "(changedEmployment = true or hoursWorked < $const.annualHoursThreshold ? 0 : fullWorkingWeeksDays)"
      },
      {
        "path": "$.qualifyingMonths",
        "expr": "(hoursPerMonth >= $const.monthlyHoursThreshold ? $floor($n(monthsWorked)) : 0)"
      },
      {
        "path": "$.methodBDays",
        "expr": "qualifyingMonths * $n(daysPerWeek) / $const.monthWeekDivisor"
      },
      {
        "path": "$.methodCHours",
        "expr": "hoursWorked * $const.hoursPercentage"
      },
      {
        "path": "$.methodCDays",
        "expr": "(hoursPerDay > 0 ? $clampFloorCap(methodCHours / hoursPerDay, null, fullWorkingWeeksDays) : 0)"
      },
      {
        "path": "$.daysEntitled",
        "expr": "($a := $n(methodADays); $b := $n(methodBDays); $c := $n(methodCDays); $ab := $a > $b ? $a : $b; $g := $ab > $c ? $ab : $c; $round($clampFloorCap($g, null, fullWorkingWeeksDays), 1))"
      },
      {
        "path": "$.methodUsed",
        "expr": "($m := daysEntitled; $a := $round($n(methodADays), 1) = $m; $b := $round($n(methodBDays), 1) = $m; $c := $round($n(methodCDays), 1) = $m; $m <= 0 ? 'none yet — no qualifying hours in this leave year' : ($a and $b and $c ? 'all three methods give the same answer' : ($a and $b ? 'methods (a) and (b)' : ($a and $c ? 'methods (a) and (c)' : ($b and $c ? 'methods (b) and (c)' : ($a ? 'method (a) — you passed the 1,365-hour threshold' : ($b ? 'method (b) — one-third of a working week per qualifying month' : 'method (c) — 8% of the hours you worked')))))))"
      },
      {
        "path": "$.daysRemaining",
        "expr": "($r := daysEntitled - $n(daysTaken); $r < 0 ? 0 : $round($r, 1))"
      }
    ],
    "constraints": [],
    "viewDefinition": {
      "renderer": "builtin",
      "defaultView": "main",
      "views": [
        {
          "id": "main",
          "label": "Holiday entitlement",
          "layout": "vertical",
          "components": [
            {
              "id": "note",
              "type": "staticText",
              "text": "Ireland does not have one annual-leave formula, it has three, and s.19(1) of the Organisation of Working Time Act 1997 awards “whichever of those periods is the greater.” All three are below. “Four working weeks” is not a fixed number of days: s.19(6) defines a working week as “the number of days that the employee concerned usually works in a week”, so it is 20 days on a five-day week and 12 on a three-day one."
            },
            {
              "id": "inputs",
              "type": "fieldSet",
              "label": "Your working year",
              "components": [
                {
                  "id": "daysPerWeek",
                  "type": "numericField",
                  "label": "Days you usually work per week",
                  "bind": "$.daysPerWeek",
                  "helperText": "s.19(6)'s working week. This is what “four working weeks” converts into."
                },
                {
                  "id": "hoursPerWeek",
                  "type": "numericField",
                  "label": "Hours you usually work per week",
                  "bind": "$.hoursPerWeek",
                  "helperText": "Methods (a) and (b) are hour thresholds — 1,365 a year and 117 a month — so this decides whether they apply to you at all."
                },
                {
                  "id": "monthsWorked",
                  "type": "numericField",
                  "label": "Months worked in the leave year (0–12)",
                  "bind": "$.monthsWorked",
                  "helperText": "The leave year runs from 1 April (s.2(1)). Leave this at 12 for a full year."
                },
                {
                  "id": "changedEmployment",
                  "type": "checkboxField",
                  "label": "I changed employment during this leave year",
                  "bind": "$.changedEmployment",
                  "helperText": "s.19(1)(a) excludes “a leave year in which he or she changes employment”, so method (a) does not apply."
                },
                {
                  "id": "daysTaken",
                  "type": "numericField",
                  "label": "Days already taken",
                  "bind": "$.daysTaken"
                }
              ]
            },
            {
              "id": "sep",
              "type": "separatorLine"
            },
            {
              "id": "entitledTile",
              "type": "statTile",
              "label": "Days entitled",
              "value": "$.daysEntitled"
            },
            {
              "id": "remainingTile",
              "type": "statTile",
              "label": "Days remaining",
              "value": "$.daysRemaining"
            },
            {
              "id": "methods",
              "type": "keyValueList",
              "label": "The three statutory methods, and which one won",
              "items": [
                {
                  "label": "Winning method",
                  "value": "$.methodUsed"
                },
                {
                  "label": "(a) 4 working weeks, if you pass 1,365 hours",
                  "value": "$.methodADays"
                },
                {
                  "label": "(b) ⅓ of a working week per month over 117 hours",
                  "value": "$.methodBDays"
                },
                {
                  "label": "(c) 8% of your hours, capped at 4 working weeks",
                  "value": "$.methodCDays"
                },
                {
                  "label": "Hours worked in the leave year",
                  "value": "$.hoursWorked"
                },
                {
                  "label": "Hours in a typical month",
                  "value": "$.hoursPerMonth"
                },
                {
                  "label": "Months that clear 117 hours",
                  "value": "$.qualifyingMonths"
                },
                {
                  "label": "4 working weeks, in your days",
                  "value": "$.fullWorkingWeeksDays"
                }
              ]
            },
            {
              "id": "assumptions",
              "type": "staticText",
              "text": "Two figures here are calendar arithmetic rather than law, and are declared as such below: 52 weeks in a year and 12 months in one. s.19(1) tests an annual hour total, a calendar month's hours and a weekly working pattern in the same sentence without saying how to convert between them, so the 117-hour test is applied to a typical month. A month holding five pay weeks will differ. The Act sets no rounding rule, so this rounds to a tenth of a day. The Workplace Relations Commission adds the ceiling: “the maximum statutory annual leave entitlement of an employee in a leave year is four of his/her normal working weeks.”"
            },
            {
              "id": "sources",
              "type": "sourceList",
              "label": "Sources",
              "layers": 1,
              "exempt": [
                "weeksPerYear",
                "monthsPerYear"
              ],
              "items": [
                {
                  "label": "Organisation of Working Time Act 1997, s.19 — revised (the three methods, their thresholds, the greater-of proviso, and what a working week is)",
                  "url": "https://revisedacts.lawreform.ie/eli/1997/act/20/section/19/revised/en/html",
                  "date": "2026-10-11",
                  "covers": [
                    "workingWeeks",
                    "annualHoursThreshold",
                    "monthlyHoursThreshold",
                    "monthWeekDivisor",
                    "hoursPercentage"
                  ]
                },
                {
                  "label": "Workplace Relations Commission — Annual Leave (the same three methods, the greater-of rule, and the four-working-week ceiling)",
                  "url": "https://www.workplacerelations.ie/en/what_you_should_know/leave/annual-leave/",
                  "date": "2026-10-11"
                },
                {
                  "label": "Organisation of Working Time Act 1997, s.2(1) — revised (“leave year” begins on a 1st day of April)",
                  "url": "https://revisedacts.lawreform.ie/eli/1997/act/20/section/2/revised/en/html",
                  "date": "2026-10-11"
                }
              ]
            }
          ]
        }
      ]
    },
    "tests": [
      {
        "description": "D1 a full-year five-day week is 20 days, and all three methods agree on it",
        "given": {
          "$.daysPerWeek": 5,
          "$.hoursPerWeek": 39,
          "$.monthsWorked": 12,
          "$.changedEmployment": false,
          "$.daysTaken": 0
        },
        "expect": {
          "$.methodADays": 20,
          "$.methodBDays": 20,
          "$.methodCDays": 20,
          "$.daysEntitled": 20,
          "$.methodUsed": "all three methods give the same answer"
        }
      },
      {
        "description": "D2 a three-day part-timer fails both hour tests, so only method (c) carries them",
        "given": {
          "$.daysPerWeek": 3,
          "$.hoursPerWeek": 24,
          "$.monthsWorked": 12,
          "$.changedEmployment": false,
          "$.daysTaken": 0
        },
        "expect": {
          "$.methodADays": 0,
          "$.methodBDays": 0,
          "$.methodCDays": 12,
          "$.daysEntitled": 12,
          "$.methodUsed": "method (c) — 8% of the hours you worked"
        }
      },
      {
        "description": "D3 half a leave year: method (c)'s 10.4 beats method (b)'s 10, which is what the proviso is for",
        "given": {
          "$.daysPerWeek": 5,
          "$.hoursPerWeek": 39,
          "$.monthsWorked": 6,
          "$.changedEmployment": false,
          "$.daysTaken": 0
        },
        "expect": {
          "$.hoursWorked": 1014,
          "$.methodADays": 0,
          "$.methodBDays": 10,
          "$.methodCDays": 10.4,
          "$.daysEntitled": 10.4
        }
      },
      {
        "description": "D4 eleven months on a six-day week clears 1,365 hours, so method (a) pays the full 4 weeks",
        "given": {
          "$.daysPerWeek": 6,
          "$.hoursPerWeek": 45,
          "$.monthsWorked": 11,
          "$.changedEmployment": false,
          "$.daysTaken": 0
        },
        "expect": {
          "$.methodADays": 24,
          "$.methodBDays": 22,
          "$.daysEntitled": 24,
          "$.methodUsed": "method (a) — you passed the 1,365-hour threshold"
        }
      },
      {
        "description": "D5 the same year, but employment changed: s.19(1)(a) is unavailable and (c) wins at 22.9",
        "given": {
          "$.daysPerWeek": 6,
          "$.hoursPerWeek": 45,
          "$.monthsWorked": 11,
          "$.changedEmployment": true,
          "$.daysTaken": 0
        },
        "expect": {
          "$.methodADays": 0,
          "$.daysEntitled": 22.9,
          "$.methodUsed": "method (c) — 8% of the hours you worked"
        }
      },
      {
        "description": "D6 the four-working-week ceiling holds against a 100-hour week",
        "given": {
          "$.daysPerWeek": 5,
          "$.hoursPerWeek": 100,
          "$.monthsWorked": 12,
          "$.changedEmployment": false,
          "$.daysTaken": 0
        },
        "expect": {
          "$.fullWorkingWeeksDays": 20,
          "$.methodCDays": 20,
          "$.daysEntitled": 20
        }
      },
      {
        "description": "D7 no months worked is no entitlement, and the method label says so rather than naming one",
        "given": {
          "$.daysPerWeek": 5,
          "$.hoursPerWeek": 39,
          "$.monthsWorked": 0,
          "$.changedEmployment": false,
          "$.daysTaken": 0
        },
        "expect": {
          "$.daysEntitled": 0,
          "$.methodUsed": "none yet — no qualifying hours in this leave year"
        }
      },
      {
        "description": "D8 a two-day week is 8 days, the (c) cap biting on 8.32",
        "given": {
          "$.daysPerWeek": 2,
          "$.hoursPerWeek": 16,
          "$.monthsWorked": 12,
          "$.changedEmployment": false,
          "$.daysTaken": 0
        },
        "expect": {
          "$.methodCDays": 8,
          "$.daysEntitled": 8
        }
      },
      {
        "description": "D9 days already taken come off the remainder and it never goes negative",
        "given": {
          "$.daysPerWeek": 5,
          "$.hoursPerWeek": 39,
          "$.monthsWorked": 12,
          "$.changedEmployment": false,
          "$.daysTaken": 25
        },
        "expect": {
          "$.daysEntitled": 20,
          "$.daysRemaining": 0
        }
      },
      {
        "description": "D10 days taken below the entitlement leave the balance",
        "given": {
          "$.daysPerWeek": 5,
          "$.hoursPerWeek": 39,
          "$.monthsWorked": 12,
          "$.changedEmployment": false,
          "$.daysTaken": 8
        },
        "expect": {
          "$.daysEntitled": 20,
          "$.daysRemaining": 12
        }
      }
    ]
  }
}
