@devo-bmad-custom/agent-orchestration 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/.agents/skills/tmux-commands/SKILL.md +353 -0
- package/src/bmm/data/project-context-template.md +26 -26
- package/src/bmm/teams/default-party.csv +20 -20
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/domain-complexity.csv +14 -14
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/prd-purpose.md +197 -197
- package/src/bmm/workflows/2-plan-workflows/create-prd/data/project-types.csv +10 -10
- package/src/bmm/workflows/2-plan-workflows/create-prd/templates/prd-template.md +10 -10
- package/src/bmm/workflows/3-solutioning/create-architecture/data/domain-complexity.csv +12 -12
- package/src/bmm/workflows/4-implementation/code-review/instructions.xml +226 -226
- package/src/bmm/workflows/4-implementation/correct-course/checklist.md +288 -288
- package/src/bmm/workflows/4-implementation/correct-course/instructions.md +207 -207
- package/src/bmm/workflows/4-implementation/retrospective/instructions.md +1444 -1444
- package/src/bmm/workflows/4-implementation/sprint-planning/sprint-status-template.yaml +55 -55
- package/src/bmm/workflows/4-implementation/sprint-status/instructions.md +230 -230
- package/src/bmm/workflows/bmad-quick-flow/quick-spec/tech-spec-template.md +74 -74
- package/src/bmm/workflows/document-project/instructions.md +130 -130
- package/src/bmm/workflows/document-project/templates/project-scan-report-schema.json +160 -160
- package/src/bmm/workflows/document-project/workflows/deep-dive-instructions.md +298 -298
- package/src/bmm/workflows/document-project/workflows/deep-dive.yaml +31 -31
- package/src/bmm/workflows/document-project/workflows/full-scan-instructions.md +1106 -1106
- package/src/bmm/workflows/document-project/workflows/full-scan.yaml +31 -31
- package/src/bmm/workflows/qa-generate-e2e-tests/checklist.md +33 -33
- package/src/bmm/workflows/qa-generate-e2e-tests/instructions.md +110 -110
- package/src/core/agents/bmad-master.md +56 -56
- package/src/core/workflows/party-mode/steps/step-02-discussion-orchestration.md +187 -187
- package/src/core/workflows/party-mode/steps/step-03-graceful-exit.md +168 -168
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/SKILL.md +0 -475
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/vision-requests.md +0 -736
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/vision-framework/references/visionkit-scanner.md +0 -738
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/SKILL.md +0 -410
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/weatherkit/references/weatherkit-patterns.md +0 -567
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/SKILL.md +0 -497
- package/src/.agents/skills/ui-ux-pro-custom/data/swift-ios-skills/widgetkit/references/widgetkit-advanced.md +0 -871
- package/src/.agents/skills/ui-ux-pro-custom/data/typography.csv +0 -58
- package/src/.agents/skills/ui-ux-pro-custom/data/ui-reasoning.csv +0 -101
- package/src/.agents/skills/ui-ux-pro-custom/data/ux-guidelines.csv +0 -100
- package/src/.agents/skills/ui-ux-pro-custom/data/web-interface.csv +0 -31
- package/src/.agents/skills/ui-ux-pro-custom/scripts/core.py +0 -253
- package/src/.agents/skills/ui-ux-pro-custom/scripts/design_system.py +0 -1067
- package/src/.agents/skills/ui-ux-pro-custom/scripts/search.py +0 -114
- package/src/.agents/skills/ux-audit/SKILL.md +0 -151
- package/src/.agents/skills/websocket-engineer/SKILL.md +0 -168
- package/src/.agents/skills/websocket-engineer/references/alternatives.md +0 -391
- package/src/.agents/skills/websocket-engineer/references/patterns.md +0 -400
- package/src/.agents/skills/websocket-engineer/references/protocol.md +0 -195
- package/src/.agents/skills/websocket-engineer/references/scaling.md +0 -333
- package/src/.agents/skills/websocket-engineer/references/security.md +0 -474
- package/src/.agents/skills/writing-skills/SKILL.md +0 -655
- package/src/.agents/skills/writing-skills/anthropic-best-practices.md +0 -1150
- package/src/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +0 -189
- package/src/.agents/skills/writing-skills/graphviz-conventions.dot +0 -172
- package/src/.agents/skills/writing-skills/persuasion-principles.md +0 -187
- package/src/.agents/skills/writing-skills/render-graphs.js +0 -168
- package/src/.agents/skills/writing-skills/testing-skills-with-subagents.md +0 -384
- package/src/.claude/commands/bmad-track-compact.md +0 -19
- package/src/.claude/commands/bmad-track-extended.md +0 -19
- package/src/.claude/commands/bmad-track-large.md +0 -19
- package/src/.claude/commands/bmad-track-medium.md +0 -19
- package/src/.claude/commands/bmad-track-nano.md +0 -19
- package/src/.claude/commands/bmad-track-rv.md +0 -18
- package/src/.claude/commands/bmad-track-small.md +0 -19
- package/src/.claude/commands/master-orchestrator.md +0 -15
- package/src/_memory/master-orchestrator-sidecar/docs-index.md +0 -3
- package/src/_memory/master-orchestrator-sidecar/instructions.md +0 -2616
- package/src/_memory/master-orchestrator-sidecar/memories.md +0 -8
- package/src/_memory/master-orchestrator-sidecar/session-state.md +0 -15
- package/src/_memory/master-orchestrator-sidecar/triage-history.md +0 -3
- package/src/_memory/master-orchestrator-sidecar/workflows-overview.html +0 -1230
- package/src/core/agents/master-orchestrator.md +0 -54
- package/src/docs/dev/tmux/actions_popup.py +0 -291
- package/src/docs/dev/tmux/actions_popup.sh +0 -110
- package/src/docs/dev/tmux/claude_usage.sh +0 -15
- package/src/docs/dev/tmux/colors.conf +0 -26
- package/src/docs/dev/tmux/cpu_usage.sh +0 -7
- package/src/docs/dev/tmux/dispatch.sh +0 -10
- package/src/docs/dev/tmux/float_init.sh +0 -13
- package/src/docs/dev/tmux/float_term.sh +0 -23
- package/src/docs/dev/tmux/open_clip.sh +0 -14
- package/src/docs/dev/tmux/paste_clipboard.sh +0 -13
- package/src/docs/dev/tmux/paste_image_wrapper.sh +0 -94
- package/src/docs/dev/tmux/ram_usage.sh +0 -3
- package/src/docs/dev/tmux/title_sync.sh +0 -54
- package/src/docs/dev/tmux/tmux-setup.md +0 -867
- package/src/docs/dev/tmux/tmux.conf +0 -127
- package/src/docs/dev/tmux/xclip +0 -18
|
@@ -1,567 +0,0 @@
|
|
|
1
|
-
# WeatherKit Extended Patterns
|
|
2
|
-
|
|
3
|
-
Overflow reference for the `weatherkit` skill. Contains advanced patterns that exceed the main skill file's scope.
|
|
4
|
-
|
|
5
|
-
## Contents
|
|
6
|
-
|
|
7
|
-
- [WeatherKit SwiftUI Integration](#weatherkit-swiftui-integration)
|
|
8
|
-
- [Charts Integration](#charts-integration)
|
|
9
|
-
- [Historical Weather Statistics](#historical-weather-statistics)
|
|
10
|
-
- [Weather Condition Mapping](#weather-condition-mapping)
|
|
11
|
-
- [Caching Strategy](#caching-strategy)
|
|
12
|
-
- [Location-Based Weather](#location-based-weather)
|
|
13
|
-
|
|
14
|
-
## WeatherKit SwiftUI Integration
|
|
15
|
-
|
|
16
|
-
### Weather Manager with @Observable
|
|
17
|
-
|
|
18
|
-
```swift
|
|
19
|
-
import WeatherKit
|
|
20
|
-
import CoreLocation
|
|
21
|
-
|
|
22
|
-
@Observable
|
|
23
|
-
@MainActor
|
|
24
|
-
final class WeatherManager {
|
|
25
|
-
private let service = WeatherService.shared
|
|
26
|
-
|
|
27
|
-
var current: CurrentWeather?
|
|
28
|
-
var hourlyForecast: Forecast<HourWeather>?
|
|
29
|
-
var dailyForecast: Forecast<DayWeather>?
|
|
30
|
-
var alerts: [WeatherAlert]?
|
|
31
|
-
var attribution: WeatherAttribution?
|
|
32
|
-
var isLoading = false
|
|
33
|
-
var error: Error?
|
|
34
|
-
|
|
35
|
-
func fetchWeather(for location: CLLocation) async {
|
|
36
|
-
isLoading = true
|
|
37
|
-
error = nil
|
|
38
|
-
|
|
39
|
-
do {
|
|
40
|
-
let (current, hourly, daily, alerts) = try await service.weather(
|
|
41
|
-
for: location,
|
|
42
|
-
including: .current, .hourly, .daily, .alerts
|
|
43
|
-
)
|
|
44
|
-
self.current = current
|
|
45
|
-
self.hourlyForecast = hourly
|
|
46
|
-
self.dailyForecast = daily
|
|
47
|
-
self.alerts = alerts
|
|
48
|
-
self.attribution = try await service.attribution
|
|
49
|
-
} catch {
|
|
50
|
-
self.error = error
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
isLoading = false
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Weather Dashboard View
|
|
59
|
-
|
|
60
|
-
```swift
|
|
61
|
-
import SwiftUI
|
|
62
|
-
import WeatherKit
|
|
63
|
-
|
|
64
|
-
struct WeatherDashboardView: View {
|
|
65
|
-
@Environment(WeatherManager.self) private var manager
|
|
66
|
-
let location: CLLocation
|
|
67
|
-
|
|
68
|
-
var body: some View {
|
|
69
|
-
NavigationStack {
|
|
70
|
-
ScrollView {
|
|
71
|
-
VStack(spacing: 20) {
|
|
72
|
-
if manager.isLoading {
|
|
73
|
-
ProgressView("Loading weather...")
|
|
74
|
-
} else if let current = manager.current {
|
|
75
|
-
currentConditionsCard(current)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if let hourly = manager.hourlyForecast {
|
|
79
|
-
hourlyForecastSection(hourly)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if let daily = manager.dailyForecast {
|
|
83
|
-
dailyForecastSection(daily)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if let alerts = manager.alerts, !alerts.isEmpty {
|
|
87
|
-
alertsSection(alerts)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if let attribution = manager.attribution {
|
|
91
|
-
WeatherAttributionView(attribution: attribution)
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
.padding()
|
|
95
|
-
}
|
|
96
|
-
.navigationTitle("Weather")
|
|
97
|
-
.task {
|
|
98
|
-
await manager.fetchWeather(for: location)
|
|
99
|
-
}
|
|
100
|
-
.refreshable {
|
|
101
|
-
await manager.fetchWeather(for: location)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
private func currentConditionsCard(_ current: CurrentWeather) -> some View {
|
|
107
|
-
VStack(spacing: 12) {
|
|
108
|
-
Image(systemName: current.symbolName)
|
|
109
|
-
.font(.system(size: 60))
|
|
110
|
-
.symbolRenderingMode(.multicolor)
|
|
111
|
-
|
|
112
|
-
Text(current.temperature.formatted())
|
|
113
|
-
.font(.system(size: 48, weight: .thin))
|
|
114
|
-
|
|
115
|
-
Text(current.condition.description)
|
|
116
|
-
.font(.title3)
|
|
117
|
-
.foregroundStyle(.secondary)
|
|
118
|
-
|
|
119
|
-
HStack(spacing: 24) {
|
|
120
|
-
Label(
|
|
121
|
-
"Humidity \(current.humidity.formatted(.percent))",
|
|
122
|
-
systemImage: "humidity"
|
|
123
|
-
)
|
|
124
|
-
Label(
|
|
125
|
-
"Wind \(current.wind.speed.formatted())",
|
|
126
|
-
systemImage: "wind"
|
|
127
|
-
)
|
|
128
|
-
Label(
|
|
129
|
-
"UV \(current.uvIndex.value)",
|
|
130
|
-
systemImage: "sun.max"
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
.font(.caption)
|
|
134
|
-
}
|
|
135
|
-
.padding()
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
private func hourlyForecastSection(_ forecast: Forecast<HourWeather>) -> some View {
|
|
139
|
-
VStack(alignment: .leading) {
|
|
140
|
-
Text("Hourly Forecast")
|
|
141
|
-
.font(.headline)
|
|
142
|
-
|
|
143
|
-
ScrollView(.horizontal, showsIndicators: false) {
|
|
144
|
-
HStack(spacing: 16) {
|
|
145
|
-
ForEach(Array(forecast.prefix(12)), id: \.date) { hour in
|
|
146
|
-
VStack(spacing: 8) {
|
|
147
|
-
Text(hour.date, format: .dateTime.hour())
|
|
148
|
-
.font(.caption)
|
|
149
|
-
Image(systemName: hour.symbolName)
|
|
150
|
-
.symbolRenderingMode(.multicolor)
|
|
151
|
-
Text(hour.temperature.formatted())
|
|
152
|
-
.font(.subheadline)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
private func dailyForecastSection(_ forecast: Forecast<DayWeather>) -> some View {
|
|
161
|
-
VStack(alignment: .leading) {
|
|
162
|
-
Text("10-Day Forecast")
|
|
163
|
-
.font(.headline)
|
|
164
|
-
|
|
165
|
-
ForEach(Array(forecast), id: \.date) { day in
|
|
166
|
-
HStack {
|
|
167
|
-
Text(day.date, format: .dateTime.weekday(.abbreviated))
|
|
168
|
-
.frame(width: 40, alignment: .leading)
|
|
169
|
-
|
|
170
|
-
Image(systemName: day.symbolName)
|
|
171
|
-
.symbolRenderingMode(.multicolor)
|
|
172
|
-
.frame(width: 30)
|
|
173
|
-
|
|
174
|
-
Text(day.lowTemperature.formatted())
|
|
175
|
-
.foregroundStyle(.secondary)
|
|
176
|
-
.frame(width: 50, alignment: .trailing)
|
|
177
|
-
|
|
178
|
-
temperatureBar(low: day.lowTemperature, high: day.highTemperature)
|
|
179
|
-
|
|
180
|
-
Text(day.highTemperature.formatted())
|
|
181
|
-
.frame(width: 50)
|
|
182
|
-
}
|
|
183
|
-
.font(.subheadline)
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
private func temperatureBar(
|
|
189
|
-
low: Measurement<UnitTemperature>,
|
|
190
|
-
high: Measurement<UnitTemperature>
|
|
191
|
-
) -> some View {
|
|
192
|
-
Capsule()
|
|
193
|
-
.fill(.linearGradient(
|
|
194
|
-
colors: [.blue, .orange],
|
|
195
|
-
startPoint: .leading,
|
|
196
|
-
endPoint: .trailing
|
|
197
|
-
))
|
|
198
|
-
.frame(height: 4)
|
|
199
|
-
.containerRelativeFrame(.horizontal) { length, _ in
|
|
200
|
-
length * 0.3
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
private func alertsSection(_ alerts: [WeatherAlert]) -> some View {
|
|
205
|
-
VStack(alignment: .leading) {
|
|
206
|
-
Text("Weather Alerts")
|
|
207
|
-
.font(.headline)
|
|
208
|
-
|
|
209
|
-
ForEach(alerts, id: \.detailsURL) { alert in
|
|
210
|
-
HStack {
|
|
211
|
-
Image(systemName: "exclamationmark.triangle.fill")
|
|
212
|
-
.foregroundStyle(alert.severity == .extreme ? .red : .orange)
|
|
213
|
-
VStack(alignment: .leading) {
|
|
214
|
-
Text(alert.summary)
|
|
215
|
-
.font(.subheadline)
|
|
216
|
-
Text(alert.region)
|
|
217
|
-
.font(.caption)
|
|
218
|
-
.foregroundStyle(.secondary)
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
.padding()
|
|
222
|
-
.background(.yellow.opacity(0.1))
|
|
223
|
-
.clipShape(RoundedRectangle(cornerRadius: 8))
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
### Attribution View
|
|
231
|
-
|
|
232
|
-
```swift
|
|
233
|
-
struct WeatherAttributionView: View {
|
|
234
|
-
let attribution: WeatherAttribution
|
|
235
|
-
@Environment(\.colorScheme) private var colorScheme
|
|
236
|
-
|
|
237
|
-
var body: some View {
|
|
238
|
-
VStack(spacing: 4) {
|
|
239
|
-
AsyncImage(url: markURL) { image in
|
|
240
|
-
image
|
|
241
|
-
.resizable()
|
|
242
|
-
.scaledToFit()
|
|
243
|
-
.frame(height: 12)
|
|
244
|
-
} placeholder: {
|
|
245
|
-
Text(attribution.serviceName)
|
|
246
|
-
.font(.caption2)
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
Link(destination: attribution.legalPageURL) {
|
|
250
|
-
Text("Data Sources")
|
|
251
|
-
.font(.caption2)
|
|
252
|
-
.foregroundStyle(.secondary)
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
.padding(.vertical, 8)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private var markURL: URL {
|
|
259
|
-
colorScheme == .dark
|
|
260
|
-
? attribution.combinedMarkDarkURL
|
|
261
|
-
: attribution.combinedMarkLightURL
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
## Charts Integration
|
|
267
|
-
|
|
268
|
-
### Hourly Temperature Chart
|
|
269
|
-
|
|
270
|
-
```swift
|
|
271
|
-
import SwiftUI
|
|
272
|
-
import Charts
|
|
273
|
-
import WeatherKit
|
|
274
|
-
|
|
275
|
-
struct HourlyTemperatureChart: View {
|
|
276
|
-
let forecast: Forecast<HourWeather>
|
|
277
|
-
|
|
278
|
-
var body: some View {
|
|
279
|
-
Chart(Array(forecast.prefix(24)), id: \.date) { hour in
|
|
280
|
-
LineMark(
|
|
281
|
-
x: .value("Hour", hour.date),
|
|
282
|
-
y: .value("Temperature", hour.temperature.converted(to: .celsius).value)
|
|
283
|
-
)
|
|
284
|
-
.interpolationMethod(.catmullRom)
|
|
285
|
-
.foregroundStyle(.orange)
|
|
286
|
-
|
|
287
|
-
AreaMark(
|
|
288
|
-
x: .value("Hour", hour.date),
|
|
289
|
-
y: .value("Temperature", hour.temperature.converted(to: .celsius).value)
|
|
290
|
-
)
|
|
291
|
-
.interpolationMethod(.catmullRom)
|
|
292
|
-
.foregroundStyle(.orange.opacity(0.1))
|
|
293
|
-
}
|
|
294
|
-
.chartYAxisLabel("Temperature (C)")
|
|
295
|
-
.chartXAxis {
|
|
296
|
-
AxisMarks(values: .stride(by: .hour, count: 3)) { _ in
|
|
297
|
-
AxisGridLine()
|
|
298
|
-
AxisValueLabel(format: .dateTime.hour())
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
.frame(height: 200)
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
### Daily Precipitation Chart
|
|
307
|
-
|
|
308
|
-
```swift
|
|
309
|
-
struct DailyPrecipitationChart: View {
|
|
310
|
-
let forecast: Forecast<DayWeather>
|
|
311
|
-
|
|
312
|
-
var body: some View {
|
|
313
|
-
Chart(Array(forecast), id: \.date) { day in
|
|
314
|
-
BarMark(
|
|
315
|
-
x: .value("Day", day.date, unit: .day),
|
|
316
|
-
y: .value("Chance", day.precipitationChance)
|
|
317
|
-
)
|
|
318
|
-
.foregroundStyle(.blue.gradient)
|
|
319
|
-
}
|
|
320
|
-
.chartYScale(domain: 0...1)
|
|
321
|
-
.chartYAxis {
|
|
322
|
-
AxisMarks(format: .percent)
|
|
323
|
-
}
|
|
324
|
-
.chartXAxis {
|
|
325
|
-
AxisMarks(values: .stride(by: .day)) { _ in
|
|
326
|
-
AxisGridLine()
|
|
327
|
-
AxisValueLabel(format: .dateTime.weekday(.abbreviated))
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
.frame(height: 150)
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
## Historical Weather Statistics
|
|
336
|
-
|
|
337
|
-
WeatherKit provides historical weather data through daily and monthly statistics.
|
|
338
|
-
|
|
339
|
-
### Daily Statistics
|
|
340
|
-
|
|
341
|
-
```swift
|
|
342
|
-
func fetchDailyStats(
|
|
343
|
-
for location: CLLocation,
|
|
344
|
-
dateRange: DateInterval
|
|
345
|
-
) async throws {
|
|
346
|
-
let stats = try await WeatherService.shared.dailyStatistics(
|
|
347
|
-
for: location,
|
|
348
|
-
forDaysIn: dateRange,
|
|
349
|
-
including: [.temperature, .precipitation]
|
|
350
|
-
)
|
|
351
|
-
|
|
352
|
-
for dayStat in stats {
|
|
353
|
-
print("Date: \(dayStat.date)")
|
|
354
|
-
if let temp = dayStat.statistics(for: .temperature) {
|
|
355
|
-
print(" Avg temp: \(temp.mean?.formatted() ?? "N/A")")
|
|
356
|
-
print(" Min temp: \(temp.minimum?.formatted() ?? "N/A")")
|
|
357
|
-
print(" Max temp: \(temp.maximum?.formatted() ?? "N/A")")
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
### Monthly Statistics
|
|
364
|
-
|
|
365
|
-
```swift
|
|
366
|
-
func fetchMonthlyStats(for location: CLLocation) async throws {
|
|
367
|
-
let stats = try await WeatherService.shared.monthlyStatistics(
|
|
368
|
-
for: location,
|
|
369
|
-
including: [.temperature, .precipitation]
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
for monthStat in stats {
|
|
373
|
-
print("Month: \(monthStat.month)")
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
## Weather Condition Mapping
|
|
379
|
-
|
|
380
|
-
### Mapping Conditions to Colors
|
|
381
|
-
|
|
382
|
-
```swift
|
|
383
|
-
extension WeatherCondition {
|
|
384
|
-
var themeColor: Color {
|
|
385
|
-
switch self {
|
|
386
|
-
case .clear, .mostlyClear:
|
|
387
|
-
return .yellow
|
|
388
|
-
case .partlyCloudy, .mostlyCloudy, .cloudy:
|
|
389
|
-
return .gray
|
|
390
|
-
case .rain, .heavyRain, .drizzle:
|
|
391
|
-
return .blue
|
|
392
|
-
case .snow, .heavySnow, .flurries, .sleet, .freezingRain,
|
|
393
|
-
.freezingDrizzle, .wintryMix, .blizzard:
|
|
394
|
-
return .cyan
|
|
395
|
-
case .thunderstorms, .strongStorms, .tropicalStorm, .hurricane:
|
|
396
|
-
return .purple
|
|
397
|
-
case .foggy, .haze, .smoky:
|
|
398
|
-
return .gray.opacity(0.6)
|
|
399
|
-
case .breezy, .windy:
|
|
400
|
-
return .teal
|
|
401
|
-
case .hot:
|
|
402
|
-
return .red
|
|
403
|
-
case .frigid, .blowingDust:
|
|
404
|
-
return .indigo
|
|
405
|
-
@unknown default:
|
|
406
|
-
return .primary
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
### Mapping Severity to Priority
|
|
413
|
-
|
|
414
|
-
```swift
|
|
415
|
-
extension WeatherSeverity {
|
|
416
|
-
var displayPriority: Int {
|
|
417
|
-
switch self {
|
|
418
|
-
case .extreme:
|
|
419
|
-
return 4
|
|
420
|
-
case .severe:
|
|
421
|
-
return 3
|
|
422
|
-
case .moderate:
|
|
423
|
-
return 2
|
|
424
|
-
case .minor:
|
|
425
|
-
return 1
|
|
426
|
-
case .unknown:
|
|
427
|
-
return 0
|
|
428
|
-
@unknown default:
|
|
429
|
-
return 0
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
```
|
|
434
|
-
|
|
435
|
-
## Caching Strategy
|
|
436
|
-
|
|
437
|
-
### Actor-Based Weather Cache
|
|
438
|
-
|
|
439
|
-
```swift
|
|
440
|
-
actor WeatherCache {
|
|
441
|
-
struct CacheEntry {
|
|
442
|
-
let weather: CurrentWeather
|
|
443
|
-
let hourly: Forecast<HourWeather>
|
|
444
|
-
let daily: Forecast<DayWeather>
|
|
445
|
-
let fetchDate: Date
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
private var cache: [String: CacheEntry] = [:]
|
|
449
|
-
private let staleness: TimeInterval
|
|
450
|
-
|
|
451
|
-
init(staleness: TimeInterval = 600) { // 10 minutes default
|
|
452
|
-
self.staleness = staleness
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
func get(for key: String) -> CacheEntry? {
|
|
456
|
-
guard let entry = cache[key],
|
|
457
|
-
Date.now.timeIntervalSince(entry.fetchDate) < staleness else {
|
|
458
|
-
cache[key] = nil
|
|
459
|
-
return nil
|
|
460
|
-
}
|
|
461
|
-
return entry
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
func set(_ entry: CacheEntry, for key: String) {
|
|
465
|
-
cache[key] = entry
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/// Generate a cache key from a location (rounded to ~1km precision)
|
|
469
|
-
static func key(for location: CLLocation) -> String {
|
|
470
|
-
let lat = (location.coordinate.latitude * 100).rounded() / 100
|
|
471
|
-
let lon = (location.coordinate.longitude * 100).rounded() / 100
|
|
472
|
-
return "\(lat),\(lon)"
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
### Using the Cache
|
|
478
|
-
|
|
479
|
-
```swift
|
|
480
|
-
@Observable
|
|
481
|
-
@MainActor
|
|
482
|
-
final class CachedWeatherManager {
|
|
483
|
-
private let service = WeatherService.shared
|
|
484
|
-
private let cache = WeatherCache()
|
|
485
|
-
|
|
486
|
-
var current: CurrentWeather?
|
|
487
|
-
|
|
488
|
-
func fetchWeather(for location: CLLocation) async throws {
|
|
489
|
-
let key = WeatherCache.key(for: location)
|
|
490
|
-
|
|
491
|
-
if let cached = await cache.get(for: key) {
|
|
492
|
-
current = cached.weather
|
|
493
|
-
return
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
let (current, hourly, daily) = try await service.weather(
|
|
497
|
-
for: location,
|
|
498
|
-
including: .current, .hourly, .daily
|
|
499
|
-
)
|
|
500
|
-
|
|
501
|
-
let entry = WeatherCache.CacheEntry(
|
|
502
|
-
weather: current,
|
|
503
|
-
hourly: hourly,
|
|
504
|
-
daily: daily,
|
|
505
|
-
fetchDate: .now
|
|
506
|
-
)
|
|
507
|
-
await cache.set(entry, for: key)
|
|
508
|
-
self.current = current
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
## Location-Based Weather
|
|
514
|
-
|
|
515
|
-
### Combining CoreLocation with WeatherKit
|
|
516
|
-
|
|
517
|
-
```swift
|
|
518
|
-
import CoreLocation
|
|
519
|
-
import WeatherKit
|
|
520
|
-
|
|
521
|
-
@Observable
|
|
522
|
-
@MainActor
|
|
523
|
-
final class LocationWeatherManager: NSObject, CLLocationManagerDelegate {
|
|
524
|
-
private let locationManager = CLLocationManager()
|
|
525
|
-
private let weatherService = WeatherService.shared
|
|
526
|
-
|
|
527
|
-
var current: CurrentWeather?
|
|
528
|
-
var locationError: Error?
|
|
529
|
-
|
|
530
|
-
override init() {
|
|
531
|
-
super.init()
|
|
532
|
-
locationManager.delegate = self
|
|
533
|
-
locationManager.desiredAccuracy = kCLLocationAccuracyKilometer
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
func requestWeather() {
|
|
537
|
-
locationManager.requestWhenInUseAuthorization()
|
|
538
|
-
locationManager.requestLocation()
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
nonisolated func locationManager(
|
|
542
|
-
_ manager: CLLocationManager,
|
|
543
|
-
didUpdateLocations locations: [CLLocation]
|
|
544
|
-
) {
|
|
545
|
-
guard let location = locations.last else { return }
|
|
546
|
-
Task { @MainActor in
|
|
547
|
-
do {
|
|
548
|
-
current = try await weatherService.weather(
|
|
549
|
-
for: location,
|
|
550
|
-
including: .current
|
|
551
|
-
)
|
|
552
|
-
} catch {
|
|
553
|
-
locationError = error
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
nonisolated func locationManager(
|
|
559
|
-
_ manager: CLLocationManager,
|
|
560
|
-
didFailWithError error: Error
|
|
561
|
-
) {
|
|
562
|
-
Task { @MainActor in
|
|
563
|
-
locationError = error
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
```
|