from __future__ import annotations from datetime import datetime from pathlib import Path from typing import Any, Literal from pydantic import BaseModel, Field class ProductInput(BaseModel): source: Literal["manual", "external"] external_product_id: str | None = None name: str sku: str = "" specification: str = "" def __init__( self, source: Literal["manual", "external"] | None = None, external_product_id: str | None = None, name: str | None = None, sku: str = "", specification: str = "", **data: Any, ) -> None: if source is not None: data["source"] = source if external_product_id is not None or "external_product_id" not in data: data["external_product_id"] = external_product_id if name is not None: data["name"] = name if sku or "sku" not in data: data["sku"] = sku if specification or "specification" not in data: data["specification"] = specification super().__init__(**data) class ProductRecord(BaseModel): product_id: str source: Literal["manual", "external"] external_product_id: str | None = None name: str sku: str = "" specification: str = "" status: Literal["active", "pending_cleanup", "purged"] = "active" created_at: datetime updated_at: datetime purge_after: datetime | None = None class ProductVersionRecord(BaseModel): version_id: str product_id: str version: str = "" metadata: dict[str, Any] = Field(default_factory=dict) created_at: datetime class ProductImageRecord(BaseModel): image_id: str product_id: str version_id: str image_path: str = "" image_type: str = "" created_at: datetime class ProductWordcloudArchiveRecord(BaseModel): archive_id: str product_id: str version_id: str archive_path: str = "" created_at: datetime 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 = "" mode: Literal["exact", "contains"] = "exact" 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