From e142a718732019c59191480d164e4c654bc781d9 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:30:33 -0400 Subject: [PATCH] Add DynamoDB integration for Slack shift manager overrides The scheduler now checks the afterhours-shifts DynamoDB table before falling back to the YAML config. This enables the Slack shift manager bot to drive schedule changes that take effect at the next 8am run. Same-day changes are handled by direct Lambda invocation from the bot. Co-Authored-By: Claude Opus 4.6 --- src/handler.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++---- template.yaml | 10 +++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/handler.py b/src/handler.py index 053a163..9bf590a 100644 --- a/src/handler.py +++ b/src/handler.py @@ -41,21 +41,73 @@ def get_3cx_credentials() -> dict: return creds +def check_dynamodb_override(date_str: str, day_name: str) -> str | None: + """Check the afterhours-shifts DynamoDB table for a Slack-driven override. + + Returns the extension if an override exists, None otherwise. + Only active when SHIFT_TABLE env var is set. + """ + table_name = os.environ.get("SHIFT_TABLE") + if not table_name: + return None + + dynamodb = boto3.resource("dynamodb") + table = dynamodb.Table(table_name) + + # Check date-specific override first + resp = table.get_item(Key={"PK": "OVERRIDE", "SK": date_str}) + item = resp.get("Item") + if item: + ext = item["extension"] + if ext == "OPEN": + # Open shift with no pickup — use fallback from DynamoDB config or default 100 + config_resp = table.get_item(Key={"PK": "CONFIG", "SK": "CONFIG"}) + fallback = config_resp.get("Item", {}).get("fallback_extension", "100") + logger.info("DynamoDB override for %s: OPEN — using fallback Ext %s", date_str, fallback) + return fallback + logger.info("DynamoDB override for %s: Ext %s", date_str, ext) + return ext + + # Check DynamoDB weekly schedule (takes precedence over YAML when SHIFT_TABLE is set) + resp = table.get_item(Key={"PK": "WEEKLY", "SK": day_name}) + item = resp.get("Item") + if item: + ext = item["extension"] + logger.info("DynamoDB weekly schedule for %s (%s): Ext %s", date_str, day_name, ext) + return ext + + return None + + def resolve_extension(schedule: dict, now: datetime) -> str: - """Determine which extension should handle after-hours calls today.""" + """Determine which extension should handle after-hours calls today. + + Resolution order: + 1. DynamoDB date override (Slack-driven pickup/drop) + 2. DynamoDB weekly schedule (if SHIFT_TABLE is set) + 3. YAML exception overrides + 4. YAML weekly schedule + """ date_str = now.strftime("%Y-%m-%d") + day_name = now.strftime("%A") + + # Check DynamoDB first (Slack shift manager) + dynamo_ext = check_dynamodb_override(date_str, day_name) + if dynamo_ext is not None: + return dynamo_ext + + # Fall back to YAML config exceptions = schedule.get("exceptions") or {} if date_str in exceptions: ext = str(exceptions[date_str]) - logger.info("Using exception override for %s: Ext %s", date_str, ext) + logger.info("Using YAML exception override for %s: Ext %s", date_str, ext) return ext - day_name = now.strftime("%A") weekly = schedule["weekly_schedule"] if day_name not in weekly: raise ValueError(f"No schedule entry for {day_name}") ext = str(weekly[day_name]) - logger.info("Using weekly schedule for %s (%s): Ext %s", date_str, day_name, ext) + logger.info("Using YAML weekly schedule for %s (%s): Ext %s", date_str, day_name, ext) return ext diff --git a/template.yaml b/template.yaml index ec1829a..d1278fd 100644 --- a/template.yaml +++ b/template.yaml @@ -6,6 +6,10 @@ Parameters: Timezone: Type: String Default: "America/New_York" + ShiftTableName: + Type: String + Default: "afterhours-shifts" + Description: DynamoDB table for Slack shift manager overrides Globals: Function: @@ -24,6 +28,7 @@ Resources: Variables: SSM_PREFIX: /3cx-scheduler TZ: !Ref Timezone + SHIFT_TABLE: !Ref ShiftTableName Policies: - Statement: - Effect: Allow @@ -41,6 +46,11 @@ Resources: Condition: StringEquals: "kms:ViaService": !Sub "ssm.${AWS::Region}.amazonaws.com" + - Effect: Allow + Action: + - dynamodb:GetItem + Resource: + - !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ShiftTableName}" Events: # EST: 8am ET = 13:00 UTC (Nov-Mar) DailyScheduleEST: