Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,6 @@ public final class Metadata<Score_> implements Status<Score_> {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private String failureMessage;

@Schema(nullable = true,
description = "The map-service region resolved when location is auto-select.")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private String resolvedMapLocation;

public Metadata() {
this((String) null);
}
Expand Down Expand Up @@ -107,7 +102,6 @@ public Metadata(Metadata<Score_> metadata) {
this.parentId = metadata.parentId;
this.originId = metadata.originId;
this.failureMessage = metadata.failureMessage;
this.resolvedMapLocation = metadata.resolvedMapLocation;
}

public String getId() {
Expand Down Expand Up @@ -266,14 +260,6 @@ public void setFailureMessage(String failureMessage) {
this.failureMessage = failureMessage;
}

public String getResolvedMapLocation() {
return resolvedMapLocation;
}

public void setResolvedMapLocation(String resolvedMapLocation) {
this.resolvedMapLocation = resolvedMapLocation;
}

@Override
public void solvingStarted() {
if (solverStatus != SolvingStatus.DATASET_COMPUTED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ public class Headers {

public static final String X_MAPS_PROVIDER_HEADER = "X-TF-MAPS-PROVIDER";

public static final String X_MAPS_LOCATION_HEADER = "X-TF-MAPS-LOCATION";

public static final String X_MAPS_CACHE_ID = "X-TF-MAPS-CACHE-ID";

public static final String X_MAPS_RESPONSE_CHUNK_BYTES = "X-TF-MAPS-CHUNK-BYTES";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +0,0 @@
package ai.timefold.solver.service.definition.internal;

import jakarta.enterprise.context.ApplicationScoped;

/**
* Side-channel used by the maps enricher to publish the resolved map-service location to the
* SolverWorker, which then writes it onto the dataset's {@code Metadata} so it propagates through
* insight events.
*/
@ApplicationScoped
public class MapEnrichmentContext {

private String resolvedMapLocation;

public void setResolvedMapLocation(String location) {
this.resolvedMapLocation = location;
}

public String getResolvedMapLocation() {
return this.resolvedMapLocation;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,6 @@ public class EnvironmentVars {
*/
public static final String ENV_TIMEFOLD_TENANT_NAME = "AI_TIMEFOLD_TENANT_NAME";

/**
* Configured map-service location: either a concrete region (e.g. {@code us-georgia}) or the
* sentinel {@link #MAP_SERVICE_LOCATION_AUTO_SELECT} for runtime region resolution.
*/
public static final String ENV_TIMEFOLD_PLATFORM_MAP_SERVICE_LOCATION = "AI_TIMEFOLD_PLATFORM_MAP_SERVICE_LOCATION";

/**
* Sentinel value for {@link #ENV_TIMEFOLD_PLATFORM_MAP_SERVICE_LOCATION}: tells the maps-service
* to auto-select the region at request time based on the locations being routed.
*/
public static final String MAP_SERVICE_LOCATION_AUTO_SELECT = "auto-select";

/**
* Kubernetes API specific environment variables that are set based on execution information like pod and node
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import jakarta.inject.Inject;

import ai.timefold.solver.service.definition.api.enrichment.SolverModelEnricher;
import ai.timefold.solver.service.definition.internal.MapEnrichmentContext;
import ai.timefold.solver.service.definition.internal.error.ErrorCodes;
import ai.timefold.solver.service.definition.internal.error.TimefoldRuntimeException;
import ai.timefold.solver.service.maps.api.model.Location;
Expand All @@ -31,14 +30,10 @@ public class TravelTimeMatrixEnricher implements SolverModelEnricher<LocationsAw

private final MapServiceOptionsSupplier optionsSupplier;

private final MapEnrichmentContext mapEnrichmentContext;

@Inject
public TravelTimeMatrixEnricher(MapService mapService, MapServiceOptionsSupplier optionsSupplier,
MapEnrichmentContext mapEnrichmentContext) {
public TravelTimeMatrixEnricher(MapService mapService, MapServiceOptionsSupplier optionsSupplier) {
this.mapService = mapService;
this.optionsSupplier = optionsSupplier;
this.mapEnrichmentContext = mapEnrichmentContext;
}

@Retry(maxRetries = 5, delay = 1, delayUnit = ChronoUnit.SECONDS, abortOn = {
Expand Down Expand Up @@ -66,7 +61,6 @@ public LocationsAwareSolverModel<?> enrich(LocationsAwareSolverModel<?> solverMo
location.setDistanceMatrix(travelTimeAndDistance.travelTimeAndDistance().distance());
});
solverModel.setLocationsNotInMap(convertIdxToLocations(travelTimeAndDistance.locationsNotInMapIdx(), locations));
mapEnrichmentContext.setResolvedMapLocation(travelTimeAndDistance.resolvedMapLocation());
return solverModel;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
import ai.timefold.solver.service.maps.service.integration.internal.model.TravelTimeAndDistance;

public record CacheItem(TravelTimeAndDistance travelTimeAndDistance, List<Location> locations, String hash,
List<Integer> locationsOutOfMap, String resolvedMapLocation) {
List<Integer> locationsOutOfMap) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import static ai.timefold.solver.service.definition.internal.Headers.X_MAPS_INVALIDATE_MATRIX_HEADER;
import static ai.timefold.solver.service.definition.internal.Headers.X_MAPS_LOCATIONS_CHUNK_BYTES;
import static ai.timefold.solver.service.definition.internal.Headers.X_MAPS_LOCATIONS_NOT_IN_MAP;
import static ai.timefold.solver.service.definition.internal.Headers.X_MAPS_LOCATION_HEADER;
import static ai.timefold.solver.service.definition.internal.Headers.X_MAPS_MATRIX_HASH_HEADER;
import static ai.timefold.solver.service.definition.internal.Headers.X_MAPS_PROVIDER_HEADER;
import static ai.timefold.solver.service.definition.internal.Headers.X_MAPS_RESPONSE_CHUNK_BYTES;
Expand Down Expand Up @@ -136,9 +135,8 @@ public TravelTimeAndDistanceWithMetadata getTravelTimeAndDistance(List<Location>
// If there are no updates, return from cache
LOGGER.info("Distance matrix in cache is up-to-date, returning from cache");
assertLocationsAreInCache(locations);
CacheItem cached = travelTimeAndDistanceSingleItemCache.get();
return new TravelTimeAndDistanceWithMetadata(cached.travelTimeAndDistance(),
cached.locationsOutOfMap(), cached.resolvedMapLocation());
return new TravelTimeAndDistanceWithMetadata(travelTimeAndDistanceSingleItemCache.get().travelTimeAndDistance(),
travelTimeAndDistanceSingleItemCache.get().locationsOutOfMap());
} else {
// If there are updates, process them and update cache
LOGGER.info("Distance matrix in cache is not up-to-date, processing updates");
Expand Down Expand Up @@ -207,8 +205,7 @@ private TravelTimeAndDistanceWithMetadata getFromCacheOrRequest(List<Location> l
if (travelTimeAndDistanceSingleItemCache.isInCache(id)) {
LOGGER.info("Distance matrix without location set name in cache, returning from cache");
CacheItem cacheItem = travelTimeAndDistanceSingleItemCache.get();
return new TravelTimeAndDistanceWithMetadata(cacheItem.travelTimeAndDistance(), cacheItem.locationsOutOfMap(),
cacheItem.resolvedMapLocation());
return new TravelTimeAndDistanceWithMetadata(cacheItem.travelTimeAndDistance(), cacheItem.locationsOutOfMap());
}

// If it does not exist, request from maps-service and store by hash of locations
Expand Down Expand Up @@ -240,7 +237,6 @@ private TravelTimeAndDistanceWithMetadata getAndStoreInCache(List<Location> loca
private TravelTimeAndDistanceWithMetadata processResponseAndStoreInCache(Response response, String localCacheId) {
String matrixHash = response.getHeaderString(X_MAPS_MATRIX_HASH_HEADER);
String provider = response.getHeaderString(X_MAPS_PROVIDER_HEADER);
String resolvedMapLocation = response.getHeaderString(X_MAPS_LOCATION_HEADER);
String tenant = response.getHeaderString(X_TENANT_ID_HEADER);
String cacheId = response.getHeaderString(X_MAPS_CACHE_ID);
String locationsNotInMapString = response.getHeaderString(X_MAPS_LOCATIONS_NOT_IN_MAP);
Expand All @@ -260,13 +256,11 @@ private TravelTimeAndDistanceWithMetadata processResponseAndStoreInCache(Respons
throw new IllegalArgumentException("No provider found to convert travel time and distance response.");
}

TravelTimeAndDistanceWithMetadata raw =
TravelTimeAndDistanceWithMetadata travelTimeAndDistance =
convertResponse(provider, chunkBytes, responseLocations, data, locationsNotInMap);
TravelTimeAndDistanceWithMetadata travelTimeAndDistance = new TravelTimeAndDistanceWithMetadata(
raw.travelTimeAndDistance(), raw.locationsNotInMapIdx(), resolvedMapLocation);
travelTimeAndDistanceSingleItemCache.put(localCacheId,
new CacheItem(travelTimeAndDistance.travelTimeAndDistance(), responseLocations, matrixHash,
locationsNotInMap, resolvedMapLocation));
locationsNotInMap));
return travelTimeAndDistance;

} catch (IllegalDistanceResponseException e) {
Expand All @@ -281,7 +275,6 @@ private TravelTimeAndDistanceWithMetadata processResponseAndStoreInCache(Respons
private TravelTimeAndDistanceWithMetadata processUpdateAndStoreInCache(Response response, String locationSetName) {
String matrixHash = response.getHeaderString(X_MAPS_MATRIX_HASH_HEADER);
String provider = response.getHeaderString(X_MAPS_PROVIDER_HEADER);
String resolvedMapLocation = response.getHeaderString(X_MAPS_LOCATION_HEADER);
String tenant = response.getHeaderString(X_TENANT_ID_HEADER);
String cacheId = response.getHeaderString(X_MAPS_CACHE_ID);
String locationsNotInMapString = response.getHeaderString(X_MAPS_LOCATIONS_NOT_IN_MAP);
Expand All @@ -301,18 +294,15 @@ private TravelTimeAndDistanceWithMetadata processUpdateAndStoreInCache(Response
throw new IllegalArgumentException("No provider found to convert travel time and distance update.");
}

TravelTimeAndDistanceWithMetadata raw =
TravelTimeAndDistanceWithMetadata travelTimeAndDistance =
convertUpdate(provider, chunkBytes, responseLocations, data, cacheItem.locationsOutOfMap(),
locationsNotInMap);
String effectiveMapLocation = resolvedMapLocation != null ? resolvedMapLocation : cacheItem.resolvedMapLocation();
TravelTimeAndDistanceWithMetadata travelTimeAndDistance = new TravelTimeAndDistanceWithMetadata(
raw.travelTimeAndDistance(), raw.locationsNotInMapIdx(), effectiveMapLocation);

List<Location> newLocations = Stream.concat(cacheItem.locations().stream(), responseLocations.stream()).toList();
if (locationSetName != null && matrixHash != null) {
travelTimeAndDistanceSingleItemCache.put(locationSetName,
new CacheItem(travelTimeAndDistance.travelTimeAndDistance(), newLocations, matrixHash,
locationsNotInMap, effectiveMapLocation));
locationsNotInMap));
}
return travelTimeAndDistance;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
package ai.timefold.solver.service.maps.service.client.api;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import jakarta.inject.Inject;

import ai.timefold.solver.service.definition.internal.MapEnrichmentContext;
import ai.timefold.solver.service.maps.api.model.Location;
import ai.timefold.solver.service.maps.api.model.travel.TravelDistance;
import ai.timefold.solver.service.maps.api.model.travel.TravelTime;
import ai.timefold.solver.service.maps.service.client.util.RemoteMapServiceConfigurationProfile;
import ai.timefold.solver.service.maps.service.client.util.SampleModel;
import ai.timefold.solver.service.maps.service.integration.api.LocationsAwareSolverModel;
import ai.timefold.solver.service.maps.service.test.api.MapServiceApiWiremockExtensions;
import ai.timefold.solver.service.maps.service.test.impl.DistanceGetUpdateResponseTransformer;
import ai.timefold.solver.service.maps.service.test.impl.HaversineDistanceResponseTransformer;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
Expand All @@ -32,9 +28,6 @@ public class TravelTimeMatrixEnricherTest {
@Inject
TravelTimeMatrixEnricher enricher;

@Inject
MapEnrichmentContext mapEnrichmentContext;

@Test
void testRemoteConnectionWithMapServer() {
Location l1 = new Location(0, 0);
Expand All @@ -48,8 +41,6 @@ void testRemoteConnectionWithMapServer() {
Assertions.assertThat(enrich.getLocations().getFirst().getTravelTimeTo(l2)).isEqualTo(TravelTime.of(11322L));
Assertions.assertThat(enrich.getLocations().getFirst().getDistanceTo(l2)).isEqualTo(TravelDistance.of(157249L));
Assertions.assertThat(enrich.getLocationsNotInMap()).isEmpty();
Assertions.assertThat(mapEnrichmentContext.getResolvedMapLocation())
.isEqualTo(HaversineDistanceResponseTransformer.RESOLVED_MAP_LOCATION);
}

@Test
Expand Down Expand Up @@ -106,8 +97,6 @@ void testDistanceMatrixWithUpdates() {
Assertions.assertThat(enrich.getLocations().get(1).getDistanceTo(l1)).isEqualTo(TravelDistance.of(157249L));
Assertions.assertThat(enrich.getLocations().get(2).getDistanceTo(l4)).isEqualTo(TravelDistance.of(157178L));
Assertions.assertThat(enrich.getLocationsNotInMap()).isEmpty();
Assertions.assertThat(mapEnrichmentContext.getResolvedMapLocation())
.isEqualTo(HaversineDistanceResponseTransformer.RESOLVED_MAP_LOCATION);
}

@Test
Expand Down Expand Up @@ -164,27 +153,4 @@ void testDistanceMatrixWithLocationsOutOfMap() {
Assertions.assertThat(enrich.getLocations().get(2).getDistanceTo(l4)).isEqualTo(TravelDistance.ZERO);
}

@Test
void updatePathFallsBackToCachedMapLocationWhenHeaderIsMissing() {
// First enrich populates the cache for "with-updates" via the POST full-matrix path,
// which emits X_MAPS_LOCATION_HEADER and cache stores resolvedMapLocation="us-georgia".
SampleModel seed = new SampleModel(DistanceGetUpdateResponseTransformer.UPDATE_AWARE_LOCATION_SET_NAME,
DistanceGetUpdateResponseTransformer.UPDATE_OLD_LOCATIONS);
enricher.enrich(seed);
Assertions.assertThat(mapEnrichmentContext.getResolvedMapLocation())
.isEqualTo(HaversineDistanceResponseTransformer.RESOLVED_MAP_LOCATION);

// Second enrich adds a new location and cache hit triggers the GET-update path.
// The transformer returns a 200 update WITHOUT X_MAPS_LOCATION_HEADER, so
// processUpdateAndStoreInCache must fall back to the value stored in the CacheItem.
List<Location> withNewLocation = new ArrayList<>(DistanceGetUpdateResponseTransformer.UPDATE_OLD_LOCATIONS);
withNewLocation.addAll(DistanceGetUpdateResponseTransformer.UPDATE_NEW_LOCATIONS);
SampleModel updated = new SampleModel(DistanceGetUpdateResponseTransformer.UPDATE_AWARE_LOCATION_SET_NAME,
withNewLocation);
enricher.enrich(updated);

Assertions.assertThat(mapEnrichmentContext.getResolvedMapLocation())
.isEqualTo(HaversineDistanceResponseTransformer.RESOLVED_MAP_LOCATION);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,5 @@
import java.util.List;

public record TravelTimeAndDistanceWithMetadata(TravelTimeAndDistance travelTimeAndDistance,
List<Integer> locationsNotInMapIdx, String resolvedMapLocation) {

public TravelTimeAndDistanceWithMetadata(TravelTimeAndDistance travelTimeAndDistance,
List<Integer> locationsNotInMapIdx) {
this(travelTimeAndDistance, locationsNotInMapIdx, null);
}
List<Integer> locationsNotInMapIdx) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,5 @@
import java.io.InputStream;
import java.util.List;

public record TravelTimeAndDistanceMatrixResponse(InputStream response, List<Integer> locationsOutOfMapIndexes,
String resolvedMapLocation) {

public TravelTimeAndDistanceMatrixResponse(InputStream response, List<Integer> locationsOutOfMapIndexes) {
this(response, locationsOutOfMapIndexes, null);
}
public record TravelTimeAndDistanceMatrixResponse(InputStream response, List<Integer> locationsOutOfMapIndexes) {
}
Loading
Loading