'use strict'; /** * 智能问数 · 深度分析报告(确定性生成,零模型) * ---------------------------------------------------------------------------- * GET /api/agent/report-deep?entity&from&to → 自包含 HTML: * KPI 概览 / 逐日序列 / 门店排行 / 环比归因 / 数据体检 verdicts / 统计异常 / 同行对标。 * 全部章节复用问数工具的确定性口径(同源不打折),build_report 工具的 deep 旗指向这里。 */ const nlQuery = require('../../nl-query.js'); const lake = require('./tools/lake.js'); const cross = require('./tools/cross.js'); const compare = require('./tools/compare.js'); const anomaly = require('./tools/anomaly.js'); const attribution = require('./tools/attribution.js'); const peer = require('./tools/peer.js'); function esc(v) { return String(v ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c])); } function buildDeepReportHtml({ entity, from, to, svc }) { const key = svc.store.sanitizeKey(entity); const meta = svc.store.getEntity(key) || {}; const dateRange = nlQuery.lakeDateRange(key, svc); const f = from && /^\d{8}$/.test(String(from)) ? String(from) : dateRange.from; const t = to && /^\d{8}$/.test(String(to)) ? String(to) : dateRange.to; const ctx = { svc, entityKey: key, meta, shops: meta.shops || [], dateRange }; const safe = (fn) => { try { return fn(); } catch (e) { return { summary: String(e.message || e), data: null }; } }; const quality = safe(() => lake.qualityHandler(ctx, lake.qualitySanitize({ from: f, to: t }, ctx))); const crossr = safe(() => cross.crossHandler(ctx, cross.crossSanitize({ from: f, to: t }, ctx))); const cmp = safe(() => compare.compareHandler(ctx, compare.compareSanitize({ from: f, to: t }, ctx))); const an = safe(() => anomaly.anomalyHandler(ctx, anomaly.anomalySanitize({ metric: 'grossAmt', from: f, to: t }, ctx))); const attr = safe(() => attribution.attributionHandler(ctx, attribution.attributionSanitize({ from: f, to: t }, ctx))); const bench = safe(() => peer.benchHandler(ctx, peer.benchSanitize({ field: 'gross_amt', from: f, to: t }, ctx))); const trend = safe(() => svc.aggregateReport({ entity: key, from: f, to: t, group: 'date' })); const byShop = safe(() => svc.aggregateReport({ entity: key, from: f, to: t, group: 'shop' })); const statusColor = { ok: '#16a34a', warn: '#d97706', fail: '#dc2626' }; const verdictRows = (verdicts) => (verdicts || []).map((v) => ` ${esc(v.status === 'ok' ? '通过' : v.status === 'warn' ? '警告' : '不一致')} ${esc(v.check)}${esc(v.detail || '')}`).join(''); const dayRows = ((trend && trend.rows) || []).map((r) => ` ${esc(String(r.date).replace(/^(\d{4})(\d{2})(\d{2})$/, '$1-$2-$3'))} ${Math.round(r.grossAmt || 0).toLocaleString('zh-CN')} ${Math.round(r.validOrderCnt || 0).toLocaleString('zh-CN')} ${Math.round(r.expUv || 0).toLocaleString('zh-CN')} ${((r.clkExpRate || 0) * 100).toFixed(2)}%`).join(''); const shopRows = ((byShop && byShop.rows) || []).slice(0, 20).map((r, i) => ` ${i + 1}${esc(r.shopName || r.shopId)} ${Math.round(r.grossAmt || 0).toLocaleString('zh-CN')} ${Math.round(r.validOrderCnt || 0).toLocaleString('zh-CN')} ${((r.clkExpRate || 0) * 100).toFixed(2)}%`).join(''); const cmpRows = ((cmp.data && cmp.data.deltas) || []).map((d) => ` ${esc(d.label)}${Math.round(d.cur * 100) / 100}${Math.round(d.prev * 100) / 100} ${d.deltaPct == null ? '—' : (d.deltaPct >= 0 ? '+' : '') + (d.deltaPct * 100).toFixed(1) + '%'}`).join(''); const attrLines = attr && attr.summary ? attr.summary.split('\n').slice(1).map((l) => `
  • ${esc(l)}
  • `).join('') : '
  • 归因不可用
  • '; const anomalyLines = an && an.summary ? an.summary.split('\n').slice(1).map((l) => `
  • ${esc(l)}
  • `).join('') : '
  • 不可用
  • '; const benchLines = bench && bench.summary ? bench.summary.split('\n').slice(1).map((l) => `
  • ${esc(l)}
  • `).join('') : '
  • 不可用
  • '; const qualityList = (quality.data && quality.data.findings || []).map((fd) => `
  • [${esc(fd.level)}] ${esc(fd.check)}:${esc(fd.detail)}
  • `).join(''); return ` 深度分析报告 · ${esc(meta.name || key)} ${f}-${t}

    深度分析报告 · ${esc(meta.name || key)}

    区间 ${f}-${t}(生成于 ${new Date().toLocaleString('zh-CN')})· 确定性口径生成,数字与问数工具同源

    一、数据体检

    二、核心指标与环比

    ${cmpRows || ''}
    指标本期上期变化
    不可用

    三、营业额归因(LMDI)

    四、统计异常检测

    五、同行对标(营业额)

    六、逐日明细

    ${dayRows || ''}
    日期营业额有效订单曝光进店率
    无数据

    七、门店排行(Top 20)

    ${shopRows || ''}
    #门店营业额有效订单进店率
    无数据

    八、校验结论汇总

    ${verdictRows((quality.data && quality.data.verdicts) || [])} ${verdictRows((crossr.data && crossr.data.verdicts) || [])} ${verdictRows((cmp.data && cmp.data.verdicts) || [])} ${verdictRows((an.data && an.data.verdicts) || [])}
    状态校验说明
    `; } module.exports = { buildDeepReportHtml };