devperf/backend/scripts/ai-tag-all.ts
zyc 5af612e3fd feat(roi): ROI 动态规则引擎 v1 + 业务体系归属
后端:
- 事件流模型(project_cost_events / project_revenue_events)+ launchedAt 截断
- 3 大业务体系归属(airhubs/airflow/aircore) + 项目类型(hw/sw) + identifier 自动生成
- AI 三件套推荐(category + bizSystem + projectType)
- 营收 mock API + 外部对接规范 + 资产摊销 cron
- 5 个 migration(0003 ROI 引擎 / 0004 driver factors / 0005 biz system)
- 单测 11/11 过

前端:
- 项目级 ROI 看板:4 KPI 卡片 + 折线图(周/月/年)+ 成本/产出事件流并排
- 全公司决策罗盘:3 大 ROI 指标 + 业务线堆叠 + 分类筛选 chip
- 项目列表 + 侧边栏:按产品线分组(可折叠 + localStorage 持久化)
- Admin: ROI 策略配置 + 项目映射 + 未映射收容

数据:
- 23 项目全部 AI 自动分类 + 自动 identifier(airhubs-hw-001 这种)
- launchedAt 按各项目首次 commit 时间设置

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 13:20:22 +08:00

61 lines
2.1 KiB
TypeScript

/**
* 一次性脚本:用 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);