Add Yahoo Finance stock data integration

- Add StockDataSource to fetch quotes from Yahoo Finance API
- Add StockSettingsStore for persisting user's stock symbols
- Add StockSettingsView with UI to manage symbols (max 5)
- Add STOCK feed item type and ranker weight (0.3)
- Integrate stock fetch into ContextOrchestrator pipeline
- Stock cards appear in FYI bucket and sync to Glass via BLE

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-10 20:30:18 +00:00
parent c13a4f3247
commit 9528b0b57e
7 changed files with 415 additions and 1 deletions

View File

@@ -90,6 +90,7 @@ final class HeuristicRanker {
case .transit: return 0.75
case .poiNearby: return 0.6
case .info: return 0.4
case .stock: return 0.3
case .nowPlaying: return 0.25
case .currentWeather: return 0.0
case .allQuiet: return 0.0

View File

@@ -0,0 +1,48 @@
//
// StockSettingsStore.swift
// iris
//
import Foundation
import Combine
@MainActor
final class StockSettingsStore: ObservableObject {
nonisolated static let userDefaultsKey = "iris.stock.symbols"
@Published private(set) var symbols: [String] = []
private let maxSymbols = 5
init() {
loadSymbols()
}
private func loadSymbols() {
symbols = UserDefaults.standard.stringArray(forKey: Self.userDefaultsKey) ?? []
}
func saveSymbols(_ newSymbols: [String]) {
let cleaned = newSymbols
.map { $0.uppercased().trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
.prefix(maxSymbols)
symbols = Array(cleaned)
UserDefaults.standard.set(symbols, forKey: Self.userDefaultsKey)
}
@discardableResult
func addSymbol(_ symbol: String) -> Bool {
guard symbols.count < maxSymbols else { return false }
let cleaned = symbol.uppercased().trimmingCharacters(in: .whitespacesAndNewlines)
guard !cleaned.isEmpty, !symbols.contains(cleaned) else { return false }
symbols.append(cleaned)
UserDefaults.standard.set(symbols, forKey: Self.userDefaultsKey)
return true
}
func removeSymbol(_ symbol: String) {
symbols.removeAll { $0 == symbol.uppercased() }
UserDefaults.standard.set(symbols, forKey: Self.userDefaultsKey)
}
}

View File

@@ -16,5 +16,6 @@ enum FeedItemType: String, Codable, CaseIterable {
case nowPlaying = "NOW_PLAYING"
case currentWeather = "CURRENT_WEATHER"
case calendarEvent = "CALENDAR_EVENT"
case stock = "STOCK"
case allQuiet = "ALL_QUIET"
}