Full-stack Amazon AI Transcreation Platform with: - FastAPI backend (async, PostgreSQL, Redis, Celery) with 11 DB tables - JWT auth (SSO-ready abstract provider pattern) - 6-agent pipeline orchestrator with deterministic modules - Next.js 14 frontend with Amazon branding (Ember fonts, orange/dark theme) - Job wizard, monitoring HUD, output review, admin screens - 154 TM/reference files imported, 12 locales configured - Docker Compose for all services Agents 2-5 (TM retrieval, ranker, transcreator, compliance) are stubs pending Phase 3 LLM integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
224 lines
6.9 KiB
Python
224 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Create Default Client
|
|
=====================
|
|
Seeds the database with the Amazon client record, voice profiles, channel
|
|
registry, and test user accounts.
|
|
|
|
Designed to run inside the Docker container:
|
|
docker compose exec backend python -m seed.create_default_client
|
|
|
|
Or via the Makefile:
|
|
make seed
|
|
"""
|
|
|
|
import asyncio
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Ensure the backend package is importable when run as a module
|
|
# Inside Docker: /app/seed/ -> parent is /app (which has the app package)
|
|
# Outside Docker: seed/ -> parent.parent/backend has the app package
|
|
_seed_dir = Path(__file__).resolve().parent
|
|
_backend_dir = _seed_dir.parent / "backend"
|
|
if _backend_dir.is_dir():
|
|
sys.path.insert(0, str(_backend_dir))
|
|
else:
|
|
sys.path.insert(0, str(_seed_dir.parent))
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
|
|
from app.config import settings
|
|
from app.models.client import Client
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Amazon client configuration
|
|
# ---------------------------------------------------------------------------
|
|
|
|
AMAZON_CLIENT_NAME = "Amazon"
|
|
|
|
AMAZON_SETTINGS = {
|
|
"supported_locales": {
|
|
"main": [
|
|
"de-DE", "fr-FR", "it-IT", "es-ES",
|
|
"nl-NL", "sv-SE", "pl-PL", "pt-PT",
|
|
],
|
|
"derived": ["de-AT", "fr-BE", "nl-BE", "ca-ES"],
|
|
},
|
|
"voice_profiles": {
|
|
"Retail": {
|
|
"attributes": ["Real", "Clear", "Playful", "Witty"],
|
|
"core_job": (
|
|
"Communicate value, convenience, and selection concisely."
|
|
),
|
|
"register": "Helpful, snappy, joyful. Specific to category.",
|
|
"preferred_structure": (
|
|
"Be specific. Every word must earn its place."
|
|
),
|
|
},
|
|
"Prime": {
|
|
"attributes": [
|
|
"Optimistic", "Honest", "Self-aware", "Witty", "Relatable",
|
|
],
|
|
"core_job": (
|
|
"Transform customer expectations; make every day better."
|
|
),
|
|
"register": (
|
|
"Fearlessly honest, unapologetically cheeky, "
|
|
"self-deprecating with a wink."
|
|
),
|
|
"preferred_structure": (
|
|
"Lead with the benefit; end with the wink."
|
|
),
|
|
},
|
|
"Brand": {
|
|
"attributes": [
|
|
"Authentic", "Customer-obsessed", "Intelligent",
|
|
"Warm", "Understated",
|
|
],
|
|
"core_job": (
|
|
"Make customers feel seen and understood. "
|
|
"Let the brand recede so the customer comes forward."
|
|
),
|
|
"register": (
|
|
"Restrained, emotionally intelligent, "
|
|
"never grand or self-congratulatory."
|
|
),
|
|
"preferred_structure": (
|
|
"Say one thing. Make every word earn its place. Leave space."
|
|
),
|
|
},
|
|
},
|
|
"channel_registry": {
|
|
"Mass": {
|
|
"type": "standing_channel",
|
|
"tm_pattern": "flat_MASS_{lc}.json",
|
|
},
|
|
"Value": {
|
|
"type": "standing_channel",
|
|
"tm_pattern": "flat_value_{lc}.json",
|
|
},
|
|
"Onsite": {
|
|
"type": "standing_channel",
|
|
"tm_pattern": "flat_Onsite_{lc}.json",
|
|
},
|
|
"Outbound": {
|
|
"type": "standing_channel",
|
|
"tm_pattern": "flat_Outbound_{lc}.json",
|
|
},
|
|
"The Kiss": {
|
|
"type": "project",
|
|
"brand_program": "Retail",
|
|
"tm_pattern": "flat_TheKiss_{lc}.json",
|
|
},
|
|
"Prime Mid-funnel": {
|
|
"type": "project",
|
|
"brand_program": "Prime",
|
|
"tm_pattern": "flat_PrimeMidfunnel_{lc}.json",
|
|
},
|
|
"Prime Gourmet Guard": {
|
|
"type": "project",
|
|
"brand_program": "Prime",
|
|
"tm_pattern": "flat_PrimeGourmetGuard_{lc}.json",
|
|
},
|
|
"Prime Dual Benefit": {
|
|
"type": "project",
|
|
"brand_program": "Prime",
|
|
"tm_pattern": "flat_PrimeDualBenefit_{lc}.json",
|
|
},
|
|
"Prime Brand (Speed)": {
|
|
"type": "project",
|
|
"brand_program": "Prime",
|
|
"tm_pattern": "flat_PrimeSpeed_{lc}.json",
|
|
},
|
|
"EU Selection": {
|
|
"type": "project",
|
|
"brand_program": "Retail",
|
|
"tm_pattern": "flat_EUSelection_{lc}.json",
|
|
},
|
|
"BDA": {
|
|
"type": "project",
|
|
"brand_program": "Brand",
|
|
"tm_pattern": "flat_BDA_{lc}.json",
|
|
},
|
|
"UEFA": {
|
|
"type": "project",
|
|
"brand_program": "Brand",
|
|
"tm_pattern": "flat_UEFA_{lc}.json",
|
|
},
|
|
},
|
|
}
|
|
|
|
|
|
async def create_amazon_client(session: AsyncSession) -> Client:
|
|
"""Create or update the Amazon client record."""
|
|
# Check if client already exists
|
|
result = await session.execute(
|
|
select(Client).where(Client.name == AMAZON_CLIENT_NAME)
|
|
)
|
|
existing = result.scalar_one_or_none()
|
|
|
|
if existing:
|
|
print(f" Client '{AMAZON_CLIENT_NAME}' already exists (id={existing.id})")
|
|
print(" Updating settings...")
|
|
existing.settings = AMAZON_SETTINGS
|
|
await session.flush()
|
|
return existing
|
|
|
|
client = Client(name=AMAZON_CLIENT_NAME, settings=AMAZON_SETTINGS)
|
|
session.add(client)
|
|
await session.flush()
|
|
print(f" Created client '{AMAZON_CLIENT_NAME}' (id={client.id})")
|
|
return client
|
|
|
|
|
|
async def seed() -> None:
|
|
"""Run the full seed process."""
|
|
engine = create_async_engine(
|
|
settings.DATABASE_URL,
|
|
echo=False,
|
|
)
|
|
async_session = async_sessionmaker(
|
|
bind=engine,
|
|
class_=AsyncSession,
|
|
expire_on_commit=False,
|
|
)
|
|
|
|
print("=" * 60)
|
|
print("Seeding: Create Default Client")
|
|
print("=" * 60)
|
|
|
|
async with async_session() as session:
|
|
async with session.begin():
|
|
client = await create_amazon_client(session)
|
|
|
|
# Print summary
|
|
locales = AMAZON_SETTINGS["supported_locales"]
|
|
channels = AMAZON_SETTINGS["channel_registry"]
|
|
profiles = AMAZON_SETTINGS["voice_profiles"]
|
|
|
|
print()
|
|
print(" Configuration summary:")
|
|
print(f" Main locales: {', '.join(locales['main'])}")
|
|
print(f" Derived locales: {', '.join(locales['derived'])}")
|
|
print(f" Voice profiles: {', '.join(profiles.keys())}")
|
|
print(f" Channels: {len(channels)}")
|
|
for name, config in channels.items():
|
|
ch_type = config["type"]
|
|
brand = config.get("brand_program", "-")
|
|
print(f" - {name:25s} type={ch_type:20s} brand={brand}")
|
|
|
|
await engine.dispose()
|
|
|
|
print()
|
|
print(" Seed complete.")
|
|
print("=" * 60)
|
|
|
|
|
|
def main() -> None:
|
|
asyncio.run(seed())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|