refactor(p1-01b): app.py 文字/顏色/數字工具抽到 utils/text_helpers.py
All checks were successful
CD Pipeline / deploy (push) Successful in 1m7s

- slugify, get_color_for_string, extract_snapshot_date_from_filename, number_format
- @app.template_filter('number_format') 保留為 Jinja 註冊薄殼,實作走 utils
- app.py: 7,206 → 7,187 (-19)
This commit is contained in:
ooo
2026-04-28 15:44:15 +08:00
parent f7a5f8505f
commit 0a3f6cb22d
2 changed files with 68 additions and 29 deletions

39
app.py
View File

@@ -496,10 +496,8 @@ for _bp_module, _bp_name in [
except Exception as _e:
sys_log.error(f"[Blueprint] ❌ {_bp_name} 註冊失敗: {_e}")
# V-Fix: 註冊 slugify 函數供模板使用,解決 'slugify is undefined' 錯誤
def slugify(text):
if not text: return ""
return str(text).replace(' ', '_').replace(':', '').replace('!', '').replace('?', '').replace('/', '').replace('&', '').replace('(', '').replace(')', '').replace('+', '_').replace('.', '_').replace('%', '').replace("'", "")
# V-Fix: 註冊 slugify 函數供模板使用(實作搬至 utils/text_helpers.py
from utils.text_helpers import slugify # noqa: E402
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = "服務啟動中..."
@@ -552,35 +550,18 @@ def load_scheduler_stats():
# ================= 🛠️ 數據處理核心 (封裝) =================
def get_color_for_string(s):
"""為字串生成一個穩定且美觀的 HSL 顏色"""
if not s: return "hsl(0, 0%, 85%)" # 預設灰色
# 使用 md5 hash 確保顏色穩定,並映射到 HSL 色彩空間以獲得柔和色彩
hash_val = int(hashlib.md5(s.encode('utf-8'), usedforsecurity=False).hexdigest(), 16)
hue = hash_val % 360
return f"hsl({hue}, 60%, 88%)"
# 純工具:實作已搬至 utils/text_helpers.py
from utils.text_helpers import ( # noqa: E402
get_color_for_string,
extract_snapshot_date_from_filename,
number_format as _number_format,
)
def extract_snapshot_date_from_filename(filename):
"""從檔名提取日期即時業績_當日_20260111.xlsx → 2026-01-11"""
match = re.search(r'(\d{8})', filename)
if match:
date_str = match.group(1) # '20260111'
try:
# 轉換為 YYYY-MM-DD 格式
year = date_str[:4]
month = date_str[4:6]
day = date_str[6:8]
return f"{year}-{month}-{day}"
except:
return None
return None
@app.template_filter('number_format')
def number_format_filter(value):
"""V9.61: 將數字格式化,加上千分位符號"""
if isinstance(value, (int, float)):
return "{:,.0f}".format(value)
return value
"""Jinja filter wrapper — 實作見 utils.text_helpers.number_format"""
return _number_format(value)
# V-Refactor: 將 find_col 移至全域,方便多個函式共用
from utils.df_helpers import find_col # noqa: E402

58
utils/text_helpers.py Normal file
View File

@@ -0,0 +1,58 @@
"""文字 / 顏色 / 檔名 / 數字格式化工具。
從 app.py 抽出的純函數,無外部副作用。
"""
import hashlib
import re
def slugify(text):
"""將任意字串轉為適合用於 HTML id / URL 的 slug。"""
if not text:
return ""
return (
str(text)
.replace(' ', '_')
.replace(':', '')
.replace('!', '')
.replace('?', '')
.replace('/', '')
.replace('&', '')
.replace('(', '')
.replace(')', '')
.replace('+', '_')
.replace('.', '_')
.replace('%', '')
.replace("'", "")
)
def get_color_for_string(s):
"""為字串生成一個穩定且美觀的 HSL 顏色(柔和淺色,適合背景)。"""
if not s:
return "hsl(0, 0%, 85%)" # 預設灰色
hash_val = int(hashlib.md5(s.encode('utf-8'), usedforsecurity=False).hexdigest(), 16)
hue = hash_val % 360
return f"hsl({hue}, 60%, 88%)"
def extract_snapshot_date_from_filename(filename):
"""從檔名提取 8 碼日期即時業績_當日_20260111.xlsx → 2026-01-11。"""
match = re.search(r'(\d{8})', filename)
if not match:
return None
date_str = match.group(1)
try:
year = date_str[:4]
month = date_str[4:6]
day = date_str[6:8]
return f"{year}-{month}-{day}"
except Exception:
return None
def number_format(value):
"""將數字格式化,加上千分位符號(無小數)。非數字直接回傳原值。"""
if isinstance(value, (int, float)):
return "{:,.0f}".format(value)
return value