import { Router } from 'express'
import { query } from '../config/database.js'
import { sendSuccess, sendError, parsePagination, parseMonth } from '../middleware/error.js'
import type { AuthRequest } from '../middleware/auth.js'

const router = Router()

// ============ Tab1: 客流热力图 ============

// 门店×小时客流分布
router.get('/traffic-heatmap', async (req: AuthRequest, res) => {
  try {
    const storeName = req.query.store as string
    let where = ''
    const params: any[] = []
    if (storeName) {
      params.push(storeName)
      where = `WHERE store_name = $${params.length}`
    }
    const result = await query(`
      SELECT store_name,
        hour,
        sum(bills) AS bills,
        round(avg(avg_guests), 1) AS avg_guests,
        round(sum(total_guests), 0) AS total_guests
      FROM mv_bill_hourly
      ${where}
      GROUP BY store_name, hour
      ORDER BY store_name, hour
    `, params)
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// 餐段客流分布
router.get('/meal-period-traffic', async (req: AuthRequest, res) => {
  try {
    const storeName = req.query.store as string
    let where = "WHERE meal_period != ''"
    const params: any[] = []
    if (storeName) {
      params.push(storeName)
      where += ` AND store_name = $${params.length}`
    }
    const result = await query(`
      SELECT store_name, meal_period,
        sum(bills) AS bills,
        round(sum(total_guests), 0) AS total_guests,
        round(avg(avg_guests), 1) AS avg_guests_per_bill
      FROM mv_bill_hourly
      ${where}
      GROUP BY store_name, meal_period
      ORDER BY store_name, bills DESC
    `, params)
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// 门店×小时在岗人员分布（从考勤打卡记录解析）
router.get('/traffic-heatmap-staffing', async (req: AuthRequest, res) => {
  try {
    const storeName = req.query.store as string
    // 从考勤表取所有员工的打卡记录，解析上下班时间
    // department格式: 北京西部马华餐饮有限公司/西部马华品牌门店/.../七里庄店/前厅/管理组
    const result = await query(`
      SELECT department, position, day_15
      FROM attendance_records
      WHERE department LIKE '%西部马华品牌门店%'
        AND day_15 IS NOT NULL AND day_15 != ''
    `)

    // 从department提取门店名（以"店"结尾的层级）
    function extractStore(dept: string): string {
      const parts = dept.split('/')
      for (let i = parts.length - 1; i >= 0; i--) {
        if (parts[i].endsWith('店')) return parts[i]
      }
      return ''
    }

    // 从打卡记录解析上下班时间，格式: 08:30(考勤机：指纹)+20:28(考勤机：指纹)
    function parseClockTimes(raw: string): { start: number; end: number } | null {
      const times = raw.match(/(\d{1,2}):(\d{2})/g)
      if (!times || times.length < 2) return null
      const startParts = times[0].match(/(\d{1,2}):(\d{2})/)
      const endParts = times[times.length - 1].match(/(\d{1,2}):(\d{2})/)
      if (!startParts || !endParts) return null
      const startHour = parseInt(startParts[1])
      let endHour = parseInt(endParts[1])
      // 如果下班时间小于上班时间，说明跨天，按23点算
      if (endHour < startHour) endHour = 23
      return { start: startHour, end: endHour }
    }

    // 汇总：store -> hour -> { 前厅, 后厨, 管理, 其他 }
    const staffing: Record<string, Record<number, { 前厅: number; 后厨: number; 管理: number; 其他: number }>> = {}

    for (const row of result.rows) {
      const store = extractStore(row.department as string)
      if (!store) continue
      if (storeName && store !== storeName) continue

      const clock = parseClockTimes(row.day_15 as string)
      if (!clock) continue

      // 用position做SQL LIKE风格的判断
      const pos = row.position as string
      const dept = row.department as string
      let role = '其他'
      if (pos.includes('店长') || pos.includes('经理') || pos.startsWith('储备') || pos.includes('副店')) role = '管理'
      else if (dept.includes('/前厅/') || pos.includes('服务员') || pos.includes('训练员') || pos.includes('迎宾') || pos.includes('传菜') || pos.includes('主管')) role = '前厅'
      else if (dept.includes('/后厨') || pos.includes('厨') || pos.includes('拉面') || pos.includes('配菜') || pos.includes('凉菜') || pos.includes('烧烤') || pos.includes('面点') || pos.includes('面工') || pos.includes('锅底') || pos.includes('切肉') || pos.includes('切菜') || pos.includes('炒锅') || pos.includes('砧板') || pos.includes('打荷') || pos.includes('洗碗') || pos.includes('上什') || pos.includes('打馕')) role = '后厨'
      else if (pos.includes('兼职') || pos.includes('小时工')) role = '兼职'

      if (!staffing[store]) staffing[store] = {}
      for (let h = clock.start; h <= clock.end; h++) {
        if (!staffing[store][h]) staffing[store][h] = { 前厅: 0, 后厨: 0, 管理: 0, 其他: 0 }
        staffing[store][h][role as '前厅' | '后厨' | '管理' | '其他']++
      }
    }

    // 转为数组输出
    const output: any[] = []
    for (const [store, hours] of Object.entries(staffing)) {
      for (let h = 0; h < 24; h++) {
        const s = hours[h] || { 前厅: 0, 后厨: 0, 管理: 0, 其他: 0 }
        output.push({
          store_name: store,
          hour: h,
          front: s.前厅,
          kitchen: s.后厨,
          management: s.管理,
          other: s.其他,
          total: s.前厅 + s.后厨 + s.管理 + s.其他,
        })
      }
    }

    sendSuccess(res, output)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// 工作日vs周末客流
router.get('/dow-traffic', async (req: AuthRequest, res) => {
  try {
    const storeName = req.query.store as string
    const month = parseMonth(req)
    const params: any[] = [month]
    let where = `WHERE c175 IS NOT NULL AND c175 != '' AND c175::timestamp >= $1::date AND c175::timestamp < $1::date + interval '1 month'`
    if (storeName) {
      params.push(storeName)
      where += ` AND c003 = $${params.length}`
    }
    const result = await query(`
      SELECT c003 AS store_name,
        extract(dow FROM c175::timestamp)::int AS dow,
        extract(hour FROM c175::timestamp)::int AS hour,
        count(*) AS bills
      FROM bill_records
      ${where}
      GROUP BY c003, dow, hour
      ORDER BY c003, dow, hour
    `, params)
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// 门店客流概览（峰谷比、集中度）
router.get('/traffic-overview', async (req: AuthRequest, res) => {
  try {
    const result = await query(`
      WITH hourly AS (
        SELECT store_name,
          hour,
          sum(bills) AS bills
        FROM mv_bill_hourly
        GROUP BY store_name, hour
      ),
      store_stats AS (
        SELECT store_name,
          max(bills) AS peak_bills,
          min(bills) AS min_bills,
          sum(bills) AS total_bills,
          round(max(bills)::numeric / nullif(min(bills), 0), 2) AS peak_valley_ratio,
          round(sum(bills) FILTER (WHERE hour IN (11, 12, 17, 18, 19))::numeric / nullif(sum(bills), 0) * 100, 2) AS peak_concentration_pct
        FROM hourly
        GROUP BY store_name
      )
      SELECT store_name,
        total_bills,
        peak_bills,
        min_bills,
        peak_valley_ratio,
        peak_concentration_pct,
        CASE
          WHEN peak_valley_ratio > 20 THEN '波动极大'
          WHEN peak_valley_ratio > 10 THEN '波动较大'
          WHEN peak_valley_ratio > 5 THEN '波动适中'
          ELSE '波动较小'
        END AS volatility_level
      FROM store_stats
      ORDER BY total_bills DESC
    `)
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// ============ Tab2: 排班匹配度 ============

// 时段在岗人数 vs 客流匹配度
router.get('/staffing-match', async (req: AuthRequest, res) => {
  try {
    const storeName = req.query.store as string || '七里庄店'
    const month = parseMonth(req)
    const result = await query(`
      WITH punch_times AS (
        SELECT employee_code, d.day_num, d.day_val
        FROM attendance_records ar
        CROSS JOIN LATERAL (
          SELECT 1 AS day_num, ar.day_01 AS day_val UNION ALL
          SELECT 2, ar.day_02 UNION ALL
          SELECT 3, ar.day_03 UNION ALL
          SELECT 4, ar.day_04 UNION ALL
          SELECT 5, ar.day_05 UNION ALL
          SELECT 6, ar.day_06 UNION ALL
          SELECT 7, ar.day_07 UNION ALL
          SELECT 8, ar.day_08 UNION ALL
          SELECT 9, ar.day_09 UNION ALL
          SELECT 10, ar.day_10 UNION ALL
          SELECT 11, ar.day_11 UNION ALL
          SELECT 12, ar.day_12 UNION ALL
          SELECT 13, ar.day_13 UNION ALL
          SELECT 14, ar.day_14 UNION ALL
          SELECT 15, ar.day_15 UNION ALL
          SELECT 16, ar.day_16 UNION ALL
          SELECT 17, ar.day_17 UNION ALL
          SELECT 18, ar.day_18 UNION ALL
          SELECT 19, ar.day_19 UNION ALL
          SELECT 20, ar.day_20 UNION ALL
          SELECT 21, ar.day_21 UNION ALL
          SELECT 22, ar.day_22 UNION ALL
          SELECT 23, ar.day_23 UNION ALL
          SELECT 24, ar.day_24 UNION ALL
          SELECT 25, ar.day_25 UNION ALL
          SELECT 26, ar.day_26 UNION ALL
          SELECT 27, ar.day_27 UNION ALL
          SELECT 28, ar.day_28 UNION ALL
          SELECT 29, ar.day_29 UNION ALL
          SELECT 30, ar.day_30
        ) d
        WHERE ar.department LIKE '%' || replace($1, '总', '') || '%'
          AND d.day_val IS NOT NULL AND d.day_val != ''
      ),
      all_punches AS (
        SELECT employee_code, day_num,
          (regexp_matches(day_val, '(\\d{2}:\\d{2})', 'g'))[1]::time AS punch_time
        FROM punch_times
      ),
      daily_range AS (
        SELECT employee_code, day_num,
          min(punch_time) AS first_punch,
          max(punch_time) AS last_punch
        FROM all_punches
        GROUP BY employee_code, day_num
      ),
      hourly_staff AS (
        SELECT h.hour, d.day_num, count(DISTINCT d.employee_code) AS staff_on_duty
        FROM generate_series(6, 23) AS h(hour)
        CROSS JOIN daily_range d
        WHERE d.first_punch <= (h.hour || ':00')::time
          AND d.last_punch >= (h.hour || ':00')::time
        GROUP BY h.hour, d.day_num
      ),
      hourly_staff_avg AS (
        SELECT hour, round(avg(staff_on_duty), 1) AS avg_staff
        FROM hourly_staff
        GROUP BY hour
      ),
      hourly_bills AS (
        SELECT extract(hour FROM c175::timestamp)::int AS hour,
          count(*) AS bills,
          round(sum(c178::numeric), 0) AS total_guests
        FROM bill_records
        WHERE c003 = $1 AND c175 IS NOT NULL AND c175 != ''
          AND c175::timestamp >= $2 AND c175::timestamp < $2::date + interval '1 month'
        GROUP BY hour
      )
      SELECT s.hour,
        s.avg_staff,
        COALESCE(b.bills, 0) AS bills,
        COALESCE(b.total_guests, 0) AS total_guests,
        round(COALESCE(b.bills, 0) / nullif(s.avg_staff, 0), 1) AS bills_per_staff,
        CASE
          WHEN COALESCE(b.bills, 0) / nullif(s.avg_staff, 0) > 200 THEN '严重不足'
          WHEN COALESCE(b.bills, 0) / nullif(s.avg_staff, 0) > 100 THEN '偏紧'
          WHEN COALESCE(b.bills, 0) / nullif(s.avg_staff, 0) < 30 THEN '过剩'
          WHEN COALESCE(b.bills, 0) / nullif(s.avg_staff, 0) < 50 THEN '偏松'
          ELSE '合理'
        END AS match_status
      FROM hourly_staff_avg s
      LEFT JOIN hourly_bills b ON s.hour = b.hour
      ORDER BY s.hour
    `, [storeName, month])
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// 门店列表（从账单表获取，确保与客流查询一致）
router.get('/stores', async (req: AuthRequest, res) => {
  try {
    const result = await query(`
      SELECT DISTINCT c003 AS store_name
      FROM bill_records
      WHERE c003 IS NOT NULL AND c003 != ''
      ORDER BY store_name
    `)
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// ============ Tab3: 人效对标 ============

// 门店人效排名
router.get('/efficiency-ranking', async (req: AuthRequest, res) => {
  try {
    const { page, pageSize, offset } = parsePagination(req)
    const sort = (req.query.sort as string) || 'revenue_per_emp'
    const order = (req.query.order as string) || 'desc'

    const validSorts: Record<string, string> = {
      revenue_per_emp: 'revenue_per_emp',
      emp_count: 'emp_count',
      gross_pay: 'gross_pay',
      net_pay: 'net_pay',
      avg_hours: 'avg_hours',
      wage_rate: 'wage_rate',
    }
    const sortField = validSorts[sort] || 'revenue_per_emp'
    const sortOrder = order === 'asc' ? 'ASC' : 'DESC'

    const countResult = await query(`
      SELECT count(*) FROM (
        SELECT s.org_level5 AS store_name
        FROM salary_detail_records s
        WHERE s.org_level2 = '西部马华品牌门店' AND s.org_level5 IS NOT NULL AND s.org_level5 != ''
        GROUP BY s.org_level5
      ) t
    `)
    const total = countResult.rows[0].count

    const result = await query(`
      WITH salary_stats AS (
        SELECT org_level5 AS store_name,
          count(*) AS emp_count,
          round(sum(gross_pay)::numeric, 2) AS gross_pay,
          round(sum(net_pay)::numeric, 2) AS net_pay,
          round(sum(actual_hours)::numeric, 0) AS total_hours,
          round(avg(actual_hours)::numeric, 0) AS avg_hours,
          round(sum(overtime_pay)::numeric, 2) AS overtime_pay,
          round(sum(perf_amount)::numeric, 2) AS perf_amount,
          round(sum(base_wage)::numeric, 2) AS base_wage
        FROM salary_detail_records
        WHERE org_level2 = '西部马华品牌门店' AND org_level5 IS NOT NULL AND org_level5 != ''
        GROUP BY org_level5
      ),
      revenue_stats AS (
        SELECT COALESCE(m.salary_name, r.store_name) AS store_name,
          r.revenue,
          r.bill_count
        FROM mv_store_revenue r
        LEFT JOIN store_name_mapping m ON m.bill_name = r.store_name
      )
      SELECT s.store_name,
        s.emp_count,
        s.gross_pay,
        s.net_pay,
        s.total_hours,
        s.avg_hours,
        s.overtime_pay,
        s.perf_amount,
        s.base_wage,
        COALESCE(r.revenue, 0) AS revenue,
        COALESCE(r.bill_count, 0) AS bill_count,
        round(COALESCE(r.revenue, 0) / nullif(s.emp_count, 0), 2) AS revenue_per_emp,
        round(s.gross_pay / nullif(s.emp_count, 0), 2) AS cost_per_emp,
        round(s.gross_pay / nullif(r.revenue, 0) * 100, 2) AS wage_rate,
        round(COALESCE(r.revenue, 0) / nullif(s.total_hours, 0), 2) AS revenue_per_hour,
        round(s.overtime_pay / nullif(s.gross_pay, 0) * 100, 2) AS overtime_rate,
        round(s.perf_amount / nullif(s.gross_pay, 0) * 100, 2) AS perf_rate,
        round(s.base_wage / nullif(s.gross_pay, 0) * 100, 2) AS base_wage_rate
      FROM salary_stats s
      LEFT JOIN revenue_stats r ON s.store_name = r.store_name
      ORDER BY ${sortField} ${sortOrder}
      LIMIT $1 OFFSET $2
    `, [pageSize, offset])
    sendSuccess(res, result.rows, { page, pageSize, total })
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// 岗位配比分析
router.get('/position-distribution', async (req: AuthRequest, res) => {
  try {
    const result = await query(`
      SELECT org_level5 AS store_name,
        count(*) AS total_emp,
        count(*) FILTER (WHERE position LIKE '%店长%' OR position LIKE '%经理%') AS manager_count,
        count(*) FILTER (WHERE position LIKE '%厨%' OR position LIKE '%拉面%' OR position LIKE '%配菜%' OR position LIKE '%烧烤%' OR position LIKE '%凉菜%') AS kitchen_count,
        count(*) FILTER (WHERE position LIKE '%服务%' OR position LIKE '%前厅%' OR position LIKE '%收银%') AS front_count,
        count(*) FILTER (WHERE position LIKE '%兼职%') AS part_time_count,
        round(count(*) FILTER (WHERE position LIKE '%店长%' OR position LIKE '%经理%')::numeric / nullif(count(*), 0) * 100, 1) AS manager_pct,
        round(count(*) FILTER (WHERE position LIKE '%厨%' OR position LIKE '%拉面%' OR position LIKE '%配菜%' OR position LIKE '%烧烤%' OR position LIKE '%凉菜%')::numeric / nullif(count(*), 0) * 100, 1) AS kitchen_pct,
        round(count(*) FILTER (WHERE position LIKE '%服务%' OR position LIKE '%前厅%' OR position LIKE '%收银%')::numeric / nullif(count(*), 0) * 100, 1) AS front_pct
      FROM salary_detail_records
      WHERE org_level2 = '西部马华品牌门店' AND org_level5 IS NOT NULL AND org_level5 != ''
      GROUP BY org_level5
      ORDER BY total_emp DESC
    `)
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// ============ Tab4: 排班建议 ============

// 排班建议（基于历史客流规律，分岗位）
router.get('/scheduling-suggestion', async (req: AuthRequest, res) => {
  try {
    const storeName = req.query.store as string || '七里庄店'
    const frontTarget = parseInt(req.query.front_target as string) || 15
    const kitchenTarget = parseInt(req.query.kitchen_target as string) || 25
    const month = parseMonth(req)

    const result = await query(`
      WITH daily_hourly AS (
        SELECT extract(hour FROM c175::timestamp)::int AS hour,
          CASE WHEN extract(dow FROM c175::timestamp)::int IN (0, 6) THEN '周末' ELSE '工作日' END AS day_type,
          DATE(c175::timestamp) AS bill_date,
          count(*) AS bills
        FROM bill_records
        WHERE c003 = $1 AND c175 IS NOT NULL AND c175 != ''
          AND c175::timestamp >= $4::date AND c175::timestamp < $4::date + interval '1 month'
        GROUP BY hour, day_type, bill_date
      ),
      hourly_stats AS (
        SELECT hour, day_type,
          round(avg(bills)::numeric, 0) AS avg_daily_bills,
          round(percentile_cont(0.85) WITHIN GROUP (ORDER BY bills)::numeric, 0) AS p85_bills,
          round((max(bills) - min(bills))::numeric / nullif(avg(bills), 0), 2) AS volatility
        FROM daily_hourly
        GROUP BY hour, day_type
      ),
      punch_times AS (
        SELECT employee_code, position, d.day_num, d.day_val
        FROM attendance_records ar
        CROSS JOIN LATERAL (
          SELECT 1 AS day_num, ar.day_01 AS day_val UNION ALL
          SELECT 2, ar.day_02 UNION ALL
          SELECT 3, ar.day_03 UNION ALL
          SELECT 4, ar.day_04 UNION ALL
          SELECT 5, ar.day_05 UNION ALL
          SELECT 6, ar.day_06 UNION ALL
          SELECT 7, ar.day_07 UNION ALL
          SELECT 8, ar.day_08 UNION ALL
          SELECT 9, ar.day_09 UNION ALL
          SELECT 10, ar.day_10 UNION ALL
          SELECT 11, ar.day_11 UNION ALL
          SELECT 12, ar.day_12 UNION ALL
          SELECT 13, ar.day_13 UNION ALL
          SELECT 14, ar.day_14 UNION ALL
          SELECT 15, ar.day_15 UNION ALL
          SELECT 16, ar.day_16 UNION ALL
          SELECT 17, ar.day_17 UNION ALL
          SELECT 18, ar.day_18 UNION ALL
          SELECT 19, ar.day_19 UNION ALL
          SELECT 20, ar.day_20 UNION ALL
          SELECT 21, ar.day_21 UNION ALL
          SELECT 22, ar.day_22 UNION ALL
          SELECT 23, ar.day_23 UNION ALL
          SELECT 24, ar.day_24 UNION ALL
          SELECT 25, ar.day_25 UNION ALL
          SELECT 26, ar.day_26 UNION ALL
          SELECT 27, ar.day_27 UNION ALL
          SELECT 28, ar.day_28 UNION ALL
          SELECT 29, ar.day_29 UNION ALL
          SELECT 30, ar.day_30
        ) d
        WHERE ar.department LIKE '%' || replace($1, '总', '') || '%'
          AND d.day_val IS NOT NULL AND d.day_val != ''
      ),
      all_punches AS (
        SELECT employee_code, position, day_num,
          (regexp_matches(day_val, '(\\d{2}:\\d{2})', 'g'))[1]::time AS punch_time
        FROM punch_times
      ),
      daily_range AS (
        SELECT employee_code, position, day_num,
          min(punch_time) AS first_punch,
          max(punch_time) AS last_punch
        FROM all_punches
        GROUP BY employee_code, position, day_num
      ),
      role_classify AS (
        SELECT employee_code, day_num, first_punch, last_punch,
          CASE
            WHEN position LIKE '%店长%' OR position LIKE '%经理%' OR position LIKE '储备店长%' THEN '管理'
            WHEN position LIKE '%服务员%' THEN '前厅服务'
            WHEN position LIKE '%厨师%' OR position LIKE '%厨工%' OR position LIKE '%拉面师%'
              OR position LIKE '%配菜师%' OR position LIKE '%凉菜师%' OR position LIKE '%烧烤师%' THEN '后厨'
            ELSE '其他'
          END AS role
        FROM daily_range
      ),
      hourly_staff AS (
        SELECT h.hour, rc.day_num, rc.role, count(DISTINCT rc.employee_code) AS staff_on_duty
        FROM generate_series(6, 23) AS h(hour)
        CROSS JOIN role_classify rc
        WHERE rc.first_punch <= (h.hour || ':00')::time
          AND rc.last_punch >= (h.hour || ':00')::time
        GROUP BY h.hour, rc.day_num, rc.role
      ),
      current_staff AS (
        SELECT hour, role, round(avg(staff_on_duty))::int AS avg_staff
        FROM hourly_staff
        GROUP BY hour, role
      ),
      current_other AS (
        SELECT hour, round(sum(avg_staff))::int AS avg_staff
        FROM current_staff
        WHERE role IN ('管理', '其他')
        GROUP BY hour
      ),
      current_total AS (
        SELECT hour, round(sum(avg_staff))::int AS avg_staff
        FROM current_staff
        GROUP BY hour
      )
      SELECT hs.hour,
        hs.day_type,
        hs.avg_daily_bills,
        hs.p85_bills,
        hs.volatility,
        GREATEST(ceil(hs.p85_bills / $2), CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END) AS suggested_front,
        GREATEST(ceil(hs.p85_bills / $3), CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END) AS suggested_kitchen,
        CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END AS suggested_manager,
        (GREATEST(ceil(hs.p85_bills / $2), CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END)
          + GREATEST(ceil(hs.p85_bills / $3), CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END)
          + CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END) AS suggested_total,
        COALESCE(cs_front.avg_staff, 0) AS current_front,
        COALESCE(cs_kitchen.avg_staff, 0) AS current_kitchen,
        COALESCE(co.avg_staff, 0) AS current_other,
        COALESCE(ct.avg_staff, 0) AS current_total,
        (GREATEST(ceil(hs.p85_bills / $2), CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END)
          + GREATEST(ceil(hs.p85_bills / $3), CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END)
          + CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END
          - COALESCE(ct.avg_staff, 0)) AS staff_gap,
        CASE
          WHEN (GREATEST(ceil(hs.p85_bills / $2), CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END)
            + GREATEST(ceil(hs.p85_bills / $3), CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END)
            + CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END
            - COALESCE(ct.avg_staff, 0)) > 2 THEN '需增配'
          WHEN (GREATEST(ceil(hs.p85_bills / $2), CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END)
            + GREATEST(ceil(hs.p85_bills / $3), CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END)
            + CASE WHEN hs.hour BETWEEN 10 AND 22 THEN 1 ELSE 0 END
            - COALESCE(ct.avg_staff, 0)) < -2 THEN '可减配'
          ELSE '配置合理'
        END AS action
      FROM hourly_stats hs
      LEFT JOIN current_staff cs_front ON hs.hour = cs_front.hour AND cs_front.role = '前厅服务'
      LEFT JOIN current_staff cs_kitchen ON hs.hour = cs_kitchen.hour AND cs_kitchen.role = '后厨'
      LEFT JOIN current_other co ON hs.hour = co.hour
      LEFT JOIN current_total ct ON hs.hour = ct.hour
      ORDER BY hs.hour, hs.day_type
    `, [storeName, frontTarget, kitchenTarget, month])
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// ============ Tab5: 考勤预警 ============

// 考勤异常预警
router.get('/attendance-alert', async (req: AuthRequest, res) => {
  try {
    const result = await query(`
      SELECT s.org_level5 AS store_name,
        s.employee_code,
        s.position,
        s.actual_attend AS salary_attend_days,
        s.expected_attend,
        round(abs(s.actual_attend - COALESCE(a.punch_days, 0))::numeric, 1) AS attend_diff,
        s.absent_days,
        s.absent_deduction,
        s.late_deduction,
        s.no_punch_deduction,
        s.personal_leave_days,
        s.actual_hours,
        CASE
          WHEN s.absent_days > 0 THEN '旷工'
          WHEN s.actual_attend = 0 AND COALESCE(a.punch_days, 0) > 0 THEN '薪资出勤为零但有打卡'
          WHEN abs(s.actual_attend - COALESCE(a.punch_days, 0)) > 5 THEN '出勤天数偏差大'
          WHEN s.late_deduction > 0 OR s.no_punch_deduction > 0 THEN '考勤扣款'
          WHEN s.actual_attend / nullif(s.expected_attend, 0) < 0.8 THEN '出勤率低'
          ELSE NULL
        END AS alert_type,
        CASE
          WHEN s.absent_days > 0 THEN 'red'
          WHEN s.actual_attend = 0 AND COALESCE(a.punch_days, 0) > 0 THEN 'red'
          WHEN abs(s.actual_attend - COALESCE(a.punch_days, 0)) > 5 THEN 'orange'
          WHEN s.late_deduction > 0 OR s.no_punch_deduction > 0 THEN 'yellow'
          WHEN s.actual_attend / nullif(s.expected_attend, 0) < 0.8 THEN 'yellow'
          ELSE NULL
        END AS alert_level
      FROM salary_detail_records s
      LEFT JOIN LATERAL (
        SELECT count(*) AS punch_days
        FROM attendance_records ar
        CROSS JOIN LATERAL unnest(ARRAY[
          ar.day_01, ar.day_02, ar.day_03, ar.day_04, ar.day_05,
          ar.day_06, ar.day_07, ar.day_08, ar.day_09, ar.day_10,
          ar.day_11, ar.day_12, ar.day_13, ar.day_14, ar.day_15,
          ar.day_16, ar.day_17, ar.day_18, ar.day_19, ar.day_20,
          ar.day_21, ar.day_22, ar.day_23, ar.day_24, ar.day_25,
          ar.day_26, ar.day_27, ar.day_28, ar.day_29, ar.day_30
        ]) AS d(day_val)
        WHERE ar.employee_code = s.employee_code
          AND day_val IS NOT NULL AND day_val != ''
      ) a ON true
      WHERE s.org_level2 = '西部马华品牌门店'
        AND s.org_level5 IS NOT NULL AND s.org_level5 != ''
        AND (
          s.absent_days > 0
          OR (s.actual_attend = 0 AND COALESCE(a.punch_days, 0) > 0)
          OR abs(s.actual_attend - COALESCE(a.punch_days, 0)) > 5
          OR s.late_deduction > 0
          OR s.no_punch_deduction > 0
          OR s.actual_attend / nullif(s.expected_attend, 0) < 0.8
        )
      ORDER BY
        CASE WHEN s.absent_days > 0 THEN 0
             WHEN s.actual_attend = 0 AND COALESCE(a.punch_days, 0) > 0 THEN 1
             WHEN abs(s.actual_attend - COALESCE(a.punch_days, 0)) > 5 THEN 2
             ELSE 3 END,
        s.org_level5, s.employee_code
      LIMIT 200
    `)
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// 门店考勤汇总
router.get('/attendance-summary', async (req: AuthRequest, res) => {
  try {
    const result = await query(`
      SELECT s.org_level5 AS store_name,
        count(*) AS emp_count,
        round(avg(s.actual_attend)::numeric, 1) AS avg_attend_days,
        round(avg(s.actual_hours)::numeric, 0) AS avg_hours,
        round(sum(s.absent_days)::numeric, 0) AS total_absent_days,
        round(sum(s.absent_deduction)::numeric, 2) AS total_absent_deduction,
        round(sum(s.late_deduction)::numeric, 2) AS total_late_deduction,
        round(sum(s.no_punch_deduction)::numeric, 2) AS total_no_punch_deduction,
        count(*) FILTER (WHERE s.absent_days > 0) AS absent_emp_count,
        count(*) FILTER (WHERE s.late_deduction > 0 OR s.no_punch_deduction > 0) AS punch_issue_count,
        round(count(*) FILTER (WHERE s.actual_attend / nullif(s.expected_attend, 0) < 0.8)::numeric / nullif(count(*), 0) * 100, 1) AS low_attendance_rate_pct
      FROM salary_detail_records s
      WHERE s.org_level2 = '西部马华品牌门店' AND s.org_level5 IS NOT NULL AND s.org_level5 != ''
      GROUP BY s.org_level5
      ORDER BY total_absent_days DESC NULLS LAST, total_late_deduction DESC NULLS LAST
    `)
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// ============ Tab6: 员工分析 ============

// 员工薪资分析
router.get('/employee-analysis', async (req: AuthRequest, res) => {
  try {
    const { page, pageSize, offset } = parsePagination(req)
    const sort = (req.query.sort as string) || 'gross_pay'
    const month = parseMonth(req)
    const order = (req.query.order as string) || 'desc'
    const storeName = req.query.store as string

    const validSorts: Record<string, string> = {
      gross_pay: 'gross_pay',
      net_pay: 'net_pay',
      actual_hours: 'actual_hours',
      perf_amount: 'perf_amount',
      overtime_pay: 'overtime_pay',
      hourly_rate: 'hourly_rate',
    }
    const sortField = validSorts[sort] || 'gross_pay'
    const sortOrder = order === 'asc' ? 'ASC' : 'DESC'

    let where = "WHERE org_level2 = '西部马华品牌门店' AND org_level5 IS NOT NULL AND org_level5 != ''"
    const params: any[] = []
    if (storeName) {
      params.push(storeName)
      where += ` AND org_level5 = $${params.length}`
    }
    // 将 2026-04 转为 2026年4月 格式匹配 salary_period
    const [yr, mo] = month.split('-')
    const periodLabel = `${parseInt(yr)}年${parseInt(mo)}月`
    params.push(periodLabel)
    where += ` AND salary_period = $${params.length}`

    const countResult = await query(`SELECT count(*) FROM salary_detail_records ${where}`, params)
    const total = countResult.rows[0].count

    params.push(month)
    const monthParam = `$${params.length}`

    params.push(pageSize, offset)
    const result = await query(`
      SELECT employee_code,
        org_level5 AS store_name,
        position,
        employment_type,
        hire_date,
        leave_date,
        salary_period,
        round(base_wage::numeric, 2) AS base_wage,
        round(overtime_pay::numeric, 2) AS overtime_pay,
        round(perf_amount::numeric, 2) AS perf_amount,
        round(perf_score::numeric, 2) AS perf_score,
        round(hourly_rate::numeric, 2) AS hourly_rate,
        round(gross_pay::numeric, 2) AS gross_pay,
        round(net_pay::numeric, 2) AS net_pay,
        round(actual_attend::numeric, 1) AS attend_days,
        round(actual_hours::numeric, 0) AS work_hours,
        round(overtime_pay / nullif(gross_pay, 0) * 100, 2) AS overtime_rate,
        round(perf_amount / nullif(gross_pay, 0) * 100, 2) AS perf_rate,
        round(gross_pay / nullif(actual_hours, 0), 2) AS effective_hourly_rate,
        CASE
          WHEN leave_date IS NOT NULL AND leave_date != '' AND leave_date != '0' AND leave_date >= ${monthParam} THEN '离职'
          WHEN hire_date IS NOT NULL AND hire_date != '' AND hire_date >= ${monthParam} THEN '新员工'
          ELSE '在职'
        END AS emp_status
      FROM salary_detail_records
      ${where}
      ORDER BY ${sortField} ${sortOrder}
      LIMIT $${params.length - 1} OFFSET $${params.length}
    `, params)
    sendSuccess(res, result.rows, { page, pageSize, total })
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// 岗位薪资对比
router.get('/position-salary-compare', async (req: AuthRequest, res) => {
  try {
    const month = parseMonth(req)
    const [yr, mo] = month.split('-')
    const periodLabel = `${parseInt(yr)}年${parseInt(mo)}月`
    const minCount = req.user?.role === 'store' ? 1 : 5
    const result = await query(`
      SELECT position,
        count(*) AS emp_count,
        count(DISTINCT org_level5) AS store_count,
        round(avg(gross_pay)::numeric, 2) AS avg_gross,
        round(min(gross_pay)::numeric, 2) AS min_gross,
        round(max(gross_pay)::numeric, 2) AS max_gross,
        round(avg(net_pay)::numeric, 2) AS avg_net,
        round(avg(actual_hours)::numeric, 0) AS avg_hours,
        round(avg(perf_score)::numeric, 2) AS avg_perf_score,
        round(avg(gross_pay / nullif(actual_hours, 0))::numeric, 2) AS avg_hourly_rate
      FROM salary_detail_records
      WHERE org_level2 = '西部马华品牌门店'
        AND position IS NOT NULL AND position != ''
        AND salary_period = $1
      GROUP BY position
      HAVING count(*) >= $2
      ORDER BY avg_gross DESC
    `, [periodLabel, minCount])
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// 离职率统计
router.get('/turnover-stats', async (req: AuthRequest, res) => {
  try {
    const month = parseMonth(req)
    const [yr, mo] = month.split('-')
    const periodLabel = `${parseInt(yr)}年${parseInt(mo)}月`
    const result = await query(`
      SELECT org_level5 AS store_name,
        count(*) AS total_emp,
        count(*) FILTER (WHERE leave_date IS NOT NULL AND leave_date != '' AND leave_date != '0' AND leave_date >= $1) AS left_count,
        count(*) FILTER (WHERE hire_date IS NOT NULL AND hire_date >= $1) AS new_count,
        round(count(*) FILTER (WHERE leave_date IS NOT NULL AND leave_date != '' AND leave_date != '0' AND leave_date >= $1)::numeric / nullif(count(*), 0) * 100, 2) AS turnover_rate,
        round(count(*) FILTER (WHERE hire_date IS NOT NULL AND hire_date >= $1)::numeric / nullif(count(*), 0) * 100, 2) AS new_hire_rate
      FROM salary_detail_records
      WHERE org_level2 = '西部马华品牌门店' AND org_level5 IS NOT NULL AND org_level5 != ''
        AND salary_period = $2
      GROUP BY org_level5
      ORDER BY turnover_rate DESC NULLS LAST
    `, [month, periodLabel])
    sendSuccess(res, result.rows)
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// ============ 总体智能分析 ============

router.get('/overall-analysis', async (req: AuthRequest, res) => {
  try {
    const month = parseMonth(req)
    const [efficiency, attendance, turnover, trafficOverview, mealPeriod] = await Promise.all([
      query(`
        WITH salary_stats AS (
          SELECT org_level5 AS store_name,
            count(*) AS emp_count,
            round(sum(gross_pay)::numeric, 2) AS gross_pay,
            round(avg(actual_hours)::numeric, 0) AS avg_hours,
            round(sum(overtime_pay)::numeric, 2) AS overtime_pay
          FROM salary_detail_records
          WHERE org_level2 = '西部马华品牌门店' AND org_level5 IS NOT NULL AND org_level5 != ''
          GROUP BY org_level5
        ),
        revenue_stats AS (
          SELECT COALESCE(m.salary_name, r.store_name) AS store_name, r.revenue, r.bill_count
          FROM mv_store_revenue r
          LEFT JOIN store_name_mapping m ON m.bill_name = r.store_name
        )
        SELECT s.store_name,
          s.emp_count, s.gross_pay, s.avg_hours, s.overtime_pay,
          COALESCE(r.revenue, 0) AS revenue,
          round(COALESCE(r.revenue, 0) / nullif(s.emp_count, 0), 2) AS revenue_per_emp,
          round(s.gross_pay / nullif(COALESCE(r.revenue, 0), 0) * 100, 2) AS wage_rate,
          round(s.overtime_pay / nullif(s.gross_pay, 0) * 100, 2) AS overtime_rate
        FROM salary_stats s
        LEFT JOIN revenue_stats r ON s.store_name = r.store_name
      `),
      query(`
        SELECT org_level5 AS store_name,
          count(*) AS emp_count,
          round(avg(actual_attend)::numeric, 1) AS avg_attend_days,
          round(avg(actual_hours)::numeric, 0) AS avg_hours,
          sum(absent_days) AS total_absent,
          sum(late_deduction + no_punch_deduction) AS total_deduction,
          count(*) FILTER (WHERE absent_days > 0) AS absent_emp
        FROM salary_detail_records
        WHERE org_level2 = '西部马华品牌门店' AND org_level5 IS NOT NULL AND org_level5 != ''
        GROUP BY org_level5
      `),
      query(`
        SELECT org_level5 AS store_name,
          count(*) AS total_emp,
          count(*) FILTER (WHERE leave_date IS NOT NULL AND leave_date != '' AND leave_date != '0' AND leave_date >= $1) AS left_count,
          count(*) FILTER (WHERE hire_date IS NOT NULL AND hire_date >= $1) AS new_count,
          round(count(*) FILTER (WHERE leave_date IS NOT NULL AND leave_date != '' AND leave_date != '0' AND leave_date >= $1)::numeric / nullif(count(*), 0) * 100, 2) AS turnover_rate
        FROM salary_detail_records
        WHERE org_level2 = '西部马华品牌门店' AND org_level5 IS NOT NULL AND org_level5 != ''
        GROUP BY org_level5
      `, [month]),
      query(`
        WITH hourly AS (
          SELECT store_name, hour, sum(bills) AS bills
          FROM mv_bill_hourly GROUP BY store_name, hour
        ),
        store_stats AS (
          SELECT store_name,
            max(bills) AS peak_bills, min(bills) AS min_bills, sum(bills) AS total_bills,
            round(max(bills)::numeric / nullif(min(bills), 0), 2) AS peak_valley_ratio,
            round(sum(bills) FILTER (WHERE hour IN (11, 12, 17, 18, 19))::numeric / nullif(sum(bills), 0) * 100, 2) AS peak_concentration_pct
          FROM hourly GROUP BY store_name
        )
        SELECT store_name, total_bills, peak_bills, min_bills, peak_valley_ratio, peak_concentration_pct,
          CASE WHEN peak_valley_ratio > 20 THEN '波动极大' WHEN peak_valley_ratio > 10 THEN '波动较大' WHEN peak_valley_ratio > 5 THEN '波动适中' ELSE '波动较小' END AS volatility_level
        FROM store_stats ORDER BY total_bills DESC
      `),
      query(`
        SELECT store_name, meal_period, sum(bills) AS bills
        FROM mv_bill_hourly WHERE meal_period != ''
        GROUP BY store_name, meal_period
      `),
    ])

    const effRows = efficiency.rows
    const attRows = attendance.rows
    const turnRows = turnover.rows
    const trafficRows = trafficOverview.rows
    const mealRows = mealPeriod.rows

    // 品牌级汇总指标
    const totalStores = effRows.length
    const totalEmp = effRows.reduce((s: number, r: any) => s + parseFloat(r.emp_count), 0)
    const totalRevenue = effRows.reduce((s: number, r: any) => s + parseFloat(r.revenue), 0)
    const totalPayroll = effRows.reduce((s: number, r: any) => s + parseFloat(r.gross_pay), 0)
    const avgRevenuePerEmp = totalEmp > 0 ? totalRevenue / totalEmp : 0
    const avgWageRate = totalRevenue > 0 ? (totalPayroll / totalRevenue) * 100 : 0
    const avgOvertimeRate = totalPayroll > 0 ? (effRows.reduce((s: number, r: any) => s + parseFloat(r.overtime_pay), 0) / totalPayroll) * 100 : 0
    const totalAbsent = attRows.reduce((s: number, r: any) => s + parseFloat(r.total_absent || 0), 0)
    const totalAbsentEmp = attRows.reduce((s: number, r: any) => s + parseFloat(r.absent_emp || 0), 0)
    const totalLeft = turnRows.reduce((s: number, r: any) => s + parseFloat(r.left_count || 0), 0)
    const totalNew = turnRows.reduce((s: number, r: any) => s + parseFloat(r.new_count || 0), 0)
    const avgTurnoverRate = totalEmp > 0 ? (totalLeft / totalEmp) * 100 : 0

    // 生成诊断建议
    const insights: any[] = []

    // 1. 人效分析
    const lowEffStores = effRows.filter((r: any) => parseFloat(r.revenue_per_emp) < 20000 && parseFloat(r.revenue_per_emp) > 0).sort((a: any, b: any) => parseFloat(a.revenue_per_emp) - parseFloat(b.revenue_per_emp))
    const highEffStores = effRows.filter((r: any) => parseFloat(r.revenue_per_emp) > 50000).sort((a: any, b: any) => parseFloat(b.revenue_per_emp) - parseFloat(a.revenue_per_emp))
    if (lowEffStores.length > 0) {
      insights.push({
        category: '人效',
        level: 'red',
        title: `${lowEffStores.length}家门店人均创收低于2万`,
        detail: `人均创收最低：${lowEffStores.slice(0, 3).map((r: any) => `${r.store_name}(${Math.round(parseFloat(r.revenue_per_emp))}元)`).join('、')}`,
        suggestion: '建议排查这些门店的排班合理性，是否存在人浮于事；同时对比高人效门店的运营模式',
        metric: 'revenue_per_emp',
        value: avgRevenuePerEmp.toFixed(0),
      })
    }
    if (highEffStores.length > 0) {
      insights.push({
        category: '人效',
        level: 'green',
        title: `${highEffStores.length}家门店人均创收超5万`,
        detail: `高人效标杆：${highEffStores.slice(0, 3).map((r: any) => `${r.store_name}(${Math.round(parseFloat(r.revenue_per_emp))}元)`).join('、')}`,
        suggestion: '建议提炼高人效门店的排班模式和岗位配置经验，向其他门店推广',
        metric: 'revenue_per_emp',
        value: avgRevenuePerEmp.toFixed(0),
      })
    }

    // 2. 人力成本率
    const highWageStores = effRows.filter((r: any) => parseFloat(r.wage_rate) > 25).sort((a: any, b: any) => parseFloat(b.wage_rate) - parseFloat(a.wage_rate))
    if (highWageStores.length > 0) {
      insights.push({
        category: '成本',
        level: highWageStores.length > 10 ? 'red' : 'orange',
        title: `${highWageStores.length}家门店人力成本率超25%`,
        detail: `成本率最高：${highWageStores.slice(0, 3).map((r: any) => `${r.store_name}(${parseFloat(r.wage_rate).toFixed(1)}%)`).join('、')}`,
        suggestion: '人力成本率过高，建议优化排班减少冗余人力，或提升营收分摊固定成本',
        metric: 'wage_rate',
        value: avgWageRate.toFixed(1) + '%',
      })
    }

    // 3. 加班占比
    const highOvertimeStores = effRows.filter((r: any) => parseFloat(r.overtime_rate) > 5).sort((a: any, b: any) => parseFloat(b.overtime_rate) - parseFloat(a.overtime_rate))
    if (highOvertimeStores.length > 0) {
      insights.push({
        category: '加班',
        level: 'orange',
        title: `${highOvertimeStores.length}家门店加班费占比超5%`,
        detail: `加班最严重：${highOvertimeStores.slice(0, 3).map((r: any) => `${r.store_name}(${parseFloat(r.overtime_rate).toFixed(1)}%)`).join('、')}`,
        suggestion: '加班占比过高说明排班与客流不匹配，建议在高峰时段增加兼职或调整班次',
        metric: 'overtime_rate',
        value: avgOvertimeRate.toFixed(1) + '%',
      })
    }

    // 4. 客流波动
    const highVolatilityStores = trafficRows.filter((r: any) => r.volatility_level === '波动极大' || r.volatility_level === '波动较大')
    if (highVolatilityStores.length > 0) {
      insights.push({
        category: '客流',
        level: highVolatilityStores.length > 20 ? 'red' : 'orange',
        title: `${highVolatilityStores.length}家门店客流波动较大`,
        detail: `峰谷比最高：${highVolatilityStores.slice(0, 3).map((r: any) => `${r.store_name}(${r.peak_valley_ratio}倍)`).join('、')}`,
        suggestion: '客流波动大的门店应推行弹性排班，低谷时段减少在岗人数，高峰前提前补人',
        metric: 'peak_valley_ratio',
        value: '',
      })
    }

    // 5. 高峰集中度
    const highConcentrationStores = trafficRows.filter((r: any) => parseFloat(r.peak_concentration_pct) > 60)
    if (highConcentrationStores.length > 0) {
      insights.push({
        category: '客流',
        level: 'orange',
        title: `${highConcentrationStores.length}家门店高峰集中度超60%`,
        detail: `集中度最高：${highConcentrationStores.slice(0, 3).map((r: any) => `${r.store_name}(${r.peak_concentration_pct}%)`).join('、')}`,
        suggestion: '客流高度集中在午晚餐高峰，建议在11-13点和17-19点增加兼职力量，非高峰时段精简人员',
        metric: 'peak_concentration_pct',
        value: '',
      })
    }

    // 6. 考勤异常
    if (totalAbsentEmp > 0) {
      const absentStores = attRows.filter((r: any) => parseFloat(r.absent_emp) > 0).sort((a: any, b: any) => parseFloat(b.absent_emp) - parseFloat(a.absent_emp))
      insights.push({
        category: '考勤',
        level: 'red',
        title: `${totalAbsentEmp}名员工存在旷工`,
        detail: `旷工最多：${absentStores.slice(0, 3).map((r: any) => `${r.store_name}(${r.absent_emp}人)`).join('、')}`,
        suggestion: '旷工直接影响门店运营，建议立即核查原因并完善考勤管理制度',
        metric: 'absent_emp',
        value: totalAbsentEmp.toString(),
      })
    }

    // 7. 离职率
    if (avgTurnoverRate > 5) {
      const highTurnoverStores = turnRows.filter((r: any) => parseFloat(r.turnover_rate) > 15).sort((a: any, b: any) => parseFloat(b.turnover_rate) - parseFloat(a.turnover_rate))
      insights.push({
        category: '离职',
        level: avgTurnoverRate > 10 ? 'red' : 'orange',
        title: `品牌整体离职率${avgTurnoverRate.toFixed(1)}%`,
        detail: highTurnoverStores.length > 0
          ? `离职率最高：${highTurnoverStores.slice(0, 3).map((r: any) => `${r.store_name}(${parseFloat(r.turnover_rate).toFixed(1)}%)`).join('、')}`
          : `共${totalLeft}人离职，${totalNew}人入职`,
        suggestion: '高离职率增加招聘培训成本，建议关注离职原因，优化薪酬福利和排班制度',
        metric: 'turnover_rate',
        value: avgTurnoverRate.toFixed(1) + '%',
      })
    }

    // 8. 新员工占比
    if (totalNew > 0) {
      const newRate = totalEmp > 0 ? (totalNew / totalEmp) * 100 : 0
      if (newRate > 15) {
        insights.push({
          category: '离职',
          level: 'orange',
          title: `新员工占比${newRate.toFixed(1)}%，人员变动频繁`,
          detail: `入职${totalNew}人，离职${totalLeft}人，净流失${totalLeft - totalNew}人`,
          suggestion: '新员工占比高说明人员流动大，建议加强新员工培训和带教制度，降低试用期离职率',
          metric: 'new_hire_rate',
          value: newRate.toFixed(1) + '%',
        })
      }
    }

    // 9. 低出勤率
    const lowAttendStores = attRows.filter((r: any) => parseFloat(r.avg_attend_days) < 20 && parseFloat(r.avg_attend_days) > 0)
    if (lowAttendStores.length > 0) {
      insights.push({
        category: '考勤',
        level: 'orange',
        title: `${lowAttendStores.length}家门店平均出勤不足20天`,
        detail: `出勤最低：${lowAttendStores.slice(0, 3).map((r: any) => `${r.store_name}(${r.avg_attend_days}天)`).join('、')}`,
        suggestion: '出勤天数偏低可能存在排班不足或人员冗余，建议核查排班计划与实际出勤的差异',
        metric: 'avg_attend_days',
        value: '',
      })
    }

    // 10. 餐段结构异常
    const mealMap: Record<string, Record<string, number>> = {}
    mealRows.forEach((r: any) => {
      if (!mealMap[r.store_name]) mealMap[r.store_name] = {}
      mealMap[r.store_name][r.meal_period] = parseFloat(r.bills)
    })
    const unbalancedStores = Object.entries(mealMap).filter(([store, meals]) => {
      const m = meals as Record<string, number>
      const total = Object.values(m).reduce((s: number, v: number) => s + v, 0)
      if (total === 0) return false
      const maxPct = Math.max(...Object.values(m).map((v: number) => v / total * 100))
      return maxPct > 70
    }).map(([store]) => store)
    if (unbalancedStores.length > 0) {
      insights.push({
        category: '客流',
        level: 'yellow',
        title: `${unbalancedStores.length}家门店餐段结构极度不均衡`,
        detail: `如：${unbalancedStores.slice(0, 3).join('、')}，单一餐段占比超70%`,
        suggestion: '餐段过于集中会增加高峰排班压力，建议在非主力餐段推出促销活动平衡客流',
        metric: 'meal_balance',
        value: '',
      })
    }

    // 品牌级KPI
    const kpis = [
      { label: '门店总数', value: totalStores.toString(), unit: '家' },
      { label: '总员工数', value: totalEmp.toString(), unit: '人' },
      { label: '总营收', value: (totalRevenue / 10000).toFixed(0), unit: '万元' },
      { label: '总工资', value: (totalPayroll / 10000).toFixed(0), unit: '万元' },
      { label: '人均创收', value: Math.round(avgRevenuePerEmp).toString(), unit: '元/人' },
      { label: '人力成本率', value: avgWageRate.toFixed(1), unit: '%' },
      { label: '加班费占比', value: avgOvertimeRate.toFixed(1), unit: '%' },
      { label: '离职率', value: avgTurnoverRate.toFixed(1), unit: '%' },
      { label: '旷工人数', value: totalAbsentEmp.toString(), unit: '人' },
      { label: '离职/入职', value: `${totalLeft}/${totalNew}`, unit: '人' },
    ]

    sendSuccess(res, { kpis, insights })
  } catch (err: any) {
    sendError(res, err.message)
  }
})

// ============ 人员招聘/解聘预测（规则引擎） ============

router.get('/staffing-forecast', async (req: AuthRequest, res) => {
  try {
    const month = parseMonth(req)
    const [roleStats, storeRevenue, storeTraffic] = await Promise.all([
      query(`
        SELECT
          org_level5 AS store_name,
          CASE
            WHEN position LIKE '%店长%' OR position LIKE '%经理%' OR position LIKE '储备%' OR position LIKE '副店%' THEN '管理'
            WHEN position LIKE '%服务员%' OR position LIKE '%训练员%' OR position LIKE '%迎宾%' OR position LIKE '%传菜%' OR position LIKE '服务主管%' OR position LIKE '主管%' THEN '前厅'
            WHEN position LIKE '%厨%' OR position LIKE '%拉面%' OR position LIKE '%配菜%' OR position LIKE '%凉菜%' OR position LIKE '%烧烤%' OR position LIKE '%面点%' OR position LIKE '%面工%' OR position LIKE '%锅底%' OR position LIKE '%切肉%' OR position LIKE '%切菜%' OR position LIKE '%炒锅%' OR position LIKE '%砧板%' OR position LIKE '%打荷%' OR position LIKE '%洗碗%' OR position LIKE '%上什%' OR position LIKE '%打馕%' THEN '后厨'
            WHEN position LIKE '%兼职%' OR position LIKE '%小时工%' THEN '兼职'
            ELSE '其他'
          END AS role,
          count(*) AS emp_count,
          count(*) FILTER (WHERE leave_date IS NULL OR leave_date = '' OR leave_date = '0') AS active_count,
          round(sum(gross_pay)::numeric, 2) AS total_pay,
          round(sum(gross_pay) FILTER (WHERE leave_date IS NULL OR leave_date = '' OR leave_date = '0')::numeric, 2) AS active_pay,
          round(avg(gross_pay)::numeric, 2) AS avg_pay,
          round(avg(actual_attend)::numeric, 1) AS avg_attend,
          round(avg(actual_hours)::numeric, 0) AS avg_hours,
          round(sum(actual_hours)::numeric, 0) AS total_hours,
          count(*) FILTER (WHERE leave_date IS NOT NULL AND leave_date != '' AND leave_date != '0' AND leave_date >= $1) AS left_count,
          count(*) FILTER (WHERE hire_date IS NOT NULL AND hire_date >= $1) AS new_count
        FROM salary_detail_records
        WHERE org_level2 = '西部马华品牌门店' AND org_level5 IS NOT NULL AND org_level5 != ''
        GROUP BY 1, 2
      `, [month]),
      query(`
        SELECT s.salary_name AS store_name, r.revenue, r.bill_count
        FROM (SELECT DISTINCT org_level5 AS salary_name FROM salary_detail_records WHERE org_level2='西部马华品牌门店' AND org_level5 IS NOT NULL AND org_level5 != '') s
        LEFT JOIN store_name_mapping m ON m.salary_name = s.salary_name
        LEFT JOIN mv_store_revenue r ON r.store_name = COALESCE(m.bill_name, s.salary_name)
      `),
      query(`
        WITH hourly AS (
          SELECT store_name, hour, sum(bills) AS bills
          FROM mv_bill_hourly GROUP BY store_name, hour
        )
        SELECT store_name,
          max(bills) AS peak_bills,
          round(max(bills)::numeric / nullif(min(bills), 0), 2) AS peak_valley_ratio,
          round(sum(bills) FILTER (WHERE hour IN (11, 12, 17, 18, 19))::numeric / nullif(sum(bills), 0) * 100, 2) AS peak_concentration_pct
        FROM hourly GROUP BY store_name
      `),
    ])

    // 组装指标数据
    const revMap: Record<string, number> = {}
    storeRevenue.rows.forEach((r: any) => { revMap[r.store_name] = parseFloat(r.revenue) })
    const billMap: Record<string, number> = {}
    storeRevenue.rows.forEach((r: any) => { billMap[r.store_name] = parseInt(r.bill_count) })
    const trafficMap: Record<string, any> = {}
    storeTraffic.rows.forEach((r: any) => { trafficMap[r.store_name] = r })

    const storeEmpCount: Record<string, number> = {}
    const storeActivePay: Record<string, number> = {}
    const storeTotalHours: Record<string, number> = {}
    roleStats.rows.forEach((r: any) => {
      storeEmpCount[r.store_name] = (storeEmpCount[r.store_name] || 0) + parseInt(r.emp_count)
      storeActivePay[r.store_name] = (storeActivePay[r.store_name] || 0) + parseFloat(r.active_pay)
      storeTotalHours[r.store_name] = (storeTotalHours[r.store_name] || 0) + parseInt(r.total_hours)
    })

    // 组装每个门店×岗位的指标数据
    const items: any[] = []
    for (const r of roleStats.rows) {
      const store = r.store_name
      const role = r.role
      const empCount = parseInt(r.emp_count)
      const activeCount = parseInt(r.active_count)
      const revenue = revMap[store] || 0
      const bills = billMap[store] || 0
      const totalEmp = storeEmpCount[store] || empCount
      const totalStorePay = storeActivePay[store] || 0
      const totalStoreHours = storeTotalHours[store] || 0

      const revenuePerEmp = totalEmp > 0 ? Math.round(revenue / totalEmp) : 0
      const revenuePerHour = totalStoreHours > 0 ? Math.round(revenue / totalStoreHours) : 0
      const wageRatio = revenue > 0 ? Math.round((totalStorePay / revenue) * 1000) / 10 : 0
      const roleEmpRatio = totalEmp > 0 ? Math.round((empCount / totalEmp) * 1000) / 10 : 0
      const rolePayRatio = totalStorePay > 0 ? Math.round((parseFloat(r.active_pay) / totalStorePay) * 1000) / 10 : 0
      const turnoverPct = empCount > 0 ? Math.round((parseInt(r.left_count) / empCount) * 1000) / 10 : 0
      const traffic = trafficMap[store]
      const peakConcentration = traffic ? parseFloat(traffic.peak_concentration_pct) : 0
      const peakValley = traffic ? parseFloat(traffic.peak_valley_ratio) : 0

      items.push({
        id: `${store}|${role}`,
        store_name: store,
        role,
        emp_count: empCount,
        active_count: activeCount,
        avg_pay: parseFloat(r.avg_pay),
        avg_attend: parseFloat(r.avg_attend) || 0,
        avg_hours: parseFloat(r.avg_hours) || 0,
        total_hours: parseInt(r.total_hours) || 0,
        total_pay: parseFloat(r.active_pay),
        left_count: parseInt(r.left_count) || 0,
        new_count: parseInt(r.new_count) || 0,
        turnover_pct: turnoverPct,
        revenue_per_emp: revenuePerEmp,
        revenue_per_hour: revenuePerHour,
        wage_ratio: wageRatio,
        role_emp_ratio: roleEmpRatio,
        role_pay_ratio: rolePayRatio,
        peak_concentration: peakConcentration,
        peak_valley_ratio: peakValley,
        bill_count: bills,
      })
    }

    // === 动态标准：基于全部门店实际数据计算（用中位数，比均值更抗极端值） ===
    const storeLevelMetrics: { store: string; revenue_per_emp: number; revenue_per_hour: number; wage_ratio: number }[] = []
    Object.keys(storeEmpCount).forEach(store => {
      const rev = revMap[store] || 0
      if (rev > 0) {
        const emp = storeEmpCount[store] || 0
        const hours = storeTotalHours[store] || 0
        const pay = storeActivePay[store] || 0
        storeLevelMetrics.push({
          store,
          revenue_per_emp: emp > 0 ? Math.round(rev / emp) : 0,
          revenue_per_hour: hours > 0 ? Math.round(rev / hours) : 0,
          wage_ratio: rev > 0 ? Math.round((pay / rev) * 1000) / 10 : 0,
        })
      }
    })

    function median(arr: number[]): number {
      if (arr.length === 0) return 0
      const sorted = [...arr].sort((a, b) => a - b)
      const mid = Math.floor(sorted.length / 2)
      return sorted.length % 2 === 0 ? Math.round((sorted[mid - 1] + sorted[mid]) / 2) : sorted[mid]
    }

    const avgRevenuePerEmp = median(storeLevelMetrics.map(s => s.revenue_per_emp)) || 15000
    const avgRevenuePerHour = median(storeLevelMetrics.map(s => s.revenue_per_hour)) || 200
    const avgWageRatio = median(storeLevelMetrics.map(s => Math.round(s.wage_ratio * 10))) / 10 || 30

    // 各岗位实际平均占比
    const roleEmpTotals: Record<string, number> = {}
    const rolePayTotals: Record<string, number> = {}
    const roleAttendSum: Record<string, number> = {}
    const roleAttendCount: Record<string, number> = {}
    const roleHoursSum: Record<string, number> = {}
    const roleHoursCount: Record<string, number> = {}
    const rolePaySum: Record<string, number> = {}
    const rolePayCount: Record<string, number> = {}
    let grandTotalEmp = 0
    roleStats.rows.forEach((r: any) => {
      const role = r.role
      const empCount = parseInt(r.emp_count)
      roleEmpTotals[role] = (roleEmpTotals[role] || 0) + empCount
      rolePayTotals[role] = (rolePayTotals[role] || 0) + parseFloat(r.active_pay)
      grandTotalEmp += empCount
      if (parseFloat(r.avg_attend) > 0) {
        roleAttendSum[role] = (roleAttendSum[role] || 0) + parseFloat(r.avg_attend) * empCount
        roleAttendCount[role] = (roleAttendCount[role] || 0) + empCount
      }
      if (parseFloat(r.avg_hours) > 0) {
        roleHoursSum[role] = (roleHoursSum[role] || 0) + parseFloat(r.avg_hours) * empCount
        roleHoursCount[role] = (roleHoursCount[role] || 0) + empCount
      }
      if (parseFloat(r.avg_pay) > 0) {
        rolePaySum[role] = (rolePaySum[role] || 0) + parseFloat(r.avg_pay) * empCount
        rolePayCount[role] = (rolePayCount[role] || 0) + empCount
      }
    })

    const DYNAMIC_STANDARDS: Record<string, any> = {}
    for (const role of ['管理', '前厅', '后厨', '兼职', '其他']) {
      DYNAMIC_STANDARDS[role] = {
        role_ratio: grandTotalEmp > 0 ? Math.round((roleEmpTotals[role] / grandTotalEmp) * 1000) / 10 : 0,
        normal_pay: rolePayCount[role] > 0 ? Math.round(rolePaySum[role] / rolePayCount[role]) : 0,
        normal_attend: roleAttendCount[role] > 0 ? Math.round(roleAttendSum[role] / roleAttendCount[role]) : 24,
        normal_hours: roleHoursCount[role] > 0 ? Math.round(roleHoursSum[role] / roleHoursCount[role]) : 160,
      }
    }
    // 管理岗max_count仍用经验值4
    DYNAMIC_STANDARDS['管理'].max_count = 4
    DYNAMIC_STANDARDS['前厅'].max_count = 15
    DYNAMIC_STANDARDS['后厨'].max_count = 20
    DYNAMIC_STANDARDS['兼职'].max_count = 5
    DYNAMIC_STANDARDS['其他'].max_count = 3
    // 兼职出勤标准为0（弹性工时）
    DYNAMIC_STANDARDS['兼职'].normal_attend = 0
    DYNAMIC_STANDARDS['兼职'].min_attend = 0
    for (const role of ['管理', '前厅', '后厨', '其他']) {
      DYNAMIC_STANDARDS[role].min_attend = 20
    }

    console.log('[StaffingForecast] 动态标准:', {
      avgRevenuePerEmp, avgRevenuePerHour, avgWageRatio,
      roleRatios: Object.fromEntries(Object.entries(DYNAMIC_STANDARDS).map(([k,v]) => [k, v.role_ratio])),
    })

    // === 规则库 ===
    const STANDARDS = DYNAMIC_STANDARDS

    const roleOrder: Record<string, number> = { '管理': 0, '前厅': 1, '后厨': 2, '兼职': 3, '其他': 4 }
    const forecasts: any[] = []

    for (const item of items) {
      const std = STANDARDS[item.role] || STANDARDS['其他']
      const expectedRoleRatio = std.role_ratio

      // === 第一层：产能诊断（优化信号） ===
      const optSignals: string[] = []
      let optScore = 0

      // R1: 人均创收低于均值 + 岗位占比偏高 → 冗余
      if (item.revenue_per_emp > 0 && item.revenue_per_emp < avgRevenuePerEmp && item.role !== '兼职') {
        if (item.role_emp_ratio > expectedRoleRatio + 5) {
          optSignals.push(`门店人均创收${item.revenue_per_emp}元（均值${avgRevenuePerEmp}元），${item.role}占比${item.role_emp_ratio}%偏高（标准${expectedRoleRatio}%），岗位冗余`)
          optScore += 30
        } else {
          optSignals.push(`门店人均创收${item.revenue_per_emp}元（均值${avgRevenuePerEmp}元），差${avgRevenuePerEmp - item.revenue_per_emp}元`)
          optScore += 15
        }
      }

      // R2: 工时产能低于均值（非兼职）→ 人效偏低，门店级指标所有岗位均受约束
      if (item.revenue_per_hour > 0 && item.revenue_per_hour < avgRevenuePerHour && item.role !== '兼职') {
        optSignals.push(`工时产能${item.revenue_per_hour}元/工时（均值${avgRevenuePerHour}元），人效偏低`)
        optScore += 20
      }

      // R3: 人力成本率高于均值 + 岗位薪资占比偏高
      if (item.wage_ratio > avgWageRatio && item.role !== '兼职' && item.role_emp_ratio > expectedRoleRatio + 3) {
        optSignals.push(`人力成本率${item.wage_ratio}%（均值${avgWageRatio}%），${item.role}薪资占比${item.role_pay_ratio}%偏高`)
        optScore += 20
      }

      // R4: 岗位人数占比超配
      if (item.role_emp_ratio > expectedRoleRatio + 8) {
        optSignals.push(`${item.role}人数占比${item.role_emp_ratio}%（标准${expectedRoleRatio}%），配置偏高`)
        optScore += 15
      }

      // R5: 管理岗超配
      if (item.role === '管理' && item.emp_count > std.max_count) {
        optSignals.push(`管理岗${item.emp_count}人（标准≤${std.max_count}人），管理层冗余`)
        optScore += 20
      }

      // R6: 其他岗超配
      if (item.role === '其他' && item.emp_count > std.max_count) {
        optSignals.push(`其他岗位${item.emp_count}人（标准≤${std.max_count}人），建议明确职责或转岗`)
        optScore += 10
      }

      // R7: 工时产能低 + 出勤低 → 工时未饱和（转为优化信号）
      if (item.avg_attend > 0 && item.avg_attend < std.min_attend && item.role !== '兼职') {
        const hasLowHourly = optSignals.some(s => s.includes('工时产能'))
        if (hasLowHourly) {
          optSignals.push(`${item.role}平均出勤${item.avg_attend}天（标准≥${std.min_attend}天），工时未饱和，应增加排班而非加人`)
          optScore += 15
        }
      }

      // === 第二层：互斥判断 ===
      const hasOptSignals = optSignals.length > 0

      // === 第三层：人手诊断（招聘信号，仅无优化信号时触发） ===
      const hireSignals: string[] = []
      let hireScore = 0

      if (!hasOptSignals) {
        const roleOverstaffed = item.role_emp_ratio > expectedRoleRatio + 3

        // R8: 岗位人数占比偏低
        if (item.role_emp_ratio < expectedRoleRatio - 5 && item.role !== '兼职' && item.active_count > 0) {
          hireSignals.push(`${item.role}人数占比${item.role_emp_ratio}%（标准${expectedRoleRatio}%），配置偏低`)
          hireScore += 15
        }

        // R9: 出勤不足（占比未超配、管理岗未满编时才触发）
        const mgmtFull = item.role === '管理' && item.emp_count >= std.max_count
        if (item.avg_attend > 0 && item.avg_attend < std.min_attend && item.role !== '兼职'
            && !roleOverstaffed && !mgmtFull) {
          hireSignals.push(`${item.role}平均出勤${item.avg_attend}天（标准≥${std.min_attend}天），排班不足`)
          hireScore += 15
        }

        // R10: 离职缺口（需同时满足：岗位未超配、门店人效正常、出勤不足——出勤正常说明人手够用）
        const attendLow = item.avg_attend > 0 && item.avg_attend < std.min_attend && item.role !== '兼职'
        if (item.left_count > 0 && item.turnover_pct >= 10
            && item.role_emp_ratio <= expectedRoleRatio + 3
            && (item.revenue_per_hour === 0 || item.revenue_per_hour >= avgRevenuePerHour || item.role !== '兼职')
            && (attendLow || item.role === '兼职')) {
          hireSignals.push(`${item.role}当月离职${item.left_count}人（离职率${item.turnover_pct}%，标准<10%），需填补缺口`)
          hireScore += 25
        }

        // R11: 新员工稳定性（占比未超配+出勤不足时才触发）
        if (item.new_count > 0 && item.new_count >= item.active_count * 0.2 && attendLow && !roleOverstaffed) {
          hireSignals.push(`${item.role}当月新入职${item.new_count}人（占在职${Math.round(item.new_count / Math.max(item.active_count, 1) * 100)}%），需带教防流失`)
          hireScore += 10
        }

        // R12: 客流匹配-前厅（占比未超配时才触发）
        if (item.role === '前厅' && item.peak_concentration > 60 && item.emp_count < 10 && !roleOverstaffed) {
          hireSignals.push(`高峰集中度${item.peak_concentration}%（标准<60%），前厅仅${item.emp_count}人，高峰服务压力大`)
          hireScore += 20
        }

        // R13: 客流匹配-后厨（占比未超配时才触发）
        if (item.role === '后厨' && item.peak_valley_ratio > 10 && item.emp_count < 8 && !roleOverstaffed) {
          hireSignals.push(`客流峰谷比${item.peak_valley_ratio}倍（标准<10倍），后厨仅${item.emp_count}人，需弹性人手`)
          hireScore += 15
        }
      }

      // R14: 兼职特殊（不受互斥限制，但占比超配或人效低时不招聘）
      if (item.role === '兼职' && item.peak_concentration > 50 && item.emp_count < 3
          && item.role_emp_ratio <= expectedRoleRatio + 3
          && (item.revenue_per_hour === 0 || item.revenue_per_hour >= avgRevenuePerHour)) {
        hireSignals.push(`高峰集中度${item.peak_concentration}%，兼职仅${item.emp_count}人，建议增加兼职覆盖高峰`)
        hireScore += 15
      }

      // === 第四层：动作决策 ===
      const urgency = Math.min(optScore + hireScore, 100)
      const noRevenueData = item.revenue_per_emp === 0

      let action = '维持'
      let actionLevel = 'green'

      if (hasOptSignals && optScore >= 20) {
        action = '建议优化'
        actionLevel = optScore >= 60 ? 'red' : optScore >= 35 ? 'orange' : 'yellow'
      } else if (hireSignals.length > 0 && hireScore >= 20) {
        // 无营收数据时无法确认是否真的缺人，降级为关注
        action = noRevenueData ? '关注' : '建议招聘'
        actionLevel = noRevenueData ? 'yellow' : (hireScore >= 60 ? 'red' : hireScore >= 35 ? 'orange' : 'yellow')
      } else if (urgency >= 20) {
        action = '关注'
        actionLevel = 'yellow'
      }

      if (action === '维持' && urgency === 0) continue

      // 生成分析文本
      const analysisParts: string[] = []
      if (item.revenue_per_emp > 0) analysisParts.push(`人均创收${item.revenue_per_emp}元${item.revenue_per_emp < avgRevenuePerEmp ? '（低于中位）' : ''}`)
      if (item.revenue_per_hour > 0) analysisParts.push(`工时产能${item.revenue_per_hour}元/h${item.revenue_per_hour < avgRevenuePerHour ? '（低于中位）' : ''}`)
      if (item.wage_ratio > 0) analysisParts.push(`人力成本率${item.wage_ratio}%${item.wage_ratio > avgWageRatio ? '（高于中位）' : ''}`)
      if (item.avg_attend > 0) analysisParts.push(`出勤${item.avg_attend}天`)
      if (item.turnover_pct > 0) analysisParts.push(`离职率${item.turnover_pct}%`)
      const analysis = analysisParts.join('，') + '。'

      // 生成建议文本
      let suggestion = ''
      if (action === '建议优化') {
        if (optSignals.some(s => s.includes('工时未饱和'))) {
          suggestion = `建议对 ${item.store_name} 的 ${item.role} 岗位提高排班覆盖率，增加现有人员工时饱和度，暂不需要增编。`
        } else if (optSignals.some(s => s.includes('冗余'))) {
          suggestion = `建议对 ${item.store_name} 的 ${item.role} 岗位进行人员优化，可考虑转岗或精简，预估可节省月人力成本约 ${item.avg_pay}元/人。`
        } else if (optSignals.some(s => s.includes('人效偏低'))) {
          suggestion = `建议对 ${item.store_name} 的 ${item.role} 岗位提升人效，优化工作流程或调整排班，暂不增编。`
        } else {
          suggestion = `建议对 ${item.store_name} 的 ${item.role} 岗位进行优化调整，关注产能指标改善。`
        }
      } else if (action === '建议招聘') {
        const hireNum = Math.max(item.left_count, 1)
        suggestion = `建议为 ${item.store_name} 的 ${item.role} 岗位补充${hireNum}人，预估月人力成本增加约 ${item.avg_pay * hireNum}元。`
      } else if (action === '关注') {
        if (noRevenueData && hireSignals.length > 0) {
          suggestion = `${item.store_name} 缺少营收数据，无法评估人效。建议先录入营收数据再判断是否需要补充 ${item.role} 人员。`
        } else {
          suggestion = `建议持续关注 ${item.store_name} 的 ${item.role} 岗位的人员变动和人效表现，暂不需要立即调整。`
        }
      }

      forecasts.push({
        store_name: item.store_name,
        role: item.role,
        emp_count: item.emp_count,
        active_count: item.active_count,
        avg_pay: item.avg_pay,
        avg_attend: item.avg_attend,
        avg_hours: item.avg_hours,
        total_hours: item.total_hours,
        total_pay: item.total_pay,
        left_count: item.left_count,
        new_count: item.new_count,
        turnover_pct: item.turnover_pct,
        revenue_per_emp: item.revenue_per_emp,
        revenue_per_hour: item.revenue_per_hour,
        wage_ratio: item.wage_ratio,
        role_emp_ratio: item.role_emp_ratio,
        role_pay_ratio: item.role_pay_ratio,
        action,
        action_level: actionLevel,
        urgency,
        analysis,
        suggestion,
        hire_reasons: hireSignals.join('；'),
        optimize_reasons: optSignals.join('；'),
        reasons: [...hireSignals, ...optSignals].join('；'),
        standards: {
          normal_attend: std.normal_attend,
          min_attend: std.min_attend,
          max_count: std.max_count,
          normal_pay: std.normal_pay,
          normal_hours: std.normal_hours,
          expected_role_ratio: expectedRoleRatio,
          revenue_per_emp: avgRevenuePerEmp,
          revenue_per_hour: avgRevenuePerHour,
          wage_ratio: avgWageRatio,
          turnover: 10,
        },
      })
    }

    // 排序
    forecasts.sort((a, b) => {
      if (b.urgency !== a.urgency) return b.urgency - a.urgency
      if (a.store_name !== b.store_name) return a.store_name.localeCompare(b.store_name)
      return (roleOrder[a.role] || 99) - (roleOrder[b.role] || 99)
    })

    const hireCount = forecasts.filter(f => f.action === '建议招聘').length
    const optimizeCount = forecasts.filter(f => f.action === '建议优化').length
    const watchCount = forecasts.filter(f => f.action === '关注').length

    sendSuccess(res, {
      forecasts,
      summary: {
        total: forecasts.length,
        hire: hireCount,
        optimize: optimizeCount,
        watch: watchCount,
      },
      ruleEngine: {
        standards: {
          revenue_per_emp: avgRevenuePerEmp,
          revenue_per_hour: avgRevenuePerHour,
          wage_ratio: avgWageRatio,
          roles: Object.fromEntries(Object.entries(DYNAMIC_STANDARDS).map(([k, v]: [string, any]) => [
            k, { role_ratio: v.role_ratio, normal_pay: v.normal_pay, normal_attend: v.normal_attend, normal_hours: v.normal_hours, min_attend: v.min_attend, max_count: v.max_count }
          ])),
        },
        optimizationRules: [
          { id: 'R1', name: '人均创收低+占比偏高', condition: `人均创收 < 中位数(${avgRevenuePerEmp}元) 且 岗位占比 > 标准+5%`, score: 30, action: '优化' },
          { id: 'R1b', name: '人均创收低', condition: `人均创收 < 中位数(${avgRevenuePerEmp}元) 且 占比正常`, score: 15, action: '优化' },
          { id: 'R2', name: '工时产能低', condition: `工时产能 < 中位数(${avgRevenuePerHour}元/h) 且 非兼职`, score: 20, action: '优化' },
          { id: 'R3', name: '人力成本率高+薪资占比高', condition: `人力成本率 > 中位数(${avgWageRatio}%) 且 岗位占比 > 标准+3% 且 非兼职`, score: 20, action: '优化' },
          { id: 'R4', name: '岗位占比超配', condition: `岗位占比 > 标准+8%`, score: 15, action: '优化' },
          { id: 'R5', name: '管理岗超编', condition: `管理岗人数 > ${DYNAMIC_STANDARDS['管理'].max_count}人`, score: 20, action: '优化' },
          { id: 'R6', name: '其他岗超编', condition: `其他岗人数 > ${DYNAMIC_STANDARDS['其他'].max_count}人`, score: 10, action: '优化' },
          { id: 'R7', name: '工时未饱和', condition: `已触发R2 且 出勤 < ${DYNAMIC_STANDARDS['后厨'].min_attend}天`, score: 15, action: '优化' },
        ],
        hireRules: [
          { id: 'R8', name: '岗位占比偏低', condition: `岗位占比 < 标准-5% 且 非兼职 且 在职>0`, score: 15, action: '招聘', constraint: '无优化信号' },
          { id: 'R9', name: '出勤不足', condition: `出勤 < ${DYNAMIC_STANDARDS['后厨'].min_attend}天 且 非兼职 且 占比未超配 且 管理岗未满编`, score: 15, action: '招聘', constraint: '无优化信号' },
          { id: 'R10', name: '离职缺口', condition: `离职率≥10% 且 占比未超配 且 人效正常 且 出勤不足`, score: 25, action: '招聘', constraint: '无优化信号' },
          { id: 'R11', name: '新员工带教', condition: `新入职≥在职20% 且 出勤不足 且 占比未超配`, score: 10, action: '招聘', constraint: '无优化信号' },
          { id: 'R12', name: '前厅高峰压力', condition: `前厅 且 高峰集中度>60% 且 人数<10 且 占比未超配`, score: 20, action: '招聘', constraint: '无优化信号' },
          { id: 'R13', name: '后厨峰谷差', condition: `后厨 且 峰谷比>10倍 且 人数<8 且 占比未超配`, score: 15, action: '招聘', constraint: '无优化信号' },
          { id: 'R14', name: '兼职高峰覆盖', condition: `兼职 且 高峰集中度>50% 且 人数<3 且 占比未超配 且 人效正常`, score: 15, action: '招聘', constraint: '不受互斥限制' },
        ],
        decisionLogic: [
          { step: 1, name: '产能诊断', desc: '依次检查R1~R7，累计优化信号和分值' },
          { step: 2, name: '互斥判断', desc: '有任何优化信号→所有招聘信号(R8~R13)被抑制' },
          { step: 3, name: '人手诊断', desc: '无优化信号时检查R8~R13，累计招聘信号和分值' },
          { step: 4, name: '动作决策', desc: '优化分≥20→建议优化；招聘分≥20→建议招聘(无营收数据降级为关注)；其他≥20→关注' },
        ],
      },
    })
  } catch (err: any) {
    sendError(res, err.message)
  }
})

export default router
