Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions UWBViewerSystem/Devices/UWBDataManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import Combine
import Foundation

/// UWBデータ管理(モックとして実装)
///
/// UWBデバイスとの通信を管理し、観測データの収集を行うクラスです。
/// 現在はシミュレーションデータを生成していますが、実際のUWBデバイスとの通信に置き換え可能です。
@MainActor
public class UWBDataManager: ObservableObject {
@Published public var connectionStatus: UWBConnectionStatus = .disconnected
@Published public var latestObservation: ObservationPoint?

private var activeSessions: Set<String> = []
private var simulationTimer: Timer?

public init() {}

/// データ収集を開始
/// - Parameters:
/// - antennaId: 対象アンテナID
/// - sessionId: セッションID
public func startDataCollection(for antennaId: String, sessionId: String) async throws {
self.activeSessions.insert(sessionId)
self.connectionStatus = .connected

// シミュレーション用タイマー開始
self.startSimulation(for: antennaId, sessionId: sessionId)
print("📡 UWBデータ収集開始: \(antennaId)")
}

/// データ収集を停止
/// - Parameter sessionId: セッションID
public func stopDataCollection(sessionId: String) async throws {
self.activeSessions.remove(sessionId)
if self.activeSessions.isEmpty {
self.simulationTimer?.invalidate()
self.simulationTimer = nil
}
print("📡 UWBデータ収集停止: \(sessionId)")
}

/// データ収集を一時停止
/// - Parameter sessionId: セッションID
public func pauseDataCollection(sessionId: String) async throws {
// 実装は省略
}

/// データ収集を再開
/// - Parameter sessionId: セッションID
public func resumeDataCollection(sessionId: String) async throws {
// 実装は省略
}

/// 最新の観測データを取得
/// - Parameter sessionId: セッションID
/// - Returns: 観測データの配列
public func getLatestObservations(for sessionId: String) async -> [ObservationPoint] {
// 実際の実装では、UWBデバイスから最新データを取得
[]
}

/// シミュレーションを開始
/// - Parameters:
/// - antennaId: アンテナID
/// - sessionId: セッションID
private func startSimulation(for antennaId: String, sessionId: String) {
self.simulationTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] _ in
Task { @MainActor in
self?.generateSimulatedObservation(antennaId: antennaId, sessionId: sessionId)
}
}
}

/// シミュレーション用の観測データを生成
/// - Parameters:
/// - antennaId: アンテナID
/// - sessionId: セッションID
private func generateSimulatedObservation(antennaId: String, sessionId: String) {
let observation = ObservationPoint(
antennaId: antennaId,
position: Point3D(
x: Double.random(in: -10...10),
y: Double.random(in: -10...10),
z: Double.random(in: 0...3)
),
quality: SignalQuality(
strength: Double.random(in: 0.3...1.0),
isLineOfSight: Bool.random(),
confidenceLevel: Double.random(in: 0.5...1.0),
errorEstimate: Double.random(in: 0.1...2.0)
),
distance: Double.random(in: 1...20),
rssi: Double.random(in: -80...(-30)),
sessionId: sessionId
)

self.latestObservation = observation
}
}
Comment on lines +1 to +99
Copy link

@coderabbitai coderabbitai bot Nov 3, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

ファイルの配置場所を見直してください

UWBデバイスとの通信・制御ロジックは、プロジェクトのLearningsによればDevices/**/*.swift配下に配置すべきです。現在Domain/Utils/に配置されていますが、これはUWBデバイス固有の通信管理クラスであるため、Devices/配下への移動を推奨します。

Based on learnings("UWBデバイス関連の通信・制御ロジックはDevices/配下に配置する")。

推奨配置先: UWBViewerSystem/Devices/UWBDataManager.swift

また、モック実装として良好ですが、以下の空実装メソッドに実装予定の旨をコメントで明記することを推奨します:

 /// データ収集を一時停止
 /// - Parameter sessionId: セッションID
 public func pauseDataCollection(sessionId: String) async throws {
-    // 実装は省略
+    // TODO: 実デバイス実装時に一時停止ロジックを追加
+    print("⏸️ データ収集一時停止: \(sessionId)")
 }
 
 /// データ収集を再開
 /// - Parameter sessionId: セッションID
 public func resumeDataCollection(sessionId: String) async throws {
-    // 実装は省略
+    // TODO: 実デバイス実装時に再開ロジックを追加
+    print("▶️ データ収集再開: \(sessionId)")
 }
 
 /// 最新の観測データを取得
 /// - Parameter sessionId: セッションID
 /// - Returns: 観測データの配列
 public func getLatestObservations(for sessionId: String) async -> [ObservationPoint] {
-    // 実際の実装では、UWBデバイスから最新データを取得
+    // TODO: 実デバイス実装時にバッファから最新データを取得
     []
 }
📝 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
import Combine
import Foundation
/// UWBデータ管理(モックとして実装)
///
/// UWBデバイスとの通信を管理し、観測データの収集を行うクラスです。
/// 現在はシミュレーションデータを生成していますが、実際のUWBデバイスとの通信に置き換え可能です。
@MainActor
public class UWBDataManager: ObservableObject {
@Published public var connectionStatus: UWBConnectionStatus = .disconnected
@Published public var latestObservation: ObservationPoint?
private var activeSessions: Set<String> = []
private var simulationTimer: Timer?
public init() {}
/// データ収集を開始
/// - Parameters:
/// - antennaId: 対象アンテナID
/// - sessionId: セッションID
public func startDataCollection(for antennaId: String, sessionId: String) async throws {
self.activeSessions.insert(sessionId)
self.connectionStatus = .connected
// シミュレーション用タイマー開始
self.startSimulation(for: antennaId, sessionId: sessionId)
print("📡 UWBデータ収集開始: \(antennaId)")
}
/// データ収集を停止
/// - Parameter sessionId: セッションID
public func stopDataCollection(sessionId: String) async throws {
self.activeSessions.remove(sessionId)
if self.activeSessions.isEmpty {
self.simulationTimer?.invalidate()
self.simulationTimer = nil
}
print("📡 UWBデータ収集停止: \(sessionId)")
}
/// データ収集を一時停止
/// - Parameter sessionId: セッションID
public func pauseDataCollection(sessionId: String) async throws {
// 実装は省略
}
/// データ収集を再開
/// - Parameter sessionId: セッションID
public func resumeDataCollection(sessionId: String) async throws {
// 実装は省略
}
/// 最新の観測データを取得
/// - Parameter sessionId: セッションID
/// - Returns: 観測データの配列
public func getLatestObservations(for sessionId: String) async -> [ObservationPoint] {
// 実際の実装では、UWBデバイスから最新データを取得
[]
}
/// シミュレーションを開始
/// - Parameters:
/// - antennaId: アンテナID
/// - sessionId: セッションID
private func startSimulation(for antennaId: String, sessionId: String) {
self.simulationTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] _ in
Task { @MainActor in
self?.generateSimulatedObservation(antennaId: antennaId, sessionId: sessionId)
}
}
}
/// シミュレーション用の観測データを生成
/// - Parameters:
/// - antennaId: アンテナID
/// - sessionId: セッションID
private func generateSimulatedObservation(antennaId: String, sessionId: String) {
let observation = ObservationPoint(
antennaId: antennaId,
position: Point3D(
x: Double.random(in: -10...10),
y: Double.random(in: -10...10),
z: Double.random(in: 0...3)
),
quality: SignalQuality(
strength: Double.random(in: 0.3...1.0),
isLineOfSight: Bool.random(),
confidenceLevel: Double.random(in: 0.5...1.0),
errorEstimate: Double.random(in: 0.1...2.0)
),
distance: Double.random(in: 1...20),
rssi: Double.random(in: -80...(-30)),
sessionId: sessionId
)
self.latestObservation = observation
}
}
import Combine
import Foundation
/// UWBデータ管理(モックとして実装)
///
/// UWBデバイスとの通信を管理し、観測データの収集を行うクラスです。
/// 現在はシミュレーションデータを生成していますが、実際のUWBデバイスとの通信に置き換え可能です。
@MainActor
public class UWBDataManager: ObservableObject {
@Published public var connectionStatus: UWBConnectionStatus = .disconnected
@Published public var latestObservation: ObservationPoint?
private var activeSessions: Set<String> = []
private var simulationTimer: Timer?
public init() {}
/// データ収集を開始
/// - Parameters:
/// - antennaId: 対象アンテナID
/// - sessionId: セッションID
public func startDataCollection(for antennaId: String, sessionId: String) async throws {
self.activeSessions.insert(sessionId)
self.connectionStatus = .connected
// シミュレーション用タイマー開始
self.startSimulation(for: antennaId, sessionId: sessionId)
print("📡 UWBデータ収集開始: \(antennaId)")
}
/// データ収集を停止
/// - Parameter sessionId: セッションID
public func stopDataCollection(sessionId: String) async throws {
self.activeSessions.remove(sessionId)
if self.activeSessions.isEmpty {
self.simulationTimer?.invalidate()
self.simulationTimer = nil
}
print("📡 UWBデータ収集停止: \(sessionId)")
}
/// データ収集を一時停止
/// - Parameter sessionId: セッションID
public func pauseDataCollection(sessionId: String) async throws {
// TODO: 実デバイス実装時に一時停止ロジックを追加
print("⏸️ データ収集一時停止: \(sessionId)")
}
/// データ収集を再開
/// - Parameter sessionId: セッションID
public func resumeDataCollection(sessionId: String) async throws {
// TODO: 実デバイス実装時に再開ロジックを追加
print("▶️ データ収集再開: \(sessionId)")
}
/// 最新の観測データを取得
/// - Parameter sessionId: セッションID
/// - Returns: 観測データの配列
public func getLatestObservations(for sessionId: String) async -> [ObservationPoint] {
// TODO: 実デバイス実装時にバッファから最新データを取得
[]
}
/// シミュレーションを開始
/// - Parameters:
/// - antennaId: アンテナID
/// - sessionId: セッションID
private func startSimulation(for antennaId: String, sessionId: String) {
self.simulationTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { [weak self] _ in
Task { @MainActor in
self?.generateSimulatedObservation(antennaId: antennaId, sessionId: sessionId)
}
}
}
/// シミュレーション用の観測データを生成
/// - Parameters:
/// - antennaId: アンテナID
/// - sessionId: セッションID
private func generateSimulatedObservation(antennaId: String, sessionId: String) {
let observation = ObservationPoint(
antennaId: antennaId,
position: Point3D(
x: Double.random(in: -10...10),
y: Double.random(in: -10...10),
z: Double.random(in: 0...3)
),
quality: SignalQuality(
strength: Double.random(in: 0.3...1.0),
isLineOfSight: Bool.random(),
confidenceLevel: Double.random(in: 0.5...1.0),
errorEstimate: Double.random(in: 0.1...2.0)
),
distance: Double.random(in: 1...20),
rssi: Double.random(in: -80...(-30)),
sessionId: sessionId
)
self.latestObservation = observation
}
}
🤖 Prompt for AI Agents
In UWBViewerSystem/Domain/Utils/UWBDataManager.swift lines 1-99: the UWB device
communication/management class belongs under Devices/ per project conventions,
so move the file to UWBViewerSystem/Devices/UWBDataManager.swift (update any
import paths or module references accordingly); additionally, add brief TODO
comments inside the currently empty methods pauseDataCollection(sessionId:),
resumeDataCollection(sessionId:) and getLatestObservations(for:) indicating they
are intentionally left unimplemented for the mock and will be implemented when
integrating real UWB device logic (e.g., "// TODO: implement pause behavior when
replacing mock with real device logic").

Copy link
Member Author

Choose a reason for hiding this comment

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

この内容は対応が終わっていますか?

Copy link

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the current content of the file to verify implementation status
cat UWBViewerSystem/Devices/UWBDataManager.swift | grep -A 3 "pauseDataCollection\|resumeDataCollection\|getLatestObservations"

Length of output: 339


@harutiro さん、対応状況を確認しました:

✅ 完了した項目:

  • ファイルの配置場所:Domain/Utils/UWBDataManager.swiftDevices/UWBDataManager.swift へ移動済み

⏳ 未完了の項目:

  • TODOコメントの追加:以下のメソッドに、より具体的なTODOコメント(実装予定の旨を明記)を追加する提案がまだ反映されていません
    • pauseDataCollection - 現在は // 実装は省略 のまま
    • resumeDataCollection - 現在は // 実装は省略 のまま
    • getLatestObservations - 現在は // 実際の実装では、UWBデバイスから最新データを取得 のまま

ファイル配置の変更は完了していますが、コメントの改善提案はまだ対応されていない状態です。このコメント改善は必須ではありませんが、将来の実装者にとってより明確な指針となります。

このまま進めるか、コメントも更新されますか?


🧠 Learnings used
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/配下に配置する

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

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 Domain/**/*.swift : Domain層のコード(Entity/Repository/Usecase/DataModel/Utils)はDomain/配下に配置する

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」とする

Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: PRには関連Issue(Closes/Fixes/Relates to)を設定し、Issueのラベルを継承し、レビュー情報を記載する

Learnt from: CR
Repo: kajiLabTeam/UWBViewerSystem PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-23T15:18:09.790Z
Learning: Claude CodeでのPRではコミットメッセージに「🤖 Generated with Claude Code」を含め、生成コードに説明コメントを付し、レビュー時の注意点を明記する

60 changes: 60 additions & 0 deletions UWBViewerSystem/Domain/Entity/DataQualityTypes.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import Foundation

// MARK: - UWB Connection Status

/// UWB接続状態
public enum UWBConnectionStatus: Equatable {
case disconnected
case connecting
case connected
case error(String)

public var displayText: String {
switch self {
case .disconnected:
return "未接続"
case .connecting:
return "接続中"
case .connected:
return "接続済み"
case .error(let message):
return "エラー: \(message)"
}
}
}

// MARK: - Data Quality Evaluation

/// データ品質評価結果
public struct DataQualityEvaluation {
public let isAcceptable: Bool
public let qualityScore: Double
public let issues: [String]
public let recommendations: [String]

public init(isAcceptable: Bool, qualityScore: Double, issues: [String], recommendations: [String]) {
self.isAcceptable = isAcceptable
self.qualityScore = qualityScore
self.issues = issues
self.recommendations = recommendations
}
}

// MARK: - NLoS Detection Result

/// nLoS検出結果
public struct NLoSDetectionResult {
public let isNLoSDetected: Bool
public let lineOfSightPercentage: Double
public let averageSignalStrength: Double
public let recommendation: String

public init(
isNLoSDetected: Bool, lineOfSightPercentage: Double, averageSignalStrength: Double, recommendation: String
) {
self.isNLoSDetected = isNLoSDetected
self.lineOfSightPercentage = lineOfSightPercentage
self.averageSignalStrength = averageSignalStrength
self.recommendation = recommendation
}
}
150 changes: 150 additions & 0 deletions UWBViewerSystem/Domain/Usecase/DataQualityUsecase.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import Foundation

/// データ品質管理のビジネスロジック実装
///
/// このUseCaseは、UWB観測データの品質評価と監視を担当します。
/// 単一責任原則に基づき、データ品質に関する処理のみを行います。
///
/// ## 主要機能
/// - **品質評価**: 個別の観測データの品質を評価
/// - **NLoS検出**: 見通し線なし状態の検出
/// - **品質統計**: セッション全体の品質統計の取得
/// - **データフィルタリング**: 品質に基づいたデータフィルタリング
///
/// ## 使用例
/// ```swift
/// let usecase = DataQualityUsecase()
///
/// // データ品質評価
/// let evaluation = usecase.evaluateDataQuality(observation)
///
/// // NLoS検出
/// let nlosResult = usecase.detectNonLineOfSight(observations)
/// ```
public class DataQualityUsecase {

// MARK: - Private Properties

/// データ品質監視インスタンス
private let qualityMonitor: DataQualityMonitor

// MARK: - Initialization

/// DataQualityUsecaseのイニシャライザ
/// - Parameter qualityMonitor: データ品質監視インスタンス(依存性注入可能)
public init(qualityMonitor: DataQualityMonitor = DataQualityMonitor()) {
self.qualityMonitor = qualityMonitor
}

// MARK: - Data Quality Evaluation

/// リアルタイム品質チェック
/// - Parameter observation: 観測データ点
/// - Returns: 品質評価結果
public func evaluateDataQuality(_ observation: ObservationPoint) -> DataQualityEvaluation {
self.qualityMonitor.evaluate(observation)
}

/// nLoS(見通し線なし)状態の検出
/// - Parameter observations: 観測データ配列
/// - Returns: nLoS検出結果
public func detectNonLineOfSight(_ observations: [ObservationPoint]) -> NLoSDetectionResult {
self.qualityMonitor.detectNLoS(observations)
}

// MARK: - Data Filtering

/// 観測データを品質に基づいてフィルタリング
/// - Parameters:
/// - observations: フィルタリング対象の観測データ配列
/// - qualityThreshold: 品質閾値(0.0-1.0)
/// - timeRange: 時間範囲(オプション)
/// - Returns: フィルタリングされた観測データ
public func filterObservations(
_ observations: [ObservationPoint],
qualityThreshold: Double = 0.5,
timeRange: DateInterval? = nil
) -> [ObservationPoint] {
observations.filter { observation in
// 品質フィルタ
if observation.quality.strength < qualityThreshold {
return false
}

// 時間範囲フィルタ
if let timeRange {
return timeRange.contains(observation.timestamp)
}

return true
}
}

// MARK: - Quality Statistics

/// セッションの品質統計を計算
/// - Parameter observations: 観測データ配列
/// - Returns: 品質統計
public func calculateQualityStatistics(_ observations: [ObservationPoint]) -> ObservationQualityStatistics {
guard !observations.isEmpty else {
return ObservationQualityStatistics(
totalPoints: 0,
validPoints: 0,
averageQuality: 0.0,
lineOfSightPercentage: 0.0,
averageErrorEstimate: 0.0
)
}

let totalPoints = observations.count
let validPoints = observations.filter { observation in
let evaluation = self.evaluateDataQuality(observation)
return evaluation.isAcceptable
}.count

let averageQuality = observations.map { $0.quality.strength }.reduce(0, +) / Double(totalPoints)
let averageErrorEstimate = observations.map { $0.quality.errorEstimate }.reduce(0, +) / Double(totalPoints)

let losCount = observations.filter { $0.quality.isLineOfSight }.count
let losPercentage = Double(losCount) / Double(totalPoints) * 100.0

return ObservationQualityStatistics(
totalPoints: totalPoints,
validPoints: validPoints,
averageQuality: averageQuality,
lineOfSightPercentage: losPercentage,
averageErrorEstimate: averageErrorEstimate
)
}

// MARK: - Batch Evaluation

/// 複数の観測データを一括で品質評価
/// - Parameter observations: 観測データ配列
/// - Returns: 各観測データの品質評価結果の配列
public func evaluateBatch(_ observations: [ObservationPoint]) -> [(ObservationPoint, DataQualityEvaluation)] {
observations.map { observation in
(observation, self.evaluateDataQuality(observation))
}
}

/// 低品質データのみを抽出
/// - Parameter observations: 観測データ配列
/// - Returns: 低品質と判定された観測データの配列
public func extractLowQualityData(_ observations: [ObservationPoint]) -> [ObservationPoint] {
observations.filter { observation in
let evaluation = self.evaluateDataQuality(observation)
return !evaluation.isAcceptable
}
}

/// 高品質データのみを抽出
/// - Parameter observations: 観測データ配列
/// - Returns: 高品質と判定された観測データの配列
public func extractHighQualityData(_ observations: [ObservationPoint]) -> [ObservationPoint] {
observations.filter { observation in
let evaluation = self.evaluateDataQuality(observation)
return evaluation.isAcceptable
}
}
}
Loading
Loading