{
  "openapi": "3.1.0",
  "info": {
    "title": "OPEREX Shift Scheduling Engine",
    "version": "0.7.0",
    "summary": "Compliance-aware shift scheduling API (headless motor)",
    "description": "Deterministic, compliance-aware shift scheduling over HTTP. Canonical crew rotation anchored on a fixed base date, Hungarian public-holiday overlay, individual work-pattern overrides, per-day coverage, and a labour-law compliance report that cites its statute references. Signals and justifies — does not guarantee. Rules live as versioned data; every response echoes the engine and rulebook versions for reproducibility."
  },
  "servers": [
    {
      "url": "https://api.operex.eu"
    }
  ],
  "externalDocs": {
    "description": "API reference",
    "url": "https://operex.eu/engine/shift-scheduling/api-reference"
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/schedule:build": {
      "post": {
        "summary": "Build one month’s roster (and optionally evaluate compliance)",
        "operationId": "scheduleBuild",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BuildRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The built schedule and optional compliance report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BuildResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid request — unknown pattern key, indivisible cycle, a crew index outside 0..n_crews-1, a duplicate staff_id, an unsupported kind (lean), a non-finite rule_overrides value (NaN/Infinity), or an unknown timezone."
          }
        }
      }
    },
    "/v1/schedule:build-range": {
      "post": {
        "summary": "Build a roster over an arbitrary date range (and optionally evaluate compliance)",
        "description": "Same deterministic rotation and per-cell assignment overrides as schedule:build, over any inclusive span (a fortnight, a quarter, a year) in one call. Compliance rolling windows span the whole range; statistics aggregate over the span.",
        "operationId": "scheduleBuildRange",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BuildRangeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The built range schedule and optional compliance report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BuildRangeResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid request — the same roster/pattern/timezone rejects as schedule:build, plus end before start or a span over 366 days."
          }
        }
      }
    },
    "/v1/version": {
      "get": {
        "summary": "Engine + rule-data versions",
        "operationId": "version",
        "security": [],
        "responses": {
          "200": {
            "description": "Component versions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/patterns": {
      "get": {
        "summary": "List available shift patterns",
        "operationId": "patterns",
        "security": [],
        "responses": {
          "200": {
            "description": "Pattern list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Pattern"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/holidays": {
      "get": {
        "summary": "Base national public holidays for a year (fixed + Easter-derived)",
        "description": "The default holiday calendar the app seeds its holiday editor with. A tenant overrides it and passes the effective set back in BuildRequest.holidays; the engine never stores edits.",
        "operationId": "holidays",
        "security": [],
        "parameters": [
          {
            "name": "year",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1900,
              "maximum": 2200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Holiday list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Holiday"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/rules": {
      "get": {
        "summary": "Base compliance rules with their editable thresholds",
        "description": "The default rulebook the app seeds its legal-compliance editor with. A tenant adjusts the numeric thresholds and on/off flag and passes the deltas back in BuildRequest.rule_overrides; which rules exist and their statute references are engine-owned.",
        "operationId": "rules",
        "security": [],
        "responses": {
          "200": {
            "description": "Rule list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Rule"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "summary": "Liveness probe",
        "operationId": "health",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer"
      }
    },
    "schemas": {
      "BuildRequest": {
        "type": "object",
        "required": [
          "site",
          "year",
          "month"
        ],
        "properties": {
          "site": {
            "$ref": "#/components/schemas/Site"
          },
          "year": {
            "type": "integer",
            "minimum": 1900,
            "maximum": 2200
          },
          "month": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12
          },
          "evaluate": {
            "type": "boolean",
            "default": true
          },
          "holidays": {
            "description": "Effective holiday calendar (edited base data). Omit to let the engine compute the national holidays. An empty array means \"no holidays\". At most 400 entries (422 above).",
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "date"
                },
                "maxItems": 400
              },
              {
                "type": "null"
              }
            ]
          },
          "rule_overrides": {
            "description": "Per-rule threshold / enabled overrides: rule_id → { param: value, \"enabled\": 0|1 }. Unknown rule ids are ignored. Input caps (v0.6.2): at most 50 rules × 50 params (422 above).",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "number"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "assignments": {
            "description": "Per-cell MANUAL plan overrides: staff_id → { day (YYYY-MM-DD) → symbol glyph }. An override replaces one person’s rotation symbol on one day; coverage and the compliance report are computed on the overridden schedule. The rotation itself is untouched, so the build stays deterministic. Unknown staff ids / out-of-range days are ignored; an invalid glyph is a 422. Input caps (v0.6.2): at most 500 staff entries × 62 days (422 above).",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string",
                    "enum": [
                      "r",
                      "d",
                      "é",
                      "R",
                      "É",
                      "N",
                      "-",
                      "Sz",
                      "CS",
                      "Üp",
                      "B"
                    ]
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "BuildRangeRequest": {
        "type": "object",
        "required": [
          "site",
          "start",
          "end"
        ],
        "description": "Like BuildRequest but anchored by an inclusive start/end date range instead of a calendar year/month. Same rotation, holidays, rule_overrides and per-cell assignments semantics.",
        "properties": {
          "site": {
            "$ref": "#/components/schemas/Site"
          },
          "start": {
            "type": "string",
            "format": "date",
            "description": "First day (inclusive)."
          },
          "end": {
            "type": "string",
            "format": "date",
            "description": "Last day (inclusive). At most 366 days after start (422 above)."
          },
          "evaluate": {
            "type": "boolean",
            "default": true
          },
          "holidays": {
            "description": "Effective holiday calendar (edited base data). Omit to let the engine compute the national holidays for every year the range touches. At most 400 entries (422 above).",
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "date"
                },
                "maxItems": 400
              },
              {
                "type": "null"
              }
            ]
          },
          "rule_overrides": {
            "description": "Per-rule threshold / enabled overrides: rule_id → { param: value, \"enabled\": 0|1 }. Input caps: at most 50 rules × 50 params (422 above).",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "number"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "assignments": {
            "description": "Per-cell MANUAL plan overrides: staff_id → { day (YYYY-MM-DD) → symbol glyph }, applied over the range exactly as in schedule:build. Input caps: at most 500 staff entries × 366 days (422 above).",
            "oneOf": [
              {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string",
                    "enum": [
                      "r",
                      "d",
                      "é",
                      "R",
                      "É",
                      "N",
                      "-",
                      "Sz",
                      "CS",
                      "Üp",
                      "B"
                    ]
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "Site": {
        "type": "object",
        "required": [
          "site_id",
          "shift_pattern_key",
          "anchor",
          "n_crews",
          "dayworker_anchor",
          "employees"
        ],
        "properties": {
          "site_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "shift_pattern_key": {
            "type": "string",
            "example": "12h-2-2-4"
          },
          "anchor": {
            "type": "string",
            "format": "date"
          },
          "n_crews": {
            "type": "integer",
            "minimum": 1,
            "maximum": 12
          },
          "dayworker_pattern_key": {
            "type": "string",
            "default": "nappalos"
          },
          "dayworker_anchor": {
            "type": "string",
            "format": "date"
          },
          "employees": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Employee"
            },
            "maxItems": 500,
            "description": "At most 500 rostered people per build (input cap, v0.6.2 — 422 above)."
          },
          "min_headcount": {
            "$ref": "#/components/schemas/MinHeadcount"
          },
          "timezone": {
            "type": "string",
            "default": "Europe/Budapest",
            "description": "IANA timezone the shift clock-times are anchored in. Drives DST-correct worked hours: a night shift spanning a clock change is 11h (spring) or 13h (autumn), not the nominal 12h."
          }
        }
      },
      "Employee": {
        "type": "object",
        "required": [
          "staff_id",
          "name"
        ],
        "properties": {
          "staff_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "shift",
              "dayworker",
              "standby_crew",
              "standby_day",
              "lean"
            ],
            "default": "shift"
          },
          "crew": {
            "type": [
              "integer",
              "null"
            ]
          },
          "lean_pattern_key": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "MinHeadcount": {
        "type": "object",
        "properties": {
          "safety_total": {
            "type": "integer",
            "default": 0
          },
          "per_role": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          }
        }
      },
      "BuildResponse": {
        "type": "object",
        "properties": {
          "engine_version": {
            "type": "string"
          },
          "schedule": {
            "$ref": "#/components/schemas/Schedule"
          },
          "compliance": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Compliance"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "Schedule": {
        "type": "object",
        "properties": {
          "site_id": {
            "type": "string"
          },
          "year": {
            "type": "integer"
          },
          "month": {
            "type": "integer"
          },
          "days": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "rows": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "coverage": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DayCoverage"
            }
          },
          "statistics": {
            "description": "Per-person month summary (staff_id → stats) from the overridden schedule: engine-authoritative worked hours, night count, worked-shift count, and day-off code counts (vacation/flex/holiday-rest).",
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/PersonStats"
            }
          }
        }
      },
      "BuildRangeResponse": {
        "type": "object",
        "properties": {
          "engine_version": {
            "type": "string"
          },
          "schedule": {
            "$ref": "#/components/schemas/RangeSchedule"
          },
          "compliance": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Compliance"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "RangeSchedule": {
        "type": "object",
        "description": "Same shape as Schedule but anchored by start/end instead of year/month; statistics aggregate over the whole span (expected_hours/balance are the frame contribution across the range).",
        "properties": {
          "site_id": {
            "type": "string"
          },
          "start": {
            "type": "string",
            "format": "date"
          },
          "end": {
            "type": "string",
            "format": "date"
          },
          "days": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date"
            }
          },
          "rows": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "coverage": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DayCoverage"
            }
          },
          "statistics": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/PersonStats"
            }
          }
        }
      },
      "PersonStats": {
        "type": "object",
        "properties": {
          "worked_hours": {
            "type": "integer"
          },
          "night_count": {
            "type": "integer"
          },
          "shift_count": {
            "type": "integer"
          },
          "vacation_days": {
            "type": "integer"
          },
          "flex_days": {
            "type": "integer",
            "description": "CS — csúszó (working-time-frame flex day off) count."
          },
          "holiday_rest_days": {
            "type": "integer",
            "description": "Üp — ünnepi pihenő (public-holiday rest) count."
          },
          "expected_hours": {
            "type": "integer"
          },
          "balance": {
            "type": "integer"
          }
        }
      },
      "DayCoverage": {
        "type": "object",
        "properties": {
          "day": {
            "type": "string",
            "format": "date"
          },
          "morning": {
            "type": "integer"
          },
          "afternoon": {
            "type": "integer"
          },
          "night": {
            "type": "integer"
          },
          "dayworker": {
            "type": "integer"
          },
          "understaffed": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Compliance": {
        "type": "object",
        "properties": {
          "rulebook_version": {
            "type": "string"
          },
          "violations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Violation"
            }
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "any_needs_review": {
            "type": "boolean"
          }
        }
      },
      "Violation": {
        "type": "object",
        "properties": {
          "staff_id": {
            "type": "string"
          },
          "rule_id": {
            "type": "string"
          },
          "rule_type": {
            "type": "string",
            "enum": [
              "daily_rest",
              "max_daily_hours",
              "max_weekly_hours",
              "night_work",
              "weekly_rest",
              "work_time_frame"
            ]
          },
          "day": {
            "type": "string",
            "format": "date"
          },
          "measured": {
            "type": "number"
          },
          "limit": {
            "type": "number"
          },
          "statute_section": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "severity": {
            "type": "string"
          },
          "needs_review": {
            "type": "boolean"
          }
        }
      },
      "Pattern": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "hours": {
            "type": "integer"
          },
          "cycle_length": {
            "type": "integer"
          }
        }
      },
      "Holiday": {
        "type": "object",
        "properties": {
          "day": {
            "type": "string",
            "format": "date"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "Rule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "rule_type": {
            "type": "string",
            "enum": [
              "daily_rest",
              "max_daily_hours",
              "max_weekly_hours",
              "night_work",
              "weekly_rest",
              "work_time_frame"
            ]
          },
          "enabled": {
            "type": "boolean"
          },
          "params": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          },
          "statute_section": {
            "type": "string"
          },
          "message_hu": {
            "type": "string"
          },
          "severity": {
            "type": "string"
          },
          "needs_review": {
            "type": "boolean"
          }
        }
      },
      "VersionResponse": {
        "type": "object",
        "properties": {
          "engine_version": {
            "type": "string"
          },
          "pattern_library_version": {
            "type": "string"
          },
          "rulebook_version": {
            "type": "string"
          },
          "shift_times_version": {
            "type": "string"
          }
        }
      }
    }
  }
}