{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://handsonailab.dev/schemas/experiment.schema.json",
  "title": "Experiment",
  "description": "직접 실험 1건의 원본 기록. 모든 벤치마크 글은 이 스키마를 통과한 experiment JSON을 evidence로 참조해야 한다.",
  "type": "object",
  "required": [
    "id",
    "date",
    "task",
    "grade",
    "purpose",
    "environment",
    "variables",
    "controlled",
    "results",
    "artifacts"
  ],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^EXP-\\d{4}-\\d{2}-\\d{3}$",
      "description": "예: EXP-2026-09-001"
    },
    "date": {
      "type": "string",
      "format": "date"
    },
    "lastVerifiedAt": {
      "type": "string",
      "format": "date",
      "description": "더 새로운 런타임에서 재확인한 날짜. 있으면 stale 계산에 date 대신 쓰인다."
    },
    "task": {
      "enum": [
        "video-gen",
        "llm",
        "image-gen",
        "image-to-3d",
        "cad-modeling"
      ]
    },
    "grade": {
      "enum": [
        "directly-tested",
        "reproduced",
        "unverified-report",
        "logbook"
      ],
      "description": "이 기록을 얼마나 믿을 수 있는가. **필수다.** 빠지면 검증이 실패한다 — 예전에는 누락 시 directly-tested로 기본 처리돼서, 가장 약한 기록이 가장 강한 주장으로 조용히 승격됐다. directly-tested는 이 랩의 하드웨어에서 직접 돌린 기록에만 쓴다. logbook은 본인이 당시 남긴 실행 기록을 나중에 옮긴 것 — 재실행이 아니고 seed·버전이 안 잡혀 있으므로 실측으로 쓰지 않는다."
    },
    "provenance": {
      "type": "object",
      "description": "이 랩 밖에서 온 기록의 출처. grade가 directly-tested가 아니면 필수다.",
      "required": [
        "type",
        "submittedAt"
      ],
      "properties": {
        "type": {
          "enum": [
            "community-submission",
            "vendor-report",
            "third-party-benchmark",
            "reproduction",
            "owner-log"
          ],
          "description": "어떤 경로로 들어왔는가. owner-log는 이 랩 밖이 아니라 이 랩의 과거 기록에서 왔다는 뜻이고 logbook 등급 전용이다."
        },
        "sourceUrl": {
          "type": "string",
          "format": "uri",
          "description": "원본 Issue·PR·글의 URL. 커뮤니티 제출에는 사실상 필수."
        },
        "submittedBy": {
          "type": "string",
          "description": "제보자가 밝힌 이름 또는 핸들"
        },
        "submittedAt": {
          "type": "string",
          "format": "date"
        },
        "reviewedBy": {
          "type": "string",
          "description": "받아들이기 전에 확인한 사람"
        },
        "notes": {
          "type": "string"
        }
      }
    },
    "sample": {
      "type": "boolean",
      "description": "스키마·표·링크를 돌려보기 위한 뼈대 레코드. true면 측정값이 아니며 headline 집계에서 제외되고 표에 그렇게 표시된다. 실측 기록에는 쓰지 않는다."
    },
    "purpose": {
      "type": "string",
      "minLength": 10
    },
    "environment": {
      "type": "object",
      "required": [
        "platform",
        "gpu",
        "vram_gb",
        "os",
        "runtime",
        "model"
      ],
      "properties": {
        "platform": {
          "enum": [
            "cuda",
            "apple-silicon"
          ]
        },
        "gpu": {
          "type": "string",
          "description": "GPU 또는 Apple SoC. 예: RTX 4070 Ti / M4 Pro"
        },
        "vram_gb": {
          "type": "number",
          "description": "VRAM 또는 통합 메모리(GB)"
        },
        "os": {
          "type": "string"
        },
        "driver": {
          "type": "string"
        },
        "runtime": {
          "type": "string",
          "description": "예: ComfyUI 커밋/버전"
        },
        "python": {
          "type": "string"
        },
        "torch": {
          "type": "string"
        },
        "model": {
          "type": "string",
          "description": "모델명+양자화. 예: Wan2.2 MoE I2V 14B Q4_K_M"
        },
        "loras": {
          "type": "array",
          "items": {
            "type": "string"
          }
        }
      }
    },
    "variables": {
      "type": "object",
      "description": "이번 실험에서 바꾼 변수. 통제 실험이면 1개만.",
      "minProperties": 1
    },
    "controlled": {
      "type": "object",
      "description": "고정한 조건 (seed, 입력 이미지, 프롬프트, 해상도, 프레임 등)",
      "required": [
        "seed"
      ],
      "properties": {
        "seed": {
          "type": [
            "integer",
            "string"
          ]
        },
        "prompt": {
          "type": "string"
        },
        "input_image": {
          "type": "string"
        },
        "resolution": {
          "type": "string"
        },
        "frames": {
          "type": "integer"
        },
        "steps": {
          "type": [
            "integer",
            "string"
          ]
        },
        "cfg": {
          "type": "number"
        }
      }
    },
    "results": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "object",
        "required": [
          "run",
          "outcome"
        ],
        "properties": {
          "run": {
            "type": "string",
            "description": "변수값 조합 라벨"
          },
          "outcome": {
            "enum": [
              "success",
              "oom",
              "error",
              "degraded",
              "unverified"
            ]
          },
          "vram_peak_gb": {
            "type": "number"
          },
          "gen_time_sec": {
            "type": "number",
            "description": "video-gen/image-gen용"
          },
          "tokens_per_sec": {
            "type": "number",
            "description": "llm용"
          },
          "context_length": {
            "type": "integer",
            "description": "llm용"
          },
          "notes": {
            "type": "string"
          },
          "error_message": {
            "type": "string"
          }
        }
      }
    },
    "failed_configs": {
      "type": "array",
      "description": "실패한 설정도 반드시 기록 — 실패 데이터가 이 사이트의 차별점이다.",
      "items": {
        "type": "string"
      }
    },
    "artifacts": {
      "type": "array",
      "description": "결과 영상/스크린샷/로그 파일 경로",
      "items": {
        "type": "string"
      },
      "minItems": 1
    },
    "conclusion": {
      "type": "string"
    },
    "limitations": {
      "type": "string",
      "description": "이 결과가 유효한 범위. '이 환경·이 설정에서 관찰'로 한정."
    }
  },
  "allOf": [
    {
      "description": "이 랩에서 직접 돌리지 않은 기록은 어디서 왔는지 밝혀야 한다.",
      "if": {
        "required": [
          "grade"
        ],
        "properties": {
          "grade": {
            "const": "directly-tested"
          }
        }
      },
      "then": true,
      "else": {
        "required": [
          "provenance"
        ]
      }
    },
    {
      "description": "뼈대 레코드는 직접 실측했다고 주장할 수 없다.",
      "if": {
        "required": [
          "sample"
        ],
        "properties": {
          "sample": {
            "const": true
          }
        }
      },
      "then": {
        "properties": {
          "grade": {
            "not": {
              "const": "directly-tested"
            }
          }
        }
      }
    },
    {
      "description": "logbook 등급이 여는 세 가지 완화는 logbook 등급에서만 열린다. provenance는 owner-log로 고정하고 기록을 옮긴 날짜를 요구한다. seed 'not-recorded'와 outcome 'unverified'는 당시 안 적어둔 것을 적어둔 척하지 않기 위한 값이므로, 실측 기록이 이 값을 빌려 쓰면 반대로 정직성이 무너진다.",
      "if": {
        "required": [
          "grade"
        ],
        "properties": {
          "grade": {
            "const": "logbook"
          }
        }
      },
      "then": {
        "required": [
          "provenance"
        ],
        "properties": {
          "provenance": {
            "required": [
              "type",
              "submittedAt"
            ],
            "properties": {
              "type": {
                "const": "owner-log"
              },
              "submittedAt": {
                "type": "string",
                "format": "date"
              }
            }
          }
        }
      },
      "else": {
        "properties": {
          "controlled": {
            "properties": {
              "seed": {
                "not": {
                  "const": "not-recorded"
                }
              }
            }
          },
          "results": {
            "items": {
              "properties": {
                "outcome": {
                  "not": {
                    "const": "unverified"
                  }
                }
              }
            }
          }
        }
      }
    }
  ]
}
