fix: Data freshness - only show active auctions

CRITICAL FIXES:
- API: Added end_time > now() filter to all auction queries
- Scheduler: Cleanup expired auctions every 15 minutes
- Scheduler: Scrape auctions every 2 hours (was 1 hour)
- Scheduler: Sniper alert matching every 30 minutes

Affected endpoints:
- GET /auctions (search)
- GET /auctions/feed (unified)
- GET /auctions/hot
- GET /auctions/ending-soon (already had filter)

Updated MARKET_CONCEPT.md with:
- 3 pillars: Pounce Direct, Live Auctions, Drops Tomorrow
- Data freshness architecture
- Unicorn roadmap
This commit is contained in:
yves.gugger
2025-12-11 09:55:27 +01:00
parent e9f06d1cbf
commit 22cb9561ad
3 changed files with 355 additions and 427 deletions

View File

@ -6,504 +6,342 @@
## 📋 Executive Summary ## 📋 Executive Summary
Die **Market Page** ist das Herzstück von Pounce. Hier fließen alle Datenquellen zusammen und werden dem User als **"Clean Feed"** präsentiert. Die **Market Page** ist das Herzstück von Pounce. Hier fließen alle Datenquellen zusammen:
### Vision (aus pounce_terminal.md) 1. **Pounce Direct** — User-Listings (unser USP, 0% Provision)
> *"Die Market Page zeigt alle Domains die entweder:* 2. **Live Auktionen** — Externe Plattformen (GoDaddy, Sedo, etc.)
> 1. *Zu Verkauf stehen (Auktionen)* 3. **Drops Tomorrow** — Domains bevor sie in Auktionen landen (Phase 3)
> 2. *Bald frei werden (Drops)*
> 3. *Über Pounce direkt angeboten werden (Pounce Direct)"*
### Aktueller Stand: Phase 1 — Intelligence ### Der Weg zum Unicorn (aus pounce_strategy.md)
> *"Der Weg zum Unicorn führt nicht über besseres Scraping, sondern über einzigartigen Content."*
**Aggregation kann jeder. Pounce Direct ist unser USP.**
---
## 🔧 KRITISCHE FIXES (Implementiert am 11.12.2025)
### Problem: Veraltete Daten wurden angezeigt
```
VORHER: Abgelaufene Auktionen wurden im Feed angezeigt
→ Schlechte User Experience
→ Vertrauensverlust
```
### Lösung: Multi-Layer Data Freshness
```python
# 1. API-Filter: Nur laufende Auktionen
query = select(DomainAuction).where(
and_(
DomainAuction.is_active == True,
DomainAuction.end_time > datetime.utcnow() # ← NEU!
)
)
# 2. Scheduler: Cleanup alle 15 Minuten
scheduler.add_job(
cleanup_expired_auctions,
CronTrigger(minute='*/15'), # Alle 15 Minuten
id="auction_cleanup"
)
# 3. Scraper: Alle 2 Stunden frische Daten
scheduler.add_job(
scrape_auctions,
CronTrigger(hour='*/2', minute=30), # Alle 2 Stunden
id="auction_scrape"
)
```
---
## 📊 Die 3 Säulen des Market
### Säule 1: POUNCE DIRECT (Unser USP!)
> *"Das sind die Domains, die es NUR bei Pounce gibt."*
``` ```
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
│ POUNCE MARKET — Aktueller Datenfluss 💎 POUNCE DIRECT
├─────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────┤
│ │ │ │
DATENQUELLEN: Warum es genial ist:
│ ───────────────────────────────────────────────────────────── │ │ ───────────────────────────────────────────────────────────── │
│ ✓ Unique Content (nur bei uns!) │
│ ✓ 0% Provision (vs. 15-20% bei Sedo) │
│ ✓ DNS-Verifizierung = Trust │
│ ✓ Instant Buy (kein Bieten) │
│ ✓ SEO: Jedes Listing = eigene Landing Page │
│ │ │ │
📦 WEB SCRAPING (Hauptquelle) Der Flow:
│ └─→ ExpiredDomains.net (325 Auktionen) ✅ │
│ └─→ GoDaddy RSS Feed (10 Auktionen) ✅ │
│ └─→ Sedo Public (7 Auktionen) ✅ │
│ └─→ NameJet Public (6 Auktionen) ✅ │
│ └─→ DropCatch Public (7 Auktionen) ✅ │
│ │
│ 🔌 OFFIZIELLE APIs (Konfiguriert) │
│ └─→ DropCatch Partner API ⚠️ (Nur eigene Aktivitäten) │
│ └─→ Sedo Partner API ⏳ (Credentials fehlen) │
│ │
│ 💎 POUNCE DIRECT (User-Listings) │
│ └─→ DNS-verifizierte Verkaufsangebote ❌ (0 Listings) │
│ │
│ 🔮 ZONE FILES (Phase 3 — Zukunft) │
│ └─→ Verisign .com/.net 🔜 │
│ └─→ PIR .org 🔜 │
│ │
│ ───────────────────────────────────────────────────────────── │ │ ───────────────────────────────────────────────────────────── │
TOTAL: 355 Domains im Feed | 0 Pounce Direct 1. User listet Domain (Trader/Tycoon Abo)
│ 2. DNS-Verifizierung (TXT Record) │
│ 3. Listing erscheint im Market Feed │
│ 4. Käufer kontaktiert Verkäufer (nach Login) │
│ │ │ │
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
``` ```
--- **Status:** ⏳ 0 Listings — Muss aktiviert werden!
## 📊 TEIL 1: Bestandsaufnahme — Was haben wir?
### A. Backend-Komponenten ✅
| Komponente | Status | Beschreibung |
|------------|--------|--------------|
| **Unified Feed API** `/auctions/feed` | ✅ Live | Kombiniert Pounce Direct + External |
| **Pounce Score v2.0** | ✅ Live | Length, TLD, Bids, Time Pressure |
| **Vanity Filter** | ✅ Live | Premium-Domains für Public Users |
| **Auction Scraper** | ✅ Läuft | 5 Plattformen, Scheduler aktiv |
| **Listings API** | ✅ Fertig | DNS-Verifizierung, Inquiry-System |
| **Sniper Alerts** | ✅ Fertig | Keyword-Matching, Notifications |
### B. Frontend-Komponenten ✅
| Seite | Status | Beschreibung |
|-------|--------|--------------|
| `/terminal/market` | ✅ Live | Vollständiger Market Feed für Auth Users |
| `/auctions` | ✅ Live | Public Market mit Vanity Filter |
| `/buy` | ✅ Live | Pounce Direct Marketplace Browse |
| `/buy/[slug]` | ✅ Live | Listing-Detailseite |
| `/terminal/listing` | ✅ Live | Seller Dashboard |
### C. Datenquellen — Realitätscheck
#### Offizielle APIs — Die Ernüchterung
**DropCatch API:**
```
Status: ✅ Authentifiziert
Problem: Zeigt nur EIGENE Aktivitäten (Bids, Backorders)
NICHT das öffentliche Auktionsinventar
Nutzen: User-Integration (verbinde dein DropCatch-Konto)
```
**Sedo API:**
```
Status: ⏳ Client bereit, Credentials fehlen
Wo finden: Sedo.com → Mein Sedo → API-Zugang
Benötigt: Partner ID + SignKey
```
#### Web Scraping — Unsere Hauptquelle
```python
# Aktuelle Scraper-Architektur
TIER_1_APIS = [
("DropCatch", _fetch_dropcatch_api), # Für eigene Aktivitäten
("Sedo", _fetch_sedo_api), # Wenn konfiguriert
]
TIER_2_SCRAPING = [
("ExpiredDomains", _scrape_expireddomains), # 325 Domains
("GoDaddy", _scrape_godaddy_rss), # 10 Domains
("Sedo", _scrape_sedo_public), # 7 Domains (Fallback)
("NameJet", _scrape_namejet_public), # 6 Domains
("DropCatch", _scrape_dropcatch_public), # 7 Domains (Fallback)
]
```
--- ---
## 🎯 TEIL 2: Das Konzept — Die 3 Säulen des Market ### Säule 2: LIVE AUKTIONEN (Content Filler)
### Säule 1: AUKTIONEN (Externe Plattformen)
> *"Zeige alle relevanten Auktionen von GoDaddy, Sedo, NameJet, etc."* > *"Zeige alle relevanten Auktionen von GoDaddy, Sedo, NameJet, etc."*
**Datenquellen:**
- Web Scraping (primär)
- Partner APIs (wenn verfügbar)
**Filter-Strategie:**
```python
# Vanity Filter für Public Users (aus pounce_features.md)
def is_premium_domain(domain: str) -> bool:
name, tld = domain.rsplit('.', 1)
# Premium TLDs only
if tld not in ['com', 'io', 'ai', 'co', 'ch', 'de', 'net', 'org', 'app', 'dev']:
return False
# Keine Spam-Muster
if len(name) > 12: return False
if '-' in name: return False
if sum(c.isdigit() for c in name) > 1: return False
return True
```
**UI-Darstellung:**
| Domain | Source | Price | Status | Action |
|--------|--------|-------|--------|--------|
| **crypto-bank.io** | 🏢 GoDaddy | $2,500 | ⏱️ 2h left | [Bid ↗] |
| **meta-shop.com** | 🏢 Sedo | $5,000 | 🤝 Offer | [View ↗] |
---
### Säule 2: POUNCE DIRECT (User-Listings)
> *"Das sind die Domains, die es NUR bei Pounce gibt. Unser USP."*
**Das Konzept (aus pounce_terminal.md):**
``` ```
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
POUNCE DIRECT — Der Listing-Wizard 🏢 LIVE AUKTIONEN
├─────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────┤
│ │ │ │
STEP 1: DOMAIN EINGEBEN Datenquellen:
│ ─────────────────────────────────────────────────────────── │ ─────────────────────────────────────────────────────────────
Domain: [zurich-immo.ch___________] 📦 Web Scraping (Hauptquelle)
Preis: [$950_______] ○ Fixpreis ● Verhandlungsbasis └─→ ExpiredDomains.net (~350 Domains)
│ └─→ GoDaddy RSS │
│ └─→ Sedo Public │
│ └─→ NameJet Public │
│ └─→ DropCatch Public │
│ │ │ │
STEP 2: DNS VERIFICATION (Trust-Check) Data Freshness:
│ ─────────────────────────────────────────────────────────── │ ─────────────────────────────────────────────────────────────
Füge diesen TXT-Record bei deinem Registrar hinzu: ⏱️ Scraping: Alle 2 Stunden
│ 🧹 Cleanup: Alle 15 Minuten │
│ ✅ Filter: Nur end_time > now() │
│ │ │ │
Name: _pounce-verify Qualitätsfilter:
Value: pounce-verify-8a3f7b9c2e1d ─────────────────────────────────────────────────────────────
• Vanity Filter für Public (nur Premium-Domains)
[🔄 VERIFY DNS] • Pounce Score (0-100)
• TLD Filter (com, io, ai, etc.)
│ STEP 3: LIVE! │
│ ─────────────────────────────────────────────────────────── │
│ ✅ Domain verifiziert! │
│ Dein Listing erscheint jetzt im Market Feed. │
│ │ │ │
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
``` ```
**Warum das genial ist:** **Status:** ✅ ~361 aktive Auktionen
| Vorteil | Erklärung |
|---------|-----------|
| **Unique Content** | Domains, die es NUR bei Pounce gibt |
| **Höhere Conversion** | "Instant Buy" statt "Bid on external site" |
| **Vendor Lock-in** | Verkäufer listen bei uns (0% Provision!) |
| **SEO Power** | Jede Listing = eigene Landing Page |
| **Trust Signal** | DNS-Verifizierung = Qualitätsgarantie |
**UI-Darstellung:**
| Domain | Source | Price | Status | Action |
|--------|--------|-------|--------|--------|
| **zurich-immo.ch** | 💎 **Pounce** | **$950** | ⚡ **Instant** | **[Buy Now]** |
--- ---
### Säule 3: DROPS (Domains die bald frei werden) ### Säule 3: DROPS TOMORROW (Tycoon Exclusive)
> *"Zeige Domains BEVOR sie in Auktionen landen."* > *"Zeige Domains BEVOR sie in Auktionen landen."*
**Phase 1 (Jetzt): Deleted Domains via Scraping**
```
ExpiredDomains.net → Deleted Domains Liste → Pounce Filter → Feed
```
**Phase 3 (Zukunft): Zone File Analysis**
``` ```
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
ZONE FILE PIPELINE — Die Unicorn-Strategie 🔮 DROPS TOMORROW — Phase 3
├─────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────┤
│ │ │ │
1. DAILY DOWNLOAD (4:00 UTC) Das Konzept:
└─→ Zone Files von Verisign, PIR, etc. ─────────────────────────────────────────────────────────────
│ 1. Zone Files von Verisign (.com/.net) beziehen │
│ 2. Tägliche Diff-Analyse (was war gestern da, ist heute weg) │
│ 3. Diese Domains droppen in 1-5 Tagen! │
│ 4. Pounce Algorithm filtert nur Premium-Domains │
│ │ │ │
2. DIFF ANALYSIS Warum das ein MONOPOL schafft:
└─→ Was war gestern da, ist heute weg? ─────────────────────────────────────────────────────────────
└─→ Diese Domains DROPPEN in 1-5 Tagen! • ExpiredDomains zeigt ALLES (Spam-Hölle)
• Pounce zeigt nur die TOP 100 (kuratiert)
3. POUNCE ALGORITHM • = Zeitersparnis = Premium Feature = $29/Monat
│ └─→ Nur Premium-Domains durchlassen (Score > 70) │
│ │
│ 4. OUTPUT: "Drops Tomorrow" (Tycoon Exclusive) │
│ └─→ Domains BEVOR sie in Auktionen erscheinen │
│ │ │ │
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
``` ```
--- **Status:** 🔜 Geplant (6-12 Monate)
## 🔧 TEIL 3: Technische Architektur
### Der Unified Feed API Endpoint
```python
# backend/app/api/auctions.py
@router.get("/feed")
async def get_market_feed(
source: str = Query("all", enum=["all", "pounce", "external"]),
keyword: Optional[str] = None,
tld: Optional[str] = None,
min_price: Optional[float] = None,
max_price: Optional[float] = None,
min_score: int = Query(0, ge=0, le=100),
ending_within: Optional[int] = None, # Stunden
verified_only: bool = False,
sort_by: str = Query("score", enum=["score", "price_asc", "price_desc", "time", "newest"]),
limit: int = Query(50, le=200),
offset: int = Query(0),
current_user: Optional[User] = Depends(get_current_user_optional),
):
"""
🚀 UNIFIED MARKET FEED — Das Herz von Pounce
Kombiniert:
- 💎 Pounce Direct: DNS-verifizierte User-Listings (Instant Buy)
- 🏢 External Auctions: Scraped von GoDaddy, Sedo, etc.
- 🔮 Drops: Domains die bald frei werden (Phase 3)
Für nicht-authentifizierte User:
- Vanity Filter aktiv (nur Premium-Domains)
- Pounce Score sichtbar, aber limited Details
Für authentifizierte User (Trader/Tycoon):
- Vollzugriff auf alle Domains
- Advanced Filtering
- Valuation Data
"""
```
### Pounce Score v2.0
```python
def calculate_pounce_score_v2(domain: str, auction_data: dict) -> int:
"""
Der Pounce Score — Qualitäts- und Opportunity-Bewertung
A) INTRINSIC VALUE (Domain selbst)
- Länge (kurz = wertvoll)
- TLD Premium (com > io > xyz)
- Dictionary Word Bonus
B) MARKET SIGNALS (Aktivität)
- Bid Activity (mehr Bids = mehr Interesse)
- Time Pressure (endet bald = Opportunity)
- Price-to-Value Ratio (unterbewertet = 🔥)
C) PENALTIES
- Bindestriche (-30)
- Zahlen wenn >3 Zeichen (-20)
- Zu lang >15 Zeichen (-25)
"""
score = 50 # Baseline
name = domain.rsplit('.', 1)[0]
tld = domain.rsplit('.', 1)[1]
# Länge
if len(name) <= 3: score += 30
elif len(name) == 4: score += 25
elif len(name) == 5: score += 20
elif len(name) <= 7: score += 10
# TLD
tld_scores = {'com': 20, 'ai': 25, 'io': 18, 'co': 12, 'ch': 15}
score += tld_scores.get(tld, 0)
# Market Signals
bids = auction_data.get('num_bids', 0)
if bids >= 20: score += 15
elif bids >= 10: score += 10
elif bids >= 5: score += 5
# Penalties
if '-' in name: score -= 30
if any(c.isdigit() for c in name) and len(name) > 3: score -= 20
return max(0, min(100, score))
```
--- ---
## 📈 TEIL 4: Roadmap ## 🎨 UI/UX: Die Market Page
### ✅ ERLEDIGT (Stand: 11. Dezember 2025)
- [x] Unified Feed API `/auctions/feed`
- [x] Pounce Score v2.0 mit Market Signals
- [x] Vanity Filter für Public Users
- [x] Pounce Direct Listing-System (DNS-Verifizierung)
- [x] Sniper Alerts mit Keyword-Matching
- [x] Web Scraping für 5 Plattformen
- [x] DropCatch API Client (für User-Integration)
- [x] Sedo API Client (bereit für Credentials)
### 🎯 NÄCHSTE SCHRITTE (Diese Woche)
1. **Sedo API Credentials eingeben**
- Sedo.com → Mein Sedo → API-Zugang
- Partner ID + SignKey in `.env`
2. **Erste Pounce Direct Listings erstellen**
- Test-Domains zum Verifizieren des Flows
- Zeigt "Unique Content" im Feed
3. **Scraper-Stabilität verbessern**
- Fallback-Logik testen
- Error-Handling optimieren
### 🔮 PHASE 3 (6-12 Monate)
1. **Zone File Access beantragen**
- Verisign (.com/.net)
- PIR (.org)
- Kosten: $0-$10,000/Jahr
2. **"Drops Tomorrow" Feature**
- Zone File Diff-Analyse
- Tycoon Exclusive ($29/mo)
3. **Pounce Instant Exchange**
- Integrierter Escrow-Service
- 5% Gebühr (statt 15-20% bei Konkurrenz)
---
## 🎨 TEIL 5: UI/UX Design
### Die Master-Tabelle (aus pounce_terminal.md)
| Spalte | Inhalt | Visualisierung |
|--------|--------|----------------|
| **Domain** | Name der Domain | Fettgedruckt. Bei "Pounce Direct" → 💎 Icon |
| **Pounce Score** | Qualitäts-Algorithmus | 0-100 (Grün > 80, Gelb 50-80, Rot < 50) |
| **Price / Bid** | Preis oder aktuelles Gebot | `$500` oder `$50 (Bid)` |
| **Status / Time** | Countdown oder Verfügbarkeit | `4h left` oder `Instant` |
| **Source** | Herkunft | 🏢 GoDaddy, 💎 Pounce |
| **Action** | Der Button | `[Bid ↗]` oder `[Buy Now]` |
### Filter Bar ### Filter Bar
``` ```
[Toggle] Hide Spam (Standard: AN) [] Hide Spam [○] Pounce Only [TLD ▾] [Price ▾] [Ending ▾]
[Toggle] Pounce Direct Only
[Dropdown] TLD: .com, .ai, .io, .ch
[Dropdown] Price: < $100, < $1k, High Roller
[Dropdown] Ending: 1h, 4h, 24h, 7d
``` ```
### Die Master-Tabelle
| Spalte | Inhalt | Visualisierung |
|--------|--------|----------------|
| **Domain** | Name | Fettgedruckt. 💎 Icon für Pounce Direct |
| **Score** | Pounce Score | 0-100 (Grün > 80, Gelb 50-80, Rot < 50) |
| **Price** | Preis/Gebot | `$500` oder `$50 (Bid)` |
| **Status** | Zeit/Verfügbarkeit | `4h left` oder `Instant` |
| **Source** | Herkunft | 🏢 GoDaddy, 💎 Pounce |
| **Action** | Button | `[Bid ↗]` oder `[Buy Now]` |
### Visuelle Hierarchie ### Visuelle Hierarchie
```tsx
// Pounce Direct Items werden prominent angezeigt
{pounceDirectItems.length > 0 && (
<section className="mb-6">
<div className="flex items-center gap-2 text-emerald-400 font-bold mb-3">
<Diamond className="w-4 h-4" />
Pounce Exclusive Verified Instant Buy
</div>
{pounceDirectItems.map(item => <PounceDirectCard />)}
</section>
)}
// External Auctions darunter
<section>
<h2>Active Auctions</h2>
{externalItems.map(item => <AuctionCard />)}
</section>
```
---
## 💰 TEIL 6: Monetarisierung
### Tier-basierte Features (aus pounce_pricing.md)
| Feature | Scout ($0) | Trader ($9) | Tycoon ($29) |
|---------|------------|-------------|--------------|
| **Market Feed** | 🌪 Raw (Vanity Filter) | Curated (Clean) | Curated + Priority |
| **Alert Speed** | 🐢 Daily | 🐇 Hourly | Real-Time (10m) |
| **Watchlist** | 5 Domains | 50 Domains | 500 Domains |
| **Sell Domains** | | 5 Listings | 50 Listings + Featured |
| **Pounce Score** | Locked | Basic | + SEO Data |
| **Drops Tomorrow** | | | Exclusive |
### Die "Conversion-Falle" (aus pounce_features.md)
Wenn ein nicht-eingeloggter User auf "Buy Now" bei einem Pounce Direct Listing klickt:
``` ```
┌─────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────┐
🔒 Secure Transaction MARKET FEED
├─────────────────────────────────────────────────────────────────┤ ├─────────────────────────────────────────────────────────────────┤
│ │ │ │
Du bist dabei, ein verifiziertes Direct-Listing anzusehen. 💎 POUNCE EXCLUSIVE — Verified Instant Buy
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ zurich-immo.ch $950 ⚡ Instant ✅ Verified [Buy] │ │
│ │ crypto-hub.io $2.5k ⚡ Instant ✅ Verified [Buy] │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │ │
Um den Verkäufer zu kontaktieren und Käuferschutz zu 🏢 LIVE AUCTIONS
genießen, logge dich bitte ein. ┌───────────────────────────────────────────────────────────┐
│ │ techflow.io $250 ⏱️ 4h left GoDaddy [Bid ↗] │ │
│ │ datalab.com $1.2k ⏱️ 23h left Sedo [Bid ↗] │ │
│ │ nexus.ai $5k ⏱️ 2d left NameJet [Bid ↗] │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │ │
[Login] [Create Free Scout Account] 🔮 DROPS TOMORROW (Tycoon Only)
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 🔒 Upgrade to Tycoon to see domains dropping tomorrow │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │ │ │
└─────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────┘
``` ```
--- ---
## 🔑 TEIL 7: Kritische Erkenntnisse ## 💰 Monetarisierung (aus pounce_pricing.md)
### API-Realität vs. Erwartung | Feature | Scout ($0) | Trader ($9) | Tycoon ($29) |
|---------|------------|-------------|--------------|
| API | Erwartung | Realität | | **Market Feed** | 🌪 Vanity Filter | Clean | Clean + Priority |
|-----|-----------|----------| | **Alert Speed** | 🐢 Daily | 🐇 Hourly | Real-Time (10m) |
| **DropCatch** | Alle öffentlichen Auktionen | Nur eigene Bids/Backorders | | **Watchlist** | 5 Domains | 50 Domains | 500 Domains |
| **Sedo** | TBD | Credentials fehlen noch | | **Sell Domains** | | 5 Listings | 50 + Featured |
| **Pounce Score** | Locked | Basic | + SEO Data |
**Konsequenz:** | **Drops Tomorrow** | | | Exclusive |
- Web Scraping bleibt unsere **Hauptquelle** für öffentliche Daten
- APIs sind nützlich für **User-Integration** (verbinde dein DropCatch-Konto)
- **Zone Files** sind der langfristige Weg zur Datenhoheit
### Der echte USP: Pounce Direct
> *"Domains die es NUR bei Pounce gibt."*
Das ist der Schlüssel. Nicht die Aggregation (das kann jeder), sondern der **Unique Content** durch User-Listings.
**Priorität:** Erste Pounce Direct Listings aktivieren!
--- ---
## 📋 Checkliste für den Launch ## ⚙️ Technische Architektur
### Backend ### Scheduler Jobs
- [x] Unified Feed API
```python
# Aktive Jobs (Scheduler)
# ─────────────────────────────────────────────────────────────────
# 1. Auction Scrape — Alle 2 Stunden
scheduler.add_job(scrape_auctions, CronTrigger(hour='*/2', minute=30))
# 2. Expired Cleanup — Alle 15 Minuten (KRITISCH!)
scheduler.add_job(cleanup_expired_auctions, CronTrigger(minute='*/15'))
# 3. Sniper Matching — Alle 30 Minuten
scheduler.add_job(match_sniper_alerts, CronTrigger(minute='*/30'))
# 4. TLD Prices — Täglich 03:00 UTC
scheduler.add_job(scrape_tld_prices, CronTrigger(hour=3))
```
### API Endpoints
```python
# Market Feed Endpoints
# ─────────────────────────────────────────────────────────────────
GET /api/v1/auctions/feed # Unified Feed (Pounce + External)
GET /api/v1/auctions # External Auctions only
GET /api/v1/auctions/ending-soon
GET /api/v1/auctions/hot
GET /api/v1/listings # Pounce Direct Listings
```
### Data Freshness Garantie
```python
# Jede Query filtert automatisch auf aktive Auktionen:
query = select(DomainAuction).where(
and_(
DomainAuction.is_active == True,
DomainAuction.end_time > datetime.utcnow() # ← IMMER!
)
)
```
---
## 📈 Roadmap
### ✅ ERLEDIGT (11. Dezember 2025)
- [x] Unified Feed API `/auctions/feed`
- [x] Pounce Score v2.0 - [x] Pounce Score v2.0
- [x] Vanity Filter - [x] Vanity Filter
- [x] Scraper aktiv - [x] Web Scraping (5 Plattformen)
- [ ] Sedo API Credentials eingeben - [x] **FIX: end_time Filter** (nur laufende Auktionen)
- [ ] Scheduler-Intervall optimieren - [x] **FIX: Cleanup alle 15 Minuten**
- [x] **FIX: Scraper alle 2 Stunden**
- [x] Sniper Alerts
### Frontend ### 🎯 NÄCHSTE SCHRITTE (Diese Woche)
- [x] Terminal Market Page
- [x] Public Auctions Page
- [x] Pounce Direct Highlighting
- [x] Filter (Source, TLD, Price)
- [ ] "Hot Right Now" Section
- [ ] Better Empty States
### Content 1. **Erste Pounce Direct Listings erstellen**
- [ ] Erste 5 Test-Listings erstellen - Test-Domains zum Verifizieren des Flows
- [ ] DNS-Verifizierung testen - USP aktivieren!
- [ ] Listing-to-Feed Flow validieren
2. **Sedo API Credentials eingeben**
- Sedo.com Mein Sedo API-Zugang
- Partner ID + SignKey in `.env`
3. **Frontend: "Live" Indikator**
- Zeige wann Daten zuletzt aktualisiert wurden
### 🔮 PHASE 2-3 (6-12 Monate)
1. **Zone File Access beantragen**
- Verisign (.com/.net)
- "Drops Tomorrow" Feature
2. **Pounce Instant Exchange**
- Integrierter Escrow-Service
- 5% Gebühr
--- ---
## 💎 Fazit ## 🚀 Der Unicorn-Pfad
Die Market Page ist **funktional**, aber der wahre USP (Pounce Direct) ist noch nicht aktiviert. ```
Phase 1: INTELLIGENCE (Jetzt)
├── Pounce Direct aktivieren (Unique Content)
├── Clean Feed (aktuelle Daten, Spam-frei)
├── Trust aufbauen
└── 10.000 User, $1M ARR
**Die Reihenfolge:** Phase 2: LIQUIDITÄT (18-36 Monate)
1. Aggregation funktioniert (Scraping) ├── Pounce Instant Exchange
2. Pounce Direct aktivieren (User-Listings) ├── Buy Now im Dashboard
3. 🔮 Zone Files für Datenhoheit (Phase 3) ├── 5% Gebühr
└── $10M ARR
> *"Der Weg zum Unicorn führt nicht über besseres Scraping, sondern über einzigartigen Content."* Phase 3: FINANZIALISIERUNG (3-5 Jahre)
├── Fractional Ownership
├── Domain-Backed Lending
└── = FINTECH ($50-100M ARR)
Phase 4: IMPERIUM (5+ Jahre)
├── Enterprise Sentinel (B2B)
├── Fortune 500 Kunden
└── = $1 Mrd. Bewertung
```
---
## 💎 Das Fazit
**Aggregation ist Commodity. Pounce Direct ist der USP.**
Der Weg zum Unicorn:
1. Datenqualität (aktuelle, saubere Daten)
2. Unique Content (Pounce Direct aktivieren!)
3. 🔮 Datenhoheit (Zone Files)
> *"Don't guess. Know."*
> >
> — pounce_strategy.md > — Phase 1: Intelligence

View File

@ -277,8 +277,14 @@ async def search_auctions(
- Look for value_ratio > 1.0 (estimated value exceeds current bid) - Look for value_ratio > 1.0 (estimated value exceeds current bid)
- Focus on auctions ending soon with low bid counts - Focus on auctions ending soon with low bid counts
""" """
# Build query # Build query - ONLY show active auctions that haven't ended yet
query = select(DomainAuction).where(DomainAuction.is_active == True) now = datetime.utcnow()
query = select(DomainAuction).where(
and_(
DomainAuction.is_active == True,
DomainAuction.end_time > now # ← KRITISCH: Nur Auktionen die noch laufen!
)
)
# VANITY FILTER: For public (non-logged-in) users, only show premium-looking domains # VANITY FILTER: For public (non-logged-in) users, only show premium-looking domains
# This ensures the first impression is high-quality, not spam domains # This ensures the first impression is high-quality, not spam domains
@ -457,9 +463,15 @@ async def get_hot_auctions(
Data is scraped from public auction sites - no mock data. Data is scraped from public auction sites - no mock data.
""" """
now = datetime.utcnow()
query = ( query = (
select(DomainAuction) select(DomainAuction)
.where(DomainAuction.is_active == True) .where(
and_(
DomainAuction.is_active == True,
DomainAuction.end_time > now # Only show active auctions
)
)
.order_by(DomainAuction.num_bids.desc()) .order_by(DomainAuction.num_bids.desc())
.limit(limit) .limit(limit)
) )
@ -996,7 +1008,13 @@ async def get_market_feed(
# 2. EXTERNAL AUCTIONS (Scraped from platforms) # 2. EXTERNAL AUCTIONS (Scraped from platforms)
# ═══════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════
if source in ["all", "external"]: if source in ["all", "external"]:
auction_query = select(DomainAuction).where(DomainAuction.is_active == True) now = datetime.utcnow()
auction_query = select(DomainAuction).where(
and_(
DomainAuction.is_active == True,
DomainAuction.end_time > now # ← KRITISCH: Nur laufende Auktionen!
)
)
if keyword: if keyword:
auction_query = auction_query.where( auction_query = auction_query.where(

View File

@ -204,12 +204,30 @@ def setup_scheduler():
replace_existing=True, replace_existing=True,
) )
# Auction scrape every hour (at :30 to avoid conflict with other jobs) # Auction scrape every 2 hours (at :30 to avoid conflict with other jobs)
scheduler.add_job( scheduler.add_job(
scrape_auctions, scrape_auctions,
CronTrigger(minute=30), # Every hour at :30 CronTrigger(hour='*/2', minute=30), # Every 2 hours at :30
id="hourly_auction_scrape", id="auction_scrape",
name="Hourly Auction Scrape", name="Auction Scrape (2h)",
replace_existing=True,
)
# Cleanup expired auctions every 15 minutes (CRITICAL for data freshness!)
scheduler.add_job(
cleanup_expired_auctions,
CronTrigger(minute='*/15'), # Every 15 minutes
id="auction_cleanup",
name="Expired Auction Cleanup (15m)",
replace_existing=True,
)
# Sniper alert matching every 30 minutes
scheduler.add_job(
match_sniper_alerts,
CronTrigger(minute='*/30'), # Every 30 minutes
id="sniper_matching",
name="Sniper Alert Matching (30m)",
replace_existing=True, replace_existing=True,
) )
@ -220,7 +238,9 @@ def setup_scheduler():
f"\n - Tycoon domain check every 10 minutes" f"\n - Tycoon domain check every 10 minutes"
f"\n - TLD price scrape at 03:00 UTC" f"\n - TLD price scrape at 03:00 UTC"
f"\n - Price change alerts at 04:00 UTC" f"\n - Price change alerts at 04:00 UTC"
f"\n - Auction scrape every hour at :30" f"\n - Auction scrape every 2 hours at :30"
f"\n - Expired auction cleanup every 15 minutes"
f"\n - Sniper alert matching every 30 minutes"
) )
@ -302,6 +322,58 @@ async def check_price_changes():
logger.exception(f"Price change check failed: {e}") logger.exception(f"Price change check failed: {e}")
async def cleanup_expired_auctions():
"""
Mark expired auctions as inactive and delete very old ones.
This is CRITICAL for data freshness! Without this, the Market page
would show auctions that ended days ago.
Runs every 15 minutes to ensure users always see live data.
"""
from app.models.auction import DomainAuction
from sqlalchemy import update, delete
logger.info("Starting expired auction cleanup...")
try:
async with AsyncSessionLocal() as db:
now = datetime.utcnow()
# 1. Mark ended auctions as inactive
stmt = (
update(DomainAuction)
.where(
and_(
DomainAuction.end_time < now,
DomainAuction.is_active == True
)
)
.values(is_active=False)
)
result = await db.execute(stmt)
marked_inactive = result.rowcount
# 2. Delete very old inactive auctions (> 7 days)
cutoff = now - timedelta(days=7)
stmt = delete(DomainAuction).where(
and_(
DomainAuction.is_active == False,
DomainAuction.end_time < cutoff
)
)
result = await db.execute(stmt)
deleted = result.rowcount
await db.commit()
if marked_inactive > 0 or deleted > 0:
logger.info(f"Auction cleanup: {marked_inactive} marked inactive, {deleted} deleted")
except Exception as e:
logger.exception(f"Auction cleanup failed: {e}")
async def scrape_auctions(): async def scrape_auctions():
"""Scheduled task to scrape domain auctions from public sources.""" """Scheduled task to scrape domain auctions from public sources."""
from app.services.auction_scraper import auction_scraper from app.services.auction_scraper import auction_scraper