import { ChannelType } from '../Common/Channels/ChannelType'; interface QueueRoutingPolicyBase { requiredSkills?: string[]; optionalSkills?: string[]; acceptedChannelTypes: ChannelType[]; requireActiveParticipation: boolean; requireAvailablePresence: boolean; requireAcceptance: boolean; capacityPolicy: { maxConcurrentByChannelType: Partial>; weightByChannelType: Partial>; }; fallback: { action: 'keep_queued' | 'overflow_queue' | 'assign_supervisor'; overflowQueueId?: string; timeoutSeconds?: number; }; } /** * Resolves the ownerUserId of the contact or business associated with the * conversation. The resolved user is still subject to every Queue eligibility * rule. */ export interface PartyOwnerAssigneeResolver { type: 'party_owner'; mode: 'prefer' | 'required'; } export type QueueOwnerAssigneeResolver = PartyOwnerAssigneeResolver; export type QueueRoutingPolicy = QueueRoutingPolicyBase & { distributionMode: 'shared'; strategy?: never; ownerResolver?: never; } | QueueRoutingPolicyBase & { distributionMode: 'manual_pull'; strategy?: never; ownerResolver?: never; } | QueueRoutingPolicyBase & { distributionMode: 'exclusive'; strategy: QueueRoutingStrategy; ownerResolver?: undefined | PartyOwnerAssigneeResolver & { mode: 'prefer'; }; } | QueueRoutingPolicyBase & { distributionMode: 'exclusive'; strategy?: never; ownerResolver: PartyOwnerAssigneeResolver & { mode: 'required'; }; }; export type QueueRoutingConfig = QueueRoutingPolicy & { queueId: string; spaceId: string; version: number; updatedAt: Date; }; export type QueueDistributionMode = 'shared' | 'exclusive' | 'manual_pull'; export type QueueRoutingStrategy = 'round_robin' | 'least_busy' | 'random' | 'highest_capacity' | 'skills' | 'account_affinity' | 'longest_idle' | 'manual'; export interface RoutingCandidate { userId: string; queueId: string; authorized: boolean; participating: boolean; available: boolean; channelEligible: boolean; capacityUsed: number; capacityTotal: number; lastAssignedAt?: Date; idleSince?: Date; skills: string[]; accountAffinityScore?: number; } export type RoutingPartyType = 'contact' | 'business'; /** * Snapshot resolved by the command producer. The routing worker must not read * Contacts again to obtain ownerUserId. */ export type RoutingPartyContext = { status: 'not_found'; partyType: RoutingPartyType; partyId?: string; ownerUserId?: never; } | { status: 'without_owner'; partyType: RoutingPartyType; partyId: string; ownerUserId?: never; } | { status: 'resolved'; partyType: RoutingPartyType; partyId: string; ownerUserId: string; }; /** * Idempotent request for routing a queued conversation. * * contact/business ownership is captured in party by the producer so the * routing worker can evaluate it against candidates without another read. */ export interface RouteConversationCommand { commandId: string; sourceEventId: string; spaceId: string; conversationId: string; queueId: string; expectedConversationVersion: number; queueConfigVersion: number; party: RoutingPartyContext; requestedAt: Date; } export type RoutingSelectionSource = 'party_owner' | 'strategy' | 'fallback'; export type PartyOwnerResolutionFailure = 'party_not_found' | 'party_without_owner' | 'owner_user_not_found' | 'not_authorized' | 'not_participating' | 'unavailable' | 'channel_ineligible' | 'capacity_exhausted'; type FailedPartyOwnerEvaluation = { party: Extract; outcome: 'party_not_found'; } | { party: Extract; outcome: 'party_without_owner'; } | { party: Extract; outcome: Exclude; }; export type PartyOwnerAssigneeEvaluation = { type: 'party_owner'; mode: PartyOwnerAssigneeResolver['mode']; party: Extract; outcome: 'selected'; nextStep?: never; } | FailedPartyOwnerEvaluation & { type: 'party_owner'; mode: 'prefer'; nextStep: 'strategy'; } | FailedPartyOwnerEvaluation & { type: 'party_owner'; mode: 'required'; nextStep: 'fallback'; }; export interface RoutingDecision { decisionId: string; spaceId: string; conversationId: string; queueId: string; distributionMode: QueueDistributionMode; strategy?: QueueRoutingStrategy; eligibleUserIds: string[]; selectedUserId?: string; selectionSource?: RoutingSelectionSource; ownerEvaluation?: PartyOwnerAssigneeEvaluation; rejected: Array<{ userId: string; reasons: string[]; }>; queueConfigVersion: number; conversationVersion: number; createdAt: Date; } export {};