MixPanel analytics tracking implementation and review Skill for Django4Lyfe optimo_analytics module. Implements new events following established patterns and reviews implementations for PII protection, schema design, and code quality.
Implements or reviews MixPanel analytics tracking in the Django4Lyfe `optimo_analytics` module. Use `/mixpanel-analytics:implement` to add new events via a 7-step checklist, or `/mixpanel-analytics:review` to audit for PII protection and schema compliance.
/plugin marketplace add DiversioTeam/agent-skills-marketplace/plugin install mixpanel-analytics@diversiotechThis skill is limited to using the following tools:
Use this Skill in the Django4Lyfe backend when working with MixPanel analytics
tracking in the optimo_analytics module:
/mixpanel-analytics:implement – to implement new MixPanel tracking events
or update existing ones following established patterns (7-step checklist)./mixpanel-analytics:review – to review MixPanel implementations for
correctness, PII protection, and adherence to Django4Lyfe standards./mixpanel-analytics:implement to add a new event for tracking when a
user completes their profile setup."/mixpanel-analytics:implement svc.surveys.reminder_sent to add tracking
for survey reminder notifications."/mixpanel-analytics:implement."/mixpanel-analytics:review staged to check my staged MixPanel changes
for PII violations and pattern compliance."/mixpanel-analytics:review branch to audit all analytics changes on
this feature branch."/mixpanel-analytics:review all."This Skill behaves differently based on how it is invoked:
implement mode – invoked via /mixpanel-analytics:implement:
review mode – invoked via /mixpanel-analytics:review:
When this Skill runs, gather context first:
# Git context
git branch --show-current
git status --porcelain
git diff --cached --name-only | grep -E "optimo_analytics|mixpanel"
# Analytics module stats
grep -c "^ [A-Z_]* = " optimo_analytics/constants.py 2>/dev/null || echo "0"
grep -c "^class Mxp" optimo_analytics/schemas.py 2>/dev/null || echo "0"
grep -c "MixPanelEvent\." optimo_analytics/registry.py 2>/dev/null || echo "0"
ls -1 optimo_analytics/service/*.py 2>/dev/null | xargs -I{} basename {} .py
Read key reference files:
optimo_analytics/AGENTS.md – module-level rules and PII guidelinesoptimo_analytics/schemas.py – existing schema patternsoptimo_analytics/service/AGENTS.md – service layer patternsoptimo_analytics/tests/AGENTS.md – test patternsFor each new event, complete these steps in order:
optimo_analytics/constants.py)# Event naming convention: {prefix}.{object}.{action}[.error]
# Examples:
# - svc.surveys.survey_delivered
# - svc.map.action_plan_created
# - svc.hris_csv.upload.analysis_completed
#
# NOTE: Do NOT include "cron" in event names - use is_cron_job property instead
class MixPanelEvent:
# Add under appropriate section with comment
NEW_EVENT_NAME = "svc.domain.action_name"
optimo_analytics/schemas.py)# Schema naming: Mxp{Domain}{Action}EventSchema
# CRITICAL RULES:
# - All UUIDs MUST be strings (str, not UUID)
# - NO PII: no names, emails, phone numbers
# - organization_name IS allowed (business approved)
# - Use STRICT_MODEL_CONFIG (no aliases) or ALIASED_MODEL_CONFIG ($ aliases)
class MxpNewEventSchema(MixpanelSuperEventPropertiesSchema):
"""Properties for svc.domain.action_name event.
Tracked when [describe when this event fires].
"""
# Required fields (no defaults)
employee_id: str = Field(description="Employee UUID as string")
organization_id: str = Field(description="Organization UUID as string")
organization_name: str = Field(description="Organization name for analytics")
role: SystemRole | None = Field(description="User role")
impersonation: bool = Field(description="Is impersonated session")
# Event-specific fields
custom_field: str = Field(description="What this field represents")
# Use STRICT_MODEL_CONFIG for internal-only schemas
# Use ALIASED_MODEL_CONFIG when field names need $ prefix for MixPanel (e.g., $device_id)
model_config = STRICT_MODEL_CONFIG
optimo_analytics/registry.py)# Add import at top
from optimo_analytics.schemas import MxpNewEventSchema
# Add to _EVENT_SCHEMA_REGISTRY dict
_EVENT_SCHEMA_REGISTRY: dict[str, type[MixpanelSuperEventPropertiesSchema]] = {
# ... existing entries ...
MixPanelEvent.NEW_EVENT_NAME: MxpNewEventSchema,
}
optimo_analytics/service/{domain}.py)Choose appropriate service file or create new one:
auth.py - Authentication eventssurvey.py - Survey lifecycle eventsrisk.py - Risk calculation eventsmap.py - Manager Action Pipeline eventscore.py - Core/HRIS eventsclass OptimoMixpanel{Domain}TrackHelper:
"""Helper class for {Domain} event tracking."""
@classmethod
def track_new_event(
cls,
*, # CRITICAL: Force keyword-only arguments
employee_id: str,
# ... other params ...
) -> None:
"""
Track new event (svc.domain.action_name).
Tracked when [describe trigger condition].
Args:
employee_id: Employee UUID as string
"""
try:
cls._track_new_event(
employee_id=employee_id,
# ... pass all args ...
)
except Exception:
# Fire-and-forget: log but don't propagate
logger.exception(
"mixpanel_new_event_tracking_failed",
employee_id=employee_id,
)
@staticmethod
def _track_new_event(
*,
employee_id: str,
# ... other params ...
) -> None:
"""Track new event implementation."""
emp_info = OptimoMixpanelService._fetch_required_emp_info(
employee_id=employee_id
)
properties = MxpNewEventSchema(
employee_id=employee_id,
organization_id=str(emp_info.organization.uuid),
organization_name=emp_info.organization.name,
role=emp_info.role,
impersonation=False,
# ... event-specific fields ...
)
# distinct_id fallback hierarchy:
# 1. User's UUID (primary)
# 2. org_<organization_uuid> (fallback when no user)
# 3. Context-specific: slack_<id>, apikey_<id>, webhook_<id>
distinct_id = employee_id # or f"org_{org_uuid}" if no user
OptimoMixpanelService.track_event(
distinct_id=distinct_id,
event_name=MixPanelEvent.NEW_EVENT_NAME,
properties=properties,
)
__init__.py (optimo_analytics/service/__init__.py)# Add to imports
from optimo_analytics.service.{domain} import OptimoMixpanel{Domain}TrackHelper
# Add to __all__
__all__ = [
# ... existing ...
"OptimoMixpanel{Domain}TrackHelper",
]
optimo_analytics/tests/test_{event}_event.py)"""Tests for {Event} MixPanel tracking."""
from unittest.mock import patch
from uuid import uuid4
import pytest
from optimo_analytics.constants import MixPanelEvent
from optimo_analytics.registry import EVENT_SCHEMA_REGISTRY, is_event_registered
from optimo_analytics.schemas import MxpNewEventSchema
from optimo_analytics.service import OptimoMixpanel{Domain}TrackHelper
pytestmark = [pytest.mark.django_db]
@pytest.fixture(autouse=True)
def eager_jobs(settings):
"""Force synchronous job execution."""
settings.OPTIMO_JOBS_EAGER_MODE = True
yield
settings.OPTIMO_JOBS_EAGER_MODE = False
@pytest.fixture
def mock_mixpanel():
"""Mock MixPanel client."""
with patch("optimo_analytics.service.MixPanelFactory.get_client") as mock:
yield mock.return_value
class TestNewEventSchema:
"""Test schema validation."""
def test_schema_creation_with_valid_properties(self):
"""Schema accepts valid properties."""
schema = MxpNewEventSchema(
employee_id=str(uuid4()),
organization_id=str(uuid4()),
organization_name="Test Org",
role=SystemRole.EMPLOYEE,
impersonation=False,
)
assert schema.employee_id is not None
class TestNewEventRegistry:
"""Test registry registration."""
def test_event_is_registered(self):
"""Event should be registered in schema registry."""
assert is_event_registered(MixPanelEvent.NEW_EVENT_NAME)
assert EVENT_SCHEMA_REGISTRY.get(MixPanelEvent.NEW_EVENT_NAME) is MxpNewEventSchema
class TestNewEventTracking:
"""Test service tracking method."""
def test_tracking_calls_mixpanel(self, mock_mixpanel, optimo_employee):
"""Tracking should call MixPanel with correct properties."""
OptimoMixpanel{Domain}TrackHelper.track_new_event(
employee_id=str(optimo_employee.uuid),
)
mock_mixpanel.track.assert_called_once()
class TestNewEventNonBlocking:
"""Test fire-and-forget behavior."""
def test_exception_does_not_propagate(self):
"""Tracking exceptions should be caught and logged."""
with patch.object(
OptimoMixpanel{Domain}TrackHelper,
"_track_new_event",
side_effect=Exception("boom"),
):
# Should NOT raise
OptimoMixpanel{Domain}TrackHelper.track_new_event(
employee_id=str(uuid4()),
)
from optimo_analytics.service import OptimoMixpanel{Domain}TrackHelper
def some_business_method(self, ...):
# ... business logic ...
# Track after successful operation
OptimoMixpanel{Domain}TrackHelper.track_new_event(
employee_id=str(employee.uuid),
)
*, in method signature){prefix}.{object}.{action}[.error]
Examples:
svc.surveys.survey_deliveredsvc.surveys.survey_delivered.error (for failures)svc.map.action_plan_createdNote: Do NOT include execution context (like "cron") in event names.
Use is_cron_job property instead.
is_cron_jobNOT all background jobs need is_cron_job=True. Only set it when you need:
time should reflect
the original user action, not when the CRON ranWhen to set is_cron_job=True:
properties = MxpYourEventSchema(
# ... other fields ...
is_cron_job=True,
cron_execution_timestamp=datetime_to_timestamp_ms(timezone.now()),
)
Validation: If is_cron_job=True, then cron_execution_timestamp is
required (enforced by validate_cron_properties).
str (never UUID)datetime_to_timestamp_ms() for MixPanelSystemRole | None, etc.list[str] for UUID listsNEVER override base schema fields as Optional to handle None values. Instead:
str fields that might have no value, pass empty string ""organization_id, organization_name, employee_id, etc.
with Optional[str] types in child schemasMixpanelSuperEventPropertiesSchema already defines these fields -
inherit them, don't redefineBAD - Don't do this:
class MxpNewEventSchema(MixpanelSuperEventPropertiesSchema):
# WRONG: duplicating base fields as Optional
organization_id: str | None = Field(default=None, description="...")
organization_name: str | None = Field(default=None, description="...")
GOOD - Do this instead:
class MxpNewEventSchema(MixpanelSuperEventPropertiesSchema):
# Inherit organization_id, organization_name from base schema
# Pass empty string when value is not available
pass
# In service method:
properties = MxpNewEventSchema(
organization_id=str(org.uuid) if org else "",
organization_name=org.name if org else "",
# ...
)
## Post-Implementation Validations
```bash
# 1. Ruff lint and format
.bin/ruff check optimo_analytics/ --fix
.bin/ruff format optimo_analytics/
# 2. Type checking
.bin/ty check optimo_analytics/
# 3. Django checks
DJANGO_CONFIGURATION=DevApp uv run python manage.py check
# 4. Run tests
.bin/pytest optimo_analytics/tests/ -v --dc=TestLocalApp
MUST CHECK:
first_name, last_name, full_name, display_name in schemasemail, email_address, user_email fieldsphone, phone_number, phone_e164 fieldsaddress, city, country as free-text fieldsorganization_name is ONLY sent to MixPanel, never loggedMUST VERIFY:
constants.py under MixPanelEventschemas.pyregistry.py _EVENT_SCHEMA_REGISTRYMixpanelSuperEventPropertiesSchemaMUST VERIFY:
str, not UUIDField(description="...")STRICT_MODEL_CONFIG or ALIASED_MODEL_CONFIG appropriatelySystemRole | None patternMixpanelSuperEventPropertiesSchema are NOT
redefined as Optional[str] - pass empty string "" for missing valuesMUST VERIFY:
@classmethod*, after cls)@staticmethodMUST HAVE:
mock_mixpanelpytestmark = [pytest.mark.django_db]Event names: {prefix}.{object}.{action}[.error]
Schema names: Mxp{Domain}{Action}EventSchema
Helper names: OptimoMixpanel{Domain}TrackHelper
is_cron_job Usage (P2)NOTE: Not all background jobs need is_cron_job=True. Only use when:
IF is_cron_job=True is used, MUST VERIFY:
cron_execution_timestamp is provided as Unix millisecondsMUST VERIFY:
datetime_to_timestamp_ms() for MixPanel timestampsdistinct_id MUST strictly follow this fallback hierarchy:
org_<organization_uuid> (when no user context exists)slack_<slack_workspace_id>apikey_<api_key_id>webhook_<webhook_id>NEVER pass organization_id directly as distinct_id - always prefix with org_.
MUST VERIFY:
org_<uuid> prefix when falling back to organizationMUST VERIFY:
service/__init__.py__all__ list# 1. PII Scan
grep -rn "first_name\|last_name\|email\|phone\|address" optimo_analytics/schemas.py
# 2. UUID Type Check
grep -rn ": UUID" optimo_analytics/schemas.py
# 3. Registration Check
for event in $(grep "^ [A-Z_]* = " optimo_analytics/constants.py | cut -d'=' -f1 | tr -d ' '); do
grep -q "$event" optimo_analytics/registry.py || echo "UNREGISTERED: $event"
done
# 4. Keyword-only Check
grep -rn "def track_" optimo_analytics/service/*.py | while read line; do
file=$(echo $line | cut -d: -f1)
linenum=$(echo $line | cut -d: -f2)
if ! sed -n "$((linenum+1)),$((linenum+5))p" "$file" | grep -q '\*,'; then
echo "MISSING *,: $line"
fi
done
# MixPanel Implementation Review
**Branch**: {branch}
**Scope**: {scope}
**Date**: {date}
## Summary
| Category | Status | Issues |
|----------|--------|--------|
| PII Protection | PASS/FAIL | {count} |
| Event Registration | PASS/FAIL | {count} |
| Schema Design | PASS/FAIL | {count} |
| Service Patterns | PASS/FAIL | {count} |
| Test Coverage | PASS/FAIL | {count} |
## Issues Found
### [P0] CRITICAL - {title}
**File**: `path:line`
**Issue**: Description
**Fix**: How to fix
### [P1] HIGH - {title}
...
## Recommendations
1. ...
2. ...
[P0] CRITICAL – PII violations, security issues; must fix before merge[P1] HIGH – Missing registrations, pattern violations; strongly recommended[P2] MEDIUM – Test coverage gaps, naming issues; should fix[P3] LOW – Minor improvements; nice to haveAfter review, if issues found:
/mixpanel-analytics:implementIf review passes:
/monty-code-review:code-review for general code quality/backend-atomic-commit:pre-commit for commit preparation.bin/pytest optimo_analytics/tests/ -v --dc=TestLocalAppThis skill is designed to work with both Claude Code and OpenAI Codex.
For Codex users:
--repo DiversioTeam/agent-skills-marketplace --path plugins/mixpanel-analytics/skills/mixpanel-analytics.$skill mixpanel-analytics to invoke.For Claude Code users:
/plugin install mixpanel-analytics@diversiotech./mixpanel-analytics:implement or /mixpanel-analytics:review to invoke.