Vereinfachen Sie Ihre komplexe Planung mit timeboard, einer Python-Bibliothek


von Maxim Mamaev

timeboard ist eine Python-Bibliothek, die Zeitpläne für Arbeitsperioden erstellt und Kalenderberechnungen darüber durchführt. Sie können sowohl Standardkalender für Geschäftstage als auch eine Vielzahl anderer einfacher oder komplexer Zeitpläne erstellen.

Die Dokumentation finden Sie hier.

Schauen Sie sich hier das GitHub-Repo an.

Hier finden Sie es auf PyPI.

Die Geschichte

Es begann mit dem Fall der Personalzählung. Da unser Unternehmen KPIs zum Umsatz pro Mitarbeiter eingeführt hat, mussten wir die durchschnittliche jährliche Mitarbeiterzahl jedes Teams kennen. Da ich bereits Python-Skripte geschrieben hatte, ließ ich mich nicht einschüchtern.

Um die Mitarbeiterzahl zu ermitteln, musste ich die Anzahl der Arbeitstage berechnen, die jeder Mitarbeiter innerhalb des Jahres im Unternehmen verbracht hat. Pandas würden es in einer Sekunde schaffen, dachte ich. Aber es stellte sich heraus, dass Pandas das nicht konnten.

Der russische Geschäftskalender ist umständlich. Sie tauschen Wochentage mit Samstagen oder Sonntagen aus, um Lücken zwischen Feiertagen und Wochenenden zu schließen. Beispielsweise müssen Sie an einem Samstag im Februar zur Arbeit kommen, um eine Rückerstattung für einen kostenlosen Montag vor einem Feiertagsdienstag irgendwo im Mai zu erhalten.

Das Schema ist für jedes Jahr einzigartig. Der Geschäftstagskalender von Pandas unterstützt nur einseitige Änderungen für Feiertagsbeobachtungen. Ich könnte also einen Arbeitstag in einen freien Tag verwandeln, aber nicht umgekehrt.

Dann gab es Telefonisten im Callcenter, und meine Angst drehte sich in die andere Richtung. Sie arbeiten in Schichten unterschiedlicher Länge, mit einer Einschicht, gefolgt von drei Auswärtsschichten. Um die Callcenter-Statistiken zu erhalten, brauchte ich den Geschäftstagskalender nicht. Dennoch musste ich die Anzahl der Schichten eines bestimmten Bedieners in einem bestimmten Zeitraum zählen.

Und schließlich ein ungewöhnliches Problem. Bei meinem örtlichen Honda-Händler arbeiten die Mechaniker nach wechselnden Wochenplänen: Montag, Dienstag, Samstag und Sonntag diese Woche und Mittwoch bis Freitag in der nächsten Woche. Ich wollte immer von einem bestimmten Mechaniker bedient werden, weil der andere mal die Bremsen kaputt gemacht hatte. Ich wollte eine einfache Möglichkeit, die nächste Schicht „meines“ Mechanikers zu bestimmen.

Diese Fälle haben eine gemeinsame Grundlage. Ihre Lösungen würden auf einem Zeitplan mit „Dienst“- und „Dienstfreien“ Zeiträumen basieren. Wir sollten in der Lage sein, unterschiedlich strukturierte Zeitpläne zu erstellen, die für unterschiedliche Geschäftsfälle geeignet sind. Abfragen und Berechnungen über den Zeitplan müssen zwischen „Dienst“- und „Dienstfreien“ Zeiten unterscheiden.

Ich konnte kein Python-Paket finden, das die Möglichkeit bietet, solche Zeitpläne zu erstellen und abzufragen. Zufälligerweise hatte ich etwas Freizeit, um es selbst zu schreiben.

Das Konzept

timeboard ist eine Python-Bibliothek, die Zeitpläne für Arbeitsperioden erstellt und Kalenderberechnungen darüber durchführt. Diese Objekte selbst werden Timeboards genannt.

Die Überlegungen zu einem Zeitplan umfassen drei Hauptschritte.

Sie beginnen mit einem Zeitintervall, das die Grenzen Ihres Kalenders festlegt. Alles wird auf dieses Intervall beschränkt sein. Es wird als (Referenz-)Frame bezeichnet. Der Rahmen besteht aus Basiseinheiten. Eine Basiseinheit ist die kleinste Zeitspanne, die Sie zur Messung Ihres Kalenders benötigen. Wenn Sie beispielsweise in Geschäftstagen argumentieren, ist die Basiseinheit ein Tag. Wenn Sie alternativ einen Zeitplan mit mehrstündigen Schichten erstellen, ist die Basiseinheit eine Stunde.

Im nächsten Schritt definieren Sie die Regeln für die Einteilung des Rahmens in Arbeitsschichten. Arbeitsschichten sind Zeiträume, die Ihnen wichtig sind. Sie bilden Ihren Kalender. Es sind Arbeitsschichten, die Sie einplanen oder zählen möchten. In einem Standard-Geschäftstageskalender ist die Arbeitsschicht ein Tag (und die Basiseinheit ist ebenfalls ein Tag, sie stimmen also überein).

In einem Callcenter ist eine Arbeitsschicht ein Zeitraum von mehreren Stunden, in dem eine bestimmte Schicht von Mitarbeitern im Dienst ist. Die Basiseinheit beträgt voraussichtlich eine Stunde und jede Arbeitsschicht umfasst eine (wahrscheinlich unterschiedliche) Anzahl von Basiseinheiten.

Die den Rahmen ausfüllende Abfolge der Arbeitsschichten wird als Zeitleiste bezeichnet.

Abschließend erstellen Sie einen oder mehrere Zeitpläne. Ein Zeitplan ist wie eine Schablone, die über den Zeitplan gelegt wird. Sein Zweck besteht darin, diensthabende Arbeitsschichten von dienstfreien Schichten zu unterscheiden.

Ein Zeitplan benötigt etwas, mit dem man arbeiten kann, um eine Arbeitsschicht als dienstbereit oder dienstfrei zu erklären. Aus diesem Grund geben Sie für jede Arbeitsschicht eine Beschriftung bzw. eine Regel für die Beschriftung an, während das Bild in der Zeitleiste markiert wird. Jeder Zeitplan definiert eine Auswahlfunktion, die die Bezeichnung der Arbeitsschicht überprüft und für die Arbeitsschichten im Dienst „True“ und andernfalls „False“ zurückgibt. Sofern Sie es nicht überschreiben, wird eine Zeitleiste von dem Standardzeitplan begleitet, dessen Selektor den booleschen Wert der Bezeichnung zurückgibt.

Manchmal möchten Sie mehrere Zeitpläne für dieselbe Zeitleiste definieren. In einem Callcenter gibt es beispielsweise einen Zeitplan für das gesamte Callcenter und einen separaten Zeitplan für jedes Team von Operatoren. Die gleiche Arbeitsschicht kann bei einigen Dienstplänen im Dienst und bei anderen außerhalb des Dienstes stattfinden.

Timeboard=Zeitleiste + Zeitpläne. Genauer gesagt handelt es sich bei Timeboard um eine Sammlung von Arbeitsplänen, die auf einer bestimmten Zeitleiste von Arbeitsschichten basieren, die auf einer Referenz basiertRahmen.

Sobald Sie einen Zeitplan erstellt haben, können Sie die nützliche Arbeit ausführen: Kalenderberechnungen durchführen, um die im Prolog beschriebenen Probleme zu lösen.

Jede mit Timeboard durchgeführte Berechnung erfolgt pflichtbewusst. Die aufgerufene Methode „sieht“ nur Arbeitsschichten mit der angegebenen Aufgabe und ignoriert die anderen. Um die Aufgaben der Arbeitsschichten aufzuzeigen, muss der Methode ein Zeitplan gegeben werden. Daher ist jede Berechnung auf der Zeittafel mit einer Pflicht und einem Zeitplan parametrisiert.

Standardmäßig ist der Dienst „ein“ und der Zeitplan ist der Standardplan der Zeitleiste. Wenn Sie beispielsweise count() ohne Argumente für ein bestimmtes Intervall einer Zeittafel aufrufen, erhalten Sie die Anzahl der Arbeitsschichten in dem Intervall, die gemäß dem Standardplan als Dienst deklariert sind. Diese Standardeinstellungen erleichtern Ihnen das Leben, da Sie sich in der Praxis hauptsächlich mit Arbeitsschichten im Dienst befassen müssen.

Die API

Die vollständige Timeboard-Dokumentation finden Sie unter „Read the Docs“.

Das Paket kann mit dem üblichen pip install timeboard installiert werden.

Richten Sie eine Zeitleiste ein

Der einfachste Einstieg ist die Verwendung eines vorkonfigurierten Kalenders, der dem Paket beiliegt. Nehmen wir einen regulären Geschäftstagskalender für die Vereinigten Staaten.

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

Das clnd-Objekt ist ein Timeboard (eine Instanz der Klasse timeboard.Timeboard). Es gibt nur einen Standardplan, der Wochentage als diensthabende Arbeitsschichten auswählt, während Wochenenden sowie Beobachtungen von US-Bundesfeiertagen als dienstfrei erklärt werden.

Die Tools zum Erstellen Ihres eigenen Timeboards werden später kurz besprochen, nachdem wir uns angesehen haben, was Sie mit einem Timeboard machen können.

Spielen Sie mit Arbeitsschichten

Durch Aufrufen einer Timeboard-Instanz clnd() mit einem einzelnen Zeitpunkt wird die Arbeitsschicht abgerufen, die diesen Punkt enthält. Wie Sie eine Arbeitsschicht haben, können Sie deren Aufgaben abfragen:

Ist ein bestimmtes Datum ein Werktag?

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

Tatsächlich war es ein Samstag.

Auch von der aktuellen Arbeitsschicht aus können Sie in die Zukunft oder in die Vergangenheit blicken:

Wann war der nächste Werktag?

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

Die zurückgegebene Arbeitsschicht hat die Sequenznummer 6359 und stellt den Tag des 30. Mai 2017 dar, der übrigens der Dienstag nach dem Memorial Day-Feiertag war.

Wenn wir das Projekt innerhalb von 22 Werktagen ab dem 1. Mai 2017 abschließen würden, wann wäre dann unsere Frist?

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

Das ist dasselbe wie:

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

Spielen Sie mit Intervallen

Der Aufruf von clnd() mit einem anderen Parametersatz erzeugt ein Objekt, das ein Intervall im Kalender darstellt. Das folgende Intervall enthält alle Arbeitsschichten des Monats Mai 2017:

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

Wie viele Werktage gab es im Mai?

>>> may2017.count()22

Wie viele freie Tage?

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

Wie viele Arbeitsstunden?

>>> may2017.worktime()176

Ein Mitarbeiter war vom 3. April 2017 bis zum 15. Mai 2017 im Personal. Welchen Teil des Aprilgehalts schuldete das Unternehmen ihm?

Beachten Sie, dass der Aufruf von clnd() mit einem Tupel aus zwei Zeitpunkten ein Intervall erzeugt, das alle Arbeitsschichten zwischen diesen Zeitpunkten enthält.

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

Tatsächlich fielen der 1. und der 2. April im Jahr 2017 auf ein Wochenende, sodass der Mitarbeiter, nachdem er am 3. begonnen hatte, alle Arbeitstage des Monats abgebucht hatte.

Und welcher Teil von May?

>>> time_in_company.what_portion_of(may2017)0.5

Wie viele Tage hat der Mitarbeiter im Mai gearbeitet?

Der Multiplikationsoperator gibt den Schnittpunkt zweier Intervalle zurück.

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

Wie viele Stunden?

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

Ein Mitarbeiter gehörte vom 01.01.2016 bis 15.07.2017 zum Personal. Wie viele Jahre war diese Person bereits für das Unternehmen tätig?

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

Erstellen Sie Ihr eigenes Timeboard

Zum Zweck der Einführung werde ich mich nur auf zwei Beispiele stürzen. Wenn es zu steil erscheint, finden Sie die ausführliche Beschreibung der Bauwerkzeuge bitte in der Projektdokumentation.

Die Importanweisung für diesen Abschnitt:

>>> import timeboard as tb

Lassen Sie mich auf den Schichtplan im Autohaus zurückkommen, den ich im Prolog erwähnt habe. Ein Mechaniker arbeitet diese Woche am Montag, Dienstag, Samstag und Sonntag und nächste Woche am Mittwoch, Donnerstag und Freitag; dann wiederholt sich der zweiwöchentliche Zyklus. Das Timeboard wird mit dem folgenden Code erstellt:

>>> 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)

Es ist sinnvoll, sich zunächst mit der letzten Aussage zu befassen. Es erstellt eine Zeitleiste mit dem Namen clnd. Die ersten drei Parameter definieren den Rahmen als eine Folge von Tagen („D“) vom 1. Oktober 2017 bis zum 31. Dezember 2018. Der Parameter layout gibt an, wie der Rahmen organisiert werden soll in die Zeitleiste der Arbeitsschichten ein. Dieser Auftrag wird an einen Organisator namens biweekly vergeben.

Die erste Anweisung erstellt diesen Organizer, der zwei Parameter akzeptiert: marker und structure. Wir verwenden einen marker, um Markierungen auf dem Rahmen zu platzieren. Die Markierungen sind eine Art Meilensteine, die den Rahmen in Unterrahmen oder „Spannweiten“ unterteilen. Im Beispiel setzt marker=’W’ eine Markierung am Anfang jeder Kalenderwoche. Daher stellt jede Spanne eine Woche dar.

Der Parameter structure gibt an, wie Arbeitsschichten innerhalb jeder Spanne erstellt werden. Das erste Element von structure, die Liste [1,1,0,0,0,1,1], wird auf die erste Spanne (d. h. auf die erste Woche) angewendet unseres Kalenders). Jede Basiseinheit (d. h. jeder Tag) innerhalb der Spanne wird zu einer Arbeitsschicht. Die Arbeitsschichten erhalten der Reihe nach Beschriftungen aus der Liste.

Das zweite Element von structure, die Liste [0,0,1,1,1,0,0], wird analog auf die zweite Spanne (die zweite Woche) angewendet. . Da wir danach keine weiteren Elemente erhalten haben, wird eine Struktur in Zyklen wiederholt. Daher wird die dritte Woche vom ersten Element von structure bedient, die vierte Woche vom zweiten und so weiter.

Dadurch wird unsere Zeitleiste zu einer Abfolge von Tagen, die mit der Nummer 1 gekennzeichnet sind, wenn der Mechaniker im Dienst ist, und mit der Nummer 0, wenn er oder sie nicht im Dienst ist. Wir haben keinen Zeitplan angegeben, da der standardmäßig erstellte Zeitplan für uns in Ordnung ist. Der Standardplan berücksichtigt den booleschen Wert der Bezeichnung, sodass 1 in „im Dienst“ und null in „außer Dienst“ übersetzt wird.

Mit dieser Zeitleiste können wir alle Arten von Berechnungen durchführen, die wir zuvor mit dem Geschäftskalender durchgeführt haben. Wenn eine Person beispielsweise seit dem 4. November 2017 nach diesem Plan beschäftigt war und das Gehalt monatlich ausgezahlt wird, welchen Anteil des Novembergehalts hat die Mitarbeiterin dann verdient?

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

Im zweiten Beispiel erstellen wir eine Zeittafel für ein Callcenter. Das Callcenter ist rund um die Uhr in unterschiedlich langen Schichten tätig: 08:00 bis 18:00 Uhr (10 Stunden), 18:00 bis 02:00 Uhr (8 Stunden) und 02:00 bis 08:00 Uhr (6 Stunden). ). Der Dienstplan eines Bedieners besteht aus einer diensthabenden Schicht, gefolgt von drei dienstfreien Schichten. Daher sind vier Bedienerteams erforderlich. Sie werden mit „A“, „B“, „C“ und „D“ bezeichnet.

>>> 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')

Es gibt vier wesentliche Unterschiede zum Händlerfall. Wir werden sie einzeln untersuchen.

Erstens ist die Basiseinheit des Rahmens jetzt ein einstündiger Zeitraum (base_unit_freq='H') anstelle eines eintägigen Zeitraums im Kalender des Händlers.

Zweitens ist der Wert des Parameters marker des Organizers jetzt ein komplexes Objekt und nicht mehr wie zuvor eine einzelne Kalenderhäufigkeit. Dieses Objekt ist eine Instanz der Klasse Marker. Es wird verwendet, um Regeln für die Anbringung von Markierungen auf dem Rahmen zu definieren, wenn die einfache Unterteilung des Rahmens in einheitliche Kalendereinheiten nicht ausreicht. Die Signatur des Markers oben ist fast lesbar – sie lautet: Markieren Sie jeden Tag („D“) um 02:00 Uhr, 08:00 Uhr und 18:00 Uhr.

Drittens ist der Wert der Struktur jetzt einfacher: Es handelt sich um eine einstufige Liste von Teambezeichnungen. Wenn ein Element der Struktur kein iterierbares Label, sondern nur ein Label ist, erzeugt seine Anwendung auf einen Bereich eine einzelne Arbeitsschicht, die sich buchstäblich über den gesamten Bereich erstreckt.

In unserem Beispiel umfasst die allererste Spanne sechs einstündige Basiseinheiten, beginnend um 2, 3, 4 … 7 Uhr morgens am 1. Januar 2009. Alle diese Basiseinheiten werden in der einzigen Arbeitsschicht mit der Bezeichnung „A“ zusammengefasst. . Die zweite Zeitspanne umfasst zehn einstündige Basiseinheiten, beginnend um 8, 9, 10 … 17 Uhr. Diese Basiseinheiten werden zu einer einzelnen Arbeitsschicht mit der Bezeichnung „B“ usw. zusammengefasst. Wenn alle Beschriftungen vergeben sind, wird die Struktur erneut abgespielt, sodass der fünfte Abschnitt (08:00:00–17:59:59 Uhr am 1. Januar 2009) zu einer Arbeitsschicht mit der Beschriftung „A“ wird.

Um es noch einmal zusammenzufassen: Wenn ein Element von structure eine Liste von Beschriftungen ist, wird jede Basiseinheit der Spanne zu einer Arbeitsschicht und erhält eine Beschriftung aus der Liste. Wenn ein Element von structure ein einzelnes Label ist, werden alle Basiseinheiten der Spanne zu einer einzigen Arbeitsschicht zusammengefasst, die dieses Label erhält.

Und schließlich haben wir explizit einen Zeitplan für Team A erstellt. Der Standardplan erfüllt unseren Zweck nicht, da er „Immer im Dienst“ zurückgibt. Dies gilt für das Callcenter als Ganzes, nicht jedoch für ein bestimmtes Team. Für den neuen Zeitplan geben wir den Namen und die Auswahlfunktion an, die für alle mit „A“ gekennzeichneten Arbeitsschichten „True“ zurückgibt. Aus praktischen Gründen möchten Sie auch die Zeitpläne für die anderen Teams erstellen.

Mit diesem Timeboard lässt es sich genauso gut arbeiten wie mit jedem anderen. Dieses Mal müssen wir jedoch explizit den Zeitplan angeben, den wir verwenden möchten.

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

Wie viele Schichten saßen die Bediener von Team A im November 2017?

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

Und wie viele Stunden waren es insgesamt?

>>> nov2017.worktime()176

Eine Person war ab dem 4. November 2017 als Operator im Team A beschäftigt. Das Gehalt wird monatlich ausgezahlt. Welchen Anteil des Novembergehalts hat der Mitarbeiter verdient?

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

Weitere Anwendungsfälle

Weitere Anwendungsfälle (fast aus dem wirklichen Leben) finden Sie im Jupyter-Notizbuch, das Teil der Projektdokumentation ist.

Bitte nutzen Sie timeboard und zögern Sie nicht, Feedback oder offene Probleme auf GitHub zu hinterlassen.