# frontend audience: flight-planning ドメイン
#
# このファイルは docs/openapi/frontend/openapi.yaml から $ref で参照される断片であり、
# 単体では有効な OpenAPI ドキュメントではない(openapi/info を持たない)。
# 横断的関心事(エラー型)は ../problem.yaml を $ref で参照する。
#
# --- x-ix-changes の使い方 ---
# Javadocの @since 相当の独自拡張。version はこのドキュメント(frontend/openapi.yaml)の
# info.version の座標系で書く。
#   kind: added      … 新規追加
#   kind: changed    … 仕様変更(note必須)
#   kind: deprecated … 非推奨(note必須。OAS標準の deprecated: true も併記される)
# 例: x-ix-changes: [{ kind: added, version: "0.1.0" }]
# バンドル時に description へのMarkdown注記や、operationへの x-badges(一覧で見つけやすいバッジ)に
# 自動変換される(property/parameterはdescription注記のみ、operationはバッジも付く)。
# 詳細: docs/openapi/policy.md の「バージョン注記と非推奨」

paths:
  #
  # 飛行計画
  #
  /api/v1/fp/flight-plans:
    post:
      operationId: createFlightPlan
      summary: 飛行計画仮登録
      tags: [FlightPlan]
      x-ix-changes: [{ kind: added, version: "0.1.0" }]
      description: |
        Operatorが入力した飛行計画情報を一時保存状態（`status=DRAFT`）としてデータベースに登録する。

        **デモ向け補足**: デモでは一時保存機能を提供対象外とするため、本APIのリクエストボディ
        （`FlightPlanCreateRequest`）は「飛行計画本登録」（`registerFlightPlan`）に記載のDIPS必須項目
        （`name`/`flightPurposes`/`departurePoint`/`flightPeriod`/`flightSpec`/`flyRoute`/
        `destinationPoint`/`riskMitigation`/`pilotInfo`/`email`）をすべて`required`とする。

        `DRAFT`のため、飛行計画と空域制限・他の飛行計画との競合判定は行わない。本登録（`DRAFT`→`ACCEPTED`
        への遷移）には別途「飛行計画本登録」（`POST /flight-plans/{flightPlanId}/registration`）の呼び出しが必要。

        `Idempotency-Key` ヘッダー指定での同一キーの再送（ネットワークリトライ等）では
        重複作成せず、最初に作成された飛行計画を 201 で返す。
        同一キーを異なるリクエストボディで再利用した場合は 422 を返す。
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FlightPlanCreateRequest"
            examples:
              full:
                summary: デモ想定（「飛行計画本登録」の必須項目をすべて入力した状態で作成。エリアは経路(route)）
                value:
                  name: "石狩川橋梁 点検飛行"
                  flightPurposes:
                    - code: INFRASTRUCTURE_INSPECTION
                  flightAirspace: [DENSELY_POPULATED_AREA]
                  flightType: [BEYOND_VISUAL_LINE_OF_SIGHT]
                  departurePoint: "札幌市北区 石狩川河川敷 離陸地点"
                  flightPeriod:
                    startTime: "2026-08-05T05:30:00.000Z"
                    plannedMaxTime: 60
                    plannedFlightTime: 30
                  flightSpec:
                    speed: 25
                    altitude: 80
                  flyRoute:
                    type: route
                    geometry:
                      type: LineString
                      coordinates:
                        - [141.3548784, 43.0604674]
                        - [141.3568784, 43.0624675]
                    bufferM: 50
                  destinationPoint: "札幌市東区 石狩川橋梁 北岸"
                  riskMitigation:
                    types: [ONSITE_CONTROL]
                    exceptionalConditionsMooring: false
                    assistantsNumber: 2
                  insuranceInformation:
                    insuranceCompany: "サンプル損害保険株式会社"
                    insuranceProduct: "ドローン保険 スタンダードプラン"
                    interPerson:
                      unlimited: true
                      amount: null
                    interObject:
                      unlimited: false
                      amount: 100000000
                    insuranceAbility: true
                  otherInformation: "橋梁の桁下を低速で飛行する区間あり。"
                  pilotInfo:
                    - pilotId: "a3f6c9e2-4b7d-48a1-9c6e-3d8f2b5a7c14"
                      aircraftIds:
                        - "9f4c7a2e-6b3d-48e1-a5c9-3d7f2b6a9c04"
                  email: "sample@email.com"
      responses:
        "201":
          description: 作成成功（使用済み Idempotency-Key で内容が一致する再送の場合は既存の飛行計画）
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlightPlanSaveResponse"
              examples:
                success:
                  summary: 作成成功（`DRAFT`のため競合判定は行わず conflict の各配列は空）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: DRAFT
                    reportRequired: false
                    reportStatus: UNREPORTED
                    dipsFlightPlanId: null
                    conflict:
                      airspaceRestrictions: []
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-07-28T09:00:00.000Z"
        "400":
          $ref: "../problem.yaml#/components/responses/BadRequest"
        "422":
          $ref: "../problem.yaml#/components/responses/ValidationError"

    get:
      operationId: listFlightPlans
      summary: 飛行計画一覧取得
      tags: [FlightPlan]
      x-ix-changes: [{ kind: added, version: "0.1.0" }]
      description: |
        IX-UTMにログインしているユーザが所属している組織に属するユーザから登録された飛行計画の一覧を、
        データベースから取得する。

        検索対象期間、登録ユーザによる絞り込みが可能。検索対象期間は、飛行開始日時から飛行終了日時
        （`startTime` + `plannedFlightTime` で算出）までの飛行時間帯が、指定期間と少しでも重なる
        飛行計画を検索する（飛行時間帯全体が指定期間内に収まっている必要はない）。

        **デモ向け補足**: デモではページング・ソートを提供対象外とするため、本APIは絞り込み条件に
        合致する飛行計画をすべて登録順（登録日時の昇順）で返す（ページ指定・件数指定・ソート順指定の
        パラメータはない）。ページング・ソートの方式（オフセット方式・カーソル方式、ソート対象カラム
        など）は、UI要件と想定件数が固まってから決定する。
      parameters:
        - name: periodFrom
          in: query
          required: false
          schema:
            allOf:
              - $ref: "#/components/schemas/TimestampMinute"
            example: "2026-08-05T05:30:00.000Z"
          description: |
            検索対象期間の開始日時。飛行終了予定日時（`startTime` + `plannedFlightTime` で算出）が
            この日時以降となる飛行計画を検索対象とする（`periodFrom`〜`periodTo` の期間と飛行時間帯が
            少しでも重なる飛行計画が対象。飛行時間帯の一部が期間からはみ出していても、重なりがあれば
            対象に含まれる）。
        - name: periodTo
          in: query
          required: false
          schema:
            allOf:
              - $ref: "#/components/schemas/TimestampMinute"
            example: "2026-08-05T06:30:00.000Z"
          description: |
            検索対象期間の終了日時。飛行開始予定日時（`startTime`）がこの日時以前となる飛行計画を
            検索対象とする。詳細は `periodFrom` の説明を参照。
        - name: registeredUserId
          in: query
          required: false
          schema:
            $ref: "../domain.yaml#/components/schemas/UserId"
          description: 絞り込み対象の飛行計画を登録したユーザのID（形式は`UserId`を参照。pattern未決定）。
      responses:
        "200":
          description: 取得成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlightPlanListResponse"
              examples:
                success:
                  summary: 取得成功（登録順に返却。`DRAFT`で未入力の項目は`null`）
                  value:
                    items:
                      - flightPlanId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
                        status: ACTIVATED
                        reportRequired: true
                        reportStatus: REPORTED
                        dipsFlightPlanId: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353426.001"
                        conflict:
                          airspaceRestrictions: []
                          flightPlanIds: []
                        createdAt: "2026-07-28T09:00:00.000Z"
                        updatedAt: "2026-08-05T05:30:00.000Z"
                        name: "丘珠周辺 空撮"
                        departurePoint: "札幌市東区 丘珠公園 駐車場"
                        destinationPoint: "札幌市東区 丘珠公園 駐車場"
                        startTime: "2026-08-05T05:30:00.000Z"
                        endTime: "2026-08-05T06:00:00.000Z"
                        flightType: [BEYOND_VISUAL_LINE_OF_SIGHT]
                        pilotInfo:
                          - pilotId: "a3f6c9e2-4b7d-48a1-9c6e-3d8f2b5a7c14"
                            pilotName: "山田 太郎"
                            aircraftIds:
                              - "9f4c7a2e-6b3d-48e1-a5c9-3d7f2b6a9c04"
                            aircraftNames:
                              - "DJI Matrice 350 RTK"
                      - flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                        status: ACCEPTED
                        reportRequired: true
                        reportStatus: UNREPORTED
                        dipsFlightPlanId: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353423.001"
                        conflict:
                          airspaceRestrictions:
                            - airspaceRestrictionId: "0199f3a0-0015-7e4f-8a9b-0c1d2e3f4a15"
                              airspaceRestrictionType: AIRPORT_VICINITY
                          flightPlanIds: []
                        createdAt: "2026-07-28T09:00:00.000Z"
                        updatedAt: "2026-07-28T10:30:00.000Z"
                        name: "石狩川橋梁 点検飛行"
                        departurePoint: "札幌市北区 石狩川河川敷 離陸地点"
                        destinationPoint: "札幌市東区 石狩川橋梁 北岸"
                        startTime: "2026-08-03T01:00:00.000Z"
                        endTime: "2026-08-03T01:45:00.000Z"
                        flightType: []
                        pilotInfo:
                          - pilotId: "a3f6c9e2-4b7d-48a1-9c6e-3d8f2b5a7c14"
                            pilotName: "山田 太郎"
                            aircraftIds:
                              - "8c3f61a4-9d2e-4b71-8f5c-3a6e9b2d7c14"
                            aircraftNames:
                              - "DJI Mavic 3 Enterprise"
                      - flightPlanId: "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                        status: DRAFT
                        reportRequired: false
                        reportStatus: UNREPORTED
                        dipsFlightPlanId: null
                        conflict:
                          airspaceRestrictions: []
                          flightPlanIds: []
                        createdAt: "2026-08-01T02:00:00.000Z"
                        updatedAt: "2026-08-01T02:00:00.000Z"
                        name: "石狩川橋梁 点検飛行（下書き）"
                        departurePoint: null
                        destinationPoint: null
                        startTime: null
                        endTime: null
                        flightType: null
                        pilotInfo: null
                empty:
                  summary: 絞り込み条件に合致する飛行計画が1件もない
                  value:
                    items: []
        "400":
          description: クエリパラメータ（`periodFrom`/`periodTo`）の形式が不正
          content:
            application/problem+json:
              schema:
                $ref: "../problem.yaml#/components/schemas/ProblemDetail"
              examples:
                invalidPeriod:
                  summary: "`periodFrom`/`periodTo`が日時形式として不正"
                  value:
                    type: /problems/parse-error
                    title: Bad Request
                    status: 400
                    detail: "invalid value for parameter 'periodFrom'"

  /api/v1/fp/flight-plans/{flightPlanId}:
    parameters:
      - $ref: "#/components/parameters/FlightPlanId"

    put:
      operationId: updateFlightPlan
      summary: 飛行計画更新
      tags: [FlightPlan]
      x-ix-changes: [{ kind: added, version: "0.1.0" }]
      description: |
        Operatorが入力した飛行計画情報をもとに、データベースの飛行計画情報を更新する。リクエストボディは
        部分更新（未指定項目は変更しない）で、必須項目はない。`name`以外の各項目はキーを省略すると
        既存の値を変更せず、明示的に`null`を指定すると入力済みの内容を未入力の状態に戻す（一度入力した
        項目を未入力に戻す更新を可能にするため）。値を指定すればその値に更新する（3値の意味を持つ、
        いわゆるJSON Merge Patch方式）。`name`は常に必須のため`null`を指定できない。

        検証の厳しさは対象の`status`により異なる。
        - `DRAFT`の間: 項目間の整合性検証・競合判定は行わない（入力途中の内容のまま保存できる）。
        - `ACCEPTED`以降: 「飛行計画本登録」（`POST /flight-plans/{flightPlanId}/registration`）と同じ
          必須項目・競合判定を適用する。飛行計画エリアについて空域制限との競合有無を確認し、競合が発生して
          いれば `conflict.airspaceRestrictions` に競合対象の空域制限IDを設定して返却するとともに、
          Operator宛にエリア競合発生のメール通知を行う（この場合も更新自体は成功する）。対象が通報済み
          （`reportStatus=REPORTED`）だった場合は内容変更により通報内容が古くなるため`reportStatus`を
          `UNREPORTED`に戻し、これに伴い`conflict.flightPlanIds`（DIPS通報で判明する、他の飛行計画
          との競合）も空配列に戻る（再通報するまで再判定されない）。

        本APIによる更新で`status`が`DRAFT`に戻ることはない（`ACCEPTED`以降の飛行計画は`ACCEPTED`以降の
        まま維持される）。

        DIPS通報済み（`reportStatus=REPORTED`）の飛行計画を更新して内容が変わった場合、更新後の内容を
        DIPSへ再通報する必要があるため`reportStatus`は`UNREPORTED`に戻る（`status`は`ACCEPTED`以降のまま
        変わらない）。`dipsFlightPlanId`は初回通報時の値を保持したままとなる。再通報は「飛行計画通報」
        （`POST /flight-plans/{flightPlanId}/report`）を再度呼び出して行う。

        `ACCEPTED`以降で、適用後の内容が現在の内容と同一になるリクエスト（空のボディ`{}`による再送、
        現在値と同じ値の指定）を受けた場合は、更新を行わず200を返す。この場合`updatedAt`は変わらず、
        通報済み（`reportStatus=REPORTED`）であれば`REPORTED`のまま維持される（内容が変わっていないため
        再通報を要しない）。同一かどうかは保存される値で判定するため、飛行開始・終了予定日時は同一時点を
        指す別のオフセット表記（`2026-10-01T10:00:00+09:00`と`2026-10-01T01:00:00Z`）も同一として扱う。
        内容が同一の場合でも入力項目の検証は行うため、不正なリクエストは400・422を返す。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/FlightPlanUpdateRequest"
            examples:
              partial:
                summary: 部分更新（指定した項目のみ変更。未指定項目は変更しない）
                value:
                  name: "石狩川橋梁 点検飛行（第2回）"
                  flightPeriod:
                    startTime: "2026-08-12T00:00:00.000Z"
                    plannedMaxTime: 60
                    plannedFlightTime: 45
              circleArea:
                summary: "飛行計画エリアを円形(circle)に変更（`flyRoute` のoneOfパターン1）"
                value:
                  flyRoute:
                    type: circle
                    geometry:
                      type: Point
                      coordinates: [141.3548784, 43.0604674]
                    radiusM: 50
              polygonArea:
                summary: "飛行計画エリアを多角形(polygon)に変更（`flyRoute` のoneOfパターン2。終点は始点と同座標が自動付与されるため重複指定しない）"
                value:
                  flyRoute:
                    type: polygon
                    geometry:
                      type: Polygon
                      coordinates:
                        - - [141.3548784, 43.0604674]
                          - [141.3568784, 43.0604674]
                          - [141.3558784, 43.0624675]
              flightPermit:
                summary: 飛行許可・承認申請が必要な飛行のため、許可・承認情報を追加する
                value:
                  flightPermitApplicationInfo:
                    flightPermitApplicationNumber: "Q190100001"
                    permitDate: "2026-07-01"
                    startDate: "2026-07-01"
                    finishDate: "2027-06-30"
                    contactPermit:
                      name: "山田 太郎"
                      country: "001"
                      prefectures: "01"
                      address: "札幌市北区北7条西2丁目1-1"
                      telephoneCountry: "001"
                      telephone: "0111234567"
                      email: "sample@email.com"
      responses:
        "200":
          description: 更新成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlightPlanSaveResponse"
              examples:
                success:
                  summary: DIPS通報済みの飛行計画を競合なしで更新成功（更新により再通報が必要になるため`reportStatus`は`REPORTED`から`UNREPORTED`へ戻る。`dipsFlightPlanId`は初回通報時の値を維持する）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: ACCEPTED
                    reportRequired: true
                    reportStatus: UNREPORTED
                    dipsFlightPlanId: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353423.001"
                    conflict:
                      airspaceRestrictions: []
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-07-28T10:30:00.000Z"
                airspaceConflict:
                  summary: 通報済みの飛行計画で空域制限との競合が発生した状態で更新成功（競合があっても更新自体は成功する）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: ACCEPTED
                    reportRequired: true
                    reportStatus: UNREPORTED
                    dipsFlightPlanId: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353423.001"
                    conflict:
                      airspaceRestrictions:
                        - airspaceRestrictionId: "0199f3a0-0015-7e4f-8a9b-0c1d2e3f4a15"
                          airspaceRestrictionType: AIRPORT_VICINITY
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-07-28T10:30:00.000Z"
                neverReported:
                  summary: 本登録後まだ一度もDIPS通報していない飛行計画の更新成功（一度も通報が成功していないため`dipsFlightPlanId`は`null`のまま。空域制限との競合チェックはACCEPTED以降の更新のため実行済みで競合なし）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: ACCEPTED
                    reportRequired: true
                    reportStatus: UNREPORTED
                    dipsFlightPlanId: null
                    conflict:
                      airspaceRestrictions: []
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-07-28T11:15:00.000Z"
                draftSuccess:
                  summary: DRAFTの内容更新（検証・競合判定は行わないため conflict の各配列は空）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: DRAFT
                    reportRequired: false
                    reportStatus: UNREPORTED
                    dipsFlightPlanId: null
                    conflict:
                      airspaceRestrictions: []
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-07-28T10:30:00.000Z"
        "400":
          $ref: "../problem.yaml#/components/responses/BadRequest"
        "404":
          $ref: "../problem.yaml#/components/responses/NotFound"
        "409":
          description: 状態不正またはロックによるリソースアクセス不可による更新失敗。
          content:
            application/problem+json:
              schema:
                $ref: "../problem.yaml#/components/schemas/ProblemDetail"
              examples:
                invalidTransition:
                  summary: 更新できない状態（`CANCELLED`・`ENDED`）の飛行計画を更新しようとした
                  value:
                    type: /problems/invalid-transition
                    title: Conflict
                    status: 409
                    detail: "flight plan cannot be updated from status: ENDED"
                lockConflict:
                  summary: 他の処理が対象の飛行計画をロック中でアクセスできない
                  value:
                    type: /problems/business-rule-violation
                    title: Conflict
                    status: 409
                    detail: "flight plan is locked by another operation: id=550e8400-e29b-41d4-a716-446655440000"
        "422":
          $ref: "../problem.yaml#/components/responses/ValidationError"

    delete:
      operationId: deleteFlightPlan
      summary: 飛行計画削除
      tags: [FlightPlan]
      x-ix-changes:
        - { kind: added, version: "0.1.0" }
        - { kind: changed, version: "0.10.0", note: "DIPS呼び出し失敗時の`502`を宣言に追加した" }
      description: |
        指定された飛行計画IDをもとにデータベースからの削除（論理削除）を行う。
        対象の飛行計画がDIPS通報済み（`reportStatus` が `REPORTED`）の場合は、削除前にあわせて
        DIPS API(飛行計画削除)を実行する。DIPS API呼び出し前に `reportStatus` を `WITHDRAWING` に
        更新し、DIPS側に登録されている飛行計画を削除（取り下げ）できたら、飛行計画自体の削除
        （論理削除）を実行し `reportStatus` を `WITHDRAWN` に更新する。

        DIPS API(飛行計画削除)の呼び出しが失敗した場合、飛行計画自体は削除せず `reportStatus` のみ更新する。
        失敗の分類軸は通報（`reportFlightPlan`）と同じく「リクエストがDIPSに届いたか」である。

        - `504`（届いたが結果が分からない。読み取りタイムアウト・送信後の切断など）: `reportStatus` は
          `WITHDRAWING` のまま保持する（状態確認手段は未整備。ADR-022の残課題）。
        - `503`（DIPSへ到達できない。接続不可・DIPS停止・クライアント証明書の検証失敗）、および
          DIPSが取り下げを受理しなかった業務エラー（同じ`503`だが `type` で区別する）:
          `reportStatus` は `REPORTED` に戻し、Operatorが本APIを再試行できるようにする。
        - `502`（完全な応答は届いたが解釈できない。200以外の応答、または200でも内容を解釈できない場合）:
          `reportStatus` は `REPORTED` に戻す。DIPS側で取り下げが成立している可能性は残るが、再試行できる
          ことを優先する。

        **デモ向け補足**: 10月デモでは模擬DIPSに飛行計画の削除APIが存在せず取り下げを実行できないため、
        上記のDIPS取り下げ（`WITHDRAWING`・`WITHDRAWN`への遷移）は行わない。`reportStatus`が`REPORTED`の
        飛行計画も論理削除だけを実行し、DIPS側の登録と`reportStatus`はそのまま残る。取り下げ専用の
        応答である`502`・`503`・`504`も返らない。

        削除できるのは`status`が`DRAFT`・`ACCEPTED`・`CANCELLED`の場合である。`ACTIVATED`（飛行中）と
        `ENDED`（終了）は削除できず409を返す。`ENDED`を削除できないのは、飛行終了では`reportStatus`が
        `REPORTED`のまま残るため、削除すると実際に行われた飛行のDIPS通報を取り下げることになるから
        である。`CANCELLED`は取り下げ済み（`WITHDRAWN`）または未通報でDIPSへの副作用がないため削除できる。

        `Idempotency-Key` ヘッダー指定での同一キーの再送（上記の再試行・ネットワークリトライ等）では
        削除を重複実行せず、最初の実行結果を 204 で返す。キーがないと再送のたびにDIPSへ二重の削除要求が
        飛ぶため定義する。同一キーを異なる飛行計画IDに対して再利用した場合は 422 を返す。
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "204":
          description: 削除成功
        "400":
          $ref: "../problem.yaml#/components/responses/BadRequest"
        "404":
          $ref: "../problem.yaml#/components/responses/NotFound"
        "409":
          description: 状態不正またはロックによるリソースアクセス不可による削除失敗。
          content:
            application/problem+json:
              schema:
                $ref: "../problem.yaml#/components/schemas/ProblemDetail"
              examples:
                invalidTransition:
                  summary: 削除できない状態（飛行中）の飛行計画を削除しようとした
                  value:
                    type: /problems/invalid-transition
                    title: Conflict
                    status: 409
                    detail: "flight plan cannot be deleted from status: ACTIVATED. flight_plan_id: 550e8400-e29b-41d4-a716-446655440000"
                lockConflict:
                  summary: 他の処理が対象の飛行計画をロック中でアクセスできない
                  value:
                    type: /problems/business-rule-violation
                    title: Conflict
                    status: 409
                    detail: "flight plan is locked by another operation: id=550e8400-e29b-41d4-a716-446655440000"
        "422":
          $ref: "../problem.yaml#/components/responses/ValidationError"
        "502":
          $ref: "#/components/responses/DipsInvalidResponse"
        "503":
          $ref: "#/components/responses/DipsCallFailed"
        "504":
          $ref: "#/components/responses/DipsTimeout"

    get:
      operationId: getFlightPlan
      summary: 飛行計画詳細取得
      tags: [FlightPlan]
      x-ix-changes: [{ kind: added, version: "0.1.0" }]
      description: 指定された飛行計画IDをもとに、飛行計画情報をデータベースから取得する。
      responses:
        "200":
          description: 取得成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlightPlanDetailResponse"
              examples:
                accepted:
                  summary: 本登録済み・DIPS通報済みの飛行計画（全項目が入力済み。エリアは経路(route)。空域制限・飛行計画同士とも競合チェック済みで競合なし）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: ACCEPTED
                    reportRequired: true
                    reportStatus: REPORTED
                    dipsFlightPlanId: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353423.001"
                    conflict:
                      airspaceRestrictions: []
                      flightPlanIds: []
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-07-28T10:30:00.000Z"
                    flightPlan:
                      name: "石狩川橋梁 点検飛行"
                      flightPurposes:
                        - code: INFRASTRUCTURE_INSPECTION
                      flightAirspace: [DENSELY_POPULATED_AREA]
                      flightType: [BEYOND_VISUAL_LINE_OF_SIGHT]
                      departurePoint: "札幌市北区 石狩川河川敷 離陸地点"
                      flightPeriod:
                        startTime: "2026-08-05T05:30:00.000Z"
                        plannedMaxTime: 60
                        plannedFlightTime: 30
                      flightSpec:
                        speed: 25
                        altitude: 80
                      flyRoute:
                        type: route
                        geometry:
                          type: LineString
                          coordinates:
                            - [141.3548784, 43.0604674]
                            - [141.3568784, 43.0624675]
                        bufferM: 50
                      destinationPoint: "札幌市東区 石狩川橋梁 北岸"
                      riskMitigation:
                        types: [ONSITE_CONTROL]
                        exceptionalConditionsMooring: false
                        assistantsNumber: 2
                      insuranceInformation:
                        insuranceCompany: "サンプル損害保険株式会社"
                        insuranceProduct: "ドローン保険 スタンダードプラン"
                        interPerson:
                          unlimited: true
                          amount: null
                        interObject:
                          unlimited: false
                          amount: 100000000
                        insuranceAbility: true
                      otherInformation: "橋梁の桁下を低速で飛行する区間あり。"
                      pilotInfo:
                        - pilotId: "a3f6c9e2-4b7d-48a1-9c6e-3d8f2b5a7c14"
                          pilotName: "山田 太郎"
                          aircraftIds:
                            - "9f4c7a2e-6b3d-48e1-a5c9-3d7f2b6a9c04"
                          aircraftNames:
                            - "DJI Matrice 350 RTK"
                      flightPermitApplicationInfo:
                        flightPermitApplicationNumber: "Q190100001"
                        permitDate: "2026-07-01"
                        startDate: "2026-07-01"
                        finishDate: "2027-06-30"
                        contactPermit:
                          name: "山田 太郎"
                          country: "001"
                          prefectures: "01"
                          address: "札幌市北区北7条西2丁目1-1"
                          telephoneCountry: "001"
                          telephone: "0111234567"
                          email: "sample@email.com"
                      email: "sample@email.com"
                airspaceConflict:
                  summary: 空域制限と競合している飛行計画（エリアは円形(circle)。DIPS通報済み）
                  value:
                    flightPlanId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
                    status: ACCEPTED
                    reportRequired: true
                    reportStatus: REPORTED
                    dipsFlightPlanId: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353426.001"
                    conflict:
                      airspaceRestrictions:
                        - airspaceRestrictionId: "0199f3a0-0015-7e4f-8a9b-0c1d2e3f4a15"
                          airspaceRestrictionType: AIRPORT_VICINITY
                      flightPlanIds:
                        - "O40SIFXUOCCAAXXTJXNZ.FP20250221050353425.001"
                    createdAt: "2026-07-30T01:00:00.000Z"
                    updatedAt: "2026-07-30T01:20:00.000Z"
                    flightPlan:
                      name: "丘珠周辺 空撮"
                      flightPurposes:
                        - code: AERIAL_PHOTOGRAPHY
                      flightAirspace: [DENSELY_POPULATED_AREA, AROUND_AIRPORT]
                      flightType: []
                      departurePoint: "札幌市東区 丘珠公園 駐車場"
                      flightPeriod:
                        startTime: "2026-08-05T05:00:00.000Z"
                        plannedMaxTime: 30
                        plannedFlightTime: 20
                      flightSpec:
                        speed: 15
                        altitude: 50
                      flyRoute:
                        type: circle
                        geometry:
                          type: Point
                          coordinates: [141.3548784, 43.0604674]
                        radiusM: 50
                      destinationPoint: "札幌市東区 丘珠公園 駐車場"
                      riskMitigation:
                        types: [NO_ENTRY_CONTROL]
                        exceptionalConditionsMooring: false
                        assistantsNumber: 1
                      pilotInfo:
                        - pilotId: "a3f6c9e2-4b7d-48a1-9c6e-3d8f2b5a7c14"
                          pilotName: "山田 太郎"
                          aircraftIds:
                            - "8c3f61a4-9d2e-4b71-8f5c-3a6e9b2d7c14"
                          aircraftNames:
                            - "DJI Mavic 3 Enterprise"
                      email: "sample@email.com"
                draft:
                  summary: 一時保存中の飛行計画（`name`以外は未入力のためキー自体を省略）
                  value:
                    flightPlanId: "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    status: DRAFT
                    reportRequired: false
                    reportStatus: UNREPORTED
                    dipsFlightPlanId: null
                    conflict:
                      airspaceRestrictions: []
                      flightPlanIds: []
                    createdAt: "2026-08-01T02:00:00.000Z"
                    updatedAt: "2026-08-01T02:00:00.000Z"
                    flightPlan:
                      name: "石狩川橋梁 点検飛行（下書き）"
        "400":
          $ref: "../problem.yaml#/components/responses/BadRequest"
        "404":
          $ref: "../problem.yaml#/components/responses/NotFound"

  /api/v1/fp/flight-plans/{flightPlanId}/registration:
    parameters:
      - $ref: "#/components/parameters/FlightPlanId"

    post:
      operationId: registerFlightPlan
      summary: 飛行計画本登録
      tags: [FlightPlan]
      x-ix-changes: [{ kind: added, version: "0.1.0" }]
      description: |
        `DRAFT`状態の飛行計画を対象に、DIPS APIの必須項目が揃っていることを検証したうえで本登録する
        （`status` を `ACCEPTED` に更新）。必須項目は `name`/`flightPurposes`/
        `departurePoint`/`flightPeriod`/`flightSpec`/`flyRoute`/`destinationPoint`/`riskMitigation`/
        `pilotInfo`/`email`（`flightPeriod`・`flightSpec`・`riskMitigation`はオブジェクト内の全項目
        （`riskMitigation`は`assistantsNumber`を含む）が必須、`flightPurposes`は各要素の`code`のみ必須で
        `note`は`code`の値に応じた条件付き必須（詳細は`FlightPurposeItem`参照）。この必須項目チェックは
        `DRAFT`での部分入力を許容するためOASの`required`では表現せず、本APIのビジネスルールとしてのみ行う。
        「飛行計画仮登録」または「飛行計画更新」で事前に保存した内容が対象）。
        `flightPermitApplicationInfo`は「飛行許可・承認申請」が必要な場合にのみ指定する任意項目のため対象外。

        飛行計画エリアについて空域制限との競合有無を確認し、競合が発生していれば `conflict` に競合対象の
        空域制限IDを設定して返却するとともに、Operator宛にエリア競合発生のメール通知を行う
        （この場合も本登録自体は成功する）。

        `DRAFT`以外の状態の飛行計画を対象に呼び出した場合は 409 を返す。本登録後（`ACCEPTED`以降）の
        内容変更は本APIではなく「飛行計画更新」で行う（`ACCEPTED`以降は`DRAFT`に戻らないため、本APIを
        再度呼び出す必要はない）。

        `Idempotency-Key` ヘッダー指定での同一キーの再送（ネットワークリトライ等）では
        重複して本登録処理を行わず、最初の実行結果を 200 で返す。
        同一キーを異なる飛行計画IDに対して再利用した場合は 422 を返す。
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: 本登録成功（使用済み Idempotency-Key で対象の飛行計画IDが一致する再送の場合は最初の実行結果）
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlightPlanSaveResponse"
              examples:
                success:
                  summary: 競合なしで本登録成功（空域制限との競合なし）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: ACCEPTED
                    reportRequired: true
                    reportStatus: UNREPORTED
                    dipsFlightPlanId: null
                    conflict:
                      airspaceRestrictions: []
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-07-28T10:30:00.000Z"
                airspaceConflict:
                  summary: 空域制限との競合が発生した状態で本登録成功
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: ACCEPTED
                    reportRequired: true
                    reportStatus: UNREPORTED
                    dipsFlightPlanId: null
                    conflict:
                      airspaceRestrictions:
                        - airspaceRestrictionId: "0199f3a0-0015-7e4f-8a9b-0c1d2e3f4a15"
                          airspaceRestrictionType: AIRPORT_VICINITY
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-07-28T10:30:00.000Z"
        "400":
          $ref: "../problem.yaml#/components/responses/BadRequest"
        "404":
          $ref: "../problem.yaml#/components/responses/NotFound"
        "409":
          description: 対象の飛行計画が`DRAFT`以外の状態、またはロックによるリソースアクセス不可による本登録失敗。
          content:
            application/problem+json:
              schema:
                $ref: "../problem.yaml#/components/schemas/ProblemDetail"
              examples:
                invalidTransition:
                  summary: 対象の飛行計画が`DRAFT`以外の状態（本登録済み）
                  value:
                    type: /problems/invalid-transition
                    title: Conflict
                    status: 409
                    detail: "flight plan cannot be registered from status: ACCEPTED"
                lockConflict:
                  summary: 他の処理が対象の飛行計画をロック中でアクセスできない
                  value:
                    type: /problems/business-rule-violation
                    title: Conflict
                    status: 409
                    detail: "flight plan is locked by another operation: id=550e8400-e29b-41d4-a716-446655440000"
        "422":
          $ref: "../problem.yaml#/components/responses/ValidationError"

  #
  # 飛行計画周辺データ更新
  #
  /api/v1/fp/flight-plans/{flightPlanId}/nearby-data-refresh:
    parameters:
      - $ref: "#/components/parameters/FlightPlanId"

    post:
      operationId: refreshNearbyFlightPlans
      summary: 飛行計画周辺データ更新
      tags: [FlightPlan]
      x-ix-changes:
        - { kind: added, version: "0.1.0" }
        - { kind: changed, version: "0.10.0", note: "パスを`nearby-refresh`から`nearby-data-refresh`へ変更した（`nearby`は形容詞であり、リソース名をパスに置く原則に合わないため）" }
      description: |
        指定された飛行計画IDをもとに、その飛行計画の周辺エリア・飛行時間帯に該当する他の飛行計画
        情報を、DIPS API「飛行計画情報参照API」（範囲指定検索）から取得し、データベースに保存する
        （データベース上に同一の飛行計画IDが存在する場合は上書きする）。

        検索範囲・時間帯は、対象の飛行計画自身の`flyRoute`（飛行経路・エリア）と`flightPeriod`
        （飛行開始日時・所要時間）から算出する。
        検索範囲は`flyRoute`が表す飛行領域から100mまでの範囲（バッファ）を「周辺」として扱う。
        `flyRoute.type=route`の飛行領域は経路そのものではなく経路に`bufferM`を適用した後の領域であり、
        検索範囲はその領域へさらに100mを加えたものになる。経路からの距離で見ると`bufferM`＋100mの
        範囲ではなく、経路端・折れ点の角ではそれより外側まで含む（角を丸めずに広げるため）。
        検索時間帯は飛行時間帯そのものではなく飛行日（JST）の0時〜24時とし、飛行が日付をまたぐ場合は
        翌日も対象とする。DIPS側が1回の検索で24時間までしか指定できないため、その場合はDIPS APIを
        暦日ごとに2回呼び出す。

        取得した他の飛行計画の詳細情報（連絡先・飛行経路等）は本APIのレスポンスに含めない。
        他の飛行計画情報の閲覧が必要な場合は、GeoSpatialが提供するエリア範囲指定APIから、
        本APIが保存したデータベースを参照して取得する（本APIの対象外）。

        実行できるのは`status`が`ACCEPTED`または`ACTIVATED`の飛行計画のみである。`DRAFT`は
        収集結果を紐づけるリビジョンが存在せず、`ENDED`・`CANCELLED`は周辺状況を最新化する
        必要がないため、いずれも409を返す。DIPS API呼び出しはトランザクションの外で行うため、
        保存の直前にも同じ判定を行う。呼び出しの間に対象の飛行計画が更新・終了・取消された場合は
        収集結果を保存せず409を返す（新しい内容に対して本APIを実行し直すこと）。

        本APIは`Idempotency-Key`を受け付けない。収集結果はDIPS側の飛行計画IDをキーにした
        上書きで保存され、再実行しても同じ最新状態に収束するためである。

      responses:
        "204":
          description: 更新成功
        "400":
          $ref: "../problem.yaml#/components/responses/BadRequest"
        "404":
          $ref: "../problem.yaml#/components/responses/NotFound"
        "409":
          description: |
            対象の飛行計画が`ACCEPTED`・`ACTIVATED`以外の状態のため収集できない、または
            DIPS API呼び出しの間に対象の飛行計画が更新・終了・取消されたため収集結果を保存できない。
          content:
            application/problem+json:
              schema:
                $ref: "../problem.yaml#/components/schemas/ProblemDetail"
              examples:
                invalidTransition:
                  summary: 対象の飛行計画が収集対象外の状態（飛行終了済み）
                  value:
                    type: /problems/invalid-transition
                    title: Conflict
                    status: 409
                    detail: "nearby flight plans cannot be refreshed from status: ENDED"
                revisionChanged:
                  summary: DIPS API呼び出しの間に対象の飛行計画が更新された
                  value:
                    type: /problems/invalid-transition
                    title: Conflict
                    status: 409
                    detail: "the current revision changed while collecting nearby flight plans"
        "502":
          $ref: "#/components/responses/DipsInvalidResponse"
        "503":
          $ref: "#/components/responses/DipsCallFailed"
        "504":
          $ref: "#/components/responses/DipsTimeout"

  #
  # DIPS通報
  #
  /api/v1/fp/flight-plans/{flightPlanId}/report:
    parameters:
      - $ref: "#/components/parameters/FlightPlanId"

    post:
      operationId: reportFlightPlan
      summary: 飛行計画通報
      tags: [FlightPlan]
      x-ix-changes: [{ kind: added, version: "0.1.0" }]
      description: |
        Operatorが指定した飛行計画IDの飛行計画情報をDIPS API(飛行計画通報受付)に設定して実行する。
        DIPS API呼び出し前に `reportStatus` を `REPORTING` に更新する。

        DIPS API(飛行計画通報受付)の実行結果（受理・競合いずれの場合でも）は `status` には影響しない。
        `reportStatus` は競合有無にかかわらず `REPORTED` に更新される。初回の通報成功時、DIPS APIの
        応答に含まれる受付番号を `dipsFlightPlanId` として保存する（以降は再通報しても値は変わらない）。

        DIPS APIの応答に他の飛行計画との競合が含まれる場合は、対象の飛行計画IDを`conflict.flightPlanIds`
        に設定する（UTM側では他Operatorの飛行計画を横断的に判定できず、DIPS通報でのみ判明するため）。
        `conflict.airspaceRestrictions`（空域制限との競合）は本APIでは再判定しない（直近の
        `updateFlightPlan`/`registerFlightPlan`時点の判定結果を保持する）。

        通報できるのは `status=ACCEPTED` で、`reportStatus` が `UNREPORTED`（未通報）または
        `REPORTED`（通報済み）の飛行計画である。`status` が `ACCEPTED` 以外、または `reportStatus` が
        `REPORTING`（通報実行中）・`WITHDRAWING`・`WITHDRAWN`（取り下げ済み）の場合は
        409（`/problems/invalid-transition`）を返す。

        `reportStatus=REPORTED` の飛行計画は、内容を更新して `UNREPORTED` に戻さなくても再通報できる
        （DIPS側の通報時刻が最新化される）。再通報は初回の受付番号を送る更新扱いになるため、
        `dipsFlightPlanId` は初回の値のまま変わらない。

        `reportRequired=false`（通報義務なし）の飛行計画も通報できる。同フラグが表すのは通報が必須か
        どうかであり、通報の可否ではない。

        DIPS API呼び出しが失敗した場合、応答コードにより `reportStatus` の扱いを分ける。失敗の分類軸は
        「リクエストがDIPSに届いたか」である。

        - `504`（届いたが結果が分からない。読み取りタイムアウト・送信後の切断など）: `reportStatus` は
          `REPORTING` のまま保持する（状態確認手段は未整備。ADR-022の残課題）。
        - `503`（DIPSへ到達できない。接続不可・DIPS停止・クライアント証明書の検証失敗）、および
          DIPSが通報を受理しなかった業務エラー（同じ`503`だが `type` で区別する）: `reportStatus` を
          通報前の状態に戻し、Operatorが本APIを再試行できるようにする。DIPSの認証はFlightPlanning内部で
          完結するため、401/403は返さない。
        - `502`（完全な応答は届いたが解釈できない。200以外の応答、または200でも内容を解釈できない場合）:
          `reportStatus` を通報前の状態に戻す。DIPS側に登録が成立している可能性は残るが、再通報できる
          ことを優先する。

        「通報前の状態」とは `UNREPORTED` からの通報なら `UNREPORTED`、再通報なら `REPORTED` である
        （再通報の失敗で `UNREPORTED` にすると、DIPS側に前回の通報が残っている実態と食い違うため）。

        `Idempotency-Key` ヘッダー指定での同一キーの再送（ネットワークリトライ等）では、DIPSへの再通報は行わず、
        最初の実行結果を 200 で返す。同一キーを異なる飛行計画IDに対して再利用した場合は 422 を返す。
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: 通報成功（使用済み Idempotency-Key で対象の飛行計画IDが一致する再送の場合は最初の実行結果）
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlightPlanResponse"
              examples:
                success:
                  summary: 競合なしで通報成功（DIPSの受付番号が`dipsFlightPlanId`に設定される）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: ACCEPTED
                    reportRequired: true
                    reportStatus: REPORTED
                    dipsFlightPlanId: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353423.001"
                    conflict:
                      airspaceRestrictions: []
                      flightPlanIds: []
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-07-28T10:45:00.000Z"
                flightPlanConflict:
                  summary: 他の飛行計画との競合ありで通報完了（競合の有無にかかわらず`reportStatus`は`REPORTED`、`status`は変化しない）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: ACCEPTED
                    reportRequired: true
                    reportStatus: REPORTED
                    dipsFlightPlanId: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353423.001"
                    conflict:
                      airspaceRestrictions: []
                      flightPlanIds:
                        - "O40SIFXUOCCAAXXTJXNZ.FP20250221050353424.001"
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-07-28T10:45:00.000Z"
        "400":
          $ref: "../problem.yaml#/components/responses/BadRequest"
        "404":
          $ref: "../problem.yaml#/components/responses/NotFound"
        "409":
          $ref: "../problem.yaml#/components/responses/LockConflict"
        "422":
          $ref: "../problem.yaml#/components/responses/ValidationError"
        "502":
          $ref: "#/components/responses/DipsInvalidResponse"
        "503":
          $ref: "#/components/responses/DipsCallFailed"
        "504":
          $ref: "#/components/responses/DipsTimeout"

  #
  # 飛行実施
  #
  /api/v1/fp/flight-plans/{flightPlanId}/start:
    parameters:
      - $ref: "#/components/parameters/FlightPlanId"

    post:
      operationId: activateFlightPlan
      summary: 飛行計画開始
      tags: [FlightPlan]
      x-ix-changes: [{ kind: added, version: "0.1.0" }]
      description: |
        指定された飛行計画IDのステータスを飛行中（`ACTIVATED`）に更新する。

        飛行開始できるのは`status=ACCEPTED`かつ「通報義務がない（`reportRequired=false`）」または
        「通報済み（`reportStatus=REPORTED`）」の場合のみである。通報義務があり未通報の場合は
        409（`type`は状態不正）を返す。この条件はDB制約では担保せずアプリケーション層で拒否する。

        `Idempotency-Key` ヘッダー指定での同一キーの再送（ネットワークリトライ等）では状態更新を
        重複実行せず、最初の実行結果を 200 で返す。同一キーを異なる飛行計画IDに対して再利用した場合は
        422 を返す。
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: 更新成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlightPlanStatusResponse"
              examples:
                success:
                  summary: 飛行開始（`ACCEPTED` → `ACTIVATED`。`reportStatus`は変化しない）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: ACTIVATED
                    reportRequired: true
                    reportStatus: REPORTED
                    dipsFlightPlanId: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353423.001"
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-08-05T05:30:00.000Z"
        "400":
          $ref: "../problem.yaml#/components/responses/BadRequest"
        "404":
          $ref: "../problem.yaml#/components/responses/NotFound"
        "409":
          $ref: "../problem.yaml#/components/responses/LockConflict"
        "422":
          $ref: "../problem.yaml#/components/responses/ValidationError"

  /api/v1/fp/flight-plans/{flightPlanId}/end:
    parameters:
      - $ref: "#/components/parameters/FlightPlanId"

    post:
      operationId: completeFlightPlan
      summary: 飛行計画終了
      tags: [FlightPlan]
      x-ix-changes: [{ kind: added, version: "0.1.0" }]
      description: |
        指定された飛行計画IDのステータスを終了（`ENDED`）に更新する。

        `Idempotency-Key` ヘッダー指定での同一キーの再送（ネットワークリトライ等）では状態更新を
        重複実行せず、最初の実行結果を 200 で返す。同一キーを異なる飛行計画IDに対して再利用した場合は
        422 を返す。
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: 更新成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlightPlanStatusResponse"
              examples:
                success:
                  summary: 飛行終了（`ACTIVATED` → `ENDED`。`reportStatus`は変化しない）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: ENDED
                    reportRequired: true
                    reportStatus: REPORTED
                    dipsFlightPlanId: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353423.001"
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-08-05T06:05:00.000Z"
        "400":
          $ref: "../problem.yaml#/components/responses/BadRequest"
        "404":
          $ref: "../problem.yaml#/components/responses/NotFound"
        "409":
          $ref: "../problem.yaml#/components/responses/LockConflict"
        "422":
          $ref: "../problem.yaml#/components/responses/ValidationError"

  /api/v1/fp/flight-plans/{flightPlanId}/cancel:
    parameters:
      - $ref: "#/components/parameters/FlightPlanId"

    post:
      operationId: cancelFlightPlan
      summary: 飛行計画キャンセル
      tags: [FlightPlan]
      x-ix-changes:
        - { kind: added, version: "0.1.0" }
        - { kind: changed, version: "0.10.0", note: "DIPS呼び出し失敗時の`502`を宣言に追加した" }
      description: |
        指定された飛行計画IDのステータスを中止（`CANCELLED`）に更新する。
        対象の飛行計画がDIPS通報済み（`reportStatus` が `REPORTED`）の場合は、ステータス更新前にあわせて
        DIPS API(飛行計画削除)を実行する。DIPS API呼び出し前に `reportStatus` を `WITHDRAWING` に更新し、
        DIPS側に登録されている飛行計画を削除（取り下げ）できたら、飛行計画のステータスを `CANCELLED` に
        更新し `reportStatus` を `WITHDRAWN` に更新する。

        DIPS API(飛行計画削除)の呼び出しが失敗した場合、ステータスは `CANCELLED` へ更新せず（キャンセル全体が成功した場合のみ確定
        する）、`reportStatus` のみ更新する。失敗の分類軸は通報（`reportFlightPlan`）と同じく
        「リクエストがDIPSに届いたか」である。

        - `504`（届いたが結果が分からない。読み取りタイムアウト・送信後の切断など）: `reportStatus` は
          `WITHDRAWING` のまま保持する（状態確認手段は未整備。ADR-022の残課題）。
        - `503`（DIPSへ到達できない。接続不可・DIPS停止・クライアント証明書の検証失敗）、および
          DIPSが取り下げを受理しなかった業務エラー（同じ`503`だが `type` で区別する）:
          `reportStatus` は `REPORTED` に戻し、Operatorが本APIを再試行できるようにする。
        - `502`（完全な応答は届いたが解釈できない。200以外の応答、または200でも内容を解釈できない場合）:
          `reportStatus` は `REPORTED` に戻す。DIPS側で取り下げが成立している可能性は残るが、再試行できる
          ことを優先する。

        `Idempotency-Key` ヘッダー指定での同一キーの再送（ネットワークリトライ等）では状態更新・DIPSへの
        削除要求を重複実行せず、最初の実行結果を 200 で返す。同一キーを異なる飛行計画IDに対して再利用した
        場合は 422 を返す。

        **デモ向け補足**: 10月デモでは模擬DIPSに飛行計画の削除APIが存在せず取り下げを実行できないため、
        上記のDIPS取り下げ（`WITHDRAWING`・`WITHDRAWN`への遷移）は行わない。`reportStatus`が`REPORTED`の
        飛行計画も論理削除だけを実行し、DIPS側の登録と`reportStatus`はそのまま残る。取り下げ専用の
        応答である`502`・`503`・`504`も返らない。
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: 更新成功
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FlightPlanStatusResponse"
              examples:
                withdrawn:
                  summary: DIPS通報済みの飛行計画をキャンセル（DIPS側も削除され`reportStatus`が`WITHDRAWN`になる）
                  value:
                    flightPlanId: "550e8400-e29b-41d4-a716-446655440000"
                    status: CANCELLED
                    reportRequired: true
                    reportStatus: WITHDRAWN
                    dipsFlightPlanId: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353423.001"
                    createdAt: "2026-07-28T09:00:00.000Z"
                    updatedAt: "2026-08-04T02:10:00.000Z"
                notReported:
                  summary: 未通報の飛行計画をキャンセル（DIPS API(飛行計画削除)は呼ばれず`reportStatus`は`UNREPORTED`のまま）
                  value:
                    flightPlanId: "3fa85f64-5717-4562-b3fc-2c963f66afa6"
                    status: CANCELLED
                    reportRequired: false
                    reportStatus: UNREPORTED
                    dipsFlightPlanId: null
                    createdAt: "2026-08-01T02:00:00.000Z"
                    updatedAt: "2026-08-04T02:10:00.000Z"
        "400":
          $ref: "../problem.yaml#/components/responses/BadRequest"
        "404":
          $ref: "../problem.yaml#/components/responses/NotFound"
        "409":
          $ref: "../problem.yaml#/components/responses/LockConflict"
        "422":
          $ref: "../problem.yaml#/components/responses/ValidationError"
        "502":
          $ref: "#/components/responses/DipsInvalidResponse"
        "503":
          $ref: "#/components/responses/DipsCallFailed"
        "504":
          $ref: "#/components/responses/DipsTimeout"

components:
  parameters:
    FlightPlanId:
      name: flightPlanId
      in: path
      required: true
      description: 飛行計画ID（UTM内で発行されるID。DIPS側で発行されるIDではない点に注意。詳細はスキーマ`FlightPlanId`の説明を参照）
      schema:
        $ref: "#/components/schemas/FlightPlanId"
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      description: |
        リトライ時の重複作成を防ぐクライアント生成の冪等性キー（UUID）。省略した場合はサーバー側で
        キーを生成する（その場合、リクエストのたびに異なるキーが割り当てられるため、同一操作の
        再送であっても重複排除は行われない）。
      schema:
        type: string
        format: uuid
  responses:
    # ボディ・パスパラメータのパース失敗(400)は横断的関心事のため、audience固有の定義は持たず
    # ../problem.yaml#/components/responses/BadRequest を各operationから直接$refする。
    # 本ファイルにはDIPS連携固有のレスポンスのみを定義する。
    #
    # DIPS連携の失敗は「リクエストがDIPSに届いたか」で3つに分ける（502/503/504）。
    # 各 type は ProblemTypes.java の定数と対応する。
    DipsInvalidResponse:
      description: |
        DIPSから完全な応答を受け取ったが、解釈できなかった。200以外の応答、または200でも
        内容を解釈できない場合（型の不一致・未知のコード値・想定外の日時形式など）。
        同じ応答が返る限り、再送しても結果は変わらない。
      content:
        application/problem+json:
          schema:
            $ref: "../problem.yaml#/components/schemas/ProblemDetail"
          examples:
            dipsInvalidResponse:
              summary: DIPSの応答を解釈できなかった
              value:
                type: /problems/dips-invalid-response
                title: Bad Gateway
                status: 502
                detail: could not interpret the response from DIPS API
    DipsCallFailed:
      description: |
        DIPSへ到達できなかった（接続不可・DIPS停止・クライアント証明書の検証失敗）、または
        DIPSが要求を受理しなかった（業務エラー）。いずれもリクエストの成否は確定している。
        `type`で区別する: `/problems/dips-call-failed`（到達できない） /
        `/problems/dips-report-rejected`（到達したがDIPSが受理しなかった）。
      content:
        application/problem+json:
          schema:
            $ref: "../problem.yaml#/components/schemas/ProblemDetail"
          examples:
            dipsCallFailed:
              summary: DIPSへ到達できなかった（接続不可・DIPS停止・証明書の検証失敗）
              value:
                type: /problems/dips-call-failed
                title: Service Unavailable
                status: 503
                detail: could not reach DIPS API
            dipsReportRejected:
              summary: DIPSへ到達したが、通報が受理されなかった（内容を修正して再通報する）
              value:
                type: /problems/dips-report-rejected
                title: Service Unavailable
                status: 503
                detail: "DIPS API returned an error: flight plan report was rejected"
    DipsTimeout:
      description: |
        DIPSへリクエストは届いたが、結果が分からない（読み取りタイムアウト・送信後の切断など）。
        DIPS側で処理が完了している可能性があるため、そのままの再送はしない。
      content:
        application/problem+json:
          schema:
            $ref: "../problem.yaml#/components/schemas/ProblemDetail"
          examples:
            dipsTimeout:
              summary: DIPS呼び出しがタイムアウトし、通報の成否が確定できない
              value:
                type: /problems/dips-timeout
                title: Gateway Timeout
                status: 504
                detail: DIPS API call timed out; the result of the operation is unknown

  schemas:
    #
    # 共通
    #
    FlyRouteInput:
      description: |
        飛行の経路・範囲を表す図形情報。`type` により circle（円形）、polygon（多角形）、
        route（経路）のいずれかの構造を取る。`geometry` は標準のGeoJSON Geometryオブジェクト
        （座標は GeoJSON 標準の [経度, 緯度] 順。緯度経度の順ではない点に注意）。

        **空域制限・他の飛行計画との競合判定は本ジオメトリのみを用いた2次元（水平方向）判定とする**。
        `flightSpec.altitude`（高度）は競合判定に使用しない。空域制限側が高度範囲を持つ場合でも、
        本APIの範囲では3次元判定は行わない（将来3次元判定を導入する場合は別途スキーマ・API設計の
        見直しが必要）。

        リクエスト専用スキーマ（`FlightPlanCreateRequest`・`FlightPlanUpdateRequest`が参照）。レスポンス
        （`FlightPlanDetailResponse.flightPlan`）には`FlyRoute`を用いる。
        将来的な項目追加や制約緩和による破壊を回避するため、リクエスト型とレスポンス型を分けて定義することで拡張性を持たせた設計とする。
      # 各サブスキーマの`type`に`enum`を書いてはならない（`FlyRoute`側も同様）。openapi-generatorは
      # discriminatorのプロパティに`enum`があるとサブタイプ側に入れ子の`TypeEnum`を生成する一方、
      # oneOfの親interfaceには`String getType()`を宣言するため、両者が非互換でコンパイルエラーになる。
      # 指定可能な値は下記`discriminator.mapping`（機械可読）と各サブスキーマの`type`のdescriptionで示す。
      # 値の妥当性は生成コードの`@JsonSubTypes`が実行時に強制する（未知の値はデシリアライズ失敗）。
      # 同じ書き方の先行例: geospatial.yamlの`Geometry`・`AirspaceRestrictionGeometry`。
      oneOf:
        - $ref: "#/components/schemas/FlyRouteCircleInput"
        - $ref: "#/components/schemas/FlyRoutePolygonInput"
        - $ref: "#/components/schemas/FlyRouteRouteInput"
      discriminator:
        propertyName: type
        mapping:
          circle: "#/components/schemas/FlyRouteCircleInput"
          polygon: "#/components/schemas/FlyRoutePolygonInput"
          route: "#/components/schemas/FlyRouteRouteInput"

    FlyRouteCircleInput:
      type: object
      description: 円形エリア。中心点（geometry）から半径（radiusM）分の範囲を飛行範囲とする。
      required: [type, geometry, radiusM]
      properties:
        type:
          type: string
          example: circle
          description: |
            エリア種別。本スキーマでは`circle`固定。指定可能な値は`circle`（円形）・`polygon`（多角形）・
            `route`（経路）で、値と構造の対応は`FlyRouteInput`/`FlyRoute`の`discriminator.mapping`が定める。
        geometry:
          $ref: "#/components/schemas/GeoJsonPoint"
          description: 中心点
        radiusM:
          type: number
          format: double
          minimum: 0
          exclusiveMinimum: true
          description: 半径（メートル）。半径が0以下のエリアは意味を持たないため0より大きい値のみ許容する。
          example: 50

    FlyRoutePolygonInput:
      type: object
      description: 多角形エリア。頂点列（geometry）で囲まれた範囲を飛行範囲とする。
      required: [type, geometry]
      properties:
        type:
          type: string
          example: polygon
          description: |
            エリア種別。本スキーマでは`polygon`固定。指定可能な値は`circle`（円形）・`polygon`（多角形）・
            `route`（経路）で、値と構造の対応は`FlyRouteInput`/`FlyRoute`の`discriminator.mapping`が定める。
        geometry:
          $ref: "#/components/schemas/GeoJsonPolygon"
          description: |
            多角形。穴（内環）は非対応のためLinearRingは1つのみ。終点は始点と同座標を
            自動付与するため、頂点として重複指定する必要はない。

    FlyRouteRouteInput:
      type: object
      x-ix-changes: [{ kind: changed, version: "0.6.0", note: "判別子の値を`path`から`route`へ改名し、スキーマ名を`FlyRoutePathInput`から変更（UTM内部の`ROUTE`と表記を揃えるため）" }]
      description: |
        経路（Route）エリア。頂点列（geometry）とバッファ（bufferM）を指定し、頂点を結ぶ経路
        （LineString）全体に沿って水平方向にバッファした範囲を飛行範囲とする。PostGISでは
        `ST_Buffer(geometry::geography, bufferM, 'quad_segs=1 endcap=square join=mitre')::geometry`に
        相当する。SRID 4326のまま`ST_Buffer(geometry, bufferM)`とすると距離の単位が度になり
        メートル指定にならないため、`geography`へキャストして計算する。バッファ生成パラメータを
        固定するのは、DIPS通報時のPolygon変換を36点以内に収めるためである。
        垂直方向・時間方向のバッファは持たない。
        頂点（geometry）に高度は持たせない。
      required: [type, geometry, bufferM]
      properties:
        type:
          type: string
          example: route
          description: |
            エリア種別。本スキーマでは`route`固定。指定可能な値は`circle`（円形）・`polygon`（多角形）・
            `route`（経路）で、値と構造の対応は`FlyRouteInput`/`FlyRoute`の`discriminator.mapping`が定める。
        geometry:
          $ref: "#/components/schemas/GeoJsonLineString"
          description: 経路（頂点2点以上、高度は含まない）
        bufferM:
          type: number
          format: double
          minimum: 0
          exclusiveMinimum: true
          maximum: 100
          description: |
            バッファ（メートル、水平方向）。経路（頂点を結ぶ線分全体）に沿って水平方向に拡張した
            範囲を飛行範囲とする（頂点ごとの円の連結ではない。詳細は本スキーマの説明を参照）。
            バッファが0以下では経路がエリアを持たないため0より大きい値のみ許容する。
            DIPS飛行計画登録で指定可能範囲より、最大値は100mとする。
          example: 50

    #
    # GeoJSON Geometry（共通部品）
    #
    GeoJsonPosition:
      type: array
      description: |
        GeoJSON標準の座標順（[経度, 緯度]）。緯度・経度の順ではない点に注意。
        緯度・経度の最小解像度は小数点以下7桁（約11mm。ASTM F3411-22aに整合）とする。

        座標系はWGS84（EPSG:4326）固定とする（GeoJSON標準・RFC 7946 §4の既定であり、本APIもこれに従う。他の座標系は非対応）。

        OAS 3.0のJSON SchemaはPositionのような配列に対し要素位置ごとの異なるmin/max
        （tuple validation）を表現できないため、`items`には経度・緯度のうち広い方の範囲
        （経度の±180）を設定する。緯度固有のより狭い範囲（±90）はこのスキーマだけでは
        強制されないため、Bean Validationのカスタム検証で別途担保すること。
      minItems: 2
      maxItems: 2
      items:
        type: number
        format: double
        multipleOf: 0.0000001
        minimum: -180
        maximum: 180
      example: [141.3548784, 43.0604674]

    GeoJsonPoint:
      type: object
      required: [type, coordinates]
      properties:
        type:
          type: string
          enum: [Point]
          example: Point
        coordinates:
          $ref: "#/components/schemas/GeoJsonPosition"

    GeoJsonLineString:
      type: object
      required: [type, coordinates]
      properties:
        type:
          type: string
          enum: [LineString]
          example: LineString
        coordinates:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxItems: 16を新設（バッファ適用後のPolygonがDIPSの36点以内に収まる上限）。従来通っていた17点以上の経路は422になる" }]
          type: array
          description: |
            経路を構成する頂点（2点以上）の配列。上限はDIPS通報時のPolygon変換が
            DIPSの上限（36点）に収まる値とする。バッファ生成パラメータを
            `quad_segs=1 endcap=square join=mitre`に固定した場合、変換後の概算点数は
            経路の頂点数Nに対して2N+4点（両側N点ずつ＋両端の角4点）となるため、
            36点以内に収めるにはN≦16となる。ただし`join=mitre`は`mitre_limit`を超える
            鋭角で面取りに切り替わり点数が増えるため、本上限は入力側のガードであり
            上限の保証ではない。通報前に実測し、36点を超える場合は外接円で`Circle`として
            通報する（`FlyRouteRouteInput`の説明を参照）。
          minItems: 2
          maxItems: 16
          items:
            $ref: "#/components/schemas/GeoJsonPosition"
          example:
            - [141.3548784, 43.0604674]
            - [141.3568784, 43.0624675]

    GeoJsonPolygon:
      type: object
      description: |
        自己交差する多角形（PostGISの`ST_IsValid`が偽になるもの）は無効なリクエストとして扱い、
        422を返す（自動補正（`ST_MakeValid`等での修復）は行わない。入力の意図を保つため）。
        この検証はGeoJSONの構造（頂点数・座標範囲）だけでは表現できない幾何的な妥当性検証のため、
        本スキーマでは強制できず、バックエンドのドメイン層（Parse, don't validateの境界）で行う。
      required: [type, coordinates]
      properties:
        type:
          type: string
          enum: [Polygon]
          example: Polygon
        coordinates:
          type: array
          description: LinearRingの配列（穴は非対応のため必ず1要素）
          minItems: 1
          maxItems: 1
          items:
            x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxItemsを1000から36へ縮小（DIPSが受け付けるPolygonの構成点数に合わせた）。従来通っていた37点以上の多角形は422になる" }]
            type: array
            description: |
              多角形を構成する頂点（3点以上）の配列。上限はDIPSの飛行計画情報登録APIが受け付ける
              Polygonの構成点数（36点）に合わせる。本スキーマは終点に始点と同じ座標を含めないため
              （`FlyRoutePolygonInput.geometry`の説明を参照）、DIPSの36点とそのまま同値になる。
              **本制約は入れ子配列に対する宣言であり、生成コードのBean Validationには出力されない**
              （最上位配列の`GeoJsonLineString.coordinates`は`@Size`が出るが、`coordinates`の要素である
              本配列には出ない）。上記の`ST_IsValid`と同様、バックエンドのドメイン層で検証する。
            minItems: 3
            maxItems: 36
            items:
              $ref: "#/components/schemas/GeoJsonPosition"
          example:
            - - [141.3548784, 43.0604674]
              - [141.3568784, 43.0604674]
              - [141.3558784, 43.0624675]

    TimestampMinute:
      type: string
      format: date-time
      nullable: true
      description: |
        日時（分単位で扱われる項目向け）。`Timestamp`と同じ形式（秒・ミリ秒・UTC固定の`Z`を
        含めて指定する）で受け付けるが、分単位でのみ扱われるフィールド。
        DIPSへの通報に使用される場合はJST（UTC+9）に変換したうえで`YYYYMMDD HHMM`形式に変換する。
        `pattern`を付けていない理由は`Timestamp`と同じ（`OffsetDateTime`にはjakarta validationの
        `@Pattern`が適用できないため）。
      example: "2026-08-05T05:30:45.000Z"

    CalendarDate:
      type: string
      format: date
      description: |
        日付（暦日）。ISO 8601 / RFC 3339のfull-date形式（`YYYY-MM-DD`）。
        「時点」ではなく暦日を表すため、時刻・タイムゾーンオフセットは持たない
        （`Timestamp`のように時刻を含めて受け付けたうえで時刻部分を読み捨てる方式は取らない。
        時刻を含めるとクライアントのタイムゾーンによって暦日の解釈が変わり、日付をまたぐ
        ずれが生じるため）。
        DIPSへの通報に使用される場合は区切り文字を除いた`YYYYMMDD`形式に変換する
        （タイムゾーン変換は不要）。
      example: "2026-08-05"

    FlightPlanId:
      type: string
      format: uuid
      description: |
        飛行計画ID。UTM内で飛行計画仮登録時に発行されるUUIDで、一時保存から本登録・飛行中・終了まで
        同一の値を維持する。DIPS通報後にDIPS側で発行されるIDは別のIDであり、`dipsFlightPlanId`で
        管理する（本IDとは異なる形式の文字列で、UUIDではない）。
      example: "550e8400-e29b-41d4-a716-446655440000"

    DipsFlightPlanId:
      type: string
      nullable: true
      description: |
        DIPS側で発行された飛行計画ID（DIPS APIの飛行計画通報受付での受付番号）。UTM内で発行される
        `flightPlanId`とは異なる、DIPS独自の識別子。DIPS通報（`reportFlightPlan`）が一度も成功して
        いない間はDIPS側でまだ発行されていないため`null`。初回の通報成功時に設定され、以降は同一の
        飛行計画に対する再通報（内容変更後の再送）でも値は変わらない。
      example: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353423.001"

    OpponentDipsFlightPlanId:
      type: string
      description: |
        競合相手（他Operator）の飛行計画のDIPS側ID。`DipsFlightPlanId`と同じ形式だが
        **`null`にはならない**。値の出所は運航調整ドメインの
        `coordination.confliction.opponent_flight_plan_id`であり、競合レコードが存在する場合は
        必ず値を持つため、配列の要素として`null`が現れることはない。
      example: "O40SIFXUOCCAAXXTJXNZ.FP20250221050353425.001"

    PilotId:
      type: string
      format: uuid
      description: |
        操縦者ID（`/api/v1/fp/pilots` に登録済みの操縦者を参照するUUID）。

        **デモ向け補足**: 10月デモでは操縦者のCRUD APIを提供対象外とするため、事前に関係者間で
        調整・払い出し済みの登録済みIDを指定する。
      example: "a3f6c9e2-4b7d-48a1-9c6e-3d8f2b5a7c14"

    AircraftId:
      type: string
      format: uuid
      description: |
        機体ID（`/api/v1/asset/aircrafts` に登録済みの機体を参照するUUID）。

        **デモ向け補足**: 10月デモでは機体のCRUD APIを提供対象外とするため、事前に関係者間で
        調整・払い出し済みの登録済みIDを指定する。
      example: "9f4c7a2e-6b3d-48e1-a5c9-3d7f2b6a9c04"

    #
    # 飛行計画
    #
    # InsuranceInformation・FlightPermitApplicationInfo・FlightPermitContactは、リクエスト側・
    # レスポンス側どちらかにだけ制約緩和やフィールド追加をしたい具体的な要求がまだないため、
    # 意図的にリクエスト/レスポンスで共有のままにしてある（PilotAssignmentInput・FlyRouteInputとは異なる判断）。
    CompensationAmount:
      type: object
      description: 補償金額。「無制限かどうか」（`unlimited`）と「金額」（`amount`）を分けて表現する。
      required: [unlimited]
      properties:
        unlimited:
          type: boolean
          description: 補償が無制限かどうか
        amount:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "format: int64とmaximum: 99999999999を新設（DIPSの11桁上限に合わせた）。従来通っていた12桁以上の金額は422になる" }]
          type: integer
          format: int64
          minimum: 0
          maximum: 99999999999
          nullable: true
          description: |
            補償金額。`unlimited`が`true`の場合は指定不要（免責金額など補足的な金額を任意で
            指定してもよいが、その場合の意味は文脈による）。`unlimited`が`false`の場合は
            補償金額そのものを表す。金額が未入力の場合は`null`。負の金額は指定不可。
          example: 100000000

    InsuranceInformation:
      type: object
      description: |
        保険に関する情報。オブジェクト自体は任意項目（未指定で保険なしを表せる）だが、
        指定する場合は本オブジェクト内の全項目が必須となる。
      required:
        - insuranceCompany
        - insuranceProduct
        - interPerson
        - interObject
        - insuranceAbility
      properties:
        insuranceCompany:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLength: 60を新設（DIPSの入力チェックに合わせた）。従来通っていた61文字以上の入力は422になる" }]
          type: string
          maxLength: 60
          description: 保険会社名
        insuranceProduct:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLength: 60を新設（DIPSの入力チェックに合わせた）。従来通っていた61文字以上の入力は422になる" }]
          type: string
          maxLength: 60
          description: 商品名
        interPerson:
          allOf:
            - $ref: "#/components/schemas/CompensationAmount"
          description: 補償金額（対人）
        interObject:
          allOf:
            - $ref: "#/components/schemas/CompensationAmount"
          description: 補償金額（対物）
        insuranceAbility:
          type: boolean
          description: 賠償能力の有無

    PilotAssignmentInput:
      type: object
      description: |
        リクエスト専用スキーマ（`FlightPlanCreateRequest`・`FlightPlanUpdateRequest`が参照）。レスポンス（`FlightPlanDetailResponse`）には`PilotAssignment`を用いる。
        将来的な項目追加や制約緩和による破壊を回避するため、リクエスト型とレスポンス型を分けて定義することで拡張性を持たせた設計とする。

        `pilotId`・`aircraftIds`は、いずれもリクエストしたユーザと同一組織に属するものでなければ
        ならない。他組織の操縦者・機体を指定した場合は422を返す。データベースの外部キー制約では
        組織の一致を担保しないため、この検証が唯一の担保であり、実装が漏れると他組織の氏名・機体名が
        レスポンスに現れる。
      required: [pilotId, aircraftIds]
      properties:
        pilotId:
          $ref: "#/components/schemas/PilotId"
        aircraftIds:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/AircraftId"
          description: 当該操縦者がこの飛行計画で操縦する機体IDの配列

    FlightPurposeCode:
      type: string
      description: 飛行目的コード。コード種別はDIPS API参照。
      enum:
        - AERIAL_PHOTOGRAPHY
        - NEWS_COVERAGE
        - SECURITY
        - AGRICULTURE_FORESTRY_FISHERIES
        - SURVEY
        - ENVIRONMENTAL_SURVEY
        - EQUIPMENT_MAINTENANCE
        - INFRASTRUCTURE_INSPECTION
        - MATERIALS_MANAGEMENT
        - TRANSPORT_DELIVERY
        - NATURE_OBSERVATION
        - ACCIDENT_DISASTER_RESPONSE
        - OTHER_BUSINESS
        - HOBBY
        - RESEARCH_DEVELOPMENT
        - OTHER_NON_BUSINESS
      x-enum-descriptions:
        - 空撮
        - 報道取材
        - 警備
        - 農林水産業
        - 測量
        - 環境調査
        - 設備メンテナンス
        - インフラ点検・保守
        - 資材管理
        - 輸送・宅配
        - 自然観測
        - 事故・災害対応等
        - その他１（業務）。指定時は当該`FlightPurposeItem.note`が入力必須
        - 趣味
        - 研究開発
        - その他２（業務以外）。指定時は当該`FlightPurposeItem.note`が入力必須

    FlightAirspaceCode:
      type: string
      description: |
        飛行空域コード。コード種別はDIPS API参照。
      enum:
        - DENSELY_POPULATED_AREA
        - ABOVE_150M
        - AROUND_AIRPORT
      x-enum-descriptions:
        - 人・家屋の密集地域の上空
        - 地表・水面から150m以上の高さの空域
        - 空港周辺

    FlightTypeCode:
      type: string
      description: |
        飛行方法コード。コード種別はDIPS API参照。
      enum:
        - WITHIN_30M_OF_PEOPLE_OR_PROPERTY
        - OVER_EVENT_VENUE
        - NIGHT_FLIGHT
        - BEYOND_VISUAL_LINE_OF_SIGHT
        - HAZARDOUS_MATERIALS_TRANSPORT
        - OBJECT_DROPPING
      x-enum-descriptions:
        - 人・物件から30m未満の距離
        - 催し物上空の飛行
        - 夜間の飛行
        - 目視外での飛行
        - 危険物の輸送
        - 物件投下

    RiskMitigationType:
      type: string
      description: リスク軽減措置の種別
      enum: [ONSITE_CONTROL, ONSITE_CONTROL_LEVEL3, ONSITE_CONTROL_LEVEL35, NO_ENTRY_CONTROL]
      x-enum-descriptions:
        - 立入管理措置
        - 立入管理措置（レベル3飛行）
        - 立入管理措置（レベル3.5飛行関連）
        - 立入禁止措置

    FlightPurposeItem:
      type: object
      description: |
        飛行目的の1項目。既存の`otherBusinessReason`/`otherNonBusinessReason`（`codes`にどの値が
        含まれるかに応じて排他的に必須化する、2つの独立フラットフィールド）を廃止し、「どの目的コード
        に対する補足か」を1項目内の属性として構造化した（コードに紐づく補足入力という要素がすでに
        存在していたための対応であり、`flightAirspace`/`flightType`等の他コード配列は今回は対象外）。
      required: [code]
      properties:
        code:
          $ref: "#/components/schemas/FlightPurposeCode"
        note:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを500から120へ縮小（DIPSの入力チェックに合わせた）。従来通っていた121文字以上の入力は422になる" }]
          type: string
          maxLength: 120
          description: |
            当該目的コードの補足説明。`code`が`OTHER_BUSINESS`（その他１・業務）または
            `OTHER_NON_BUSINESS`（その他２・業務以外）の場合は入力必須（JSON Schemaでは値に応じた
            条件付き必須を表現できないため、FlightPlanningService側のビジネスルールとして検証する。
            「飛行計画本登録」・登録済み（`ACCEPTED`以降）の「飛行計画更新」時に検証し、違反時は422を
            返す）。それ以外のコードでは指定不要（指定してもDIPS通報では利用しない）。
            DIPS通報時は、`code=OTHER_BUSINESS`の`note`を`othergyomutext`、`code=OTHER_NON_BUSINESS`
            の`note`を`othergyomugaitext`にそれぞれ変換する。

    FlightPeriod:
      type: object
      description: 飛行の時間帯・所要時間に関する情報。
      properties:
        startTime:
          allOf:
            - $ref: "#/components/schemas/TimestampMinute"
          description: |
            飛行開始予定日時。DIPSへの通報時はJST（UTC+9）に変換したうえで
            `flightPlanInfo.startTime`（`YYYYMMDD HHMM`形式。年月日と時刻の間は半角スペース）
            への変換が必要だが、これはバックエンド内部の処理として行うため、本APIの型・
            フォーマットは`TimestampMinute`のまま維持する。

            指定できる期間は、JST（UTC+9）の暦日に変換した値が**API実行日の前日以降**、かつ
            API実行日から1年以内とする。「1年以内」の出典は同別紙2のID37・ID38である。
            「前日以降」の出典はDIPS APIガイドライン（USP向け）の
            別紙2_入力チェック一覧のID39で、対象項目「飛行開始日時」、チェック内容
            「飛行開始日時がAPI実行日の1日前まで」、違反時メッセージ「飛行計画の登録・更新で
            エラーが発生しました。：予定開始時間が2日以前です。」である。**メッセージが「2日以前」を
            境界としているため、判定は時刻単位（実行時刻の24時間前）ではなく暦日単位である。**
            API実行日を基準とする相対的な範囲はJSON Schemaでは表現できないため、バックエンドの
            ドメイン層で検証し、違反は422で返す。

            DIPSは登録・更新の両方でこのチェックを行うため、UTM側も`createFlightPlan`・
            `updateFlightPlan`・`registerFlightPlan`のいずれでも検証する。更新時のガードを緩めて
            UTM側に保存できるようにしても、その飛行計画は`reportFlightPlan`でDIPSに弾かれるため、
            緩める意味がない。画面は日時を変更していなくても`flightPeriod`を含めて送るため、
            **開始日時が範囲から外れた飛行計画は、他の項目だけを直す更新も422になる**。この場合は
            開始日時もあわせて範囲内へ直す必要がある。
        plannedMaxTime:
          type: integer
          minimum: 5
          maximum: 1440
          multipleOf: 5
          description: 航続可能時間(分)。飛行させる機体の航続可能時間。
        plannedFlightTime:
          type: integer
          minimum: 5
          maximum: 1440
          multipleOf: 5
          description: 所要時間(分)。飛行開始時刻から飛行終了時刻までの時間。

    FlightSpec:
      type: object
      description: |
        飛行速度・高度など、当該飛行で用いる諸元。DIPS飛行計画参照APIのレスポンスにも含まれ、
        他USPとの重複調整等でも参照される実質データ（`departurePoint`/`destinationPoint`とは異なり、
        通報後も読み出せる）。
      properties:
        speed:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "type: number(format: double)からintegerへ変更（DIPSが整数値を要求するため）。従来通っていた小数を含む値は422になる" }]
          type: integer
          minimum: 1
          maximum: 100
          description: 飛行速度(km/h)。当該飛行で多用する速度、又は最大速度（GS）。
        altitude:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "type: number(format: double)からintegerへ変更（DIPSが整数値を要求するため）。従来通っていた小数を含む値は422になる" }]
          type: integer
          minimum: 1
          maximum: 999
          description: |
            飛行する最高高度(m, AGL)。
            空域制限・他の飛行計画との競合判定には使用しない（判定は`flyRoute`のみによる2次元判定。詳細は`FlyRouteInput`の説明を参照）。

    RiskMitigation:
      type: object
      description: 講じるリスク軽減措置に関する情報。
      properties:
        types:
          type: array
          items:
            $ref: "#/components/schemas/RiskMitigationType"
          description: |
            講じるリスク軽減措置。飛行形態に応じて必要な措置を指定する。本項目（キー）自体は必須だが、
            該当する措置が一つもない場合は空配列を指定すればよい（`minItems`は設けず、「1つ以上の選択」を
            必須とはしない）。
        exceptionalConditionsMooring:
          type: boolean
          description: 係留飛行を行うか
        assistantsNumber:
          type: integer
          minimum: 0
          description: 補助者数

    FlightPlanUpdateRequest:
      type: object
      description: |
        「飛行計画更新」（`updateFlightPlan`）専用のリクエストスキーマ。`FlightPlanCreateRequest`
        （作成用）とは、必須項目・`nullable`の扱いが異なるため独立したスキーマとして定義する
        （JSON Merge Patch方式で入力済みの内容を未入力の状態に戻せるよう、`name`以外の各項目は
        `nullable`とする。詳細は`FlightPlanCreateRequest`の説明を参照）。
        レスポンス（`FlightPlanDetailResponse.flightPlan`）には`FlightPlanFields`を用いる。
        将来的な項目追加や制約緩和による破壊を回避するため、リクエスト型とレスポンス型を分けて定義することで拡張性を持たせた設計とする。
      properties:
        name:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを255から30へ縮小（DIPSの入力チェックに合わせた）。従来通っていた31文字以上の入力は422になる" }]
          type: string
          minLength: 1
          maxLength: 30
          description: 飛行計画名称
        flightPurposes:
          type: array
          nullable: true
          minItems: 1
          items:
            $ref: "#/components/schemas/FlightPurposeItem"
          description: 飛行目的の配列（複数指定可能）
        flightAirspace:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/FlightAirspaceCode"
          description: 飛行空域コード配列（特定飛行の飛行形態で空域に関するもの）
        flightType:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/FlightTypeCode"
          description: 飛行方法コード配列（特定飛行の飛行形態で方法に関するもの）
        departurePoint:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを255から120へ縮小（DIPSの入力チェックに合わせた）。従来通っていた121文字以上の入力は422になる" }]
          type: string
          maxLength: 120
          nullable: true
          description: |
            出発地（地名・固有名称など）。緯度経度を持たない自由記述の文字列で、DIPSへの飛行計画通報
            時のみ書面上保持される（DIPS飛行計画参照APIのレスポンスには含まれないため、**DIPS側からは**
            読み出せない。`flightSpec`が外部に公開される実質データであるのとは対照的な性質）。
            本APIの詳細取得・一覧取得では返るため、OpenAPIの`writeOnly`は宣言しない。
        flightPeriod:
          type: object
          allOf:
            - $ref: "#/components/schemas/FlightPeriod"
          nullable: true
        flightSpec:
          type: object
          allOf:
            - $ref: "#/components/schemas/FlightSpec"
          nullable: true
        flyRoute:
          type: object
          allOf:
            - $ref: "#/components/schemas/FlyRouteInput"
          nullable: true
        destinationPoint:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを255から120へ縮小（DIPSの入力チェックに合わせた）。従来通っていた121文字以上の入力は422になる" }]
          type: string
          maxLength: 120
          nullable: true
          description: |
            目的地（地名・固有名称など）。`departurePoint`と同様、緯度経度を持たずDIPS通報時のみ
            保持される自由記述の文字列で、DIPS側からは読み出せない（本APIでは返る）。
            DIPSの登録・更新が本項目を必須とするため、
            目的地の概念がない飛行（円形エリアの点検飛行など）でも登録・更新時には代表的な地名等を
            指定する必要がある。
        riskMitigation:
          type: object
          allOf:
            - $ref: "#/components/schemas/RiskMitigation"
          nullable: true
        insuranceInformation:
          type: object
          allOf:
            - $ref: "#/components/schemas/InsuranceInformation"
          nullable: true
        otherInformation:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを1000から300へ縮小（DIPSの入力チェックに合わせた）。従来通っていた301文字以上の入力は422になる" }]
          type: string
          maxLength: 300
          nullable: true
          description: その他特記事項（補足的な情報）
        pilotInfo:
          type: array
          nullable: true
          minItems: 1
          items:
            $ref: "#/components/schemas/PilotAssignmentInput"
          description: この飛行計画に紐づく操縦者と使用する機体
        flightPermitApplicationInfo:
          type: object
          allOf:
            - $ref: "#/components/schemas/FlightPermitApplicationInfo"
          nullable: true
          description: 「飛行許可・承認申請」が必要な場合に指定する、DIPSへの飛行計画通報時に必要な許可・承認情報
        email:
          type: string
          allOf:
            - $ref: "../domain.yaml#/components/schemas/EmailAddress"
          nullable: true
          description: 調整連絡先メールアドレス。他の飛行計画と重複する場合に調整を行うための連絡先（形式・最大長は`EmailAddress`を参照）。

    FlightPermitApplicationInfo:
      type: object
      description: |
        飛行許可・承認を必要とする飛行の場合に指定する、DIPSへの飛行計画通報時に
        必要となる許可・承認情報一式。

        以下は複数項目にまたがる整合性検証のためJSON Schemaでは表現できず、FlightPlanningService側の
        ビジネスルールとして検証する（「飛行計画本登録」・登録済み（`ACCEPTED`以降）の「飛行計画更新」時に
        検証し、違反時は422を返す）。
        - `permitDate` <= `startDate` <= `finishDate`（許可・承認の発行日・期間の順序関係）
        - `flightPeriod.startTime`（UTCの日時）をJST（UTC+9）の暦日に変換した値が、許可・承認期間
          （`startDate`〜`finishDate`。いずれもJSTの暦日）の範囲内にあること
      required: [flightPermitApplicationNumber, permitDate, startDate, finishDate, contactPermit]
      properties:
        flightPermitApplicationNumber:
          type: string
          maxLength: 100
          description: |
            DIPSへの申請時に発行された許可・承認番号。DIPSガイドラインに文字数・書式の規定は
            なく、サンプル値（10桁の英数字）から十分な余裕を持たせた暫定値としてmaxLengthのみ
            設定する（patternは根拠が確認できないため未設定）。
          example: "Q190100001"
        permitDate:
          allOf:
            - $ref: "#/components/schemas/CalendarDate"
          description: 許可・承認の発行日
        startDate:
          allOf:
            - $ref: "#/components/schemas/CalendarDate"
          description: 許可・承認期間（自）
        finishDate:
          allOf:
            - $ref: "#/components/schemas/CalendarDate"
          description: 許可・承認期間（至）
        contactPermit:
          $ref: "#/components/schemas/FlightPermitContact"


    FlightPermitContact:
      type: object
      description: 飛行許可・承認に関する連絡先情報
      required: [name, country, prefectures, address, telephoneCountry, telephone, email]
      properties:
        name:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを255から120へ縮小（DIPSの入力チェックに合わせた）。従来通っていた121文字以上の入力は422になる" }]
          type: string
          maxLength: 120
          description: 氏名
        country:
          $ref: "../domain.yaml#/components/schemas/CountryCode"
        prefectures:
          $ref: "../domain.yaml#/components/schemas/PrefectureCode"
        address:
          type: string
          maxLength: 255
          x-ix-changes: [{ kind: changed, version: "0.3.0", note: "項目名を`municipality`から`address`に変更（内容は住所のまま）" }]
          description: 住所。DIPS通報時は`flightPlanInfo.flightPermitApplicationInfo.contactPermit.municipality`に設定する。
        telephoneCountry:
          $ref: "../domain.yaml#/components/schemas/CountryCode"
        telephone:
          $ref: "../domain.yaml#/components/schemas/PhoneNumber"
        email:
          $ref: "../domain.yaml#/components/schemas/EmailAddress"

    FlightPlanCreateRequest:
      type: object
      description: |
        飛行計画仮登録リクエスト。デモでは一時保存機能を提供対象外とするため、「飛行計画本登録」
        （`registerFlightPlan`）と同じDIPS必須項目をすべて必須（`null`不可）とする。

        `flightPurposes`内の`note`（`code`の値に応じた条件付き必須）は、兄弟プロパティの値に応じた
        条件分岐のため、`FlightPlanningService`側のビジネスルールとして検証する（詳細は`FlightPurposeItem`参照）。
      required:
        - name
        - flightPurposes
        - departurePoint
        - flightPeriod
        - flightSpec
        - flyRoute
        - destinationPoint
        - riskMitigation
        - pilotInfo
        - email
      properties:
        name:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを255から30へ縮小（DIPSの入力チェックに合わせた）。従来通っていた31文字以上の入力は422になる" }]
          type: string
          minLength: 1
          maxLength: 30
          description: 飛行計画名称
        flightPurposes:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/FlightPurposeItem"
          description: 飛行目的の配列（複数指定可能）
        flightAirspace:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/FlightAirspaceCode"
          description: 飛行空域コード配列（特定飛行の飛行形態で空域に関するもの）。任意項目のため`nullable`。
        flightType:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/FlightTypeCode"
          description: 飛行方法コード配列（特定飛行の飛行形態で方法に関するもの）。任意項目のため`nullable`。
        departurePoint:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを255から120へ縮小（DIPSの入力チェックに合わせた）。従来通っていた121文字以上の入力は422になる" }]
          type: string
          maxLength: 120
          description: |
            出発地（地名・固有名称など）。緯度経度を持たない自由記述の文字列で、DIPSへの飛行計画通報
            時のみ書面上保持される（DIPS飛行計画参照APIのレスポンスには含まれないため、**DIPS側からは**
            読み出せない。`flightSpec`が外部に公開される実質データであるのとは対照的な性質）。
            本APIの詳細取得・一覧取得では返るため、OpenAPIの`writeOnly`は宣言しない。
        flightPeriod:
          allOf:
            - $ref: "#/components/schemas/FlightPeriod"
            - type: object
              required: [startTime, plannedMaxTime, plannedFlightTime]
        flightSpec:
          allOf:
            - $ref: "#/components/schemas/FlightSpec"
            - type: object
              required: [speed, altitude]
        flyRoute:
          $ref: "#/components/schemas/FlyRouteInput"
        destinationPoint:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを255から120へ縮小（DIPSの入力チェックに合わせた）。従来通っていた121文字以上の入力は422になる" }]
          type: string
          maxLength: 120
          description: |
            目的地（地名・固有名称など）。`departurePoint`と同様、緯度経度を持たずDIPS通報時のみ
            保持される自由記述の文字列で、DIPS側からは読み出せない（本APIでは返る）。
            DIPSの登録・更新が本項目を必須とするため、
            目的地の概念がない飛行（円形エリアの点検飛行など）でも登録・更新時には代表的な地名等を
            指定する必要がある。
        riskMitigation:
          allOf:
            - $ref: "#/components/schemas/RiskMitigation"
            - type: object
              required: [types, exceptionalConditionsMooring, assistantsNumber]
        insuranceInformation:
          type: object
          allOf:
            - $ref: "#/components/schemas/InsuranceInformation"
          nullable: true
          description: 保険に関する情報。任意項目のため`nullable`。
        otherInformation:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを1000から300へ縮小（DIPSの入力チェックに合わせた）。従来通っていた301文字以上の入力は422になる" }]
          type: string
          maxLength: 300
          nullable: true
          description: その他特記事項（補足的な情報）。任意項目のため`nullable`。
        pilotInfo:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/PilotAssignmentInput"
          description: この飛行計画に紐づく操縦者と使用する機体
        flightPermitApplicationInfo:
          type: object
          allOf:
            - $ref: "#/components/schemas/FlightPermitApplicationInfo"
          nullable: true
          description: |
            「飛行許可・承認申請」が必要な場合に指定する、DIPSへの飛行計画通報時に必要な許可・承認情報。
            対象外の場合はキー自体を省略するか`null`を指定する（任意項目のため`nullable`）。
        email:
          $ref: "../domain.yaml#/components/schemas/EmailAddress"

    # 設計メモ: 競合種別（気象・NOTAM・地上リスクなど）が今後増えるとFullConflictSummaryが太り、一覧
    # （FlightPlanListResponse）の全アイテムに乗ってペイロードが線形に膨らむ。GET /{flightPlanId}
    # /conflicts のようなサブリソースへの切り出しが対策になるが、今回は見送り、将来の課題として記録する。
    #
    # 設計メモ: 競合判定は`FlyRouteInput`のジオメトリのみによる2次元判定（高度は使用しない）。
    # 詳細は`FlyRouteInput`の説明を参照。
    #
    # 設計メモ: 飛行計画エリアの競合には空域制限との競合・飛行計画同士の競合の2種類があり、
    # 「値が入りうる条件」が異なるため、レスポンススキーマを2種類に分離している。
    # - 空域制限との競合（airspaceRestrictions）: チェック自体が行われるのは「飛行計画本登録」
    #   （registerFlightPlan）、および既に登録済み（`ACCEPTED`以降）の飛行計画に対する
    #   「飛行計画更新」（updateFlightPlan）時のみ。「飛行計画仮登録」（createFlightPlan。常に
    #   `DRAFT`で作成される）と、`DRAFT`のままの飛行計画に対する`updateFlightPlan`ではチェック
    #   自体を行わない。チェックが行われた場合は競合の有無によらず`conflict`が設定され
    #   （競合が無ければ`airspaceRestrictions`は空配列）、チェックが行われていない場合も
    #   `conflict`は設定され`airspaceRestrictions`が空配列になる（`conflict`自体は`null`にしない）。
    # - 飛行計画同士の競合（flightPlanIds）: UTM側では他Operatorの飛行計画を横断的に判定できず、
    #   DIPS通報応答（`reportFlightPlan`のDIPS API応答）またはDIPSからの競合通知でのみ判明する。
    #   したがって`createFlightPlan`/`updateFlightPlan`/`registerFlightPlan`はこの種の競合を
    #   検知しえないため、レスポンスには`flightPlanIds`に相当する項目自体を持たせない
    #   （`AirspaceRestrictionConflictSummary`を使用）。`reportFlightPlan`実行後、および
    #   `reportStatus=REPORTED`を保持している間の一覧・詳細取得（`FullConflictSummary`を使用）でのみ
    #   値が入りうる。利用側は`reportStatus`と合わせて本フィールドの意味を判断する。
    AirspaceRestrictionConflictSummary:
      type: object
      x-ix-changes: [{ kind: changed, version: "0.6.0", note: "`nullable`を廃止し常に設定されるようにした（未チェックは空配列で表す）" }]
      description: |
        飛行計画エリアと空域制限との競合の情報。**常に設定され`null`にはならない**。
        競合していない場合と、競合チェック自体を行っていない場合（`createFlightPlan`、`DRAFT`の
        ままの飛行計画に対する`updateFlightPlan`）のいずれも`airspaceRestrictions`は空配列になる。
        したがって空配列は「保持している競合が0件」を意味し、チェック済みかどうかは表さない。
        チェックが行われたかは`status`から判断する（`DRAFT`は未チェック、`ACCEPTED`以降はチェック済み）。

        飛行計画同士の競合はDIPS通報応答またはDIPSからの競合通知でのみ判明し、`createFlightPlan`/
        `updateFlightPlan`/`registerFlightPlan`はこれを検知しえないため、本スキーマには
        `flightPlanIds`に相当する項目を持たせていない（両方の競合種別を扱うスキーマは
        `FullConflictSummary`を参照）。
      required: [airspaceRestrictions]
      properties:
        airspaceRestrictions:
          type: array
          description: |
            競合している空域制限の一覧。空域制限との競合チェックが行われるのは「飛行計画本登録」
            （registerFlightPlan）、および既に登録済み（`ACCEPTED`以降）の飛行計画に対する
            「飛行計画更新」（updateFlightPlan）時のみ（詳細は本schema手前の設計メモを参照）。
            競合が無い場合、およびチェック自体が行われていない場合は空配列になる
            （空配列は「保持している競合が0件」を意味する）。
          items:
            $ref: "#/components/schemas/AirspaceRestrictionConflict"

    FullConflictSummary:
      type: object
      x-ix-changes: [{ kind: changed, version: "0.6.0", note: "`nullable`を廃止し常に設定されるようにした（未チェックは空配列で表す）" }]
      description: |
        飛行計画エリアと空域制限との競合、または飛行計画同士の競合の情報。**常に設定され`null`には
        ならない**。競合していない種別、およびチェック自体を行っていない場合（対象が`DRAFT`のまま）は
        空配列になる（例: 空域制限とのみ競合している場合、`flightPlanIds`は空配列）。したがって
        空配列は「保持している競合が0件」を意味する。チェックが行われたかは`status`から判断する
        （`DRAFT`は未チェック、`ACCEPTED`以降は空域制限とのチェック済み）。各プロパティが値を持ちうる
        条件は異なる（詳細は本schema手前の設計メモを参照）。

        飛行計画同士の競合（`flightPlanIds`）はDIPS通報応答またはDIPSからの競合通知でのみ判明する
        ため、本スキーマは`reportFlightPlan`・`getFlightPlan`・`listFlightPlans`など、その検知結果を
        参照しうるAPIでのみ使用する。飛行計画仮登録・更新・本登録（DIPSに問い合わせない・DIPSからの
        通知を受けていない時点のレスポンス）は`AirspaceRestrictionConflictSummary`を使用する。
      required: [airspaceRestrictions, flightPlanIds]
      properties:
        airspaceRestrictions:
          type: array
          description: |
            競合している空域制限の一覧。空域制限との競合チェックが行われるのは「飛行計画本登録」
            （registerFlightPlan）、および既に登録済み（`ACCEPTED`以降）の飛行計画に対する
            「飛行計画更新」（updateFlightPlan）時のみで、チェック済みの結果を保持する
            （`reportFlightPlan`では再判定しない）。競合が発生していない場合は空配列。
          items:
            $ref: "#/components/schemas/AirspaceRestrictionConflict"
        flightPlanIds:
          type: array
          description: |
            競合している他の飛行計画IDの一覧。DIPS通報応答またはDIPSからの競合通知で判明するため、
            `reportStatus`が現在の内容に対して`REPORTED`になっている間のみ値が入りうる。
            `createFlightPlan`/`updateFlightPlan`/`registerFlightPlan`はこの種の競合を検知しえない
            ため、レスポンスには本項目自体を持たない`AirspaceRestrictionConflictSummary`を使用する。

            競合相手は他Operatorの計画であり、UTMが保持するのはDIPS側のIDのみである。
            UTMの代理キーを返しても`getFlightPlan`では扱えないIDになるため、DIPS側のIDを返す。
            要素の値は運航調整ドメインの`coordination.confliction.opponent_flight_plan_id`が出所で、
            競合レコードがある場合は必ず値を持つ。そのため要素型には`null`を許さない
            `OpponentDipsFlightPlanId`を用いる（`DipsFlightPlanId`は未通報時に`null`となる単一値用）。
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "要素型を`FlightPlanId`(UTM内部のuuid)から`OpponentDipsFlightPlanId`(DIPS独自形式の文字列、非null)に変更" }]
          items:
            $ref: "#/components/schemas/OpponentDipsFlightPlanId"

    AirspaceRestrictionType:
      $ref: "../domain.yaml#/components/schemas/AirspaceRestrictionType"

    AirspaceRestrictionConflict:
      type: object
      description: |
        競合している空域制限の情報。

        本スキーマは競合の**検出結果**のみを表す。競合の解決状況（解決中・解決済み・無視など）は
        10月デモの対象外であり、項目として持たない。解決などの過程を画面に表示する要件は以降の
        リリースで検討する。
      required:
        - airspaceRestrictionId
        - airspaceRestrictionType
      properties:
        airspaceRestrictionId:
          type: string
          format: uuid
          x-ix-changes: [{ kind: changed, version: "0.10.0", note: "DIPS由来の識別子を返す形をやめ、UTMが採番した`format: uuid`へ戻した（GeoSpatialの`restrictionId`と揃える）" }]
          description: |
            空域制限のID。**UTMが採番した識別子**（`AIRSPACE_RESTRICTION.id`）を返す。
            データソースが増えても同一性の判定を1系統のIDで行えるようにするためである。
            GeoSpatialの`AirspaceRestrictionProperties.restrictionId`と型・値を揃えている。
            取得元における識別子はGeoSpatialの`AirspaceRestrictionProperties.externalId`で参照できる
            （本スキーマは競合の検出結果のみを表すため持たない）。
        airspaceRestrictionType:
          $ref: "#/components/schemas/AirspaceRestrictionType"

    FlightPlanStatus:
      type: string
      description: 飛行計画ステータス
      enum:
        - DRAFT
        - ACCEPTED
        - ACTIVATED
        - CANCELLED
        - ENDED
      x-enum-descriptions:
        - 一時保存（下書き）状態。「飛行計画仮登録」/更新APIで保存され、「飛行計画本登録」は未完了
        - 本登録済み状態。本状態よりDIPS通報が可能(DIPS通報状態は`reportStatus`にて管理)
        - 飛行開始により飛行中となっている状態
        - 飛行計画が中止（取消）された状態
        - 飛行終了により運航が完了した状態
      x-enum-varnames:
        - DRAFT
        - ACCEPTED
        - ACTIVATED
        - CANCELLED
        - ENDED

    ReportStatus:
      type: string
      description: |
        DIPS通報状態。`FlightPlanStatus` は一時保存・飛行中・終了なども含む飛行計画全体のライフサイクルを
        表すのに対し、`ReportStatus` はDIPSへの通報に関する進捗のみを独立して表す。

        通報要否は `reportRequired` で別途判定するため、本フィールドは通報不要・未通報のいずれも `UNREPORTED` として扱う。
      enum:
        - UNREPORTED
        - REPORTING
        - REPORTED
        - WITHDRAWING
        - WITHDRAWN
      x-enum-descriptions:
        - |
          DIPS通報が完了していない状態（`reportRequired=false` の通報不要、`true` の未通報のいずれも
          含む）。本状態に入る経路は3つある。
          (1) 飛行計画本登録時の初期値、
          (2) DIPS APIの呼び出し自体が失敗した場合に `REPORTING` から戻る、
          (3) 通報済み（`REPORTED`）の飛行計画を「飛行計画更新」で更新して内容が変わった場合
          （再通報が必要になるため）。(3)の場合、`dipsFlightPlanId` は初回通報時の値を保持したままとなる。
        - DIPSへの飛行計画通報を実行中で、受理結果待ちの状態
        - |
          DIPSへの飛行計画通報が完了した状態（受理・競合などの結果は `FlightPlanStatus` を参照）。
          本状態の飛行計画を「飛行計画更新」で更新して内容が変わると、再通報が必要になるため
          `UNREPORTED` に戻る（内容が変わらない更新では本状態を維持する）。
        - 「飛行計画キャンセル」または「飛行計画削除」によるDIPS側の飛行計画削除API呼び出し中で、結果待ちの状態。
        - 通報済みの飛行計画が「飛行計画キャンセル」または「飛行計画削除」によりDIPS側の飛行計画を削除（取り下げ）した状態
      x-enum-varnames:
        - UNREPORTED
        - REPORTING
        - REPORTED
        - WITHDRAWING
        - WITHDRAWN

    FlightPlanStatusResponse:
      type: object
      description: 飛行計画のステータス情報
      required:
        - flightPlanId
        - status
        - reportRequired
        - reportStatus
        - dipsFlightPlanId
        - createdAt
        - updatedAt
      properties:
        flightPlanId:
          $ref: "#/components/schemas/FlightPlanId"
        status:
          allOf:
            - $ref: "#/components/schemas/FlightPlanStatus"
          example: DRAFT
        reportRequired:
          type: boolean
          description: |
            DIPS通報義務。当該飛行計画がDIPSへの通報を必要とする飛行かどうか。
            一時保存（`status=DRAFT`）の間は判定を行わないため、常に `false` を返す。
          example: true
        reportStatus:
          allOf:
            - $ref: "#/components/schemas/ReportStatus"
          description: DIPS通報状態。`status` の飛行計画ライフサイクル（一時保存／登録／飛行中／終了など）とは独立して、DIPS通報の進捗のみを表す
          example: UNREPORTED
        dipsFlightPlanId:
          allOf:
            - $ref: "#/components/schemas/DipsFlightPlanId"
          description: |
            DIPS側で発行された飛行計画ID。`reportStatus`が一度も`REPORTED`になっていない間は`null`。
            詳細はスキーマ`DipsFlightPlanId`の説明を参照。
        createdAt:
          allOf:
            - $ref: "../domain.yaml#/components/schemas/Timestamp"
          description: 作成日時
        updatedAt:
          allOf:
            - $ref: "../domain.yaml#/components/schemas/Timestamp"
          description: 更新日時

    FlightPlanResponse:
      description: |
        登録された飛行計画の要約情報。飛行計画のステータス情報（`FlightPlanStatusResponse`）に、
        空域制限・飛行計画同士の競合情報を `conflict` として付加したもの。`conflict.flightPlanIds`
        （飛行計画同士の競合）はDIPS通報応答またはDIPSからの競合通知でのみ判明するため、値を
        持ちうる条件は`FullConflictSummary`手前の設計メモを参照（`reportStatus`が`REPORTED`の間のみ）。

        本スキーマは、飛行計画同士の競合を検知しうる`reportFlightPlan`のレスポンス、および
        `FlightPlanDetailResponse`（`getFlightPlan`）・`FlightPlanListItem`
        （`listFlightPlans`）のベースとして使用する。飛行計画仮登録・更新・本登録（`createFlightPlan`/
        `updateFlightPlan`/`registerFlightPlan`）はDIPSに問い合わせないため飛行計画同士の競合を
        検知しえず、それらのレスポンスには`FlightPlanSaveResponse`を使用する。
      allOf:
        - $ref: "#/components/schemas/FlightPlanStatusResponse"
        - type: object
          required: [conflict]
          properties:
            conflict:
              allOf:
                - $ref: "#/components/schemas/FullConflictSummary"
              description: |
                常に設定され`null`にはならない。競合していない種別、および競合チェックを実行して
                いない場合（対象の飛行計画が`DRAFT`のまま）は空配列になる。チェックが実行された
                かどうかは`status`から判断する（詳細は`FullConflictSummary`の説明を参照）。

    FlightPlanSaveResponse:
      description: |
        飛行計画仮登録・更新・本登録（`createFlightPlan`/`updateFlightPlan`/`registerFlightPlan`）の
        レスポンス。飛行計画のステータス情報（`FlightPlanStatusResponse`）に、空域制限との競合情報を
        `conflict` として付加したもの。

        空域制限との競合チェックが行われるのは「飛行計画本登録」（registerFlightPlan）、および
        既に登録済み（`ACCEPTED`以降）の飛行計画に対する「飛行計画更新」（updateFlightPlan）時
        のみ。「飛行計画仮登録」（createFlightPlan）と、`DRAFT`のままの飛行計画に対する
        `updateFlightPlan`ではチェック自体を行わないため、`conflict`の`airspaceRestrictions`は
        空配列になる（`conflict`自体は常に設定される）。

        飛行計画同士の競合はDIPS通報応答またはDIPSからの競合通知でのみ判明し、これらのAPIは
        DIPSに問い合わせないため検知しえない。そのため`conflict`には`flightPlanIds`を持たない
        `AirspaceRestrictionConflictSummary`を用いる（`FlightPlanResponse.conflict`
        （`FullConflictSummary`）との違いはこの点のみ）。飛行計画同士の競合を含む要約情報が必要な場合は
        `reportFlightPlan`・`getFlightPlan`・`listFlightPlans`（`FlightPlanResponse`）を参照。
      allOf:
        - $ref: "#/components/schemas/FlightPlanStatusResponse"
        - type: object
          required: [conflict]
          properties:
            conflict:
              allOf:
                - $ref: "#/components/schemas/AirspaceRestrictionConflictSummary"
              description: |
                常に設定され`null`にはならない。競合チェックを実行していない場合（`createFlightPlan`、
                または`DRAFT`の飛行計画に対する`updateFlightPlan`）と、実行して競合が無かった
                場合のいずれも`airspaceRestrictions`は空配列になる。

    #
    # 飛行計画詳細（レスポンス専用）
    #
    # 将来的な項目追加や制約緩和による破壊を回避するため、リクエスト型とレスポンス型を分けて定義することで拡張性を持たせた設計とする。

    PilotAssignment:
      type: object
      description: |
        操縦者・機体の割り当て情報（レスポンス専用）。`PilotAssignmentInput`（リクエスト用）に対し、
        一覧・詳細画面での表示用にPilot/Assetマスタから解決した氏名・機体名（`pilotName`・`aircraftNames`）
        を追加したもの。
      required: [pilotId, pilotName, aircraftIds, aircraftNames]
      properties:
        pilotId:
          $ref: "#/components/schemas/PilotId"
        pilotName:
          type: string
          maxLength: 120
          x-ix-changes: [{ kind: added, version: "0.7.0", note: "maxLengthを120で新規に追加（DIPS通報時のpilotInfo[].contactPilot.nameの上限に合わせた。別紙2_入力チェック一覧 ID100）" }]
          description: 操縦者氏名（`pilotId`からPilotマスタを解決した現在値）
        aircraftIds:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/AircraftId"
          description: 当該操縦者がこの飛行計画で操縦する機体IDの配列
        aircraftNames:
          type: array
          minItems: 1
          items:
            type: string
            maxLength: 100
          x-ix-changes: [{ kind: added, version: "0.7.0", note: "maxLengthを100で新規に追加（DIPS通報時のflightPlanInfo.aircraftInfo[].modelの上限に合わせた。別紙2_入力チェック一覧 ID126）" }]
          description: |
            `aircraftIds`に対応する機体名（型式／名称）の配列（Assetマスタを解決した現在値）。
            `aircraftIds`と同じ順序・同じ件数で対応する。

    FlyRoute:
      description: |
        飛行の経路・範囲を表す図形情報（レスポンス専用）。`FlyRouteInput`（リクエスト用）と現時点では
        同一構成。構造の詳細は`FlyRouteInput`の説明を参照。
      # 各サブスキーマの`type`に`enum`を書いてはならない（理由は`FlyRouteInput`のコメントを参照）。
      oneOf:
        - $ref: "#/components/schemas/FlyRouteCircle"
        - $ref: "#/components/schemas/FlyRoutePolygon"
        - $ref: "#/components/schemas/FlyRouteRoute"
      discriminator:
        propertyName: type
        mapping:
          circle: "#/components/schemas/FlyRouteCircle"
          polygon: "#/components/schemas/FlyRoutePolygon"
          route: "#/components/schemas/FlyRouteRoute"

    FlyRouteCircle:
      type: object
      description: 円形エリア（レスポンス専用）。`FlyRouteCircleInput`と同一構成。
      required: [type, geometry, radiusM]
      properties:
        type:
          type: string
          example: circle
          description: |
            エリア種別。本スキーマでは`circle`固定。指定可能な値は`circle`（円形）・`polygon`（多角形）・
            `route`（経路）で、値と構造の対応は`FlyRouteInput`/`FlyRoute`の`discriminator.mapping`が定める。
        geometry:
          $ref: "#/components/schemas/GeoJsonPoint"
          description: 中心点
        radiusM:
          type: number
          format: double
          minimum: 0
          exclusiveMinimum: true
          description: |
            半径（メートル）。半径が0以下のエリアは意味を持たないため0より大きい値のみ許容する。
            上限は設定しない。
          example: 50

    FlyRoutePolygon:
      type: object
      description: 多角形エリア（レスポンス専用）。`FlyRoutePolygonInput`と同一構成。
      required: [type, geometry]
      properties:
        type:
          type: string
          example: polygon
          description: |
            エリア種別。本スキーマでは`polygon`固定。指定可能な値は`circle`（円形）・`polygon`（多角形）・
            `route`（経路）で、値と構造の対応は`FlyRouteInput`/`FlyRoute`の`discriminator.mapping`が定める。
        geometry:
          $ref: "#/components/schemas/GeoJsonPolygon"
          description: |
            多角形。穴（内環）は非対応のためLinearRingは1つのみ。終点（始点と同座標）は
            レスポンスにも含めない（リクエストと同じ数え方。DIPS通報時に自動付与されるため。
            `FlyRoutePolygonInput.geometry`参照）。

    FlyRouteRoute:
      type: object
      x-ix-changes: [{ kind: changed, version: "0.6.0", note: "判別子の値を`path`から`route`へ改名し、スキーマ名を`FlyRoutePath`から変更（UTM内部の`ROUTE`と表記を揃えるため）" }]
      description: |
        経路（Route）エリア（レスポンス専用）。`FlyRouteRouteInput`と同一構成。バッファ処理の詳細
        （経路全体に沿ったバッファであり頂点ごとの円の連結ではない点）は`FlyRouteRouteInput`の説明を参照。
      required: [type, geometry, bufferM]
      properties:
        type:
          type: string
          example: route
          description: |
            エリア種別。本スキーマでは`route`固定。指定可能な値は`circle`（円形）・`polygon`（多角形）・
            `route`（経路）で、値と構造の対応は`FlyRouteInput`/`FlyRoute`の`discriminator.mapping`が定める。
        geometry:
          $ref: "#/components/schemas/GeoJsonLineString"
          description: 経路（頂点2点以上、高度は含まない）
        bufferM:
          type: number
          format: double
          minimum: 0
          exclusiveMinimum: true
          maximum: 100
          description: |
            バッファ（メートル、水平方向）。経路（頂点を結ぶ線分全体）に沿って水平方向に拡張した
            範囲を飛行範囲とする（頂点ごとの円の連結ではない。詳細は`FlyRouteRouteInput`の説明を参照）。
            バッファが0以下では経路がエリアを持たないため0より大きい値のみ許容する。
            DIPS飛行計画登録で指定可能範囲より、最大値は100mとする。
          example: 50

    FlightPlanFields:
      type: object
      description: |
        飛行計画登録時のリクエスト内容を表す、レスポンス専用のフィールド集合。
        `FlightPlanUpdateRequest`（リクエスト用）とはキーの構成が対応するが、**同一ではない**。
        次の2項目がレスポンス専用の型になる。

        - `pilotInfo`: `PilotAssignment`（`pilotName`・`aircraftNames`を必須で持つ）。リクエストの
          `PilotAssignmentInput`は`pilotId`・`aircraftIds`のみで、表示名は持たない。
        - `flyRoute`: `FlyRoute`（構造はリクエストの`FlyRouteInput`と同一。リクエスト型と
          レスポンス型を分ける方針のため別スキーマとしており、生成コードでも別型になる）。

        本レスポンスの内容をそのまま「飛行計画更新」のリクエストボディへ送ってよい。リクエスト
        スキーマに存在しないキー（`pilotInfo[].pilotName`・`aircraftNames`）は保存内容へ反映されず、
        エラーにもならない。

        **キーを省略するのは`DRAFT`の間だけである。** `name`は作成時点（`createFlightPlan`）から必須の
        ため常に存在し、それ以外の項目は`DRAFT`で未入力のまま存在しうるためキー自体を省略する。
        本登録（`registerFlightPlan`）を通った`ACCEPTED`以降は、`FlightPlanCreateRequest`が必須とする
        項目（`flightPurposes`・`departurePoint`・`flightPeriod`・`flightSpec`・`flyRoute`・
        `destinationPoint`・`riskMitigation`・`pilotInfo`・`email` の9項目）も常に存在する。
        本スキーマの`required`が`name`のみなのは、
        `DRAFT`と`ACCEPTED`以降を同じスキーマで表すためである。したがって`ACCEPTED`以降の必須は
        **本スキーマでは強制されない**。状態による必須の切り替えはバックエンドの実装で担保する。
      required:
        - name
      properties:
        name:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを255から30へ縮小（DIPSの入力チェックに合わせた）。従来通っていた31文字以上の入力は422になる" }]
          type: string
          minLength: 1
          maxLength: 30
          description: 飛行計画名称
        flightPurposes:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/FlightPurposeItem"
          description: 飛行目的の配列（複数指定可能）。未入力の場合はキー自体を省略する。
        flightAirspace:
          type: array
          items:
            $ref: "#/components/schemas/FlightAirspaceCode"
          description: |
            飛行空域コード配列（特定飛行の飛行形態で空域に関するもの）。`DRAFT`で未入力の場合はキー自体を省略、
            「該当なし」を明示的に選択した場合は空配列。
        flightType:
          type: array
          items:
            $ref: "#/components/schemas/FlightTypeCode"
          description: |
            飛行方法コード配列（特定飛行の飛行形態で方法に関するもの）。`DRAFT`で未入力の場合はキー自体を省略、
            「該当なし」を明示的に選択した場合は空配列。
        departurePoint:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを255から120へ縮小（DIPSの入力チェックに合わせた）。従来通っていた121文字以上の入力は422になる" }]
          type: string
          maxLength: 120
          description: |
            出発地（地名・固有名称など）。緯度経度を持たない自由記述の文字列で、DIPSへの飛行計画通報
            時のみ書面上保持される（DIPS飛行計画参照APIのレスポンスには含まれないため、**DIPS側からは**
            読み出せない。`flightSpec`が外部に公開される実質データであるのとは対照的な性質）。
            本APIの詳細取得・一覧取得では返るため、OpenAPIの`writeOnly`は宣言しない。
            未入力の場合はキー自体を省略する。
        flightPeriod:
          allOf:
            - $ref: "#/components/schemas/FlightPeriod"
          description: 飛行の時間帯・所要時間に関する情報。未入力の場合はキー自体を省略する。
        flightSpec:
          allOf:
            - $ref: "#/components/schemas/FlightSpec"
          description: |
            飛行速度・高度など、当該飛行で用いる諸元。他USPとの重複調整等でも参照される実質データ。
            未入力の場合はキー自体を省略する。
        flyRoute:
          allOf:
            - $ref: "#/components/schemas/FlyRoute"
          description: 飛行の経路・範囲を表す図形情報。未入力の場合はキー自体を省略する。
        destinationPoint:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを255から120へ縮小（DIPSの入力チェックに合わせた）。従来通っていた121文字以上の入力は422になる" }]
          type: string
          maxLength: 120
          description: |
            目的地（地名・固有名称など）。`departurePoint`と同様、緯度経度を持たずDIPS通報時のみ
            保持される自由記述の文字列で、DIPS側からは読み出せない（本APIでは返る）。
            DIPSの登録・更新が本項目を必須とするため、
            目的地の概念がない飛行（円形エリアの点検飛行など）でも登録・更新時には代表的な地名等を
            指定する必要がある。未入力の場合はキー自体を省略する。
        riskMitigation:
          allOf:
            - $ref: "#/components/schemas/RiskMitigation"
          description: 講じるリスク軽減措置に関する情報。未入力の場合はキー自体を省略する。
        insuranceInformation:
          allOf:
            - $ref: "#/components/schemas/InsuranceInformation"
          description: 保険に関する情報。未入力の場合はキー自体を省略する。
        otherInformation:
          x-ix-changes: [{ kind: changed, version: "0.6.0", note: "maxLengthを1000から300へ縮小（DIPSの入力チェックに合わせた）。従来通っていた301文字以上の入力は422になる" }]
          type: string
          maxLength: 300
          description: その他特記事項（補足的な情報）。未入力の場合はキー自体を省略する。
        pilotInfo:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/PilotAssignment"
          description: この飛行計画に紐づく操縦者と使用する機体。`DRAFT`で未入力の場合はキー自体を省略する。
        flightPermitApplicationInfo:
          allOf:
            - $ref: "#/components/schemas/FlightPermitApplicationInfo"
          description: 「飛行許可・承認申請」が必要な場合に指定する、DIPSへの飛行計画通報時に必要な許可・承認情報。対象外・未入力の場合はキー自体を省略する。
        email:
          allOf:
            - $ref: "../domain.yaml#/components/schemas/EmailAddress"
          description: |
            調整連絡先メールアドレス。他の飛行計画と重複する場合に調整を行うための連絡先
            （形式・最大長は`EmailAddress`を参照）。未入力の場合はキー自体を省略する。

    FlightPlanDetailResponse:
      description: 飛行計画詳細情報。登録結果情報（`FlightPlanResponse`）に、飛行計画登録時のリクエスト内容相当の情報（`FlightPlanFields`）を `flightPlan` としてネストしたもの。
      allOf:
        - $ref: "#/components/schemas/FlightPlanResponse"
        - type: object
          required: [flightPlan]
          properties:
            flightPlan:
              $ref: "#/components/schemas/FlightPlanFields"

    FlightPlanListItem:
      description: |
        飛行計画一覧の1件分の情報。飛行計画の要約情報（`FlightPlanResponse`）に、一覧画面表示に
        必要な項目を飛行計画本体から抜粋して追加したもの。キー自体は常に存在し、`DRAFT`で未入力の
        項目は値が`null`になる。全項目を確認する場合は「飛行計画詳細取得」（`FlightPlanDetailResponse`）
        を参照する。
      allOf:
        - $ref: "#/components/schemas/FlightPlanResponse"
        - type: object
          required: [name, departurePoint, destinationPoint, startTime, endTime, flightType, pilotInfo]
          properties:
            name:
              type: string
              description: 飛行計画名称。作成時点から必須のため、`DRAFT`でも常に値を持つ。
            departurePoint:
              type: string
              nullable: true
              description: |
                出発地（地名・固有名称など）。DIPS通報上の扱い（DIPS側から読み出せない）は`FlightPlanFields`
                の説明を参照。`DRAFT`で未入力の場合は`null`。
            destinationPoint:
              type: string
              nullable: true
              description: |
                目的地（地名・固有名称など）。DIPS通報上の扱い（DIPS側から読み出せない）は`FlightPlanFields`
                の説明を参照。`DRAFT`で未入力の場合は`null`。
            startTime:
              allOf:
                - $ref: "#/components/schemas/TimestampMinute"
              description: 飛行開始予定日時。`DRAFT`で未入力の場合は`null`。
            endTime:
              allOf:
                - $ref: "#/components/schemas/TimestampMinute"
              description: |
                飛行終了予定日時（`startTime` + `plannedFlightTime` から算出）。`startTime`・
                `plannedFlightTime`のいずれかが未入力の場合は`null`。
            flightType:
              type: array
              nullable: true
              items:
                $ref: "#/components/schemas/FlightTypeCode"
              description: |
                飛行方法コード配列。`DRAFT`で未入力の場合は`null`、「該当なし」を明示的に
                選択した場合は空配列。
            pilotInfo:
              type: array
              nullable: true
              items:
                $ref: "#/components/schemas/PilotAssignment"
              description: |
                この飛行計画に紐づく操縦者・機体の一覧（氏名・機体名を解決済み）。`DRAFT`で
                未入力の場合は`null`。

    # 設計メモ: デモではページング・ソートを提供対象外としたため、ページのメタ情報（ページ番号・件数・
    # 総件数）やクライアント指定のソート順は定義しない（返却順序は登録順で固定）。将来ページング・
    # ソートを導入する場合に、トップレベルを配列にしていると項目を追加できないため、単一項目でも
    # オブジェクトで包む形は維持する（メタ情報の追加のみで方式を導入できる）。
    FlightPlanListResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/FlightPlanListItem"
          description: 絞り込み条件に合致した飛行計画の全件（登録日時の昇順）
