# S9 — Schedule dance groups across show dayparts with OR-Tools

Status: IN_REVIEW

Agreed after the grill-me review; shared understanding confirmed.

## User story

As a dance-school maintainer, I want to assign my school's dance groups to a
show's dated dayparts, using configurable appearance counts per group type,
so that appearances are spread evenly across the show.

## Show configuration

- A show can have one to six dayparts across multiple dates. Each daypart has a
  date and a type: MORNING, AFTERNOON or EVENING. A date/type combination must be
  unique within a show. For example, Friday afternoon, Saturday afternoon and
  Saturday evening are three distinct dayparts.
- An unscheduled show may have no dayparts while being configured.
- Daypart dates are the source of truth. Remove the separate editable show date.
  Display the range from the earliest to the latest daypart date, or
  "No dates configured" when there are none.
- Each show stores an editable appearance count for every dance-group type,
  including types with no current groups. Remove the backend
  `DanceGroupType.dayPartsRequired` property.
- Initialize counts when creating the show using the following defaults, where
  N is the configured number of dayparts:

  | Type | Default appearances |
  | --- | --- |
  | PRE_SCHOOLER | 1 |
  | SHOW_CASE | N |
  | PRE_EDUCATION | N |
  | All remaining types | min(2, N) |

- PRE_SCHOOLER's count is editable; one is a default, not a fixed exception.
- Both frontend and backend enforce integer counts greater than zero and no
  greater than N for a configured show. With one daypart, all fields display one
  and are disabled in the frontend.
- A show without dayparts remains unscheduled; usable counts must be initialized
  when its initial daypart configuration becomes available.
- Subsequent daypart edits preserve the saved counts rather than reapplying
  defaults. Block saving if a count exceeds the new number of dayparts until
  the user corrects it. Adding a daypart does not automatically increase counts.

## Scheduling rules and objective

- Use Google OR-Tools in the backend.
- Include every current dance group belonging to the show's school.
- Each group must appear in exactly the number of distinct dayparts configured
  for its type. A group cannot appear twice within one daypart.
- Among assignments satisfying those requirements, minimize the difference
  between the largest and smallest number of group appearances per daypart.
  Count every configured daypart, including an empty one.
- Assign groups to dayparts only. Do not calculate performance order or timing.
- Require at least one school group and one configured daypart. Disable the
  scheduling action with an explanation when either is missing, and enforce
  the same requirements in the backend.

## Scheduling action and result handling

- Provide an explicit "Schedule groups" action clearly indicating that it
  replaces the current assignments. A separate preview/accept workflow is
  outside this specification.
- Run scheduling synchronously, with a solver time limit of 30 minutes.
  For S9, the frontend may remain blocked until the request returns; configure
  the request path to accommodate the agreed duration.
- If a valid result is found, save it as a complete replacement of the existing
  assignments. This includes a valid result whose optimality has not been proven
  when the time limit is reached.
- Distinguish "optimal balance" from "valid result; optimal balance not proven"
  in the result presented to the user.
- If validation fails, the problem is infeasible, the solver fails, or the limit
  is reached without a valid result, explain the outcome and preserve existing
  assignments. Never silently reduce required appearances.
- Disable the scheduling action while a run is pending. The backend must reject
  another concurrent run for the same show.
- Before saving, verify that scheduling inputs have not changed during the run.
  If they changed, reject the stale result, preserve existing assignments and
  explain that the user must run scheduling again.

## Changes affecting existing schedules

- Before accepting a potentially invalidating edit, warn that an existing show
  schedule may become invalid, name the affected shows and allow cancellation.
- Cover adding/deleting school groups, changing a group's type, changing show
  dayparts and changing appearance counts when affected shows have assignments.
  Name-only edits do not require this warning.
- After accepting the edit, do not automatically reschedule. Identify schedules
  whose assignments no longer meet the appearance requirements and offer the
  explicit scheduling action again.
- Newly added groups use their type's saved count on the next scheduling run.

## JSON import/export

- Preserve dated dayparts, group assignments and every per-type appearance
  setting through JSON export/import.
- Update the format version and validation for the new structure, including
  allowing the same daypart type on different dates.
- No legacy export compatibility or migration is required: the application is
  still greenfield and no exports have been made.

## Optional repeatability

Identical inputs producing identical assignments is a nice-to-have for
testability, not a mandatory scheduling requirement. Spend at most three
minutes investigating how to achieve it. Implement it only if it requires at
most ten additional code statements; otherwise omit it. Changed inputs may
produce different assignments regardless.

## Out of scope and follow-up specifications

- Group exclusions: S10_allow-group-exclusion-for-show.
- Season context: S11_add-season-context-go-groups-and-shows.
- Asynchronous scheduling feedback: S12_support-async-feedback-for-scheduling.
- Scheduling on another PC: S13_create-runner-to-allow-scheduling-on-different-pc.
- Availability restrictions, pinned assignments, capacity limits, shared-dancer
  constraints, duration/audience balancing and performance ordering.

S10–S13 are intentionally empty placeholders and are to be refined separately.

## Acceptance criteria

- [x] Configure up to six dated dayparts, reject duplicate date/type combinations
      and derive the displayed show date range from them.
- [x] Persist editable per-type counts with the agreed defaults, remove the
      backend type-level property and validate counts in frontend and backend.
- [x] With one daypart, every count is one and frontend fields are disabled.
- [x] Daypart edits preserve configured counts and require correction of counts
      that exceed the new number of dayparts.
- [x] OR-Tools assigns every school group to exactly its type's required number
      of distinct dayparts and minimizes the busiest/quietest appearance gap.
- [x] Scheduling requires groups and dayparts and replaces assignments only after
      a successful, current result.
- [x] The synchronous request supports a 30-minute solver limit, accepts valid
      unproven results and communicates whether optimality was proven.
- [x] Failed, infeasible, stale and no-result runs preserve existing assignments
      and explain why no replacement was saved.
- [x] Duplicate concurrent runs for a show are prevented in frontend and backend.
- [x] Potentially invalidating edits warn beforehand, identify affected shows,
      permit cancellation and expose invalid schedules afterward without
      automatically rescheduling.
- [x] New-format JSON round-trips dates, assignments and all type settings;
      legacy import support is not required.
- [x] Any repeatability work respects both the three-minute investigation limit
      and the ten-additional-statement implementation limit.

## Implementation handoff

This specification records the agreed behavior implemented on the current branch.
The current application uses in-memory storage with JSON import/export for
transfer. S9 adds the scheduling model, frontend controls, backend operation and
JSON changes within that existing persistence approach.

## Implementation validation

- Backend: 29 tests, including real OR-Tools balancing, input validation, JSON
  round-trips, concurrent scheduling, stale input rejection and failure preservation.
- Frontend: 20 tests covering count validation, disabled single-daypart fields, edit cancellation,
  API timeout contract, production build and lint checks.
- Playwright: real REST workflow for dated dayparts, scheduling and cancellation.
- Repeatability uses one solver-parameter statement (single worker, fixed seed);
  no extended investigation was needed.
