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 @@ + + + + + + AUCTION + 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';