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
12 changes: 6 additions & 6 deletions src/main/java/com/p2ps/service/DataDecayService.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ public class DataDecayService {
@Value("${data-decay.enabled:true}")
private boolean dataDecayEnabled = true;

@Value("${data-decay.penalty:0.02}")
private double penalty = 0.02d;
@Value("${data-decay.penalty:0.1}")
private double penalty = 0.1d;

@Value("${data-decay.cutoff-days:14}")
private long cutoffDays = 14L;
@Value("${data-decay.cutoff-days:3}")
private long cutoffDays = 3L;

@Value("${data-decay.min-confidence-floor:0.15}")
private double minConfidenceFloor = 0.15d;
@Value("${data-decay.min-confidence-floor:0.05}")
private double minConfidenceFloor = 0.05d;

public DataDecayService(StoreInventoryMapRepository repository) {
this.repository = repository;
Expand Down
33 changes: 19 additions & 14 deletions src/main/java/com/p2ps/service/LocationProcessorWorker.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
public class LocationProcessorWorker {

private static final Logger logger = LoggerFactory.getLogger(LocationProcessorWorker.class);
private static final double LOW_CONFIDENCE_THRESHOLD = 0.4d;
private static final int MIN_PING_COUNT_FOR_CONFIDENCE = 5;
private static final double LOW_CONFIDENCE_THRESHOLD = 0.1d;
private static final int MIN_PING_COUNT_FOR_CONFIDENCE = 1;
private static final AtomicLong RAPID_RECALCULATION_FAILURES = new AtomicLong();

private final JdbcTemplate jdbcTemplate;
Expand Down Expand Up @@ -170,13 +170,15 @@ public void processAndCalculateCenters() {
String sql = """
INSERT INTO store_inventory_map (map_id, store_id, item_id, estimated_loc_point, confidence_score, ping_count, last_updated)
WITH FilteredPings AS (
SELECT item_id, store_id, location_point, accuracy_m
SELECT item_id, store_id, location_point, accuracy_m, marked_at
FROM raw_user_pings
WHERE loc_provider IN ('WIFI_RTT', 'GPS') AND accuracy_m < 30.0
WHERE loc_provider IN ('WIFI_RTT', 'GPS')
AND accuracy_m < 30.0
AND marked_at > NOW() - INTERVAL '3 days'
),
ClusteredData AS (
SELECT
item_id, store_id, location_point, accuracy_m,
item_id, store_id, location_point, accuracy_m, marked_at,
ST_ClusterDBSCAN(ST_Transform(location_point, 3857), eps := 30.0, minpoints := 1)
OVER (PARTITION BY store_id, item_id) AS cluster_id
FROM FilteredPings
Expand All @@ -187,8 +189,9 @@ ClusterStats AS (
item_id,
cluster_id,
ST_GeometricMedian(ST_Collect(location_point)) AS estimated_loc_point,
LEAST(1.0, (COUNT(item_id) / 50.0) * (1.0 / GREATEST(1.0, AVG(accuracy_m)))) AS confidence_score,
COUNT(item_id) AS ping_count
1.0 AS confidence_score,
COUNT(item_id) AS ping_count,
MAX(marked_at) AS last_seen
FROM ClusteredData
WHERE cluster_id IS NOT NULL
GROUP BY store_id, item_id, cluster_id
Expand All @@ -200,14 +203,14 @@ SELECT DISTINCT ON (store_id, item_id)
estimated_loc_point,
confidence_score,
ping_count,
NOW()
last_seen
FROM ClusterStats
ORDER BY store_id, item_id, ping_count DESC
ON CONFLICT (store_id, item_id) DO UPDATE SET
estimated_loc_point = EXCLUDED.estimated_loc_point,
confidence_score = EXCLUDED.confidence_score,
ping_count = EXCLUDED.ping_count,
last_updated = NOW()
last_updated = EXCLUDED.last_updated
""";

int insertedRows = jdbcTemplate.update(sql);
Expand Down Expand Up @@ -239,14 +242,15 @@ public CompletableFuture<Void> recalculateSingleItem(UUID storeId, UUID itemId)
try {
String sql = """
WITH ItemPings AS (
SELECT location_point, accuracy_m
SELECT location_point, accuracy_m, marked_at
FROM raw_user_pings
WHERE store_id = ? AND item_id = ?
AND loc_provider IN ('WIFI_RTT', 'GPS')
AND accuracy_m < 30.0
AND marked_at > NOW() - INTERVAL '3 days'
),
Clustered AS (
SELECT location_point, accuracy_m,
SELECT location_point, accuracy_m, marked_at,
ST_ClusterDBSCAN(ST_Transform(location_point, 3857), eps := 30.0, minpoints := 1)
OVER () AS cluster_id
FROM ItemPings
Expand All @@ -255,8 +259,9 @@ ClusterStats AS (
SELECT
cluster_id,
ST_GeometricMedian(ST_Collect(location_point)) AS estimated_loc_point,
LEAST(1.0, (COUNT(*) / 50.0) * (1.0 / GREATEST(1.0, AVG(accuracy_m)))) AS confidence_score,
COUNT(*) AS ping_count
1.0 AS confidence_score,
COUNT(*) AS ping_count,
MAX(marked_at) AS last_seen
FROM Clustered
WHERE cluster_id IS NOT NULL
GROUP BY cluster_id
Expand All @@ -267,7 +272,7 @@ ClusterStats AS (
SET estimated_loc_point = COALESCE(stats.estimated_loc_point, inventory.estimated_loc_point),
confidence_score = COALESCE(stats.confidence_score, inventory.confidence_score),
ping_count = COALESCE(stats.ping_count, inventory.ping_count),
last_updated = NOW()
last_updated = COALESCE(stats.last_seen, inventory.last_updated)
FROM ClusterStats stats
WHERE inventory.store_id = ? AND inventory.item_id = ?
""";
Expand Down
13 changes: 10 additions & 3 deletions src/main/java/com/p2ps/service/RoutingAsyncService.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,17 @@ public void completeRouteAsync(String routeId,
try {
List<RoutePoint> optimized = optimizer.threeOptImprove(fullNnRoute);

// BE 3.2 Add audio instructions
// BE 3.2 Add audio instructions (must do while user_loc is present to get first segment)
RoutingService routingService = applicationContext.getBean(RoutingService.class);
routingService.addAudioInstructions(optimized);

double fullDistance = optimizer.routeDistance(optimized);

// Remove user point from the list sent to UI (issue: redundant with blue ping)
if (!optimized.isEmpty() && "user_loc".equals(optimized.getFirst().getItemId())) {
optimized.removeFirst();
}

// Updated instantiation to use constructor and setters
RoutingResponse fullResponse = new RoutingResponse();
fullResponse.setStatus("success");
Expand All @@ -75,8 +82,8 @@ public void completeRouteAsync(String routeId,
fullResponse.setWarnings(warnings);
fullResponse.setPartial(false);

fullResponse.setTotalDistanceMeters(optimizer.routeDistance(optimized));
fullResponse.setTotalStops(optimized.size() - 1);
fullResponse.setTotalDistanceMeters(fullDistance);
fullResponse.setTotalStops(optimized.size());
fullResponse.setEstimatedTimeSeconds((int) (fullResponse.getTotalDistanceMeters() / 1.4));

String json = objectMapper.writeValueAsString(fullResponse);
Expand Down
29 changes: 21 additions & 8 deletions src/main/java/com/p2ps/service/RoutingService.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
return errorResponse;
}

RoutePoint userPoint = new RoutePoint("user_loc", "Tu", request.getUserLat(), request.getUserLng());
RoutePoint userPoint = new RoutePoint("user_loc", "", request.getUserLat(), request.getUserLng(), "USER");

Check failure on line 80 in src/main/java/com/p2ps/service/RoutingService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of duplicating this literal "user_loc" 3 times.

See more on https://sonarcloud.io/project/issues?id=P2P-Shopping_P2P-Shopping&issues=AZ6I5WzIy_nVXzNPfBHZ&open=AZ6I5WzIy_nVXzNPfBHZ&pullRequest=296

// Full NN route (fast — always computed eagerly)
List<RoutePoint> nnRoute = new ArrayList<>();
Expand All @@ -95,6 +95,15 @@
List<RoutePoint> optimizedRoute = optimizer.threeOptImprove(nnRoute);
logImprovement(nnRoute, optimizedRoute);
addAudioInstructions(optimizedRoute); // BE 3.2

// --- Injectarea metricilor pentru răspunsul complet (Eager) ---
double distEager = optimizer.routeDistance(optimizedRoute);

// Remove user point from the list sent to UI (issue: redundant with blue ping)
if (!optimizedRoute.isEmpty() && "user_loc".equals(optimizedRoute.getFirst().getItemId())) {
optimizedRoute.removeFirst();
}

logger.info("Ruta calculata: {} puncte, {} warnings", optimizedRoute.size(), warnings.size());

RoutingResponse response = new RoutingResponse();
Expand All @@ -103,8 +112,6 @@
response.setWarnings(warnings);
response.setPartial(false);

// --- Injectarea metricilor pentru răspunsul complet (Eager) ---
double distEager = optimizer.routeDistance(optimizedRoute);
response.setTotalDistanceMeters(distEager);
response.setTotalStops(optimizedRoute.size());
response.setEstimatedTimeSeconds((int) (distEager / 1.4)); // viteză estimată 1.4 m/s
Expand All @@ -125,12 +132,20 @@
String routeId = UUID.randomUUID().toString();

// Partial response: user point + first lazyN products
List<RoutePoint> partial = fullNnRoute.subList(0, lazyN + 1); // inclusive of user point
List<RoutePoint> partial = new ArrayList<>(fullNnRoute.subList(0, Math.min(lazyN + 1, fullNnRoute.size())));
addAudioInstructions(partial); // BE 3.2

logger.info("Lazy routing: returnez {} noduri imediat, {} in background (routeId={})",
partial.size(), fullNnRoute.size() - partial.size(), routeId);

// --- Injectarea metricilor pentru ruta parțială returnată imediat (Lazy) ---
double distLazy = optimizer.routeDistance(partial);

// Remove user point from the list sent to UI (issue: redundant with blue ping)
if (!partial.isEmpty() && "user_loc".equals(partial.getFirst().getItemId())) {
partial.removeFirst();
}

// Set pending marker in Redis
String pendingKey = RoutingAsyncService.PENDING_KEY_PREFIX + routeId;
redis.opsForValue().set(pendingKey, "true", RoutingAsyncService.PENDING_TTL);
Expand All @@ -139,14 +154,12 @@
asyncService.completeRouteAsync(routeId, new ArrayList<>(fullNnRoute), new ArrayList<>(warnings));

RoutingResponse partialResponse = new RoutingResponse();
partialResponse.setStatus("success"); // Fixed: Now correctly setting status to "partial"
partialResponse.setStatus("success");
partialResponse.setRouteId(routeId);
partialResponse.setRoute(new ArrayList<>(partial));
partialResponse.setRoute(partial);
partialResponse.setWarnings(warnings);
partialResponse.setPartial(true);

// --- Injectarea metricilor pentru ruta parțială returnată imediat (Lazy) ---
double distLazy = optimizer.routeDistance(partial);
partialResponse.setTotalDistanceMeters(distLazy);
partialResponse.setTotalStops(partial.size());
partialResponse.setEstimatedTimeSeconds((int) (distLazy / 1.4));
Expand Down
6 changes: 3 additions & 3 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ routing.recalculation.guard.max-size=${ROUTING_RECALCULATION_GUARD_MAX_SIZE:1000
data-decay.enabled=${DATA_DECAY_ENABLED:true}
data-decay.cron=${DATA_DECAY_CRON:0 0 3 * * ?}
data-decay.zone=${DATA_DECAY_ZONE:UTC}
data-decay.penalty=${DATA_DECAY_PENALTY:0.02}
data-decay.cutoff-days=${DATA_DECAY_CUTOFF_DAYS:14}
data-decay.min-confidence-floor=${DATA_DECAY_MIN_CONFIDENCE_FLOOR:0.15}
data-decay.penalty=${DATA_DECAY_PENALTY:0.1}
data-decay.cutoff-days=${DATA_DECAY_CUTOFF_DAYS:3}
data-decay.min-confidence-floor=${DATA_DECAY_MIN_CONFIDENCE_FLOOR:0.05}

spring.data.redis.host=localhost
spring.data.redis.port=6379
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ void shouldReturnSuccessStatusAndMockRouteWhenCalculateRouteIsCalled() {
RoutingResponse mockResponse = new RoutingResponse();
mockResponse.setStatus("success");
mockResponse.setRoute(List.of(
new RoutePoint("user_loc", "Punctul Albastru (Tu)", 47.151726, 27.587914),
new RoutePoint("item_101", "Lapte", 47.151800, 27.588000),
new RoutePoint("item_102", "Paine", 47.151850, 27.588050),
new RoutePoint("item_103", "Mere", 47.151900, 27.588100)
Expand All @@ -84,8 +83,8 @@ void shouldReturnSuccessStatusAndMockRouteWhenCalculateRouteIsCalled() {

assertEquals("success", response.getStatus());
assertNotNull(response.getRoute());
assertEquals(4, response.getRoute().size());
assertEquals("user_loc", response.getRoute().getFirst().getItemId());
assertEquals(3, response.getRoute().size());
assertNotEquals("user_loc", response.getRoute().getFirst().getItemId());
assertFalse(response.isPartial());
}

Expand All @@ -95,9 +94,7 @@ void shouldReturnMockRouteForEmptyItemList() {

RoutingResponse mockResponse = new RoutingResponse();
mockResponse.setStatus("success");
mockResponse.setRoute(List.of(
new RoutePoint("user_loc", "Tu", 47.151726, 27.587914)
));
mockResponse.setRoute(List.of()); // user point removed, route empty if no products
mockResponse.setWarnings(List.of());

when(routingService.calculateOptimalRoute(request)).thenReturn(mockResponse);
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/com/p2ps/service/DataDecayServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ class DataDecayServiceTest {

@Test
void shouldExecuteDataDecayAndCallRepositoryWithCorrectPenalty() {
Double expectedPenalty = 0.02;
Double expectedMinConfidenceFloor = 0.15;
Double expectedPenalty = 0.1;
Double expectedMinConfidenceFloor = 0.05;

when(repository.applyDecayToOldRecords(eq(expectedPenalty), any(LocalDateTime.class), eq(expectedMinConfidenceFloor)))
.thenReturn(5);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,9 @@ void processAndCalculateCenters_ThrowsExceptionOnError() throws Exception {
@Test
@DisplayName("Trebuie să marcheze corect produsele cu confidence scăzut")
void isLowConfidence_ShouldReflectThresholds() {
assertTrue(worker.isLowConfidence(0.39d, 10));
assertTrue(worker.isLowConfidence(0.8d, 4));
assertFalse(worker.isLowConfidence(0.8d, 6));
assertTrue(worker.isLowConfidence(0.05d, 10)); // 0.05 < 0.1
assertTrue(worker.isLowConfidence(0.8d, 0)); // 0 < 1
assertFalse(worker.isLowConfidence(0.15d, 2)); // 0.15 > 0.1, 2 > 1
}

@Test
Expand Down Expand Up @@ -291,7 +291,7 @@ void detectDatabaseType_Success() throws Exception {

org.junit.jupiter.api.Assertions.assertDoesNotThrow(() -> worker.initialize());

assertTrue(worker.isLowConfidence(0.1d, 1)); // Just a dummy call to isLowConfidence to show worker is active
assertTrue(worker.isLowConfidence(0.05d, 1)); // 0.05 < 0.1 threshold -> true
// Verify database type was detected correctly
verify(connection, atLeastOnce()).getMetaData();
}
Expand Down
4 changes: 2 additions & 2 deletions src/test/java/com/p2ps/service/RoutingServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ void calculateOptimalRoute_lazy_shouldReturnPartialResponseWithRouteId() {
assertEquals("success", response.getStatus());
assertTrue(response.isPartial());
assertNotNull(response.getRouteId());
assertEquals(6, response.getRoute().size());
assertEquals(5, response.getRoute().size()); // 5 products, user point removed
}

// -------------------------------------------------------------------------
Expand Down Expand Up @@ -474,7 +474,7 @@ void calculateOptimalRoute_shouldHandleDuplicateProductIds() {
// Depending on implementation, we might have 1 product in route (unique) or 2.
// Current logic in getProductLocations doesn't de-duplicate, it just returns what the DB returns.
// If DB returns one row (because item_id is unique per store), then we have 1 product.
assertTrue(response.getRoute().size() >= 2); // user + at least one product
assertTrue(response.getRoute().size() >= 1); // at least one product (user removed)
}

@Test
Expand Down
Loading