// pagespeed-audit findings document v1 — generated from /docs/findings.schema.json. Do not edit by hand. // Deserialize with System.Text.Json: JsonSerializer.Deserialize(json). using System.Collections.Generic; using System.Text.Json; using System.Text.Json.Serialization; namespace PageSpeedAudit.Findings; public sealed record AdjudicationCounts { [JsonPropertyName("confirmed")] public required int Confirmed { get; init; } [JsonPropertyName("false_positive")] public required int FalsePositive { get; init; } [JsonPropertyName("deprioritized")] public required int Deprioritized { get; init; } [JsonPropertyName("platform_blocked")] public required int PlatformBlocked { get; init; } [JsonPropertyName("readout")] public required int Readout { get; init; } } public sealed record RunInfo { [JsonPropertyName("run_id")] public required string RunId { get; init; } [JsonPropertyName("generated_at")] public required string GeneratedAt { get; init; } [JsonPropertyName("skill_version")] public required string SkillVersion { get; init; } [JsonPropertyName("site")] public required string Site { get; init; } [JsonPropertyName("url")] public required string Url { get; init; } [JsonPropertyName("final_url")] public string? FinalUrl { get; init; } /// One of: "mobile", "desktop". [JsonPropertyName("strategy")] public required string Strategy { get; init; } [JsonPropertyName("date")] public required string Date { get; init; } [JsonPropertyName("lighthouse_version")] public string? LighthouseVersion { get; init; } [JsonPropertyName("data_sources")] public IReadOnlyList? DataSources { get; init; } [JsonPropertyName("field_available")] public required bool FieldAvailable { get; init; } [JsonPropertyName("binding_constraint")] public required string BindingConstraint { get; init; } [JsonPropertyName("missing_from_psi")] public string? MissingFromPsi { get; init; } [JsonPropertyName("adjudication_counts")] public required AdjudicationCounts AdjudicationCounts { get; init; } /// 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) [JsonPropertyName("framework")] public string? Framework { get; init; } /// Noise & Silence framework: (false_positive + deprioritized + platform_blocked) / adjudicated flags, readout excluded [JsonPropertyName("noise_rate")] public double? NoiseRate { get; init; } } public sealed record MetricRow { /// One of: "LCP", "INP", "CLS", "FCP", "TTFB". [JsonPropertyName("metric")] public required string Metric { get; init; } /// One of: "field_page", "field_origin", "lab". [JsonPropertyName("source")] public required string Source { get; init; } [JsonPropertyName("value")] public required double? Value { get; init; } /// One of: "ms", "". [JsonPropertyName("unit")] public string? Unit { get; init; } [JsonPropertyName("category")] public string? Category { get; init; } [JsonPropertyName("good")] public double? Good { get; init; } [JsonPropertyName("poor")] public double? Poor { get; init; } } public sealed record HistoryRow { [JsonPropertyName("date")] public required string Date { get; init; } /// One of: "LCP", "INP", "CLS", "FCP", "TTFB". [JsonPropertyName("metric")] public required string Metric { get; init; } [JsonPropertyName("p75")] public required double? P75 { get; init; } /// One of: "ms", "". [JsonPropertyName("unit")] public string? Unit { get; init; } } public sealed record QuickFix { [JsonPropertyName("lang")] public string? Lang { get; init; } [JsonPropertyName("code")] public string? Code { get; init; } [JsonPropertyName("note")] public string? Note { get; init; } } public sealed record Adjudication { [JsonPropertyName("audit_id")] public required string AuditId { get; init; } /// One of: "confirmed", "false_positive", "deprioritized", "platform_blocked", "readout". [JsonPropertyName("disposition")] public required string Disposition { get; init; } [JsonPropertyName("claim")] public required string Claim { get; init; } [JsonPropertyName("evidence")] public required string Evidence { get; init; } [JsonPropertyName("finding_ref")] public string? FindingRef { get; init; } [JsonPropertyName("metric_savings")] public JsonElement? MetricSavings { get; init; } [JsonPropertyName("has_quick_fix")] public bool? HasQuickFix { get; init; } [JsonPropertyName("fix")] public QuickFix? Fix { get; init; } } public sealed record Coupling { [JsonPropertyName("finding")] public required string Finding { get; init; } /// One of: "invalidates", "invalidated_by", "depends_on", "required_by", "co-requisite", "interacts". [JsonPropertyName("kind")] public required string Kind { get; init; } [JsonPropertyName("note")] public string? Note { get; init; } /// true = generated from the other finding's declaration, not authored here [JsonPropertyName("mirrored")] public bool? Mirrored { get; init; } } public sealed record FindingFix { [JsonPropertyName("lang")] public string? Lang { get; init; } [JsonPropertyName("before")] public string? Before { get; init; } [JsonPropertyName("after")] public string? After { get; init; } [JsonPropertyName("note")] public string? Note { get; init; } } public sealed record Impact { [JsonPropertyName("narrative")] public required string Narrative { get; init; } [JsonPropertyName("arithmetic")] public string? Arithmetic { get; init; } /// One of: "LCP", "INP", "CLS", "FCP", "TTFB". [JsonPropertyName("metric")] public string? Metric { get; init; } /// One of: "ms", "". [JsonPropertyName("unit")] public string? Unit { get; init; } [JsonPropertyName("min")] public double? Min { get; init; } [JsonPropertyName("max")] public double? Max { get; init; } /// One of: "decrease", "increase". [JsonPropertyName("direction")] public string? Direction { get; init; } /// One of: "HIGH", "MEDIUM-HIGH", "MEDIUM", "LOW". [JsonPropertyName("confidence")] public string? Confidence { get; init; } } public sealed record Finding { /// 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. [JsonPropertyName("id")] public required string Id { get; init; } /// 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. [JsonPropertyName("slug")] public required string Slug { get; init; } [JsonPropertyName("title")] public required string Title { get; init; } /// One of: "CRITICAL", "HIGH", "MEDIUM", "LOW". [JsonPropertyName("severity")] public required string Severity { get; init; } /// One of: "HIGH", "MEDIUM-HIGH", "MEDIUM", "LOW". [JsonPropertyName("confidence")] public required string Confidence { get; init; } [JsonPropertyName("effort")] public string? Effort { get; init; } [JsonPropertyName("tags")] public IReadOnlyList? Tags { get; init; } [JsonPropertyName("evidence")] public string? Evidence { get; init; } /// 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. [JsonPropertyName("scope")] public string? Scope { get; init; } /// 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. [JsonPropertyName("falsification")] public string? Falsification { get; init; } /// Claims the audit OBSERVED — header, byte count, snippet, response. Optional; added additively to @1. [JsonPropertyName("falsification_measured")] public IReadOnlyList? FalsificationMeasured { get; init; } /// 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. [JsonPropertyName("falsification_reasoned")] public IReadOnlyList? FalsificationReasoned { get; init; } /// The concrete test that settles a reasoned inference. REQUIRED (validate_findings) whenever falsification_reasoned is non-empty on a finding that ships a fix. [JsonPropertyName("verify_before_shipping")] public string? VerifyBeforeShipping { get; init; } /// 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. [JsonPropertyName("coupling")] public IReadOnlyList? Coupling { get; init; } [JsonPropertyName("chain")] public string? Chain { get; init; } [JsonPropertyName("fix")] public FindingFix? Fix { get; init; } [JsonPropertyName("impact")] public required Impact Impact { get; init; } [JsonPropertyName("sequencing")] public string? Sequencing { get; init; } /// 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. [JsonPropertyName("locked")] public bool? Locked { get; init; } /// For a locked finding: the controllable equivalent (merchant-side lever) or an explicit stop-trying note shown in place of a fix. [JsonPropertyName("redirect")] public string? Redirect { get; init; } } public sealed record Deliverable { [JsonPropertyName("title")] public required string Title { get; init; } [JsonPropertyName("lang")] public string? Lang { get; init; } [JsonPropertyName("code")] public required string Code { get; init; } } public sealed record DoneWellItem { [JsonPropertyName("item")] public required string Item { get; init; } [JsonPropertyName("evidence")] public string? Evidence { get; init; } } public sealed record PlanItem { /// One of: "today", "sprint", "architectural". [JsonPropertyName("bucket")] public required string Bucket { get; init; } [JsonPropertyName("position")] public required int Position { get; init; } [JsonPropertyName("item")] public required string Item { get; init; } } /// 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). public sealed record FindingsDocument { [JsonPropertyName("schema")] public required string Schema { get; init; } [JsonPropertyName("run")] public required RunInfo Run { get; init; } [JsonPropertyName("metrics")] public required IReadOnlyList Metrics { get; init; } [JsonPropertyName("history")] public IReadOnlyList? History { get; init; } [JsonPropertyName("adjudications")] public required IReadOnlyList Adjudications { get; init; } [JsonPropertyName("findings")] public required IReadOnlyList Findings { get; init; } [JsonPropertyName("deliverables")] public IReadOnlyList? Deliverables { get; init; } [JsonPropertyName("done_well")] public IReadOnlyList? DoneWell { get; init; } [JsonPropertyName("plan")] public IReadOnlyList? Plan { get; init; } }