# pagespeed-audit findings document v1 — generated from /docs/findings.schema.json. Do not edit by hand. # Load with json.load(); the TypedDicts describe the shape for type checkers. # Python 3.11+ (NotRequired); on older versions import NotRequired and TypedDict from typing_extensions. from __future__ import annotations from typing import Any, Literal, NotRequired, TypedDict class AdjudicationCounts(TypedDict): confirmed: int false_positive: int deprioritized: int platform_blocked: int readout: int class RunInfo(TypedDict): run_id: str generated_at: str skill_version: str site: str url: str final_url: NotRequired[str | None] strategy: Literal["mobile", "desktop"] date: str lighthouse_version: NotRequired[str | None] data_sources: NotRequired[list[str]] field_available: bool binding_constraint: str missing_from_psi: NotRequired[str | None] adjudication_counts: AdjudicationCounts # Public naming layer applied to this run's report, e.g. 'noise-silence/four-verdicts@1' (Real=confirmed, Wrong=false_positive, Trivial=deprioritized, Locked=platform_blocked) framework: NotRequired[str] # Noise & Silence framework: (false_positive + deprioritized + platform_blocked) / adjudicated flags, readout excluded noise_rate: NotRequired[float | None] class MetricRow(TypedDict): metric: Literal["LCP", "INP", "CLS", "FCP", "TTFB"] source: Literal["field_page", "field_origin", "lab"] value: float | None unit: NotRequired[Literal["ms", ""]] category: NotRequired[str | None] good: NotRequired[float | None] poor: NotRequired[float | None] class HistoryRow(TypedDict): date: str metric: Literal["LCP", "INP", "CLS", "FCP", "TTFB"] p75: float | None unit: NotRequired[Literal["ms", ""]] class QuickFix(TypedDict): lang: NotRequired[str] code: NotRequired[str] note: NotRequired[str] class Adjudication(TypedDict): audit_id: str disposition: Literal["confirmed", "false_positive", "deprioritized", "platform_blocked", "readout"] claim: str evidence: str finding_ref: NotRequired[str | None] metric_savings: NotRequired[dict[str, Any] | None] has_quick_fix: NotRequired[bool] fix: NotRequired[QuickFix | None] class Coupling(TypedDict): finding: str kind: Literal["invalidates", "invalidated_by", "depends_on", "required_by", "co-requisite", "interacts"] note: NotRequired[str | None] # true = generated from the other finding's declaration, not authored here mirrored: NotRequired[bool] class FindingFix(TypedDict): lang: NotRequired[str] before: NotRequired[str | None] after: NotRequired[str] note: NotRequired[str | None] class Impact(TypedDict): narrative: str arithmetic: NotRequired[str | None] metric: NotRequired[Literal["LCP", "INP", "CLS", "FCP", "TTFB"] | None] unit: NotRequired[Literal["ms", ""] | None] min: NotRequired[float | None] max: NotRequired[float | None] direction: NotRequired[Literal["decrease", "increase"] | None] confidence: NotRequired[Literal["HIGH", "MEDIUM-HIGH", "MEDIUM", "LOW"] | None] class Finding(TypedDict): # Positional in-report anchor, re-assigned every run (F1, F2, …) and the target of adjudications[].finding_ref. NOT stable across runs — never join on it. Use slug for that. id: str # Stable cross-run identifier for the PROBLEM CLASS, not the wording: the same underlying problem on the same site produces the same slug in every future audit, even when title, prose, severity or measured numbers change. Never encodes run-specific data (no values, dates, run numbers, ordering, Lighthouse version). Unique within a run; a class that legitimately recurs is disambiguated with a stable discriminator derived from the resource itself, e.g. 'render-blocking-css:fonts.googleapis.com' — never a positional counter. Join findings across runs on run.url (or run.final_url) + findings[].slug. Authoring vocabulary and the fallback rule live in SKILL.md. slug: str title: str severity: Literal["CRITICAL", "HIGH", "MEDIUM", "LOW"] confidence: Literal["HIGH", "MEDIUM-HIGH", "MEDIUM", "LOW"] effort: NotRequired[str | None] tags: NotRequired[list[str]] evidence: NotRequired[str] # What the defect was VERIFIED to affect, in the words of what was tested: 'origin-wide (verified on / /pricing /blog)' or 'this route only (tested / only)'. Never an inference from a single route. scope: NotRequired[str | None] # Human-readable falsification note. When the graded arrays below are present this is their flattened rendering ('Measured: … | Reasoned (inference): … | Verify before shipping: …'), so a v1 consumer that only reads this field keeps working. falsification: NotRequired[str | None] # Claims the audit OBSERVED — header, byte count, snippet, response. Optional; added additively to @1. falsification_measured: NotRequired[list[str] | None] # Claims the audit INFERRED from those observations. Presenting an inference as a confirmed check overstates the evidence grade, so the two are carried apart. Optional; added additively to @1. falsification_reasoned: NotRequired[list[str] | None] # The concrete test that settles a reasoned inference. REQUIRED (validate_findings) whenever falsification_reasoned is non-empty on a finding that ships a fix. verify_before_shipping: NotRequired[str | None] # Interactions with other findings in this run: one finding's fix changing a precondition another rests on, a prerequisite, or a co-requisite. Mirrored onto the other finding by the renderer and re-checked for symmetry by validate_findings — an interaction stated on only one side is an error. Optional; added additively to @1. coupling: NotRequired[list[Coupling] | None] chain: NotRequired[str | None] fix: NotRequired[FindingFix | None] impact: Impact sequencing: NotRequired[str | None] # Noise & Silence: true = platform-owned / outside operator control. A locked finding carries NO fix; the renderer shows the redirect instead, and validate_findings rejects a locked finding that has a fix. locked: NotRequired[bool | None] # For a locked finding: the controllable equivalent (merchant-side lever) or an explicit stop-trying note shown in place of a fix. redirect: NotRequired[str | None] class Deliverable(TypedDict): title: str lang: NotRequired[str] code: str class DoneWellItem(TypedDict): item: str evidence: NotRequired[str | None] class PlanItem(TypedDict): bucket: Literal["today", "sprint", "architectural"] position: int item: str class FindingsDocument(TypedDict): """Machine-readable twin of the audit report. Long-form arrays (metrics, history, adjudications, findings, plan) map 1:1 onto SQL tables; join runs on run.url + run.date + run.strategy, join flags across runs on adjudications[].audit_id (stable Lighthouse IDs), and join findings across runs on findings[].slug (stable problem-class ID; findings[].id is positional and re-assigned every run).""" schema: Literal["pagespeed-audit/findings@1"] run: RunInfo metrics: list[MetricRow] history: NotRequired[list[HistoryRow]] adjudications: list[Adjudication] findings: list[Finding] deliverables: NotRequired[list[Deliverable]] done_well: NotRequired[list[DoneWellItem]] plan: NotRequired[list[PlanItem]]