diff --git a/.mvn.bak/extensions.xml b/.mvn.bak/extensions.xml
deleted file mode 100644
index 47df64f..0000000
--- a/.mvn.bak/extensions.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
- com.google.inject
- guice
- 7.0.0
-
-
diff --git a/.mvn.bak/jvm.config b/.mvn.bak/jvm.config
deleted file mode 100644
index 3b7e754..0000000
--- a/.mvn.bak/jvm.config
+++ /dev/null
@@ -1,5 +0,0 @@
---add-opens=java.base/java.lang=ALL-UNNAMED
---add-opens=java.base/jdk.internal.misc=ALL-UNNAMED
---add-exports=java.base/sun.nio.ch=ALL-UNNAMED
--Djdk.module.illegalAccess=deny
--XX:+IgnoreUnrecognizedVMOptions
diff --git a/.mvn.bak/wrapper/MavenWrapperDownloader.java b/.mvn.bak/wrapper/MavenWrapperDownloader.java
deleted file mode 100644
index b901097..0000000
--- a/.mvn.bak/wrapper/MavenWrapperDownloader.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright 2007-present the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import java.net.*;
-import java.io.*;
-import java.nio.channels.*;
-import java.util.Properties;
-
-public class MavenWrapperDownloader {
-
- private static final String WRAPPER_VERSION = "0.5.6";
- /**
- * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
- */
- private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
- + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
-
- /**
- * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
- * use instead of the default one.
- */
- private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
- ".mvn/wrapper/maven-wrapper.properties";
-
- /**
- * Path where the maven-wrapper.jar will be saved to.
- */
- private static final String MAVEN_WRAPPER_JAR_PATH =
- ".mvn/wrapper/maven-wrapper.jar";
-
- /**
- * Name of the property which should be used to override the default download url for the wrapper.
- */
- private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
-
- public static void main(String args[]) {
- System.out.println("- Downloader started");
- File baseDirectory = new File(args[0]);
- System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
-
- // If the maven-wrapper.properties exists, read it and check if it contains a custom
- // wrapperUrl parameter.
- File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
- String url = DEFAULT_DOWNLOAD_URL;
- if(mavenWrapperPropertyFile.exists()) {
- FileInputStream mavenWrapperPropertyFileInputStream = null;
- try {
- mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
- Properties mavenWrapperProperties = new Properties();
- mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
- url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
- } catch (IOException e) {
- System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
- } finally {
- try {
- if(mavenWrapperPropertyFileInputStream != null) {
- mavenWrapperPropertyFileInputStream.close();
- }
- } catch (IOException e) {
- // Ignore ...
- }
- }
- }
- System.out.println("- Downloading from: " + url);
-
- File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
- if(!outputFile.getParentFile().exists()) {
- if(!outputFile.getParentFile().mkdirs()) {
- System.out.println(
- "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
- }
- }
- System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
- try {
- downloadFileFromURL(url, outputFile);
- System.out.println("Done");
- System.exit(0);
- } catch (Throwable e) {
- System.out.println("- Error downloading");
- e.printStackTrace();
- System.exit(1);
- }
- }
-
- private static void downloadFileFromURL(String urlString, File destination) throws Exception {
- if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
- String username = System.getenv("MVNW_USERNAME");
- char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
- Authenticator.setDefault(new Authenticator() {
- @Override
- protected PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication(username, password);
- }
- });
- }
- URL website = new URL(urlString);
- ReadableByteChannel rbc;
- rbc = Channels.newChannel(website.openStream());
- FileOutputStream fos = new FileOutputStream(destination);
- fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
- fos.close();
- rbc.close();
- }
-
-}
diff --git a/.mvn.bak/wrapper/maven-wrapper.jar b/.mvn.bak/wrapper/maven-wrapper.jar
deleted file mode 100644
index 2cc7d4a..0000000
Binary files a/.mvn.bak/wrapper/maven-wrapper.jar and /dev/null differ
diff --git a/.mvn.bak/wrapper/maven-wrapper.properties b/.mvn.bak/wrapper/maven-wrapper.properties
deleted file mode 100644
index 86c4dec..0000000
--- a/.mvn.bak/wrapper/maven-wrapper.properties
+++ /dev/null
@@ -1,2 +0,0 @@
-distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3/apache-maven-3-bin.zip
-wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/docs/DATABASE_CLEANUP_GUIDE.md b/docs/DATABASE_CLEANUP_GUIDE.md
new file mode 100644
index 0000000..b8119e4
--- /dev/null
+++ b/docs/DATABASE_CLEANUP_GUIDE.md
@@ -0,0 +1,192 @@
+# Database Cleanup Guide
+
+## Problem: Mixed Data Formats
+
+Your production database (`cache.db`) contains data from two different scrapers:
+
+### Valid Data (99.92%)
+- **Format**: `A1-34732-49` (lot_id) + `c1f44ec2-ad6e-4c98-b0e2-cb1d8ccddcab` (auction_id UUID)
+- **Count**: 16,794 lots
+- **Source**: Current GraphQL-based scraper
+- **Status**: ✅ Clean, with proper auction_id
+
+### Invalid Data (0.08%)
+- **Format**: `bmw-550i-4-4-v8-high-executive-...` (slug as lot_id) + `""` (empty auction_id)
+- **Count**: 13 lots
+- **Source**: Old legacy scraper
+- **Status**: ❌ Missing auction_id, causes issues
+
+## Impact
+
+These 13 invalid entries:
+- Cause `NullPointerException` in analytics when grouping by country
+- Cannot be properly linked to auctions
+- Skew statistics slightly
+- May cause issues with intelligence features that rely on auction_id
+
+## Solution 1: Clean Sync (Recommended)
+
+The updated sync script now **automatically removes old local data** before syncing:
+
+```bash
+# Windows PowerShell
+.\scripts\Sync-ProductionData.ps1
+
+# Linux/Mac
+./scripts/sync-production-data.sh --db-only
+```
+
+**What it does**:
+1. Backs up existing database to `cache.db.backup-YYYYMMDD-HHMMSS`
+2. **Removes old local database completely**
+3. Downloads fresh copy from production
+4. Shows data quality report
+
+**Output includes**:
+```
+Database statistics:
+┌─────────────┬────────┐
+│ table_name │ count │
+├─────────────┼────────┤
+│ auctions │ 526 │
+│ lots │ 16807 │
+│ images │ 536502 │
+│ cache │ 2134 │
+└─────────────┴────────┘
+
+Data quality:
+┌────────────────────────────────────┬────────┬────────────┐
+│ metric │ count │ percentage │
+├────────────────────────────────────┼────────┼────────────┤
+│ Valid lots │ 16794 │ 99.92% │
+│ Invalid lots (missing auction_id) │ 13 │ 0.08% │
+│ Lots with intelligence fields │ 0 │ 0.00% │
+└────────────────────────────────────┴────────┴────────────┘
+```
+
+## Solution 2: Manual Cleanup
+
+If you want to clean your existing local database without re-downloading:
+
+```bash
+# Dry run (see what would be deleted)
+./scripts/cleanup-database.sh --dry-run
+
+# Actual cleanup
+./scripts/cleanup-database.sh
+```
+
+**What it does**:
+1. Creates backup before cleanup
+2. Deletes lots with missing auction_id
+3. Deletes orphaned images (images without matching lots)
+4. Compacts database (VACUUM) to reclaim space
+5. Shows before/after statistics
+
+**Example output**:
+```
+Current database state:
+┌──────────────────────────────────┬────────┐
+│ metric │ count │
+├──────────────────────────────────┼────────┤
+│ Total lots │ 16807 │
+│ Valid lots (with auction_id) │ 16794 │
+│ Invalid lots (missing auction_id) │ 13 │
+└──────────────────────────────────┴────────┘
+
+Analyzing data to clean up...
+ → Invalid lots to delete: 13
+ → Orphaned images to delete: 0
+
+This will permanently delete the above records.
+Continue? (y/N) y
+
+Cleaning up database...
+ [1/2] Deleting invalid lots...
+ ✓ Deleted 13 invalid lots
+ [2/2] Deleting orphaned images...
+ ✓ Deleted 0 orphaned images
+ [3/3] Compacting database...
+ ✓ Database compacted
+
+Final database state:
+┌───────────────┬────────┐
+│ metric │ count │
+├───────────────┼────────┤
+│ Total lots │ 16794 │
+│ Total images │ 536502 │
+└───────────────┴────────┘
+
+Database size: 8.9G
+```
+
+## Solution 3: SQL Manual Cleanup
+
+If you prefer to manually clean using SQL:
+
+```sql
+-- Backup first!
+-- cp cache.db cache.db.backup
+
+-- Check invalid entries
+SELECT COUNT(*), 'Invalid' as type
+FROM lots
+WHERE auction_id IS NULL OR auction_id = ''
+UNION ALL
+SELECT COUNT(*), 'Valid'
+FROM lots
+WHERE auction_id IS NOT NULL AND auction_id != '';
+
+-- Delete invalid lots
+DELETE FROM lots
+WHERE auction_id IS NULL OR auction_id = '';
+
+-- Delete orphaned images
+DELETE FROM images
+WHERE lot_id NOT IN (SELECT lot_id FROM lots);
+
+-- Compact database
+VACUUM;
+```
+
+## Prevention: Production Database Cleanup
+
+To prevent these invalid entries from accumulating on production, you can:
+
+1. **Clean production database** (one-time):
+```bash
+ssh tour@athena.lan
+docker run --rm -v shared-auction-data:/data alpine sqlite3 /data/cache.db "DELETE FROM lots WHERE auction_id IS NULL OR auction_id = '';"
+```
+
+2. **Update scraper** to ensure all lots have auction_id
+3. **Add validation** in scraper to reject lots without auction_id
+
+## When to Clean
+
+### Immediately if:
+- ❌ Seeing `NullPointerException` in analytics
+- ❌ Dashboard insights failing
+- ❌ Country distribution not working
+
+### Periodically:
+- 🔄 After syncing from production (if production has invalid data)
+- 🔄 Weekly/monthly maintenance
+- 🔄 Before major testing or demos
+
+## Recommendation
+
+**Use Solution 1 (Clean Sync)** for simplicity:
+- ✅ Guarantees clean state
+- ✅ No manual SQL needed
+- ✅ Shows data quality report
+- ✅ Safe (automatic backup)
+
+The 13 invalid entries are from an old scraper and represent only 0.08% of data, so cleaning them up has minimal impact but prevents future errors.
+
+---
+
+**Related Documentation**:
+- [Sync Scripts README](../scripts/README.md)
+- [Data Sync Setup](DATA_SYNC_SETUP.md)
+- [Database Architecture](../wiki/DATABASE_ARCHITECTURE.md)
diff --git a/script/check-status.bat b/script/check-status.bat
deleted file mode 100644
index 7f0964b..0000000
--- a/script/check-status.bat
+++ /dev/null
@@ -1,20 +0,0 @@
-@echo off
-REM ============================================================================
-REM Troostwijk Auction Monitor - Status Check (Windows)
-REM ============================================================================
-REM
-REM This script shows the current status and exits.
-REM
-REM Usage:
-REM check-status.bat
-REM
-REM ============================================================================
-
-REM Set configuration
-set DATABASE_FILE=C:\mnt\okcomputer\output\cache.db
-set NOTIFICATION_CONFIG=desktop
-
-REM Check status
-java -jar target\auctiora-1.0-SNAPSHOT-jar-with-dependencies.jar status
-
-pause
diff --git a/script/persist.sh b/script/persist.sh
deleted file mode 100644
index ea0867d..0000000
--- a/script/persist.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-@echo off
-if "%1"=="" (
- echo Error: Commit message is required
- echo Usage: persist "Your commit message"
- echo Example: persist "Fixed login bug"
- exit /b 1
-)
-
-echo Adding all changes...
-git add .
-if errorlevel 1 (
- echo Error: Failed to add changes
- exit /b 1
-)
-
-echo Committing with message: "%*"
-git commit -a -m "%*"
-if errorlevel 1 (
- echo Error: Failed to commit
- exit /b 1
-)
-
-echo Pushing to remote...
-git push
-if errorlevel 1 (
- echo Error: Failed to push
- exit /b 1
-)
-
-echo All operations completed successfully!
\ No newline at end of file
diff --git a/script/run-once.bat b/script/run-once.bat
deleted file mode 100644
index d8aef18..0000000
--- a/script/run-once.bat
+++ /dev/null
@@ -1,27 +0,0 @@
-@echo off
-REM ============================================================================
-REM Troostwijk Auction Monitor - Run Once (Windows)
-REM ============================================================================
-REM
-REM This script runs the complete workflow once and exits.
-REM Perfect for Windows Task Scheduler.
-REM
-REM Usage:
-REM run-once.bat
-REM
-REM Schedule in Task Scheduler:
-REM - Every 30 minutes: Data import
-REM - Every 1 hour: Image processing
-REM - Every 15 minutes: Bid monitoring
-REM
-REM ============================================================================
-
-REM Set configuration
-set DATABASE_FILE=C:\mnt\okcomputer\output\cache.db
-set NOTIFICATION_CONFIG=desktop
-
-REM Run the application once
-java -jar target\troostwijk-scraper-1.0-SNAPSHOT-jar-with-dependencies.jar once
-
-REM Exit code for Task Scheduler
-exit /b %ERRORLEVEL%
diff --git a/script/run-workflow.bat b/script/run-workflow.bat
deleted file mode 100644
index 9fc574e..0000000
--- a/script/run-workflow.bat
+++ /dev/null
@@ -1,27 +0,0 @@
-@echo off
-REM ============================================================================
-REM Troostwijk Auction Monitor - Workflow Runner (Windows)
-REM ============================================================================
-REM
-REM This script runs the auction monitor in workflow mode (continuous operation)
-REM with all scheduled tasks running automatically.
-REM
-REM Usage:
-REM run-workflow.bat
-REM
-REM ============================================================================
-
-echo Starting Troostwijk Auction Monitor - Workflow Mode...
-echo.
-
-REM Set configuration
-set DATABASE_FILE=C:\mnt\okcomputer\output\cache.db
-set NOTIFICATION_CONFIG=desktop
-
-REM Optional: Set for email notifications
-REM set NOTIFICATION_CONFIG=smtp:your@gmail.com:app_password:your@gmail.com
-
-REM Run the application
-java -jar target\auctiora-1.0-SNAPSHOT-jar-with-dependencies.jar workflow
-
-pause
diff --git a/script/setup-windows-task.ps1 b/script/setup-windows-task.ps1
deleted file mode 100644
index 6a6d040..0000000
--- a/script/setup-windows-task.ps1
+++ /dev/null
@@ -1,71 +0,0 @@
-# ============================================================================
-# Troostwijk Auction Monitor - Windows Task Scheduler Setup
-# ============================================================================
-#
-# This PowerShell script creates scheduled tasks in Windows Task Scheduler
-# to run the auction monitor automatically.
-#
-# Usage:
-# Run PowerShell as Administrator, then:
-# .\setup-windows-task.ps1
-#
-# ============================================================================
-
-Write-Host "=== Auctiora Monitor - Task Scheduler Setup ===" -ForegroundColor Cyan
-Write-Host ""
-
-# Configuration
-$scriptPath = $PSScriptRoot
-$jarPath = Join-Path $scriptPath "target\auctiora-1.0-SNAPSHOT-jar-with-dependencies.jar"
-$javaExe = "java"
-
-# Check if JAR exists
-if (-not (Test-Path $jarPath)) {
- Write-Host "ERROR: JAR file not found at: $jarPath" -ForegroundColor Red
- Write-Host "Please run 'mvn clean package' first." -ForegroundColor Yellow
- exit 1
-}
-
-Write-Host "Creating scheduled tasks..." -ForegroundColor Green
-Write-Host ""
-
-# Task 1: Complete Workflow - Every 30 minutes
-$task1Name = "TroostwijkMonitor-Workflow"
-$task1Description = "Runs complete auction monitoring workflow every 30 minutes"
-$task1Action = New-ScheduledTaskAction -Execute $javaExe -Argument "-jar `"$jarPath`" once" -WorkingDirectory $scriptPath
-$task1Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 30) -RepetitionDuration ([TimeSpan]::MaxValue)
-$task1Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
-
-try {
- Register-ScheduledTask -TaskName $task1Name -Action $task1Action -Trigger $task1Trigger -Settings $task1Settings -Description $task1Description -Force
- Write-Host "[✓] Created task: $task1Name (every 30 min)" -ForegroundColor Green
-} catch {
- Write-Host "[✗] Failed to create task: $task1Name" -ForegroundColor Red
- Write-Host " Error: $_" -ForegroundColor Yellow
-}
-
-# Task 2: Status Check - Every 6 hours
-$task2Name = "TroostwijkMonitor-StatusCheck"
-$task2Description = "Checks auction monitoring status every 6 hours"
-$task2Action = New-ScheduledTaskAction -Execute $javaExe -Argument "-jar `"$jarPath`" status" -WorkingDirectory $scriptPath
-$task2Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Hours 6) -RepetitionDuration ([TimeSpan]::MaxValue)
-$task2Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable
-
-try {
- Register-ScheduledTask -TaskName $task2Name -Action $task2Action -Trigger $task2Trigger -Settings $task2Settings -Description $task2Description -Force
- Write-Host "[✓] Created task: $task2Name (every 6 hours)" -ForegroundColor Green
-} catch {
- Write-Host "[✗] Failed to create task: $task2Name" -ForegroundColor Red
- Write-Host " Error: $_" -ForegroundColor Yellow
-}
-
-Write-Host ""
-Write-Host "=== Setup Complete ===" -ForegroundColor Cyan
-Write-Host ""
-Write-Host "Created tasks:" -ForegroundColor White
-Write-Host " 1. $task1Name - Runs every 30 minutes" -ForegroundColor Gray
-Write-Host " 2. $task2Name - Runs every 6 hours" -ForegroundColor Gray
-Write-Host ""
-Write-Host "To view tasks: Open Task Scheduler and look for 'TroostwijkMonitor-*'" -ForegroundColor Yellow
-Write-Host "To remove tasks: Run 'Unregister-ScheduledTask -TaskName TroostwijkMonitor-*'" -ForegroundColor Yellow
-Write-Host ""
diff --git a/scripts/cleanup-database.sh b/scripts/cleanup-database.sh
new file mode 100644
index 0000000..1f2c505
--- /dev/null
+++ b/scripts/cleanup-database.sh
@@ -0,0 +1,160 @@
+#!/bin/bash
+#
+# Database Cleanup Utility
+#
+# Removes invalid/old data from the local database
+#
+# Usage:
+# ./scripts/cleanup-database.sh [--dry-run]
+#
+# Options:
+# --dry-run Show what would be deleted without actually deleting
+#
+
+set -e
+
+# Configuration
+LOCAL_DB_PATH="${1:-c:/mnt/okcomputer/cache.db}"
+DRY_RUN=false
+
+# Colors
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+RED='\033[0;31m'
+BLUE='\033[0;34m'
+NC='\033[0m'
+
+# Parse arguments
+if [ "$1" = "--dry-run" ] || [ "$2" = "--dry-run" ]; then
+ DRY_RUN=true
+fi
+
+if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
+ grep '^#' "$0" | sed 's/^# \?//'
+ exit 0
+fi
+
+echo -e "${BLUE}╔════════════════════════════════════════════════════════╗${NC}"
+echo -e "${BLUE}║ Database Cleanup - Auctiora Monitor ║${NC}"
+echo -e "${BLUE}╚════════════════════════════════════════════════════════╝${NC}"
+echo ""
+
+if [ ! -f "${LOCAL_DB_PATH}" ]; then
+ echo -e "${RED}Error: Database not found at ${LOCAL_DB_PATH}${NC}"
+ exit 1
+fi
+
+# Backup database before cleanup
+if [ "$DRY_RUN" = false ]; then
+ BACKUP_PATH="${LOCAL_DB_PATH}.backup-before-cleanup-$(date +%Y%m%d-%H%M%S)"
+ echo -e "${YELLOW}Creating backup: ${BACKUP_PATH}${NC}"
+ cp "${LOCAL_DB_PATH}" "${BACKUP_PATH}"
+ echo ""
+fi
+
+# Show current state
+echo -e "${BLUE}Current database state:${NC}"
+sqlite3 "${LOCAL_DB_PATH}" < 50 * 1024 * 1024) { // 50 MB limit
+ log.warn(" Image file too large ({}MB): {}", fileSizeBytes / (1024 * 1024), localPath);
+ return false;
+ }
+
// Run object detection on the local file
var labels = detector.detectObjects(localPath);
diff --git a/src/main/java/auctiora/ObjectDetectionService.java b/src/main/java/auctiora/ObjectDetectionService.java
index c9bc9a7..7a4801d 100644
--- a/src/main/java/auctiora/ObjectDetectionService.java
+++ b/src/main/java/auctiora/ObjectDetectionService.java
@@ -104,7 +104,16 @@ public class ObjectDetectionService {
for (var i = 0; i < out.rows(); i++) {
var data = out.get(i, 0);
if (data == null) continue;
+
// 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;
+ }
+
var scores = new double[classNames.size()];
System.arraycopy(data, 5, scores, 0, scores.length);
var classId = argMax(scores);
@@ -117,6 +126,13 @@ public class ObjectDetectionService {
}
}
}
+
+ // Release resources
+ image.release();
+ blob.release();
+ for (var out : outs) {
+ out.release();
+ }
return labels;
}
/**
diff --git a/src/main/java/auctiora/QuarkusWorkflowScheduler.java b/src/main/java/auctiora/QuarkusWorkflowScheduler.java
index b4db690..882d0a1 100644
--- a/src/main/java/auctiora/QuarkusWorkflowScheduler.java
+++ b/src/main/java/auctiora/QuarkusWorkflowScheduler.java
@@ -94,10 +94,20 @@ public class QuarkusWorkflowScheduler {
return;
}
- LOG.infof(" → Processing %d images", pendingImages.size());
+ // Limit batch size to prevent thread blocking (max 100 images per run)
+ final int MAX_BATCH_SIZE = 100;
+ int totalPending = pendingImages.size();
+ if (totalPending > MAX_BATCH_SIZE) {
+ LOG.infof(" → Found %d pending images, processing first %d (batch limit)",
+ totalPending, MAX_BATCH_SIZE);
+ pendingImages = pendingImages.subList(0, MAX_BATCH_SIZE);
+ } else {
+ LOG.infof(" → Processing %d images", totalPending);
+ }
var processed = 0;
var detected = 0;
+ var failed = 0;
for (var image : pendingImages) {
try {
@@ -121,19 +131,26 @@ public class QuarkusWorkflowScheduler {
);
}
}
+ } else {
+ failed++;
}
// Rate limiting (lighter since no network I/O)
Thread.sleep(100);
-
+
} catch (Exception e) {
+ failed++;
LOG.warnf(" ⚠️ Failed to process image: %s", e.getMessage());
}
}
var duration = System.currentTimeMillis() - start;
- LOG.infof(" ✓ Processed %d images, detected objects in %d (%.1fs)",
- processed, detected, duration / 1000.0);
+ LOG.infof(" ✓ Processed %d/%d images, detected objects in %d, failed %d (%.1fs)",
+ processed, totalPending, detected, failed, duration / 1000.0);
+
+ if (totalPending > MAX_BATCH_SIZE) {
+ LOG.infof(" → %d images remaining for next run", totalPending - MAX_BATCH_SIZE);
+ }
} catch (Exception e) {
LOG.errorf(e, " ❌ Image processing failed: %s", e.getMessage());
diff --git a/src/main/resources/META-INF/resources/favicon.svg b/src/main/resources/META-INF/resources/favicon.svg
new file mode 100644
index 0000000..35a350c
--- /dev/null
+++ b/src/main/resources/META-INF/resources/favicon.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/main/resources/META-INF/resources/index.html b/src/main/resources/META-INF/resources/index.html
index a722870..1c2d5a1 100644
--- a/src/main/resources/META-INF/resources/index.html
+++ b/src/main/resources/META-INF/resources/index.html
@@ -1355,11 +1355,32 @@ function updatePagination(total, perPage, currentPage) {
// Trigger workflow with progress tracking
async function triggerWorkflow(workflow) {
- const statusId = `status-${workflow.split('-')[0]}`;
- const progressId = `progress-${workflow.split('-')[0]}`;
+ // Map workflow names to DOM element IDs
+ const idMap = {
+ 'scraper-import': 'scraper',
+ 'image-processing': 'images',
+ 'bid-monitoring': 'bids',
+ 'closing-alerts': 'alerts'
+ };
+
+ const elementId = idMap[workflow];
+ if (!elementId) {
+ console.error('Unknown workflow:', workflow);
+ showToast(`Unknown workflow: ${workflow}`, 'error');
+ return;
+ }
+
+ const statusId = `status-${elementId}`;
+ const progressId = `progress-${elementId}`;
const statusEl = document.getElementById(statusId);
const progressEl = document.getElementById(progressId);
+ if (!statusEl || !progressEl) {
+ console.error('Workflow UI elements not found:', statusId, progressId);
+ showToast(`Cannot trigger workflow: UI elements not found`, 'error');
+ return;
+ }
+
// Update UI
statusEl.innerHTML = 'Running';
statusEl.className = 'text-xs font-semibold px-2 py-1 rounded-full bg-blue-100 text-blue-800';