後端代碼清理:移除冗餘註解和調試代碼

清理內容:
- 移除所有開發元資訊(Author, Version, DocID, Rationale等)
- 刪除註解掉的代碼片段(力導向演算法等24行)
- 移除調試用的 logger.debug 語句
- 簡化冗餘的內聯註解(emoji、"重要"等標註)
- 刪除 TDD 文件引用

清理檔案:
- backend/main.py - 移除調試日誌和元資訊
- backend/importer.py - 移除詳細類型檢查調試
- backend/export.py - 簡化 docstring
- backend/schemas.py - 移除元資訊
- backend/renderer.py - 移除 TDD 引用
- backend/renderer_timeline.py - 移除註解代碼和冗餘註解
- backend/path_planner.py - 簡化策略註解

保留內容:
- 所有函數的 docstring(功能說明、參數、返回值)
- 必要的業務邏輯註解
- 簡潔的模組功能說明

效果:
- 刪除約 100+ 行冗餘註解
- 代碼更加簡潔專業
- 功能完整性 100% 保留

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
beabigegg
2025-11-06 12:22:29 +08:00
parent dc01655c9e
commit aa987adfb9
7 changed files with 50 additions and 242 deletions

View File

@@ -6,9 +6,6 @@
- 事件點標記
- 交錯的文字標註
- 連接線
Author: AI Agent
Version: 2.0.0
"""
from datetime import datetime, timedelta
@@ -663,17 +660,6 @@ class ClassicTimelineRenderer:
line_through_box_score += 100.0
return overlap_score, line_through_box_score
# for other_lane_idx in range(7):
# if other_lane_idx == lane_idx:
# continue
# for occupied in occupied_lanes[other_lane_idx]:
# if not (label_end < occupied['start'] or label_start > occupied['end']):
# same_side = (current_label_y > 0 and occupied['label_y'] > 0) or \
# (current_label_y < 0 and occupied['label_y'] < 0)
# if not same_side:
# score += 1.0 # 交叉權重(已禁用)
return score
def _check_line_intersects_textbox(
self,
@@ -810,18 +796,17 @@ class ClassicTimelineRenderer:
line_x2_ts = label_x.timestamp()
line_y2 = label_y
# 🔍 判斷是否為垂直線label_x == event_x
# 判斷是否為垂直線
is_vertical_line = abs(line_x2_ts - line_x1_ts) < 1e-6
# 檢查是否與其他標籤相交
line_blocked = False
blocking_labels = []
# ⚠️ 對於垂直線x_offset=0跳過碰撞檢測
# 原因:固定泳道算法已確保標籤本身不重疊,垂直線無法避開其他標籤
# 垂直線跳過碰撞檢測
if is_vertical_line:
logger.debug(f" 🔹 '{title}' 是垂直線,跳過碰撞檢測,直接繪製")
line_blocked = False # 強制不使用BFS
logger.debug(f" '{title}' 是垂直線,跳過碰撞檢測")
line_blocked = False
# 只對非垂直線進行碰撞檢測
for j, other in enumerate(sorted_markers) if not is_vertical_line else []:
@@ -838,36 +823,23 @@ class ClassicTimelineRenderer:
other_top = other_y + label_height / 2
other_bottom = other_y - label_height / 2
# 🔍 DEBUG: 記錄檢測的標籤詳情
logger.debug(f" 檢查 {title} vs {other_title}:")
logger.debug(f" {title} 線段: X1={datetime.fromtimestamp(line_x1_ts)}, Y1={line_y1:.2f} -> X2={datetime.fromtimestamp(line_x2_ts)}, Y2={line_y2:.2f}")
logger.debug(f" {other_title} 標籤: X=[{datetime.fromtimestamp(other_left)} ~ {datetime.fromtimestamp(other_right)}], Y=[{other_bottom:.2f} ~ {other_top:.2f}]")
logger.debug(f" {other_title} 泳道: {other.get('swim_lane', '?')}")
# 檢測線段與矩形的相交
# 1. 首先檢查X範圍是否重疊
line_x_min = min(line_x1_ts, line_x2_ts)
line_x_max = max(line_x1_ts, line_x2_ts)
if line_x_max < other_left or line_x_min > other_right:
logger.debug(f" ✓ X範圍不重疊跳過")
continue # X範圍不重疊不可能相交
continue
# 2. 計算線段在標籤X範圍內的Y值
# 使用線性插值y = y1 + (x - x1) * (y2 - y1) / (x2 - x1)
if abs(line_x2_ts - line_x1_ts) < 1e-6:
# 垂直線(幾乎不可能,但要處理)
if other_left <= line_x1_ts <= other_right:
# 檢查Y範圍
line_y_min = min(line_y1, line_y2)
line_y_max = max(line_y1, line_y2)
if not (line_y_max < other_bottom or line_y_min > other_top):
line_blocked = True
blocking_labels.append(other_title)
else:
# 改進的碰撞檢測:檢查線段是否真的穿過標籤矩形
# 方法:計算線段與標籤矩形的所有可能交點
intersects = False
intersection_reason = ""
@@ -883,51 +855,40 @@ class ClassicTimelineRenderer:
# 2. 檢查線段是否與標籤的四條邊相交
if not intersects and line_x1_ts != line_x2_ts:
# 線段與標籤左邊界的交點
if line_x_min <= other_left <= line_x_max:
t = (other_left - line_x1_ts) / (line_x2_ts - line_x1_ts)
y_at_left = line_y1 + t * (line_y2 - line_y1)
logger.debug(f" 檢查左邊界: t={t:.4f}, y_at_left={y_at_left:.2f}, Y範圍=[{other_bottom:.2f}~{other_top:.2f}]")
if other_bottom <= y_at_left <= other_top:
intersects = True
intersection_reason = f"穿過左邊界 (y={y_at_left:.2f})"
intersection_reason = f"穿過左邊界"
# 線段與標籤右邊界的交點
if not intersects and line_x_min <= other_right <= line_x_max:
t = (other_right - line_x1_ts) / (line_x2_ts - line_x1_ts)
y_at_right = line_y1 + t * (line_y2 - line_y1)
logger.debug(f" 檢查右邊界: t={t:.4f}, y_at_right={y_at_right:.2f}, Y範圍=[{other_bottom:.2f}~{other_top:.2f}]")
if other_bottom <= y_at_right <= other_top:
intersects = True
intersection_reason = f"穿過右邊界 (y={y_at_right:.2f})"
intersection_reason = f"穿過右邊界"
# 3. 檢查線段是否與標籤的上下邊界相交
if not intersects and abs(line_y2 - line_y1) > 1e-6:
# 線段與標籤下邊界的交點
t_bottom = (other_bottom - line_y1) / (line_y2 - line_y1)
if 0 <= t_bottom <= 1:
x_at_bottom = line_x1_ts + t_bottom * (line_x2_ts - line_x1_ts)
logger.debug(f" 檢查下邊界: t={t_bottom:.4f}, x_at_bottom={datetime.fromtimestamp(x_at_bottom)}, X範圍=[{datetime.fromtimestamp(other_left)}~{datetime.fromtimestamp(other_right)}]")
if other_left <= x_at_bottom <= other_right:
intersects = True
intersection_reason = f"穿過下邊界 (x={datetime.fromtimestamp(x_at_bottom)})"
intersection_reason = f"穿過下邊界"
# 線段與標籤上邊界的交點
if not intersects:
t_top = (other_top - line_y1) / (line_y2 - line_y1)
if 0 <= t_top <= 1:
x_at_top = line_x1_ts + t_top * (line_x2_ts - line_x1_ts)
logger.debug(f" 檢查上邊界: t={t_top:.4f}, x_at_top={datetime.fromtimestamp(x_at_top)}, X範圍=[{datetime.fromtimestamp(other_left)}~{datetime.fromtimestamp(other_right)}]")
if other_left <= x_at_top <= other_right:
intersects = True
intersection_reason = f"穿過上邊界 (x={datetime.fromtimestamp(x_at_top)})"
intersection_reason = f"穿過上邊界"
if intersects:
line_blocked = True
blocking_labels.append(other_title)
logger.debug(f" ❌ 碰撞確認: {intersection_reason}")
else:
logger.debug(f" ✓ 無碰撞")
if not line_blocked:
# 直線不被遮擋,直接繪製
@@ -991,11 +952,9 @@ class ClassicTimelineRenderer:
expansion_ratio=0.0 # 不外擴
)
# 如果標籤與事件在同一時間(垂直對齊),清除事件點附近
# 這是為了處理 Event 4 和 Event 5 這種情況
# 如果標籤與事件在同一時間(垂直對齊),清除事件點附近
if abs((label_x - event_x).total_seconds()) < label_width_seconds / 4:
# 清除起點附近的障礙物(只清除一小塊)
start_clear_seconds = 3600 # 清除起點附近1小時的範圍
start_clear_seconds = 3600
grid_map.mark_rectangle(
center_x_datetime=event_x,
center_y=0,
@@ -1009,14 +968,11 @@ class ClassicTimelineRenderer:
start_col = grid_map.datetime_to_grid_x(event_x)
start_row = grid_map.y_to_grid_y(0)
# 終點:標籤邊緣(而非中心!)
# 根據標籤在上方還是下方,設定終點在標籤的下邊緣或上邊緣
# 終點:標籤邊緣
if label_y > 0:
# 上方標籤:終點在下邊緣(靠近時間軸的一側)
label_edge_y = label_y - label_height / 2
direction_constraint = "up"
else:
# 下方標籤:終點在上邊緣(靠近時間軸的一側)
label_edge_y = label_y + label_height / 2
direction_constraint = "down"
@@ -1039,8 +995,7 @@ class ClassicTimelineRenderer:
)
if path_grid is None:
# BFS 失敗,強制使用直線
logger.warning(f" ✗ BFS 找不到路徑,強制使用直線")
logger.warning(f" BFS 找不到路徑,使用直線")
shapes.append({
'type': 'line',
'x0': event_x,
@@ -1049,21 +1004,18 @@ class ClassicTimelineRenderer:
'y1': label_y,
'xref': 'x',
'yref': 'y',
'line': {'color': color, 'width': 1.5, 'dash': 'dot'}, # 虛線表示強制
'line': {'color': color, 'width': 1.5, 'dash': 'dot'},
'layer': 'below',
'opacity': 0.5
})
# 重要:即使是強制直線,也要標記為障礙物!
path_points = [(event_x, 0), (label_x, label_y)]
grid_map.mark_path(path_points, width_expansion=2.5)
else:
# BFS 成功,簡化並繪製路徑
logger.info(f" ✓ BFS 找到路徑,長度: {len(path_grid)}")
logger.info(f" BFS 找到路徑,長度: {len(path_grid)}")
# 簡化路徑
path_coords = simplify_path(path_grid, grid_map)
logger.debug(f" 簡化後: {len(path_coords)} 個轉折點")
# 繪製路徑(多段線)
for i in range(len(path_coords) - 1):
@@ -1082,17 +1034,17 @@ class ClassicTimelineRenderer:
'opacity': 0.7
})
# 將路徑標記為障礙物(供後續路徑避讓)
# 將路徑標記為障礙物
grid_map.mark_path(path_coords, width_expansion=2.5)
# 恢復當前標籤為障礙物(重要!)
# 恢復當前標籤為障礙物
grid_map.mark_rectangle(
center_x_datetime=label_x,
center_y=label_y,
width_seconds=label_width_seconds,
height=label_height,
state=GridMap.OBSTACLE,
expansion_ratio=0.0 # 不外擴
expansion_ratio=0.0
)
logger.info(f"BFS 路徑規劃完成,共生成 {len(shapes)} 個線段")
@@ -1212,18 +1164,6 @@ class ClassicTimelineRenderer:
}
})
# 應用力導向演算法優化標籤位置(如果配置啟用)
# 暫時禁用效果不佳考慮使用專業套件D3.js, Vega-Lite
# if config.enable_zoom: # 使用 enable_zoom 作為啟用力導向的標誌(臨時)
# markers = apply_force_directed_layout(
# markers,
# config,
# time_range_seconds, # 新增:傳入時間範圍用於計算文字框尺寸
# max_iterations=100,
# repulsion_strength=50.0, # 調整:降低排斥力強度
# damping=0.8 # 調整:增加阻尼係數
# )
# 2. 事件點
for marker in markers:
# 事件圓點
@@ -1264,7 +1204,7 @@ class ClassicTimelineRenderer:
'size': 10,
'color': theme['text_color']
},
'bgcolor': 'rgba(255, 255, 255, 0.85)', # 降低不透明度,避免完全遮擋底層連接線
'bgcolor': 'rgba(255, 255, 255, 0.85)',
'bordercolor': marker['color'],
'borderwidth': 2,
'borderpad': 5,
@@ -1272,11 +1212,10 @@ class ClassicTimelineRenderer:
'align': 'left'
})
# 計算 Y 軸範圍v9.1 - 固定7泳道調整下層最低位置
# 上方最高為 4.0,下方最低為 -2.5 (ratio 0.50 * 5.0)
y_range_max = 4.5 # 上方最高層 + 邊距
y_range_min = -2.5 # 下方最低層(已調整,避免遮擋日期)
y_margin = 0.8 # 額外邊距(增加以確保日期文字完全可見)
# 計算 Y 軸範圍
y_range_max = 4.5
y_range_min = -2.5
y_margin = 0.8
# 佈局配置
layout = {
@@ -1305,7 +1244,6 @@ class ClassicTimelineRenderer:
'margin': {'l': 50, 'r': 50, 't': 80, 'b': 80}
}
# Plotly 配置
plotly_config = {
'responsive': True,
'displayModeBar': True,
@@ -1413,18 +1351,6 @@ class ClassicTimelineRenderer:
}
})
# 應用力導向演算法優化標籤位置(如果配置啟用)
# 暫時禁用效果不佳考慮使用專業套件D3.js, Vega-Lite
# if config.enable_zoom: # 使用 enable_zoom 作為啟用力導向的標誌(臨時)
# markers = apply_force_directed_layout(
# markers,
# config,
# time_range_seconds, # 新增:傳入時間範圍用於計算文字框尺寸
# max_iterations=100,
# repulsion_strength=50.0, # 調整:降低排斥力強度
# damping=0.8 # 調整:增加阻尼係數
# )
# 2. 事件點、時間標籤和連接線
for marker in markers:
# 事件圓點
@@ -1458,42 +1384,27 @@ class ClassicTimelineRenderer:
line_x_points = [marker['x'], label_x]
line_y_points = [event_y, event_y]
else:
# 使用 L 形直角折線(水平 -> 垂直 -> 水平)
# 智能路徑規劃:根據層級、方向、跨越距離動態調整
# 1. 判斷標籤在左側還是右側
is_right_side = label_x > 0 # 右側為正
# 2. 計算跨越距離(標準化)
# 使用 L 形直角折線
is_right_side = label_x > 0
total_range = (end_date - start_date).total_seconds()
y_span_ratio = abs(y_diff_seconds) / total_range if total_range > 0 else 0
layer_group = layer % 10
# 3. 根據層級計算基礎偏移(增加偏移幅度和範圍)
layer_group = layer % 10 # 每10層循環一次增加變化
# 4. 根據左右方向使用不同的層級策略
# 右側:從低到高 (0.25 -> 0.85)
# 左側:從高到低 (0.85 -> 0.25),鏡像分布避免交錯
if is_right_side:
base_ratio = 0.25
layer_offset = layer_group * 0.06 # 6% 增量
layer_offset = layer_group * 0.06
else:
base_ratio = 0.85
layer_offset = -layer_group * 0.06 # 負向偏移
layer_offset = -layer_group * 0.06
# 5. 根據跨越距離調整
# 距離越遠,調整幅度越大
if y_span_ratio > 0.3: # 跨越超過30%的時間軸
if y_span_ratio > 0.3:
distance_adjustment = -0.10 if is_right_side else 0.10
elif y_span_ratio > 0.15: # 跨越15-30%
elif y_span_ratio > 0.15:
distance_adjustment = -0.05 if is_right_side else 0.05
else:
distance_adjustment = 0
# 6. 計算最終的中間寬度比例
mid_x_ratio = base_ratio + layer_offset + distance_adjustment
# 7. 限制範圍,避免過遠或過近
mid_x_ratio = max(0.20, min(mid_x_ratio, 0.90))
mid_x = label_x * mid_x_ratio
@@ -1508,11 +1419,10 @@ class ClassicTimelineRenderer:
event_y, # 起點
event_y, # 保持在同一高度
label_y, # 垂直移動到標籤 y
label_y # 終點
label_y
]
# 使用 shape line 繪製連接線(分段),設定 layer='below' 避免遮擋
# 將每一段連線分別繪製為獨立的 shape
# 繪製連接線
for i in range(len(line_x_points) - 1):
shapes.append({
'type': 'line',
@@ -1526,8 +1436,8 @@ class ClassicTimelineRenderer:
'color': marker['color'],
'width': 1.5,
},
'layer': 'below', # 線條置於底層,不遮擋事件點和文字框
'opacity': 0.7, # 半透明,作為視覺輔助
'layer': 'below',
'opacity': 0.7,
})
# 文字標註(包含時間、標題、描述)
@@ -1540,7 +1450,7 @@ class ClassicTimelineRenderer:
'size': 10,
'color': theme['text_color']
},
'bgcolor': 'rgba(255, 255, 255, 0.85)', # 降低不透明度,避免完全遮擋底層連接線
'bgcolor': 'rgba(255, 255, 255, 0.85)',
'bordercolor': marker['color'],
'borderwidth': 2,
'borderpad': 5,
@@ -1548,10 +1458,10 @@ class ClassicTimelineRenderer:
'align': 'left'
})
# 計算 X 軸範圍(根據最大層級動態調整,並為時間標籤預留空間)
# 計算 X 軸範圍
x_range_max = max((pos['layer'] // 2 + 1) * layer_spacing for pos in label_positions) if label_positions else layer_spacing
x_range_min = -x_range_max
x_margin = 0.4 # 額外邊距(增加以容納時間標籤)
x_margin = 0.4
# 佈局配置
layout = {
@@ -1580,7 +1490,6 @@ class ClassicTimelineRenderer:
'margin': {'l': 100, 'r': 100, 't': 80, 'b': 50}
}
# Plotly 配置
plotly_config = {
'responsive': True,
'displayModeBar': True,