apps/server/docs/ai-context/verifications/product-analytics-smoke.md
Status: code-level instrumentation verified; live PostHog dashboard updated; Grafana dashboard updated; alert setup pending Owner: Community / Product Analytics Last updated: 2026-07-01 Related:
product-analytics-instrumentation.mdproduct-analytics-dashboard-setup.mdairi-server-overview-cloud.jsonproduct_events 能看到服务端 TTS metadata;Grafana 能看到低基数 server-side product health。AIRI Server Overview - Product Analytics (ad8qbp5) 已补 TTS blocked reason / Flux bucket 面板;alert 仍需人工配置。| Area | Evidence |
|---|---|
| Frontend analytics API | packages/stage-ui/src/composables/use-analytics.test.ts 覆盖 activation、model list、provider config、voice selection、voice input、feedback event API |
| Chat activation hooks | packages/core-agent/src/runtime/chat-orchestrator-runtime.test.ts 覆盖 activation started / succeeded / failed hook |
| Voice input failures | packages/stage-ui/src/composables/audio/audio-device.test.ts 与 packages/stage-ui/src/stores/modules/hearing.analytics.test.ts 覆盖 permission / device / cancel / STT failed |
| Server TTS metadata | apps/server/src/routes/openai/v1/route.test.ts 与 apps/server/src/routes/audio-speech-ws/route.test.ts 覆盖 REST / WS TTS voice_id、voice_type、voice_pack_id metadata |
| Grafana product row | apps/server/otel/grafana/dashboards/build.test.ts 覆盖 Product Analytics panels、layout references、PromQL 不包含 high-cardinality voice / user fields |
Run this after deploying a build with the instrumentation changes. The PostHog dashboard and Grafana dashboard shell are already created, but they still need live event traffic from the deployed build.
Action:
Expected PostHog events:
chat_activation_started
chat_activation_succeeded
second_turn_started
Required properties:
provider_mode = official
provider_id = <official provider id>
model_id = <selected model id>
surface = web | mobile | electron
turn_index = 2
Fail if:
chat_activation_started appears but chat_activation_succeeded never appears for a successful chat.second_turn_started does not appear.provider_mode is missing or always unknown.surface is missing.Action:
Expected PostHog events:
official_provider_selected
Required properties:
provider_mode = official
provider_id = <official provider id>
source = default_auto | settings
auto_selected = true | false
Fail if:
auto_selected = false.auto_selected = true.Action:
Expected PostHog events:
provider_config_started
provider_config_failed
Required properties:
provider_mode = custom
provider_id = <provider id>
step = settings_auto_validate | manual_chat_ping
error_code = <bounded error code>
Fail if:
provider_config_failed has no error_code.Action:
Expected PostHog events:
tts_provider_selected
official_tts_exposed
official_tts_preview_started
official_tts_preview_succeeded
voice_selected
voice_preview_played
Required properties:
tts_provider_id = <provider id>
tts_model_id = <model id>
voice_id = <catalog voice id or custom>
voice_type = official_default | official_selected | custom_configured | voice_pack | unknown
source = settings | manual_preview
Fail if:
voice_selected is missing, because this blocks “哪个 TTS 音色比较多”的核心问题。official_tts_preview_succeeded is missing.Action:
Expected PostHog events:
official_tts_auto_enabled
Required properties:
tts_provider_id = <official provider id>
tts_model_id = <model id>
source = chat_auto_tts
enabled = true
Fail if:
official_tts_auto_enabled is missing.Action:
Expected PostHog events:
voice_input_started
microphone_permission_requested
microphone_permission_denied
audio_device_unavailable
voice_input_cancelled
stt_failed
Only the events that match the exercised path need to appear.
Fail if:
stt_failed.error_code contains raw browser error text.Action:
Expected PostHog events:
paywall_seen
Required properties:
surface = settings_flux
reason = manual_topup
flux_balance_bucket = zero | 1_100 | 101_1000 | 1001_10000 | 10000_plus | unknown
Fail if:
paywall_seen is missing.flux_balance_bucket.Action:
Query:
SELECT
created_at,
user_id,
source,
provider,
model,
action,
status,
metadata->>'voice_id' AS voice_id,
metadata->>'voice_type' AS voice_type,
metadata->>'voice_pack_id' AS voice_pack_id
FROM product_events
WHERE feature = 'tts'
AND created_at >= now() - interval '1 hour'
ORDER BY created_at DESC
LIMIT 50;
Expected:
speech_requested and speech_succeeded rows exist for successful TTS.voice_id is present when the request provided a selected voice.voice_type distinguishes official default / selected / custom / voice pack where available.block_reason and flux_balance_bucket.failure_reason.Fail if:
product_events row is written.Action:
https://projairi.grafana.net/d/ad8qbp5/airi-server-overview.Product Analytics row.Expected panels:
Product Events (range)
Product Failure %
TTS Success %
TTS Failed / Blocked (range)
TTS Blocked by Reason
TTS Blocked by Flux Bucket
Top Product Actions (range)
Product Event Rate
TTS Event Rate by Source
PromQL sanity:
sum(increase(airi_product_events_total{feature="tts"}[1h]))
Expected:
feature, action, status, source, reason, flux_balance_bucket.Fail if:
voice_id, voice_pack_id, user_id, session_id, or request_id.Top selected voices should come from PostHog voice_selected for frontend intent. Server-side playback can be cross-checked from Postgres:
SELECT
metadata->>'voice_id' AS voice_id,
metadata->>'voice_type' AS voice_type,
provider,
model,
COUNT(*) AS play_count,
COUNT(DISTINCT user_id) AS distinct_users
FROM product_events
WHERE feature = 'tts'
AND action = 'speech_succeeded'
AND created_at >= now() - interval '7 days'
GROUP BY 1, 2, 3, 4
ORDER BY play_count DESC
LIMIT 20;
Server-side TTS blocked / failed ranking:
SELECT
action,
status,
source,
reason,
COUNT(*) AS event_count,
COUNT(DISTINCT user_id) AS distinct_users
FROM product_events
WHERE feature = 'tts'
AND action IN ('speech_failed', 'speech_blocked')
AND created_at >= now() - interval '24 hours'
GROUP BY 1, 2, 3, 4
ORDER BY event_count DESC;
| Item | Pass condition |
|---|---|
| Activation | PostHog funnel shows chat_activation_started -> chat_activation_succeeded by provider_mode |
| Retention proxy | PostHog shows second_turn_started for the second message in a successful session |
| Official provider | PostHog shows official_provider_selected by provider_id and source |
| Provider config | Failed custom config emits provider_config_failed with bounded error_code |
| TTS voice | PostHog can rank voice_selected by voice_id; official TTS exposure / preview / auto playback events appear; Postgres can rank actual speech_succeeded by metadata voice |
| Voice input | Permission / device / cancel paths are distinguishable |
| Feedback | Feedback event API exists with bounded fields; product feedback UI/server submission is split into a separate PR |
| Grafana | Product Analytics row renders TTS reason / Flux bucket panels and uses only bounded Prometheus labels |