'use strict'; /** * 智能问数智能体 · 跨主体对比工具 * ---------------------------------------------------------------------------- * cross_entity:湖内全部主体(多账号/多食堂)本期 vs 基期核心指标对比, * 供「跨校对比」「哪家餐厅更好」类问题。窗口锚定当前主体的湖内区间, * 无本期数据的主体自动滤除并说明。 */ const { ymdDash } = require('./lake.js'); const z2 = (n) => String(n).padStart(2, '0'); function dayShift(ymd, delta) { const d = new Date(Number(ymd.slice(0, 4)), Number(ymd.slice(4, 6)) - 1, Number(ymd.slice(6, 8))); d.setDate(d.getDate() + delta); return `${d.getFullYear()}${z2(d.getMonth() + 1)}${z2(d.getDate())}`; } function entitySanitize(args, ctx) { const a = args || {}; const { dateRange } = ctx; const dates = ctx.svc.store.datesInLake(ctx.entityKey); let from = /^\d{8}$/.test(String(a.from || '')) ? String(a.from) : ''; let to = /^\d{8}$/.test(String(a.to || '')) ? String(a.to) : ''; from = from && from >= dateRange.from && from <= dateRange.to ? from : ''; to = to && to >= dateRange.from && to <= dateRange.to ? to : ''; if (!from || !to || from > to) { if (!dates.length) return { empty: true }; to = dates[dates.length - 1]; from = dates[Math.max(0, dates.length - 7)]; } const len = Math.max(1, Math.round( (new Date(Number(to.slice(0, 4)), Number(to.slice(4, 6)) - 1, Number(to.slice(6, 8))) - new Date(Number(from.slice(0, 4)), Number(from.slice(4, 6)) - 1, Number(from.slice(6, 8)))) / 86400000) + 1); const prevTo = dayShift(from, -1); const prevFrom = dayShift(prevTo, -(len - 1)); return { from, to, prevFrom, prevTo, empty: false }; } function windowTotals(svc, key, from, to) { const rows = svc.store.queryRows(key, { from, to, scope: 'shop' }); const s = svc.summarizeRows(rows); return { gross: s.grossAmt, orders: s.validOrderCnt, days: s.dayCount, hasData: rows.length > 0 }; } function entityHandler(ctx, args) { const { svc } = ctx; if (args.empty) return { summary: '当前主体湖内暂无数据,无法跨主体对比。', data: { empty: true } }; const { from, to, prevFrom, prevTo } = args; const entities = svc.store.listEntities(); const out = []; const skipped = []; for (const e of entities) { const cur = windowTotals(svc, e.key, from, to); if (!cur.hasData) { skipped.push(e.name || e.key); continue; } const prev = windowTotals(svc, e.key, prevFrom, prevTo); out.push({ key: e.key, name: e.name || e.key, shopCount: e.shopCount || 0, cur, prev, grossDeltaPct: prev.gross > 0 ? +(((cur.gross - prev.gross) / prev.gross) * 100).toFixed(1) : null, }); } out.sort((a, b) => b.cur.gross - a.cur.gross); const lines = [`跨主体对比(本期 ${ymdDash(from)} ~ ${ymdDash(to)} vs 上期 ${ymdDash(prevFrom)} ~ ${ymdDash(prevTo)},按本期营业额降序):`]; for (const e of out) { const delta = e.grossDeltaPct == null ? '上期无数据' : `环比 ${e.grossDeltaPct >= 0 ? '+' : ''}${e.grossDeltaPct}%`; lines.push(` ${e.name}(${e.shopCount} 店):营业额 ${Math.round(e.cur.gross)} / 有效订单 ${Math.round(e.cur.orders)}(${delta})`); } if (skipped.length) lines.push(`本期无数据未纳入:${skipped.join('、')}`); if (!out.length) lines.push('湖内没有任何主体在本期有数据。'); return { summary: lines.join('\n'), data: { from, to, prevFrom, prevTo, entities: out, skipped } }; } module.exports = { entitySanitize, entityHandler };