## Summary
Phase A of #1828: extract the 5 aggregate builders in
`handleObserverAnalytics` into pure helpers in a new
`cmd/server/observer_analytics.go`. Handler becomes a snapshot + filter
+ 5 composed calls.
Also adopts the `byTxID` direct-read in `buildPacketTypes` (issue body's
core observation): the payload-type histogram no longer allocates a full
`enrichObs` map + interface-boxed fields just to read `tx.PayloadType`.
That's the ~90% perf win the triage called out.
Scope is exactly Phase A per the second triage comment. Phase B
(sub-endpoints, caching, SQL migration) is deferred to a follow-up.
## Byte-identical output
- Timeline / NodesTimeline: same key set, same sort, same labels.
- PacketTypes: same keys/counts. Both legacy
(`enriched["payload_type"].(int)`) and new (`tx.PayloadType == nil`
guard) skip obs whose tx is missing or `PayloadType` is `nil`.
- SnrDistribution: same 2-unit floor bucketing (negative-side rounding
preserved), same ascending sort.
- RecentPackets: still the first 20 enriched observations (`enrichObs`
kept only here, where the extra fields are actually needed).
## TDD
- Red commit: `9dc62f43` — 7 unit tests fail on assertions (not build
errors) against stubs.
- Green commit: `8d41011d` — implementations + handler rewire. All new
tests + existing `TestObserverAnalytics*` handler tests pass.
## Preflight
`bash ~/.openclaw/skills/pr-preflight/scripts/run-all.sh origin/master`
→ clean (all 8 hard gates + 3 warnings pass).
## Non-goals
- No new endpoints.
- No SQL migration.
- No public API signature change.
- Snapshot count unchanged (still one under RLock, per #1481 P0-2).
Fixes#1828.
---------
Co-authored-by: fix-1828-bot <bot@corescope.local>
Co-authored-by: clawbot <bot@corescope>