/** * 一次性脚本:用 AI 给所有未打标的项目自动建议 + 应用 category。 * 已打标的项目跳过(避免覆盖人工决策)。 * - 默认立项日:取项目 created_at(若有)否则用 today-90天 * - 护城河项目需要 V_asset,AI 推荐 moat 时默认填 100,000(占位,管理员后续手填) * 用法: bun run scripts/ai-tag-all.ts [--force] */ import dayjs from 'dayjs'; import { eq } from 'drizzle-orm'; import { db } from '../src/db/index'; import { projects } from '../src/db/schema'; import { suggestProjectTag } from '../src/services/roi/ai-tag-suggester'; const force = process.argv.includes('--force'); const all = await db.select().from(projects); console.log(`Total projects: ${all.length}, force=${force}`); let okCount = 0, skipCount = 0, failCount = 0; const startedAt = Date.now(); for (const p of all) { const label = `${p.identifier || '?'} (${p.name})`; if (!force && p.category) { console.log(` ⊘ SKIP ${label} — already tagged as ${p.category}`); skipCount += 1; continue; } try { console.log(` → AI ${label} ...`); const sug = await suggestProjectTag(p.id); const launchedAt = p.launchedAt ?? p.createdAt ?? dayjs().subtract(90, 'day').toDate(); // 护城河默认 V_asset 占位 const needsAsset = sug.suggestedCategory === 'moat'; const vAsset = needsAsset ? (p.vAsset ?? 100_000) : (p.vAsset ?? null); await db.update(projects).set({ category: sug.suggestedCategory, launchedAt: launchedAt as any, vAsset: vAsset, updatedAt: new Date(), }).where(eq(projects.id, p.id)); console.log(` ✓ ${sug.suggestedCategory} (conf=${sug.confidence}) — ${sug.reasoning.slice(0, 60)}`); okCount += 1; } catch (e) { console.error(` ✗ FAIL ${label}: ${(e as Error).message.slice(0, 200)}`); failCount += 1; } // 1 秒间隔避免 LLM 限流 await new Promise(r => setTimeout(r, 1000)); } const elapsed = ((Date.now() - startedAt) / 1000).toFixed(1); console.log(''); console.log(`Done. ok=${okCount} skipped=${skipCount} failed=${failCount} elapsed=${elapsed}s`); process.exit(0);