Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
pac
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ai-tools
pac
Commits
f4426d4b
Commit
f4426d4b
authored
Aug 30, 2026
by
luoqi
Browse files
Options
Browse Files
Download
Plain Diff
merge: 预取消 O(患者数) 项 + 三查询并行 → test
parents
28b30afc
66c54b46
Pipeline
#3642
failed in 0 seconds
Changes
1
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
59 additions
and
49 deletions
+59
-49
apps/pac-service/src/modules/plan/engine/plan-engine.service.ts
+59
-49
No files found.
apps/pac-service/src/modules/plan/engine/plan-engine.service.ts
View file @
f4426d4b
...
...
@@ -930,24 +930,20 @@ export class PlanEngineService {
const snoozedByPatient = new Map<string, Map<string, Date>>();
const personaByPatient = new Map<string, string>();
const lastVisitClinicByPatient = new Map<string, string>();
const CHUNK = 2000;
for (let i = 0; i < patientIds.length; i += CHUNK) {
const ids = patientIds.slice(i, i + CHUNK);
// latest plan(含 reasons)— 每患者最高 version 那条(对齐 upsertPlan 的 orderBy version desc)
const plans = await this.prisma.followupPlan.findMany({
where: { hostId: scope.hostId, tenantId: scope.tenantId, patientId: { in: ids } },
include: { reasons: true },
orderBy: [{ patientId: 'asc' }, { version: 'desc' }],
});
for (const p of plans) {
if (p.patientId && !latestByPatient.has(p.patientId)) latestByPatient.set(p.patientId, p);
}
// snooze 抑制集(对齐 fetchSnoozedSignalKeys:终态 + 冷静期未到期 plan 的 reason → 结案锚点)
const terminal = await this.prisma.followupPlan.findMany({
// ⭐ snooze 抑制集**提到循环外一次查完** —— 它的代价跟「终态且冷静期未到期的计划数」走,
// 跟患者数无关。2026-08-30 生产实测:全库符合条件的只有 **106 行**,
// 而原来按 chunk 查会跑 274 次(54.8 万患者 / 2000),**273 次是在查空**。
// 索引 (status, …) 前导 status,completed/abandoned 是稀有态 → Bitmap 扫 42 buffers / 0.6ms。
//
// 🔴 这不是省常数,是**消掉一个 O(患者数) 项** —— 原写法到 200 万患者会变成 1000 次往返,
// 新写法仍是 1 次。生产是百万级且在涨,这类项必须按规模而不是按当下耗时来判断。
// 口径不变:map 只会被本批患者查到,多取的那些患者条目不影响任何判定。
{
const terminalAll = await this.prisma.followupPlan.findMany({
where: {
hostId: scope.hostId,
tenantId: scope.tenantId,
patientId: { in: ids },
status: { in: ['completed', 'abandoned'] },
snoozedUntil: { gt: now },
},
...
...
@@ -955,49 +951,63 @@ export class PlanEngineService {
patientId: true,
updatedAt: true,
reasons: { select: { scenario: true, subKey: true } },
// 结案锚点 = 结案 execution.createdAt(不可变;updatedAt 会被召回反馈等后续写顶后,仅兜底)
executions: { orderBy: { createdAt: 'desc' }, take: 1, select: { createdAt: true } },
},
});
{
const plansByPatient = new Map<string, typeof terminal>();
for (const t of terminal) {
if (!t.patientId) continue;
const arr = plansByPatient.get(t.patientId) ?? [];
arr.push(t);
plansByPatient.set(t.patientId, arr);
}
for (const [pid, plans] of plansByPatient) {
snoozedByPatient.set(pid, buildSnoozeAnchors(plans));
}
const plansByPatient = new Map<string, typeof terminalAll>();
for (const t of terminalAll) {
if (!t.patientId) continue;
const arr = plansByPatient.get(t.patientId) ?? [];
arr.push(t);
plansByPatient.set(t.patientId, arr);
}
for (const [pid, plans] of plansByPatient) {
snoozedByPatient.set(pid, buildSnoozeAnchors(plans));
}
}
const CHUNK = 2000;
for (let i = 0; i < patientIds.length; i += CHUNK) {
const ids = patientIds.slice(i, i + CHUNK);
// ⭐ 三条彼此独立,**并行发** —— 原来是串行,每 chunk 的墙钟 = 三条之和;
// 并行后 = 最慢那条。三条都只读、无共享状态,并行不改任何口径。
// 并发度就是 3(不随患者数涨),不会挤爆连接池(Prisma 默认池 = 核数×2+1)。
const [plans, personas, visits] = await Promise.all([
// latest plan(含 reasons)— 每患者最高 version 那条(对齐 upsertPlan 的 orderBy version desc)
this.prisma.followupPlan.findMany({
where: { hostId: scope.hostId, tenantId: scope.tenantId, patientId: { in: ids } },
include: { reasons: true },
orderBy: [{ patientId: 'asc' }, { version: 'desc' }],
}),
// active persona id(每患最新 active 版本)
this.prisma.persona.findMany({
where: { patientId: { in: ids }, supersededAt: null },
orderBy: [{ patientId: 'asc' }, { version: 'desc' }],
select: { id: true, patientId: true },
}),
// 每患最后一次到诊(encounter/emr)所在诊所 → 跟进归属(见 upsertPlan 说明)。
// DISTINCT ON 取 occurred_at 最新那条;跟"就诊冷静期"用同一 type 口径,语义一致。
this.prisma.$queryRaw<Array<{ patient_id: string; clinic_id: string }>>`
SELECT
DISTINCT
ON
(
patient_id
)
patient_id
,
clinic_id
FROM
patient_facts
WHERE
host_id
=
$
{
scope
.
hostId
}::
uuid
AND
tenant_id
=
$
{
scope
.
tenantId
}
AND
patient_id
=
ANY
(
$
{
ids
}::
uuid
[])
AND
type
IN
(
'encounter_record'
,
'emr_record'
)
AND
occurred_at
IS
NOT
NULL
AND
clinic_id
IS
NOT
NULL
AND
clinic_id
<>
''
AND
superseded_at
IS
NULL
ORDER
BY
patient_id
,
occurred_at
DESC
`,
]);
for (const p of plans) {
if (p.patientId && !latestByPatient.has(p.patientId)) latestByPatient.set(p.patientId, p);
}
// active persona id(每患最新 active 版本)
const personas = await this.prisma.persona.findMany({
where: { patientId: { in: ids }, supersededAt: null },
orderBy: [{ patientId: 'asc' }, { version: 'desc' }],
select: { id: true, patientId: true },
});
for (const pe of personas) {
if (pe.patientId && !personaByPatient.has(pe.patientId)) {
personaByPatient.set(pe.patientId, pe.id);
}
}
// 每患最后一次到诊(encounter/emr)所在诊所 → 跟进归属(见 upsertPlan 说明)。
// DISTINCT ON 取 occurred_at 最新那条;跟"就诊冷静期"用同一 type 口径,语义一致。
const visits = await this.prisma.$queryRaw<
Array<{ patient_id: string; clinic_id: string }>
>`
SELECT
DISTINCT
ON
(
patient_id
)
patient_id
,
clinic_id
FROM
patient_facts
WHERE
host_id
=
$
{
scope
.
hostId
}::
uuid
AND
tenant_id
=
$
{
scope
.
tenantId
}
AND
patient_id
=
ANY
(
$
{
ids
}::
uuid
[])
AND
type
IN
(
'encounter_record'
,
'emr_record'
)
AND
occurred_at
IS
NOT
NULL
AND
clinic_id
IS
NOT
NULL
AND
clinic_id
<>
''
AND
superseded_at
IS
NULL
ORDER
BY
patient_id
,
occurred_at
DESC
`;
for (const v of visits) lastVisitClinicByPatient.set(v.patient_id, v.clinic_id);
}
// ⭐ 一次查完「客服碰过哪些单」—— 只问带 assignment_id 的那些(通常是很小的子集),
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment