class-ai-anomaly.php

AI anomaly detection — thresholds, breach/alarm state, RUM digest, CSS-refresh reactions.

Source includes/Insight/class-ai-anomaly.php12 min readPart of Performance Optimisation

includes/Insight/class-ai-anomaly.php

AI anomaly detection — thresholds, breach/alarm state, RUM digest, CSS-refresh reactions.

Namespace: PerformanceOptimise\\Inc · Lines: 2619

Class Ai_Anomaly

Detects performance regressions and reacts to them.

Source: includes/Insight/class-ai-anomaly.php, line 33

class Ai_Anomaly

Tags: @since 2.4.0

Constants

ConstantVisibilityValueLine
ANOMALY_COOLDOWN_KEYprivate\'wppo_ai_anomaly_last_alarm\'107
ANOMALY_COOLDOWN_DAYSprivate7115
ANOMALY_MIN_SAMPLESprivate10123
CLS_ABSOLUTE_DELTAprivate0.05131
LCP_RELATIVE_MULTIPLIERprivate1.3139
INP_RELATIVE_MULTIPLIERprivate1.3151
ANOMALY_TOLERANCE_PCTprivate5.0163
ANOMALY_TOLERANCE_ABSprivate0.01175
ANOMALY_PERSISTENCE_WINDOWSprivate3187
ANOMALY_P75_MIN_SAMPLESprivate10196
ANOMALY_GATE_MAXprivate30209
ANOMALY_PERSISTENCE_MAXprivate29223
ANOMALY_BAND_WINDOWprivate10235
ANOMALY_RECOVERY_DAYSprivate3246
BREACH_STATE_OPTIONprivate\'wppo_ai_anomaly_breach_state\'258
DEPLOY_NOTES_OPTIONprivate\'wppo_ai_deploy_notes\'270
DEPLOY_NOTES_LIMITprivate20280
BREACH_STATE_LIMITprivate20291
DEPLOY_CORRELATION_DAYSprivate7302
CSS_REFRESH_COOLDOWN_PREFIXprivate\'wppo_ai_css_refresh_\'1954
CSS_REFRESH_SNAPSHOT_OPTIONprivate\'wppo_ai_css_refresh_snapshots\'1965
CSS_REFRESH_COOLDOWN_DAYSprivate71973

Properties

PropertyVisibilityTypeDefaultLine
$rum_digest_memoprivate static?arraynull48
$rum_digest_memo_computedprivate staticboolfalse59
$rum_digest_memo_blogprivate staticint067

publicstatic reset_rum_anomaly_digest_memo()

public static function reset_rum_anomaly_digest_memo(): void

Reset the per-request RUM anomaly digest memo (for testing).

Return: void.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 75

privatestatic rum_digest_memo_blog_id()

private static function rum_digest_memo_blog_id(): int

Resolve the blog ID for digest memo scoping (multisite safety).

Return: int — Current blog ID or 0 when unavailable.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 89

privatestatic anomaly_min_samples()

private static function anomaly_min_samples(): int

Resolve the anomaly minimum-sample threshold.

Return: int — Minimum samples (>=1).

Tags: @since 2.0.0

Source: includes/Insight/class-ai-anomaly.php, line 314

privatestatic anomaly_cooldown_days()

private static function anomaly_cooldown_days(): int

Resolve the anomaly cooldown window in days.

Return: int — Cooldown days (>=0; 0 disables the cooldown gate).

Tags: @since 2.0.0

Source: includes/Insight/class-ai-anomaly.php, line 349

privatestatic anomaly_tolerance_pct()

private static function anomaly_tolerance_pct(): float

Resolve the relative tolerance band in percent.

Return: float — Tolerance percent (>=0).

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 385

privatestatic anomaly_persistence_windows()

private static function anomaly_persistence_windows(): int

Resolve the anomaly persistence-window count.

Return: int — Trailing windows that must each breach (>=1, <=29).

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 424

privatestatic anomaly_tolerance_abs()

private static function anomaly_tolerance_abs(): float

Resolve the absolute tolerance band for the CLS arm.

Return: float — Absolute tolerance (>=0).

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 463

privatestatic anomaly_p75_min_samples()

private static function anomaly_p75_min_samples(): int

Resolve the RUM corroboration sample floor.

Return: int — Minimum RUM samples (>=1, <=30).

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 500

privatestatic anomaly_band_window()

private static function anomaly_band_window(): int

Resolve the moving-average band window size (issue #1313).

Return: int — Trailing samples forming the band baseline (>=1, <=29).

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 540

privatestatic anomaly_recovery_days()

private static function anomaly_recovery_days(): int

Resolve the recovery hysteresis window in days (issue #1313).

Return: int — In-band stabilization days (>=0; 0 recovers immediately).

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 578

privatestatic moving_band()

private static function moving_band(array $prior_window, string $metric): array

Compute the moving-average band for a trailing baseline window (issue #1313).

ParameterTypeDefaultDescription
$prior_windowarray—Trailing numeric samples (oldest first).
$metricstring—Metric name (\’lcp\’|\’cls\’; others use the LCP shape).

Return: array{mean:float,std:float,upper:float,lower:float} — Band edges.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 622

publicstatic get_deploy_notes()

public static function get_deploy_notes(): array

Read the stored deploy notes (issue #1313).

Return: array[] — List of {ts:int, note:string} rows (newest last, max 20).

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 681

publicstatic add_deploy_note()

public static function add_deploy_note(string $note, ?int=null $ts): bool

Record a manual deploy note (issue #1313).

ParameterTypeDefaultDescription
$notestring—Note text (sanitized, max 200 chars).
$ts?int=null—Optional timestamp (defaults to now).

Return: bool — True when stored.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 763

publicstatic find_deploy_note_near()

public static function find_deploy_note_near(int $breach_ts): string

Find the deploy note nearest before a breach timestamp (issue #1313).

ParameterTypeDefaultDescription
$breach_tsint—Breach timestamp.

Return: string — Matching note text, or \’\’ when none nearby.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 807

publicstatic get_breach_state()

public static function get_breach_state(): array

Read the active breach state (issue #1313).

Return: array<string, — array{metric:string,baseline:float,current:float,breached_at:int}> Breach rows keyed by trend key.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 849

publicstatic set_breach_state()

public static function set_breach_state(array $state): void

Persist the active breach state (issue #1313).

ParameterTypeDefaultDescription
$statearray—Breach rows keyed by trend key.

Return: void.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 880

privatestatic record_breach_state()

private static function record_breach_state(string $trend_key, string $metric, float $baseline, float $current, int $breached_at): void

Record a breach for recovery hysteresis tracking (issue #1313).

ParameterTypeDefaultDescription
$trend_keystring—Trend key.
$metricstring—Metric name.
$baselinefloat—Band baseline.
$currentfloat—Breach sample.
$breached_atint—Breach timestamp.

Return: void.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 907

privatestatic clear_breach_state()

private static function clear_breach_state(string $trend_key): void

Clear a tracked breach after recovery (issue #1313).

ParameterTypeDefaultDescription
$trend_keystring—Trend key.

Return: void.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 934

publicstatic detect_recoveries()

public static function detect_recoveries(?array=null $trends, ?int=null $now): array

Detect recoveries closing the loop on tracked breaches (issue #1313).

ParameterTypeDefaultDescription
$trends?array=null—Optional trends map (null = live Pagespeed::get_trends()).
$now?int=null—Optional current timestamp (tests).

Return: array[] — At most one recovery: array(array(\’key\’=>string,\’route\’=>string,\’metric\’=>string,\’baseline\’=>float,\’current\’=>float,\’samples\’=>int,\’recovered\’=>true,\’breached_at\’=>int)).

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 973

privatestatic is_rum_collection_enabled()

private static function is_rum_collection_enabled(): bool

Whether RUM collection is enabled (read-only probe).

Return: bool — True when RUM collection is enabled.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 1097

privatestatic anomaly_p75()

private static function anomaly_p75(array $samples): float

Compute the p75 of a numeric sample list (nearest-rank).

ParameterTypeDefaultDescription
$samplesarray—Numeric samples.

Return: float — p75 value or 0.0 when empty.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 1121

privatestatic read_rum_aggregate_for_anomaly()

private static function read_rum_aggregate_for_anomaly(): array

Read the RUM aggregate for anomaly corroboration (read-only).

Return: array — Aggregate data (empty array when missing/invalid).

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 1153

publicstatic get_last_anomaly_alarm()

public static function get_last_anomaly_alarm(): int

Get the last anomaly alarm timestamp.

Return: int — Unix timestamp (0 when never alarmed).

Tags: @since 2.0.0

Source: includes/Insight/class-ai-anomaly.php, line 1192

publicstatic set_last_anomaly_alarm()

public static function set_last_anomaly_alarm(int $ts): void

Persist the last anomaly alarm timestamp.

ParameterTypeDefaultDescription
$tsint—Unix timestamp.

Return: void.

Tags: @since 2.0.0

Source: includes/Insight/class-ai-anomaly.php, line 1214

privatestatic anomaly_now()

private static function anomaly_now(?int=null $now): int

Resolve the current timestamp deterministically.

ParameterTypeDefaultDescription
$now?int=null—Optional injected timestamp (tests).

Return: int.

Tags: @since 2.0.0

Source: includes/Insight/class-ai-anomaly.php, line 1232

publicstatic is_anomaly_cooled_down()

public static function is_anomaly_cooled_down(?int=null $now): bool

Whether the anomaly cooldown has elapsed.

ParameterTypeDefaultDescription
$now?int=null—Optional injected timestamp (tests).

Return: bool — True when a new banner may fire.

Tags: @since 2.0.0

Source: includes/Insight/class-ai-anomaly.php, line 1246

privatestatic is_rum_corroborated()

private static function is_rum_corroborated(string $metric, ?array $rum, float $baseline, ?int=null $p75_min_samples): bool

Whether RUM field data corroborates a trend anomaly.

ParameterTypeDefaultDescription
$metricstring—Metric name (\’lcp\’|\’inp\’|\’cls\’).
$rum?array—Optional RUM aggregate (null = live read-only read).
$baselinefloat—Trend baseline for the firing arm.
$p75_min_samples?int=null—Optional pre-resolved sample floor (null = resolve once via anomaly_p75_min_samples()).

Return: bool — True when real-user data agrees with the trend arm.

Tags: @since 2.0.0 · @since 2.3.0 Supports the \’inp\’ metric (behaves like \’lcp\’). · @since 2.3.0 RUM-disabled short-circuit; read-only aggregate path; p75 sample floor.

Source: includes/Insight/class-ai-anomaly.php, line 1301

privatestatic collect_trend_samples()

private static function collect_trend_samples(array $snapshots, string $metric, bool $require_positive): array

Collect numeric samples for a metric from trend snapshots.

ParameterTypeDefaultDescription
$snapshotsarray—Trend snapshots for one URL+strategy key.
$metricstring—Metric key (\’lcp\’|\’cls\’).
$require_positivebool—Whether to drop non-positive values (LCP only).

Return: float[].

Tags: @since 2.0.0

Source: includes/Insight/class-ai-anomaly.php, line 1374

privatestatic rum_median()

private static function rum_median(array $samples): float

Compute the median of a numeric sample list.

ParameterTypeDefaultDescription
$samplesarray—Numeric samples.

Return: float — Median value or 0.0 when empty.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 1409

publicstatic get_rum_anomaly_digest()

public static function get_rum_anomaly_digest(?array=null $rum, ?int=null $now): array

Local RUM anomaly digest for LCP/INP/CLS regressions (read-only).

ParameterTypeDefaultDescription
$rum?array=null—Optional RUM aggregate for testability. When null, reads RUM::get_aggregate_readonly().
$now?int=null—Optional current timestamp for testability. When null, uses time().

Return: array[] — At most one digest anomaly: array(array(\’key\’=>string,\’metric\’=>string,\’path\’=>string,\’baseline\’=>float,\’current\’=>float,\’recent\’=>float,\’window\’=>string,\’samples\’=>int,\’source\’=>string,\’change_pct\’=>float|\’change_abs\’=>float)).

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 1475

privatestatic compute_rum_anomaly_digest()

private static function compute_rum_anomaly_digest(?array=null $rum, ?int=null $now): array

Compute the RUM anomaly digest (unmemoized worker).

ParameterTypeDefaultDescription
$rum?array=null—Optional RUM aggregate for testability.
$now?int=null—Optional current timestamp for testability.

Return: array[] — At most one digest anomaly.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 1518

publicstatic detect_anomalies()

public static function detect_anomalies(?array=null $trends, ?array=null $rum, ?int=null $now): array

Detect LCP/CLS regressions from stored Web Vitals trend history.

ParameterTypeDefaultDescription
$trends?array=null—Optional trends map for testability. When null, reads Pagespeed::get_trends().
$rum?array=null—Optional RUM aggregate for testability. When null, reads the read-only RUM aggregate (zero notices when RUM disabled).
$now?int=null—Optional current timestamp for testability. When null, uses time().

Return: array[] — At most one anomaly: array(array(\’key\’=>string,\’route\’=>string,\’metric\’=>string,\’baseline\’=>float,\’current\’=>float,\’p75\’=>float,\’delta\’=>float,\’samples\’=>int,\’change_pct\’=>float|\’change_abs\’=>float)).

Tags: @since 2.0.0 · @since 2.3.0 Three-window ratio persistence; RUM-disabled short-circuit; enriched route/p75/baseline/delta/samples payload. · @since 2.3.0 v2 moving-average bands, deploy-note annotation, breach-state tracking for recovery hysteresis.

Source: includes/Insight/class-ai-anomaly.php, line 1766

publicstatic is_css_refresh_enabled()

public static function is_css_refresh_enabled(): bool

Whether RUM-triggered CSS refresh on LCP regression is enabled.

Return: bool — True when a regression may queue a CSS regen job.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 1987

publicstatic css_refresh_cooldown_days()

public static function css_refresh_cooldown_days(): int

Resolve the per-URL CSS-refresh cooldown window in days.

Return: int — Cooldown days (>=1).

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 2025

publicstatic resolve_anomaly_url()

public static function resolve_anomaly_url(string $trend_key): string

Resolve a trend anomaly key back to its scanned URL.

ParameterTypeDefaultDescription
$trend_keystring—Trend key (`md5(url)_strategy`).

Return: string — Resolved absolute URL, or \’\’ when unresolvable.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 2078

publicstatic is_homepage_url()

public static function is_homepage_url(string $url): bool

Whether a URL is the site homepage.

ParameterTypeDefaultDescription
$urlstring—Absolute URL.

Return: bool — True when the URL is the homepage.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 2141

publicstatic resolve_front_page_post_id()

public static function resolve_front_page_post_id(): int

Resolve the static front-page post ID, if one is configured.

Return: int — Front-page post ID (>0), or 0 when none configured.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 2175

publicstatic resolve_anomaly_post_id()

public static function resolve_anomaly_post_id(string $url): int

Resolve a URL to its post ID for single-post used-CSS queueing.

ParameterTypeDefaultDescription
$urlstring—Absolute URL.

Return: int — Post ID (>0), or 0 when unresolvable.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 2208

privatestatic record_css_refresh_snapshot()

private static function record_css_refresh_snapshot(string $snapshot_key, array $entry): void

Record the before/after LCP snapshot for a refresh decision.

ParameterTypeDefaultDescription
$snapshot_keystring—Snapshot key (md5 of URL or trend key).
$entryarray—Snapshot entry (url, before_lcp, current_lcp, queued, …).

Return: void.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 2243

publicstatic get_css_refresh_snapshot()

public static function get_css_refresh_snapshot(string $url): array

Get the stored before/after LCP snapshot for a URL.

ParameterTypeDefaultDescription
$urlstring—Absolute URL.

Return: array — Snapshot entry, or empty array when none stored.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 2272

publicstatic maybe_queue_css_refresh()

public static function maybe_queue_css_refresh(array $anomaly, ?int=null $now): array

Bridge an LCP anomaly to a guarded single used-CSS regen job.

ParameterTypeDefaultDescription
$anomalyarray—Anomaly array from detect_anomalies().
$now?int=null—Optional current timestamp (tests).

Return: array{queued:bool,reason:string,url:string,post_id:int,before_lcp:float,current_lcp:float} — Refresh decision.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 2318

privatestatic set_css_refresh_cooldown()

private static function set_css_refresh_cooldown(string $cooldown_key, int $now): void

Arm the per-URL CSS-refresh cooldown transient.

ParameterTypeDefaultDescription
$cooldown_keystring—Blog-aware transient key.
$nowint—Current timestamp.

Return: void.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 2483

publicstatic get_anomaly_provisional_state()

public static function get_anomaly_provisional_state(?array=null $trends, ?array=null $rum, ?int=null $now): array

Describe the provisional (collecting-data) anomaly state.

ParameterTypeDefaultDescription
$trends?array=null—Optional trends map (null = live Pagespeed::get_trends()).
$rum?array=null—Optional RUM aggregate (null = live read-only aggregate).
$now?int=null—Optional current timestamp for testability.

Return: array{provisional:bool,reason:string,samples:int,min_samples:int,rum_samples:int,rum_min_samples:int} — Provisional state.

Tags: @since 2.3.0

Source: includes/Insight/class-ai-anomaly.php, line 2524

Hooks

Hooks referenced in includes/Insight/class-ai-anomaly.php:

HookTypeLineNotes
wppo_ai_anomaly_min_samplesfilter327—
wppo_ai_anomaly_cooldown_daysfilter362—
wppo_ai_anomaly_tolerance_pctfilter395—
wppo_ai_anomaly_persistence_windowsfilter437—
wppo_ai_anomaly_tolerance_absfilter473—
wppo_ai_anomaly_p75_min_samplesfilter513—
wppo_ai_anomaly_band_windowfilter553—
wppo_ai_anomaly_recovery_daysfilter591—
wppo_ai_anomaly_recoveredfilter1066—
wppo_ai_anomaly_detectedfilter1685@param ×1
wppo_ai_lcp_regressionfilter1697—
wppo_ai_anomaly_detectedfilter1913@param ×1
wppo_ai_lcp_regressionfilter1925—
wppo_ai_css_refresh_enabledfilter2001@param ×1
wppo_ai_css_refresh_cooldown_daysfilter2047@param ×1
wppo_ai_css_refresh_queuedaction2460@param ×3