Files
xianyu-hunter-hermes-skill/project/xianyu-hunter/tools/hermes_report.mjs
T

108 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dirname, '..');
const TASKS_FILE = path.join(ROOT, 'data', 'tasks.json');
const REPORT_DIR = path.join(ROOT, 'reports');
function parseArgs(argv) {
const args = { limit: 30 };
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === '--task' || a === '--task-id') args.taskId = argv[++i];
else if (a === '--limit') args.limit = Number(argv[++i] || 30);
else if (a === '--out') args.out = argv[++i];
}
return args;
}
function priceNumber(value) {
const m = String(value || '').replace(/,/g, '').match(/\d+(?:\.\d+)?/);
return m ? Number(m[0]) : null;
}
function riskFlags(item, task) {
const text = `${item.title || ''} ${item.description || ''}`.toLowerCase();
const flags = [];
const badWords = ['维修', '求购', '租', '回收', '展示机', '资源机', '监管机', '扩容', '改装', '仅拆封包装'];
for (const w of badWords) if (text.includes(w)) flags.push(`疑似${w}`);
if (/配件|零件|外壳|保护壳|贴膜/.test(text) && !/配件齐|原装配件|全套配件/.test(text)) flags.push('疑似配件/零件');
if (!item.description) flags.push('详情描述为空');
if (item.wantCount && /\d/.test(item.wantCount)) flags.push(`热度: ${item.wantCount}`);
const p = priceNumber(item.detailPrice || item.price);
if (task.config?.priceMax && p && p > task.config.priceMax) flags.push(`超预算(${p} > ${task.config.priceMax})`);
if (item.sellerRating) flags.push(item.sellerRating);
if (item.localCoarseReason) flags.push(`粗筛: ${item.localCoarseReason}`);
if (item.localFineReason) flags.push(`细筛: ${item.localFineReason}`);
return [...new Set(flags)].slice(0, 8);
}
function scoreItem(item, task) {
let score = 60;
const flags = riskFlags(item, task);
score -= flags.filter(f => /疑似|为空|超预算/.test(f)).length * 15;
if (item.description && item.description.length > 30) score += 8;
if (item.sellerRating && /好评/.test(item.sellerRating)) score += 5;
if (item.images?.length) score += Math.min(item.images.length, 5);
return Math.max(0, Math.min(100, score));
}
function renderTask(task, limit) {
const items = [...(task.products?.fineFiltered || [])]
.map(item => ({ item, score: scoreItem(item, task), flags: riskFlags(item, task) }))
.sort((a, b) => b.score - a.score)
.slice(0, limit);
const lines = [];
lines.push(`# ${task.name || task.id} — Hermes 候选分析报告`);
lines.push('');
lines.push(`- 任务ID\`${task.id}\``);
lines.push(`- 阶段:${task.stage}`);
lines.push(`- 关键词:${(task.config?.queries || []).join('、') || '-'}`);
lines.push(`- 需求:${task.config?.customRequirements || '-'}`);
lines.push(`- 数量:采集 ${task.products?.raw?.length || 0} / 粗筛 ${task.products?.coarseFiltered?.length || 0} / 候选 ${task.products?.fineFiltered?.length || 0}`);
lines.push('');
if (items.length === 0) {
lines.push('暂无可分析候选。');
return lines.join('\n');
}
lines.push('## 优先查看');
lines.push('');
for (const { item, score, flags } of items) {
const price = item.detailPrice || item.price || '-';
const loc = item.sellerLocation || '-';
const href = item.href || item.url || '';
lines.push(`### ${score}分|${item.title || '(无标题)'}`);
lines.push(`- 价格:${price}`);
lines.push(`- 地区/卖家:${loc}${item.sellerName ? ` / ${item.sellerName}` : ''}`);
if (href) lines.push(`- 链接:${href}`);
if (item.description) lines.push(`- 描述:${String(item.description).replace(/\s+/g, ' ').slice(0, 220)}`);
if (flags.length) lines.push(`- 风险/备注:${flags.join('')}`);
lines.push('- 建议:人工打开链接核对成色、拆修、序列号/保修、配件与交易方式;当前工具不会自动联系卖家。');
lines.push('');
}
return lines.join('\n');
}
const args = parseArgs(process.argv.slice(2));
if (!fs.existsSync(TASKS_FILE)) {
console.error(`未找到 ${TASKS_FILE}`);
process.exit(1);
}
const tasks = JSON.parse(fs.readFileSync(TASKS_FILE, 'utf-8'));
const task = args.taskId ? tasks.find(t => t.id === args.taskId) : tasks[tasks.length - 1];
if (!task) {
console.error(`未找到任务: ${args.taskId || '(latest)'}`);
process.exit(1);
}
const report = renderTask(task, args.limit);
fs.mkdirSync(REPORT_DIR, { recursive: true });
const out = args.out || path.join(REPORT_DIR, `${task.id}-hermes-report.md`);
fs.writeFileSync(out, report, 'utf-8');
console.log(out);