metrics.js 3.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. 'use strict';
  2. /**
  3. * 智能问数 · 指标层
  4. * ----------------------------------------------------------------------------
  5. * 把「指标 = 维度 × 聚合 × 时间粒度 × 口径」从列注册表升级为一等对象:
  6. * · 基础指标:合计 / 加权(比率按曝光或进店人数加权)/ 比值(客单价)
  7. * · 派生指标:基于基础指标动态计算(7 日移动平均等),模型引用指标 key,
  8. * 不自己组合口径——flash 模型引用比自算的出错率低一个量级。
  9. * 与 REPORT_COLUMNS 的关系:列注册表服务表格语义(label/同义词/类型),
  10. * 本注册表服务问数聚合语义(怎么算),两者键保持一致。
  11. */
  12. const BASE = {
  13. grossAmt: { name: '营业额', unit: 'money', agg: 'sum', synonyms: ['流水', 'GMV', '销售额'] },
  14. validOrderCnt: { name: '有效订单', unit: 'int', agg: 'sum', synonyms: ['单量', '订单量'] },
  15. incomeAmt: { name: '收入', unit: 'money', agg: 'sum', synonyms: ['到账'] },
  16. expenseAmt: { name: '支出', unit: 'money', agg: 'sum', synonyms: ['费用'] },
  17. expUv: { name: '曝光人数', unit: 'int', agg: 'sum', synonyms: ['曝光'] },
  18. orderUserCnt: { name: '下单人数', unit: 'int', agg: 'sum', synonyms: ['购买人数'] },
  19. actOrdCnt: { name: '活动订单', unit: 'int', agg: 'sum' },
  20. actSubsidyAmt: { name: '活动补贴', unit: 'money', agg: 'sum' },
  21. clkExpRate: { name: '进店转化率', unit: 'pct', agg: 'weighted', weightBy: 'expUv' },
  22. ordClkRate: { name: '下单转化率', unit: 'pct', agg: 'weighted', weightBy: 'orderUserCnt' },
  23. perActualPay: { name: '客单价', unit: 'money', agg: 'ratio', num: 'grossAmt', den: 'validOrderCnt' },
  24. };
  25. /** 派生指标:基于基础指标按窗口动态计算(按天粒度) */
  26. const DERIVED = {
  27. gross_mov7: { name: '营业额7日移动平均', unit: 'money', base: 'grossAmt', window: 7 },
  28. orders_mov7: { name: '有效订单7日移动平均', unit: 'int', base: 'validOrderCnt', window: 7 },
  29. uv_mov7: { name: '曝光人数7日移动平均', unit: 'int', base: 'expUv', window: 7 },
  30. };
  31. const ALL = { ...BASE, ...DERIVED };
  32. function get(key) { return ALL[key] || null; }
  33. /** 常驻提示词:聚合口径一句话说清(含派生指标) */
  34. function manifestText() {
  35. const lines = Object.entries(ALL).map(([key, m]) => {
  36. const how = m.agg === 'sum' ? '合计'
  37. : m.agg === 'weighted' ? `加权平均(按${(BASE[m.weightBy] || {}).name || m.weightBy}加权)`
  38. : m.agg === 'ratio' ? `比值(${(BASE[m.num] || {}).name || m.num} / ${(BASE[m.den] || {}).name || m.den})`
  39. : `${m.window} 日移动平均(基于${(BASE[m.base] || {}).name || m.base})`;
  40. return `- ${key}|${m.name}|${how}`;
  41. });
  42. return lines.join('\n');
  43. }
  44. /** 单日聚合:把当天门店行聚成一个数(口径与 summarizeRows 同源——比率加权) */
  45. function dayValue(key, rows) {
  46. const m = ALL[key];
  47. if (!m || !rows.length) return null;
  48. if (m.agg === 'weighted') {
  49. let wv = 0;
  50. let w = 0;
  51. for (const r of rows) {
  52. const weight = Number(r[m.weightBy]) || 0;
  53. wv += (Number(r[key]) || 0) * weight;
  54. w += weight;
  55. }
  56. return w > 0 ? +(wv / w).toFixed(4) : null;
  57. }
  58. if (m.agg === 'ratio') {
  59. const num = rows.reduce((s, r) => s + (Number(r[m.num]) || 0), 0);
  60. const den = rows.reduce((s, r) => s + (Number(r[m.den]) || 0), 0);
  61. return den > 0 ? +(num / den).toFixed(2) : null;
  62. }
  63. return Math.round(rows.reduce((s, r) => s + (Number(r[key]) || 0), 0) * 100) / 100;
  64. }
  65. module.exports = { BASE, DERIVED, ALL, get, manifestText, dayValue };