Skip to content

Track meeting attendance automatically via Slack huddles - #10

Open
HanCotterell wants to merge 3 commits into
mainfrom
feature/meeting-attendance-tracking
Open

Track meeting attendance automatically via Slack huddles#10
HanCotterell wants to merge 3 commits into
mainfrom
feature/meeting-attendance-tracking

Conversation

@HanCotterell

@HanCotterell HanCotterell commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Records whether students attend their team meetings, and flags those who don't.

Attendance is derived from Slack huddle activity rather than asked for. A mentor joining a huddle in a project channel opens a meeting; students who join are recorded present, and a daily task marks those who never joined as absent. Nobody has to submit anything.

Note

This PR previously captured attendance from mentor reflection surveys. That approach has been removed in favour of automatic tracking, so the survey path no longer exists here. The general Survey/SurveyResponse system is untouched — only the attendance-extraction layer built on it was dropped.

Opting out

Not every team meets on Slack. Since absence is inferred from the lack of a huddle join, an untracked team would otherwise show 0% attendance and have every student flagged.

Projects carry attendanceTracking, inheriting from Event.defaultAttendanceTracking when unset. Setting it to NOT_TRACKED excludes the team at every layer:

Component Behaviour when NOT_TRACKED
huddleHandler No meetings or attendance created
markAbsentStudents Project skipped — no student marked absent
statStudentAttendance Returns trackingMode: NOT_TRACKED, never sets isFlagged
flaggedStudents Students never flagged
sendAttendanceAlerts Excluded; reported only as a count

The trackingMode field lets the dashboard distinguish "this student missed meetings" from "we don't measure this team".

Fixes found while consolidating

This branch was cut before 5 commits landed on main, so its diff was silently proposing to revert them. Rebuilt on current main, which avoids:

  • deleting the entire Attio sync and weekly low standup report (16 files)
  • reverting config.ts (Attio env vars), Review.ts (partnerCode filter), getProjectMatches.ts, and yarn.lock
  • resurrecting mentorResume/mentorJudging email templates that main deleted — these auto-send, so re-adding them would have started sending mail again
  • reverting the .test.ts filter in the automation task loader

Also fixed: two test files sat in src/automation/tasks/, where the loader throws on any file lacking a default export — this crashed the automation runner at boot. They now live in scripts/. A migration for SlackHuddleParticipation was also missing entirely and has been written.

The diff is now purely additive: 28 files, +2432 −21, zero deletions.

Verification

  • yarn test:attendance passes (opt-out resolution + alert message formatting)
  • npx prisma validate passes
  • tsc error count unchanged from main (378, all pre-existing in generated Prisma types and src/badgr)
  • Automation loader confirmed to register 18 tasks, with markAbsentStudents at 0 2 * * * and sendAttendanceAlerts at 0 9 * * MON

Not yet done

Deployment needs the migration applied, then Slack app config: event subscription URL https://<host>/{WEBHOOK_KEY}/slack, subscribe to user_huddle_changed, and scopes channels:read + users:read. End-to-end testing against a real huddle hasn't happened yet — worth piloting on one project before rollout. Setup steps are in SLACK_HUDDLE_ATTENDANCE.md.

@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds meeting attendance tracking to the Labs GraphQL API — storing attendance records sourced from mentor reflections, a weekly Slack alert for low-attendance students, new GraphQL queries/mutations for meetings and attendance stats, and a database migration extending Meeting and MeetingAttendance with source, confidence, and scheduling fields.

  • Schema & migration add AttendanceSource enum, new columns on Meeting/MeetingAttendance, and a Project→Meeting relation; the migration is non-destructive with safe defaults.
  • processMentorReflections automation parses mentor survey JSON and auto-creates Meeting/MeetingAttendance records each Monday, but sets scheduledEndAt to weekStart (copy-paste bug) causing every auto-created meeting to have zero duration.
  • MeetingResolver exposes createMeeting, recordMeetingAttendance, and meetingAttendance queries, but the mentor-accessible endpoints do not scope access to the authenticated mentor's own projects, allowing cross-project reads and writes.
  • flaggedStudents in StatsResolver always returns null for the mentor field because the underlying statStudentAttendance query does not include the mentors relation when fetching projects.

Confidence Score: 2/5

Not safe to merge without fixes: mentors can read and overwrite attendance for any project, and the flaggedStudents query returns broken mentor data.

The new mentor-accessible mutations and queries in MeetingResolver perform no project-scoping check, so any authenticated mentor can read attendance records or submit false attendance data for other teams. The flaggedStudents resolver silently returns null for every mentor because the project query it depends on never loads the mentors relation. The auto-meeting creation in processMentorReflections also has a copy-paste error that sets the end time to the start time. These are functional defects on the changed paths rather than theoretical risks.

src/resolvers/Meeting.ts and src/resolvers/Stats.ts need the most attention — the authorization gaps and broken mentor lookup are both there. src/automation/tasks/processMentorReflections.ts needs the scheduledEndAt fix.

Important Files Changed

Filename Overview
src/resolvers/Meeting.ts New resolver for Meeting CRUD and attendance recording; multiple authorization gaps allow mentors to read/write attendance for meetings outside their own projects
src/resolvers/Stats.ts Adds attendance stats and flaggedStudents queries; flaggedStudents always returns null mentor because the upstream query doesn't include the mentors relation on project
src/automation/tasks/processMentorReflections.ts Auto-creates Meeting records from mentor survey responses; scheduledEndAt is incorrectly set to weekStart instead of weekEnd for auto-created meetings
src/automation/tasks/sendAttendanceAlerts.ts Weekly Slack alert task for attendance and reflection issues; contains N+1 query pattern (one DB count per mentor per event)
prisma/schema.prisma Adds AttendanceSource enum, extends Meeting with Slack/project fields, adds source/confidence/metadata to MeetingAttendance, and links Project to Meeting; schema looks correct
prisma/migrations/20260714193312_add_meeting_attendance_tracking/migration.sql SQL migration adding enum, new columns with safe defaults, foreign key, and indexes; migration is non-destructive and looks correct
src/types/AttendanceStats.ts Defines StudentAttendanceStat, MentorReflectionStat, and FlaggedStudent GraphQL object types; definitions are clean
src/email/templates/weeklyAttendanceAlert.md Handlebars email template for weekly attendance reports; contains a hardcoded personal email address in the to: field
src/types/Meeting.ts New GraphQL Meeting type with lazy-loaded relations; implementation follows existing codebase patterns correctly
src/types/MeetingAttendance.ts New GraphQL MeetingAttendance type with source/confidence/metadata fields; follows codebase patterns correctly
scripts/testAttendanceSlack.ts Manual test/seed script for Slack alert integration; logic is sound though it duplicates the event lookup in two places

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Mentor
    participant MeetingResolver
    participant StatsResolver
    participant processMentorReflections
    participant sendAttendanceAlerts
    participant DB as PostgreSQL
    participant Slack

    Mentor->>MeetingResolver: recordMeetingAttendance(meetingId, studentId, attended)
    MeetingResolver->>DB: findFirst MeetingAttendance where meetingId+studentId
    DB-->>MeetingResolver: "existing | null"
    MeetingResolver->>DB: "upsert MeetingAttendance (source=MANUAL)"
    DB-->>MeetingResolver: MeetingAttendance record
    MeetingResolver-->>Mentor: MeetingAttendance

    Note over processMentorReflections: Every Monday 6 AM
    processMentorReflections->>DB: findMany SurveyResponse (mentor, last 8 days)
    DB-->>processMentorReflections: reflections[]
    loop per reflection
        processMentorReflections->>DB: findFirst Meeting (projectId, week window)
        DB-->>processMentorReflections: "meeting | null"
        processMentorReflections->>DB: create Meeting if missing
        loop per student
            processMentorReflections->>DB: "upsert MeetingAttendance (source=MENTOR_REPORT)"
        end
    end

    Note over sendAttendanceAlerts: Every Monday 9 AM
    sendAttendanceAlerts->>DB: findMany active Events
    loop per event
        sendAttendanceAlerts->>DB: findMany Projects (with meetings+attendance)
        loop per project mentor
            sendAttendanceAlerts->>DB: count SurveyResponses for mentor
        end
        sendAttendanceAlerts->>Slack: "postMessage #stats (if issues found)"
    end

    Note over StatsResolver: Admin/Manager query
    StatsResolver->>DB: "findMany Students (with projects->meetings->attendance)"
    DB-->>StatsResolver: students[]
    StatsResolver-->>StatsResolver: flaggedStudents calls statStudentAttendance
    Note right of StatsResolver: mentor field always null - mentors not included in query
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 Mentor
    participant MeetingResolver
    participant StatsResolver
    participant processMentorReflections
    participant sendAttendanceAlerts
    participant DB as PostgreSQL
    participant Slack

    Mentor->>MeetingResolver: recordMeetingAttendance(meetingId, studentId, attended)
    MeetingResolver->>DB: findFirst MeetingAttendance where meetingId+studentId
    DB-->>MeetingResolver: "existing | null"
    MeetingResolver->>DB: "upsert MeetingAttendance (source=MANUAL)"
    DB-->>MeetingResolver: MeetingAttendance record
    MeetingResolver-->>Mentor: MeetingAttendance

    Note over processMentorReflections: Every Monday 6 AM
    processMentorReflections->>DB: findMany SurveyResponse (mentor, last 8 days)
    DB-->>processMentorReflections: reflections[]
    loop per reflection
        processMentorReflections->>DB: findFirst Meeting (projectId, week window)
        DB-->>processMentorReflections: "meeting | null"
        processMentorReflections->>DB: create Meeting if missing
        loop per student
            processMentorReflections->>DB: "upsert MeetingAttendance (source=MENTOR_REPORT)"
        end
    end

    Note over sendAttendanceAlerts: Every Monday 9 AM
    sendAttendanceAlerts->>DB: findMany active Events
    loop per event
        sendAttendanceAlerts->>DB: findMany Projects (with meetings+attendance)
        loop per project mentor
            sendAttendanceAlerts->>DB: count SurveyResponses for mentor
        end
        sendAttendanceAlerts->>Slack: "postMessage #stats (if issues found)"
    end

    Note over StatsResolver: Admin/Manager query
    StatsResolver->>DB: "findMany Students (with projects->meetings->attendance)"
    DB-->>StatsResolver: students[]
    StatsResolver-->>StatsResolver: flaggedStudents calls statStudentAttendance
    Note right of StatsResolver: mentor field always null - mentors not included in query
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "First phase for tracking student attenda..." | Re-trigger Greptile

Comment thread src/resolvers/Stats.ts
Comment on lines +275 to +276
for (const stat of attendanceStats.filter((s) => s.isFlagged)) {
const mentor = stat.project?.mentors?.[0];

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 flaggedStudents always returns undefined mentor

stat.project?.mentors?.[0] will always be undefined because the underlying call to statStudentAttendance does not include the mentors relation on projects — only meetings and its attendance are included. Every FlaggedStudent will therefore have mentor: undefined, making the mentor field in the response always null regardless of the project's actual mentors.

Fix in Claude Code

Comment on lines +147 to +148
scheduledStartAt: weekStart,
scheduledEndAt: weekStart,

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 scheduledEndAt is set to weekStart instead of weekEnd, so every auto-created meeting will have a zero-duration window (start equals end). This will make the meeting appear to end the moment it begins in any UI that surfaces scheduled times.

Suggested change
scheduledStartAt: weekStart,
scheduledEndAt: weekStart,
scheduledStartAt: weekStart,
scheduledEndAt: weekEnd,

Fix in Claude Code

Comment thread src/resolvers/Meeting.ts
Comment on lines +89 to +131
@Authorized(AuthRole.ADMIN, AuthRole.MANAGER, AuthRole.MENTOR)
@Mutation(() => MeetingAttendance)
async recordMeetingAttendance(
@Ctx() { auth }: Context,
@Arg('data', () => MeetingAttendanceInput) data: MeetingAttendanceInput,
): Promise<PrismaMeetingAttendance> {
DEBUG(`Recording attendance for meeting ${data.meetingId}, student ${data.studentId}: ${data.attended}`);

// Check for existing attendance record
const existing = await this.prisma.meetingAttendance.findFirst({
where: {
meetingId: data.meetingId,
studentId: data.studentId,
},
});

if (existing) {
// Update existing record
return this.prisma.meetingAttendance.update({
where: { id: existing.id },
data: {
attended: data.attended,
prepared: data.prepared ?? existing.prepared,
source: data.source ?? existing.source,
confidence: data.confidence ?? existing.confidence,
metadata: data.metadata as any ?? existing.metadata,
},
});
}

// Create new record
return this.prisma.meetingAttendance.create({
data: {
meetingId: data.meetingId,
studentId: data.studentId,
attended: data.attended,
prepared: data.prepared ?? false,
source: data.source ?? 'MANUAL',
confidence: data.confidence ?? 1.0,
metadata: data.metadata as any,
},
});
}

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 Mentor can record attendance for any meeting

The recordMeetingAttendance mutation is accessible to the MENTOR role but performs no check that the targeted meetingId belongs to a project the authenticated mentor is actually assigned to. Any mentor with a valid token can overwrite attendance records for meetings in other mentors' projects or even other events, allowing incorrect data to be written at will.

Fix in Claude Code

Comment thread src/resolvers/Meeting.ts
Comment on lines +133 to +144
@Authorized(AuthRole.ADMIN, AuthRole.MANAGER, AuthRole.MENTOR)
@Query(() => [MeetingAttendance])
async meetingAttendance(
@Ctx() { auth }: Context,
@Arg('meetingId', () => String) meetingId: string,
): Promise<PrismaMeetingAttendance[]> {
return this.prisma.meetingAttendance.findMany({
where: { meetingId },
include: { student: true },
orderBy: { createdAt: 'asc' },
});
}

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 Mentor can read attendance for any meeting

The meetingAttendance query accepts any meetingId and returns all attendance records for it without verifying that the requesting mentor is associated with that meeting's project. A mentor can enumerate attendance data for every other team in the event by guessing or iterating meeting IDs.

Fix in Claude Code

Comment thread src/resolvers/Meeting.ts
Comment on lines +39 to +61
@Authorized(AuthRole.ADMIN, AuthRole.MANAGER, AuthRole.MENTOR, AuthRole.STUDENT)
@Query(() => Meeting, { nullable: true })
async meeting(
@Ctx() { auth }: Context,
@Arg('id', () => String) id: string,
): Promise<PrismaMeeting | null> {
const meeting = await this.prisma.meeting.findUnique({
where: { id },
include: { project: true },
});

if (!meeting) return null;

// Verify access
if (!auth.isAdmin && !auth.isManager) {
if (auth.isMentor || auth.isStudent) {
if (meeting.eventId !== auth.eventId) {
throw new Error('No permission to view this meeting.');
}
}
}

return meeting;

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 Overly broad event-level access for mentor/student on meeting query

The access check for mentors and students only verifies that meeting.eventId === auth.eventId, but a meeting may belong to a different project within the same event. A mentor or student can therefore retrieve meeting details (including schedule and attendance) for any project in their event, not just their own team.

Fix in Claude Code

Comment on lines +163 to +175
// Check mentor reflection completion
const mentorReflections = await prisma.surveyResponse.count({
where: {
authorMentorId: mentor.id,
surveyOccurence: {
survey: {
personType: 'MENTOR',
eventId: event.id,
},
},
},
});

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 N+1 query pattern per mentor

prisma.surveyResponse.count() is called inside the for (const project of projects) loop, issuing one separate database round-trip per mentor. For an event with 100 mentors this becomes 100 sequential queries. The same pattern exists in scripts/testAttendanceSlack.ts. Consider grouping these counts with a single groupBy call outside the loop.

Fix in Claude Code

Comment on lines +1 to +4
---
to: "akif@codeday.org"
subject: "Weekly Attendance Report for {{ event.name }}"
---

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 Hardcoded recipient email address

The template's to: frontmatter is set to a hardcoded personal email address (akif@codeday.org). If this template is ever wired up to the email-sending pipeline it will route all weekly attendance reports directly to that inbox regardless of the event's configured contact. The address should be a template variable like {{ event.contactEmail }} or removed if the template is not yet used.

Fix in Claude Code

@augmentcode

augmentcode Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor
🤖 Augment PR Summary

Summary: Adds meeting attendance tracking to Labs GraphQL, primarily sourced from mentor reflection reporting and used to drive weekly alerting.

Changes:

  • Prisma: adds AttendanceSource; adds source/confidence/metadata to MeetingAttendance; adds projectId plus Slack scheduling fields to Meeting and links Project → meetings.
  • GraphQL: introduces Meeting/MeetingAttendance/MeetingResponse types and inputs, plus a new Meeting resolver with queries and mutations.
  • Stats: adds queries for student attendance %, mentor reflection completion, and a flaggedStudents convenience query.
  • Automation: adds a mentor-reflection processor (Mon 6am) to upsert attendance records and a weekly Slack alert job (Mon 9am).
  • Testing/ops: adds simple task test runners and a manual Slack test script for seeding and previewing alert messages.
  • Email: adds a weekly attendance report template.

🤖 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. 5 suggestions posted.

Fix All in Augment

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

import { DateTime } from 'luxon';

const DEBUG = makeDebug('automation:tasks:sendAttendanceAlerts');
const ATTENDANCE_ALERT_CHANNEL = 'stats';

@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.

src/automation/tasks/sendAttendanceAlerts.ts:8 ATTENDANCE_ALERT_CHANNEL is set to 'stats', but Slack chat.postMessage expects a channel ID (and other Slack flows here persist IDs like slackMentorChannelId), so this may fail to post. Consider resolving #stats to an ID or using an event-configured channel ID.

Severity: medium

Fix This in Augment

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

Comment thread src/resolvers/Stats.ts
const flagged: FlaggedStudent[] = [];

for (const stat of attendanceStats.filter((s) => s.isFlagged)) {
const mentor = stat.project?.mentors?.[0];

@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.

src/resolvers/Stats.ts:276 stat.project?.mentors?.[0] will always be undefined here because statStudentAttendance doesn’t include mentors on the loaded project, so flaggedStudents likely returns mentor: null even when a mentor exists. Consider including mentors when building the stats (or fetching the mentor separately) before populating FlaggedStudent.mentor.

Severity: medium

Fix This in Augment

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

Comment thread src/resolvers/Meeting.ts
DEBUG(`Recording attendance for meeting ${data.meetingId}, student ${data.studentId}: ${data.attended}`);

// Check for existing attendance record
const existing = await this.prisma.meetingAttendance.findFirst({

@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.

src/resolvers/Meeting.ts:98 recordMeetingAttendance updates/creates by meetingId+studentId without validating that the meeting/student belong to auth.eventId (and for mentors, that they’re associated with the meeting’s project), which could allow cross-project/event edits if IDs are known. The same scoping concern also applies to meetingAttendance (src/resolvers/Meeting.ts:139).

Severity: high

Other Locations
  • src/resolvers/Meeting.ts:139

Fix This in Augment

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

let meeting = await prisma.meeting.findFirst({
where: {
projectId: project.id,
scheduledStartAt: { gte: weekStart, lte: weekEnd },

@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.

src/automation/tasks/processMentorReflections.ts:134 The meeting lookup keys off scheduledStartAt, but this column is newly added and may be NULL for existing Meeting rows, which can cause duplicate meetings to be created for the same project/week. Consider matching on the existing visibleAt/dueAt week window or explicitly handling scheduledStartAt being null.

Severity: medium

Fix This in Augment

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

visibleAt: weekStart,
dueAt: weekEnd,
scheduledStartAt: weekStart,
scheduledEndAt: weekStart,

@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.

src/automation/tasks/processMentorReflections.ts:148 When auto-creating a weekly meeting, scheduledEndAt is set to weekStart, making start/end identical and potentially confusing downstream ordering/reporting. Consider setting a more representative end timestamp (e.g., weekEnd or a default duration).

Severity: low

Fix This in Augment

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

augmentcode Bot added 3 commits August 17, 2026 20:40
Introduces the Meeting/MeetingAttendance data model used to record whether
students attend their team meetings:

- AttendanceSource enum (SLACK_HUDDLE, MANUAL) plus source/confidence/metadata
  on MeetingAttendance, so records carry their provenance.
- AttendanceTrackingMode enum with Project.attendanceTracking and
  Event.defaultAttendanceTracking, letting teams which do not meet on Slack opt
  out of automated tracking.
- Meeting gains projectId, slackHuddleId, and scheduled start/end times.
- SlackHuddleParticipation records raw huddle join/leave events.
- GraphQL types, inputs, and a Meeting resolver for reading meetings and
  recording attendance manually.
Attendance is derived from Slack huddle activity rather than asked for:

- A Slack events webhook receives user_huddle_changed and acknowledges within
  Slack's 3 second budget, processing the event asynchronously.
- A mentor joining a huddle in a project channel opens a Meeting; students
  joining are recorded as present with source=SLACK_HUDDLE. The meeting end time
  is set when the last participant leaves.
- markAbsentStudents runs daily and marks students who never joined, so absence
  is recorded without anyone submitting a form.
Surfaces attendance to program managers while ensuring teams that do not meet on
Slack are not misreported. Because absence is inferred from the absence of a
huddle, an untracked team would otherwise appear to have 0% attendance and every
student would be flagged.

- resolveAttendanceTracking/isAttendanceTracked resolve a project's mode, falling
  back to its event default.
- statStudentAttendance exposes trackingMode and never flags untracked projects;
  flaggedStudents and the weekly Slack alert exclude them, reporting only a count.
- attendanceTracking is settable via editProject and readable on Project.
- Documents setup, the opt-out, and its effect on each component.
@augmentcode
augmentcode Bot force-pushed the feature/meeting-attendance-tracking branch from 4f338da to a4484c4 Compare August 17, 2026 20:40
@augmentcode augmentcode Bot changed the title Feature/meeting attendance tracking Track meeting attendance automatically via Slack huddles Aug 17, 2026
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.

1 participant