v9.5: 實作標籤完全不重疊算法

- 新增 _calculate_lane_conflicts_v2() 分開返回標籤重疊和線穿框分數
- 修改泳道選擇算法,優先選擇無標籤重疊的泳道
- 兩階段搜尋:優先側別無可用泳道則嘗試另一側
- 增強日誌輸出,顯示標籤範圍和詳細衝突分數

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
beabigegg
2025-11-06 11:35:29 +08:00
commit 2d37d23bcf
83 changed files with 22971 additions and 0 deletions

257
backend/schemas.py Normal file
View 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
}
}