# ShiftPattern

## Description

ShiftPattern is a **reusable shift definition (シフトパターン)** — the "早番 / 遅番 / 日勤 / 夜勤 / 宿直 / 中抜け / 公休" symbols a planner picks from rather than re-entering times on every shift. It corresponds to キンタイミライ's *pattern-型シフト* (issue #11): a named template carrying a stable `code`, a display `name`, and a `kind`. Its working intervals live in an ordered `segments` array embedded directly on the pattern (ADR-022) — a single-part pattern (日勤 / 夜勤 …) has one segment, a 中抜け pattern (e.g. a restaurant lunch-then-dinner split) has two or more, and an `OFF` (公休/休み) pattern has zero segments (it represents the explicit absence of work, not a slot to fill). A `Shift` may instantiate a pattern (copying its segments onto a concrete date) or be entered ad-hoc without one.

The pattern is reference / master data, not a per-day plan, so it is **not** effective-dated — it is edited in place or superseded by a new code. Following the semantic-key discipline (ADR-015), selection and downstream labeling bind to the stable `code`, never the display `name`, so renaming "夜勤" does not change which shifts reference it (the #14 反面教師: never key logic to a display string).

A pattern's `code` is unique **within its scope** (`scopeId`), not tenant-wide: `scopeId` null designates a tenant-shared pattern (the common case), while a set `scopeId` (an organization `Site`) designates a pattern whose `code` is meaningful only within that site — the same `code` string may exist on two different sites with two different meanings (e.g. a rail operator's per-office duty-code vocabulary, where `"DM18"` means something different at each office). Uniqueness of `code` is therefore enforced on the pair `(scopeId, code)`.

## Domain Model Definitions

### Model type

Standard

### Command Definitions

- createShiftPattern — define a reusable pattern (早番 / 遅番 / 日勤 / 夜勤 / 宿直 / 中抜け) with one or more segments
- updateShiftPattern — adjust the kind, name, or segments (add / reorder / update / remove segments in the embedded array)
- deleteShiftPattern — remove a pattern no longer offered (rejected while Shifts reference it)

### Query Definitions

- getShiftPattern — retrieve a single pattern by id (segments included in response)
- getShiftPatternByCode — resolve a pattern by its stable `code`
- listShiftPatterns — paginated list of patterns

### Models

- ShiftPattern

**Embedded: `segments` array**

Each element in the `segments` array represents a single planned work interval within the pattern (formerly the independent `ShiftPatternSegment` entity, embedded per ADR-022):

| Field | Type | Description |
|---|---|---|
| `sequence` | int | 1-based order within the pattern |
| `startTime` | int | Minutes from midnight [0, 1440) |
| `endTime` | int | Minutes from midnight [0, 1440) |
| `breakMinutes` | int | Intra-segment rest (non-negative, < gross span) |
| `spansMidnight` | boolean | True when the segment crosses midnight (`endTime <= startTime`) |

### Invariants

- `code` is a stable key; pattern selection and labeling bind to `code`, never to the display `name` (ADR-015; #14 反面教師). Uniqueness is scoped: `(scopeId, code)` is unique, so the same `code` may be reused across different `scopeId`s (including the tenant-shared `scopeId = null` scope, which is itself one scope for uniqueness purposes)
- `scopeId` is optional; when set it references an organization `Site` and the pattern is meaningful only within that site; when null the pattern is shared tenant-wide
- `kind` uses normalized enum values — `DAY` (日勤) / `EARLY` (早番) / `LATE` (遅番) / `NIGHT` (夜勤) / `ON_CALL` (宿直) / `OFF` (公休/休み) (ADR-006); `kind` describes the day-part character and is orthogonal to segment count (a 中抜け pattern is, e.g., a `DAY` pattern with two segments)
- A pattern has **at least one** segment in the `segments` array, **except** an `OFF` pattern which has **zero** segments (it represents the absence of a work slot, not a slot to fill); a single-part pattern has one, a 中抜け pattern has two or more separated by a positive gap
- `segments[].sequence` is contiguous (1, 2, 3, …) and consistent with time order
- `segments[].startTime` and `endTime` are minutes from midnight in `[0, 1440)`
- `segments[].spansMidnight` is true exactly when the segment crosses midnight (`endTime <= startTime`); when false, `startTime < endTime`
- `segments[].breakMinutes` is a non-negative minute count strictly less than the segment's gross span; it is an intra-segment rest, distinct from the inter-segment 中抜け gap
- Consecutive segments leave a positive gap (the 中抜け interval)
- The pattern carries no date and no assignee — a `Shift` that instantiates it may override the copied times; the pattern itself is a template only

### Relationships

- **Referenced by Shift** (optional): `Shift.shiftPatternId` instantiates this pattern onto a concrete date, copying its `segments` into the Shift's own `segments`; a Shift may exist without a pattern
- **Reused across many Shifts**: one pattern is the template for many shifts across dates, sites, and assignments
- **Kind aligns with shiftType**: the pattern `kind` (e.g. `ON_CALL` 宿直) corresponds to the `Shift.shiftType` a planner sets, though a shift may deviate from its pattern
- **References Site** (organization, cross-module, optional): `scopeId` scopes the pattern's `code` to a single Site; null means tenant-shared
