diff --git a/rota_generator/shifts.py b/rota_generator/shifts.py index 6bb9b3b..b2bf2f6 100644 --- a/rota_generator/shifts.py +++ b/rota_generator/shifts.py @@ -1702,6 +1702,103 @@ class RotaBuilder(object): ) for week in weeks_to_apply: + # If the worker has any forced assignment in the dependency group, + # emit a concise warning. For visibility include every day in the + # group showing the worker's forced shifts (if any) and other + # workers' forced shifts on those days so blocking days are visible. + worker_has_forced_in_group = False + for (fw, fd, fs) in getattr(worker, "forced_assignments", []): + if fw == week and fd in day_group: + worker_has_forced_in_group = True + break + if not worker_has_forced_in_group: + try: + for (d_date, d_shift) in getattr(worker, "forced_assignments_by_date", []): + ww, dd = self.date_week_day_map.get(d_date, (None, None)) + if ww == week and dd in day_group: + worker_has_forced_in_group = True + break + except Exception: + pass + + if worker_has_forced_in_group: + forced_day_details = [] + try: + date_map_for_week = {self.get_date_by_week_day(week, d): d for d in day_group} + except Exception: + date_map_for_week = {} + + for day in day_group: + worker_forced_shifts = set() + # week-based forced assignments + for (fw, fd, fs) in getattr(worker, "forced_assignments", []): + if fw == week and fd == day: + worker_forced_shifts.add(fs) + # date-based forced assignments + try: + date = self.get_date_by_week_day(week, day) + for (d_date, d_shift) in getattr(worker, "forced_assignments_by_date", []): + try: + ww, dd = self.date_week_day_map.get(d_date, (None, None)) + except Exception: + ww = dd = None + if ww == week and dd == day: + worker_forced_shifts.add(d_shift) + else: + try: + if str(d_date) == str(date): + worker_forced_shifts.add(d_shift) + except Exception: + pass + except Exception: + pass + + # collect other workers' forced shifts on this day + others_forced = {} + try: + date_for_day = self.get_date_by_week_day(week, day) + except Exception: + date_for_day = None + + for other in self.workers: + if other is worker: + continue + other_shifts = set() + # week-based forced assignments + for (ofw, ofd, ofs) in getattr(other, "forced_assignments", []): + if ofw == week and ofd == day: + other_shifts.add(ofs) + # date-based forced assignments: try mapping then fallback + for (odate, oshift) in getattr(other, "forced_assignments_by_date", []): + try: + oww, odd = self.date_week_day_map.get(odate, (None, None)) + except Exception: + oww = odd = None + if oww == week and odd == day: + other_shifts.add(oshift) + continue + try: + if date_for_day is not None and str(odate) == str(date_for_day): + other_shifts.add(oshift) + except Exception: + pass + + if other_shifts: + others_forced[other.name] = sorted(list(other_shifts)) + + forced_day_details.append((day, sorted(list(worker_forced_shifts)), others_forced)) + + parts = [] + for d, wfs, others in forced_day_details: + olist = ", ".join([f"{n}: {', '.join(s)}" for n, s in others.items()]) or "none" + wfs_str = ", ".join(wfs) or "none" + parts.append(f"{d} - worker forced: {wfs_str}; others forced: {olist}") + details = "; ".join(parts) + logger.warning(f"HardDayDependency forced-assignment warning for worker={worker.name} week={week}: {details}") + self.add_warning( + "HardDayDependency forced assignment", + f"Worker '{worker.name}' has hard_day_dependency {set(day_group)} in week {week}; forced assignments on dependency days: {details}. If the rota is not infeasible this is unlikely to be an issue.", + ) assigned_any_shift = {} for day in day_group: shifts_today = self.get_shift_names_by_week_day(week, day)