#!/bin/bash
# 三部门逐月拆分看板（工具/仪表盘/三部门逐月拆分_demo.html）数据刷新管道
#   按序跑 4 步（顺序不能乱：②③ 依赖 ①，③ 重建 ② 的产物，④ 在 ① 的 salespersons 之上补目标）：
#     ① gen_dept_monthly_json.py  —— ERP 现算 月×部门 实际/目标(1-12月)/2025同期 + 客户/销售员
#     ② apply_group_rollup.py     —— 补集团口径 customers_grp
#     ③ apply_sales_owner.py      —— 补客户归属 sales + 重建 customers_grp(sales_list/mixed)
#     ④ apply_salesperson_targets.py —— 给 salespersons 补个人目标/达成率（7-12月月目标+年度）
#   产出：数据/三部门逐月_2026.json（后端 /api/sandept 直读）
#
# 【解释器必须钉死】本脚本可由 cron(no_agent) 调用，父进程 PATH 首位是 zone-b 的 3.14.7（无 pandas），
#   裸 `python3` 会命中它 → import pandas 全崩 → 静默空转。此处显式探测第一个能 import pandas 的解释器。
#   （与 脚本/cb_sync.sh 同一套探测写法）
#
# 失败即非零退出，绝不静默。用法：bash 脚本/refresh_sandept.sh
set -uo pipefail

WORKDIR="$HOME/Desktop/Hermes输出-工作类"
SDIR="$WORKDIR/脚本"
OUT="$WORKDIR/数据/三部门逐月_2026.json"
LOG="$WORKDIR/数据/sandept_refresh.log"

unset PYTHONPATH PYTHONHOME VIRTUAL_ENV

PY=""
for _c in /usr/bin/python3 /opt/homebrew/bin/python3 /usr/local/bin/python3; do
  if [ -x "$_c" ] && "$_c" -c "import pandas" >/dev/null 2>&1; then PY="$_c"; break; fi
done
if [ -z "$PY" ]; then
  _w=$(command -v python3 2>/dev/null || true)
  if [ -n "$_w" ] && "$_w" -c "import pandas" >/dev/null 2>&1; then PY="$_w"; fi
fi
if [ -z "$PY" ]; then
  echo "FATAL: no pandas-capable python3 found (PATH=$PATH)" >&2
  echo "=== $(date '+%Y-%m-%d %H:%M:%S') ❌ 致命：找不到带 pandas 的 python3，三部门看板刷新中止 ===" >> "$LOG"
  exit 1
fi

START=$(date +%s)
FAIL=0
STEP_LOG="$WORKDIR/数据/_sandept_refresh_steps.tmp"
: > "$STEP_LOG"

_say() { echo "$1"; echo "$1" >> "$LOG"; }

_say "=== $(date '+%Y-%m-%d %H:%M:%S') 三部门看板刷新开始 (py=$PY) ==="

_run() {  # $1=描述 $2=脚本名
  local desc="$1" scr="$2"
  _say ">>> $desc ($scr)"
  local t0=$(date +%s)
  if ( cd "$SDIR" && "$PY" "$scr" ) >> "$STEP_LOG" 2>&1; then
    local dt=$(( $(date +%s) - t0 ))
    _say "  ✅ $desc  (${dt}s)"
  else
    local dt=$(( $(date +%s) - t0 ))
    _say "  ❌ $desc 失败 (${dt}s) —— 详见 $STEP_LOG"
    FAIL=$((FAIL + 1))
  fi
}

_run "① 生成主 JSON（ERP 现算，含 10-12 月目标）" gen_dept_monthly_json.py
if [ "$FAIL" -eq 0 ]; then
  _run "② 补集团口径 customers_grp" apply_group_rollup.py
  _run "③ 补客户归属 sales + 重建集团口径" apply_sales_owner.py
  _run "④ 给 salespersons 补个人目标/达成率" apply_salesperson_targets.py
else
  _say "  ⚠️ ① 失败，后续步骤（②③④）依赖其产物，已跳过。"
fi

# ── 自检：读回产出，硬校验关键口径 ───────────────────────────────────────────
if [ "$FAIL" -eq 0 ]; then
  "$PY" - "$OUT" <<'PYEOF' >> "$STEP_LOG" 2>&1
import json, sys
d = json.load(open(sys.argv[1], encoding='utf-8'))
mon = d['months']; tm = d['total']['target_monthly']
q4 = d.get('q4') or {}
amt = d['total']['actual_monthly']
assert len(mon) == 12, 'months != 12'
assert len(tm) == 12, 'target_monthly != 12'
s = round(sum(tm.values()), 2)
assert abs(s - 6650) <= 0.5, f'全年目标合计 {s} != 6650'
_actual_months = sorted(amt)
assert _actual_months, '无实际月份（当前月应有实际）'
_latest_actual = _actual_months[-1]
assert _latest_actual >= '2026-10', f'最新实际月份 {_latest_actual} 早于 2026-10'
assert all(m not in amt for m in mon if m > _latest_actual), '未到月份不得有实际键'
assert _latest_actual in amt, '最新实际月份缺失'
assert q4.get('target') == 1913.6, f"Q4目标 {q4.get('target')} != 1913.6"
_sf = q4.get('annual_shortfall_at_full_q4')
_exp = 6650 - sum(amt.values()) - (q4.get('target') or 0)
assert abs(_sf - _exp) <= 1.0, f"年度缺口 {_sf} 与 6650-实际-{q4.get('target')} 不一致（应为 {_exp:.2f}）"
# 生命科学部新目标 600 + 广东/浙江 各 300 子维度
_ls = d['depts']['生命科学部']
assert abs(_ls['target_annual'] - 600.0) < 1e-9, f"生命科学部年度目标 {_ls['target_annual']} != 600"
assert abs(sum(_ls['target_monthly'].values()) - 600.0) < 1e-9, '生命科学部12月目标合计 != 600'
_lsr = _ls.get('regions') or {}
assert set(_lsr.keys()) == {'广东', '浙江'}, '生命科学部 regions 子维度缺失'
for _r in ('广东', '浙江'):
    assert abs(_lsr[_r]['target_annual'] - 300.0) < 1e-9, f'{_r} 年度目标 != 300'
    assert abs(sum(_lsr[_r]['target_monthly'].values()) - 300.0) < 1e-9, f'{_r} 12月目标合计 != 300'
    assert abs(sum(_lsr[_r]['actual_monthly'].values())) >= 0
    _ppl = {p['name']: p['target_annual'] for p in (_lsr[_r].get('people') or [])}
    assert abs(sum(_ppl.values()) - 300.0) < 1e-9, f'{_r} 逐人目标合计 != 300: {_ppl}'
assert abs(_lsr['浙江']['people'][0]['target_annual'] or 0) >= 0
_zjp = [p for p in _lsr['浙江']['people'] if p['name'] == '张晋菲']
assert _zjp and abs(_zjp[0]['target_annual'] - 15.0) < 1e-9 and _zjp[0]['is_new'], '张晋菲 15 万目标缺失'
_gdh = [p for p in _lsr['广东']['people'] if p['name'] == '韩远怀']
assert _gdh and abs(_gdh[0]['target_annual'] - 165.0) < 1e-9, '韩远怀 165 万目标缺失（刘斌离职重摊）'
assert '刘斌' not in {p['name'] for p in _lsr['广东']['people']}, '刘斌不应再出现在广东 people'
assert abs(sum(1 for p in _lsr['广东']['people']) - 3) < 1e-9, '广东 people 应为 3 人'
assert abs(sum(1 for p in _lsr['浙江']['people']) - 5) < 1e-9, '浙江 people 应为 5 人'
assert abs(sum(_lsr['广东']['actual_monthly'].values()) + sum(_lsr['浙江']['actual_monthly'].values())
           - sum(_ls['actual_monthly'].values())) < 1e-9, '区域实际合计 != 部门实际'
# 离职人员不得在 salespersons 单列
_leftover = sorted({r['sp'] for r in (d.get('salespersons') or []) if r['sp'] in
                    ('赵以诺', '李岚风', '曾琪', '黄建佳', '黄自鑫', '段玉', '梁莹莹', '肖立',
                     '龙友波', '王慧洁', '刘静文', '刘斌')})
assert not _leftover, f'离职人员仍在 salespersons: {_leftover}'
sp = d.get('salespersons') or []
has_tgt = [r for r in sp if r.get('target') is not None]
print(f"  自检: months={len(mon)} 目标12月合计={s} Q4={q4.get('target')} 缺口={q4.get('annual_shortfall_at_full_q4')} "
      f"salespersons={len(sp)}(带月目标 {len(has_tgt)}) customers={len(d.get('customers') or [])} "
      f"customers_grp={len(d.get('customers_grp') or [])}")
print("  SELFCHECK: PASS")
PYEOF
  if [ $? -ne 0 ]; then
    _say "  ❌ 自检未通过（见 ${STEP_LOG}）"
    FAIL=$((FAIL + 1))
  else
    _say "  ✅ 自检通过"
  fi
fi

DT=$(( $(date +%s) - START ))
if [ "$FAIL" -gt 0 ]; then
  _say "=== $(date '+%Y-%m-%d %H:%M:%S') ❌ 三部门看板刷新失败（$FAIL 处），耗时 ${DT}s ==="
  echo "❌ 三部门看板刷新失败（$FAIL 处，耗时 ${DT}s），详见 $STEP_LOG / $LOG" >&2
  exit 1
fi
_say "=== $(date '+%Y-%m-%d %H:%M:%S') ✅ 三部门看板刷新完成，耗时 ${DT}s ==="
echo "✅ 三部门看板刷新完成，耗时 ${DT}s（日志 ${LOG}）"
exit 0
