Compare commits
10
Commits
f90e6895b4
...
981cc314f0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
981cc314f0 | ||
|
|
0861e371e9 | ||
|
|
13c5ae598a | ||
|
|
0f31dfe1a9 | ||
|
|
02598cb665 | ||
|
|
09c8124cdd | ||
|
|
2949327d39 | ||
|
|
ea68885ff3 | ||
|
|
e288585cd3 | ||
|
|
4cad1158c6 |
@@ -45,6 +45,7 @@ class WorkerForm(forms.ModelForm):
|
||||
# Complex structured options edited as JSON
|
||||
assign_as_block_preferences = forms.CharField(required=False, widget=forms.Textarea, help_text="JSON mapping shift_name->weight", label="Block preferences (JSON)")
|
||||
shift_fte_overrides = forms.CharField(required=False, widget=forms.Textarea, help_text="JSON mapping shift_name->fte", label="Shift FTE overrides (JSON)")
|
||||
exact_shifts = forms.CharField(required=False, widget=forms.Textarea, help_text="JSON mapping shift_name->exact_count", label="Exact shifts (JSON)")
|
||||
previous_shifts = forms.CharField(required=False, widget=forms.Textarea, help_text="Free JSON structure for previous shifts", label="Previous shifts (JSON)")
|
||||
shift_balance_extra = forms.CharField(required=False, widget=forms.Textarea, help_text="JSON structure for extra shift-balance penalties", label="Shift balance extra (JSON)")
|
||||
allowed_multi_shift_sets = forms.CharField(required=False, widget=forms.Textarea, help_text="JSON list of sets (e.g. [[\"a\",\"b\"], ...])", label="Allowed multi-shift sets (JSON)")
|
||||
@@ -94,6 +95,7 @@ class WorkerForm(forms.ModelForm):
|
||||
for json_fld in [
|
||||
"assign_as_block_preferences",
|
||||
"shift_fte_overrides",
|
||||
"exact_shifts",
|
||||
"previous_shifts",
|
||||
"shift_balance_extra",
|
||||
"allowed_multi_shift_sets",
|
||||
@@ -146,6 +148,7 @@ class WorkerForm(forms.ModelForm):
|
||||
json_fields = [
|
||||
"assign_as_block_preferences",
|
||||
"shift_fte_overrides",
|
||||
"exact_shifts",
|
||||
"previous_shifts",
|
||||
"shift_balance_extra",
|
||||
"allowed_multi_shift_sets",
|
||||
|
||||
@@ -229,6 +229,7 @@ class Worker(models.Model):
|
||||
"max_shifts_per_week_by_shift_name",
|
||||
"assign_as_block_preferences",
|
||||
"shift_fte_overrides",
|
||||
"exact_shifts",
|
||||
]
|
||||
list_keys = [
|
||||
"oop",
|
||||
|
||||
+180
-143
@@ -2,6 +2,7 @@ from pyomo.opt.results.container import ignore
|
||||
from collections import defaultdict
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from rota_generator.shifts import MaxShiftsPerWeekConstraint, NoWorkers, PostShiftConstraint, PreShiftConstraint, RotaBuilder, SingleShift, WorkerRequirement, days, RotaConstraintOptions, MaxShiftsPerWeekBlockConstraint
|
||||
@@ -22,14 +23,52 @@ from loguru import logger
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
ROTA_REQUESTS = "cons/requests.ods"
|
||||
ROTA_B_PATH = "cons/rota_b.xlsx"
|
||||
ROTA_REQUESTS = "/home/ross/Sync/cons/requests.ods"
|
||||
ROTA_B_PATH = "/home/ross/Sync/cons/rota_b.xlsx"
|
||||
ROTA_START_DATE = "2026-08-03"
|
||||
|
||||
|
||||
sites = ("rota a", "rota b", "rota c")
|
||||
|
||||
|
||||
def _normalize_worker_key(value):
|
||||
text = str(value or "").strip()
|
||||
if not text:
|
||||
return ""
|
||||
return re.sub(r"\s+", " ", text).lower()
|
||||
|
||||
|
||||
def _validate_worker_alignment(base_workers, other_workers, base_label, other_label):
|
||||
"""Validate that two worker collections contain the same members.
|
||||
|
||||
Comparison is case-insensitive and whitespace-normalized.
|
||||
Raises ValueError with a clear diff when the sets do not match.
|
||||
"""
|
||||
base_clean = [str(w).strip() for w in base_workers if str(w).strip()]
|
||||
other_clean = [str(w).strip() for w in other_workers if str(w).strip()]
|
||||
|
||||
base_norm_to_raw = {_normalize_worker_key(w): w for w in base_clean}
|
||||
other_norm_to_raw = {_normalize_worker_key(w): w for w in other_clean}
|
||||
|
||||
base_norm = set(base_norm_to_raw.keys())
|
||||
other_norm = set(other_norm_to_raw.keys())
|
||||
|
||||
only_in_base_norm = sorted(base_norm - other_norm)
|
||||
only_in_other_norm = sorted(other_norm - base_norm)
|
||||
|
||||
if not only_in_base_norm and not only_in_other_norm:
|
||||
return
|
||||
|
||||
only_in_base = [base_norm_to_raw[k] for k in only_in_base_norm]
|
||||
only_in_other = [other_norm_to_raw[k] for k in only_in_other_norm]
|
||||
|
||||
raise ValueError(
|
||||
"Worker mismatch detected between sources. "
|
||||
f"{base_label} only: {only_in_base}. "
|
||||
f"{other_label} only: {only_in_other}."
|
||||
)
|
||||
|
||||
|
||||
def _parse_sheet_date(value):
|
||||
if pd.isna(value):
|
||||
return None
|
||||
@@ -38,6 +77,100 @@ def _parse_sheet_date(value):
|
||||
return None
|
||||
return parsed.date()
|
||||
|
||||
|
||||
def _parse_worked_target_cell(value):
|
||||
"""Parse values like '5 (6.88)' into (worked, target)."""
|
||||
if pd.isna(value):
|
||||
return None
|
||||
|
||||
text = str(value).strip()
|
||||
if not text or text.lower() in {"nan", "-"}:
|
||||
return None
|
||||
|
||||
match = re.match(r"^\s*(-?\d+(?:\.\d+)?)\s*\(([^)]+)\)\s*$", text)
|
||||
if match:
|
||||
try:
|
||||
worked = float(match.group(1).strip())
|
||||
target = float(match.group(2).strip())
|
||||
return worked, target
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
# Fallback for cells that contain only one number.
|
||||
try:
|
||||
target = float(text)
|
||||
return 0.0, target
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _load_priors_from_running_totals(running_totals_df):
|
||||
"""Extract previous shifts map from Running totals tab.
|
||||
|
||||
Reads only oncall nights, twilights and weekend A values.
|
||||
"""
|
||||
priors_map = {}
|
||||
if running_totals_df is None or running_totals_df.empty:
|
||||
return priors_map
|
||||
|
||||
# Find the header row that starts with 'Worker'.
|
||||
header_row_index = None
|
||||
for i in range(len(running_totals_df)):
|
||||
first_cell = str(running_totals_df.iloc[i, 0]).strip().lower()
|
||||
if first_cell == "worker":
|
||||
header_row_index = i
|
||||
break
|
||||
|
||||
if header_row_index is None:
|
||||
return priors_map
|
||||
|
||||
header_values = [
|
||||
str(c).strip().lower() if not pd.isna(c) else ""
|
||||
for c in running_totals_df.iloc[header_row_index].tolist()
|
||||
]
|
||||
|
||||
def _find_col(header_name):
|
||||
for idx, col in enumerate(header_values):
|
||||
if col == header_name:
|
||||
return idx
|
||||
return None
|
||||
|
||||
worker_col = _find_col("worker")
|
||||
oncall_col = _find_col("oncall night")
|
||||
twilight_col = _find_col("twilight")
|
||||
weekend_a_col = _find_col("weekend a")
|
||||
|
||||
if worker_col is None:
|
||||
return priors_map
|
||||
|
||||
for i in range(header_row_index + 1, len(running_totals_df)):
|
||||
row = running_totals_df.iloc[i]
|
||||
worker_raw = row.iloc[worker_col] if worker_col < len(row) else None
|
||||
worker = str(worker_raw).strip() if not pd.isna(worker_raw) else ""
|
||||
if not worker or worker.lower() in {"nan", "total"}:
|
||||
continue
|
||||
|
||||
prev = {}
|
||||
if oncall_col is not None and oncall_col < len(row):
|
||||
parsed = _parse_worked_target_cell(row.iloc[oncall_col])
|
||||
if parsed is not None:
|
||||
prev["oncall"] = parsed
|
||||
|
||||
if twilight_col is not None and twilight_col < len(row):
|
||||
parsed = _parse_worked_target_cell(row.iloc[twilight_col])
|
||||
if parsed is not None:
|
||||
prev["twilight"] = parsed
|
||||
|
||||
if weekend_a_col is not None and weekend_a_col < len(row):
|
||||
parsed = _parse_worked_target_cell(row.iloc[weekend_a_col])
|
||||
if parsed is not None:
|
||||
prev["weekend"] = parsed
|
||||
|
||||
if prev:
|
||||
priors_map[worker] = prev
|
||||
|
||||
return priors_map
|
||||
|
||||
def extract_leave_and_rota_from_calender(calender_df):
|
||||
"""
|
||||
Extract leave and rota assignments from calender_df.
|
||||
@@ -90,7 +223,7 @@ def extract_leave_and_rota_from_calender(calender_df):
|
||||
|
||||
|
||||
#print(leave_requests)
|
||||
return leave_requests, work_requests#, rota_data
|
||||
return leave_requests, work_requests, worker_names
|
||||
|
||||
def extract_shift_assignments_from_rota(rota_df):
|
||||
"""
|
||||
@@ -168,6 +301,7 @@ def load_workers():
|
||||
days_df = xls["Days"]
|
||||
calender_df = xls["Calendar"]
|
||||
rota_df = xls["Rota"]
|
||||
running_totals_df = xls.get("Running totals")
|
||||
|
||||
|
||||
rota_b_path = ROTA_B_PATH
|
||||
@@ -196,53 +330,9 @@ def load_workers():
|
||||
print(rota_b_assignments)
|
||||
# You can now use rota_b_assignments as needed
|
||||
|
||||
# --- Load prior allocations (targets) from CSV ---
|
||||
priors_path = "cons/sep2025priors.csv"
|
||||
priors_map = {}
|
||||
try:
|
||||
priors_df = pd.read_csv(priors_path)
|
||||
for _, prow in priors_df.iterrows():
|
||||
initial = str(prow.get("Worker", "")).strip()
|
||||
if not initial or initial.lower() == "nan":
|
||||
continue
|
||||
prev = {}
|
||||
# expected columns: oncall, twilight, weekend
|
||||
for col in ("oncall", "twilight", "weekend"):
|
||||
raw = prow.get(col, "")
|
||||
if pd.isna(raw) or raw == "" or str(raw).strip() in ("-", "nan"):
|
||||
continue
|
||||
s = str(raw).strip()
|
||||
# Expect formats like: '4 (2.50)' or '3 (2.86)'
|
||||
worked = 0.0
|
||||
allocated = 0.0
|
||||
if "(" in s:
|
||||
try:
|
||||
worked_part = s.split("(")[0].strip()
|
||||
worked = float(worked_part)
|
||||
except Exception:
|
||||
worked = 0.0
|
||||
try:
|
||||
inner = s.split("(", 1)[1].split(")", 1)[0]
|
||||
allocated = float(inner.strip())
|
||||
except Exception:
|
||||
allocated = worked
|
||||
else:
|
||||
try:
|
||||
allocated = float(s)
|
||||
except Exception:
|
||||
continue
|
||||
worked = 0.0
|
||||
|
||||
# store as tuple (worked, allocated) as expected by RotaBuilder
|
||||
prev[col] = (worked, allocated)
|
||||
|
||||
if prev:
|
||||
priors_map[initial] = prev
|
||||
logger.debug("priors_map: {}", priors_map)
|
||||
except FileNotFoundError:
|
||||
print(f"Prior allocations file not found: {priors_path}")
|
||||
except Exception as e:
|
||||
print(f"Error reading prior allocations: {e}")
|
||||
# --- Load prior allocations from requests.ods -> Running totals tab ---
|
||||
priors_map = _load_priors_from_running_totals(running_totals_df)
|
||||
logger.debug("priors_map from Running totals: {}", priors_map)
|
||||
|
||||
# --- Example: Print the first few rows of each sheet ---
|
||||
#print("Days sheet:")
|
||||
@@ -267,106 +357,44 @@ def load_workers():
|
||||
workers_days.append({"initial": initial, "name": name, "availability": availability, "requests": requests})
|
||||
|
||||
|
||||
leave_requests, work_requests = extract_leave_and_rota_from_calender(calender_df)
|
||||
leave_requests, work_requests, calendar_workers = extract_leave_and_rota_from_calender(calender_df)
|
||||
|
||||
assignments, assignments_by_worker = extract_shift_assignments_from_rota(rota_df)
|
||||
print(assignments)
|
||||
|
||||
# --- Load per-day prior worked data (Sat/Sun) from sep2025priordays.csv for Rota A workers ---
|
||||
# Build set of initials who are on Rota A
|
||||
rota_a_initials = set()
|
||||
for wd in workers_days:
|
||||
name = wd["name"]
|
||||
initial = wd["initial"]
|
||||
if rota_data.get(name, "") == "rota a":
|
||||
rota_a_initials.add(initial)
|
||||
# Validate cross-source worker matching before constructing Worker objects.
|
||||
days_workers = [w["name"] for w in workers_days]
|
||||
days_initials = [w["initial"] for w in workers_days]
|
||||
|
||||
priordays_path = "cons/sep2025priordays.csv"
|
||||
try:
|
||||
import csv
|
||||
_validate_worker_alignment(
|
||||
base_workers=days_workers,
|
||||
other_workers=calendar_workers,
|
||||
base_label="Days tab worker names",
|
||||
other_label="Calendar tab worker names",
|
||||
)
|
||||
|
||||
perday_counts = defaultdict(lambda: defaultdict(int))
|
||||
with open(priordays_path, newline="", encoding="utf-8") as f:
|
||||
reader = csv.reader(f)
|
||||
for row in reader:
|
||||
if len(row) < 4:
|
||||
continue
|
||||
day = row[2].strip()
|
||||
if day not in ("Sat", "Sun"):
|
||||
continue
|
||||
# fields from index 3 onwards are assignments; some may be empty
|
||||
for field in row[3:]:
|
||||
if not field:
|
||||
continue
|
||||
val = field.strip()
|
||||
if not val:
|
||||
continue
|
||||
# entries may be like: RK (oncall, weekend) or CK (weekend b)
|
||||
import re
|
||||
_validate_worker_alignment(
|
||||
base_workers=days_initials,
|
||||
other_workers=list(assignments_by_worker.keys()),
|
||||
base_label="Days tab worker initials",
|
||||
other_label="Rota tab assigned initials",
|
||||
)
|
||||
|
||||
m = re.match(r"\s*([A-Za-z0-9]+)\s*\(([^)]*)\)", val)
|
||||
if not m:
|
||||
# sometimes multiple assignments are concatenated in a single cell separated by commas
|
||||
parts = [p.strip() for p in re.split(r",\s*", val) if p.strip()]
|
||||
for p in parts:
|
||||
m2 = re.match(r"\s*([A-Za-z0-9]+)\s*\(([^)]*)\)", p)
|
||||
if m2:
|
||||
initial = m2.group(1).strip()
|
||||
paren = m2.group(2).lower()
|
||||
if initial in rota_a_initials and "weekend" in paren:
|
||||
perday_counts[initial][day] += 1
|
||||
continue
|
||||
_validate_worker_alignment(
|
||||
base_workers=days_initials,
|
||||
other_workers=list(rota_b_assignments.keys()),
|
||||
base_label="Days tab worker initials",
|
||||
other_label="Rota B assignments initials",
|
||||
)
|
||||
|
||||
initial = m.group(1).strip()
|
||||
paren = m.group(2).lower()
|
||||
if initial in rota_a_initials and "weekend" in paren:
|
||||
perday_counts[initial][day] += 1
|
||||
_validate_worker_alignment(
|
||||
base_workers=days_initials,
|
||||
other_workers=list(priors_map.keys()),
|
||||
base_label="Days tab worker initials",
|
||||
other_label="Running totals worker initials",
|
||||
)
|
||||
|
||||
# Merge per-day counts into priors_map under 'weekend' per-day keys
|
||||
for initial, daymap in perday_counts.items():
|
||||
prev = priors_map.get(initial, {})
|
||||
existing = prev.get("weekend")
|
||||
# If existing is a tuple, convert to dict preserving overall tuple under '__all__'
|
||||
if existing and not isinstance(existing, dict):
|
||||
prev["weekend"] = {"__all__": existing}
|
||||
existing = prev["weekend"]
|
||||
|
||||
for day, worked_count in daymap.items():
|
||||
# Determine allocated value for this per-day entry
|
||||
allocated_day = worked_count
|
||||
if existing:
|
||||
# prefer an explicit per-day allocated if present
|
||||
if isinstance(existing, dict) and day in existing:
|
||||
try:
|
||||
allocated_day = float(existing[day][1])
|
||||
except Exception:
|
||||
allocated_day = worked_count
|
||||
elif isinstance(existing, dict) and "__all__" in existing:
|
||||
try:
|
||||
overall_alloc = float(existing["__all__"][1])
|
||||
allocated_day = overall_alloc / 2.0
|
||||
except Exception:
|
||||
allocated_day = worked_count
|
||||
elif isinstance(existing, tuple):
|
||||
try:
|
||||
allocated_day = float(existing[1]) / 2.0
|
||||
except Exception:
|
||||
allocated_day = worked_count
|
||||
|
||||
# ensure structure
|
||||
if "weekend" not in prev or not isinstance(prev["weekend"], dict):
|
||||
prev.setdefault("weekend", {})
|
||||
|
||||
prev["weekend"][day] = (float(worked_count), float(allocated_day))
|
||||
|
||||
if prev:
|
||||
priors_map[initial] = prev
|
||||
|
||||
logger.debug("priors_map after per-day merge: {}", priors_map)
|
||||
except FileNotFoundError:
|
||||
print(f"Per-day prior file not found: {priordays_path}")
|
||||
except Exception as e:
|
||||
print(f"Error reading per-day priors: {e}")
|
||||
logger.debug("Using Running totals priors for oncall/twilight/weekend A only")
|
||||
|
||||
workers = []
|
||||
for worker_day in workers_days:
|
||||
@@ -452,6 +480,7 @@ def load_workers():
|
||||
shift_name="weekend b",
|
||||
)
|
||||
|
||||
w.add_hard_day_exclusion({"Sun", "Mon"})
|
||||
if w.site == "rota a":
|
||||
|
||||
w.prefer_multi_shift_together = 10
|
||||
@@ -459,10 +488,12 @@ def load_workers():
|
||||
#w.add_force_assign_with("twilight", "oncall")
|
||||
w.add_force_assign_with("weekend", "oncall")
|
||||
|
||||
if w.name in ("DS","RG"):
|
||||
if w.name in ("DS","RG","MOB","TB"):
|
||||
w.add_hard_day_dependency({"Fri", "Sat", "Sun"}, ignore_dates=[datetime.date(2026, 5, 2)])
|
||||
w.max_days_worked_per_week = 3
|
||||
else:
|
||||
w.max_days_worked_per_week = 2
|
||||
w.add_max_days_per_week_block_constraint(max_days=2, week_block=2)
|
||||
w.add_hard_day_dependency({"Fri", "Sun"})
|
||||
w.add_hard_day_exclusion({"Sat", "Sun"})
|
||||
else:
|
||||
@@ -494,7 +525,7 @@ def main(
|
||||
weeks_to_rota=weeks,
|
||||
balance_offset_modifier=bom,
|
||||
use_previous_shifts=True,
|
||||
name="cons rota 2026 july",
|
||||
name="cons rota test",
|
||||
allow_force_assignment_with_leave_conflict=True,
|
||||
constraint_options=RotaConstraintOptions(
|
||||
balance_weekends=True,
|
||||
@@ -535,6 +566,8 @@ def main(
|
||||
balance_offset=1,
|
||||
constraints=[
|
||||
PostShiftConstraint(days=1, start_date="2025-11-17", ignore_shifts=["oncall"]),
|
||||
#PreShiftConstraint(days=1),
|
||||
#PostShiftConstraint(days=3),
|
||||
],
|
||||
display_char="a",
|
||||
#force_assign_with=["oncall"]
|
||||
@@ -548,6 +581,10 @@ def main(
|
||||
days=days[5:],
|
||||
balance_offset=2,
|
||||
display_char="b",
|
||||
constraints=[
|
||||
PreShiftConstraint(days=2),
|
||||
PostShiftConstraint(days=2),
|
||||
],
|
||||
),
|
||||
SingleShift(
|
||||
sites=(
|
||||
|
||||
+77
-40
@@ -30,6 +30,10 @@ sites = (
|
||||
"plymouth",
|
||||
"taunton",
|
||||
"proc",
|
||||
"plymouth ir",
|
||||
"truro ir",
|
||||
"exeter ir",
|
||||
"torbay ir",
|
||||
)
|
||||
|
||||
from rota_generator.workers import (
|
||||
@@ -47,9 +51,9 @@ def main(
|
||||
suspend: bool = False,
|
||||
solve: bool = True,
|
||||
time_to_run: int = 60 * 60 * 10,
|
||||
ratio: float = 0.0001,
|
||||
start_date: datetime.datetime = "2026-03-02",
|
||||
weeks: int = 27,
|
||||
ratio: float = 0.1,
|
||||
start_date: datetime.datetime = "2026-09-07",
|
||||
weeks: int = 26,
|
||||
bom: float = 1,
|
||||
):
|
||||
rota_start_date = start_date.date()
|
||||
@@ -59,20 +63,19 @@ def main(
|
||||
rota_start_date,
|
||||
weeks_to_rota=weeks,
|
||||
balance_offset_modifier=bom,
|
||||
name="proc_rota3",
|
||||
name="proc_rota_sep_2026",
|
||||
)
|
||||
# Rota = RotaBuilder(start_date, weeks_to_rota=20, balance_offset_modifier=1)
|
||||
Rota.constraint_options["balance_shifts"] = False
|
||||
Rota.constraint_options["balance_shifts_quadratic"] = True
|
||||
Rota.constraint_options["balance_weekends"] = True
|
||||
Rota.constraint_options["max_night_frequency"] = 4 # 3
|
||||
Rota.constraint_options["max_night_frequency_week_exclusions"] = []
|
||||
Rota.constraint_options["max_night_frequency"] = 3 # 3
|
||||
#Rota.constraint_options["max_night_frequency_week_exclusions"] = []
|
||||
Rota.constraint_options["max_weekend_frequency"] = 3
|
||||
Rota.constraint_options["hard_constrain_pair_separation"] = True
|
||||
|
||||
Rota.constraint_options["max_days_per_week_block"] = [(8,4)]
|
||||
|
||||
#wr = [
|
||||
# wr = [
|
||||
# WorkerRequirement(
|
||||
# end_date=datetime.datetime.strptime("2025-06-02", "%Y-%m-%d").date(),
|
||||
# number=3,
|
||||
@@ -81,7 +84,7 @@ def main(
|
||||
# start_date=datetime.datetime.strptime("2025-06-02", "%Y-%m-%d").date(),
|
||||
# number=4,
|
||||
# ),
|
||||
#]
|
||||
# ]
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
@@ -90,6 +93,7 @@ def main(
|
||||
"exeter twilights and weekends",
|
||||
"exeter no nights",
|
||||
"exeter twilights",
|
||||
"exeter ir",
|
||||
),
|
||||
name="exeter_twilight",
|
||||
length=12.5,
|
||||
@@ -105,6 +109,7 @@ def main(
|
||||
"truro twilights and weekends",
|
||||
"truro twilights and weekend nights",
|
||||
"truro twilights, weekends and weekend nights",
|
||||
"truro ir",
|
||||
),
|
||||
name="truro_twilight",
|
||||
length=12.5,
|
||||
@@ -113,7 +118,12 @@ def main(
|
||||
constraints=[MaxShiftsPerWeekConstraint(max_shifts=1)],
|
||||
),
|
||||
SingleShift(
|
||||
sites=("torbay", "torbay twilights", "torbay twilights and weekends"),
|
||||
sites=(
|
||||
"torbay",
|
||||
"torbay twilights",
|
||||
"torbay twilights and weekends",
|
||||
"torbay ir",
|
||||
),
|
||||
name="torbay_twilight",
|
||||
length=12.5,
|
||||
days=days[:4],
|
||||
@@ -126,6 +136,7 @@ def main(
|
||||
"plymouth twilights",
|
||||
"plymouth twilights and weekends",
|
||||
"plymouth twilights, weekends and weekend nights",
|
||||
"plymouth ir",
|
||||
),
|
||||
name="plymouth_twilight",
|
||||
length=12.5,
|
||||
@@ -140,12 +151,12 @@ def main(
|
||||
"exeter twilights and weekends",
|
||||
"exeter no nights",
|
||||
"exeter weekends",
|
||||
#"exeter twilights",
|
||||
# "exeter twilights",
|
||||
),
|
||||
name="weekend_exeter",
|
||||
length=12.5,
|
||||
days=days[5:],
|
||||
#balance_offset=2,
|
||||
# balance_offset=2,
|
||||
rota_on_nwds=True,
|
||||
force_as_block=True,
|
||||
constraints=[PostShiftConstraint(days=2), PreShiftConstraint(days=2)],
|
||||
@@ -162,10 +173,10 @@ def main(
|
||||
name="weekend_truro",
|
||||
length=12.5,
|
||||
days=days[4:],
|
||||
#balance_offset=3,
|
||||
# balance_offset=3,
|
||||
rota_on_nwds=True,
|
||||
force_as_block=True,
|
||||
#assign_as_block=True,
|
||||
# assign_as_block=True,
|
||||
constraints=[PreShiftConstraint(days=2), PostShiftConstraint(days=2)],
|
||||
# force_as_block_unless_nwd=True
|
||||
),
|
||||
@@ -174,7 +185,7 @@ def main(
|
||||
name="weekend_torbay",
|
||||
length=12.5,
|
||||
days=days[4:],
|
||||
#balance_offset=2,
|
||||
# balance_offset=2,
|
||||
rota_on_nwds=True,
|
||||
force_as_block=True,
|
||||
constraints=[PreShiftConstraint(days=2), PostShiftConstraint(days=2)],
|
||||
@@ -190,7 +201,7 @@ def main(
|
||||
name="weekend_plymouth1",
|
||||
length=8,
|
||||
days=days[5:],
|
||||
#balance_offset=2.9,
|
||||
# balance_offset=2.9,
|
||||
workers_required=1,
|
||||
rota_on_nwds=True,
|
||||
force_as_block=True,
|
||||
@@ -205,7 +216,7 @@ def main(
|
||||
name="weekend_plymouth2",
|
||||
length=8,
|
||||
days=days[5:],
|
||||
#balance_offset=2.9,
|
||||
# balance_offset=2.9,
|
||||
workers_required=1,
|
||||
rota_on_nwds=True,
|
||||
force_as_block=True,
|
||||
@@ -217,6 +228,7 @@ def main(
|
||||
"plymouth twilights",
|
||||
"plymouth twilights and weekends",
|
||||
"plymouth twilights, weekends and weekend nights",
|
||||
"plymouth ir",
|
||||
),
|
||||
name="plymouth_bank_holidays",
|
||||
length=8,
|
||||
@@ -228,11 +240,13 @@ def main(
|
||||
bank_holidays_only=True,
|
||||
),
|
||||
SingleShift(
|
||||
sites=sites,
|
||||
sites=[
|
||||
*sites,
|
||||
],
|
||||
name="night_weekday",
|
||||
length=12.25,
|
||||
days=days[:4],
|
||||
#balance_offset=3.9,
|
||||
# balance_offset=3.9,
|
||||
balance_weighting=1,
|
||||
# hard_constrain_shift=False,
|
||||
workers_required=4,
|
||||
@@ -246,7 +260,7 @@ def main(
|
||||
LimitGradeNumberConstraint(grade=2, max_number=2),
|
||||
MinimumGradeNumberConstraint(grade=4, min_number=1),
|
||||
],
|
||||
#end_date=datetime.datetime.strptime("2025-06-01", "%Y-%m-%d").date(),
|
||||
# end_date=datetime.datetime.strptime("2025-06-01", "%Y-%m-%d").date(),
|
||||
),
|
||||
SingleShift(
|
||||
sites=[
|
||||
@@ -259,7 +273,7 @@ def main(
|
||||
name="night_weekend",
|
||||
length=12.25,
|
||||
days=days[4:],
|
||||
#balance_offset=2.9,
|
||||
# balance_offset=2.9,
|
||||
balance_weighting=1,
|
||||
# hard_constrain_shift=False,
|
||||
workers_required=4,
|
||||
@@ -273,15 +287,19 @@ def main(
|
||||
LimitGradeNumberConstraint(grade=2, max_number=2),
|
||||
MinimumGradeNumberConstraint(grade=4, min_number=1),
|
||||
],
|
||||
#end_date=datetime.datetime.strptime("2025-06-01", "%Y-%m-%d").date(),
|
||||
# end_date=datetime.datetime.strptime("2025-06-01", "%Y-%m-%d").date(),
|
||||
),
|
||||
)
|
||||
|
||||
# Prevent shifts for ST2s for 2 weeks
|
||||
#Rota.add_grade_constraint_by_week([2], [1, 2], [shift.name for shift in Rota.shifts])
|
||||
# Rota.add_grade_constraint_by_week([2], [1, 2], [shift.name for shift in Rota.shifts])
|
||||
|
||||
Rota.add_min_summed_grade_by_shifts_per_day_constraint(["weekend_plymouth1", "weekend_plymouth2"], 6)
|
||||
Rota.add_min_summed_grade_by_shifts_per_day_constraint(["plymouth_twilight", "plymouth_bank_holidays"], 6)
|
||||
Rota.add_min_summed_grade_by_shifts_per_day_constraint(
|
||||
["weekend_plymouth1", "weekend_plymouth2"], 6
|
||||
)
|
||||
Rota.add_min_summed_grade_by_shifts_per_day_constraint(
|
||||
["plymouth_twilight", "plymouth_bank_holidays"], 6
|
||||
)
|
||||
|
||||
Rota.add_min_summed_grade_by_shifts_per_day_constraint(["night_weekday"], 12)
|
||||
Rota.add_min_summed_grade_by_shifts_per_day_constraint(["night_weekend"], 12)
|
||||
@@ -328,7 +346,7 @@ def main(
|
||||
after_count = len(filtered)
|
||||
if after_count != before_count:
|
||||
print(
|
||||
f"Removed {before_count-after_count} leave entries for '{name}' between {_from} and {_to}"
|
||||
f"Removed {before_count - after_count} leave entries for '{name}' between {_from} and {_to}"
|
||||
)
|
||||
w["leave"] = filtered
|
||||
|
||||
@@ -376,9 +394,9 @@ def main(
|
||||
nwd_end_date = Rota.rota_end_date
|
||||
|
||||
if "[" in i:
|
||||
print("Split nwd", worker_name, i)
|
||||
# print("Split nwd", worker_name, i)
|
||||
a, b = i.split("[")[1][:-1].split("-")
|
||||
print(f"{a=} {b=}")
|
||||
# print(f"{a=} {b=}")
|
||||
nwd_start_date = datetime.datetime.strptime(
|
||||
a, "%d/%m/%Y"
|
||||
).date()
|
||||
@@ -407,7 +425,9 @@ def main(
|
||||
elif "to" in raw:
|
||||
s, e = raw.split("to", 1)
|
||||
else:
|
||||
raise ValueError(f"Cannot parse OOP dates: '{oop}' for {worker}")
|
||||
raise ValueError(
|
||||
f"Cannot parse OOP dates: '{oop}' for {worker}"
|
||||
)
|
||||
|
||||
s = s.strip().strip("()[]\"' ")
|
||||
e = e.strip().strip("()[]\"' ")
|
||||
@@ -417,7 +437,9 @@ def main(
|
||||
try:
|
||||
start_dt = datetime.datetime.strptime(s, fmt).date()
|
||||
end_dt = datetime.datetime.strptime(e, fmt).date()
|
||||
formatted_oops.append({"start_date": start_dt, "end_date": end_dt})
|
||||
formatted_oops.append(
|
||||
{"start_date": start_dt, "end_date": end_dt}
|
||||
)
|
||||
parsed = True
|
||||
break
|
||||
except ValueError:
|
||||
@@ -426,13 +448,15 @@ def main(
|
||||
if not parsed:
|
||||
print("WORKER", worker)
|
||||
print("DATES", s, e)
|
||||
raise ValueError(f"Cannot parse OOP dates: '{oop}' for {worker}")
|
||||
raise ValueError(
|
||||
f"Cannot parse OOP dates: '{oop}' for {worker}"
|
||||
)
|
||||
|
||||
oop = formatted_oops
|
||||
else:
|
||||
oop = []
|
||||
|
||||
print(f"{worker} OOP {oop}")
|
||||
# print(f"{worker} OOP {oop}")
|
||||
|
||||
previous_shifts = {}
|
||||
# if twi:
|
||||
@@ -466,16 +490,29 @@ def main(
|
||||
|
||||
shift_fte_overrides = {}
|
||||
|
||||
if worker_name == "Hadi Mohamed":
|
||||
shift_fte_overrides = {
|
||||
"weekend_exeter": 50,
|
||||
exact_shifts = {}
|
||||
|
||||
if worker in ["hannah.lewis29@nhs.net", "shruti.bodapati@nhs.net", "a.abouelatta@nnhs.net"]:
|
||||
exact_shifts = {
|
||||
"night_weekday": 4,
|
||||
"night_weekend": 3,
|
||||
}
|
||||
#elif worker_name == "Joel Lim":
|
||||
elif worker in ["g.abdelhalim@nhs.net", "nang.thiriphoo@nhs.net"]:
|
||||
exact_shifts = {
|
||||
"night_weekend": 3,
|
||||
"night_weekday": 0,
|
||||
}
|
||||
|
||||
# if worker_name == "Hadi Mohamed":
|
||||
# shift_fte_overrides = {
|
||||
# "weekend_exeter": 50,
|
||||
# }
|
||||
# elif worker_name == "Joel Lim":
|
||||
# shift_fte_overrides = {
|
||||
# "plymouth_twilight": 100,
|
||||
# "weekend_exeter": 50,
|
||||
# }
|
||||
#if worker_name == "Nang Thiriphoo":
|
||||
# if worker_name == "Nang Thiriphoo":
|
||||
# shift_fte_overrides = {
|
||||
# "weekend_exeter": 40,
|
||||
# }
|
||||
@@ -498,9 +535,10 @@ def main(
|
||||
shift_balance_extra=w["shift_balance_extra"],
|
||||
bank_holiday_extra=w["bank_holiday_extra"],
|
||||
shift_fte_overrides=shift_fte_overrides,
|
||||
exact_shifts=exact_shifts,
|
||||
)
|
||||
|
||||
print(w)
|
||||
# print(w)
|
||||
|
||||
Rota.add_worker(w)
|
||||
leave.load_academy(Rota)
|
||||
@@ -508,12 +546,11 @@ def main(
|
||||
# Rota.build_workers()
|
||||
# Rota.build_model()
|
||||
|
||||
|
||||
solver_options = {"ratio": ratio, "seconds": time_to_run, "threads": 10}
|
||||
# solver_options = {"seconds": time_to_run, "threads": 10}
|
||||
|
||||
# start_time = time.time()
|
||||
Rota.build_and_solve(solver_options, export=True, solve=solve, solver="appsi_highs")
|
||||
Rota.build_and_solve(solver_options, export=True, solve=solve, solver="appsi_highs", export_with_timestamp=True)
|
||||
|
||||
# Rota.solve_shifts_by_block(solver_options, block_length=13)
|
||||
# Rota.solve_shifts_individually(solver_options)
|
||||
|
||||
@@ -1,387 +0,0 @@
|
||||
Rota 2021/2022,,, (POSSIBLE IDT),,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,80% until dec 1st,,,,,,,,,,,,,,,,
|
||||
Name,,Stephanie Bailey,Luciana Calovi Motschenbacher,Layla Bell,Seren Peters,Alex wood,Salma Aslam,Max Ireland,Charles Finan,Paul Ward,Tom Welsh,Delilah Trimmer,Max Finzel,Zainab Sharaf,Maththew O'Brien,Kyaw Tint,Nicolas Dziadulewicz,Jean Skumar,Michael Tomek,Vilim Kalamar,Dhuvresh Patel,Khalil Madbak,Georgina Edwards,Jenn Haw fong,Paul Jenkins,Jenna Millington,Amoolya Mannava,Ross Kruger,Sophie McGlade,Rebecca Murphy,Fiona Lyall,Joel Lim,Wijhdan Abusrewil,Mark Haley,Andrew MacCormick,Alexander Sanchez - cabello,Sayed Hashim Alqarooni,Chong Yew Ng,Matthew Thorley,Harriet Conley,Jon Skinner,Cameron Bullock,Alexander Wijnberg,Christian Greer,Alan Eccles,Hannah Lewis,Sarath Vennam,Amina Odeh,Mo Babsail,Richard Chaytor,Sherafghan PROC,Madalina Drumea,Mark Fellows,Clement Leung
|
||||
Rotation,,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Exeter,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford,Derriford no proc,Derriford,Derriford,Derriford,Derriford,Derriford Twilights,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay,Torbay Twilights,Truro,Truro,Truro,Truro,Truro,Truro,Truro,Truro,Truro,Truro no PROC,Barnstaple,Taunton,Taunton
|
||||
Grade,,ST2,ST2,ST2,ST3,ST3,ST3,ST3,ST4,ST4,ST5,ST5,ST2,ST2,ST2,ST2,ST3,ST3,ST3,ST3,ST3,ST4,ST4,ST4,ST4,ST4,ST5,ST5,ST5,ST5,ST6,ST2,ST2,ST2,ST2,ST3,ST4,ST4,ST4,ST5,ST6,ST2,ST2,ST3,ST3,ST3,ST4,ST4,ST5,ST5,ST5 (until dec),ST3,ST2,ST4
|
||||
PROC site,,Derriford,,exeter,Torbay,Exeter,,Derriford,Derriford,Exeter,Exeter,Derriford,Derriford,torbay,Torbay,derriford,,derriford,exeter,,Exeter,,,derriford,Derriford,,derriford,Derriford,Derriford,Exeter,NA,derriford,Torbay,Derriford,Derriford,,torbay,Derriford,Derriford,Torbay,NA,Exeter,Derriford,Truro,Truro,Derriford,Truro,Truro,Derriford,derriford,Derriford,Barnstaple,NA,NA
|
||||
%FTE,,0,0,100,100,100,0,100,100,100,100,80,100,100,100,100,100,60,80,100,100,0,80,100,100,0,100,100,0,100,60,100,100,100,100,100,100,100,100,60,100,100,100,100,80,100,100,100,100,100,100,80,0,0
|
||||
NWD,,,,,,,"Thursday,Friday",,"Tuesday,Thursday",,,wednesday,,,,,,"Wednesday,thursday",Wednesday,,,,Friday,,,"thursday, friday",,,Friday,,"monday,friday",,"thursday, friday",,,,,,,"monday,thursday",,,,,"wednesday, thursday,Friday",,,,,,,,,
|
||||
Flexible NWD,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
End Date,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
OOP,,,,,,,6/9/21-31/3/22,,,,,,,,,,,,,,,,,,,6/9/21-2/5/22,,,1/1/22-5/9/22,6/9/21-28/2/22,,,1/3/22-31/3/22,,,,,,,6/9/21-20/9/21,,,,,,,,,1/8/22-4/9/22,23/7/22-4/9/22,,,,
|
||||
Group,,Y1G1,Y1G1,Y1G2,Y3G1,Y3G1,Y3G2,Y3G2,,,,,Y2G1,Y2G2,Y2G2,Y2G2,Y3G1,Y3G1,Y3G2,Y3G2,Y3G2,,,,,,,,,,,Y2G1,Y2G1,Y2G2,Y2G2,Y3G2,,,,,,Y2G1,Y2G2,Y3G1,Y3G1,Y3G2,,,,,,,Y2G1,
|
||||
?PROC,,,,,,,,,,,,,,,,,,,,,,NO,,,,WEEKEND ONLY,,,,,No,,,,,,,,,,No,,,,,,,,,,Yes - until ST6 in dec,???,NO,NO
|
||||
Pair,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,2,,,
|
||||
Extra_night_weekday,,3.72,,-0.28,-0.28,-0.28,,-0.28,-0.28,-0.28,-0.28,0.63,-0.28,-0.28,-0.28,-0.28,-0.28,4.63,2.17,-0.28,3.72,,2.17,-0.28,-0.28,,-0.28,-0.28,,0.3,0,-0.28,-0.28,-0.28,3.72,-0.28,-0.28,-1.06,-0.28,0.27,,-0.28,-0.28,-0.28,-1.83,-0.28,-0.28,-0.28,-0.28,-0.28,0,-0.28,,
|
||||
Extra_night_weekend,,-0.21,,-0.21,-0.21,-0.21,,-0.21,-0.21,-0.21,-0.21,0.47,-0.21,2.79,2.79,2.79,-0.21,-2.53,-1.37,-0.21,-0.21,,-1.37,-0.21,-0.21,,-0.21,-0.21,,0.22,0,-0.21,-0.21,-0.21,-0.21,-0.21,-0.21,2.21,-0.21,0.21,,-0.21,-0.21,-0.21,1.63,-0.21,-0.21,-0.21,-0.21,-0.21,0,2.79,,
|
||||
Extra_twilight,,-1.97,,-0.97,1.03,1.03,,-0.97,-1.97,1.03,1.03,-0.38,0.47,0.47,-1.53,0.47,-0.53,-3.92,-2.23,0.47,-2.53,,-2.23,0.47,0.47,,0.47,0.47,,0.33,0.08,,,,,,,,,,,-1.39,-1.39,0.61,0.49,-1.39,-0.39,1.61,2.61,-1.39,0.61,0,,
|
||||
Extra_weekend,,-0.73,,2.27,-0.73,-0.73,,2.27,2.27,-0.73,-0.73,-1.04,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1.96,-1.04,-1.04,-0.63,1.96,0.96,-1.04,-2.04,1.96,-1.04,0,,
|
||||
Extra_weekend_plymouth1,,,,,,,,,,,,,-2.22,-0.22,-2.22,-0.22,-0.22,2.27,-0.98,-2.22,-0.22,,1.02,1.78,-0.22,,1.78,1.78,,0.15,0,,,,,,,,,,,,,,,,,,,,,,,
|
||||
Extra_weekend_plymouth2,,,,,,,,,,,,,1.78,-2.22,1.78,-2.22,-0.22,0.27,3.02,1.78,-0.22,,1.02,-2.22,-0.22,,-2.22,-0.22,,0.15,0,,,,,,,,,,,,,,,,,,,,,,,
|
||||
Extra_plymouth_bank_holiday,,,,,,,,,,,,,0.2,0.2,0.2,0.2,0.2,0.12,-0.84,0.2,0.2,,0.16,0.2,0.2,,0.2,-1.8,,0.01,0.12,,,,,,,,,,,,,,,,,,,,,,,
|
||||
bank_holidays_worked,,,,1,,,,1,,2,1,3,,2,1,2,,2,1,,3,,,,,,2,2,,,,2,,2,,1,,1,2,,,,2,2,,,,2,,,1,,,
|
||||
,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
Date,Exams,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
6/9/21,,,NO ONCALL,,,POST ONCALL,Mat leave,,exeter_twilight,,exeter_twilight,,,A/L,,,,,A/L,,,No Oncall,POST ONCALL,POST ONCALL,POST ONCALL,Mat leave,,,plymouth_twilight,OOPT,NO oncals,,POST ONCALL,,,,A/L,torbay_twilight,torbay_twilight,Mat leave,POST ONCALL,,,,,truro_twilight,truro_twilight,,A/L,POST ONCALL,,,,
|
||||
7/9/21,,,NO ONCALL,,,POST ONCALL,Mat leave,,exeter_twilight,,exeter_twilight,,,A/L,,,,,A/L,,,No Oncall,POST ONCALL,POST ONCALL,POST ONCALL,Mat leave,,,plymouth_twilight,OOPT,NO oncals,,POST ONCALL,,,,A/L,torbay_twilight,torbay_twilight,Mat leave,POST ONCALL,,,,,truro_twilight,truro_twilight,,A/L,POST ONCALL,,,,
|
||||
8/9/21,,,NO ONCALL,,,,Mat leave,,2b course,2b course,,,,,,,,,A/L,,,No Oncall,2b course,2b course,,Mat leave,,2b course,,OOPT,NO oncals,,,,,,A/L,2b course,2b course,Mat leave,,,,,,,2b course,2b course,A/L,,,,,
|
||||
9/9/21,,,NO ONCALL,,,,Mat leave,,2b course,2b course,,,,,,,,,A/L,,,No Oncall,2b course,2b course,,Mat leave,,2b course,,OOPT,NO oncals,,,,,,A/L,2b course,2b course,Mat leave,,,,,,,2b course,2b course,A/L,,,,,
|
||||
10/9/21,,,NO ONCALL,,A/L,,Mat leave,,,,,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,A/L,OOPT,NO oncals,,,,,,,,,Mat leave,,,,,,,,,A/L,,,,,
|
||||
11/9/21,,,NO ONCALL,,A/L,,Mat leave,,,,,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,A/L,OOPT,NO oncals,,,,,,,,,Mat leave,,,,,,,,,A/L,,,,,
|
||||
12/9/21,,,NO ONCALL,,A/L,,Mat leave,,,,,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,A/L,OOPT,NO oncals,,,,,,,,,Mat leave,,,,,,,,,A/L,,,,,
|
||||
13/9/21,,,NO ONCALL,,A/L,,Mat leave,,,,,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,A/L,OOPT,NO oncals,,,,,,,,,Mat leave,,,,,,,,,,,,,,
|
||||
14/9/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,,OOPT,NO oncals,,,,,,,,,Mat leave,,,,,,,,,,,,,,
|
||||
15/9/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,,OOPT,NO oncals,,,,,,,,,Mat leave,,,,,,,,,,,,,,
|
||||
16/9/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,,OOPT,NO oncals,,,,,,,,,Mat leave,,,,,,,,,,,,,,
|
||||
17/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,A/L,Pre 2B,,,,,,,A/L,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,NO oncals,,,,,,Pre 2B,Pre 2B,Pre 2B,Mat leave,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
18/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,A/L,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,NO oncals,,,,,,Pre 2B,Pre 2B,Pre 2B,Mat leave,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
19/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,A/L,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,NO oncals,,,,,,Pre 2B,Pre 2B,Pre 2B,Mat leave,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
20/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,A/L,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,NO oncals,,,,,,Pre 2B,Pre 2B,Pre 2B,Mat leave,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
21/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,NO oncals,,,,,,Pre 2B,Pre 2B,Pre 2B,No nights,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
22/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,NO oncals,,,,,,Pre 2B,Pre 2B,Pre 2B,No nights,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
23/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,NO oncals,,A/L,,,,Pre 2B,Pre 2B,Pre 2B,No nights,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
24/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,NO oncals,,A/L,,,,Pre 2B,Pre 2B,Pre 2B,A/L,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
25/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,NO oncals,,A/L,,,,Pre 2B,Pre 2B,Pre 2B,A/L,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
26/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,NO oncals,,A/L,,,,Pre 2B,Pre 2B,Pre 2B,A/L,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
27/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,,,,,,,Pre 2B,Pre 2B,Pre 2B,No nights,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
28/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,,,,,,,Pre 2B,Pre 2B,Pre 2B,No nights,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
29/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,,,,,,,Pre 2B,Pre 2B,Pre 2B,No nights,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
30/9/21,Pre 2B,,NO ONCALL,,,,Mat leave,,Pre 2B,Pre 2B,,Pre 2B,,,,,,,,,,No Oncall,,Pre 2B,,Mat leave,,,,OOPT,,,,,,,Pre 2B,Pre 2B,Pre 2B,No nights,,,,,Pre 2B,,Pre 2B,Pre 2B,,,,,,
|
||||
1/10/21,2B,,NO ONCALL,,,,Mat leave,,2B,2B,A/L,2B,A/L,,,,,,,,,No Oncall,,2B,,Mat leave,,,,OOPT,,,,,,,2B,2B,2B,No nights,,,,,2B,,2B,2B,,,,,,
|
||||
2/10/21,2B,,NO ONCALL,,,,Mat leave,,2B,2B,A/L,2B,A/L,,,,,,,,,No Oncall,,2B,,Mat leave,,,,OOPT,,,,,,,2B,2B,2B,No nights,,,,,2B,,2B,2B,,,,,,
|
||||
3/10/21,2B,,NO ONCALL,,,,Mat leave,,2B,2B,A/L,2B,A/L,,,,,,,,,No Oncall,,2B,,Mat leave,,,,OOPT,,,,,,,2B,2B,2B,No nights,,,,,2B,,2B,2B,,,,,,
|
||||
4/10/21,2B,,NO ONCALL,,,,Mat leave,,2B,2B,A/L,2B,,,,,,,,,,No Oncall,,2B,,Mat leave,,,,OOPT,,,,,,,2B,2B,2B,No nights,,,,,2B,,2B,2B,,,,,,
|
||||
5/10/21,2B,,NO ONCALL,,,,Mat leave,,2B,2B,,2B,,,,,,,,,,No Oncall,,2B,,Mat leave,,,,OOPT,,,,,,,2B,2B,2B,No nights,,,,,2B,,2B,2B,,,,,,
|
||||
6/10/21,2B,,NO ONCALL,,,,Mat leave,,2B,2B,,2B,,,,,,,,,,No Oncall,,2B,,Mat leave,,,,OOPT,,,,,,,2B,2B,2B,No nights,,,,,2B,,2B,2B,,,,,,
|
||||
7/10/21,2B,,NO ONCALL,,,,Mat leave,,2B,2B,,2B,,,,,,,,,,No Oncall,,2B,,Mat leave,,,,OOPT,,,,,,,2B,2B,2B,No nights,,,,,2B,,2B,2B,,,,,,
|
||||
8/10/21,2B,,NO ONCALL,,,,Mat leave,,2B,2B,,2B,,,,,,,,,,No Oncall,,2B,,Mat leave,,,A/L,OOPT,,,,,,,2B,2B,2B,No nights,,,,,2B,,2B,2B,,,,,,
|
||||
9/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,A/L,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
10/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,A/L,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
11/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,A/L,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
12/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
13/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
14/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
15/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
16/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
17/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
18/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
19/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
20/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
21/10/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,No nights,,,,,,,,,,,,,,
|
||||
22/10/21,,,NO ONCALL,,,,Mat leave,,A/L,,A/L,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,A/L,,,,No nights,,,,,,A/L,,,,,,,,
|
||||
23/10/21,,,NO ONCALL,,,,Mat leave,,A/L,,A/L,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,A/L,,,,,A/L,A/L,,,,,,,,,A/L,,,,,,,,
|
||||
24/10/21,,,NO ONCALL,,,,Mat leave,,A/L,,A/L,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,A/L,,,,,A/L,A/L,,,,,,,,,A/L,,,,,,,,
|
||||
25/10/21,,,NO ONCALL,,,,Mat leave,,A/L,,A/L,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,A/L,,,,,,A/L,,,,,,,,,,,,,,,,,
|
||||
26/10/21,,,NO ONCALL,,,,Mat leave,,,,A/L,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,A/L,,,,,,A/L,,,,,,,,,,,,,,,,,
|
||||
27/10/21,,,NO ONCALL,,,,Mat leave,,,,A/L,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,A/L,,,,,,A/L,,,,,,,,,,,,,,,,,
|
||||
28/10/21,,,NO ONCALL,,,,Mat leave,,,,A/L,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,A/L,,,,,,A/L,,,,,,,,,,,,,,,,,
|
||||
29/10/21,,,NO ONCALL,,,,Mat leave,,,,A/L,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,A/L,,,,,,A/L,,,,,,,,,,,,,,,,,
|
||||
30/10/21,,,NO ONCALL,,,A/L,Mat leave,,,,A/L,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,A/L,,A/L,,,,A/L,,,,,,,,,,,,,,,,,
|
||||
31/10/21,,,NO ONCALL,,,A/L,Mat leave,,,,A/L,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,A/L,,A/L,,,,A/L,,,,,,,,,,,,,,,,,
|
||||
1/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,,,,Pre 2a,,Pre 2a
|
||||
2/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,,,,Pre 2a,,Pre 2a
|
||||
3/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,A/L,,,Pre 2a,,Pre 2a
|
||||
4/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,A/L,,,Pre 2a,,Pre 2a
|
||||
5/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,A/L,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,A/L,,,Pre 2a,,Pre 2a
|
||||
6/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,A/L,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,A/L,,,Pre 2a,,Pre 2a
|
||||
7/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,A/L,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,A/L,,,Pre 2a,,Pre 2a
|
||||
8/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,,,,Pre 2a,,Pre 2a
|
||||
9/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,,,,Pre 2a,,Pre 2a
|
||||
10/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,A/L,,,Pre 2a,,Pre 2a
|
||||
11/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,A/L,,,Pre 2a,,Pre 2a
|
||||
12/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,A/L,,,Pre 2a,,Pre 2a
|
||||
13/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,A/L,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,A/L,,,Pre 2a,,Pre 2a
|
||||
14/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,A/L,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,A/L,,,Pre 2a,,Pre 2a
|
||||
15/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,A/L,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,,,,Pre 2a,,Pre 2a
|
||||
16/11/21,Pre 2a,,NO ONCALL,,Pre 2a,Pre 2a,Mat leave,Pre 2a,,Pre 2a,,,,,,,Pre 2a,Pre 2a,Pre 2a,Pre 2a,Pre 2a,No Oncall,Pre 2a,Pre 2a,,Mat leave,,,,OOPT,,A/L,Pre 2a,,,Pre 2a,,Pre 2a,,,,,,Pre 2a,,Pre 2a,Pre 2a,,,,,Pre 2a,,Pre 2a
|
||||
17/11/21,2A,,NO ONCALL,,2A,2A,Mat leave,2A,,2A,,,,,,,2A,2A,2A,2A,2A,No Oncall,2A,2A,,Mat leave,,,,OOPT,,A/L,2A,,,2A,,2A,,,,,,2A,,2A,2A,,,,,2A,,2A
|
||||
18/11/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,A/L,,,,A/L,,,,,,,,,,A/L,,,,,,,,
|
||||
19/11/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,A/L,,,,A/L,,,,,,,,,,A/L,,,,,,,,
|
||||
20/11/21,,,NO ONCALL,,,A/L,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,A/L,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
21/11/21,,,NO ONCALL,,,A/L,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,A/L,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
22/11/21,,,NO ONCALL,,,A/L,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,A/L,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
23/11/21,,,NO ONCALL,,,A/L,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,A/L,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
24/11/21,,,NO ONCALL,,,A/L,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,A/L,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
25/11/21,,,NO ONCALL,,,A/L,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,A/L,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
26/11/21,,,NO ONCALL,,,A/L,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,A/L,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
27/11/21,,,NO ONCALL,,,A/L,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,A/L,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
28/11/21,,,NO ONCALL,,,A/L,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,A/L,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
29/11/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
30/11/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
1/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
2/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
3/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,OFF,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,A/L,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
4/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,OFF,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
5/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,OFF,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
6/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
7/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
8/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
9/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,,,,,,,,,,,A/L,,,,
|
||||
10/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,,,,,,,,,,,A/L,,,,
|
||||
11/12/21,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,A/L,,,,,,,,,,,,,,,,A/L,,,,
|
||||
12/12/21,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,A/L,,,,,,,,,,,,,,,,A/L,,,,
|
||||
13/12/21,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
14/12/21,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
15/12/21,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
16/12/21,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
17/12/21,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,A/L,OOPT,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
18/12/21,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,A/L,OOPT,,,,,,,,,,,,,,,,,,,,,,,,A/L
|
||||
19/12/21,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,A/L,OOPT,,,,,,,,,,,,,,,,,,,,,,,,A/L
|
||||
20/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,A/L
|
||||
21/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,A/L
|
||||
22/12/21,,,NO ONCALL,,,,Mat leave,,,,,,A/L,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,A/L
|
||||
23/12/21,,,NO ONCALL,,OFF,,Mat leave,,,,,,A/L,,,,,,,OFF,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,OFF,OFF,OFF,,,,,,,,OFF,OFF,,OFF,,,OFF,,A/L
|
||||
24/12/21,,,NO ONCALL,,OFF,,Mat leave,,,,,,A/L,,,,,,,OFF,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,OFF,OFF,OFF,,,,,,,,OFF,OFF,,OFF,,,OFF,,A/L
|
||||
25/12/21,,,NO ONCALL,,OFF,,Mat leave,,,,,,A/L,,,,,,,OFF,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,OFF,OFF,OFF,,,,,,,,OFF,OFF,,OFF,,,OFF,,A/L
|
||||
26/12/21,,,NO ONCALL,,OFF,,Mat leave,,,,,,A/L,,,,,,,OFF,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,OFF,OFF,OFF,,,,,,,,OFF,OFF,,OFF,,,OFF,,A/L
|
||||
27/12/21,,,NO ONCALL,,OFF,,Mat leave,,,,,,A/L,,,,,,,OFF,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,OFF,OFF,OFF,,,,,,,,OFF,OFF,,OFF,,,OFF,,A/L
|
||||
28/12/21,,,NO ONCALL,,,,Mat leave,,,,,,A/L,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,OFF,,,,,,,,,,,,,,,,,A/L
|
||||
29/12/21,,,NO ONCALL,,,,Mat leave,,,,,,A/L,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,OFF,,,,,,,,,,,,,,,,,A/L
|
||||
30/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,OFF,,,,,,,,,,,,,,,,,A/L
|
||||
31/12/21,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,,OOPT,,,,,,,OFF,,,,,,,,,,,,,,,,,A/L
|
||||
1/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,OFF,,,,,,,,,,,,,,,,,A/L
|
||||
2/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,OFF,,,,,,,,,,,,,,,,,A/L
|
||||
3/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,A/L,,,,OFF,,,,,,,,,,,,,,,,,A/L
|
||||
4/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,A/L,,,,OFF,,,,,,,,,,,,,,,,,A/L
|
||||
5/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,A/L,,,,,,,,,,,,,,,,,,,,,A/L
|
||||
6/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,A/L,,,,,,,,,,,,,,,,,,,,,A/L
|
||||
7/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,A/L,,,,,,,,,,,,,,,,,,,,,A/L
|
||||
8/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,A/L,,,,,,,,,,,,,,,,A/L,,,,,A/L
|
||||
9/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,A/L,,,,,,,,,,,,,,,,A/L,,,,,A/L
|
||||
10/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
11/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
12/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
13/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
14/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
15/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
16/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
17/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
18/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
19/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
20/1/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
21/1/22,,,NO ONCALL,,A/L,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,A/L,,,,,,,,,,,,,,
|
||||
22/1/22,,,NO ONCALL,,A/L,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,,,,,,,,,A/L,,,,,,,,,,,,,,
|
||||
23/1/22,,,NO ONCALL,,A/L,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,,,,,,,,,A/L,,,,,,,,,,,,,,
|
||||
24/1/22,,,NO ONCALL,,A/L,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
25/1/22,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
26/1/22,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
27/1/22,,,NO ONCALL,,,,Mat leave,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
28/1/22,,,NO ONCALL,,,,Mat leave,,A/L,,A/L,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
29/1/22,,,NO ONCALL,,,,Mat leave,,A/L,,A/L,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,,,,,,,,,
|
||||
30/1/22,,,NO ONCALL,,,,Mat leave,,A/L,,A/L,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,,,,,,,,,
|
||||
31/1/22,,,NO ONCALL,,,,Mat leave,,,,A/L,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,,,,,,,,,
|
||||
1/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
2/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
3/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
4/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,A/L,,,A/L,,,,,
|
||||
5/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,A/L,,,,A/L,,,,
|
||||
6/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,A/L,,,,A/L,,,,
|
||||
7/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,,,,,A/L,,,,
|
||||
8/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,,,,,A/L,,,,
|
||||
9/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,,,,,A/L,,,,
|
||||
10/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,,,,,A/L,,,,
|
||||
11/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,,,,,A/L,,,,
|
||||
12/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,,,,,A/L,,,,
|
||||
13/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,OOPT,,A/L,,,,,,,,,,,,,,,,,,A/L,,,,
|
||||
14/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
15/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
16/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
17/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
18/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,A/L,,A/L,,A/L,,,,,,,,
|
||||
19/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,A/L,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,OOPT,A/L,,,,,,A/L,,,,,A/L,,A/L,,A/L,,,,,,,,
|
||||
20/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,A/L,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,OOPT,A/L,,,,,,A/L,,,,,A/L,,A/L,,A/L,,,,,,,,
|
||||
21/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,A/L,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,OOPT,A/L,,,,,,A/L,,,,,,,A/L,,,,,,,,,,
|
||||
22/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,A/L,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,OOPT,A/L,,,,,,A/L,,,,,,,A/L,,,,,,,,,,
|
||||
23/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,A/L,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,OOPT,A/L,,,,,,A/L,,,,,,,A/L,,,,,,,,,,
|
||||
24/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,A/L,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,OOPT,A/L,,,,,,A/L,,,,,,,A/L,,,,,,,,,,
|
||||
25/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,A/L,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,OOPT,A/L,,,,,,A/L,,,,,,,,,,,,A/L,,,,,
|
||||
26/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,A/L,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,OOPT,A/L,,,,,,A/L,,,,,,,,,,,,A/L,,,,,
|
||||
27/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,A/L,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,OOPT,A/L,,,,,,A/L,,,,,,,,,,,,A/L,,,,,
|
||||
28/2/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,OOPT,,,,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
1/3/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,,,,,,,,,,,,,,,A/L,A/L,,,,,
|
||||
2/3/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,A/L,,,,,,,,,,,,,,A/L,A/L,,,,,
|
||||
3/3/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,A/L,,,,,,A/L,,,,,,,,A/L,A/L,,,,,
|
||||
4/3/22,,,NO ONCALL,,,,Mat leave,,,,,,,,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,,,,OOPC,A/L,,,,,,,,,,,,,,A/L,A/L,,,,,
|
||||
5/3/22,,,NO ONCALL,,,,Mat leave,,,,OOPT,,,,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,,IR oncall,,OOPC,,,,,,A/L,,,,,,,,,A/L,A/L,A/L,,,,
|
||||
6/3/22,,,NO ONCALL,,,,Mat leave,,,,OOPT,,,,,,,,,A/L,,No Oncall,,,,Mat leave,,,OOPT,,IR oncall,,OOPC,,,,,,A/L,,,,,,,,,A/L,A/L,A/L,,,,
|
||||
7/3/22,,OFF ONCALL,IDT,POST ONCALL,POST ONCALL,,OOPT,,,,OOPT,,,,,,,POST ONCALL,,,,No Oncall,,POST ONCALL,,Mat leave,,POST ONCALL,OOPT,,,,OOPC,,,POST ONCALL,POST ONCALL,,A/L,,,,,A/L,POST ONCALL,,,,,A/L,,,,
|
||||
8/3/22,,OFF ONCALL,IDT,POST ONCALL,POST ONCALL,,OOPT,,,,OOPT,,,,,,,POST ONCALL,,,,No Oncall,,POST ONCALL,,Mat leave,,POST ONCALL,OOPT,,,,OOPC,,,POST ONCALL,POST ONCALL,,A/L,,,,,A/L,POST ONCALL,,,,,A/L,,,,
|
||||
9/3/22,,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,,,,,,,,,,,No Oncall,,,,Mat leave,POST ONCALL,A/L,OOPT,,,,OOPC,,,,,,A/L,,,,,A/L,,,,,,A/L,,,,
|
||||
10/3/22,,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,A/L,,,,,A/L,,,,,,A/L,,,,
|
||||
11/3/22,,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,A/L,,,,,A/L,,,,,,A/L,,,,
|
||||
12/3/22,,OFF ONCALL,IDT,POST ONCALL,,,OOPT,,,,OOPT,A/L,,,,,,POST ONCALL,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,A/L,POST ONCALL,,A/L,,,,,A/L,,A/L,,,,A/L,,,,
|
||||
13/3/22,,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,A/L,,,A/L,,,,,A/L,,A/L,,,,A/L,,,,
|
||||
14/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,A/L,,,,,,,,A/L,,A/L,,pre 2b,,,,,,
|
||||
15/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,A/L,,,,,,,,A/L,,A/L,,pre 2b,,,,,,
|
||||
16/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,A/L,,,,,,,POST ONCALL,A/L,,A/L,,pre 2b,A/L,,,,,
|
||||
17/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,,A/L,,OOPT,A/L,,,,A/L,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,A/L,,,,,,,,A/L,,A/L,,pre 2b,,,,,,
|
||||
18/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,,,,OOPT,A/L,,,,A/L,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,A/L,,,,,,,,A/L,,A/L,,pre 2b,,,,,,
|
||||
19/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,OOPT,A/L,,,,A/L,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,A/L,,,,,IR ONCALL,,,A/L,,A/L,,pre 2b,,A/L,,,,
|
||||
20/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,OOPT,A/L,,,,A/L,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,A/L,,,,,IR ONCALL,,,A/L,,A/L,,pre 2b,,A/L,,,,
|
||||
21/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,,,,,,A/L,,,,pre 2b,,A/L,,,,
|
||||
22/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,A/L,,,OOPC,,,,,,,,,,,,,,,pre 2b,,A/L,,,,
|
||||
23/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,,,,,,,,,,pre 2b,,A/L,,,,
|
||||
24/3/22,pre 2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,,,,,,,,,,pre 2b,,A/L,,,,
|
||||
25/3/22,2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,OOPT,A/L,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,,,,,,,,,,2b,,A/L,,,,
|
||||
26/3/22,2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,OOPT,A/L,,,,A/L,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,,,,,,,,,,2b,,A/L,,,,
|
||||
27/3/22,2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,OOPT,A/L,,,,A/L,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,,,,,,,,,,2b,,A/L,,,,
|
||||
28/3/22,2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,,,,,,A/L,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,,,,,,,,,,2b,,,,,,
|
||||
29/3/22,2b,OFF ONCALL,IDT,,,,OOPT,A/L,,,,,,,,A/L,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,,,,,,,,,,2b,,A/L,,,,
|
||||
30/3/22,2b,OFF ONCALL,IDT,,,,OOPT,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,,,,,,,,,,2b,,A/L,,,,
|
||||
31/3/22,2b,OFF ONCALL,IDT,,,,OOPT,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,OOPC,,,,,,,,,,,,,,,2b,,A/L,,,,
|
||||
1/4/22,2b,OFF ONCALL,IDT,,,,OOPT,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,,,,,,,,,,,,2b,,,Ramadan,A/L,,
|
||||
2/4/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,,,,A/L,,,,,,,,,,,Ramadan,A/L,,
|
||||
3/4/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,,,,A/L,,,,,,,,,,,Ramadan,A/L,,
|
||||
4/4/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,,,,,,,,,,,,,,,Ramadan,A/L,,
|
||||
5/4/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,,,,,,,,,,,,,,,Ramadan,A/L,,
|
||||
6/4/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,,,,,,,,,,,,,,,Ramadan,A/L,,
|
||||
7/4/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,S/L,,,No nights or twiligts,,,,,,,,,,,,,,,,,,Ramadan,A/L,,
|
||||
8/4/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,,,,,A/L,A/L,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,A/L,,,,,,,,,,,,,,Ramadan,A/L,,
|
||||
9/4/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,A/L,A/L,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,A/L,,,,,,,,,,,,,,Ramadan,A/L,,
|
||||
10/4/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,A/L,A/L,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,A/L,,,,,,,,,,,,,,Ramadan,A/L,,
|
||||
11/4/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,A/L,A/L,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,A/L,,,,,,,,,,,,,,Ramadan,,,
|
||||
12/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,A/L,A/L,,,A/L,,,,,,A/L,,,,,Ramadan,,,
|
||||
13/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,A/L,,,A/L,,,,,,,,,,,Ramadan,,,
|
||||
14/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,No nights or twiligts,,,,A/L,,,,,,,,,,,,,,Ramadan,,,
|
||||
15/4/22,,OFF ONCALL,IDT,,,,OOPT,,,Worked Xmas,,Worked Xmas,,A/L,,,,Worked Xmas,,,Worked Xmas,No Oncall,,,,Mat leave,Worked Xmas,,OOPT,,,Worked Xmas,No nights or twiligts,Worked Xmas,,,A/L,,,,,,Worked Xmas,,,,,,,,Ramadan,,,
|
||||
16/4/22,,OFF ONCALL,IDT,,,,OOPT,,,Worked Xmas,,Worked Xmas,,A/L,,,,Worked Xmas,,,Worked Xmas,No Oncall,,,,Mat leave,Worked Xmas,,OOPT,,,Worked Xmas,No nights,Worked Xmas,,,A/L,,,,,,Worked Xmas,,,,,,,,Ramadan,,,
|
||||
17/4/22,,OFF ONCALL,IDT,,,,OOPT,,,Worked Xmas,,Worked Xmas,,A/L,,,,Worked Xmas,,,Worked Xmas,No Oncall,,,,Mat leave,Worked Xmas,,OOPT,,,Worked Xmas,No nights,Worked Xmas,,,A/L,,,,,,Worked Xmas,,,,,,,,Ramadan,,,
|
||||
18/4/22,,OFF ONCALL,IDT,,,,OOPT,,,Worked Xmas,,Worked Xmas,,A/L,,,,Worked Xmas,,,Worked Xmas,No Oncall,,,,Mat leave,Worked Xmas,,OOPT,,A/L,Worked Xmas,No nights,Worked Xmas,,,A/L,,,,,,Worked Xmas,,,,,,,,Ramadan,,,
|
||||
19/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,A/L,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,A/L,,No nights,,,,A/L,,,A/L,,,,,,,,,A/L,,Ramadan,,,
|
||||
20/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,A/L,,,,,,,,No Oncall,,,A/L,Mat leave,,,OOPT,,A/L,,No nights,,,,A/L,,,,,,,,,,,,A/L,,Ramadan,,,
|
||||
21/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,A/L,,,,,,,,No Oncall,,,A/L,Mat leave,,,OOPT,,A/L,,No nights,,,,A/L,,,,,,,,,,,,A/L,,Ramadan,,,
|
||||
22/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,A/L,,,,,,,,No Oncall,,,A/L,Mat leave,,,OOPT,,A/L,,No nights,,,,A/L,,,,,,,,,,,,A/L,,Ramadan,,,
|
||||
23/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,A/L,A/L,,,,,,,No Oncall,,,A/L,Mat leave,,,OOPT,,A/L,,No nights,,,,A/L,,,,IR ONCALL,,,,,,,,A/L,,Ramadan,,,
|
||||
24/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,A/L,A/L,,,,,,,No Oncall,,,A/L,Mat leave,,,OOPT,,A/L,,No nights,,,,A/L,,,,IR ONCALL,,,,,,,,A/L,,Ramadan,,,
|
||||
25/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,,,,,,,,,No Oncall,,,A/L,Mat leave,,,OOPT,,,,No nights,,,,,,,,,,,,,,,,A/L,,Ramadan,,,
|
||||
26/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,,,,,,,,,No Oncall,,,A/L,Mat leave,,,OOPT,,,,No nights,,,,,,,,,,,,,,,,A/L,,Ramadan,,,
|
||||
27/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,,,,,,,,,No Oncall,,,A/L,Mat leave,,,OOPT,,S/L,,No nights,,,,,,,,,,,,,,,,A/L,,Ramadan,,,
|
||||
28/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,A/L,S/L,,No nights,,,,,,,A/L,,,,,,,,,A/L,,Ramadan,,,
|
||||
29/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,No nights,,,,,,,A/L,,,,,,,,,A/L,,Ramadan,,,
|
||||
30/4/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,,,A/L,,,,,,A/L,,,,,,,,,,,A//L,,Ramadan,,,
|
||||
1/5/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,,,A/L,,,,,,A/L,,,,,,,,,,,A//L,,Ramadan,,,
|
||||
2/5/22,,OFF ONCALL,IDT,,,,OOPT,,,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,,,A/L,,,,,,A/L,,,,,,,,,,,A//L,,Ramadan,,,
|
||||
3/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,,,A/L,,,,,,,,,,,,,,,,,A/L,,Ramadan,,,
|
||||
4/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,,,A/L,,,,,,,,,,,,,,,,,A/L,,Ramadan,,,
|
||||
5/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,,,A/L,,,,,,,,,,,,,,,,,A/L,,Ramadan,,,
|
||||
6/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,,,A/L,,,,,,,,,IR ONCALL,,,,,,,,A/L,,Ramadan,,,
|
||||
7/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,,,A/L,,,,,,,,,IR ONCALL,,,,,,,,,,Ramadan,,,
|
||||
8/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,Mat leave,,,OOPT,,,A/L,,,,,,,,,IR ONCALL,,,,,,,,,,Ramadan,,,
|
||||
9/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,A/L,,,,,,,,,,,,,,,,,,,Ramadan,,,
|
||||
10/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,A/L,,,,,,,,,,,,,,,,,,,Ramadan,,,
|
||||
11/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,A/L,,,,,,,,,,,,,,,,,,,Ramadan,,,
|
||||
12/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,A/L,,,,,,,,,,,,,,,,,,,Ramadan,,,
|
||||
13/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,A/L,,,,,,,,,,,,,,,,,,,Ramadan,,,
|
||||
14/5/22,,OFF ONCALL,IDT,,,,OOPT,A/L,A/L,,,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,OOPT,,,A/L,A/L,,,,,,,,,,,,,,A/L,,,,,,,
|
||||
15/5/22,,OFF ONCALL,IDT,,,,OOPT,A/L,A/L,,,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,OOPT,,,A/L,A/L,,,,,,,,,,,,,,A/L,,,,,,,
|
||||
16/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,A/L,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
17/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,A/L,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
18/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,A/L,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
19/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,A/L,,,,,,,,,,,,,,,,A/L,,,,,
|
||||
20/5/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,,,A/L,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,A/L,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
21/5/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,A/L,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,A/L,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
22/5/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,A/L,,,,,,,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,A/L,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
23/5/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,A/L,,,,A/L,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,IR ONCALL,,,,,,,,,,,A/L,,
|
||||
24/5/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,A/L,,,,A/L,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,IR ONCALL,,,,,,,,,,,A/L,,
|
||||
25/5/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,A/L,,,,A/L,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,IR ONCALL,,,,,,,,,,,A/L,,
|
||||
26/5/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,A/L,,,,A/L,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,IR ONCALL,,,,,,,,,,,A/L,,
|
||||
27/5/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,A/L,,,,A/L,,,No Oncall,,,A/L,Mat leave,,A/L,OOPT,,,,,,,,,,,,IR ONCALL,,,,,,,,,,,A/L,,
|
||||
28/5/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,A/L,,,,A/L,,,,A/L,,,No Oncall,,,A/L,Mat leave,,A/L,OOPT,,,,,,,A/L,,,,,,,,,,A/L,,,,,,A/L,,
|
||||
29/5/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,A/L,,,,A/L,,,,A/L,,,No Oncall,,,A/L,Mat leave,,A/L,OOPT,,,,,,,A/L,,,,,,,,,,A/L,,,,,,A/L,,
|
||||
30/5/22,Pre 2a,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,A/L,,,Pre 2a,Pre 2a,,,No Oncall,,,A/L,Mat leave,,A/L,OOPT,,A/L,,,,,,,,,,,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
31/5/22,Pre 2a,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,A/L,,,Pre 2a,Pre 2a,,,No Oncall,,,A/L,Mat leave,,A/L,OOPT,,A/L,,,,,,,,,,,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
1/6/22,Pre 2a,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,A/L,,,Pre 2a,Pre 2a,,,No Oncall,,,A/L,Mat leave,,A/L,OOPT,,A/L,,,,,,,,,,,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
2/6/22,Pre 2a,OFF ONCALL,IDT,,,A/L,OOPT,,,,,,,,A/L,,,Pre 2a,Pre 2a,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
3/6/22,Pre 2a,OFF ONCALL,IDT,,,A/L,OOPT,,,,,,,,A/L,,,Pre 2a,Pre 2a,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
4/6/22,Pre 2a,OFF ONCALL,IDT,,,,OOPT,,,,,,,,A/L,,,Pre 2a,Pre 2a,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
5/6/22,Pre 2a,OFF ONCALL,IDT,,,,OOPT,,,,,,,,A/L,,,Pre 2a,Pre 2a,,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
6/6/22,Pre 2a,OFF ONCALL,IDT,,,,OOPT,,,,,pre 2b,,,A/L,,,Pre 2a,Pre 2a,A/L,,No Oncall,,,,Mat leave,,A/L,OOPT,A/L,,,,,,,,,,,IR ONCALL,,,,,Pre 2a,,A/L,,,,Pre 2a,,Pre 2a
|
||||
7/6/22,Pre 2a,OFF ONCALL,IDT,,,,OOPT,,,,,pre 2b,,,A/L,,,Pre 2a,Pre 2a,A/L,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,IR ONCALL,,,,,Pre 2a,,A/L,,,,Pre 2a,,Pre 2a
|
||||
8/6/22,Pre 2a,OFF ONCALL,IDT,,,,OOPT,,,,,pre 2b,,,A/L,,,Pre 2a,Pre 2a,A/L,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,IR ONCALL,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
9/6/22,Pre 2a,OFF ONCALL,IDT,,,,OOPT,,A/L,,,pre 2b,,,A/L,,,Pre 2a,Pre 2a,A/L,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,IR ONCALL,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
10/6/22,Pre 2a,OFF ONCALL,IDT,,,,OOPT,,A/L,,,pre 2b,,,,,,Pre 2a,Pre 2a,A/L,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,IR ONCALL,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
11/6/22,Pre 2a,OFF ONCALL,IDT,,,,OOPT,A/L,,,,pre 2b,,,,,,Pre 2a,Pre 2a,A/L,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,IR ONCALL,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
12/6/22,Pre 2a,OFF ONCALL,IDT,,,,OOPT,A/L,,,,pre 2b,,,,,,Pre 2a,Pre 2a,A/L,,No Oncall,,,,Mat leave,,A/L,OOPT,,,,,,,,,,,,IR ONCALL,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
13/6/22,Pre 2a,OFF ONCALL,IDT,,,,OOPT,,,,,pre 2b,,,,,,Pre 2a,Pre 2a,A/L,,No Oncall,,,,Mat leave,,,OOPT,,,,,,,,,,,,,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
14/6/22,Pre 2a,OFF ONCALL,IDT,,,,OOPT,,,,,pre 2b,,,,,,Pre 2a,Pre 2a,A/L,,No Oncall,,,,Mat leave,,,OOPT,,,,,,,,,,,,,,,,,Pre 2a,,,,,,Pre 2a,,Pre 2a
|
||||
15/6/22,2a,OFF ONCALL,IDT,,,,OOPT,,,,,pre 2b,,,,,,2a,2a,A/L,,No Oncall,,,,Mat leave,,,OOPT,,,,,,,,,,,,,,,,,2a,,,,,,2a,,2a
|
||||
16/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,,pre 2b,,,A/L,,,A/L,,A/L,,No Oncall,,,,Mat leave,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
17/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,,2b,,,A/L,,,A/L,,A/L,,No Oncall,,,,Mat leave,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
18/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,2b,,,A/L,,,A/L,A/L,A/L,,No Oncall,,,,Mat leave,,,OOPT,,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
19/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,2b,,,A/L,,,A/L,A/L,A/L,,No Oncall,,,,Mat leave,,,OOPT,,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
20/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,2b,,,,,,A/L,A/L,,,No Oncall,,,,Mat leave,,,OOPT,,,,,A/L,,,,,,,,,,,,,,,A/L,,,,,
|
||||
21/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,2b,,,,,,A/L,A/L,,,No Oncall,,,,Mat leave,,,OOPT,,,,,A/L,,,,,,,,,,,,,,,A/L,,,,,
|
||||
22/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,2b,,,,,,A/L,A/L,,,No Oncall,,,,Mat leave,,,OOPT,,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
23/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,2b,,,,,,A/L,A/L,,,No Oncall,,,,Mat leave,,,OOPT,,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
24/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,2b,,,,,,A/L,A/L,,,No Oncall,,,,Mat leave,,,OOPT,,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
25/6/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,,A/L,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,OOPT,,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
26/6/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,,A/L,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,OOPT,,,,,A/L,,,,,,,,,,,,,,,,,,,,
|
||||
27/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
28/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,,,,,,,,A/L,,,No Oncall,,,,Mat leave,,,OOPT,A/L,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
29/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,A/L,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
30/6/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,,,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
1/7/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,,A/L,,,,,,,,,No Oncall,,,,Mat leave,,,OOPT,,,,,,,A/L,,,,,,,,,,A/L,,,,,,,,
|
||||
2/7/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,A/L,,,,,,,,,,A/L,,,,,,,,
|
||||
3/7/22,,OFF ONCALL,IDT,,,,OOPT,,A/L,,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,A/L,,,,,,,,,,A/L,,,,,,,,
|
||||
4/7/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,,,A/L,,,,,,,
|
||||
5/7/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,,,A/L,,,,,,,
|
||||
6/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,,,A/L,,,,,,,
|
||||
7/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
8/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
9/7/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
10/7/22,,OFF ONCALL,IDT,,,,OOPT,,,,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
11/7/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
12/7/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
13/7/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
14/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,A/L,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,,,,,,,,,,
|
||||
15/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,A/L,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,A/L,,,,,,,,,A/L,A/L,,,,,,,,
|
||||
16/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,A/L,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,A/L,,,,,,,,,A/L,A/L,,,,,,,,
|
||||
17/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,A/L,A/L,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,A/L,,,,,,,,,A/L,A/L,,,,,,,,
|
||||
18/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,A/L,,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,A/L,,,,,,,,,
|
||||
19/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,A/L,,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,A/L,,,,,,,,,
|
||||
20/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,A/L,,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,A/L,,,,,,,,,
|
||||
21/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,A/L,,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,A/L,,,,,,,,,
|
||||
22/7/22,,OFF ONCALL,IDT,,,,OOPT,A/L,,A/L,,,,,,,,,,,,No Oncall,,,,,,,OOPT,,,,,,,,,,,,,,,,A/L,,,,,,,,,
|
||||
23/7/22,,OFF ONCALL,IDT,,,A/L,OOPT,A/L,,A/L,,,,A/L,,,,,,,,No Oncall,,,,,,,OOPT,,A/L,,A/L,,,,,,,,,,,,A/L,,,A/L,,OOPT,,,,
|
||||
24/7/22,,OFF ONCALL,IDT,,,A/L,OOPT,A/L,,A/L,,,,A/L,,,,,,,,No Oncall,,,,,,,OOPT,,A/L,,A/L,,,,,,,,,,,,A/L,,,A/L,,OOPT,,,,
|
||||
25/7/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,A/L,,,,A/L,,,,,,A/L,,No Oncall,,,,,,,OOPT,,A/L,,A/L,,,,,,,,,,,,A/L,,,A/L,,OOPT,,,,
|
||||
26/7/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,A/L,,,,A/L,,,,,,A/L,,No Oncall,,,,,,,OOPT,,A/L,,A/L,,,,,,,,,,,,A/L,,,A/L,,OOPT,,,,
|
||||
27/7/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,A/L,,,,A/L,,,,,,A/L,,No Oncall,,,,,,,OOPT,,A/L,,A/L,,,,,,,,,,,,A/L,,,A/L,,OOPT,,,,
|
||||
28/7/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,A/L,,,,A/L,,,,,,A/L,,No Oncall,,,,,,,OOPT,,A/L,,A/L,,,,,,,,,,,,A/L,,,A/L,,OOPT,,,,
|
||||
29/7/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,A/L,,,,A/L,,,,,,A/L,,No Oncall,,,,,,,OOPT,,A/L,,A/L,,,,,,,,,A/L,,,A/L,,,A/L,,OOPT,,,,
|
||||
30/7/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,A/L,,,,A/L,,,,,,A/L,,No Oncall,,A/L,,,,,OOPT,,A/L,,A/L,,,,,,,,,A/L,,A/L,A/L,,,A/L,,OOPT,,,,
|
||||
31/7/22,,OFF ONCALL,IDT,,,A/L,OOPT,,,A/L,,,,A/L,,,,,,A/L,,No Oncall,,A/L,,,,,OOPT,,A/L,,A/L,,,,,,,,,A/L,,A/L,A/L,,,A/L,,OOPT,,,,
|
||||
1/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,A/L,,No Oncall,,A/L,,,,,OOPT,,A/L,,,,,,,,,,,,,A/L,,,,A/L,OOPE,OOPT,,,,
|
||||
2/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,A/L,,No Oncall,,A/L,,,,,OOPT,,A/L,,,,,,,,,,,,,A/L,,,,A/L,OOPE,OOPT,,,,
|
||||
3/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,A/L,,No Oncall,,A/L,,,,,OOPT,,A/L,,,,,,,,,,,,,A/L,,,,A/L,OOPE,OOPT,,,,
|
||||
4/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,A/L,,No Oncall,,A/L,,,,,OOPT,,A/L,,,,,,,,,,,,,A/L,,,,A/L,OOPE,OOPT,,,,
|
||||
5/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,A/L,,No Oncall,,A/L,,,,,OOPT,,A/L,,,,,A/L,,,,,,,,A/L,,A/L,,A/L,OOPE,OOPT,,A/L,,
|
||||
6/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,A/L,,No Oncall,,A/L,,,,,OOPT,,A/L,,,,,A/L,,,,,,,,A/L,,A/L,,A/L,OOPE,OOPT,,A/L,,
|
||||
7/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,A/L,,No Oncall,,A/L,,,,,OOPT,,A/L,,,,,A/L,,,,,,,,A/L,,A/L,,A/L,OOPE,OOPT,,A/L,,
|
||||
8/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,,,,,,,,,A/L,OOPE,OOPT,,A/L,,
|
||||
9/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,,,,,,,,,A/L,OOPE,OOPT,,A/L,,
|
||||
10/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,,,,,,,,,,OOPE,OOPT,,A/L,,
|
||||
11/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,,,,,,,,,,OOPE,OOPT,,A/L,,
|
||||
12/8/22,,OFF ONCALL,IDT,,,,OOPT,,,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,,,,,,,,,,OOPE,OOPT,,A/L,,
|
||||
13/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,A/L,,,,,,,,,OOPE,OOPT,,A/L,,
|
||||
14/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,A/L,,,,,,,,,OOPE,OOPT,,A/L,,
|
||||
15/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,A/L,,,,,,,,,OOPE,OOPT,,,,
|
||||
16/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,A/L,,,,,,,,,OOPE,OOPT,,,,
|
||||
17/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,A/L,,,,,,,,,OOPE,OOPT,,,,
|
||||
18/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,A/L,,,,,,,,,OOPE,OOPT,,,,
|
||||
19/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,A/L,,,,,,,,,OOPE,OOPT,,,,
|
||||
20/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,A/L,,,,,,,,,OOPE,OOPT,,,,
|
||||
21/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,A/L,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,A/L,,,,,,,,,OOPE,OOPT,,,,
|
||||
22/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,A/L,,,,,,,,,,,A/L,OOPE,OOPT,,,,
|
||||
23/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,A/L,,,A/L,,,,,,,,A/L,OOPE,OOPT,,,,
|
||||
24/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,A/L,,,A/L,,,,,,,,A/L,OOPE,OOPT,,,,
|
||||
25/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,A/L,,,,,,,,,,,A/L,OOPE,OOPT,,,,
|
||||
26/8/22,,OFF ONCALL,IDT,,,A/L,OOPT,,A/L,,,,A/L,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,A/L,,,A/L,,,,,,,,A/L,OOPE,OOPT,,,,
|
||||
27/8/22,,OFF ONCALL,IDT,A/L,,,OOPT,,A/L,,,,A/L,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,,,,,,,,,A/L,OOPE,OOPT,,,,
|
||||
28/8/22,,OFF ONCALL,IDT,A/L,,,OOPT,,A/L,,,,A/L,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,,,,,,,,,A/L,OOPE,OOPT,,,,
|
||||
29/8/22,,OFF ONCALL,IDT,A/L,,,OOPT,,A/L,,,,A/L,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,,,,,,,,,,,,A/L,OOPE,OOPT,,,,
|
||||
30/8/22,,OFF ONCALL,IDT,A/L,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,A/L,,,,,,A/L,,,A/L,,,,,,,,,OOPE,OOPT,,,,
|
||||
31/8/22,,OFF ONCALL,IDT,A/L,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,A/L,,,,,,A/L,,,A/L,,,,,,,,,OOPE,OOPT,,,,
|
||||
1/9/22,,OFF ONCALL,IDT,A/L,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,A/L,,,,,,A/L,,,,,,,,,,,A/L,OOPE,OOPT,,,,
|
||||
2/9/22,,OFF ONCALL,IDT,A/L,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,A/L,,,,,,,,,,,A/L,OOPE,OOPT,,,,
|
||||
3/9/22,,OFF ONCALL,IDT,A/L,,,OOPT,,A/L,,,,,,,,,,,,,No Oncall,,A/L,,,,,OOPT,,,,,,,,A/L,,,,,,,,,,,,OOPE,OOPT,,,,
|
||||
4/9/22,,OFF ONCALL,IDT,A/L,,,OOPT,,A/L,,OFF,,,,,,,,,,,No Oncall,,A/L,,,OFF,OFF,OOPT,OFF,,,,,,,A/L,,,,OFF,,,,,,,,OOPE,OOPT,OFF,,,
|
||||
|
@@ -33,7 +33,7 @@ def load_leave(Rota):
|
||||
|
||||
if live_rota:
|
||||
download = s.get(
|
||||
"https://docs.google.com/spreadsheets/d/e/2PACX-1vQtNrp9F1fjtAh8vFX-W_R3RlD9H-2P_vCLMoHrVKR0e24yabbsdrD65VjwxoHlV1Qnatmw1NeghQHG/pub?gid=1199196967&single=true&output=csv",
|
||||
"https://docs.google.com/spreadsheets/d/e/2PACX-1vSh64r9uUbPsCdhcV7zmZz10gVqIy6rhXFUvDgJxRD5W45IYEaZpKMxAqDsD1ph6dob4AiRzJXVVtOu/pub?gid=580824586&single=true&output=csv",
|
||||
headers=headers,
|
||||
)
|
||||
# download = s.get("https://docs.google.com/spreadsheets/d/e/2PACX-1vSRx9VWXSlRubPyA0RhiI-Oqf5eHNYYEc6rFzlraDbR5_8qqr5g13-4uV-gn4u-TjZxiSMv1fBUaESq/pub?gid=814517272&single=true&output=csv", headers=headers)
|
||||
@@ -55,10 +55,12 @@ def load_leave(Rota):
|
||||
n = 1
|
||||
for row in reader:
|
||||
if n < 10:
|
||||
print(row)
|
||||
pass
|
||||
#print(row)
|
||||
row_title = row[0]
|
||||
print(row_title)
|
||||
#print(row_title)
|
||||
row_date = row[0]
|
||||
row_extra = row[1]
|
||||
r = row[2:]
|
||||
# header, extract names
|
||||
if n == 1:
|
||||
@@ -210,6 +212,29 @@ def load_leave(Rota):
|
||||
if lower_item:
|
||||
worker["bank_holiday_extra"] = int(lower_item)
|
||||
|
||||
elif row_title == "Weekends/Twilights":
|
||||
if lower_item:
|
||||
worker["site"] = lower_item
|
||||
|
||||
|
||||
#elif row_title == "Academy":
|
||||
# pass
|
||||
#elif "night specific" in row_title:
|
||||
# if lower_item:
|
||||
# for shift in lower_item.split(","):
|
||||
# shift = shift.strip()
|
||||
# if shift == "nights":
|
||||
# shift = "night_weekday"
|
||||
# elif shift == "nights only":
|
||||
# if date.weekday() < 5:
|
||||
# shift = "night_weekday"
|
||||
# else:
|
||||
# shift = "night_weekend"
|
||||
# worker["single_nights"].append(
|
||||
# WorkRequests(date=date, shift=shift)
|
||||
# )
|
||||
|
||||
|
||||
elif re.match(date_re, row_date) is not None:
|
||||
if lower_item != "":
|
||||
try:
|
||||
@@ -258,70 +283,79 @@ def load_leave(Rota):
|
||||
return workers
|
||||
|
||||
|
||||
# load_leave()
|
||||
|
||||
def load_academy(Rota):
|
||||
"""Load academy month blocks from the main published CSV and register avoids.
|
||||
|
||||
Spreadsheet layout expected:
|
||||
- Column A: row title (should be 'Academy' for relevant rows)
|
||||
- Column B: start date for the month (e.g. '07/09/2026')
|
||||
- Columns C...: worker columns matching the header row (first CSV row's columns C...)
|
||||
Cells containing the word 'academy' (case-insensitive) mark that worker as being in academy
|
||||
for that month.
|
||||
"""
|
||||
Some trainees spend month blocks in the academy. This function loads that information from a google sheet that published it as a csv file.
|
||||
"""
|
||||
url = "https://docs.google.com/spreadsheets/d/e/2PACX-1vQtNrp9F1fjtAh8vFX-W_R3RlD9H-2P_vCLMoHrVKR0e24yabbsdrD65VjwxoHlV1Qnatmw1NeghQHG/pub?gid=1113837782&single=true&output=csv"
|
||||
# Use the same published CSV as load_leave (main sheet)
|
||||
url = (
|
||||
"https://docs.google.com/spreadsheets/d/e/2PACX-1vSh64r9uUbPsCdhcV7zmZz10gVqIy6rhXFUvDgJxRD5W45IYEaZpKMxAqDsD1ph6dob4AiRzJXVVtOu/pub?gid=580824586&single=true&output=csv"
|
||||
)
|
||||
|
||||
with Session() as s:
|
||||
headers = {"Cache-Control": "no-cache", "Pragma": "no-cache"}
|
||||
download = s.get(url, headers=headers)
|
||||
decoded = download.content.decode("utf-8")
|
||||
|
||||
reader = csv.reader(decoded.splitlines(), delimiter=',')
|
||||
reader = csv.reader(decoded.splitlines(), delimiter=",")
|
||||
|
||||
names = []
|
||||
month_starts: list[datetime.date] = []
|
||||
month_cells: list[list[str]] = []
|
||||
|
||||
n = 0
|
||||
names = []
|
||||
# collect date rows (these are the month start boundaries) and their cells
|
||||
date_rows: list[datetime.date] = []
|
||||
date_row_cells: list[list[str]] = []
|
||||
|
||||
for row in reader:
|
||||
n += 1
|
||||
if n == 1:
|
||||
# header row: first cell is column title, remaining are worker names
|
||||
names = [c.strip() for c in row[1:]]
|
||||
continue
|
||||
|
||||
if not row:
|
||||
continue
|
||||
|
||||
row_title = row[0].strip()
|
||||
cells = row[1:]
|
||||
# header row: collect worker names from column C onwards
|
||||
if n == 1:
|
||||
# some files may have two leading columns (like 'Email Address' and blank/extra), so take from index 2
|
||||
names = [c.strip() for c in (row[2:] if len(row) > 2 else row[1:])]
|
||||
continue
|
||||
|
||||
# If this row is a date row like '02/03/2026' then treat it as a month-start boundary
|
||||
if re.match(date_re, row_title):
|
||||
row_title = row[0].strip() if len(row) > 0 else ""
|
||||
start_cell = row[1].strip() if len(row) > 1 else ""
|
||||
cells = row[2:] if len(row) > 2 else []
|
||||
|
||||
# Only treat rows where the first column mentions 'Academy' (some sheets may include other rows)
|
||||
if row_title and "academy" in row_title.lower():
|
||||
# parse start date
|
||||
try:
|
||||
try:
|
||||
d = datetime.strptime(row_title, "%d/%m/%Y").date()
|
||||
start_date = datetime.strptime(start_cell, "%d/%m/%Y").date()
|
||||
except ValueError:
|
||||
d = datetime.strptime(row_title, "%d/%m/%y").date()
|
||||
start_date = datetime.strptime(start_cell, "%d/%m/%y").date()
|
||||
except Exception:
|
||||
# skip rows without a valid date
|
||||
continue
|
||||
|
||||
date_rows.append(d)
|
||||
# normalize cells length to match names
|
||||
row_cells = [ (cells[i] if i < len(cells) else "").strip() for i in range(len(names)) ]
|
||||
date_row_cells.append(row_cells)
|
||||
month_starts.append(start_date)
|
||||
# normalize cell list to names length
|
||||
row_cells = [(cells[i] if i < len(cells) else "").strip() for i in range(len(names))]
|
||||
month_cells.append(row_cells)
|
||||
|
||||
# If no date rows found, nothing to do
|
||||
if not date_rows:
|
||||
if not month_starts:
|
||||
return
|
||||
|
||||
# Identify twilight shift names available in the rota
|
||||
# determine twilight shifts available
|
||||
twilight_shift_names = [s.name for s in Rota.get_shifts() if "twilight" in s.name]
|
||||
|
||||
# For each date boundary, compute its end (next boundary - 1 day) and register avoids where cell=='academy'
|
||||
for idx, start_date in enumerate(date_rows):
|
||||
if idx + 1 < len(date_rows):
|
||||
end_date = date_rows[idx + 1] - timedelta(days=1)
|
||||
# for each month start, compute end date and register avoids
|
||||
for idx, start_date in enumerate(month_starts):
|
||||
if idx + 1 < len(month_starts):
|
||||
end_date = month_starts[idx + 1] - timedelta(days=1)
|
||||
else:
|
||||
end_date = Rota.rota_end_date
|
||||
|
||||
cells = date_row_cells[idx]
|
||||
cells = month_cells[idx]
|
||||
for i, cell in enumerate(cells):
|
||||
if i >= len(names):
|
||||
break
|
||||
@@ -341,7 +375,7 @@ def load_academy(Rota):
|
||||
sh = Rota.get_shift_by_name(sh_name)
|
||||
except Exception:
|
||||
continue
|
||||
if hasattr(worker, "site") and worker.site in sh.sites:
|
||||
if hasattr(worker, "site") and worker.site in getattr(sh, "sites", []):
|
||||
candidate_shifts.append(sh_name)
|
||||
elif sh_name.startswith(f"{worker.site}_"):
|
||||
candidate_shifts.append(sh_name)
|
||||
@@ -349,7 +383,12 @@ def load_academy(Rota):
|
||||
if not candidate_shifts:
|
||||
candidate_shifts = twilight_shift_names.copy()
|
||||
|
||||
# register as a date range with a reason 'academy'
|
||||
#print(f"Academy: {worker.name} on academy from {start_date} to {end_date}, avoiding shifts: {candidate_shifts}")
|
||||
|
||||
Rota.add_avoid_shifts_for_workers_on_dates(
|
||||
names=[worker.name], shifts=candidate_shifts, start_date=start_date, end_date=end_date, reason="academy"
|
||||
names=[worker.name],
|
||||
shifts=candidate_shifts,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
reason="academy",
|
||||
)
|
||||
@@ -445,9 +445,19 @@ $("#rota-table tr td:first-child").each((n, td) => {
|
||||
|
||||
function viewWorker(worker) {
|
||||
ds = worker.dataset;
|
||||
let exactShiftsText = "(none)";
|
||||
if (ds.exactShifts) {
|
||||
try {
|
||||
let parsed = JSON.parse(ds.exactShifts);
|
||||
if (Object.keys(parsed).length > 0) {
|
||||
exactShiftsText = Object.entries(parsed).map(([s, c]) => `${s}: ${c}`).join(", ");
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
dlg = $(`<div class='dialog' title='${ds.worker}'>
|
||||
Site: ${ds.site}</br>
|
||||
FTE: ${ds.fte} (${ds.fte_adj})</br>
|
||||
Exact shifts: ${exactShiftsText}</br>
|
||||
Start date: ${ds.start_date}</br>
|
||||
End date: ${ds.end_date}</br>
|
||||
Non working days: ${ds.nwds}</br>
|
||||
|
||||
+207
-1
@@ -1026,6 +1026,8 @@ $("table.tsummary").find("td.prior-adjust .prior-adjust-part").each((n, span) =>
|
||||
let selectedShifts = new Set();
|
||||
|
||||
let shiftColors = {};
|
||||
let rotaExportViewEnabled = false;
|
||||
let shiftDisplayOrder = [...oshifts];
|
||||
|
||||
oshifts.forEach((shift, i) => {
|
||||
selectedShifts.add(shift);
|
||||
@@ -1038,6 +1040,21 @@ $("#shift-timetable-options").append(
|
||||
`<button id="toggle-grid-view" style="margin-left:10px;">Toggle Linear/Grid View</button><br/>`
|
||||
);
|
||||
|
||||
// Add a button to toggle rota export view (spreadsheet-style day-by-day shifts)
|
||||
$("#shift-timetable-options").append(
|
||||
`<button id="toggle-rota-export-view" style="margin-left:10px;">Show Rota Export View</button>`
|
||||
);
|
||||
|
||||
// Add optional control to show worker names in rota export view
|
||||
$("#shift-timetable-options").append(`
|
||||
<span id="rota-export-show-workers-wrap" style="margin-left:12px;">
|
||||
<label>
|
||||
<input type="checkbox" id="rota-export-show-workers" checked>
|
||||
Show workers
|
||||
</label>
|
||||
</span><br/>
|
||||
`);
|
||||
|
||||
let gridViewEnabled = false;
|
||||
|
||||
$("#toggle-grid-view").on("click", function () {
|
||||
@@ -1045,6 +1062,22 @@ $("#toggle-grid-view").on("click", function () {
|
||||
renderShiftTimetable();
|
||||
});
|
||||
|
||||
$("#toggle-rota-export-view").on("click", function () {
|
||||
rotaExportViewEnabled = !rotaExportViewEnabled;
|
||||
if (rotaExportViewEnabled) {
|
||||
$(this).text("Show Linear/Grid View");
|
||||
} else {
|
||||
$(this).text("Show Rota Export View");
|
||||
}
|
||||
renderShiftTimetable();
|
||||
});
|
||||
|
||||
$(document).on("change", "#rota-export-show-workers", function () {
|
||||
if (rotaExportViewEnabled) {
|
||||
renderShiftTimetable();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const shiftColourControls = $(`
|
||||
<details><summary>Shift Timetable Colours</summary>
|
||||
@@ -1272,6 +1305,12 @@ function renderShiftTimetable() {
|
||||
console.log(workerFilterSet)
|
||||
$("#shift-timetable-div").empty();
|
||||
if (selectedShifts.size === 0) return;
|
||||
|
||||
if (rotaExportViewEnabled) {
|
||||
renderRotaExportTimetable();
|
||||
return;
|
||||
}
|
||||
|
||||
if (workerFilterSet.size === 0) return;
|
||||
|
||||
// Helper: returns true if worker id matches filter set or set is empty (all)
|
||||
@@ -1445,6 +1484,127 @@ function renderShiftTimetable() {
|
||||
$("#shift-timetable-div").append(gridTable);
|
||||
}
|
||||
|
||||
function formatDateForExport(dateStr) {
|
||||
if (!dateStr) return "";
|
||||
let d = new Date(dateStr);
|
||||
if (Number.isNaN(d.getTime())) return dateStr;
|
||||
let dd = String(d.getDate()).padStart(2, "0");
|
||||
let mm = String(d.getMonth() + 1).padStart(2, "0");
|
||||
let yyyy = d.getFullYear();
|
||||
return `${dd}/${mm}/${yyyy}`;
|
||||
}
|
||||
|
||||
function getShiftHeaderLabel(shift) {
|
||||
let s = (shift || "").toLowerCase();
|
||||
if (s.includes("twil")) return "TWIL";
|
||||
if (s.includes("night") || s.includes("oncall")) return "NIGHT";
|
||||
if (s.includes("weekend")) return "WEEKEND";
|
||||
return (shift || "").toUpperCase();
|
||||
}
|
||||
|
||||
function renderRotaExportTimetable() {
|
||||
let weeks = [];
|
||||
let daysOfWeek = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
||||
let weekStartDates = {};
|
||||
let showWorkers = $("#rota-export-show-workers").is(":checked");
|
||||
|
||||
// Build week index and week start dates from existing table data
|
||||
$("table#main-table td[data-week][data-date]").each(function () {
|
||||
let week = $(this).attr("data-week");
|
||||
let date = $(this).attr("data-date");
|
||||
if (!week || !date) return;
|
||||
if (!weeks.includes(week)) weeks.push(week);
|
||||
if (!weekStartDates[week] || new Date(date) < new Date(weekStartDates[week])) {
|
||||
weekStartDates[week] = date;
|
||||
}
|
||||
});
|
||||
|
||||
weeks = weeks.sort((a, b) => parseInt(a) - parseInt(b));
|
||||
|
||||
const orderedSelectedShifts = shiftDisplayOrder.filter(s => selectedShifts.has(s));
|
||||
const dayShiftOrder = {};
|
||||
daysOfWeek.forEach(day => {
|
||||
dayShiftOrder[day] = orderedSelectedShifts.filter(shift => {
|
||||
let found = false;
|
||||
$(`table#main-table td[data-day='${day}']`).each(function () {
|
||||
let shifts = ($(this).attr("data-shift") || "").split(",").map(x => x.trim()).filter(Boolean);
|
||||
if (shifts.includes(shift)) {
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return found;
|
||||
});
|
||||
});
|
||||
|
||||
let table = $(`<table id="rota-export-table" class="shift-grid-table" style="margin-bottom:20px; border-collapse:collapse; width:auto; table-layout:fixed;"><caption>Rota Export (Shifts by Day)</caption></table>`);
|
||||
let headerTop = $("<tr></tr>");
|
||||
headerTop.append("<th style='border:1px solid #888; min-width:130px;'>Week Start</th>");
|
||||
headerTop.append("<th style='border:1px solid #888; min-width:70px;'>Week</th>");
|
||||
daysOfWeek.forEach(day => {
|
||||
let span = Math.max(1, dayShiftOrder[day].length);
|
||||
headerTop.append(`<th style='border:1px solid #888; min-width:120px;' colspan='${span}'>${day.toUpperCase()}</th>`);
|
||||
});
|
||||
table.append(headerTop);
|
||||
|
||||
let headerBottom = $("<tr></tr>");
|
||||
headerBottom.append("<th style='border:1px solid #888; min-width:130px;'></th>");
|
||||
headerBottom.append("<th style='border:1px solid #888; min-width:70px;'>No</th>");
|
||||
daysOfWeek.forEach(day => {
|
||||
if (dayShiftOrder[day].length === 0) {
|
||||
headerBottom.append("<th style='border:1px solid #888; min-width:120px;'></th>");
|
||||
return;
|
||||
}
|
||||
dayShiftOrder[day].forEach(shift => {
|
||||
headerBottom.append(`<th style='border:1px solid #888; min-width:120px;'>${getShiftHeaderLabel(shift)}</th>`);
|
||||
});
|
||||
});
|
||||
table.append(headerBottom);
|
||||
|
||||
weeks.forEach(week => {
|
||||
let row = $("<tr></tr>");
|
||||
let startDateFormatted = formatDateForExport(weekStartDates[week]);
|
||||
row.append(`<td style='border:1px solid #888; font-weight:bold; vertical-align:top;'>${startDateFormatted}</td>`);
|
||||
row.append(`<td style='border:1px solid #888; text-align:center; font-weight:bold; vertical-align:top;'>${week}</td>`);
|
||||
|
||||
daysOfWeek.forEach(day => {
|
||||
const dayShifts = dayShiftOrder[day];
|
||||
if (dayShifts.length === 0) {
|
||||
row.append(`<td style='border:1px solid #888; color:#999; text-align:center;'>-</td>`);
|
||||
return;
|
||||
}
|
||||
|
||||
dayShifts.forEach(shift => {
|
||||
let workersForShift = new Set();
|
||||
$(`table#main-table td[data-week='${week}'][data-day='${day}']`).each(function () {
|
||||
let shifts = ($(this).attr("data-shift") || "").split(",").map(s => s.trim()).filter(Boolean);
|
||||
if (!shifts.includes(shift)) return;
|
||||
let workerName = $(this).closest("tr").find("td.worker span.name").text().trim();
|
||||
if (workerName) workersForShift.add(workerName);
|
||||
});
|
||||
|
||||
let workerList = Array.from(workersForShift).sort();
|
||||
let text = "";
|
||||
if (showWorkers) {
|
||||
text = workerList.join("/");
|
||||
} else {
|
||||
text = workerList.length > 0 ? "X" : "";
|
||||
}
|
||||
|
||||
row.append(`<td style='border:1px solid #888; vertical-align:top; text-align:center;'>${text}</td>`);
|
||||
});
|
||||
});
|
||||
|
||||
table.append(row);
|
||||
});
|
||||
|
||||
if (weeks.length === 0) {
|
||||
table.append(`<tr><td colspan="16" style="border:1px solid #888; text-align:center; color:#666;">No rota data found.</td></tr>`);
|
||||
}
|
||||
|
||||
$("#shift-timetable-div").append(table);
|
||||
}
|
||||
|
||||
// Re-render worker checkboxes after timetable is updated (in case workers change)
|
||||
function rerenderWorkerCheckboxesIfNeeded() {
|
||||
$("#shift-timetable-worker-filter").remove();
|
||||
@@ -1462,7 +1622,7 @@ rerenderWorkerCheckboxesIfNeeded();
|
||||
|
||||
// Patch the timetable button logic to use renderShiftTimetable
|
||||
oshifts.forEach((shift) => {
|
||||
let button = $(`<button data-shift='${shift}'>${shift}</button>`);
|
||||
let button = $(`<button data-shift='${shift}' draggable='true' title='Drag to reorder shifts'>${shift}</button>`);
|
||||
button.css({
|
||||
background: "",
|
||||
border: "1px solid #888",
|
||||
@@ -1487,6 +1647,42 @@ oshifts.forEach((shift) => {
|
||||
$("#shift-timetable-options").append(button);
|
||||
});
|
||||
|
||||
let draggedShiftButton = null;
|
||||
|
||||
$(document).on("dragstart", "#shift-timetable-options button[data-shift]", function (e) {
|
||||
draggedShiftButton = this;
|
||||
if (e.originalEvent && e.originalEvent.dataTransfer) {
|
||||
e.originalEvent.dataTransfer.effectAllowed = "move";
|
||||
}
|
||||
});
|
||||
|
||||
$(document).on("dragover", "#shift-timetable-options button[data-shift]", function (e) {
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
$(document).on("drop", "#shift-timetable-options button[data-shift]", function (e) {
|
||||
e.preventDefault();
|
||||
if (!draggedShiftButton || draggedShiftButton === this) return;
|
||||
|
||||
const $dragged = $(draggedShiftButton);
|
||||
const $target = $(this);
|
||||
const draggedIndex = $dragged.index();
|
||||
const targetIndex = $target.index();
|
||||
|
||||
if (draggedIndex < targetIndex) {
|
||||
$target.after($dragged);
|
||||
} else {
|
||||
$target.before($dragged);
|
||||
}
|
||||
|
||||
shiftDisplayOrder = $("#shift-timetable-options button[data-shift]").map(function () {
|
||||
return $(this).attr("data-shift");
|
||||
}).get();
|
||||
|
||||
renderShiftTimetable();
|
||||
draggedShiftButton = null;
|
||||
});
|
||||
|
||||
|
||||
// Add help text to the shift timetable section
|
||||
$("#shift-timetable-options").prepend(`
|
||||
@@ -1587,9 +1783,19 @@ $("#rota-table tr td:first-child").each((n, td) => {
|
||||
|
||||
function viewWorker(worker) {
|
||||
ds = worker.dataset;
|
||||
let exactShiftsText = "(none)";
|
||||
if (ds.exactShifts) {
|
||||
try {
|
||||
let parsed = JSON.parse(ds.exactShifts);
|
||||
if (Object.keys(parsed).length > 0) {
|
||||
exactShiftsText = Object.entries(parsed).map(([s, c]) => `${s}: ${c}`).join(", ");
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
dlg = $(`<div class='dialog' title='${ds.worker}'>
|
||||
Site: ${ds.site}</br>
|
||||
FTE: ${ds.fte} (${ds.fte_adj})</br>
|
||||
Exact shifts: ${exactShiftsText}</br>
|
||||
Start date: ${ds.start_date}</br>
|
||||
End date: ${ds.end_date}</br>
|
||||
Non working days: ${ds.nwds}</br>
|
||||
|
||||
+105
-71
@@ -2648,89 +2648,102 @@ class RotaBuilder(object):
|
||||
):
|
||||
if (
|
||||
worker.site in shift.sites
|
||||
): # Each site specfies which sites self.workers can fullfill it
|
||||
# calaculate the total number of shifts that need assigning over the rota
|
||||
# total_shifts = (len(self.weeks) * len(shift.days) *
|
||||
# TODO: check if shift_counts is still required?
|
||||
# total_shifts = (
|
||||
# self.shift_counts[shift.name] * shift.workers_required
|
||||
# )
|
||||
): # Each site specfies which sites self.workers can fullfill
|
||||
total_shifts = self.shift_worker_counts[shift.name]
|
||||
|
||||
full_time_equivalent_joined = sum(
|
||||
self.full_time_equivalent_sites[i][shift.name]
|
||||
for i in shift.sites
|
||||
)
|
||||
# Find workers who have exact shifts for this shift type
|
||||
exact_workers = [
|
||||
w for w in self.workers
|
||||
if w.site in shift.sites and shift.name in getattr(w, "exact_shifts", {})
|
||||
]
|
||||
exact_shifts_sum = sum(w.exact_shifts[shift.name] for w in exact_workers)
|
||||
|
||||
# Guard against division by zero: if no full-time
|
||||
# equivalent is available for the shift's sites the
|
||||
# denominator may be zero (for example when all site
|
||||
# FTEs are 0). In that case emit a warning and set the
|
||||
# target to 0 rather than raising an exception.
|
||||
if not full_time_equivalent_joined:
|
||||
# avoid ZeroDivisionError
|
||||
self.add_warning(
|
||||
"Zero FTE for sites",
|
||||
f"full_time_equivalent sum is zero for shift {shift.name}; setting target_shifts=0 for worker {worker.name}",
|
||||
)
|
||||
target_shifts = 0
|
||||
if exact_shifts_sum > total_shifts:
|
||||
warning_msg = f"For shift {shift.name}, total exact shifts ({exact_shifts_sum}) exceeds total required shifts ({total_shifts})."
|
||||
if not any(warn[1] == warning_msg for warn in self.warnings):
|
||||
self.add_warning("Exact shifts exceed total shifts", warning_msg)
|
||||
|
||||
if worker in exact_workers:
|
||||
target_shifts = worker.exact_shifts[shift.name]
|
||||
else:
|
||||
target_shifts = (
|
||||
total_shifts
|
||||
/ full_time_equivalent_joined
|
||||
* worker.get_fte(shift=shift.name)
|
||||
remaining_shifts = total_shifts - exact_shifts_sum
|
||||
# Sum FTE only for workers who do not have exact shifts for this shift type
|
||||
full_time_equivalent_joined = sum(
|
||||
w.get_fte(shift=shift.name)
|
||||
for w in self.workers
|
||||
if w.site in shift.sites and w not in exact_workers
|
||||
)
|
||||
|
||||
if self.use_previous_shifts:
|
||||
if shift.name in worker.previous_shifts:
|
||||
entry = worker.previous_shifts[shift.name]
|
||||
# entry may be a tuple (worked, allocated) or a dict of per-day tuples
|
||||
if isinstance(entry, dict):
|
||||
# prefer overall tuple if present
|
||||
if "__all__" in entry:
|
||||
try:
|
||||
worked = float(entry["__all__"][0])
|
||||
allocated = float(entry["__all__"][1])
|
||||
target_shifts = target_shifts + allocated - worked
|
||||
except Exception:
|
||||
pass
|
||||
if not full_time_equivalent_joined:
|
||||
# avoid ZeroDivisionError
|
||||
if remaining_shifts > 0:
|
||||
self.add_warning(
|
||||
"Zero FTE for sites with remaining shifts",
|
||||
f"full_time_equivalent sum is zero for shift {shift.name} but remaining_shifts = {remaining_shifts}; setting target_shifts=0 for worker {worker.name}",
|
||||
)
|
||||
target_shifts = 0
|
||||
else:
|
||||
target_shifts = (
|
||||
max(0.0, float(remaining_shifts))
|
||||
/ full_time_equivalent_joined
|
||||
* worker.get_fte(shift=shift.name)
|
||||
)
|
||||
|
||||
if worker not in exact_workers:
|
||||
if self.use_previous_shifts:
|
||||
if shift.name in worker.previous_shifts:
|
||||
entry = worker.previous_shifts[shift.name]
|
||||
# entry may be a tuple (worked, allocated) or a dict of per-day tuples
|
||||
if isinstance(entry, dict):
|
||||
# prefer overall tuple if present
|
||||
if "__all__" in entry:
|
||||
try:
|
||||
worked = float(entry["__all__"][0])
|
||||
allocated = float(entry["__all__"][1])
|
||||
target_shifts = target_shifts + allocated - worked
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
# sum any per-day entries
|
||||
try:
|
||||
total_worked = 0.0
|
||||
total_alloc = 0.0
|
||||
for v in entry.values():
|
||||
total_worked += float(v[0])
|
||||
total_alloc += float(v[1])
|
||||
target_shifts = target_shifts + total_alloc - total_worked
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
# sum any per-day entries
|
||||
try:
|
||||
total_worked = 0.0
|
||||
total_alloc = 0.0
|
||||
for v in entry.values():
|
||||
total_worked += float(v[0])
|
||||
total_alloc += float(v[1])
|
||||
target_shifts = target_shifts + total_alloc - total_worked
|
||||
worked, allocated = entry
|
||||
target_shifts = (
|
||||
target_shifts + float(allocated) - float(worked)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
worked, allocated = entry
|
||||
target_shifts = (
|
||||
target_shifts + float(allocated) - float(worked)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.use_shift_balance_extra:
|
||||
if shift.name in worker.shift_balance_extra:
|
||||
extra = worker.shift_balance_extra[shift.name]
|
||||
if self.use_shift_balance_extra:
|
||||
if shift.name in worker.shift_balance_extra:
|
||||
extra = worker.shift_balance_extra[shift.name]
|
||||
|
||||
# TODO look at how this affects allocation (how does it affect fte)
|
||||
match extra:
|
||||
case "double":
|
||||
target_shifts = target_shifts * 2
|
||||
case "half":
|
||||
target_shifts = target_shifts / 2
|
||||
case _:
|
||||
target_shifts = target_shifts + extra
|
||||
# TODO look at how this affects allocation (how does it affect fte)
|
||||
match extra:
|
||||
case "double":
|
||||
target_shifts = target_shifts * 2
|
||||
case "half":
|
||||
target_shifts = target_shifts / 2
|
||||
case _:
|
||||
target_shifts = target_shifts + extra
|
||||
|
||||
# print(worker.name, shift.name, target_shifts)
|
||||
worker.shift_target_number[shift.name] = target_shifts
|
||||
|
||||
if shift.hard_constrain_shift:
|
||||
if worker in exact_workers:
|
||||
self.model.constraints.add(
|
||||
self.model.shift_count[worker.id, shift.name] == target_shifts
|
||||
)
|
||||
elif shift.hard_constrain_shift:
|
||||
adjusted_balance_offset = shift.balance_offset
|
||||
if worker.get_fte(shift=shift.name) < 100:
|
||||
adjusted_balance_offset = (
|
||||
@@ -4244,6 +4257,9 @@ class RotaBuilder(object):
|
||||
|
||||
Must be called prior to attempting to solve
|
||||
"""
|
||||
if not hasattr(self, "shifts_by_name"):
|
||||
self.build_shifts()
|
||||
|
||||
if not self.workers:
|
||||
raise NoWorkers("Workers must be added prior to calling build_workers")
|
||||
|
||||
@@ -4253,6 +4269,19 @@ class RotaBuilder(object):
|
||||
for worker in track(self.workers, description="Building workers"):
|
||||
print(worker.name)
|
||||
worker.load_rota(self)
|
||||
for s_name in getattr(worker, "exact_shifts", {}):
|
||||
try:
|
||||
s = self.get_shift_by_name(s_name)
|
||||
if worker.site not in s.sites:
|
||||
self.add_warning(
|
||||
"Invalid exact shift site",
|
||||
f"Worker {worker.name} requested exact shifts for shift {s_name} but their site {worker.site} is not in the shift sites {s.sites}",
|
||||
)
|
||||
except KeyError:
|
||||
self.add_warning(
|
||||
"Invalid exact shift",
|
||||
f"Worker {worker.name} requested exact shifts for non-existent shift {s_name}",
|
||||
)
|
||||
wid = worker.id
|
||||
if wid in self.workers_id_map:
|
||||
message = f"Worker with id '{wid}' has been added twice"
|
||||
@@ -5146,6 +5175,10 @@ class RotaBuilder(object):
|
||||
l4.extend([worker.fte for worker in self.workers])
|
||||
wr.writerow(l4)
|
||||
|
||||
l5 = ["Exact Shifts"]
|
||||
l5.extend([json.dumps(getattr(worker, "exact_shifts", {})) for worker in self.workers])
|
||||
wr.writerow(l5)
|
||||
|
||||
for week, day in self.get_week_day_combinations():
|
||||
d = [f"Week {week} Day {day}"]
|
||||
|
||||
@@ -5475,9 +5508,7 @@ class RotaBuilder(object):
|
||||
# --- Highlight force assigned shifts ---
|
||||
if force_assigned:
|
||||
css_class += " force-assigned"
|
||||
title = f"{shift_name} ({d}) [FORCE ASSIGNED]"
|
||||
else:
|
||||
title = f"{shift_name} ({d})"
|
||||
title = f"{title} [FORCE ASSIGNED]"
|
||||
|
||||
bank_holiday = ""
|
||||
if d in self.bank_holidays:
|
||||
@@ -5517,8 +5548,9 @@ class RotaBuilder(object):
|
||||
if shift.has_constraint(NightConstraint):
|
||||
css_class = " ".join((css_class, "night-shift"))
|
||||
|
||||
title_escaped = title.replace("'", "'").replace('"', """)
|
||||
shift_tds.append(
|
||||
f"<td title='{','.join(shift_names)} ({d})' class='rota-day {css_class}'"
|
||||
f"<td title='{title_escaped}' class='rota-day {css_class}'"
|
||||
f" data-shift='{','.join(assigned_shift_names)}'"
|
||||
f" data-shift-display='{','.join(shift_names)}'"
|
||||
f" data-available='{available}'"
|
||||
@@ -5729,6 +5761,7 @@ class RotaBuilder(object):
|
||||
data-weekend-sat='{weekend_sat}'
|
||||
data-weekend-sun='{weekend_sun}'
|
||||
data-previous-shifts='{previous_shifts}'
|
||||
data-exact-shifts='{exact_shifts}'
|
||||
data-shift-diff='{shift_diff}'
|
||||
data-shift-diff-summed='{shift_diff_summed}'
|
||||
>
|
||||
@@ -5752,6 +5785,7 @@ class RotaBuilder(object):
|
||||
weekend_sun=sun_count,
|
||||
worker_summary_html=worker_summary_html,
|
||||
previous_shifts=json.dumps(prior_map),
|
||||
exact_shifts=json.dumps(getattr(worker, "exact_shifts", {})),
|
||||
pair=worker.pair,
|
||||
shift_balance_extra=json.dumps(worker.shift_balance_extra),
|
||||
shift_diff=json.dumps(shift_diff_dict),
|
||||
|
||||
@@ -213,6 +213,8 @@ class Worker(BaseModel):
|
||||
|
||||
shift_fte_overrides: dict[str, int] = {} # Need checks to ensure shifts exist
|
||||
|
||||
exact_shifts: dict[str, int] = {} # Map shift_name to exact number of shifts
|
||||
|
||||
weekend_shift_target_number: float = 0.0
|
||||
avoid_shifts_on_dates: list[AvoidShiftOnDates] = []
|
||||
|
||||
@@ -552,3 +554,7 @@ class Worker(BaseModel):
|
||||
def force_assign_shift_by_date(self, date: datetime.date, shift_name: str):
|
||||
"""Force this worker to be assigned to a shift on a specific date."""
|
||||
self.forced_assignments_by_date.append((date, shift_name))
|
||||
|
||||
def add_max_days_per_week_block_constraint(self, max_days: int, week_block: int, ignore_dates: Optional[List[datetime.date]] = None):
|
||||
"""Add a constraint to limit the number of days worked in any sliding block of weeks."""
|
||||
self.max_days_per_week_block.append(MaxDaysPerWeekBlockConstraint(max_days=max_days, week_block=week_block, ignore_dates=ignore_dates or []))
|
||||
|
||||
+245
-1
@@ -1624,4 +1624,248 @@ def test_worker_hard_day_exclusion2():
|
||||
for week in range(16):
|
||||
sat_idx = week * 7 + days.index("Sat")
|
||||
sun_idx = week * 7 + days.index("Sun")
|
||||
assert not (shift_list[sat_idx] == "a" and shift_list[sun_idx] == "a"), f"Worker {worker.name} has 'a' on both Mon and Tue in week {week}, which should not happen due to hard day exclusion"
|
||||
assert not (shift_list[sat_idx] == "a" and shift_list[sun_idx] == "a"), f"Worker {worker.name} has 'a' on both Mon and Tue in week {week}, which should not happen due to hard day exclusion"
|
||||
|
||||
|
||||
def test_exact_shifts_distribution():
|
||||
weeks_to_rota = 10
|
||||
start_date = datetime.date(2022, 3, 7)
|
||||
|
||||
Rota = RotaBuilder(
|
||||
start_date,
|
||||
weeks_to_rota=weeks_to_rota,
|
||||
)
|
||||
|
||||
workers = [
|
||||
Worker(name="worker1", site="group1", grade=1, fte=100, exact_shifts={"a": 4}),
|
||||
Worker(name="worker2", site="group1", grade=1, fte=100),
|
||||
Worker(name="worker3", site="group1", grade=1, fte=50),
|
||||
]
|
||||
Rota.add_workers(workers)
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1",),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=("Mon",),
|
||||
workers_required=1,
|
||||
hard_constrain_shift=True,
|
||||
balance_offset=0,
|
||||
),
|
||||
)
|
||||
|
||||
Rota.build_and_solve()
|
||||
|
||||
assert Rota.workers_name_map["worker1"].shift_target_number["a"] == 4
|
||||
assert Rota.workers_name_map["worker2"].shift_target_number["a"] == 4
|
||||
assert Rota.workers_name_map["worker3"].shift_target_number["a"] == 2
|
||||
|
||||
# Verify actual assigned counts are exactly correct
|
||||
worker_shift_counts = {w.name: 0 for w in Rota.get_workers()}
|
||||
for week, day, shiftname in Rota.get_all_shiftname_combinations():
|
||||
for worker in Rota.get_workers():
|
||||
val = Rota.model.works[worker.id, week, day, shiftname].value
|
||||
if val is not None and val > 0.5:
|
||||
if shiftname == "a":
|
||||
worker_shift_counts[worker.name] += 1
|
||||
|
||||
assert worker_shift_counts["worker1"] == 4
|
||||
assert worker_shift_counts["worker2"] == 4
|
||||
assert worker_shift_counts["worker3"] == 2
|
||||
|
||||
|
||||
def test_exact_shifts_distribution_unbalanced():
|
||||
weeks_to_rota = 16
|
||||
start_date = datetime.date(2022, 3, 7)
|
||||
|
||||
Rota = RotaBuilder(
|
||||
start_date,
|
||||
weeks_to_rota=weeks_to_rota,
|
||||
)
|
||||
|
||||
workers = [
|
||||
Worker(name="worker1", site="group1", grade=1, fte=100, exact_shifts={"a": 4}),
|
||||
Worker(name="worker2", site="group1", grade=1, fte=100),
|
||||
Worker(name="worker3", site="group1", grade=1, fte=50),
|
||||
]
|
||||
Rota.add_workers(workers)
|
||||
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1",),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=("Mon",),
|
||||
workers_required=1,
|
||||
hard_constrain_shift=True,
|
||||
balance_offset=0,
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group1",),
|
||||
name="b",
|
||||
length=12.5,
|
||||
days=("Tue",),
|
||||
workers_required=1,
|
||||
hard_constrain_shift=True,
|
||||
balance_offset=0.8,
|
||||
),
|
||||
)
|
||||
|
||||
Rota.build_and_solve()
|
||||
Rota.export_rota_to_html("test_exact_shifts_distribution_unbalanced", folder="tests")
|
||||
|
||||
assert Rota.results.solver.status == "ok"
|
||||
|
||||
assert Rota.workers_name_map["worker1"].shift_target_number["a"] == 4
|
||||
assert Rota.workers_name_map["worker2"].shift_target_number["a"] == 8
|
||||
assert Rota.workers_name_map["worker3"].shift_target_number["a"] == 4
|
||||
|
||||
assert Rota.workers_name_map["worker1"].shift_target_number["b"] == 6.4
|
||||
assert Rota.workers_name_map["worker2"].shift_target_number["b"] == 6.4
|
||||
assert Rota.workers_name_map["worker3"].shift_target_number["b"] == 3.2
|
||||
|
||||
# Verify actual assigned counts are exactly correct
|
||||
worker_shift_counts_a = {w.name: 0 for w in Rota.get_workers()}
|
||||
worker_shift_counts_b = {w.name: 0 for w in Rota.get_workers()}
|
||||
for week, day, shiftname in Rota.get_all_shiftname_combinations():
|
||||
for worker in Rota.get_workers():
|
||||
val = Rota.model.works[worker.id, week, day, shiftname].value
|
||||
if val is not None and val > 0.5:
|
||||
if shiftname == "a":
|
||||
worker_shift_counts_a[worker.name] += 1
|
||||
elif shiftname == "b":
|
||||
worker_shift_counts_b[worker.name] += 1
|
||||
|
||||
assert worker_shift_counts_a["worker1"] == 4
|
||||
assert worker_shift_counts_a["worker2"] == 8
|
||||
assert worker_shift_counts_a["worker3"] == 4
|
||||
|
||||
assert worker_shift_counts_b["worker1"] in (6, 7) # Allowing for rounding differences
|
||||
assert worker_shift_counts_b["worker2"] in (6, 7) # Allowing for rounding differences
|
||||
assert worker_shift_counts_b["worker3"] in (3, 4) # Allowing for rounding differences
|
||||
|
||||
|
||||
def test_exact_shifts_invalid_site_warning():
|
||||
weeks_to_rota = 2
|
||||
start_date = datetime.date(2022, 3, 7)
|
||||
|
||||
Rota = RotaBuilder(
|
||||
start_date,
|
||||
weeks_to_rota=weeks_to_rota,
|
||||
)
|
||||
Rota.add_workers([
|
||||
Worker(name="worker1", site="group2", grade=1, fte=100, exact_shifts={"a": 4}),
|
||||
])
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1",),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=("Mon",),
|
||||
workers_required=1,
|
||||
),
|
||||
SingleShift(
|
||||
sites=("group2",),
|
||||
name="b",
|
||||
length=12.5,
|
||||
days=("Mon",),
|
||||
workers_required=1,
|
||||
),
|
||||
)
|
||||
Rota.build_workers()
|
||||
|
||||
warnings = Rota.get_warnings("Invalid exact shift site")
|
||||
assert len(warnings) > 0
|
||||
assert "group2 is not in the shift sites" in warnings[0][1]
|
||||
|
||||
|
||||
def test_exact_shifts_invalid_shift_warning():
|
||||
weeks_to_rota = 2
|
||||
start_date = datetime.date(2022, 3, 7)
|
||||
|
||||
Rota = RotaBuilder(
|
||||
start_date,
|
||||
weeks_to_rota=weeks_to_rota,
|
||||
)
|
||||
Rota.add_workers([
|
||||
Worker(name="worker1", site="group1", grade=1, fte=100, exact_shifts={"nonexistent": 4}),
|
||||
])
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1",),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=("Mon",),
|
||||
workers_required=1,
|
||||
),
|
||||
)
|
||||
Rota.build_workers()
|
||||
|
||||
warnings = Rota.get_warnings("Invalid exact shift")
|
||||
assert len(warnings) > 0
|
||||
assert "non-existent shift nonexistent" in warnings[0][1]
|
||||
|
||||
|
||||
def test_exact_shifts_exceed_total_warning():
|
||||
weeks_to_rota = 2
|
||||
start_date = datetime.date(2022, 3, 7)
|
||||
|
||||
Rota = RotaBuilder(
|
||||
start_date,
|
||||
weeks_to_rota=weeks_to_rota,
|
||||
)
|
||||
Rota.add_workers([
|
||||
Worker(name="worker1", site="group1", grade=1, fte=100, exact_shifts={"a": 4}),
|
||||
])
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1",),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=("Mon",),
|
||||
workers_required=1,
|
||||
),
|
||||
)
|
||||
Rota.build_workers()
|
||||
Rota.build_model()
|
||||
|
||||
warnings = Rota.get_warnings("Exact shifts exceed total shifts")
|
||||
assert len(warnings) > 0
|
||||
assert "total exact shifts (4) exceeds total required shifts" in warnings[0][1]
|
||||
|
||||
|
||||
def test_html_export_worker_details_and_unavailable_reason():
|
||||
weeks_to_rota = 2
|
||||
start_date = datetime.date(2022, 3, 7)
|
||||
|
||||
Rota = RotaBuilder(
|
||||
start_date,
|
||||
weeks_to_rota=weeks_to_rota,
|
||||
)
|
||||
Rota.add_workers([
|
||||
Worker(
|
||||
name="worker1",
|
||||
site="group1",
|
||||
grade=1,
|
||||
fte=100,
|
||||
exact_shifts={"a": 1},
|
||||
start_date=datetime.date(2022, 3, 14)
|
||||
),
|
||||
])
|
||||
Rota.add_shifts(
|
||||
SingleShift(
|
||||
sites=("group1",),
|
||||
name="a",
|
||||
length=12.5,
|
||||
days=("Mon",),
|
||||
workers_required=1,
|
||||
),
|
||||
)
|
||||
Rota.build_workers()
|
||||
Rota.build_model()
|
||||
|
||||
html = Rota.get_worker_timetable_html()
|
||||
|
||||
assert 'data-exact-shifts=\'{"a": 1}\'' in html
|
||||
assert 'title=\' (2022-03-07) / START DATE: 2022-03-14\'' in html
|
||||
Reference in New Issue
Block a user