"""Lambda handler — posts the weekly on-call schedule and previous week's pay summary to Slack every Monday at 7am ET.""" import logging import os from datetime import datetime, timedelta from zoneinfo import ZoneInfo import boto3 from slack_sdk import WebClient from src.blocks import build_pay_summary_blocks, build_week_schedule from src.schedule import ShiftSchedule logger = logging.getLogger() logger.setLevel(logging.INFO) EASTERN = ZoneInfo("America/New_York") DAY_ORDER = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] def _calculate_weekly_pay(schedule: ShiftSchedule, week_start: datetime) -> dict: """Calculate pay for a Mon–Sun week. Returns pay record dict.""" rate = schedule.get_shift_rate() breakdown = [] totals = {} for i in range(7): date = week_start + timedelta(days=i) date_str = date.strftime("%Y-%m-%d") day_name = DAY_ORDER[i] ext, name, source = schedule.resolve_shift(date_str, day_name) if source == "available": continue breakdown.append({ "date": date_str, "date_label": date.strftime("%b %-d"), "day": day_name[:3], "name": name, "extension": ext, "rate": rate, }) if name not in totals: totals[name] = {"shifts": 0, "total": 0.0, "extension": ext} totals[name]["shifts"] += 1 totals[name]["total"] += rate return { "week_start": week_start.strftime("%Y-%m-%d"), "rate": str(rate), "breakdown": breakdown, "totals": totals, } def handler(event, context): now = datetime.now(EASTERN) # DST guard — same pattern as the 3CX scheduler if now.hour != 7: logger.info("ET hour is %d, not 7am — skipping (wrong DST rule fired)", now.hour) return {"skipped": True} ssm = boto3.client("ssm") bot_token = ssm.get_parameter( Name=os.environ["SLACK_BOT_TOKEN_PARAM"], WithDecryption=True )["Parameter"]["Value"] channel_id = ssm.get_parameter( Name=os.environ["SHIFT_CHANNEL_PARAM"], WithDecryption=True )["Parameter"]["Value"] schedule = ShiftSchedule() slack = WebClient(token=bot_token) # --- Previous week's pay summary --- prev_monday = now - timedelta(days=7) prev_monday = prev_monday.replace(hour=0, minute=0, second=0, microsecond=0) pay_record = _calculate_weekly_pay(schedule, prev_monday) if pay_record["breakdown"]: week_key = prev_monday.strftime("%Y-%m-%d") schedule.save_pay_record(week_key, pay_record) prev_sunday = prev_monday + timedelta(days=6) week_label = f"{prev_monday.strftime('%b %-d')} to {prev_sunday.strftime('%b %-d')}" pay_blocks = build_pay_summary_blocks( week_label, pay_record["breakdown"], pay_record["totals"], float(pay_record["rate"]) ) slack.chat_postMessage( channel=channel_id, blocks=pay_blocks, text=f"After-Hours Pay — {week_label}", ) logger.info("Posted pay summary for week of %s", week_key) # --- Two-week schedule --- blocks = build_week_schedule(schedule, start_date=now) end_date = now + timedelta(days=13) slack.chat_postMessage( channel=channel_id, blocks=blocks, text=f"After-Hours Schedule — {now.strftime('%b %-d')} to {end_date.strftime('%b %-d')}", ) logger.info("Posted weekly schedule to channel %s", channel_id) return {"posted": True, "channel": channel_id, "pay_calculated": bool(pay_record["breakdown"])}