Skip to content

CON-181: Campaign Scheduling defaults - #104

Merged
grsmv merged 3 commits into
mainfrom
feature/con-181-campaign-scheduling-defaults
Aug 6, 2026
Merged

CON-181: Campaign Scheduling defaults#104
grsmv merged 3 commits into
mainfrom
feature/con-181-campaign-scheduling-defaults

Conversation

@grsmv

@grsmv grsmv commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

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) and persistOne stores it as midnight UTC — no time-of-day, no weekday filter, no jitter. This makes the generated scheduled_at a meaningful local timestamp derived from the campaign's own settings.

What's included

  • New pure package 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 (deterministic fnv32a jitter, no RNG), and
    ComposeScheduledAt (snater → UTC).
  • Data model — 4 new campaigns columns (publishing_time, timezone,
    publishing_days jsonb, ampaign fields. Columndefaults (09:00` / UTC / every day / ±15) backfill existing campaigns.
  • Campaign APIcampaignRequest gains the four fields;
    normalizeScheduling() a (time HH:MM, timezone
    via settings.ResolveTimenique, spread 0..720`) →400 on bad input. Create + Update both map the normalized values.
  • **Content-plan consumpt scheduled_at from thecampaign settings, snaps disabled-day dates to the nearest enabled day, reflects the snapped date back onto the streamed DraftPost, and surfaces the enabled weekdays to the model as a prompt hard-rule. Applies to both full-plan and
    CON-114 targeted generati
  • .http samples + unit tests.

Locked decisions

Decision Choice
Where settings live inheritance* — eachcampaign owns tz/time/days/spread with hardcoded defaults. The existing workspace
timezone setting is unt-assistant).
Spread field Includedspread_minutes deterministic per-post jitter.
Publishing-days enforcement Model guidance + server snap — the prompt
lists allowed weekdays; te to the nearest enabled
day (the "force strategistee).

Behavior change to call out

Content-plan drafts' schight UTC** to a real localpublish instant (composed date @ publishing_time in the campaign timezone, ± spread, stored as UTC). Drafts are not auto-published (status=draft`), so this
only sets the suggestedd/scheduled — no publishing
behavior changes.

scheduled_at = snapToEnabledDay(model_date) @ publishing_time (campaign tz)
             ± determinisstable across re-runs)
             -> UTC

Migration

20260807000001_campaign_dditive ALTER TABLE
campaigns ADD COLUMN ...`the four columns.
Auto-discovered.

Testing

  • go build ./..., go vet ./src/... (incl. -tags integration), and gofmt
    clean.
  • New scheduling unit tests: clock/weekday validity, snapping (incl. forward-preference and no-enabled-day-in-window), deterministic bounded spread, and timezone composition.
  • Content-plan test asser; existing CON-188persistOnetests updated for the*DraftPost` signature.
  • DB-backed campaign testrun in the sandbox).

Deviation from the PR

The PRD suggested an SSE warning for the rare "campaign window contains no enabled weekday" case. Since persistOne doesn't carry the onEvent sink, it's logged at WARN instead of threading the callback through — kept minimal; easy to upgrade if desired.

Summary by CodeRabbit

  • New Features
    • Added campaign publishing schedules with configurable time, timezone, weekdays, and posting spread.
    • Campaigns now schedule content on eligible publishing days and apply timezone-aware publish times.
    • Added deterministic spacing between posts published on the same day.
    • Content generation now considers configured publishing weekdays.
  • Bug Fixes
    • Added validation for invalid times, timezones, weekdays, duplicate days, and spread values, returning clear request errors.
    • Existing campaigns receive safe default scheduling settings.

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
@grsmv grsmv added the to test label Aug 6, 2026
@linear-code

linear-code Bot commented Aug 6, 2026

Copy link
Copy Markdown

CON-181

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@grsmv, you've reached your PR review limit, so we couldn't start this review.

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4e0b88a6-bbc9-4fe5-8f99-c30e896a1908

📥 Commits

Reviewing files that changed from the base of the PR and between abc4a0d and b4c4f5b.

📒 Files selected for processing (4)
  • src/genkit/flows/content_plan/generate.go
  • src/genkit/flows/content_plan/generate_test.go
  • src/scheduling/scheduling.go
  • src/scheduling/scheduling_test.go

Walkthrough

Campaigns 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.

Changes

Campaign scheduling

Layer / File(s) Summary
Scheduling model and calculation
src/database/migrations/*, src/models/campaign.go, src/scheduling/*
Campaign scheduling fields, defaults, validation, weekday snapping, deterministic spreading, and UTC timestamp composition are added with tests.
Campaign API scheduling fields
src/handlers/campaigns.go, http-client/campaigns/campaigns.http
Create and update requests normalize and validate scheduling values before persistence. HTTP examples cover valid and invalid inputs.
Content-plan scheduling integration
src/genkit/flows/content_plan/...
Prompt data includes publishing-day labels. Draft-post persistence computes scheduled timestamps and updates effective publish dates.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • ogen-app/ogen#105: Directly overlaps with the campaign scheduling fields and scheduling logic.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding campaign scheduling defaults and settings.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/con-181-campaign-scheduling-defaults

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@grsmv

grsmv commented Aug 6, 2026

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ogen-app ogen-app deleted a comment from coderabbitai Bot Aug 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between a3f0bcf and abc4a0d.

📒 Files selected for processing (11)
  • http-client/campaigns/campaigns.http
  • src/database/migrations/20260807000001_campaign_scheduling.down.sql
  • src/database/migrations/20260807000001_campaign_scheduling.up.sql
  • src/genkit/flows/content_plan/generate.go
  • src/genkit/flows/content_plan/generate_test.go
  • src/genkit/flows/content_plan/prompts/content_plan.tmpl
  • src/genkit/flows/content_plan/types.go
  • src/handlers/campaigns.go
  • src/models/campaign.go
  • src/scheduling/scheduling.go
  • src/scheduling/scheduling_test.go

Comment thread src/genkit/flows/content_plan/generate.go
Comment thread src/scheduling/scheduling.go Outdated
grsmv and others added 2 commits August 6, 2026 21:36
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>
@grsmv
grsmv merged commit d8c6f5a into main Aug 6, 2026
1 check passed
@grsmv
grsmv deleted the feature/con-181-campaign-scheduling-defaults branch August 10, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant