Commit 3fb81481 by luoqi

feat(persona): recompute-persona 新增 --clinics=X,Y 按诊所收窄重算范围

按诊所补摄(cold-import --clinics)后,只重算新灌入那批患者,免全 host 白扫。
从 patient_transactions.clinic_id 反查患者集(与 cold-import --clinics 同口径),
与 --concurrency / --shard 组合。plans 侧不需要:runAllForHost 已并发+预取,
全量 ~5min 且幂等,直接全量跑即可。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
parent 953d321e
Pipeline #3364 failed in 0 seconds
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
* pnpm recompute-persona # 默认 host=demo,所有 active patient * pnpm recompute-persona # 默认 host=demo,所有 active patient
* pnpm recompute-persona -- --host=friday # 指定 host * pnpm recompute-persona -- --host=friday # 指定 host
* pnpm recompute-persona -- --pid=P001 # 单 patient(host 内 externalId) * pnpm recompute-persona -- --pid=P001 # 单 patient(host 内 externalId)
* pnpm recompute-persona -- --host=jvs-dw --clinics=<orgId> --concurrency=16 # 按诊所补摄后定向重算
*/ */
import { NestFactory } from '@nestjs/core'; import { NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
...@@ -21,6 +22,7 @@ interface Args { ...@@ -21,6 +22,7 @@ interface Args {
force?: boolean; // 跳过水位幂等闸:算法/特征变更后(数据没变)也强制重算 force?: boolean; // 跳过水位幂等闸:算法/特征变更后(数据没变)也强制重算
concurrency: number; // 并发度:每患者 recompute 含 ~15 次 DB 往返,并发可重叠 I/O 等待 concurrency: number; // 并发度:每患者 recompute 含 ~15 次 DB 往返,并发可重叠 I/O 等待
shard?: { i: number; k: number }; // 分片 i/k:多进程并行(吃多核),按患者序号取模 shard?: { i: number; k: number }; // 分片 i/k:多进程并行(吃多核),按患者序号取模
clinics?: string[]; // --clinics=X,Y:只重算"在这些诊所有过操作"的患者(按诊所补摄后定向重算,免全 host 白扫)
} }
function parseArgs(argv: string[]): Args { function parseArgs(argv: string[]): Args {
...@@ -37,6 +39,8 @@ function parseArgs(argv: string[]): Args { ...@@ -37,6 +39,8 @@ function parseArgs(argv: string[]): Args {
} else if (a.startsWith('--shard=')) { } else if (a.startsWith('--shard=')) {
const m = a.slice('--shard='.length).match(/^(\d+)\/(\d+)$/); const m = a.slice('--shard='.length).match(/^(\d+)\/(\d+)$/);
if (m) args.shard = { i: Number(m[1]), k: Number(m[2]) }; if (m) args.shard = { i: Number(m[1]), k: Number(m[2]) };
} else if (a.startsWith('--clinics=')) {
args.clinics = a.slice('--clinics='.length).split(',').map((s) => s.trim()).filter(Boolean);
} }
} }
return args; return args;
...@@ -56,10 +60,26 @@ async function bootstrap() { ...@@ -56,10 +60,26 @@ async function bootstrap() {
const host = await prisma.host.findUnique({ where: { name: args.host } }); const host = await prisma.host.findUnique({ where: { name: args.host } });
if (!host) throw new Error(`Host '${args.host}' not found`); if (!host) throw new Error(`Host '${args.host}' not found`);
// --clinics:把重算范围收窄到"在这些诊所有过操作(patient_transactions.clinic_id)"的患者。
// 和 cold-import --clinics 同口径,用于按诊所补摄后只重算新灌入的那批,避免全 host 白扫。
let clinicPatientIds: string[] | undefined;
if (args.clinics?.length) {
const rows = await prisma.patientTransaction.findMany({
where: { hostId: host.id, clinicId: { in: args.clinics } },
select: { patientId: true },
distinct: ['patientId'],
});
const ids = rows.map((r) => r.patientId).filter((x): x is string => !!x);
logger.log(`--clinics=${args.clinics.join(',')} → 命中 ${ids.length} 位患者(按诊所收窄)`);
if (ids.length === 0) throw new Error('--clinics 未命中任何患者(诊所 id 是否正确 / 是否已摄入?)');
clinicPatientIds = ids;
}
let patients = await prisma.patient.findMany({ let patients = await prisma.patient.findMany({
where: { where: {
hostId: host.id, hostId: host.id,
active: true, active: true,
...(clinicPatientIds ? { id: { in: clinicPatientIds } } : {}),
...(args.pids?.length ? { externalId: { in: args.pids } } : args.pid ? { externalId: args.pid } : {}), ...(args.pids?.length ? { externalId: { in: args.pids } } : args.pid ? { externalId: args.pid } : {}),
}, },
select: { id: true, externalId: true, name: true }, select: { id: true, externalId: true, name: true },
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment