Skip to content

Publish Intelligent Advisory Site #40

Publish Intelligent Advisory Site

Publish Intelligent Advisory Site #40

name: Publish Intelligent Advisory Site
on:
workflow_dispatch:
inputs:
as_of:
description: "Report date. Defaults to current UTC date when empty."
required: false
type: string
site_url:
description: "Public site URL used in RSS links."
required: false
default: "https://quantstrategylab.github.io/QuantAdvisorResearch"
type: string
political_events_path:
description: "Path inside PoliticalEventTrackingResearch."
required: false
default: "data/live/political_events.csv"
type: string
political_watchlist_path:
description: "Path inside PoliticalEventTrackingResearch."
required: false
default: "data/live/political_watchlist.csv"
type: string
ai_signal_path:
description: "Path inside ResearchSignalContextPipelines."
required: false
default: "data/output/latest_signal.json"
type: string
theme_momentum_path:
description: "Optional path inside ResearchSignalContextPipelines. Empty disables theme momentum context."
required: false
default: "data/output/theme_momentum_snapshot.json"
type: string
market_confirmation_path:
description: "Optional market confirmation CSV path inside advisor repository. Empty disables this input."
required: false
default: ""
type: string
market_data_proxy_urls:
description: "Optional comma/newline-separated public proxy URLs for free market data fetches."
required: false
default: ""
type: string
market_data_proxy_pool_url:
description: "Optional public text URL returning one proxy per line for free market data fetches."
required: false
default: ""
type: string
schedule:
- cron: "0 13 * * 6"
permissions:
contents: read
pages: write
id-token: write
jobs:
build-site:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Checkout advisor repository
uses: actions/checkout@v6
with:
path: advisor
- name: Checkout political event repository
uses: actions/checkout@v6
with:
repository: QuantStrategyLab/PoliticalEventTrackingResearch
path: political-events
- name: Checkout signal context repository
uses: actions/checkout@v6
with:
repository: QuantStrategyLab/ResearchSignalContextPipelines
path: research-signal-context
- uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Install advisor package
working-directory: advisor
run: python -m pip install -e .
- name: Build report and static site
id: build_report
working-directory: advisor
env:
INPUT_AS_OF: ${{ github.event.inputs.as_of || '' }}
SITE_URL: ${{ github.event.inputs.site_url || 'https://quantstrategylab.github.io/QuantAdvisorResearch' }}
POLITICAL_EVENTS_PATH: ${{ github.event.inputs.political_events_path || 'data/live/political_events.csv' }}
POLITICAL_WATCHLIST_PATH: ${{ github.event.inputs.political_watchlist_path || 'data/live/political_watchlist.csv' }}
AI_SIGNAL_PATH: ${{ github.event.inputs.ai_signal_path || 'data/output/latest_signal.json' }}
THEME_MOMENTUM_PATH: ${{ github.event.inputs.theme_momentum_path || 'data/output/theme_momentum_snapshot.json' }}
MARKET_CONFIRMATION_PATH: ${{ github.event.inputs.market_confirmation_path || '' }}
MARKET_DATA_PROXY_URLS: ${{ github.event.inputs.market_data_proxy_urls || vars.MARKET_DATA_PROXY_URLS || '' }}
MARKET_DATA_PROXY_POOL_URL: ${{ github.event.inputs.market_data_proxy_pool_url || vars.MARKET_DATA_PROXY_POOL_URL || '' }}
run: |
set -euo pipefail
AS_OF="${INPUT_AS_OF:-$(date -u +%F)}"
mkdir -p data/output/published site
THEME_ARGS=()
RESOLVED_THEME_MOMENTUM=""
if [ -n "${THEME_MOMENTUM_PATH}" ] && [ -f "../research-signal-context/${THEME_MOMENTUM_PATH}" ]; then
RESOLVED_THEME_MOMENTUM="../research-signal-context/${THEME_MOMENTUM_PATH}"
THEME_ARGS=(--theme-momentum "${RESOLVED_THEME_MOMENTUM}")
fi
MARKET_ARGS=()
if [ -n "${MARKET_CONFIRMATION_PATH}" ] && [ -f "${MARKET_CONFIRMATION_PATH}" ]; then
MARKET_ARGS=(--market-confirmation "${MARKET_CONFIRMATION_PATH}")
else
GENERATED_MARKET_CONFIRMATION="data/output/published/market_confirmation_${AS_OF}.csv"
MARKET_BUILD_ARGS=(
--as-of "${AS_OF}"
--political-watchlist "../political-events/${POLITICAL_WATCHLIST_PATH}"
--ai-signal "../research-signal-context/${AI_SIGNAL_PATH}"
--output "${GENERATED_MARKET_CONFIRMATION}"
)
if [ -n "${RESOLVED_THEME_MOMENTUM}" ]; then
MARKET_BUILD_ARGS+=(--theme-momentum "${RESOLVED_THEME_MOMENTUM}")
fi
if [ -n "${MARKET_DATA_PROXY_URLS}" ]; then
MARKET_BUILD_ARGS+=(--proxy-urls "${MARKET_DATA_PROXY_URLS}")
fi
if [ -n "${MARKET_DATA_PROXY_POOL_URL}" ]; then
MARKET_BUILD_ARGS+=(--proxy-pool-url "${MARKET_DATA_PROXY_POOL_URL}")
fi
python scripts/build_market_confirmation.py "${MARKET_BUILD_ARGS[@]}"
MARKET_ARGS=(--market-confirmation "${GENERATED_MARKET_CONFIRMATION}")
fi
python scripts/build_advisory_report.py \
--as-of "${AS_OF}" \
--cadence weekly \
--political-events "../political-events/${POLITICAL_EVENTS_PATH}" \
--political-watchlist "../political-events/${POLITICAL_WATCHLIST_PATH}" \
--ai-signal "../research-signal-context/${AI_SIGNAL_PATH}" \
"${THEME_ARGS[@]}" \
"${MARKET_ARGS[@]}" \
--output-json "data/output/published/advisory_report_${AS_OF}.json" \
--output-md "data/output/published/advisory_report_${AS_OF}.md"
REPORT_ARGS=("data/output/published/advisory_report_${AS_OF}.json")
HISTORY_DIR="data/output/published/history"
mkdir -p "${HISTORY_DIR}"
HISTORY_INDEX="${HISTORY_DIR}/reports_index.json"
if curl -fsL "${SITE_URL%/}/reports_index.json" -o "${HISTORY_INDEX}"; then
python - "${SITE_URL}" "${HISTORY_INDEX}" "${HISTORY_DIR}" "${AS_OF}" <<'PY'
import json
import re
import sys
import urllib.parse
import urllib.request
from pathlib import Path
site_url, index_path, history_dir, current_as_of = sys.argv[1:]
history_dir_path = Path(history_dir)
index = json.loads(Path(index_path).read_text(encoding="utf-8"))
paths = []
seen_as_of = {current_as_of}
for item in index.get("reports", []):
as_of = str(item.get("as_of", "")).strip()
json_name = Path(str(item.get("json", ""))).name
if not as_of or as_of in seen_as_of:
continue
if not re.fullmatch(r"advisory_report_\d{4}-\d{2}-\d{2}\.json", json_name):
continue
seen_as_of.add(as_of)
url = f"{site_url.rstrip('/')}/{urllib.parse.quote(json_name)}"
destination = history_dir_path / json_name
try:
urllib.request.urlretrieve(url, destination)
payload = json.loads(destination.read_text(encoding="utf-8"))
except Exception as exc: # noqa: BLE001 - best-effort public archive recovery
print(f"Skipped historical report {json_name}: {exc}")
continue
if str(payload.get("as_of", "")) != as_of:
print(f"Skipped historical report {json_name}: as_of mismatch")
continue
paths.append(str(destination))
(history_dir_path / "report_paths.txt").write_text("\n".join(paths), encoding="utf-8")
print(f"Recovered {len(paths)} historical reports from published site.")
PY
if [ -s "${HISTORY_DIR}/report_paths.txt" ]; then
mapfile -t HISTORY_REPORTS < "${HISTORY_DIR}/report_paths.txt"
REPORT_ARGS+=("${HISTORY_REPORTS[@]}")
fi
else
echo "No existing reports_index.json found on published site; starting archive with current report."
fi
python scripts/publish_advisory_site.py \
--reports "${REPORT_ARGS[@]}" \
--output-dir site \
--site-url "${SITE_URL}" \
--feed-title "智慧投顾研究系统"
for REPORT_JSON in "${REPORT_ARGS[@]}"; do
cp "${REPORT_JSON}" site/
done
cp "data/output/published/advisory_report_${AS_OF}.md" site/
cp "data/output/published/advisory_report_${AS_OF}.json.manifest.json" site/
echo "as_of=${AS_OF}" >> "$GITHUB_OUTPUT"
echo "report_path=data/output/published/advisory_report_${AS_OF}.json" >> "$GITHUB_OUTPUT"
- name: Upload site artifact
uses: actions/upload-artifact@v7
with:
name: model-recommendations-site
path: advisor/site
if-no-files-found: error
- name: Configure Pages
uses: actions/configure-pages@v6
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: advisor/site
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
- name: Notify Telegram subscribers
if: ${{ success() }}
continue-on-error: true
working-directory: advisor
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
SITE_URL: ${{ github.event.inputs.site_url || 'https://quantstrategylab.github.io/QuantAdvisorResearch' }}
REPORT_PATH: ${{ steps.build_report.outputs.report_path }}
run: |
set -euo pipefail
python scripts/notify_advisory_telegram.py \
--report "${REPORT_PATH}" \
--site-url "${SITE_URL}"