#!/usr/bin/env bash
# 货代商机 Watcher v3 (v23新增, v25修复, v27: ImportYeti, v3: 客户画像)
# 每天 08:00/14:00/20:00 HKT 由系统crontab触发
# 调试记录：#84(信号源切换), #91(--session-id修复), 第31章(脚本设计宪法)
# V2变更：⭐⭐⭐⭐+ 条目自动附加 ImportYeti 企业查询链接
# V3变更：三层情报漏斗 — 信号捕获→ImportYeti深挖→客户画像生成
# cron 环境 PATH 极简，必须显式声明（规则 #13）
export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:$PATH"
set -eo pipefail

# V37.9.57: 公共反幻觉守卫 LEVEL_2_STANDARD (MR-8 single-source-of-truth)
# FAIL-OPEN: hallucination_guards 模块缺失 → 空字符串, 不阻塞 prompt 主流程
HG_GUARD_TEXT=$(python3 -c "
import sys, os
sys.path.insert(0, os.path.expanduser('~'))
sys.path.insert(0, '$(cd \"$(dirname \"$0\")\" && pwd)')
try:
    import hallucination_guards as hg
    print(hg.get_guard('LEVEL_2_STANDARD'))
except Exception:
    print('')
" 2>/dev/null)
export HG_GUARD_TEXT

# 防重叠执行（mkdir 原子锁，macOS 兼容）
LOCK="/tmp/freight_watcher.lockdir"
mkdir "$LOCK" 2>/dev/null || { echo "[freight] Already running, skip"; exit 0; }
trap 'rmdir "$LOCK" 2>/dev/null' EXIT

# ── --test 模式：跳过 RSS 抓取和 LLM 分析，直接从 Step 9 (ImportYeti) 开始 ──
TEST_MODE=0
if [ "${1:-}" = "--test" ]; then
    TEST_MODE=1
    echo "[freight] ⚡ 测试模式：跳过 RSS + LLM，直接执行 ImportYeti 深挖"
fi

OPENCLAW="${OPENCLAW:-/opt/homebrew/bin/openclaw}"
ROOT="${HOME}/.openclaw"
JOB="$ROOT/jobs/freight_watcher"
CACHE="$JOB/cache"
KB_SRC="${KB_BASE:-$HOME/.kb}/sources/freight_daily.md"
KB_WRITE_SCRIPT="${KB_WRITE_SCRIPT:-$HOME/kb_write.sh}"
KB_INBOX="${KB_BASE:-$HOME/.kb}/inbox.md"
TO="${OPENCLAW_PHONE:-+85200000000}"
LLM_RAW="$CACHE/llm_raw_last.txt"
TS="$(TZ=Asia/Hong_Kong date '+%Y-%m-%d %H:%M:%S')"
STATUS_FILE="$CACHE/last_run.json"

log() { echo "[$TS] freight: $1" >&2; }

mkdir -p "$CACHE" "${KB_BASE:-$HOME/.kb}/sources"
test -f "$KB_SRC"   || echo "# 货代商机 Watcher" > "$KB_SRC"
test -f "$KB_INBOX" || echo "# INBOX" > "$KB_INBOX"

DAY="$(TZ=Asia/Hong_Kong date '+%Y-%m-%d %H:%M')"
NEW_FILE="$CACHE/freight_new.jsonl"

# 测试模式跳过 RSS（Step 1-7），直接跳到 Step 8
if [ "$TEST_MODE" -eq 1 ]; then
    HIGH_STARS="$CACHE/high_star_companies.txt"
    if [ ! -s "$HIGH_STARS" ]; then
        echo "[freight] ⚠️ 测试模式需要 $HIGH_STARS 有内容"
        echo "[freight] 用法: echo 'Volkswagen China' > $HIGH_STARS && bash $0 --test"
        exit 1
    fi
    COMPANY_COUNT=$(wc -l < "$HIGH_STARS" | tr -d ' ')
    echo "[freight] 测试模式：从缓存读取 ${COMPANY_COUNT} 个企业"
    # 测试模式下设置 MSG_FILE（Step 10 画像生成需要引用）
    MSG_FILE="$CACHE/system_message_freight.txt"
    test -f "$MSG_FILE" || echo "(测试模式 — 无新闻信号数据)" > "$MSG_FILE"
fi

: > "$NEW_FILE"

if [ "$TEST_MODE" -eq 0 ]; then
# ── 1. 抓取多源RSS + Google News ────────────────────────────────────────
if ! python3 - "$KB_INBOX" "$NEW_FILE" << 'PYEOF'
import sys, json, re, urllib.request, xml.etree.ElementTree as ET
from datetime import datetime, timezone, timedelta

INBOX_FILE = sys.argv[1]
OUT_FILE   = sys.argv[2]

try:
    with open(INBOX_FILE) as f:
        inbox = f.read()
except OSError:
    inbox = ""

# 关键词过滤
FREIGHT_KW   = ["freight","forwarder","logistics","shipping","cargo","import","export",
                 "supply chain","ocean freight","air freight","customs","tariff"]
EXPANSION_KW = ["expand","acquisition","new facility","warehouse","distribution center",
                 "manufacturing","production","sourcing china","china supplier"]
# V37.9.33: 全球航运经济晴雨表关键词集 — 用户论点 "货代是 1-3 个月领先经济指标"
# Tier 1/2 权威源不直连 (反爬 + GeoIP 限制), 通过 Google News RSS 精确搜索 indexed 内容
INDEX_KW     = ["index","rate","spot rate","container rate","freight rate","shipping cost",
                 "scfi","ccfi","fbx","wci","bdi","tac","baltic","drewry","freightos",
                 "运价","指数","集装箱","船运"]
CARRIER_KW   = ["maersk","cma cgm","msc","hapag-lloyd","cosco","evergreen","yang ming",
                 "one network","hmm","blank sailing","capacity","alliance","vessel",
                 "船公司","班轮","运力","空班"]
PORT_KW      = ["port of","shanghai port","ningbo","los angeles port","long beach",
                 "rotterdam","singapore port","hamburg","antwerp","throughput",
                 "congestion","teu","container terminal",
                 "港口","吞吐量","拥堵","集装箱码头"]
CUSTOMS_KW   = ["customs","exports","imports","trade data","trade balance",
                 "trade volume","gtas","fts900","pmi",
                 "海关","进出口","贸易数据","贸易顺差","贸易逆差"]

SOURCES = [
    # === Tier 3: 行业新闻 RSS (V25-V37 已有) =================================
    ("https://www.freightwaves.com/news/feed",   FREIGHT_KW),
    ("https://theloadstar.com/feed/",             FREIGHT_KW),
    ("https://www.aircargonews.net/feed/",        FREIGHT_KW),
    ("https://www.dcvelocity.com/rss/articles",   FREIGHT_KW),
    ("https://www.chinadaily.com.cn/rss/bizchina_rss.xml", EXPANSION_KW),
    ("https://www.scmp.com/rss/91/feed",          EXPANSION_KW),
    ("https://www.prnewswire.com/rss/news-releases-list.rss", EXPANSION_KW),
    # Google News — 原有 (V3-V37 已有)
    ("https://news.google.com/rss/search?q=freight+forwarder+china&hl=en-US&gl=US&ceid=US:en", FREIGHT_KW),
    ("https://news.google.com/rss/search?q=importing+from+china+logistics&hl=en-US&gl=US&ceid=US:en", FREIGHT_KW),
    ("https://news.google.com/rss/search?q=supply+chain+china+expansion&hl=en-US&gl=US&ceid=US:en", EXPANSION_KW),
    # V3新增 — 聚焦真实货运需求信号
    ("https://news.google.com/rss/search?q=%22looking+for+freight+forwarder%22+OR+%22logistics+partner%22+OR+%22shipping+partner%22&hl=en-US&gl=US&ceid=US:en", FREIGHT_KW),
    ("https://news.google.com/rss/search?q=%22new+warehouse%22+OR+%22fulfillment+center%22+china&hl=en-US&gl=US&ceid=US:en", EXPANSION_KW),
    ("https://news.google.com/rss/search?q=%22FBA%22+%22freight+forwarder%22+OR+%22ocean+freight%22+rate&hl=en-US&gl=US&ceid=US:en", FREIGHT_KW),

    # === V37.9.33 新增：Tier 1/2 权威经济晴雨表 ============================
    # 集装箱运价指数 (周更, 经济温度计)
    ("https://news.google.com/rss/search?q=%22SCFI%22+OR+%22Shanghai+Containerized+Freight+Index%22&hl=en-US&gl=US&ceid=US:en", INDEX_KW),
    ("https://news.google.com/rss/search?q=%22Drewry%22+%22World+Container+Index%22+OR+%22WCI%22&hl=en-US&gl=US&ceid=US:en", INDEX_KW),
    ("https://news.google.com/rss/search?q=%22Freightos+Baltic+Index%22+OR+%22FBX%22+container&hl=en-US&gl=US&ceid=US:en", INDEX_KW),
    # 散货 + 油轮指数 (大宗商品晴雨表)
    ("https://news.google.com/rss/search?q=%22Baltic+Dry+Index%22+OR+%22BDI%22&hl=en-US&gl=US&ceid=US:en", INDEX_KW),
    # 空运指数
    ("https://news.google.com/rss/search?q=%22TAC+Index%22+OR+%22air+cargo+rate%22+OR+%22IATA+CASS%22&hl=en-US&gl=US&ceid=US:en", INDEX_KW),
    # 班轮公司动态 (运营信号 — 缩舱/空班/航线调整领先 1-2 个月反映需求)
    ("https://news.google.com/rss/search?q=%22Maersk%22+%22blank+sailing%22+OR+capacity+OR+rate&hl=en-US&gl=US&ceid=US:en", CARRIER_KW),
    ("https://news.google.com/rss/search?q=%22CMA+CGM%22+OR+%22MSC%22+OR+%22Hapag-Lloyd%22+capacity+OR+route&hl=en-US&gl=US&ceid=US:en", CARRIER_KW),
    ("https://news.google.com/rss/search?q=%22COSCO%22+OR+%22Evergreen%22+OR+%22Yang+Ming%22+route+OR+capacity&hl=en-US&gl=US&ceid=US:en", CARRIER_KW),
    # 港口吞吐量 (实时拥堵 + 月度吞吐数据)
    ("https://news.google.com/rss/search?q=%22Port+of+Los+Angeles%22+OR+%22Long+Beach%22+volume+OR+throughput&hl=en-US&gl=US&ceid=US:en", PORT_KW),
    ("https://news.google.com/rss/search?q=%22Shanghai+port%22+OR+%22Ningbo+port%22+container+volume&hl=en-US&gl=US&ceid=US:en", PORT_KW),
    ("https://news.google.com/rss/search?q=%22port+congestion%22+OR+%22container+throughput%22+%22TEU%22&hl=en-US&gl=US&ceid=US:en", PORT_KW),
    # 海关 / 贸易数据 (月度经济宏观信号)
    ("https://news.google.com/rss/search?q=%22China+exports%22+OR+%22China+trade+balance%22+monthly&hl=en-US&gl=US&ceid=US:en", CUSTOMS_KW),
    ("https://news.google.com/rss/search?q=%22US+imports%22+OR+%22US+trade+deficit%22+%22Census+Bureau%22&hl=en-US&gl=US&ceid=US:en", CUSTOMS_KW),
]

results = []
NS = {"a": "http://www.w3.org/2005/Atom"}

for url, kws in SOURCES:
    try:
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
        with urllib.request.urlopen(req, timeout=15) as r:
            raw = r.read()
        try:
            root = ET.fromstring(raw)
        except ET.ParseError:
            continue

        items = root.findall(".//item") or root.findall(".//a:entry", NS)
        for item in items[:20]:
            title = (item.findtext("title") or item.findtext("a:title", "", NS) or "").strip()
            link  = (item.findtext("link")  or item.findtext("a:link",  "", NS) or "").strip()
            if not link:
                for lk in item.findall("a:link", NS):
                    if lk.get("type") == "text/html":
                        link = lk.get("href", "")
            title_low = title.lower()
            if any(k in title_low for k in kws) and link and link not in inbox:
                results.append({"title": title, "url": link, "source": url.split("/")[2]})
    except Exception as e:
        print(f"[freight] WARN: {url[:60]} -> {e}", file=sys.stderr)

# V37.9.33: 去重 + 限制15条 (从10→15, 给新增 12 个权威 Tier 1/2 源留空间)
# 防止 Tier 3 行业新闻挤掉 SCFI/BDI/FBX 等运价指数 (这些源每周才出一条但价值最高)
seen = set()
with open(OUT_FILE, "w") as f:
    count = 0
    for r in results:
        if r["url"] not in seen and count < 15:
            seen.add(r["url"])
            f.write(json.dumps(r, ensure_ascii=False) + "\n")
            count += 1
print(f"[freight] 抓取完成，新条目: {count}", file=sys.stderr)
PYEOF
then
  log "WARN: RSS抓取Python脚本失败"
fi

# ── 2. 计算新条目数 ──────────────────────────────────────────────────────
NEW_COUNT="$(wc -l < "$NEW_FILE" | tr -d ' ')"
if [ "$NEW_COUNT" -eq 0 ]; then
    log "暂无新商机，本轮跳过。"
    # V37.9.31: deep_dive=skipped_no_news so preflight knows this is legitimate
    # (NEW_COUNT=0 means no triggers for high-star extraction → step 9 skipped
    # by design, not a fault). Without this field, preflight reports "missing
    # — 旧版本脚本？" false alarm.
    printf '{"time":"%s","status":"ok","new":0,"deep_dive":"skipped_no_news"}\n' "$TS" > "$STATUS_FILE"
    exit 0
fi

# 提前写入INBOX（与LLM结果解耦，#85原则）
while IFS= read -r line; do
    url="$(echo "$line" | python3 -c "import json,sys; print(json.load(sys.stdin).get('url',''))" 2>/dev/null || true)"
    [ -n "$url" ] && echo "- $url" >> "$KB_INBOX"
done < "$NEW_FILE"

# ── 3. 单次批量LLM调用（V37.9.33 三层深度分析: 经济晴雨表 + 运营信号 + 商机）─────
# 用户论点 (V37.9.33): "全球发货信息是经济晴雨表, 比专家智囊更值得信赖"
# 三层分类让 LLM 把权威 Tier 1/2 数据 (SCFI/BDI/FBX/班轮/港口) 从 Tier 3 行业新闻中
# 区分出来, 输出可执行的经济判断 + 业务行动建议. Step 8 ImportYeti 提取 regex
# 仍只匹配"企业信号:" 前缀, 不会误捕"经济信号"/"动作"块 (向后兼容).
PROMPT="你是货代行业资深分析师 + 全球贸易经济分析师。以下是${NEW_COUNT}条权威信源新闻，
请按主题分类做深度分析（不只是逐条陈述，要做交叉判断）：

$(python3 -c "
import json, sys
lines = open('$NEW_FILE').readlines()
for i, l in enumerate(lines, 1):
    d = json.loads(l)
    print(f'{i}. 标题：{d[\"title\"]}')
    print(f'   来源：{d[\"source\"]}')
" 2>/dev/null)

请严格按以下三层格式输出（每段标题独占一行，每条之间空一行）：

📊 【第一层：经济晴雨表】运价指数 / 港口吞吐量 / 海关数据
（货代是 1-3 个月领先经济指标；提取关键数字+方向，给出经济信号判断）
格式：序号. 指数：[指数名] [当前值或方向] — [来源标题摘要≤20字]
解读：[≤40字 — 这反映了什么经济动向？对货代业务意味着什么？]
评级：⭐（5星=强信号，1星=噪声）
（如本期无相关信号，输出\"📊 本期无显著经济晴雨表信号\"）

🏢 【第二层：运营信号】班轮公司 / 港口拥堵 / 路线变化
（运营动作领先 1-2 个月反映需求变化；如 MAERSK blank sailing = 需求疲软）
格式：序号. 动作：[公司或港口] - [具体动作≤30字]
影响：[≤30字 — 对运价/航线/客户成本的影响]
评级：⭐（1-5个星）
（如本期无相关信号，输出\"🏢 本期无显著运营信号\"）

🚢 【第三层：商机条目】具体客户/采购信号
（V25 原格式保留, 是 Step 8 ImportYeti 深挖入口；企业名必须在'企业信号：'后）
格式：序号. 企业信号：[企业名] — [≤25字需求描述]（无明确企业时写'行业信号 — 描述'）
行动：[≤30字可执行行动建议]
评级：⭐（1-5个星）
（如本期无具体企业，输出'🚢 本期无显著商机条目'）

⚠️ 严格约束：
1. 三层独立分类，不要在一段里混合（避免 Step 8 误提取）
2. 每段最多输出 5 条（防 WhatsApp 推送超长）
3. 评级⭐≥4 的'企业信号'会被自动深挖客户画像（ImportYeti 海关数据查询）
4. 严禁虚构具体数字（运价/港口吞吐量 必须来自原文，无则写'未提供'）${HG_GUARD_TEXT}"

# 规则 #27: 纯推理直接 curl proxy:5002，禁止用 openclaw agent（#94教训）
LLM_PAYLOAD=$(python3 -c "
import json, sys
prompt = sys.stdin.read()
print(json.dumps({
    'model': 'Qwen3-235B-A22B-Instruct-2507-W8A8',
    'messages': [{'role': 'user', 'content': prompt}],
    'max_tokens': 4096,
    'temperature': 0.3
}))
" <<< "$PROMPT")

LLM_RESP=$(curl -s --max-time 120 \
    -H "Content-Type: application/json" \
    -d "$LLM_PAYLOAD" \
    http://127.0.0.1:5002/v1/chat/completions 2>"$LLM_RAW.stderr" || true)

LLM_OUT=$(echo "$LLM_RESP" | python3 -c "
import json, sys
try:
    d = json.load(sys.stdin)
    print(d['choices'][0]['message']['content'])
except Exception:
    pass
" 2>/dev/null || true)

echo "returncode=$?" > "$LLM_RAW"
echo "--- stderr ---" >> "$LLM_RAW"
cat "$LLM_RAW.stderr" >> "$LLM_RAW" 2>/dev/null || true
echo "--- stdout ---" >> "$LLM_RAW"
echo "$LLM_OUT" >> "$LLM_RAW"

# L1检查：LLM输出为空
if [ -z "${LLM_OUT// }" ]; then
    ERR_MSG="⚠️ 货代Watcher LLM调用失败（${DAY}），请检查 $LLM_RAW"
    echo "$ERR_MSG"
    "$OPENCLAW" message send --channel whatsapp --target "$TO" --message "$ERR_MSG" --json >/dev/null 2>&1 || true
    "$OPENCLAW" message send --channel discord --target "${DISCORD_CH_ALERTS:-}" --message "$ERR_MSG" --json >/dev/null 2>&1 || true
    # V37.9.31: deep_dive=skipped_llm_failed so preflight reports legitimate skip
    printf '{"time":"%s","status":"llm_failed","new":%d,"deep_dive":"skipped_llm_failed"}\n' "$TS" "$NEW_COUNT" > "$STATUS_FILE"
    exit 1
fi

# L2检查：解析成功率 < 50%
PARSE_OK="$(echo "$LLM_OUT" | grep -c '评级：' || true)"
if [ "$PARSE_OK" -lt $(( NEW_COUNT / 2 )) ] && [ "$NEW_COUNT" -gt 2 ]; then
    WARN_MSG="⚠️ 货代Watcher解析成功率低 ${PARSE_OK}/${NEW_COUNT}（${DAY}），请查 $LLM_RAW"
    echo "$WARN_MSG"
    "$OPENCLAW" message send --channel whatsapp --target "$TO" --message "$WARN_MSG" --json >/dev/null 2>&1 || true
    "$OPENCLAW" message send --channel discord --target "${DISCORD_CH_ALERTS:-}" --message "$WARN_MSG" --json >/dev/null 2>&1 || true
    # V37.9.31: deep_dive=skipped_parse_low so preflight reports legitimate skip
    printf '{"time":"%s","status":"parse_low","new":%d,"parse_ok":%d,"deep_dive":"skipped_parse_low"}\n' "$TS" "$NEW_COUNT" "$PARSE_OK" > "$STATUS_FILE"
    exit 2
fi

# ── 4. 组装WhatsApp消息（V37.9.33 三层结构化推送：经济晴雨表 + 运营信号 + 商机）──
MSG_FILE="$CACHE/system_message_freight.txt"
{
    echo "🚢 货代商机速报 (${DAY})"
    echo ""
    # V37.9.33: passthrough 三层 LLM 输出 + ⭐≥4 商机条目附 ImportYeti 链接.
    # URL 1:1 关联在三层重排后失效, 改为末尾"完整新闻链接"段一次性输出 (15 条).
    python3 - "$NEW_FILE" "$LLM_RAW" << 'PYEOF2'
import sys, json, re, urllib.parse

# Load original news items for KB full-link footer
lines_file = open(sys.argv[1]).readlines()
news_items = []  # list of (title, url, source)
for l in lines_file:
    try:
        d = json.loads(l)
        news_items.append((
            d.get("title", "")[:80],
            d.get("url", ""),
            d.get("source", ""),
        ))
    except Exception:
        pass

def extract_company(block):
    """从'企业信号：XX — 描述'提取企业名 (Step 8 兼容: 仅匹配 V25 原前缀)."""
    m = re.search(r'企业信号：(.+)', block)
    if not m:
        return None
    signal = m.group(1).strip()
    if signal.startswith("行业信号"):
        return None
    parts = re.split(r'\s*[—–-]\s*', signal, maxsplit=1)
    company = parts[0].strip()
    if len(company) < 2 or len(company) > 30:
        return None
    return company

def count_stars(block):
    m = re.search(r'评级：(⭐+)', block)
    return len(m.group(1)) if m else 0

def importyeti_url(company):
    q = urllib.parse.quote(company)
    return f"https://www.importyeti.com/search?q={q}"

# Read LLM 3-section output
raw = open(sys.argv[2]).read()
stdout_part = raw.split("--- stdout ---")[-1] if "--- stdout ---" in raw else raw
text = stdout_part.strip()

# Pass-through LLM output but enhance ⭐≥4 "企业信号" blocks with ImportYeti.
# Section headers (📊/🏢/🚢) print as-is. Numbered entries split by `\n\d+\.`.
# Track current section so ImportYeti enhancement only fires inside 🚢 section.
current_section = None  # None / "index" / "ops" / "business"

# Split text into chunks: section headers + numbered blocks
# Match either a section header (📊/🏢/🚢) line, or `\n\d+\.` numbered block start.
chunks = re.split(r'(?=\n📊|\n🏢|\n🚢|\n\d+\.)', '\n' + text)

for chunk in chunks:
    chunk = chunk.strip()
    if not chunk:
        continue
    # Section header detection (preserve LLM-generated header lines verbatim)
    if chunk.startswith("📊"):
        current_section = "index"
        print(chunk)
        print("")
        continue
    if chunk.startswith("🏢"):
        current_section = "ops"
        print(chunk)
        print("")
        continue
    if chunk.startswith("🚢"):
        current_section = "business"
        print(chunk)
        print("")
        continue
    # Numbered block — output as-is
    print(chunk)
    # ImportYeti enhancement only inside 🚢 business section + ⭐≥4 + has 企业信号
    if current_section == "business" and "企业信号：" in chunk:
        stars = count_stars(chunk)
        if stars >= 4:
            company = extract_company(chunk)
            if company:
                print(f"📦 ImportYeti：{importyeti_url(company)}")
    print("")

# V37.9.33: footer — full news links for KB traceability + WhatsApp transparency
# (15 items max per 1-source dedup cap, fits comfortably in WhatsApp single message)
if news_items:
    print("---")
    print(f"📚 本期数据来源 ({len(news_items)} 条权威信源)：")
    for i, (title, url, source) in enumerate(news_items, 1):
        if url:
            print(f"  {i}. [{source}] {title[:50]}")
            print(f"     {url}")
PYEOF2
} > "$MSG_FILE"

# ── 5. 推送WhatsApp ─────────────────────────────────────────────────────
SEND_ERR=$(mktemp)
if "$OPENCLAW" message send --channel whatsapp --target "$TO" --message "$(cat "$MSG_FILE")" --json >/dev/null 2>"$SEND_ERR"; then
    log "已推送 ${NEW_COUNT} 条商机（${DAY}）"
    "$OPENCLAW" message send --channel discord --target "${DISCORD_CH_FREIGHT:-}" --message "$(cat "$MSG_FILE")" --json >/dev/null 2>&1 || true
    # V37.9.31: deep_dive=pending here. Step 9 will overwrite with ok/no_data/skipped
    # if it runs. If Step 7/8 exits early (set -e + rsync fail pre-V37.9.31, or
    # other future failure modes), pending is the honest answer that preflight
    # can flag as "Step 9 unreached" rather than the bogus "missing — 旧版本脚本？".
    printf '{"time":"%s","status":"ok","new":%d,"sent":true,"deep_dive":"pending"}\n' "$TS" "$NEW_COUNT" > "$STATUS_FILE"
else
    log "ERROR: 推送失败（${NEW_COUNT} 条待发）: $(cat "$SEND_ERR" | head -3)"
    # V37.9.31: send_failed = WhatsApp delivery failed but data is ready.
    # deep_dive remains pending because Step 9 might still run if set -e doesn't kill.
    printf '{"time":"%s","status":"send_failed","new":%d,"sent":false,"deep_dive":"pending"}\n' "$TS" "$NEW_COUNT" > "$STATUS_FILE"
fi

# ── 6. KB归档 ───────────────────────────────────────────────────────────
# V37.6: idempotent H2-dedup append。DAY 已含 HH:MM，三次每日 slot 天然
# 不冲突；只有同一 slot 的重复触发（watchdog/手动）才触发幂等跳过。
{
    echo ""
    echo "## ${DAY}"
    cat "$MSG_FILE"
} | bash "$HOME/kb_append_source.sh" "$KB_SRC" "## ${DAY}"

# 写入 KB notes（与其他 job 对齐双写模式）
bash "$KB_WRITE_SCRIPT" "# 货代商机 ${DAY}

$(cat "$MSG_FILE")" "freight" "note" 2>/dev/null || true

# ── 7. rsync备份 ────────────────────────────────────────────────────────
bash "$HOME/movespeed_rsync_helper.sh" "$0" -- -a "$HOME/.kb/" "/Volumes/MOVESPEED/KB/"  # V37.9.27 jitter+retry+fail-loud+capture (replaces V37.9.4/V37.9.14 inline pattern)
# ══════════════════════════════════════════════════════════════════════════
# V3: 三层情报漏斗 — 第2层 ImportYeti 深挖 + 第3层 客户画像
# ══════════════════════════════════════════════════════════════════════════

# ── 8. 提取高星企业名（≥4星 + 非行业信号）─────────────────────────────
HIGH_STARS="$CACHE/high_star_companies.txt"
: > "$HIGH_STARS"

python3 -c "
import re
raw = open('$LLM_RAW').read()
stdout = raw.split('--- stdout ---')[-1] if '--- stdout ---' in raw else raw
seen = set()
for block in re.split(r'\n(?=\d+\.)', stdout.strip()):
    if not block.strip():
        continue
    if len(re.findall(r'⭐', block)) >= 4:
        m = re.search(r'企业信号：(.+?)[\s]*[—–\-]', block)
        if m:
            name = m.group(1).strip()
            if name != '行业信号' and 2 <= len(name) <= 30 and name not in seen:
                seen.add(name)
                print(name)
" > "$HIGH_STARS" 2>/dev/null || true

COMPANY_COUNT=$(wc -l < "$HIGH_STARS" | tr -d ' ')
echo "[freight] 发现 ${COMPANY_COUNT} 个高星企业待深挖"

# 限制最多深挖5个，取前5条（LLM输出越靠前通常评级越高）
if [ "$COMPANY_COUNT" -gt 5 ]; then
    head -5 "$HIGH_STARS" > "$HIGH_STARS.tmp" && mv "$HIGH_STARS.tmp" "$HIGH_STARS"
    COMPANY_COUNT=5
    echo "[freight] 截取前5个企业进行深挖"
fi

fi  # END TEST_MODE guard (Steps 1-8)

# ── 9. ImportYeti 自动化深挖（Playwright + stealth，替代 openclaw agent）──
if [ "$COMPANY_COUNT" -gt 0 ]; then
    ENRICHED_FILE="$CACHE/enriched_data.txt"
    : > "$ENRICHED_FILE"
    SCRAPER="$(dirname "$0")/importyeti_scraper.py"

    # 企业名翻译（中文→英文，ImportYeti 是美国海关数据库，只认英文）
    COMPANIES_LIST=$(cat "$HIGH_STARS" | tr '\n' '、')
    TRANS_PROMPT="将以下企业/机构名翻译为英文（ImportYeti搜索用），每行一个，只输出英文名，不要序号不要解释：
${COMPANIES_LIST}"
    TRANS_PAYLOAD=$(python3 -c "
import json, sys
prompt = sys.stdin.read()
print(json.dumps({
    'model': 'Qwen3-235B-A22B-Instruct-2507-W8A8',
    'messages': [{'role': 'user', 'content': prompt}],
    'max_tokens': 512,
    'temperature': 0.1
}))
" <<< "$TRANS_PROMPT")
    TRANS_RESP=$(curl -s --max-time 30 \
        -H "Content-Type: application/json" \
        -d "$TRANS_PAYLOAD" \
        http://127.0.0.1:5002/v1/chat/completions 2>/dev/null || true)
    TRANS_OUT=$(echo "$TRANS_RESP" | python3 -c "
import json, sys
try:
    d = json.load(sys.stdin)
    print(d['choices'][0]['message']['content'].strip())
except Exception:
    pass
" 2>/dev/null || true)

    # 组装英文企业名列表
    CLEAN_NAMES=()
    if [ -n "$TRANS_OUT" ]; then
        while IFS= read -r en_name; do
            en_name=$(echo "$en_name" | sed 's/^[0-9]*[.、) ]*//' | tr -d '\r')
            [ -n "$en_name" ] && CLEAN_NAMES+=("$en_name")
        done <<< "$TRANS_OUT"
        echo "[freight] 翻译结果: ${CLEAN_NAMES[*]}"
    fi

    # 翻译失败时回退到原始中文名
    if [ "${#CLEAN_NAMES[@]}" -eq 0 ]; then
        echo "[freight] 翻译失败，使用原始名称"
        while IFS= read -r company; do
            CLEAN_NAMES+=("$company")
        done < "$HIGH_STARS"
    fi

    # 一次启动 Playwright 批量查询所有企业（避免重复启动浏览器）
    # 注意：playwright 装在系统 Python 3.9 下，homebrew Python 3.14 没有
    /usr/bin/python3 "$SCRAPER" "${CLEAN_NAMES[@]}" > "$ENRICHED_FILE" 2>"$CACHE/scraper.log"
    SCRAPER_RC=$?
    cat "$CACHE/scraper.log" >&2
    if [ "$SCRAPER_RC" -ne 0 ]; then
        echo "[freight] ERROR: ImportYeti scraper 失败 (exit=$SCRAPER_RC)"
    fi
    echo "[freight] ImportYeti 深挖完成"

    # ── 10. 客户画像生成（LLM综合推理）──────────────────────────────
    ENRICHED_DATA="$(cat "$ENRICHED_FILE" 2>/dev/null || true)"

    # 只在有有效深挖数据时才生成画像（至少一个公司有非N/A数据）
    HAS_DATA=$(echo "$ENRICHED_DATA" | grep -c "总发货次数" || true)
    ALL_NA=$(echo "$ENRICHED_DATA" | { grep "总发货次数：N/A" || true; } | wc -l | tr -d ' ')

    if [ "$HAS_DATA" -gt 0 ] && [ "$HAS_DATA" -gt "$ALL_NA" ]; then
        echo "[freight] 生成客户画像..."

        # 直接调用 LLM API（不走 openclaw agent），彻底避免 Gateway 注入工具
        PROFILE_PROMPT="你是资深货代销售顾问。基于以下企业新闻信号和 ImportYeti 美国海关提单数据，为每个企业生成完整客户画像。

== 今日新闻信号 ==
$(cat "$MSG_FILE")

== ImportYeti 海关数据 ==
${ENRICHED_DATA}

== 当前市场运价参考 ==
中国→美西整柜: USD 2,500-4,000/TEU
中国→美东整柜: USD 3,500-5,500/TEU
中国→欧洲整柜: USD 1,800-3,000/TEU
中国→东南亚整柜: USD 800-1,500/TEU
空运中国→美国: USD 4-6/kg

请为每个有数据的企业输出客户画像卡片，严格使用以下格式：

📋 客户：[企业名]
├ 📦 需求量：[月均TEU或重量，标注数据来源]
├ 🕐 运输周期：[基于航线的海运/空运天数]
├ 💰 月物流预算：[货量×运价推算，给出USD范围]
├ 🚢 主要路径：[起运港→目的港，可多条]
├ 📈 趋势：[增长/平稳/下降 + 依据]
├ 🏭 主要供应商：[前3个，含国家]
└ 🎯 开发建议：[≤60字，联系哪个部门、什么切入点]

规则：
1. ImportYeti数据全部N/A的公司→跳过不输出
2. 部分N/A→基于新闻信号保守估算，标注'⚠估算'
3. 预算 = 月均TEU × 对应航线运价，给出USD范围
4. 开发建议要具体可执行"

        PROFILE_PAYLOAD=$(python3 -c "
import json, sys
prompt = sys.stdin.read()
print(json.dumps({
    'model': 'Qwen3-235B-A22B-Instruct-2507-W8A8',
    'messages': [{'role': 'user', 'content': prompt}],
    'max_tokens': 4096,
    'temperature': 0.7
}))
" <<< "$PROFILE_PROMPT")

        PROFILE_RESP=$(curl -s --max-time 120 \
            -H "Content-Type: application/json" \
            -d "$PROFILE_PAYLOAD" \
            http://127.0.0.1:5002/v1/chat/completions 2>/dev/null || true)

        PROFILE_OUT=$(echo "$PROFILE_RESP" | python3 -c "
import json, sys
try:
    r = json.load(sys.stdin)
    print(r['choices'][0]['message']['content'])
except Exception:
    pass
" 2>/dev/null || true)

        # 推送客户画像
        if [ -n "${PROFILE_OUT// }" ]; then
            if "$OPENCLAW" message send --channel whatsapp --target "$TO" \
                --message "📊 货代客户画像 (${DAY})

${PROFILE_OUT}

💡 数据来源：ImportYeti美国海关提单 + 行业新闻
⚠ 预算为运价推算值，仅供参考" --json >/dev/null 2>&1; then
                log "已推送客户画像"
                "$OPENCLAW" message send --channel discord --target "${DISCORD_CH_FREIGHT:-}" --message "📊 货代客户画像 (${DAY})

${PROFILE_OUT}

💡 数据来源：ImportYeti美国海关提单 + 行业新闻
⚠ 预算为运价推算值，仅供参考" --json >/dev/null 2>&1 || true
            else
                log "ERROR: 客户画像推送失败，请检查 gateway。"
            fi

            # KB归档（V37.6: idempotent append，DAY 含 HH:MM 天然不冲突）
            {
                echo ""
                echo "## 📊 客户画像 ${DAY}"
                echo "$PROFILE_OUT"
            } | bash "$HOME/kb_append_source.sh" "$KB_SRC" "## 📊 客户画像 ${DAY}"

            # 再次备份
            bash "$HOME/movespeed_rsync_helper.sh" "$0" -- -a "$HOME/.kb/" "/Volumes/MOVESPEED/KB/"  # V37.9.27 jitter+retry+fail-loud+capture (replaces V37.9.4/V37.9.14 inline pattern)
        else
            echo "[freight] 客户画像LLM调用无输出，跳过"
        fi
        DEEP_DIVE_STATUS="ok"
    else
        echo "[freight] 深挖数据不足（${HAS_DATA}条中${ALL_NA}条全N/A），跳过画像生成"
        DEEP_DIVE_STATUS="no_data"
    fi
else
    # V37.9.43-hotfix: rename generic 'skipped' → 'skipped_no_high_star'
    # 让 preflight 识别为合法跳过 (无 ⭐≥4 高星条目, Step 9 by-design 不进入).
    # V37.9.31 设计了 skipped_no_news / skipped_llm_failed / skipped_parse_low 三档,
    # 但漏改 Step 9 条件分支的"无高星"路径, 导致 preflight 误报 warn.
    DEEP_DIVE_STATUS="skipped_no_high_star"
fi

# 更新 status_file 追加 deep_dive 状态（覆盖 Step 5 写入的版本）
if [ -f "$STATUS_FILE" ] && [ -n "$DEEP_DIVE_STATUS" ]; then
    python3 -c "
import json, sys
try:
    d = json.load(open('$STATUS_FILE'))
    d['deep_dive'] = '$DEEP_DIVE_STATUS'
    json.dump(d, sys.stdout)
    print()
except Exception:
    pass
" > "$STATUS_FILE.tmp" 2>/dev/null && mv "$STATUS_FILE.tmp" "$STATUS_FILE"
fi
