121 lines
3.4 KiB
TypeScript
121 lines
3.4 KiB
TypeScript
/**
|
|
* Domain Entity: EngagementEvent
|
|
*
|
|
* Represents user interactions beyond page views.
|
|
* Tracks clicks, downloads, sign-ups, and other engagement actions.
|
|
*/
|
|
|
|
export type EngagementAction =
|
|
| 'click_sponsor_logo'
|
|
| 'click_sponsor_url'
|
|
| 'download_livery_pack'
|
|
| 'join_league'
|
|
| 'register_race'
|
|
| 'view_standings'
|
|
| 'view_schedule'
|
|
| 'share_social'
|
|
| 'contact_sponsor';
|
|
|
|
export type EngagementEntityType = 'league' | 'driver' | 'team' | 'race' | 'sponsor' | 'sponsorship';
|
|
|
|
export interface EngagementEventProps {
|
|
id: string;
|
|
action: EngagementAction;
|
|
entityType: EngagementEntityType;
|
|
entityId: string;
|
|
actorId?: string;
|
|
actorType: 'anonymous' | 'driver' | 'sponsor';
|
|
sessionId: string;
|
|
metadata?: Record<string, string | number | boolean>;
|
|
timestamp: Date;
|
|
}
|
|
|
|
export class EngagementEvent {
|
|
readonly id: string;
|
|
readonly action: EngagementAction;
|
|
readonly entityType: EngagementEntityType;
|
|
readonly entityId: string;
|
|
readonly actorId?: string;
|
|
readonly actorType: 'anonymous' | 'driver' | 'sponsor';
|
|
readonly sessionId: string;
|
|
readonly metadata?: Record<string, string | number | boolean>;
|
|
readonly timestamp: Date;
|
|
|
|
private constructor(props: EngagementEventProps) {
|
|
this.id = props.id;
|
|
this.action = props.action;
|
|
this.entityType = props.entityType;
|
|
this.entityId = props.entityId;
|
|
this.actorId = props.actorId;
|
|
this.actorType = props.actorType;
|
|
this.sessionId = props.sessionId;
|
|
this.metadata = props.metadata;
|
|
this.timestamp = props.timestamp;
|
|
}
|
|
|
|
static create(props: Omit<EngagementEventProps, 'timestamp'> & { timestamp?: Date }): EngagementEvent {
|
|
this.validate(props);
|
|
|
|
return new EngagementEvent({
|
|
...props,
|
|
timestamp: props.timestamp ?? new Date(),
|
|
});
|
|
}
|
|
|
|
private static validate(props: Omit<EngagementEventProps, 'timestamp'>): 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<EngagementAction, number> = {
|
|
'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;
|
|
}
|
|
} |