V10.514 新增 Webcrumbs host data API
All checks were successful
CD Pipeline / deploy (push) Successful in 1m10s
All checks were successful
CD Pipeline / deploy (push) Successful in 1m10s
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
================================================================================
|
||||
|
||||
【已完成】
|
||||
- V10.514 新增 Webcrumbs MOMO/PChome host data read-only API:`/api/webcrumbs/marketplace-host-data` 回傳與 `/webcrumbs` inline seed 相同的登入後 JSON contract,提供 plugin / QA / 其他專案 proxy 驗證;API boundary 明確標示不寫 DB、不呼叫 LLM、不抓外站,只允許 exact / total_price / price_alert_exact 價差摘要。
|
||||
- V10.513 外部工具診斷頁 payload 模組化:新增 `services/external_tool_payload_service.py`,把 Metabase/Grist/Webcrumbs 的診斷 payload 與 Webcrumbs host data 組裝移出 `routes/system_public_routes.py`,讓 route 回到 HTTP glue,`system_public_routes.py` 從 600+ 行降至 500 行內。
|
||||
- V10.512 Webcrumbs live plugin 接上 MOMO/PChome 只讀 host data:新增 `services/webcrumbs_host_data_service.py`,復用 `competitor_intel_repository.fetch_top_competitor_risks()` / coverage,把 exact / total_price / price_alert_exact 價差摘要轉成 `StockPlatformSharedUI.marketSnapshot` / `aiCandidate`;不呼叫 LLM、不抓外站、不寫 DB,無風險或失敗時仍輸出安全空狀態。
|
||||
- V10.511 Webcrumbs live plugin 補 host data 安全空狀態:`/webcrumbs` 會注入 `StockPlatformSharedUI.marketSnapshot` / `aiCandidate` 的診斷空資料,避免 plugin fallback demo 數字被誤認成真實市場或 AI 建議。
|
||||
|
||||
@@ -402,7 +402,7 @@ YOUTUBE_API_KEY = os.getenv('YOUTUBE_API_KEY', '')
|
||||
# ==========================================
|
||||
# 系統版本與路徑
|
||||
# ==========================================
|
||||
SYSTEM_VERSION = "V10.513"
|
||||
SYSTEM_VERSION = "V10.514"
|
||||
LOG_FILE_PATH = os.path.join(BASE_DIR, 'logs/system.log')
|
||||
public_url = PUBLIC_URL # 用於模板顯示
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ style.css
|
||||
|
||||
- `/webcrumbs` 顯示 runtime URL、版本與 plugin base。
|
||||
- `/webcrumbs` 會嵌入同源 `/webcrumbs-assets/plugins/...` 的 live plugin preview,並由 momo-pro 注入只讀 MOMO/PChome exact 價差摘要;若資料源不可用或無風險候選,改注入診斷空狀態,避免 plugin fallback demo 數字在正式頁面被誤認成真實市場或 AI 建議。
|
||||
- `/api/webcrumbs/marketplace-host-data` 會回傳同一份登入後只讀 host data contract,供 plugin / QA / 其他專案 proxy 驗證;boundary 必須標示 `writes_database=false`、`calls_llm=false`、`fetches_external=false`。
|
||||
- `/webcrumbs-assets/loader/webcrumbs-compatible-loader.js` 回 200 且 content type 是 JavaScript。
|
||||
- `ewoooc_base.html` 在 `WEBCRUMBS_ENABLED=true` 且 runtime URL 有效時輸出 `<script data-webcrumbs-runtime=...>`。
|
||||
- 任一試點頁嵌入 plugin 後,瀏覽器 console 不應有 `MISSING_URI`、`STYLE_LOAD_ERROR`、`SCRIPT_LOAD_ERROR`。
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
## 📅 詳細更新日誌 (考古存檔)
|
||||
|
||||
### 2026-05-31:Webcrumbs 共用 UI Runtime 與市場情報 writer approval
|
||||
- **V10.514 Webcrumbs host data read-only API**: 新增登入後 `/api/webcrumbs/marketplace-host-data`,回傳與 `/webcrumbs` inline seed 相同的 MOMO/PChome exact 價差 host data contract,供 plugin、QA 與其他專案 proxy 驗證;API boundary 明確標示 `writes_database=false`、`calls_llm=false`、`fetches_external=false`,且只允許 exact / total_price / price_alert_exact 摘要。
|
||||
- **V10.513 外部工具診斷 payload 模組化**: 新增 `services/external_tool_payload_service.py`,把 Metabase/Grist/Webcrumbs 診斷頁 payload 與 Webcrumbs host data 組裝從 `routes/system_public_routes.py` 移出;route 保持 HTTP glue 與 asset proxy,避免 Webcrumbs live plugin 與比價 host data 持續把公開系統 route 撐成大檔。
|
||||
- **V10.512 Webcrumbs MOMO/PChome host data**: 新增 `services/webcrumbs_host_data_service.py`,讓 `/webcrumbs` live plugin preview 復用 `competitor_intel_repository.fetch_top_competitor_risks()` 與 coverage,將 exact / total_price / price_alert_exact 的只讀價差摘要轉成 `StockPlatformSharedUI.marketSnapshot` / `aiCandidate`;不呼叫 LLM、不抓外站、不寫 DB,無風險或讀取失敗時仍輸出安全空狀態。
|
||||
- **V10.511 Webcrumbs host data 安全空狀態**: `/webcrumbs` 會注入 `StockPlatformSharedUI.marketSnapshot` / `aiCandidate` 的診斷空資料,避免 Shared UI Hub plugin 因資料源未接入而 fallback 顯示假市場數字、假 AI 候選或假信心分數。
|
||||
|
||||
@@ -12,7 +12,7 @@ import zipfile
|
||||
from datetime import datetime, timezone, timedelta
|
||||
from urllib.parse import quote
|
||||
|
||||
from flask import Blueprint, Response, jsonify, render_template, send_from_directory, url_for
|
||||
from flask import Blueprint, Response, jsonify, render_template, request, send_from_directory, url_for
|
||||
import requests
|
||||
from sqlalchemy import text
|
||||
|
||||
@@ -26,7 +26,7 @@ from config import (
|
||||
)
|
||||
from database.manager import DatabaseManager
|
||||
from database.models import Product, PriceRecord
|
||||
from services.external_tool_payload_service import build_external_tool_payload
|
||||
from services.external_tool_payload_service import build_external_tool_payload, build_webcrumbs_seed_data
|
||||
from services.json_storage import load_categories
|
||||
from services.logger_manager import SystemLogger
|
||||
from utils.security import safe_join
|
||||
@@ -108,6 +108,27 @@ def webcrumbs_status():
|
||||
)
|
||||
|
||||
|
||||
@system_public_bp.route('/api/webcrumbs/marketplace-host-data')
|
||||
@login_required
|
||||
def webcrumbs_marketplace_host_data_api():
|
||||
"""Read-only host data contract for Webcrumbs shared-ui plugins."""
|
||||
limit = request.args.get('limit', 5, type=int) or 5
|
||||
limit = max(1, min(limit, 8))
|
||||
payload = build_webcrumbs_seed_data(limit=limit)
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'data': payload,
|
||||
'metadata': payload.get('metadata') or {},
|
||||
'boundary': {
|
||||
'auth_required': True,
|
||||
'writes_database': False,
|
||||
'calls_llm': False,
|
||||
'fetches_external': False,
|
||||
'allowed_match_contract': 'exact/total_price/price_alert_exact',
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
def _normalize_webcrumbs_asset_path(asset_path):
|
||||
candidate = (asset_path or '').strip().replace('\\', '/')
|
||||
normalized = posixpath.normpath(candidate).lstrip('/')
|
||||
|
||||
@@ -62,9 +62,10 @@ def _is_blocked_external_url(configured_url):
|
||||
return parsed.scheme in {"http", "https"} and parsed.hostname != "mo.wooo.work"
|
||||
|
||||
|
||||
def _webcrumbs_seed_data():
|
||||
def build_webcrumbs_seed_data(limit: int = 5):
|
||||
"""Build safe Webcrumbs host data for page seed and read-only API."""
|
||||
try:
|
||||
return build_webcrumbs_marketplace_host_data(limit=5)
|
||||
return build_webcrumbs_marketplace_host_data(limit=limit)
|
||||
except Exception as exc:
|
||||
sys_log.warning(f"[Webcrumbs] host data build skipped: {exc}")
|
||||
return {
|
||||
@@ -170,7 +171,7 @@ def _webcrumbs_payload():
|
||||
],
|
||||
"plugin_previews": plugin_previews,
|
||||
"plugin_preview_uris": [preview["uri"] for preview in plugin_previews],
|
||||
"plugin_seed_data": _webcrumbs_seed_data(),
|
||||
"plugin_seed_data": build_webcrumbs_seed_data(limit=5),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -39,9 +39,14 @@ def test_external_tool_bridge_pages_are_diagnostic_not_blank():
|
||||
assert "external-tool-diagnostics" in template
|
||||
assert "external-tool-action-grid" in template
|
||||
assert "def webcrumbs_status()" in route_source
|
||||
assert "@system_public_bp.route('/api/webcrumbs/marketplace-host-data')" in route_source
|
||||
assert "def webcrumbs_marketplace_host_data_api()" in route_source
|
||||
assert "build_webcrumbs_seed_data(limit=limit)" in route_source
|
||||
assert "'allowed_match_contract': 'exact/total_price/price_alert_exact'" in route_source
|
||||
assert "def webcrumbs_asset_proxy(asset_path)" in route_source
|
||||
assert "WEBCRUMBS_ASSET_ALLOWED_PREFIXES" in route_source
|
||||
assert "Webcrumbs 共用 UI Runtime" in service_source
|
||||
assert "def build_webcrumbs_seed_data(limit: int = 5)" in service_source
|
||||
assert "build_webcrumbs_marketplace_host_data" in service_source
|
||||
assert "plugin_previews" in service_source
|
||||
assert "finance.market-ticker-strip/0.1.0" in service_source
|
||||
|
||||
@@ -65,3 +65,19 @@ def test_webcrumbs_host_data_uses_empty_state_without_risks(monkeypatch):
|
||||
assert payload["marketSnapshot"][0]["freshness_status"] == "no_current_exact_risk"
|
||||
assert payload["aiCandidate"]["release_status"] == "blocked"
|
||||
assert "非同款、單位價或變體候選" in payload["aiCandidate"]["thesis"]
|
||||
|
||||
|
||||
def test_webcrumbs_seed_data_fallback_does_not_expose_demo_values(monkeypatch):
|
||||
from services import external_tool_payload_service as payload_service
|
||||
|
||||
def boom(limit):
|
||||
raise RuntimeError("db unavailable")
|
||||
|
||||
monkeypatch.setattr(payload_service, "build_webcrumbs_marketplace_host_data", boom)
|
||||
|
||||
payload = payload_service.build_webcrumbs_seed_data(limit=5)
|
||||
|
||||
assert payload["marketSnapshot"][0]["freshness_status"] == "diagnostic_unavailable"
|
||||
assert payload["aiCandidate"]["release_status"] == "blocked"
|
||||
assert "fallback demo" in payload["aiCandidate"]["thesis"]
|
||||
assert "TAIEX" not in str(payload)
|
||||
|
||||
Reference in New Issue
Block a user