Skip to content

OUT-3736 | Eligibility SQL: single-day reminder query#1236

Merged
arpandhakal merged 3 commits into
feature/email-remindersfrom
OUT-3736
May 25, 2026
Merged

OUT-3736 | Eligibility SQL: single-day reminder query#1236
arpandhakal merged 3 commits into
feature/email-remindersfrom
OUT-3736

Conversation

@arpandhakal
Copy link
Copy Markdown
Collaborator

Changes

  • New module src/jobs/notifications/eligibility.ts exposing getEligibleReminders() — a single raw-SQL query that returns one row per (task, assignee, reminderType) eligible for a task reminder today.
  • Covers the six exact-day windows from the PRD: NO_DUE_DATE_3D, NO_DUE_DATE_7D, DUE_DATE_BEFORE_3D, DUE_DATE_TODAY, DUE_DATE_OVERDUE_3D, DUE_DATE_OVERDUE_7D.
  • Exclusions per PRD: soft-deleted, archived, completed, and unassigned tasks are dropped; same-assignee subtasks fold into the parent (using IS DISTINCT FROM so a NULL parent assignee still counts as "different").
  • Malformed-date guard: regex check on Task.dueDate (VARCHAR(10)) before ::date cast so one bad row can't poison the query.
  • EligibilityRow carries companyId derived per assigneeType (client → task.companyId; company → task.assigneeId; internalUser → null) so the future sender can stamp ClientNotifications.companyId and Copilot's recipientCompanyId without re-fetching the task.
  • Unit tests in eligibility.test.ts covering the function's behavior (mocked $queryRaw).

Linear: https://linear.app/assemblycom/issue/OUT-3736/eligibility-sql-single-day-reminder-query

Testing Criteria

  • yarn test src/jobs/notifications/eligibility.test.ts — 4 passing, 100% coverage on the module.
  • Smoke-tested the SQL against Supabase preview / production using a widened date window to confirm the filters (deleted / archived / completed / subtask carve-out / dueDate regex) behave as expected against real data. Reverted to exact-day match for production.
  • Verified by inspection that the per-assigneeType companyId derivation matches what notification.service.ts:558 and ClientNotifications's unique key (taskId, clientId, companyId, deletedAt) expect.

Boundary fixtures (D-3 hit, D-2 / D-4 miss) are deferred to the consumer ticket where a Postgres-backed integration test is more useful — this module is a thin SQL wrapper and is currently not wired into any cron.

Notes

  • Already-sent reminders are not filtered here. The TaskReminderSents unique constraint is the dedupe primitive at insert time, so a retried cron run is naturally idempotent. The exact-day windows mean a given (task, window) pair only matches on one calendar day, so day-N reminders don't repeat in normal operation.
  • Company fan-out (one task → many client recipients) is deferred to the caller via copilot.getCompanyClients(...). Keeping the SQL Copilot-free mirrors the pattern in src/jobs/auto-archive/auto-archive-completed-tasks.ts:31-82.
  • No Trigger.dev cron wiring in this PR — that lands with the sender ticket.

Impact & Surface Area of Change

  • Net-new module, not imported anywhere yet. Zero runtime behavior change in this PR.
  • No schema changes, no migrations, no Prisma client regeneration needed.
  • Safe to merge ahead of the sender + cron PRs without affecting any existing notification path.

🤖 Generated with Claude Code

@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 18, 2026

OUT-3736

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tasks-app Ready Ready Preview, Comment May 25, 2026 6:52am

Request Review

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 18, 2026

Greptile Summary

This PR adds src/jobs/notifications/eligibility.ts, a net-new, unwired module that issues a single raw-SQL query returning one row per (task, assignee, reminderType) eligible for a task reminder on the current day, covering six PRD-specified windows across no-due-date and due-date scenarios.

  • New getEligibleReminders() function with a defensive regex guard on the VARCHAR(10) dueDate column, a subtask carve-out using IS DISTINCT FROM, and per-assigneeType companyId derivation for downstream notification inserts.
  • Unit tests mock $queryRaw and confirm pass-through, empty results, call count, and error propagation; SQL correctness testing is deferred to integration tests on the consumer ticket.

Confidence Score: 3/5

Safe to merge in terms of runtime impact (no cron wiring), but the module will not function correctly once wired without fixes.

The module is net-new and not yet imported anywhere, so merging introduces no immediate runtime regression. However, two defects mean it cannot be wired up as-is: the TaskReminderType enum is absent from every Prisma schema file and migration, and the unguarded parent LEFT JOIN will silently drop subtask reminders whenever a parent task has been soft-deleted.

src/jobs/notifications/eligibility.ts requires attention for both the TaskReminderType schema gap and the soft-deleted parent join filter.

Important Files Changed

Filename Overview
src/jobs/notifications/eligibility.ts New module exposing getEligibleReminders() via raw SQL. Two issues: TaskReminderType is used but missing from the Prisma schema/migrations, and the LEFT JOIN to parent tasks does not filter soft-deleted parents, causing subtasks under deleted parents to be silently excluded from all reminder windows.
src/jobs/notifications/eligibility.test.ts Unit tests cover pass-through, empty result, call count, and error propagation. Since $queryRaw is fully mocked, the SQL logic itself is untested, but that is acknowledged in the PR. Tests would fail to compile/run if TaskReminderType is not in the generated Prisma client.

Reviews (1): Last reviewed commit: "feat(OUT-3736): add eligibility SQL for ..." | Re-trigger Greptile

Comment thread src/jobs/notifications/eligibility.ts
Comment thread src/jobs/notifications/eligibility.ts Outdated
@arpandhakal arpandhakal changed the base branch from main to OUT-3735-reminder-copy-helper May 18, 2026 10:44
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 18, 2026

Deployment failed with the following error:

Deploying Serverless Functions to multiple regions is restricted to the Pro and Enterprise plans.

Learn More: https://vercel.link/multiple-function-regions

arpandhakal and others added 2 commits May 18, 2026 16:57
Adds getEligibleReminders() that returns one row per (task, assignee, reminderType)
eligible for a task reminder today, across the six exact-day windows defined in the
Reminder Emails PRD. The EligibilityRow carries the companyId derived per assigneeType
so the future sender can stamp ClientNotifications.companyId and Copilot's
recipientCompanyId without a follow-up task lookup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without this, a same-assignee subtask under a soft-deleted (or archived /
completed) parent is silently dropped: the parent itself is filtered out
by the main WHERE, but the LEFT JOIN still returns its assigneeId, which
fails IS DISTINCT FROM and drops the subtask. Filtering parent lifecycle
in the JOIN makes parent.assigneeId come back NULL for dead parents,
so subtasks correctly emit their own reminder.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think these comments are too much. Also the name of the file. Maybe it should be get-tasks-for-reminder, or something else rather that just eligibility.ts. The name is very generic imo.

@arpandhakal arpandhakal changed the base branch from OUT-3735-reminder-copy-helper to feature/email-reminders May 25, 2026 06:51
@arpandhakal arpandhakal merged commit 9fa6f0e into feature/email-reminders May 25, 2026
1 of 2 checks passed
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