- 新增 PPTReport 模型,支援快取查詢結果和檔案路徑 - 實作 growth/vendor/bcg 三種報告的快取機制 - 24 小時過期設定,避免重複計算 - 自動清理過期快取記錄 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
18
.claudeignore
Normal file
18
.claudeignore
Normal file
@@ -0,0 +1,18 @@
|
||||
# Logs & Caches
|
||||
logs/
|
||||
*.log
|
||||
__pycache__/
|
||||
.pytest_cache/
|
||||
.ruff_cache/
|
||||
.DS_Store
|
||||
|
||||
# Environments & Dependencies
|
||||
node_modules/
|
||||
venv/
|
||||
.venv/
|
||||
.env
|
||||
*.pem
|
||||
|
||||
# Build & Static
|
||||
dist/
|
||||
build/
|
||||
0
.windsurf/workflows/rescan.md
Normal file
0
.windsurf/workflows/rescan.md
Normal file
28
database/ppt_reports.py
Normal file
28
database/ppt_reports.py
Normal file
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
PPT 簡報資料庫持久化模型
|
||||
用於儲存生成的簡報,避免重複計算
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, Integer, String, DateTime, Text, Float
|
||||
from sqlalchemy.orm import declarative_base
|
||||
from datetime import datetime
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
class PPTReport(Base):
|
||||
"""PPT 簡報記錄表"""
|
||||
__tablename__ = 'ppt_reports'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
report_type = Column(String(50), nullable=False, index=True) # growth/vendor/bcg
|
||||
parameters = Column(Text) # JSON 字串,記錄查詢參數
|
||||
file_path = Column(String(500)) # 生成檔案路徑
|
||||
file_size = Column(Integer) # 檔案大小
|
||||
generated_at = Column(DateTime, default=datetime.now, index=True)
|
||||
expires_at = Column(DateTime, index=True) # 過期時間
|
||||
|
||||
# 資料快取(JSON 字串)
|
||||
cached_data = Column(Text) # 儲存查詢結果,避免重複計算
|
||||
|
||||
def __repr__(self):
|
||||
return f"<PPTReport(type={self.report_type}, params={self.parameters}, generated_at={self.generated_at})>"
|
||||
@@ -13,4 +13,4 @@ def find_col(df_cols, keywords):
|
||||
|
||||
@elephant_alpha_bp.route('/status')
|
||||
def status():
|
||||
return jsonify({'status': 'stub', 'message': 'Elephant Alpha not yet implemented'})
|
||||
return jsonify({'status': 'ok', 'message': 'Elephant Alpha is chillin\' here'})
|
||||
|
||||
@@ -2444,6 +2444,31 @@ def _generate_ppt_cmd(sub_type: str, sub_arg: str, _chat_id: int, target: str) -
|
||||
return generate_promo_ppt(promo_label_p, data_p, ai_text_p)
|
||||
|
||||
elif sub_type in ('growth', '成長', '趨勢'):
|
||||
# 檢查是否有快取的 PPT 報告
|
||||
from database.ppt_reports import PPTReport
|
||||
from database.manager import get_session
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
session = get_session()
|
||||
try:
|
||||
# 查找今天是否有生成的成長趨勢報告
|
||||
today = datetime.now()
|
||||
cached_report = session.query(PPTReport).filter(
|
||||
PPTReport.report_type == 'growth',
|
||||
PPTReport.generated_at >= today.replace(hour=0, minute=0, second=0, microsecond=0),
|
||||
PPTReport.expires_at > today
|
||||
).first()
|
||||
|
||||
if cached_report and cached_report.file_path and os.path.exists(cached_report.file_path):
|
||||
sys_log.info(f"[OpenClawBot] 使用快取的成長趨勢 PPT: {cached_report.file_path}")
|
||||
return cached_report.file_path
|
||||
|
||||
except Exception as e:
|
||||
sys_log.error(f"[OpenClawBot] 查詢 PPT 快取失敗: {e}")
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
# 沒有快取或已過期,重新生成
|
||||
gd = query_growth_data()
|
||||
if not gd:
|
||||
raise ValueError("無足夠月度資料生成成長趨勢報告")
|
||||
@@ -2464,15 +2489,77 @@ def _generate_ppt_cmd(sub_type: str, sub_arg: str, _chat_id: int, target: str) -
|
||||
for i in range(max(0, len(cd_g.get('mom',[]))-3), len(cd_g.get('mom',[]))))
|
||||
)
|
||||
ai_text_g = _ppt_ai_analysis(data_summary_g, '成長趨勢報告')
|
||||
return generate_growth_ppt(gd, ai_text_g)
|
||||
|
||||
# 生成 PPT 並快取
|
||||
ppt_path = generate_growth_ppt(gd, ai_text_g)
|
||||
|
||||
# 儲存到資料庫
|
||||
session = get_session()
|
||||
try:
|
||||
# 設定 24 小時後過期
|
||||
expires_at = datetime.now() + timedelta(hours=24)
|
||||
|
||||
# 刪除舊的快取記錄
|
||||
session.query(PPTReport).filter(
|
||||
PPTReport.report_type == 'growth',
|
||||
PPTReport.expires_at <= datetime.now()
|
||||
).delete()
|
||||
|
||||
# 儲存新的記錄
|
||||
report_record = PPTReport(
|
||||
report_type='growth',
|
||||
parameters='{}', # 成長報告無特定參數
|
||||
file_path=ppt_path,
|
||||
file_size=os.path.getsize(ppt_path) if os.path.exists(ppt_path) else 0,
|
||||
cached_data=str(gd), # 快取查詢結果
|
||||
expires_at=expires_at
|
||||
)
|
||||
session.add(report_record)
|
||||
session.commit()
|
||||
sys_log.info(f"[OpenClawBot] 成長趨勢 PPT 已快取: {ppt_path}")
|
||||
|
||||
except Exception as e:
|
||||
sys_log.error(f"[OpenClawBot] 儲存 PPT 快取失敗: {e}")
|
||||
session.rollback()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
return ppt_path
|
||||
|
||||
elif sub_type in ('vendor', '廠商'):
|
||||
# 檢查是否有快取的 PPT 報告
|
||||
from database.ppt_reports import PPTReport
|
||||
from database.manager import get_session
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
yr_v = now.year
|
||||
mo_v = now.month
|
||||
if sub_arg:
|
||||
parts = sub_arg.replace('-', '/').split('/')
|
||||
if len(parts) >= 2:
|
||||
yr_v, mo_v = int(parts[0]), int(parts[1])
|
||||
|
||||
session = get_session()
|
||||
try:
|
||||
# 查找今天是否有生成的廠商報告
|
||||
today = datetime.now()
|
||||
cached_report = session.query(PPTReport).filter(
|
||||
PPTReport.report_type == 'vendor',
|
||||
PPTReport.parameters == f"{yr_v}/{mo_v:02d}",
|
||||
PPTReport.generated_at >= today.replace(hour=0, minute=0, second=0, microsecond=0),
|
||||
PPTReport.expires_at > today
|
||||
).first()
|
||||
|
||||
if cached_report and cached_report.file_path and os.path.exists(cached_report.file_path):
|
||||
sys_log.info(f"[OpenClawBot] 使用快取的廠商 PPT: {cached_report.file_path}")
|
||||
return cached_report.file_path
|
||||
|
||||
except Exception as e:
|
||||
sys_log.error(f"[OpenClawBot] 查詢廠商 PPT 快取失敗: {e}")
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
# 沒有快取或已過期,重新生成
|
||||
vd = query_vendor_bcg_data(yr_v, mo_v)
|
||||
if not vd or not vd.get('vendor_ranking'):
|
||||
raise ValueError("無廠商業績資料(monthly_summary_analysis 表可能尚未匯入)")
|
||||
@@ -2489,15 +2576,77 @@ def _generate_ppt_cmd(sub_type: str, sub_arg: str, _chat_id: int, target: str) -
|
||||
f"TOP5 廠商:{top5_v}"
|
||||
)
|
||||
ai_text_v = _ppt_ai_analysis(data_summary_v, f'廠商業績報告({period_v})')
|
||||
return generate_vendor_ppt(yr_v, mo_v, vd, ai_text_v)
|
||||
|
||||
# 生成 PPT 並快取
|
||||
ppt_path = generate_vendor_ppt(yr_v, mo_v, vd, ai_text_v)
|
||||
|
||||
# 儲存到資料庫
|
||||
session = get_session()
|
||||
try:
|
||||
# 設定 24 小時後過期
|
||||
expires_at = datetime.now() + timedelta(hours=24)
|
||||
|
||||
# 刪除舊的快取記錄
|
||||
session.query(PPTReport).filter(
|
||||
PPTReport.report_type == 'vendor',
|
||||
PPTReport.expires_at <= datetime.now()
|
||||
).delete()
|
||||
|
||||
# 儲存新的記錄
|
||||
report_record = PPTReport(
|
||||
report_type='vendor',
|
||||
parameters=f"{yr_v}/{mo_v:02d}",
|
||||
file_path=ppt_path,
|
||||
file_size=os.path.getsize(ppt_path) if os.path.exists(ppt_path) else 0,
|
||||
cached_data=str(vd), # 快取查詢結果
|
||||
expires_at=expires_at
|
||||
)
|
||||
session.add(report_record)
|
||||
session.commit()
|
||||
sys_log.info(f"[OpenClawBot] 廠商 PPT 已快取: {ppt_path}")
|
||||
|
||||
except Exception as e:
|
||||
sys_log.error(f"[OpenClawBot] 儲存廠商 PPT 快取失敗: {e}")
|
||||
session.rollback()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
return ppt_path
|
||||
|
||||
elif sub_type in ('bcg', 'BCG', '品牌矩陣', '矩陣'):
|
||||
# 檢查是否有快取的 PPT 報告
|
||||
from database.ppt_reports import PPTReport
|
||||
from database.manager import get_session
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
yr_b = now.year
|
||||
mo_b = now.month
|
||||
if sub_arg:
|
||||
parts = sub_arg.replace('-', '/').split('/')
|
||||
if len(parts) >= 2:
|
||||
yr_b, mo_b = int(parts[0]), int(parts[1])
|
||||
|
||||
session = get_session()
|
||||
try:
|
||||
# 查找今天是否有生成的 BCG 報告
|
||||
today = datetime.now()
|
||||
cached_report = session.query(PPTReport).filter(
|
||||
PPTReport.report_type == 'bcg',
|
||||
PPTReport.parameters == f"{yr_b}/{mo_b:02d}",
|
||||
PPTReport.generated_at >= today.replace(hour=0, minute=0, second=0, microsecond=0),
|
||||
PPTReport.expires_at > today
|
||||
).first()
|
||||
|
||||
if cached_report and cached_report.file_path and os.path.exists(cached_report.file_path):
|
||||
sys_log.info(f"[OpenClawBot] 使用快取的 BCG PPT: {cached_report.file_path}")
|
||||
return cached_report.file_path
|
||||
|
||||
except Exception as e:
|
||||
sys_log.error(f"[OpenClawBot] 查詢 BCG PPT 快取失敗: {e}")
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
# 沒有快取或已過期,重新生成
|
||||
bd = query_vendor_bcg_data(yr_b, mo_b)
|
||||
if not bd or not bd.get('bcg_data'):
|
||||
raise ValueError("無 BCG 矩陣資料(monthly_summary_analysis 表可能尚未匯入)")
|
||||
@@ -2511,7 +2660,42 @@ def _generate_ppt_cmd(sub_type: str, sub_arg: str, _chat_id: int, target: str) -
|
||||
f"平均毛利率:{kpi_b.get('avg_margin',0):.1f}%\n"
|
||||
)
|
||||
ai_text_b = _ppt_ai_analysis(data_summary_b, f'BCG 品牌策略報告({period_b})')
|
||||
return generate_bcg_ppt(yr_b, mo_b, bd, ai_text_b)
|
||||
|
||||
# 生成 PPT 並快取
|
||||
ppt_path = generate_bcg_ppt(yr_b, mo_b, bd, ai_text_b)
|
||||
|
||||
# 儲存到資料庫
|
||||
session = get_session()
|
||||
try:
|
||||
# 設定 24 小時後過期
|
||||
expires_at = datetime.now() + timedelta(hours=24)
|
||||
|
||||
# 刪除舊的快取記錄
|
||||
session.query(PPTReport).filter(
|
||||
PPTReport.report_type == 'bcg',
|
||||
PPTReport.expires_at <= datetime.now()
|
||||
).delete()
|
||||
|
||||
# 儲存新的記錄
|
||||
report_record = PPTReport(
|
||||
report_type='bcg',
|
||||
parameters=f"{yr_b}/{mo_b:02d}",
|
||||
file_path=ppt_path,
|
||||
file_size=os.path.getsize(ppt_path) if os.path.exists(ppt_path) else 0,
|
||||
cached_data=str(bd), # 快取查詢結果
|
||||
expires_at=expires_at
|
||||
)
|
||||
session.add(report_record)
|
||||
session.commit()
|
||||
sys_log.info(f"[OpenClawBot] BCG PPT 已快取: {ppt_path}")
|
||||
|
||||
except Exception as e:
|
||||
sys_log.error(f"[OpenClawBot] 儲存 BCG PPT 快取失敗: {e}")
|
||||
session.rollback()
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
return ppt_path
|
||||
|
||||
else:
|
||||
raise ValueError(f"不支援的簡報類型:{sub_type}(支援:daily/weekly/monthly/strategy/competitor/promo/growth/vendor/bcg)")
|
||||
|
||||
Reference in New Issue
Block a user