-
Notifications
You must be signed in to change notification settings - Fork 0
refactor: ObservationDataUsecaseを分割して責務を明確化 #33 #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
8b3cd3f
refactor: ObservationDataUsecaseを分割して責務を明確化
harutiro a864340
Merge branch 'main' into feature/issue-33-split-observation-usecase
harutiro 1aeaf8c
refactor: Remove BaseViewModel.swift references from project configur…
harutiro 92d4db8
feat: Implement UWBDataManager for managing UWB device communication
harutiro File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
150
UWBViewerSystem/Domain/Usecase/DataQualityUsecase.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
この内容は対応が終わっていますか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Length of output: 339
@harutiro さん、対応状況を確認しました:
✅ 完了した項目:
Domain/Utils/UWBDataManager.swift→Devices/UWBDataManager.swiftへ移動済み⏳ 未完了の項目:
pauseDataCollection- 現在は// 実装は省略のままresumeDataCollection- 現在は// 実装は省略のままgetLatestObservations- 現在は// 実際の実装では、UWBデバイスから最新データを取得のままファイル配置の変更は完了していますが、コメントの改善提案はまだ対応されていない状態です。このコメント改善は必須ではありませんが、将来の実装者にとってより明確な指針となります。
このまま進めるか、コメントも更新されますか?
🧠 Learnings used