后端: - 事件流模型(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>
50 lines
1.5 KiB
TypeScript
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;
|
|
}
|