diff --git a/README.md b/README.md index a369d48..486dc49 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ mvn exec:java -Dexec.mainClass="com.auction.scraper.TroostwijkScraper" ▼ ┌──────────────────┐ │ SQLITE DATABASE │ - │ troostwijk.db │ + │ output/cache.db │ └──────────────────┘ │ ┌─────────────────┼─────────────────┐ @@ -329,19 +329,112 @@ mvn exec:java -Dexec.mainClass="com.auction.scraper.TroostwijkScraper" value > threshold] ``` +```mermaid +flowchart TD + subgraph P1["PHASE 1: EXTERNAL SCRAPER (Python/Playwright)"] + direction LR + A1[Listing Pages/auctions?page=N] --> A2[Extract URLs] + B1[Auction Pages/a/auction-id] --> B2[Parse __NEXT_DATA__ JSON] + C1[Lot Pages/l/lot-id] --> C2[Parse __NEXT_DATA__ JSON] + A2 --> D1[INSERT auctions to SQLite] + B2 --> D1 + C2 --> D2[INSERT lots & image URLs] + + D1 --> DB[(SQLite Databaseoutput/cache.db)] + D2 --> DB + end + + DB --> P2_Entry + + subgraph P2["PHASE 2: MONITORING & PROCESSING (Java)"] + direction TB + P2_Entry[Data Ready] --> Monitor[TroostwijkMonitorRead lots every hour] + P2_Entry --> DBService[DatabaseServiceQuery & Import] + P2_Entry --> Adapter[ScraperDataAdapterTransform TEXT → INTEGER] + + Monitor --> BM[Bid MonitoringCheck API every 1h] + DBService --> IP[Image ProcessingDownload & Analyze] + Adapter --> DataForNotify[Formatted Data] + + BM --> BidUpdate{New bid?} + BidUpdate -->|Yes| UpdateDB[Update current_bid in DB] + UpdateDB --> NotifyTrigger1 + + IP --> Detection[Object DetectionYOLO/OpenCV DNN] + Detection --> ObjectCheck{Detect objects?} + ObjectCheck -->|Vehicle| Save1[Save labels & estimate value] + ObjectCheck -->|Furniture| Save2[Save labels & estimate value] + ObjectCheck -->|Machinery| Save3[Save labels & estimate value] + Save1 --> NotifyTrigger2 + Save2 --> NotifyTrigger2 + Save3 --> NotifyTrigger2 + + CA[Closing AlertsCheck < 5 min] --> TimeCheck{Time critical?} + TimeCheck -->|Yes| NotifyTrigger3 + end + + NotifyTrigger1 --> NS + NotifyTrigger2 --> NS + NotifyTrigger3 --> NS + + subgraph P3["PHASE 3: NOTIFICATION SYSTEM"] + NS[NotificationService] --> DN[Desktop NotifyWindows/macOS/Linux] + NS --> EN[Email NotifyGmail SMTP] + NS --> PL[Set Priority Level0=Normal, 1=High] + end + + DN --> UI[User Interaction & Decisions] + EN --> UI + PL --> UI + + subgraph UI_Details[User Decision Points / Trigger Events] + direction LR + E1["1. BID CHANGE'Nieuw bod op kavel 12345...'Actions: Place bid? Monitor? Ignore?"] + E2["2. OBJECT DETECTED'Lot contains: Vehicle...'Actions: Review? Confirm value?"] + E3["3. CLOSING ALERT'Kavel 12345 sluit binnen 5 min.'Actions: Place final bid? Let expire?"] + E4["4. VIEWING DAY QUESTIONS'Bezichtiging op [date]...'"] + E5["5. ITEM RECOGNITION CONFIRMATION'Detected: [object]...'"] + E6["6. VALUE ESTIMATE APPROVAL'Geschatte waarde: €X...'"] + E7["7. EXCEPTION HANDLING'Afwijkende sluitingstijd...'"] + end + + UI --> UI_Details + + %% Object Detection Sub-Flow Detail + subgraph P2_Detail["Object Detection & Value Estimation Pipeline"] + direction LR + DI[Downloaded Image] --> IPS[ImageProcessingService] + IPS --> ODS[ObjectDetectionService] + ODS --> Load[Load YOLO model] + ODS --> Run[Run inference] + ODS --> Post[Post-process detectionsconfidence > 0.5] + Post --> ObjList["Detected Objects List(80 COCO classes)"] + ObjList --> VEL[Value Estimation LogicFuture enhancement] + VEL --> Match[Match to categories] + VEL --> History[Historical price analysis] + VEL --> Condition[Condition assessment] + VEL --> Market[Market trends] + Market --> ValueEst["Estimated Value RangeConfidence: 75%"] + ValueEst --> SaveToDB[Save to Database] + SaveToDB --> TriggerNotify{Value > threshold?} + end + + IP -.-> P2_Detail + TriggerNotify -.-> NotifyTrigger2 +``` ## Integration Hooks & Timing -| Event | Frequency | Trigger | Notification Type | User Action Required | -|-------|-----------|---------|-------------------|---------------------| -| **New auction discovered** | On scrape | Scraper finds new auction | Desktop + Email (optional) | Review auction | -| **Bid change detected** | Every 1 hour | Monitor detects higher bid | Desktop + Email | Place counter-bid? | -| **Closing soon (< 30 min)** | When detected | Time-based check | Desktop + Email | Review lot | -| **Closing imminent (< 5 min)** | When detected | Time-based check | Desktop + Email (HIGH) | Final bid decision | -| **Object detected** | On image process | YOLO finds objects | Desktop + Email | Confirm identification | -| **Value estimated** | After detection | Estimation complete | Desktop + Email | Approve estimate | -| **Viewing day scheduled** | From lot metadata | Scraper extracts date | Desktop + Email | Confirm attendance | -| **Exception/Change** | On update | Scraper detects change | Desktop + Email (HIGH) | Acknowledge | +| Event | Frequency | Trigger | Notification Type | User Action Required | +|--------------------------------|-------------------|----------------------------|----------------------------|------------------------| +| **New auction discovered** | On scrape | Scraper finds new auction | Desktop + Email (optional) | Review auction | +| **Bid change detected** | Every 1 hour | Monitor detects higher bid | Desktop + Email | Place counter-bid? | +| **Closing soon (< 30 min)** | When detected | Time-based check | Desktop + Email | Review lot | +| **Closing imminent (< 5 min)** | When detected | Time-based check | Desktop + Email (HIGH) | Final bid decision | +| **Object detected** | On image process | YOLO finds objects | Desktop + Email | Confirm identification | +| **Value estimated** | After detection | Estimation complete | Desktop + Email | Approve estimate | +| **Viewing day scheduled** | From lot metadata | Scraper extracts date | Desktop + Email | Confirm attendance | +| **Exception/Change** | On update | Scraper detects change | Desktop + Email (HIGH) | Acknowledge | ## Project Structure diff --git a/src/main/java/auctiora/AuctionMonitorResource.java b/src/main/java/auctiora/AuctionMonitorResource.java index f01e143..0d4ae65 100644 --- a/src/main/java/auctiora/AuctionMonitorResource.java +++ b/src/main/java/auctiora/AuctionMonitorResource.java @@ -6,9 +6,15 @@ import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import org.jboss.logging.Logger; +import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; - +import java.util.stream.Collectors; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.stream.Collectors; /** * REST API for Auction Monitor control and status. * Provides endpoints for: @@ -359,4 +365,170 @@ public class AuctionMonitorResource { .build(); } } + /** + * GET /api/monitor/charts/country-distribution + * Returns dynamic country distribution for charts + */ + @GET + @Path("/charts/country-distribution") + public Response getCountryDistribution() { + try { + var auctions = db.getAllAuctions(); + Map distribution = auctions.stream() + .filter(a -> a.country() != null && !a.country().isEmpty()) + .collect(Collectors.groupingBy( + AuctionInfo::country, + Collectors.counting() + )); + + return Response.ok(distribution).build(); + } catch (Exception e) { + LOG.error("Failed to get country distribution", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", e.getMessage())) + .build(); + } + } + + /** + * GET /api/monitor/charts/category-distribution + * Returns dynamic category distribution for charts + */ + @GET + @Path("/charts/category-distribution") + public Response getCategoryDistribution() { + try { + var lots = db.getAllLots(); + Map distribution = lots.stream() + .filter(l -> l.category() != null && !l.category().isEmpty()) + .collect(Collectors.groupingBy( + l -> l.category().length() > 20 ? l.category().substring(0, 20) + "..." : l.category(), + Collectors.counting() + )); + + return Response.ok(distribution).build(); + } catch (Exception e) { + LOG.error("Failed to get category distribution", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", e.getMessage())) + .build(); + } + } + + /** + * GET /api/monitor/charts/bidding-trend + * Returns time series data for last N hours + */ + @GET + @Path("/charts/bidding-trend") + public Response getBiddingTrend(@QueryParam("hours") @DefaultValue("24") int hours) { + try { + var lots = db.getAllLots(); + Map trends = new HashMap<>(); + + // Initialize hours + LocalDateTime now = LocalDateTime.now(); + for (int i = hours - 1; i >= 0; i--) { + LocalDateTime hour = now.minusHours(i); + int hourKey = hour.getHour(); + trends.put(hourKey, new TrendHour(hourKey, 0, 0)); + } + + // Count lots and bids per hour (mock implementation - in real app, use timestamp data) + // This is a simplified version - you'd need actual timestamps in DB + for (var lot : lots) { + if (lot.closingTime() != null) { + int hour = lot.closingTime().getHour(); + TrendHour trend = trends.getOrDefault(hour, new TrendHour(hour, 0, 0)); + trend.lots++; + if (lot.currentBid() > 0) trend.bids++; + } + } + + return Response.ok(trends.values()).build(); + } catch (Exception e) { + LOG.error("Failed to get bidding trend", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", e.getMessage())) + .build(); + } + } + + /** + * GET /api/monitor/charts/insights + * Returns intelligent insights + */ + @GET + @Path("/charts/insights") + public Response getInsights() { + try { + var lots = db.getAllLots(); + var auctions = db.getAllAuctions(); + + List> insights = new ArrayList<>(); + + // Calculate insights + long criticalCount = lots.stream().filter(l -> l.minutesUntilClose() < 30).count(); + if (criticalCount > 10) { + insights.add(Map.of( + "icon", "fa-exclamation-circle", + "title", criticalCount + " lots closing soon", + "description", "High urgency items require attention" + )); + } + + double bidRate = lots.stream().filter(l -> l.currentBid() > 0).count() * 100.0 / lots.size(); + if (bidRate > 60) { + insights.add(Map.of( + "icon", "fa-chart-line", + "title", String.format("%.1f%% bid rate", bidRate), + "description", "Strong market engagement detected" + )); + } + + long imageCoverage = db.getImageCount() * 100 / Math.max(lots.size(), 1); + if (imageCoverage < 80) { + insights.add(Map.of( + "icon", "fa-images", + "title", imageCoverage + "% image coverage", + "description", "Consider processing more images" + )); + } + + // Add geographic insight + String topCountry = auctions.stream() + .collect(Collectors.groupingBy(AuctionInfo::country, Collectors.counting())) + .entrySet().stream() + .max(Map.Entry.comparingByValue()) + .map(Map.Entry::getKey) + .orElse("N/A"); + + insights.add(Map.of( + "icon", "fa-globe", + "title", topCountry + " leading", + "description", "Top performing country" + )); + + return Response.ok(insights).build(); + } catch (Exception e) { + LOG.error("Failed to get insights", e); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(Map.of("error", e.getMessage())) + .build(); + } + } + + // Helper class for trend data + public static class TrendHour { + + public int hour; + public int lots; + public int bids; + + public TrendHour(int hour, int lots, int bids) { + this.hour = hour; + this.lots = lots; + this.bids = bids; + } + } } diff --git a/src/main/resources/META-INF/resources/index.html b/src/main/resources/META-INF/resources/index.html index 0746686..dda3d0f 100644 --- a/src/main/resources/META-INF/resources/index.html +++ b/src/main/resources/META-INF/resources/index.html @@ -3,222 +3,1397 @@ - Scrape-UI 1 - Enterprise + Auctiora - Intelligence Dashboard + + + - - - - Scrape-UI Enterprise - Powered by Quarkus + Modern Frontend - - + + - - - - - - Build & Runtime Status - - - - - 📦 Maven Build - - - Group: - - - - - Artifact: - - - - - Version: - - - - - - - - - 🚀 Runtime - - - Status: - - - - - Java: - - - - - Platform: - - - - - + + + + + + + + Auctiora Intelligence Dashboard + + Real-time Auction Analytics & Predictive Monitoring - - - - - - Last Updated - - - - - 🔄 Refresh + + + + System Online + Uptime: 00:00:00 + + Last updated: --:-- + + + Auto: ON + + + Refresh - - - - API Test - - Test Greeting API - - - Click the button to test the API + + + + + + + + + + Active Auctions + + + + + -- + + + + + + + + + + + + Total Lots + + + + + -- + + + + + + + + + + + + Image Assets + + + + + -- + + + + + + + + + + + + Active Bidding + + + + + -- + + + + + + + + + + + + Closing Soon + + + + + -- + + + + + + - - - - ⚡ Quarkus Backend - Fast startup, low memory footprint, optimized for containers + + + + + Live Insights - - 🚀 REST API - RESTful endpoints with JSON responses + + + Loading insights... + - - 🎨 Modern UI - Responsive design with Tailwind CSS + + + + + + + + + Bidding Intelligence + + + + -- lots/hour + + + + + Lots with Bids + + + + --% conversion + + + Total Bid Value + + + + --% vs avg + + + Average Bid + + + + --% premium + + + + + + + + API Performance + + + + + Total Requests + + + + + + + + Success Rate + + + + + + + + Failed + + + + + + + + Avg Latency + + + + + + + + + + + + + + + Workflow Orchestration + + + All systems ready + + + + + + + + Ready + + + Scraper Import + Load external auction data + + + + + + + + + + Ready + + + Vision AI Processing + Object detection & labeling + + + + + + + + + + Ready + + + Bid Intelligence + Track & analyze bidding patterns + + + + + + + + + + Ready + + + Closing Alerts + Proactive notifications (30min) + + + + + + + + + + + + + + + Geographic Distribution + + + Live + + + + + + + + + + + -- + Top Market + + + -- + Countries + + + -- + Growth + + + + + + + + + + Category Intelligence + + + Live + + + + + + + + + + + -- + Top Category + + + -- + Categories + + + -- + Avg Bids + + + + + + + + + + Market Activity Trend (Last 24h) + + + + + + + + + + + + + + + Critical Time Alerts + + -- + + + + + All Auctions + + + All Countries + + + + Filter + + + Reset + + + Export + + + Refresh + + + + + + + + + Lot ID + + + Title + + + Current Bid + + + Closing Time + + + Time Left + + + Actions + + + + + + + + Loading critical alerts... + + + + + + + Showing 0 of 0 lots + + + + + + + + + + Activity Intelligence + + + + Clear + + + Export + + + + + + + --:-- + Dashboard initialized and monitoring started... + + + + Last 50 events | Auto-scroll enabled + Errors: 0 + + + -
Powered by Quarkus + Modern Frontend
Real-time Auction Analytics & Predictive Monitoring
Last Updated
-
Click the button to test the API
Fast startup, low memory footprint, optimized for containers
RESTful endpoints with JSON responses
Responsive design with Tailwind CSS