deploy: push latest version to production
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ package-lock.json
|
||||
.env
|
||||
/ops/shared
|
||||
/ops/*.log
|
||||
*/__pycache__
|
||||
|
||||
26
docs/crawler-source-policy.md
Normal file
26
docs/crawler-source-policy.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# 爬蟲與資料來源政策
|
||||
|
||||
正式站目標是提供高時效、高可信度的世界盃投注研究資料,但所有來源都必須分級處理,避免把未授權資料當成主資料源。
|
||||
|
||||
## 來源分級
|
||||
|
||||
1. `active_api`:有授權 API、可機器讀取、可監控 quota 與錯誤率,例如 The Odds API。
|
||||
2. `official_reference`:官方賽程、公告、比分與規則來源,例如 FIFA 官方網站。
|
||||
3. `market_reference`:本地市場口徑核對來源,例如台灣運彩官方網站。
|
||||
4. `news_reference`:新聞與事件訊號來源,例如 Google 新聞 RSS、NewsAPI、路透社、BBC、ESPN。
|
||||
5. `manual_reference`:只能人工核對,不能自動高頻抓取。
|
||||
|
||||
## 台灣運彩使用原則
|
||||
|
||||
- `https://www.sportslottery.com.tw/` 可作為台灣市場投注語言、玩法名稱與盤口口徑參考。
|
||||
- 未完成條款與 robots 檢查前,不做高頻爬取。
|
||||
- 若未來要自動化導入,必須加入節流、快取、錯誤退避、User-Agent 標示與資料來源標記。
|
||||
- 台灣運彩資料不得覆蓋授權 odds API,只能作市場口徑交叉檢查與前台繁體中文術語校準。
|
||||
|
||||
## 首頁推薦資料優先序
|
||||
|
||||
1. 授權賠率 API 與官方賽程。
|
||||
2. 後端量化模型產生的勝率、期望值、模型優勢與倉位建議。
|
||||
3. 新聞與事件訊號作風險調整。
|
||||
4. 台灣運彩作台灣市場口徑參考。
|
||||
5. 若資料源不健康,首頁必須顯示資料延遲或暫停推薦,不得硬推單。
|
||||
29
docs/fifa-official-data-seed-2026-06-14.md
Normal file
29
docs/fifa-official-data-seed-2026-06-14.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# 2026 世界盃官方資料種子與 ingestion 邊界
|
||||
|
||||
更新時間:2026-06-14 Asia/Taipei
|
||||
|
||||
## 已落地
|
||||
|
||||
- 新增 `platform/backend/app/analytics/worldcup_seed.py`。
|
||||
- 以 upsert 方式建立 16 個 2026 世界盃主辦場館,加上 `待確認場館` fallback。
|
||||
- 每筆場館包含:FIFA 賽事期間場館名稱、城市、國家、海拔、時區。
|
||||
- production compose 新增 `fifa2026-seed` 一次性服務,`backend` 與 `odds-worker` 會等待 seed 成功後啟動。
|
||||
- `/analytics/source-health` 會回報:盤口筆數、賽事數、場館數、高海拔場館數、最新盤口時間、worker 狀態。
|
||||
|
||||
## 資料來源基準
|
||||
|
||||
- FIFA 官方 2026 世界盃場館頁:`https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/world-cup-2026-stadiums-fifa-soccer-football-mexico-usa-canada`
|
||||
- FIFA 官方 2026 世界盃賽程頁:`https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/articles/match-schedule-fixtures-results-teams-stadiums`
|
||||
- FIFA 官方賽程 PDF:`https://digitalhub.fifa.com/m/1be9ce37eb98fcc5/original/FWC26-Match-Schedule_English.pdf`
|
||||
|
||||
## 邊界
|
||||
|
||||
- 場館資料可以穩定 seed,因為官方 16 個 host city/stadium 結構已明確。
|
||||
- 完整賽程不在本次硬寫,後續應以官方 schedule PDF/API parser 或授權資料源 ingestion 產生,不手動填入不完整賽程。
|
||||
- 台灣運彩可作為台灣盤口參考源,但必須先確認 robots、條款與頻率限制,不能無差別高頻爬取。
|
||||
|
||||
## 下一個 P0
|
||||
|
||||
- 建立官方賽程 parser,把 104 場完整賽事寫入 `matches`。
|
||||
- 將 sportsbook 賽事與官方 schedule 做 team/time/venue reconciliation。
|
||||
- 補上場館別模型權重,特別是 Mexico City / Guadalajara 高海拔影響。
|
||||
67
docs/gemini-work-audit-2026-06-14.md
Normal file
67
docs/gemini-work-audit-2026-06-14.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Gemini 工作內容完整盤點與接續推進報告
|
||||
|
||||
盤點日期:2026-06-14(Asia/Taipei)
|
||||
正式站目標:https://2026fifa.wooo.work/
|
||||
|
||||
## 一、使用者目標
|
||||
|
||||
1. 全站內容與所有溝通都必須使用繁體中文。
|
||||
2. 2026 世界盃賽事期間,首頁每天都要能看到專業分析後的投注推薦。
|
||||
3. 推薦不能只是報牌,要包含模型勝率、市場隱含機率、期望值、模型優勢、信心分數、倉位建議與資料新鮮度。
|
||||
4. 賽程、賠率、新聞、場地、資金流等資訊必須盡可能即時更新。
|
||||
5. 正式環境以 `https://2026fifa.wooo.work/` 為準,不以本機頁面完成當作交付。
|
||||
|
||||
## 二、Gemini 已完成內容
|
||||
|
||||
### 平台骨架
|
||||
|
||||
- `platform/web`:Next.js 15 前端,包含首頁、賽事中心、每日作戰室、Kelly、回測、反向盤口、Proof of Yield、球員道具盤、投資組合等頁面。
|
||||
- `platform/backend`:FastAPI 後端,包含 `/analytics/*` API、WebSocket、Redis Pub/Sub、PostgreSQL/TimescaleDB 查詢。
|
||||
- `platform/alerts`:Redis 掃描與 Telegram 高價值投注警報 worker。
|
||||
- `platform/deploy`、`ops`:Docker、K3s、PM2、Nginx、healthcheck 與部署腳本。
|
||||
|
||||
### 量化與投注功能模組
|
||||
|
||||
- 期望值、Kelly、去水、Poisson、機器學習、反向盤口、裁判天候、球員道具盤、對沖、投資組合弱點、Proof of Yield。
|
||||
- 資料庫 schema 已涵蓋 `matches`、`odds_history`、`smart_money_flow`、`value_bet_recommendations`、affiliate/copy-bet 等延伸表。
|
||||
|
||||
### 文件與產品方向
|
||||
|
||||
- `docs/professional-market-architecture.md` 定義 Gemini 路線:資料分層、賠率矩陣、期望值偵測、Poisson、Monte Carlo、CLV。
|
||||
- `docs/professional-data-reference.md` 定義官方、賠率、新聞、統計、天氣來源台帳。
|
||||
|
||||
## 三、主要缺口
|
||||
|
||||
1. 首頁原本呼叫每日作戰室資料,但缺少 Next API 代理 route,導致高勝率推薦入口可能斷線。
|
||||
2. 首頁原本連到 `/schedule`,但沒有實際賽程頁。
|
||||
3. 文件提到新聞來源,但前台沒有新聞脈搏或新聞頁。
|
||||
4. Daily Card 推薦邏輯過度樣板化,缺少市場隱含機率、模型優勢、信心分數與資料檢查點。
|
||||
5. root `README.md` 仍描述舊 Express 緊急版,與目前 `platform/web`、`platform/backend` 主線不一致。
|
||||
6. 部分頁面仍是展示骨架或樣本資料,例如回測、深度投注連結、部分 proof-of-yield 流程。
|
||||
7. FIFA 官方賽程、The Odds API、生產資料庫、新聞權重與結算回寫還沒有完整閉環。
|
||||
|
||||
## 四、本次已推進
|
||||
|
||||
1. 新增每日作戰室 API 代理:`platform/web/app/api/analytics/daily-card/[targetDate]/route.ts`。
|
||||
2. 重做首頁為「世界盃即時投研主控台」,直接顯示資料健康、今日推薦、近期賽程與新聞脈搏。
|
||||
3. 新增 Google 新聞 RSS 備援 API:`platform/web/app/api/news/route.ts`。
|
||||
4. 新增新聞頁:`platform/web/app/news/page.tsx`。
|
||||
5. 新增完整賽程頁:`platform/web/app/schedule/page.tsx`。
|
||||
6. 強化推薦卡:顯示信心分數、風險類型、市場隱含機率、模型優勢、期望值、建議配置與資料檢查點。
|
||||
7. 強化 Daily Card 後端:加入保守勝平負機率、平局機率、正期望值過濾、模型優勢過濾、相關性折減與倉位上限。
|
||||
8. 將核心新增畫面內容改為繁體中文。
|
||||
|
||||
## 五、外部來源核對
|
||||
|
||||
- FIFA 官方賽程與比分入口:https://www.fifa.com/en/tournaments/mens/worldcup/canadamexicousa2026/scores-fixtures
|
||||
- The Odds API V4 文件:https://the-odds-api.com/liveapi/guides/v4/
|
||||
- The Odds API sport key 台帳:https://the-odds-api.com/sports-odds-data/sports-apis.html
|
||||
|
||||
## 六、下一步 P0
|
||||
|
||||
1. 正式站部署後確認首頁是否能看到每日推薦與資料健康。
|
||||
2. 把 FIFA 官方賽程正式灌入 `matches`,建立每日校準 job。
|
||||
3. 確認 The Odds API production key、`soccer_fifa_world_cup` sport key、regions、markets 與 quota。
|
||||
4. 建立 source registry platform 版,直接回傳來源健康、延遲、最後更新時間與錯誤原因。
|
||||
5. 把 Proof of Yield 從展示帳本推到真實推薦 id、closing odds、賽果結算與 CLV。
|
||||
6. 建立賽事期間高頻刷新:賽前 24 小時、賽前 3 小時、開賽後不同頻率的賠率與新聞刷新策略。
|
||||
77
docs/production-page-audit-2026-06-14.md
Normal file
77
docs/production-page-audit-2026-06-14.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# 正式環境頁面盤點與優化紀錄
|
||||
|
||||
盤點時間:2026-06-14 02:45 Asia/Taipei
|
||||
正式網址:https://2026fifa.wooo.work/
|
||||
|
||||
## 一、正式站狀態
|
||||
|
||||
所有主要頁面 HTTP 狀態皆為 200:
|
||||
|
||||
- `/`:首頁主控台
|
||||
- `/daily-card`:每日作戰室
|
||||
- `/schedule`:完整賽程表
|
||||
- `/news`:即時新聞情報
|
||||
- `/matches`:賽事中心
|
||||
- `/odds`:市場指數比較
|
||||
- `/sharp-money`:聰明錢追蹤
|
||||
- `/models`:量化模型庫
|
||||
- `/ml-edge`:機器學習優勢
|
||||
- `/match-conditions`:裁判/天候模型
|
||||
- `/rlm`:反向盤口雷達
|
||||
- `/proof-of-yield`:公開收益驗證
|
||||
- `/props`:球員道具盤
|
||||
- `/kelly`:凱利配置
|
||||
- `/backtesting`:策略回測
|
||||
- `/deep-bet`:一鍵配置
|
||||
- `/portfolio`:個人組合
|
||||
- `/paywall`:付費牆
|
||||
- `/offline`:離線頁
|
||||
|
||||
API 狀態:
|
||||
|
||||
- `/api/news`:200,Google 新聞 RSS 備援可用。
|
||||
- `/api/analytics/matches`:200,目前只有少量賽事資料。
|
||||
- `/api/analytics/daily-card/2026-06-14`:已由 500 修復為 200。
|
||||
|
||||
## 二、已完成優化
|
||||
|
||||
1. 正式站改跑平台版 Next.js + FastAPI + TimescaleDB + Redis,不再只依賴舊 Express PM2 腳本。
|
||||
2. 首頁改為「世界盃即時投研主控台」,直接顯示資料健康、今日推薦、近期賽程、新聞脈搏與專業模組入口。
|
||||
3. 新增 `/schedule` 頁,修復原本入口存在但頁面不存在的問題。
|
||||
4. 新增 `/news` 頁與 `/api/news`,以 Google 新聞 RSS 作無金鑰備援。
|
||||
5. 修復 `/api/analytics/daily-card/[date]` Next route handler 型別與後端 `generate_daily_card` import 問題。
|
||||
6. 移除 Daily Card 的假樣本賽事 fallback,資料不足時不硬推單。
|
||||
7. 補充台灣運彩 `https://www.sportslottery.com.tw/` 為台灣市場參考來源,僅作口徑核對,不作未授權高頻抓取。
|
||||
8. 前台主要新增內容改為繁體中文,並開始清理英文模型術語。
|
||||
|
||||
## 三、目前未達標風險
|
||||
|
||||
1. 每日推薦目前可能顯示 0 組,原因是正式賠率資料與 `odds_history` 還沒有穩定支撐每日 Daily Card。
|
||||
2. `/api/analytics/matches` 目前只有少量賽事,且場地仍顯示 Unknown,官方賽程與場地資料未完整灌入。
|
||||
3. 部分進階頁面仍是模型展示或手動輸入,不是全自動真資料看板。
|
||||
4. Proof of Yield 尚未與真實推薦 id、closing odds、賽果結算完整閉環。
|
||||
5. Next.js 15.0.3 build 時提示版本存在安全風險,需升級到修補版本。
|
||||
6. 後端 `xgboost` 會拉取大型 NCCL 依賴,導致部署慢且映像偏重。
|
||||
7. 多頁仍有 Next `themeColor` metadata warning,已開始調整但需後續完全清理。
|
||||
|
||||
## 四、下一步 P0
|
||||
|
||||
1. 建立真實賽事與賠率 ingestion:FIFA 官方賽程 + The Odds API `soccer_fifa_world_cup` + h2h/spreads/totals/btts。
|
||||
2. 把 `odds_history` 補齊主勝、平局、客勝與各 bookmaker 最新盤口,讓 Daily Card 有資料可算。
|
||||
3. 建立來源健康 API:每個來源最後更新時間、延遲、錯誤、資料筆數。
|
||||
4. 首頁推薦只在資料源健康時顯示;資料不足時顯示「暫停推薦」而不是假推薦。
|
||||
5. 將台灣運彩納入 `reference_only` crawler 規格,先做條款與頻率限制檢查。
|
||||
|
||||
## 2026-06-14 03:00 Asia/Taipei 正式環境覆核
|
||||
|
||||
正式網址:`https://2026fifa.wooo.work/`
|
||||
|
||||
已確認 HTTP 200:首頁、每日作戰室、完整賽程表、即時新聞情報、賽事中心、賠率、聰明錢、模型庫、ML 優勢、場地條件、RLM、公開收益、球員道具盤、凱利、回測、深度下注、投資組合、付費牆、離線頁。
|
||||
|
||||
API 覆核:
|
||||
|
||||
- `/api/news`:HTTP 200,回傳 12 則新聞項目。
|
||||
- `/api/analytics/matches`:HTTP 200,回傳 3 場賽事資料。
|
||||
- `/api/analytics/daily-card/2026-06-14`:HTTP 200,但 `matched_matches=0`、`safe_singles=0`、`high_risk_singles=0`。
|
||||
|
||||
目前判定:正式站已可用,但尚未達到最終商業目標。推薦引擎已避免在缺乏真實盤口與足夠賽事資料時硬產生投注建議;下一個 P0 必須補齊即時賠率、正式賽程、場地、台灣運彩參考盤與排程 ingestion,才會穩定產出每天可檢查的高信心候選。
|
||||
32
docs/professional-betting-market-playbook-2026-06-14.md
Normal file
32
docs/professional-betting-market-playbook-2026-06-14.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 專業投注玩法與推薦計算規則
|
||||
|
||||
更新時間:2026-06-14 Asia/Taipei
|
||||
|
||||
## 已支援的推薦玩法
|
||||
|
||||
| 類型 | 市場 | 勝率模型 | 推薦條件 |
|
||||
|---|---|---|---|
|
||||
| 單關 | 勝平負 | Poisson xG score matrix | 勝率、EV、edge、倉位上限均通過 |
|
||||
| 單關 | 大小球 2.5 | Poisson 總進球分布 | 正 EV 且市場隱含機率偏差明確 |
|
||||
| 單關 | 雙方進球 BTTS | 雙隊至少一球分布 | 正 EV 且雙方 xG 支撐 |
|
||||
| 小倉高賠 | 勝平負 / 大小球 / BTTS | 同上 | 更高 EV/edge 門檻,嚴格小倉位 |
|
||||
| 跨場串關 | 2 串 1 | 單關勝率相乘後乘 0.92 | 兩腿不同賽事,重新計算組合 EV |
|
||||
| 同場串關 | 同場不同市場 | 單關勝率相乘後乘 0.68 | 高 EV 門檻,小倉位上限 |
|
||||
|
||||
## 不硬推的玩法
|
||||
|
||||
以下玩法需要完整市場盤口線,不能只靠模型想像:
|
||||
|
||||
- 亞洲讓球:需要 handicap line,例如 -0.25、-0.5、+0.75。
|
||||
- 球隊進球數:需要 team total line。
|
||||
- 正確比分:需要 correct score odds。
|
||||
- 半場/全場:需要 HT/FT market odds。
|
||||
- 角球、牌數、球員道具盤:需要對應事件源與市場賠率。
|
||||
|
||||
## 專業守則
|
||||
|
||||
- 沒有盤口,不產生投注推薦。
|
||||
- 有模型勝率但沒有市場賠率,只能列入觀察,不列入可下注推薦。
|
||||
- 串關不得直接把單關勝率相乘,必須套用相關性折減。
|
||||
- 同場串關風險最高,預設只允許小倉位。
|
||||
- 所有推薦都必須顯示:勝率、目標賠率、市場隱含機率、edge、EV、建議單位與資料檢查。
|
||||
33
docs/realtime-scheduler-matrix-2026-06-14.md
Normal file
33
docs/realtime-scheduler-matrix-2026-06-14.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# 即時資料排程矩陣
|
||||
|
||||
更新時間:2026-06-14 Asia/Taipei
|
||||
|
||||
## Production workers
|
||||
|
||||
| Worker | 頻率 | 職責 | 狀態寫入 |
|
||||
|---|---:|---|---|
|
||||
| `fifa2026-odds-worker` | 300 秒 | 賠率、比分、比賽狀態、完賽結果同步;The Odds API + ESPN scoreboard 備援 | `ingestion:odds:last_run` |
|
||||
| `fifa2026-news-worker` | 900 秒 | 世界盃新聞 RSS 快照更新 | `news:worldcup:latest`, `ingestion:news:last_run` |
|
||||
| `fifa2026-fixtures-worker` | 21600 秒 | 完整賽程 fixtures JSON upsert,可用 `FIXTURES_JSON_URL` 切換官方或授權來源 | `ingestion:fixtures:last_run` |
|
||||
| `fifa2026-seed` | 部署時一次 | 場館、比分欄位、安全 upsert | Docker completed success |
|
||||
|
||||
## 前台讀取策略
|
||||
|
||||
- 首頁資料源健康卡讀 `/api/analytics/source-health`。
|
||||
- 新聞頁與首頁新聞讀 `/api/news`。
|
||||
- `/api/news` 優先讀 backend news snapshot;沒有快照才 fallback Google News RSS。
|
||||
- 賽事列表讀 `/api/analytics/matches`,包含繁中隊名、比分、狀態、場館。
|
||||
- 每日推薦讀 `/api/analytics/daily-card/{date}`,包含勝平負、大小球、雙方進球、串關/同場候選。
|
||||
|
||||
## 資料來源策略
|
||||
|
||||
- `FIXTURES_JSON_URL` 預設為 `https://www.thestatsapi.com/world-cup/data/fixtures.json`。
|
||||
- 若取得 FIFA 官方 API 或授權 sports data feed,直接替換 `FIXTURES_JSON_URL`,worker 不需改程式。
|
||||
- 台灣運彩 adapter 仍需確認 robots、條款與更新頻率限制後再接入。
|
||||
|
||||
## 還需要補強
|
||||
|
||||
- 台灣運彩合法參考盤 adapter。
|
||||
- live minute / possession / red card 事件源。
|
||||
- Worker failure alert:Telegram/Email 告警。
|
||||
- 資料延遲 SLA:首頁顯示下一次更新時間與 stale 警示。
|
||||
BIN
platform/backend/app/__pycache__/main.cpython-311.pyc
Normal file
BIN
platform/backend/app/__pycache__/main.cpython-311.pyc
Normal file
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,34 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const ANALYTICS_BACKEND = process.env.ANALYTICS_BACKEND_URL || 'http://127.0.0.1:8000';
|
||||
|
||||
type RouteContext = {
|
||||
params: Promise<{ date: string }>;
|
||||
};
|
||||
|
||||
export async function GET(_request: Request, { params }: RouteContext) {
|
||||
const { date } = await params;
|
||||
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
||||
return NextResponse.json({ message: '日期格式必須為 YYYY-MM-DD' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${ANALYTICS_BACKEND}/analytics/agent-daily-review/${encodeURIComponent(date)}`, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
return NextResponse.json({ message }, { status: response.status });
|
||||
}
|
||||
|
||||
const data = (await response.json()) as Record<string, unknown>;
|
||||
return NextResponse.json({ generated_at: new Date().toISOString(), ...data });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'NemoTron 反方稽核暫時無法連線';
|
||||
return NextResponse.json({ message }, { status: 502 });
|
||||
}
|
||||
}
|
||||
24
platform/web/app/api/analytics/agent-verification/route.ts
Normal file
24
platform/web/app/api/analytics/agent-verification/route.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const ANALYTICS_BACKEND = process.env.ANALYTICS_BACKEND_URL || 'http://127.0.0.1:8000';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const response = await fetch(`${ANALYTICS_BACKEND}/analytics/agent-verification`, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
return NextResponse.json({ message }, { status: response.status });
|
||||
}
|
||||
|
||||
const data = (await response.json()) as Record<string, unknown>;
|
||||
return NextResponse.json({ generated_at: new Date().toISOString(), ...data });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'AI 驗證服務暫時無法連線';
|
||||
return NextResponse.json({ message }, { status: 502 });
|
||||
}
|
||||
}
|
||||
43
platform/web/app/api/analytics/daily-card-calendar/route.ts
Normal file
43
platform/web/app/api/analytics/daily-card-calendar/route.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const ANALYTICS_BACKEND = process.env.ANALYTICS_BACKEND_URL || 'http://127.0.0.1:8000';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const startDate = url.searchParams.get('start_date') || '2026-06-11';
|
||||
const endDate = url.searchParams.get('end_date');
|
||||
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(startDate)) {
|
||||
return NextResponse.json({ message: '開始日期格式必須為 YYYY-MM-DD' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (endDate && !/^\d{4}-\d{2}-\d{2}$/.test(endDate)) {
|
||||
return NextResponse.json({ message: '結束日期格式必須為 YYYY-MM-DD' }, { status: 400 });
|
||||
}
|
||||
|
||||
const params = new URLSearchParams({ start_date: startDate });
|
||||
if (endDate) params.set('end_date', endDate);
|
||||
|
||||
try {
|
||||
const response = await fetch(`${ANALYTICS_BACKEND}/analytics/daily-card-calendar?${params.toString()}`, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
return NextResponse.json({ message }, { status: response.status });
|
||||
}
|
||||
|
||||
const data = (await response.json()) as Record<string, unknown>;
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : '日期推薦摘要服務暫時無法連線';
|
||||
return NextResponse.json({ message }, { status: 502 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const ANALYTICS_BACKEND = process.env.ANALYTICS_BACKEND_URL || 'http://127.0.0.1:8000';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const response = await fetch(`${ANALYTICS_BACKEND}/analytics/daily-card-calendar/status`, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
return NextResponse.json({ message }, { status: response.status });
|
||||
}
|
||||
|
||||
const data = (await response.json()) as Record<string, unknown>;
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : '日期推薦摘要快取狀態暫時無法讀取';
|
||||
return NextResponse.json({ message }, { status: 502 });
|
||||
}
|
||||
}
|
||||
24
platform/web/app/api/analytics/gemini-usage/route.ts
Normal file
24
platform/web/app/api/analytics/gemini-usage/route.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const ANALYTICS_BACKEND = process.env.ANALYTICS_BACKEND_URL || 'http://127.0.0.1:8000';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const response = await fetch(`${ANALYTICS_BACKEND}/analytics/gemini-usage`, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
return NextResponse.json({ message }, { status: response.status });
|
||||
}
|
||||
|
||||
const data = (await response.json()) as Record<string, unknown>;
|
||||
return NextResponse.json({ generated_at: new Date().toISOString(), ...data });
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Gemini 用量服務暫時無法連線';
|
||||
return NextResponse.json({ message }, { status: 502 });
|
||||
}
|
||||
}
|
||||
29
platform/web/app/api/analytics/market-coverage/route.ts
Normal file
29
platform/web/app/api/analytics/market-coverage/route.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
const ANALYTICS_BACKEND = process.env.ANALYTICS_BACKEND_URL || 'http://127.0.0.1:8000';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const url = new URL(request.url);
|
||||
const daysAhead = url.searchParams.get('days_ahead') || '2';
|
||||
|
||||
try {
|
||||
const response = await fetch(`${ANALYTICS_BACKEND}/analytics/market-coverage?days_ahead=${encodeURIComponent(daysAhead)}`, {
|
||||
method: 'GET',
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const message = await response.text();
|
||||
return NextResponse.json({ message }, { status: response.status });
|
||||
}
|
||||
|
||||
const data = (await response.json()) as Record<string, unknown>;
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : '盤口覆蓋率暫時無法連線';
|
||||
return NextResponse.json({ message }, { status: 502 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user