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.
| Field | Type | Description |
|---|
organizationId | string | Tenant isolation key |
applicationId | string | The grant application being tracked |
grantOpportunityId | string? | Linked opportunity, if any |
deadline | string | Canonical YYYY-MM-DD deadline date |
currentWindow | DeadlineRadarWindow | Window the application just entered |
previousWindow | DeadlineRadarWindow | null | Last window that triggered a notification, or null on first emit |
daysUntilDeadline | number | Whole calendar days from evaluation date to deadline (UTC) |
evaluatedAt | string | ISO 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.
| Parameter | Type | Description |
|---|
deadline | Date | string | null | undefined | The deadline date or ISO string |
today | Date | Reference 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.
| Parameter | Type | Description |
|---|
deadline | Date | string | null | undefined | The deadline date or ISO string |
today | Date | Reference date (injected for testability) |
Returns: DeadlineWindow bucket.
| Days remaining | Window |
|---|
< 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).
| Parameter | Type | Description |
|---|
current | DeadlineWindow | The window the application is currently in |
last | DeadlineWindow | null | undefined | The window from the last notification, or null if none yet |
Returns: true if a notification should be sent.
Emit rules:
| Condition | Emit? |
|---|
current is 'future' or 'no-deadline' | No |
last is null / undefined (first entry) | Yes |
current is more urgent than last | Yes |
current is same or less urgent than last | No |
Phase Roadmap
| Phase | Status | Scope |
|---|
| 1 — Foundation | ✅ Shipped (GM-98, 2026-05-04) | Date math + event contract |
| 2 — Trigger | 🔜 Planned | Scheduled Cloud Function (daily sweep) |
| 3 — Postmark | 🔜 Planned | Digest email template + unsubscribe flow |
| 4 — UI Badge | 🔜 Planned | Pipeline-card DeadlineWindow badge component |