/** * Domain Entity: EngagementEvent * * Represents user interactions beyond page views. * Tracks clicks, downloads, sign-ups, and other engagement actions. */ import type { IEntity } from '@gridpilot/shared/domain'; import type { EngagementAction, EngagementEntityType, EngagementEventProps, } from '../types/EngagementEvent'; export type { EngagementAction, EngagementEntityType } from '../types/EngagementEvent'; import { AnalyticsEntityId } from '../value-objects/AnalyticsEntityId'; export class EngagementEvent implements IEntity { readonly id: string; readonly action: EngagementAction; readonly entityType: EngagementEntityType; readonly actorId: string | undefined; readonly actorType: 'anonymous' | 'driver' | 'sponsor'; readonly sessionId: string; readonly metadata: Record | undefined; readonly timestamp: Date; private readonly entityIdVo: AnalyticsEntityId; private constructor(props: EngagementEventProps) { this.id = props.id; this.action = props.action; this.entityType = props.entityType; this.entityIdVo = AnalyticsEntityId.create(props.entityId); this.actorId = props.actorId; this.actorType = props.actorType; this.sessionId = props.sessionId; this.metadata = props.metadata; this.timestamp = props.timestamp; } get entityId(): string { return this.entityIdVo.value; } static create(props: Omit & { timestamp?: Date }): EngagementEvent { this.validate(props); return new EngagementEvent({ ...props, timestamp: props.timestamp ?? new Date(), }); } private static validate(props: Omit): void { if (!props.id || props.id.trim().length === 0) { throw new Error('EngagementEvent ID is required'); } if (!props.action) { throw new Error('EngagementEvent action is required'); } if (!props.entityType) { throw new Error('EngagementEvent entityType is required'); } if (!props.entityId || props.entityId.trim().length === 0) { throw new Error('EngagementEvent entityId is required'); } if (!props.sessionId || props.sessionId.trim().length === 0) { throw new Error('EngagementEvent sessionId is required'); } } /** * Check if this is a sponsor-related engagement */ isSponsorEngagement(): boolean { return this.action.startsWith('click_sponsor') || this.action === 'contact_sponsor' || this.entityType === 'sponsor' || this.entityType === 'sponsorship'; } /** * Check if this is a conversion event (high-value action) */ isConversionEvent(): boolean { return ['join_league', 'register_race', 'click_sponsor_url', 'contact_sponsor'].includes(this.action); } /** * Get engagement weight for analytics calculations */ getEngagementWeight(): number { const weights: Record = { 'click_sponsor_logo': 2, 'click_sponsor_url': 5, 'download_livery_pack': 3, 'join_league': 10, 'register_race': 8, 'view_standings': 1, 'view_schedule': 1, 'share_social': 4, 'contact_sponsor': 15, }; return weights[this.action] || 1; } }