Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled
Pounce Eigenwerbung (from pounce_endgame.md):
- Add 'pounce_promo' as fallback partner for generic/unclear intent domains
- Create dedicated Pounce promo landing page with CTA to register
- Update footer on all yield pages: 'Monetized by Pounce • Own a domain? Start yielding'
Tech/Investment Domain Detection:
- Add 'investment_domains' category (invest, crypto, trading, domain, startup)
- Add 'tech_dev' category (developer, web3, fintech, proptech)
- Both categories have 'pounce_affinity' flag for higher Pounce conversion
Referral Tracking for Domain Owners:
- Add user fields: referred_by_user_id, referred_by_domain, referral_code
- Parse yield referral codes (yield_{user_id}_{domain_id}) on registration
- Domain owners earn lifetime commission when visitors sign up via their domain
DB Migrations:
- Add referral tracking columns to users table
475 lines
15 KiB
Python
475 lines
15 KiB
Python
"""
|
|
Seed data for Yield affiliate partners.
|
|
|
|
Run via: python -m app.seeds.yield_partners
|
|
Or: from app.seeds.yield_partners import seed_partners; await seed_partners(db)
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
from decimal import Decimal
|
|
from typing import Any
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.database import AsyncSessionLocal
|
|
from app.models.yield_domain import AffiliatePartner
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Partner configurations grouped by category
|
|
PARTNER_SEED_DATA: list[dict[str, Any]] = [
|
|
# =========================================================================
|
|
# MEDICAL / HEALTH
|
|
# =========================================================================
|
|
{
|
|
"name": "Comparis Dental",
|
|
"slug": "comparis_dental",
|
|
"network": "direct",
|
|
"intent_categories": "medical_dental",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("25.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Dental treatment comparison platform. High conversion for Swiss dental searches.",
|
|
"priority": 100,
|
|
},
|
|
{
|
|
"name": "Swisssmile",
|
|
"slug": "swisssmile",
|
|
"network": "awin",
|
|
"intent_categories": "medical_dental",
|
|
"geo_countries": "CH,DE,AT",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("30.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Premium dental clinics network.",
|
|
"priority": 90,
|
|
},
|
|
{
|
|
"name": "Comparis Health",
|
|
"slug": "comparis_health",
|
|
"network": "direct",
|
|
"intent_categories": "medical_general",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("20.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Health insurance comparison.",
|
|
"priority": 100,
|
|
},
|
|
{
|
|
"name": "Sanitas",
|
|
"slug": "sanitas",
|
|
"network": "awin",
|
|
"intent_categories": "medical_general",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("35.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Swiss health insurance provider.",
|
|
"priority": 80,
|
|
},
|
|
{
|
|
"name": "Swiss Esthetic",
|
|
"slug": "swissesthetic",
|
|
"network": "direct",
|
|
"intent_categories": "medical_beauty",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("40.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Aesthetic treatments and beauty clinics.",
|
|
"priority": 90,
|
|
},
|
|
|
|
# =========================================================================
|
|
# FINANCE / INSURANCE
|
|
# =========================================================================
|
|
{
|
|
"name": "Comparis Insurance",
|
|
"slug": "comparis_insurance",
|
|
"network": "direct",
|
|
"intent_categories": "finance_insurance",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("30.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "All-in-one insurance comparison.",
|
|
"priority": 100,
|
|
},
|
|
{
|
|
"name": "Bonus.ch",
|
|
"slug": "bonus_ch",
|
|
"network": "awin",
|
|
"intent_categories": "finance_insurance",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("25.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Swiss insurance comparison portal.",
|
|
"priority": 80,
|
|
},
|
|
{
|
|
"name": "Comparis Hypo",
|
|
"slug": "comparis_hypo",
|
|
"network": "direct",
|
|
"intent_categories": "finance_mortgage",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("100.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Mortgage comparison - high value leads.",
|
|
"priority": 100,
|
|
},
|
|
{
|
|
"name": "MoneyPark",
|
|
"slug": "moneypark",
|
|
"network": "awin",
|
|
"intent_categories": "finance_mortgage",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("120.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Independent mortgage broker.",
|
|
"priority": 90,
|
|
},
|
|
{
|
|
"name": "Neon Bank",
|
|
"slug": "neon_bank",
|
|
"network": "partnerstack",
|
|
"intent_categories": "finance_banking",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cps",
|
|
"payout_amount": Decimal("50.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Swiss mobile banking app.",
|
|
"priority": 80,
|
|
},
|
|
|
|
# =========================================================================
|
|
# LEGAL
|
|
# =========================================================================
|
|
{
|
|
"name": "Legal CH",
|
|
"slug": "legal_ch",
|
|
"network": "direct",
|
|
"intent_categories": "legal_general",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("50.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Lawyer matching service.",
|
|
"priority": 100,
|
|
},
|
|
{
|
|
"name": "Anwalt24",
|
|
"slug": "anwalt24",
|
|
"network": "awin",
|
|
"intent_categories": "legal_general",
|
|
"geo_countries": "DE,AT",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("35.00"),
|
|
"payout_currency": "EUR",
|
|
"description": "German lawyer directory.",
|
|
"priority": 80,
|
|
},
|
|
|
|
# =========================================================================
|
|
# REAL ESTATE
|
|
# =========================================================================
|
|
{
|
|
"name": "Homegate",
|
|
"slug": "homegate",
|
|
"network": "awin",
|
|
"intent_categories": "realestate_buy,realestate_rent",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpc",
|
|
"payout_amount": Decimal("0.50"),
|
|
"payout_currency": "CHF",
|
|
"description": "Switzerland's #1 real estate platform.",
|
|
"priority": 100,
|
|
},
|
|
{
|
|
"name": "ImmoScout24",
|
|
"slug": "immoscout",
|
|
"network": "awin",
|
|
"intent_categories": "realestate_buy,realestate_rent",
|
|
"geo_countries": "CH,DE",
|
|
"payout_type": "cpc",
|
|
"payout_amount": Decimal("0.40"),
|
|
"payout_currency": "CHF",
|
|
"description": "Real estate marketplace.",
|
|
"priority": 90,
|
|
},
|
|
{
|
|
"name": "Comparis Immo",
|
|
"slug": "comparis_immo",
|
|
"network": "direct",
|
|
"intent_categories": "realestate_buy,realestate_rent",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("15.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Property valuation and search.",
|
|
"priority": 85,
|
|
},
|
|
|
|
# =========================================================================
|
|
# TRAVEL
|
|
# =========================================================================
|
|
{
|
|
"name": "Skyscanner",
|
|
"slug": "skyscanner",
|
|
"network": "awin",
|
|
"intent_categories": "travel_flights",
|
|
"geo_countries": "CH,DE,AT",
|
|
"payout_type": "cpc",
|
|
"payout_amount": Decimal("0.30"),
|
|
"payout_currency": "CHF",
|
|
"description": "Flight comparison engine.",
|
|
"priority": 90,
|
|
},
|
|
{
|
|
"name": "Booking.com",
|
|
"slug": "booking_com",
|
|
"network": "awin",
|
|
"intent_categories": "travel_hotels",
|
|
"geo_countries": "CH,DE,AT",
|
|
"payout_type": "cps",
|
|
"payout_amount": Decimal("4.00"), # 4% commission
|
|
"payout_currency": "CHF",
|
|
"description": "World's leading accommodation site.",
|
|
"priority": 100,
|
|
},
|
|
|
|
# =========================================================================
|
|
# AUTOMOTIVE
|
|
# =========================================================================
|
|
{
|
|
"name": "AutoScout24",
|
|
"slug": "autoscout",
|
|
"network": "awin",
|
|
"intent_categories": "auto_buy",
|
|
"geo_countries": "CH,DE",
|
|
"payout_type": "cpc",
|
|
"payout_amount": Decimal("0.60"),
|
|
"payout_currency": "CHF",
|
|
"description": "Auto marketplace.",
|
|
"priority": 100,
|
|
},
|
|
{
|
|
"name": "Comparis Auto",
|
|
"slug": "comparis_auto",
|
|
"network": "direct",
|
|
"intent_categories": "auto_buy,auto_service",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpl",
|
|
"payout_amount": Decimal("25.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Car insurance & leasing comparison.",
|
|
"priority": 90,
|
|
},
|
|
|
|
# =========================================================================
|
|
# JOBS
|
|
# =========================================================================
|
|
{
|
|
"name": "Jobs.ch",
|
|
"slug": "jobs_ch",
|
|
"network": "awin",
|
|
"intent_categories": "jobs",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cpc",
|
|
"payout_amount": Decimal("0.40"),
|
|
"payout_currency": "CHF",
|
|
"description": "Swiss job board.",
|
|
"priority": 100,
|
|
},
|
|
{
|
|
"name": "Indeed",
|
|
"slug": "indeed",
|
|
"network": "awin",
|
|
"intent_categories": "jobs",
|
|
"geo_countries": "CH,DE,AT",
|
|
"payout_type": "cpc",
|
|
"payout_amount": Decimal("0.25"),
|
|
"payout_currency": "CHF",
|
|
"description": "Global job search engine.",
|
|
"priority": 80,
|
|
},
|
|
|
|
# =========================================================================
|
|
# EDUCATION
|
|
# =========================================================================
|
|
{
|
|
"name": "Udemy",
|
|
"slug": "udemy",
|
|
"network": "awin",
|
|
"intent_categories": "education",
|
|
"geo_countries": "CH,DE,AT",
|
|
"payout_type": "cps",
|
|
"payout_amount": Decimal("10.00"), # Per sale
|
|
"payout_currency": "USD",
|
|
"description": "Online courses platform.",
|
|
"priority": 80,
|
|
},
|
|
|
|
# =========================================================================
|
|
# TECHNOLOGY / HOSTING
|
|
# =========================================================================
|
|
{
|
|
"name": "Hostpoint",
|
|
"slug": "hostpoint",
|
|
"network": "partnerstack",
|
|
"intent_categories": "tech_hosting",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cps",
|
|
"payout_amount": Decimal("30.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Swiss web hosting leader.",
|
|
"priority": 100,
|
|
},
|
|
{
|
|
"name": "Infomaniak",
|
|
"slug": "infomaniak",
|
|
"network": "direct",
|
|
"intent_categories": "tech_hosting",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cps",
|
|
"payout_amount": Decimal("25.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Eco-friendly Swiss hosting.",
|
|
"priority": 90,
|
|
},
|
|
|
|
# =========================================================================
|
|
# SHOPPING
|
|
# =========================================================================
|
|
{
|
|
"name": "Galaxus",
|
|
"slug": "galaxus",
|
|
"network": "awin",
|
|
"intent_categories": "shopping_general",
|
|
"geo_countries": "CH",
|
|
"payout_type": "cps",
|
|
"payout_amount": Decimal("2.00"), # 2% commission
|
|
"payout_currency": "CHF",
|
|
"description": "Switzerland's largest online shop.",
|
|
"priority": 100,
|
|
},
|
|
{
|
|
"name": "Zalando",
|
|
"slug": "zalando",
|
|
"network": "awin",
|
|
"intent_categories": "shopping_fashion",
|
|
"geo_countries": "CH,DE,AT",
|
|
"payout_type": "cps",
|
|
"payout_amount": Decimal("8.00"), # 8% commission
|
|
"payout_currency": "CHF",
|
|
"description": "Fashion & lifestyle.",
|
|
"priority": 100,
|
|
},
|
|
|
|
# =========================================================================
|
|
# FOOD / DELIVERY
|
|
# =========================================================================
|
|
{
|
|
"name": "Uber Eats",
|
|
"slug": "uber_eats",
|
|
"network": "awin",
|
|
"intent_categories": "food_restaurant,food_delivery",
|
|
"geo_countries": "CH,DE",
|
|
"payout_type": "cps",
|
|
"payout_amount": Decimal("5.00"),
|
|
"payout_currency": "CHF",
|
|
"description": "Food delivery service.",
|
|
"priority": 90,
|
|
},
|
|
|
|
# =========================================================================
|
|
# POUNCE SELF-PROMOTION (Viral Growth)
|
|
# =========================================================================
|
|
{
|
|
"name": "Pounce Promo",
|
|
"slug": "pounce_promo",
|
|
"network": "internal",
|
|
"intent_categories": "investment_domains,tech_dev,generic",
|
|
"geo_countries": "CH,DE,AT",
|
|
"payout_type": "cps",
|
|
"payout_amount": Decimal("0"), # 30% lifetime commission handled separately
|
|
"payout_currency": "CHF",
|
|
"description": "Pounce self-promotion. Domain owners earn 30% lifetime commission on referrals.",
|
|
"priority": 50, # Higher than generic but lower than high-value partners
|
|
},
|
|
|
|
# =========================================================================
|
|
# GENERIC FALLBACK
|
|
# =========================================================================
|
|
{
|
|
"name": "Generic Affiliate",
|
|
"slug": "generic_affiliate",
|
|
"network": "internal",
|
|
"intent_categories": "generic",
|
|
"geo_countries": "CH,DE,AT",
|
|
"payout_type": "cpc",
|
|
"payout_amount": Decimal("0.10"),
|
|
"payout_currency": "CHF",
|
|
"description": "Fallback for unclassified domains - shows Pounce marketplace.",
|
|
"priority": 1,
|
|
},
|
|
]
|
|
|
|
|
|
async def seed_partners(db: AsyncSession) -> int:
|
|
"""
|
|
Seed affiliate partners into database.
|
|
|
|
Idempotent: updates existing partners by slug, creates new ones.
|
|
|
|
Returns:
|
|
Number of partners created/updated.
|
|
"""
|
|
count = 0
|
|
|
|
for data in PARTNER_SEED_DATA:
|
|
slug = data["slug"]
|
|
|
|
# Check if partner exists
|
|
result = await db.execute(
|
|
select(AffiliatePartner).where(AffiliatePartner.slug == slug)
|
|
)
|
|
existing = result.scalar_one_or_none()
|
|
|
|
if existing:
|
|
# Update existing partner
|
|
for key, value in data.items():
|
|
setattr(existing, key, value)
|
|
logger.info(f"Updated partner: {slug}")
|
|
else:
|
|
# Create new partner
|
|
partner = AffiliatePartner(**data)
|
|
db.add(partner)
|
|
logger.info(f"Created partner: {slug}")
|
|
|
|
count += 1
|
|
|
|
await db.commit()
|
|
logger.info(f"Seeded {count} affiliate partners")
|
|
return count
|
|
|
|
|
|
async def main():
|
|
"""Run seed script standalone."""
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
async with AsyncSessionLocal() as db:
|
|
count = await seed_partners(db)
|
|
print(f"✅ Seeded {count} affiliate partners")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|
|
|