Context
Nimbus PR 3 ships smoke as `Skywatch.smoke(at:)` → `Array` (matches the `storms` pattern). Considered a `SmokeAnalysis` aggregate (matching the `convection` pattern with `fetched_at`, `at`, `active?`, `worst_density` predicates) but deferred — single homogeneous data type doesn't earn the wrapper's complexity for MVP.
When to revisit
If consumers (Skywatch.brief, future formatters, agent-shaped tools) repeatedly need:
- `worst_density` summary across plumes
- `active?` predicate as a single boolean
- `fetched_at` context for staleness checks
- A natural place to hang derived attributes (e.g., "is this analysis from before sunrise?" — HMS analysis depends on visible-band satellite imagery)
then promoting to a `SmokeAnalysis` aggregate (`Nimbus::Models::SmokeAnalysis`) becomes worth the indirection.
Suggested shape
```ruby
class SmokeAnalysis
attr_reader :at, :fetched_at, :plumes
def active?
def worst_density # :light/:medium/:heavy/nil
def to_h
end
```
`Skywatch.smoke(at:)` would change return shape — minor API break, but only one caller (the CLI) plus the brief composer would need updates.
Context
Nimbus PR 3 ships smoke as `Skywatch.smoke(at:)` → `Array` (matches the `storms` pattern). Considered a `SmokeAnalysis` aggregate (matching the `convection` pattern with `fetched_at`, `at`, `active?`, `worst_density` predicates) but deferred — single homogeneous data type doesn't earn the wrapper's complexity for MVP.
When to revisit
If consumers (Skywatch.brief, future formatters, agent-shaped tools) repeatedly need:
then promoting to a `SmokeAnalysis` aggregate (`Nimbus::Models::SmokeAnalysis`) becomes worth the indirection.
Suggested shape
```ruby
class SmokeAnalysis
attr_reader :at, :fetched_at, :plumes
def active?
def worst_density # :light/:medium/:heavy/nil
def to_h
end
```
`Skywatch.smoke(at:)` would change return shape — minor API break, but only one caller (the CLI) plus the brief composer would need updates.