-
-
Notifications
You must be signed in to change notification settings - Fork 183
Description
Implementation Plan: Configurable Weekly Reset Time
Consensus Summary
This plan adopts a providers-only scope for the first PR, following the Reducer's recommendation to minimize risk for an external contribution. It extends existing function signatures with optional parameters rather than creating parallel helpers, and adds a "Weekly Reset Settings" UI section. The Critique's Redis migration concern is addressed with a dual-read fallback strategy.
Goal
The weekly cost reset is currently hardcoded to Monday 00:00 in time-utils.ts (3 locations: getTimeRangeForPeriod, getTTLForPeriod, getResetInfo). Administrators need the ability to configure which day and time the weekly cost window resets, at least for providers.
Success criteria:
- Provider weekly reset day (0-6, Sunday=0) and time (HH:mm) are configurable via the provider settings UI
- Weekly cost tracking in Redis uses the configured reset day/time for TTL and time range calculations
- Existing providers default to Monday 00:00 (backward compatible, nullable fields)
- All user-facing strings use i18n (5 languages)
- Unit test coverage >= 80% for changed logic
bun run build,bun run lint,bun run typecheck,bun run testall pass
Out of scope:
- Weekly reset configuration for users and keys tables -- follow-up PR after pattern is proven on providers
- Rolling mode for weekly windows -- significant complexity (sorted set tracking), deferred
- Monthly reset customization -- not requested
- Dashboard/leaderboard UI changes for weekly boundaries
Codebase Analysis
Files verified:
src/lib/rate-limit/time-utils.ts-- 3 hardcodedweekStartsOn: 1locations (lines 68, 133, 195)src/lib/rate-limit/service.ts-- Redis keyscost_weekly(lines 707-708, 720-721, 1242);trackCostoptions interface (line 610);checkCostLimitslimits interface (line 143);checkCostLimitsWithLeaseweekly window config (line 1349-1354)src/drizzle/schema.ts--providerstable hasdailyResetMode/dailyResetTimepattern (lines 218-223); no weekly reset fields existsrc/app/[locale]/settings/providers/_components/forms/provider-form/sections/limits-section.tsx-- Daily Reset Settings FieldGroup (lines 198-245)src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts--RateLimitState(lines 52-61),ProviderFormAction(lines 124-131)src/app/v1/_lib/proxy/response-handler.ts--trackCostToRedispasses daily reset config totrackCost(lines 2000-2007)src/actions/providers.ts-- server action that persists provider form datamessages/en/settings/providers/form/sections.json-- i18n pattern for daily reset fields (lines 133-146)CONTRIBUTING.md-- PR targetsdev, Conventional Commits, Squash and merge
File changes:
| File | Level | Purpose |
|---|---|---|
src/drizzle/schema.ts |
medium | Add weeklyResetDay (integer, nullable) and weeklyResetTime (varchar(5), nullable) to providers table |
drizzle/XXXX_*.sql |
medium | Generated migration (via bun run db:generate) |
src/lib/rate-limit/time-utils.ts |
major | Extend getTimeRangeForPeriod, getTTLForPeriod, getResetInfo with optional weeklyResetDay/weeklyResetTime params |
src/lib/rate-limit/service.ts |
major | Thread weekly config through checkCostLimits, trackCost, getCurrentCost, getCurrentCostBatch, checkCostLimitsWithLease; change Redis key to cost_weekly_{day}_{HHmm} with fallback read |
src/app/v1/_lib/proxy/response-handler.ts |
minor | Pass providerWeeklyResetDay/providerWeeklyResetTime through trackCost options |
src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-types.ts |
medium | Add weeklyResetDay/weeklyResetTime to RateLimitState and actions |
src/app/[locale]/settings/providers/_components/forms/provider-form/provider-form-reducer.ts |
medium | Handle new actions and LOAD_PROVIDER |
src/app/[locale]/settings/providers/_components/forms/provider-form/sections/limits-section.tsx |
medium | Add "Weekly Reset Settings" FieldGroup (day select + time input) |
src/actions/providers.ts |
medium | Read/write new fields in create/update actions |
messages/en/settings/providers/form/sections.json |
minor | Add weekly reset i18n keys |
messages/zh-CN/settings/providers/form/sections.json |
minor | Chinese translations |
messages/zh-TW/settings/providers/form/sections.json |
minor | Traditional Chinese translations |
messages/ja/settings/providers/form/sections.json |
minor | Japanese translations |
messages/ru/settings/providers/form/sections.json |
minor | Russian translations |
tests/unit/lib/rate-limit/time-utils.test.ts |
medium | Test weekly reset with custom day/time |
tests/unit/lib/rate-limit/service.test.ts |
medium | Test Redis key naming with weekly config, fallback read |
Current architecture notes:
date-fnsstartOfWeekalready acceptsweekStartsOn: 0-6-- core time calculation change is minimal- Redis keys for daily use
cost_daily_{HHmm}suffix pattern; weekly will follow:cost_weekly_{day}_{HHmm} trackCostoptions already carry per-entity daily reset config; weekly config follows same pattern- Lease system's
checkCostLimitsWithLeasehardcodesresetTime: "00:00"for weekly (line 1349-1354)
Interface Design
Modified interfaces:
// time-utils.ts - getTimeRangeForPeriod
export async function getTimeRangeForPeriod(
period: TimePeriod,
- resetTime = "00:00"
+ resetTime = "00:00",
+ weeklyResetDay?: number, // 0-6 (Sunday=0), default 1 (Monday)
+ weeklyResetTime?: string // HH:mm, default "00:00"
): Promise<TimeRange>;
// Same pattern for getTTLForPeriod, getResetInfo, and WithMode variants// service.ts - CostLimits interface (used by checkCostLimits)
interface CostLimits {
limit_5h_usd?: number;
limit_daily_usd?: number;
limit_weekly_usd?: number;
limit_monthly_usd?: number;
+ weekly_reset_day?: number;
+ weekly_reset_time?: string;
}// service.ts - TrackCostOptions (used by trackCost)
interface TrackCostOptions {
keyResetTime?: string;
keyResetMode?: string;
providerResetTime?: string;
providerResetMode?: string;
+ providerWeeklyResetDay?: number;
+ providerWeeklyResetTime?: string;
}// provider-form-types.ts - RateLimitState
export interface RateLimitState {
// ... existing fields ...
+ weeklyResetDay: number; // 0-6, default 1
+ weeklyResetTime: string; // HH:mm, default "00:00"
}Test Strategy
Test modifications:
-
tests/unit/lib/rate-limit/time-utils.test.ts- Extend existing weekly tests- Test case: Custom day (Sunday=0) with default time returns correct time range starting Sunday 00:00
- Test case: Custom day (Wednesday=3) with custom time (18:00) returns correct boundary
- Test case: Default behavior unchanged when weeklyResetDay/weeklyResetTime omitted
- Test case: TTL calculation to next custom weekly boundary
- Test case: Reset info returns correct next reset time for custom day/time
-
tests/unit/lib/rate-limit/service.test.ts- Test Redis key and fallback- Test case: Redis key includes
cost_weekly_1_0000for Monday 00:00 - Test case: Redis key includes
cost_weekly_0_1800for Sunday 18:00 - Test case: Fallback read from old
cost_weeklykey when new key returns null - Test case: Migration: old key value copied to new key and old key deleted
- Test case: Redis key includes
Implementation Steps
Step 1: Schema migration (Est: ~20 LOC)
- Add
weeklyResetDay(integer, nullable) andweeklyResetTime(varchar(5), nullable) toproviderstable insrc/drizzle/schema.ts - Run
bun run db:generateto auto-generate migration SQL - Review generated SQL; ensure columns are nullable with no default (runtime handles defaults)
Dependencies: None
Step 2: Extend time-utils.ts (Est: ~40 LOC)
- Add optional
weeklyResetDayandweeklyResetTimeparams togetTimeRangeForPeriod,getTTLForPeriod,getResetInfo - In the
weeklyswitch case: useweekStartsOn: weeklyResetDay ?? 1forstartOfWeek - Apply custom time offset using existing
buildZonedDatehelper pattern (same as daily fixed) - Also extend
WithModevariants to pass through weekly params
Dependencies: Step 1
Step 3: Extend service.ts (Est: ~80 LOC)
- Add
weekly_reset_day/weekly_reset_timetoCostLimitsinterface andTrackCostOptions - Add
resolveWeeklyReset(day?: number, time?: string): stringhelper that returns suffix like1_0000 - Change Redis key from
cost_weeklytocost_weekly_{suffix}in all relevant methods - Add fallback: when new key returns null, check old
cost_weeklykey; if found, copy value to new key with appropriate TTL, then delete old key - Thread config through
checkCostLimitsWithLeaseweekly window
Dependencies: Step 2
Step 4: Update response-handler.ts (Est: ~10 LOC)
- Pass
providerWeeklyResetDayandproviderWeeklyResetTimefrom provider object throughtrackCostoptions
Dependencies: Step 3
Step 5: Update server action (Est: ~15 LOC)
- In
src/actions/providers.ts, read/writeweeklyResetDayandweeklyResetTimein create/update actions - Add validation: day is integer 0-6, time matches HH:mm pattern
Dependencies: Step 1
Step 6: Update UI types and reducer (Est: ~25 LOC)
- Add
weeklyResetDay/weeklyResetTimetoRateLimitStatein provider-form-types.ts - Add
SET_WEEKLY_RESET_DAY/SET_WEEKLY_RESET_TIMEactions - Handle in reducer, wire through
LOAD_PROVIDER
Dependencies: Step 5
Step 7: Update limits-section.tsx UI (Est: ~40 LOC)
- Add "Weekly Reset Settings"
FieldGroupbelow existing "Daily Reset Settings" - Day-of-week
Select(7 options: Sunday through Saturday) - Time
Input(type="time") - Conditionally show when
limitWeeklyUsdis set (matching daily pattern)
Dependencies: Step 6
Step 8: i18n translations (Est: ~30 LOC across 5 files)
- Add keys:
weeklyReset.title,weeklyReset.description,weeklyResetDay.label,weeklyResetTime.label, day names (Sun-Sat) - All 5 languages: en, zh-CN, zh-TW, ja, ru
Dependencies: Step 7
Step 9: Unit tests (Est: ~60 LOC)
- time-utils: test custom weekly boundaries (all 7 days, various times, default fallback)
- service: test Redis key suffix, fallback read from old key, migration logic
Dependencies: Steps 2, 3
Step 10: Pre-commit verification
- Run
bun run build && bun run lint:fix && bun run typecheck && bun run test - Ensure all checks pass before submitting PR
Dependencies: All previous steps
Total estimated complexity: ~320 LOC (Medium)
Recommended approach: Single session
Success Criteria
- Providers table has
weekly_reset_dayandweekly_reset_timenullable columns - Weekly time range calculation respects configured day and time
- Redis keys for weekly cost use
cost_weekly_{day}_{HHmm}format - Fallback read from old
cost_weeklykey prevents data loss during migration - Provider form UI allows configuring weekly reset day and time
- All 5 language files updated with weekly reset translations
- Unit tests pass with >= 80% coverage on changed files
-
bun run build && bun run lint && bun run typecheck && bun run testall green - PR targets
devbranch withfeat:Conventional Commit format
Risks and Mitigations
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
Redis key migration: old cost_weekly keys orphaned, potential double-counting |
High | High | Dual-read fallback: check new key first, fall back to old key, migrate on read. Old keys expire naturally via TTL (max ~7 days). |
time-utils function signature change breaks existing callers |
Medium | Medium | New params are optional with defaults matching current behavior (Monday 00:00). All existing callers pass no weekly args. |
| Lease system not receiving weekly config | Medium | Medium | Update checkCostLimitsWithLease to pass weekly reset day/time for the weekly window. |
trackCost options interface grows unwieldy |
Low | Low | Only adding 2 optional fields for provider weekly. User/key weekly is deferred. |
| Mid-week reset day change creates short/long transition window | Low | Low | Acceptable behavior -- same as how daily reset time changes work. Old Redis key expires naturally. |
Dependencies
date-fnsstartOfWeekalready supportsweekStartsOnoption (0-6) -- no new dependency- Drizzle ORM migration tooling (
bun run db:generate) - No external service dependencies
Metadata
Metadata
Assignees
Labels
Projects
Status