"""잡 행을 프론트가 그리는 모양으로 옮김 필드는 web/lib/api.ts 의 Job, web/lib/playreel.ts 의 PlayreelJob 과 맞춤 """ from models.motion import MotionPlan from models.pipeline_state import PlayreelState, PosterAliveState, stage_keys from pipelines.gate import build_review from pipelines.playreel import thumbnail_url from tables.task import PlayreelTask, PosterAliveTask def current_stage(task) -> str | None: return next((key for key, entry in (task.stage_timings or {}).items() if entry["status"] == "running"), None) def error_view(task) -> dict | None: if not task.error_stage: return None return {"stage": task.error_stage, "detail": task.error_detail or ""} def motion_elements(motion_plan: dict | None) -> list[str] | None: if not motion_plan: return None return [item.motion for item in MotionPlan.model_validate(motion_plan).kept] IDLE = {"status": "idle", "started": None, "ended": None} # 이름은 narration_text 가 포스터에서 읽어 채운다. 그 전까지 화면에 쓸 말 UNNAMED = "이름 없는 행사" def ordered_stages(task, state_type) -> dict: """스테퍼가 키 순서대로 그리므로 실행 순서로 다시 세운다. JSON 객체는 순서를 담는 자리가 아니고, 순서의 정본은 state enum이다. """ stored = task.stage_timings or {} return {key: stored.get(key, IDLE) for key in stage_keys(state_type)} def base_view(task, state_type) -> dict: return { "id": task.id, "name": task.name or UNNAMED, "status": task.status, "stage": current_stage(task), "stages": ordered_stages(task, state_type), "error": error_view(task), "created_at": task.created_at.timestamp(), "credits_used": task.credits_used, "queue_size": 0, } def poster_alive_view(task: PosterAliveTask) -> dict: return { **base_view(task, PosterAliveState), "kind": "f1", "narration": task.narration_lines, "motion_elements": motion_elements(task.motion_plan), "metadata": task.poster_metadata, "template_id": None, "poster_size": [task.poster_width, task.poster_height], "low_res": task.is_low_resolution, "artifacts": {key: url for key, url in ( ("video", task.video_url), ("thumbnail", task.thumbnail_url), ("check_jpg", task.detect_check_url), ) if url}, } def playreel_view(task: PlayreelTask) -> dict: return { **base_view(task, PlayreelState), "kind": "playreel", "source": {"url": task.source_url, "goods_id": task.goods_id, "slug": task.slug}, "gate": task.gate, "review": build_review(task, task.gate) if task.gate else None, "version": task.version, "narration": [line["text"] for line in task.narration_lines or []] or None, "metadata": None, "artifacts": {key: url for key, url in ( ("video", task.video_url), ("thumbnail", thumbnail_url(task)), ) if url}, }