{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://tekigou.jp/data/schema.json",
  "title": "tekigou.jp 適合データ（/data/<分野>.json）",
  "description": "分野ごとの JSON の形。各行の対応関係はメーカー公式のページで確かめた事実。価格・在庫・レビュー・確からしさの数字は含まない。出典が楽天市場・Yahoo!ショッピングの商品ページだけの行・部品は含まない。目録は /data/index.json。台帳用の軽いデータは /data/version.json（$defs.dataVersion）と /data/parts/<分野>.json（$defs.partsLedger）、型番から台帳の機器を組んだ物は /data/ledger-models/<キー>.json（$defs.ledgerModelShard・$defs.ledgerModel）。",
  "schemaVersion": "1.2",
  "type": "object",
  "required": [
    "schemaVersion",
    "site",
    "license",
    "field",
    "updatedAt",
    "notes",
    "fieldSources",
    "pages"
  ],
  "properties": {
    "schemaVersion": {
      "type": "string",
      "description": "形の版（今は 1.2）。欄を足すときは小さい方の数、欄の意味を変える・消すときは大きい方の数を上げる。"
    },
    "site": {
      "type": "string",
      "description": "サイトの名前。"
    },
    "license": {
      "type": "object",
      "description": "利用条件。",
      "properties": {
        "name": {
          "type": "string",
          "description": "条件の名前（当サイト独自の条件。決まったライセンスではない）。"
        },
        "url": {
          "type": "string",
          "description": "条件の本文（/about/#data-license）。",
          "format": "uri"
        },
        "summary": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "条件の要点。"
        }
      }
    },
    "field": {
      "type": "object",
      "description": "分野。",
      "properties": {
        "id": {
          "type": "string",
          "description": "分野の識別子（ファイル名と同じ）。"
        },
        "name": {
          "type": "string",
          "description": "分野の名前。"
        },
        "url": {
          "type": "string",
          "description": "分野の入口のページ。",
          "format": "uri"
        }
      }
    },
    "updatedAt": {
      "type": "string",
      "description": "この分野で一番新しいページの更新日。",
      "format": "date"
    },
    "notes": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "読み方の注意（文）。"
    },
    "fieldSources": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/source"
      },
      "description": "出典の一覧。"
    },
    "pages": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/page"
      }
    }
  },
  "$defs": {
    "source": {
      "type": "object",
      "description": "出典（メーカー公式のページ）。",
      "required": [
        "url",
        "checkedAt",
        "label",
        "sourceType"
      ],
      "properties": {
        "url": {
          "type": "string",
          "description": "出典のページの URL。",
          "format": "uri"
        },
        "checkedAt": {
          "type": "string",
          "description": "当サイトがそのページで確かめた日（YYYY-MM-DD）。",
          "format": "date"
        },
        "label": {
          "type": "string",
          "description": "出典の名前（当サイトの文）。"
        },
        "sourceType": {
          "type": [
            "string",
            "null"
          ],
          "enum": [
            "official_website",
            "official_store",
            "official_api",
            "retailer",
            "marketplace",
            null
          ],
          "description": "出典の種類（どこで確かめたかの事実）。official_website＝メーカー公式サイト、official_store＝メーカー直営の通販、official_api＝メーカー公式ショップの商品検索 API（この書き出しには入れない）、retailer＝販売店、marketplace＝モール。確からしさの段ではない。"
        }
      }
    },
    "row": {
      "type": "object",
      "description": "適合表の 1 行（本体の形名 → 純正の部品の品番）。",
      "required": [
        "model",
        "category",
        "parts",
        "sources",
        "status"
      ],
      "properties": {
        "model": {
          "type": "string",
          "description": "本体の形名（色記号 -W・-H などを除く）。"
        },
        "category": {
          "type": "string",
          "description": "本体の種類（メーカーの分類のことば）。"
        },
        "parts": {
          "type": "object",
          "description": "部品の種類 → 品番。種類の名前は parts[].kind と同じ。",
          "additionalProperties": {
            "type": "string"
          }
        },
        "alternatives": {
          "type": "object",
          "description": "部品の種類 → 同じ本体に使えるほかの品番の一覧（メーカー公式の案内）。",
          "additionalProperties": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "replaced": {
          "type": "object",
          "description": "部品の種類 → { from: 元の品番, to: 後継の品番, verified: 後継の部品のページでこの本体を確かめたか }。verified が false の時は parts にその種類を入れない（適合とは書かない）。",
          "additionalProperties": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "description": "元の品番。"
              },
              "to": {
                "type": "string",
                "description": "後継の品番。"
              },
              "verified": {
                "type": "boolean",
                "description": "後継の部品の公式ページの適用機種でこの本体を確かめた。"
              }
            }
          }
        },
        "notes": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "注記（条件・補足。当サイトの文）。"
        },
        "sources": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/source"
          },
          "description": "出典の一覧。"
        },
        "modelPageUrl": {
          "type": "string",
          "description": "この本体の型番のページ（ページのある行にだけ付く）。",
          "format": "uri"
        },
        "status": {
          "type": "string",
          "enum": [
            "confirmed",
            "with_notes",
            "successor_confirmed",
            "successor_guidance"
          ],
          "description": "行の形（data/ から書き出しのときに導く。数字の評価ではない）。confirmed＝メーカー公式で本体 → 部品を確かめた。with_notes＝確かめたうえで notes がある（中身は notes を読む）。successor_confirmed＝元の部品が後継に替わり、後継の部品の公式ページでこの本体を確かめた（replaced）。successor_guidance＝メーカーが後継の品番を案内しているが、表の本体で使えるかは確かめていない（replaced の verified: false）。"
        }
      }
    },
    "part": {
      "type": "object",
      "description": "部品（純正の交換部品・消耗品）。",
      "required": [
        "partNumber",
        "name",
        "kind",
        "genuine",
        "replacementGuide",
        "replacementCondition",
        "sources"
      ],
      "properties": {
        "partNumber": {
          "type": "string",
          "description": "品番。"
        },
        "name": {
          "type": "string",
          "description": "部品の名前（当サイトの文）。"
        },
        "kind": {
          "type": "string",
          "description": "部品の種類。"
        },
        "genuine": {
          "type": "boolean",
          "description": "true＝メーカー純正品。"
        },
        "replacementGuide": {
          "type": [
            "string",
            "null"
          ],
          "description": "メーカー公式の交換の目安。null はメーカー公式で確かめられなかったもの（推測で埋めていない）。"
        },
        "replacementCondition": {
          "type": [
            "string",
            "null"
          ],
          "description": "交換の目安の条件（メーカー公式の文の要点）。"
        },
        "replacementIntervalMonths": {
          "type": "integer",
          "minimum": 1,
          "description": "交換の目安の月数（1.1 で追加。任意）。replacementGuide を data/replacement-interval-map.json（人が 1 文言ずつ確かめた表）で月数にした物。範囲（約3〜6か月）は短い方。単一の期間・回数（約1年半に1回）・括弧内に月数があるシーズンだけに付き、使い方しだい・部位ごと・読み取れない物には付かない（無い＝メーカーの目安から月数を決められない。推測で埋めていない）。表示は replacementGuide の元の文言を使う。"
        },
        "replacementIntervalMaxMonths": {
          "type": "integer",
          "minimum": 1,
          "description": "範囲の長い方の月数（1.1 で追加。任意。replacementIntervalMonths より大きい。範囲の時だけ付く）。"
        },
        "handlingNote": {
          "type": "string",
          "description": "取り扱いの注意（当サイトの文）。"
        },
        "discontinued": {
          "type": "string",
          "description": "メーカーで販売終了（値は根拠の文）。"
        },
        "endOfSaleScheduled": {
          "type": "string",
          "description": "メーカーで販売終了の予定（値は根拠の文）。"
        },
        "successor": {
          "type": "string",
          "description": "メーカー公式が後継品・代替品として案内している品番（販売終了・終了予定の部品だけ）。各本体で使えるかは、その本体の行で確かめる。"
        },
        "howToReplaceUrl": {
          "type": "string",
          "description": "メーカー公式の交換の手順のページ。",
          "format": "uri"
        },
        "sources": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/source"
          },
          "description": "出典の一覧。"
        },
        "partPageUrl": {
          "type": "string",
          "description": "この部品の品番のページ（対応機種の一覧。ページのある部品にだけ付く）。",
          "format": "uri"
        }
      }
    },
    "excluded": {
      "type": "object",
      "description": "載せていない本体（公式情報どうしで確かめきれなかったもの）。",
      "required": [
        "model",
        "reason",
        "status"
      ],
      "properties": {
        "model": {
          "type": "string",
          "description": "本体の形名。"
        },
        "reason": {
          "type": "string",
          "description": "載せていない理由（当サイトの文）。"
        },
        "status": {
          "type": "string",
          "enum": [
            "excluded"
          ],
          "description": "常に excluded（適合かどうかを書いていない）。"
        }
      }
    },
    "page": {
      "type": "object",
      "description": "シリーズの適合表のページ 1 つ分。",
      "required": [
        "slug",
        "url",
        "title",
        "manufacturer",
        "updatedAt",
        "dataCheckedAt",
        "parts",
        "rows",
        "excluded"
      ],
      "properties": {
        "slug": {
          "type": "string",
          "description": "ページの識別子。"
        },
        "url": {
          "type": "string",
          "description": "ページの URL。",
          "format": "uri"
        },
        "title": {
          "type": "string",
          "description": "ページの題（質問の形）。"
        },
        "answer": {
          "type": "string",
          "description": "ページの結論の文。"
        },
        "omitted": {
          "type": "object",
          "description": "この書き出しに入れなかった行・部品の数（公式の通販が根拠で営利に使わない物）。ある時は answer・excluded を出さない。",
          "properties": {
            "parts": {
              "type": "integer"
            },
            "rows": {
              "type": "integer"
            }
          }
        },
        "manufacturer": {
          "type": "string",
          "description": "メーカー名。"
        },
        "updatedAt": {
          "type": "string",
          "description": "ページの更新日。",
          "format": "date"
        },
        "dataCheckedAt": {
          "type": "string",
          "description": "表の確認日。",
          "format": "date"
        },
        "parts": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/part"
          }
        },
        "rows": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/row"
          }
        },
        "excluded": {
          "type": "array",
          "items": {
            "$ref": "#/$defs/excluded"
          }
        }
      }
    },
    "dataVersion": {
      "type": "object",
      "description": "/data/version.json。データが新しくなったかをこれ 1 つで判断する。Web の台帳・Service Worker と Android アプリが共通で読む読み取り専用データ。",
      "required": [
        "schemaVersion",
        "dataVersion",
        "builtAt"
      ],
      "properties": {
        "schemaVersion": {
          "type": "string",
          "description": "形の版（schemaVersion と同じ。形を変えるときは上げる）。"
        },
        "dataVersion": {
          "type": "string",
          "description": "/data/parts/<分野>.json の中身から作った短いハッシュ。台帳の元になるデータが変わると変わり、変わらなければ同じ。"
        },
        "builtAt": {
          "type": "string",
          "description": "このデータを書き出した時刻（ISO 8601）。",
          "format": "date-time"
        }
      }
    },
    "ledgerPart": {
      "type": "object",
      "description": "台帳用の部品 1 件（品番がキー）。価格・在庫・レビューは含まない。",
      "required": [
        "saleStatus",
        "successor",
        "checkedAt",
        "intervalMonths",
        "intervalMaxMonths",
        "hash"
      ],
      "properties": {
        "saleStatus": {
          "type": "string",
          "enum": [
            "listed",
            "end_of_sale_scheduled",
            "discontinued"
          ],
          "description": "メーカー公式の販売の案内。listed＝販売終了の案内を確かめていない（販売中・在庫を保証しない）、end_of_sale_scheduled＝販売終了の予定、discontinued＝販売終了。"
        },
        "successor": {
          "type": [
            "string",
            "null"
          ],
          "description": "メーカー公式が後継品・代替品として案内している品番。無ければ null。"
        },
        "checkedAt": {
          "type": [
            "string",
            "null"
          ],
          "format": "date",
          "description": "その部品の出典を当サイトが確かめた日の一番新しい日（YYYY-MM-DD）。"
        },
        "intervalMonths": {
          "type": [
            "integer",
            "null"
          ],
          "description": "交換の目安の月数（replacementIntervalMonths と同じ。月数を決められなければ null）。"
        },
        "intervalMaxMonths": {
          "type": [
            "integer",
            "null"
          ],
          "description": "範囲の長い方の月数（範囲の時だけ。それ以外は null）。"
        },
        "hash": {
          "type": "string",
          "description": "販売状態・後継・確認日・交換間隔・出典の URL の並びから作った 8 文字のハッシュ。台帳に保存した時の値と違えば「情報が更新されています」を出す。"
        }
      }
    },
    "partsLedger": {
      "type": "object",
      "description": "/data/parts/<分野>.json。台帳に保存した部品の分野だけ読む。/data/<分野>.json と同じ範囲の部品（楽天・Yahoo!だけの部品・営利に使わない部品は含まない）。Web の台帳と Android アプリが共通で読む読み取り専用データ。",
      "required": [
        "schemaVersion",
        "field",
        "parts"
      ],
      "properties": {
        "schemaVersion": {
          "type": "string",
          "description": "形の版。"
        },
        "field": {
          "type": "object",
          "description": "分野。",
          "properties": {
            "id": {
              "type": "string",
              "description": "分野の識別子（ファイル名と同じ）。"
            },
            "name": {
              "type": "string",
              "description": "分野の名前。"
            }
          }
        },
        "parts": {
          "type": "object",
          "description": "品番 → 台帳用の部品。",
          "additionalProperties": {
            "$ref": "#/$defs/ledgerPart"
          }
        }
      }
    },
    "ledgerModel": {
      "type": "object",
      "description": "型番から組んだ台帳の機器 1 件。Web の「この機器を台帳に保存」が組む device（docs/ux-v16/ledger.schema.json）と同じ欄名・入れ子で、利用者が持つ欄（id・label・modelInput・memo・lastReplacedAt・startedAt・利用者が決めた間隔・savedAt・lastCheckedAt・updatedAt）だけが無い。parts はこの本体の行の部品の全部（行の並び。同じ種類 kind に品番が複数あるときは公式の候補で、利用者が選ぶ。順位ではない）。Web の保存の画面で選べる件数に上限は無く、台帳に保存できるのは 1 機器 30 部品まで。楽天・Yahoo!だけの部品・営利に使わない部品は含まない。",
      "additionalProperties": false,
      "required": [
        "field",
        "model",
        "parts"
      ],
      "properties": {
        "field": {
          "type": "string",
          "description": "分野 ID（/data/parts/<分野>.json の <分野>）。"
        },
        "model": {
          "type": "string",
          "description": "本体の形名（/data/<分野>.json の rows[].model と同じ。色記号を外した形）。"
        },
        "parts": {
          "type": "array",
          "minItems": 1,
          "description": "部品の候補。台帳の parts[] と同じ形（kind・partNumber・interval・hash）。",
          "items": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "kind",
              "partNumber"
            ],
            "properties": {
              "kind": {
                "type": "string",
                "description": "部品の種類の表示名（「フィルター」など）。"
              },
              "partNumber": {
                "type": "string",
                "description": "品番。"
              },
              "interval": {
                "type": "object",
                "description": "メーカーの交換目安（/data/parts/<分野>.json の intervalMonths が決められた部品だけ。利用者の間隔は含まない）。",
                "additionalProperties": false,
                "required": [
                  "months",
                  "basis"
                ],
                "properties": {
                  "months": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 240,
                    "description": "目安の月数（範囲なら短い方）。"
                  },
                  "basis": {
                    "const": "manufacturer"
                  },
                  "text": {
                    "type": "string",
                    "description": "メーカーの元の文言（replacementGuide）。表示はこれを使う。"
                  },
                  "condition": {
                    "type": "string",
                    "description": "その条件（replacementCondition）。"
                  }
                }
              },
              "hash": {
                "type": "string",
                "description": "/data/parts/<分野>.json の同じ品番の hash（8 文字）。台帳に保存するときにそのまま部品に持たせる。"
              }
            }
          }
        }
      }
    },
    "ledgerModelShard": {
      "type": "object",
      "description": "/data/ledger-models/<キー>.json。キーは /search-index/ と同じ（正規化した型番 normalizeCode の先頭 2 文字・大文字）。入れた型番を normalizeCode にかけ、そのキーのファイル 1 つだけを読み、models[正規化した型番] を引く（完全一致だけ。無ければ Web の検索へ）。値は配列: 同じ型番が複数の分野にある本体（エアコンのフィルターとリモコンなど）は分野ごとの機器が並び、利用者が選ぶ。split があるときは、型番の先頭 split 文字のキーのファイルをもう一度読む（1 ファイルを 64KB 以内にするための分割）。Web の台帳と Android アプリが共通で読む読み取り専用データ。",
      "required": [
        "schemaVersion",
        "dataVersion",
        "models"
      ],
      "properties": {
        "schemaVersion": {
          "type": "string",
          "description": "形の版。"
        },
        "dataVersion": {
          "type": "string",
          "description": "/data/version.json の dataVersion と同じ。"
        },
        "split": {
          "type": "integer",
          "minimum": 3,
          "description": "このキーのファイルが大きすぎて分けたときだけ。分けた先のキーの長さ。models には、これより短い型番だけが残る。"
        },
        "models": {
          "type": "object",
          "description": "正規化した型番 → その型番の機器（ledgerModel）の配列。",
          "additionalProperties": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/$defs/ledgerModel"
            }
          }
        }
      }
    }
  }
}