175 lines
7.0 KiB
Java
175 lines
7.0 KiB
Java
package auctiora;
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import java.io.IOException;
|
|
import java.sql.SQLException;
|
|
import java.util.concurrent.Executors;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
/**
|
|
* Monitoring service for Troostwijk auction lots.
|
|
* This class focuses on:
|
|
* - Monitoring bid changes on lots (populated by external scraper)
|
|
* - Sending notifications for important events
|
|
* - Coordinating image processing
|
|
*
|
|
* Does NOT handle scraping - that's done by the external ARCHITECTURE-TROOSTWIJK-SCRAPER process.
|
|
*/
|
|
public class TroostwijkMonitor {
|
|
|
|
private static final String LOT_API = "https://api.troostwijkauctions.com/lot/7/list";
|
|
|
|
private final RateLimitedHttpClient httpClient;
|
|
private final ObjectMapper objectMapper;
|
|
public final DatabaseService db;
|
|
private final NotificationService notifier;
|
|
private final ObjectDetectionService detector;
|
|
private final ImageProcessingService imageProcessor;
|
|
|
|
/**
|
|
* Constructor for the monitoring service.
|
|
*
|
|
* @param databasePath Path to SQLite database file (shared with external scraper)
|
|
* @param notificationConfig "desktop" or "smtp:user:pass:email"
|
|
* @param yoloCfgPath YOLO config file path
|
|
* @param yoloWeightsPath YOLO weights file path
|
|
* @param classNamesPath Class names file path
|
|
*/
|
|
public TroostwijkMonitor(String databasePath, String notificationConfig,
|
|
String yoloCfgPath, String yoloWeightsPath, String classNamesPath)
|
|
throws SQLException, IOException {
|
|
this.httpClient = new RateLimitedHttpClient();
|
|
this.objectMapper = new ObjectMapper();
|
|
this.db = new DatabaseService(databasePath);
|
|
this.notifier = new NotificationService(notificationConfig, "");
|
|
this.detector = new ObjectDetectionService(yoloCfgPath, yoloWeightsPath, classNamesPath);
|
|
this.imageProcessor = new ImageProcessingService(db, detector, httpClient);
|
|
|
|
// Initialize database schema
|
|
db.ensureSchema();
|
|
}
|
|
|
|
/**
|
|
* Schedules periodic monitoring of all lots.
|
|
* Runs every hour to refresh bids and detect changes.
|
|
* Increases frequency for lots closing soon.
|
|
*/
|
|
public void scheduleMonitoring() {
|
|
var scheduler = Executors.newScheduledThreadPool(1);
|
|
scheduler.scheduleAtFixedRate(() -> {
|
|
try {
|
|
var activeLots = db.getActiveLots();
|
|
Console.println("Monitoring " + activeLots.size() + " active lots...");
|
|
|
|
for (var lot : activeLots) {
|
|
// Refresh lot bidding information
|
|
refreshLotBid(lot);
|
|
|
|
// Check closing time
|
|
var minutesLeft = lot.minutesUntilClose();
|
|
if (minutesLeft < 30) {
|
|
// Send warning when within 5 minutes
|
|
if (minutesLeft <= 5 && !lot.closingNotified()) {
|
|
notifier.sendNotification(
|
|
"Kavel " + lot.lotId() + " sluit binnen " + minutesLeft + " min.",
|
|
"Lot nearing closure", 1);
|
|
|
|
// Update notification flag
|
|
var updated = new Lot(
|
|
lot.saleId(), lot.lotId(), lot.title(), lot.description(),
|
|
lot.manufacturer(), lot.type(), lot.year(), lot.category(),
|
|
lot.currentBid(), lot.currency(), lot.url(),
|
|
lot.closingTime(), true
|
|
);
|
|
db.updateLotNotificationFlags(updated);
|
|
}
|
|
|
|
// Schedule additional quick check
|
|
scheduler.schedule(() -> refreshLotBid(lot), 5, TimeUnit.MINUTES);
|
|
}
|
|
}
|
|
} catch (SQLException e) {
|
|
System.err.println("Error during scheduled monitoring: " + e.getMessage());
|
|
}
|
|
}, 0, 1, TimeUnit.HOURS);
|
|
|
|
Console.println("✓ Monitoring service started");
|
|
}
|
|
|
|
/**
|
|
* Refreshes the bid for a single lot and sends notification if changed.
|
|
*
|
|
* @param lot the lot to refresh
|
|
*/
|
|
private void refreshLotBid(Lot lot) {
|
|
try {
|
|
var url = LOT_API + "?batchSize=1&listType=7&offset=0&sortOption=0&saleID=" + lot.saleId()
|
|
+ "&parentID=0&relationID=0&buildversion=201807311&lotID=" + lot.lotId();
|
|
|
|
var response = httpClient.sendGet(url);
|
|
|
|
if (response.statusCode() != 200) return;
|
|
|
|
var root = objectMapper.readTree(response.body());
|
|
var results = root.path("results");
|
|
|
|
if (results.isArray() && !results.isEmpty()) {
|
|
var node = results.get(0);
|
|
var newBid = node.path("cb").asDouble();
|
|
|
|
if (Double.compare(newBid, lot.currentBid()) > 0) {
|
|
var previous = lot.currentBid();
|
|
|
|
// Create updated lot with new bid
|
|
var updatedLot = new Lot(
|
|
lot.saleId(), lot.lotId(), lot.title(), lot.description(),
|
|
lot.manufacturer(), lot.type(), lot.year(), lot.category(),
|
|
newBid, lot.currency(), lot.url(),
|
|
lot.closingTime(), lot.closingNotified()
|
|
);
|
|
|
|
db.updateLotCurrentBid(updatedLot);
|
|
|
|
var msg = String.format("Nieuw bod op kavel %d: €%.2f (was €%.2f)",
|
|
lot.lotId(), newBid, previous);
|
|
notifier.sendNotification(msg, "Kavel bieding update", 0);
|
|
}
|
|
}
|
|
} catch (IOException | InterruptedException | SQLException e) {
|
|
System.err.println("Failed to refresh bid for lot " + lot.lotId() + ": " + e.getMessage());
|
|
if (e instanceof InterruptedException) {
|
|
Thread.currentThread().interrupt();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints statistics about the data in the database.
|
|
*/
|
|
public void printDatabaseStats() {
|
|
try {
|
|
var allLots = db.getAllLots();
|
|
var imageCount = db.getImageCount();
|
|
|
|
Console.println("📊 Database Summary:");
|
|
Console.println(" Total lots in database: " + allLots.size());
|
|
Console.println(" Total images processed: " + imageCount);
|
|
|
|
if (!allLots.isEmpty()) {
|
|
var totalBids = allLots.stream().mapToDouble(Lot::currentBid).sum();
|
|
Console.println(" Total current bids: €" + String.format("%.2f", totalBids));
|
|
}
|
|
} catch (SQLException e) {
|
|
System.err.println(" ⚠️ Could not retrieve database stats: " + e.getMessage());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process pending images for lots in the database.
|
|
* This should be called after the external scraper has populated lot data.
|
|
*/
|
|
public void processPendingImages() {
|
|
imageProcessor.processPendingImages();
|
|
}
|
|
}
|