{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://drobe.day/ai/schema.json",
  "title": "Drobe import envelope",
  "description": "The file Drobe's importer accepts. Contract v1 — prose contract at https://drobe.day/ai.",
  "x-contract-version": 1,
  "type": "object",
  "required": [
    "app",
    "version"
  ],
  "properties": {
    "app": {
      "const": "wardrobe-planner"
    },
    "version": {
      "const": 1
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "id",
          "name",
          "category",
          "colours"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique across the file. lowercase-hyphenated. Outfits reference this."
          },
          "name": {
            "type": "string",
            "description": "Short, human name."
          },
          "category": {
            "type": "string",
            "enum": [
              "headwear",
              "outerwear",
              "midlayer",
              "knitwear",
              "top",
              "dress",
              "bottom",
              "footwear",
              "gloves",
              "accessory",
              "bag",
              "underwear"
            ]
          },
          "brand": {
            "type": "string"
          },
          "subcategory": {
            "type": "string",
            "description": "Free text, but it routes the outfit slot: socks need \"socks\", a belt \"belt\", a scarf \"scarf\", sunglasses \"sunglasses\", a thermal exactly \"base layer\"."
          },
          "groupName": {
            "type": "string",
            "description": "Shared label for the same garment in different colours. Usage accounting is per group."
          },
          "quantity": {
            "type": "integer",
            "minimum": 1,
            "description": "Only for several IDENTICAL copies. Never put the count in the name."
          },
          "colours": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "hex"
              ],
              "properties": {
                "hex": {
                  "type": "string",
                  "pattern": "^#[0-9a-fA-F]{6}$",
                  "description": "Six-digit hex. Everything else is derived from it."
                },
                "role": {
                  "type": "string",
                  "enum": [
                    "primary",
                    "accent",
                    "trim"
                  ],
                  "description": "primary (list it first), accent (a clear secondary), or trim (piping, soles, buttons)."
                }
              }
            }
          },
          "materials": {
            "type": "string",
            "description": "Never invent a composition. Omit rather than guess."
          },
          "price": {
            "type": "number",
            "description": "What the owner PAID, in major units (79.90). Include only if they said so."
          },
          "formality": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "weight": {
            "type": "string",
            "enum": [
              "light",
              "mid",
              "heavy"
            ]
          },
          "breathability": {
            "type": "string",
            "enum": [
              "low",
              "medium",
              "high"
            ]
          },
          "silhouette": {
            "type": "string",
            "enum": [
              "slim",
              "regular",
              "relaxed",
              "oversized"
            ]
          },
          "pattern": {
            "type": "string",
            "enum": [
              "solid",
              "texture",
              "graphic",
              "print",
              "stripe",
              "check"
            ]
          },
          "role": {
            "type": "string",
            "enum": [
              "workhorse",
              "statement",
              "base",
              "layer",
              "accent"
            ],
            "description": "Styling FUNCTION. Not frequency, not availability."
          },
          "rotationIntent": {
            "type": "string",
            "enum": [
              "hero",
              "regular",
              "occasional",
              "retiring"
            ],
            "description": "Desired wear FREQUENCY. Never inferred from the garment — only the owner can set it."
          },
          "tempMinC": {
            "type": "number"
          },
          "tempMaxC": {
            "type": "number"
          },
          "rainproof": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3
          },
          "windproof": {
            "type": "integer",
            "minimum": 0,
            "maximum": 3
          },
          "hasLeather": {
            "type": "boolean"
          },
          "leatherTone": {
            "type": "string",
            "enum": [
              "brown",
              "black",
              "tan"
            ]
          },
          "constraints": {
            "description": "HARD rules, one per line. Non-negotiable: an outfit breaking one is wrong. A newline-separated string is accepted too.",
            "anyOf": [
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              {
                "type": "string"
              }
            ]
          },
          "notes": {
            "type": "string"
          },
          "cleaning": {
            "type": "object",
            "description": "Per-item wash instructions.",
            "properties": {
              "machineTempC": {
                "type": [
                  "number",
                  "null"
                ],
                "enum": [
                  30,
                  40,
                  60,
                  null
                ],
                "description": "null when it is not machine washable."
              },
              "cycle": {
                "type": "string",
                "enum": [
                  "normal",
                  "delicate",
                  "wool",
                  "hand_wash"
                ]
              },
              "dryMethod": {
                "type": "string",
                "enum": [
                  "tumble_low",
                  "air_dry",
                  "flat_dry",
                  "drip_dry"
                ]
              },
              "iron": {
                "type": "string",
                "enum": [
                  "none",
                  "low",
                  "medium",
                  "high"
                ]
              },
              "dryCleanOnly": {
                "type": "boolean"
              },
              "washWith": {
                "type": "string",
                "enum": [
                  "darks",
                  "lights",
                  "similar",
                  "alone"
                ],
                "description": "Route by the garment's ACTUAL colour — never a pale garment with darks."
              },
              "washFrequencyWears": {
                "type": "integer",
                "minimum": 1
              },
              "specialNotes": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "outfits": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "name",
          "items"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Unique and short. Re-using an existing name updates that outfit in place."
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            },
            "description": "Each piece's item id (preferred — exact, survives renames) or its EXACT name."
          },
          "tempMinC": {
            "type": "number",
            "description": "Snap to the band grid: below 0, 0-5, 5-10, 10-15, 15-20, 20-25, 25-30, 30+."
          },
          "tempMaxC": {
            "type": "number",
            "description": "A look may span two adjacent bands; it then shelves by the MIDPOINT, i.e. the upper band."
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Lowercase occasion tags."
          },
          "notes": {
            "type": "string",
            "description": "Lead with ONE sentence of why the look works."
          },
          "alternates": {
            "type": "object",
            "description": "Optional swap candidates per slot: up to 2 each, never the piece already in that slot, most outfits have none.",
            "propertyNames": {
              "enum": [
                "headwear",
                "eyewear",
                "outerwear",
                "knitwear",
                "top",
                "dress",
                "neckwear",
                "accessory",
                "bottom",
                "hosiery",
                "socks",
                "footwear",
                "wrist",
                "gloves",
                "bag"
              ]
            },
            "additionalProperties": {
              "type": "array",
              "maxItems": 2,
              "items": {
                "type": "string"
              }
            }
          },
          "core": {
            "type": "boolean",
            "description": "A proven, rebuild-the-rotation-around-it look."
          },
          "favourite": {
            "type": "boolean",
            "description": "Independent of core — this one is the heart."
          }
        }
      }
    },
    "careGuides": {
      "type": "array",
      "items": {
        "type": "object",
        "required": [
          "title",
          "items",
          "body"
        ],
        "properties": {
          "title": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "EXACT, byte-identical item names. A near-miss attaches to nothing. [] = a general reference guide."
          },
          "body": {
            "type": "string",
            "description": "Plain paragraphs with **bold** lead-ins. No headings, bullets or tables."
          }
        }
      }
    }
  },
  "$defs": {
    "item": {
      "type": "object",
      "required": [
        "id",
        "name",
        "category",
        "colours"
      ],
      "properties": {
        "id": {
          "type": "string",
          "description": "Unique across the file. lowercase-hyphenated. Outfits reference this."
        },
        "name": {
          "type": "string",
          "description": "Short, human name."
        },
        "category": {
          "type": "string",
          "enum": [
            "headwear",
            "outerwear",
            "midlayer",
            "knitwear",
            "top",
            "dress",
            "bottom",
            "footwear",
            "gloves",
            "accessory",
            "bag",
            "underwear"
          ]
        },
        "brand": {
          "type": "string"
        },
        "subcategory": {
          "type": "string",
          "description": "Free text, but it routes the outfit slot: socks need \"socks\", a belt \"belt\", a scarf \"scarf\", sunglasses \"sunglasses\", a thermal exactly \"base layer\"."
        },
        "groupName": {
          "type": "string",
          "description": "Shared label for the same garment in different colours. Usage accounting is per group."
        },
        "quantity": {
          "type": "integer",
          "minimum": 1,
          "description": "Only for several IDENTICAL copies. Never put the count in the name."
        },
        "colours": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "object",
            "required": [
              "hex"
            ],
            "properties": {
              "hex": {
                "type": "string",
                "pattern": "^#[0-9a-fA-F]{6}$",
                "description": "Six-digit hex. Everything else is derived from it."
              },
              "role": {
                "type": "string",
                "enum": [
                  "primary",
                  "accent",
                  "trim"
                ],
                "description": "primary (list it first), accent (a clear secondary), or trim (piping, soles, buttons)."
              }
            }
          }
        },
        "materials": {
          "type": "string",
          "description": "Never invent a composition. Omit rather than guess."
        },
        "price": {
          "type": "number",
          "description": "What the owner PAID, in major units (79.90). Include only if they said so."
        },
        "formality": {
          "type": "integer",
          "minimum": 1,
          "maximum": 5
        },
        "weight": {
          "type": "string",
          "enum": [
            "light",
            "mid",
            "heavy"
          ]
        },
        "breathability": {
          "type": "string",
          "enum": [
            "low",
            "medium",
            "high"
          ]
        },
        "silhouette": {
          "type": "string",
          "enum": [
            "slim",
            "regular",
            "relaxed",
            "oversized"
          ]
        },
        "pattern": {
          "type": "string",
          "enum": [
            "solid",
            "texture",
            "graphic",
            "print",
            "stripe",
            "check"
          ]
        },
        "role": {
          "type": "string",
          "enum": [
            "workhorse",
            "statement",
            "base",
            "layer",
            "accent"
          ],
          "description": "Styling FUNCTION. Not frequency, not availability."
        },
        "rotationIntent": {
          "type": "string",
          "enum": [
            "hero",
            "regular",
            "occasional",
            "retiring"
          ],
          "description": "Desired wear FREQUENCY. Never inferred from the garment — only the owner can set it."
        },
        "tempMinC": {
          "type": "number"
        },
        "tempMaxC": {
          "type": "number"
        },
        "rainproof": {
          "type": "integer",
          "minimum": 0,
          "maximum": 3
        },
        "windproof": {
          "type": "integer",
          "minimum": 0,
          "maximum": 3
        },
        "hasLeather": {
          "type": "boolean"
        },
        "leatherTone": {
          "type": "string",
          "enum": [
            "brown",
            "black",
            "tan"
          ]
        },
        "constraints": {
          "description": "HARD rules, one per line. Non-negotiable: an outfit breaking one is wrong. A newline-separated string is accepted too.",
          "anyOf": [
            {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            {
              "type": "string"
            }
          ]
        },
        "notes": {
          "type": "string"
        },
        "cleaning": {
          "type": "object",
          "description": "Per-item wash instructions.",
          "properties": {
            "machineTempC": {
              "type": [
                "number",
                "null"
              ],
              "enum": [
                30,
                40,
                60,
                null
              ],
              "description": "null when it is not machine washable."
            },
            "cycle": {
              "type": "string",
              "enum": [
                "normal",
                "delicate",
                "wool",
                "hand_wash"
              ]
            },
            "dryMethod": {
              "type": "string",
              "enum": [
                "tumble_low",
                "air_dry",
                "flat_dry",
                "drip_dry"
              ]
            },
            "iron": {
              "type": "string",
              "enum": [
                "none",
                "low",
                "medium",
                "high"
              ]
            },
            "dryCleanOnly": {
              "type": "boolean"
            },
            "washWith": {
              "type": "string",
              "enum": [
                "darks",
                "lights",
                "similar",
                "alone"
              ],
              "description": "Route by the garment's ACTUAL colour — never a pale garment with darks."
            },
            "washFrequencyWears": {
              "type": "integer",
              "minimum": 1
            },
            "specialNotes": {
              "type": "string"
            }
          }
        }
      }
    },
    "outfit": {
      "type": "object",
      "required": [
        "name",
        "items"
      ],
      "properties": {
        "name": {
          "type": "string",
          "description": "Unique and short. Re-using an existing name updates that outfit in place."
        },
        "items": {
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string"
          },
          "description": "Each piece's item id (preferred — exact, survives renames) or its EXACT name."
        },
        "tempMinC": {
          "type": "number",
          "description": "Snap to the band grid: below 0, 0-5, 5-10, 10-15, 15-20, 20-25, 25-30, 30+."
        },
        "tempMaxC": {
          "type": "number",
          "description": "A look may span two adjacent bands; it then shelves by the MIDPOINT, i.e. the upper band."
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Lowercase occasion tags."
        },
        "notes": {
          "type": "string",
          "description": "Lead with ONE sentence of why the look works."
        },
        "alternates": {
          "type": "object",
          "description": "Optional swap candidates per slot: up to 2 each, never the piece already in that slot, most outfits have none.",
          "propertyNames": {
            "enum": [
              "headwear",
              "eyewear",
              "outerwear",
              "knitwear",
              "top",
              "dress",
              "neckwear",
              "accessory",
              "bottom",
              "hosiery",
              "socks",
              "footwear",
              "wrist",
              "gloves",
              "bag"
            ]
          },
          "additionalProperties": {
            "type": "array",
            "maxItems": 2,
            "items": {
              "type": "string"
            }
          }
        },
        "core": {
          "type": "boolean",
          "description": "A proven, rebuild-the-rotation-around-it look."
        },
        "favourite": {
          "type": "boolean",
          "description": "Independent of core — this one is the heart."
        }
      }
    },
    "careGuide": {
      "type": "object",
      "required": [
        "title",
        "items",
        "body"
      ],
      "properties": {
        "title": {
          "type": "string"
        },
        "items": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "EXACT, byte-identical item names. A near-miss attaches to nothing. [] = a general reference guide."
        },
        "body": {
          "type": "string",
          "description": "Plain paragraphs with **bold** lead-ins. No headings, bullets or tables."
        }
      }
    },
    "colour": {
      "type": "object",
      "required": [
        "hex"
      ],
      "properties": {
        "hex": {
          "type": "string",
          "pattern": "^#[0-9a-fA-F]{6}$",
          "description": "Six-digit hex. Everything else is derived from it."
        },
        "role": {
          "type": "string",
          "enum": [
            "primary",
            "accent",
            "trim"
          ],
          "description": "primary (list it first), accent (a clear secondary), or trim (piping, soles, buttons)."
        }
      }
    },
    "cleaning": {
      "type": "object",
      "description": "Per-item wash instructions.",
      "properties": {
        "machineTempC": {
          "type": [
            "number",
            "null"
          ],
          "enum": [
            30,
            40,
            60,
            null
          ],
          "description": "null when it is not machine washable."
        },
        "cycle": {
          "type": "string",
          "enum": [
            "normal",
            "delicate",
            "wool",
            "hand_wash"
          ]
        },
        "dryMethod": {
          "type": "string",
          "enum": [
            "tumble_low",
            "air_dry",
            "flat_dry",
            "drip_dry"
          ]
        },
        "iron": {
          "type": "string",
          "enum": [
            "none",
            "low",
            "medium",
            "high"
          ]
        },
        "dryCleanOnly": {
          "type": "boolean"
        },
        "washWith": {
          "type": "string",
          "enum": [
            "darks",
            "lights",
            "similar",
            "alone"
          ],
          "description": "Route by the garment's ACTUAL colour — never a pale garment with darks."
        },
        "washFrequencyWears": {
          "type": "integer",
          "minimum": 1
        },
        "specialNotes": {
          "type": "string"
        }
      }
    }
  }
}
