CON-181: Campaign Scheduling defaults - #104
Conversation
Give each campaign its own publishing time, timezone, publishing days, and spread, and make the content-plan strategist place every generated draft accordingly. - new src/scheduling package: pure, testable helpers (weekday/clock validation, enabled-day set, day labels, nearest-enabled-day snap, deterministic fnv spread, ComposeScheduledAt) shared by the handler and the flow - campaigns gains publishing_time / timezone / publishing_days / spread_minutes columns + model fields (defaults 09:00 / UTC / all days / +/-15, backfilled) - campaign create/update: normalize + validate the scheduling fields (400 on bad time, timezone, weekday set, or spread range) - content_plan persistOne composes scheduled_at = snap-to-enabled-day @ publishing_time in the campaign timezone +/- deterministic spread, reflects the snapped date back onto the streamed post, and surfaces the enabled weekdays to the model; covers full-plan and CON-114 targeted generation - http-client samples + scheduling/content-plan unit tests
|
Warning Review limit reached
Next review available in: 40 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
WalkthroughCampaigns now support stored publishing time, timezone, weekdays, and spread settings. APIs validate and persist these values. Content-plan generation uses them to select eligible dates and compose scheduled UTC timestamps. ChangesCampaign scheduling
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@CodeRabbit review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/genkit/flows/content_plan/generate.go`:
- Around line 193-195: Update the persistFn closure and its persistOne call to
carry the active startDate and endDate bounds alongside campaign, then update
the corresponding persistOne path (including the flow around ComposeScheduledAt)
to pass those bounds into ComposeScheduledAt so snapping stays within the
targeted generation window.
In `@src/scheduling/scheduling.go`:
- Around line 181-182: Update the scheduling logic around SpreadOffset and the
returned effective date so jitter is retained only when the adjusted local time
remains on day. If the offset crosses into a different local calendar date,
clamp or discard it and return a time on the selected day, keeping
PublishingDays and campaign bounds intact.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 10456423-8d9f-4fd7-a51b-4ccd63ca2f56
📒 Files selected for processing (11)
http-client/campaigns/campaigns.httpsrc/database/migrations/20260807000001_campaign_scheduling.down.sqlsrc/database/migrations/20260807000001_campaign_scheduling.up.sqlsrc/genkit/flows/content_plan/generate.gosrc/genkit/flows/content_plan/generate_test.gosrc/genkit/flows/content_plan/prompts/content_plan.tmplsrc/genkit/flows/content_plan/types.gosrc/handlers/campaigns.gosrc/models/campaign.gosrc/scheduling/scheduling.gosrc/scheduling/scheduling_test.go
persistOne snapped each post's publishing day within the full campaign window even for CON-114 targeted generation, whose window is narrower. A post on a disabled edge-day could snap onto an enabled day outside the targeting window the validator had just checked. Thread the active startDate/endDate into persistOne and ComposeScheduledAt so snapping stays inside the requested window; the full-plan path is unchanged. Adds TestPersistOne_SnapsWithinWindow and updates existing call sites. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A near-midnight publishing time combined with a large spread could push the jittered instant into an adjacent calendar day, landing the post on a day outside PublishingDays or the campaign bounds, and disagreeing with the returned effectiveDate. Clamp the spread-adjusted local time to [00:00, 23:59] of the snapped day before converting to UTC, retaining only the on-day portion of the jitter. Adds boundary tests for positive and negative spreads at both day edges. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gives each Campaign its own scheduling settings — publishing time, timezone, publishing days, and a spread (± minutes) — and makes the content-plan generation flow (the "strategist") consume them, so every generated draft lands on an enabled day, at the publishing time in the campaign's timezone, nudged a little either side so a week of posts doesn't all sit on the same minute.
Closes CON-181.
Why
Today the strategist lets the model pick a bare
publishDate(YYYY-MM-DD) andpersistOnestores it as midnight UTC — no time-of-day, no weekday filter, no jitter. This makes the generatedscheduled_ata meaningful local timestamp derived from the campaign's own settings.What's included
src/scheduling/— dependency-free, unit-tested core shared by the handler and the flow so they can't drift: weekday/clock validation,EnabledWeekdays,DayLabels,SnapToPublishingDay(nearest enabled day, forward-preferred),SpreadOffset(deterministicfnv32ajitter, no RNG), andComposeScheduledAt(snater → UTC).campaignscolumns (publishing_time,timezone,publishing_daysjsonb, ampaignfields. Columndefaults (09:00` / UTC / every day / ±15) backfill existing campaigns.campaignRequestgains the four fields;normalizeScheduling()a (timeHH:MM, timezonevia
settings.ResolveTimenique, spread0..720`) →400 on bad input. Create + Update both map the normalized values.scheduled_atfrom thecampaign settings, snaps disabled-day dates to the nearest enabled day, reflects the snapped date back onto the streamedDraftPost, and surfaces the enabled weekdays to the model as a prompt hard-rule. Applies to both full-plan andCON-114 targeted generati
.httpsamples + unit tests.Locked decisions
timezonesetting is unt-assistant).spread_minutesdeterministic per-post jitter.Behavior change to call out
Content-plan drafts'
schight UTC** to a real localpublish instant (composed date @publishing_timein the campaign timezone, ± spread, stored as UTC). Drafts are not auto-published (status=draft`), so thisonly sets the suggestedd/scheduled — no publishing
behavior changes.
Migration
20260807000001_campaign_dditiveALTER TABLEcampaigns ADD COLUMN ...`the four columns.
Auto-discovered.
Testing
go build ./...,go vet ./src/...(incl.-tags integration), and gofmtclean.
schedulingunit tests: clock/weekday validity, snapping (incl. forward-preference and no-enabled-day-in-window), deterministic bounded spread, and timezone composition.; existing CON-188persistOnetests updated for the*DraftPost` signature.Deviation from the PR
The PRD suggested an SSE warning for the rare "campaign window contains no enabled weekday" case. Since
persistOnedoesn't carry theonEventsink, it's logged at WARN instead of threading the callback through — kept minimal; easy to upgrade if desired.Summary by CodeRabbit