Skip to content

Dynamic Flight Cache

Pre-fetches and stores flight pricing data for travel products to enable fast price lookups without real-time API calls.

  • Displaying flight prices on product listing pages
  • Calculating total trip costs for offers
  • Price trend analysis and monitoring
  • Batch pricing for seasonal campaigns

The system uses PostgreSQL as the cache storage backend (not Redis or file cache). Flight pricing data is stored in normalized tables with relationships between routes, cache entries, and flight segments.

ProductByMarket
└── ProductByMarketFlightConfig (per departure airport)
└── DynamicFlightCacheRoute (unique route + duration)
└── DynamicFlightCache (pricing per date/CUG)
└── DynamicFlightCacheSegment (flight details)
  1. Route Preparation - Creates route definitions from product flight configs
  2. Cache Entry Creation - Creates pending entries for each searchable date
  3. Search Execution - Calls Aerticket API and stores pricing results
  4. Price Retrieval - Queries completed cache entries for display

Routes Table (dynamic_flight_cache_routes)

Section titled “Routes Table (dynamic_flight_cache_routes)”

Defines unique flight routes for caching. Routes are product-agnostic and identified by flight segments + trip duration.

ColumnTypeDescription
idbigintPrimary key
flight_routejsonbArray of segments with origin, destination, day_offset
trip_duration_dayssmallintTotal trip duration
is_domesticbooleanDomestic flight flag
is_multi_citybooleanMulti-city booking flag
segment_indexsmallintOrder in trip (1-based)
is_activebooleanRoute active for caching

Flight Route JSON Structure:

[
{"origin": "BCN", "destination": "CMB", "day_offset": 0},
{"origin": "CMB", "destination": "MLE", "day_offset": 3},
{"origin": "MLE", "destination": "BCN", "day_offset": 10}
]

Indexes:

  • GIN index on flight_route for JSONB queries
  • Unique constraint on (trip_duration_days, is_multi_city)

Cache Entries Table (dynamic_flight_caches)

Section titled “Cache Entries Table (dynamic_flight_caches)”

Stores pricing data for specific dates and routes.

ColumnTypeDescription
idbigintPrimary key
route_idbigintFK to routes table
provider_idbigintFK to providers table
departure_datedateTrip start date
return_datedateTrip end date (nullable)
statusvarcharCache entry status
searched_attimestampLast search execution time
cug_typevarcharClosed User Group type
request_identifiervarcharAerticket API flow_id
fare_positionsmallintPrice ranking (1=cheapest)
currencyvarcharPrice currency (EUR)
base_pricenumericBase fare amount
taxnumericTax amount
total_pricenumericTotal price
expires_attimestampCache expiration (nullable)
api_response_time_msintegerAPI response time

Indexes:

  • Composite index on (route_id, departure_date, return_date, total_price)
  • Index on status for batch processing
  • Index on cug_type for filtering
  • Index on expires_at for expiration queries
StatusDescriptionCan Search
pendingEntry created, awaiting searchYes
searchingAPI call in progressNo
completedSuccessfully cachedNo
failedSearch failedYes
expiredCache expiredYes

Segments Table (dynamic_flight_cache_segments)

Section titled “Segments Table (dynamic_flight_cache_segments)”

Stores detailed flight information for cached fares.

ColumnTypeDescription
idbigintPrimary key
flight_cache_idbigintFK to caches table
leg_sequencesmallintLeg number (1=outbound)
segment_numberintegerSegment within leg
flight_numbervarcharAirline + flight number
cabin_classvarcharCabin class
departure_airport_idbigintFK to airports
departure_timetimestampDeparture datetime
arrival_airport_idbigintFK to airports
arrival_timetimestampArrival datetime
operating_carriervarcharOperating airline code
baggage_allowancevarcharBaggage info
duration_minutesintegerFlight duration

Main service for preparing routes and executing searches.

Source: backend/app/Services/Flights/DynamicFlightCachePopulatorService.php

$service = app(DynamicFlightCachePopulatorService::class);
// Prepare routes for a single config
$result = $service->prepareRoutesForConfig($config);
// Returns: routes_created, routes_reused, segments, errors
// Prepare routes for entire product
$result = $service->prepareRoutesForProduct($product);
// Returns: configs_processed, routes_created, routes_reused, errors
// Create pending entries for date range
$result = $service->createCacheEntriesForConfig($config);
// Returns: entries_created, entries_existing, errors
// Combined: prepare routes AND create entries
$result = $service->prepareForConfig($config);
// Execute all pending searches
$result = $service->executeSearches();
// Execute specific entries
$result = $service->executeSearches(collect([$cacheEntry]));
// Returns: total_searches, successful_searches, failed_searches, fares_cached, segments_cached, errors

Handles caching search results from manual flight searches.

Source: backend/app/Services/Flights/DynamicFlightCacheService.php

$service = app(DynamicFlightCacheService::class);
// Cache results from a search (only if matching route exists)
$faresCached = $service->cacheSearchResults($searchRequest, $searchResponse);
  • Maximum fares per search: 5 (positions 1-5 by price)
  • Provider: Aerticket (aerticket code)

Cache entries are created with:

  • CUG Type: ALL (default)
  • Currency: EUR
  • Fare Position: 1-5 (1 = cheapest)

The current implementation stores cache entries without automatic expiration (expires_at = null). Entries are manually refreshed through:

  1. Manual Refresh - Admin triggers re-search
  2. Status-Based Refresh - Re-search pending, failed, or expired entries
// Get non-expired entries
DynamicFlightCache::active()->get();
// Get expired entries
DynamicFlightCache::expired()->get();
// Check if entry is expired
$cache->isExpired(); // false if expires_at is null

The DynamicFlightRefreshSchedule model supports scheduled cache refresh with different triggers:

TriggerDescription
scheduledRegular TTL-based refresh
gap_triggeredPrice gap detection
sales_triggeredSales event
manualOperator-initiated
calibrationAccuracy measurement

Source: backend/app/Models/DynamicFlightRefreshSchedule.php

// Find due schedules
$schedules = DynamicFlightRefreshSchedule::due()->get();
// Execute and mark complete
$schedule->markRunning();
// ... execute refresh ...
$schedule->markExecuted();
use App\Models\DynamicFlightCache;
use App\Enums\FlightCugType;
$totalPrice = DynamicFlightCache::getTotalFlightPrice(
flightRoute: 'BCN -> CMB, CMB -> MLE, MLE -> BCN',
tripDuration: 11,
tripStartDate: Carbon::parse('2025-03-01'),
cugType: FlightCugType::ALL
);
// Filter by status
DynamicFlightCache::pending()->get();
DynamicFlightCache::completed()->get();
DynamicFlightCache::searchable()->get(); // pending, failed, expired
// Filter by route type
DynamicFlightCache::domestic()->get();
DynamicFlightCache::international()->get();
// Filter by CUG type
DynamicFlightCache::forCug(FlightCugType::ALL)->get();
// Filter by date range
DynamicFlightCache::forDepartureDateRange($from, $to)->get();

URL: /admin/dynamic-flight-caches

Features:

  • View all cache entries with filtering
  • Execute searches for pending entries
  • View detailed fare and segment information

Execute Searches - Bulk process all searchable entries:

Source: backend/app/Filament/Resources/DynamicFlightCaches/Actions/ExecuteSearchesAction.php

Execute Single Search - Process individual entry:

Source: backend/app/Filament/Resources/DynamicFlightCaches/Actions/ExecuteSingleSearchAction.php

Prepare Flight Searches - Create routes and entries from product:

Source: backend/app/Filament/Resources/ProductsByMarket/Actions/PrepareFlightSearchesAction.php

Immutable value object representing a multi-segment flight route.

Source: backend/app/ValueObjects/FlightRoute.php

// Create from string
$route = FlightRoute::fromString('BCN -> CMB, CMB -> MLE');
// Create from array
$route = FlightRoute::fromArray([
['origin' => 'BCN', 'destination' => 'CMB', 'day_offset' => 0],
['origin' => 'CMB', 'destination' => 'MLE', 'day_offset' => 3],
]);
// Query methods
$route->getOrigin(); // 'BCN'
$route->getDestination(); // 'MLE'
$route->getSegmentCount(); // 2
$route->isMultiSegment(); // true
$route->containsSegment('BCN', 'CMB'); // true
  • Maximum 5 fares cached per search (positions 1-5 by price)
  • Routes are product-agnostic - shared across products with same flight pattern
  • Day offsets determine actual flight dates from trip start date
  • Multi-city routes stored as single combined entry
  • CUG type filtering - searches can target specific fare groups