Files
service-finder/backend/scripts/seed_regions.py

374 lines
12 KiB
Python

#!/usr/bin/env python3
"""
🌍 Seed script for Global Region Registry (system.region_config).
Populates the system.region_config table with ALL 27 EU member states,
plus United Kingdom (GB), Switzerland (CH), and a DEFAULT/Eurozone fallback.
Total: 30 regions with accurate VAT rates, currencies, locales, and timezones.
Usage:
docker compose exec sf_api python3 /app/scripts/seed_regions.py
"""
import asyncio
import logging
import sys
import os
# Ensure we can import from the project
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from app.core.config import settings
from app.models.core_logic import RegionConfig
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)
# ──────────────────────────────────────────────────────────────────────────────
# 🌍 COMPREHENSIVE EU + EEA REGION REGISTRY
# ──────────────────────────────────────────────────────────────────────────────
# All 27 EU member states + GB + CH + DEFAULT fallback = 30 entries
# VAT rates verified against official EU VAT rates (2025-2026)
# ──────────────────────────────────────────────────────────────────────────────
DEFAULT_REGIONS = [
# ── EU MEMBER STATES (27) ──────────────────────────────────────────────
{
"country_code": "AT",
"name": "Austria",
"currency": "EUR",
"default_vat_rate": 20.0,
"locale_code": "de-AT",
"timezone": "Europe/Vienna",
"is_active": True,
},
{
"country_code": "BE",
"name": "Belgium",
"currency": "EUR",
"default_vat_rate": 21.0,
"locale_code": "nl-BE",
"timezone": "Europe/Brussels",
"is_active": True,
},
{
"country_code": "BG",
"name": "Bulgaria",
"currency": "BGN",
"default_vat_rate": 20.0,
"locale_code": "bg-BG",
"timezone": "Europe/Sofia",
"is_active": True,
},
{
"country_code": "HR",
"name": "Croatia",
"currency": "EUR",
"default_vat_rate": 25.0,
"locale_code": "hr-HR",
"timezone": "Europe/Zagreb",
"is_active": True,
},
{
"country_code": "CY",
"name": "Cyprus",
"currency": "EUR",
"default_vat_rate": 19.0,
"locale_code": "el-CY",
"timezone": "Asia/Nicosia",
"is_active": True,
},
{
"country_code": "CZ",
"name": "Czech Republic",
"currency": "CZK",
"default_vat_rate": 21.0,
"locale_code": "cs-CZ",
"timezone": "Europe/Prague",
"is_active": True,
},
{
"country_code": "DK",
"name": "Denmark",
"currency": "DKK",
"default_vat_rate": 25.0,
"locale_code": "da-DK",
"timezone": "Europe/Copenhagen",
"is_active": True,
},
{
"country_code": "EE",
"name": "Estonia",
"currency": "EUR",
"default_vat_rate": 22.0,
"locale_code": "et-EE",
"timezone": "Europe/Tallinn",
"is_active": True,
},
{
"country_code": "FI",
"name": "Finland",
"currency": "EUR",
"default_vat_rate": 25.5,
"locale_code": "fi-FI",
"timezone": "Europe/Helsinki",
"is_active": True,
},
{
"country_code": "FR",
"name": "France",
"currency": "EUR",
"default_vat_rate": 20.0,
"locale_code": "fr-FR",
"timezone": "Europe/Paris",
"is_active": True,
},
{
"country_code": "DE",
"name": "Germany",
"currency": "EUR",
"default_vat_rate": 19.0,
"locale_code": "de-DE",
"timezone": "Europe/Berlin",
"is_active": True,
},
{
"country_code": "GR",
"name": "Greece",
"currency": "EUR",
"default_vat_rate": 24.0,
"locale_code": "el-GR",
"timezone": "Europe/Athens",
"is_active": True,
},
{
"country_code": "HU",
"name": "Hungary",
"currency": "HUF",
"default_vat_rate": 27.0,
"locale_code": "hu-HU",
"timezone": "Europe/Budapest",
"is_active": True,
},
{
"country_code": "IE",
"name": "Ireland",
"currency": "EUR",
"default_vat_rate": 23.0,
"locale_code": "en-IE",
"timezone": "Europe/Dublin",
"is_active": True,
},
{
"country_code": "IT",
"name": "Italy",
"currency": "EUR",
"default_vat_rate": 22.0,
"locale_code": "it-IT",
"timezone": "Europe/Rome",
"is_active": True,
},
{
"country_code": "LV",
"name": "Latvia",
"currency": "EUR",
"default_vat_rate": 21.0,
"locale_code": "lv-LV",
"timezone": "Europe/Riga",
"is_active": True,
},
{
"country_code": "LT",
"name": "Lithuania",
"currency": "EUR",
"default_vat_rate": 21.0,
"locale_code": "lt-LT",
"timezone": "Europe/Vilnius",
"is_active": True,
},
{
"country_code": "LU",
"name": "Luxembourg",
"currency": "EUR",
"default_vat_rate": 17.0,
"locale_code": "lb-LU",
"timezone": "Europe/Luxembourg",
"is_active": True,
},
{
"country_code": "MT",
"name": "Malta",
"currency": "EUR",
"default_vat_rate": 18.0,
"locale_code": "mt-MT",
"timezone": "Europe/Malta",
"is_active": True,
},
{
"country_code": "NL",
"name": "Netherlands",
"currency": "EUR",
"default_vat_rate": 21.0,
"locale_code": "nl-NL",
"timezone": "Europe/Amsterdam",
"is_active": True,
},
{
"country_code": "PL",
"name": "Poland",
"currency": "PLN",
"default_vat_rate": 23.0,
"locale_code": "pl-PL",
"timezone": "Europe/Warsaw",
"is_active": True,
},
{
"country_code": "PT",
"name": "Portugal",
"currency": "EUR",
"default_vat_rate": 23.0,
"locale_code": "pt-PT",
"timezone": "Europe/Lisbon",
"is_active": True,
},
{
"country_code": "RO",
"name": "Romania",
"currency": "RON",
"default_vat_rate": 19.0,
"locale_code": "ro-RO",
"timezone": "Europe/Bucharest",
"is_active": True,
},
{
"country_code": "SK",
"name": "Slovakia",
"currency": "EUR",
"default_vat_rate": 23.0,
"locale_code": "sk-SK",
"timezone": "Europe/Bratislava",
"is_active": True,
},
{
"country_code": "SI",
"name": "Slovenia",
"currency": "EUR",
"default_vat_rate": 22.0,
"locale_code": "sl-SI",
"timezone": "Europe/Ljubljana",
"is_active": True,
},
{
"country_code": "ES",
"name": "Spain",
"currency": "EUR",
"default_vat_rate": 21.0,
"locale_code": "es-ES",
"timezone": "Europe/Madrid",
"is_active": True,
},
{
"country_code": "SE",
"name": "Sweden",
"currency": "SEK",
"default_vat_rate": 25.0,
"locale_code": "sv-SE",
"timezone": "Europe/Stockholm",
"is_active": True,
},
# ── NON-EU EEA / ASSOCIATED ────────────────────────────────────────────
{
"country_code": "GB",
"name": "United Kingdom",
"currency": "GBP",
"default_vat_rate": 20.0,
"locale_code": "en-GB",
"timezone": "Europe/London",
"is_active": True,
},
{
"country_code": "CH",
"name": "Switzerland",
"currency": "CHF",
"default_vat_rate": 8.1,
"locale_code": "de-CH",
"timezone": "Europe/Zurich",
"is_active": True,
},
# ── FALLBACK / DEFAULT ─────────────────────────────────────────────────
{
"country_code": "DEFAULT",
"name": "Eurozone (Default)",
"currency": "EUR",
"default_vat_rate": 0.0,
"locale_code": "en-EU",
"timezone": "Europe/Brussels",
"is_active": True,
},
]
async def seed_regions():
"""
Seed the system.region_config table with all EU regions + GB + CH + DEFAULT.
Uses UPSERT logic: if a region with the same country_code already exists,
its fields are UPDATED (name, currency, VAT rate, locale, timezone, is_active)
rather than skipped. This ensures the script is idempotent and can be re-run
to update VAT rates or other configuration changes.
"""
engine = create_async_engine(str(settings.SQLALCHEMY_DATABASE_URI), echo=False)
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with async_session() as session:
async with session.begin():
for region_data in DEFAULT_REGIONS:
country_code = region_data["country_code"]
# Check if region already exists
stmt = select(RegionConfig).where(
RegionConfig.country_code == country_code
)
result = await session.execute(stmt)
existing = result.scalar_one_or_none()
if existing:
# UPSERT: update existing record with new values
existing.name = region_data["name"]
existing.currency = region_data["currency"]
existing.default_vat_rate = region_data["default_vat_rate"]
existing.locale_code = region_data["locale_code"]
existing.timezone = region_data["timezone"]
existing.is_active = region_data["is_active"]
logger.info(
f"🔄 Updated region: {country_code} - {region_data['name']} "
f"(VAT: {region_data['default_vat_rate']}%, "
f"Currency: {region_data['currency']})"
)
else:
# INSERT: create new region
region = RegionConfig(**region_data)
session.add(region)
logger.info(
f"✅ Created region: {country_code} - {region_data['name']} "
f"(VAT: {region_data['default_vat_rate']}%, "
f"Currency: {region_data['currency']})"
)
await session.commit()
# Count total regions
count_stmt = select(RegionConfig)
count_result = await session.execute(count_stmt)
total = len(count_result.scalars().all())
logger.info(f"🎉 Region seeding completed! Total regions in database: {total}")
await engine.dispose()
if __name__ == "__main__":
asyncio.run(seed_regions())