Skip to content

Conversation

@harutiro
Copy link
Member

@harutiro harutiro commented Nov 4, 2025

Summary by CodeRabbit

  • 新機能

    • デバイス接続状態表示と切断理由ラベルを追加。
    • 接続復旧画面(自動再接続・手動再検出・進捗表示)を追加。
    • 取得データのCSV出力・ZIP化・共有・削除機能を追加。
    • マルチアンテナ統合表示、座標変換、セッションID公開、アンテナ別データ管理、フロアマップIDを渡すナビゲーションを導入。
  • 改善

    • 接続・ペアリング管理と再接続の挙動・ログを強化。
    • キャリブレーション精度向上(重み付けIRLS)と外れ値検出(IQR)を導入。
    • UIフロー整理とデバッグ設定(スキップ)追加。
  • 除去

    • 旧センシング画面とProjectProgressベースの進捗管理を削除。

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 4, 2025

Walkthrough

フロアマップIDのルート伝搬、接続状態モデルと接続管理の拡張、接続回復 UI と自動再接続、Realtime データのアンテナ単位拡張と座標変換ユースケース、CSV/ZIP エクスポート機能、ProjectProgress の削除と SwiftData スキーマ整理を行う大規模移行です。

変更一覧

コホート / ファイル(s) 変更内容
接続状態定義
UWBViewerSystem/Domain/Entity/DeviceConnectionState.swift
新規 public enum DeviceConnectionStateDisconnectionReason を追加。接続ケース、判定プロパティ、description を含む。
接続管理ユースケース
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
接続エラー状態と最後に切断したデバイスの公開プロパティ、ペアリング API(pair/unpair/clear/get)、antennaPairings 公開、endpoint↔deviceName マップ追加、sendMessage を全接続エンドポイントへブロードキャスト、切断時の deviceName 中心の処理追加。
接続回復 UI
UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift
新規 SwiftUI コンポーネントを追加(表示バインド、再接続試行ロジック、最大試行回数、バックオフ、Task/MainActor 使用)。
ルーティング/ナビゲーション(floorMapId 伝搬)
UWBViewerSystem/Presentation/Router/...
Router.swift, NavigationRouter.swift, SensingFlowNavigator.swift, NavigationRouterModel.swift
複数 Route ケースに floorMapId: String を追加。SensingFlowNavigator に currentFloorMapIdproceedToNextStep(floorMapId:)、NavigationRouterModel.navigateTo に resetStack オプション追加。
フロアマップ関連ビュー群
UWBViewerSystem/Presentation/Scenes/FloorMapTab/...
例: AntennaPositioningView.swift, PairingSettingView.swift, AutoAntennaCalibrationView.swift, DataCollectionView.swift, FloorMapView.swift
多数の画面で floorMapId を導入。AutoAntennaCalibration に接続監視・flowNavigator 統合と ConnectionRecovery シート追加。FloatingControlPanel 等の呼び出しを更新。
キャリブレーション / ペアリング VM
.../AutoAntennaCalibrationViewModel.swift, .../PairingSettingViewModel.swift
接続監視、再接続フラグ・状態、前のタグへ戻る操作、ペアリング処理を ConnectionManagementUsecase へ委譲、フロアマップ読み込み補助を追加。
Realtime データモデル拡張
UWBViewerSystem/Domain/Entity/RealtimeData.swift, UWBViewerSystem/Domain/DataModel/SwiftDataModels.swift
RealtimeDataantennaId 追加。PersistentRealtimeDataantennaId/sessionId を追加しマッピング更新。TagObservation / IntegratedTagPosition / IntegratedCoordinateRecord 等を追加。 PersistentProjectProgress を削除。
SwiftData スキーマ/リポジトリ整理
UWBViewerSystemApp.swift, UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift, テストモック等
PersistentProjectProgress と ProjectProgress 関連 CRUD を削除。Realtime データ保存/読み出しに sessionId フィルタを導入。モック/テストを整備。
座標変換ユースケース
UWBViewerSystem/Domain/Usecase/RealtimeCoordinateTransformUsecase.swift
極座標→局所直交→フロアマップグローバル座標への変換(単点・バッチ)、アンテナ位置取得 API を提供する新規 public Usecase を追加。
RealtimeData 処理拡張
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
複数アンテナ対応のデータマップ、globalCoordinates、座標変換連携、フロアマップ ID 管理、重心/統合タグ位置計算などを追加。
DataCollection 再設計
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift, DataCollectionViewModel.swift
floorMapId 導入、SwiftData 統合、フロアマップ中心 UI へ再構成、CSV エクスポート統合、初期化パス変更。
CSV / セッション ZIP / ファイル管理
UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift, UWBViewerSystem/Devices/File/ZipFileManager.swift, UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift, Info.plist
CSV 出力ユーティリティ、ZIP 作成ユーティリティ、セッション ZIP エクスポート/削除 Usecase を追加。Info.plist にファイル共有関連キーを追記。
旧 SensingTab の削除
UWBViewerSystem/Presentation/Scenes/SensingTab/SensingView.swift, SensingViewModel.swift
旧 SensingView と ViewModel を削除(機能を DataCollection 等へ統合)。
データ表示のシェア/削除対応
UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/...
セッション ZIP 共有と削除フロー追加。HistorySessionCardonShare/onDelete を受け取る形へ変更。
設定にデバッグトグル追加
UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsView.swift, SettingsViewModel.swift
skipCalibration トグルを追加、UserDefaults に永続化、UI 表示を追加。
数値処理・前処理拡張
UWBViewerSystem/Domain/Utils/AntennaAffineCalibration.swift, SensorDataProcessor.swift
AntennaAffineCalibration に IRLS/重み付き推定を追加。SensorDataProcessor に IQR ベースの外れ値除去を導入(設定フラグ追加)。
テスト更新
UWBViewerSystemTests/...
ProjectProgress 関連のモックとテストを削除。AntennaAffineCalibration と SensorDataProcessor に対する IRLS/IQR テストを追加(重複ブロックあり)。

シーケンス図

sequenceDiagram
    participant AutoCalView as AutoAntennaCalibrationView
    participant VM as AutoAntennaCalibrationViewModel
    participant ConnUC as ConnectionManagementUsecase
    participant Recovery as ConnectionRecoveryView

    AutoCalView->>VM: onAppear -> setupConnectionMonitoring()
    VM->>ConnUC: subscribe hasConnectionError / connectedDeviceNames
    ConnUC-->>VM: hasConnectionError = true, lastDisconnectedDevice
    VM->>AutoCalView: showConnectionRecovery = true
    AutoCalView->>Recovery: present sheet
    alt 自動再接続
        Recovery->>ConnUC: request auto-reconnect (restart advertise/discovery, reset)
        ConnUC-->>Recovery: connection success (deviceName/endpoint)
        Recovery->>VM: dismiss sheet
        VM->>AutoCalView: resume/restore state
    else 手動キャンセル/失敗
        Recovery->>ConnUC: stop attempts or resume discovery
        Recovery->>AutoCalView: dismiss sheet
    end
Loading
sequenceDiagram
    participant FloorMapSetting as FloorMapSettingView
    participant VM as FloorMapSettingViewModel
    participant Repo as SwiftDataRepository
    participant Navigator as SensingFlowNavigator

    FloorMapSetting->>VM: ユーザが「保存」
    VM->>Repo: save floorMap (async)
    Repo-->>VM: returns floorMapId or nil
    alt 保存成功
        VM->>Navigator: proceedToNextStep(floorMapId)
        Navigator->>Navigator: push route .systemCalibration(floorMapId)
    else 保存失敗
        VM-->>FloorMapSetting: show error / abort navigation
    end
Loading

推定コード レビュー工数

🎯 4 (Complex) | ⏱️ ~60 分

特に注意すべき箇所:

  • RealtimeCoordinateTransformUsecase の数式(極座標→直交、回転・並進の順序)と単位系確認。
  • ConnectionManagementUsecase の endpointId↔deviceName マップ、切断時のクリーンアップ、ペアリング整合性。
  • ConnectionRecoveryView / 各 ViewModel の非同期再接続ロジック(Task、リトライ、バックオフ、MainActor 同期)。
  • SwiftData スキーマ変更(PersistentProjectProgress 削除)に伴うリポジトリ/モック/テストの整合性。
  • RealtimeData / PersistentRealtimeData の antennaId/sessionId 永続化・読み出しおよび session フィルタ適用箇所。
  • AntennaAffineCalibration の IRLS 実装(数値安定性、ゼロ重み・特異行列ハンドリング)。

Possibly related PRs

うさぎの詩

🐰 コードの畑でぴょんと一歩、
切断が来てもまたつなぐよ。
アンテナに耳をすませば座標が歌い、
CSV 包んで風に乗る、ぴょん。

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning PRのタイトル「connection recovery feature」は、多数のファイル変更(約40ファイル)における主要な変更内容と部分的に関連しています。タイトルは接続回復機能に焦点を当てていますが、実際にはペアリング状態の同期修正、リアルタイムデータのクリア処理、フロアマップID統合、CSV出力機能、座標変換、複数アンテナ対応など、より広範な変更が含まれています。 タイトルをより正確に反映させてください。例:「Add connection recovery with pairing sync and multi-antenna support」または「connection recovery and realtime data refactoring」など、主要な複数の変更領域を示すタイトルが適切です。
Docstring Coverage ⚠️ Warning Docstring coverage is 62.44% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/retry-connection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (1)

189-201: 自動接続の動作を確認してください

デバイス発見時に自動的に接続リクエストを送信していますが、この動作はユーザーの意図しない接続を引き起こす可能性があります。特に複数のデバイスが検出される環境では問題となる可能性があります。ユーザーが明示的に接続を開始するフローを検討してください。

また、デバッグ用のprint文(lines 191, 195, 197, 199)が多数残されています。コーディングガイドラインに従い、不要なデバッグコードは削除してください。

Based on coding guidelines

UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift (2)

173-232: 再接続ロジックの改善を検討してください

自動再接続時にresetAll()を呼び出していますが、これは全ての既存接続を切断してしまいます。複数デバイスが接続されている環境では、切断されていないデバイスまで影響を受ける可能性があります。

特定の切断されたデバイスのみを対象とした再接続ロジックを検討してください。lastDisconnectedDeviceの情報を活用して、そのデバイスのみを再接続対象とすることができます。


114-135: アクセシビリティの改善を検討してください(任意)

自動再接続ボタンの状態変化がアクセシビリティツールに適切に伝わるよう、.accessibilityLabel.accessibilityHintの追加を検討してください。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between feb32bb and e31c357.

📒 Files selected for processing (5)
  • UWBViewerSystem/Domain/Entity/DeviceConnectionState.swift (1 hunks)
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (5 hunks)
  • UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Domain/Entity/DeviceConnectionState.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift
**/*Usecase.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Usecaseのファイル名は「〜Usecase.swift」とする

Files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to Devices/**/*.swift : UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to Devices/**/*.swift : UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する

Applied to files:

  • UWBViewerSystem/Domain/Entity/DeviceConnectionState.swift
🧬 Code graph analysis (2)
UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift (1)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (6)
  • getConnectedDeviceCount (161-163)
  • resetAll (113-141)
  • startAdvertising (72-79)
  • startDiscovery (86-92)
  • hasConnectedDevices (157-159)
  • stopDiscovery (94-96)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (2)
UWBViewerSystem/Devices/NearByConnection/NearByConnectionApi.swift (1)
  • requestConnection (194-216)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (1)
  • removeDisconnectedDevice (102-107)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run Swift Tests
🔇 Additional comments (6)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (1)

237-242: LGTM: 接続時の状態管理が適切です

endpointIdとdeviceNameのマッピング保存と、エラーフラグのクリアが適切に実装されています。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (1)

52-60: LGTM: 接続復旧UIの統合が適切です

ConnectionRecoveryViewのモーダル表示が適切に実装されています。iOS向けのpresentationDetentsの使用も適切です。

UWBViewerSystem/Domain/Entity/DeviceConnectionState.swift (2)

4-67: LGTM: 接続状態モデルの設計が適切です

DeviceConnectionStateの設計は明確で、各状態が適切にモデル化されています。computed propertiesも読みやすく実装されています。


70-81: LGTM: 切断理由の列挙型が適切です

DisconnectionReasonの定義は明確で、一般的な切断シナリオを適切にカバーしています。

UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift (1)

7-36: LGTM: ビューの構造が適切です

プロパティラッパーの使用とビュー構造が適切に実装されています。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (1)

50-51: LGTM: プロパティの定義が適切です

接続復旧画面の表示制御用プロパティが適切に定義されています。

Comment on lines +214 to +217
print("📥 [ConnectionManagement] 接続リクエスト受信: \(deviceName) (\(endpointId))")
// 自動的に接続を承認
accept(true)
print("✅ [ConnectionManagement] 接続リクエストを承認しました")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

デバッグ用のprint文を削除してください

コーディングガイドラインに従い、デバッグ用のprint文は削除してください。

Based on coding guidelines

🤖 Prompt for AI Agents
In UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift around lines
214 to 217, remove the two debug print statements ("📥 [ConnectionManagement]
接続リクエスト受信..." and "✅ [ConnectionManagement] 接続リクエストを承認しました") to comply with the
coding guidelines; if runtime visibility is required replace them with the
project's standard logging API (e.g., logger.info/debug) or remove entirely,
keeping the accept(true) call unchanged.

Comment on lines +254 to 274
// endpointIdからdeviceNameを取得
let deviceName = self.endpointToDeviceNameMap[endpointId]

self.connectedEndpoints.remove(endpointId)
self.connectState = "端末切断: \(endpointId)"
self.connectState = "端末切断: \(deviceName ?? endpointId)"

// 接続エラーフラグを設定
self.hasConnectionError = true
self.lastDisconnectedDevice = deviceName

// RealtimeDataUsecaseに端末切断を通知
// endpointIdではなくdeviceNameが必要だが、ここではendpointIdしかないので
// 接続中のdeviceNamesから削除する
if let deviceName = self.connectedDeviceNames.first(where: { _ in true }) {
if let deviceName {
self.connectedDeviceNames.remove(deviceName)
self.realtimeDataUsecase?.removeDisconnectedDevice(deviceName)
print("📱 RealtimeDataUsecaseに端末切断を通知: \(deviceName)")

// マッピングから削除
self.endpointToDeviceNameMap.removeValue(forKey: endpointId)
} else {
print("⚠️ endpointId \(endpointId) に対応するdeviceNameが見つかりません")
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

LGTM: 切断時の状態管理が適切です

endpointIdからdeviceNameを取得し、マッピングのクリーンアップも適切に行われています。マッピングが見つからない場合の警告ログも有用です。

ただし、Line 268のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

🤖 Prompt for AI Agents
In UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift around lines
254 to 274, remove the debug print on line 268 ("📱 RealtimeDataUsecaseに端末切断を通知:
\(deviceName)") to comply with coding guidelines; if persistent audit is
required replace it with the project's standard logging mechanism (e.g., use the
shared logger or a configurable logger call) otherwise simply delete the print
statement so no debug prints remain in production code.

Comment on lines 235 to 249
private func restartDiscovery() {
print("🔍 手動で再度検索を開始")

// エラーフラグをクリア
self.connectionUsecase.hasConnectionError = false
self.connectionUsecase.lastDisconnectedDevice = nil

// 検索と広告を再開
self.connectionUsecase.stopDiscovery()
self.connectionUsecase.startDiscovery()
self.connectionUsecase.startAdvertising()

// 画面を閉じる
self.isPresented = false
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

デバッグ用のprint文を削除してください

Line 236のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

🤖 Prompt for AI Agents
In UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift around
lines 235 to 249, remove the debug print statement on line 236 ("🔍 手動で再度検索を開始")
so the method no longer outputs debug text; keep the remaining logic (clearing
error flags, stopping/starting discovery and advertising, and dismissing the
view) unchanged to preserve behavior and coding-guideline compliance.

Comment on lines 225 to 237
/// 接続監視を設定
private func setupConnectionMonitoring() {
// hasConnectionErrorの変更を監視
ConnectionManagementUsecase.shared.$hasConnectionError
.sink { [weak self] hasError in
guard let self else { return }
if hasError {
print("⚠️ 接続断検出: 接続復旧画面を表示します")
self.handleConnectionError()
}
}
.store(in: &self.cancellables)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

接続監視の実装が適切です

Combineを使った接続エラーの監視が適切に実装されています。weak selfの使用によりメモリリークも防止されています。

ただし、Line 232のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

🤖 Prompt for AI Agents
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
around lines 225 to 237: the debug print at line 232 must be removed per coding
guidelines; replace the print with a proper logging call (e.g., use the app's
logger or OSLog) or simply remove it if no runtime log is required, keeping the
existing ConnectionManagementUsecase sink and self.handleConnectionError()
behavior unchanged and preserve the weak self capture and cancellable storage.

Comment on lines 239 to 257
/// 接続エラーハンドリング
private func handleConnectionError() {
// データ収集中・キャリブレーション中の場合は停止
if self.isCollecting || self.isCalibrating {
print("⚠️ データ収集/キャリブレーションを中断します")
self.isCollecting = false
self.isCalibrating = false
}

// エラーメッセージを設定
if let deviceName = ConnectionManagementUsecase.shared.lastDisconnectedDevice {
self.errorMessage = "デバイス「\(deviceName)」との接続が切断されました"
} else {
self.errorMessage = "接続が切断されました"
}

// 接続復旧画面を表示
self.showConnectionRecovery = true
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

エラーハンドリングが適切です

接続エラー発生時に実行中の処理を適切に停止し、ユーザーにわかりやすいエラーメッセージを提供しています。

ただし、Lines 243と256のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

🤖 Prompt for AI Agents
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
around lines 239 to 257: remove debug print statements per coding guidelines;
specifically delete the print("⚠️ データ収集/キャリブレーションを中断します") on line ~243 and any
other stray debug prints in this block (e.g., at ~256) so that logging follows
production standards, leaving only the state updates, errorMessage assignment,
and showConnectionRecovery toggle.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (1)

235-264: デバッグprintを削除してください

setupConnectionMonitoringhandleConnectionError に追加された print はデバッグ用のまま残っています。コーディングガイドラインではデバッグ用コードを残さないことが求められているため、これらは削除するか、必要であれば正式なロガーに置き換えてください。
提案diff:

         ConnectionManagementUsecase.shared.$hasConnectionError
             .sink { [weak self] hasError in
                 guard let self else { return }
                 if hasError {
-                    print("⚠️ 接続断検出: 接続復旧画面を表示します")
                     self.handleConnectionError()
                 }
             }
             .store(in: &self.cancellables)
@@
         // データ収集中・キャリブレーション中の場合は停止
         if self.isCollecting || self.isCalibrating {
-            print("⚠️ データ収集/キャリブレーションを中断します")
             self.isCollecting = false
             self.isCalibrating = false
         }

As per coding guidelines

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e31c357 and 4c1963f.

📒 Files selected for processing (3)
  • UWBViewerSystem/Domain/Usecase/AutoAntennaCalibrationUsecase.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (2 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (4 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Domain/Usecase/AutoAntennaCalibrationUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
**/*Usecase.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Usecaseのファイル名は「〜Usecase.swift」とする

Files:

  • UWBViewerSystem/Domain/Usecase/AutoAntennaCalibrationUsecase.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧠 Learnings (1)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
🧬 Code graph analysis (2)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (1)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (2)
  • goToPreviousTagPosition (354-384)
  • proceedToNextTagPosition (347-351)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (1)
UWBViewerSystem/Domain/Usecase/AutoAntennaCalibrationUsecase.swift (3)
  • clearData (311-315)
  • clearData (318-324)
  • clearData (331-336)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run Swift Tests

Comment on lines 379 to 380
print("⬅️ 現在のタグ(\(currentTag.tagId))を取り消して前のタグ位置に戻る: \(self.trueTagPositions[self.currentTagPositionIndex].tagId)")

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

デバッグprintの削除をお願いします

goToPreviousTagPosition 内の print("⬅️ ...") もデバッグログのまま残っています。ガイドラインに従い削除するか、必要な場合は正式なロギング手段へ置換してください。
提案diff:

-            print("⬅️  現在のタグ(\(currentTag.tagId))を取り消して前のタグ位置に戻る: \(self.trueTagPositions[self.currentTagPositionIndex].tagId)")

As per coding guidelines

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
print("⬅️ 現在のタグ(\(currentTag.tagId))を取り消して前のタグ位置に戻る: \(self.trueTagPositions[self.currentTagPositionIndex].tagId)")
🤖 Prompt for AI Agents
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
around lines 379-380: the debug print call print("⬅️ 
現在のタグ(\(currentTag.tagId))を取り消して前のタグ位置に戻る:
\(self.trueTagPositions[self.currentTagPositionIndex].tagId)") should be removed
or replaced with the app's formal logging mechanism; delete the print statement,
or replace it with a structured logger/OSLog/Swift.Log call at the appropriate
level (e.g., debug) and include the same contextual message and variables,
ensuring no stray debug prints remain.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c1963f and 8585672.

📒 Files selected for processing (5)
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (6 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (4 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (7 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (7 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift
**/*Usecase.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Usecaseのファイル名は「〜Usecase.swift」とする

Files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧠 Learnings (8)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Applied to files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する

Applied to files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to Devices/**/*.swift : UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する

Applied to files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*Usecase.swift : Usecaseのファイル名は「〜Usecase.swift」とする

Applied to files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*ViewModel.swift : ViewModelのファイル名は「〜ViewModel.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift
📚 Learning: 2025-09-23T14:37:07.451Z
Learnt from: harutiro
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: :0-0
Timestamp: 2025-09-23T14:37:07.451Z
Learning: UWBViewerSystemでUserDefaults変更通知のテストを行う際は、NotificationCenterの非同期処理完了を確実に待機するため、1.5秒程度の待機時間とポーリング方式の検証を使用する。これにより競合状態やタイミング問題を回避できる。

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧬 Code graph analysis (5)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (2)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (1)
  • setRouter (32-34)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (3)
  • setFlowNavigator (235-237)
  • goToPreviousTagPosition (372-408)
  • proceedToNextTagPosition (365-369)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (3)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)
  • pairAntennaWithDevice (205-276)
UWBViewerSystem/Devices/NearByConnection/NearByConnectionApi.swift (1)
  • requestConnection (194-216)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (1)
  • removeDisconnectedDevice (102-107)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift (1)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)
  • saveAndProceedToNextStep (344-355)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (3)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (3)
  • pairAntennaWithDevice (178-181)
  • unpairAntenna (184-187)
  • clearAllPairings (190-193)
UWBViewerSystem/Presentation/Router/NavigationRouterModel.swift (1)
  • push (25-31)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (1)
  • proceedToNextStep (44-78)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (1)
UWBViewerSystem/Domain/Usecase/AutoAntennaCalibrationUsecase.swift (3)
  • clearData (311-315)
  • clearData (318-324)
  • clearData (331-336)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run Swift Tests

Comment on lines 525 to +531
self.antennaPairings.removeAll { $0.device.id == endpointId }
self.isConnected = !self.antennaPairings.isEmpty
self.savePairingData()

// ConnectionManagementUsecaseからも削除
if let pairing = self.antennaPairings.first(where: { $0.device.id == endpointId }) {
self.connectionUsecase.unpairAntenna(antennaId: pairing.antenna.id)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

ペアリング解除が ConnectionManagementUsecase に反映されません

self.antennaPairings.removeAll { … } を実行した直後に first(where:) でペアリングを探しているため、常に nil になり connectionUsecase.unpairAntenna(...) が呼ばれなくなっています。その結果、接続が切れた端末のペアリング情報が ConnectionManagementUsecase 側に残り続け、後続処理や復旧フローで不整合が発生します。

-        self.antennaPairings.removeAll { $0.device.id == endpointId }
-        self.isConnected = !self.antennaPairings.isEmpty
-
-        // ConnectionManagementUsecaseからも削除
-        if let pairing = self.antennaPairings.first(where: { $0.device.id == endpointId }) {
-            self.connectionUsecase.unpairAntenna(antennaId: pairing.antenna.id)
-        }
+        if let pairing = self.antennaPairings.first(where: { $0.device.id == endpointId }) {
+            self.connectionUsecase.unpairAntenna(antennaId: pairing.antenna.id)
+        }
+
+        self.antennaPairings.removeAll { $0.device.id == endpointId }
+        self.isConnected = !self.antennaPairings.isEmpty
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self.antennaPairings.removeAll { $0.device.id == endpointId }
self.isConnected = !self.antennaPairings.isEmpty
self.savePairingData()
// ConnectionManagementUsecaseからも削除
if let pairing = self.antennaPairings.first(where: { $0.device.id == endpointId }) {
self.connectionUsecase.unpairAntenna(antennaId: pairing.antenna.id)
}
if let pairing = self.antennaPairings.first(where: { $0.device.id == endpointId }) {
self.connectionUsecase.unpairAntenna(antennaId: pairing.antenna.id)
}
self.antennaPairings.removeAll { $0.device.id == endpointId }
self.isConnected = !self.antennaPairings.isEmpty
🤖 Prompt for AI Agents
In
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift
around lines 525 to 531, the code removes antennaPairings then tries to find the
same pairing with first(where:), which will always be nil so
connectionUsecase.unpairAntenna(...) is never called; to fix this, first locate
(or filter) the pairing(s) matching endpointId and call
connectionUsecase.unpairAntenna(antennaId:) for each found pairing before
removing them from self.antennaPairings, then update self.isConnected
accordingly so ConnectionManagementUsecase stays in sync.

- Added floorMapId property to AntennaPositioningView and updated its initialization.
- Modified AntennaPositioningViewModel to accept floorMapId for loading specific floor map data.
- Updated AutoAntennaCalibrationView and its ViewModel to handle floorMapId.
- Enhanced FloorMapSettingView to return floorMapId upon saving settings.
- Adjusted navigation in FloorMapView to pass floorMapId to subsequent views.
- Removed SensingView and SensingViewModel, consolidating sensing functionality.
- Updated DataCollectionView and its ViewModel to load floor map information based on floorMapId.
- Improved PairingSettingView and ViewModel to utilize floorMapId for loading relevant data.
- Cleaned up unnecessary notifications and preference updates related to floor maps.
…tionalities

- Added sessionId property to PersistentRealtimeData to track session information.
- Updated the toPersistent method in RealtimeData to accept sessionId.
- Modified saveRealtimeData method in SwiftDataRepository to utilize sessionId.
- Enhanced loadRealtimeData method to filter results based on sessionId.
- Updated SensingControlUsecase to manage sessionId and log relevant actions.
- Implemented createSessionDirectory method in SensingDataCSVExporter for organized data storage.
- Refactored CSV export methods to include sessionId and improve data handling.
- Adjusted DataCollectionView and DataCollectionViewModel to accommodate new session management features.
- Removed obsolete project progress tests from SwiftDataRepositoryTests.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (1)

479-513: loadRealtimeData(for:) の全件フェッチ&デバッグログはパフォーマンス面でリスクがあり、sessionId バリデーションも強化できそうです

現状の実装だと:

  • loadRealtimeData(for:) 冒頭で FetchDescriptor<PersistentRealtimeData>() による全件フェッチを行い、
  • その件数と最初の要素の sessionIdprint で出力したあと、
  • 改めて sessionId で絞り込んだフェッチを行い、その件数も print しています。

リアルタイムデータが増えてきた場合、この「デバッグ目的の全件フェッチ」が I/O/メモリ両面で無視できないコストになり得るのと、print#if DEBUG でガードされていないため、本番ビルドでもコンソール汚染+わずかながらパフォーマンス低下につながります。Coding guidelines でも「デバッグ用コードは削除する」とされているので、以下のように整理することをおすすめします。

あわせて、saveRealtimeData(_:sessionId:) 側で sessionId の空文字を明示的に弾いておくと、空のセッション ID を持つレコードが紛れ込むことを防げるのでデータ整合性の面でも安心です。

     public func saveRealtimeData(_ data: RealtimeData, sessionId: String) async throws {
-        let persistentData = data.toPersistent(sessionId: sessionId)
+        // セッションIDのバリデーション
+        guard !sessionId.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
+            throw RepositoryError.invalidData("セッションIDが空です")
+        }
+
+        let persistentData = data.toPersistent(sessionId: sessionId)
@@
-    public func loadRealtimeData(for sessionId: String) async throws -> [RealtimeData] {
-        // まず全データを取得して確認
-        let allDescriptor = FetchDescriptor<PersistentRealtimeData>()
-        let allData = try modelContext.fetch(allDescriptor)
-        print("📊 [DEBUG] SwiftData内の全リアルタイムデータ数: \(allData.count)")
-        if !allData.isEmpty {
-            print("📊 [DEBUG] 最初のデータのsessionId: \(allData[0].sessionId)")
-        }
-
-        // sessionIdフィールドで絞り込み
-        let predicate = #Predicate<PersistentRealtimeData> { $0.sessionId == sessionId }
-        let descriptor = FetchDescriptor<PersistentRealtimeData>(
-            predicate: predicate,
-            sortBy: [SortDescriptor(\.timestamp, order: .reverse)]
-        )
-
-        let persistentData = try modelContext.fetch(descriptor)
-        print("📊 [DEBUG] sessionId '\(sessionId)' で絞り込んだデータ数: \(persistentData.count)")
+    public func loadRealtimeData(for sessionId: String) async throws -> [RealtimeData] {
+        // sessionId フィールドで絞り込み
+        let predicate = #Predicate<PersistentRealtimeData> { $0.sessionId == sessionId }
+        let descriptor = FetchDescriptor<PersistentRealtimeData>(
+            predicate: predicate,
+            sortBy: [SortDescriptor(\.timestamp, order: .reverse)]
+        )
+
+        let persistentData = try modelContext.fetch(descriptor)
+        #if DEBUG
+            print("📊 [DEBUG] sessionId '\(sessionId)' で絞り込んだデータ数: \(persistentData.count)")
+        #endif

このようにすれば、本番環境で不要な全件フェッチとログ出力を避けつつ、デバッグ時には必要な情報だけを最小限のコストで確認できるようになると思います。
As per coding guidelines, デバッグ用途のコードはこのタイミングで整理しておくのが良さそうです。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (1)

246-292: CSV エクスポート時の座標データの整合性とセッションIDの扱いに注意が必要です

stopSensingexportSensingDataToCSV のフローは概ね良いのですが、いくつか重要なポイントがあります。

  1. globalCoordinates の内容と CSV 出力の整合性

    現状 exportSensingDataToCSV では

    let allRealtimeData = try await swiftDataRepository?.loadRealtimeData(for: sessionId) ?? []
    let sortedData = allRealtimeData.sorted { $0.timestamp < $1.timestamp }
    
    let result = try SensingDataCSVExporter.exportAllData(
        realtimeDataList: sortedData,
        globalCoordinates: self.globalCoordinates,
        startTime: sessionStartTime,
        processor: processor
    )

    のように、セッション全体の sortedData に対して、UI 側が保持している self.globalCoordinates: [String: Point3D] をそのまま渡しています。

    しかし self.globalCoordinates が「各デバイスの最新位置のみ」を保持する辞書である場合、SensingDataCSVExporter 側で各行ごとのグローバル座標を参照する際(globalCoordinates[data.deviceName])に、同一デバイスの全行が同じ座標になってしまいます。この点は SensingDataCSVExporter のコメントでも指摘しましたが、CSV の仕様として「各サンプル時刻の座標を出したい」のか「最終位置だけ分かればよい」のか、要件とのギャップが大きそうです。

    • セッション全体の軌跡を CSV にしたい場合は、ここで sortedData に対して再度座標変換を行い、サンプルごとの Point3D を渡す必要があります。
    • あるいは RealtimeDataUsecase 側で「時系列のグローバル座標」を保持する構造(例: [String: [Point3D]][RealtimeData.ID: Point3D])にするなど、設計の見直しが必要です。

    現状のままでは「大量のサンプルを持つセッションでも、各デバイスにつき1点分の意味しかない座標 CSV」が出力されることになるので、ここは要件に合わせて修正した方が良いと思います。

  2. swiftDataRepository が未設定の場合のログ

    try await swiftDataRepository?.loadRealtimeData(for: sessionId) ?? [] となっているため、リポジトリ未設定でも単に「データがありません」というログになり、根本原因が分かりにくいです。例えば以下のように分岐しておくとデバッグしやすくなります。

    guard let repository = self.swiftDataRepository else {
        print("⚠️ SwiftDataRepository が未設定のため、CSV エクスポートをスキップします")
        return
    }
    let allRealtimeData = try await repository.loadRealtimeData(for: sessionId)
  3. セッションIDのクリア処理が未実装

    stopSensing の中で

    // CSV出力完了後にSensingControlUsecaseのセッションIDをクリア
    await MainActor.run {
        // SensingControlUsecaseのcurrentSessionIdをクリア
        // これにより次のセンシングセッションで新しいIDが使用される
    }

    というコメントがありますが、実際には何もクリアしていません。SensingControlUsecase 側にセッションIDを明示的にリセットする API があるのであれば、ここで必ず呼ぶようにするか、そういった API がない場合はコメントを削除しておく方が混乱が少ないと思います。

上記3点のうち、特に1点目(時系列座標と CSV の齟齬)は、データ解析結果に直接影響するため優先度高めで見直しをおすすめします。

Also applies to: 338-391

♻️ Duplicate comments (2)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (1)

50-52: デバッグ用 print が多く残っているので整理をお願いします

このViewModel内で、今回の変更としてもかなりの数の print が追加/変更されています(例):

  • 接続監視まわり
    • Line 246: print("⚠️ 接続断検出: 接続復旧画面を表示します")
    • Line 257: print("🔌 接続デバイスの変更を検出: アンテナリストを再読み込みします")
  • 接続エラー処理
    • Line 268: print("⚠️ データ収集/キャリブレーションを中断します")
  • タグ戻し
    • Line 387: print("⚠️ 完了済みのタグが見つかりません")
    • Line 411: print("⬅️ タグ(\(tagToUndo.tagId))を取り消してそのタグ位置に戻る...")
  • フロアマップ読み込み
    • Line 533: print("🗺️ フロアマップ読み込み完了: ...")
    • Line 535: print("⚠️ フロアマップが見つかりません ...")
  • アンテナ読込/ペアリング・接続状態
    • Line 571, 578–582, 589–593, 597–598, 604–608, 612–613, 623 などの [DEBUG] 付き print

コーディングガイドラインで「デバッグ用コードや不要なコードは削除」とされているので、

  • リリースで残したくないログは #if DEBUG ... #endif で囲む
  • もしくは完全に削除するか、必要なものだけ Logger 等の正式なロギングに移行する

方向で整理していただくのが良いと思います。特に接続監視まわりや handleConnectionErrorgoToPreviousTagPositionprint は過去のレビューでも削除をお願いしていた箇所なので、今回あわせて対応しておくとスッキリします。

-                if hasError {
-                    print("⚠️ 接続断検出: 接続復旧画面を表示します")
-                    self.handleConnectionError()
-                }
+                if hasError {
+                    self.handleConnectionError()
+                }

のように、UIや状態更新で十分にユーザ/開発者に伝わる場合は、単純に print を落としてしまうだけでもよいと思います。

As per coding guidelines

Also applies to: 96-99, 228-282, 379-417, 511-540, 568-627

UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)

553-560: onDisconnected で ConnectionManagementUsecase 側のペアリング解除が行われていません(削除順序の問題)

onDisconnected 内で

// ペアリング情報からも削除
self.antennaPairings.removeAll { $0.device.id == endpointId }
self.isConnected = !self.antennaPairings.isEmpty

// ConnectionManagementUsecaseからも削除
if let pairing = self.antennaPairings.first(where: { $0.device.id == endpointId }) {
    self.connectionUsecase.unpairAntenna(antennaId: pairing.antenna.id)
}

という順序になっているため、removeAll 実行後には self.antennaPairings に該当ペアリングが存在せず、first(where:) が常に nil になります。その結果、connectionUsecase.unpairAntenna が一度も呼ばれず、ConnectionManagementUsecase 側にペアリング情報が残り続けてしまいます(既存レビューコメントで指摘されていた内容と同じ問題です)。

unpairAntenna を呼ぶ前にローカルの antennaPairings を消してしまわないよう、順序を入れ替える必要があります。

-        // ペアリング情報からも削除
-        self.antennaPairings.removeAll { $0.device.id == endpointId }
-        self.isConnected = !self.antennaPairings.isEmpty
-
-        // ConnectionManagementUsecaseからも削除
-        if let pairing = self.antennaPairings.first(where: { $0.device.id == endpointId }) {
-            self.connectionUsecase.unpairAntenna(antennaId: pairing.antenna.id)
-        }
+        // ConnectionManagementUsecaseからも削除
+        if let pairing = self.antennaPairings.first(where: { $0.device.id == endpointId }) {
+            self.connectionUsecase.unpairAntenna(antennaId: pairing.antenna.id)
+        }
+
+        // ペアリング情報からも削除
+        self.antennaPairings.removeAll { $0.device.id == endpointId }
+        self.isConnected = !self.antennaPairings.isEmpty

これにより、切断された端末に対応するペアリングが ConnectionManagementUsecase 側からも正しく削除され、後続の復旧フローや状態管理との不整合を防げます。

🧹 Nitpick comments (21)
UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift (1)

26-30: saveRealtimeData 内の Task { @MainActor in ... } は不要です

SensingControlUsecase 自体が @MainActor なので、saveRealtimeData 内での dataPointCount 更新はそのまま self.dataPointCount += 1 と書けます。

-            // データポイント数を更新
-            Task { @MainActor in
-                self.dataPointCount += 1
-            }
+            // データポイント数を更新
+            self.dataPointCount += 1

のようにシンプルにしておくと、余計なTask生成も減り読みやすくなります。

activeSessionId の追加と currentSessionId nil 時のガード自体は妥当な変更に見えます。

Also applies to: 227-242

UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsViewModel.swift (1)

6-17: UserDefaults 連携は問題なし/@AppStorage で簡略化も可能

skipCalibration@PublisheddidSetUserDefaults と同期する実装は正しく動くと思います。

もしもう少しボイラープレートを減らしたければ、View 側か ViewModel 側で

@AppStorage("skipCalibration") var skipCalibration = false

のように @AppStorage を使う選択肢もあります(値の読み込み/保存を自動でやってくれるため)。

現状の実装のままでも特に問題はありません。

Based on learnings

UWBViewerSystem/UWBViewerSystemApp.swift (1)

76-82: deleteExistingDatabase が実質デッドコードになっている点だけ整理を検討ください

Schema への PersistentRealtimeData / PersistentAntennaPairing 追加や、DEBUG時の自動削除を止める判断自体は納得感があります。

一方で、

// deleteExistingDatabase()

が初期化時・エラー時の両方でコメントアウトされており、deleteExistingDatabase() 自体も他から呼ばれていないため、現状ほぼデッドコードになっています。

  • 今後デバッグ用途で都度手動呼び出しする想定なら、その旨コメントを残す
  • そうでなければメソッドごと削除する

など、どちらかに整理しておくと読み手にとって意図が明確になると思います。

As per coding guidelines

Also applies to: 87-93, 125-145

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (1)

293-300: フロアマップ読み込みロジックの重複は将来的に共通化を検討してもよさそうです

loadFloorMapInfo(floorMapId:)loadFloorMapInfoById(floorMapId:) の実装は、DataCollectionViewModel 側の loadFloorMapInfoById とかなり近い構造になっていそうです(スニペット参照)。

現状問題になるほどではありませんが、

  • 取得~ currentFloorMapInfo 更新
  • 画像読み込み(プラットフォーム分岐)
  • エラー時の showError(...)

あたりは共通のヘルパーか、小さなUsecaseに切り出しておくと、将来の仕様変更(例: エラーメッセージ文言変更、画像処理の追加)に強くなると思います。

Also applies to: 511-540

UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (2)

241-258: for-where構文の使用を検討してください。

SwiftLintの推奨に従い、forループ内の単一のif文はwhere句を使用してより簡潔に記述できます。

以下のdiffを適用してコードを改善できます:

-    for (antennaId, pairedDeviceName) in antennaPairings {
-        if pairedDeviceName == deviceName {
+    for (antennaId, pairedDeviceName) in antennaPairings where pairedDeviceName == deviceName {
             #if DEBUG
                 print("🔗 デバイス \(deviceName) はアンテナ \(antennaId) に紐づいています")
             #endif
             return antennaId
-        }
     }

27-48: デバッグログの削除を検討してください。

多数のデバッグ用print文が追加されていますが、コーディングガイドラインでは「デバッグ用コードは削除する」とされています。プロダクション環境では、os.logを使用した構造化ロギングへの移行を検討してください(既にloggerプロパティが定義されています)。

Based on coding guidelines, デバッグ用コードは削除することが推奨されています。

Also applies to: 174-183, 231-293

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift (1)

12-14: floorMapId の受け渡しと onAppear での初期化フローが整理されていて良いです

View が floorMapId を受け取り、onAppear

  • viewModel.setFloorMapId(_:)
  • viewModel.loadMapAndDevices(floorMapId:)
  • flowNavigator.currentFloorMapId へのセット

まで一気通貫で行っているので、フロア単位のコンテキストが ViewModel / フローナビゲータ双方で同期される設計になっていて分かりやすいです。

もし将来的にこの画面が何度も再表示され、loadMapAndDevices が重い処理になってくるようであれば、「すでに同じ floorMapId のロードが完了済みならスキップする」などの再入防止を ViewModel 側に入れておくとより安心かもしれません(現状でもバグというほどではないので任意です)。

Also applies to: 88-95

UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift (1)

89-93: onAppear で floorMapId を使ってフロアマップをロードする流れは妥当です

modelContext から SwiftDataRepository を生成して ViewModel にセットした後に、loadFloorMapInfo(floorMapId:) を呼び出しているため、

  1. Repository の依存差し替え
  2. floorMapId に紐づくフロアマップ情報とアンテナ情報のロード
  3. フローの現在ステップ/Router 連携の初期化

という順番が保証されていて、責務分離としても自然だと思います。もしこの画面が頻繁に再表示されるケースでロード処理が重くなるようであれば、ViewModel 内で「同じ floorMapId をすでにロード済みなら再ロードをスキップする」といった最適化を検討してもよさそうですが、現状必須というほどではありません。

UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (2)

18-56: floorMapId の扱いとルーティングの一貫性について

全体として floorMapId の伝搬と Route 生成は筋が通っていますが、いくつか設計上の気になる点があります。

  • SensingFlowStep.route(floorMapId:) 内で let mapId = floorMapId ?? "" としているため、本来必須なはずの floorMapId が欠けていても空文字でルートが生成されてしまい、設定漏れが隠蔽されるリスクがあります。少なくともデバッグビルドでは preconditionFailure / assert で検出する、あるいは antennaConfiguration 以降のステップでは非オプショナル引数にしてコンパイル時に保証する方が安全です。
  • currentFloorMapId はメモリ上のみで saveFlowState/loadFlowState には含まれていないので、アプリ再起動後にフローだけ復元されて floorMapId が失われる可能性があります。フロー継続を前提にするなら、ID も一緒に保存するか、常にアクティブな FloorMap から再解決する仕組みを検討しても良さそうです。

挙動としては正しいのでクリティカルではありませんが、将来的なバグの温床になりやすい部分なので検討をおすすめします。

Also applies to: 81-105, 120-139, 262-281


81-95: キャリブレーションスキップ時の再帰呼び出しの簡素化

skipCalibration 有効時に

  • .systemCalibrationmarkStepAsCompleted した上で
  • proceedToNextStep(floorMapId: self.currentFloorMapId) を再帰呼び出し

する実装になっていますが、再帰呼び出し側でも再度 markStepAsCompleted(.systemCalibration) されるため、同じステップを2回完了扱いにしています(Set なので実害はありません)。

ロジック自体は正しいものの、読みやすさの観点では以下のように「次の nextStep を一気に2つ先に進める」形にして再帰を避けると分かりやすくなります。

-        if nextStep == .systemCalibration && UserDefaults.standard.bool(forKey: "skipCalibration") {
-            ...
-            self.currentStep = nextStep
-            self.markStepAsCompleted(nextStep)
-            ...
-            self.proceedToNextStep(floorMapId: self.currentFloorMapId)
-            return
-        }
+        if nextStep == .systemCalibration && UserDefaults.standard.bool(forKey: "skipCalibration") {
+            ...
+            // systemCalibration をスキップして sensingExecution へ直接進む
+            if let sensingIndex = SensingFlowStep.allCases.firstIndex(of: .sensingExecution) {
+                let sensingStep = SensingFlowStep.allCases[sensingIndex]
+                self.currentStep = sensingStep
+                self.markStepAsCompleted(.systemCalibration)
+                self.updateProgress()
+                self.saveFlowState()
+                let route = sensingStep.route(floorMapId: self.currentFloorMapId)
+                self.router.navigateTo(route)
+            }
+            return
+        }

※あくまで読みやすさの改善レベルなので、優先度は低めです。

Also applies to: 429-445

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift (1)

40-41: floorMapId の管理方法の整理(単一ソース化の提案)

floorMapId 周りの設計は概ね良いのですが、現状だと少し中途半端な状態に見えます。

  • private var floorMapId: String?setFloorMapId(_:) が追加されていますが、実際のデータロードでは loadMapAndDevices(floorMapId:) の引数だけを使っており、プロパティ側はどこからも参照されていません。
  • 呼び出し側が setFloorMapIdloadMapAndDevices の両方を呼ぶ場合、ID の整合性を二重に保つ必要が出てくるため、バグの温床になりがちです。

どちらかに寄せて「単一ソース」にするとスッキリすると思います。

例:

 func setFloorMapId(_ floorMapId: String) {
-    self.floorMapId = floorMapId
+    self.floorMapId = floorMapId
+    self.loadMapAndDevices()
 }

-func loadMapAndDevices(floorMapId: String) {
+func loadMapAndDevices() {
-    Task { @MainActor in
-        await self.loadFloorMapInfoFromSwiftData(floorMapId: floorMapId)
+    guard let floorMapId else { return }
+    Task { @MainActor in
+        await self.loadFloorMapInfoFromSwiftData(floorMapId: floorMapId)
         await self.loadCalibrationDataAsync()
         self.loadSelectedDevices()
     }
 }

あるいはプロパティを取らず loadMapAndDevices(floorMapId:) だけに統一する方針でも構いませんが、どちらかに寄せておくと今後の変更が楽になります。

Also applies to: 103-113, 158-205

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (1)

6-24: DataCollectionView 内の未使用 UI セクションの整理

floorMapId の受け渡しと onAppear での ViewModel 初期化フローは良くまとまっています。一方で、本ファイル内に以下のような「現在 body から参照されていない UI パーツ」が多数残っています。

  • headerSection
  • sensingControlCard, statusIndicator
  • recentSessionsCard
  • realtimeDataDisplaySection, realtimeDataSection, realtimeDataHeader
  • floorMapDisplaySection, floorMapHeader, floorMapInfo, floorMapCanvas(floorMapInfo:), floorMapLegend
  • CompactDeviceDataViewcompactRealtimeDataDisplay から利用されているので OK

これらは過去のレイアウトの名残と思われ、現状のレイアウトとは二重管理になっているため、読み手にとってどれが実際に使われているのか分かりづらくなっています。プロジェクトのガイドライン的にもデッドコードは削除推奨なので、未使用の private View プロパティは一度整理してしまうとよいと思います(必要になれば git からいつでも戻せます)。

また、realtimeDataDisplaySection 内の

Text("🔍 セクション内部: count=\(self.viewModel.dataPointCount)")
    .font(.caption2)
    .foregroundColor(.red)

のような明確にデバッグ用の表示は、将来的に再利用する予定がなければ合わせて削除してしまう方がスッキリしそうです。

Also applies to: 79-90, 191-271, 448-588, 590-609, 627-792, 797-833, 1423-1425

UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapViewModel.swift (1)

17-37: アクティブなフロアマップの選択仕様について

FloorMap から進捗関連を外し、loadFloorMaps

  • SwiftData から全 FloorMap を取得 → isActive: false で生成
  • UI 反映時に「先頭の FloorMap をアクティブ & selectedFloorMap に設定」

というシンプルな挙動に整理されているのは分かりやすいです。

一方で、この変更により「ユーザーが前回選択していた FloorMap」や「PreferenceRepository に保存されているアクティブ FloorMap」の情報は復元に使われなくなっています。仕様として

  • 毎回一覧の先頭をデフォルト選択にする

という方針で問題ないか、あるいは以前通りアクティブ FloorMap を復元した方が UX として自然か、一度確認しておくと良いと思います。

Also applies to: 87-131

UWBViewerSystem/Domain/Usecase/RealtimeCoordinateTransformUsecase.swift (3)

24-77: 複数データ変換時のアンテナ位置ロード回数を削減した方が良さそうです

transformMultipleData から各要素ごとに transformToGlobalCoordinate を呼び出しているため、そのたびに loadAntennaPositions(for:) が実行される構造になっています。データ点が多い場合、同一 floorMapId に対してアンテナ一覧を毎回ロードすることになり、パフォーマンス面でかなり無駄が出そうです。

一度だけアンテナ情報をロードして辞書化し、それをループ内で使い回す形にするとよいと思います。

-    public func transformMultipleData(
-        _ realtimeDataList: [RealtimeData],
-        floorMapId: String
-    ) async -> [String: Point3D] {
-        var results: [String: Point3D] = [:]
-
-        for data in realtimeDataList {
-            if let globalCoord = await self.transformToGlobalCoordinate(
-                distance: data.distance,
-                elevation: data.elevation,
-                azimuth: data.azimuth,
-                antennaId: data.antennaId,
-                floorMapId: floorMapId
-            ) {
-                results[data.deviceName] = globalCoord
-            }
-        }
-
-        return results
-    }
+    public func transformMultipleData(
+        _ realtimeDataList: [RealtimeData],
+        floorMapId: String
+    ) async -> [String: Point3D] {
+        // 先にアンテナ位置をまとめてロード
+        guard let antennaPositions = try? await swiftDataRepository.loadAntennaPositions(for: floorMapId) else {
+            return [:]
+        }
+        let antennaDict = Dictionary(uniqueKeysWithValues: antennaPositions.map { ($0.antennaId, $0) })
+
+        var results: [String: Point3D] = [:]
+
+        for data in realtimeDataList {
+            guard let antenna = antennaDict[data.antennaId] else { continue }
+
+            let localCoord = self.polarToCartesian(
+                distance: data.distance,
+                elevation: data.elevation,
+                azimuth: data.azimuth
+            )
+            let globalCoord = self.localToGlobal(
+                localCoord: localCoord,
+                antennaPosition: antenna.position,
+                antennaRotation: antenna.rotation
+            )
+            results[data.deviceName] = globalCoord
+        }
+
+        return results
+    }

(インタフェースを変えずに性能だけ改善する案なので、必要に応じて微調整してください)


81-132: 座標変換ロジックは整合していそうですが、ユニットテストで軸定義を固定しておくと安心です

polarToCartesianlocalToGlobal の実装は、コメントに書かれている前提(UWB座標系で東が0度・仰角が度単位・Z軸回りの回転)と整合しており、典型的な球面→直交+Z軸回転の実装に見えます。

座標系の向き(東/北・右手系など)は後から混乱しやすいので、例えば「距離5m・方位0度・仰角0度のとき X 正方向に出る」「アンテナ回転90度で X/Y がどのように入れ替わるか」など、期待値がはっきりしたケースをユニットテストにしておくと、将来の変更でも安心だと思います。


140-156: アンテナ情報取得時のエラーハンドリング方針を揃えるとデバッグしやすくなります

transformToGlobalCoordinate ではアンテナ情報が取得できない場合に print で警告を出していますが、getAntennaPosition / getAllAntennaPositions は同様の状況でも静かに nil / 空配列を返しています。

どれが正解というわけではありませんが、最低限「ロードエラー(throw)なのか、データ未登録なのか」をログで区別しておくと、現場で原因切り分けしやすくなると思います。必要に応じて ExportError 的なドメインエラーを導入するか、ログメッセージだけでも統一を検討してみてください。

UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (3)

40-80: セッションディレクトリ構造とコメント表記の整合性・将来の拡張性について

createSessionDirectoryDocuments/sensing/yyyyMMdd/HHmmss の下にディレクトリを切っており、「ファイルアプリから見えるようにする」という目的に合った構成になっていて良いと思います。

一方でコメントに ディレクトリ構造: /Applications/sensing/yyyymmdd/hhmmss/ と書かれているため、実際のパスとの対応がやや分かりづらいです。<Documents>/sensing/... のように、実際の実装と一致する表記にしておくと、将来ディレクトリ構造を変更する際の混乱が減ると思います。


270-285: CSV書き込みヘルパーはシンプルで良いが、既存ファイル上書きの挙動をコメントで明示すると親切です

writeCSVcontent.write(to:atomically:encoding:) をそのままラップしており、異常系も ExportError.writeFailed にマッピングされていて分かりやすいです。

一点だけ、既に同名ファイルが存在する場合は「無条件に上書き」される挙動なので、その前提をメソッドコメントに一行追記しておくと、将来「追記したい/上書きしたくない」といった要求が出たときに気付きやすくなると思います。


293-303: createShareViewController は UI スレッド前提であることを明示しておくと安全です

UIActivityViewController の生成は UI スレッド前提なので、実際の呼び出し側が MainActor 上で動いている前提であれば問題ありませんが、ユーティリティ側で @MainActor を付けておくと誤用を防ぎやすくなります。

-        #if os(iOS)
-            static func createShareViewController(for fileURL: URL) -> UIActivityViewController
+        #if os(iOS)
+            @MainActor
+            static func createShareViewController(for fileURL: URL) -> UIActivityViewController
             {

程度のアノテーションを付けておくと、コンパイラがスレッド境界をチェックしてくれるので安心です。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (1)

19-225: SwiftDataRepository の初期化パスと Usecase 再構成のタイミングを整理した方が良さそうです

setupSwiftDataRepository(modelContext:)

  • SwiftDataRepository の生成
  • SensingControlUsecase / RealtimeDataUsecase / ConnectionManagementUsecase の再構成
  • setupObservers() の張り直し
  • loadInitialData() 呼び出し

まで一括でやっているのは分かりやすいです。

一方で少し気になった点が2つあります:

  1. init で swiftDataRepository を注入した場合、このセットアップが走らない

    setupSwiftDataRepositoryif self.swiftDataRepository == nil でガードしているため、イニシャライザ引数でリポジトリを渡したケースでは

    • SensingControlUsecase に SwiftData が渡されない
    • RealtimeDataUsecase.updateSwiftDataRepository(_:) も呼ばれない
    • connectionUsecase.realtimeDataUsecase も設定されない

    という状態になります。将来テストなどで「DI でリポジトリを差し替える」用途を想定しているなら、この条件分岐の見直し(例: 「リポジトリが変わったら常に再配線する」)を検討してもよいと思います。

  2. loadInitialData() は現状ほぼ no-op

    コメントに「非推奨」とありますが、実装は await self.loadAntennaPositions() のみで、こちらは currentFloorMapInfo?.id が無いと何もしないため、実際には何もロードされません。既に loadFloorMapInfo(floorMapId:) を正式 API にしているので、loadInitialData 自体を削除するか、呼び出し元コメントに「互換性維持のため残している」旨を明示しておくと読み手に親切だと思います。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)

79-157: フロアマップID指定のロードフローは概ね良いが、SwiftData と UserDefaults のフォールバック境界を明示するとさらに分かりやすいです

loadFloorMapInfo(floorMapId:)loadFloorMapInfoByIdloadAntennasFromPositionData の流れで、

  • SwiftData にフロアマップ/アンテナ位置があればそちらを優先
  • floorMapId 未設定やロード失敗時には loadAntennasFromUserDefaults() にフォールバック

という構成になっており、既存データとの互換性も考慮されていて良いと思います。

細かい点ですが、

  • loadFloorMapInfoById は現在ログを出すだけで floorMapId 由来の情報は UserDefaults などには保存していません(getCurrentFloorMapInfo() との関係がやや分かりづらい)。
  • loadAntennasFromPositionData@MainActor なクラスメソッド内でさらに await MainActor.run しているため、厳密には二重でメインスレッド指定になっています(動作上は問題ありませんが、将来リファクタリング時の混乱を避けるなら、どちらか片方に寄せても良さそうです)。

動作に直結する問題ではないので優先度は低いですが、コメントかメソッド名で「SwiftData優先/なければUserDefaultsへフォールバック」というポリシーを明示しておくと、後から読む人にも意図が伝わりやすいと思います。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8585672 and bc615d7.

📒 Files selected for processing (32)
  • UWBViewerSystem/Domain/DataModel/SwiftDataModels.swift (5 hunks)
  • UWBViewerSystem/Domain/Entity/CommonTypes.swift (1 hunks)
  • UWBViewerSystem/Domain/Entity/RealtimeData.swift (2 hunks)
  • UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (2 hunks)
  • UWBViewerSystem/Domain/Usecase/RealtimeCoordinateTransformUsecase.swift (1 hunks)
  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (5 hunks)
  • UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift (3 hunks)
  • UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (1 hunks)
  • UWBViewerSystem/Info.plist (1 hunks)
  • UWBViewerSystem/Presentation/Router/NavigationRouter.swift (1 hunks)
  • UWBViewerSystem/Presentation/Router/Router.swift (1 hunks)
  • UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (6 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift (4 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift (4 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (5 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (9 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingView.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift (2 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift (4 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapViewModel.swift (2 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift (4 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (10 hunks)
  • UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (3 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (6 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/SensingView.swift (0 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/SensingViewModel.swift (0 hunks)
  • UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsView.swift (2 hunks)
  • UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsViewModel.swift (1 hunks)
  • UWBViewerSystem/UWBViewerSystemApp.swift (4 hunks)
  • UWBViewerSystemTests/TestHelpers/MockDataRepository.swift (0 hunks)
  • UWBViewerSystemTests/UWBViewerSystemTests.swift (0 hunks)
💤 Files with no reviewable changes (4)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/SensingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/SensingView.swift
  • UWBViewerSystemTests/UWBViewerSystemTests.swift
  • UWBViewerSystemTests/TestHelpers/MockDataRepository.swift
🧰 Additional context used
📓 Path-based instructions (5)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsView.swift
  • UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift
  • UWBViewerSystem/Presentation/Router/Router.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingView.swift
  • UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift
  • UWBViewerSystem/Domain/Entity/RealtimeData.swift
  • UWBViewerSystem/Domain/Usecase/RealtimeCoordinateTransformUsecase.swift
  • UWBViewerSystem/Domain/Entity/CommonTypes.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift
  • UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/UWBViewerSystemApp.swift
  • UWBViewerSystem/Presentation/Router/NavigationRouter.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift
  • UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift
  • UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsViewModel.swift
  • UWBViewerSystem/Domain/DataModel/SwiftDataModels.swift
  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift
  • UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
**/*Usecase.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Usecaseのファイル名は「〜Usecase.swift」とする

Files:

  • UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift
  • UWBViewerSystem/Domain/Usecase/RealtimeCoordinateTransformUsecase.swift
  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
**/*Repository.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Repositoryのファイル名は「〜Repository.swift」とする

Files:

  • UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧠 Learnings (10)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*Repository.swift : Repositoryのファイル名は「〜Repository.swift」とする

Applied to files:

  • UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*Usecase.swift : Usecaseのファイル名は「〜Usecase.swift」とする

Applied to files:

  • UWBViewerSystem/Domain/Usecase/RealtimeCoordinateTransformUsecase.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsViewModel.swift
  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*View.swift : Viewのファイル名は「〜View.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift
  • UWBViewerSystem/Presentation/Router/NavigationRouter.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to Devices/**/*.swift : UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*ViewModel.swift : ViewModelのファイル名は「〜ViewModel.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T14:37:07.451Z
Learnt from: harutiro
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: :0-0
Timestamp: 2025-09-23T14:37:07.451Z
Learning: UWBViewerSystemでUserDefaults変更通知のテストを行う際は、NotificationCenterの非同期処理完了を確実に待機するため、1.5秒程度の待機時間とポーリング方式の検証を使用する。これにより競合状態やタイミング問題を回避できる。

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧬 Code graph analysis (10)
UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift (2)
UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (2)
  • saveRealtimeData (479-493)
  • saveRealtimeData (972-972)
UWBViewerSystemTests/TestHelpers/MockDataRepository.swift (1)
  • saveRealtimeData (261-268)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingView.swift (1)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (1)
  • proceedToNextStep (45-106)
UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (1)
UWBViewerSystem/Domain/DataModel/SwiftDataModels.swift (8)
  • toPersistent (346-354)
  • toPersistent (358-369)
  • toPersistent (373-383)
  • toPersistent (387-400)
  • toPersistent (404-417)
  • toPersistent (421-431)
  • toPersistent (435-456)
  • toPersistent (523-545)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift (3)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (1)
  • loadFloorMapInfo (159-164)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (2)
  • loadFloorMapInfo (80-87)
  • saveAndProceedToNextStep (373-384)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (2)
  • loadFloorMapInfo (294-299)
  • loadFloorMapInfo (542-566)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (4)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (1)
  • loadFloorMapInfo (159-164)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)
  • loadFloorMapInfo (80-87)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (5)
  • loadFloorMapInfo (294-299)
  • loadFloorMapInfo (542-566)
  • setFlowNavigator (235-237)
  • goToPreviousTagPosition (380-416)
  • proceedToNextTagPosition (373-377)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (1)
  • setRouter (33-35)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift (3)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (1)
  • setFloorMapId (45-48)
UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (2)
  • loadFloorMap (648-654)
  • loadFloorMap (984-984)
UWBViewerSystemTests/TestHelpers/MockDataRepository.swift (1)
  • loadFloorMap (325-327)
UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (2)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
UWBViewerSystem/Domain/Utils/SensorDataProcessor.swift (1)
  • applyMovingAverageToPoints (76-96)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift (1)
UWBViewerSystem/Presentation/Router/NavigationRouterModel.swift (1)
  • push (25-31)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (2)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)
  • proceedToNextStep (355-370)
UWBViewerSystem/Presentation/Router/NavigationRouterModel.swift (1)
  • navigateTo (44-53)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (5)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (3)
  • loadInitialData (151-156)
  • loadFloorMapInfo (159-164)
  • loadFloorMapInfoById (167-208)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (3)
  • loadFloorMapInfo (80-87)
  • loadFloorMapInfoById (90-100)
  • proceedToNextStep (355-370)
UWBViewerSystem/Domain/Usecase/AutoAntennaCalibrationUsecase.swift (3)
  • clearData (311-315)
  • clearData (318-324)
  • clearData (331-336)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (1)
  • proceedToNextStep (45-106)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift (1)
  • showError (231-234)
🪛 SwiftLint (0.57.0)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift

[Warning] 246-246: where clauses are preferred over a single if inside a for

(for_where)

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift

[Warning] 95-95: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🔇 Additional comments (26)
UWBViewerSystem/Info.plist (1)

12-17: ドキュメント共有フラグ追加は一貫しています

ファイル共有/ドキュメントブラウザ系フラグの組み合わせとしては問題なさそうです。ただし、Documents 配下に機微情報を保存している場合は、ユーザから直接参照可能になる点だけ再確認しておいてください。

アプリがDocuments配下にどのようなデータを書き出しているか、一度実機でFilesアプリ経由で確認してみてください。

UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift (1)

126-130: currentSessionId のクリアタイミングの整合性だけ確認してください

stopRemoteSensingcurrentSessionId をクリアしなくなった一方で、saveRealtimeDatacurrentSessionId が nil の場合にデータ保存をスキップする実装になっています。

コメントにある通り「CSV出力完了後に DataCollectionViewModel から明示的にクリア」する前提だと思うので、

  • DataCollectionViewModel 側でどのタイミング・どのメソッド経由で currentSessionId をクリアするか
  • クリア前後で saveRealtimeData がどのように呼ばれうるか

を一度通しで確認しておくと、意図しないデータ取りこぼしを防げると思います。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (2)

136-143: 前のタグ位置へ戻るロジックは整合性が取れています

canGoToPreviousTag の条件と goToPreviousTagPosition() の実装がきちんと揃っていて、

  • 最後に isCollected == true なタグを後ろから探索
  • Usecase 側のデータを clearData(for:antennaId, tagId:) で削除
  • isCollected を false に戻し、インデックスと collectionProgress を再計算
  • その後 updateDataStatistics() を再実行

という流れになっているので、状態管理として筋が良いと思います。

Also applies to: 379-417


428-490: キャリブレーション完了時のフロー連携は分かりやすい構成です

proceedToNextAntenna() 末尾で、

  • 全アンテナ完了時に saveCalibrationResultToUserDefaults() で結果要約を保存
  • showSuccessAlert を true にしてUI側に通知
  • flowNavigator があれば 2 秒待機後に proceedToNextStep() で次のステップへ遷移

という流れになっており、キャリブレーション完了 → 成功通知 → 自動遷移の意図が明確で良いと思います。

saveCalibrationResultToUserDefaults() も、完了アンテナ数/総アンテナ数を SystemCalibrationResult 経由で1つのキーにまとめて保存していて、後段から扱いやすい形になっています。

UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift (1)

13-19: タブのインデックス再割り当てが正しく行われています。

SensingViewタブが削除され、2つのタブ(FloorMapViewとSettingsView)に整理されました。タグ値が0と1に正しく更新されています。

UWBViewerSystem/Domain/Entity/RealtimeData.swift (1)

15-30: antennaIdプロパティの追加が適切に実装されています。

デバイスに紐づくアンテナIDを追跡するための新しいプロパティが追加され、イニシャライザにデフォルト値""が設定されています。座標変換機能との統合に必要な変更です。

UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)

184-202: デバッグログの追加を確認してください。

画像読み込み処理にデバッグログが追加されていますが、コーディングガイドラインでは「デバッグ用コードは削除する」とされています。これらのログが恒久的に必要か、一時的なトラブルシューティング用かを確認してください。

一時的なデバッグ用の場合は、問題解決後に削除することをお勧めします。

Based on coding guidelines, デバッグ用コードは削除することが推奨されています。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingView.swift (1)

253-259: floorMapIdを使用した画面遷移ロジックが正しく実装されています。

saveFloorMapSettings()から返されたfloorMapIdがnil以外の場合のみ次のステップに進むように適切に実装されています。エラーハンドリングもViewModelのアラート機能で行われています。

UWBViewerSystem/Presentation/Router/Router.swift (1)

17-23: ルート定義へのfloorMapIdパラメータ追加が一貫して実装されています。

センシングフロー関連のルートケースにfloorMapIdが追加され、フロアマップコンテキストの伝播が可能になりました。関連ファイル(NavigationRouter.swift、SensingFlowNavigator.swift)との整合性も取れています。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift (1)

93-138: 戻り値の型変更が適切に実装されています。

saveFloorMapSettings()の戻り値がBoolからString?に変更され、成功時にはフロアマップIDを、失敗時にはnilを返すようになりました。この変更により、呼び出し側で保存されたフロアマップのIDを直接利用でき、より明示的なエラーハンドリングが可能になっています。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift (2)

136-136: iOSでのナビゲーション呼び出しが正しく更新されています。

フロアマップ行のタップ時にfloorMapIdを渡してアンテナ設定画面に遷移するように更新されています。


270-298: FloorMapDetailViewのアクションボタンが正しく更新されています。

すべてのアクションボタン(アンテナ配置設定、端末ペアリング設定、センシング開始)がfloorMapIdを渡して画面遷移するように一貫して更新されています。

UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (1)

14-14: 検証結果から、強参照サイクルに関する懸念は根拠がないことが確認されました。

検証結果:

RealtimeDataUsecaseのswiftDataRepositoryプロパティを含む所有関係を分析した結果、以下が確認されました:

  1. RealtimeCoordinateTransformUsecase は、swiftDataRepositoryのみを保持し、RealtimeDataUsecaseへの逆参照を持っていません

  2. SwiftDataRepository は、ModelContextのみを保持し、RealtimeDataUsecaseへのいかなる参照も持っていません

  3. 所有関係は単方向のみ:

    • RealtimeDataUsecase → swiftDataRepository → ModelContext
    • 相互参照(bidirectional reference)が存在しないため、強参照サイクルは発生しません
  4. sensingControlUsecaseはweak参照のまま保持されており、その領域でのサイクルは防止されています

したがって、swiftDataRepositoryからweakを削除することは適切な変更であり、データアクセス層(Repository)は強参照で保持されるべきです。

Likely an incorrect or invalid review comment.

UWBViewerSystem/Presentation/Router/NavigationRouter.swift (1)

57-69: floorMapId を Route から各画面へ明示的に伝搬できており、一貫した設計になっています

各 Route case が floorMapId の associated value を受け取り、そのまま対応する AntennaPositioningView / AutoAntennaCalibrationView / PairingSettingView / DataCollectionView に引き渡しているので、フロアごとの文脈がナビゲーション全体で揃っていて良いと思います。コンパイル時に引数渡し漏れも検出できる形になっている点も◎です。

UWBViewerSystem/Domain/DataModel/SwiftDataModels.swift (2)

146-183: PersistentRealtimeData への sessionId 追加は Repository 側の利用と整合しています

sessionId: String フィールドと、それを受け取る init 引数の追加により、リアルタイムデータがどのセッションに紐づくかを永続化レベルで追跡できるようになっていて、SwiftDataRepository.loadRealtimeData(for:) からの利用とも噛み合っているように見えます。既存データとの互換性確保のためにデフォルト値を空文字にしているのも現実的だと思います。


403-416: RealtimeData.toPersistent(sessionId:) の拡張も一貫性があり問題なさそうです

toPersistent(sessionId: String = "") でセッション ID を受け取り、PersistentRealtimeData の新フィールドにそのまま渡している構造はシンプルで分かりやすいです。デフォルト引数により、既存の呼び出し箇所を壊さずに、Repository からは常に明示的な sessionId を指定できる形になっている点も良いと思います。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift (2)

160-213: フロアマップ未ロード時にローディングプレースホルダを出す変更は UX 的に良いです

mapImagecurrentFloorMapInfo の両方が揃った場合のみ FloorMapCanvas を描画し、それ以外は ProgressView+説明テキストを表示するようになっているため、

  • nil を誤ってキャンバスに渡してクラッシュ/レイアウト崩れになるリスクが減り、
  • ユーザーにも「読み込み中」であることがはっきり伝わる

という意味で良い変更だと思います。


798-801: Preview での初期化も floorMapId 対応に追随できています

AntennaPositioningView(floorMapId: "test-floor-map-id") に更新されており、新しい API 仕様と Preview のコードがずれていないので問題なさそうです。NavigationRouterModel.shared を環境に渡している点も含めて、依存関係がきれいに解決されています。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift (3)

5-7: PairingSettingView に floorMapId を注入する設計は他画面と一貫しており、StateObject パターンとも整合しています

floorMapId をプロパティとして保持しつつ、初期化時には DummySwiftDataRepository@StateObject な ViewModel を生成し、onAppear で実際の SwiftDataRepository に差し替えるパターンは、他の画面でも使っている StateObject ベースの ViewModel パターンとよく揃っていて分かりやすいです。View 側が floorMapId だけを受け取り、以降の処理は ViewModel/Usecase に委譲している点も Clean Architecture 的に良いと思います。
Based on learnings, この方向性で問題ないと感じました。

Also applies to: 13-19


223-225: 「次へ」ボタンで saveAndProceedToNextStep に一本化したのは責務の集約として良いです

Button("次へ") 側からは self.viewModel.saveAndProceedToNextStep(flowNavigator: self.flowNavigator) の呼び出しだけにしており、

  • canProceedToNext の判定
  • ペアリング情報の保存
  • floorMapId を含むフローナビゲーション

といった処理を ViewModel に閉じ込めているのは、View の見通しを良くしつつフロー制御のロジックをテストしやすくするという意味でも良い変更だと思います。


475-478: Preview でも新しいイニシャライザと Router 依存に追随できています

PairingSettingView(floorMapId: "test-floor-map-id") に更新され、さらに .environmentObject(NavigationRouterModel()) を渡しているので、Preview 実行時にも必要な依存関係が揃うようになっていて良いと思います。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (2)

10-21: floorMapId とフローナビゲータの連携設計について

AutoAntennaCalibrationViewfloorMapId を明示的に受け取り、

  • onAppearviewModel.setup(modelContext:)
  • viewModel.loadFloorMapInfo(floorMapId:) で対象フロアの情報と画像をロード
  • @StateObject flowNavigator.systemCalibration から開始し、router を渡した上で viewModel.setFlowNavigator で ViewModel と連携

という流れに整理されていて、他の ViewModel(例: PairingSettingViewModel, DataCollectionViewModel)の設計とも揃っており、データフロー的に自然だと思います。(Based on learnings)

接続リカバリ用の

.sheet(isPresented: self.$viewModel.showConnectionRecovery) {
    ConnectionRecoveryView(
        connectionUsecase: ConnectionManagementUsecase.shared,
        isPresented: self.$viewModel.showConnectionRecovery
    )
    ...
}

も、フロー中で通信断が発生した場合にそのまま復旧 UI を出せる構成になっていて、今回の PR 目的に合致しています。

この部分については特に懸念はありません。

Also applies to: 45-52, 66-74, 837-841


517-647: タグ位置ナビゲーション UI の改善は妥当

dataCollectionStepCompact 内のタグ移動ロジックが

  • 未収集 & 非収集中: 「センシング開始」ボタンのみ
  • 収集済み & まだ次のタグがある (hasMoreTagPositions == true): 「前のタグへ」(canGoToPreviousTag の時だけ)+「次のタグ位置へ」
  • 最後のタグ & 戻れる場合のみ: 「前のタグへ」ボタン (オレンジ)

という分岐になっており、canGoToPreviousTag / hasMoreTagPositions のフラグ設計とも整合していて分かりやすいです。

ビジネスロジックは ViewModel 側の

  • goToPreviousTagPosition()
  • proceedToNextTagPosition()
  • canGoToPreviousTag
  • hasMoreTagPositions

に閉じ込められており、View 側は状態に応じてボタンを出し分けているだけなので、MVVM 的にも良い切り分けになっていると思います。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (3)

251-303: ペアリング登録と ConnectionManagementUsecase 連携のタイミングは妥当です

pairAntennaWithDevice

  • 1対1制約チェック
  • availableDevices への追加
  • AntennaPairing の作成と antennaPairings への追加
  • connectionUsecase.pairAntennaWithDevice(antennaId:deviceName:) への反映

という順に処理しており、NearBy デバイス/従来ロジックの両方で ConnectionManagementUsecase に状態を寄せる設計になっているのは良いと思います。

特に従来パス(isNearbyDevice == false)でも pairAntennaWithDevice を呼ぶように統一されているので、後続のロジックから見たときに「どこで何がペアリングされているか」が一箇所に集約されていて分かりやすいです。


329-351: 全ペアリング削除時に ConnectionManagementUsecase 側もクリアしており整合性が取れています

removeAllPairings()

  • NearBy 接続の実切断
  • antennaPairings.removeAll()
  • connectionUsecase.clearAllPairings()
  • availableDevices の接続状態リセット
  • ハンドラーのクリーンアップ

まで一括で行っており、ViewModel ローカル状態と ConnectionManagementUsecase の状態がきちんと同期されるようになっているのは良いと思います。

このメソッドの挙動が単一の「全リセット」操作として完結しているので、後から機能追加する際にも影響範囲を把握しやすそうです。


355-384: フローナビゲーション時に floorMapId を明示的に伝搬しているのは良い設計です

proceedToNextStep() / saveAndProceedToNextStep(flowNavigator:) の両方で、

  • ペアリング情報を UserDefaults へ保存(互換性維持)
  • floorMapId があれば .systemCalibration(floorMapId:) あるいは flowNavigator.proceedToNextStep(floorMapId:) に渡す

という構造になっており、「どのフロアマップの設定フローなのか」が Navigation レイヤにしっかり伝わるようになっています。

このおかげで、後続のキャリブレーションやセンシング画面側で SwiftData を使ったロードがしやすくなっていると思います。

Comment on lines +135 to +189
/// グローバル座標変換後のデータをCSVとしてエクスポート
///
/// CSVフォーマット:
/// ```
/// timestamp,deviceName,antennaId,global_x,global_y,global_z,elevation,azimuth,distance,nlos,rssi
/// 1699876543210,Device1,antenna1,14.123,18.456,0.0,45.5,120.0,5.23,0,-65.5
/// ```
///
/// - Parameters:
/// - realtimeDataList: エクスポートするリアルタイムデータのリスト
/// - globalCoordinates: デバイス名をキーとしたグローバル座標の辞書
/// - directoryURL: 保存先ディレクトリのURL
/// - fileName: ファイル名(デフォルト: "global_coordinates.csv")
/// - Returns: 生成されたCSVファイルのURL
/// - Throws: データが空、またはファイル作成/書き込みに失敗した場合
static func exportGlobalCoordinateDataToCSV(
realtimeDataList: [RealtimeData],
globalCoordinates: [String: Point3D],
directoryURL: URL,
fileName: String = "global_coordinates.csv"
) throws -> URL {
guard !realtimeDataList.isEmpty else {
throw ExportError.noDataToExport
}

// CSVヘッダー
var csvContent =
"timestamp,deviceName,antennaId,global_x,global_y,global_z,elevation,azimuth,distance,nlos,rssi\n"

// データ行
for data in realtimeDataList {
// グローバル座標を取得(存在しない場合は0.0を使用)
let globalCoord = globalCoordinates[data.deviceName] ?? Point3D(x: 0, y: 0, z: 0)

let row = [
String(data.timestamp),
data.deviceName,
data.antennaId,
String(format: "%.6f", globalCoord.x),
String(format: "%.6f", globalCoord.y),
String(format: "%.6f", globalCoord.z),
String(format: "%.6f", data.elevation),
String(format: "%.6f", data.azimuth),
String(format: "%.6f", data.distance),
String(data.nlos),
String(format: "%.2f", data.rssi),
].joined(separator: ",")

csvContent += row + "\n"
}

// ファイルに書き込み
let fileURL = directoryURL.appendingPathComponent(fileName)
return try self.writeCSV(content: csvContent, to: fileURL)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

globalCoordinates の扱いがタイムシリーズと整合しておらず、CSV内容が意図とずれる可能性があります

exportGlobalCoordinateDataToCSV / exportFilteredDataToCSV いずれも、globalCoordinates: [String: Point3D] を「各行のグローバル座標」として使っていますが、この辞書が「デバイスごとの最新位置」しか持っていない場合、各タイムスタンプ行に常に同じ座標が出力されることになります。

具体的には:

  • exportGlobalCoordinateDataToCSV

    • 各行で let globalCoord = globalCoordinates[data.deviceName] ?? Point3D(x: 0, y: 0, z: 0) を呼んでいるため、同一 deviceName の複数行があっても、辞書に入っている1つの座標しか参照できません。
    • セッション全体の軌跡を CSV にしたい場合、全ての行が「最後に観測された座標」になる可能性があります。
  • exportFilteredDataToCSV

    • let coordinates = sortedData.compactMap { globalCoordinates[$0.deviceName] } も同様に、1デバイスあたり1座標しか取得できません。
    • その座標列に対して移動平均をかけているので、「時間方向の変化を反映したフィルタリング」にはなっていません。
    • compactMap により一部行がスキップされた場合、sortedData.enumerated()coordinates[index] の対応も崩れます(現状は index < filteredCoordinates.count で早期 break しているため、末尾データがまるごと出力されない可能性もあります)。

もし「セッション全期間の各サンプルに対するグローバル座標を CSV として出したい」という要件であれば、

  • globalCoordinates の構造を
    • [(realtimeDataId: XXX, point: Point3D)]
    • [String: [Point3D]](デバイス別の時系列配列)
      のように変更するか、
  • もしくは SensingDataCSVExporter の内部で RealtimeCoordinateTransformUsecase 等を用いて、realtimeDataList から毎行のグローバル座標を再計算する

といった設計が必要になると思います。

現状のままだと「サンプル数の多いセッションでも、各デバイスにつき1地点相当しか意味を持たない CSV」になってしまうため、要件と合っているか一度見直していただくのを強くおすすめします。

Also applies to: 193-266

🤖 Prompt for AI Agents
In UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift around lines
135-189 (also applies to 193-266): the code uses globalCoordinates: [String:
Point3D] as a single per-device point so every timestamp row for the same device
emits the same coordinate and filtered logic misaligns indices; change the
implementation so that each realtime sample gets its own global coordinate —
either (A) change the parameter type to a time-aligned structure (e.g.
[RealtimeData.ID: Point3D] or [String: [Point3D]] / [(realtimeDataId, Point3D)])
and look up coordinates by the specific sample id when building each CSV row, or
(B) recompute per-row global coordinates inside the exporter using the
transform/usecase that converts a RealtimeData to a Point3D; also update
exportFilteredDataToCSV to build a coordinates array that aligns one-to-one with
sortedData (no compactMap that drops rows) before applying moving average so
indices remain consistent.

Comment on lines +11 to 12
self.debugSection
self.aboutSection
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

onChange 内の print は削除か正式なロギングへの置換を検討してください

debugSection 自体の構造やバインディングは問題ありませんが、Line 65 の

print("🔧 キャリブレーションスキップ設定: \(newValue)")

は明示的なデバッグ用コードなので、コーディングガイドライン的には削除するか、Logger など正式なロギング手段に置き換えた方が良いと思います。

また、このセクションが「デバッグ設定」であるなら、必要に応じて #if DEBUG で囲ってリリースビルドから隠すかどうかも検討しておくと安心です。
[spreadsheet]

As per coding guidelines

Also applies to: 43-72

🤖 Prompt for AI Agents
In UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsView.swift around
lines 43 to 72 (notably line 65), remove the raw print("🔧 キャリブレーションスキップ設定:
\(newValue)") from the onChange handler and replace it with a proper logging
call (e.g., use a Logger instance or app-wide logging facility) or delete it
entirely; if this output is only for debug builds, wrap the logging or the whole
debugSection in #if DEBUG / #endif so it is excluded from release builds.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (1)

101-105: disconnectFromDeviceconnectedDeviceNames の更新が endpointId ベースになっており、状態がずれる可能性があります

connectedDeviceNames は「デバイス名」の Set<String> ですが、disconnectFromDevice(endpointId:) では:

self.connectedEndpoints.remove(endpointId)
self.connectedDeviceNames = self.connectedDeviceNames.filter { $0 != endpointId }

と、endpointId をキーにフィルタしています。
接続時には onDeviceConnectedself.connectedDeviceNames.insert(deviceName) しているため、endpointIddeviceName は通常異なる値です。この結果:

  • disconnectFromDevice を呼んでも connectedDeviceNames から該当デバイス名が削除されない
  • その後の UI(接続中デバイス数など)が実際より多く見える

という不整合が起こり得ます。

endpointToDeviceNameMap を既に導入しているので、例えば次のようにするのが自然だと思います:

public func disconnectFromDevice(endpointId: String) {
    self.nearbyRepository.disconnect(endpointId)
    self.connectedEndpoints.remove(endpointId)
-   self.connectedDeviceNames = self.connectedDeviceNames.filter { $0 != endpointId }
+   if let deviceName = self.endpointToDeviceNameMap[endpointId] {
+       self.connectedDeviceNames.remove(deviceName)
+       self.endpointToDeviceNameMap.removeValue(forKey: endpointId)
+   }
}

これにより、明示的切断と onDeviceDisconnected の両方で状態が一貫しやすくなります。

Also applies to: 28-30, 275-293, 295-320

♻️ Duplicate comments (2)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (1)

18-24: デバッグ用の print がファイル全体に散在しているため、ガイドラインに合わせた整理をおすすめします

このファイルには、今回追加・変更された箇所を含めて多数の print が存在します:

  • ペアリング管理 (pairAntennaWithDevice / unpairAntenna / clearAllPairings)
  • onDeviceFound の自動接続リクエスト関連ログ
  • onConnectionRequest の受信ログ
  • onDeviceConnected / onDeviceDisconnected での通知ログ など

過去レビューでも同様の指摘がありましたが、リポジトリのコーディングガイドライン(デバッグ用コードは削除)に従うなら:

  • 本番で必須でないものは削除、もしくは #if DEBUG でガード
  • 残したいものは Logger / os_log などプロジェクト共通のロガーに寄せる

といった整理を一度行っておくと良さそうです。
特に接続イベントは発火頻度も高いので、標準出力ベースの print がそのまま残っていると、デバッグログがノイズになりやすいです。

また、lastDisconnectedDevice についても、onDeviceDisconnecteddeviceName が取得できなかった場合には endpointId を入れておくなど、

- self.lastDisconnectedDevice = deviceName
+ self.lastDisconnectedDevice = deviceName ?? endpointId

としておくと、UI 側で常に何らかの識別子を表示できて扱いやすくなります。

Also applies to: 185-215, 234-247, 255-263, 275-293, 295-320

UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (1)

159-198: globalCoordinates の構造と時系列の整合性が取れておらず、CSV 内容が意図とズレる可能性があります

この点は過去レビューでも指摘されていますが、現状のままだと挙動は変わっていないようです。

  • exportGlobalCoordinateDataToCSV / exportFilteredDataToCSV ともに、globalCoordinates: [String: Point3D] を「各行の座標」として参照していますが、この辞書は「デバイスごとの最後の1点」しか持てません。
    • 同一 deviceName の複数サンプル行があっても、常に辞書に入っている 1 座標だけが出力されます。
  • exportFilteredDataToCSV では
    let coordinates = sortedData.compactMap { globalCoordinates[$0.deviceName] }
    としているため、
    • 実際には「同じ座標の繰り返し」をフィルタしているだけになりやすい
    • compactMap により座標が無い行がスキップされると、sortedData.enumerated()indexcoordinates[index] の対応が崩れます(現在は index < filteredCoordinates.count で打ち切るため、末尾のサンプルが丸ごと落ちるケースもあり得ます)。

「各サンプルごとのグローバル座標を CSV にしたい」要件であれば、例えば次のような設計変更が必要になりそうです:

  • globalCoordinates を:
    • [(realtimeDataId: UUID, point: Point3D)]
    • [UUID: Point3D]RealtimeData.id をキー)
      のようにサンプルID単位で持つ、または
  • Exporter 内で RealtimeCoordinateTransformUsecase 等を用いて、realtimeDataList から毎行のグローバル座標を都度再計算する

いずれにせよ、「1デバイス1座標」では時系列の情報が失われるので、CSV の用途・要件と実装が合っているかを再検討してもらうのが良いと思います。

Also applies to: 218-275

🧹 Nitpick comments (6)
UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (1)

42-89: ディレクトリ作成・CSV保存時の print をデバッグ専用 or ロガーに寄せた方がよさそうです

createSessionDirectorywriteCSV 内の print は常に実行されるため、本番環境でコンソールをかなり汚す可能性があります。リポジトリのガイドライン(デバッグ用コードの削除)に合わせるなら:

  • #if DEBUG ... #endif でガードする
  • もしくは Logger 等の共通ロギングに置き換える

のどちらかに寄せておくのをおすすめします。

Also applies to: 286-293

UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (2)

40-53: updateSwiftDataRepository / setFloorMapIdprint はデバッグ用途であれば DEBUG ガード推奨です

この2メソッド内のログ:

  • RealtimeDataUsecase: SwiftDataRepositoryを更新しました
  • FloorMapIDを設定しました

は頻度も高く、本番環境でも常に標準出力に出る形になっています。ほかの箇所が #if DEBUG でガードされている方針に合わせるなら、ここも:

  • #if DEBUG で囲む、または
  • Logger を使ったロギングに置き換える

のどちらかに寄せると、ガイドラインと一貫した形になると思います。


270-290: getAntennaId(for:)for where を使うと少しスッキリ書けます

ロジック自体は正しく、ConnectionManagementUsecase.shared.antennaPairings から逆引きしていますが、SwiftLint の指摘どおり for where 句を使うと読みやすくなります。

- for (antennaId, pairedDeviceName) in antennaPairings {
-     if pairedDeviceName == deviceName {
-         ...
-         return antennaId
-     }
- }
+ for (antennaId, pairedDeviceName) in antennaPairings where pairedDeviceName == deviceName {
+     #if DEBUG
+         print("🔗 デバイス \(deviceName) はアンテナ \(antennaId) に紐づいています")
+     #endif
+     return antennaId
+ }

挙動は変えずに少しだけシンプルにできます。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (1)

94-187: フロアマップ関連のセクションのうち一部が未使用のように見えるので整理を検討してください

  • fullScreenFloorMap / compactSensingControl / compactRealtimeDataDisplaybody から参照されており、現在の UI の中核になっていて問題ありません。
  • 一方で、
    • floorMapDisplaySection
    • floorMapHeader / floorMapInfo / floorMapCanvas / floorMapLegend
    • 旧来の headerSection / sensingControlCard / realtimeDataDisplaySection / realtimeDataSection など
      はこの View 内から参照されていない private プロパティのため、現状「デッドコード」に近い状態です。

将来的に別レイアウトで再利用する予定がなければ、この PR か別 PR で削除するか、あるいはコメントに「今後使う予定」である旨を残すなど、役割を明確にしておくとコードベースがすっきりします(ガイドラインの「デッドコード削除」にも沿います)。

Also applies to: 239-285, 641-806, 811-909

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (2)

419-535: CSVエクスポートロジックの重複

空のアンテナIDを処理する部分(lines 419-470)と通常のアンテナIDを処理する部分(lines 472-535)で、raw/global/filteredの3種類のCSVファイルを出力するロジックが重複しています。

共通のヘルパーメソッドを抽出することで、コードの重複を削減できます:

private func exportCSVFiles(
    data: [RealtimeData],
    identifier: String,
    sessionDirectory: URL,
    globalCoordinates: [String: Point3D]
) throws {
    let sortedData = data.sorted { $0.timestamp < $1.timestamp }
    let baseFileName = self.currentFileName.isEmpty ? "sensing" : self.currentFileName
    
    // 生データ
    _ = try SensingDataCSVExporter.exportRawDataToCSV(
        realtimeDataList: sortedData,
        directoryURL: sessionDirectory,
        fileName: "\(baseFileName)_\(identifier)_raw.csv"
    )
    
    // グローバル座標データ
    _ = try SensingDataCSVExporter.exportGlobalCoordinateDataToCSV(
        realtimeDataList: sortedData,
        globalCoordinates: globalCoordinates,
        directoryURL: sessionDirectory,
        fileName: "\(baseFileName)_\(identifier)_global.csv"
    )
    
    // フィルタリング後データ
    let processor = SensorDataProcessor()
    _ = try SensingDataCSVExporter.exportFilteredDataToCSV(
        realtimeDataList: sortedData,
        globalCoordinates: globalCoordinates,
        processor: processor,
        directoryURL: sessionDirectory,
        fileName: "\(baseFileName)_\(identifier)_filtered.csv"
    )
}

このメソッドを使用することで、デバイス別・アンテナ別のエクスポート処理を統一できます。


154-541: デバッグ用print文の整理が必要

コーディングガイドラインに従い、デバッグ用コードは削除する必要があります。現在、多数のprint文が使用されていますが、本番環境では適切なログフレームワーク(os.logLogger)を使用すべきです。

以下のように変更することを推奨します:

import OSLog

private let logger = Logger(subsystem: "com.example.UWBViewerSystem", category: "DataCollection")

// print文を置き換え
logger.info("✅ SensingControlUsecaseに正しいSwiftDataRepositoryを設定しました")
logger.warning("⚠️ SwiftDataRepositoryが利用できません")
logger.error("❌ フロアマップ情報の読み込みに失敗: \(error)")

また、開発時のみ必要なデバッグログは#if DEBUGで囲むことを検討してください:

#if DEBUG
logger.debug("📊 アンテナごとのデータ分布: \(groupedByAntenna.count)")
#endif

コーディングガイドラインより

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc615d7 and 429fa3f.

📒 Files selected for processing (6)
  • UWBViewerSystem/Domain/DataModel/SwiftDataModels.swift (6 hunks)
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (7 hunks)
  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (5 hunks)
  • UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (3 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (6 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Domain/DataModel/SwiftDataModels.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
**/*Usecase.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Usecaseのファイル名は「〜Usecase.swift」とする

Files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
🧠 Learnings (7)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Applied to files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する

Applied to files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to Devices/**/*.swift : UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する

Applied to files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*Usecase.swift : Usecaseのファイル名は「〜Usecase.swift」とする

Applied to files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*ViewModel.swift : ViewModelのファイル名は「〜ViewModel.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
🧬 Code graph analysis (3)
UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (2)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
UWBViewerSystem/Domain/Utils/SensorDataProcessor.swift (1)
  • applyMovingAverageToPoints (76-96)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (2)
UWBViewerSystem/Devices/NearByConnection/NearByConnectionApi.swift (2)
  • sendDataToDevice (275-310)
  • requestConnection (194-216)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (1)
  • removeDisconnectedDevice (136-141)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (5)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (2)
  • updateSwiftDataRepository (41-47)
  • setFloorMapId (50-53)
UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (6)
  • loadAntennaPositions (309-316)
  • loadAntennaPositions (318-353)
  • loadAntennaPositions (964-964)
  • loadAntennaPositions (965-965)
  • loadFloorMap (648-654)
  • loadFloorMap (984-984)
UWBViewerSystem/Domain/Repository/DataRepository.swift (1)
  • loadAntennaPositions (76-79)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift (1)
  • setFloorMapId (111-113)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
🪛 SwiftLint (0.57.0)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift

[Warning] 278-278: where clauses are preferred over a single if inside a for

(for_where)

🔇 Additional comments (4)
UWBViewerSystem/Domain/DataModel/SwiftDataModels.swift (1)

146-185: PersistentRealtimeData への antennaId / sessionId 追加は整合していますが、sessionId の扱いは意図確認したいです

  • PersistentRealtimeDataantennaId / sessionId を追加し、RealtimeData.toPersistent(sessionId:) から正しく渡している点は良さそうです。
  • 一方で、sessionId 引数にデフォルト値 "" を持たせているため、既存呼び出しがそのままコンパイルできてしまい、「セッション紐付けを忘れても空文字で保存される」状態になります。

運用として「必ず有効な sessionId が必要」であれば、少なくとも新しい呼び出し箇所で確実に sessionId を渡せているかを一度洗い出しておくと安心です(将来的にはデフォルト引数を外すのも選択肢です)。
toEntity() 側で sessionId を使っていないのは、「セッション情報は別コンテキストで管理する」という設計であれば問題ありません。

Also applies to: 188-201, 407-421

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (1)

10-22: floorMapIdmodelContext の初期化フローは概ね良さそうです

  • init(floorMapId:)@StateObject を初期化し、onAppearmodelContext を使って Repository をセットアップしている流れは、Environment 経由で SwiftData を渡す前提として妥当です。
  • onAppear 内で loadFloorMapInfo(floorMapId:) を呼んでいるので、RealtimeDataUsecase.setFloorMapId まで適切に伝播していること(ViewModel 実装側)が鍵になります。

一点だけ、Previews では modelContext を提供していないので、必要に応じて:

#Preview {
    DataCollectionView(floorMapId: "test-floor-map-id")
        .modelContainer(for: [...]) // 必要なモデル
        .environmentObject(NavigationRouterModel())
}

のように Preview 用のコンテナを明示しておくと、プレビュー実行時のクラッシュを避けやすいです。

Also applies to: 24-90

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (2)

79-141: Observer設定の改善を確認

既存の購読をクリアしてから再設定する処理が追加されており、setupSwiftDataRepositoryでの再初期化に対応しています。また、すべてのsinkクロージャで[weak self]を使用しており、メモリリークを適切に防いでいます。


484-498: データの意味変更について確認が必要

Lines 484-498で、deviceNameantennaNameに置き換えた新しいRealtimeDataインスタンスを作成していますが、これはデータの本来の意味を変更しています。CSVファイルでdeviceName列にアンテナ名が出力されることになり、混乱を招く可能性があります。

以下のいずれかの対応を検討してください:

オプション1: CSVエクスポーターでヘッダーを変更
データを変更せず、CSVエクスポーター側でカラム名をantennaNameに変更する。

オプション2: 別のフィールドとして追加
deviceNameはそのままにして、antennaNameを追加フィールドとしてCSVに含める。

オプション3: 現在の実装を維持
意図的にdeviceNameをantennaNameで上書きしている場合は、その理由をコメントで明記してください。

どの方針が正しいか、ビジネス要件を確認することを推奨します。

Comment on lines +11 to 17
@Published var globalCoordinates: [String: Point3D] = [:] // デバイス名 → グローバル座標

// 複数アンテナ対応のプロパティ
@Published var antennaDataMap: [String: [DeviceRealtimeData]] = [:] // アンテナID別のデータ
@Published var activeAntennaIds = Set<String>() // アクティブなアンテナIDのセット
@Published var totalDataPointCount = 0 // 全アンテナの総データポイント数

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

クリア系メソッドで新しい状態 (globalCoordinates / antennaDataMap など) がリセットされていないように見えます

globalCoordinates / antennaDataMap / activeAntennaIds / totalDataPointCount を導入したのは良いのですが、

  • clearAllRealtimeData()deviceRealtimeDataListisReceivingRealtimeData しかクリアしていません。
  • clearRealtimeDataForSensing() も各 deviceData.clearData() のみで、新しい Published プロパティはそのまま残ります。

このままだと:

  • UI ではデータリストが空でも、アンテナ数や総データポイント数が古い値のまま表示される
  • フロアマップ上のタグ位置 (globalCoordinates) やアンテナ別マップも前回セッションの情報を引きずる

といった不整合が発生し得ます。

例えば次のようなリセットを追加することを検討してみてください:

func clearAllRealtimeData() {
    #if DEBUG
    print("🗑️ リアルタイムデータクリア")
    #endif
    self.deviceRealtimeDataList.removeAll()
    self.isReceivingRealtimeData = false
+   self.globalCoordinates.removeAll()
+   self.antennaDataMap.removeAll()
+   self.activeAntennaIds.removeAll()
+   self.totalDataPointCount = 0
    objectWillChange.send()
}

func clearRealtimeDataForSensing() {
    for deviceData in self.deviceRealtimeDataList {
        deviceData.clearData()
    }
+   self.globalCoordinates.removeAll()
+   self.antennaDataMap.removeAll()
+   self.activeAntennaIds.removeAll()
+   self.totalDataPointCount = 0
    objectWillChange.send()
}

こうしておくと、セッションの切り替えやリセット時に UI 状態が一貫しやすくなります。

Also applies to: 143-158, 241-259, 263-265

🤖 Prompt for AI Agents
In UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (around lines 11-17;
also review 143-158, 241-259, 263-265), the new Published properties
globalCoordinates, antennaDataMap, activeAntennaIds, and totalDataPointCount are
not being reset by the clear methods; update clearAllRealtimeData() and
clearRealtimeDataForSensing() (and any other clear/reset functions in the noted
ranges) to explicitly set globalCoordinates = [:], antennaDataMap = [:],
activeAntennaIds.removeAll(), and totalDataPointCount = 0 so UI state remains
consistent after session reset or sensing stop.

Comment on lines +174 to +180
/// 初期データの読み込み(非推奨:loadFloorMapInfo(floorMapId:)を使用すること)
private func loadInitialData() {
Task {
// フロアマップは画面遷移時に明示的に指定されるため、ここでは読み込まない
await self.loadAntennaPositions()
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

非推奨メソッドの扱いを明確化

コメントで非推奨と記載されていますが、@available属性が付いていません。また、Line 170のsetupSwiftDataRepositoryから実際に呼び出されているため、非推奨の意図と実装が矛盾しています。

以下のいずれかの対応を推奨します:

オプション1: 非推奨属性を追加

+    @available(*, deprecated, message: "loadFloorMapInfo(floorMapId:)を使用してください")
     private func loadInitialData() {

オプション2: コメントを削除(実際に使用されている場合)

-    /// 初期データの読み込み(非推奨:loadFloorMapInfo(floorMapId:)を使用すること)
+    /// 初期データの読み込み
     private func loadInitialData() {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// 初期データの読み込み(非推奨:loadFloorMapInfo(floorMapId:)を使用すること)
private func loadInitialData() {
Task {
// フロアマップは画面遷移時に明示的に指定されるため、ここでは読み込まない
await self.loadAntennaPositions()
}
}
/// 初期データの読み込み
private func loadInitialData() {
Task {
// フロアマップは画面遷移時に明示的に指定されるため、ここでは読み込まない
await self.loadAntennaPositions()
}
}
🤖 Prompt for AI Agents
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
lines 174-180: the method is marked "deprecated" only in a comment but is still
being called from setupSwiftDataRepository, causing a mismatch between intent
and implementation; either annotate the method with the Swift deprecation
attribute or remove the deprecation remark and keep it as active. If you intend
it deprecated, add @available(*, deprecated, message: "Use
loadFloorMapInfo(floorMapId:) instead") above the method, update or refactor
callers (e.g., change setupSwiftDataRepository to call the new API or suppress
the call with an explicit migration plan) and run tests; if you intend it to
remain in use, delete the "非推奨" comment so the code and comments are consistent.

Comment on lines +205 to +223
#if canImport(UIKit)
#if os(iOS)
self.floorMapImage = floorMap.image
if self.floorMapImage != nil {
print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
} else {
print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
}
#endif
#endif

#if os(macOS)
self.floorMapImage = floorMap.image
if self.floorMapImage != nil {
print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
} else {
print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
}
#endif
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

プラットフォーム固有コードの重複を解消

iOSとmacOSで同一のロジックが重複しています。DRY原則に違反しており、保守性を低下させます。

以下のように共通化を推奨します:

-                // フロアマップ画像を読み込み
-                #if canImport(UIKit)
-                    #if os(iOS)
-                        self.floorMapImage = floorMap.image
-                        if self.floorMapImage != nil {
-                            print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
-                        } else {
-                            print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
-                        }
-                    #endif
-                #endif
-
-                #if os(macOS)
-                    self.floorMapImage = floorMap.image
-                    if self.floorMapImage != nil {
-                        print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
-                    } else {
-                        print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
-                    }
-                #endif
+                // フロアマップ画像を読み込み
+                self.floorMapImage = floorMap.image
+                if self.floorMapImage != nil {
+                    print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
+                } else {
+                    print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
+                }

floorMapImageプロパティが既にプラットフォーム別に定義されているため、代入とログ処理は共通化できます。

🤖 Prompt for AI Agents
In
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
around lines 205–223, the identical iOS and macOS blocks assign
self.floorMapImage and print identical success/failure messages; collapse this
duplicated logic by moving the assignment and logging into a single shared path:
determine the platform-specific floorMap.image only where necessary (or rely on
the already platform-specific floorMapImage property), then perform one
assignment to self.floorMapImage and one conditional print based on nil/non-nil;
remove the duplicated #if os(iOS)/#if os(macOS) blocks so the assignment and log
are not repeated.

Comment on lines 274 to 286
// センシングデータをCSVとしてエクスポート
Task {
// SwiftDataの永続化完了を待つため少し待機
try? await Task.sleep(nanoseconds: 500_000_000) // 0.5秒待機

await self.exportSensingDataToCSV()

// CSV出力完了後にSensingControlUsecaseのセッションIDをクリア
await MainActor.run {
// SensingControlUsecaseのcurrentSessionIdをクリア
// これにより次のセンシングセッションで新しいIDが使用される
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

非同期処理の待機方法に問題あり

Line 277の0.5秒固定スリープは、SwiftDataの永続化完了を保証しない不確実な実装です。永続化に0.5秒以上かかる場合、データ損失のリスクがあります。

以下のいずれかの方法で適切な非同期制御を実装してください:

オプション1: SwiftDataの永続化完了を明示的に待機

// センシングデータをCSVとしてエクスポート
Task {
    // SwiftDataRepositoryに永続化完了を確認するメソッドを追加
    await swiftDataRepository?.waitForPersistence()
    await self.exportSensingDataToCSV()
}

オプション2: Notification/Combineで永続化完了を通知
SwiftDataRepositoryから永続化完了のイベントを発行し、それを待機する実装に変更してください。

🤖 Prompt for AI Agents
In
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
around lines 274–286, remove the fixed 0.5s Task.sleep and replace it with a
deterministic wait for SwiftData persistence: either (A) add a suspendable API
on your SwiftDataRepository (e.g., waitForPersistence or a completion async
method) and await that before calling exportSensingDataToCSV, or (B) publish a
persistence-completed event (NotificationCenter or Combine Publisher) from the
repository and start/export only after receiving that event; keep the MainActor
block that clears the SensingControlUsecase session ID but perform it after the
guaranteed persistence+export completion.

Comment on lines +282 to +285
await MainActor.run {
// SensingControlUsecaseのcurrentSessionIdをクリア
// これにより次のセンシングセッションで新しいIDが使用される
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

未実装のコメントブロックを削除または実装

Lines 282-285のコメントは実装が必要な処理を示唆していますが、実際のコードがありません。意図が不明確です。

以下のいずれかの対応が必要です:

オプション1: 必要な実装を追加

             await MainActor.run {
-                // SensingControlUsecaseのcurrentSessionIdをクリア
-                // これにより次のセンシングセッションで新しいIDが使用される
+                self.sensingControlUsecase.clearSessionId()
             }

オプション2: 不要な場合はコメントごと削除

-            // CSV出力完了後にSensingControlUsecaseのセッションIDをクリア
-            await MainActor.run {
-                // SensingControlUsecaseのcurrentSessionIdをクリア
-                // これにより次のセンシングセッションで新しいIDが使用される
-            }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
around lines 282-285, there is an empty MainActor.run block with only comments
suggesting clearing SensingControlUsecase.currentSessionId; either implement the
intended behavior or remove the comment block: if the session id must be
cleared, call into the SensingControlUsecase on the MainActor to set its
currentSessionId to nil (or the appropriate "cleared" value) ensuring you
reference the viewModel's usecase instance and maintain actor/thread-safety;
otherwise delete the comment and empty MainActor.run to avoid dead code.

Comment on lines 367 to 543
private func exportSensingDataToCSV() async {
print("📊 CSVエクスポート開始")
print(" デバイス数: \(self.deviceRealtimeDataList.count)")
print(" アンテナ数: \(self.activeAntennaIds.count)")
print(" ファイル名: \(self.currentFileName)")

// SensingControlUsecaseの実際のセッションIDを使用
guard let sessionId = sensingControlUsecase.activeSessionId else {
print("⚠️ アクティブなセッションIDが見つかりません")
return
}
print(" 使用するセッションID: \(sessionId)")

guard let sessionStartTime = self.startTime else {
print("⚠️ セッション開始時刻が見つかりません")
return
}

do {
// SwiftDataから全リアルタイムデータを読み込み
let allRealtimeData = try await swiftDataRepository?.loadRealtimeData(for: sessionId) ?? []

print(" SwiftDataから読み込んだデータ数: \(allRealtimeData.count)")

guard !allRealtimeData.isEmpty else {
print("⚠️ エクスポートするデータがありません")
return
}

// アンテナ位置情報を取得してアンテナID→アンテナ名のマッピングを作成
var antennaIdToNameMap: [String: String] = [:]
for antennaPosition in self.allAntennaPositions {
antennaIdToNameMap[antennaPosition.antennaId] = antennaPosition.antennaName
}

// アンテナIDごとにデータをグループ化
let groupedByAntenna = Dictionary(grouping: allRealtimeData) { $0.antennaId }

print("📊 アンテナごとのデータ分布:")
for (antennaId, data) in groupedByAntenna {
print(" アンテナID: \(antennaId.isEmpty ? "" : antennaId) - データ数: \(data.count)")
}

// センシングファイル名を使用してセッションディレクトリを作成
let sessionDirectory = try SensingDataCSVExporter.createSessionDirectory(
startTime: sessionStartTime,
customName: self.currentFileName.isEmpty ? nil : self.currentFileName
)

// 各アンテナごとにCSVファイルを出力
var exportedFileCount = 0
for (antennaId, antennaData) in groupedByAntenna {
guard !antennaId.isEmpty else {
print("⚠️ 空のアンテナIDをスキップ (データ数: \(antennaData.count))")
// 空のアンテナIDのデータも処理する(フォールバック)
if !antennaData.isEmpty {
// デバイス名でグループ化して処理
let deviceGroups = Dictionary(grouping: antennaData) { $0.deviceName }
for (deviceName, deviceData) in deviceGroups {
print("📱 デバイス \(deviceName) のデータをエクスポート中...")
let sortedData = deviceData.sorted { $0.timestamp < $1.timestamp }
let baseFileName = self.currentFileName.isEmpty ? "sensing" : self.currentFileName

// デバイス名を使用したファイル名
let rawFileName = "\(baseFileName)_\(deviceName)_raw.csv"
let globalFileName = "\(baseFileName)_\(deviceName)_global.csv"
let filteredFileName = "\(baseFileName)_\(deviceName)_filtered.csv"

// 生データをエクスポート
_ = try SensingDataCSVExporter.exportRawDataToCSV(
realtimeDataList: sortedData,
directoryURL: sessionDirectory,
fileName: rawFileName
)

// グローバル座標データ
var deviceGlobalCoordinates: [String: Point3D] = [:]
if let coord = self.globalCoordinates[deviceName] {
deviceGlobalCoordinates[deviceName] = coord
}

_ = try SensingDataCSVExporter.exportGlobalCoordinateDataToCSV(
realtimeDataList: sortedData,
globalCoordinates: deviceGlobalCoordinates,
directoryURL: sessionDirectory,
fileName: globalFileName
)

// フィルタリング後データ
let processor = SensorDataProcessor()
_ = try SensingDataCSVExporter.exportFilteredDataToCSV(
realtimeDataList: sortedData,
globalCoordinates: deviceGlobalCoordinates,
processor: processor,
directoryURL: sessionDirectory,
fileName: filteredFileName
)

exportedFileCount += 3
print("✅ デバイス \(deviceName) のデータエクスポート完了")
}
}
continue
}

// アンテナ名を取得(登録されていない場合はアンテナIDを使用)
let antennaName = antennaIdToNameMap[antennaId] ?? antennaId
print("📡 アンテナ \(antennaName) のデータをエクスポート中...")

// タイムスタンプでソート
let sortedData = antennaData.sorted { $0.timestamp < $1.timestamp }

// アンテナ名を含むファイル名を生成
let rawFileName = "\(self.currentFileName)_\(antennaName)_raw.csv"
let globalFileName = "\(self.currentFileName)_\(antennaName)_global.csv"
let filteredFileName = "\(self.currentFileName)_\(antennaName)_filtered.csv"

// デバイス名をアンテナ名に置換したデータを作成
let modifiedData = sortedData.map { data in
RealtimeData(
id: data.id,
deviceName: antennaName, // アンテナ名を使用
timestamp: data.timestamp,
elevation: data.elevation,
azimuth: data.azimuth,
distance: data.distance,
nlos: data.nlos,
rssi: data.rssi,
seqCount: data.seqCount,
antennaId: data.antennaId
)
}

// 生データをエクスポート
_ = try SensingDataCSVExporter.exportRawDataToCSV(
realtimeDataList: modifiedData,
directoryURL: sessionDirectory,
fileName: rawFileName
)

// このアンテナに関連するグローバル座標のみを抽出
var antennaGlobalCoordinates: [String: Point3D] = [:]
for data in antennaData {
if let coord = self.globalCoordinates[data.deviceName] {
antennaGlobalCoordinates[antennaName] = coord // アンテナ名をキーとして使用
}
}

// グローバル座標データをエクスポート
_ = try SensingDataCSVExporter.exportGlobalCoordinateDataToCSV(
realtimeDataList: modifiedData,
globalCoordinates: antennaGlobalCoordinates,
directoryURL: sessionDirectory,
fileName: globalFileName
)

// フィルタリング後データをエクスポート
let processor = SensorDataProcessor()
_ = try SensingDataCSVExporter.exportFilteredDataToCSV(
realtimeDataList: modifiedData,
globalCoordinates: antennaGlobalCoordinates,
processor: processor,
directoryURL: sessionDirectory,
fileName: filteredFileName
)

print("✅ アンテナ \(antennaName) のデータエクスポート完了")
print(" データポイント数: \(sortedData.count)")
}

print("✅ 全センシングデータのCSVエクスポート成功")
print(" セッションディレクトリ: \(sessionDirectory.path)")
print(" アンテナ別ファイル数: \(groupedByAntenna.count * 3) ファイル")
} catch {
print("❌ CSVエクスポートエラー: \(error.localizedDescription)")
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

メソッドが長すぎて複雑度が高い

このメソッドは176行あり、複数の責務を持っています。単一責任の原則(SRP)に違反しており、テストと保守が困難です。

以下のように小さなメソッドに分割することを推奨します:

private func exportSensingDataToCSV() async {
    guard let sessionId = sensingControlUsecase.activeSessionId,
          let sessionStartTime = self.startTime else {
        print("⚠️ エクスポートに必要な情報が不足しています")
        return
    }
    
    do {
        let allRealtimeData = try await loadRealtimeDataForExport(sessionId: sessionId)
        guard !allRealtimeData.isEmpty else {
            print("⚠️ エクスポートするデータがありません")
            return
        }
        
        let sessionDirectory = try createExportDirectory(startTime: sessionStartTime)
        let antennaIdToNameMap = buildAntennaIdToNameMap()
        let groupedByAntenna = Dictionary(grouping: allRealtimeData) { $0.antennaId }
        
        try await exportGroupedData(
            groupedByAntenna: groupedByAntenna,
            antennaIdToNameMap: antennaIdToNameMap,
            sessionDirectory: sessionDirectory
        )
        
        print("✅ 全センシングデータのCSVエクスポート成功")
    } catch {
        print("❌ CSVエクスポートエラー: \(error.localizedDescription)")
    }
}

private func loadRealtimeDataForExport(sessionId: String) async throws -> [RealtimeData] {
    // データ読み込みロジック
}

private func createExportDirectory(startTime: Date) throws -> URL {
    // ディレクトリ作成ロジック
}

private func buildAntennaIdToNameMap() -> [String: String] {
    // アンテナIDとアンテナ名のマッピング作成
}

private func exportGroupedData(
    groupedByAntenna: [String: [RealtimeData]],
    antennaIdToNameMap: [String: String],
    sessionDirectory: URL
) async throws {
    // グループ化されたデータのエクスポート
}

private func exportDataForAntenna(
    antennaId: String,
    antennaData: [RealtimeData],
    antennaName: String,
    sessionDirectory: URL
) throws {
    // 単一アンテナのデータエクスポート
}

private func exportDataForDevice(
    deviceName: String,
    deviceData: [RealtimeData],
    sessionDirectory: URL
) throws {
    // 単一デバイスのデータエクスポート(空アンテナID用)
}

この分割により、各メソッドの責務が明確になり、テスト可能性と保守性が向上します。

🤖 Prompt for AI Agents
In
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
around lines 367-543, the exportSensingDataToCSV method is too long and violates
SRP; split it into smaller helper methods: extract an async
loadRealtimeDataForExport(sessionId:) that returns [RealtimeData],
createExportDirectory(startTime:customName:) that returns URL,
buildAntennaIdToNameMap() -> [String:String],
exportGroupedData(groupedByAntenna:antennaIdToNameMap:sessionDirectory:) async
throws to orchestrate per-antenna exports,
exportDataForAntenna(antennaId:antennaData:antennaName:sessionDirectory:) throws
to handle antenna-normal path, and
exportDataForDevice(deviceName:deviceData:sessionDirectory:) throws to handle
empty-antenna fallback; ensure proper async/throws propagation, move sorting,
filename generation, coordinate mapping and CSV-export calls into those helpers,
preserve existing logging and error handling, and update the top-level
exportSensingDataToCSV to perform validations then call these helpers so the
main method becomes concise and testable.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (7)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (5)

205-223: プラットフォーム固有コードの重複が未修正です(重複指摘)

過去のレビューで指摘された通り、iOSとmacOSで同一のロジックが重複しています。DRY原則に違反しており、保守性を低下させます。

以下のように共通化を推奨します:

                 // フロアマップIDをRealtimeDataUsecaseに設定
                 self.realtimeDataUsecase.setFloorMapId(floorMap.id)

                 // フロアマップ画像を読み込み
-                #if canImport(UIKit)
-                    #if os(iOS)
-                        self.floorMapImage = floorMap.image
-                        if self.floorMapImage != nil {
-                            print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
-                        } else {
-                            print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
-                        }
-                    #endif
-                #endif
-
-                #if os(macOS)
-                    self.floorMapImage = floorMap.image
-                    if self.floorMapImage != nil {
-                        print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
-                    } else {
-                        print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
-                    }
-                #endif
+                self.floorMapImage = floorMap.image
+                if self.floorMapImage != nil {
+                    print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
+                } else {
+                    print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
+                }

                 print("📍 フロアマップ情報読み込み完了: \(floorMap.name) (ID: \(floorMap.id))")

floorMapImageプロパティが既にプラットフォーム別に定義されているため、代入とログ処理は共通化できます。


371-551: メソッドが長すぎる問題が未修正です(重複指摘)

過去のレビューで指摘された通り、このメソッドは180行あり、複数の責務を持っています。単一責任の原則(SRP)に違反しており、テストと保守が困難です。

以下のように小さなメソッドに分割することを推奨します:

private func exportSensingDataToCSV(fileName: String = "") async {
    guard let sessionId = sensingControlUsecase.activeSessionId,
          let sessionStartTime = self.startTime else {
        return
    }
    
    do {
        let allRealtimeData = try await loadRealtimeDataForExport(sessionId: sessionId)
        guard !allRealtimeData.isEmpty else { return }
        
        let sessionDirectory = try createExportDirectory(
            startTime: sessionStartTime, 
            customName: fileName.isEmpty ? self.currentFileName : fileName
        )
        let antennaIdToNameMap = buildAntennaIdToNameMap()
        let groupedByAntenna = Dictionary(grouping: allRealtimeData) { $0.antennaId }
        
        try await exportGroupedData(
            groupedByAntenna: groupedByAntenna,
            antennaIdToNameMap: antennaIdToNameMap,
            sessionDirectory: sessionDirectory,
            fileNameToUse: fileName.isEmpty ? self.currentFileName : fileName
        )
    } catch {
        // エラーハンドリング
    }
}

private func loadRealtimeDataForExport(sessionId: String) async throws -> [RealtimeData] {
    try await swiftDataRepository?.loadRealtimeData(for: sessionId) ?? []
}

private func createExportDirectory(startTime: Date, customName: String) throws -> URL {
    try SensingDataCSVExporter.createSessionDirectory(startTime: startTime, customName: customName)
}

private func buildAntennaIdToNameMap() -> [String: String] {
    var map: [String: String] = [:]
    for antenna in self.allAntennaPositions {
        map[antenna.antennaId] = antenna.antennaName
    }
    return map
}

private func exportGroupedData(
    groupedByAntenna: [String: [RealtimeData]],
    antennaIdToNameMap: [String: String],
    sessionDirectory: URL,
    fileNameToUse: String
) throws {
    // グループ化されたデータのエクスポートロジック
}

この分割により、各メソッドの責務が明確になり、テスト可能性と保守性が向上します。


174-180: 非推奨メソッドの扱いが未修正です(重複指摘)

過去のレビューで指摘された通り、Line 174のコメントで「非推奨」と記載されていますが、@available属性が付いていません。また、Line 170のsetupSwiftDataRepositoryから実際に呼び出されているため、非推奨の意図と実装が矛盾しています。

以下のいずれかの対応を推奨します:

オプション1: 非推奨属性を追加

+    @available(*, deprecated, message: "loadFloorMapInfo(floorMapId:)を使用してください")
     private func loadInitialData() {

オプション2: コメントを削除(実際に使用されている場合)

-    /// 初期データの読み込み(非推奨:loadFloorMapInfo(floorMapId:)を使用すること)
+    /// 初期データの読み込み
     private func loadInitialData() {

277-285: 非同期処理の待機方法が未修正です(重複指摘)

過去のレビューで指摘された通り、Line 280の0.5秒固定スリープは、SwiftDataの永続化完了を保証しない不確実な実装です。永続化に0.5秒以上かかる場合、データ損失のリスクがあります。

以下のいずれかの方法で適切な非同期制御を実装してください:

オプション1: SwiftDataの永続化完了を明示的に待機

// センシングデータをCSVとしてエクスポート
Task {
    // SwiftDataRepositoryに永続化完了を確認するメソッドを追加
    await swiftDataRepository?.waitForPersistence()
    await self.exportSensingDataToCSV(fileName: savedFileName)
}

オプション2: Notification/Combineで永続化完了を通知
SwiftDataRepositoryから永続化完了のイベントを発行し、それを待機する実装に変更してください。


286-289: 未実装のコメントブロックが未修正です(重複指摘)

過去のレビューで指摘された通り、Lines 286-289のコメントは実装が必要な処理を示唆していますが、実際のコードがありません。意図が不明確です。

以下のいずれかの対応が必要です:

オプション1: 必要な実装を追加

             // CSV出力完了後にSensingControlUsecaseのセッションIDをクリア
             await MainActor.run {
-                // SensingControlUsecaseのcurrentSessionIdをクリア
-                // これにより次のセンシングセッションで新しいIDが使用される
+                // 必要に応じてセッションIDをクリアする処理を実装
+                // 例: self.sensingControlUsecase.clearSessionId()
             }

オプション2: 不要な場合はコメントごと削除

             await self.exportSensingDataToCSV(fileName: savedFileName)

-            // CSV出力完了後にSensingControlUsecaseのセッションIDをクリア
-            await MainActor.run {
-                // SensingControlUsecaseのcurrentSessionIdをクリア
-                // これにより次のセンシングセッションで新しいIDが使用される
-            }
         }
UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (2)

146-200: globalCoordinatesの設計問題が未修正です(重複指摘)

過去のレビューで指摘された通り、globalCoordinates: [String: Point3D]の構造では、各タイムスタンプ行に対して適切な座標を出力できません。

Line 178のglobalCoordinates[data.deviceName]は、同一デバイスの全てのタイムスタンプに対して同じ座標を返すため、時系列の軌跡データとして意味を持ちません。

以下のいずれかの対応が必要です:

オプション1: パラメータ構造を変更

// RealtimeData.IDごとに座標を持つ
static func exportGlobalCoordinateDataToCSV(
    realtimeDataList: [RealtimeData],
    globalCoordinates: [RealtimeData.ID: Point3D],  // 変更
    directoryURL: URL,
    fileName: String = "global_coordinates.csv"
) throws -> URL {
    // ...
    for data in realtimeDataList {
        let globalCoord = globalCoordinates[data.id] ?? Point3D(x: 0, y: 0, z: 0)
        // ...
    }
}

オプション2: エクスポータ内部で座標変換を実行
RealtimeDataからグローバル座標を都度計算する実装に変更してください。


220-277: フィルタリング後データのインデックス不整合が未修正です(重複指摘)

過去のレビューで指摘された通り、Line 244のcompactMap実装により、coordinates配列とsortedData配列の要素数が一致しない問題が残っています。

具体的な問題:

  1. compactMap { globalCoordinates[$0.deviceName] }は、同一デバイスの複数サンプルに対して同じ座標(または存在しない場合はnil)を返すため、実質的に1座標しか取得できません
  2. Line 253のguard index < filteredCoordinates.count else { break }により、末尾のデータが出力されない可能性があります
  3. coordinatessortedDataの要素が1対1で対応していないため、Line 255-256でのインデックスアクセスが不正確です

修正方法:

// デバイスごとにグループ化
let groupedData = Dictionary(grouping: realtimeDataList) { $0.deviceName }

var csvContent =
    "timestamp,deviceName,antennaId,filtered_x,filtered_y,filtered_z,original_x,original_y,original_z\n"

for (deviceName, deviceData) in groupedData.sorted(by: { $0.key < $1.key }) {
    let sortedData = deviceData.sorted { $0.timestamp < $1.timestamp }
    
    // 各データポイントに対応する座標配列を作成(1対1対応)
    let coordinates = sortedData.map { data in
        globalCoordinates[data.id] ?? Point3D(x: 0, y: 0, z: 0)  // IDベースでルックアップ
    }
    
    guard !coordinates.isEmpty else { continue }
    
    let filteredCoordinates = processor.applyMovingAverageToPoints(coordinates)
    
    // インデックスが確実に対応している状態でループ
    for (index, data) in sortedData.enumerated() {
        let originalCoord = coordinates[index]
        let filteredCoord = filteredCoordinates[index]
        // CSV行を作成
    }
}

ただし、この修正もglobalCoordinatesの構造が[String: Point3D]から[RealtimeData.ID: Point3D]に変更されることを前提としています。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 429fa3f and d8de131.

📒 Files selected for processing (3)
  • UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (7 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (6 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
🧠 Learnings (8)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : 不要なimport文は削除する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to Devices/**/*.swift : UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T14:37:07.451Z
Learnt from: harutiro
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: :0-0
Timestamp: 2025-09-23T14:37:07.451Z
Learning: UWBViewerSystemでUserDefaults変更通知のテストを行う際は、NotificationCenterの非同期処理完了を確実に待機するため、1.5秒程度の待機時間とポーリング方式の検証を使用する。これにより競合状態やタイミング問題を回避できる。

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*ViewModel.swift : ViewModelのファイル名は「〜ViewModel.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
🧬 Code graph analysis (3)
UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (2)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
UWBViewerSystem/Domain/Utils/SensorDataProcessor.swift (1)
  • applyMovingAverageToPoints (76-96)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (3)
UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (8)
  • loadAntennaPositions (309-316)
  • loadAntennaPositions (318-353)
  • loadAntennaPositions (964-964)
  • loadAntennaPositions (965-965)
  • loadFloorMap (648-654)
  • loadFloorMap (984-984)
  • loadRealtimeData (495-514)
  • loadRealtimeData (973-973)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift (1)
  • setFloorMapId (111-113)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (3)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (2)
  • setupSwiftDataRepository (144-172)
  • loadFloorMapInfo (183-188)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)
  • loadFloorMapInfo (80-87)
UWBViewerSystem/Presentation/Components/FloorMapCanvas.swift (2)
  • realWorldToNormalized (339-355)
  • normalizedToImageCoordinate (323-328)
🪛 SwiftLint (0.57.0)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift

[Warning] 95-95: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🔇 Additional comments (6)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (2)

1-141: SwiftData統合と複数アンテナ対応の実装が適切です

以下の変更が正しく実装されています:

  • SwiftDataRepositoryの依存性注入
  • フロアマップとアンテナ位置の@publishedプロパティ追加
  • 複数アンテナ対応のためのantennaDataMap、activeAntennaIds、totalDataPointCount
  • setupObservers内でのcancellables.removeAll()による重複購読の防止

これらの変更により、Clean Architecture + MVVMのデータフローが適切に維持されています。


450-454: globalCoordinates設計問題の影響がこのメソッドにも及んでいます

Lines 450-454とLines 516-521で、self.globalCoordinates[deviceName]を使用してCSVエクスポートを行っていますが、SensingDataCSVExporterの設計問題により、時系列データとして正確な座標が出力されません。

具体的には:

  • Line 452: self.globalCoordinates[deviceName]は単一の座標のみを返します
  • これがSensingDataCSVExporter.exportGlobalCoordinateDataToCSVに渡され、全てのタイムスタンプ行が同じ座標を持つCSVが生成されます

SensingDataCSVExporterのglobalCoordinatesパラメータ設計を修正する際に、このメソッドの呼び出し側も合わせて修正が必要です。

SensingDataCSVExporterの設計修正後、以下のようにRealtimeData.IDベースの座標マッピングに変更してください:

// 各RealtimeDataのIDに対応するグローバル座標を準備
var realtimeDataCoordinates: [RealtimeData.ID: Point3D] = [:]
for data in sortedData {
    if let coord = /* 適切な座標取得ロジック */ {
        realtimeDataCoordinates[data.id] = coord
    }
}

_ = try SensingDataCSVExporter.exportGlobalCoordinateDataToCSV(
    realtimeDataList: sortedData,
    globalCoordinates: realtimeDataCoordinates,  // 修正後の型
    directoryURL: sessionDirectory,
    fileName: globalFileName
)

Also applies to: 516-521

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (4)

16-22: ViewModelの初期化パターンが適切です

@StateObjectの初期化をinit内で行うアプローチは正しい実装です。modelContext@Environmentを通じてランタイムで取得されるため、onAppearsetupSwiftDataRepositoryを呼び出してSwiftDataを設定する設計は適切です。

Clean Architecture + MVVMの原則に従った依存性の注入が実現されています。


206-300: コンパクトUIの実装が適切です

以下の点で優れた実装となっています:

  1. 関心の分離: compactSensingControlcompactRealtimeDataDisplayを分離
  2. 再利用可能なコンポーネント: CompactAntennaGroupViewCompactDeviceDataViewを独立したビューとして実装
  3. 条件付きレンダリング: アンテナIDの有無に基づいた適切なフォールバック処理
  4. 可読性: 各コンポーネントが明確な責務を持ち、理解しやすい構造

ViewModelとのバインディングも@Publishedプロパティを通じて適切に実装されており、MVVMパターンに準拠しています。

Also applies to: 826-945


947-1162: リアルタイムデバイスカードの実装が優れています

RealtimeDeviceCardViewの実装は以下の点で優れています:

  1. NLOS検出のビジュアル表現: Lines 1002-1021とLines 1058-1071で、NLOS状態を明確に視覚化
  2. データ品質インジケータ: Lines 1025-1053でデータ品質を5段階で表示
  3. リアルタイム更新の視覚的フィードバック: 色とステータスで接続状態を表現
  4. 適切なコンポーネント分割: dataQualityBarmainMeasurementsauxiliaryInfoなどのプライベートビュー

ユーザーエクスペリエンスとコード保守性の両面で優れた設計です。


656-822: デッドコードを削除してください

floorMapDisplaySectionおよびその関連サブビュー(floorMapHeaderfloorMapInfofloorMapCanvasfloorMapLegend)はファイル内で定義されていますが、どこからも参照されていません。メインのbodyプロパティはこれらを使用せず、代わりにfullScreenFloorMapを使用しています。

Lines 656-822の以下を削除してください:

  • floorMapDisplaySection
  • floorMapHeader
  • floorMapInfo
  • floorMapCanvas
  • floorMapLegend

コーディングガイドラインに従い、不要なデッドコードは削除する必要があります。

⛔ Skipped due to learnings
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する


/// センシングセッション用のディレクトリを作成
///
/// ディレクトリ構造: /Applications/sensing/yyyymmdd/customName_hhmmss/
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

コメントの不正確性とデバッグコードの削除が必要です

Line 35のコメントで「Applications」と記載されていますが、実際のコードではDocumentsディレクトリを使用しています。また、Lines 65-71とLines 86-88のデバッグ用print文は、コーディングガイドラインに従って削除する必要があります。

以下の修正を適用してください:

-    /// ディレクトリ構造: /Applications/sensing/yyyymmdd/customName_hhmmss/
+    /// ディレクトリ構造: Documents/sensing/yyyymmdd/customName_hhmmss/
     ///
     /// - Parameters:
     ///   - startTime: センシング開始時刻
     ///   - customName: カスタムディレクトリ名(省略時はhhmmssのみ)
     /// - Returns: 作成されたディレクトリのURL
     /// - Throws: ディレクトリ作成に失敗した場合
     static func createSessionDirectory(startTime: Date, customName: String) throws -> URL {
         // Documentsディレクトリを取得(ファイルアプリから見えるようにするため)
         guard let documentsDirectory = FileManager.default.urls(
             for: .documentDirectory,
             in: .userDomainMask
         ).first else {
             throw ExportError.fileCreationFailed("Documentsディレクトリが見つかりません")
         }

         // 日付フォーマッターの設定
         let dateFormatter = DateFormatter()
         dateFormatter.locale = Locale(identifier: "en_US_POSIX")
         dateFormatter.timeZone = TimeZone.current

         // 日付ディレクトリ名 (yyyymmdd)
         dateFormatter.dateFormat = "yyyyMMdd"
         let dateString = dateFormatter.string(from: startTime)

         // 時刻ディレクトリ名 (hhmmss)
         dateFormatter.dateFormat = "HHmmss"
         let timeString = dateFormatter.string(from: startTime)

         // ディレクトリ名を決定(カスタム名がある場合は「hhmmss-customName」形式)
-        print("🔍 createSessionDirectory - customName: '\(customName)' (空?: \(customName.isEmpty))")
         let directoryName = if !customName.isEmpty {
             "\(timeString)-\(customName)"
         } else {
             timeString
         }
-        print("🔍 決定されたディレクトリ名: '\(directoryName)'")

         // ディレクトリパスを構築
         let sessionDirectory = documentsDirectory
             .appendingPathComponent("sensing")
             .appendingPathComponent(dateString)
             .appendingPathComponent(directoryName)

         // ディレクトリを作成
         try FileManager.default.createDirectory(
             at: sessionDirectory,
             withIntermediateDirectories: true,
             attributes: nil
         )

-        print("✅ セッションディレクトリを作成: \(sessionDirectory.path)")
-        print("📁 Documentsディレクトリ: \(documentsDirectory.path)")
-        print("📁 相対パス: sensing/\(dateString)/\(directoryName)")

         return sessionDirectory
     }

Based on learnings, デバッグ用コードは削除し、コメントは必要最小限に留めてください。

Also applies to: 65-71, 86-88

🤖 Prompt for AI Agents
In UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift around line 35,
update the header comment to accurately reflect the actual directory used
(Documents directory) and keep it minimal (e.g., "Directory structure:
~/Documents/sensing/yyyymmdd/customName_hhmmss/"); additionally remove all
debugging print statements on lines 65-71 and 86-88 so no debug prints remain in
production code, and leave only necessary logging if required by the coding
guidelines.

Comment on lines +288 to +296
private static func writeCSV(content: String, to fileURL: URL) throws -> URL {
do {
try content.write(to: fileURL, atomically: true, encoding: .utf8)
print("✅ CSVファイルを保存しました: \(fileURL.path)")
return fileURL
} catch {
throw ExportError.writeFailed("ファイル書き込みに失敗: \(error.localizedDescription)")
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

残りのデバッグprint文を削除してください

Lines 291と365-366のデバッグ用print文は、コーディングガイドラインに従って削除する必要があります。

     private static func writeCSV(content: String, to fileURL: URL) throws -> URL {
         do {
             try content.write(to: fileURL, atomically: true, encoding: .utf8)
-            print("✅ CSVファイルを保存しました: \(fileURL.path)")
             return fileURL
         } catch {
             throw ExportError.writeFailed("ファイル書き込みに失敗: \(error.localizedDescription)")
         }
     }
         let filteredDataURL = try exportFilteredDataToCSV(
             realtimeDataList: realtimeDataList,
             globalCoordinates: globalCoordinates,
             processor: processor,
             directoryURL: sessionDirectory
         )

-        print("✅ 全センシングデータのエクスポート完了")
-        print("   セッションディレクトリ: \(sessionDirectory.path)")

         return (
             sessionDirectory: sessionDirectory,
             rawDataURL: rawDataURL,
             globalCoordinateURL: globalCoordinateURL,
             filteredDataURL: filteredDataURL
         )

Based on learnings, デバッグ用コードは削除してください。

Also applies to: 365-366

🤖 Prompt for AI Agents
In UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift around lines
288–296 and also remove the remaining debug prints at lines 365–366: delete the
debug print calls (the "✅ CSVファイルを保存しました..." at ~291 and the prints at 365–366)
so no stray debugging output remains; if persistent feedback is required replace
them with the project logging mechanism or silent error handling and ensure the
method signatures and error handling still compile.

Comment on lines +94 to +95
private var fullScreenFloorMap: some View {
GeometryReader { geometry in
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

未使用パラメータを_に置き換えてください

静的解析ツールが指摘している通り、Line 95のgeometryパラメータは使用されていません。クロージャ内ではFloorMapCanvasから提供されるcanvasGeometryを使用しているため、外側のgeometryは不要です。

以下の修正を適用してください:

     private var fullScreenFloorMap: some View {
-        GeometryReader { geometry in
+        GeometryReader { _ in
             if let floorMapImage = self.viewModel.floorMapImage,
                let floorMapInfo = self.viewModel.currentFloorMapInfo {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private var fullScreenFloorMap: some View {
GeometryReader { geometry in
private var fullScreenFloorMap: some View {
GeometryReader { _ in
if let floorMapImage = self.viewModel.floorMapImage,
let floorMapInfo = self.viewModel.currentFloorMapInfo {
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 95-95: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🤖 Prompt for AI Agents
In
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
around lines 94–95, the GeometryReader closure parameter `geometry` is unused;
replace the named parameter with an underscore to silence the unused-parameter
warning (i.e., change the closure signature from `GeometryReader { geometry in`
to `GeometryReader { _ in`), leaving the inner body unchanged.

Comment on lines 372 to 549
print("📊 CSVエクスポート開始")
print(" デバイス数: \(self.deviceRealtimeDataList.count)")
print(" アンテナ数: \(self.activeAntennaIds.count)")
print(" ファイル名(引数): '\(fileName)'")
print(" ファイル名(currentFileName): '\(self.currentFileName)'")

// SensingControlUsecaseの実際のセッションIDを使用
guard let sessionId = sensingControlUsecase.activeSessionId else {
print("⚠️ アクティブなセッションIDが見つかりません")
return
}
print(" 使用するセッションID: \(sessionId)")

guard let sessionStartTime = self.startTime else {
print("⚠️ セッション開始時刻が見つかりません")
return
}

do {
// SwiftDataから全リアルタイムデータを読み込み
let allRealtimeData = try await swiftDataRepository?.loadRealtimeData(for: sessionId) ?? []

print(" SwiftDataから読み込んだデータ数: \(allRealtimeData.count)")

guard !allRealtimeData.isEmpty else {
print("⚠️ エクスポートするデータがありません")
return
}

// アンテナ位置情報を取得してアンテナID→アンテナ名のマッピングを作成
var antennaIdToNameMap: [String: String] = [:]
for antennaPosition in self.allAntennaPositions {
antennaIdToNameMap[antennaPosition.antennaId] = antennaPosition.antennaName
}

// アンテナIDごとにデータをグループ化
let groupedByAntenna = Dictionary(grouping: allRealtimeData) { $0.antennaId }

print("📊 アンテナごとのデータ分布:")
for (antennaId, data) in groupedByAntenna {
print(" アンテナID: \(antennaId.isEmpty ? "" : antennaId) - データ数: \(data.count)")
}

// センシングファイル名を使用してセッションディレクトリを作成
let fileNameToUse = fileName.isEmpty ? self.currentFileName : fileName
print("📁 センシングファイル名: '\(fileNameToUse)' (空?: \(fileNameToUse.isEmpty))")
let sessionDirectory = try SensingDataCSVExporter.createSessionDirectory(
startTime: sessionStartTime,
customName: fileNameToUse
)
print("📁 作成されたセッションディレクトリ: \(sessionDirectory.path)")

// 各アンテナごとにCSVファイルを出力
var exportedFileCount = 0
for (antennaId, antennaData) in groupedByAntenna {
guard !antennaId.isEmpty else {
print("⚠️ 空のアンテナIDをスキップ (データ数: \(antennaData.count))")
// 空のアンテナIDのデータも処理する(フォールバック)
if !antennaData.isEmpty {
// デバイス名でグループ化して処理
let deviceGroups = Dictionary(grouping: antennaData) { $0.deviceName }
for (deviceName, deviceData) in deviceGroups {
print("📱 デバイス \(deviceName) のデータをエクスポート中...")
let sortedData = deviceData.sorted { $0.timestamp < $1.timestamp }
let baseFileName = fileNameToUse.isEmpty ? "sensing" : fileNameToUse

// デバイス名を使用したファイル名
let rawFileName = "\(baseFileName)_\(deviceName)_raw.csv"
let globalFileName = "\(baseFileName)_\(deviceName)_global.csv"
let filteredFileName = "\(baseFileName)_\(deviceName)_filtered.csv"

// 生データをエクスポート
_ = try SensingDataCSVExporter.exportRawDataToCSV(
realtimeDataList: sortedData,
directoryURL: sessionDirectory,
fileName: rawFileName
)

// グローバル座標データ
var deviceGlobalCoordinates: [String: Point3D] = [:]
if let coord = self.globalCoordinates[deviceName] {
deviceGlobalCoordinates[deviceName] = coord
}

_ = try SensingDataCSVExporter.exportGlobalCoordinateDataToCSV(
realtimeDataList: sortedData,
globalCoordinates: deviceGlobalCoordinates,
directoryURL: sessionDirectory,
fileName: globalFileName
)

// フィルタリング後データ
let processor = SensorDataProcessor()
_ = try SensingDataCSVExporter.exportFilteredDataToCSV(
realtimeDataList: sortedData,
globalCoordinates: deviceGlobalCoordinates,
processor: processor,
directoryURL: sessionDirectory,
fileName: filteredFileName
)

exportedFileCount += 3
print("✅ デバイス \(deviceName) のデータエクスポート完了")
}
}
continue
}

// アンテナ名を取得(登録されていない場合はアンテナIDを使用)
let antennaName = antennaIdToNameMap[antennaId] ?? antennaId
print("📡 アンテナ \(antennaName) のデータをエクスポート中...")

// タイムスタンプでソート
let sortedData = antennaData.sorted { $0.timestamp < $1.timestamp }

// アンテナ名を含むファイル名を生成
let rawFileName = "\(fileNameToUse)_\(antennaName)_raw.csv"
let globalFileName = "\(fileNameToUse)_\(antennaName)_global.csv"
let filteredFileName = "\(fileNameToUse)_\(antennaName)_filtered.csv"

// デバイス名をアンテナ名に置換したデータを作成
let modifiedData = sortedData.map { data in
RealtimeData(
id: data.id,
deviceName: antennaName, // アンテナ名を使用
timestamp: data.timestamp,
elevation: data.elevation,
azimuth: data.azimuth,
distance: data.distance,
nlos: data.nlos,
rssi: data.rssi,
seqCount: data.seqCount,
antennaId: data.antennaId
)
}

// 生データをエクスポート
_ = try SensingDataCSVExporter.exportRawDataToCSV(
realtimeDataList: modifiedData,
directoryURL: sessionDirectory,
fileName: rawFileName
)

// このアンテナに関連するグローバル座標のみを抽出
var antennaGlobalCoordinates: [String: Point3D] = [:]
for data in antennaData {
if let coord = self.globalCoordinates[data.deviceName] {
antennaGlobalCoordinates[antennaName] = coord // アンテナ名をキーとして使用
}
}

// グローバル座標データをエクスポート
_ = try SensingDataCSVExporter.exportGlobalCoordinateDataToCSV(
realtimeDataList: modifiedData,
globalCoordinates: antennaGlobalCoordinates,
directoryURL: sessionDirectory,
fileName: globalFileName
)

// フィルタリング後データをエクスポート
let processor = SensorDataProcessor()
_ = try SensingDataCSVExporter.exportFilteredDataToCSV(
realtimeDataList: modifiedData,
globalCoordinates: antennaGlobalCoordinates,
processor: processor,
directoryURL: sessionDirectory,
fileName: filteredFileName
)

print("✅ アンテナ \(antennaName) のデータエクスポート完了")
print(" データポイント数: \(sortedData.count)")
}

print("✅ 全センシングデータのCSVエクスポート成功")
print(" セッションディレクトリ: \(sessionDirectory.path)")
print(" アンテナ別ファイル数: \(groupedByAntenna.count * 3) ファイル")
} catch {
print("❌ CSVエクスポートエラー: \(error.localizedDescription)")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

デバッグprint文を削除してください

Lines 372-549に多数のデバッグ用print文が含まれています。コーディングガイドラインに従って、これらを削除する必要があります。

主なデバッグprint文の箇所:

  • Lines 372-383: セッション情報のデバッグ出力
  • Lines 394, 410-413: データ読み込み状況
  • Lines 417-422: ディレクトリ作成情報
  • Lines 428, 434-474: アンテナ/デバイスごとのエクスポート状況
  • Lines 482, 541-546: エクスポート完了メッセージ

本番環境では適切なロギングフレームワーク(OSLogなど)の使用を検討してください。

Based on learnings, デバッグ用コードは削除し、必要に応じて適切なロギング機構を使用してください。

🤖 Prompt for AI Agents
In
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
around lines 372-549, remove all debugging print(...) statements listed in the
review (session info, data load counts, directory creation, per-antenna/device
export progress, and final success messages); replace only user/ops-relevant
messages with a proper logging API (e.g., use os.log / Logger) at appropriate
levels (info/error) where persistent logs are needed, and convert the catch
block to log.error the error instead of printing; ensure no stray debug prints
remain and that behavior (export flow, error handling, return/continuation) is
unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (1)

93-103: GeometryReader のクロージャ引数 geometry が未使用です

GeometryReader { geometry in ... }geometry は内部で参照されておらず、FloorMapCanvas からの canvasGeometry だけを使っています。SwiftLint の unused_closure_parameter 警告を避けるため、以前の指摘どおり GeometryReader { _ in ... } に変更するのがよさそうです。

🧹 Nitpick comments (4)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (4)

95-203: フロアマップ上のアンテナ/タグ描画ロジックが重複しています

fullScreenFloorMap 内と floorMapCanvas(floorMapInfo:) 内で、アンテナとタグ座標の正規化→画像座標変換→ZStack でピンを描画する処理がほぼ同一に重複しています。将来的な仕様変更時に両方を同期して修正する必要があり、メンテナンスコストが高くなるので、FloorMapOverlayView(geometry:viewModel:) のような小さな View に共通ロジックを切り出して再利用することを検討してください。

Also applies to: 722-803


483-489: デバッグ用テキストはリリースビルドでは無効化した方がよさそうです

Text("🔍 セクション内部: count=...") は明示的に「デバッグ表示」とコメントされており、本番 UI には不要だと思われます。#if DEBUG ... #endif で条件コンパイルするか、この行自体を削除することを検討してください。As per coding guidelines, ...


664-690: floorMapDisplaySection など未使用のセクション View が残っています

現状の body からは floorMapDisplaySection が参照されておらず、同様に realtimeDataDisplaySectionrealtimeDataSectionheaderSectionsensingControlCardrecentSessionsCard もこのファイル内で未使用に見えます。今後も使わない想定であれば削除した方がコードが読みやすくなり、メンテナンス対象も減らせます(将来再利用する意図があるなら、その旨コメントで補足すると読んだ人に伝わりやすいです)。As per coding guidelines, ...


1563-1566: SwiftData 利用 View の Preview で modelContainer 設定を確認してください

この View は @Environment(\.modelContext) を参照するため、Preview で上位に modelContainer が無い場合は実行時クラッシュする可能性があります。既にアプリ共通の Preview ラッパーで設定済みでなければ、#Preview.modelContainer(for: [...])(もしくは専用の in-memory Container)を追加するか、SwiftData 依存のないスタブ用 ViewModel を使う形も検討してください。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8de131 and c290c12.

📒 Files selected for processing (1)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
🧠 Learnings (3)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*ViewModel.swift : ViewModelのファイル名は「〜ViewModel.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
🧬 Code graph analysis (1)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (3)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (2)
  • setupSwiftDataRepository (144-172)
  • loadFloorMapInfo (183-188)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)
  • loadFloorMapInfo (80-87)
UWBViewerSystem/Presentation/Components/FloorMapCanvas.swift (2)
  • realWorldToNormalized (339-355)
  • normalizedToImageCoordinate (323-328)
🔇 Additional comments (6)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (6)

7-23: floorMapId と SwiftData 初期化のデータフローは妥当です

floorMapId を init で受け取りつつ、@StateObject の初期化を init 内で行い、modelContextonAppearsetupSwiftDataRepository に渡してから loadFloorMapInfo(floorMapId:) を呼ぶ流れは、SwiftUI と Clean Architecture + MVVM の制約に沿っており特に問題は見当たりません。Based on learnings, ...

Also applies to: 80-85


207-251: センシング開始/終了の制御とバリデーションは一貫していて問題ありません

compactSensingControl のトグルボタンが viewModel.isSensingActive に応じて開始/停止を切り替え、startSensing() 側でファイル名未入力を guard とアラートで防いでいる構成は整合しており、安全側に倒れていると思います。特に修正が必要な箇所はありません。

Also applies to: 468-479, 86-90


255-305: コンパクトなリアルタイム表示の分岐ロジックは分かりやすいです

isRealtimeDataExpanded による展開/折りたたみと、activeAntennaIds の有無でアンテナ単位表示とデバイス単位のフォールバックを切り替える構成は直感的で、接続状況に応じた UI として自然です。特に懸念点はありません。


832-890: アンテナ単位のグルーピング UI はシンプルで扱いやすそうです

CompactAntennaGroupView でアンテナごとにデバイスをまとめ、isExpanded をローカル @State で開閉している構成は理解しやすく、CompactDeviceDataView の再利用もうまくできています。特に問題ありません。


894-951: コンパクトデバイスカードでも NLOS の強調表示が一貫していて良いです

CompactDeviceDataView は NLOS=1 の場合にラベル+背景・枠線を赤系に変え、距離と RSSI を最小限の情報で見せるコンパクトなカードとしてまとまっています。フルサイズカードとの表現もおおむね揃っており、UX 的に良い追加だと思います。


1009-1015: NLOS 状態を背景・枠線・バナーで強調する変更は分かりやすく有用です

RealtimeDeviceCardView の背景グラデーション/枠線色、および mainMeasurements 内の NLOS 警告バナーと距離ラベルの色切り替えにより、NLOS 状態が一目で判別できるようになっていて、接続回復や品質確認の観点でも有用な UI になっています。ロジック上の問題は特にありません。

Also applies to: 1024-1028, 1064-1078, 1091-1091

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (1)

113-221: GeometryReader のクロージャ引数 geometry が未使用なので _ に置き換えてください

fullScreenFloorMap 内の

GeometryReader { geometry in
    ...
}

では、geometry がどこからも参照されておらず、内側のクロージャでは canvasGeometry を使っているため、SwiftLint が指摘している未使用パラメータの警告が解消されていません。以下のようにプレースホルダを _ に変更するとよいです。

-    private var fullScreenFloorMap: some View {
-        GeometryReader { geometry in
+    private var fullScreenFloorMap: some View {
+        GeometryReader { _ in
             if let floorMapImage = self.viewModel.floorMapImage,
                let floorMapInfo = self.viewModel.currentFloorMapInfo {
                 FloorMapCanvas(
                     floorMapImage: floorMapImage,
                     floorMapInfo: floorMapInfo,
                     ...
🧹 Nitpick comments (5)
UWBViewerSystem/Presentation/Router/NavigationRouterModel.swift (1)

43-60: navigateTo(_:resetStack:)の挙動は妥当ですが、ログ出力量は検討の余地があります

resetStacktrue のときだけ reset() を呼んでから push(route) する構造になっており、ナビゲーションスタックのリセット挙動としては問題なさそうです。一方で、1回の呼び出しごとに複数の print を出しているので、リリースビルドでも実行されるのであれば #if DEBUG で囲むか、専用のロガーに切り替えることを検討してもよさそうです。

UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (3)

18-18: floorMapId の保持と Route 生成の流れは整合していますが、空文字デフォルトの扱いを確認してください

proceedToNextStep(floorMapId:) で渡された ID を currentFloorMapId に保持し、SensingFlowStep.route(floorMapId:) から各画面の Route に引き渡す構造は、フロア単位のフロー遷移として自然に見えます。

一方で、floorMapIdnil の場合に空文字 "" を渡す実装になっているため、下位の View / Usecase 側で「空文字の ID」をどう扱う想定か(エラー扱いか、デフォルト扱いか)を仕様として決めておくと安心です。必要であれば、currentFloorMapId が設定されていない状態ではそもそも次ステップに進めないようにするなど、早期バリデーションも検討ください。

Also applies to: 45-56, 101-105, 262-281


81-95: キャリブレーションスキップ処理は正しく終了しますが、skipCalibration フラグの責務を整理すると読みやすくなります

nextStep == .systemCalibration かつ skipCalibration 有効時に、キャリブレーションステップを完了扱いにしてから再帰的に proceedToNextStep を呼ぶ実装になっており、次のステップ(センシング実行)へ正しく進むため無限再帰の心配はなさそうです。

ただし同じ "skipCalibration" フラグを proceedToNextStepcheckSystemCalibrationCompletion の両方で参照しており、「ステップ自体をスキップする」と「systemCalibration ステップ到達時は常に完了扱いにする」の2つの意味を持っています。将来の仕様変更時に条件がずれるリスクがあるので、どちらか一方を主(例: 「ステップ丸ごとスキップ」)と決め、もう一方はそのポリシーに従う形で実装を揃えると保守性が上がると思います。

Also applies to: 429-445


109-122: 戻る/フロー再開始時の currentFloorMapId の扱いを UI 仕様と合わせて確認してください

goToPreviousStep()router.pop() を使うようになり、ナビゲーションスタックと currentStep の同期が取りやすくなっているのは良いと思います。

一方で、startNewFlow()resetFlow() ではステップ関連の状態はリセットされていますが、currentFloorMapId はそのまま保持されます。同じフロアでフローだけをやり直す想定であれば現状の挙動で問題ありませんが、「フローをリセットしたらフロア選択も最初からやり直したい」という UX を想定している場合は、これらのメソッド内で self.currentFloorMapId = nil も行う方が一貫します。

どちらの挙動を期待しているか、画面遷移設計と合わせて確認しておくことをおすすめします。

Also applies to: 133-139, 154-161

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (1)

328-483: 未使用の private ビュー(セクション)を整理すると可読性が向上します

現在の body では、fullScreenFloorMap とコンパクト系ビューのみが使われており、

  • headerSection
  • sensingControlCard
  • recentSessionsCard
  • realtimeDataDisplaySection
  • realtimeDataSection / realtimeDataHeader
  • floorMapDisplaySection
  • floorMapHeader / floorMapInfo / floorMapCanvas / floorMapLegend

といった private な View プロパティ/関数はどこからも参照されていない状態に見えます。

実行時には影響ありませんが、「どのレイアウトが現行仕様なのか」をコードから読み取るのが難しくなるので、今後再利用する予定がなければ削除し、再利用を見越して残すのであれば別ファイル(例: 開発用デバッグビュー)へ切り出す、あるいは // TODO: 将来の UI バリエーション用 など意図をコメントしておくと保守がしやすくなると思います。

Also applies to: 501-641, 643-678, 680-845

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c290c12 and 01b1c43.

📒 Files selected for processing (3)
  • UWBViewerSystem/Presentation/Router/NavigationRouterModel.swift (1 hunks)
  • UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (6 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Router/NavigationRouterModel.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
🧠 Learnings (3)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*ViewModel.swift : ViewModelのファイル名は「〜ViewModel.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
🧬 Code graph analysis (3)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (2)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)
  • proceedToNextStep (355-370)
UWBViewerSystem/Presentation/Router/NavigationRouterModel.swift (3)
  • push (25-31)
  • pop (34-36)
  • navigateTo (47-60)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (1)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (4)
  • setupSwiftDataRepository (144-172)
  • loadFloorMapInfo (183-188)
  • stopSensing (270-320)
  • startSensing (253-268)
UWBViewerSystem/Presentation/Router/NavigationRouterModel.swift (2)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (1)
  • route (263-281)
UWBViewerSystem/Presentation/Components/CalibrationResultVisualizationView.swift (1)
  • path (356-363)
🪛 SwiftLint (0.57.0)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift

[Warning] 114-114: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🔇 Additional comments (1)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (1)

7-23: floorMapId と ViewModel/SwiftData の注入、および NLOS 表示の構成は方針に沿っていて問題なさそうです

  • init(floorMapId:) でフロアマップ ID を受け取り、@StateObjectDataCollectionViewModel を生成し、onAppearmodelContext を渡して setupSwiftDataRepository および loadFloorMapInfo(floorMapId:) を呼ぶ流れは、View→ViewModel→Usecase→Repository のデータフローに沿っていて妥当だと思います。
  • コンパクトなセンシング制御・リアルタイム表示(compactSensingControl, compactRealtimeDataDisplay, CompactAntennaGroupView, CompactDeviceDataView)と、RealtimeDeviceCardView における NLOS=1 のときの色・枠線・警告表示も、フロアマップ上の NLOS 表示と一貫しており、ユーザー視点で状態が分かりやすい構成になっています。
  • プレビューでも floorMapIdNavigationRouterModelenvironmentObject を渡しており、利用側のインターフェースも明確です。

特に致命的な懸念はなく、この方向性で問題ないと感じました。Based on learnings, ...

Also applies to: 25-109, 225-324, 486-497, 848-969, 972-1187, 1581-1584

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift (1)

1-15: 未使用のmodelContextSwiftData importは削除を検討してください

MainTabView内でmodelContextが参照されておらず、import SwiftDataも本ファイルでは不要に見えます。ガイドラインの「不要なimport文」「デッドコードは削除」に合わせて削除すると警告も減らせそうです。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift (1)

141-145: デバッグ用ログの扱いを整理する余地があります

loadSessionData内のprint("📊 セッション詳細を読み込み: ...")はデバッグ色が強いので、将来的には#if DEBUGで囲むか、不要であれば削除してガイドライン(デバッグ用コードは削除)に合わせても良さそうです。

UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift (2)

12-18: 重いファイルI/Oを@MainActorで実行している点に注意が必要です

SessionDataExportUsecase全体が@MainActorで、FileManagerZipFileManagerによる同期I/OをそのままUIスレッドで実行する形になっています。セッション数やファイル数が増えるとUIフリーズの要因になり得るので、Usecase自体から@MainActorを外すか、内部でTask.detached等を使ってバックグラウンドに逃がす設計も検討してよさそうです。


53-97: 診断用ログが非常に多いため、ビルド設定やログレベルで制御することを検討してください

sensing配下の全ディレクトリ列挙や詳細情報をprintしており、実運用ではログがかなり多くなりそうです。デバッグ時には有用ですが、ガイドラインの「デバッグ用コードは削除」にも触れるため、#if DEBUGで囲む、簡略化する、もしくはロガー経由でレベルを落とす等を検討しても良いと思います。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift (1)

392-414: ShareSheetの詳細なprintはDEBUG限定にすることも検討できます

共有アイテムの型や中身をすべてprintしており、デバッグには有用ですが、リリースビルドではログが多くなる可能性があります。必要に応じて#if DEBUGで囲むか、最低限のログに絞るとログノイズを減らせそうです。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01b1c43 and 3deccdf.

📒 Files selected for processing (5)
  • UWBViewerSystem/Devices/File/ZipFileManager.swift (1 hunks)
  • UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift (5 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift
  • UWBViewerSystem/Devices/File/ZipFileManager.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
**/*Usecase.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Usecaseのファイル名は「〜Usecase.swift」とする

Files:

  • UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
🧠 Learnings (5)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*Usecase.swift : Usecaseのファイル名は「〜Usecase.swift」とする

Applied to files:

  • UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*ViewModel.swift : ViewModelのファイル名は「〜ViewModel.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*View.swift : Viewのファイル名は「〜View.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/MainTab/MainTabView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
🧬 Code graph analysis (3)
UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift (1)
UWBViewerSystem/Devices/File/ZipFileManager.swift (1)
  • zipDirectoryContents (106-173)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift (1)
UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift (1)
  • exportSessionToZip (23-140)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift (1)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift (1)
  • shareSessionData (149-152)
🔇 Additional comments (4)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift (1)

44-65: UsecaseのDI追加は妥当です

SessionDataExportUsecaseをコンストラクタDIに揃えたのはテスト容易性・責務分離の面で良いと思います。デフォルト引数で既存呼び出しも壊れていません。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift (3)

11-55: シェア用の状態管理と.sheetの構成は整合しています

shareURLshowShareSheetで共有状態を分離し、URL有無で分岐させる.sheet構成は分かりやすいです。onDismissshareURLnilに戻しているので、後続の共有でも前回状態を引きずらずに扱えそうです。


135-146: セッション共有フローはViewModelのUsecase委譲と整合しています

HistorySessionCardタップ時にTaskviewModel.shareSessionDataをawaitし、成功時のみMainActor.runshareURLshowShareSheetを更新する流れは、UIスレッド更新のルールにも沿っていて良いと思います。shareSessionData側の戻り値修正後は、そのまま期待通り動作しそうです。


281-283: 共有ボタンのアイコン変更は意図が明確で問題ありません

履歴カードのボタンをchevronから"square.and.arrow.up"に変更しており、ユーザーに「共有」アクションであることが直感的に伝わるようになっています。

Comment on lines +39 to +57
coordinator.coordinate(
readingItemAt: sourceURL,
options: .forUploading,
error: &coordinationError
) { zipFileURL in
do {
try FileManager.default.copyItem(at: zipFileURL, to: zipURL)
print("📦 ZIP圧縮成功: \(zipURL.path)")
} catch {
print("❌ ZIP作成エラー: \(error)")
}
}

if let error = coordinationError {
throw error
}

return zipURL
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

zipDirectoryでZIP作成に失敗してもURLを返してしまう可能性があります

NSFileCoordinator内のcopyItemでエラーが発生してもprintするだけで、zipDirectory自体は常にzipURLを返しています。その結果、実際には存在しない(または壊れた)ZIPファイルのURLを上位が扱うリスクがあります。

coordinationErrorとは別にcopyErrorを捕捉してthrowする、あるいはFileManager.default.fileExists(atPath: zipURL.path)で存在確認して、存在しない場合はエラーを投げるようにすると堅牢になります。

-        coordinator.coordinate(
+        var copyError: Error?
+        coordinator.coordinate(
             readingItemAt: sourceURL,
             options: .forUploading,
             error: &coordinationError
         ) { zipFileURL in
             do {
                 try FileManager.default.copyItem(at: zipFileURL, to: zipURL)
                 print("📦 ZIP圧縮成功: \(zipURL.path)")
             } catch {
-                print("❌ ZIP作成エラー: \(error)")
+                print("❌ ZIP作成エラー: \(error)")
+                copyError = error
             }
         }
 
         if let error = coordinationError {
             throw error
         }
+
+        if let error = copyError {
+            throw error
+        }
+
+        // 必要であれば存在確認も追加
+        // guard FileManager.default.fileExists(atPath: zipURL.path) else { throw SomeError() }


---

🏁 Script executed:

```shell
#!/bin/bash
# Examine ZipFileManager.swift to verify error handling claims
fd -e swift -p "ZipFileManager.swift" --exec cat {} \;

Length of output: 5446


🏁 Script executed:

#!/bin/bash
# Also search for how zipDirectory is called/used in codebase
rg -n "zipDirectory" --type swift -C 5

Length of output: 5143


🏁 Script executed:

#!/bin/bash
# Search for zipDirectoryContents to compare error handling approach
rg -n "zipDirectoryContents" --type swift -A 20

Length of output: 3707


zipDirectoryでZIP作成に失敗してもURLを返してしまう可能性があります

NSFileCoordinator内のcopyItemでエラーが発生しても、44~47行目のcatchブロックでprintするだけで、エラーが捕捉されず、zipDirectory自体は常にzipURLを返しています。その結果、実際には存在しない(または壊れた)ZIPファイルのURLを上位(zipFileszipDirectoryContentsなど)が受け取るリスクがあります。

coordinationErrorと同様にcopyErrorを捕捉し、ブロック後にthrowする必要があります。

         var coordinationError: NSError?
+        var copyError: Error?
 
         coordinator.coordinate(
             readingItemAt: sourceURL,
             options: .forUploading,
             error: &coordinationError
         ) { zipFileURL in
             do {
                 try FileManager.default.copyItem(at: zipFileURL, to: zipURL)
                 print("📦 ZIP圧縮成功: \(zipURL.path)")
             } catch {
                 print("❌ ZIP作成エラー: \(error)")
+                copyError = error
             }
         }
 
         if let error = coordinationError {
             throw error
         }
+
+        if let error = copyError {
+            throw error
+        }
 
         return zipURL
🤖 Prompt for AI Agents
In UWBViewerSystem/Devices/File/ZipFileManager.swift around lines 39 to 57, the
NSFileCoordinator closure currently catches copy errors only with a print, so
zipDirectory may still return the zipURL even if copy failed; declare a local
var copyError: Error? before calling coordinator, assign copyError = error in
the catch block inside the closure, and after the coordinator call check both
coordinationError and copyError and throw the first non-nil error instead of
returning zipURL on failure so the caller does not receive a non-existent or
corrupted URL.

Comment on lines 147 to 152
/// セッションデータをZIP圧縮してAirDropで共有
/// UseCaseに処理を委譲し、ViewModelはUI状態管理のみを担当
func shareSessionData(_ session: SensingSession) async -> URL? {
// UseCaseにエクスポート処理を委譲
await self.sessionDataExportUsecase.exportSessionToZip(session)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

shareSessionDataが値を返しておらずコンパイルエラーになります

async -> URL?の関数ですが、exportSessionToZipの結果を返しておらず、Swiftでは関数本体で暗黙のreturnは使えないためコンパイルに失敗します。DataDisplayView側でもif let url = await ...としているので、戻り値が必須です。

-    func shareSessionData(_ session: SensingSession) async -> URL? {
-        // UseCaseにエクスポート処理を委譲
-        await self.sessionDataExportUsecase.exportSessionToZip(session)
-    }
+    func shareSessionData(_ session: SensingSession) async -> URL? {
+        // UseCaseにエクスポート処理を委譲
+        return await self.sessionDataExportUsecase.exportSessionToZip(session)
+    }


---

🏁 Script executed:

```shell
#!/bin/bash
# Check the actual code in DataDisplayViewModel.swift at lines 147-152
find . -name "DataDisplayViewModel.swift" -type f | head -1 | xargs -I {} sed -n '145,155p' {}

Length of output: 321


🏁 Script executed:

#!/bin/bash
# Search for calls to shareSessionData in DataDisplayView to confirm usage pattern
rg -n "shareSessionData" --type swift -A 2 -B 2

Length of output: 1527


🏁 Script executed:

#!/bin/bash
# Check if exportSessionToZip return type is URL?
rg -n "exportSessionToZip" --type swift -B 2 -A 2

Length of output: 1204


returnステートメントの追加が必須 - コンパイルエラー確定

検証結果により、レビューコメントの指摘は正確です。

shareSessionData関数(行149-152)は戻り値の型としてasync -> URL?を宣言していますが、関数本体で明示的なreturnステートメントがありません。Swiftでは関数本体に暗黙のリターンは使用できないため、これはコンパイルエラーになります。

証拠:

  • 関数シグネチャ: func shareSessionData(_ session: SensingSession) async -> URL?
  • 実装: await self.sessionDataExportUsecase.exportSessionToZip(session) (戻り値なし)
  • 呼び出し元(DataDisplayView.swift:137): if let url = await self.viewModel.shareSessionData(session) - 戻り値を期待している

修正: 行151の前にreturnキーワードを追加してください:

    func shareSessionData(_ session: SensingSession) async -> URL? {
        // UseCaseにエクスポート処理を委譲
-       await self.sessionDataExportUsecase.exportSessionToZip(session)
+       return await self.sessionDataExportUsecase.exportSessionToZip(session)
    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// セッションデータをZIP圧縮してAirDropで共有
/// UseCaseに処理を委譲し、ViewModelはUI状態管理のみを担当
func shareSessionData(_ session: SensingSession) async -> URL? {
// UseCaseにエクスポート処理を委譲
await self.sessionDataExportUsecase.exportSessionToZip(session)
}
/// セッションデータをZIP圧縮してAirDropで共有
/// UseCaseに処理を委譲し、ViewModelはUI状態管理のみを担当
func shareSessionData(_ session: SensingSession) async -> URL? {
// UseCaseにエクスポート処理を委譲
return await self.sessionDataExportUsecase.exportSessionToZip(session)
}
🤖 Prompt for AI Agents
In
UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift
around lines 147–152, the async function shareSessionData(_: ) is declared to
return URL? but its body does not return a value; add a return statement so the
result of await self.sessionDataExportUsecase.exportSessionToZip(session) is
returned (i.e., use return await ...), ensuring the returned type matches the
usecase method's optional URL signature.

@harutiro
Copy link
Member Author

ペアリング解除のバグを修正しました

修正内容:
PairingSettingViewModel.swiftonConnectionDisconnected メソッドにて、antennaPairings.removeAll を実行する前に ConnectionManagementUsecase.unpairAntenna を呼び出すように順序を変更しました。

変更理由:

  • 元のコードでは removeAll の後に first(where:) を実行していたため、常に nil が返され、unpairAntenna が呼ばれませんでした
  • これにより ConnectionManagementUsecase 側にペアリング情報が残り続け、接続復旧フローで不整合が発生していました

修正後の動作:

  1. まず antennaPairings から該当するペアリングを検索
  2. 見つかった場合は ConnectionManagementUsecase.unpairAntenna を呼び出し
  3. その後 antennaPairings から削除

これにより、両方の状態が正しく同期されます。

Commit: 845bbef

@harutiro
Copy link
Member Author

RealtimeDataUsecaseのクリアメソッドを修正しました

修正内容:
RealtimeDataUsecase.swiftclearAllRealtimeData() および clearRealtimeDataForSensing() メソッドに、新しく追加された Published プロパティのクリア処理を追加しました。

追加したクリア処理:

  • globalCoordinates.removeAll()
  • antennaDataMap.removeAll()
  • activeAntennaIds.removeAll()
  • totalDataPointCount = 0

修正理由:
これらの新しいプロパティがクリアされないと、セッションの切り替えやリセット時に以下の問題が発生します:

  • UI ではデータリストが空でも、アンテナ数や総データポイント数が古い値のまま表示される
  • フロアマップ上のタグ位置 (globalCoordinates) やアンテナ別マップも前回セッションの情報を引きずる

修正後の動作:
セッションのクリア時に UI 状態が一貫して正しくリセットされます。

Commit: 845bbef

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift (1)

185-201: saveCurrentFloorMapInfo のエラーが伝播されず、サイレント失敗の可能性があります

saveFloorMapInfo メソッド内で preferenceRepository.saveCurrentFloorMapInfo(info)(187行目)が呼び出されていますが、PreferenceRepository の実装(関連コードスニペット参照)ではエラーを内部でキャッチして print するのみで、呼び出し元に伝播しません。

これにより以下の問題が発生する可能性があります:

  • SwiftData への保存は成功したが PreferenceRepository への保存が失敗した場合、メソッドは成功を返す
  • コメント(186行目)によると、この保存は「SensingFlowNavigatorの検証に必要」とあり、失敗すると後続のナビゲーションが破綻する可能性がある
  • データの不整合状態が発生する

以下の修正を検討してください:

オプション1:エラーを伝播させる

-    private func saveFloorMapInfo(_ info: FloorMapInfo) throws {
+    private func saveFloorMapInfo(_ info: FloorMapInfo) throws {
-        // PreferenceRepositoryにフロアマップ情報を保存(SensingFlowNavigatorの検証に必要)
-        self.preferenceRepository.saveCurrentFloorMapInfo(info)
+        // PreferenceRepositoryにフロアマップ情報を保存(SensingFlowNavigatorの検証に必要)
+        do {
+            try self.preferenceRepository.saveCurrentFloorMapInfo(info)
+        } catch {
+            throw FloorMapSettingError.savingFailed
+        }

※ この場合、PreferenceRepository.saveCurrentFloorMapInfo の実装も throws に変更する必要があります

オプション2:失敗を許容する場合は明示的にログ出力
現在の実装を維持する場合でも、失敗の可能性をログで明示してください。

-        self.preferenceRepository.saveCurrentFloorMapInfo(info)
+        // Note: この保存が失敗してもエラーは伝播されない(内部でログ出力される)
+        self.preferenceRepository.saveCurrentFloorMapInfo(info)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift (1)

221-264: FloorMapDetailView は未使用コード。削除が必須です

リポジトリ全体の検索結果から、FloorMapDetailView は定義されている 177 行目以外に参照がないことが確認されました。コーディングガイドラインに従い、デッドコードは削除する必要があります。

3 つのアクションボタンで self.floorMap.id を正しく受け渡している部分の実装は良好ですが、その前に定義されている未使用の FloorMapDetailView 構造体(UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift の 177 行目付近)を削除してください。

♻️ Duplicate comments (1)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift (1)

127-129: コンパイルエラー:returnステートメントが欠落しています

過去のレビューコメントで指摘された問題が未修正です。shareSessionData関数はasync -> URL?を返すと宣言していますが、関数本体でreturnキーワードが欠落しているため、コンパイルエラーになります。

Swiftでは単一式の関数でない限り、明示的なreturnが必要です。DataDisplayView.swift(line 127)ではif let url = await self.viewModel.shareSessionData(session)として戻り値を期待しているため、この修正は必須です。

以下のように修正してください:

 func shareSessionData(_ session: SensingSession) async -> URL? {
     // UseCaseにエクスポート処理を委譲
-    await self.sessionDataExportUsecase.exportSessionToZip(session)
+    return await self.sessionDataExportUsecase.exportSessionToZip(session)
 }
🧹 Nitpick comments (5)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift (1)

118-124: 構造化ログフレームワークの使用を検討してください(任意)

#if DEBUG ブロック内の print 文は現在機能的には問題ありませんが、コーディングガイドラインでは不要なデバッグコードの削除が推奨されています。より良いアプローチとして、OSLog や SwiftLog などの構造化ログフレームワークの使用を検討してください。これにより、ログレベルの管理やフィルタリングが容易になります。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift (1)

9-41: VStack 構成と onAppear ログまわりの軽い整理提案

動作上は問題ありませんが、外側の VStack(Line 10, 40)は内側の VStack を素通しでラップしているだけなので、削ってしまうとレイアウトが少しシンプルになります。
また print("📱 FloorMapView (iOS): onAppear called") は macOS でも実行されるため、OS 非依存の文言にするか、#if os(iOS) で囲むとログが読みやすくなりそうです。デバッグ用途の print であれば、最終的に Logger#if DEBUG でガードする運用も検討してください。As per coding guidelines, ...

UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift (1)

25-97: 本番コードでのデバッグログが過剰です

行25-97に広範囲なデバッグ出力(print文)が含まれています。コーディングガイドラインに従い、「デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する」必要があります。開発中のトラブルシューティングには有用ですが、本番環境では以下の対応を推奨します:

  • エラー条件のログのみ残す(lines 32-33, 101-102, 128-129など)
  • 詳細なデバッグ出力(lines 54-97)は削除
  • または、条件付きコンパイル(#if DEBUG)で囲む

コーディングガイドラインに基づく指摘です。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift (1)

122-122: デバッグ用のprint文が残っています

コーディングガイドラインに従い、「デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する」必要があります。本番環境に不要なデバッグログは削除してください。

コーディングガイドラインに基づく指摘です。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift (1)

270-274: 本番コードでのデバッグログ出力

ShareSheetの初期化時にデバッグ用のprint文(lines 270-274)が含まれています。コーディングガイドラインに従い、不要なデバッグコードは削除するか、条件付きコンパイル(#if DEBUG)で囲むことを推奨します。

コーディングガイドラインに基づく指摘です。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3deccdf and 845bbef.

📒 Files selected for processing (6)
  • UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift (3 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift (6 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift (5 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift (2 hunks)
  • UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsView.swift (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • UWBViewerSystem/Presentation/Scenes/SettingsTab/SettingsView.swift
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift
  • UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift
**/*Usecase.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Usecaseのファイル名は「〜Usecase.swift」とする

Files:

  • UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift
🧠 Learnings (6)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*ViewModel.swift : ViewModelのファイル名は「〜ViewModel.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*View.swift : Viewのファイル名は「〜View.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to Devices/**/*.swift : UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*Usecase.swift : Usecaseのファイル名は「〜Usecase.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift
  • UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift
🧬 Code graph analysis (4)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift (3)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (1)
  • showError (836-840)
UWBViewerSystemTests/TestHelpers/MockDataRepository.swift (1)
  • saveCurrentFloorMapInfo (488-496)
UWBViewerSystem/Domain/Repository/PreferenceRepository.swift (1)
  • saveCurrentFloorMapInfo (245-251)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift (1)
UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift (1)
  • deleteSessionData (147-211)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift (1)
UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift (2)
  • exportSessionToZip (23-140)
  • deleteSessionData (147-211)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift (1)
UWBViewerSystem/Presentation/Router/NavigationRouterModel.swift (1)
  • push (25-31)
🔇 Additional comments (8)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift (1)

93-138: メソッドシグネチャの変更が適切に実装されています

saveFloorMapSettings() の戻り値が Bool から String?(FloorMapID)に変更され、すべての return 文が正しく更新されています。エラーハンドリングも適切で、失敗時には nil を返し、成功時には floorMapInfo.id を返すことで、後続のナビゲーションフローで使用できるようになっています。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapView.swift (1)

83-100: floorMapId を渡すルーティング更新は妥当そうです

FloorMapRow タップ時に viewModel.selectFloorMap(map) のあとで self.router.push(.antennaConfiguration(floorMapId: map.id)) を呼ぶ形は、新しい Route 仕様(floorMapId 付き)と整合しており、iOS での遷移として問題なさそうです。

UWBViewerSystem/Domain/Usecase/SessionDataExportUsecase.swift (1)

10-18: 依存性注入パターンが適切に実装されています

ZipFileManagerの依存性注入がデフォルト値付きで実装されており、テスタビリティとプロダクションコードの両立ができています。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift (2)

45-45: SessionDataExportUsecaseの依存性注入が適切に実装されています

UseCaseの依存性注入がデフォルト値付きで実装されており、Clean Architecture + MVVMのアーキテクチャパターンに準拠しています。テスト時にモックを注入可能で、本番ではデフォルトインスタンスを使用できる良い設計です。

Based on learnings, Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)に準拠しています。

Also applies to: 52-53, 59-59


135-147: 削除処理の実装が適切です

deleteSessionDataメソッドは、UseCaseへの委譲、成功時の履歴データ再読み込み、戻り値の適切な伝播が正しく実装されています。エラーハンドリングもUseCaseに委譲されており、責務分離ができています。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift (3)

265-285: iOS向けShareSheetの実装が適切です

UIViewControllerRepresentableを使用したShareSheetの実装は、SwiftUIとUIKitの相互運用パターンとして適切です。条件付きコンパイルでiOSのみに限定し、macOSでは代替メッセージを表示する設計も良好です。


50-67: 削除アラートの実装が適切です

alert(isPresented:presenting:)モディファイアを使用した削除確認フローは、iOS標準のパターンに従っており、ユーザー体験を損なわない実装です。非同期削除処理もTask内で適切にハンドリングされています。


10-13: 状態管理とSheet表示の実装が適切です

@State変数を使用した共有フローの状態管理と、.sheetモディファイアの実装は、SwiftUIの標準パターンに従っています。onDismissでのクリーンアップも適切に実装されています。

Also applies to: 29-49

Comment on lines +36 to +51
// 日付フォーマッターの設定
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone.current
dateFormatter.dateFormat = "yyyyMMdd"
let dateString = dateFormatter.string(from: session.startTime)

dateFormatter.dateFormat = "HHmmss"
let timeString = dateFormatter.string(from: session.startTime)

// セッションディレクトリ名を構築(customNameがある場合は「HHmmss-customName」形式、空の場合は「HHmmss」のみ)
let directoryName = if !session.name.isEmpty {
"\(timeString)-\(session.name)"
} else {
timeString
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

ディレクトリパス構築ロジックの重複

exportSessionToZip(lines 36-51)とdeleteSessionData(lines 163-178)で、日付フォーマッターの設定とディレクトリ名の構築ロジックが完全に重複しています。DRY原則に違反しており、将来的なメンテナンス負荷が増加します。

以下のようなヘルパーメソッドへの抽出を推奨します:

private func buildSessionDirectoryPath(for session: SensingSession) -> URL? {
    guard let documentsDirectory = FileManager.default.urls(
        for: .documentDirectory,
        in: .userDomainMask
    ).first else {
        return nil
    }
    
    let dateFormatter = DateFormatter()
    dateFormatter.locale = Locale(identifier: "en_US_POSIX")
    dateFormatter.timeZone = TimeZone.current
    dateFormatter.dateFormat = "yyyyMMdd"
    let dateString = dateFormatter.string(from: session.startTime)
    
    dateFormatter.dateFormat = "HHmmss"
    let timeString = dateFormatter.string(from: session.startTime)
    
    let directoryName = if !session.name.isEmpty {
        "\(timeString)-\(session.name)"
    } else {
        timeString
    }
    
    return documentsDirectory
        .appendingPathComponent("sensing")
        .appendingPathComponent(dateString)
        .appendingPathComponent(directoryName)
}

Also applies to: 163-178

Comment on lines +125 to +136
Task {
print("🔄 共有ボタンがタップされました: \(session.name)")
if let url = await self.viewModel.shareSessionData(session) {
print("✅ ZIPファイルURL取得成功: \(url.path)")
await MainActor.run {
self.shareURL = url
self.showShareSheet = true
}
} else {
print("❌ ZIPファイルの生成に失敗しました")
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

非構造化Taskの使用を検証してください

onShareコールバック内でTaskを作成していますが、このTaskはキャンセル不可能で、ビューが破棄された後も実行を続ける可能性があります。セッションの共有処理が完了する前にユーザーが画面を離れた場合、メモリリークやクラッシュのリスクがあります。

以下のスクリプトで、他の非構造化Task使用箇所を確認し、一貫性を確認してください:

もし問題がある場合は、@StateObjectで管理されるタスクや、.taskモディファイアの使用を検討してください。


🏁 Script executed:

#!/bin/bash
# Description: Check for unstructured Task usage patterns in View files

rg -n "Task\s*\{" --type swift -g "**/*View.swift" -C 3

Length of output: 3732


検証を進めるため、ViewModelの実装と、追加の情報を確認します。

これでViewModelの実装を確認します。


---

🏁 Script executed:

```shell
#!/bin/bash
# Check if DataDisplayView tracks tasks and has cleanup
rg -n "@State.*[Tt]ask|@State.*isLoading|@State.*isDeleting" UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift

# Check for deinit or cleanup
rg -n "deinit|onDisappear|\.onReceive" UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayView.swift

# Search for ViewModel method implementations
fd -t f "ViewModel.swift" --search-path UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/

Length of output: 89


ViewModelの実装を確認します。


---

🏁 Script executed:

```shell
#!/bin/bash
# Check ViewModel implementations for shareSessionData and deleteSessionData
rg -n "func shareSessionData|func deleteSessionData" UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift -A 15

# Check for any cancellation support in ViewModel
rg -n "Task|@State|cancellationToken|AnyCancellable" UWBViewerSystem/Presentation/Scenes/SensingTab/DataDisplayPage/DataDisplayViewModel.swift -C 2

Length of output: 1473


非構造化Taskの使用は、確認された問題として検証されました

ViewModel実装調査により、以下の問題が確認されました:

  • shareSessionData(127-130行目):ZIPエクスポート処理で、ファイルI/Oを含む潜在的に長時間実行される操作
  • deleteSessionData(135-147行目):ファイル削除とloadHistoryData()呼び出しにより複数秒かかる可能性がある操作
  • DataDisplayViewには@Stateのタスク追跡がなく、onDisappearやdeinitのクリーンアップハンドラもない

このため、以下のシナリオでメモリリークやクラッシュが発生します:

  1. ユーザーが共有ボタンをタップ(Task開始)
  2. ZIPファイル生成中にビューから離脱
  3. Taskはビュー破棄後も実行継続 → メモリリーク・状態不整合

修正が必要です。以下のいずれかを実装してください:

  • @Stateでタスク参照を追跡し、onDisappear.cancel()呼び出し
  • .taskモディファイアの使用(ビューライフサイクルに自動追従)
  • TaskGroupでの管理とビュー破棄時キャンセル

Copy link
Member Author

@harutiro harutiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ プラットフォーム固有コードの重複を解消しました

DataCollectionViewModel.swift の205-223行目の重複を修正しました。

変更内容:

  • iOSとmacOSで重複していたフロアマップ画像読み込みロジックを統合
  • floorMapImage プロパティは既にプラットフォーム対応型のため、分岐は不要
  • 画像読み込み成功/失敗のログ出力も共通化

修正後のコード:

// フロアマップ画像を読み込み
self.floorMapImage = floorMap.image
if self.floorMapImage != nil {
    print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
} else {
    print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
}

DRY原則に従い、保守性が向上しました。

Commit: 845bbef

Copy link
Member Author

@harutiro harutiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ デバッグ用print文を削除しました

コーディングガイドラインに従い、#if DEBUGで囲まれていないデバッグ用print文を削除しました。

対象ファイルと削除数:

  • ConnectionManagementUsecase.swift: 27箇所
  • AutoAntennaCalibrationViewModel.swift: 19箇所
  • SettingsView.swift: 1箇所
  • SensingDataCSVExporter.swift: 4箇所

合計: 51箇所のデバッグprint文を削除

削除対象:

  • #if DEBUGで囲まれていないprint文
  • 絵文字付き、[DEBUG]マーカー付きなど開発者向けログ
  • UI/状態更新で十分伝わる冗長なログ

ビルド結果:

  • ✅ ビルド成功(BUILD SUCCEEDED)
  • エラー: 0件
  • 警告: 1件(既存の軽微な警告のみ)

Commit: 845bbef

Copy link
Member Author

@harutiro harutiro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 残りのレビューコメントについて

以下のMajor/Critical問題については、アーキテクチャの大きな変更が必要なため、別issueでの対応を推奨します:

1. 🔴 Critical: 非同期処理待機問題(Line 277)

現状: 該当コード(0.5秒スリープ)は既に削除済みです ✅

2. 🟠 Major: 未実装コメントブロック(Lines 272-276)

現状: 空のMainActor.runブロックが残っています
対応: SensingControlUsecaseclearSessionId()メソッドがあれば実装、なければコメントごと削除が必要です

3. 🟠 Major: globalCoordinatesのデータ構造問題

問題: globalCoordinates: [String: Point3D]が各デバイスの最新位置のみを保持しているため、時系列データとして不十分
影響: CSVエクスポート時に全サンプルが同じ座標になる
推奨: 以下のいずれかの設計変更が必要

  • [RealtimeData.ID: Point3D]に変更
  • [String: [Point3D]](デバイス別時系列配列)に変更
  • CSV出力時に座標変換を再実行

4. 🟠 Major: exportSensingDataToCSVメソッドが長すぎる(176行)

問題: 単一責任原則違反、テスト困難
推奨: 以下のヘルパーメソッドに分割

  • loadRealtimeDataForExport(sessionId:)
  • createExportDirectory(startTime:)
  • buildAntennaIdToNameMap()
  • exportGroupedData(...)
  • exportDataForAntenna(...)

これらの変更は既存の設計に大きな影響を与えるため、別PRでの対応が適切と考えます。

Commit: 845bbef

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (12)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (3)

239-262: 接続監視の実装は適切ですが、デバッグprintを削除してください

Combineを使った接続エラーと接続デバイス変更の監視が適切に実装されています。weak self の使用によりメモリリークも防止されています。

ただし、Line 246とLine 257のデバッグ用print文はコーディングガイドラインに従い削除してください。

As per coding guidelines


264-282: エラーハンドリングは適切ですが、デバッグprintを削除してください

接続エラー発生時に実行中の処理を適切に停止し、デバイス名を含むわかりやすいエラーメッセージを提供しています。

ただし、Line 268のデバッグ用print文はコーディングガイドラインに従い削除してください。

As per coding guidelines


379-416: 前のタグ位置に戻る機能は適切に実装されていますが、デバッグprintを削除してください

最後に完了したタグを見つけ、そのタグのデータをクリアし、収集状態をリセットして進行状況を更新するロジックが正しく実装されています。

ただし、Line 411のデバッグ用print文はコーディングガイドラインに従い削除してください。

As per coding guidelines

UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (1)

143-157: クリアメソッドで新しい状態がリセットされていません。

以前のレビューで指摘された通り、clearAllRealtimeData()clearRealtimeDataForSensing() は新しく追加された globalCoordinatesantennaDataMapactiveAntennaIdstotalDataPointCountcentroidCoordinateintegratedTagCoordinates をクリアしていません。

PR目的のコメントサマリーにはこの修正が含まれていると記載されていますが、コードには反映されていないようです。

 public func clearAllRealtimeData() {
     #if DEBUG
         print("🗑️ リアルタイムデータクリア")
     #endif
     self.deviceRealtimeDataList.removeAll()
     self.isReceivingRealtimeData = false
+    self.globalCoordinates.removeAll()
+    self.antennaDataMap.removeAll()
+    self.activeAntennaIds.removeAll()
+    self.totalDataPointCount = 0
+    self.centroidCoordinate = nil
+    self.integratedTagCoordinates.removeAll()
     objectWillChange.send()
 }

 public func clearRealtimeDataForSensing() {
     for deviceData in self.deviceRealtimeDataList {
         deviceData.clearData()
     }
+    self.globalCoordinates.removeAll()
+    self.antennaDataMap.removeAll()
+    self.activeAntennaIds.removeAll()
+    self.totalDataPointCount = 0
+    self.centroidCoordinate = nil
+    self.integratedTagCoordinates.removeAll()
     objectWillChange.send()
 }
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (2)

113-116: 未使用パラメータを_に置き換えてください。

静的解析ツールが指摘している通り、geometryパラメータは使用されていません。クロージャ内ではFloorMapCanvasから提供されるcanvasGeometryを使用しています。

 private var fullScreenFloorMap: some View {
-    GeometryReader { geometry in
+    GeometryReader { _ in
         if let floorMapImage = self.viewModel.floorMapImage,

810-832: 凡例の色と実際の描画仕様に不整合があります。

凡例では「タグ=青」と固定表示されていますが、fullScreenFloorMap および individualTagPositions ではNLOSの場合は赤、LOSの場合は青で表示されます。凡例にNLOS/LOSの説明を追加することを検討してください。

 private var floorMapLegend: some View {
     HStack(spacing: 20) {
         HStack(spacing: 4) {
             Circle()
                 .fill(Color.red)
                 .frame(width: 12, height: 12)
             Text("アンテナ")
                 .font(.caption)
                 .foregroundColor(.secondary)
         }

         HStack(spacing: 4) {
             Circle()
                 .fill(Color.blue)
                 .frame(width: 12, height: 12)
-            Text("タグ")
+            Text("タグ(LOS)")
+                .font(.caption)
+                .foregroundColor(.secondary)
+        }
+
+        HStack(spacing: 4) {
+            Circle()
+                .fill(Color.red)
+                .frame(width: 12, height: 12)
+            Text("タグ(NLOS)")
                 .font(.caption)
                 .foregroundColor(.secondary)
         }

         Spacer()
     }
 }
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (6)

387-570: メソッドが長すぎて複雑度が高いです。

このメソッドは184行あり、複数の責務を持っています。単一責任の原則(SRP)に違反しており、テストと保守が困難です。

以下のように小さなメソッドに分割することを推奨します:

private func exportSensingDataToCSV(fileName: String = "") async {
    guard let sessionId = sensingControlUsecase.activeSessionId,
          let sessionStartTime = self.startTime else {
        return
    }
    
    do {
        let allRealtimeData = try await loadRealtimeDataForExport(sessionId: sessionId)
        guard !allRealtimeData.isEmpty else { return }
        
        let sessionDirectory = try createExportDirectory(
            startTime: sessionStartTime,
            fileName: fileName
        )
        let antennaIdToNameMap = buildAntennaIdToNameMap()
        let groupedByAntenna = Dictionary(grouping: allRealtimeData) { $0.antennaId }
        
        try await exportGroupedData(
            groupedByAntenna: groupedByAntenna,
            antennaIdToNameMap: antennaIdToNameMap,
            sessionDirectory: sessionDirectory,
            fileName: fileName
        )
    } catch {
        // エラーハンドリング
    }
}

private func loadRealtimeDataForExport(sessionId: String) async throws -> [RealtimeData] {
    // データ読み込みロジック
}

private func createExportDirectory(startTime: Date, fileName: String) throws -> URL {
    // ディレクトリ作成ロジック
}

private func buildAntennaIdToNameMap() -> [String: String] {
    // アンテナIDとアンテナ名のマッピング作成
}

private func exportGroupedData(
    groupedByAntenna: [String: [RealtimeData]],
    antennaIdToNameMap: [String: String],
    sessionDirectory: URL,
    fileName: String
) async throws {
    // グループ化されたデータのエクスポート
}

この分割により、各メソッドの責務が明確になり、テスト可能性と保守性が向上します。


193-199: 非推奨メソッドの扱いを明確化してください。

コメントで非推奨と記載されていますが、@available属性が付いていません。また、Line 189のsetupSwiftDataRepositoryから実際に呼び出されているため、非推奨の意図と実装が矛盾しています。

以下のいずれかの対応を推奨します:

オプション1: 非推奨属性を追加

@available(*, deprecated, message: "loadFloorMapInfo(floorMapId:)を使用してください")
private func loadInitialData() {

オプション2: コメントを削除(実際に使用されている場合)

/// 初期データの読み込み
private func loadInitialData() {

224-242: プラットフォーム固有コードの重複を解消してください。

iOSとmacOSで同一のロジックが重複しています。DRY原則に違反しており、保守性を低下させます。

以下のように共通化を推奨します:

-                // フロアマップ画像を読み込み
-                #if canImport(UIKit)
-                    #if os(iOS)
-                        self.floorMapImage = floorMap.image
-                        if self.floorMapImage != nil {
-                            print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
-                        } else {
-                            print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
-                        }
-                    #endif
-                #endif
-
-                #if os(macOS)
-                    self.floorMapImage = floorMap.image
-                    if self.floorMapImage != nil {
-                        print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
-                    } else {
-                        print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
-                    }
-                #endif
+                // フロアマップ画像を読み込み
+                self.floorMapImage = floorMap.image

floorMapImageプロパティが既にプラットフォーム別に定義されているため、代入処理は共通化できます。


296-309: 非同期処理の待機方法に問題があります。

Line 299の0.5秒固定スリープは、SwiftDataの永続化完了を保証しない不確実な実装です。永続化に0.5秒以上かかる場合、データ損失のリスクがあります。

以下のいずれかの方法で適切な非同期制御を実装してください:

オプション1: SwiftDataの永続化完了を明示的に待機

// センシングデータをCSVとしてエクスポート
Task {
    // SwiftDataRepositoryに永続化完了を確認するメソッドを追加
    await swiftDataRepository?.waitForPersistence()
    await self.exportSensingDataToCSV(fileName: savedFileName)
}

オプション2: Notification/Combineで永続化完了を通知
SwiftDataRepositoryから永続化完了のイベントを発行し、それを待機する実装に変更してください。


305-308: 未実装のコメントブロックを削除または実装してください。

Lines 305-308のコメントは実装が必要な処理を示唆していますが、実際のコードがありません。意図が不明確です。

以下のいずれかの対応が必要です:

オプション1: 必要な実装を追加

await MainActor.run {
    self.sensingControlUsecase.clearSessionId()
}

オプション2: 不要な場合はコメントごと削除

// 空のMainActor.runブロックとコメントを削除

391-568: デバッグprint文を削除してください。

Lines 391-395, 402, 413, 416, 429-432, 436, 441, 447, 453, 493, 501, 560-562, 564-566, 568に多数のデバッグ用print文が含まれています。コーディングガイドラインに従って、これらを削除する必要があります。

本番環境では適切なロギングフレームワーク(OSLogなど)の使用を検討してください。

Based on learnings, デバッグ用コードは削除し、必要に応じて適切なロギング機構を使用してください。

🧹 Nitpick comments (3)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (2)

451-470: 全アンテナ完了時の処理は適切ですが、改善点があります

キャリブレーション結果の保存、成功アラート表示、次ステップへの自動遷移が適切に実装されています。

ただし、以下の改善をお願いします:

  1. Line 460とLine 467のデバッグ用print文はコーディングガイドラインに従い削除してください
  2. Line 463のハードコードされた遅延時間(2.0秒)は、クラスレベルの定数として定義することを推奨します(例:private let successAlertDelay: TimeInterval = 2.0

As per coding guidelines


472-491: UserDefaultsへの保存は機能していますが、改善を推奨します

キャリブレーション結果の軽量な要約をUserDefaultsに保存する実装は適切です。

以下の改善を推奨します:

  1. Line 489のデバッグ用print文はコーディングガイドラインに従い削除してください
  2. Line 487のtry?による無言のエラー無視ではなく、エラーハンドリング(do-catchまたはログ出力)を追加することを推奨します
  3. Line 488のUserDefaultsキー"lastCalibrationResult"は、クラスまたはアプリ全体の定数として定義することを推奨します

As per coding guidelines

UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (1)

355-395: 重心座標計算のNLOS重み付けについて。

NLOS観測の重みを0.3に設定していますが、この値の根拠をコメントで説明するとコードの意図がより明確になります。また、将来的に設定可能にすることも検討できます。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 845bbef and d4da821.

📒 Files selected for processing (7)
  • UWBViewerSystem/Domain/Entity/RealtimeData.swift (4 hunks)
  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (6 hunks)
  • UWBViewerSystem/Presentation/Components/AntennaMarker.swift (5 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift (7 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (9 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (7 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (6 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Presentation/Components/AntennaMarker.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Domain/Entity/RealtimeData.swift
  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
**/*Usecase.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Usecaseのファイル名は「〜Usecase.swift」とする

Files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
🧠 Learnings (8)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*ViewModel.swift : ViewModelのファイル名は「〜ViewModel.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : 不要なimport文は削除する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to Devices/**/*.swift : UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T14:37:07.451Z
Learnt from: harutiro
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: :0-0
Timestamp: 2025-09-23T14:37:07.451Z
Learning: UWBViewerSystemでUserDefaults変更通知のテストを行う際は、NotificationCenterの非同期処理完了を確実に待機するため、1.5秒程度の待機時間とポーリング方式の検証を使用する。これにより競合状態やタイミング問題を回避できる。

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧬 Code graph analysis (4)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (5)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (3)
  • updateSwiftDataRepository (41-47)
  • setSensingControlUsecase (170-172)
  • setFloorMapId (50-53)
UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (4)
  • loadAntennaPositions (309-316)
  • loadAntennaPositions (318-353)
  • loadAntennaPositions (964-964)
  • loadAntennaPositions (965-965)
UWBViewerSystem/Domain/Repository/DataRepository.swift (1)
  • loadAntennaPositions (76-79)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift (1)
  • setFloorMapId (111-113)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
UWBViewerSystem/Domain/Entity/RealtimeData.swift (1)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift (5)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (1)
  • setFloorMapId (50-53)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift (4)
  • setFloorMapId (111-113)
  • loadMapAndDevices (158-166)
  • updateAntennaPosition (414-425)
  • updateAntennaPosition (428-444)
UWBViewerSystem/Presentation/Components/FloorMapCanvas.swift (4)
  • normalizedToImageCoordinate (323-328)
  • antennaSizeInPixels (373-376)
  • sensorRangeInPixels (379-388)
  • imageCoordinateToNormalized (331-336)
UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (2)
  • updateAntennaPosition (421-435)
  • updateAntennaPosition (967-967)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (1)
  • proceedToNextStep (45-106)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (6)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (3)
  • loadInitialData (194-199)
  • loadFloorMapInfo (202-207)
  • loadFloorMapInfoById (210-251)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (3)
  • loadFloorMapInfo (80-87)
  • loadFloorMapInfoById (90-100)
  • proceedToNextStep (355-370)
UWBViewerSystem/Domain/Entity/RealtimeData.swift (1)
  • clearData (118-122)
UWBViewerSystem/Domain/Usecase/AutoAntennaCalibrationUsecase.swift (3)
  • clearData (311-315)
  • clearData (318-324)
  • clearData (331-336)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (1)
  • proceedToNextStep (45-106)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/FloorMapSettingPage/FloorMapSettingViewModel.swift (1)
  • showError (234-237)
🪛 SwiftLint (0.57.0)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift

[Warning] 114-114: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🔇 Additional comments (25)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (8)

50-51: 接続復旧UI フラグの追加は適切です

接続エラー発生時にユーザーに復旧画面を表示するためのフラグが適切に追加されています。


96-96: フローナビゲーター参照は適切です

weak 参照により循環参照を防止し、適切な設計になっています。


136-142: 前のタグ位置に戻る条件チェックが適切です

データ収集ステップでのみ有効化し、収集中・キャリブレーション中は無効化する安全な実装になっています。


228-229: 接続監視の初期化が適切に配置されています

依存関係の初期化後に接続監視を設定する配置は適切です。


234-237: フローナビゲーター設定メソッドは適切です

依存性注入パターンに従った明確な実装です。


293-299: フロアマップIDによる読み込みメソッドは適切です

指定されたフロアマップIDでデータを読み込む明確なインターフェースが提供されています。


512-541: フロアマップ読み込みは適切に実装されていますが、デバッグprintを削除してください

指定されたIDでフロアマップを読み込み、プラットフォーム固有の画像処理を行う実装は適切です。エラーハンドリングも適切に実装されています。

ただし、Line 534とLine 536のデバッグ用print文はコーディングガイドラインに従い削除してください。

As per coding guidelines


588-624: アンテナフィルタリングロジックは適切ですが、デバッグprintを削除してください

ペアリングされており、かつ接続中のアンテナのみをキャリブレーション対象とするフィルタリングロジックは、接続復旧機能の実装として適切です。

ただし、Lines 590, 593, 598, 605, 608, 613, 624の多数のデバッグ用print文はコーディングガイドラインに従い削除してください。プロダクションコードでは、必要に応じて正式なロギング機構(OSLogなど)を使用してください。

As per coding guidelines

UWBViewerSystem/Domain/Entity/RealtimeData.swift (3)

3-27: LGTM! 表示モードの設計が明確です。

TagDisplayModedisplayNamedescription の計算プロパティを持ち、UI層での使用が容易になっています。CaseIterable への準拠も適切です。


125-149: LGTM! 観測データモデルが適切に設計されています。

TagObservation はアンテナごとの観測データを表現するのに必要なフィールドを持っています。


151-185: LGTM! 統合位置モデルが適切に実装されています。

IntegratedTagPositionlosCountnlosCount 計算プロパティは、観測データのフィルタリングを簡潔に表現しています。hasNLOSOnly フラグも信頼度の判断に有用です。

UWBViewerSystem/Presentation/Components/AntennaMarker.swift (3)

159-165: LGTM! 座標系変換が適切に実装されています。

内部座標系(反時計回り、北=0°)からSwiftUIの回転座標系(時計回り、東=0°)への変換が -rotation + 90.0 で正しく実装されています。コメントも変換理由を明確に説明しています。


248-256: LGTM! 逆変換ヘルパーが正しく実装されています。

displayAngledisplayToInternaldisplayRotation の逆変換として一貫性があります。


265-278: 回転ボタンの動作方向について確認してください。

反時計回りアイコン(arrow.counterclockwise)が rotation + 15(表示座標系では時計回り)、時計回りアイコン(arrow.clockwise)が rotation - 15(表示座標系では反時計回り)を呼び出しています。コメントで説明されていますが、ユーザーの期待と一致しているか実機での確認をお勧めします。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningView.swift (2)

89-96: LGTM! フロアマップIDの伝播が適切に実装されています。

onAppearviewModelloadMapAndDevicesflowNavigatorfloorMapId を設定することで、ビュー階層全体で一貫した状態管理が実現されています。通知ベースのリロードからパラメータベースのアプローチへの移行は、データフローをより明確にしています。


162-214: LGTM! マップキャンバスの条件付きレンダリングが適切です。

floorMapImagefloorMapInfo の両方が存在する場合のみ FloorMapCanvas を表示し、それ以外はローディングプレースホルダーを表示する設計は、ユーザー体験を向上させます。

UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (2)

270-290: LGTM! アンテナID逆引き処理が適切に実装されています。

ConnectionManagementUsecase.shared.antennaPairings からデバイス名に対応するアンテナIDを逆引きするロジックは明確です。マッチしない場合は空文字列を返し、呼び出し元で適切にハンドリングされています。


459-513: LGTM! 統合位置計算のロジックが適切です。

calculateIntegratedPosition は以下の点で良い設計になっています:

  • (0, 0, 0) の無効な座標を除外
  • LOSのみの観測を優先し、NLOSしかない場合のみNLOSを使用
  • 信頼度を観測の種類と数に基づいて計算
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (3)

17-23: LGTM! StateObjectの初期化パターンが適切です。

init(floorMapId:)_viewModel = StateObject(wrappedValue:) を使用し、modelContextonAppear で設定する2段階の初期化は、SwiftUIのライフサイクルに適した設計です。コメントでその理由も説明されています。Based on learnings, ViewModelはPublished/StateObjectでViewと双方向バインディングする設計が推奨されています。


154-163: LGTM! タグ表示モードの切り替えが適切に実装されています。

tagDisplayMode に基づく switch 文で個別表示と統合表示を切り替える設計は、拡張性が高く可読性も良好です。


959-1017: LGTM! コンパクトビューの設計が適切です。

CompactAntennaGroupView は展開/折りたたみ機能を持ち、アンテナごとのデバイスをグループ化して表示します。UIの一貫性が保たれています。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (4)

3-3: SwiftDataのインポートは適切です。

新しいSwiftData統合機能に必要なインポートです。


19-32: 新しい状態プロパティの追加は適切です。

フロアマップ、アンテナ位置、グローバル座標などの複数アンテナ対応に必要な状態が正しく@Publishedで宣言されており、MVVMパターンに従っています。


34-42: プラットフォーム固有のプロパティ定義は適切です。

条件付きコンパイルを使用してiOSとmacOSで異なる画像型を正しく処理しています。


85-160: Observerのセットアップは正しく実装されています。

既存の購読をクリアしてから新しい購読を設定し、weak selfを使用してメモリリークを防止しています。複数の新しいプロパティに対するCombine購読が適切に実装されています。

Comment on lines 162 to 191
/// SwiftDataRepositoryを設定(ViewのonAppearから呼ばれる)
func setupSwiftDataRepository(modelContext: ModelContext) {
if self.swiftDataRepository == nil {
let repository = SwiftDataRepository(modelContext: modelContext)
self.swiftDataRepository = repository

// SensingControlUsecaseを新しく作成(正しいSwiftDataRepositoryを使用)
self.sensingControlUsecase = SensingControlUsecase(
connectionUsecase: self.connectionUsecase,
swiftDataRepository: repository
)
print("✅ SensingControlUsecaseに正しいSwiftDataRepositoryを設定しました")

// RealtimeDataUsecaseにSwiftDataRepositoryを設定
self.realtimeDataUsecase.updateSwiftDataRepository(repository)

// RealtimeDataUsecaseにSensingControlUsecaseを設定(データ永続化に必要)
self.realtimeDataUsecase.setSensingControlUsecase(self.sensingControlUsecase)
print("✅ RealtimeDataUsecaseにSensingControlUsecaseを設定しました")

// ConnectionManagementUsecaseにRealtimeDataUsecaseを設定
self.connectionUsecase.realtimeDataUsecase = self.realtimeDataUsecase
print("✅ ConnectionManagementUsecaseにRealtimeDataUsecaseを設定しました")

// Observersを再設定(新しいSensingControlUsecaseのイベントを購読)
self.setupObservers()

self.loadInitialData()
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

デバッグprint文を削除してください。

Lines 173, 180, 184のデバッグ用print文は、コーディングガイドラインに従って削除する必要があります。

以下のdiffを適用してprint文を削除してください:

             self.sensingControlUsecase = SensingControlUsecase(
                 connectionUsecase: self.connectionUsecase,
                 swiftDataRepository: repository
             )
-            print("✅ SensingControlUsecaseに正しいSwiftDataRepositoryを設定しました")

             // RealtimeDataUsecaseにSwiftDataRepositoryを設定
             self.realtimeDataUsecase.updateSwiftDataRepository(repository)

             // RealtimeDataUsecaseにSensingControlUsecaseを設定(データ永続化に必要)
             self.realtimeDataUsecase.setSensingControlUsecase(self.sensingControlUsecase)
-            print("✅ RealtimeDataUsecaseにSensingControlUsecaseを設定しました")

             // ConnectionManagementUsecaseにRealtimeDataUsecaseを設定
             self.connectionUsecase.realtimeDataUsecase = self.realtimeDataUsecase
-            print("✅ ConnectionManagementUsecaseにRealtimeDataUsecaseを設定しました")

Based on learnings, デバッグ用コードは削除する必要があります。

🤖 Prompt for AI Agents
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
lines 162-191: remove the three debug print statements at lines 173, 180, and
184; delete the print("✅ SensingControlUsecaseに正しいSwiftDataRepositoryを設定しました"),
print("✅ RealtimeDataUsecaseにSensingControlUsecaseを設定しました"), and print("✅
ConnectionManagementUsecaseにRealtimeDataUsecaseを設定しました") calls so the method
only performs repository/usecase setup and observer initialization without debug
output.

Comment on lines +209 to +251
/// 指定されたIDのフロアマップ情報を読み込み
private func loadFloorMapInfoById(floorMapId: String) async {
guard let repository = swiftDataRepository else {
print("⚠️ SwiftDataRepositoryが利用できません")
return
}

do {
if let floorMap = try await repository.loadFloorMap(by: floorMapId) {
self.currentFloorMapInfo = floorMap

// フロアマップIDをRealtimeDataUsecaseに設定
self.realtimeDataUsecase.setFloorMapId(floorMap.id)

// フロアマップ画像を読み込み
#if canImport(UIKit)
#if os(iOS)
self.floorMapImage = floorMap.image
if self.floorMapImage != nil {
print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
} else {
print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
}
#endif
#endif

#if os(macOS)
self.floorMapImage = floorMap.image
if self.floorMapImage != nil {
print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
} else {
print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
}
#endif

print("📍 フロアマップ情報読み込み完了: \(floorMap.name) (ID: \(floorMap.id))")
} else {
print("⚠️ フロアマップが見つかりません (ID: \(floorMapId))")
}
} catch {
print("❌ フロアマップ情報の読み込みに失敗: \(error)")
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

デバッグprint文を削除してください。

Lines 212, 228, 230, 238, 240, 244, 246, 249のデバッグ用print文は、コーディングガイドラインに従って削除する必要があります。本番環境では適切なロギングフレームワーク(OSLogなど)の使用を検討してください。

Based on learnings, デバッグ用コードは削除する必要があります。

🤖 Prompt for AI Agents
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
around lines 209–251: remove the debug print statements at lines 212, 228, 230,
238, 240, 244, 246, and 249; keep the surrounding logic and error handling
intact, and if persistent logging is required replace these prints with a proper
logging API (e.g., OSLog) or the app's logging abstraction, ensuring no stray
print(...) calls remain.

Comment on lines +253 to +267
/// アンテナ位置情報を読み込み
private func loadAntennaPositions() async {
guard let repository = swiftDataRepository,
let floorMapId = currentFloorMapInfo?.id
else {
return
}

do {
let positions = try await repository.loadAntennaPositions(for: floorMapId)
self.allAntennaPositions = positions
print("📍 アンテナ位置情報読み込み完了: \(positions.count)")
} catch {
print("❌ アンテナ位置情報の読み込みに失敗: \(error)")
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

デバッグprint文を削除してください。

Lines 264, 266のデバッグ用print文は、コーディングガイドラインに従って削除する必要があります。

Based on learnings, デバッグ用コードは削除する必要があります。

🤖 Prompt for AI Agents
In
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
around lines 253–267, remove the two debug print statements on lines 264 and 266
(the success and error printouts) so no debug-only console output remains; keep
the await call, assignment to self.allAntennaPositions, and the do/catch intact,
and if structured logging is required later replace these prints with your app's
logger instead of plain print.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (1)

143-157: クリア系メソッドで新しい状態プロパティがリセットされていません

過去のレビューで指摘された問題が未対応です。さらに、重心計算関連のプロパティ(centroidCoordinateintegratedTagCoordinatespreviousFilteredCentroidpreviousFilteredIntegratedPosition)も追加されているため、これらもクリアする必要があります。

 func clearAllRealtimeData() {
     #if DEBUG
     print("🗑️ リアルタイムデータクリア")
     #endif
     self.deviceRealtimeDataList.removeAll()
     self.isReceivingRealtimeData = false
+    self.globalCoordinates.removeAll()
+    self.antennaDataMap.removeAll()
+    self.activeAntennaIds.removeAll()
+    self.totalDataPointCount = 0
+    self.centroidCoordinate = nil
+    self.integratedTagCoordinates.removeAll()
+    self.previousFilteredCentroid = nil
+    self.previousFilteredIntegratedPosition = nil
     objectWillChange.send()
 }

 func clearRealtimeDataForSensing() {
     for deviceData in self.deviceRealtimeDataList {
         deviceData.clearData()
     }
+    self.globalCoordinates.removeAll()
+    self.antennaDataMap.removeAll()
+    self.activeAntennaIds.removeAll()
+    self.totalDataPointCount = 0
+    self.centroidCoordinate = nil
+    self.integratedTagCoordinates.removeAll()
+    self.previousFilteredCentroid = nil
+    self.previousFilteredIntegratedPosition = nil
     objectWillChange.send()
 }
🧹 Nitpick comments (2)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (2)

273-290: シングルトンへの直接アクセスはテスト容易性を低下させます

ConnectionManagementUsecase.sharedへの直接アクセスは密結合を生み、ユニットテスト時にモック化が困難になります。依存性注入パターンの使用を検討してください。

+    private weak var connectionManagementUsecase: ConnectionManagementUsecase?
+
+    public func setConnectionManagementUsecase(_ usecase: ConnectionManagementUsecase) {
+        self.connectionManagementUsecase = usecase
+    }
+
     private func getAntennaId(for deviceName: String) -> String {
-        let antennaPairings = ConnectionManagementUsecase.shared.antennaPairings
+        guard let antennaPairings = connectionManagementUsecase?.antennaPairings else {
+            return ""
+        }

277-284: where句の使用を検討してください

SwiftLintのfor_whereルールに基づき、forループ内の単一if文はwhere句で置き換えることが推奨されます。または、より簡潔にfirst(where:)を使用できます。

-        for (antennaId, pairedDeviceName) in antennaPairings {
-            if pairedDeviceName == deviceName {
-                #if DEBUG
-                    print("🔗 デバイス \(deviceName) はアンテナ \(antennaId) に紐づいています")
-                #endif
-                return antennaId
-            }
-        }
+        if let (antennaId, _) = antennaPairings.first(where: { $0.value == deviceName }) {
+            #if DEBUG
+                print("🔗 デバイス \(deviceName) はアンテナ \(antennaId) に紐づいています")
+            #endif
+            return antennaId
+        }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4da821 and 9e1e104.

📒 Files selected for processing (1)
  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (6 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
**/*Usecase.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Usecaseのファイル名は「〜Usecase.swift」とする

Files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
🧠 Learnings (1)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
🧬 Code graph analysis (1)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (6)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift (1)
  • setFloorMapId (111-113)
UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift (1)
  • saveRealtimeData (226-243)
UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (2)
  • saveRealtimeData (479-493)
  • saveRealtimeData (972-972)
UWBViewerSystemTests/TestHelpers/MockDataRepository.swift (1)
  • saveRealtimeData (261-268)
UWBViewerSystem/Domain/Usecase/RealtimeCoordinateTransformUsecase.swift (1)
  • transformToGlobalCoordinate (24-50)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
🪛 SwiftLint (0.57.0)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift

[Warning] 278-278: where clauses are preferred over a single if inside a for

(for_where)

🔇 Additional comments (4)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (4)

25-53: LGTM!

初期化ロジックとリポジトリ更新メソッドの実装は適切です。型チェックによる条件付きの座標変換Usecase初期化も妥当なアプローチです。


292-333: LGTM!

座標変換のロジックは適切に実装されています。前提条件のチェック(transformUsecase、floorMapId、antennaId)も適切です。


368-441: LGTM!

重心座標の計算ロジックは適切です。LOS/NLOS の優先順位付け、無効座標(0,0,0)と距離0のスキップ処理、ローパスフィルターによる平滑化は、リアルタイムUWBデータ処理として妥当なアプローチです。


462-531: LGTM!

統合タグ位置の更新ロジックも適切に実装されています。複数アンテナからの観測を統合し、LOS優先・NLOS代替のフォールバック戦略は重心計算と一貫性があります。

Comment on lines +558 to +561
/// デバイス名からタグIDを抽出
private func extractTagId(from deviceName: String) -> String {
deviceName
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg "extractTagId" --type swift -n

Repository: kajiLabTeam/UWBViewerSystem

Length of output: 198


未使用のデッドコード:extractTagIdを削除してください

extractTagId関数はコード内で呼び出されておらず、入力をそのまま返すだけの未完成なコードです。コーディングガイドラインに基づき、デッドコードは削除する必要があります。

🤖 Prompt for AI Agents
In UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift around lines 558 to
561 there is an unused, incomplete function `extractTagId(from:)` that simply
returns its input; remove this dead code by deleting the entire private function
declaration (including its doc comment) and run a project-wide search to ensure
there are no remaining references; if any callers are found, either implement
the intended extraction logic or update callers accordingly before removal.

- Added IQR outlier detection functionality to SensorDataProcessor.
- Introduced configuration options for IQR outlier detection and multiplier in SensorDataProcessingConfig.
- Updated SensorDataProcessor to apply IQR outlier removal before nLoS filtering.
- Implemented IQR calculation and outlier removal methods.
- Enhanced AutoAntennaCalibrationViewModel to provide real-time feedback on sensing status, including elapsed time, data point count, and RMSE estimates.
- Improved UI in AutoAntennaCalibrationView to display detailed sensing feedback.
- Added unit tests for IQR outlier detection functionality, including various scenarios for detection and handling of outliers.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (5)

464-527: キャリブレーション完了フローが適切に実装されています

全アンテナ完了時のフローナビゲーションとUserDefaultsへの結果保存が適切に実装されていますが、Lines 486, 496, 503, 525のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff:

-            print("✅ 全アンテナのキャリブレーション完了")
-                print("🚀 次のステップ(センシング実行)へ自動遷移します")
-                print("⚠️ FlowNavigatorが設定されていないため、手動で次へ進んでください")
-            print("💾 キャリブレーション結果をUserDefaultsに保存しました")

605-664: アンテナフィルタリングロジックが適切に強化されています

ペアリング情報と接続状態を組み合わせてアンテナをフィルタリングする実装が正しく追加されています。これにより接続回復機能と連携した適切なアンテナ選択が可能になります。

ただし、Lines 608, 615, 619, 626, 630, 634, 641, 644, 649, 660の多数のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff(全てのデバッグprint文を削除):

-            print("⚠️ [DEBUG] フロアマップIDが取得できません")
-            print("🔍 [DEBUG] loadAntennaPositions()で取得したアンテナ数: \(antennaPositions.count)件")
-                print("🔍 [DEBUG] Antenna[\(index)]: id=\(position.antennaId), name=\(position.antennaName), pos=(\(position.position.x), \(position.position.y))")
-            print("🔗 [DEBUG] ペアリング情報: \(antennaPairings.count)件")
-                print("🔗 [DEBUG] ペアリング: \(antennaId) → \(deviceName)")
-            print("🔌 [DEBUG] 接続中のデバイス: \(connectedDeviceNames)")
-                    print("🔍 [DEBUG] \(position.antennaName) (\(position.antennaId)) → デバイス: \(deviceName), 接続: \(isConnected)")
-                    print("⚠️ [DEBUG] \(position.antennaName) (\(position.antennaId)) はペアリングされていません")
-            print("📡 [DEBUG] 接続中のアンテナ: \(connectedAntennaPositions.count)個")
-            print("📡 キャリブレーション対象アンテナ: \(self.availableAntennas.count)個")

676-844: リアルタイムフィードバック統合が適切に実装されています

センシング中の経過時間、データポイント数、信号品質情報を収集・更新するロジックが正しく実装されています。信号品質の計算(RSSI平均、LoS率、信号強度正規化)も妥当です。

ただし、Lines 689, 702, 708, 781, 786, 801, 820, 834のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff(全てのデバッグprint文を削除):

-        print("📍 タグ位置: \(tagPos.tagId) のデータ収集開始")
-            print("✅ デバイス接続確認: \(connectionUsecase.getConnectedDeviceCount())台")
-            print("🎬 センシングセッション開始: \(sessionId)")
-            print("🛑 センシング停止")
-            print("⏳ データ送信待機中...")
-                print("📊 デバイス \(deviceData.deviceName) のデータ収集: \(deviceData.dataHistory.count)件")
-                    print("  ➕ データ追加: antenna=\(antennaId), tag=\(tagPos.tagId), pos=(\(String(format: "%.2f", position.x)), \(String(format: "%.2f", position.y)))")
-            print("✅ タグ位置: \(tagPos.tagId) のデータ収集完了 (\(completedCount)/\(self.trueTagPositions.count))")

846-905: キャリブレーション実行ロジックが適切です

単一アンテナのキャリブレーション実行、結果保存、結果ステップへの自動遷移が正しく実装されていますが、Lines 856, 898, 899のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff:

-        print("🔧 \(self.currentAntennaName) のキャリブレーション開始")
-            print("🎉 \(self.currentAntennaName) のキャリブレーション完了")
-            print("   位置: (\(config.x), \(config.y)), 角度: \(config.angleDegrees)°, RMSE: \(config.rmse)")

337-396: その他のデバッグprint文も削除してください

以下のメソッド内のデバッグ用print文もコーディングガイドラインに従い削除してください:

  • Line 344: addTagPosition
  • Line 363, 365: selectAntennaForCalibration
  • Line 412: proceedToNextTagPosition
  • Line 484: proceedToNextAntenna

Based on coding guidelines

Also applies to: 529-544

♻️ Duplicate comments (2)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (2)

275-298: デバッグprint文を削除してください

接続監視の実装は適切ですが、Lines 282と293のデバッグ用print文はコーディングガイドラインに従い削除してください。過去のレビューでも同様の指摘がされています。

Based on coding guidelines

適用するdiff:

-                    print("⚠️ 接続断検出: 接続復旧画面を表示します")
-                    print("🔌 接続デバイスの変更を検出: アンテナリストを再読み込みします")

300-318: デバッグprint文を削除してください

エラーハンドリングのロジックは適切ですが、Line 304のデバッグ用print文はコーディングガイドラインに従い削除してください。過去のレビューでも同様の指摘がされています。

Based on coding guidelines

適用するdiff:

-            print("⚠️ データ収集/キャリブレーションを中断します")
🧹 Nitpick comments (7)
UWBViewerSystem/Domain/Utils/AntennaAffineCalibration.swift (4)

68-75: マジックナンバーの明確化を検討してください

重み計算のロジックは正しく動作しますが、0.01(ゼロ除算防止)と10.0(正規化係数)のマジックナンバーについて、定数として定義するか、コメントで根拠を補足すると可読性が向上します。

+        private static let minErrorEstimate = 0.01  // 重み計算時のゼロ除算防止
+        private static let errorWeightNormalizer = 10.0  // errorWeight正規化係数
+
         var weight: Double {
             let strengthWeight = self.quality.strength
             let confidenceWeight = self.quality.confidenceLevel
-            let errorWeight = 1.0 / max(self.quality.errorEstimate, 0.01)
-            // 正規化して0-1の範囲に
-            let normalizedErrorWeight = min(errorWeight / 10.0, 1.0)
+            let errorWeight = 1.0 / max(self.quality.errorEstimate, Self.minErrorEstimate)
+            let normalizedErrorWeight = min(errorWeight / Self.errorWeightNormalizer, 1.0)
             return strengthWeight * confidenceWeight * normalizedErrorWeight
         }

368-379: デバッグ出力はリリースビルドで削除を検討

デバッグ用のprint文が複数箇所に追加されています。コーディングガイドラインに従い、リリースビルドではこれらを削除するか、条件付きコンパイルで制御することを検討してください。


576-595: 共線性チェックのコード重複

estimateAntennaConfigメソッド内(lines 382-402)に類似の共線性チェックロジックが存在します。この新しいcheckCollinearityヘルパーを両方の箇所で再利用することで、DRY原則に従うことができます。

また、4点以上の場合は最初の3点のみをチェックしていますが、他の3点の組み合わせが共線になる可能性も考慮すると、より堅牢なチェックが必要かもしれません。


924-930: SwiftLintの指摘: where句の使用を推奨

静的解析ツールが指摘しているように、forループ内の単一if文はwhere句で置き換えることができます。

-            // 他の行から消去
-            for row in 0..<n {
-                if row != col {
-                    let factor = augmented[row][col]
-                    for j in col...(n) {
-                        augmented[row][j] -= factor * augmented[col][j]
-                    }
+            // 他の行から消去
+            for row in 0..<n where row != col {
+                let factor = augmented[row][col]
+                for j in col...(n) {
+                    augmented[row][j] -= factor * augmented[col][j]
                 }
             }
UWBViewerSystemTests/AntennaAffineCalibrationTests.swift (1)

432-494: 外れ値テストの検証強化を検討

外れ値存在下でのIRLS収束テストは良いですが、外れ値の影響が実際に低減されていることを検証するため、外れ値なしのケースと比較するアサーションを追加すると、より堅牢なテストになります。

例えば、外れ値ありとなしの結果を比較して、位置推定の差が小さいことを確認できます。

// 外れ値なしのデータでも同様のテストを行い、結果を比較
// #expect(abs(configWithOutlier.x - configWithoutOutlier.x) < 1.0)
UWBViewerSystem/Domain/Utils/SensorDataProcessor.swift (1)

164-185: 四分位数計算について確認してください。

簡易的なインデックスベースの四分位数計算を使用していますが、外れ値検出の目的には十分機能します。ただし、より正確な結果が必要な場合は、線形補間による四分位数計算を検討してください。

より正確な四分位数計算が必要な場合:

 private func calculateIQRBounds(_ sortedValues: [Double]) -> (lower: Double, upper: Double) {
     let count = sortedValues.count
 
-    // Q1(第1四分位数)とQ3(第3四分位数)を計算
-    let q1Index = count / 4
-    let q3Index = (count * 3) / 4
-
-    let q1 = sortedValues[q1Index]
-    let q3 = sortedValues[q3Index]
+    // 線形補間による四分位数計算
+    let q1 = percentile(sortedValues, p: 0.25)
+    let q3 = percentile(sortedValues, p: 0.75)
 
     // IQR(四分位範囲)
     let iqr = q3 - q1
 
     // 上下限を計算(係数はconfig.iqrMultiplier)
     let lowerBound = q1 - self.config.iqrMultiplier * iqr
     let upperBound = q3 + self.config.iqrMultiplier * iqr
 
     return (lowerBound, upperBound)
 }
+
+private func percentile(_ sortedValues: [Double], p: Double) -> Double {
+    let index = p * Double(sortedValues.count - 1)
+    let lower = Int(index)
+    let upper = min(lower + 1, sortedValues.count - 1)
+    let fraction = index - Double(lower)
+    return sortedValues[lower] + fraction * (sortedValues[upper] - sortedValues[lower])
+}
UWBViewerSystemTests/SensorDataProcessorTests.swift (1)

51-68: 既存テストでIQR設定を明示的に無効化することを推奨します。

useIQROutlierDetectionのデフォルトがtrueに変更されたため、トリミングのみをテストする意図であれば、明示的にfalseを設定する方が明確です。現在のテストデータには外れ値が含まれにくいため問題なく動作しますが、将来的なテストの安定性のために設定を明示することを推奨します。

 let config = SensorDataProcessingConfig(
     firstTrim: 5,
     endTrim: 5,
     movingAverageWindowSize: 1,  // 移動平均無効化
-    filterNLOS: false
+    filterNLOS: false,
+    useIQROutlierDetection: false  // IQRフィルタ無効化
 )

同様に、trimmingInsufficientDatamovingAveragemovingAverageLargeWindownLOSFilternLOSFilterDisabledstatisticsstandardDeviationImprovementtestApplyMovingAverageToPointsの各テストでも明示的に設定することを検討してください。

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e1e104 and 0c333fc.

📒 Files selected for processing (6)
  • UWBViewerSystem/Domain/Utils/AntennaAffineCalibration.swift (6 hunks)
  • UWBViewerSystem/Domain/Utils/SensorDataProcessor.swift (3 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (7 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (12 hunks)
  • UWBViewerSystemTests/AntennaAffineCalibrationTests.swift (1 hunks)
  • UWBViewerSystemTests/SensorDataProcessorTests.swift (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Domain/Utils/SensorDataProcessor.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Domain/Utils/AntennaAffineCalibration.swift
  • UWBViewerSystemTests/AntennaAffineCalibrationTests.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
  • UWBViewerSystemTests/SensorDataProcessorTests.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧠 Learnings (5)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T14:37:07.451Z
Learnt from: harutiro
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: :0-0
Timestamp: 2025-09-23T14:37:07.451Z
Learning: UWBViewerSystemでUserDefaults変更通知のテストを行う際は、NotificationCenterの非同期処理完了を確実に待機するため、1.5秒程度の待機時間とポーリング方式の検証を使用する。これにより競合状態やタイミング問題を回避できる。

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧬 Code graph analysis (4)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (4)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (5)
  • loadFloorMapInfo (330-335)
  • loadFloorMapInfo (579-603)
  • setFlowNavigator (271-273)
  • goToPreviousTagPosition (416-452)
  • proceedToNextTagPosition (409-413)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (1)
  • loadFloorMapInfo (202-207)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)
  • loadFloorMapInfo (80-87)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (1)
  • setRouter (33-35)
UWBViewerSystem/Domain/Utils/AntennaAffineCalibration.swift (1)
UWBViewerSystemTests/AntennaAffineCalibrationTests.swift (3)
  • estimateAffineTransform (78-109)
  • insufficientPoints (192-223)
  • extractRotationAngle (111-134)
UWBViewerSystemTests/AntennaAffineCalibrationTests.swift (1)
UWBViewerSystem/Domain/Utils/AntennaAffineCalibration.swift (1)
  • estimateAntennaConfigWithQuality (458-538)
UWBViewerSystemTests/SensorDataProcessorTests.swift (2)
UWBViewerSystem/Domain/Utils/SensorDataProcessor.swift (2)
  • filterNLOS (136-138)
  • processObservations (72-87)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
🪛 SwiftLint (0.57.0)
UWBViewerSystem/Domain/Utils/AntennaAffineCalibration.swift

[Warning] 902-902: where clauses are preferred over a single if inside a for

(for_where)

🔇 Additional comments (22)
UWBViewerSystem/Domain/Utils/AntennaAffineCalibration.swift (5)

104-126: LGTM!

IRLSConfig構造体とイニシャライザは適切に実装されています。デフォルト値も妥当です。


404-416: LGTM!

IRLSとの条件分岐は適切に実装されています。useCauchyLossフラグに基づいて適切なメソッドを選択しています。


458-538: LGTM!

品質情報付きキャリブレーションメソッドは適切に実装されています。共通タグの検証、重み付け平均の計算、共線性チェック、IRLS推定の流れが論理的です。


543-573: LGTM!

重み付け平均の実装は堅牢です。空配列のケースと重みの合計がゼロに近い場合のフォールバック処理が適切に行われています。


681-758: IRLS実装はロバストです

Cauchy Loss IRLSの実装は適切です。重み更新式 1 / (1 + 0.5 * residual^2) と損失関数 log(1 + 0.5 * residual^2) が正しく対応しています。収束判定も適切に実装されています。

UWBViewerSystemTests/AntennaAffineCalibrationTests.swift (2)

348-354: LGTM!

IRLSConfigのデフォルト値を検証するシンプルで効果的なテストです。


356-430: LGTM!

重み付きキャリブレーションのテストは適切です。高品質・低品質データの重み比較と、実際のキャリブレーションフローの両方をカバーしています。

UWBViewerSystem/Domain/Utils/SensorDataProcessor.swift (3)

25-55: IQR設定の追加は適切です。

新しい設定プロパティ(useIQROutlierDetectioniqrMultiplier)の追加は正しく実装されています。デフォルト値(有効、係数1.5)は外れ値検出の標準的な設定です。


72-87: 処理パイプラインの順序が適切です。

トリミング → IQR外れ値除去 → NLOSフィルタリング → 移動平均の順序は論理的で、各ステップが期待通りに機能します。


140-162: IQR外れ値除去の実装は正しいです。

最低4点のチェックとX/Y両座標での独立したIQR計算は適切なアプローチです。両座標が範囲内である場合のみデータを保持するロジックは、2D位置データの外れ値検出として妥当です。

UWBViewerSystemTests/SensorDataProcessorTests.swift (2)

268-331: IQR外れ値検出テストが適切に実装されています。

テストデータの設計が良好です:

  • 正常データ(0〜9の範囲、20点)で安定したIQRを生成
  • 明らかな外れ値(100, 100)を追加
  • 外れ値除去後の検証が適切

333-423: エッジケーステストが適切です。

IQR無効時と不十分なデータ数のケースを正しくテストしています。特に4点未満でのスキップ動作の検証は重要です。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (4)

11-28: 信号品質表示構造体の実装が適切です

アンテナ別の信号品質を表示するためのデータ構造が適切に定義されています。品質レベルの閾値も妥当です。


107-122: リアルタイムフィードバック用プロパティが適切に追加されています

センシング経過時間、データポイント数、RMSE推定値、アンテナ別信号品質など、ユーザーへのフィードバックに必要なプロパティが適切に定義されています。


172-178: タグ位置戻り機能の判定ロジックが適切です

データ収集ステップで、完了済みのタグが存在し、収集中・キャリブレーション中でない場合にのみ前のタグに戻れる判定ロジックが正しく実装されています。


270-273: フローナビゲーター設定メソッドが適切です

weak参照で循環参照を防ぎつつ、フローナビゲーターを設定する実装が適切です。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (6)

10-20: 初期化とプロパティ宣言が適切です

floorMapIdパラメータの追加、StateObjectとEnvironmentObjectの適切な使用により、フロアマップ指定とナビゲーション統合が正しく実装されています。


45-52: 初期化シーケンスが適切です

指定されたフロアマップIDでのデータ読み込み、フローナビゲーターの設定、ViewModelへの連携が適切な順序で実装されています。


66-74: 接続復旧UI統合が適切です

接続エラー時のシート表示、ConnectionManagementUsecaseの連携、iOS向けの表示サイズ設定が正しく実装されています。


562-805: センシング詳細フィードバックUIが優れています

リアルタイムでの経過時間、データポイント数、RMSE推定値、アンテナ別信号品質を表示するUIが包括的に実装されています。プログレスバー、色分けされた品質インジケーター、詳細な信号情報により、ユーザーはセンシング状態を的確に把握できます。


584-638: タグナビゲーションUIが適切に実装されています

複数タグ位置がある場合の「前のタグへ」「次のタグ位置へ」ボタンの条件付き表示、最後のタグでの「前のタグへ」のみ表示など、状態に応じた適切なナビゲーションUIが実装されています。色分け(オレンジ/ブルー)と矢印アイコンによる方向の明示も優れています。


970-975: プレビュー設定が更新されています

新しいfloorMapIdパラメータとNavigationRouterModel環境オブジェクトを含むプレビュー設定が適切に更新されています。

Comment on lines +329 to +335
/// 指定されたフロアマップ情報を読み込み
func loadFloorMapInfo(floorMapId: String) {
Task {
await self.loadFloorMapInfoById(floorMapId: floorMapId)
await self.loadAvailableAntennas()
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

フロアマップ読み込み機能が適切に追加されています

指定されたIDでフロアマップ情報を読み込む機能が適切に実装されていますが、Line 570のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff:

-                print("🗺️ フロアマップ読み込み完了: \(floorMap.name), 画像: \(self.floorMapImage != nil ? "あり" : "なし")")

Also applies to: 548-577

🤖 Prompt for AI Agents
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
lines ~548-577 (and referenced snippet at 329-335): there's a leftover debug
print at around line 570 that violates coding guidelines; remove the print(...)
statement and, if the information is still needed for diagnostics, replace it
with a proper logging call using the project's logging facility or wrap it with
a debug-only conditional, ensuring no stray console prints remain.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (4)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (4)

275-298: 接続監視の実装は適切ですが、デバッグ用print文を削除してください

Combineを使った接続エラーと接続デバイス変更の監視が正しく実装されており、weak selfによりメモリリークも防止されています。

ただし、Lines 282と293のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff:

-                    print("⚠️ 接続断検出: 接続復旧画面を表示します")
-                    print("🔌 接続デバイスの変更を検出: アンテナリストを再読み込みします")

300-318: エラーハンドリングは適切ですが、デバッグ用print文を削除してください

接続エラー発生時に実行中の処理を適切に停止し、ユーザーにわかりやすいエラーメッセージを提供しています。

ただし、Line 304のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff:

-            print("⚠️ データ収集/キャリブレーションを中断します")

415-452: 前のタグ位置に戻る機能が適切に実装されていますが、デバッグ用print文を削除してください

最後に完了したタグを見つけてデータをクリアし、インデックスを戻すロジックが正しく実装されています。

ただし、Lines 423と447のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff:

-            print("⚠️  完了済みのタグが見つかりません")
-            print("⬅️  タグ(\(tagToUndo.tagId))を取り消してそのタグ位置に戻る(index: \(lastCompletedIndex))")

548-577: フロアマップ読み込み処理が適切に実装されていますが、デバッグ用print文を削除してください

指定されたIDでフロアマップ情報を読み込む機能が正しく実装されており、プラットフォーム固有の画像読み込みも適切です。

ただし、Line 570のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff:

-                print("🗺️ フロアマップ読み込み完了: \(floorMap.name), 画像: \(self.floorMapImage != nil ? "あり" : "なし")")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c333fc and 83f3139.

📒 Files selected for processing (1)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (13 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧠 Learnings (4)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T14:37:07.451Z
Learnt from: harutiro
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: :0-0
Timestamp: 2025-09-23T14:37:07.451Z
Learning: UWBViewerSystemでUserDefaults変更通知のテストを行う際は、NotificationCenterの非同期処理完了を確実に待機するため、1.5秒程度の待機時間とポーリング方式の検証を使用する。これにより競合状態やタイミング問題を回避できる。

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧬 Code graph analysis (1)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (5)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (3)
  • loadFloorMapInfo (80-87)
  • loadFloorMapInfoById (90-100)
  • proceedToNextStep (355-370)
UWBViewerSystem/Domain/Entity/RealtimeData.swift (1)
  • clearData (118-122)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (1)
  • proceedToNextStep (45-106)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (1)
  • getDeviceName (206-208)
🔇 Additional comments (6)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (6)

11-28: 信号品質表示用の構造体が適切に実装されています

SignalQualityDisplay構造体は、RSSI、LoS率、信号強度、データポイント数を適切にカプセル化し、品質レベルの判定ロジックも明確です。


69-70: 新規プロパティが適切に追加されています

接続復旧UI用のshowConnectionRecovery、リアルタイムフィードバック用の各種プロパティ、およびweak参照によるflowNavigatorが正しく実装されています。メモリリークも防止されています。

Also applies to: 107-122, 132-132


172-178: 前のタグ位置に戻る判定ロジックが正確です

データ収集ステップでのみ戻ることができ、収集中・キャリブレーション中は禁止、完了済みタグの存在確認も適切です。


270-273: フローナビゲーター設定メソッドが適切です

weak参照により循環参照を防ぎ、依存性注入パターンが正しく実装されています。


329-335: フロアマップ読み込みメソッドが適切に追加されています

指定されたIDでフロアマップ情報を読み込む機能が正しく実装されており、内部メソッドへの委譲とアンテナリストの読み込みも適切です。


713-776: リアルタイムデータ収集とフィードバック更新が適切に実装されています

センシング中のリアルタイムフィードバック変数の初期化、0.5秒間隔でのデータ更新、信号品質の集計ロジック(RSSI平均、LoS率、信号強度の正規化)がすべて正確に実装されています。アクティブなデバイスのみを処理する条件分岐も適切です。

Comment on lines +487 to 527

// キャリブレーション結果をUserDefaultsに保存
self.saveCalibrationResultToUserDefaults()

// 成功アラートを表示
self.showSuccessAlert = true

// フローナビゲーターで次のステップへ進む
if let flowNavigator = self.flowNavigator {
print("🚀 次のステップ(センシング実行)へ自動遷移します")
// アラート表示後に自動で次へ進むため、少し待機
let floorMapId = self.currentFloorMapInfo?.id
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
flowNavigator.proceedToNextStep(floorMapId: floorMapId)
}
} else {
print("⚠️ FlowNavigatorが設定されていないため、手動で次へ進んでください")
}
}
}

/// キャリブレーション結果をUserDefaultsに保存
private func saveCalibrationResultToUserDefaults() {
// キャリブレーションデータを作成(アンテナ数の情報を含める)
let calibrationData: [String: Double] = [
"completedAntennaCount": Double(self.completedAntennaIds.count),
"totalAntennaCount": Double(self.availableAntennas.count)
]

let calibrationResult = SystemCalibrationResult(
timestamp: Date(),
wasSuccessful: true,
calibrationData: calibrationData,
errorMessage: nil
)

if let encoded = try? JSONEncoder().encode(calibrationResult) {
UserDefaults.standard.set(encoded, forKey: "lastCalibrationResult")
print("💾 キャリブレーション結果をUserDefaultsに保存しました")
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

キャリブレーション完了処理が適切に実装されていますが、デバッグ用print文を削除してください

全アンテナ完了時の結果保存、アラート表示、自動遷移のフローが正しく実装されています。UserDefaultsへの保存も適切です。

ただし、Lines 496, 503, 525のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff:

-                print("🚀 次のステップ(センシング実行)へ自動遷移します")
-                print("⚠️ FlowNavigatorが設定されていないため、手動で次へ進んでください")
-            print("💾 キャリブレーション結果をUserDefaultsに保存しました")
🤖 Prompt for AI Agents
In
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
around lines 487 to 527, remove the three debug print statements on lines ~496,
~503 and ~525 that output progress and warnings (the "🚀
次のステップ(センシング実行)へ自動遷移します", "⚠️ FlowNavigatorが設定されていないため、手動で次へ進んでください", and "💾
キャリブレーション結果をUserDefaultsに保存しました" prints) so the code complies with the coding
guidelines; simply delete those lines (or replace with appropriate structured
logging if required by project standards) and keep the existing save, alert and
navigation logic intact.

Comment on lines +624 to +660
// ConnectionManagementUsecaseからペアリング情報を取得
let antennaPairings = ConnectionManagementUsecase.shared.antennaPairings
print("🔗 [DEBUG] ペアリング情報: \(antennaPairings.count)")

for (antennaId, deviceName) in antennaPairings {
print("🔗 [DEBUG] ペアリング: \(antennaId)\(deviceName)")
}

// 接続中のデバイス名を取得
let connectedDeviceNames = ConnectionManagementUsecase.shared.connectedDeviceNames
print("🔌 [DEBUG] 接続中のデバイス: \(connectedDeviceNames)")

// ペアリングされている かつ 接続中のアンテナのみをフィルタリング
let connectedAntennaPositions = antennaPositions.filter { position in
// アンテナIDに紐づくデバイス名を取得
if let deviceName = antennaPairings[position.antennaId] {
let isConnected = connectedDeviceNames.contains(deviceName)
print("🔍 [DEBUG] \(position.antennaName) (\(position.antennaId)) → デバイス: \(deviceName), 接続: \(isConnected)")
return isConnected
} else {
print("⚠️ [DEBUG] \(position.antennaName) (\(position.antennaId)) はペアリングされていません")
return false
}
}

print("📡 [DEBUG] 接続中のアンテナ: \(connectedAntennaPositions.count)")

// アンテナ位置データからアンテナリストを構築(接続中のアンテナのみ)
self.availableAntennas = connectedAntennaPositions.map { position in
AntennaInfo(
id: position.antennaId,
name: position.antennaName,
isSelected: false
)
}

print("📡 利用可能なアンテナ: \(self.availableAntennas.count)")
print("📡 キャリブレーション対象アンテナ: \(self.availableAntennas.count)")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

接続中アンテナのフィルタリングロジックが正確ですが、デバッグ用print文を削除してください

ペアリングされているかつ接続中のアンテナのみをフィルタリングする実装が正しく、ConnectionManagementUsecaseのAPIも適切に使用されています。

ただし、Lines 626, 629, 634, 641, 644, 649, 660の複数のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff:

-            print("🔗 [DEBUG] ペアリング情報: \(antennaPairings.count)件")
-
-            for (antennaId, deviceName) in antennaPairings {
-                print("🔗 [DEBUG] ペアリング: \(antennaId) → \(deviceName)")
-            }
-
-            // 接続中のデバイス名を取得
-            let connectedDeviceNames = ConnectionManagementUsecase.shared.connectedDeviceNames
-            print("🔌 [DEBUG] 接続中のデバイス: \(connectedDeviceNames)")
-                    print("🔍 [DEBUG] \(position.antennaName) (\(position.antennaId)) → デバイス: \(deviceName), 接続: \(isConnected)")
-                    print("⚠️ [DEBUG] \(position.antennaName) (\(position.antennaId)) はペアリングされていません")
-            print("📡 [DEBUG] 接続中のアンテナ: \(connectedAntennaPositions.count)個")
-            print("📡 キャリブレーション対象アンテナ: \(self.availableAntennas.count)個")

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
around lines 624–660, remove the debug print statements at lines 626, 629, 634,
641, 644, 649 and 660 that log pairing/connection/selection info; keep the
filtering and mapping logic intact and ensure no other behavior is changed (if
persistent logging is required replace prints with the project logger instead,
otherwise just delete the print calls).

…DataCollection, and SensingManagement modules

- Added connection recovery state management to AutoAntennaCalibrationViewModel, including saving operation state before disconnection and attempting reconnection.
- Enhanced DataCollectionViewModel with automatic reconnection logic, preserving sensing state before disconnection.
- Introduced UI overlays to indicate reconnection attempts in DataCollectionView and SensingManagementView.
- Removed alert messages in PairingSettingViewModel, replacing them with console logs for pairing status.
- Improved connection error handling across various view models, ensuring seamless user experience during disconnections.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)

517-534: ペアリング解除がConnectionManagementUsecaseに反映されていません(未修正)

過去のレビューで指摘された問題が修正されていません。527行目でself.antennaPairings.removeAll { $0.device.id == endpointId }を実行した後に、531行目でfirst(where:)でペアリングを探しているため、常にnilになりconnectionUsecase.unpairAntenna(...)が呼ばれません。

     nonisolated func onDisconnected(_ endpointId: String) {
         Task { @MainActor in
             if let index = availableDevices.firstIndex(where: { $0.id == endpointId }) {
                 // デバイス情報を保持しつつ接続状態のみ更新
                 var updatedDevice = self.availableDevices[index]
                 updatedDevice.isConnected = false
                 self.availableDevices[index] = updatedDevice
             }

-            // ペアリング情報からも削除
-            self.antennaPairings.removeAll { $0.device.id == endpointId }
-            self.isConnected = !self.antennaPairings.isEmpty
-
             // ConnectionManagementUsecaseからも削除
             if let pairing = self.antennaPairings.first(where: { $0.device.id == endpointId }) {
                 self.connectionUsecase.unpairAntenna(antennaId: pairing.antenna.id)
             }
+
+            // ペアリング情報からも削除
+            self.antennaPairings.removeAll { $0.device.id == endpointId }
+            self.isConnected = !self.antennaPairings.isEmpty
         }
     }
♻️ Duplicate comments (12)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (5)

362-368: 非推奨メソッドの扱いを明確化してください

コメントで非推奨と記載されていますが、@available属性が付いておらず、Line 358から呼び出されています。


393-411: プラットフォーム固有コードの重複を解消してください

iOSとmacOSで同一のロジックが重複しています。floorMapImageプロパティが既にプラットフォーム別に定義されているため、代入とログ処理は共通化できます。

-                #if canImport(UIKit)
-                    #if os(iOS)
-                        self.floorMapImage = floorMap.image
-                        if self.floorMapImage != nil {
-                            print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
-                        } else {
-                            print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
-                        }
-                    #endif
-                #endif
-
-                #if os(macOS)
-                    self.floorMapImage = floorMap.image
-                    if self.floorMapImage != nil {
-                        print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
-                    } else {
-                        print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
-                    }
-                #endif
+                self.floorMapImage = floorMap.image

339-350: デバッグprint文の削除が必要です

Lines 339, 346, 350のデバッグ用print文は、コーディングガイドラインに従って削除する必要があります。


467-477: 非同期処理の待機方法と未実装コードに問題があります

  1. Line 468の0.5秒固定スリープは、SwiftDataの永続化完了を保証しません
  2. Lines 474-477のMainActor.runブロックはコメントのみで実装がありません

559-739: エクスポートメソッドが長すぎ、デバッグprint文が多数含まれています

このメソッドは約180行あり、複数の責務を持っています。また、多数のデバッグ用print文が含まれています。

これらの問題は過去のレビューで既に指摘されています。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (1)

165-165: 未使用パラメータを_に置き換えてください

静的解析ツールが指摘している通り、Line 165のgeometryパラメータは使用されていません。クロージャ内ではFloorMapCanvasから提供されるcanvasGeometryを使用しています。

     private var fullScreenFloorMap: some View {
-        GeometryReader { geometry in
+        GeometryReader { _ in
             if let floorMapImage = self.viewModel.floorMapImage,
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (1)

263-272: デバッグ用print文を削除してください

過去のレビューで指摘されている通り、デバッグ用のprint文はコーディングガイドラインに従い削除してください。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (5)

359-421: 接続監視の実装が適切です

Combineを使用した包括的な接続状態監視が適切に実装されています。過去のレビューで指摘されているように、デバッグ用print文の削除を推奨します。


684-721: タグ位置戻り機能が適切に実装されています

最後に完了したタグを見つけてデータをクリアし、インデックスを戻すロジックが正しく実装されています。過去のレビューで指摘されているデバッグ用print文の削除を推奨します。


777-796: キャリブレーション結果の保存が適切に実装されています

SystemCalibrationResultを使用したUserDefaultsへの保存が適切です。過去のレビューで指摘されているデバッグ用print文の削除を推奨します。


893-929: 接続中アンテナのフィルタリングロジックが正確です

ペアリングされているかつ接続中のアンテナのみをフィルタリングする実装が正しく、ConnectionManagementUsecaseのAPIも適切に使用されています。過去のレビューで指摘されているように、デバッグ用print文の削除を推奨します。


1136-1189: デバイス固有データ収集ロジックが正確です

ターゲットデバイスからのデータのみを収集し、UWBデータから3D座標への変換を行っています。データが取得できない場合のエラーハンドリングも適切です。過去のレビューで指摘されているデバッグ用print文の削除を推奨します。

🧹 Nitpick comments (11)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (1)

263-298: 再接続ロジックの固定スリープ時間に注意

attemptAutoReconnect内で複数のTask.sleepが使用されています。特にLine 271の1秒待機とLine 281の500ms×16回ループは、ネットワーク状況によっては不十分または過剰な場合があります。

現在の実装でも動作しますが、将来的には以下の改善を検討してください:

  • 接続成功のPublisherを監視して、接続確立を検知する
  • タイムアウトをプロパティ化して調整可能にする
UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift (1)

166-192: stopRemoteSensingForDevicecurrentSessionIdをクリアしない理由を確認してください

stopRemoteSensing()(Line 217-218)ではCSV出力のためにcurrentSessionIdをクリアしないというコメントがありますが、stopRemoteSensingForDeviceにはそのコメントがなく、暗黙的にクリアしていません。

キャリブレーション用途では意図的かもしれませんが、一貫性のためにコメントを追加するか、意図を明確にすることを推奨します。

UWBViewerSystem/Devices/NearByConnection/NearByConnectionApi.swift (2)

197-230: Discovery状態管理の一貫性

startDiscoveryInternalではエラー時にself.isDiscovering = falseを設定していますが(Line 206)、対応するstartAdvertiseInternalには同様の広告状態フラグがありません。

ConnectionManagementUsecase側でisAdvertisingフラグを管理しているため(外部スニペット参照)、この非対称性は許容されますが、将来的にNearbyRepository内で一貫した状態管理を検討してください。


187-192: 既存Discovery停止時のログ出力

デバッグ用のprint文(Line 189)がありますが、コーディングガイドラインに従い、本番コードではLoggerを使用するか削除することを推奨します。

 if self.isDiscovering {
-    print("📡 既にDiscovery中のため、一度停止します")
     discoverer.stopDiscovery()
     self.isDiscovering = false
 }
UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift (2)

167-168: デバッグ用のprint文を削除してください

Lines 167、196、213にデバッグ用のprint文があります。コーディングガイドラインに従い、本番コードでは削除するか、Loggerを使用してください。

-print("🔄 再接続試行 \(attempt)/\(self.maxReconnectAttempts)")
+// Logger経由でログ出力するか、削除してください

-print("✅ 再接続成功")
+// 削除

-print("❌ 再接続失敗: 最大試行回数に達しました")
+// 削除

Also applies to: 196-196, 213-213


189-203: 接続確立の待機ロジック

最大5秒間(500ms × 10回)接続確立を待機するロジックは適切です。ただし、接続が確立されなかった場合のユーザーフィードバックが不足しています。各試行の終了時に何らかの状態更新を行うことを検討してください。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (1)

86-117: macOSでのProgressViewスタイリング

Line 94でCircularProgressViewStyle(tint: .white)を使用していますが、#if os(iOS)でラップされていません。macOSでもコンパイル可能ですが、SensingManagementView(Lines 74-76)では条件付きでスタイルを適用しています。一貫性のため同様のアプローチを検討してください。

 ProgressView()
     .scaleEffect(1.5)
-    .progressViewStyle(CircularProgressViewStyle(tint: .white))
+#if os(iOS)
+    .progressViewStyle(CircularProgressViewStyle(tint: .white))
+#endif
UWBViewerSystem/Presentation/Scenes/SensingTab/SensingManagementPage/SensingManagementViewModel.swift (3)

145-188: 接続エラー監視のセットアップが適切です

isConnectionMonitoringSetupフラグによる重複防止と、weak selfキャプチャによるメモリリーク防止が適切に実装されています。ただし、デバッグ用print文はコーディングガイドラインに従い削除してください。

     private func setupConnectionErrorObserver() {
         // 既に設定済みの場合はスキップ(重複実行防止)
         guard !self.isConnectionMonitoringSetup else {
-            print("ℹ️ SensingManagement: 接続監視は既に設定済みです")
             return
         }
         self.isConnectionMonitoringSetup = true
         ...
-                print("🔴 SensingManagement: 初期化時に既存の接続エラーを検知")
                 self.handleConnectionError()
             } else if !connectionUsecase.hasConnectedDevices() {
-                print("🔴 SensingManagement: 初期化時に接続デバイスなしを検知")
                 connectionUsecase.hasConnectionError = true
                 self.handleConnectionError()
             }
         ...
             .sink { [weak self] hasError in
                 guard let self else { return }
                 if hasError {
-                    print("🔴 SensingManagement: 接続エラーを検知")
                     self.handleConnectionError()
                 }
             }

Based on coding guidelines


191-217: 接続エラー処理ロジックが適切です

センシング中に接続が切断された場合の状態保存と一時停止処理が正しく実装されています。デバッグ用print文の削除を推奨します。


220-271: 自動再接続ロジックがDataCollectionViewModelと重複しています

attemptAutoReconnect()の実装がDataCollectionViewModel(relevant_code_snippetsの253-304行目)とほぼ同一です。共通のユーティリティクラスまたはプロトコルエクステンションへの抽出を検討してください。

また、スリープ時間のマジックナンバーを定数化することを推奨します。

+    private let reconnectWaitInterval: UInt64 = 1_000_000_000  // 1秒
+    private let connectionCheckInterval: UInt64 = 500_000_000  // 0.5秒
+    private let connectionCheckMaxCount = 16  // 最大8秒待機
+
     private func attemptAutoReconnect() async {
         ...
-            try? await Task.sleep(nanoseconds: 1_000_000_000)
+            try? await Task.sleep(nanoseconds: self.reconnectWaitInterval)
             ...
-            for _ in 0..<16 {
-                try? await Task.sleep(nanoseconds: 500_000_000)
+            for _ in 0..<self.connectionCheckMaxCount {
+                try? await Task.sleep(nanoseconds: self.connectionCheckInterval)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (1)

188-223: ペアリング管理APIが適切に設計されています

シンプルなDictionary操作によるペアリング管理は理解しやすく、適切です。ただし、デバッグ用print文はコーディングガイドラインに従い削除してください。

     public func pairAntennaWithDevice(antennaId: String, deviceName: String) {
         self.antennaPairings[antennaId] = deviceName
-        print("🔗 ペアリング登録: \(antennaId) → \(deviceName)")
     }

     public func unpairAntenna(antennaId: String) {
         self.antennaPairings.removeValue(forKey: antennaId)
-        print("✂️ ペアリング削除: \(antennaId)")
     }

     public func clearAllPairings() {
         self.antennaPairings.removeAll()
-        print("🧹 すべてのペアリングをクリアしました")
     }

Based on coding guidelines

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83f3139 and c5ce0cd.

📒 Files selected for processing (12)
  • UWBViewerSystem/Devices/NearByConnection/NearByConnectionApi.swift (2 hunks)
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (7 hunks)
  • UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift (4 hunks)
  • UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (8 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (14 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift (4 hunks)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (15 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (7 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (6 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/SensingManagementPage/SensingManagementView.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/SensingManagementPage/SensingManagementViewModel.swift (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingView.swift
🧰 Additional context used
📓 Path-based instructions (4)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Devices/NearByConnection/NearByConnectionApi.swift
  • UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/SensingManagementPage/SensingManagementView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/SensingManagementPage/SensingManagementViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
**/*View.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Viewのファイル名は「〜View.swift」とする

Files:

  • UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/SensingManagementPage/SensingManagementView.swift
**/*Usecase.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Usecaseのファイル名は「〜Usecase.swift」とする

Files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/SensingManagementPage/SensingManagementViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧠 Learnings (10)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する

Applied to files:

  • UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Applied to files:

  • UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift
  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/SensingManagementPage/SensingManagementViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to Devices/**/*.swift : UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する

Applied to files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*Usecase.swift : Usecaseのファイル名は「〜Usecase.swift」とする

Applied to files:

  • UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Clean Architecture + MVVMを採用する(データフロー: View→ViewModel→Usecase→Repository)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*ViewModel.swift : ViewModelのファイル名は「〜ViewModel.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : 不要なimport文は削除する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T14:37:07.451Z
Learnt from: harutiro
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: :0-0
Timestamp: 2025-09-23T14:37:07.451Z
Learning: UWBViewerSystemでUserDefaults変更通知のテストを行う際は、NotificationCenterの非同期処理完了を確実に待機するため、1.5秒程度の待機時間とポーリング方式の検証を使用する。これにより競合状態やタイミング問題を回避できる。

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*Repository.swift : Repositoryのファイル名は「〜Repository.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
🧬 Code graph analysis (7)
UWBViewerSystem/Devices/NearByConnection/NearByConnectionApi.swift (2)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (6)
  • stopAdvertising (87-90)
  • startAdvertising (78-85)
  • onConnectionStateChanged (235-240)
  • stopDiscovery (100-102)
  • startDiscovery (92-98)
  • onDiscoveryStateChanged (229-233)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (2)
  • onConnectionStateChanged (434-438)
  • onDiscoveryStateChanged (547-554)
UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift (2)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (5)
  • getConnectedDeviceCount (177-179)
  • resetAll (119-147)
  • startAdvertising (78-85)
  • startDiscovery (92-98)
  • hasConnectedDevices (173-175)
UWBViewerSystem/Devices/NearByConnection/NearByConnectionApi.swift (5)
  • resetAll (376-386)
  • startAdvertising (696-698)
  • startDiscovery (181-195)
  • startDiscovery (709-709)
  • hasConnectedDevices (415-417)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (3)
UWBViewerSystem/Devices/NearByConnection/NearByConnectionApi.swift (2)
  • sendDataToDevice (321-356)
  • requestConnection (240-262)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)
  • pairAntennaWithDevice (230-297)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (1)
  • removeDisconnectedDevice (136-141)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (4)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (5)
  • loadFloorMapInfo (599-604)
  • loadFloorMapInfo (848-872)
  • setFlowNavigator (355-357)
  • goToPreviousTagPosition (685-721)
  • proceedToNextTagPosition (678-682)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)
  • loadFloorMapInfo (78-85)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (1)
  • loadFloorMapInfo (371-376)
UWBViewerSystem/Presentation/Router/SensingFlowNavigator.swift (1)
  • setRouter (33-35)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (5)
UWBViewerSystem/Presentation/Router/NavigationRouterModel.swift (2)
  • pop (34-36)
  • push (25-31)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (2)
  • loadFloorMapInfo (599-604)
  • loadFloorMapInfo (848-872)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (1)
  • loadFloorMapInfo (78-85)
UWBViewerSystem/Presentation/Components/FloorMapCanvas.swift (2)
  • realWorldToNormalized (339-355)
  • normalizedToImageCoordinate (323-328)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
UWBViewerSystem/Presentation/Scenes/SensingTab/SensingManagementPage/SensingManagementViewModel.swift (3)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (3)
  • handleConnectionError (231-251)
  • attemptAutoReconnect (254-305)
  • handleReconnectionSuccess (308-326)
UWBViewerSystem/Devices/NearByConnection/NearByConnectionApi.swift (5)
  • hasConnectedDevices (415-417)
  • resetAll (376-386)
  • startAdvertising (696-698)
  • startDiscovery (181-195)
  • startDiscovery (709-709)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (4)
  • hasConnectedDevices (173-175)
  • resetAll (119-147)
  • startAdvertising (78-85)
  • startDiscovery (92-98)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (3)
UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (6)
  • loadFloorMap (648-654)
  • loadFloorMap (984-984)
  • loadAntennaPositions (309-316)
  • loadAntennaPositions (318-353)
  • loadAntennaPositions (964-964)
  • loadAntennaPositions (965-965)
UWBViewerSystemTests/TestHelpers/MockDataRepository.swift (4)
  • loadFloorMap (325-327)
  • loadAntennaPositions (81-83)
  • loadAntennaPositions (220-222)
  • loadAntennaPositions (224-226)
UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (3)
  • pairAntennaWithDevice (191-194)
  • unpairAntenna (197-200)
  • clearAllPairings (203-206)
🪛 SwiftLint (0.57.0)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift

[Warning] 165-165: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🔇 Additional comments (34)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionView.swift (5)

17-23: StateObjectの初期化パターンは適切です

init内での_viewModel = StateObject(wrappedValue:)パターンは、SwiftUIの@StateObjectを動的パラメータで初期化する正しい方法です。modelContextonAppearで設定する設計も適切です。


126-160: 再接続オーバーレイのUI実装が適切です

進行状況、試行回数、センシング一時停止状態を明確に表示しています。ユーザーに現在の状態を適切にフィードバックする良い実装です。


889-934: 個別タグ位置表示の実装が適切です

座標変換、NLOS/LOSの視覚的区別、アニメーションが適切に実装されています。FloorMapCanvasのAPIを正しく使用しています。


1010-1068: CompactAntennaGroupViewの実装が適切です

展開/折りたたみ機能、アンテナ情報表示、デバイスリストの水平スクロールが適切に実装されています。UIの責務を明確に分離した良い設計です。


861-883: 凡例の色表示について確認してください

過去のレビューで指摘された凡例の色とNLOS/LOSの実際の表示色の不一致について、コミットでアドレスされたとマークされていますが、現在の実装では依然として静的な「アンテナ=赤」「タグ=青」となっています。意図的な設計であれば問題ありませんが、ユーザーへの説明が必要な場合は凡例を更新することを検討してください。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (2)

105-184: オブザーバーのセットアップパターンが適切です

cancellables.removeAll()で既存の購読をクリアしてから新しい購読を設定するパターンは、setupObservers()が複数回呼ばれる可能性を考慮した正しい実装です。新しいプロパティ(globalCoordinatesantennaDataMap等)への購読も一貫したパターンで追加されています。


19-63: 新しい@publishedプロパティの追加が適切です

フロアマップ表示、複数アンテナ対応、タグ表示モード、接続復旧状態のプロパティが整理されて追加されています。MARKコメントによる分類も明確です。

UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift (1)

26-29: LGTM: セッションIDの公開プロパティ

CSV出力用にactiveSessionIdを公開するのは適切な設計です。読み取り専用のcomputed propertyとして実装されています。

UWBViewerSystem/Presentation/Scenes/SensingTab/SensingManagementPage/SensingManagementView.swift (2)

49-60: LGTM: 接続回復UIの追加

再接続中のオーバーレイとConnectionRecoveryViewシートの統合は適切に実装されています。isPresentedバインディングを使用して状態管理を行っており、SwiftUIのパターンに従っています。


65-100: macOSでのProgressViewスタイリングを確認してください

Lines 74-76でiOS用のCircularProgressViewStyle(tint: .white)を適用していますが、macOSではデフォルトのスタイルが使用されます。暗い背景上で視認性に問題がないか確認してください。

ProgressView()
    .scaleEffect(1.5)
#if os(iOS)
    .progressViewStyle(CircularProgressViewStyle(tint: .white))
#elseif os(macOS)
    .colorScheme(.dark) // macOSでの視認性向上
#endif
UWBViewerSystem/Devices/NearByConnection/NearByConnectionApi.swift (1)

149-179: リトライロジックの実装は適切です

指数バックオフ(0.5秒、1秒、1.5秒)を使用したリトライ機構は適切に実装されています。リトライ前に既存の広告を停止し、エラー時にコールバックで状態を通知しています。

ただし、try? await Task.sleepでキャンセルを無視していますが、Taskがキャンセルされた場合でもリトライが継続します。意図的な場合は問題ありませんが、キャンセル対応が必要な場合はtry awaitに変更してください。

UWBViewerSystem/Presentation/Components/ConnectionRecoveryView.swift (2)

169-172: resetAll()の呼び出しが積極的すぎる可能性があります

attemptAutoReconnect内でresetAll()を呼び出していますが、これは全ての接続を切断し、全てのメッセージ履歴をクリアします(NearByConnectionApi.swift Lines 376-386参照)。

一部のデバイスのみが切断された場合、まだ接続中の他のデバイスも切断されてしまいます。特定のデバイスのみ再接続する方が適切かもしれません。


7-12: LGTM: Viewの構造と初期化

@ObservedObject@Bindingを適切に使用しており、ViewModelとの双方向バインディングパターンに従っています。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationView.swift (5)

10-20: LGTM: フロアマップID初期化と依存関係の追加

floorMapIdをプロパティとして保持し、イニシャライザで受け取る実装は適切です。SensingFlowNavigatorNavigationRouterModelの統合も正しく行われています。


66-80: 接続回復UIの一貫した実装

ConnectionRecoveryViewシートと再接続オーバーレイの実装は、SensingManagementViewと一貫しています。iOS用のpresentationDetentsも適切に設定されています。


627-681: タグナビゲーションボタンの実装

「前のタグへ」と「次のタグ位置へ」ボタンの条件付きレンダリングは適切に実装されています。エッジケース(最初のタグ、最後のタグ)も正しく処理されています。

UIの色分け(前のタグ: オレンジ、次のタグ: 青)は視覚的に明確です。


708-815: センシング詳細パネルの実装

センシング中の詳細フィードバック(経過時間、データポイント数、RMSE推定、信号品質)は優れたUX改善です。(0,0)データ検出警告は、データ品質の問題を早期に発見するのに役立ちます。


1059-1063: プレビューの更新

テスト用のfloorMapIdNavigationRouterModelの注入は適切です。ただし、プレビューでNavigationRouterModel()を直接インスタンス化していますが、他のプレビューではNavigationRouterModel.sharedを使用している場合があります。一貫性を確認してください。

UWBViewerSystem/Presentation/Scenes/SensingTab/SensingManagementPage/SensingManagementViewModel.swift (3)

18-38: 接続復旧状態の管理プロパティが適切に追加されています

@Publishedプロパティを使用したUI状態管理は、SwiftUIのViewとの双方向バインディングにおいて適切なパターンです。Based on learnings, ViewModelはPublished/StateObjectでViewと双方向バインディングする。


274-309: 再接続成功時の状態復元が適切です

センシング開始時刻の復元と一時停止状態の維持/再開ロジックが正しく実装されています。


452-461: テスト用の便利イニシャライザが適切に追加されています

DummySwiftDataRepositoryを使用したテスト用初期化は、ユニットテストやプレビューで有用です。

UWBViewerSystem/Domain/Usecase/ConnectionManagementUsecase.swift (5)

18-26: 接続状態管理プロパティが適切に追加されています

hasConnectionErrorlastDisconnectedDeviceisAutoReconnectingの追加により、UI層での接続復旧フローの制御が可能になりました。antennaPairingsは一元的なペアリング管理を実現しています。


151-165: メッセージブロードキャストの実装が適切です

#if DEBUGによる条件付きログ出力は、本番環境でのパフォーマンスに影響を与えない良いプラクティスです。


242-254: 自動接続の動作を確認してください

発見したすべての接続可能なデバイスに自動的に接続リクエストを送信しています。本番環境では意図しないデバイスへの接続を防ぐため、ユーザー確認やホワイトリスト機能の追加を検討してください。


283-300: デバイス接続時の状態管理が適切です

endpointToDeviceNameMapへのマッピング保存、エラーフラグのクリア、RealtimeDataUsecaseへの通知が正しく実装されています。


303-328: 切断時の状態管理が適切です

endpointIdからdeviceNameを解決し、エラーフラグの設定、マッピングのクリーンアップが適切に行われています。マッピングが見つからない場合のフォールバック処理も実装されています。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/PairingSettingPage/PairingSettingViewModel.swift (4)

77-98: フロアマップ情報読み込み機能が適切に追加されています

loadFloorMapInfo(floorMapId:)でフロアマップIDを設定し、関連するアンテナ情報を読み込むフローが適切に実装されています。


126-155: 非同期アンテナデータ読み込みが適切に実装されています

floorMapIdを使用したSwiftDataからの読み込みと、UserDefaultsへのフォールバックが適切に実装されています。


248-296: ペアリング処理がConnectionManagementUsecaseと適切に統合されています

ローカルのantennaPairings配列への追加と同時にConnectionManagementUsecaseへの登録を行うことで、状態の一貫性が保たれています。


349-377: ナビゲーションフローがfloorMapIdと適切に統合されています

floorMapIdを使用したナビゲーションとSensingFlowNavigatorとの連携が適切に実装されています。

UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (4)

11-28: SignalQualityDisplayが適切に設計されています

信号品質の表示用データ構造として、RSSIやLoS率などの指標と品質レベルの計算ロジックが適切に実装されています。Equatable準拠によりSwiftUIでの差分検出も効率的です。


138-167: 接続復旧状態の管理が適切に設計されています

SavedOperationState構造体により、切断時の操作状態を詳細に保存できます。タイムスタンプの追加により、古い状態の自動破棄も可能です。


248-285: キャリブレーション結果の検証ロジックが適切です

原点付近の疑わしい位置検出、RMSE閾値チェック、スケールファクターの範囲チェックにより、異常なキャリブレーション結果をユーザーに警告できます。


982-1099: センシング中のリアルタイムフィードバック収集が適切に実装されています

センシング中の接続状態チェック、信号品質の収集、疑わしいデータの検出が適切に行われています。特定のデバイスのみにセンシングコマンドを送信する設計により、正確なキャリブレーションが可能です。

Comment on lines +127 to +146
Task {
do {
// 新しいセンシングセッションを作成してSwiftDataに保存
let session = SensingSession(name: fileName, startTime: Date(), isActive: true)
try await self.swiftDataRepository.saveSensingSession(session)
self.currentSessionId = session.id

// システム活動ログも記録
let activity = SystemActivity(
activityType: "sensing",
activityDescription: "センシングセッション開始(単一デバイス): \(fileName) - デバイス: \(deviceName)"
)
try await self.swiftDataRepository.saveSystemActivity(activity)

self.logger.info("センシングセッション作成完了: \(session.id)")
} catch {
self.logger.error("センシングセッション作成エラー: \(error)")
self.sensingStatus = "セッション作成に失敗しました"
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Taskブロック内のエラー後にreturnがありません

Lines 142-145でセッション作成エラーが発生した場合、self.sensingStatusを更新していますが、外側のスコープにreturnがないため、後続の処理(コマンド送信やUI状態更新)が継続されます。

startRemoteSensing(Lines 76-80)ではエラー時にreturnで処理を中断していますが、この関数ではTask内でキャッチしても関数自体はtrueを返す可能性があります。

             } catch {
                 self.logger.error("センシングセッション作成エラー: \(error)")
                 self.sensingStatus = "セッション作成に失敗しました"
+                return
             }
         }
+
+        // Task内でエラーが発生した場合、以降の処理は実行されるため注意が必要
+        // awaitを使用して同期的に処理するか、エラーハンドリングを見直してください
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Task {
do {
// 新しいセンシングセッションを作成してSwiftDataに保存
let session = SensingSession(name: fileName, startTime: Date(), isActive: true)
try await self.swiftDataRepository.saveSensingSession(session)
self.currentSessionId = session.id
// システム活動ログも記録
let activity = SystemActivity(
activityType: "sensing",
activityDescription: "センシングセッション開始(単一デバイス): \(fileName) - デバイス: \(deviceName)"
)
try await self.swiftDataRepository.saveSystemActivity(activity)
self.logger.info("センシングセッション作成完了: \(session.id)")
} catch {
self.logger.error("センシングセッション作成エラー: \(error)")
self.sensingStatus = "セッション作成に失敗しました"
}
}
Task {
do {
// 新しいセンシングセッションを作成してSwiftDataに保存
let session = SensingSession(name: fileName, startTime: Date(), isActive: true)
try await self.swiftDataRepository.saveSensingSession(session)
self.currentSessionId = session.id
// システム活動ログも記録
let activity = SystemActivity(
activityType: "sensing",
activityDescription: "センシングセッション開始(単一デバイス): \(fileName) - デバイス: \(deviceName)"
)
try await self.swiftDataRepository.saveSystemActivity(activity)
self.logger.info("センシングセッション作成完了: \(session.id)")
} catch {
self.logger.error("センシングセッション作成エラー: \(error)")
self.sensingStatus = "セッション作成に失敗しました"
return
}
}
🤖 Prompt for AI Agents
In UWBViewerSystem/Domain/Usecase/SensingControlUsecase.swift around lines
127-146, the Task's catch block updates self.sensingStatus on error but does not
stop further execution of the Task or the surrounding function; add an early
exit from the Task after setting the error state (i.e., return from the Task
closure) so no subsequent command sending or UI updates run, and ensure the
overall startRemoteSensing flow returns/propagates a failure (e.g., return false
or propagate the error) so callers don't assume success.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (15)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (3)

143-149: 新しいプロパティがクリアされていません(PR目的と不一致)

PR目的では clearAllRealtimeData()globalCoordinates.removeAll(), antennaDataMap.removeAll(), activeAntennaIds.removeAll(), totalDataPointCount = 0 の追加が記載されていますが、コードには反映されていません。

このままでは、セッション切り替え時に以下の問題が発生します:

  • UIのデータリストは空でも、アンテナ数や総データポイント数が古い値のまま表示される
  • フロアマップ上のタグ位置やアンテナ別マップが前回セッションの情報を引きずる

以下のdiffを適用してください:

 func clearAllRealtimeData() {
     #if DEBUG
     print("🗑️ リアルタイムデータクリア")
     #endif
     self.deviceRealtimeDataList.removeAll()
     self.isReceivingRealtimeData = false
+    self.globalCoordinates.removeAll()
+    self.antennaDataMap.removeAll()
+    self.activeAntennaIds.removeAll()
+    self.totalDataPointCount = 0
     objectWillChange.send()
 }

152-159: センシング用クリアメソッドでも新しいプロパティがクリアされていません

clearRealtimeDataForSensing() でも、globalCoordinates, antennaDataMap, activeAntennaIds, totalDataPointCount がクリアされていません。センシング停止時にこれらの状態が残り、次のセンシング開始時に古いデータが表示される可能性があります。

以下のdiffを適用してください:

 func clearRealtimeDataForSensing() {
     for deviceData in self.deviceRealtimeDataList {
         deviceData.clearData()
     }
     // 統合座標履歴もクリア
     self.integratedCoordinateHistory.removeAll()
+    self.globalCoordinates.removeAll()
+    self.antennaDataMap.removeAll()
+    self.activeAntennaIds.removeAll()
+    self.totalDataPointCount = 0
     objectWillChange.send()
 }

581-584: 未使用のデッドコードを削除してください

extractTagId 関数は入力をそのまま返すだけの未完成な実装で、コード内で使用されていません。

コーディングガイドラインに基づき、以下のdiffを適用してデッドコードを削除してください:

-    /// デバイス名からタグIDを抽出
-    private func extractTagId(from deviceName: String) -> String {
-        deviceName
-    }
-

Based on learnings, デッドコードは削除する必要があります。

UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (6)

393-411: プラットフォーム固有コードの重複を解消してください

iOSとmacOSで同一のロジックが重複しています。DRY原則に違反しており、保守性を低下させます。

以下のように共通化を推奨します:

                 // フロアマップIDをRealtimeDataUsecaseに設定
                 self.realtimeDataUsecase.setFloorMapId(floorMap.id)

                 // フロアマップ画像を読み込み
-                #if canImport(UIKit)
-                    #if os(iOS)
-                        self.floorMapImage = floorMap.image
-                        if self.floorMapImage != nil {
-                            print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
-                        } else {
-                            print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
-                        }
-                    #endif
-                #endif
-
-                #if os(macOS)
-                    self.floorMapImage = floorMap.image
-                    if self.floorMapImage != nil {
-                        print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
-                    } else {
-                        print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
-                    }
-                #endif
+                self.floorMapImage = floorMap.image
+                if self.floorMapImage != nil {
+                    print("📍 フロアマップ画像読み込み成功: \(floorMap.name)")
+                } else {
+                    print("⚠️ フロアマップ画像が見つかりません: \(floorMap.name)")
+                }

                 print("📍 フロアマップ情報読み込み完了: \(floorMap.name) (ID: \(floorMap.id))")

floorMapImageプロパティが既にプラットフォーム別に定義されているため、代入とログ処理は共通化できます。


559-753: メソッドが長すぎて複雑度が高い、かつデバッグprint文を削除してください

このメソッドは194行あり、複数の責務を持っています。単一責任の原則(SRP)に違反しており、テストと保守が困難です。また、Lines 560-565, 571, 582, 598-601, 605, 610, 616, 622, 662, 670, 729-730, 742, 744, 747-749に多数のデバッグ用print文が含まれています。

以下のように小さなメソッドに分割し、デバッグprint文を削除することを推奨します:

private func exportSensingDataToCSV(fileName: String = "") async {
    guard let sessionId = sensingControlUsecase.activeSessionId,
          let sessionStartTime = self.startTime else {
        return
    }
    
    do {
        let allRealtimeData = try await loadRealtimeDataForExport(sessionId: sessionId)
        guard !allRealtimeData.isEmpty else { return }
        
        let sessionDirectory = try createExportDirectory(startTime: sessionStartTime, fileName: fileName)
        let antennaIdToNameMap = buildAntennaIdToNameMap()
        let groupedByAntenna = Dictionary(grouping: allRealtimeData) { $0.antennaId }
        
        try await exportGroupedData(
            groupedByAntenna: groupedByAntenna,
            antennaIdToNameMap: antennaIdToNameMap,
            sessionDirectory: sessionDirectory,
            fileName: fileName
        )
    } catch {
        // エラーログ(OSLogなど適切なロギング機構を使用)
    }
}

private func loadRealtimeDataForExport(sessionId: String) async throws -> [RealtimeData] {
    try await swiftDataRepository?.loadRealtimeData(for: sessionId) ?? []
}

private func createExportDirectory(startTime: Date, fileName: String) throws -> URL {
    let fileNameToUse = fileName.isEmpty ? self.currentFileName : fileName
    return try SensingDataCSVExporter.createSessionDirectory(
        startTime: startTime,
        customName: fileNameToUse
    )
}

private func buildAntennaIdToNameMap() -> [String: String] {
    var map: [String: String] = [:]
    for antennaPosition in self.allAntennaPositions {
        map[antennaPosition.antennaId] = antennaPosition.antennaName
    }
    return map
}

// 他のヘルパーメソッドも同様に抽出...

Based on learnings, デバッグ用コードは削除し、必要に応じて適切なロギング機構を使用してください。


329-360: デバッグprint文を削除してください

Lines 339, 346, 350のデバッグ用print文は、コーディングガイドラインに従って削除する必要があります。

以下のdiffを適用してください:

             self.sensingControlUsecase = SensingControlUsecase(
                 connectionUsecase: self.connectionUsecase,
                 swiftDataRepository: repository
             )
-            print("✅ SensingControlUsecaseに正しいSwiftDataRepositoryを設定しました")

             // RealtimeDataUsecaseにSwiftDataRepositoryを設定
             self.realtimeDataUsecase.updateSwiftDataRepository(repository)

             // RealtimeDataUsecaseにSensingControlUsecaseを設定(データ永続化に必要)
             self.realtimeDataUsecase.setSensingControlUsecase(self.sensingControlUsecase)
-            print("✅ RealtimeDataUsecaseにSensingControlUsecaseを設定しました")

             // ConnectionManagementUsecaseにRealtimeDataUsecaseを設定
             self.connectionUsecase.realtimeDataUsecase = self.realtimeDataUsecase
-            print("✅ ConnectionManagementUsecaseにRealtimeDataUsecaseを設定しました")

Based on learnings, デバッグ用コードは削除する必要があります。


362-368: 非推奨メソッドの扱いを明確化してください

コメントで非推奨と記載されていますが、@available属性が付いておらず、Line 358のsetupSwiftDataRepositoryから実際に呼び出されているため、非推奨の意図と実装が矛盾しています。

以下のいずれかの対応を推奨します:

オプション1: 非推奨属性を追加

+    @available(*, deprecated, message: "loadFloorMapInfo(floorMapId:)を使用してください")
     private func loadInitialData() {

オプション2: コメントを削除(実際に使用されている場合)

-    /// 初期データの読み込み(非推奨:loadFloorMapInfo(floorMapId:)を使用すること)
+    /// 初期データの読み込み
     private func loadInitialData() {

465-478: 非同期処理の待機方法に問題があります

Line 468の0.5秒固定スリープは、SwiftDataの永続化完了を保証しない不確実な実装です。永続化に0.5秒以上かかる場合、データ損失のリスクがあります。

以下のいずれかの方法で適切な非同期制御を実装してください:

オプション1: SwiftDataの永続化完了を明示的に待機

// センシングデータをCSVとしてエクスポート
Task {
    // SwiftDataRepositoryに永続化完了を確認するメソッドを追加
    await swiftDataRepository?.waitForPersistence()
    await self.exportSensingDataToCSV(fileName: savedFileName)
}

オプション2: Notification/Combineで永続化完了を通知

SwiftDataRepositoryから永続化完了のイベントを発行し、それを待機する実装に変更してください。


474-477: 未実装のコメントブロックを削除または実装してください

Lines 474-477のコメントは実装が必要な処理を示唆していますが、実際のコードがありません。意図が不明確です。

以下のいずれかの対応が必要です:

オプション1: 必要な実装を追加

             await MainActor.run {
-                // SensingControlUsecaseのcurrentSessionIdをクリア
-                // これにより次のセンシングセッションで新しいIDが使用される
+                self.sensingControlUsecase.clearSessionId()
             }

オプション2: 不要な場合はコメントごと削除

-            // CSV出力完了後にSensingControlUsecaseのセッションIDをクリア
-            await MainActor.run {
-                // SensingControlUsecaseのcurrentSessionIdをクリア
-                // これにより次のセンシングセッションで新しいIDが使用される
-            }
UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (6)

35-35: コメントの不正確性を修正してください

Line 35のコメントで「/Applications/sensing/」と記載されていますが、実際のコードではDocumentsディレクトリを使用しています。

以下のdiffを適用してください:

-    /// ディレクトリ構造: /Applications/sensing/yyyymmdd/customName_hhmmss/
+    /// ディレクトリ構造: Documents/sensing/yyyymmdd/customName_hhmmss/

65-71: デバッグprint文を削除してください

Lines 65, 71, 86-88のデバッグ用print文は、コーディングガイドラインに従って削除する必要があります。

以下のdiffを適用してください:

         // ディレクトリ名を決定(カスタム名がある場合は「hhmmss-customName」形式)
-        print("🔍 createSessionDirectory - customName: '\(customName)' (空?: \(customName.isEmpty))")
         let directoryName = if !customName.isEmpty {
             "\(timeString)-\(customName)"
         } else {
             timeString
         }
-        print("🔍 決定されたディレクトリ名: '\(directoryName)'")

         // ディレクトリパスを構築
         let sessionDirectory = documentsDirectory
             .appendingPathComponent("sensing")
             .appendingPathComponent(dateString)
             .appendingPathComponent(directoryName)

         // ディレクトリを作成
         try FileManager.default.createDirectory(
             at: sessionDirectory,
             withIntermediateDirectories: true,
             attributes: nil
         )

-        print("✅ セッションディレクトリを作成: \(sessionDirectory.path)")
-        print("📁 Documentsディレクトリ: \(documentsDirectory.path)")
-        print("📁 相対パス: sensing/\(dateString)/\(directoryName)")

         return sessionDirectory

Based on learnings, デバッグ用コードは削除する必要があります。

Also applies to: 86-88


161-200: globalCoordinates の設計がタイムシリーズと整合していません

exportGlobalCoordinateDataToCSVglobalCoordinates: [String: Point3D] を受け取り、各行で globalCoordinates[data.deviceName] を参照していますが、この辞書は「デバイスごとの最新位置」しか持っていないため、同一デバイスの全ての行に同じ座標が出力されます。

セッション全体の軌跡をCSVに出力したい場合、以下のいずれかの対応が必要です:

オプション1: パラメータ型を時系列に対応した構造に変更(例:[RealtimeData.ID: Point3D][(realtimeDataId, Point3D)])し、各行で該当するIDの座標を参照する

オプション2: エクスポーター内部で RealtimeCoordinateTransformUsecase を使用して、各 RealtimeData から行ごとにグローバル座標を再計算する

現状のままでは「サンプル数の多いセッションでも、各デバイスにつき1地点相当しか意味を持たないCSV」になってしまいます。要件を確認し、設計を見直すことを強く推奨します。


220-277: フィルタリング後データでも同様の座標とインデックスの問題があります

exportFilteredDataToCSV でも同じ globalCoordinates の問題に加えて、以下の追加の問題があります:

  1. Line 244: compactMap により一部行がスキップされる可能性があり、sortedData.enumerated()coordinates[index] の対応が崩れます
  2. Line 253: index < filteredCoordinates.count で早期 break しているため、末尾データが出力されない可能性があります

exportFilteredDataToCSV は、sortedData と1対1に対応する座標配列を構築してから移動平均を適用する必要があります(compactMap で行をドロップしないように)。


341-349: デバッグprint文を削除してください

Line 344のデバッグ用print文は、コーディングガイドラインに従って削除する必要があります。

以下のdiffを適用してください:

 private static func writeCSV(content: String, to fileURL: URL) throws -> URL {
     do {
         try content.write(to: fileURL, atomically: true, encoding: .utf8)
-        print("✅ CSVファイルを保存しました: \(fileURL.path)")
         return fileURL
     } catch {
         throw ExportError.writeFailed("ファイル書き込みに失敗: \(error.localizedDescription)")
     }
 }

Based on learnings, デバッグ用コードは削除する必要があります。


418-419: デバッグprint文を削除してください

Lines 418-419のデバッグ用print文は、コーディングガイドラインに従って削除する必要があります。

以下のdiffを適用してください:

         let filteredDataURL = try exportFilteredDataToCSV(
             realtimeDataList: realtimeDataList,
             globalCoordinates: globalCoordinates,
             processor: processor,
             directoryURL: sessionDirectory
         )

-        print("✅ 全センシングデータのエクスポート完了")
-        print("   セッションディレクトリ: \(sessionDirectory.path)")

         return (
             sessionDirectory: sessionDirectory,
             rawDataURL: rawDataURL,
             globalCoordinateURL: globalCoordinateURL,
             filteredDataURL: filteredDataURL
         )

Based on learnings, デバッグ用コードは削除する必要があります。

🧹 Nitpick comments (2)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (1)

288-305: where 句の使用を検討してください

SwiftLintが指摘しているように、for ループ内の単一の if 条件は where 句に置き換えることでコードがより簡潔になります。

以下のdiffを適用できます:

 private func getAntennaId(for deviceName: String) -> String {
     let antennaPairings = ConnectionManagementUsecase.shared.antennaPairings

     // アンテナID → デバイス名のマッピングから逆引き
-    for (antennaId, pairedDeviceName) in antennaPairings {
-        if pairedDeviceName == deviceName {
-            #if DEBUG
-                print("🔗 デバイス \(deviceName) はアンテナ \(antennaId) に紐づいています")
-            #endif
-            return antennaId
-        }
+    for (antennaId, pairedDeviceName) in antennaPairings where pairedDeviceName == deviceName {
+        #if DEBUG
+            print("🔗 デバイス \(deviceName) はアンテナ \(antennaId) に紐づいています")
+        #endif
+        return antennaId
     }

     #if DEBUG
         print("⚠️ デバイス \(deviceName) に対応するアンテナIDが見つかりません")
     #endif
     return ""
 }
UWBViewerSystem/Domain/Entity/RealtimeData.swift (1)

6-27: 列挙型の冗長な文字列値を削除できます

SwiftLintが指摘しているように、列挙型のケース名と文字列値が一致している場合、明示的な値は省略できます。

以下のdiffを適用できます:

 public enum TagDisplayMode: String, CaseIterable {
-    case individual = "individual"  // 各アンテナからの個別位置を表示
-    case integrated = "integrated"  // 統合位置(重心)のみを表示
+    case individual  // 各アンテナからの個別位置を表示
+    case integrated  // 統合位置(重心)のみを表示

     public var displayName: String {
         switch self {
         case .individual:
             return "個別表示"
         case .integrated:
             return "統合表示"
         }
     }

     public var description: String {
         switch self {
         case .individual:
             return "各アンテナからの観測位置を全て表示"
         case .integrated:
             return "NLOSを考慮した重心位置を表示"
         }
     }
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5ce0cd and 8ca782c.

📒 Files selected for processing (4)
  • UWBViewerSystem/Domain/Entity/RealtimeData.swift (4 hunks)
  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift (7 hunks)
  • UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (1 hunks)
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (6 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Domain/Entity/RealtimeData.swift
  • UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
**/*Usecase.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Usecaseのファイル名は「〜Usecase.swift」とする

Files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
🧠 Learnings (8)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Applied to files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する

Applied to files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : 不要なimport文は削除する

Applied to files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift
  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to Devices/**/*.swift : UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T14:37:07.451Z
Learnt from: harutiro
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: :0-0
Timestamp: 2025-09-23T14:37:07.451Z
Learning: UWBViewerSystemでUserDefaults変更通知のテストを行う際は、NotificationCenterの非同期処理完了を確実に待機するため、1.5秒程度の待機時間とポーリング方式の検証を使用する。これにより競合状態やタイミング問題を回避できる。

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*Usecase.swift : Usecaseのファイル名は「〜Usecase.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*Repository.swift : Repositoryのファイル名は「〜Repository.swift」とする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift
🧬 Code graph analysis (2)
UWBViewerSystem/Domain/Utils/SensingDataCSVExporter.swift (2)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
UWBViewerSystem/Domain/Utils/SensorDataProcessor.swift (1)
  • applyMovingAverageToPoints (92-112)
UWBViewerSystem/Presentation/Scenes/SensingTab/DataCollectionPage/DataCollectionViewModel.swift (3)
UWBViewerSystem/Domain/Repository/SwiftDataRepository.swift (6)
  • loadAntennaPositions (309-316)
  • loadAntennaPositions (318-353)
  • loadAntennaPositions (964-964)
  • loadAntennaPositions (965-965)
  • loadFloorMap (648-654)
  • loadFloorMap (984-984)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AntennaPositioningPage/AntennaPositioningViewModel.swift (1)
  • setFloorMapId (111-113)
UWBViewerSystem/Domain/Entity/CommonTypes.swift (1)
  • distance (645-650)
🪛 SwiftLint (0.57.0)
UWBViewerSystem/Domain/Usecase/RealtimeDataUsecase.swift

[Warning] 293-293: where clauses are preferred over a single if inside a for

(for_where)

UWBViewerSystem/Domain/Entity/RealtimeData.swift

[Warning] 7-7: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)


[Warning] 8-8: String enum values can be omitted when they are equal to the enumcase name

(redundant_string_enum_value)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (3)

776-813: タグ位置戻り機能は適切ですが、デバッグprint文を削除してください

最後に完了したタグを見つけてデータをクリアし、インデックスを戻すロジックが正しく実装されています。進行状況の更新も適切です。

ただし、Lines 784, 808のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines


825-888: キャリブレーション完了処理とUserDefaults保存が適切ですが、デバッグprint文を削除してください

全アンテナ完了時の結果保存、成功アラート表示、flowNavigatorを使った自動遷移のフローが正しく実装されています。SystemCalibrationResultのエンコードとUserDefaultsへの保存も適切です。

ただし、Lines 847, 857, 864, 886のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines


1037-1303: デバイス固有センシング機能の実装は優れていますが、デバッグprint文を削除してください

デバイス固有のセンシング実装が正しく追加されています:

  • ConnectionManagementUsecase.shared.getDeviceName(for:)を使用したデバイス名の取得
  • ターゲットデバイスのみへのセンシング開始コマンド送信
  • センシング中の接続状態監視と中断処理
  • リアルタイム信号品質データの収集
  • ターゲットデバイスからのデータのみの抽出と追加

エラーハンドリングも包括的で、ペアリング・接続状態の確認を促す明確なメッセージを提供しています。

ただし、Lines 1111, 1123, 1212, 1228, 1246-1248, 1278-1280のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

🧹 Nitpick comments (4)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (4)

359-421: 接続監視の実装は適切ですが、デバッグprint文を削除してください

Combineを使った接続エラーの監視が適切に実装されています。weak selfの使用によりメモリリークも防止されています。

ただし、Lines 370, 374, 385, 396, 401のデバッグ用print文はコーディングガイドラインに従い削除してください。必要であれば、プロジェクトの正式なロギング機構(OSLogなど)に置き換えてください。

Based on coding guidelines

適用するdiff
-            if connectionUsecase.hasConnectionError {
-                print("🔴 AutoCalibration: 初期化時に既存の接続エラーを検知")
-                self.handleConnectionError()
-            } else if !connectionUsecase.hasConnectedDevices() {
-                print("🔴 AutoCalibration: 初期化時に接続デバイスなしを検知")
-                connectionUsecase.hasConnectionError = true
+            if connectionUsecase.hasConnectionError {
+                self.handleConnectionError()
+            } else if !connectionUsecase.hasConnectedDevices() {
+                connectionUsecase.hasConnectionError = true
                 self.handleConnectionError()
             }
             .sink { [weak self] hasError in
                 guard let self else { return }
                 if hasError {
-                    print("⚠️ 接続断検出: 自動再接続を試みます")
                     self.handleConnectionError()
                 }
             }
             .sink { [weak self] deviceNames in
                 guard let self else { return }
                 Task {
-                    print("🔌 接続デバイスの変更を検出: アンテナリストを再読み込みします")
                     await self.loadAvailableAntennas()
 
                     // 再接続成功を検出
                     if !deviceNames.isEmpty && self.isAttemptingReconnect {
-                        print("✅ 再接続成功を検出")
                         await self.handleReconnectionSuccess()
                     }
                 }

423-515: 接続復旧ロジックは堅牢ですが、デバッグprint文を削除してください

エラーハンドリングと自動再接続の実装が適切です。操作状態の保存、エクスポネンシャルバックオフを用いた再接続試行、最大試行回数の制限など、堅牢な接続復旧機能が実装されています。

ただし、Lines 436, 439, 469, 490, 503, 508のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff
             self.savedOperationState = SavedOperationState(
                 wasCollecting: self.isCollecting,
                 wasCalibrating: self.isCalibrating,
                 stepAtDisconnect: self.currentStep,
                 antennaIdAtDisconnect: self.currentAntennaId,
                 tagPositionIndexAtDisconnect: self.currentTagPositionIndex,
                 sensingElapsedTimeAtDisconnect: self.sensingElapsedTime,
                 timestamp: Date()
             )
-            print("💾 操作状態を保存しました: collecting=\(self.isCollecting), calibrating=\(self.isCalibrating)")
 
             // 操作を一時停止
-            print("⚠️ データ収集/キャリブレーションを一時停止します")
             self.isCollecting = false
             self.isCalibrating = false
         for attempt in 1...self.maxAutoReconnectAttempts {
             self.reconnectAttemptCount = attempt
-            print("🔄 自動再接続試行 \(attempt)/\(self.maxAutoReconnectAttempts)")
             for waitCount in 0..<16 {
                 try? await Task.sleep(nanoseconds: 500_000_000)
 
                 if connectionUsecase.hasConnectedDevices() {
-                    print("✅ 自動再接続成功 (試行\(attempt)回目, 待機\(waitCount * 500)ms)")
                     self.isAttemptingReconnect = false
             // バックオフ:次の試行まで待機
             let backoffSeconds = attempt * 2
-            print("⏳ 再接続失敗。\(backoffSeconds)秒後に再試行...")
             try? await Task.sleep(nanoseconds: UInt64(backoffSeconds * 1_000_000_000))
         // すべての自動試行が失敗
-        print("❌ 自動再接続失敗: 最大試行回数(\(self.maxAutoReconnectAttempts))に達しました")
         self.isAttemptingReconnect = false

517-680: ペアリング復元ロジックは包括的ですが、デバッグprint文を削除してください

再接続成功時の処理、ペアリング情報の検証・復元、操作の再開など、複雑な復旧シナリオを適切にハンドリングしています。エンドポイントマッピングの復元処理も適切です。

ただし、Lines 519, 541, 547, 554, 563, 568, 575-576, 580, 586, 603, 610, 615, 624, 631, 638, 642, 653, 673の多数のデバッグ用print文はコーディングガイドラインに従い削除してください。必要な情報は、エラーメッセージとしてshowError()で適切にユーザーに提示されています。

Based on coding guidelines


909-938: フロアマップ読み込み機能が適切に実装されていますが、デバッグprint文を削除してください

指定されたIDでフロアマップ情報を読み込む機能が正しく実装されています。プラットフォーム別の画像処理も適切です。

ただし、Lines 912, 931, 933のデバッグ用print文はコーディングガイドラインに従い削除してください。

Based on coding guidelines

適用するdiff
     private func loadFloorMapInfoById(floorMapId: String) async {
         guard let repository = swiftDataRepository else {
-            print("⚠️ SwiftDataRepositoryが利用できません")
             return
         }
                 #elseif canImport(AppKit)
                     self.floorMapImage = floorMap.image
                 #endif
 
-                print("🗺️ フロアマップ読み込み完了: \(floorMap.name), 画像: \(self.floorMapImage != nil ? "あり" : "なし")")
             } else {
-                print("⚠️ フロアマップが見つかりません (ID: \(floorMapId))")
             }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ca782c and 39e4415.

📒 Files selected for processing (1)
  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (14 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.swift: SwiftFormat(swift-format)ルールに従い、make formatでコード整形を行う
デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する
不要なimport文は削除する
デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
**/*ViewModel.swift

📄 CodeRabbit inference engine (CLAUDE.md)

ViewModelのファイル名は「〜ViewModel.swift」とする

Files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🧠 Learnings (5)
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デッドコードは削除し、コメントは必要最小限に留める(自己文書化を優先)

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to **/*.swift : デバッグ用コードやコメントアウトされた古いコードなどの不要なコードは削除する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T14:37:07.451Z
Learnt from: harutiro
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: :0-0
Timestamp: 2025-09-23T14:37:07.451Z
Learning: UWBViewerSystemでUserDefaults変更通知のテストを行う際は、NotificationCenterの非同期処理完了を確実に待機するため、1.5秒程度の待機時間とポーリング方式の検証を使用する。これにより競合状態やタイミング問題を回避できる。

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Applies to Devices/**/*.swift : UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
📚 Learning: 2025-09-23T15:18:09.790Z
Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: ViewModelはPublished/StateObjectでViewと双方向バインディングする

Applied to files:

  • UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift
🔇 Additional comments (1)
UWBViewerSystem/Presentation/Scenes/FloorMapTab/AutoAntennaCalibrationPage/AutoAntennaCalibrationViewModel.swift (1)

11-28: 信号品質表示モデルの実装が適切です

SignalQualityDisplay構造体が適切に実装されています。品質レベルの判定ロジックも明確で、UI表示に必要な情報が適切に含まれています。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants