import type { FlightTicket } from "../data/entities"; import { Logger } from "@pkg/logger"; import { round } from "$lib/core/num.utils"; import { type Result } from "@pkg/result"; import { type LimitedOrderWithTicketInfoModel } from "$lib/domains/order/data/entities"; import { getJustDateString } from "@pkg/logic/core/date.utils"; type OrderWithUsageInfo = { order: LimitedOrderWithTicketInfoModel; usePartialAmount: boolean; }; export class TicketWithOrderSkiddaddler { THRESHOLD_DIFF_PERCENTAGE = 15; ELEVATED_THRESHOLD_DIFF_PERCENTAGE = 50; tickets: FlightTicket[]; private reservedOrders: number[]; private allActiveOrders: LimitedOrderWithTicketInfoModel[]; private urgentActiveOrders: LimitedOrderWithTicketInfoModel[]; // Stores oids that have their amount divided into smaller amounts for being reused for multiple tickets // this record keeps track of the oid, with how much remainder is left to be divided private reservedPartialOrders: Record; private minTicketPrice: number; private maxTicketPrice: number; constructor( tickets: FlightTicket[], allActiveOrders: LimitedOrderWithTicketInfoModel[], ) { this.tickets = tickets; this.reservedOrders = []; this.reservedPartialOrders = {}; this.minTicketPrice = 0; this.maxTicketPrice = 0; this.allActiveOrders = []; this.urgentActiveOrders = []; this.loadupActiveOrders(allActiveOrders); } async magic(): Promise> { // Sorting the orders by price in ascending order this.allActiveOrders.sort((a, b) => b.basePrice - a.basePrice); this.loadMinMaxPrices(); for (const ticket of this.tickets) { if (this.areAllOrdersUsedUp()) { break; } const suitableOrders = this.getSuitableOrders(ticket); if (!suitableOrders) { continue; } console.log("--------- suitable orders ---------"); console.log(suitableOrders); console.log("-----------------------------------"); const [discountAmt, newDisplayPrice] = this.calculateNewAmounts( ticket, suitableOrders, ); ticket.priceDetails.discountAmount = discountAmt; ticket.priceDetails.displayPrice = newDisplayPrice; const oids = Array.from( new Set(suitableOrders.map((o) => o.order.id)), ).toSorted(); ticket.refOIds = oids; this.reservedOrders.push(...oids); } Logger.debug(`Assigned ${this.reservedOrders.length} orders to tickets`); return { data: true }; } private loadupActiveOrders(data: LimitedOrderWithTicketInfoModel[]) { const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); // Removing all orders which have tickets with departure date of yesterday and older this.allActiveOrders = data.filter((o) => { return ( new Date( getJustDateString(new Date(o.flightTicketInfo.departureDate)), ) >= today ); }); this.urgentActiveOrders = []; const threeDaysFromNowMs = 3 * 24 * 3600 * 1000; for (const order of this.allActiveOrders) { const depDate = new Date(order.flightTicketInfo.departureDate); if (now.getTime() + threeDaysFromNowMs > depDate.getTime()) { this.urgentActiveOrders.push(order); } } Logger.info(`Found ${this.allActiveOrders.length} active orders`); Logger.info(`We got ${this.urgentActiveOrders.length} urgent orders`); } private loadMinMaxPrices() { this.minTicketPrice = 100000; this.maxTicketPrice = 0; for (const ticket of this.tickets) { const _dPrice = ticket.priceDetails.displayPrice; if (_dPrice < this.minTicketPrice) { this.minTicketPrice = _dPrice; } if (_dPrice > this.maxTicketPrice) { this.maxTicketPrice = _dPrice; } } } private areAllOrdersUsedUp() { if (this.urgentActiveOrders.length === 0) { return this.reservedOrders.length >= this.allActiveOrders.length; } const areUrgentOrdersUsedUp = this.reservedOrders.length >= this.urgentActiveOrders.length; return areUrgentOrdersUsedUp; } /** * An order is used up in 2 cases * 1. it's not being partially fulfulled and it's found in the used orders list * 2. it's being partially fulfilled and it's found in both the used orders list and sub chunked orders list */ private isOrderUsedUp(oid: number, displayPrice: number) { if (this.reservedPartialOrders.hasOwnProperty(oid)) { return this.reservedPartialOrders[oid] < displayPrice; } return this.reservedOrders.includes(oid); } private calculateNewAmounts( ticket: FlightTicket, orders: OrderWithUsageInfo[], ) { if (orders.length < 1) { return [ ticket.priceDetails.discountAmount, ticket.priceDetails.displayPrice, ]; } const totalAmount = orders.reduce((sum, { order, usePartialAmount }) => { return ( sum + (usePartialAmount ? order.pricePerPassenger : order.displayPrice) ); }, 0); const discountAmt = round(ticket.priceDetails.displayPrice - totalAmount); return [discountAmt, totalAmount]; } /** * Suitable orders are those orders that are ideally within the threshold and are not already reserved. So there's a handful of cases, which other sub methods are handling */ private getSuitableOrders( ticket: FlightTicket, ): OrderWithUsageInfo[] | undefined { const activeOrders = this.urgentActiveOrders.length > 0 ? this.urgentActiveOrders : this.allActiveOrders; const cases = [ (t: any, a: any) => { return this.findFirstSuitableDirectOId(t, a); }, (t: any, a: any) => { return this.findFirstSuitablePartialOId(t, a); }, (t: any, a: any) => { return this.findManySuitableOIds(t, a); }, (t: any, a: any) => { return this.findFirstSuitableDirectOIdElevated(t, a); }, ]; let c = 1; for (const each of cases) { const out = each(ticket, activeOrders); if (out) { console.log(`Case ${c} worked, returning it's response`); return out; } c++; } console.log("NO CASE WORKED, WUT, yee"); } private findFirstSuitableDirectOId( ticket: FlightTicket, activeOrders: LimitedOrderWithTicketInfoModel[], ): OrderWithUsageInfo[] | undefined { let ord: LimitedOrderWithTicketInfoModel | undefined; for (const o of activeOrders) { const [op, tp] = [o.displayPrice, ticket.priceDetails.displayPrice]; const diff = Math.abs(op - tp); const diffPtage = (diff / tp) * 100; if (this.isOrderSuitable(o.id, op, tp, diffPtage)) { ord = o; break; } } if (!ord) { return; } return [{ order: ord, usePartialAmount: false }]; } private findFirstSuitablePartialOId( ticket: FlightTicket, activeOrders: LimitedOrderWithTicketInfoModel[], ): OrderWithUsageInfo[] | undefined { const tp = ticket.priceDetails.displayPrice; let ord: LimitedOrderWithTicketInfoModel | undefined; for (const o of activeOrders) { const op = o.pricePerPassenger; const diff = op - tp; const diffPtage = (diff / tp) * 100; if (this.isOrderSuitable(o.id, op, tp, diffPtage)) { ord = o; break; } } if (!ord) { return; } this.upsertPartialOrderToCache( ord.id, ord.pricePerPassenger, ord.fullfilledPrice, ); return [{ order: ord, usePartialAmount: true }]; } private findManySuitableOIds( ticket: FlightTicket, activeOrders: LimitedOrderWithTicketInfoModel[], ): OrderWithUsageInfo[] | undefined { const targetPrice = ticket.priceDetails.displayPrice; const validOrderOptions = activeOrders.flatMap((order) => { const options: OrderWithUsageInfo[] = []; // Add full price option if suitable if ( !this.isOrderUsedUp(order.id, order.displayPrice) && order.displayPrice <= targetPrice ) { options.push({ order, usePartialAmount: false }); } // Add partial price option if suitable if ( !this.isOrderUsedUp(order.id, order.pricePerPassenger) && order.pricePerPassenger <= targetPrice ) { options.push({ order, usePartialAmount: true }); } return options; }); if (validOrderOptions.length === 0) return undefined; // Helper function to get the effective price of an order const getEffectivePrice = (ordInfo: { order: LimitedOrderWithTicketInfoModel; usePerPassenger: boolean; }) => { return ordInfo.usePerPassenger ? ordInfo.order.pricePerPassenger : ordInfo.order.displayPrice; }; const sumCombination = (combo: OrderWithUsageInfo[]): number => { return combo.reduce( (sum, { order, usePartialAmount }) => sum + (usePartialAmount ? order.pricePerPassenger : order.displayPrice), 0, ); }; let bestCombination: OrderWithUsageInfo[] = []; let bestDiff = targetPrice; // Try combinations of different sizes for ( let size = 1; size <= Math.min(validOrderOptions.length, 3); size++ ) { const combinations = this.getCombinations(validOrderOptions, size); for (const combo of combinations) { const sum = sumCombination(combo); const diff = targetPrice - sum; if ( diff >= 0 && (diff / targetPrice) * 100 <= this.THRESHOLD_DIFF_PERCENTAGE && diff < bestDiff ) { // Verify we're not using the same order twice const orderIds = new Set(combo.map((c) => c.order.id)); if (orderIds.size === combo.length) { bestDiff = diff; bestCombination = combo; } } } } if (bestCombination.length === 0) return undefined; // Update partial orders cache bestCombination.forEach(({ order, usePartialAmount }) => { if (usePartialAmount) { this.upsertPartialOrderToCache( order.id, order.pricePerPassenger, order.fullfilledPrice, ); } }); return bestCombination; } /** * In case no other cases worked, but we have an order with far lower price amount, * then we juss use it but bump up it's amount to match the order */ private findFirstSuitableDirectOIdElevated( ticket: FlightTicket, activeOrders: LimitedOrderWithTicketInfoModel[], ): OrderWithUsageInfo[] | undefined { const targetPrice = ticket.priceDetails.displayPrice; // Find the first unreserved order with the smallest price difference let bestOrder: LimitedOrderWithTicketInfoModel | undefined; let smallestPriceDiff = Number.MAX_VALUE; for (const order of activeOrders) { if (this.isOrderUsedUp(order.id, order.displayPrice)) { continue; } // Check both regular price and per-passenger price const prices = [order.displayPrice]; if (order.pricePerPassenger) { prices.push(order.pricePerPassenger); } for (const price of prices) { const diff = Math.abs(targetPrice - price); if (diff < smallestPriceDiff) { smallestPriceDiff = diff; bestOrder = order; } } } if (!bestOrder) { return undefined; } // Calculate if we should use partial amount const usePartial = bestOrder.pricePerPassenger && Math.abs(targetPrice - bestOrder.pricePerPassenger) < Math.abs(targetPrice - bestOrder.displayPrice); // The price will be elevated to match the ticket price // We'll use the original price ratio to determine the new display price const originalPrice = usePartial ? bestOrder.pricePerPassenger : bestOrder.displayPrice; // Only elevate if the price difference is reasonable const priceDiffPercentage = (Math.abs(targetPrice - originalPrice) / targetPrice) * 100; if (priceDiffPercentage > this.ELEVATED_THRESHOLD_DIFF_PERCENTAGE) { return undefined; } // if (usePartial) { // bestOrder.pricePerPassenger = // } else { // } return [{ order: bestOrder, usePartialAmount: !!usePartial }]; } private getCombinations(arr: T[], size: number): T[][] { if (size === 0) return [[]]; if (arr.length === 0) return []; const first = arr[0]; const rest = arr.slice(1); const combosWithoutFirst = this.getCombinations(rest, size); const combosWithFirst = this.getCombinations(rest, size - 1).map( (combo) => [first, ...combo], ); return [...combosWithoutFirst, ...combosWithFirst]; } private isOrderSuitable( orderId: number, orderPrice: number, ticketPrice: number, diffPercentage: number, ) { return ( diffPercentage > 0 && diffPercentage <= this.THRESHOLD_DIFF_PERCENTAGE && orderPrice <= ticketPrice && !this.isOrderUsedUp(orderId, orderPrice) ); } private upsertPartialOrderToCache( oid: number, price: number, _defaultPrice: number = 0, ) { if (!this.reservedPartialOrders[oid]) { this.reservedPartialOrders[oid] = _defaultPrice; return; } this.reservedPartialOrders[oid] += price; } }