/** * 一次性迁移:把「当前全库 Ypt 三分位」定格写入每个材料的 health_level 字段。 * * - 公共 + 自建材料合为一个总体计算三分位(与线上分档口径一致) * - 数据完整的材料写入 A/B/C;任一 be_area 缺失的材料写入 null("—") * - 覆盖自建材料原有的手动 health_level,并给公共材料补上 * - 幂等:可重复运行(每次按当前数据重新定格) * * 运行(本地):npx dotenv -e .env.development -- npx ts-node scripts/migrate-health-level.ts * 运行(线上 iapips2):npx dotenv -e .env -- npx ts-node scripts/migrate-health-level.ts * (DATABASE_URL 由 dotenv 注入;确认指向目标库后再执行) */ import { PrismaClient } from '@prisma/client' import { computeYpt, buildTierClassifier } from '../src/common/material-ranking' const db = new PrismaClient() const BE_SELECT = { methanal_be_area: true, tvoc_be_area: true, benzene_be_area: true, toluene_be_area: true, p_xylene_be_area: true } as const async function main() { // SelfMaterial 主键是复合键 [material_id, creator_id],需一并取出 const [pub, self] = await Promise.all([ db.publicMaterial.findMany({ select: { material_id: true, ...BE_SELECT } }), db.selfMaterial.findMany({ select: { material_id: true, creator_id: true, ...BE_SELECT } }) ]) const classify = buildTierClassifier([...pub, ...self]) const dist: Record = { A: 0, B: 0, C: 0, 'null': 0 } for (const m of pub) { const tier = classify(computeYpt(m)) dist[tier ?? 'null']++ await db.publicMaterial.update({ where: { material_id: m.material_id }, data: { health_level: tier } }) } for (const m of self) { const tier = classify(computeYpt(m)) dist[tier ?? 'null']++ await db.selfMaterial.updateMany({ where: { material_id: m.material_id, creator_id: m.creator_id }, data: { health_level: tier } }) } console.log(`migrate-health-level done: public=${pub.length}, self=${self.length}`) console.log(`分档分布 -> A:${dist.A} B:${dist.B} C:${dist.C} 缺值(null):${dist['null']}`) } main() .catch(e => { console.error('migrate failed:', e) process.exitCode = 1 }) .finally(() => db.$disconnect())