Skip to content

CON-182: Campaign Goals - #105

Merged
grsmv merged 2 commits into
mainfrom
feature/con-182-campaign-goals
Aug 6, 2026
Merged

CON-182: Campaign Goals#105
grsmv merged 2 commits into
mainfrom
feature/con-182-campaign-goals

Conversation

@grsmv

@grsmv grsmv commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Adds a campaign post-rate goal (CON-182). Instead of a single absolute "estimated posts" number, a campaign now expresses a goal as a rateN posts counted over each week / month — which:

  1. drives how many drafts the content_plan flow generates, and
  2. is what the campaign overview measures progress against (per-period + overall, with a streak for gamification).

Reuses the existing estimated_post_count as the posts-per-period count and adds one new goal_cadence column.

Stacked on CON-181 (feature/con-181-campaign-scheduling-defaults). Merge CON-181 first; this PR then shows only the goals diff against main.

What changed

Data model

  • Migration 20260808000001_campaign_goals: adds goal_cadence TEXT NOT NULL DEFAULT 'month'.
  • estimated_post_count is reinterpreted as the target posts per cadence period.
ALTER TABLE campaigns
    ADD COLUMN goal_cadence TEXT NOT NULL DEFAULT 'month';

New pure package src/campaigngoal/ (stdlib-only, no import cycles, fully unit-tested)

  • Normalize / ValidCadenceweek | month, empty → month.
  • Periods(cadence, start, end, loc) — number of periods, partial periods rounded up. Weeks = ceil(daysInclusive/7); months = calendar months touched. Missing/invalid dates → 1. Day-counting done in UTC so DST-length days don't skew it.
  • EffectiveCount(...)perPeriod × Periods (0 when unset → model decides the count, unchanged behavior).
  • Windows(...) — the [Start, End) periods (labelled "Week 1", "Jul 2026") used for progress buckets.

content_plan (generate.go)

  • Full-campaign plan estCount now derives from campaigngoal.EffectiveCount(...) (timezone resolved via settings.ResolveTimezone). Everything downstream — planBatches, the "exactly N posts" prompt, phase/platform/date distribution — is unchanged.
  • The targeted POST /:id/generate-posts path still uses its explicit count (not multiplied).

REST (handlers/campaigns.go)

  • goal_cadence added to the campaign request DTO, validated via campaigngoal.Normalize (invalid → 400) in both Create and Update, mirroring the normalizeScheduling pattern.

Overview API (GET /api/campaigns/:id/overview)

  • New nullable goal block. Buckets count committed posts (scheduled / scheduled_for_manual_publishing / published) by their scheduled_at, against the per-period target, with an overall verdict and a trailing streak. null when the campaign has no positive estimated_post_count.
"goal": {
  "cadence": "week", "postsPerPeriod": 5, "periods": 2,
  "totalTarget": 10, "totalAchieved": 8, "reached": false, "percent": 80, "streak": 1,
  "buckets": [
    {"index":1,"label":"Week 1","start":"2026-06-01T00:00:00Z","end":"2026-06-08T00:00:00Z","target":5,"achieved":5,"reached":true},
    {"index":2,"label":"Week 2","start":"2026-06-08T00:00:00Z","end":"2026-06-15T00:00:00Z","target":5,"achieved":3,"reached":false}
  ]
}

⚠️ Behavior change on existing rows

The migration backfills goal_cadence = 'month', so every existing campaign's estimated_post_count now means per month. On the next full-plan generation the effective total becomes count × months-spanned instead of count. content_plan is user-triggered, so nothing regenerates until a user asks.

Decisions (from PRD)

  • Campaign-wide single goal (per-platform goals deferred to a follow-up).
  • Cadence week | month, default month.
  • Partial periods round up (a 2-month span ≈ 9 weekly quotas, from real dates).
  • Progress metric = scheduled + published, bucketed by scheduled_at.

Testing

  • go build ./..., go vet — clean.
  • src/campaigngoal unit tests: periods (incl. 5/week over Jun 1→Jul 31 = 9 → 45), effective count, windows tiling/labels, normalize.
  • src/campaign_actions/overview goal tests: weekly buckets, streak + 100%, missing-dates degrade, nil-goal.
  • Note: src/handlers package test requires a local Postgres (pre-existing; unrelated to this change).

Related

CON-181 (scheduling defaults — same field-add + content_plan-wiring pattern, base of this branch), CON-152 (summaries — future home for a goal badge), CON-113 (overview endpoint this extends).

Summary by CodeRabbit

  • New Features
    • Configure campaign publishing time, timezone, weekdays, and spread intervals.
    • Set weekly or monthly campaign goal cadences and view progress, targets, achievements, and streaks.
    • Generated content plans now respect campaign publishing days and scheduling settings.
  • Bug Fixes
    • Improved handling of invalid scheduling values, missing dates, timezones, and unavailable publishing days.
    • Scheduled publication timestamps now accurately reflect configured dates and times.
  • Validation
    • Added safeguards for invalid clocks, weekdays, duplicate days, timezone values, and spread limits.

@linear-code

linear-code Bot commented Aug 6, 2026

Copy link
Copy Markdown

CON-182

@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: 27 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: a12a3593-c007-4f97-a2a3-40c9c64ee9f7

📥 Commits

Reviewing files that changed from the base of the PR and between d8c6f5a and 6df0ce2.

📒 Files selected for processing (10)
  • src/campaign_actions/overview/overview.go
  • src/campaign_actions/overview/service.go
  • src/campaign_actions/overview/service_test.go
  • src/campaigngoal/campaigngoal.go
  • src/campaigngoal/campaigngoal_test.go
  • src/database/migrations/20260808000001_campaign_goals.down.sql
  • src/database/migrations/20260808000001_campaign_goals.up.sql
  • src/genkit/flows/content_plan/generate.go
  • src/handlers/campaigns.go
  • src/models/campaign.go

Walkthrough

Campaigns now support validated publishing schedules and weekly or monthly goal cadence. Content generation uses timezone-aware scheduling and deterministic spread. Campaign overviews expose goal progress by period, including achievement, reach, percentage, and streak data.

Changes

Campaign planning

Layer / File(s) Summary
Scheduling foundation
src/scheduling/*, src/models/campaign.go, src/database/migrations/*campaign_scheduling*
Adds scheduling defaults, weekday validation, timezone-aware date snapping, deterministic spread, timestamp composition, campaign fields, and database columns.
Campaign configuration API
src/handlers/campaigns.go, http-client/campaigns/campaigns.http
Campaign creation and updates validate, normalize, and persist scheduling settings and goal cadence. HTTP requests cover valid fields and invalid-value responses.
Goal period calculations
src/campaigngoal/*, src/database/migrations/*campaign_goals*
Adds cadence normalization, effective target counts, and timezone-aware weekly or monthly campaign windows.
Content-plan scheduling
src/genkit/flows/content_plan/*
Generation uses cadence-based counts and publishing-day labels. Persistence composes scheduled timestamps and updates draft publish dates.
Overview goal progress
src/campaign_actions/overview/*
Overview aggregation reports goal buckets, committed-post counts, reached status, completion percentage, and trailing streaks.

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

Sequence Diagram(s)

sequenceDiagram
  participant ContentPlanGeneration
  participant PersistOne
  participant Scheduling
  ContentPlanGeneration->>PersistOne: pass mutable DraftPost
  PersistOne->>Scheduling: compose scheduled timestamp
  Scheduling-->>PersistOne: UTC timestamp and effective date
  PersistOne-->>ContentPlanGeneration: persist updated DraftPost
Loading

Possibly related PRs

  • ogen-app/ogen#42: Shares post-scheduling and timezone code with this campaign scheduling change.
  • ogen-app/ogen#69: Also modifies content-plan generation and persistOne.
  • ogen-app/ogen#72: Also modifies post-generation and persistence flow in generate.go.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 post-rate goals and related goal cadence support.
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 💡 1
📝 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-182-campaign-goals

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.

@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/campaign_actions/overview/service.go`:
- Around line 170-178: The no-window fallback in the overview service must apply
the scheduled_at requirement consistently: update the total-count loop around
isCommitted to increment only when p.ScheduledAt is non-nil. Update
src/campaign_actions/overview/service_test.go lines 271-292 to expect only the
committed post with ScheduledAt; the service.go site requires the implementation
change, and the test site requires the expectation update.

In `@src/scheduling/scheduling.go`:
- Around line 181-182: Update the scheduling logic around SpreadOffset and the
returned effectiveDate so the spread-adjusted local time is clamped to the same
day’s 00:00–23:59 boundaries before converting it to UTC; ensure the returned
date remains consistent with that bounded time, and add tests covering positive
and negative spreads at both day boundaries.
🪄 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: ef61975f-f599-4b11-ab40-dd8907092009

📥 Commits

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

📒 Files selected for processing (18)
  • http-client/campaigns/campaigns.http
  • src/campaign_actions/overview/overview.go
  • src/campaign_actions/overview/service.go
  • src/campaign_actions/overview/service_test.go
  • src/campaigngoal/campaigngoal.go
  • src/campaigngoal/campaigngoal_test.go
  • src/database/migrations/20260807000001_campaign_scheduling.down.sql
  • src/database/migrations/20260807000001_campaign_scheduling.up.sql
  • src/database/migrations/20260808000001_campaign_goals.down.sql
  • src/database/migrations/20260808000001_campaign_goals.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/campaign_actions/overview/service.go
Comment thread src/scheduling/scheduling.go Outdated
grsmv added 2 commits August 6, 2026 21:43
Reinterpret estimated_post_count as posts PER goal_cadence period and add
a goal_cadence column (week|month, default month). The full-campaign
content-plan flow now generates estimated_post_count x periods-spanned
posts; the targeted generate-posts path keeps its explicit count.

- migration 20260808000001_campaign_goals (goal_cadence, backfill 'month')
- new pure campaigngoal pkg: Normalize/Periods(ceil)/EffectiveCount/Windows
- model + campaignRequest field, validated in Create/Update
- overview gains a nullable goal block: per-period buckets of committed
  (scheduled+published, by scheduled_at) posts vs target, plus overall
  reached/percent and a trailing streak
- unit tests for campaigngoal math and overview goal progress
Make the overview goal fallback (missing/invalid campaign dates)
consistent with the windowed branch: TotalAchieved counts committed
posts only when scheduled_at is set, so an undated post can't count
toward a goal it can't be attributed to a period of.
@grsmv
grsmv force-pushed the feature/con-182-campaign-goals branch from cf149e4 to 6df0ce2 Compare August 6, 2026 18:51
@grsmv
grsmv merged commit 5668e0f into main Aug 6, 2026
1 check passed
@grsmv
grsmv deleted the feature/con-182-campaign-goals 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