/** * Diff two snapshots (M8): what a brand won or lost between two `check` runs. * * Prompt identity is the prompt text, which is stable across runs because * queries only regenerate on `--regenerate` (M5). If the two runs asked * different prompt sets (a regenerated or hand-edited pack), the comparison is * no longer apples-to-apples: {@link SnapshotDiff.promptSetChanged} is set, and * won/lost are computed ONLY over prompts present in both runs so a * added/removed prompt is never mislabeled as a win/loss. * * A prompt counts as "mentioned" for an engine when the brand appears in ANY of * that engine's answers to it - the presence signal a won/lost flip tracks. * Pure and deterministic (results sorted). * * Direction is explicit: `a` is the earlier baseline, `b` the later run, and the * result labels `from: a.generatedAt` / `to: b.generatedAt`. Passing them * reversed yields a valid backwards-in-time diff (from > to), not a wrong one. */ import type { EngineId } from '../types.js'; import { type VisibilityEnvelope } from './envelope.js'; /** Per-engine change between two snapshots. */ export interface EngineDiff { engine: EngineId; /** `b.score - a.score` for this engine. */ scoreDelta: number; /** Prompts (in both runs) newly mentioned in b. */ wonPrompts: string[]; /** Prompts (in both runs) no longer mentioned in b. */ lostPrompts: string[]; } /** The change between two snapshots (M8). */ export interface SnapshotDiff { schema_version: string; domain: string; /** `generatedAt` of the earlier (baseline) snapshot. */ from: string; /** `generatedAt` of the later snapshot. */ to: string; /** * Headline AI Visibility Score delta (`b.score - a.score`). * * `null` when either side was not assessed (no score). A delta against a * missing score is meaningless - inventing one (e.g. treating null as 0) * would report a dramatic drop or recovery that never happened (rule #6). */ scoreDelta: number | null; /** Per-engine changes, for engines present in BOTH runs (sorted by id). */ engines: EngineDiff[]; /** * True when the two runs asked different prompt sets; deltas are then not * apples-to-apples and won/lost cover only the shared prompts. */ promptSetChanged: boolean; /** * True when an engine appeared in only one run (e.g. a key expired). The * headline `scoreDelta` then partly reflects an engine leaving the sample, * not a visibility change - and `engines` has no row for the missing one. */ engineSetChanged: boolean; /** * True when the two runs used different scoring methodologies (or one predates * scoring versioning). The `scoreDelta` is then methodology-driven, not a real * visibility move (rule #2). */ scoringChanged: boolean; /** * True when a shared engine retrieved on a different share of its answers * than last time. Its composite weight moves with that share (SCORING_VERSION * 3), so the headline `scoreDelta` partly reflects the engines choosing to * search more or less - not a change in how often you were recommended. The * per-engine deltas cannot show it: those scores are weight-independent. */ retrievalChanged: boolean; /** * True when either compared run was partial (any signal counted by * `isPartialRun`; see `partialCauses` for the causes that actually fired, or * skipped engines). The delta is then not a full-confidence change (rule #6). */ partial: boolean; } export declare function diffEnvelopes(a: VisibilityEnvelope, b: VisibilityEnvelope): SnapshotDiff;