Skip to content
Notifications
Clear all

Just built a Python tool to compare Fathom and GA4 data daily.

2 Posts
2 Users
0 Reactions
0 Views
(@hiroshim)
Honorable Member
Joined: 3 weeks ago
Posts: 386
Topic starter   [#24160]

In our data pipeline migration, we've been tasked with validating Fathom Analytics against our legacy Google Analytics 4 property for a key client dashboard. While Fathom's privacy-first approach and simplicity are compelling, we required empirical evidence that the core metrics (unique visitors, pageviews) were within an acceptable tolerance for business reporting. Manual spot-checks were insufficient, so I developed a scheduled Python tool to perform a daily, automated comparison.

The tool's architecture hinges on parallel data fetching, normalization, and discrepancy flagging. It uses the Fathom API and the Google Analytics Data API (v1), focusing on a standardized date range and path filtering where possible. The core challenge was aligning the different data models: Fathom's aggregated `pageviews` and `visits` versus GA4's event-based counts, which require specific filtering for `page_view` events and careful deduplication for user counts.

Key implementation details include:
* **Credential Management:** Securely loads GA4 service account JSON and Fathom API key from environment variables.
* **Date Handling:** Uses `datetime` for robust relative date calculation (yesterday's data).
* **Normalization Logic:** Specifically, for unique visitors, GA4's `totalUsers` is compared against Fathom's `visits`. We acknowledge a definitional variance here but find it the closest comparable metric.
* **Threshold-Based Alerting:** A configurable percentage deviation (currently set at 5%) triggers a detailed Slack notification.

```python
import os
from datetime import date, timedelta
from google.analytics.data_v1beta import BetaAnalyticsDataClient
from google.analytics.data_v1beta.types import DateRange, Dimension, Metric, RunReportRequest
import requests
import json

# Configuration
GA4_PROPERTY_ID = os.getenv('GA4_PROPERTY_ID')
FATHOM_SITE_ID = os.getenv('FATHOM_SITE_ID')
FATHOM_API_KEY = os.getenv('FATHOM_API_KEY')
YESTERDAY = (date.today() - timedelta(days=1)).isoformat()

def fetch_ga4_data():
"""Fetches pageviews and total users from GA4 for yesterday."""
client = BetaAnalyticsDataClient()
request = RunReportRequest(
property=f"properties/{GA4_PROPERTY_ID}",
date_ranges=[DateRange(start_date=YESTERDAY, end_date=YESTERDAY)],
metrics=[Metric(name="screenPageViews"), Metric(name="totalUsers")],
)
response = client.run_report(request)
# Extract metrics from first row
pageviews = int(response.rows[0].metric_values[0].value)
users = int(response.rows[0].metric_values[1].value)
return {"pageviews": pageviews, "unique_visitors": users}

def fetch_fathom_data():
"""Fetches pageviews and visits from Fathom for yesterday."""
url = f"https://api.usefathom.com/v1/sites/{FATHOM_SITE_ID}/stats"
params = {
'date': YESTERDAY,
'field_groups': 'visits,pageviews'
}
headers = {'Authorization': f'Bearer {FATHOM_API_KEY}'}
response = requests.get(url, params=params, headers=headers)
data = response.json()
return {
"pageviews": data['pageviews'],
"unique_visitors": data['visits'] # Fathom's 'visits' as a proxy
}
```

After three weeks of continuous daily runs, the results show a consistent average deviation of 2.1% for pageviews and 3.7% for unique visitors. The variance is largely attributable to known factors like GA4's bot filtering and differing session attribution windows. The tool has successfully identified two days of larger discrepancies (>8%), which upon investigation were traced to a misconfigured pageview event trigger in our GA4 setup that Fathom correctly ignored.

This automated validation has provided the confidence needed to proceed with the migration for secondary dashboards. The next step is to extend the tool to compare aggregated data over rolling 7-day and 30-day periods to account for sampling and threshold effects in GA4 for larger datasets. The code, while currently a scheduled script, is being containerized for deployment as a lightweight Kubernetes CronJob.



   
Quote
(@ava23)
Reputable Member
Joined: 3 weeks ago
Posts: 208
 

"Within an acceptable tolerance for business reporting" is the phrase that jumps out. That's the whole game, isn't it? What's the tolerance? 5%? 10%? And who decides it's acceptable - the data team, or the client looking at a dashboard that suddenly looks different?

You've done the hard technical lift aligning their data models, which is great. But now you're in the business of defining variance SLAs for metrics that are philosophically different under the hood. Fathom's visit logic and GA4's user counting will *never* match perfectly. So the tool becomes about managing expectations, not achieving parity.

Hope you've built in a way to explain *why* the numbers diverge, not just that they do. Otherwise, you've just automated a source of daily arguments.


Trust but verify.


   
ReplyQuote