移出誤入的本地變更
All checks were successful
CD Pipeline / deploy (push) Successful in 58s

This commit is contained in:
OoO
2026-05-13 10:31:52 +08:00
parent ae79cdd9d6
commit 2b1174a902
2 changed files with 29 additions and 87 deletions

View File

@@ -320,7 +320,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
# ==========================================
# 系統版本與路徑
# ==========================================
SYSTEM_VERSION = "V10.99"
SYSTEM_VERSION = "V10.98"
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
public_url = PUBLIC_URL # 用於模板顯示

View File

@@ -61,13 +61,10 @@ def _get_data_fingerprint(engine, table_name='daily_sales_snapshot'):
"""DB 指紋 (max_snapshot_date, row_count):資料一變動指紋就跳號,
讓 4 worker 的 in-memory cache 在下一次 request 自然失效。"""
try:
validate_table_name(table_name)
if engine.dialect.name == 'postgresql':
fingerprint_sql = f'SELECT MAX(snapshot_date)::text, COUNT(*) FROM "{table_name}"'
else:
fingerprint_sql = f'SELECT CAST(MAX(snapshot_date) AS TEXT), COUNT(*) FROM "{table_name}"'
with engine.connect() as conn:
row = conn.execute(text(fingerprint_sql)).fetchone()
row = conn.execute(text(
f'SELECT MAX(snapshot_date)::text, COUNT(*) FROM "{table_name}"'
)).fetchone()
return (row[0], row[1]) if row else (None, 0)
except Exception as e:
sys_log.warning(f"[Cache] fingerprint 查詢失敗(保守維持現有 cache: {e}")
@@ -92,45 +89,6 @@ def _is_cache_valid(cache_key, engine=None, table_name='daily_sales_snapshot'):
return True
def _get_available_daily_dates(engine, table_name='daily_sales_snapshot'):
"""取得可選日期清單,避免為了 date selector 載入整張業績表。"""
validate_table_name(table_name)
if engine.dialect.name == 'postgresql':
date_expr = 'snapshot_date::date'
else:
date_expr = 'date(snapshot_date)'
query = text(
f'SELECT DISTINCT {date_expr} AS snapshot_date '
f'FROM "{table_name}" '
'WHERE snapshot_date IS NOT NULL '
'ORDER BY snapshot_date DESC'
)
with engine.connect() as conn:
rows = conn.execute(query).fetchall()
dates = []
for row in rows:
try:
dates.append(pd.to_datetime(row[0]).normalize())
except Exception:
continue
return dates
def _read_daily_sales_window(engine, table_name, start_date, end_date):
"""只讀取畫面需要的日期窗口,降低冷 worker 首頁等待時間。"""
return safe_read_sql(
table_name,
engine=engine,
where_clause='"snapshot_date" >= :start_date AND "snapshot_date" <= :end_date',
params={
'start_date': start_date.strftime('%Y-%m-%d'),
'end_date': end_date.strftime('%Y-%m-%d'),
},
)
# ==========================================
# 輔助函數
# ==========================================
@@ -415,21 +373,35 @@ def daily_sales():
chart_data=None, categories=None, calendar_data=None, selected_month=None,
datetime_now=datetime_now_str, active_page='daily_sales')
available_dates = _get_available_daily_dates(engine, table_name)
if not available_dates:
return render_template('daily_sales.html',
error="資料表為空,請先匯入當日業績資料。",
selected_date=None, available_dates=[], current=None, dod=None, wow=None,
chart_data=None, categories=None, calendar_data=None, selected_month=None,
datetime_now=datetime_now_str, active_page='daily_sales')
cache_key = f'{table_name}_daily'
# TTL + DB fingerprint 雙閘檢查(資料變動即自動失效,跨 worker 強一致)
if _is_cache_valid(cache_key, engine, table_name):
df = _SALES_PROCESSED_CACHE[cache_key]['df']
sys_log.debug(f"使用緩存數據,剩餘有效時間: {_CACHE_EXPIRY_SECONDS - (datetime.now() - _SALES_PROCESSED_CACHE[cache_key]['timestamp']).total_seconds():.0f}")
else:
df = safe_read_sql(table_name, engine=engine)
if df.empty:
return render_template('daily_sales.html',
error="資料表為空,請先匯入當日業績資料。",
selected_date=None, available_dates=[], current=None, dod=None, wow=None,
chart_data=None, categories=None, calendar_data=None, selected_month=None,
datetime_now=datetime_now_str, active_page='daily_sales')
available_dates_str = [d.strftime('%Y-%m-%d') for d in available_dates]
df = preprocess_daily_sales_data(df)
_SALES_PROCESSED_CACHE[cache_key] = {
'df': df, 'timestamp': datetime.now(),
'fingerprint': _get_data_fingerprint(engine, table_name),
}
sys_log.info(f"已重新載入數據並更新緩存,共 {len(df)} 筆記錄")
available_dates = sorted(df['snapshot_date'].unique(), reverse=True)
available_dates_str = [d.strftime('%Y-%m-%d') if isinstance(d, pd.Timestamp) else str(d) for d in available_dates]
selected_date_param = request.args.get('date')
if selected_date_param:
selected_date = pd.to_datetime(selected_date_param)
else:
selected_date = available_dates[0]
selected_date = df['snapshot_date'].max()
selected_month_param = request.args.get('month')
if selected_month_param:
@@ -441,42 +413,12 @@ def daily_sales():
if selected_month_param and not selected_date_param:
is_month_view = True
month_start = selected_month.replace(day=1)
month_end = (month_start + pd.DateOffset(months=1)) - pd.Timedelta(days=1)
data_start = min(
selected_date - pd.Timedelta(days=30),
selected_date - pd.Timedelta(days=7),
month_start - pd.Timedelta(days=1),
)
data_end = max(selected_date, month_end)
cache_key = f"{table_name}_daily_{data_start.strftime('%Y%m%d')}_{data_end.strftime('%Y%m%d')}"
# TTL + DB fingerprint 雙閘檢查(資料變動即自動失效,跨 worker 強一致)
if _is_cache_valid(cache_key, engine, table_name):
df = _SALES_PROCESSED_CACHE[cache_key]['df']
sys_log.debug(f"使用緩存數據,剩餘有效時間: {_CACHE_EXPIRY_SECONDS - (datetime.now() - _SALES_PROCESSED_CACHE[cache_key]['timestamp']).total_seconds():.0f}")
else:
df = _read_daily_sales_window(engine, table_name, data_start, data_end)
if df.empty:
return render_template('daily_sales.html',
error="所選日期區間沒有業績資料。",
selected_date=None, available_dates=available_dates_str, current=None, dod=None, wow=None,
chart_data=None, categories=None, calendar_data=None, selected_month=None,
datetime_now=datetime_now_str, active_page='daily_sales')
df = preprocess_daily_sales_data(df)
_SALES_PROCESSED_CACHE[cache_key] = {
'df': df, 'timestamp': datetime.now(),
'fingerprint': _get_data_fingerprint(engine, table_name),
}
sys_log.info(
f"已載入當日業績窗口 {data_start.strftime('%Y-%m-%d')}~{data_end.strftime('%Y-%m-%d')},共 {len(df)} 筆記錄"
)
current_kpi = calculate_daily_kpis(df, selected_date)
dod_kpi = calculate_dod(df, selected_date)
wow_kpi = calculate_wow(df, selected_date)
month_start = selected_month.replace(day=1)
month_end = (month_start + pd.DateOffset(months=1)) - pd.Timedelta(days=1)
month_df = df[(df['snapshot_date'] >= month_start) & (df['snapshot_date'] <= month_end)]
cols = month_df.columns.tolist()