Recherche de site Web

Simplifiez votre planification complexe avec timeboard, une bibliothèque Python


par Maxim Mamaev

timeboard est une bibliothèque Python qui crée des horaires de périodes de travail et effectue des calculs de calendrier sur celles-ci. Vous pouvez créer des calendriers de jours ouvrables standard ainsi qu'une variété d'autres calendriers, simples ou complexes.

Vous pouvez trouver la documentation ici.

Consultez le dépôt GitHub ici.

Trouvez-le sur PyPI ici.

L'histoire

Cela a commencé avec l’affaire des effectifs. Notre entreprise a introduit des KPI impliquant le chiffre d'affaires par employé, nous avions donc besoin de connaître l'effectif annuel moyen de chaque équipe. J'avais déjà écrit des scripts Python, donc je n'étais pas intimidé.

Pour obtenir un effectif, j'ai dû calculer le nombre de jours ouvrables que chaque employé a passé dans l'entreprise au cours de l'année. Les pandas s'en occuperaient en une seconde, pensais-je. Mais il s’est avéré que les Pandas ne le pouvaient pas.

Le calendrier des affaires russes est chargé. Ils échangent les jours de la semaine avec le samedi ou le dimanche pour combler les écarts entre les jours fériés et les week-ends. Par exemple, il faut venir travailler un samedi de février pour être remboursé d'un lundi gratuit précédant un mardi férié quelque part en mai.

Le dispositif de chaque année est unique. Le calendrier des jours ouvrables de Pandas ne prend en charge que les modifications unidirectionnelles pour les observations de jours fériés. Ainsi, je pourrais transformer un jour ouvrable en jour de congé, mais pas l'inverse.

Puis il y a eu des opérateurs dans le centre d’appels, et mon anxiété a basculé dans l’autre sens. Ils travaillent par équipes de durée variable, et une équipe d'arrivée suivie de trois équipes de sortie. Pour obtenir les statistiques du centre d’appels, je n’avais pas besoin du calendrier des jours ouvrables. Pourtant, j’ai dû compter le nombre de quarts de travail d’un opérateur particulier au cours d’une période donnée.

Et enfin, un problème décalé. Chez mon concessionnaire Honda local, les mécaniciens travaillent selon des horaires hebdomadaires alternés : lundi, mardi, samedi et dimanche cette semaine, et du mercredi au vendredi la semaine suivante. Je voulais toujours être servi par un mécanicien en particulier, car l'autre avait une fois raté les freins. Je voulais un moyen simple de déterminer le prochain quart de travail de « mon » mécanicien.

Ces cas ont un fondement commun. Leurs solutions s'appuieraient sur un calendrier de périodes de « service » et de « repos ». Nous devrions être capables de construire des calendriers structurés de différentes manières, adaptés à différentes analyses de rentabilisation. Les requêtes et calculs exécutés sur le planning doivent distinguer les périodes de « service » et de « repos ».

Je n'ai pas trouvé de package Python fournissant les moyens de créer et d'interroger de tels horaires. En fait, j’ai eu du temps libre pour l’écrire moi-même.

Le concept

timeboard est une bibliothèque Python qui crée des horaires de périodes de travail et effectue des calculs de calendrier sur celles-ci. Ces objets eux-mêmes sont appelés tableaux de temps.

Il y a trois étapes principales dans le raisonnement sur un timeboard.

Vous commencez avec un intervalle de temps qui fixe les limites de votre calendrier. Tout se limitera à cet intervalle. C’est ce qu’on appelle le cadre (de référence). Le cadre se compose d'unités de base. Une unité de base est la plus petite période de temps dont vous avez besoin pour évaluer votre calendrier. Par exemple, si vous raisonnez en jours ouvrés, alors l’unité de base est le jour. Alternativement, si vous créez un horaire de travail de plusieurs heures, l'unité de base est d'une heure.

À l'étape suivante, vous définissez les règles de balisage du cadre en quarts de travail. Les quarts de travail sont des périodes de temps qui vous tiennent à cœur. Ils composent votre calendrier. Ce sont des quarts de travail que vous souhaitez planifier ou compter. Dans un calendrier de jours ouvrables standard, le quart de travail est un jour (et l'unité de base est également un jour, donc ils coïncident).

Dans un centre d'appels, un quart de travail est une période de plusieurs heures pendant laquelle une équipe particulière d'opérateurs est en service. L'unité de base durera probablement une heure et chaque quart de travail comprend un nombre (probablement variable) d'unités de base.

La séquence de quarts de travail remplissant le cadre est appelée la chronologie.

Enfin, vous créez un ou plusieurs plannings. Un calendrier est comme un pochoir posé sur la chronologie. Son objectif est de distinguer les quarts de travail en service de ceux hors service.

Un horaire a besoin de quelque chose avec lequel travailler afin de déclarer un quart de travail en service ou hors service. C'est pourquoi vous fournissez une étiquette pour chaque quart de travail, ou plutôt une règle pour les étiqueter pendant que le cadre est marqué dans la chronologie. Chaque planning définit une fonction de sélection qui inspecte l'étiquette du quart de travail et renvoie True pour les quarts de travail en service et False dans le cas contraire. Sauf si vous la remplacez, une timeline est accompagnée du planning par défaut dont le sélecteur renvoie la valeur booléenne du label.

Parfois, vous souhaitez définir plusieurs plannings pour une même timeline. Par exemple, dans un centre d'appels, il y aura le planning pour le centre d'appels dans son ensemble, et un planning distinct pour chaque équipe d'opérateurs. Le même quart de travail peut être trouvé en service sous certains horaires et en repos sous d'autres.

Timeboard=chronologie + horaires. Plus précisément, timeboard est un ensemble d'horaires de travail basés sur une chronologie spécifique de quarts de travail construite sur une référence <cadre.

Une fois que vous disposez d'un tableau du temps, vous pouvez effectuer le travail utile : faire des calculs de calendrier afin de résoudre les problèmes comme ceux décrits dans le prologue.

Chaque calcul effectué avec timeboard est conscient du devoir. La méthode invoquée « voit » uniquement les quarts de travail avec la tâche spécifiée et ignore les autres. Afin de révéler le devoir des quarts de travail, la méthode doit être dotée d'un horaire. Par conséquent, chaque calcul sur le tableau des temps est paramétré avec une tâche et un horaire.

Par défaut, le service est « activé » et le planning est le planning par défaut du timeboard. Par exemple, si vous appelez count() sans arguments sur un intervalle d'un tableau de temps, vous obtiendrez le nombre d'équipes de travail dans l'intervalle qui sont déclarées en service selon l'horaire par défaut. Ces valeurs par défaut facilitent la vie car, dans la pratique, vous souhaiterez vous occuper principalement des quarts de travail en service.

L'API

La documentation complète du timeboard est disponible sur Read the Docs.

Le package peut être installé avec le pip install timeboard habituel.

Mettre en place un timeboard

Le moyen le plus simple de commencer consiste à utiliser un calendrier préconfiguré fourni avec le package. Prenons un calendrier de jours ouvrables habituels pour les États-Unis.

 >>> import timeboard.calendars.US as US >>> clnd = US.Weekly8x5()

L'objet clnd est un timeboard (une instance de la classe timeboard.Timeboard). Il n'a qu'un seul horaire par défaut qui sélectionne les jours de la semaine comme quarts de travail tandis que les week-ends, ainsi que les observations des jours fériés fédéraux américains, sont déclarés hors service.

Les outils permettant de créer votre propre timeboard seront brièvement examinés plus tard, après avoir examiné ce que vous pouvez faire avec un timeboard.

Jouez avec les horaires de travail

L’appel d’une instance de timeboard clnd() avec un seul point dans le temps récupère le quart de travail qui contient ce point. Comment avez-vous un quart de travail, vous pouvez interroger son devoir :

Une certaine date est-elle un jour ouvrable ?

>>> ws = clnd('27 May 2017')>>> ws.is_on_duty()False

En effet, c'était un samedi.

Vous pouvez également regarder vers le futur ou le passé à partir du quart de travail actuel :

Quel était le jour ouvrable suivant ?

>>> ws.rollforward()Workshift(6359) of 'D' at 2017–05–30

L'équipe de travail renvoyée porte le numéro d'ordre 6359 et représente le jour du 30 mai 2017, qui était d'ailleurs le mardi suivant le jour férié du Memorial Day.

Si nous devions terminer le projet dans un délai de 22 jours ouvrables à compter du 1er mai 2017, quelle serait notre date limite ?

>>> clnd('01 May 2017') + 22Workshift(6361) of 'D' at 2017–06–01

C'est la même chose que :

>>> clnd('01 May 2017').rollforward(22)Workshift(6361) of 'D' at 2017–06–01

Jouez avec les intervalles

L'appel de clnd() avec un ensemble de paramètres différent produit un objet représentant un intervalle sur le calendrier. L'intervalle ci-dessous contient tous les quarts de travail du mois de mai 2017 :

>>> may2017 = clnd('May 2017', period='M')

Combien de jours ouvrables y avait-il en mai ?

>>> may2017.count()22

Combien de jours de congé ?

>>> may2017.count(duty='off')9

Combien d'heures de travail ?

>>> may2017.worktime()176

Un employé a fait partie du personnel du 3 avril 2017 au 15 mai 2017. Quelle part du salaire d'avril l'entreprise lui devait-elle ?

Notez que l'appel de clnd() avec un tuple de deux points dans le temps produit un intervalle contenant tous les quarts de travail entre ces points, inclusivement.

>>> time_in_company = clnd(('03 Apr 2017','15 May 2017'))>>> time_in_company.what_portion_of(clnd('Apr 2017', period='M'))1.0

En effet, les 1er et 2 avril 2017 tombaient un week-end, donc, ayant commencé le 3, le salarié cochait tous les jours ouvrables du mois.

Et quelle portion de celle de mai ?

>>> time_in_company.what_portion_of(may2017)0.5

Combien de jours l'employé a-t-il travaillé en mai ?

L'opérateur de multiplication renvoie l'intersection de deux intervalles.

>>> (time_in_company * may2017).count()11

Combien d'heures ?

>>> (time_in_company * may2017).worktime()88

Un employé faisait partie de l'équipe du 1er janvier 2016 au 15 juillet 2017. Combien d'années cette personne a-t-elle travaillé pour l'entreprise ?

>>> clnd(('01 Jan 2016', '15 Jul 2017')).count_periods('A')1.5421686746987953

Créez votre propre tableau de temps

Aux fins d’introduction, je me contenterai de me plonger dans deux exemples. Si cela vous semble trop raide, veuillez consulter la discussion approfondie sur les outils de construction dans la documentation du projet.

L'instruction d'importation pour cette section :

>>> import timeboard as tb

Permettez-moi de revenir sur un horaire de travail chez le concessionnaire automobile dont j'ai parlé dans le prologue. Un mécanicien travaille le lundi, mardi, samedi et dimanche cette semaine, et le mercredi, jeudi et vendredi la semaine prochaine ; puis le cycle bihebdomadaire se répète. Le timeboard est créé par le code suivant :

>>> biweekly = tb.Organizer(marker='W',...     structure=[[1,1,0,0,0,1,1], [0,0,1,1,1,0,0]])>>> clnd = tb.Timeboard(base_unit_freq='D', ...     start='01 Oct 2017', end='31 Dec 2018', ...     layout=biweekly)

Il est logique d’examiner d’abord la dernière déclaration. Il crée un timeboard nommé clnd. Les trois premiers paramètres définissent le cadre comme étant une séquence de jours (« D ») du 1er octobre 2017 au 31 décembre 2018. Le paramètre layout indique comment organiser le cadre. dans la chronologie des quarts de travail. Ce travail est confié à un Organisateur nommé bihebdomadaire.

La première instruction crée cet Organizer qui prend deux paramètres : marker et structure. Nous utilisons unmarqueur pour placer des marques sur le cadre. Les marques sont des sortes de jalons qui divisent le cadre en sous-cadres, ou « travées ». Dans l'exemple, marker='W' met une marque au début de chaque semaine civile. Chaque période représente donc une semaine.

Le paramètre structure indique comment créer des quarts de travail au sein de chaque période. Le premier élément de la structure, la liste [1,1,0,0,0,1,1], est appliqué au premier span (c'est-à-dire à la première semaine de notre calendrier). Chaque unité de base (c'est-à-dire chaque jour) au cours de la période devient un quart de travail. Les équipes de travail reçoivent les étiquettes de la liste, dans l'ordre.

Le deuxième élément de la structure, la liste [0,0,1,1,1,0,0], est appliqué de manière analogue à la deuxième travée (la deuxième semaine) . Après cela, comme nous n'avons plus d'éléments, une structure est rejouée par cycles. Ainsi, la troisième semaine est desservie par le premier élément de structure, la quatrième semaine par le deuxième, et ainsi de suite.

En conséquence, notre chronologie devient la séquence de jours étiquetés avec le chiffre 1 lorsque le mécanicien est de service et avec le chiffre 0 lorsqu'il ne l'est pas. Nous n'avons précisé aucun planning, car le planning construit par défaut nous convient très bien. Le planning par défaut prend en compte la valeur booléenne de l'étiquette, donc 1 se traduit par « en service » et zéro par « hors service ».

Avec ce timeboard, nous pouvons effectuer tout type de calculs que nous avons effectués précédemment avec le calendrier professionnel. Par exemple, si une personne était employée selon cet horaire à partir du 4 novembre 2017 et que son salaire est versé mensuellement, quelle part du salaire de novembre l'employé a-t-il gagné ?

>>> time_in_company = clnd(('4 Nov 2017', None))>>> nov2017 = clnd('Nov 2017', period='M')>>> time_in_company.what_portion_of(nov2017)0.8125

Dans le deuxième exemple, nous allons créer un timeboard pour un centre d'appels. Le centre d'appels fonctionne 24 heures sur 24, par équipes de durée variable : de 08h00 à 18h00 (10 heures), de 18h00 à 02h00 (8 heures) et de 02h00 à 08h00 (6 heures). ). L’horaire d’un opérateur comprend un quart de travail suivi de trois quarts de repos. Quatre équipes d'opérateurs sont donc nécessaires. Ils sont désignés par « A », « B », « C » et « D ».

>>> day_parts = tb.Marker(each='D', ...     at=[{'hours':2}, {'hours':8}, {'hours':18}])>>> shifts = tb.Organizer(marker=day_parts, ...     structure=['A', 'B', 'C', 'D'])>>> clnd = tb.Timeboard(base_unit_freq='H', ...     start='01 Jan 2009 02:00', end='01 Jan 2019 01:59',...     layout=shifts)>>> clnd.add_schedule(name='team_A', ...    selector=lambda label: label=='A')

Il existe quatre différences clés par rapport au cas du concessionnaire. Nous les examinerons un par un.

Premièrement, l'unité de base du cadre est désormais une période d'une heure (base_unit_freq='H') au lieu d'une période d'un jour du calendrier du concessionnaire.

Deuxièmement, la valeur du paramètre marker de l'Organisateur est désormais un objet complexe au lieu d'une fréquence de calendrier unique comme c'était le cas auparavant. Cet objet est une instance de la classe Marker. Il est utilisé pour définir des règles de placement de repères sur le cadre lorsque la simple division du cadre en unités calendaires uniformes n'est pas suffisante. La signature du marqueur ci-dessus est presque lisible : elle dit : placez une marque sur chaque jour ("D") à 02h00, 08h00 et 18h00.

Troisièmement, la valeur de la structure est désormais plus simple : il s'agit d'une liste à un seul niveau des étiquettes des équipes. Lorsqu'un élément de la structure n'est pas un itérable d'étiquettes mais juste une étiquette, son application à une étendue produit un seul quart de travail qui, littéralement, s'étend sur l'étendue.

Dans notre exemple, la toute première travée comprend six unités de base d'une heure commençant à 2, 3, 4… 7 heures du matin du 1er janvier 2009. Toutes ces unités de base sont regroupées dans un seul quart de travail portant l'étiquette « A ». . La deuxième travée comprend dix unités de base d’une heure commençant à 8, 9, 10… 17 heures. Ces unités de base sont regroupées en un seul poste de travail portant l’étiquette « B », et ainsi de suite. Lorsque toutes les étiquettes ont été prises, la structure est rejouée, de sorte que la cinquième tranche (08:00:00-17:59:59 le 1er janvier 2009) devient un quart de travail avec l'étiquette « A ».

Pour récapituler, si un élément de structure est une liste d'étiquettes, chaque unité de base du span devient un workshift et reçoit une étiquette de la liste. Si un élément de structure est une étiquette unique, toutes les unités de base de la travée sont combinées pour former un seul quart de travail qui reçoit cette étiquette.

Et enfin, nous avons explicitement créé un planning pour l'équipe A. Le planning par défaut ne répond pas à notre objectif puisqu'il revient « toujours en service ». Cela est vrai pour le centre d’appels dans son ensemble, mais pas pour une équipe en particulier. Pour le nouvel horaire, nous fournissons le nom et la fonction de sélection qui renvoie True pour tous les quarts de travail étiquetés par « A ». Pour une utilisation pratique, vous souhaiterez également créer les horaires des autres équipes.

Ce timeboard est aussi agréable à utiliser qu'un autre. Cependant, cette fois, nous devrons préciser explicitement le planning que nous souhaitons utiliser.

>>> schedule_A = clnd.schedules['team_A']

À combien d'équipes les opérateurs de l'équipe A ont-ils travaillé en novembre 2017 ?

>>> nov2017 = clnd('Nov 2017', period='M', schedule=schedule_A)>>> nov2017.count()22

Et combien d'heures y a-t-il eu au total ?

>>> nov2017.worktime()176

Une personne était employée comme opérateur dans l'équipe A à partir du 4 novembre 2017. Le salaire est payé mensuellement. Quelle part du salaire de novembre l'employé a-t-il gagné ?

>>> time_in_company = clnd(('4 Nov 2017',None), schedule=schedule_A)>>> time_in_company.what_portion_of(nov2017)0.9090909090909091

Plus de cas d'utilisation

Vous pouvez trouver plus de cas d'utilisation (tirés presque de la vie réelle) dans le notebook jupyter qui fait partie de la documentation du projet.

N'hésitez pas à utiliser timeboard et n'hésitez pas à laisser des commentaires ou à ouvrir des problèmes sur GitHub .

Articles connexes