Automation-driven operations — freeing sellers from repetitive manual work.
Background
Day-to-day operations on the Temu platform involve a large volume of repetitive manual work: filling product Excel sheets according to category-specific rules, uploading product images one by one to cloud storage, parsing SKUs from order export files and matching them to image assets, manually removing watermarks from product images — these workflows are tedious, error-prone, and have no reusable engineering foundation.
This project is the full-pipeline automation system I built independently after taking over these workflows as an engineer. The core goal is simple: turn repetitive manual operations into a single click.
Vue 3 Composition API
TypeScript · Vite
Naive UI · Pinia
Vue Router · ofetch
useSSETaskuseOrderTaskusePlanEditorPython 3.12 · Uvicorn
Pydantic · SQLModel
Alembic · boto3
google-genai · Playwright
PostgreSQL — Image library metadata
SQLite — Order history
JSON — SKU mapping · Resource index
Cloudflare R2 — Image CDN
Each datasource chosen for its access pattern
Technical Depth
All long-running tasks share the same problem: they take seconds to minutes, and users need real-time progress feedback. I chose SSE over WebSocket — pure HTTP unidirectional push, no protocol upgrade required, and a natural fit for FastAPI’s StreamingResponse. The backend assigns a UUID to each task; the frontend useSSETask composable manages connection lifecycle, giving all modules zero-boilerplate integration.
@router.get("/sse/{task_id}")
async def sse_stream(task_id: str):
async def event_generator():
queue = task_queues.get(task_id)
while True:
msg = await queue.get()
if msg is None: break # task done signal
yield f"data: {msg}\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")Image upload is I/O-bound — network transfer dominates over CPU time. Using ThreadPoolExecutor(max_workers=48) compresses serial upload time to roughly 1/48. The value 48 was determined empirically as the optimal balance between R2 API rate limits and local bandwidth. After upload completes, the system automatically rebuilds the data.json resource index — the single data contract between the upload and order modules.
with ThreadPoolExecutor(max_workers=48) as executor:
futures = {executor.submit(upload_single, p, bucket): p for p in paths}
for future in as_completed(futures):
try:
future.result()
task_queue.put(f"✓ {futures[future].name}") # SSE push
except Exception as e:
failed.append((futures[future], str(e)))
rebuild_index(bucket) # rebuild resource indexCore principle: the view layer only renders; business logic lives in Composables. Four Composables each own their domain and compose with each other — useOrderTask internally composes useSSETask, reusing log-stream logic without duplication. TypeScript unified request/response types across 10 API modules, catching field-rename errors at compile time during multiple refactors.
useSSETaskgenericManages SSE connection lifecycle, exposes logs / isRunning — reused by all modules
useOrderTaskordersOrder selection, pre-processing, batch submission, failedImages state — composes useSSETask
usePlanEditorconfigYAML Plan CRUD: GET load → local edit → POST persist
useUploadEditoruploadUpload task config management, same pattern as usePlanEditor with added field validation
- ›Exposes HTTP endpoints only
- ›Pydantic input validation
- ›Calls Service, no business logic
- ›10 domain-split routers
- ›Core business logic
- ›Independently unit-testable
- ›Cross-router logic reuse
- ›Encapsulates external API calls
- ›PostgreSQL — structured metadata
- ›SQLite — lightweight local history
- ›JSON — high-read low-write
- ›R2 — image object storage
SQLModel (SQLAlchemy + Pydantic fusion) lets the same Model class serve as both ORM mapping and API schema, eliminating redundant definitions. Alembic manages PostgreSQL migration history so every schema change is versioned. The three datasources were chosen for their access patterns — not over-engineering, but the right tool for each job.
Traditional watermark removal relies on fixed-position masks, but product image watermarks vary in position, font, and opacity. By integrating Gemini 2.5 Flash multimodal, the system feeds the image together with natural language instructions, letting the model semantically understand and execute the edit.
The engineering focus was prompt engineering: a template structure of “describe task + constrain format + provide example”, with separate strategies for solid-color vs. complex backgrounds. Failed images enter a retry queue, with full SSE progress streaming throughout.