> $agents Candidate agents already filtered by the application layer. */ public function generate( AvailabilityQuery $query, array $agents ): SlotCollection { if ( [] === $agents ) { return new SlotCollection( [], BusyReason::NoAgents ); } $per_agent_slots = []; $busy_counts = []; foreach ( $agents as $agent ) { $working = $this->working_builder->build( $agent['timezone'], $query->range_start_utc, $query->range_end_utc, $agent['weekly'], $agent['overrides'] ); $collection = $this->slot_generator->generate( $query, $working, $agent['busy'], $agent['agentId'], $agent['capacityUsage'] ?? [] ); $per_agent_slots[ $agent['agentId'] ] = $collection->all(); $busy_counts[ $agent['agentId'] ] = $agent['busyCount'] ?? 0; } if ( null !== $query->agent_id ) { return new SlotCollection( $per_agent_slots[ $query->agent_id ] ?? [] ); } return $this->merge_agents( $per_agent_slots, $busy_counts ); } /** * Merge per-agent slots that share a start instant. * * @param array> $per_agent_slots Slots by agent. * @param array $busy_counts Busy counts for assignment. */ private function merge_agents( array $per_agent_slots, array $busy_counts ): SlotCollection { $by_start = []; foreach ( $per_agent_slots as $agent_id => $slots ) { foreach ( $slots as $slot ) { if ( ! $slot->is_available() ) { continue; } $key = $slot->start()->setTimezone( new DateTimeZone( 'UTC' ) )->format( 'Y-m-d\TH:i:s\Z' ); $by_start[ $key ]['slots'][] = $slot; $by_start[ $key ]['agents'][] = $agent_id; } } ksort( $by_start ); $merged = []; $rr = []; foreach ( $by_start as $group ) { $agents = array_values( array_unique( $group['agents'] ) ); $rr['seed'] = $group['slots'][0]->start()->getTimestamp(); $chosen = $this->assignment->choose( $agents, $busy_counts, $rr ); $total_remaining = 0; $total_capacity = 0; $chosen_slot = null; foreach ( $group['slots'] as $slot ) { $total_remaining += $slot->capacity_remaining(); $total_capacity += $slot->capacity_total(); if ( $slot->agent_id() === $chosen ) { $chosen_slot = $slot; } } if ( null === $chosen_slot ) { $chosen_slot = $group['slots'][0]; } $merged[] = $chosen_slot->with_capacity( $total_capacity, $total_remaining )->with_agent_id( $chosen ); } return new SlotCollection( $merged ); } }