TypeScript 55.4%
Python 43.2%
SQL 1.2%
1import { describe, expect, it } from "vitest";2import { diffJson, diffList, diffText } from "./diff";3import { classifyChange, classifyLine, extractFieldChanges, isCosmeticPair, isTimestampPair } from "./semantic";4import { parseSearch, stringifySearch } from "./search";5import { breakingState, computeImpact, computeSignalScore, dailyAnomaly, velocityScore } from "./scoring";6import { evaluateChange } from "./heuristics";78// ---- Source simulator fixtures (spec §89) --------------------------------------------------------9const PRICING_BEFORE = `API Pricing\nInput tokens: $10.00 / million tokens\nOutput tokens: $30.00 / million tokens\nContext window: 128k tokens\nStatus: beta\n© 2025 Example AI. All rights reserved.`;10const PRICING_AFTER = `API Pricing\nInput tokens: $8.00 / million tokens\nOutput tokens: $24.00 / million tokens\nContext window: 256k tokens\nStatus: general availability\n© 2026 Example AI. All rights reserved.`;1112describe("semantic line classification", () => {13 it("recognizes noise lines", () => {14 expect(classifyLine("© 2026 Example Inc. All rights reserved.")).toBe("boilerplate");15 expect(classifyLine("2026")).toBe("boilerplate");16 expect(classifyLine("Updated 3 minutes ago")).toBe("timestamp");17 expect(classifyLine("2026-09-11T04:12:00Z")).toBe("timestamp");18 expect(classifyLine("5 min read")).toBe("timestamp");19 expect(classifyLine("1,204 views")).toBe("timestamp");20 expect(classifyLine("Skip to main content")).toBe("navigation");21 expect(classifyLine("Home › Products › Pricing")).toBe("navigation");22 expect(classifyLine("Subscribe now to our newsletter and save 20%")).toBe("advertisement");23 expect(classifyLine("We use cookies to improve your experience. Accept all")).toBe("boilerplate");24 });25 it("keeps real content", () => {26 expect(classifyLine("Input tokens: $8.00 / million tokens")).toBe("content");27 expect(classifyLine("The company appointed Jane Doe as Chief Financial Officer effective October 1.")).toBe("content");28 });29 it("detects cosmetic and timestamp-only pairs", () => {30 expect(isCosmeticPair("Hello, World!", "hello world")).toBe(true);31 expect(isCosmeticPair("Price: $10", "Price: $12")).toBe(false);32 expect(isTimestampPair("Last updated 2026-09-10 12:00 UTC", "Last updated 2026-09-11 08:30 UTC")).toBe(true);33 expect(isTimestampPair("Rate: 4.25%", "Rate: 4.00%")).toBe(false);34 });35});3637describe("classifyChange", () => {38 it("classifies a footer-year bump as boilerplate noise", () => {39 const d = diffText("Welcome\n© 2025 Acme", "Welcome\n© 2026 Acme");40 const r = classifyChange(d, { url: "https://acme.com/", sensorType: "HTML" });41 expect(["boilerplate", "cosmetic"]).toContain(r.class);42 expect(r.contentLines).toBe(0);43 });44 it("classifies a relative-time bump as timestamp noise", () => {45 const d = diffText("Status\nUpdated 2 minutes ago\nAll systems operational", "Status\nUpdated 7 minutes ago\nAll systems operational");46 const r = classifyChange(d, { url: "https://status.acme.com/", sensorType: "HTML" });47 expect(r.class).toBe("timestamp");48 });49 it("classifies a pricing change as pricing and extracts fields with deltas", () => {50 const d = diffText(PRICING_BEFORE, PRICING_AFTER);51 const r = classifyChange(d, { url: "https://example-ai.com/pricing", sensorType: "HTML", title: "API Pricing" });52 expect(r.class).toBe("pricing");53 const input = r.fieldChanges.find((f) => f.kind === "price" && f.before?.includes("10.00"));54 expect(input).toBeTruthy();55 expect(input!.after).toContain("8.00");56 expect(input!.deltaPct).toBe(-20);57 const ctx = r.fieldChanges.find((f) => f.label.toLowerCase().includes("context"));58 expect(ctx?.before).toContain("128k");59 expect(ctx?.after).toContain("256k");60 const status = r.fieldChanges.find((f) => f.kind === "status");61 expect(status?.before).toBe("beta");62 expect(status?.after).toBe("general availability");63 });64 it("classifies a leadership change as personnel", () => {65 const d = diffText("Leadership\nCEO: John Smith\nCFO: Ana Ruiz", "Leadership\nCEO: Maria Chen\nCFO: Ana Ruiz");66 const r = classifyChange(d, { url: "https://acme.com/about/leadership", sensorType: "HTML" });67 expect(r.class).toBe("personnel");68 expect(r.fieldChanges.some((f) => f.label === "CEO" && f.before === "John Smith" && f.after === "Maria Chen")).toBe(true);69 });70 it("treats a redesign burst of short fragments as navigation churn", () => {71 const before = Array.from({ length: 30 }, (_, i) => `Item ${i}`).join("\n");72 const after = Array.from({ length: 30 }, (_, i) => `Nav ${i}`).join("\n");73 const r = classifyChange(diffText(before, after), { url: "https://acme.com/", sensorType: "HTML" });74 expect(r.reasons.some((x) => /template|navigation/.test(x))).toBe(true);75 });76 it("classifies JSON volatile paths as timestamp noise and real fields as meaningful", () => {77 const noise = classifyChange(diffJson({ updated_at: 1, a: 1 }, { updated_at: 2, a: 1 }), { url: "https://api.acme.com/x", sensorType: "JSON" });78 expect(noise.class).toBe("timestamp");79 const real = classifyChange(diffJson({ limit: 100, status: "beta" }, { limit: 200, status: "ga" }), { url: "https://api.acme.com/x", sensorType: "JSON" });80 expect(["meaningful", "product"]).toContain(real.class);81 expect(real.fieldChanges.find((f) => f.label.includes("limit"))?.deltaPct).toBe(100);82 });83 it("treats new feed items as meaningful", () => {84 const d = diffList([{ key: "a", title: "Old post" }], [{ key: "a", title: "Old post" }, { key: "b", title: "Acme launches Widget 2", summary: "Today we introduce…" }]);85 const r = classifyChange(d, { url: "https://acme.com/feed.xml", sensorType: "RSS" });86 expect(["meaningful", "product"]).toContain(r.class);87 });88});8990describe("heuristics: new event classes", () => {91 const ctx = { sensorType: "RSS", url: "https://vendor.example/advisories.xml", sourceCategories: ["cyber"] };92 it("detects active exploitation from KEV-like records", () => {93 const d = diffList([], [{ key: "CVE-2026-1234", title: "Acme Router Command Injection", cveID: "CVE-2026-1234", dateAdded: "2026-09-11", knownRansomwareCampaignUse: "Known" }]);94 const h = evaluateChange(d, { ...ctx, url: "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json", sensorType: "REST_API" });95 expect(h.eventType).toBe("active_exploitation");96 });97 it("detects zero-day wording", () => {98 const d = diffList([], [{ key: "x", title: "Zero-day in WidgetOS exploited in the wild, no patch available", summary: "…" }]);99 expect(["zero_day", "active_exploitation"]).toContain(evaluateChange(d, ctx).eventType);100 });101 it("detects supply-chain compromise", () => {102 const d = diffList([], [{ key: "x", title: "Malicious npm package typosquatting popular library steals tokens", summary: "supply-chain attack" }]);103 expect(evaluateChange(d, ctx).eventType).toBe("supply_chain_attack");104 });105 it("detects guidance and buyback in finance feeds", () => {106 const fin = { ...ctx, sourceCategories: ["finance"], url: "https://ir.example.com/rss" };107 expect(evaluateChange(diffList([], [{ key: "1", title: "Acme raises full-year guidance after strong quarter" }]), fin).eventType).toBe("guidance");108 expect(evaluateChange(diffList([], [{ key: "2", title: "Acme announces $5 billion share repurchase program" }]), fin).eventType).toBe("buyback");109 });110 it("detects emergency notices and outbreaks", () => {111 const gov = { ...ctx, sourceCategories: ["government"], url: "https://gov.example/news.rss" };112 expect(evaluateChange(diffList([], [{ key: "1", title: "State of emergency declared as wildfire evacuation order expands" }]), gov).eventType).toBe("emergency_notice");113 expect(evaluateChange(diffList([], [{ key: "2", title: "Measles outbreak: 42 cases confirmed in three regions" }]), { ...gov, sourceCategories: ["health"] }).eventType).toBe("outbreak");114 });115 it("keeps sports results in their own class", () => {116 const sp = { ...ctx, sourceCategories: ["sports"], url: "https://league.example/news.rss" };117 expect(["sports_result", "sports_transaction", "announcement"]).toContain(evaluateChange(diffList([], [{ key: "1", title: "Final score: Montréal defeats Toronto 4-2 in overtime" }]), sp).eventType);118 });119});120121describe("search syntax", () => {122 it("parses keys, quotes, relative dates and free text", () => {123 const p = parseSearch('openai pricing entity:openai type:pricing_change,terms_change after:7d silent:true importance:>70 source:"bank of canada" country:ca lang:FR is:confirmed');124 expect(p.text).toBe("openai pricing");125 expect(p.filters.entity).toBe("org_openai");126 expect(p.filters.event_type).toBe("pricing_change,terms_change");127 expect(p.filters.silent_change).toBe(true);128 expect(p.filters.importance_min).toBe(70);129 expect(p.filters.source).toBe("bank-of-canada");130 expect(p.filters.country).toBe("CA");131 expect(p.filters.language).toBe("fr");132 expect(p.filters.confirmed).toBe(true);133 expect(new Date(p.filters.after!).getTime()).toBeLessThan(Date.now());134 });135 it("round-trips through stringifySearch", () => {136 const s = stringifySearch({ text: "gpu", filters: { entity: "org_nvidia", importance_min: 60, silent_change: false } });137 const p = parseSearch(s);138 expect(p.text).toBe("gpu");139 expect(p.filters.entity).toBe("org_nvidia");140 expect(p.filters.importance_min).toBe(60);141 expect(p.filters.silent_change).toBe(false);142 });143});144145describe("scores", () => {146 it("impact grows with numeric magnitude", () => {147 const small = computeImpact({ eventType: "pricing_change", magnitude: 30, entityImportance: 80, maxDeltaPct: 5, fieldChanges: 1, firstParty: true });148 const big = computeImpact({ eventType: "pricing_change", magnitude: 30, entityImportance: 80, maxDeltaPct: 400, fieldChanges: 1, firstParty: true });149 expect(big).toBeGreaterThan(small);150 });151 it("velocity grows with signals and sources", () => {152 expect(velocityScore({ signals: 1, windowHours: 1, uniqueSources: 1, firstPartySignals: 1 })).toBeLessThanOrEqual(15);153 expect(velocityScore({ signals: 17, windowHours: 1, uniqueSources: 9, firstPartySignals: 3 })).toBeGreaterThan(60);154 });155 it("signal score is explainable and penalizes noise classes", () => {156 const good = computeSignalScore({ importance: 88, confidence: 90, novelty: 85, velocity: 55, impact: 80, anomaly: 65, confirmations: 3, firstParty: true, silent: false, sourceTier: "S" });157 expect(good.score).toBeGreaterThan(80);158 expect(good.reasons.some((r) => r.sign === "+" && /confirmation/.test(r.text))).toBe(true);159 const noise = computeSignalScore({ importance: 60, confidence: 60, novelty: 60, velocity: 0, impact: 40, anomaly: 0, confirmations: 0, firstParty: true, silent: false, changeClass: "timestamp" });160 expect(noise.score).toBeLessThanOrEqual(20);161 });162 it("breaking state depends on strength, confirmation and age", () => {163 expect(breakingState({ signal: 85, importance: 90, velocity: 40, confirmations: 2, firstPartyCount: 1, ageMinutes: 30 })).toBe("breaking");164 expect(breakingState({ signal: 85, importance: 90, velocity: 40, confirmations: 2, firstPartyCount: 1, ageMinutes: 20 * 60 })).toBe("confirmed");165 expect(breakingState({ signal: 65, importance: 60, velocity: 40, confirmations: 0, firstPartyCount: 1, ageMinutes: 60 })).toBe("developing");166 expect(breakingState({ signal: 40, importance: 40, velocity: 5, confirmations: 0, firstPartyCount: 1, ageMinutes: 60 })).toBe("watching");167 expect(breakingState({ signal: 95, importance: 95, velocity: 90, confirmations: 5, firstPartyCount: 3, ageMinutes: 5 * 24 * 60 })).toBe("closed");168 });169 it("daily anomaly reports percent versus baseline", () => {170 const a = dailyAnomaly(38, 2, 24);171 expect(a.pct).toBeGreaterThan(1000);172 expect(a.score).toBeGreaterThan(80);173 expect(dailyAnomaly(1, 2, 24).score).toBeLessThan(30);174 });175});176177describe("field extraction on structured lists", () => {178 it("extracts status flips in keyed lists", () => {179 const d = diffList([{ key: "NCT1", title: "Trial A", status: "Recruiting" }], [{ key: "NCT1", title: "Trial A", status: "Terminated" }], ["status"]);180 const f = extractFieldChanges(d);181 expect(f[0]?.before).toBe("Recruiting");182 expect(f[0]?.after).toBe("Terminated");183 expect(f[0]?.kind).toBe("status");184 });185});186187describe("describeChange titles", () => {188 it("humanizes URL-only sitemap items and lists batch labels", async () => {189 const { humanizeUrl, describeChange, evaluateChange } = await import("./heuristics");190 expect(humanizeUrl("https://www.daiichisankyo.com/sustainability/performance-reports/news/detail/index_4751.html")).toBe("performance reports / news / detail / index 4751 (daiichisankyo.com)");191 const d = diffList([], [{ key: "a", url: "https://acme.com/news/new-widget-launch.html" }]);192 const h = evaluateChange(d, { sensorType: "SITEMAP", url: "https://acme.com/sitemap.xml", sourceCategories: [] });193 const t = describeChange(h, d, { sourceName: "Acme", url: "https://acme.com/sitemap.xml", sensorName: "sitemap" });194 expect(t.title).toBe("Acme: news / new widget launch (acme.com)");195 const many = diffList([], Array.from({ length: 12 }, (_, i) => ({ key: `CVE-2026-${1000 + i}`, title: `CVE-2026-${1000 + i}` })));196 const h2 = evaluateChange(many, { sensorType: "REST_API", url: "https://services.nvd.nist.gov/rest/json/cves/2.0", sourceCategories: ["cyber"] });197 const t2 = describeChange(h2, many, { sourceName: "NVD", url: "https://services.nvd.nist.gov/rest/json/cves/2.0", sensorName: "recent cves" });198 expect(t2.title.startsWith("NVD: 12 new CVEs in recent cves — CVE-2026-1000")).toBe(true);199 expect(h2.eventType).toBe("vulnerability");200 });201});202