v9.5: 實作標籤完全不重疊算法
- 新增 _calculate_lane_conflicts_v2() 分開返回標籤重疊和線穿框分數 - 修改泳道選擇算法,優先選擇無標籤重疊的泳道 - 兩階段搜尋:優先側別無可用泳道則嘗試另一側 - 增強日誌輸出,顯示標籤範圍和詳細衝突分數 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
257
backend/schemas.py
Normal file
257
backend/schemas.py
Normal file
@@ -0,0 +1,257 @@
|
||||
"""
|
||||
資料模型定義 (Data Schemas)
|
||||
|
||||
本模組定義 TimeLine Designer 所有資料結構。
|
||||
遵循 Pydantic BaseModel 進行嚴格型別驗證。
|
||||
|
||||
Author: AI Agent
|
||||
Version: 1.0.0
|
||||
DocID: SDD-SCHEMA-001
|
||||
Rationale: 實現 SDD.md 第2節定義的資料模型
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional, Literal, List
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class EventType(str, Enum):
|
||||
"""事件類型枚舉"""
|
||||
POINT = "point" # 時間點事件
|
||||
RANGE = "range" # 時間區間事件
|
||||
MILESTONE = "milestone" # 里程碑
|
||||
|
||||
|
||||
class Event(BaseModel):
|
||||
"""
|
||||
時間軸事件模型
|
||||
|
||||
對應 SDD.md - 2. 資料模型 - Event
|
||||
用於表示時間軸上的單一事件或時間區間。
|
||||
"""
|
||||
id: str = Field(..., description="事件唯一識別碼")
|
||||
title: str = Field(..., min_length=1, max_length=200, description="事件標題")
|
||||
start: datetime = Field(..., description="開始時間")
|
||||
end: Optional[datetime] = Field(None, description="結束時間(可選)")
|
||||
group: Optional[str] = Field(None, description="事件群組/分類")
|
||||
description: Optional[str] = Field(None, max_length=1000, description="事件詳細描述")
|
||||
color: str = Field(default='#3B82F6', pattern=r'^#[0-9A-Fa-f]{6}$', description="事件顏色(HEX格式)")
|
||||
event_type: EventType = Field(EventType.POINT, description="事件類型")
|
||||
|
||||
@field_validator('end')
|
||||
@classmethod
|
||||
def validate_end_after_start(cls, end, info):
|
||||
"""驗證結束時間必須晚於開始時間"""
|
||||
if end and info.data.get('start') and end < info.data['start']:
|
||||
raise ValueError('結束時間必須晚於開始時間')
|
||||
return end
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"id": "evt-001",
|
||||
"title": "專案啟動",
|
||||
"start": "2024-01-01T09:00:00",
|
||||
"end": "2024-01-01T17:00:00",
|
||||
"group": "Phase 1",
|
||||
"description": "專案正式啟動會議",
|
||||
"color": "#3B82F6",
|
||||
"event_type": "range"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ThemeStyle(str, Enum):
|
||||
"""主題樣式枚舉"""
|
||||
MODERN = "modern"
|
||||
CLASSIC = "classic"
|
||||
MINIMAL = "minimal"
|
||||
CORPORATE = "corporate"
|
||||
|
||||
|
||||
class TimelineConfig(BaseModel):
|
||||
"""
|
||||
時間軸配置模型
|
||||
|
||||
對應 SDD.md - 2. 資料模型 - TimelineConfig
|
||||
控制時間軸的顯示方式與視覺樣式。
|
||||
"""
|
||||
direction: Literal['horizontal', 'vertical'] = Field(
|
||||
'horizontal',
|
||||
description="時間軸方向"
|
||||
)
|
||||
theme: ThemeStyle = Field(
|
||||
ThemeStyle.MODERN,
|
||||
description="視覺主題"
|
||||
)
|
||||
show_grid: bool = Field(
|
||||
True,
|
||||
description="是否顯示網格線"
|
||||
)
|
||||
show_tooltip: bool = Field(
|
||||
True,
|
||||
description="是否顯示提示訊息"
|
||||
)
|
||||
enable_zoom: bool = Field(
|
||||
True,
|
||||
description="是否啟用縮放功能"
|
||||
)
|
||||
enable_drag: bool = Field(
|
||||
True,
|
||||
description="是否啟用拖曳功能"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"direction": "horizontal",
|
||||
"theme": "modern",
|
||||
"show_grid": True,
|
||||
"show_tooltip": True,
|
||||
"enable_zoom": True,
|
||||
"enable_drag": True
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ExportFormat(str, Enum):
|
||||
"""匯出格式枚舉"""
|
||||
PNG = "png"
|
||||
PDF = "pdf"
|
||||
SVG = "svg"
|
||||
|
||||
|
||||
class ExportOptions(BaseModel):
|
||||
"""
|
||||
匯出選項模型
|
||||
|
||||
對應 SDD.md - 2. 資料模型 - ExportOptions
|
||||
控制時間軸圖檔的匯出格式與品質。
|
||||
"""
|
||||
fmt: ExportFormat = Field(..., description="匯出格式")
|
||||
dpi: int = Field(
|
||||
300,
|
||||
ge=72,
|
||||
le=600,
|
||||
description="解析度(DPI)"
|
||||
)
|
||||
width: Optional[int] = Field(
|
||||
1920,
|
||||
ge=800,
|
||||
le=4096,
|
||||
description="圖片寬度(像素)"
|
||||
)
|
||||
height: Optional[int] = Field(
|
||||
1080,
|
||||
ge=600,
|
||||
le=4096,
|
||||
description="圖片高度(像素)"
|
||||
)
|
||||
transparent_background: bool = Field(
|
||||
False,
|
||||
description="是否使用透明背景"
|
||||
)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"fmt": "pdf",
|
||||
"dpi": 300,
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"transparent_background": False
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Theme(BaseModel):
|
||||
"""
|
||||
主題定義模型
|
||||
|
||||
用於 /themes API 回傳主題列表。
|
||||
"""
|
||||
name: str = Field(..., description="主題名稱")
|
||||
style: ThemeStyle = Field(..., description="主題樣式識別碼")
|
||||
primary_color: str = Field(..., pattern=r'^#[0-9A-Fa-f]{6}$', description="主要顏色")
|
||||
background_color: str = Field(..., pattern=r'^#[0-9A-Fa-f]{6}$', description="背景顏色")
|
||||
text_color: str = Field(..., pattern=r'^#[0-9A-Fa-f]{6}$', description="文字顏色")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"name": "現代風格",
|
||||
"style": "modern",
|
||||
"primary_color": "#3B82F6",
|
||||
"background_color": "#FFFFFF",
|
||||
"text_color": "#1F2937"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ImportResult(BaseModel):
|
||||
"""
|
||||
匯入結果模型
|
||||
|
||||
用於 /import API 回傳匯入結果。
|
||||
"""
|
||||
success: bool = Field(..., description="是否成功")
|
||||
events: List[Event] = Field(default_factory=list, description="成功匯入的事件列表")
|
||||
errors: List[str] = Field(default_factory=list, description="錯誤訊息列表")
|
||||
total_rows: int = Field(0, description="總行數")
|
||||
imported_count: int = Field(0, description="成功匯入數量")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"success": True,
|
||||
"events": [],
|
||||
"errors": [],
|
||||
"total_rows": 100,
|
||||
"imported_count": 98
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class RenderResult(BaseModel):
|
||||
"""
|
||||
渲染結果模型
|
||||
|
||||
用於 /render API 回傳 Plotly JSON 格式的時間軸資料。
|
||||
"""
|
||||
success: bool = Field(..., description="是否成功")
|
||||
data: dict = Field(..., description="Plotly 圖表資料(JSON格式)")
|
||||
layout: dict = Field(..., description="Plotly 佈局設定")
|
||||
config: dict = Field(default_factory=dict, description="Plotly 配置")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"success": True,
|
||||
"data": {},
|
||||
"layout": {},
|
||||
"config": {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class APIResponse(BaseModel):
|
||||
"""
|
||||
通用 API 回應模型
|
||||
|
||||
用於標準化 API 回應格式,提供一致的錯誤處理。
|
||||
"""
|
||||
success: bool = Field(..., description="操作是否成功")
|
||||
message: str = Field("", description="回應訊息")
|
||||
data: Optional[dict] = Field(None, description="回應資料")
|
||||
error_code: Optional[str] = Field(None, description="錯誤代碼(如有)")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"success": True,
|
||||
"message": "操作成功",
|
||||
"data": None,
|
||||
"error_code": None
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user