This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
> 本文件定義專案開發的核心準則與不可違反的規範
|
||||
> **建立日期**: 2026-01-12
|
||||
> **當前版本**: V10.46 (Fix product pick sales date casting)
|
||||
> **當前版本**: V10.47 (Auto-detect sales columns for product picks)
|
||||
> **最後更新**: 2026-05-01
|
||||
|
||||
---
|
||||
|
||||
4
app.py
4
app.py
@@ -95,8 +95,8 @@ except Exception as e:
|
||||
sys_log.error(f"無法檢測磁碟空間: {e}")
|
||||
|
||||
# 🚩 系統版本定義 (備份與顯示用)
|
||||
# 🚩 2026-05-01 V10.46: Fix product pick sales date casting
|
||||
SYSTEM_VERSION = "V10.46"
|
||||
# 🚩 2026-05-01 V10.47: Auto-detect sales columns for product picks
|
||||
SYSTEM_VERSION = "V10.47"
|
||||
|
||||
# ==========================================
|
||||
# 🔒 SQL Injection 防護函數
|
||||
|
||||
@@ -66,27 +66,68 @@ def _has_daily_sales_snapshot(conn) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _daily_sales_columns(conn) -> Dict[str, str]:
|
||||
"""依正式匯入表實際欄位挑選可用欄名。"""
|
||||
from sqlalchemy import text
|
||||
|
||||
rows = conn.execute(text("""
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'daily_sales_snapshot'
|
||||
""")).fetchall()
|
||||
columns = {row[0] for row in rows}
|
||||
|
||||
def first_available(candidates):
|
||||
return next((col for col in candidates if col in columns), None)
|
||||
|
||||
return {
|
||||
"sku": first_available(["商品ID", "Product ID", "ID", "i_code", "Item Code"]),
|
||||
"date": first_available(["snapshot_date", "日期", "訂單日期", "交易日期", "Date"]),
|
||||
"revenue": first_available(["總業績", "銷售金額", "業績", "金額", "Amount", "Sales", "Total"]),
|
||||
"qty": first_available(["數量", "銷售數量", "銷量", "Qty", "Quantity"]),
|
||||
}
|
||||
|
||||
|
||||
def _quote_identifier(identifier: str) -> str:
|
||||
return '"' + identifier.replace('"', '""') + '"'
|
||||
|
||||
|
||||
def _fetch_candidates(conn, limit: int) -> List[Dict[str, Any]]:
|
||||
from sqlalchemy import text
|
||||
|
||||
sales_join = ""
|
||||
sales_select = "0 AS sales_7d, 0 AS sales_prev_7d, 0 AS qty_7d"
|
||||
sales_cols = {}
|
||||
if _has_daily_sales_snapshot(conn):
|
||||
sales_cols = _daily_sales_columns(conn)
|
||||
if not all([sales_cols.get("sku"), sales_cols.get("date"), sales_cols.get("revenue"), sales_cols.get("qty")]):
|
||||
sales_cols = {}
|
||||
|
||||
if sales_cols:
|
||||
sku_col = _quote_identifier(sales_cols["sku"])
|
||||
date_col = _quote_identifier(sales_cols["date"])
|
||||
revenue_col = _quote_identifier(sales_cols["revenue"])
|
||||
qty_col = _quote_identifier(sales_cols["qty"])
|
||||
sales_join = """
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
"商品ID" AS sku,
|
||||
SUM(CASE WHEN snapshot_date::date >= CURRENT_DATE - 7
|
||||
THEN COALESCE("銷售金額"::numeric, 0) ELSE 0 END) AS sales_7d,
|
||||
SUM(CASE WHEN snapshot_date::date >= CURRENT_DATE - 14
|
||||
AND snapshot_date::date < CURRENT_DATE - 7
|
||||
THEN COALESCE("銷售金額"::numeric, 0) ELSE 0 END) AS sales_prev_7d,
|
||||
SUM(CASE WHEN snapshot_date::date >= CURRENT_DATE - 7
|
||||
THEN COALESCE("數量"::numeric, 0) ELSE 0 END) AS qty_7d
|
||||
{sku_col} AS sku,
|
||||
SUM(CASE WHEN {date_col}::date >= CURRENT_DATE - 7
|
||||
THEN COALESCE({revenue_col}::numeric, 0) ELSE 0 END) AS sales_7d,
|
||||
SUM(CASE WHEN {date_col}::date >= CURRENT_DATE - 14
|
||||
AND {date_col}::date < CURRENT_DATE - 7
|
||||
THEN COALESCE({revenue_col}::numeric, 0) ELSE 0 END) AS sales_prev_7d,
|
||||
SUM(CASE WHEN {date_col}::date >= CURRENT_DATE - 7
|
||||
THEN COALESCE({qty_col}::numeric, 0) ELSE 0 END) AS qty_7d
|
||||
FROM daily_sales_snapshot
|
||||
GROUP BY "商品ID"
|
||||
GROUP BY {sku_col}
|
||||
) sales ON sales.sku = lm.sku
|
||||
"""
|
||||
""".format(
|
||||
sku_col=sku_col,
|
||||
date_col=date_col,
|
||||
revenue_col=revenue_col,
|
||||
qty_col=qty_col,
|
||||
)
|
||||
sales_select = """
|
||||
COALESCE(sales.sales_7d, 0) AS sales_7d,
|
||||
COALESCE(sales.sales_prev_7d, 0) AS sales_prev_7d,
|
||||
|
||||
@@ -148,7 +148,9 @@ def test_ai_product_pick_agent_uses_real_competitor_data_and_dashboard_action():
|
||||
assert "'product_pick'" in agent_source
|
||||
assert "PChomeProductPickAgent" in agent_source
|
||||
assert "PChome 價格優勢" in agent_source
|
||||
assert "snapshot_date::date" in agent_source
|
||||
assert "_daily_sales_columns" in agent_source
|
||||
assert '"總業績"' in agent_source
|
||||
assert "{date_col}::date" in agent_source
|
||||
assert "conn.rollback()" in agent_source
|
||||
|
||||
assert "@ai_bp.route('/api/ai/product-picks/generate', methods=['POST'])" in route_source
|
||||
|
||||
Reference in New Issue
Block a user