Analytics & Success Metrics
This note records the analytics setup that is currently in the application and how it maps to the Google/CDP handoff notes.
The source handoff documents identify two measurement surfaces:
- Looker Studio / GA4 / BigQuery for general usage and “Population Reached via Research”.
- PostHog for richer product interaction and Ask CDP AI metrics.
Dashboard Links
| Dashboard | Link | Notes |
|---|---|---|
| Looker Studio traffic monitor | Traffic monitor dashboard | Listed near the top of the Google x CDP technical handoff. |
| Looker Studio dashboard | Looker Studio report | Listed later in the technical handoff as the Looker dashboard. |
| GA4 detailed analysis | Detailed analysis dashboard | Linked from the success-metrics doc for location_viewed reporting. |
| GA4 reporting hub draft | Reporting hub dashboard | Described in the success-metrics doc as an early-preview draft dashboard. |
| GA4 pages and screens report | Pages and screens report | Linked from the GA/Looker work log for page and screen engagement review. |
| PostHog project | PostHog project 141282 | Core product analytics project for Adapt Action Explorer. |
| PostHog AI Observability | PostHog AI traces | Ask CDP AI LLM traces. Full input/output content is stored in posthog.ai_events (input, output_choices). |
| Langfuse traces | Langfuse cdp ask traces | LLM trace dashboard with full input, output, usage, latency, and cost details. |
| Ask CDP AI weekly stats | Ask CDP AI stats | Markdown reports that separate AI-chat opens and real user actions from internal follow-up generation and smoke-test traffic. |
Dashboard Access Status
Slack follow-up on 2026-06-24 reported that GA4 dashboard access has been requested and that the other Google/Looker links appear empty from the requester’s view. Treat those dashboards as handoff links that still need access and data-source verification, not as confirmed working reporting surfaces.
2026-06-30 update: direct testing of the GA4 detailed-analysis link returned a missing-permissions response. The required next step is for a Google Analytics administrator or report owner to grant access to the relevant GA4 property, linked Looker Studio reports, and any underlying BigQuery data source.
Next steps:
- Confirm which Google account should have access to GA4 property
G-Z6QWJ09VM8, the linked Looker Studio reports, and any underlying BigQuery data source. - After access is granted, open each Google/Looker link above and check whether the report is empty because of permissions, date-range filters, disconnected data sources, or genuinely missing events.
- Validate that production still emits
location_viewedinto GA4 by using DebugView or the GA4 event report after opening a location detail page onhttps://cdp-action-explorer.net/. - If the Looker reports remain empty after permissions and date ranges are confirmed, ask CDP/Google to identify the owner of the GA4-to-BigQuery/Looker pipeline for the “Population Reached via Research” metric.
- Use the PostHog links as the immediately actionable product-analytics fallback while the Google/Looker dashboard access is being resolved.
Current Tooling
| Tool | Current role | Configuration |
|---|---|---|
| Google Analytics 4 | General page and selected custom event tracking. | The frontend loads gtag.js for property G-Z6QWJ09VM8 in frontend/src/index.html. |
| PostHog | Product interaction, user-role, and Ask CDP AI events. | Enabled at frontend build time with FRONTEND_POSTHOG_ENABLED=true and FRONTEND_POSTHOG_KEY. The default host is https://eu.i.posthog.com. |
| Looker Studio / BigQuery | Reporting layer for GA4-derived dashboard metrics. | External to the app repo. The Google handoff describes monthly joins from distinct location_viewed locations to population data. |
| Sentry | Browser exception and performance monitoring. | Optional frontend build-time config through FRONTEND_SENTRY_* variables. |
Evidence Sources
This page is grounded in the current implementation rather than only the original handoff notes:
| Source | What it confirms |
|---|---|
frontend/src/index.html and frontend/src/app/app.ts | GA4 property G-Z6QWJ09VM8 is loaded and configured at app startup. |
frontend/src/app/core/analytics/posthog.service.ts | PostHog initialization, manual $pageview, exception capture, session recording masking, and stored user_type enrichment. |
frontend/src/app/core/analytics/analytics-events.ts | Shared PostHog property builders for locations, pins, hazards, action items, and solutions. |
frontend/src/app/features/** | Component-level user interaction events sent to PostHog and GA4. |
app/observability.py in cdp-ai-engine | AI-server $ai_generation payloads sent to PostHog and Langfuse, including full input/output content when enabled. |
.github/workflows/ai-server-deploy.yml in cdp-ai-engine | Deploy smoke check verifies PostHog AI content lands in posthog.ai_events.input and posthog.ai_events.output_choices. |
Implemented Events
Google Analytics
The Angular app configures GA4 at startup and fires a small set of custom events:
| Event | Trigger | Properties |
|---|---|---|
location_viewed | A full location detail profile is loaded. | location_id, currently the location name in the GA event payload. |
chatbot_query_submit | A user submits a manual Ask CDP AI query. | No query text is sent to GA. |
chatbot_suggest_click | A user clicks an Ask CDP AI follow-up suggestion. | query_text is sent to GA by the current implementation. |
The Google success-metrics notes say GA should receive location_id for the population-reached dashboard. In the current code, GA uses the display name as location_id; PostHog receives the stable organization id plus location_name.
PostHog
PostHog is disabled unless the frontend build includes a key and enables it. When enabled, the app captures page views manually and captures exceptions through the shared error handler. The wrapper also adds the stored user_type to custom events unless the event already sets one.
Session replay is configured defensively: input values are masked, page text is masked, and elements matching .sensitive-data or [data-ph-no-capture] are blocked. Autocapture is restricted to https://cdp-action-explorer.net, localhost, and 127.0.0.1; the app still sends explicit custom events on Cloud Run preview URLs.
Key custom events include:
| Area | Events |
|---|---|
| Search and map | search_location_selected, map_pin_selected |
| Location detail | location_viewed, location_tab_changed |
| Hazards and actions | hazard_detail_expanded, hazard_actions_explore_clicked, action_hazard_filter_applied, action_hazard_filter_cleared |
| Government actions | government_action_opened, funding_project_opened, adaptation_goal_opened |
| Action ideas | solution_hazard_filter_applied, solution_hazard_filter_cleared, solution_detail_opened |
| Ask CDP AI | ai_chat_opened, ai_chat_query_submitted, ai_chat_followup_clicked |
| Feedback and role | feedback_opened, user_role_selected |
PostHog event properties are built from frontend/src/app/core/analytics/analytics-events.ts. Location events include organization id, location name, country, public status, disclosure year, and counts for hazards/actions/goals/projects when available.
PostHog Event Payloads
| Event | Trigger | Core properties |
|---|---|---|
$pageview | PostHog service init and every Angular NavigationEnd. | $current_url, path, search, plus stored user_type when available. |
location_viewed | A full location profile with hazards loads for a new location. | location_id, location_name, country, public_status, disclosure_year, hazards_count, actions_count, goals_count, projects_count. |
location_tab_changed | User changes the location detail tab, or hazard exploration switches to actions. | Location properties, from_tab, to_tab, optional source. |
search_location_selected | User selects a search result, loads a typed search result, or opens details from a map pin. | location_id, location_name, country, source, optional query_length, optional result_rank. |
map_pin_selected | User clicks a map pin. | location_name, org_type, zoom_level. |
hazard_detail_expanded | User expands a hazard detail card. | Location properties, hazard_type, other_hazard_details, hazard_rank, hazard_source. |
hazard_actions_explore_clicked | User clicks from a hazard into related actions. | Location properties, hazard properties, action_count. |
action_hazard_filter_applied | User applies an actions hazard filter. | Location properties, hazard_type, other_hazard_details, matching_action_count. |
action_hazard_filter_cleared | User clears the actions hazard filter. | Location properties, previously selected hazard_type and other_hazard_details. |
government_action_opened | User opens an adaptation action detail. | Location context, item_type, item_key, hazards_addressed_count, status, co_benefits_count. |
funding_project_opened | User opens a project seeking funding. | Location context, item_type, item_key, hazards_addressed_count, status, finance_status, funded_percent. |
adaptation_goal_opened | User opens an adaptation goal. | Location context, item_type, item_key, hazards_addressed_count, target_year. |
solution_hazard_filter_applied | User filters action ideas by hazard. | Location properties, hazard_type, matching_solution_count. |
solution_hazard_filter_cleared | User clears the action-ideas hazard filter. | Location properties, hazard_type, matching_solution_count. |
solution_detail_opened | User opens a solution detail modal. | Location properties, solution_key, solution_category, hazards_addressed_count, peer_actions_count, pct_peer_taking_action, has_local_action, selected_hazard_filter. |
ai_chat_opened | Ask CDP AI panel opens. | Location properties, context_area. |
ai_chat_query_submitted | User submits a manual Ask CDP AI query. | Location properties, context_area, sanitized/truncated query, query_length, source: manual. |
ai_chat_followup_clicked | User clicks an Ask CDP AI suggested follow-up. | Location properties, context_area, sanitized/truncated query, query_length, source: followup. |
feedback_opened | User opens the feedback modal. | path, language, viewport, optional location_id, optional location_name, optional active_tab. |
user_role_selected | User confirms or changes their role. | user_type, role, previous_role, source; the selected role is also registered as a PostHog super property. |
AI Server Observability
Ask CDP AI interactions also generate backend LLM telemetry from cdp-ai-engine.
| Destination | Event or trace | Content captured |
|---|---|---|
| PostHog AI Observability | $ai_generation | $ai_trace_id, $ai_span_id, $ai_span_name, $ai_model, $ai_provider, $ai_stream, $ai_temperature, $ai_max_tokens, metadata such as sessionId, userId, location_name, token counts, cost fields, latency, full input, and full output. |
| Langfuse | gemini.generate_content generation trace | Full input payload, output text, model, model parameters, merged metadata, usage details, latency, and cost. |
PostHog stores heavy AI content outside events.properties: full prompt/input content is queryable in posthog.ai_events.input, and full output choices are queryable in posthog.ai_events.output_choices. The deploy workflow now checks those columns after every AI-server deploy.
Useful verification query:
SELECT timestamp, trace_id, event, model, input, output_choices
FROM posthog.ai_events
WHERE event = '$ai_generation'
ORDER BY timestamp DESC
LIMIT 10Handoff Names Not Currently Emitted
The handoff and older notes reference a few names that are not present in the current frontend event-capture code:
| Event name | Current status |
|---|---|
section_engaged | Not found in the current frontend code. Tab changes and item opens are tracked instead. |
export_clicked, export_completed, export_failed | Not found in the current frontend code. The success-metrics row for exports should be treated as a gap until export tracking is implemented. |
peer_location_click | Not emitted. Peer solution interest is partially covered by solution_detail_opened and solution hazard-filter events. |
funding_card_expanded | Not emitted. Funding-card engagement is represented by funding_project_opened. |
Success-Metrics Mapping
| Handoff metric | Current implementation status |
|---|---|
| Population reached via research | Partially implemented. The frontend emits location_viewed; reporting still depends on the external GA4/BigQuery/Looker pipeline described in the handoff. |
| General active users, sessions, session duration, geography, device, traffic source | Covered by GA4/Looker outside this repo. |
| Location detail page view count | Covered by GA4 location_viewed and PostHog location_viewed. |
| AI chat usage | Covered in PostHog through open, manual query, and follow-up click events; GA also receives manual submit and suggestion click events. See Ask CDP AI weekly stats for the repeatable interest funnel reports. |
| Peer solution discovery rate | Partially covered. The app records solution filter and detail-open events in PostHog, but it does not emit the handoff’s proposed peer_location_click event name. |
| Mobilization intent via PDF export | Not implemented as a PDF export metric. No export_clicked / export_completed / export_failed capture calls are present in the current frontend code. |
| Funding-card engagement | Implemented in PostHog as funding_project_opened, not the handoff’s proposed funding_card_expanded event name. |
| Long-term readiness outcomes | Not directly measurable in app telemetry. These remain CDP survey, disclosure, and follow-up metrics. |
Operational Notes
- The production Cloud Run URL in the Google technical handoff is useful for internal handover, but the public site metadata in the frontend points at
https://cdp-action-explorer.net/. - The handoff’s GA DebugView setup still applies for validating
location_viewed: open a location detail page, then inspect thelocation_viewedevent and itslocation_idparameter in GA4. - PostHog autocapture is restricted to
https://cdp-action-explorer.net, localhost, and127.0.0.1. Cloud Run preview URLs rely on explicit custom events rather than PostHog autocapture. - Query text is sanitized and truncated before being sent to PostHog. Manual query text is not sent to GA, but follow-up suggestion text is currently sent as
query_text.