Gestione dei turni di lavoro

Wed 15 September 2021

Nel tuo ufficio, una particolare mansione riveste un ruolo molto importante e pertanto richiede che ci sia sempre (24 ore su 24) qualcuno disponibile a risolvere eventuali criticità.

Il responsabile del personale ha stabilito di suddividere i dipendenti in 6 gruppi e, su base trimestrale, assegnare dei giorni di reperibilità a ciascun gruppo.

Un dipendente è stato incaricato di assegnare i giorni di reperibilità manualmente ai vari gruppi ma l'operazione si rivela molto noiosa e, data la compilazione manuale, si verificano molti errori.

Come potremmo risolvere il problema implementando il tutto su uno script Python?

Definizione del problema

La prima cosa da fare è capire quali sono gli input e gli output del nostro script.

Gli input del nostro programma sono:

  • intervallo di date del trimestre considerato (ad esempio 01/10/2021 - 31/12/2021)
  • numero di gruppi (nel nostro caso sono 6)
  • numero del gruppo dal quale cominceremo l'assegnazione dei turni (non per forza si partirà da 1)

Gli output attesi del nostro programma sono:

  • file json strutturato come segue
{
    '1': {
        '2021-10': [1, 7, 13, ...],
        '2021-11': [1, 7, 13, ...],
        '2021-12': [1, 7, 13, ...]
    },
    '2': {
        '2021-10': [1, 7, 13, ...],
        '2021-11': [1, 7, 13, ...],
        '2021-12': [1, 7, 13, ...]
    },
    ...
}

Il file JSON conterrà un oggetto che avrà delle proprietà, alle quali è associato il nome del gruppo, che conterranno la lista delle date di reperibilità associate al gruppo.

Prerequisiti

Per poter risolvere l'esercizio è opportuno saper gestire in Python:

  • argomenti di input
  • date
  • dizionari
  • file json
  • classi

Gestione argomenti di input

Il nostro script dovrà attendere dall'utente i seguenti argomenti:

  • begin_date
  • end_date
  • group_number

Per gestire gli argomenti utilizzeremo il modulo argparse come segue:

import argparse
parser = argparse.ArgumentParser()
parser.add_argument(
    "-b", 
    "--begin-date", 
    help="Interval begin date: format date as YYYY-MM-DD.", 
    required=True
)
parser.add_argument(
    "-e", 
    "--end-date", 
    help="Interval end date: format date as YYYY-MM-DD.", 
    required=True
)
parser.add_argument(
    "-n", 
    "--group-number", 
    help="Number of groups to assign", 
    required=True
)
parser.add_argument(
    "-s", 
    "--group-begin", 
    help="Number of group to assign on first day", 
    required=True
)
args = parser.parse_args()

I quattro argomenti che vogliamo ricevere come input sono obbligatori(required=True).

Possiamo a questo punto invocare il nostro script: invocandolo senza parametri avremo come output:

$ python turni_reperibilita.py
usage: turni_reperibilita.py [-h] -b BEGIN_DATE -e END_DATE -n GROUP_NUMBER -s GROUP_BEGIN
turni_reperibilita.py: error: the following arguments are required: -b/--begin-date, -e/--end-date, -n/--group-number, -s/--group-begin

Il precedente messaggio di errore indica l'obbligatorietà degli argomenti.

Richiamando lo script con gli opportuni argomenti:

$ python turni_reperibilita.py -b 2021-10-01 -e 2021-12-31 -n 6 -s 2y

non otterremo alcun messaggio di errore perché gli argomenti saranno opportunamente compilati.

Una volta ottenuti i corretti argomenti di input, provvederemo a convertirli nei tipi di dato che ci serviranno nel seguito del codice. Per convertirli eseguiremo il seguente codice:

start_date = datetime.datetime.strptime(args.begin_date, "%Y-%m-%d")
end_date = datetime.datetime.strptime(args.end_date, "%Y-%m-%d")
number_of_groups = int(args.group_number)
start_group = int(args.start_group)

Definizione della classe Turni

La gestione dell'assegnazione dei turni ai vari gruppi sarà implementata all'interno di una classe denominata Turni.

Il programma chiamante che consentirà di implementare quanto desiderato è il seguente:

turni = Turni(number_of_groups)
turni.load(start_date, end_date, start_group)
turni.to_json(

Dal precedente codice si evincono i seguenti metodi:

  • load: implementa la creazione della struttura dati che ospiterà il nostro output
  • to_json: scrive il risultato all'interno di un file json sul nostro filesystem

Metodo load

Nel metodo load provvederemo ad implementare la logica di assegnazione dei giorni ai vari gruppi di lavoro.

Il codice implementato da load è il seguente:

def load(self, start_date, end_date, start_group):
    while start_date <= end_date:
        if not self._has_date_key(start_group, start_date):
            self._add_date_key(start_group, start_date)
        self._add_day(start_group, start_date)
        start_date = self._get_next_date(start_date)
        start_group = self._get_next_group(start_group)

Il metodo prende come input la data di inizio(start_date), la data di fine intervallo(end_date), ed il numero del gruppo al quale associare il primo giorno dell'intervallo(start_group). Durante l'esecuzione sono ciclate tutte le date dell'intervallo e ogni data è associata ad uno ed un solo gruppo. Ogni data è inserita, all'interno del dizionario del gruppo, con la chiave formata dalla stringa %Y-%m. Le ultime due righe di codice prelevano i dati che sono utilizzati durante l'esecuzione del prossimo ciclo.

Il codice dei metodi privati richiamati da load è il seguente:

def _has_date_key(self, target_group, target_date):
    return self._get_date_key(target_date) in self.turni[target_group]

def _add_date_key(self, target_group, target_date):
    dict_data_key = {self._get_date_key(target_date): []}
    self.turni[target_group].update(dict_data_key)

def _add_day(self, target_group, target_date):
    days = self.turni[target_group][self._get_date_key(target_date)]
    days.append(target_date.day)
    self.turni[target_group].update({self._get_date_key(target_date): days})

def _get_date_key(self, target_date):
    return target_date.strftime("%Y-%m")

def _get_next_date(self, target_date):
    return target_date + self.delta

def _get_next_group(self, current_group):
    return (current_group % self.number_of_groups) + 1

Metodo to_json

Il metodo consente di scrivere la struttura dati implementata dal metodo load all'interno di un file json.

Il codice del metodo è il seguente:

def to_json(self, target_path='/tmp/turni.json'):
    fh = open(target_path, 'w')
    fh.write(json.dumps(self.turni))
    fh.close()

La segnatura del metodo ci indica un target_path che è valorizzato con un path di default (modificabile dall'utente).

Il codice scrive il contenuto del dizionario self.turni all'interno del json.

Conclusioni

In questo articolo hai visto come risolvere un problema di assegnazione di date all'interno di gruppi di lavoro.

Per vedere il codice completo consultare il nostro repository github alla seguente pagina.