Sitelet https://github.com/ansvisor/ansvisor/pull/561
Skip to content

feat(pulse): per-brand Daily Pulse digest with highlights, warnings and email/webhook delivery - #561

Merged
gkhngyk merged 2 commits into
mainfrom
feature/540-daily-pulse
Jul 30, 2026
Merged

gkhngyk merged 2 commits into
mainfrom
feature/540-daily-pulse

Conversation

@gkhngyk

@gkhngyk gkhngyk commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements Daily Pulse (#540): a per-brand digest generated right after the daily tracking run — good news and warnings in one email instead of an alerts-only bad-news machine.

Content (server/src/lib/pulse/metrics.js) — computed through the same RPCs the Insights dashboard uses (visible_prompt_stats, tracked_prompt_count, insights_aggregates, competitor_aggregates, prompt_performance_aggregates) with the same rounding, so the email always matches the dashboard:

  • KPI strip — visibility rate (24h window, or 7d for weekly pulses), 7-day trend vs the previous 7 days, mentions / citations / positive-sentiment deltas
  • Highlights — first-time citations of target URLs (prompt_target_urls.first_cited_at), top 3 prompts by weekly visibility gain (≥5 pts), competitors overtaken on the leaderboard, first appearance on a new answer engine
  • Warnings — three detectors per the issue spec: sharp visibility drop (≥15 pts and ≥30% relative, ≥10-prompt floor), competitor surge (≥15 pts WoW or crossing above the brand's rate), high-volume prompt losing citations (cited on ≥60% of result-days over 14d, then 0 citations for 3 consecutive result-days)
  • Platform-outage guard — if a platform's result volume across all orgs collapses below 25% of its trailing 7-day daily average, drop-type warnings are suppressed and the email carries a data-collection note instead

Delivery (server/src/lib/pulse/engine.js):

  • Email via the Resend HTTP API (no SDK dependency), cloud only (isCloud() gate), with a List-Unsubscribe header and a settings deep link. Recipients: explicit per-brand list, or every org member by default.
  • daily_pulse.created webhook event on cloud and self-host alike, through a new shared dispatchBrandWebhook() helper extracted from the Content routes (same flat payload + X-Webhook-Secret format).
  • Eligibility hard-gates: only active/trialing orgs, active brands, and only when the window has fresh results. One pulse per brand per day, race-safe via a UNIQUE(brand_id, pulse_date) claim. Warnings repeat at most once per subject per 7 days (warning_keys cooldown).
  • Triggered fire-and-forget from job-runner.js after a completed non-immediate tracking job — manual runs and single-prompt refreshes never send pulses. A pulse failure can never fail the tracking job.

Settings — new Settings → Notifications section: per-brand frequency (Daily / Weekly / Only when notable / Off, default Daily) and recipient list, admin/manager writable, backed by GET/PUT /api/settings/pulse. Weekly pulses run the same engine over a 7-day window and send on Mondays.

Data model — migration 00036_daily_pulse.sql: pulse_settings + sent_pulses with member-select RLS (same shape as topic_suggestions). Already applied to the hosted database and verified live.

Also: README "Anomaly alerts" TODO updated to reference this feature, CHANGELOG entry, webhook docs updated with the new event, server/.env.example gains RESEND_API_KEY / RESEND_FROM_EMAIL / PUBLIC_APP_URL, and a manual trigger script (node src/scripts/test-pulse.js <brandId>).

Note: migration 00035 is reserved by #560 — if that merges after this PR, supabase/schema.sql needs a quick bash supabase/build-schema.sh regen on whichever lands second.

Related issue

Closes #540

Type of change

  • feat — New feature
  • fix — Bug fix
  • chore — Maintenance / dependencies
  • docs — Documentation only
  • refactor — Code change that neither fixes a bug nor adds a feature
  • test — Adding or updating tests

How to test

  1. Set RESEND_API_KEY, RESEND_FROM_EMAIL and PUBLIC_APP_URL in server/.env (cloud mode: IS_CLOUD=true).
  2. Run node src/scripts/test-pulse.js <brandId> from server/ for a brand with fresh results — an email arrives for each org member, a sent_pulses row is written, and re-running skips with already_sent.
  3. Tested live: pulse generated from ~1.2k same-day results, email delivered to 2 recipients, KPI/highlight/warning numbers verified against the Insights dashboard for the same windows.
  4. Settings → Notifications: change a brand's frequency to Off, re-trigger — pulse skips.

Checklist

  • Branch follows the naming convention (feature/, fix/, chore/, docs/) — see CONTRIBUTING.md
  • Commits follow Conventional Commits
  • yarn lint passes (run from web/)
  • yarn typecheck passes (run from web/)
  • yarn format:check passes (run from web/)
  • Changes are focused — one concern per PR

@gkhngyk
gkhngyk merged commit e04836c into main Jul 30, 2026
4 checks passed
@gkhngyk
gkhngyk deleted the feature/540-daily-pulse branch July 30, 2026 15:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Daily Pulse email — cloud-only per-brand daily digest with highlights and anomaly alerts

1 participant