includes/Insight/class-ai-anomaly.php
AI anomaly detection — thresholds, breach/alarm state, RUM digest, CSS-refresh reactions.
Class Ai_Anomaly
Detects performance regressions and reacts to them.
class Ai_AnomalyConstants
| Constant | Visibility | Value | Line |
|---|---|---|---|
ANOMALY_COOLDOWN_KEY | private | \'wppo_ai_anomaly_last_alarm\' | 107 |
ANOMALY_COOLDOWN_DAYS | private | 7 | 115 |
ANOMALY_MIN_SAMPLES | private | 10 | 123 |
CLS_ABSOLUTE_DELTA | private | 0.05 | 131 |
LCP_RELATIVE_MULTIPLIER | private | 1.3 | 139 |
INP_RELATIVE_MULTIPLIER | private | 1.3 | 151 |
ANOMALY_TOLERANCE_PCT | private | 5.0 | 163 |
ANOMALY_TOLERANCE_ABS | private | 0.01 | 175 |
ANOMALY_PERSISTENCE_WINDOWS | private | 3 | 187 |
ANOMALY_P75_MIN_SAMPLES | private | 10 | 196 |
ANOMALY_GATE_MAX | private | 30 | 209 |
ANOMALY_PERSISTENCE_MAX | private | 29 | 223 |
ANOMALY_BAND_WINDOW | private | 10 | 235 |
ANOMALY_RECOVERY_DAYS | private | 3 | 246 |
BREACH_STATE_OPTION | private | \'wppo_ai_anomaly_breach_state\' | 258 |
DEPLOY_NOTES_OPTION | private | \'wppo_ai_deploy_notes\' | 270 |
DEPLOY_NOTES_LIMIT | private | 20 | 280 |
BREACH_STATE_LIMIT | private | 20 | 291 |
DEPLOY_CORRELATION_DAYS | private | 7 | 302 |
CSS_REFRESH_COOLDOWN_PREFIX | private | \'wppo_ai_css_refresh_\' | 1954 |
CSS_REFRESH_SNAPSHOT_OPTION | private | \'wppo_ai_css_refresh_snapshots\' | 1965 |
CSS_REFRESH_COOLDOWN_DAYS | private | 7 | 1973 |
Properties
| Property | Visibility | Type | Default | Line |
|---|---|---|---|---|
$rum_digest_memo | private static | ?array | null | 48 |
$rum_digest_memo_computed | private static | bool | false | 59 |
$rum_digest_memo_blog | private static | int | 0 | 67 |
publicstatic reset_rum_anomaly_digest_memo()
public static function reset_rum_anomaly_digest_memo(): voidReset the per-request RUM anomaly digest memo (for testing).
Return: void.
privatestatic rum_digest_memo_blog_id()
private static function rum_digest_memo_blog_id(): intResolve the blog ID for digest memo scoping (multisite safety).
Return: int — Current blog ID or 0 when unavailable.
privatestatic anomaly_min_samples()
private static function anomaly_min_samples(): intResolve the anomaly minimum-sample threshold.
Return: int — Minimum samples (>=1).
privatestatic anomaly_cooldown_days()
private static function anomaly_cooldown_days(): intResolve the anomaly cooldown window in days.
Return: int — Cooldown days (>=0; 0 disables the cooldown gate).
privatestatic anomaly_tolerance_pct()
private static function anomaly_tolerance_pct(): floatResolve the relative tolerance band in percent.
Return: float — Tolerance percent (>=0).
privatestatic anomaly_persistence_windows()
private static function anomaly_persistence_windows(): intResolve the anomaly persistence-window count.
Return: int — Trailing windows that must each breach (>=1, <=29).
privatestatic anomaly_tolerance_abs()
private static function anomaly_tolerance_abs(): floatResolve the absolute tolerance band for the CLS arm.
Return: float — Absolute tolerance (>=0).
privatestatic anomaly_p75_min_samples()
private static function anomaly_p75_min_samples(): intResolve the RUM corroboration sample floor.
Return: int — Minimum RUM samples (>=1, <=30).
privatestatic anomaly_band_window()
private static function anomaly_band_window(): intResolve the moving-average band window size (issue #1313).
Return: int — Trailing samples forming the band baseline (>=1, <=29).
privatestatic anomaly_recovery_days()
private static function anomaly_recovery_days(): intResolve the recovery hysteresis window in days (issue #1313).
Return: int — In-band stabilization days (>=0; 0 recovers immediately).
privatestatic moving_band()
private static function moving_band(array $prior_window, string $metric): arrayCompute the moving-average band for a trailing baseline window (issue #1313). Parameter Type Default Description $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.
publicstatic get_deploy_notes()
public static function get_deploy_notes(): arrayRead the stored deploy notes (issue #1313).
Return: array[] — List of {ts:int, note:string} rows (newest last, max 20).
publicstatic add_deploy_note()
public static function add_deploy_note(string $note, ?int=null $ts): boolRecord a manual deploy note (issue #1313). Parameter Type Default Description $notestring— Note text (sanitized, max 200 chars). $ts?int=null— Optional timestamp (defaults to now).
Return: bool — True when stored.
publicstatic find_deploy_note_near()
public static function find_deploy_note_near(int $breach_ts): stringFind the deploy note nearest before a breach timestamp (issue #1313). Parameter Type Default Description $breach_tsint— Breach timestamp.
Return: string — Matching note text, or \’\’ when none nearby.
publicstatic get_breach_state()
public static function get_breach_state(): arrayRead 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.
publicstatic set_breach_state()
public static function set_breach_state(array $state): voidPersist the active breach state (issue #1313). Parameter Type Default Description $statearray— Breach rows keyed by trend key.
Return: void.
privatestatic record_breach_state()
private static function record_breach_state(string $trend_key, string $metric, float $baseline, float $current, int $breached_at): voidRecord a breach for recovery hysteresis tracking (issue #1313). Parameter Type Default Description $trend_keystring— Trend key. $metricstring— Metric name. $baselinefloat— Band baseline. $currentfloat— Breach sample. $breached_atint— Breach timestamp.
Return: void.
privatestatic clear_breach_state()
private static function clear_breach_state(string $trend_key): voidClear a tracked breach after recovery (issue #1313). Parameter Type Default Description $trend_keystring— Trend key.
Return: void.
publicstatic detect_recoveries()
public static function detect_recoveries(?array=null $trends, ?int=null $now): arrayDetect recoveries closing the loop on tracked breaches (issue #1313). Parameter Type Default Description $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)).
privatestatic is_rum_collection_enabled()
private static function is_rum_collection_enabled(): boolWhether RUM collection is enabled (read-only probe).
Return: bool — True when RUM collection is enabled.
privatestatic anomaly_p75()
private static function anomaly_p75(array $samples): floatCompute the p75 of a numeric sample list (nearest-rank). Parameter Type Default Description $samplesarray— Numeric samples.
Return: float — p75 value or 0.0 when empty.
privatestatic read_rum_aggregate_for_anomaly()
private static function read_rum_aggregate_for_anomaly(): arrayRead the RUM aggregate for anomaly corroboration (read-only).
Return: array — Aggregate data (empty array when missing/invalid).
publicstatic get_last_anomaly_alarm()
public static function get_last_anomaly_alarm(): intGet the last anomaly alarm timestamp.
Return: int — Unix timestamp (0 when never alarmed).
publicstatic set_last_anomaly_alarm()
public static function set_last_anomaly_alarm(int $ts): voidPersist the last anomaly alarm timestamp. Parameter Type Default Description $tsint— Unix timestamp.
Return: void.
privatestatic anomaly_now()
private static function anomaly_now(?int=null $now): intResolve the current timestamp deterministically. Parameter Type Default Description $now?int=null— Optional injected timestamp (tests).
Return: int.
publicstatic is_anomaly_cooled_down()
public static function is_anomaly_cooled_down(?int=null $now): boolWhether the anomaly cooldown has elapsed. Parameter Type Default Description $now?int=null— Optional injected timestamp (tests).
Return: bool — True when a new banner may fire.
privatestatic is_rum_corroborated()
private static function is_rum_corroborated(string $metric, ?array $rum, float $baseline, ?int=null $p75_min_samples): boolWhether RUM field data corroborates a trend anomaly. Parameter Type Default Description $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.
privatestatic collect_trend_samples()
private static function collect_trend_samples(array $snapshots, string $metric, bool $require_positive): arrayCollect numeric samples for a metric from trend snapshots. Parameter Type Default Description $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[].
privatestatic rum_median()
private static function rum_median(array $samples): floatCompute the median of a numeric sample list. Parameter Type Default Description $samplesarray— Numeric samples.
Return: float — Median value or 0.0 when empty.
publicstatic get_rum_anomaly_digest()
public static function get_rum_anomaly_digest(?array=null $rum, ?int=null $now): arrayLocal RUM anomaly digest for LCP/INP/CLS regressions (read-only). Parameter Type Default Description $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)).
privatestatic compute_rum_anomaly_digest()
private static function compute_rum_anomaly_digest(?array=null $rum, ?int=null $now): arrayCompute the RUM anomaly digest (unmemoized worker). Parameter Type Default Description $rum?array=null— Optional RUM aggregate for testability. $now?int=null— Optional current timestamp for testability.
Return: array[] — At most one digest anomaly.
publicstatic detect_anomalies()
public static function detect_anomalies(?array=null $trends, ?array=null $rum, ?int=null $now): arrayDetect LCP/CLS regressions from stored Web Vitals trend history. Parameter Type Default Description $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)).
publicstatic is_css_refresh_enabled()
public static function is_css_refresh_enabled(): boolWhether RUM-triggered CSS refresh on LCP regression is enabled.
Return: bool — True when a regression may queue a CSS regen job.
publicstatic css_refresh_cooldown_days()
public static function css_refresh_cooldown_days(): intResolve the per-URL CSS-refresh cooldown window in days.
Return: int — Cooldown days (>=1).
publicstatic resolve_anomaly_url()
public static function resolve_anomaly_url(string $trend_key): stringResolve a trend anomaly key back to its scanned URL. Parameter Type Default Description $trend_keystring— Trend key (`md5(url)_strategy`).
Return: string — Resolved absolute URL, or \’\’ when unresolvable.
publicstatic is_homepage_url()
public static function is_homepage_url(string $url): boolWhether a URL is the site homepage. Parameter Type Default Description $urlstring— Absolute URL.
Return: bool — True when the URL is the homepage.
publicstatic resolve_front_page_post_id()
public static function resolve_front_page_post_id(): intResolve the static front-page post ID, if one is configured.
Return: int — Front-page post ID (>0), or 0 when none configured.
publicstatic resolve_anomaly_post_id()
public static function resolve_anomaly_post_id(string $url): intResolve a URL to its post ID for single-post used-CSS queueing. Parameter Type Default Description $urlstring— Absolute URL.
Return: int — Post ID (>0), or 0 when unresolvable.
privatestatic record_css_refresh_snapshot()
private static function record_css_refresh_snapshot(string $snapshot_key, array $entry): voidRecord the before/after LCP snapshot for a refresh decision. Parameter Type Default Description $snapshot_keystring— Snapshot key (md5 of URL or trend key). $entryarray— Snapshot entry (url, before_lcp, current_lcp, queued, …).
Return: void.
publicstatic get_css_refresh_snapshot()
public static function get_css_refresh_snapshot(string $url): arrayGet the stored before/after LCP snapshot for a URL. Parameter Type Default Description $urlstring— Absolute URL.
Return: array — Snapshot entry, or empty array when none stored.
publicstatic maybe_queue_css_refresh()
public static function maybe_queue_css_refresh(array $anomaly, ?int=null $now): arrayBridge an LCP anomaly to a guarded single used-CSS regen job. Parameter Type Default Description $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.
privatestatic set_css_refresh_cooldown()
private static function set_css_refresh_cooldown(string $cooldown_key, int $now): voidArm the per-URL CSS-refresh cooldown transient. Parameter Type Default Description $cooldown_keystring— Blog-aware transient key. $nowint— Current timestamp.
Return: void.
publicstatic get_anomaly_provisional_state()
public static function get_anomaly_provisional_state(?array=null $trends, ?array=null $rum, ?int=null $now): arrayDescribe the provisional (collecting-data) anomaly state. Parameter Type Default Description $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.
Hooks
Hooks referenced in includes/Insight/class-ai-anomaly.php: Hook Type Line Notes wppo_ai_anomaly_min_samplesfilter 327 — wppo_ai_anomaly_cooldown_daysfilter 362 — wppo_ai_anomaly_tolerance_pctfilter 395 — wppo_ai_anomaly_persistence_windowsfilter 437 — wppo_ai_anomaly_tolerance_absfilter 473 — wppo_ai_anomaly_p75_min_samplesfilter 513 — wppo_ai_anomaly_band_windowfilter 553 — wppo_ai_anomaly_recovery_daysfilter 591 — wppo_ai_anomaly_recoveredfilter 1066 — wppo_ai_anomaly_detectedfilter 1685 @param ×1 wppo_ai_lcp_regressionfilter 1697 — wppo_ai_anomaly_detectedfilter 1913 @param ×1 wppo_ai_lcp_regressionfilter 1925 — wppo_ai_css_refresh_enabledfilter 2001 @param ×1 wppo_ai_css_refresh_cooldown_daysfilter 2047 @param ×1 wppo_ai_css_refresh_queuedaction 2460 @param ×3