openapi: 3.1.0
info:
  title: yamanaka-bengoshi.jp REST API
  version: 1.0.0
  summary: 裁判官・検察官データ検索 API
  description: |
    弁護士 山中理司のブログ (yamanaka-bengoshi.jp) が公開する裁判官・検察官データの
    検索および取得 API。商用利用は別途ライセンス契約が必要。

    認証方式: 一部エンドポイント (`/dump` 等) は `X-API-Key` ヘッダー必須。
    キー発行は山中弁護士へ別途連絡。

    レート制限: 認証ルートはキー単位で 5 分間 100 リクエスト。超過時 HTTP 429。
  contact:
    name: 弁護士 山中理司
    url: https://yamanaka-bengoshi.jp/
  license:
    name: Proprietary - Contact for commercial use
servers:
  - url: https://yamanaka-bengoshi.jp/wp-json/yamanaka/v1
    description: Production
paths:
  /search:
    get:
      summary: 裁判官検索 (無認証公開)
      description: |
        裁判官の記事を全文検索 + 修習期 / 大学 / エリア / 生年月日でフィルタ。
        既定 100 件・?limit で最大 5000 件まで返却。
      parameters:
        - name: s
          in: query
          description: 検索キーワード (空白区切り AND)
          schema: { type: string }
        - name: term_min
          in: query
          description: 修習期下限 (最小値 34)
          schema: { type: integer, minimum: 34, default: 34 }
        - name: term_max
          in: query
          description: 修習期上限 (最大値 99)
          schema: { type: integer, maximum: 99, default: 99 }
        - name: uni
          in: query
          description: 出身大学 (カンマ区切り、いずれかに該当する OR フィルタ)
          schema: { type: string }
          example: "東大,京大"
        - name: area
          in: query
          description: 勤務地エリア (カンマ区切り、いずれかに該当する OR フィルタ)
          schema: { type: string }
          example: "東京,大阪"
        - name: bday
          in: query
          description: 生年月日 MMDD 4 桁 (誕生日カード機能用)
          schema:
            type: string
            pattern: "^[0-9]{4}$"
          example: "0513"
        - name: limit
          in: query
          description: 取得件数上限。既定 100・最大 5000 (ファセット UI の全件取得用)。
          schema: { type: integer, default: 100, maximum: 5000 }
        - name: cat
          in: query
          description: |
            現職 / 元職カテゴリ切替 (既定は現職経歴)。元職では uni/area 併用は空になる場合あり (v1 経路)。
            both は現職＋元職の両カテゴリ (「○○期の裁判官」「○○年生の裁判官」タグ archive 用)。
            moto と both は修習期の下限が 1 になる (現職のみは 34)。
          schema:
            type: string
            enum: [sonota-genshoku-keireki, sonota-moto-keireki, both]
            default: sonota-genshoku-keireki
        - name: status
          in: query
          description: 在職区分フィルタ (cat=both のみ有効・カンマ区切り OR)。値は genshoku / moto。未指定は両方。
          schema: { type: string }
          example: "moto"
        - name: tag
          in: query
          description: タグスラッグで追加絞り込み (任意)。
          schema: { type: string }
        - name: rtype
          in: query
          description: 退官種別フィルタ (元職 cat=sonota-moto-keireki のみ有効・カンマ区切り OR)。値は 罷免等/死亡/定年/任期満了/依願。
          schema: { type: string }
          example: "定年,依願"
        - name: rage
          in: query
          description: |
            退官年齢フィルタ (元職 cat=sonota-moto-keireki のみ有効・カンマ区切り OR)。退官日−生年月日の満年齢でバケット。
            キー: a0_24/a25_29/a30_34/a35_39/a40_44/a45_49/a50_54/a55_59/a60_64/a65teinen(65歳定年)/a65_69/a70teinen(70歳定年)。
            a65teinen・a70teinen は退官種別=定年との複合、a65_69 は 65-69 歳の非定年。
          schema: { type: string }
          example: "a65teinen,a60_64"
      responses:
        "200":
          description: 検索結果
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: "#/components/schemas/YamanakaJudgeRecord"
  /search_diff:
    get:
      summary: v1/v2 検索結果の差分検証 (管理者専用・診断用)
      description: |
        旧 meta_query 経路 (v1) と投影テーブル経路 (v2) を同一パラメータで実行し、URL 集合が
        一致するか (is_equal) を返す回帰検証エンドポイント。ログイン Cookie 認証 (管理者のみ)。
        パラメータは /search と同形 (s / term_min / term_max / uni / area / bday)。no-store。
      responses:
        "200":
          description: 差分検証結果 (v1/v2 件数・URL 集合の一致可否)
          content:
            application/json: {}
        "401":
          description: 管理者ログイン Cookie 無し
  /openapi.yaml:
    get:
      summary: OpenAPI 仕様書配信
      description: この文書そのもの。クライアント側 SDK 自動生成に利用。
      responses:
        "200":
          description: YAML 形式の OpenAPI 仕様書
          content:
            application/yaml: {}
  /schema.json:
    get:
      summary: JSON Schema 配信 (Draft 2020-12)
      description: |
        YamanakaJudgeRecord の構造を JSON Schema として公開。
        `json-schema-to-typescript` で TypeScript 型を自動生成可能。
      responses:
        "200":
          description: JSON Schema 仕様書
          content:
            application/json: {}
  /health:
    get:
      summary: ヘルスチェック
      description: |
        データ件数 / 最終更新時刻 / チェックサム / rebuild 状態を返す。
        SLA 監視の基点。`status: rebuilding` の場合は HTTP 503 + `Retry-After: 30`。
      responses:
        "200":
          description: 正常稼働中
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
        "503":
          description: rebuild 進行中
          headers:
            Retry-After:
              schema: { type: integer }
              description: 再試行までの秒数
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
  /dump:
    get:
      summary: 全件ダンプ (認証必須)
      description: |
        cursor pagination + JSON Lines streaming で全件取得。
        末尾に `_meta` 行 1 行 (`count` / `next_cursor` / `has_more`) を付加。
        `has_more: false` までクライアント側で cursor を進めて反復取得する。
      security:
        - ApiKeyAuth: []
      parameters:
        - name: cursor
          in: query
          description: 前回応答の `next_cursor`。初回は 0
          schema: { type: integer, default: 0 }
        - name: limit
          in: query
          description: 1 ページあたりの件数。最大 500
          schema: { type: integer, default: 500, maximum: 500 }
        - name: term_min
          in: query
          schema: { type: integer, minimum: 0, default: 0 }
        - name: term_max
          in: query
          schema: { type: integer, maximum: 99, default: 99 }
        - name: category
          in: query
          description: WordPress カテゴリスラッグ
          schema:
            type: string
            enum:
              - sonota-genshoku-keireki
              - sonota-moto-keireki
            default: sonota-genshoku-keireki
      responses:
        "200":
          description: JSON Lines ストリーム
          content:
            application/x-jsonlines: {}
        "401":
          description: X-API-Key ヘッダー欠落
        "403":
          description: 無効な API キー
        "429":
          description: レート制限超過 (5 分間 100 リクエスト)
  /pdf_search:
    get:
      summary: メディアライブラリ内 PDF のファイル名検索 (無認証公開)
      description: アップロード済み PDF をファイル名で部分一致検索。ページネーション対応。
      parameters:
        - name: q
          in: query
          description: 検索語 (ファイル名部分一致)
          schema: { type: string }
        - name: page
          in: query
          description: ページ番号 (1 始まり)
          schema: { type: integer, default: 1, minimum: 1 }
      responses:
        "200":
          description: PDF 検索結果
          content:
            application/json: {}
  /rum:
    post:
      summary: Real User Monitoring ビーコン受信 (無認証公開・対 IP スロットル)
      description: |
        Core Web Vitals (LCP / INP / CLS / TTFB / FCP) のフィールド計測値を sendBeacon で受信し、
        uploads 配下の月次 NDJSON に追記 (パスのみ・PII 非保存)。公開書込ルートのため対 IP
        スロットル (60 req/60 秒) の対象。集計は管理者の /?yamanaka_rum_report=1 で閲覧。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: PerformanceObserver が収集した CWV メトリクスとページパス
      responses:
        "200":
          description: 受理
        "204":
          description: 受理 (本文なし)
        "429":
          description: レート制限超過 (60 req/60 秒/IP)
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: 商用ライセンス契約者向けに発行される API キー
  schemas:
    YamanakaJudgeRecord:
      type: object
      required: [title, url, term]
      properties:
        title:
          type: string
          description: 裁判官氏名 (記事タイトル)
        url:
          type: string
          format: uri
          description: 記事パーマリンク
        term:
          oneOf:
            - { type: integer, minimum: 0, maximum: 99 }
            - { type: string }
          description: 修習期。0 または空文字は期外 (旧法時代等)
        uni:
          type: array
          items: { type: string }
          description: 出身大学 (複数可)
        area:
          type: array
          items: { type: string }
          description: 現在の勤務地エリア (複数可)
        birth:
          type: [string, "null"]
          pattern: "^[0-9]{8}$"
          description: 生年月日 YYYYMMDD 文字列。null は不明
        excerpt:
          type: string
          description: 記事抜粋 (キーワードヒット周辺 80 字)
        rtype:
          type: string
          description: 退官種別 (元職のみ・罷免等/死亡/定年/任期満了/依願)。現職は空文字。
    HealthResponse:
      type: object
      required: [status, schema_version, judges_current, server_time]
      properties:
        status:
          type: string
          enum: [ok, rebuilding]
        schema_version:
          type: string
          example: "1.0.0"
        last_rebuild_at:
          type: [string, "null"]
          format: date-time
        judges_current:
          type: integer
          description: 現職裁判官の件数
        judges_retired:
          type: integer
          description: 元裁判官の件数
        judges_total:
          type: integer
        checksum_sha256:
          type: [string, "null"]
          description: データ全体の SHA-256 チェックサム
        server_time:
          type: string
          format: date-time
        php_version:
          type: string
        wp_version:
          type: string
        from_cache:
          type: boolean
          description: 5 分 transient キャッシュからの応答かどうか
