estimateRunDuration() computes a per-run ETA from a target count, a fire time, and an assumed per-account send rate (40/min, mirroring the bot env). Adds a 15% buffer with a 1-minute floor. Pure helper, covered by 6 round-trip tests including the rate-defaults path. Header CTA buttons on /accounts and /reminders are now size="lg" rounded-full pills with a shadow that lifts on hover. Mobile shows just the plus icon (label collapses) so the button doesn't dominate narrow screens. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
40 lines
1.4 KiB
TypeScript
40 lines
1.4 KiB
TypeScript
/**
|
|
* Default per-account send rate, mirroring `BOT_MAX_SEND_PER_MINUTE`
|
|
* in the bot env. The web bundle hardcodes this — operators who tune
|
|
* the bot env are expected to redeploy web with the matching value.
|
|
*/
|
|
export const ASSUMED_RATE_PER_MINUTE = 40;
|
|
|
|
const ETA_BUFFER = 1.15;
|
|
|
|
/**
|
|
* Pure ETA helper. Given a target count and a fire time, returns the
|
|
* estimated duration in whole minutes and the projected finish
|
|
* timestamp.
|
|
*
|
|
* Calculation:
|
|
* ceil((targetCount / ratePerMinute) * 1.15) minutes
|
|
* estimatedFinishAt = fireAt + that many minutes
|
|
*
|
|
* Floor of 1 minute when targetCount > 0 (anything non-zero takes at
|
|
* least a minute to feel real). Returns 0 minutes when targetCount
|
|
* is zero — the run is a no-op.
|
|
*/
|
|
export function estimateRunDuration(opts: {
|
|
targetCount: number;
|
|
ratePerMinute?: number;
|
|
fireAt: Date;
|
|
}): { durationMinutes: number; estimatedFinishAt: Date } {
|
|
const rate = opts.ratePerMinute ?? ASSUMED_RATE_PER_MINUTE;
|
|
if (rate <= 0) throw new Error("ratePerMinute must be > 0");
|
|
if (opts.targetCount <= 0) {
|
|
return { durationMinutes: 0, estimatedFinishAt: new Date(opts.fireAt) };
|
|
}
|
|
const raw = (opts.targetCount / rate) * ETA_BUFFER;
|
|
const durationMinutes = Math.max(1, Math.ceil(raw));
|
|
const estimatedFinishAt = new Date(
|
|
opts.fireAt.getTime() + durationMinutes * 60_000,
|
|
);
|
|
return { durationMinutes, estimatedFinishAt };
|
|
}
|