refactor: rename thumbnail_rendering queue to asset_pipeline

The queue handles far more than thumbnails: OCC tessellation, USD master
generation, GLB production, order line renders, and workflow renders.
asset_pipeline better reflects its role as the render-worker's primary queue.

Updated all references in: task decorators, celery_app.py, beat_tasks.py,
docker-compose.yml worker command, worker.py MONITORED_QUEUES, admin.py,
CLAUDE.md, LEARNINGS.md, Dockerfile, helpTexts.ts, test files,
and all .claude/commands/*.md skill files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 22:28:38 +01:00
parent e7b70a35ea
commit 1321ef2bd4
39 changed files with 540 additions and 122 deletions
+1 -1
View File
@@ -366,7 +366,7 @@ async def update_settings(
await db.commit()
# Note: blender-renderer HTTP service removed; concurrency is now controlled
# via render-worker Docker concurrency setting (thumbnail_rendering queue).
# via render-worker Docker concurrency setting (asset_pipeline queue).
return _settings_to_out(await _load_settings(db))
+8
View File
@@ -1,4 +1,5 @@
"""CAD file router - serve thumbnails, glTF models, parsed objects, and trigger reprocessing."""
import logging
import uuid
from datetime import datetime
from pathlib import Path
@@ -20,6 +21,7 @@ from app.utils.auth import get_current_user, is_privileged
from app.services.product_service import link_cad_to_product, lookup_product
router = APIRouter(prefix="/cad", tags=["cad"])
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
@@ -273,6 +275,7 @@ async def get_objects(
"cad_file_id": str(cad.id),
"original_name": cad.original_name,
"processing_status": cad.processing_status.value,
"step_hash": cad.step_file_hash,
"parsed_objects": cad.parsed_objects,
}
@@ -318,6 +321,11 @@ async def generate_gltf_production(
if not cad.stored_path:
raise HTTPException(status_code=404, detail="STEP file not uploaded for this CAD file")
logger.warning(
"generate_gltf_production called for cad %s"
"deprecated: renders now consume usd_master directly",
id,
)
from app.tasks.step_tasks import generate_gltf_production_task
task = generate_gltf_production_task.delay(str(id))
return {"status": "queued", "task_id": task.id, "cad_file_id": str(id)}
+47
View File
@@ -1000,6 +1000,53 @@ async def cancel_line_render(
}
class RejectLineBody(BaseModel):
reason: str = ""
@router.post("/{order_id}/lines/{line_id}/reject", status_code=200)
async def reject_order_line(
order_id: uuid.UUID,
line_id: uuid.UUID,
body: RejectLineBody,
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""Reject a single order line (admin/PM only).
Sets item_status to 'rejected' and stores the reason in the notes field.
"""
if not _is_privileged(user):
raise HTTPException(status_code=403, detail="Insufficient permissions")
result = await db.execute(select(Order).where(Order.id == order_id))
order = result.scalar_one_or_none()
if not order:
raise HTTPException(404, detail="Order not found")
line_result = await db.execute(
select(OrderLine).where(OrderLine.id == line_id, OrderLine.order_id == order_id)
)
line = line_result.scalar_one_or_none()
if not line:
raise HTTPException(404, detail="Order line not found")
from sqlalchemy import update as sql_update
notes_value = body.reason.strip() if body.reason and body.reason.strip() else line.notes
await db.execute(
sql_update(OrderLine)
.where(OrderLine.id == line.id)
.values(
item_status="rejected",
notes=notes_value,
)
)
await db.commit()
return {"rejected": True, "line_id": str(line.id), "reason": body.reason}
@router.post("/{order_id}/cancel-renders")
async def cancel_order_renders(
order_id: uuid.UUID,
+6 -6
View File
@@ -237,7 +237,7 @@ async def reprocess_cad_file(
# Queue inspection + control
# ---------------------------------------------------------------------------
MONITORED_QUEUES = ["step_processing", "thumbnail_rendering", "ai_validation"]
MONITORED_QUEUES = ["step_processing", "asset_pipeline", "ai_validation"]
def _parse_redis_task(raw: str) -> dict | None:
@@ -515,7 +515,7 @@ async def render_health(
details: dict = {}
# 1. Check if render-worker (thumbnail_rendering queue) is connected + has Blender
# 1. Check if render-worker (asset_pipeline queue) is connected + has Blender
render_worker_connected = False
blender_available = False
@@ -534,10 +534,10 @@ async def render_health(
else:
all_workers = list(inspect_result.get("ping", {}).keys())
details["workers"] = all_workers
# Find any worker consuming thumbnail_rendering queue
# Find any worker consuming asset_pipeline queue
for worker_name, queues in inspect_result.get("active_queues", {}).items():
queue_names = [q.get("name") for q in (queues or [])]
if "thumbnail_rendering" in queue_names:
if "asset_pipeline" in queue_names:
render_worker_connected = True
# render-worker always has Blender — it starts Blender successfully
blender_available = True
@@ -547,11 +547,11 @@ async def render_health(
render_worker_connected = True
details["worker_detection"] = "fallback"
# 3. Queue depth for thumbnail_rendering
# 3. Queue depth for asset_pipeline
thumbnail_queue_depth = 0
try:
r = redis_lib.from_url(app_settings.redis_url, decode_responses=True)
thumbnail_queue_depth = r.llen("thumbnail_rendering") or 0
thumbnail_queue_depth = r.llen("asset_pipeline") or 0
except Exception as exc:
details["redis_error"] = str(exc)