Skip to content

Feature/weekly low standup report - #9

Merged
tylermenezes merged 8 commits into
mainfrom
feature/weekly-low-standup-report
Jul 21, 2026
Merged

Feature/weekly low standup report#9
tylermenezes merged 8 commits into
mainfrom
feature/weekly-low-standup-report

Conversation

@HanCotterell

Copy link
Copy Markdown
Contributor

Adds automation that flags students with 2 consecutive stand up scores less than 2 during a week and puts a message in a designated slack channel with a list of those flagged students.

augmentcode Bot and others added 6 commits June 16, 2026 19:06
- Create new automation task that runs every Monday at 9 AM
- Identifies students with two consecutive standup scores under 2 from previous week
- Posts formatted report to #stats Slack channel
- Supports multiple active events with Slack integration
- Uses Slack Block Kit for rich message formatting
- Includes student Slack mentions when available
- Follows existing codebase patterns and error handling
- Refactor filtering logic into testable pure functions
- Export findConsecutiveLowScores and formatStudentList for testing
- Add comprehensive manual test suite (12 test cases)
- Fix TypeScript type errors with PickNonNullable
- All tests passing ✅
- Remove duplicated function code from test file
- Import findConsecutiveLowScores and formatStudentList from actual implementation
- Add .env.test.example with minimal test environment variables
- Update test documentation with setup instructions
- Tests now stay in sync with implementation automatically
- Create testWeeklyStandupReport.ts manual testing script
- Support dry-run mode to preview messages without posting
- Support custom channel selection for testing
- Use fake student data to avoid pinging real people
- Add comprehensive testing documentation
- Include troubleshooting guide

Usage:
  npx ts-node scripts/testWeeklyStandupReport.ts --dry-run
  npx ts-node scripts/testWeeklyStandupReport.ts --channel=test-notifications
@augmentcode

augmentcode Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
🤖 Augment PR Summary

Summary: Adds an automated weekly Slack report to flag students who had two consecutive low standup ratings in the prior week.

Changes:

  • Introduces a new automation task weeklyLowStandupReport scheduled via JOBSPEC and run through the existing automation loader.
  • Queries each active event with Slack credentials and identifies accepted students with consecutive standup ratings < 2 within the previous-week window.
  • Formats flagged students (with optional Slack mentions and mentor attribution) into Slack Block Kit and posts the report to #stats.
  • Adds a runnable test harness (scripts/testWeeklyStandupReport.ts) that supports dry-run previews, posting to a chosen channel, and optional local DB seeding.
  • Adds a second manual test script under src/automation/tasks to validate the pure helper functions and optionally test Slack/DB connectivity.
  • Includes a .env.test.example template and a dedicated testing guide documenting how to validate formatting and Slack integration safely.

Technical Notes: Uses Prisma for data access, Luxon for date windows, and Slack Web API for channel lookup and message posting.

🤖 Was this summary useful? React with 👍 or 👎

@augmentcode augmentcode Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.


const DEBUG = makeDebug('automation:tasks:weeklyLowStandupReport');

// Run every Monday at 9 AM Pacific Time

@augmentcode augmentcode Bot Jul 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code/comment says this runs at “9 AM Pacific Time”, but CronJob in src/automation/index.ts doesn’t specify a timezone, so this will run in the server/process timezone (often UTC in production) which can shift the actual send time and the “previous week” window.

Severity: medium

Other Locations
  • docs/testing-weekly-standup-report.md:7
  • docs/testing-weekly-standup-report.md:142

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.


// Find the stats channel
try {
const channelsList = await slack.conversations.list({

@augmentcode augmentcode Bot Jul 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

slack.conversations.list is paginated; searching only the first page can miss #stats in workspaces with many channels (elsewhere the codebase uses slack.paginate(...) for this).

Severity: medium

Other Locations
  • scripts/testWeeklyStandupReport.ts:376

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a weekly scheduled automation that queries standup scores for all active events, finds students with two or more consecutive scores below 2 within the past 7 days, and posts a formatted report to the #stats Slack channel every Monday at 9 AM. Supporting artifacts include a test script, a manual-test file, example env vars, and documentation.

  • weeklyLowStandupReport.ts implements the core logic: DB query → consecutive-score detection → Slack message; the task index files were also updated to filter out *.test.ts files so the new .manual-test.ts file is excluded from auto-loading.
  • scripts/testWeeklyStandupReport.ts is a standalone utility that supports dry-run, fake-data, real-data, and optional DB seeding modes for safe pre-deployment testing.

Confidence Score: 4/5

Safe to merge with a low-risk operational blind spot: per-event errors are swallowed by a DEBUG-only catch, so Monday report failures would be invisible in production without the DEBUG env var set.

The new task's main loop catches all per-event errors and routes them only to DEBUG logging. In a standard production deployment without that debug namespace enabled, any failure — Slack token invalid, DB timeout, channel not found — will be silently dropped and the report never sent. No alert fires, no log line appears, and the scheduler considers the job successful.

src/automation/tasks/weeklyLowStandupReport.ts — specifically the catch block around sendReportForEvent and the conversations.list call that lacks pagination.

Important Files Changed

Filename Overview
src/automation/tasks/weeklyLowStandupReport.ts Core task implementation; errors in per-event report sending are only logged via DEBUG (silent in production), conversations.list is unpaginated, and the consecutive-score scan returns the first pair found rather than the most recent.
src/automation/tasks/index.ts Added test.ts / test.js filter to prevent the new manual-test file from being auto-loaded as a task; change is correct and safe.
src/activities/tasks/index.ts Same test-file filter added for consistency; cosmetic formatting changes only, no logic risk.
src/automation/tasks/weeklyLowStandupReport.manual-test.ts Manual unit-test file for pure functions; correctly filtered out of the auto-loader by the updated index.
scripts/testWeeklyStandupReport.ts Comprehensive integration test script with dry-run and seeding modes; conversations.list call is also unpaginated here, matching the production code's limitation.
.env.test.example New example env file with only placeholder values; safe to commit.
docs/testing-weekly-standup-report.md Documentation for testing the feature; one note says 'Not Yet Implemented' for real-data mode, but the script does actually implement it via --use-real-data.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Scheduler
    participant weeklyLowStandupReport
    participant Prisma DB
    participant Slack API

    Scheduler->>weeklyLowStandupReport: trigger (Mon 9 AM)
    weeklyLowStandupReport->>Prisma DB: findMany active events (isActive, slackToken set)
    Prisma DB-->>weeklyLowStandupReport: events[]

    loop for each event
        weeklyLowStandupReport->>Prisma DB: findMany accepted students + standupResults (last 7 days)
        Prisma DB-->>weeklyLowStandupReport: students[]
        weeklyLowStandupReport->>weeklyLowStandupReport: findConsecutiveLowScores() per student
        alt flagged students exist
            weeklyLowStandupReport->>Slack API: conversations.list (unpaginated)
            Slack API-->>weeklyLowStandupReport: channels[]
            weeklyLowStandupReport->>weeklyLowStandupReport: "find #stats channel"
            alt "#stats found"
                weeklyLowStandupReport->>Slack API: chat.postMessage → #stats
                Slack API-->>weeklyLowStandupReport: ok
            else "#stats not found"
                weeklyLowStandupReport->>weeklyLowStandupReport: DEBUG + return (silent)
            end
        else no flagged students
            weeklyLowStandupReport->>weeklyLowStandupReport: skip (no message)
        end
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Scheduler
    participant weeklyLowStandupReport
    participant Prisma DB
    participant Slack API

    Scheduler->>weeklyLowStandupReport: trigger (Mon 9 AM)
    weeklyLowStandupReport->>Prisma DB: findMany active events (isActive, slackToken set)
    Prisma DB-->>weeklyLowStandupReport: events[]

    loop for each event
        weeklyLowStandupReport->>Prisma DB: findMany accepted students + standupResults (last 7 days)
        Prisma DB-->>weeklyLowStandupReport: students[]
        weeklyLowStandupReport->>weeklyLowStandupReport: findConsecutiveLowScores() per student
        alt flagged students exist
            weeklyLowStandupReport->>Slack API: conversations.list (unpaginated)
            Slack API-->>weeklyLowStandupReport: channels[]
            weeklyLowStandupReport->>weeklyLowStandupReport: "find #stats channel"
            alt "#stats found"
                weeklyLowStandupReport->>Slack API: chat.postMessage → #stats
                Slack API-->>weeklyLowStandupReport: ok
            else "#stats not found"
                weeklyLowStandupReport->>weeklyLowStandupReport: DEBUG + return (silent)
            end
        else no flagged students
            weeklyLowStandupReport->>weeklyLowStandupReport: skip (no message)
        end
    end
Loading

Reviews (2): Last reviewed commit: "Fix test loading in automation" | Re-trigger Greptile

Comment on lines +1 to +20
/**
* Manual test script for weeklyLowStandupReport
*
* Prerequisites:
* - Copy .env.test.example to .env (if you don't have a .env file):
* cp .env.test.example .env
*
* Run with:
* npx ts-node src/automation/tasks/weeklyLowStandupReport.manual-test.ts
*
* To test Slack channel lookup, set SLACK_BOT_TOKEN environment variable:
* SLACK_BOT_TOKEN=xoxb-your-token npx ts-node src/automation/tasks/weeklyLowStandupReport.manual-test.ts
*
* This tests the pure functions (findConsecutiveLowScores and formatStudentList)
* imported from the actual implementation file to ensure tests stay in sync with code.
* It also optionally tests Slack channel lookup if a token is provided.
*/

import 'reflect-metadata';
import { findConsecutiveLowScores, formatStudentList } from './weeklyLowStandupReport';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Manual test file placed in the auto-loaded tasks directory

src/automation/tasks/index.ts calls fs.readdirSync(__dirname) and loads every file in the directory that isn't index.ts/index.js. Because weeklyLowStandupReport.manual-test.ts lives in that same directory and has no default export, the task loader will throw Error: Task weeklyLowStandupReport.manual-test.ts does not include a default export. at startup. Additionally, the top-level synchronous test calls (lines 50–138) and the floating runAllTests() call at line 365 execute at require-time, printing test output and potentially exiting the process before the loader even checks for the missing export. This file should be moved outside the tasks directory (e.g., to src/automation/__tests__/ or scripts/) so the task auto-loader does not pick it up.

Fix in Claude Code

Comment on lines +243 to +255
const channelsList = await slack.conversations.list({
exclude_archived: true,
types: 'public_channel,private_channel',
});

const statsChannel = channelsList.channels?.find(
(c: any) => c.name === STATS_CHANNEL_NAME
);

if (!statsChannel) {
DEBUG(`Channel #${STATS_CHANNEL_NAME} not found, skipping report.`);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 conversations.list not paginated — may silently miss #stats channel

Slack's conversations.list API returns at most 100 channels per page by default. For any workspace with more than 100 channels, the #stats channel may fall outside the first page, causing statsChannel to be undefined and the report to be silently dropped. The production code does not pass limit or handle response_metadata.next_cursor for subsequent pages, while even the test script at least passes limit: 100. A workspace that grows past the page boundary will silently stop receiving reports with no error logged.

Fix in Claude Code

Comment on lines +11 to +12
// Run every Monday at 9 AM Pacific Time
export const JOBSPEC = '0 9 * * 1';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The cron expression '0 9 * * 1' runs at 9 AM in whatever timezone the server uses, not necessarily Pacific Time. Cron has no built-in timezone support; the comment may mislead operators who configure the server in UTC (where this fires at 9 AM UTC, which is 1/2 AM Pacific). Consider documenting the expected server timezone or using a scheduler that supports explicit timezone configuration.

Suggested change
// Run every Monday at 9 AM Pacific Time
export const JOBSPEC = '0 9 * * 1';
// Run every Monday at 9 AM in the server's local timezone.
// Ensure the server is configured to run in the intended timezone (e.g. America/Los_Angeles).
export const JOBSPEC = '0 9 * * 1';

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Comment on lines +181 to +207
for (let i = 0; i < ratings.length - 1; i++) {
const current = ratings[i];
const next = ratings[i + 1];

if (current !== null && next !== null && current < 2 && next < 2) {
const mentorById = new Map<string, { givenName: string; surname: string; slackId: string | null }>();
for (const project of student.projects || []) {
for (const mentor of project.mentors) {
mentorById.set(mentor.id, {
givenName: mentor.givenName,
surname: mentor.surname,
slackId: mentor.slackId,
});
}
}

return {
studentId: student.id,
givenName: student.givenName,
surname: student.surname,
slackId: student.slackId,
assignedMentors: Array.from(mentorById.values()),
eventName,
consecutiveLowScores: 2,
lastTwoRatings: [current, next],
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 findConsecutiveLowScores reports the first consecutive pair, not the most recent

The loop returns on the very first consecutive pair with scores < 2 it encounters, and lastTwoRatings captures those two scores. If a student had ratings [1, 1, 3, 3] (struggled early but recovered), they are still flagged, and the message shows the stale scores from the beginning of the week rather than a current picture. The PR description implies checking the most recent two consecutive scores. Consider scanning from the end of the sorted array (most recent entries first) to surface the latest trend.

Fix in Claude Code

@tylermenezes
tylermenezes merged commit 53089e2 into main Jul 21, 2026
2 checks passed
@tylermenezes
tylermenezes deleted the feature/weekly-low-standup-report branch July 21, 2026 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants