feat: 新增免费演出雷达工具与定投评估功能,优化周报提醒逻辑
1. 新增免费演出雷达工具:含秀动/猫眼/大麦平台抓取、本地代理鉴权配置 2. 新增建行定投评估模块:四大指数PE-TTM估值与定投档位建议 3. 新增推送告警白名单过滤功能,限制推送指定指数告警 4. 重构周报首页提醒逻辑,移至服务端处理并减少前端请求 5. 新增多套调试探针脚本与单元测试用例
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 单元测试:markets_dip_eval 建行定投评估模块
|
||||
* 用法:node dev_test_scripts/unit/test_markets_dip_eval.js
|
||||
*/
|
||||
process.chdir(require('path').join(__dirname, '..', '..'))
|
||||
|
||||
const { getDipEvalReport } = require('../../src/server/markets_dip_eval')
|
||||
|
||||
;(async () => {
|
||||
try {
|
||||
const payload = await getDipEvalReport()
|
||||
console.log('=== 返回 JSON ===')
|
||||
console.log(JSON.stringify(payload, null, 2))
|
||||
if (!payload || payload.ok === false) {
|
||||
console.log('\n[测试结果] 整体失败(可能是网络问题,见 errors)')
|
||||
process.exit(1)
|
||||
}
|
||||
const expectKeys = ['name', 'code', 'market', 'pe_ttm', 'pe_date', 'percentile', 'source', 'advice', 'level']
|
||||
let bad = 0
|
||||
for (const row of payload.rows) {
|
||||
for (const k of expectKeys) {
|
||||
if (!(k in row)) { console.log(`[字段缺失] ${row.name} 缺少 ${k}`); bad++ }
|
||||
}
|
||||
if (!Number.isFinite(row.pe_ttm)) { console.log(`[PE无效] ${row.name} pe_ttm=${row.pe_ttm}`); bad++ }
|
||||
}
|
||||
console.log(bad === 0 ? '\n[测试结果] PASS:行数=' + payload.rows.length + ',字段完整,PE 有效' : `\n[测试结果] FAIL:${bad} 处问题`)
|
||||
process.exit(bad === 0 ? 0 : 1)
|
||||
} catch (e) {
|
||||
console.log('[测试结果] 异常崩溃:', e)
|
||||
process.exit(1)
|
||||
}
|
||||
})()
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* 单元测试:markets 推送告警白名单过滤 applyNotifyAllowlist
|
||||
* 背景:日历/邮件推送只保留 8 个指定 symbol,其余卡片只展示不推送
|
||||
* 运行:node dev_test_scripts/unit/test_markets_notify_allowlist.js
|
||||
*/
|
||||
const path = require('path')
|
||||
|
||||
// 切到仓库根目录,保证 markets.js 内 process.cwd() 相对路径正确
|
||||
process.chdir(path.join(__dirname, '..', '..'))
|
||||
|
||||
const { applyNotifyAllowlist } = require(path.join(process.cwd(), 'src', 'server', 'markets.js'))
|
||||
|
||||
const ALLOWLIST = ['sh000001', 'sz399001', 'sh000300', 'sh000688', 'sz399006', 'usINX', 'usIXIC', 'hf_GC']
|
||||
|
||||
// 模拟全量 quotes:8 个保留项 + 若干应被排除的卡片
|
||||
const mockQuotes = [
|
||||
{ symbol: 'sh000001', label: '上证指数' },
|
||||
{ symbol: 'sz399001', label: '深证成指' },
|
||||
{ symbol: 'sh000300', label: '沪深300' },
|
||||
{ symbol: 'sh000688', label: '科创50' },
|
||||
{ symbol: 'sz399006', label: '创业板指' },
|
||||
{ symbol: 'usINX', label: '标普500' },
|
||||
{ symbol: 'usIXIC', label: '纳斯达克' },
|
||||
{ symbol: 'hf_GC', label: 'COMEX黄金' },
|
||||
// 以下应被排除
|
||||
{ symbol: 'hf_SI', label: 'COMEX白银' },
|
||||
{ symbol: 'usDJI', label: '道琼斯' },
|
||||
{ symbol: 'hkHSI', label: '恒生指数' },
|
||||
{ symbol: 'bj899050', label: '北证50' },
|
||||
{ symbol: 'usEWG', label: '德国DAX30' }
|
||||
]
|
||||
|
||||
let failed = 0
|
||||
const assert = (cond, name) => {
|
||||
if (cond) {
|
||||
console.log(` PASS ${name}`)
|
||||
} else {
|
||||
failed += 1
|
||||
console.log(` FAIL ${name}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('== test: applyNotifyAllowlist ==')
|
||||
|
||||
// 1. 白名单生效:只保留 8 个
|
||||
const r1 = applyNotifyAllowlist(mockQuotes, { notify_symbol_allowlist: ALLOWLIST })
|
||||
assert(r1.kept.length === 8, '白名单模式保留 8 项')
|
||||
assert(r1.excluded.length === 5, '白名单模式排除 5 项')
|
||||
assert(r1.kept.every(q => ALLOWLIST.map(s => s.toLowerCase()).includes(String(q.symbol).toLowerCase())), '保留项全部在白名单内')
|
||||
assert(r1.excluded.every(q => !ALLOWLIST.map(s => s.toLowerCase()).includes(String(q.symbol).toLowerCase())), '排除项全部不在白名单内')
|
||||
|
||||
// 2. 大小写不敏感
|
||||
const r2 = applyNotifyAllowlist([{ symbol: 'USINX' }, { symbol: 'hf_gc' }], { notify_symbol_allowlist: ALLOWLIST })
|
||||
assert(r2.kept.length === 2, 'symbol 匹配大小写不敏感')
|
||||
|
||||
// 3. 空白名单 = 不限制(兼容旧配置)
|
||||
const r3 = applyNotifyAllowlist(mockQuotes, { notify_symbol_allowlist: [] })
|
||||
assert(r3.kept.length === mockQuotes.length && r3.excluded.length === 0, '空白名单不过滤')
|
||||
|
||||
// 4. 缺省字段 = 不限制
|
||||
const r4 = applyNotifyAllowlist(mockQuotes, {})
|
||||
assert(r4.kept.length === mockQuotes.length, '缺省字段不过滤')
|
||||
|
||||
// 5. 异常输入不崩溃
|
||||
const r5 = applyNotifyAllowlist(null, { notify_symbol_allowlist: ALLOWLIST })
|
||||
assert(Array.isArray(r5.kept) && r5.kept.length === 0, 'quotes 为 null 时不崩溃')
|
||||
|
||||
console.log(failed === 0 ? '\nALL TESTS PASSED' : `\n${failed} TEST(S) FAILED`)
|
||||
process.exit(failed === 0 ? 0 : 1)
|
||||
Reference in New Issue
Block a user