Diagram som viser vaktplanlegging

Hvilket problem løser dette?

Vaktplanlegging er ofte svært komplisert, og ulike bransjer har ulike innfallsvinkler. Likevel er det noen fellestrekk på tvers av bransjer, og denne algoritmen er i stand til å lage svært gode vaktplaner for mange bransjer. Dersom du har mer spesialiserte krav så gjerne ta kontakt.

Utgangspunktet er at vi har et sett med vakter som skal bemannes, og et sett med ansatte. De ansatte har preferanser for hvilke vakter de ønsker å jobbe.

Hvordan kan vi fordele vaktene slik at alle blir mest mulig fornøyde?

Vaktplanlegging er et mange-til-mange allokeringsproblem: én ansatt kan jobbe flere vakter og én vakt kan ha flere ansatte. Denne algoritmen balanserer tre målsetninger i prioritert rekkefølge:

  1. Dekke vaktbehovet: Sørge for at hver vakt har nok folk (f.eks. morgenvakten på mandager i butikk A skal ha 3 ansatte)
  2. Gi arbeidere nok timer: Sikre at alles stillingsprosent er innfridd (f.eks. skal Anna ha 4 * 4 = 16 vakter over 4 uker dersom hun jobber 80%)
  3. Maksimere trivsel: Ta hensyn til individuelle preferanser (Anna foretrekker morgenvakter, Per foretrekker kvelder og gjerne helger, osv)

Algoritmen søker gjennom milliarder av ulike vaktplaner før den gir et svar, og fungerer for hundrevis av ansatte på tvers av ulike avdelinger.

Bruksområder

Denne algoritmen egner seg til vaktplanlegging i mange bransjer og sektorer:

  • Sykehus og helsevesen: Fordel sykepleiere og leger på dag-, kvelds- og nattvakter
  • Butikk og servering: Planlegg bemanning i butikker, restauranter og kafeer
  • Industri og produksjon: Dekk skiftarbeid i fabrikker og produksjonsanlegg
  • Hotell og reiseliv: Fordel resepsjonister, renholdere og kokker på vakter
  • Frivillige organisasjoner: Koordiner vakter for festivaler, dugnader eller veldedighetsarbeid

Fordeler

  • Rettferdig fordeling: Algoritmen minimerer både gjennomsnittlig og maksimalt avvik fra ønsket bemanning
  • Tar hensyn til preferanser: Arbeidernes ønsker vektlegges, noe som gir høyere trivsel
  • Relativ rettferdighet: Hvis det er for få folk, fordeles mangelen proporsjonalt - en vakt som trenger 8 personer får dobbelt så mange som en som trenger 4

Viktig å vite

  • Maks én vakt per dag. Ingen arbeider settes opp på flere vakter samme dag. En antagelse er at for hver dag, så kan hver arbeider kun jobbe en vakt.
  • Preferanser må oppgis: Algoritmen kan bare ta hensyn til vakter arbeideren har sagt de kan jobbe. Manglende preferanser tolkes som at vakten ikke er aktuell.
  • Ikke strategisikker: En ansatt kan i prinsippet lyve om vaktene de ønsker og oppnå bedre resultater. For eksempel velge å ikke oppgi ubeleielige vakter heller enn å innrømme at det er ubeleielige, men likevel aktuelle.

Eksempler

Du kan teste Vaktplanlegging på eksempelene nedenfor i APIet helt gratis. Eksempelene er små for å vise struktur og hva som er mulig. Med betalt tilgang kan du sende inn store datasett. De fleste problemer løses på under ett sekund.

Eksempel 1

Et enkelt eksempel med to vakter og to arbeidere. Ola foretrekker morgenvakten (score 10), mens Kari foretrekker kveldsvakten (score 10). Algoritmen gir Ola morgenvakten og Kari kveldsvakten - begge får det de ønsker mest.

{"data": {
  "shifts": [
    {"id": "1", "department": "A", "date": "2025-12-20", "period": "morning", "desired_workers": 1},
    {"id": "2", "department": "A", "date": "2025-12-20", "period": "evening", "desired_workers": 1}
  ],
  "workers": [
    {"id": "Ola", "desired_shifts": 1},
    {"id": "Kari", "desired_shifts": 1}
  ],
  "preferences": [
    {"worker": "Ola", "shift": "1", "score": 10},
    {"worker": "Ola", "shift": "2", "score": 5},
    {"worker": "Kari", "shift": "1", "score": 5},
    {"worker": "Kari", "shift": "2", "score": 10}
  ]
}}

Resultat:

{"1": ["Ola"], "2": ["Kari"]}

Eksempel 2

Dette eksempelet viser relativ rettferdighet ved underbemanning. Vaktene ønsker 4 + 8 + 16 = 28 arbeidere, men bare 14 er tilgjengelige. Algoritmen fordeler mangelen proporsjonalt: vaktene får 2, 4 og 8 arbeidere. Alle vakter fylles til 50% - ingen vakt blir urettferdig nedprioritert.

{"data": {
  "shifts": [
    {"id": "A", "department": "A", "date": "2025-12-08", "period": "morning", "desired_workers": 4},
    {"id": "B", "department": "A", "date": "2025-12-08", "period": "evening", "desired_workers": 8},
    {"id": "C", "department": "A", "date": "2025-12-08", "period": "night", "desired_workers": 16}
  ],
  "workers": [
    {"id": "0", "desired_shifts": 1}, {"id": "1", "desired_shifts": 1},
    {"id": "2", "desired_shifts": 1}, {"id": "3", "desired_shifts": 1},
    {"id": "4", "desired_shifts": 1}, {"id": "5", "desired_shifts": 1},
    {"id": "6", "desired_shifts": 1}, {"id": "7", "desired_shifts": 1},
    {"id": "8", "desired_shifts": 1}, {"id": "9", "desired_shifts": 1}
  ],
  "preferences": [
    {"worker": "0", "shift": "A", "score": 1}, {"worker": "0", "shift": "B", "score": 1}, {"worker": "0", "shift": "C", "score": 1},
    {"worker": "1", "shift": "A", "score": 1}, {"worker": "1", "shift": "B", "score": 1}, {"worker": "1", "shift": "C", "score": 1},
    {"worker": "2", "shift": "A", "score": 1}, {"worker": "2", "shift": "B", "score": 1}, {"worker": "2", "shift": "C", "score": 1},
    {"worker": "3", "shift": "A", "score": 1}, {"worker": "3", "shift": "B", "score": 1}, {"worker": "3", "shift": "C", "score": 1},
    {"worker": "4", "shift": "A", "score": 1}, {"worker": "4", "shift": "B", "score": 1}, {"worker": "4", "shift": "C", "score": 1},
    {"worker": "5", "shift": "A", "score": 1}, {"worker": "5", "shift": "B", "score": 1}, {"worker": "5", "shift": "C", "score": 1},
    {"worker": "6", "shift": "A", "score": 1}, {"worker": "6", "shift": "B", "score": 1}, {"worker": "6", "shift": "C", "score": 1},
    {"worker": "7", "shift": "A", "score": 1}, {"worker": "7", "shift": "B", "score": 1}, {"worker": "7", "shift": "C", "score": 1},
    {"worker": "8", "shift": "A", "score": 1}, {"worker": "8", "shift": "B", "score": 1}, {"worker": "8", "shift": "C", "score": 1},
    {"worker": "9", "shift": "A", "score": 1}, {"worker": "9", "shift": "B", "score": 1}, {"worker": "9", "shift": "C", "score": 1}
  ]
}}

Resultat:

{"A": ["0", "7"], "B": ["3", "5"], "C": ["1", "2", "4", "6", "8", "9"]}