Skip to main content

Documentation Index

Fetch the complete documentation index at: https://grantmaster.dev/llms.txt

Use this file to discover all available pages before exploring further.

Grants Feature — Service API Reference

Public contract: src/features/grants/public.ts Domain contracts: src/features/grants/contracts.ts Services: src/features/grants/services/
Covers internal service APIs and event contracts that are not exported through the public hook surface but are consumed by notification pipelines, UI layers, and the EventBus system. For the public hook and context API surface (React-layer), see docs/engineering/api-reference/public-api/grants.md.

Deadline Radar (GM-98)

Source: src/features/grants/services/deadlineRadar.ts Contract types: src/features/grants/contracts.ts (§ DEADLINE RADAR) Roadmap item: H1.2.4 — Application Deadline Radar
Pure date-arithmetic layer for bucketing grant application deadlines into urgency windows and deciding when to emit EventBus notifications. Zero external coupling — no Firebase, Postmark, or Cloud Function dependencies. Future phases will add a scheduled trigger, Postmark digest template, and pipeline-card UI badge.

Quick Example

import {
  computeDeadlineWindow,
  shouldNotifyForWindow,
  daysUntilDeadline,
} from '@/features/grants/services/deadlineRadar';
import type { DeadlineRadarWindow } from '@/features/grants/contracts';

const today = new Date('2026-05-05');
const deadline = '2026-05-10';

const days = daysUntilDeadline(deadline, today);          // 5
const window = computeDeadlineWindow(deadline, today);    // '7d'
const shouldSend = shouldNotifyForWindow(window, null);   // true  (first entry)
const repeat    = shouldNotifyForWindow(window, '7d');    // false (same window)
const escalate  = shouldNotifyForWindow('due-today', '7d'); // true  (more urgent)

Types

DeadlineWindow

Discrete urgency buckets used internally by the radar service.
type DeadlineWindow =
  | 'overdue'    // past deadline
  | 'due-today'  // 0 days remaining
  | '7d'         // 1–7 days
  | '14d'        // 8–14 days
  | '30d'        // 15–30 days
  | 'future'     // >30 days
  | 'no-deadline'; // null / unparseable input

DeadlineRadarWindow

Public alias for DeadlineWindow, re-exported via contracts.ts for use in EventBus consumers and UI components.
type DeadlineRadarWindow = DeadlineWindow;

DeadlineRadarEvent

EventBus payload emitted when a tracked application enters a new, more-urgent radar window. Consumers: notification engine (Postmark digest), UI badge, audit log.
FieldTypeDescription
organizationIdstringTenant isolation key
applicationIdstringThe grant application being tracked
grantOpportunityIdstring?Linked opportunity, if any
deadlinestringCanonical YYYY-MM-DD deadline date
currentWindowDeadlineRadarWindowWindow the application just entered
previousWindowDeadlineRadarWindow | nullLast window that triggered a notification, or null on first emit
daysUntilDeadlinenumberWhole calendar days from evaluation date to deadline (UTC)
evaluatedAtstringISO 8601 UTC instant of evaluation
Idempotency: the producer uses previousWindow together with shouldNotifyForWindow to ensure a notification fires only on first entry or escalation — never on re-evaluation within the same window or de-escalation.

Functions

daysUntilDeadline(deadline, today)

function daysUntilDeadline(deadline: Date | string | null | undefined, today: Date): number
Returns whole UTC calendar days from today until deadline. Same calendar day → 0. Yesterday → -1. Returns NaN for null / unparseable inputs.
ParameterTypeDescription
deadlineDate | string | null | undefinedThe deadline date or ISO string
todayDateReference date (injected for testability)
Returns: integer days (or NaN if deadline is absent/invalid).

computeDeadlineWindow(deadline, today)

function computeDeadlineWindow(
  deadline: Date | string | null | undefined,
  today: Date,
): DeadlineWindow
Maps a deadline to one of the seven discrete radar windows.
ParameterTypeDescription
deadlineDate | string | null | undefinedThe deadline date or ISO string
todayDateReference date (injected for testability)
Returns: DeadlineWindow bucket.
Days remainingWindow
< 0'overdue'
0'due-today'
1–7'7d'
8–14'14d'
15–30'30d'
> 30'future'
null / invalid'no-deadline'

shouldNotifyForWindow(current, last)

function shouldNotifyForWindow(
  current: DeadlineWindow,
  last: DeadlineWindow | null | undefined,
): boolean
Decides whether to emit a notification given the current and previously-notified window. Enforces idempotency (no repeat on same window) and monotonic escalation (never notify on de-escalation).
ParameterTypeDescription
currentDeadlineWindowThe window the application is currently in
lastDeadlineWindow | null | undefinedThe window from the last notification, or null if none yet
Returns: true if a notification should be sent. Emit rules:
ConditionEmit?
current is 'future' or 'no-deadline'No
last is null / undefined (first entry)Yes
current is more urgent than lastYes
current is same or less urgent than lastNo

Phase Roadmap

PhaseStatusScope
1 — Foundation✅ Shipped (GM-98, 2026-05-04)Date math + event contract
2 — Trigger🔜 PlannedScheduled Cloud Function (daily sweep)
3 — Postmark🔜 PlannedDigest email template + unsubscribe flow
4 — UI Badge🔜 PlannedPipeline-card DeadlineWindow badge component