linkedin-studio/scripts/analytics/tests/alerts.test.ts
Kjell Tore Guttormsen 4dc868ea62 feat(ultraplan-local): v1.6.0 — /ultraresearch-local deep research command
Add /ultraresearch-local for structured research combining local codebase
analysis with external knowledge via parallel agent swarms. Produces research
briefs with triangulation, confidence ratings, and source quality assessment.

New command: /ultraresearch-local with modes --quick, --local, --external, --fg.
New agents: research-orchestrator (opus), docs-researcher, community-researcher,
security-researcher, contrarian-researcher, gemini-bridge (all sonnet).
New template: research-brief-template.md.

Integration: --research flag in /ultraplan-local accepts pre-built research
briefs (up to 3), enriches the interview and exploration phases. Planning
orchestrator cross-references brief findings during synthesis.

Design principle: Context Engineering — right information to right agent at
right time. Research briefs are structured artifacts in the pipeline:
ultraresearch → brief → ultraplan --research → plan → ultraexecute.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 08:58:35 +02:00

205 lines
9.3 KiB
TypeScript

import { describe, test } from "node:test";
import assert from "node:assert/strict";
import { detectAlerts, detectWeeklyAlerts } from "../src/utils/alerts.js";
import type { PostAnalytics } from "../src/models/types.js";
/**
* Helper function to create PostAnalytics with default values.
*/
function makePost(overrides: Partial<PostAnalytics> = {}): PostAnalytics {
return {
id: `post-${Math.random().toString(36).substr(2, 9)}`,
title: "Test Post",
publishedDate: "2026-01-15",
metrics: {
impressions: 1000,
reactions: 50,
comments: 10,
shares: 5,
clicks: 100,
engagementRate: 5.0,
},
importedAt: new Date().toISOString(),
exportSource: "LinkedIn",
...overrides,
};
}
describe("alerts", () => {
describe("detectAlerts", () => {
test("should find spike posts", () => {
// Create posts with one outlier high value
// Need value that's > 2.0 standard deviations from mean (not >=)
// Using more base values to create a scenario where outlier exceeds threshold
const posts = [
makePost({ id: "1", title: "Normal Post 1", metrics: { impressions: 1000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "2", title: "Normal Post 2", metrics: { impressions: 1200, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "3", title: "Normal Post 3", metrics: { impressions: 1100, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "4", title: "Normal Post 4", metrics: { impressions: 900, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "5", title: "Normal Post 5", metrics: { impressions: 1050, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "6", title: "Viral Post", metrics: { impressions: 10000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
];
const alerts = detectAlerts(posts, "impressions");
assert.ok(alerts.length > 0, "Should detect at least one alert");
const spikeAlert = alerts.find(a => a.type === "spike");
assert.ok(spikeAlert, "Should have a spike alert");
assert.equal(spikeAlert.severity, "info");
assert.equal(spikeAlert.postId, "6");
assert.ok(spikeAlert.message.includes("Viral Post"));
});
test("should find drop posts", () => {
// Create posts with one outlier low value
const posts = [
makePost({ id: "1", title: "Normal Post 1", metrics: { impressions: 10000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "2", title: "Normal Post 2", metrics: { impressions: 10000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "3", title: "Normal Post 3", metrics: { impressions: 10000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "4", title: "Low Reach Post", metrics: { impressions: 100, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
];
const alerts = detectAlerts(posts, "impressions");
assert.equal(alerts.length, 1);
assert.equal(alerts[0].type, "drop");
assert.equal(alerts[0].severity, "warning");
assert.equal(alerts[0].postId, "4");
assert.ok(alerts[0].message.includes("Low Reach Post"));
});
test("should return empty for uniform data", () => {
const posts = [
makePost({ id: "1", metrics: { impressions: 5000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "2", metrics: { impressions: 5000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "3", metrics: { impressions: 5000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
];
const alerts = detectAlerts(posts, "impressions");
assert.equal(alerts.length, 0);
});
test("should handle empty posts array", () => {
const alerts = detectAlerts([]);
assert.equal(alerts.length, 0);
});
test("should sort alerts by severity", () => {
// Create scenario with multiple alerts of different severities
// For this, we'd need to manually create alerts with different severities
// Since detectAlerts only produces "info" spikes and "warning" drops,
// let's just verify the sorting works with what we have
const posts = [
makePost({ id: "1", metrics: { impressions: 5000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "2", metrics: { impressions: 5000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }),
makePost({ id: "3", metrics: { impressions: 100, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }), // Drop
makePost({ id: "4", metrics: { impressions: 50000, reactions: 50, comments: 10, shares: 5, clicks: 100, engagementRate: 5.0 } }), // Spike
];
const alerts = detectAlerts(posts, "impressions");
// Should have drop (warning) first, then spike (info)
if (alerts.length > 1) {
assert.equal(alerts[0].severity, "warning");
assert.equal(alerts[1].severity, "info");
}
});
});
describe("detectWeeklyAlerts", () => {
test("should detect critical drop in impressions", () => {
const current = { impressions: 1000, engagementRate: 5.0 };
const previous = { impressions: 3000, engagementRate: 5.0 }; // -66.7% drop
const alerts = detectWeeklyAlerts(current, previous);
const impressionAlerts = alerts.filter((a) => a.metric === "impressions");
assert.ok(impressionAlerts.length > 0);
assert.equal(impressionAlerts[0].severity, "critical");
assert.equal(impressionAlerts[0].type, "drop");
});
test("should detect warning drop in impressions", () => {
const current = { impressions: 6000, engagementRate: 5.0 };
const previous = { impressions: 10000, engagementRate: 5.0 }; // -40% drop
const alerts = detectWeeklyAlerts(current, previous);
const impressionAlerts = alerts.filter((a) => a.metric === "impressions");
assert.ok(impressionAlerts.length > 0);
assert.equal(impressionAlerts[0].severity, "warning");
assert.equal(impressionAlerts[0].type, "drop");
});
test("should detect spike in impressions", () => {
const current = { impressions: 25000, engagementRate: 5.0 };
const previous = { impressions: 10000, engagementRate: 5.0 }; // +150% increase
const alerts = detectWeeklyAlerts(current, previous);
const impressionAlerts = alerts.filter((a) => a.metric === "impressions");
assert.ok(impressionAlerts.length > 0);
assert.equal(impressionAlerts[0].severity, "info");
assert.equal(impressionAlerts[0].type, "spike");
});
test("should detect critical drop in engagement rate", () => {
const current = { impressions: 10000, engagementRate: 2.0 };
const previous = { impressions: 10000, engagementRate: 6.0 }; // -66.7% drop
const alerts = detectWeeklyAlerts(current, previous);
const engagementAlerts = alerts.filter((a) => a.metric === "engagementRate");
assert.ok(engagementAlerts.length > 0);
assert.equal(engagementAlerts[0].severity, "critical");
assert.equal(engagementAlerts[0].type, "drop");
});
test("should detect warning drop in engagement rate", () => {
const current = { impressions: 10000, engagementRate: 3.0 };
const previous = { impressions: 10000, engagementRate: 5.0 }; // -40% drop
const alerts = detectWeeklyAlerts(current, previous);
const engagementAlerts = alerts.filter((a) => a.metric === "engagementRate");
assert.ok(engagementAlerts.length > 0);
assert.equal(engagementAlerts[0].severity, "warning");
assert.equal(engagementAlerts[0].type, "drop");
});
test("should detect spike in engagement rate", () => {
const current = { impressions: 10000, engagementRate: 12.0 };
const previous = { impressions: 10000, engagementRate: 5.0 }; // +140% increase
const alerts = detectWeeklyAlerts(current, previous);
const engagementAlerts = alerts.filter((a) => a.metric === "engagementRate");
assert.ok(engagementAlerts.length > 0);
assert.equal(engagementAlerts[0].severity, "info");
assert.equal(engagementAlerts[0].type, "spike");
});
test("should return empty for stable metrics", () => {
const current = { impressions: 10000, engagementRate: 5.0 };
const previous = { impressions: 10200, engagementRate: 5.1 }; // Small changes
const alerts = detectWeeklyAlerts(current, previous);
assert.equal(alerts.length, 0);
});
test("should handle multiple alerts and sort by severity", () => {
const current = { impressions: 1000, engagementRate: 2.0 };
const previous = { impressions: 3000, engagementRate: 6.0 }; // Both critical drops
const alerts = detectWeeklyAlerts(current, previous);
assert.ok(alerts.length >= 2);
// All should be critical
alerts.forEach((alert) => {
assert.equal(alert.severity, "critical");
});
});
});
});