From fb31915b3917aa679f5a1e2c13c08b36a76e4a48 Mon Sep 17 00:00:00 2001 From: Tour Date: Sun, 7 Dec 2025 11:08:59 +0100 Subject: [PATCH] Fix mock tests Former-commit-id: 43b5fc03fdc541dba64af481a566ab070ac7daad --- scripts/sync-production-data.sh | 2 +- .../java/auctiora/AuctionMonitorResource.java | 44 ++++ src/main/java/auctiora/DatabaseService.java | 75 ++++--- src/main/java/auctiora/Lot.java | 13 ++ .../java/auctiora/ObjectDetectionService.java | 47 +++-- .../auctiora/ValuationAnalyticsResource.java | 14 ++ .../resources/META-INF/resources/index.html | 31 ++- .../resources/valuation-analytics.html | 190 +++++++++++++----- 8 files changed, 322 insertions(+), 94 deletions(-) diff --git a/scripts/sync-production-data.sh b/scripts/sync-production-data.sh index d87d525..7a6ff29 100644 --- a/scripts/sync-production-data.sh +++ b/scripts/sync-production-data.sh @@ -20,7 +20,7 @@ set -e # Exit on error # Configuration REMOTE_HOST="tour@athena.lan" REMOTE_VOLUME="shared-auction-data" -LOCAL_DB_PATH="c:/mnt/okcomputer/cache.db" +LOCAL_DB_PATH="c:/mnt/okcomputer/output/cache.db" LOCAL_IMAGES_PATH="c:/mnt/okcomputer/images" REMOTE_TMP="/tmp" diff --git a/src/main/java/auctiora/AuctionMonitorResource.java b/src/main/java/auctiora/AuctionMonitorResource.java index 5d30e12..04c222d 100644 --- a/src/main/java/auctiora/AuctionMonitorResource.java +++ b/src/main/java/auctiora/AuctionMonitorResource.java @@ -5,6 +5,7 @@ import jakarta.ws.rs.*; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.jboss.logging.Logger; +import java.time.LocalDateTime; import java.time.LocalDateTime; import java.util.ArrayList; @@ -121,6 +122,49 @@ public class AuctionMonitorResource { } } + /** + * GET /api/monitor/closing-soon + * Returns lots closing within the next specified hours (default: 24 hours) + */ + @GET + @Path("/closing-soon") + public Response getClosingSoon(@QueryParam("hours") @DefaultValue("24") int hours) { + try { + var lots = db.getAllLots(); + var closingSoon = lots.stream() + .filter(lot -> lot.closingTime() != null) + .filter(lot -> lot.minutesUntilClose() > 0 && lot.minutesUntilClose() <= hours * 60) + .sorted((a, b) -> Long.compare(a.minutesUntilClose(), b.minutesUntilClose())) + .limit(100) + .toList(); + + return Response.ok(closingSoon).build(); + } catch (Exception e) { + LOG.error("Failed to get closing soon lots", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", e.getMessage())) + .build(); + } + } + + /** + * GET /api/monitor/lots/{lotId}/bid-history + * Returns bid history for a specific lot + */ + @GET + @Path("/lots/{lotId}/bid-history") + public Response getBidHistory(@PathParam("lotId") String lotId) { + try { + var history = db.getBidHistory(lotId); + return Response.ok(history).build(); + } catch (Exception e) { + LOG.error("Failed to get bid history for lot {}", lotId, e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", e.getMessage())) + .build(); + } + } + /** * POST /api/monitor/trigger/scraper-import * Manually trigger scraper import workflow diff --git a/src/main/java/auctiora/DatabaseService.java b/src/main/java/auctiora/DatabaseService.java index fd18ee9..bc17e2a 100644 --- a/src/main/java/auctiora/DatabaseService.java +++ b/src/main/java/auctiora/DatabaseService.java @@ -544,42 +544,19 @@ public class DatabaseService { */ synchronized List getActiveLots() throws SQLException { List list = new ArrayList<>(); - var sql = "SELECT lot_id, sale_id, title, description, manufacturer, type, year, category, " + + var sql = "SELECT lot_id, sale_id as auction_id, title, description, manufacturer, type, year, category, " + "current_bid, currency, url, closing_time, closing_notified FROM lots"; try (var conn = DriverManager.getConnection(url); var stmt = conn.createStatement()) { var rs = stmt.executeQuery(sql); while (rs.next()) { - var closingStr = rs.getString("closing_time"); - LocalDateTime closing = null; - if (closingStr != null && !closingStr.isBlank()) { - try { - closing = LocalDateTime.parse(closingStr); - } catch (Exception e) { - log.debug("Invalid closing_time format for lot {}: {}", rs.getLong("lot_id"), closingStr); - } + try { + // Use ScraperDataAdapter to handle TEXT parsing from legacy database + var lot = ScraperDataAdapter.fromScraperLot(rs); + list.add(lot); + } catch (Exception e) { + log.warn("Failed to parse lot {}: {}", rs.getString("lot_id"), e.getMessage()); } - - list.add(new Lot( - rs.getLong("sale_id"), - rs.getLong("lot_id"), - rs.getString("title"), - rs.getString("description"), - rs.getString("manufacturer"), - rs.getString("type"), - rs.getInt("year"), - rs.getString("category"), - rs.getDouble("current_bid"), - rs.getString("currency"), - rs.getString("url"), - closing, - rs.getInt("closing_notified") != 0, - // New intelligence fields - set to null for now - null, null, null, null, null, null, null, null, - null, null, null, null, null, null, null, - null, null, null, null, null, null, null, null, - null, null - )); } } return list; @@ -629,6 +606,44 @@ public class DatabaseService { ps.executeUpdate(); } } + + /** + * Retrieves bid history for a specific lot + */ + synchronized List getBidHistory(String lotId) throws SQLException { + List history = new ArrayList<>(); + var sql = "SELECT id, lot_id, bid_amount, bid_time, is_autobid, bidder_id, bidder_number " + + "FROM bid_history WHERE lot_id = ? ORDER BY bid_time DESC LIMIT 100"; + + try (var conn = DriverManager.getConnection(url); + var ps = conn.prepareStatement(sql)) { + ps.setString(1, lotId); + var rs = ps.executeQuery(); + + while (rs.next()) { + LocalDateTime bidTime = null; + var bidTimeStr = rs.getString("bid_time"); + if (bidTimeStr != null && !bidTimeStr.isBlank()) { + try { + bidTime = LocalDateTime.parse(bidTimeStr); + } catch (Exception e) { + log.debug("Invalid bid_time format: {}", bidTimeStr); + } + } + + history.add(new BidHistory( + rs.getInt("id"), + rs.getString("lot_id"), + rs.getDouble("bid_amount"), + bidTime, + rs.getInt("is_autobid") != 0, + rs.getString("bidder_id"), + rs.getInt("bidder_number") + )); + } + } + return history; + } /** * Imports auctions from scraper's schema format. diff --git a/src/main/java/auctiora/Lot.java b/src/main/java/auctiora/Lot.java index 1fc07c6..d92966a 100644 --- a/src/main/java/auctiora/Lot.java +++ b/src/main/java/auctiora/Lot.java @@ -4,6 +4,19 @@ import lombok.With; import java.time.Duration; import java.time.LocalDateTime; +/** + * Represents a bid in the bid history + */ +record BidHistory( + int id, + String lotId, + double bidAmount, + LocalDateTime bidTime, + boolean isAutobid, + String bidderId, + Integer bidderNumber +) {} + /// Represents a lot (kavel) in an auction. /// Data typically populated by the external scraper process. /// This project enriches the data with image analysis and monitoring. diff --git a/src/main/java/auctiora/ObjectDetectionService.java b/src/main/java/auctiora/ObjectDetectionService.java index 7a4801d..8d0d111 100644 --- a/src/main/java/auctiora/ObjectDetectionService.java +++ b/src/main/java/auctiora/ObjectDetectionService.java @@ -37,6 +37,8 @@ public class ObjectDetectionService { private final Net net; private final List classNames; private final boolean enabled; + private int warnCount = 0; + private static final int MAX_WARNINGS = 5; ObjectDetectionService(String cfgPath, String weightsPath, String classNamesPath) throws IOException { // Check if model files exist @@ -101,23 +103,44 @@ public class ObjectDetectionService { // Post‑process: for each detection compute score and choose class var confThreshold = 0.5f; for (var out : outs) { - for (var i = 0; i < out.rows(); i++) { - var data = out.get(i, 0); - if (data == null) continue; + // YOLO output shape: [num_detections, 85] where 85 = 4 (bbox) + 1 (objectness) + 80 (classes) + int numDetections = out.rows(); + int numElements = out.cols(); + int expectedLength = 5 + classNames.size(); - // The first 5 numbers are bounding box, then class scores - // Check if data has enough elements before copying - int expectedLength = 5 + classNames.size(); - if (data.length < expectedLength) { - log.warn("Detection data too short: expected {} elements, got {}. Skipping detection.", - expectedLength, data.length); - continue; + if (numElements < expectedLength) { + // Rate-limit warnings to prevent thread blocking from excessive logging + if (warnCount < MAX_WARNINGS) { + log.warn("Output matrix has wrong dimensions: expected {} columns, got {}. Output shape: [{}, {}]", + expectedLength, numElements, numDetections, numElements); + warnCount++; + if (warnCount == MAX_WARNINGS) { + log.warn("Suppressing further dimension warnings (reached {} warnings)", MAX_WARNINGS); + } + } + continue; + } + + for (var i = 0; i < numDetections; i++) { + // Get entire row (all 85 elements) + var data = new double[numElements]; + for (int j = 0; j < numElements; j++) { + data[j] = out.get(i, j)[0]; } + // Extract objectness score (index 4) and class scores (index 5+) + double objectness = data[4]; + if (objectness < confThreshold) { + continue; // Skip low-confidence detections + } + + // Extract class scores var scores = new double[classNames.size()]; - System.arraycopy(data, 5, scores, 0, scores.length); + System.arraycopy(data, 5, scores, 0, Math.min(scores.length, data.length - 5)); + var classId = argMax(scores); - var confidence = scores[classId]; + var confidence = scores[classId] * objectness; // Combine objectness with class confidence + if (confidence > confThreshold) { var label = classNames.get(classId); if (!labels.contains(label)) { diff --git a/src/main/java/auctiora/ValuationAnalyticsResource.java b/src/main/java/auctiora/ValuationAnalyticsResource.java index 5794d62..7cef83b 100644 --- a/src/main/java/auctiora/ValuationAnalyticsResource.java +++ b/src/main/java/auctiora/ValuationAnalyticsResource.java @@ -26,6 +26,20 @@ public class ValuationAnalyticsResource { @Inject DatabaseService db; + /** + * GET /api/analytics/valuation/health + * Health check endpoint to verify API availability + */ + @GET + @Path("/valuation/health") + public Response healthCheck() { + return Response.ok(Map.of( + "status", "healthy", + "service", "valuation-analytics", + "timestamp", java.time.LocalDateTime.now().toString() + )).build(); + } + /** * POST /api/analytics/valuation * Main valuation endpoint that calculates FMV, undervaluation score, diff --git a/src/main/resources/META-INF/resources/index.html b/src/main/resources/META-INF/resources/index.html index 1c2d5a1..e114be3 100644 --- a/src/main/resources/META-INF/resources/index.html +++ b/src/main/resources/META-INF/resources/index.html @@ -6,7 +6,6 @@ Auctiora - Intelligence Dashboard -