{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://essuietoutlavable.fr/assets/schema-resultats-test-essuie-tout-lavable.json",
  "title": "Résultats d’un test d’essuie-tout lavable",
  "description": "Schéma ouvert pour publier les conditions, répétitions et mesures brutes issues du protocole essuietoutlavable.fr.",
  "type": "object",
  "additionalProperties": false,
  "required": ["protocol_version", "license", "tested_product", "test_conditions", "repetitions"],
  "properties": {
    "protocol_version": {"const": "2026-07-10"},
    "protocol_url": {"const": "https://essuietoutlavable.fr/protocole-test-essuie-tout-lavable.html"},
    "license": {"type": "string", "format": "uri"},
    "publisher": {
      "type": "object",
      "additionalProperties": false,
      "required": ["name"],
      "properties": {
        "name": {"type": "string", "minLength": 1},
        "url": {"type": "string", "format": "uri"}
      }
    },
    "tested_product": {
      "type": "object",
      "additionalProperties": false,
      "required": ["name", "manufacturer", "condition"],
      "properties": {
        "name": {"type": "string", "minLength": 1},
        "manufacturer": {"type": "string", "minLength": 1},
        "source_url": {"type": "string", "format": "uri"},
        "composition_claimed": {"type": "string"},
        "dimensions_claimed_cm": {"type": "string"},
        "weight_claimed_g_m2": {"type": ["number", "null"], "exclusiveMinimum": 0},
        "condition": {"enum": ["new", "washed"]},
        "wash_cycles_before_test": {"type": "integer", "minimum": 0}
      }
    },
    "test_conditions": {
      "type": "object",
      "additionalProperties": false,
      "required": ["date_time", "balance_resolution_g", "immersion_s", "drain_s"],
      "properties": {
        "date_time": {"type": "string", "format": "date-time"},
        "room_temperature_c": {"type": ["number", "null"]},
        "relative_humidity_pct": {"type": ["number", "null"], "minimum": 0, "maximum": 100},
        "water_temperature_c": {"type": ["number", "null"]},
        "balance_resolution_g": {"type": "number", "exclusiveMinimum": 0},
        "immersion_s": {"const": 30},
        "drain_s": {"const": 60},
        "drying_support": {"type": "string"},
        "notes": {"type": "string"}
      }
    },
    "repetitions": {
      "type": "array",
      "minItems": 3,
      "items": {
        "type": "object",
        "additionalProperties": false,
        "required": ["repetition", "dry_mass_g", "wet_mass_g"],
        "properties": {
          "repetition": {"type": "integer", "minimum": 1},
          "dry_mass_g": {"type": "number", "exclusiveMinimum": 0},
          "wet_mass_g": {"type": "number", "exclusiveMinimum": 0},
          "water_retained_g": {"type": "number", "minimum": 0},
          "absorption_g_per_g": {"type": "number", "minimum": 0},
          "drying_time_min": {"type": ["number", "null"], "minimum": 0},
          "length_before_cm": {"type": ["number", "null"], "exclusiveMinimum": 0},
          "width_before_cm": {"type": ["number", "null"], "exclusiveMinimum": 0},
          "length_after_cm": {"type": ["number", "null"], "exclusiveMinimum": 0},
          "width_after_cm": {"type": ["number", "null"], "exclusiveMinimum": 0},
          "length_change_pct": {"type": ["number", "null"]},
          "width_change_pct": {"type": ["number", "null"]},
          "seams_observation": {"type": "string"},
          "pilling_observation": {"type": "string"},
          "photo_urls": {
            "type": "array",
            "items": {"type": "string", "format": "uri"}
          }
        }
      }
    },
    "calculation_notes": {
      "type": "array",
      "items": {"type": "string"},
      "default": [
        "water_retained_g = wet_mass_g - dry_mass_g",
        "absorption_g_per_g = water_retained_g / dry_mass_g",
        "dry endpoint = measured mass <= dry_mass_g × 1.05"
      ]
    }
  }
}
