コンテンツにスキップ

テレメトリドメインER図(10月デモ向けの合わせ込み)

10月のGUTMA/ASTMデモへ向けたテレメトリのデータモデルを定める。方針は 「USSの実装を流用し、DBスキーマの変更で対応する」である。したがって本ドキュメントは、 旧USSのスキーマを新しいデータモデル(飛行計画・Asset・IAM)へ合わせ込む範囲に限る。

取り込みの実装はtelemetry-service-protoにあり、 処理の流れは同リポジトリのdocs/design.mdにある。受信したテレメトリを使う側(動態監視の画面表示、 Conformance Monitoring)は範囲外である。

本ドキュメントで決めなかったことは設計ドキュメント横断のフォローアップ台帳へ登録する。 保持期間・保存先・Conformance Monitoringとの連携・RLSポリシーの具体などが該当する。 数と内訳は台帳を正とし、ここには列挙しない。

TO-BEはPR #29にドラフトがある。レビューは 完了しておらず、確定した設計ではない。同PRは10月デモの方針(実装をほぼ変えない)と合わないため 打ち切られたが、検討内容はα版のベースとする。 本ドキュメントはテーブル名・カラム名をTO-BEに合わせたうえで、構造は旧USSのままにとどめる

α版でTO-BEへ寄せるときの差はTO-BEから外している点に示す。

用語意味
テレメトリ機体が報告する位置・速度・方位などの観測値。1回の報告が1件
カレント機体ごとの最新のテレメトリ1件。動態監視の画面表示と判定に使う
履歴受信したテレメトリすべて。飛行実績の提供と事後の調査に使う
合わせ込み旧USSのスキーマの構造を変えずに、参照先と名前を新しいデータモデルへ揃えること
旧USS移植元であるix-reamo/ussの実装とスキーマ。本設計が置き換える対象であり、本ドキュメントでは対応関係を示すためだけに参照する

図の色分け・カラムのマーカー(PKFKUKPK,FKFK,UK)・カーディナリティ記法は データモデル設計ドキュメントの規約に定める。

本ドキュメントで概念モデルに対して追加したテーブルは以下の4テーブル。概念モデルのTelemetryドメインは TODO: IX側で検討するのまま実体を持たないため、本ドメインのテーブルはすべて追加になる。

追加テーブル追加した理由
TELEMETRY受信したテレメトリを追記のみで保持するため。USSのuss.t_telemetryに対応する
CURRENT_TELEMETRY機体ごとの最新1件を、探索するデータ量を抑えた読取モデルとして保持するため。USSのcrid.t_current_telemetryに対応する(カレントは履歴の射影
TELEMETRY_DEAD_LETTER正常な動態として扱えなかった受信を残すため。「機体が送信しているのに表示されない」を切り分けられるようにする(却下の記録は旧USSから引き継ぐ
TELEMETRY_DISPLAY_DEFAULT飛行計画から解決できない表示用の値を機体ごとに持つため。operator_nameは複写元のIAMが存在せず常にここから引き、operator_idstatusは飛行計画を解決できない機体の既定値として引く。USSに対応するテーブルはなく、10月デモ限りの追加である(表示用の値は機体ごとの既定値で埋める

図1 テレメトリドメイン(10月デモ向け)

Section titled “図1 テレメトリドメイン(10月デモ向け)”
erDiagram
    ORGANIZATION ||--o{ TELEMETRY : "所有 (RLSの分離キー)"
    ASSET ||--o{ TELEMETRY : "報告元 (kind=UAS)"
    FLIGHT_PLAN ||--o{ TELEMETRY : "観測時刻で対応づけた計画"
    FLIGHT_PLAN_REVISION ||--o{ TELEMETRY : "受信時点のリビジョン"
    ORGANIZATION ||--o{ CURRENT_TELEMETRY : "所有 (RLSの分離キー)"
    FLIGHT_PLAN ||--o{ CURRENT_TELEMETRY : "観測時刻で対応づけた計画"
    ASSET ||--o| CURRENT_TELEMETRY : "機体ごとに最新1件"
    ASSET ||--o| TELEMETRY_DISPLAY_DEFAULT : "表示する機体は行を持つ"
    TELEMETRY ||--o| CURRENT_TELEMETRY : "射影元 (FKは持たず aircraft_id と observed_at で対応)"

    ORGANIZATION {
    }
    ASSET {
    }
    FLIGHT_PLAN {
    }
    FLIGHT_PLAN_REVISION {
    }
    TELEMETRY {
        uuid id PK
        uuid organization_id FK "RLSの分離キー"
        uuid aircraft_id FK "ASSET.id (kind=UAS)"
        string reported_uas_id "受信生値。解決キーとして不変保持"
        string uas_nickname "Nullable。ASSET.nicknameの複写。10月デモ限り"
        uuid flight_plan_id FK "Nullable"
        uuid flight_plan_revision_id FK "Nullable"
        uuid operator_id "Nullable。飛行計画→既定値の順に解決。10月デモ限り"
        string operator_name "Nullable。常にTELEMETRY_DISPLAY_DEFAULTから。10月デモ限り"
        enum status "Nullable。飛行計画→既定値の順に解決。10月デモ限り"
        geometry position "PointZ, 4326。Z=測地高度"
        float altitude_agl_m "対地高度。DEM参照による導出値。Nullable"
        int direction "真北基準 0-359"
        string direction_unit "定数'degree'。10月デモ限り"
        float speed "対地速度"
        string speed_unit "定数'm/s'。10月デモ限り"
        float speed_vertical
        int horizontal_accuracy "ASTM F3411の精度コード"
        int vertical_accuracy
        int speed_accuracy
        int timestamp_accuracy
        int ua_type "機体の宣言属性 (受信値)"
        int classification_type
        int category_eu
        int class_eu
        timestamp observed_at "機体が観測した時刻 (UTC)"
        timestamp received_at "UTMが受信した時刻 (UTC)"
    }
    CURRENT_TELEMETRY {
        uuid aircraft_id PK,FK "ASSETと1:0..1"
        uuid organization_id FK "RLSの分離キー"
        uuid flight_plan_id FK "Nullable"
        uuid operator_id "Nullable。飛行計画→既定値の順に解決。10月デモ限り"
        string operator_name "Nullable。常にTELEMETRY_DISPLAY_DEFAULTから。10月デモ限り"
        enum status "Nullable。飛行計画→既定値の順に解決。10月デモ限り"
        string reported_uas_id "APIのuasIdとして返す"
        string uas_nickname "Nullable。ASSET.nicknameの複写。10月デモ限り"
        geometry position "PointZ, 4326"
        float altitude_agl_m "対地高度。Nullable"
        int direction
        float speed
        float speed_vertical
        timestamp observed_at "鮮度判定の基準"
        timestamp received_at "遅延監視用"
    }
    TELEMETRY_DISPLAY_DEFAULT {
        uuid aircraft_id PK,FK "ASSETと1:0..1"
        uuid operator_id "Nullable。飛行計画を解決できない機体の既定値"
        string operator_name "全機体の供給源。IAMが無く複写元がない"
        enum status "飛行計画を解決できない機体の既定値"
    }
    TELEMETRY_DEAD_LETTER {
        uuid id PK
        string reported_uas_id "Nullable: JSONパース失敗時は取り出せない"
        enum reason "MALFORMED_JSON/MISSING_VALUE/INVALID_CHARACTER/OUT_OF_RANGE/UNKNOWN_VALUE"
        string detail "どの項目がどう不正だったか"
        text raw_payload "受信データ全文"
        timestamp received_at
    }

    classDef added fill:#fdd,stroke:#c00,stroke-width:2px,color:#c00
    class TELEMETRY,CURRENT_TELEMETRY,TELEMETRY_DEAD_LETTER,TELEMETRY_DISPLAY_DEFAULT added

図に表現できない制約・索引・NULL可否を補う。PostGIS型は規約に従い本節を正とする。

カラム補足
TELEMETRY.idTELEMETRY_DEAD_LETTER.id代理キー。他ドメインと同じくUUIDv7(common.UuidV7PR #206)で採番する。テレメトリはobserved_atの順に追記される時系列データのため、時間順に単調増加するUUIDv7は挿入局所性の面でとくに効く
TELEMETRY.positiongeometry(PointZ, 4326)。Z値は楕円体高度(要件5.1.3.2)で、Remote ID入力の実測値をそのまま入れる。NOT NULL
TELEMETRY.observed_at機体側の観測時刻。GeoSpatialがミリ秒まで返すためtimestamp(3)以上とする(要件5.1.1.2)。NOT NULL。ただし既存のSQSの受信I/Fが秒未満を切り捨てるため、10月デモの期間中は秒単位の値が入る。この受信I/Fには手を入れない方針のため対処しない(followups.md
TELEMETRY.received_atUTM側の受信時刻。observed_atとの差が遅延であり、受信間隔の算出にも使う
TELEMETRY.uas_nicknameCURRENT_TELEMETRY.uas_nicknameNullable。flight-plan-er.mdASSET.nickname(運航者が機体に付けた名前)の複写。テレメトリ画面で機体を見分けるために表示する。取り込みがASSETを照会して機体を解決する際に一緒に取得する。10月デモ限りで、TO-BEではASSETとの結合で解決する(TO-BEから外している点)。複写にする理由はoperator_name(複写元のUSERがutm-backendに実体を持たず結合できない)とは異なり、asset.assetは実在するため結合は今日でも可能である。それでも複写にするのは、1Hzで全機体を返す動態表示の読み出し経路に別ドメインへの結合を入れないためである。ASSET.nicknameは可変な現在値のため複写値は陳腐化する。追記のみのTELEMETRYは観測時点の名前が残って妥当だが、CURRENT_TELEMETRYは次のテレメトリが届くまで旧名を返し、飛行を終えた機体の行は消さない(カレントは履歴の射影)ため旧名が残り続けうる。デモ中に機体名を変更しないため10月デモでは許容し、TO-BEの結合で解消する
TELEMETRY.reported_uas_id受信したuas_idの生値。登録記号・機体UUID・製造番号・セッションIDのいずれが入るかは値からは判別できない。GeoSpatialのuasIdはこの値を返す
TELEMETRY の索引(aircraft_id, observed_at DESC)(flight_plan_id, observed_at DESC)。前者は一意索引とし、機体ごとの時系列取得・過去時点のスナップショット取得に加えて重複配信の排除を担う(受信は順序保証も一意性もない前提で設計する)。後者は飛行軌跡API(GET /telemetry/{flightPlanId}geospatial-api-design.md5.3節)がflight_plan_id起点で直近60秒分を取得するために必要
CURRENT_TELEMETRY の索引positionGiST索引を張る。現在位置検索(bboxuasEntityIdsflightPlanIdscurrentTimegeospatial-api-design.md5.3節)がbbox条件で絞るため
CURRENT_TELEMETRY.aircraft_id主キー。機体ごとに最新1件であることを型で保証する。旧USSの実装はuas_id(文字列)をUPSERTのキーにしており、これを機体IDへ移すのが本合わせ込みの要点
CURRENT_TELEMETRY が射影元へのFKを持たない射影元のTELEMETRYを指す列(TO-BEのlatest_report_id)は持たず、対応づけはaircraft_idobserved_atの一致で行う。理由はカレントは履歴の射影
TELEMETRY_DEAD_LETTER.raw_payload受信データ全文。JSONとして不正な場合もあるためtextで持つ
TELEMETRY_DEAD_LETTER他テーブルへの参照を持たない。機体を解決できていない段階でも記録するため。organization_idを持たないためRLSの対象外になる。位置情報・機体識別子を含み得るraw_payloadをテナント分離なしで保持することになるが、旧USSの実装(crid.t_telemetry_special_values)を流用する以上この境界も引き継ぐ。保持期間は本ドメイン共通の未決事項(テレメトリの保持期間とライフサイクルが未定)に含める
TELEMETRY_DEAD_LETTER.reason旧USSの実装のerror_type(整数1〜5)を列挙値へ置き換える。MALFORMED_JSON=1(JSON解析失敗)、MISSING_VALUE=2(NoValueCheck)、INVALID_CHARACTER=3(InvalidCharacterCheck)、OUT_OF_RANGE=4(InvalidRangeCheck)、UNKNOWN_VALUE=5(UnknownCheck。ASTM F3411の「値なし」を表す特殊値、例: 高度-1000・方向361)。これにUNKNOWN_AIRCRAFTuas_idに一致する機体がASSETに無い)を加える(未登録機体も却下記録に残す
*.organization_idマルチテナント分離(RLS)のキー。要件6.3.6.1がDBレベルの分離を求めるため持たせる
TELEMETRY.altitude_agl_m対地高度。positionのZ値(測地高度)と地表標高(DEM)から導出し、取り込みが受信時に書き込む。DEMが無い地点では導出できずNULLのまま固定されるためNullable(対地高度は受信時に充填する
CURRENT_TELEMETRY.altitude_agl_m同上。射影元と同じ値を書き込み時に入れる
TELEMETRY.operator_idCURRENT_TELEMETRY.operator_idNullable。飛行計画を対応づけられればflight-plan-er.mdFLIGHT_PLAN.created_byを複写し、できなければTELEMETRY_DISPLAY_DEFAULTから引く。どちらも無ければNULL。表示用の参照値で、イベントの実行者ではないため<動作>_by規約の対象外とする
TELEMETRY.operator_nameCURRENT_TELEMETRY.operator_nameNullable。飛行計画の有無によらずTELEMETRY_DISPLAY_DEFAULTから引く。 TO-BEの複写元であるUSER.display_nameはIAMドメインの実体がutm-backendに無く、自組織の機体でも結合できないためである(表示用の値は機体ごとの既定値で埋める)。既定値の行が無ければNULL
TELEMETRY.statusCURRENT_TELEMETRY.statusNullable。型はflight-plan-er.mdFLIGHT_PLAN.statusと揃える。operator_idと同じく飛行計画→既定値の順に解決する。複写のため元のFLIGHT_PLANが更新されても追随せず、飛行計画がENDED等へ遷移した後も古い値のまま検索・表示され得る。statusはGeoSpatialの検索フィルタにも使われる(geospatial-api-design.md「6. 各APIの検索仕様」)ため、この副作用はoperator_idoperator_nameより影響が大きい
TELEMETRY.flight_plan_idCURRENT_TELEMETRY.flight_plan_idNullable。飛行計画照会で解決できなかった場合(受信時点で該当する計画がない等)はNULLになる。この場合operator_idoperator_namestatusTELEMETRY_DISPLAY_DEFAULTから埋め、そこにも行がなければNULLのままになる。一方docs/openapi/frontend/geospatial.yamlTelemetryTrackPropertiesTelemetryPositionPropertiesflightPlanIdoperatorIdoperatorNamestatusをいずれもrequiredとしており、OASとの食い違いはfollowups.mdへ登録した
TELEMETRY_DISPLAY_DEFAULT.aircraft_id主キー。ASSET.idkind=UAS)を指す。機体ごとに1組でよいため代理キーを持たせない
TELEMETRY_DISPLAY_DEFAULT.operator_nameNOT NULL。全機体のoperator_nameの供給源である(表示用の値は機体ごとの既定値で埋める)。行が無い機体はoperator_nameを埋められないため、動態を表示する機体には必ず行を作る
TELEMETRY_DISPLAY_DEFAULT.operator_idNullable。飛行計画を解決できない機体の既定値で、他社運航者はIX UTM上にUSERの実体を持たないためNULLになる。飛行計画を解決できた機体では使わない
TELEMETRY_DISPLAY_DEFAULT.statusNOT NULL。飛行計画を解決できない機体の既定値。飛行計画を解決できた機体では使わない
TELEMETRY_DISPLAY_DEFAULT参照データであり観測の事実ではないためorganization_idを持たず、RLSの対象外になる。登録は運用(デモ準備)で行い、APIは設けない

概念モデルはイミュータブルデータモデル(リソースとイベントの分離)を方針とし、現在状態は イベント列からの射影(read model)として持つ。テレメトリはこの形にそのまま当てはまる。

旧USSの実装が持つ「履歴用テーブル+カレント用テーブル」は、名前こそ違うがこの構造と同じである。 分けている理由も射影の動機と一致する。動態監視は最新の位置を短時間で判定する必要があり、 機体ごとに1件だけを持てば探索するデータ量を抑えられる。したがって合わせ込みは構造の変更を伴わない。

ただし射影元を指すFKは持たない。 TO-BEはCURRENT_TELEMETRY.latest_report_idで射影元の TELEMETRYを指すが、本設計はこの列を持たない。旧USSの実装は履歴とカレントへ 同じ値の並びを書くため、履歴側で採番した主キーをカレント側へ渡す作りになっておらず、 列を足すと取り込み側の改修が要るからである。対応づけが必要なときはaircraft_idobserved_atの一致で辿る。射影として同じ観測を写している以上この2つで一意に定まり、 FKがなくても射影元は特定できる。

受信は順序保証も一意性もない前提で設計する

Section titled “受信は順序保証も一意性もない前提で設計する”

テレメトリはSQSの標準キューで届く。標準キューはat-least-onceで、配信順序は保証されず、同じ メッセージが複数回届きうる。受信した順や受信した回数を、観測の順序や件数として扱えない。

カレントは「最後に受信した観測」ではなく「observed_atが最も新しい観測」である。 UPSERTはobserved_atが現在値より新しいときだけ更新する。

ON CONFLICT (aircraft_id) DO UPDATE SET ...
WHERE EXCLUDED.observed_at > current_telemetry.observed_at

配信が入れ替わっても位置が後退しない。等号を含めないのは、重複配信で同じ値を書き直さない ためである。カレントは動態監視のSSEでそのまま配信されるため、この条件がないと画面上で機体が 巻き戻って見える。

履歴は重複を落とす。 TELEMETRY(aircraft_id, observed_at)の一意制約を置き、 取り込みは衝突時に何もしない(ON CONFLICT DO NOTHING)。同じ機体の同じ観測時刻に2つの 観測は存在しないため、一意制約が重複配信を弾く。索引は図1 カラム補足(aircraft_id, observed_at DESC)を一意索引にするだけで、索引は増えない。

機体が異常な時刻を送ってきた場合までは扱わない。UTMの責任範囲外であり、10月デモでは 検出も補正もしない。

[!IMPORTANT] この一意制約はobserved_atが秒未満の精度を持つことを前提にする。 受信I/Fが秒未満を 切り捨てている問題(followups.md)を10月デモまでに直す必要がある。 直さないと、同一秒に届いた2件目以降が正常な観測でも重複として落ちる。

デモのテレメトリはBroadcast Remote ID受信機から流れ、流量制御がかかっているか未確認で、 同一秒に2件以上届きうる。一方、受信するJSONのtimestampはエポック秒の小数(例1705916942.4)で 0.1秒の分解能を持つため、切り捨てを直せば区別できる。落ちた件数は取り込み側でログに出し、 黙って消えないようにする。

[!IMPORTANT] 本節は10月デモ対応の措置である。 TO-BEでは、テレメトリの受信を契機に飛行計画の状態を 自動で遷移させる(受信時点で飛行中、または飛行前=ACCEPTEDならACTIVATEDへ変更する)ことが 期待されており、そのうえで運航状態を使って対応づける形になる。10月デモではこの自動遷移を 実装しないため、状態を使わず時刻で対応づける。

受信したテレメトリに紐づける飛行計画は、解決した機体(FLIGHT_PLAN_PILOT_ASSIGNMENT.aircraft_id) に紐づくもののうち、次の優先順で1件に絞る。

  1. observed_atFLIGHT_PLAN_REVISIONplanned_start_atplanned_end_atの範囲内
  2. planned_start_atobserved_atと同じ日付

2があるのは、予定からずれて飛んだ場合(早発・遅延)を拾うためである。同順位が複数あるときは planned_start_atobserved_atに近いものを採る。日付の比較はAsia/Tokyoを基準にする。 UTC基準だと日本時間9時より前の飛行が前日扱いになるためである。

statusは対応づけの条件に使わない。状態遷移は運航者の操作に依存し、操作を忘れる・遅れると 実際に飛んでいてもACTIVATEDにならないためである。ただしCANCELLEDの計画だけは除外する。 飛ばないと決めた計画に、飛んでいる機体を紐づけるのは誤りだからである。

DRAFTは条件に書かなくても外れる。FLIGHT_PLAN.current_revision_idがNULLであることが CHECK制約(ck_flight_plan_draft_revision)で保証されており、リビジョンとの結合で落ちるためである。

複写するstatusENDEDになることがある。 終了した計画の時間帯・日付に受信したテレメトリは その計画に紐づくためである。GeoSpatialの現在位置検索はstatusをAND条件で適用するため (geospatial-api-design.md「6. 各APIの検索仕様」)、 status=['ACTIVATED']での検索からは外れる。

flight_plan_idに加えてflight_plan_revision_idを持つ。飛行計画は flight-plan-er.mdのとおりリビジョンで内容を保持するため、IDだけでは 「受信した時点でどの内容の計画だったか」を復元できない。事故調査で当時の計画と実際の飛行経路を 突き合わせる場面で、この復元ができないと意味がない。

旧USSの実装は飛行計画の照会時にリビジョンを取得していないため、ここだけは取り込み側の変更を伴う。 照会するクエリに列を1つ足す範囲にとどまり、処理の流れは変えない。

GeoSpatialのAPIはgeometryのZ値を原則として対地高度(AGL)基準で返す (geospatial-api-design.mdの「5.3 テレメトリ」)。 一方、機体から受け取るのは測地高度だけである。両者の差を埋めるのがaltitude_agl_mで、 positionのZ値と地表標高(DEM)から導出する。

導出は取り込みのLambdaが受信時に行い、書き込みに含める。 テレメトリは書き込み1回に対して 読み出しが何度も発生する(現在位置APIとSSEが、接続中のクライアント数×配信のたび)ため、 読み出し側で導出すると同じ換算を繰り返すことになる。読み出し時に充填する案も採りうるが、 参照の経路に書き込みが混ざり、参照をレプリカへ寄せる構成と両立しなくなる。

Lambdaは高度変換をutm-backendへ問い合わせるのではなく、変換の実装を同梱して自前で行う。 問い合わせにすると、取り込みの経路に外部サービスへの依存が増える。同梱は10月デモ限りの措置では なく、TO-BEでもLambda側が変換を持つ形(下記)への一歩である。

同梱できるのはデータが小さいうちに限られる。現在のDEMは東京23区を10m・それ以外を全国1kmに 絞っており、ジオイド高とあわせて約96MBで、GeoToolsと合わせてもLambdaのパッケージ上限に収まる。 初期化には約3.9秒かかるが、変換自体は1回0.026msで、実行環境ごとに一度のコストである (elevation-service-design.mdの「4.7 初期化コスト」)。

[!IMPORTANT] DEMが無い地点のaltitude_agl_mはNULLのまま固定される。 受信時に導出できなければ、後から 埋め直す契機が無いためである。現在のDEMのカバレッジ(東京23区10m・それ以外は全国1km)を 外れる地点では実際に発生する。GeoSpatialのAPIは対地高度が無い場合に altitudeReference=WGS84として測地高度を返すため表示は破綻しないが、再充填が必要になれば 別途バッチが要る。

TO-BEはDEM・ジオイドをS3へ置き、COG(Cloud Optimized GeoTIFF)で参照する構成とする。 カバレッジを全国10mへ広げると同梱では収まらなくなるため、その時点で移行する。移行はutm-backendと Lambdaの両方を同時に行う。片方だけがS3を参照すると、同じ地点で異なる対地高度が出るためである。 あわせて、両者に同じ変換の実装が存在する状態を共有ライブラリへ畳む。いずれもfollowups.mdへ登録した。

TELEMETRYは追記のみで、この列もUPDATEしない。 受信時に値が決まるため、 カレントは履歴の射影の原則から外れない。

DEMの参照手段は飛行計画領域・空域制限と共有する(PR #157で実装済みのdomain.port.AltitudeConverter)。飛行計画側はelevation.enabled=trueの環境では登録・更新時に換算値カラムを常にNOT NULLとし、遅延充填は行わない方針をPR #250で決定しており(flight-plan-er.md参照)、テレメトリも書き込み時に決める点で揃う。

飛行終了後のカレントはデータモデルでは消さない

Section titled “飛行終了後のカレントはデータモデルでは消さない”

CURRENT_TELEMETRYは機体ごとに最新1件を上書きし続けるだけなので、飛行が終わってテレメトリが 途絶えても最後の1件が残る。このレコードをデータモデル側で消したり無効化したりはしない。 古くなったかどうかの判定は読み出し側がobserved_atで行う。

旧USSの実装も同じ構造で、表示用のView(crid.if2_telemetry)が current_timestamp - timestamp < 60(秒)で絞ることで、途絶えた機体を表示から外していた。 本設計でもこの方式を踏襲し、GeoSpatialのAPIが60秒より古いものを返さないことで表示を制御する。

データモデル側に「飛行終了」を表す状態を持たせない理由は、テレメトリが観測の事実だけを 持つ追記のみのデータであり、終了はテレメトリが届かなくなったことでしか判別できないためである。 時間による打ち切りは判定の閾値であって観測の事実ではないので、読み出し側に置く。

他社UTMの機体はASSETとしてだけ登録する

Section titled “他社UTMの機体はASSETとしてだけ登録する”

10月デモでは、他社UTM(NTT Data UTM)が管理する機体の動態もテレメトリ画面に表示する。 これはutm-design-docsの機能要件が「初版で支援・対応しないこと」に挙げる 「他UTM管理の動態情報表示」(docs/functional-requirements/requirements.md)に該当するが、 デモの見栄えのために必要との判断により実施する。

対象の機体はIX UTM上にASSETkind=UAS)として登録するが、飛行計画は作らない。 デモでは他社との運航調整も披露するため他社の飛行計画がIX UTMへ収集される。IX UTM上にも 同じ飛行の計画を作ると、収集した計画と二重に見えてしまう。

機体の照合は、ASSETの登録記号(ASSET_UAS_DIPS_ATTRS.registration_symbol)に、 SQSで届くuas_idと同じ値を登録することで成立させる。ASTM F3411のuas_idには 登録記号・機体UUID・製造番号・セッションIDのいずれかが入るが、本デモでは登録記号が入る。

飛行計画に紐づかないテレメトリを受け入れる

Section titled “飛行計画に紐づかないテレメトリを受け入れる”

飛行計画を作らないので、この機体のテレメトリは飛行計画を解決できない。これは新しい要求では なく、USSの取り込み実装が元々備えていた挙動である。OiDAOは飛行計画が引けなくても機体 マスタに存在すれば受け入れるUNIONのフォールバックを持ち、flight_plan_idoperator_idoperator_namestatusをNULLのまま登録していた。表示側もこれを前提にしており、GeoServerの レイヤ定義はこの4項目をいずれもnillableとし、ビューcrid.if2_telemetryの絞り込みは鮮度 (60秒)だけで、飛行計画の有無では絞っていない。

このフォールバックは2024年11月のデータ構造再設計で失われ、移植元とした旧USSの実装は飛行計画が 引けないテレメトリを破棄する。本設計はフォールバックのあった挙動へ戻す。機体の解決を飛行計画 照会ではなくASSET照会で行い、飛行計画は引けたときだけ複写列を埋める。organization_idは NOT NULLだが、飛行計画ではなくASSETの所有組織から解決するため常に埋まる。

表示に使うoperator_namestatusは別途埋める(次節)。

表示用の値は機体ごとの既定値で埋める

Section titled “表示用の値は機体ごとの既定値で埋める”

本節の方式は10月デモ限りである。 恒久的にはIAMドメインのUSERを結合して解決する。

TELEMETRY_DISPLAY_DEFAULTを置き、機体ごとにoperator_idoperator_namestatusを持つ。 理由は2つあり、引く条件が違う。

1. operator_nameは複写元が存在しない(全機体が対象)

複写元としているUSER.display_nameのIAMドメインは、utm-backendに実体が無い。db/schema/iamスキーマもユーザー表も無く、ASSETorganization_idregistered_byもFK制約を持たない 参照だけの列である(db/schema/asset.sqlのコメント「ORGANIZATION・USER(IAMドメイン)はこの スキーマの外」)。認証自体が10月デモのスコープ外で、実行中のユーザー・組織も定数である (docs/policy/flight-planning/forDemo/AuthenticationSpecification_Flightplanning_forDemo.md)。

したがってoperator_nameは他社機だけでなく自組織の機体でも解決できない。飛行計画の有無に かかわらず、常に本テーブルから引く。動態を表示する機体には行を作る必要がある。

2. operator_idstatusは飛行計画を解決できない機体の既定値

飛行計画を解決できればFLIGHT_PLAN.created_byFLIGHT_PLAN.statusから複写できるため、 本テーブルは引かない。解決できない機体(前節の他社UTM機)だけが対象になる。旧USSはこの場合を 空欄のまま表示していたが、statusはGeoSpatialのAPIが検索条件に採るため(geospatial-api-design.md「6. 各APIの検索仕様」)、 NULLのままだと絞り込みで機体が消える。

運航調整で収集した他社計画から補うことはできない。COORDINATION.conflictionDIPS_FLIGHT_PLANも機体識別子の列を持たず、テレメトリ側のreported_uas_idと突き合わせられない ためである。

同じ趣旨の暫定表は運航調整ドメインにもある(COORDINATION.uss_default_info。DIPSの重複通知に 含まれない情報の既定値を持つ)。IAM不在と他Operator機の扱いはfollowups.mdおよび Issue #135で扱う。

却下の記録は旧USSから引き継ぐ

Section titled “却下の記録は旧USSから引き継ぐ”

TELEMETRY_DEAD_LETTERは10月デモのために新たに足すものではない。USSの取り込み実装は 2024年9月からcrid.t_telemetry_special_valuesへ却下を記録しており、実装を流用する以上この テーブルは要る。本設計はその置き換えであり、増える作業は列名とreasonの値域の付け替えに限る。

残す目的は、受信したが動態として現れない事象を後から追えるようにすることである。 「機体が送信しているのに表示されない」という問い合わせに対して、そもそも届いていないのか、 届いたが却下されたのかを区別できる。

uas_idに一致する機体がASSETに無い受信は、テレメトリとしては記録しない。組織スコープを 決められず、TELEMETRYCURRENT_TELEMETRYorganization_idをNOT NULLで要求するためである。 ただし却下はTELEMETRY_DEAD_LETTERUNKNOWN_AIRCRAFTとして残す。

当初は却下記録にも残さない判断をしていたが、理由として挙げていた「組織スコープを決められず テナント分離の外に置くことになる」はTELEMETRY_DEAD_LETTERには当てはまらない。この表は もともとorganization_idを持たずRLSの対象外で、raw_payloadに位置情報・機体識別子を含み得る ことも織り込み済みである(図1 カラム補足)。

残す側に倒すのは、却下の追跡経路を1本にするためである。他の却下はすべてこの表に入るため、 未登録機体だけがログにしか残らないと「送っているのに表示されない」の切り分けで手段が変わる。 これは本テーブルを置く目的そのものである(却下の記録は旧USSから引き継ぐ)。

[!NOTE] 未登録機体からの受信は、1機が送り続ける限り毎秒積まれる。保持期間とライフサイクルは 本ドメイン共通の未決事項(followups.md)に含めており、そこで併せて決める。

PR #29のTO-BEに対して、本設計が外している点と理由。 いずれもα版で寄せる。

TO-BE本設計外す理由
operator_idoperator_namestatusを複写しないTELEMETRYCURRENT_TELEMETRY双方に複写を残す表示のたびに飛行計画へ結合すると、動態表示の読み出し経路に別ドメインへの結合が入る。10月デモは複写で単純に保つ(結合へ移すと取り込み側と参照側の両方を変えることになる)
direction_unitspeed_unitを持たない(単位は列名で表す)残す取り込み実装が書き込む列のため
FLIGHT_SESSIONを介して飛行計画と結ぶFLIGHT_PLANを直接参照する飛行セッションの実体がまだないため
テレメトリの受信を契機に飛行計画を自動で飛行中へ遷移させ、運航状態で対応づける状態を使わず観測時刻で対応づける自動遷移を10月デモでは実装しないため(飛行計画は時刻で対応づける
UAS_REMOTE_IDで識別子を正規化し1回の索引検索にする旧USSの4カラムOR検索のまま正規化には機体登録側の対応が要る。デモの規模では性能上の問題にならない。ただし新データモデルのASSET側にはasset.serial_numberasset_uas_dips_attrs.registration_symbolの2列しかなく、uasUtmIduasSpecificSessionIdに相当する列が実在しない。この2列分の実体欠如はfollowups.mdへ登録した
uas_nicknameを持たずASSETの結合で解決する複写を持つ表示のたびにASSETへ結合すると、動態表示の読み出し経路に別ドメインへの結合が入る。結合自体は今日でも可能(asset.assetは実在する)で、operator_nameの「結合先が無い」とは別の理由である。複写値が陳腐化する点は10月デモでは許容する(図1 カラム補足
operator_nameUSERの結合で解決するTELEMETRY_DISPLAY_DEFAULTから機体ごとに埋めるIAMドメインの実体がutm-backendに無く、自組織の機体でも結合できないため(表示用の値は機体ごとの既定値で埋める
operator_idstatusは飛行計画由来の値だけを持つ飛行計画が引けない機体はTELEMETRY_DISPLAY_DEFAULTの既定値で埋める他社UTMの機体を表示するためのデモ限りの措置(同上)。α版で他Operator機を正式に扱えば不要になる
CURRENT_TELEMETRY.latest_report_idで射影元を指す列を持たずaircraft_idobserved_atで対応づける旧USSの実装は履歴とカレントへ同じ値の並びを書くため、履歴側の主キーをカレントへ渡すには取り込み側の改修が要るため

以降の2節は移植のための付属資料である。設計そのものは図1設計方針が正で、本節は取り込み実装を移し替えるときの対応表として置く。

旧USSの列と本設計の対応を示す。構造を変える変更は加えない。

旧USS本設計変更の理由
uss.t_telemetry / crid.t_current_telemetry が別DBインスタンス同一DB(Aurora)内のtelemetryスキーマに置く(他ドメインのflight_planningassetairspace_restrictionと並ぶ)射影が壊れたとき履歴から再構築できるようにするため。接続先の設定変更で足り、実装は変えない
locationpositionGeoSpatialのAPI設計がTELEMETRY.positionを前提に書かれているため
timestampobserved_at予約語を避け、受信時刻との区別を名前で表すため
created_at(INSERT時刻)received_at実質的に受信時刻であり、意味を名前に出すため
uas_id(文字列)reported_uas_id(生値の保持)とaircraft_idASSET.id)に役割を分ける旧USSもuas_entity_id列(oi.uas_id=飛行計画の割当機体に由来)を書いており、UPSERTのキーを文字列から機体IDへ移す。列名はflight-plan-er.mdFLIGHT_PLAN_PILOT_ASSIGNMENT.aircraft_id(同じくASSETへのFK)に揃える。ただし同カラムは(flight_plan_revision_id, pilot_id, aircraft_id)のN:M関係で1リビジョンに複数機体が紐付き得る一方、テレメトリ側は1件のテレメトリに1機体(受信時点で飛行計画照会により解決した1件)を前提にする。この差はテレメトリが「その時点でどの機体が送信したか」という事実を1件だけ持てばよいためで、複数機体のうちどれと紐付けるかの選択自体は取り込み実装(飛行計画照会)側が担い、本カラムはその結果を受け取るだけである
flight_plan_iduss.t_operational_intentのID)flight_plan_idFLIGHT_PLAN.id参照先を新しい飛行計画ドメインへ向けるため
(なし)flight_plan_revision_id受信時点の計画内容を復元するため(リビジョンも持つ
(なし)organization_idRLSの分離キーを持たせるため
(なし。高度変換の結果)altitude_agl_mGeoSpatialが対地高度基準で返すため。取り込みが同梱した変換で導出して書き込む(対地高度は受信時に充填する
statusVARCHARstatusenum複写元のFLIGHT_PLAN.statusの実体はflight_planning.flight_plan_status_type(スキーマ修飾のENUM型)である。複写列は値の複製にとどめ、複製として型もずれないよう同じ型をスキーマをまたいで直接参照する(別にtelemetryスキーマ側で複製の型を定義しない)
crid.t_telemetry_special_valueserror_type(1〜5の整数)・json_dataerror_messageTELEMETRY_DEAD_LETTERreason(enum)・raw_payloaddetail整数コードの意味がテーブルから読めないため。値の対応は取り込み実装のTelemetryCheckが持つ
created_user / updated_user / updated_at / is_delete引き継ぐ(削除しない)情報量はないが、削除すると取り込み実装のINSERT列を変えることになるため10月デモでは残す。α版で落とす

telemetry-service-protoが受け取るSQSメッセージ(Telemetry.javaのJSONフィールド)と、 TELEMETRY列の対応を示す。実装は同じ値の並び(Telemetry.toString())を TELEMETRYへのINSERTとCURRENT_TELEMETRYへのUPSERTの両方に使うため、 この対応はCURRENT_TELEMETRYが持つ列に限り同じ列名でそのまま当てはまる。ただし CURRENT_TELEMETRYは図1のとおりflight_plan_revision_iddirection_unitspeed_unit・ 各accuracy系4列・ua_typeclassification_typecategory_euclass_euに対応する列を 持たないため、これらはUPSERT対象に含まれない(図を単一の正とする の規約により、この対応表と図が食い違う場合は図を正とする)。取り込み実装の旧USS からの引き継ぎは旧USSスキーマとの対応を参照。

SQS受信フィールドTELEMETRY備考
uas_idreported_uas_id受信生値をそのまま保持する
longitudelatitudealtitude_geodeticpositionPostGISのPOINTZへ組み立てる。longitudelatitudeは1e7倍した整数文字列(7桁目に小数点を挿入して復元)、altitude_geodeticはそのまま数値として使う
directiondirection
ua_typeua_type
classification_typeclassification_type
category_eucategory_eu
class_euclass_eu
timestampobserved_atエポック秒から変換。秒未満切り捨ての既知の問題はfollowups.md参照
timestamp_accuracytimestamp_accuracy
vertical_accuracyvertical_accuracy
horizontal_accuracyhorizontal_accuracy
speed_accuracyspeed_accuracy
speed_horizontalspeed
speed_verticalspeed_vertical
operator_id(SQS自身が持つ値)(書き込まない)受信するが使わない。運航者は次項の飛行計画照会の結果を正とする(本表の後の記述を参照)
(なし。機体照会の結果)aircraft_idorganization_iduas_idをキーにASSETを照会して解決する。ASSETに無い機体のテレメトリは破棄する(旧USSの実装の挙動を変えないため。TO-BEから外している点参照)。TELEMETRY_DEAD_LETTER.reasonの値域にも未登録機体に対応する値はなく、却下記録には残さない
(なし。飛行計画照会の結果)flight_plan_idflight_plan_revision_idoperator_idstatus解決した機体に紐づく飛行計画をobserved_atで対応づけた結果(飛行計画は時刻で対応づける)で埋める。現行の照会処理の命名はOiDAOだが、Backendリポジトリには「OI(Operational Intent)」という概念自体がなく実装時に改める。計画が引けない場合はoperator_idstatusTELEMETRY_DISPLAY_DEFAULTから埋め、そこにも行がなければNULLのままにする(表示用の値は機体ごとの既定値で埋める
(なし。機体照会の結果)uas_nickname機体を解決した際にASSET.nicknameを一緒に取得して複写する。機体を解決できないテレメトリは破棄するため、取り込み側が複写を実装すれば常に埋まる。その実装はtelemetry-service-proto PR #7で、反映されるまでは全行NULLになる(followups.mdの台帳)
(なし。既定値の参照結果)operator_name飛行計画の有無によらずTELEMETRY_DISPLAY_DEFAULTから埋める。複写元としているIAMのUSERがutm-backendに実体を持たないためである(同上)

SQSメッセージ自身もoperator_idを持つが、本設計では書き込まない。テレメトリの送信元 (機体・コマンドサービス)が申告する値ではなく、UTMが飛行計画から解決した値を正とする。 申告値は送信側の設定に依存し、UTMが登録を確認した運航者と一致する保証がないためである。

テーブル名・カラム名はデータモデル設計ドキュメントの規約に従う。 本ドメインでの逸脱は次の2つで、改名しない。

  • TELEMETRY … 追記のみのイベントだが_EVENTを付けない。テレメトリは状態遷移ではなく 観測値の報告で、TELEMETRY_EVENTでは何のイベントか分からないため
  • CURRENT_TELEMETRY … 修飾語(CURRENT_)が先頭に来ており、他ドメインの<親>_<種別>(例: ASSET_UAS_ATTRS)という親を先頭に置く語順と逆である。他ドメインは現在値をリソース本体の 導出キャッシュ列(例: FLIGHT_PLAN.status)として持ち、独立テーブルの1行として持つ前例が 本リポジトリにないため、従うべき既存の語順そのものがない。PR #29 のTO-BEと同じ名前を採ることを優先し、TELEMETRY_CURRENTへの改名は行わない

本ドメインの未決事項は設計ドキュメント横断のフォローアップ台帳へ登録する。