from __future__ import annotations from datetime import datetime from pathlib import Path from typing import Literal from pydantic import BaseModel, Field class JobCreateResponse(BaseModel): job_id: str class JobEvent(BaseModel): type: Literal["log", "status"] stage: str progress_percent: int = Field(ge=0, le=100) message: str timestamp: datetime elapsed_seconds: float | None = None class JobStatus(BaseModel): job_id: str status: Literal["queued", "running", "success", "failed"] stage: str progress_percent: int = Field(ge=0, le=100) message: str created_at: datetime updated_at: datetime artifacts: dict[str, str] error: str = "" elapsed_seconds: float | None = None class JobDetail(BaseModel): status: JobStatus recent_events: list[JobEvent] class JobResult(BaseModel): job_id: str status: Literal["queued", "running", "success", "failed"] image_url: str = "" svg_url: str = "" svg_stroke_url: str = "" db_url: str = "" metrics_url: str = "" elapsed_seconds: float | None = None class WordLocation(BaseModel): id: int name: str x: int y: int font_size: int color: str = "" orientation: Literal["horizontal", "vertical"] = "horizontal" box_x: int box_y: int box_width: int box_height: int class JobLocationSearchResult(BaseModel): job_id: str query: str = "" total: int = 0 canvas_width: int = 0 canvas_height: int = 0 matches: list[WordLocation] = Field(default_factory=list) class JobPaths(BaseModel): root: Path input_dir: Path output_dir: Path mask_path: Path excel_path: Path config_path: Path class Template(BaseModel): id: str name: str width: int height: int aspect_ratio: str description: str = "" class Asset(BaseModel): asset_id: str name: str type: str mime_type: str width: int height: int file_size: int file_url: str job_id: str = "" created_at: datetime class LineSpacingAnalysisRequest(BaseModel): percentile: float = Field(default=3, ge=0, le=100) elementWidth: float = Field(gt=0) elementHeight: float = Field(gt=0) sampleStep: float = Field(default=2, gt=0) class LineSpacingClosestPoints(BaseModel): ax: float ay: float bx: float by: float class LineSpacingAnalysisSummary(BaseModel): percentile: float spacingPx: float spacingMm: float minSpacingPx: float minSpacingMm: float sampleStep: float curveCount: int segmentCount: int nearestCount: int sourceWidth: float sourceHeight: float elementWidth: float elementHeight: float computedAt: str closestPoints: LineSpacingClosestPoints | None = None class DesignTemplate(BaseModel): template_id: str name: str description: str = "" document: dict reference_asset_ids: list[str] = Field(default_factory=list) cover_asset_id: str = "" created_at: datetime updated_at: datetime class Project(BaseModel): project_id: str name: str template_id: str background_color: str stickers: list[dict] = Field(default_factory=list) created_at: datetime updated_at: datetime class ProjectSummary(BaseModel): project_id: str name: str template_id: str background_color: str sticker_count: int = 0 created_at: datetime updated_at: datetime class Font(BaseModel): font_id: str name: str filename: str file_size: int created_at: datetime