devperf/backend/src/services/roi/strategy-params.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

50 lines
1.5 KiB
TypeScript

import { eq } from 'drizzle-orm';
import { db } from '../../db/index';
import { roiStrategies } from '../../db/schema';
import type { RoiCategory, StrategyParams } from './types';
const DEFAULTS: StrategyParams = {
hourlyRate: 400,
amortYears: 3,
commitHourCoef: 0.5,
taskHourCoef: 6,
};
let cache: Map<RoiCategory, StrategyParams> | null = null;
let cacheLoadedAt = 0;
const CACHE_TTL_MS = 60_000; // 1 分钟缓存,改完策略最多 1 分钟生效
async function loadCache(): Promise<Map<RoiCategory, StrategyParams>> {
const now = Date.now();
if (cache && now - cacheLoadedAt < CACHE_TTL_MS) return cache;
const rows = await db.select().from(roiStrategies);
const map = new Map<RoiCategory, StrategyParams>();
for (const row of rows) {
const p = (row.params as Partial<StrategyParams>) || {};
map.set(row.category, {
hourlyRate: p.hourlyRate ?? DEFAULTS.hourlyRate,
amortYears: p.amortYears ?? DEFAULTS.amortYears,
commitHourCoef: p.commitHourCoef ?? DEFAULTS.commitHourCoef,
taskHourCoef: p.taskHourCoef ?? DEFAULTS.taskHourCoef,
});
}
cache = map;
cacheLoadedAt = now;
return map;
}
/**
* 取某个分类的策略参数。未打标(null)时返回 cash_cow 的参数作为默认。
*/
export async function getStrategyParams(category: RoiCategory | null): Promise<StrategyParams> {
const map = await loadCache();
const cat: RoiCategory = category ?? 'cash_cow';
return map.get(cat) ?? DEFAULTS;
}
export function invalidateStrategyCache(): void {
cache = null;
cacheLoadedAt = 0;
}