// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
priv struct CompactIsolatedSubject {
painted_boundary_displacement : PaintedBoundaryDisplacementMagnitude?
painted_coverage_difference : PaintedCoverageDifferenceMagnitude?
}
///|
fn[T : ToJson] put_optional(
fields : Map[String, Json],
key : String,
value : T?,
) -> Unit {
match value {
Some(value) => fields[key] = value.to_json()
None => ()
}
}
///|
fn compact_rendered_magnitude(value : RenderedMagnitude) -> Json {
{
"changed_pixels": value.changed_pixels,
"changed_fraction": value.changed_pixel_fraction,
"linear_rgba_rmse": value.linear_premultiplied_rgba_rmse,
}
}
///|
fn compact_canvas_outcome(value : CanvasOutcome) -> Json {
let fields : Map[String, Json] = Map([])
fields["status"] = value.status.to_json()
match value.magnitude {
Some(magnitude) => {
fields["changed_pixels"] = magnitude.changed_pixels.to_json()
fields["changed_fraction"] = magnitude.changed_pixel_fraction.to_json()
fields["linear_rgba_rmse"] = magnitude.linear_premultiplied_rgba_rmse.to_json()
}
None => put_optional(fields, "reason", value.reason_code)
}
if value.perceptual_flip.status == "computed" {
put_optional(
fields,
"perceptual_difference",
value.perceptual_flip.canvas_mean,
)
}
Json::object(fields)
}
///|
fn compact_comparison(profile : ComparisonProfile) -> Json {
let fields : Map[String, Json] = Map([])
fields["viewport"] = {
"width": profile.viewport_width,
"height": profile.viewport_height,
}
put_optional(fields, "perceptual_background", profile.perceptual_background)
match profile.flip_viewing_conditions {
Some(value) =>
fields["flip_pixels_per_degree"] = value.pixels_per_degree().to_json()
None => ()
}
match profile.flip_error_threshold {
Some(value) => fields["flip_error_threshold"] = value.value().to_json()
None => ()
}
Json::object(fields)
}
///|
fn compact_intrinsic_raster(value : IntrinsicRasterMagnitude) -> Json {
let fields : Map[String, Json] = Map([])
fields["before_size"] = [value.before_width, value.before_height].to_json()
fields["after_size"] = [value.after_width, value.after_height].to_json()
put_optional(fields, "compared_pixels", value.compared_pixels)
put_optional(fields, "changed_pixels", value.changed_pixels)
put_optional(fields, "changed_fraction", value.changed_pixel_fraction)
put_optional(fields, "linear_rgba_rmse", value.linear_premultiplied_rgba_rmse)
Json::object(fields)
}
///|
fn compact_boundary(value : PaintedBoundaryDisplacementMagnitude) -> Json {
{
"mean_css_px": value.mean_css_px,
"p95_css_px": value.p95_css_px,
"max_css_px": value.max_css_px,
}
}
///|
fn compact_coverage(value : PaintedCoverageDifferenceMagnitude) -> Json {
{
"before_css_px2": value.before_coverage_css_px2,
"after_css_px2": value.after_coverage_css_px2,
"absolute_difference_css_px2": value.absolute_difference_css_px2,
"union_css_px2": value.union_coverage_css_px2,
"fraction": value.fraction,
}
}
///|
fn compact_difference_magnitude(
report : StructuredReport,
difference : AtomicDifference,
) -> Json? {
let value = difference.magnitude
let isolated_subject = isolated_subject_for_difference(report, difference.id)
let shared_boundary = match isolated_subject {
Some(subject) => subject.painted_boundary_displacement
None => None
}
let shared_coverage = match isolated_subject {
Some(subject) => subject.painted_coverage_difference
None => None
}
let shared_path_displacement = difference.domain.has_prefix("geometry.path.") &&
(match (value.geometry_displacement_css_px, shared_boundary) {
(Some(displacement), Some(boundary)) =>
displacement == boundary.max_css_px
_ => false
})
let fields : Map[String, Json] = Map([])
put_optional(
fields,
"parameter_abs_user_units",
value.parameter_abs_user_units,
)
put_optional(
fields,
"parameter_signed_user_units",
value.parameter_signed_user_units,
)
put_optional(fields, "symmetric_relative", value.symmetric_relative)
match value.parameter_abs_css_px {
Some(number) => fields["parameter_abs_css_px"] = number.to_json()
None =>
put_optional(
fields,
"parameter_abs_css_px",
difference.parameter_delta_css_px,
)
}
put_optional(
fields,
"parameter_viewport_fraction",
value.parameter_viewport_fraction,
)
put_optional(
fields,
"parameter_entity_fraction",
value.parameter_entity_fraction,
)
if !shared_path_displacement {
put_optional(
fields,
"geometry_displacement_css_px",
value.geometry_displacement_css_px,
)
}
match value.painted_boundary_displacement {
Some(boundary) if Some(boundary) != shared_boundary =>
fields["painted_boundary_displacement"] = compact_boundary(boundary)
_ => ()
}
match value.painted_coverage_difference {
Some(coverage) if Some(coverage) != shared_coverage =>
fields["painted_coverage_difference"] = compact_coverage(coverage)
_ => ()
}
if !shared_path_displacement {
put_optional(
fields,
"geometry_viewport_fraction",
value.geometry_viewport_fraction,
)
}
put_optional(
fields,
"presence_painted_viewport_fraction",
value.presence_painted_viewport_fraction,
)
match value.intrinsic_raster {
Some(raster) =>
fields["intrinsic_raster"] = compact_intrinsic_raster(raster)
None => ()
}
put_optional(fields, "transform", value.transform_effect)
match difference.presence_magnitude {
Some(presence) => fields["presence"] = presence.to_json()
None => ()
}
if fields.is_empty() {
None
} else {
Some(Json::object(fields))
}
}
///|
fn difference_category(domain : String) -> String {
if domain.has_prefix("presence.") {
"presence"
} else if domain.has_prefix("geometry.") || domain.has_prefix("transform.") {
"geometry"
} else if domain.has_prefix("paint.") || domain.has_prefix("stroke.") {
"paint"
} else if domain.has_prefix("resource.") {
"resource"
} else if domain.has_prefix("text.") {
"text"
} else if domain.has_prefix("structure.") || domain.has_prefix("document.") {
"structure"
} else if domain.has_prefix("compositing.") ||
domain.has_prefix("effect.") ||
domain.has_prefix("effects.") ||
domain.has_prefix("filter.") ||
domain.has_prefix("filtering.") ||
domain.has_prefix("mask.") ||
domain.has_prefix("masking.") ||
domain.has_prefix("clip.") ||
domain.has_prefix("clipping.") ||
domain.has_prefix("blend.") ||
domain.has_prefix("blending.") ||
domain.has_prefix("isolation.") ||
domain.has_prefix("opacity.") {
"compositing"
} else {
"other"
}
}
///|
fn difference_subject(
report : StructuredReport,
difference : AtomicDifference,
) -> String {
for event in report.events {
if event.atomic_difference_ids.contains(difference.id) {
return event.primary_subject_id
}
}
match difference.subject_alignment_id {
Some(value) => value
None => difference.subject_role
}
}
///|
fn authored_before(difference : AtomicDifference) -> String {
match difference.source_fact_before {
Some(fact) => fact.authored_value
None => difference.source_before
}
}
///|
fn authored_after(difference : AtomicDifference) -> String {
match difference.source_fact_after {
Some(fact) => fact.authored_value
None => difference.source_after
}
}
///|
fn compact_difference(
report : StructuredReport,
difference : AtomicDifference,
) -> Json {
let fields : Map[String, Json] = Map([])
fields["id"] = difference.id.to_json()
fields["subject"] = difference_subject(report, difference).to_json()
fields["subject_role"] = difference.subject_role.to_json()
fields["kind"] = difference.domain.to_json()
fields["source"] = {
"before": authored_before(difference),
"after": authored_after(difference),
}
let effective : Map[String, Json] = Map([])
effective["relation"] = difference.computed_relation.status.to_json()
if difference.computed_relation.status == "indeterminate" ||
difference.computed_relation.status == "not_applicable" {
effective["reason"] = difference.computed_relation.reason_code.to_json()
}
if !difference.computed_relation.diagnostic_ids.is_empty() {
effective["limitation_ids"] = difference.computed_relation.diagnostic_ids.to_json()
}
fields["effective"] = Json::object(effective)
match compact_difference_magnitude(report, difference) {
Some(magnitude) => fields["magnitude"] = magnitude
None => ()
}
Json::object(fields)
}
///|
fn compact_difference_groups(report : StructuredReport) -> Array[Json] {
let categories : Array[String] = []
for difference in report.atomic_differences {
let category = difference_category(difference.domain)
if !categories.contains(category) {
categories.push(category)
}
}
let groups : Array[Json] = []
for category in categories {
let items : Array[Json] = []
for difference in report.atomic_differences {
if difference_category(difference.domain) == category {
items.push(compact_difference(report, difference))
}
}
groups.push({ "category": category, "items": items })
}
groups
}
///|
fn candidate_difference_ids(
report : StructuredReport,
changed_fact_ids : Array[String],
) -> Array[String] {
let result : Array[String] = []
for difference in report.atomic_differences {
if difference.changed_fact_ids.any(id => changed_fact_ids.contains(id)) {
result.push(difference.id)
}
}
result
}
///|
fn isolated_subject_for_event(
report : StructuredReport,
event : VisualEvent,
) -> CompactIsolatedSubject? {
let mut boundary : PaintedBoundaryDisplacementMagnitude? = None
let mut coverage : PaintedCoverageDifferenceMagnitude? = None
let mut boundary_conflict = false
let mut coverage_conflict = false
for difference in report.atomic_differences {
if !event.atomic_difference_ids.contains(difference.id) {
continue
}
match difference.magnitude.painted_boundary_displacement {
Some(value) =>
match boundary {
Some(existing) if existing != value => boundary_conflict = true
None => boundary = Some(value)
_ => ()
}
None => ()
}
match difference.magnitude.painted_coverage_difference {
Some(value) =>
match coverage {
Some(existing) if existing != value => coverage_conflict = true
None => coverage = Some(value)
_ => ()
}
None => ()
}
}
if boundary_conflict {
boundary = None
}
if coverage_conflict {
coverage = None
}
if boundary is None && coverage is None {
None
} else {
Some({
painted_boundary_displacement: boundary,
painted_coverage_difference: coverage,
})
}
}
///|
fn isolated_subject_for_difference(
report : StructuredReport,
difference_id : String,
) -> CompactIsolatedSubject? {
for event in report.events {
if event.atomic_difference_ids.contains(difference_id) {
return isolated_subject_for_event(report, event)
}
}
None
}
///|
fn compact_isolated_subject(value : CompactIsolatedSubject) -> Json {
let fields : Map[String, Json] = Map([])
match value.painted_boundary_displacement {
Some(boundary) =>
fields["painted_boundary_displacement"] = compact_boundary(boundary)
None => ()
}
match value.painted_coverage_difference {
Some(coverage) =>
fields["painted_coverage_difference"] = compact_coverage(coverage)
None => ()
}
Json::object(fields)
}
///|
fn compact_region(report : StructuredReport, region : DifferenceRegion) -> Json {
let cause : Map[String, Json] = Map([])
cause["guarantee"] = region.cause_envelope.guarantee.to_json()
cause["coverage"] = region.cause_envelope.coverage.to_json()
cause["scope"] = region.cause_envelope.fallback_scope.to_json()
if region.cause_envelope.fallback_scope != "comparison" {
cause["candidate_difference_ids"] = candidate_difference_ids(
report,
region.cause_envelope.candidate_changed_fact_ids,
).to_json()
}
if !region.cause_envelope.diagnostic_ids.is_empty() {
cause["limitation_ids"] = region.cause_envelope.diagnostic_ids.to_json()
}
{
"id": region.id,
"kind": if region.source == "pixel_mask" {
"observed"
} else {
"conservative"
},
"bounds": {
"x": region.css_x,
"y": region.css_y,
"width": region.css_width,
"height": region.css_height,
},
"changed_pixels": region.changed_pixels,
"viewport_fraction": region.viewport_fraction,
"possible_causes": Json::object(cause),
}
}
///|
fn compact_event_outcome(
value : RenderedEvidence,
isolated_subject : CompactIsolatedSubject?,
) -> Json {
let fields : Map[String, Json] = Map([])
fields["status"] = value.status.to_json()
match value.magnitude {
Some(magnitude) => {
let compact = compact_rendered_magnitude(magnitude)
guard compact is Object(values) else { return compact }
for key, item in values {
fields[key] = item
}
}
None => put_optional(fields, "reason", value.reason_code)
}
match value.perceptual_color {
{ status: "computed", magnitude: Some(magnitude), .. } =>
fields["perceptual_color"] = {
"sample_count": magnitude.sample_count,
"mean_delta_e_ok": magnitude.mean_delta_e_ok,
}
_ => ()
}
match value.perceptual_flip {
{ status: "computed", statistics: Some(statistics), .. } => {
let flip : Map[String, Json] = Map([])
flip["canvas_mean"] = statistics.canvas_mean.to_json()
flip["event_region_mean"] = statistics.event_region_mean.to_json()
flip["response_p95"] = statistics.response_p95.to_json()
flip["response_maximum"] = statistics.response_maximum.to_json()
put_optional(
flip,
"area_above_threshold",
statistics.area_above_threshold,
)
fields["perceptual_difference"] = Json::object(flip)
}
_ => ()
}
match isolated_subject {
Some(subject) =>
fields["isolated_subject"] = compact_isolated_subject(subject)
None => ()
}
Json::object(fields)
}
///|
fn compact_event(report : StructuredReport, event : VisualEvent) -> Json {
{
"id": event.id,
"subject": event.primary_subject_id,
"difference_ids": event.atomic_difference_ids,
"outcome": compact_event_outcome(
event.rendered_outcome,
isolated_subject_for_event(report, event),
),
"regions": [
for region in event.difference_regions => compact_region(report, region)
],
}
}
///|
fn compact_limitation(value : Diagnostic) -> Json {
{
"id": value.id,
"code": value.code,
"subject": value.subject_id,
"affects": value.affected_evidence_layers,
}
}
///|
pub impl ToJson for StructuredReport with fn to_json(self) {
{
"schema_version": self.schema_version,
"analysis_status": self.analysis_status,
"comparison": compact_comparison(self.profile),
"canvas": compact_canvas_outcome(self.canvas_outcome),
"difference_groups": compact_difference_groups(self),
"events": [
for event in self.events => compact_event(self, event)
],
"limitations": [
for diagnostic in self.diagnostics => compact_limitation(diagnostic)
],
}
}