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
69eede94
Commit
69eede94
authored
Jun 02, 2026
by
luoqi
Browse files
Options
Browse Files
Download
Plain Diff
merge: 权益身份特征 + 专属客服摄入
parents
d005735f
d8285684
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
203 additions
and
6 deletions
+203
-6
apps/pac-service/data/jvs-dw/assemblers/patient.yaml
+4
-0
apps/pac-service/data/jvs-dw/assemblers/payment.yaml
+4
-0
apps/pac-service/src/modules/persona/features/entitlement-status.feature.ts
+134
-0
apps/pac-service/src/modules/persona/features/feature.interface.ts
+3
-0
apps/pac-service/src/modules/persona/features/feature.registry.ts
+4
-1
apps/pac-service/src/modules/persona/persona.module.ts
+2
-0
apps/pac-service/src/modules/sync/cold-import/cold-import.service.ts
+3
-4
apps/pac-service/src/modules/sync/pipeline/fact-content-schemas.ts
+2
-0
apps/pac-service/src/modules/sync/pipeline/parsers/payment.parser.ts
+4
-0
apps/pac-service/src/modules/sync/pipeline/patient-preferences.util.ts
+28
-0
apps/pac-service/src/modules/sync/pipeline/pipeline-dispatcher.service.ts
+3
-1
apps/pac-web/src/components/plan-detail/persona-feature-hover.tsx
+10
-0
packages/types/src/enums/index.ts
+1
-0
packages/types/src/labels.ts
+1
-0
No files found.
apps/pac-service/data/jvs-dw/assemblers/patient.yaml
View file @
69eede94
...
@@ -16,4 +16,8 @@ field_mapping:
...
@@ -16,4 +16,8 @@ field_mapping:
birthDate
:
birthday
birthDate
:
birthday
medicalRecordNumber
:
file_num
# 病历号(辅助标识,客服沟通用)
medicalRecordNumber
:
file_num
# 病历号(辅助标识,客服沟通用)
notes
:
follow_content
notes
:
follow_content
# 专属客服(当前任务负责人)— 路由/指派属性,非画像特征。upsert 时并入 patients.preferences.dedicatedCs。
# 时效:"当前"会换 → 存当前值(upsert 覆盖);用于详情页展示 + 推送人匹配。
dedicatedCsName
:
current_task_director
dedicatedCsId
:
current_task_director_id
# doNotContact / deceased 不映射 — 走 PatientCanonicalSchema default false
# doNotContact / deceased 不映射 — 走 PatientCanonicalSchema default false
apps/pac-service/data/jvs-dw/assemblers/payment.yaml
View file @
69eede94
...
@@ -30,6 +30,10 @@ field_mapping:
...
@@ -30,6 +30,10 @@ field_mapping:
# - 跟 design.md 患者价值定义对齐:LTV = 患者带来的业务量,不是现金流
# - 跟 design.md 患者价值定义对齐:LTV = 患者带来的业务量,不是现金流
amount
:
receivable_this
# FieldMapper.normalize 按 amount_unit=yuan 转 cents
amount
:
receivable_this
# FieldMapper.normalize 按 amount_unit=yuan 转 cents
method
:
payment_channel
# transforms.pick_first_nonzero 推断的主导支付通道
method
:
payment_channel
# transforms.pick_first_nonzero 推断的主导支付通道
# 商业保险公司名(settlement_mode_out.insurance_name)— 喂 persona「权益身份」特征。
# 保司名脏(57 个别名),归一在 entitlement-status.feature 的 canonicalInsurer 里做;
# 此处原样带入 fact.content.insurance_name(单一收口),非空即"商保结算"强信号。
insuranceName
:
insurance_name
doctorId
:
doctor_id
# 收费医生(医患关系信号)
doctorId
:
doctor_id
# 收费医生(医患关系信号)
encounterExternalId
:
registration_id
# 关联接诊(语义准:registration_id = 接诊号)
encounterExternalId
:
registration_id
# 关联接诊(语义准:registration_id = 接诊号)
# orderExternalId 不映射:host 没真正的"医嘱单 id"概念,留 null 等其他 host(charge_order)填
# orderExternalId 不映射:host 没真正的"医嘱单 id"概念,留 null 等其他 host(charge_order)填
apps/pac-service/src/modules/persona/features/entitlement-status.feature.ts
0 → 100644
View file @
69eede94
import
{
Injectable
}
from
'@nestjs/common'
;
import
{
PersonaFeatureKey
}
from
'@pac/types'
;
import
type
{
ActiveFact
,
FeatureExtractor
,
FeatureExtractorContext
,
PersonaFeatureDraft
,
}
from
'./feature.interface'
;
/**
* entitlement_status 权益身份(商保 / 医保 …)
*
* ⭐ 本特征是「事实投影型」,跟 value / recall_risk(计算分档型)性质不同:
* - 不引入业务阈值判断,只把"用过哪类保险结算"的事实 rollup 出来
* - 商业保险**强时效**(雇主团险换工作即失效;实测 61% 患者最近一次商保 >2 年前)
* 且 DW 无保单有效期字段 → **不断言"当前在保"**,只产「史 + 最近日期」,
* 时效判断留给读取方(UI 按 lastInsuranceAt 变措辞,scorer 按 monthsSinceLast 套窗口)
*
* 数据来源(payment_record fact,单一收口 fact.content):
* - 商保:channel='insurance' 或 content.insurance_name 非空(阶段2 re-ingest 后才有保司名)
* - 医保:channel='medical_insurance'(社保普惠,**不当 VIP**,仅记录)
*
* 落库(零迁移):description 人读串 / score=monthsSinceLast 给排序 /
* evidence.data 放结构化明细(insurers / lastInsuranceAt …)给 scorer 与 UI 结构化消费。
*
* 未命中(既无商保也无医保结算)→ 返回 null,不打标签。
*/
@
Injectable
()
export
class
EntitlementStatusFeatureExtractor
implements
FeatureExtractor
{
readonly
key
=
PersonaFeatureKey
.
ENTITLEMENT_STATUS
;
/** 保司名归一(57 个脏名 → canonical;别名折叠 + 排除测试数据)。
* 阶段1(未 re-ingest)content.insurance_name 为空 → 列表自然为空,不影响"是否商保"判定。 */
private
static
canonicalInsurer
(
raw
:
unknown
):
string
|
null
{
const
s
=
String
(
raw
??
''
).
trim
();
if
(
!
s
)
return
null
;
if
(
/测试|test|xxx|宣南书馆|乐雅健康科技|乐牙/i
.
test
(
s
))
return
null
;
// 测试/非保险脏数据
const
ALIAS
:
[
RegExp
,
string
][]
=
[
[
/万欣和|MSH/i
,
'万欣和'
],
[
/招商信诺|信诺|CIGNA/i
,
'招商信诺'
],
[
/平安/i
,
'平安健康险'
],
[
/中意/i
,
'中意人寿'
],
[
/太保安联|太平洋|CPIC/i
,
'太保安联'
],
[
/安态|AETNA/i
,
'安态'
],
[
/柏盛/i
,
'柏盛健康'
],
[
/保柏|BUPA/i
,
'保柏'
],
[
/友邦|AIA/i
,
'友邦'
],
[
/中间带|Medilink/i
,
'中间带'
],
[
/安联|ALLIANZ/i
,
'安联'
],
[
/吉倍吉|GBG/i
,
'吉倍吉'
],
[
/工银安盛/i
,
'工银安盛'
],
[
/复星|FOSUN/i
,
'复星联合'
],
[
/方胜|FESCO/i
,
'方胜'
],
[
/江泰/i
,
'江泰救援'
],
[
/休荪|HSC/i
,
'休荪'
],
[
/安顾|ERV/i
,
'安顾援助'
],
[
/泰康/i
,
'泰康养老'
],
];
for
(
const
[
re
,
name
]
of
ALIAS
)
if
(
re
.
test
(
s
))
return
name
;
const
zh
=
s
.
split
(
'/'
)[
0
]
!
.
trim
();
return
zh
||
null
;
}
private
monthsSince
(
from
:
Date
,
now
:
Date
):
number
{
return
Math
.
max
(
0
,
Math
.
floor
((
now
.
getTime
()
-
from
.
getTime
())
/
(
1000
*
60
*
60
*
24
*
30.4375
)));
}
private
isCommercial
(
c
:
Record
<
string
,
unknown
>
):
boolean
{
return
c
.
channel
===
'insurance'
||
!!
String
(
c
.
insurance_name
??
''
).
trim
();
}
extract
(
ctx
:
FeatureExtractorContext
):
PersonaFeatureDraft
|
null
{
const
payments
=
ctx
.
factsByType
.
get
(
'payment_record'
)
??
[];
const
commercialFactIds
:
string
[]
=
[];
const
insurerSet
=
new
Set
<
string
>
();
let
lastCommercialAt
:
Date
|
null
=
null
;
let
lastMedicalAt
:
Date
|
null
=
null
;
const
medicalFactIds
:
string
[]
=
[];
for
(
const
f
of
payments
as
ActiveFact
[])
{
const
c
=
(
f
.
content
??
{})
as
Record
<
string
,
unknown
>
;
const
at
=
f
.
occurredAt
??
null
;
if
(
this
.
isCommercial
(
c
))
{
commercialFactIds
.
push
(
f
.
id
);
const
ins
=
EntitlementStatusFeatureExtractor
.
canonicalInsurer
(
c
.
insurance_name
);
if
(
ins
)
insurerSet
.
add
(
ins
);
if
(
at
&&
(
!
lastCommercialAt
||
at
>
lastCommercialAt
))
lastCommercialAt
=
at
;
}
else
if
(
c
.
channel
===
'medical_insurance'
)
{
medicalFactIds
.
push
(
f
.
id
);
if
(
at
&&
(
!
lastMedicalAt
||
at
>
lastMedicalAt
))
lastMedicalAt
=
at
;
}
}
const
hasCommercial
=
commercialFactIds
.
length
>
0
;
const
hasMedical
=
medicalFactIds
.
length
>
0
;
if
(
!
hasCommercial
&&
!
hasMedical
)
return
null
;
// 未命中,不打标签
const
insurers
=
[...
insurerSet
].
sort
();
const
fmtYm
=
(
d
:
Date
|
null
)
=>
(
d
?
`
${
d
.
getFullYear
()}
-
${
String
(
d
.
getMonth
()
+
1
).
padStart
(
2
,
'0'
)}
`
:
null
);
const
monthsCommercial
=
lastCommercialAt
?
this
.
monthsSince
(
lastCommercialAt
,
ctx
.
now
)
:
null
;
const
monthsMedical
=
lastMedicalAt
?
this
.
monthsSince
(
lastMedicalAt
,
ctx
.
now
)
:
null
;
// description:人读自包含;商保优先,把"最近日期"显式写出来(时效判断留给人/scorer)
const
parts
:
string
[]
=
[];
if
(
hasCommercial
)
{
const
who
=
insurers
.
length
?
` ·
${
insurers
.
join
(
'、'
)}
`
:
''
;
const
when
=
fmtYm
(
lastCommercialAt
);
parts
.
push
(
`商保客户
${
who
}${
when
?
` · 最近
${
when
}
(
${
monthsCommercial
}
个月前)`
:
''
}
`
);
}
if
(
hasMedical
)
{
const
when
=
fmtYm
(
lastMedicalAt
);
parts
.
push
(
`医保结算
${
when
?
` · 最近
${
when
}
`
:
''
}
`
);
}
return
{
key
:
this
.
key
,
description
:
parts
.
join
(
';'
),
// score = 商保最近月数(越小越新);仅医保时用医保月数兜底。旗标型,非梯度,scorer 读 data 更精确。
score
:
monthsCommercial
??
monthsMedical
??
null
,
evidence
:
{
factIds
:
[...
commercialFactIds
,
...
medicalFactIds
],
data
:
{
commercialInsured
:
hasCommercial
,
commercialInsurers
:
insurers
,
// 阶段2 re-ingest 后才有保司名
lastCommercialInsuranceAt
:
lastCommercialAt
?
lastCommercialAt
.
toISOString
()
:
null
,
monthsSinceLastCommercial
:
monthsCommercial
,
medicalInsured
:
hasMedical
,
lastMedicalInsuranceAt
:
lastMedicalAt
?
lastMedicalAt
.
toISOString
()
:
null
,
},
},
};
}
}
apps/pac-service/src/modules/persona/features/feature.interface.ts
View file @
69eede94
...
@@ -57,6 +57,9 @@ export interface PersonaFeatureDraft {
...
@@ -57,6 +57,9 @@ export interface PersonaFeatureDraft {
evidence
:
{
evidence
:
{
factIds
:
string
[];
factIds
:
string
[];
agentInvocationIds
?:
string
[];
agentInvocationIds
?:
string
[];
/// 结构化 payload(零迁移路线):description 是人读串、score 是排序数,
/// 需要被 scorer / UI 结构化消费的明细放这里(如权益身份 insurers / lastInsuranceAt)。
data
?:
Record
<
string
,
unknown
>
;
};
};
}
}
...
...
apps/pac-service/src/modules/persona/features/feature.registry.ts
View file @
69eede94
...
@@ -4,11 +4,13 @@ import { ValueFeatureExtractor } from './value.feature';
...
@@ -4,11 +4,13 @@ import { ValueFeatureExtractor } from './value.feature';
import
{
TreatmentChainStatusFeatureExtractor
}
from
'./treatment-chain-status.feature'
;
import
{
TreatmentChainStatusFeatureExtractor
}
from
'./treatment-chain-status.feature'
;
import
{
RecallRiskFeatureExtractor
}
from
'./recall-risk.feature'
;
import
{
RecallRiskFeatureExtractor
}
from
'./recall-risk.feature'
;
import
{
DoNotContactStatusFeatureExtractor
}
from
'./do-not-contact-status.feature'
;
import
{
DoNotContactStatusFeatureExtractor
}
from
'./do-not-contact-status.feature'
;
import
{
EntitlementStatusFeatureExtractor
}
from
'./entitlement-status.feature'
;
/**
/**
* FeatureRegistry — 收集所有规则路径的 PersonaFeature 提取器。
* FeatureRegistry — 收集所有规则路径的 PersonaFeature 提取器。
*
*
* v1 起步 4 个:value / treatment_chain_status / recall_risk / do_not_contact_status。
* v1 起步 4 个:value / treatment_chain_status / recall_risk / do_not_contact_status。
* + entitlement_status 权益身份(商保/医保,事实投影型,史+最近日期)。
* 单个 extractor 抛错不影响其余 — PersonaService 整体记 partial(orchestration log)。
* 单个 extractor 抛错不影响其余 — PersonaService 整体记 partial(orchestration log)。
*/
*/
@
Injectable
()
@
Injectable
()
...
@@ -20,7 +22,8 @@ export class FeatureRegistry {
...
@@ -20,7 +22,8 @@ export class FeatureRegistry {
chain
:
TreatmentChainStatusFeatureExtractor
,
chain
:
TreatmentChainStatusFeatureExtractor
,
risk
:
RecallRiskFeatureExtractor
,
risk
:
RecallRiskFeatureExtractor
,
dnc
:
DoNotContactStatusFeatureExtractor
,
dnc
:
DoNotContactStatusFeatureExtractor
,
entitlement
:
EntitlementStatusFeatureExtractor
,
)
{
)
{
this
.
extractors
=
[
value
,
chain
,
risk
,
dnc
];
this
.
extractors
=
[
value
,
chain
,
risk
,
dnc
,
entitlement
];
}
}
}
}
apps/pac-service/src/modules/persona/persona.module.ts
View file @
69eede94
...
@@ -6,6 +6,7 @@ import { ValueFeatureExtractor } from './features/value.feature';
...
@@ -6,6 +6,7 @@ import { ValueFeatureExtractor } from './features/value.feature';
import
{
TreatmentChainStatusFeatureExtractor
}
from
'./features/treatment-chain-status.feature'
;
import
{
TreatmentChainStatusFeatureExtractor
}
from
'./features/treatment-chain-status.feature'
;
import
{
RecallRiskFeatureExtractor
}
from
'./features/recall-risk.feature'
;
import
{
RecallRiskFeatureExtractor
}
from
'./features/recall-risk.feature'
;
import
{
DoNotContactStatusFeatureExtractor
}
from
'./features/do-not-contact-status.feature'
;
import
{
DoNotContactStatusFeatureExtractor
}
from
'./features/do-not-contact-status.feature'
;
import
{
EntitlementStatusFeatureExtractor
}
from
'./features/entitlement-status.feature'
;
@
Module
({
@
Module
({
controllers
:
[
PersonaController
],
controllers
:
[
PersonaController
],
...
@@ -16,6 +17,7 @@ import { DoNotContactStatusFeatureExtractor } from './features/do-not-contact-st
...
@@ -16,6 +17,7 @@ import { DoNotContactStatusFeatureExtractor } from './features/do-not-contact-st
TreatmentChainStatusFeatureExtractor
,
TreatmentChainStatusFeatureExtractor
,
RecallRiskFeatureExtractor
,
RecallRiskFeatureExtractor
,
DoNotContactStatusFeatureExtractor
,
DoNotContactStatusFeatureExtractor
,
EntitlementStatusFeatureExtractor
,
],
],
exports
:
[
PersonaService
],
exports
:
[
PersonaService
],
})
})
...
...
apps/pac-service/src/modules/sync/cold-import/cold-import.service.ts
View file @
69eede94
...
@@ -28,6 +28,7 @@ import {
...
@@ -28,6 +28,7 @@ import {
}
from
'./clickhouse-source.service'
;
}
from
'./clickhouse-source.service'
;
import
{
TransformEngine
}
from
'../transforms/transform-engine'
;
import
{
TransformEngine
}
from
'../transforms/transform-engine'
;
import
{
buildTenantResolver
,
type
TenantResolver
}
from
'./tenant-resolver'
;
import
{
buildTenantResolver
,
type
TenantResolver
}
from
'./tenant-resolver'
;
import
{
mergePatientPreferences
}
from
'../pipeline/patient-preferences.util'
;
/**
/**
* ColdImportService(v2 — AssemblerEngine 驱动)
* ColdImportService(v2 — AssemblerEngine 驱动)
...
@@ -729,10 +730,8 @@ export class ColdImportService {
...
@@ -729,10 +730,8 @@ export class ColdImportService {
birthDate
:
c
.
birthDate
?
new
Date
(
c
.
birthDate
as
string
)
:
null
,
birthDate
:
c
.
birthDate
?
new
Date
(
c
.
birthDate
as
string
)
:
null
,
medicalRecordNumber
:
medicalRecordNumber
:
(
c
.
medicalRecordNumber
as
string
|
undefined
)
??
null
,
(
c
.
medicalRecordNumber
as
string
|
undefined
)
??
null
,
preferences
:
// 专属客服(current_task_director)并入 preferences.dedicatedCs(零迁移,非画像特征)
c
.
preferences
&&
typeof
c
.
preferences
===
'object'
preferences
:
mergePatientPreferences
(
c
),
?
(
c
.
preferences
as
Prisma
.
InputJsonValue
)
:
undefined
,
// status='archived'/'merged' → active=false;'active' / 缺省 → true
// status='archived'/'merged' → active=false;'active' / 缺省 → true
active
:
((
c
.
status
as
string
|
undefined
)
??
'active'
)
===
'active'
,
active
:
((
c
.
status
as
string
|
undefined
)
??
'active'
)
===
'active'
,
};
};
...
...
apps/pac-service/src/modules/sync/pipeline/fact-content-schemas.ts
View file @
69eede94
...
@@ -331,6 +331,8 @@ const PaymentRecordContent = z
...
@@ -331,6 +331,8 @@ const PaymentRecordContent = z
payment_external_id
:
z
.
string
().
min
(
1
),
payment_external_id
:
z
.
string
().
min
(
1
),
amount_cents
:
z
.
number
().
int
().
nonnegative
(),
amount_cents
:
z
.
number
().
int
().
nonnegative
(),
channel
:
nullableString
(),
channel
:
nullableString
(),
/// 商业保险公司名(可空)— 非空 = 商保结算;喂 persona「权益身份」。保司名脏,归一在 feature 层做。
insurance_name
:
nullableString
(),
/// 收费医生 id(host 侧)— 医患关系信号(患者主要花钱给哪个医生)
/// 收费医生 id(host 侧)— 医患关系信号(患者主要花钱给哪个医生)
doctor_id
:
nullableString
(),
doctor_id
:
nullableString
(),
/// 关联接诊 id(反查"这次收款关联哪次接诊")
/// 关联接诊 id(反查"这次收款关联哪次接诊")
...
...
apps/pac-service/src/modules/sync/pipeline/parsers/payment.parser.ts
View file @
69eede94
...
@@ -44,6 +44,9 @@ export class PaymentParser implements Parser {
...
@@ -44,6 +44,9 @@ export class PaymentParser implements Parser {
const
method
=
(
c
.
method
as
string
|
undefined
)
??
null
;
const
method
=
(
c
.
method
as
string
|
undefined
)
??
null
;
const
doctorId
=
c
.
doctorId
?
String
(
c
.
doctorId
)
:
null
;
// host Int64,0/null → null
const
doctorId
=
c
.
doctorId
?
String
(
c
.
doctorId
)
:
null
;
// host Int64,0/null → null
const
currency
=
(
c
.
currency
as
string
|
undefined
)
??
'CNY'
;
const
currency
=
(
c
.
currency
as
string
|
undefined
)
??
'CNY'
;
// 商保公司名(可空):非空 = 商保结算;喂 persona「权益身份」。归一在 feature 层做。
const
insRaw
=
(
c
.
insuranceName
as
string
|
undefined
)
??
null
;
const
insuranceName
=
insRaw
&&
insRaw
.
trim
()
?
insRaw
.
trim
()
:
null
;
return
[
return
[
{
{
...
@@ -59,6 +62,7 @@ export class PaymentParser implements Parser {
...
@@ -59,6 +62,7 @@ export class PaymentParser implements Parser {
payment_external_id
:
externalId
,
payment_external_id
:
externalId
,
amount_cents
:
amount
,
amount_cents
:
amount
,
channel
:
method
,
channel
:
method
,
insurance_name
:
insuranceName
,
doctor_id
:
doctorId
,
doctor_id
:
doctorId
,
encounter_external_id
:
encounterExternalId
,
encounter_external_id
:
encounterExternalId
,
related_order_external_id
:
orderExternalId
,
related_order_external_id
:
orderExternalId
,
...
...
apps/pac-service/src/modules/sync/pipeline/patient-preferences.util.ts
0 → 100644
View file @
69eede94
import
type
{
Prisma
}
from
'@prisma/client'
;
/**
* 把 canonical patient row 的派生属性并入 patients.preferences(Json)——零 schema 迁移。
*
* 当前承载:
* - dedicatedCs 专属客服(current_task_director):路由/指派属性,非画像特征。
* "当前"会换 → upsert 覆盖(存当前值)。用于详情页展示 + 推送人匹配。
*
* 返回 undefined 表示"无可写偏好"(让 prisma upsert 不动该列)。
*/
export
function
mergePatientPreferences
(
c
:
Record
<
string
,
unknown
>
,
):
Prisma
.
InputJsonValue
|
undefined
{
const
base
=
c
.
preferences
&&
typeof
c
.
preferences
===
'object'
?
({
...(
c
.
preferences
as
Record
<
string
,
unknown
>
)
}
as
Record
<
string
,
unknown
>
)
:
({}
as
Record
<
string
,
unknown
>
);
const
csName
=
((
c
.
dedicatedCsName
as
string
|
undefined
)
??
''
).
trim
()
||
null
;
const
rawId
=
c
.
dedicatedCsId
==
null
?
''
:
String
(
c
.
dedicatedCsId
).
trim
();
const
csId
=
rawId
&&
rawId
!==
'0'
?
rawId
:
null
;
if
(
csName
||
csId
)
{
base
.
dedicatedCs
=
{
id
:
csId
,
name
:
csName
};
}
return
Object
.
keys
(
base
).
length
?
(
base
as
Prisma
.
InputJsonValue
)
:
undefined
;
}
apps/pac-service/src/modules/sync/pipeline/pipeline-dispatcher.service.ts
View file @
69eede94
...
@@ -6,6 +6,7 @@ import { QueueProducer } from '../../../queues/queue-producer.service';
...
@@ -6,6 +6,7 @@ import { QueueProducer } from '../../../queues/queue-producer.service';
import
{
TransactionSynthesizer
}
from
'./transaction-synthesizer'
;
import
{
TransactionSynthesizer
}
from
'./transaction-synthesizer'
;
import
{
ParserPipeline
}
from
'./parser-pipeline.service'
;
import
{
ParserPipeline
}
from
'./parser-pipeline.service'
;
import
type
{
EmitsConfig
}
from
'../assembler/assembler.schema'
;
import
type
{
EmitsConfig
}
from
'../assembler/assembler.schema'
;
import
{
mergePatientPreferences
}
from
'./patient-preferences.util'
;
/**
/**
* PipelineDispatcher — "canonical tables → transaction + fact" 的共享内核。
* PipelineDispatcher — "canonical tables → transaction + fact" 的共享内核。
...
@@ -222,7 +223,8 @@ export class PipelineDispatcher {
...
@@ -222,7 +223,8 @@ export class PipelineDispatcher {
phone
:
(
row
.
phone
as
string
|
undefined
)
??
null
,
phone
:
(
row
.
phone
as
string
|
undefined
)
??
null
,
gender
:
(
row
.
gender
as
string
|
undefined
)
??
null
,
gender
:
(
row
.
gender
as
string
|
undefined
)
??
null
,
birthDate
:
row
.
birthDate
?
new
Date
(
row
.
birthDate
as
string
)
:
null
,
birthDate
:
row
.
birthDate
?
new
Date
(
row
.
birthDate
as
string
)
:
null
,
preferences
:
row
.
preferences
?
(
row
.
preferences
as
Prisma
.
InputJsonValue
)
:
undefined
,
// 专属客服(current_task_director)并入 preferences.dedicatedCs(零迁移)
preferences
:
mergePatientPreferences
(
row
),
active
:
((
row
.
status
as
string
|
undefined
)
??
'active'
)
===
'active'
,
active
:
((
row
.
status
as
string
|
undefined
)
??
'active'
)
===
'active'
,
};
};
const
profileData
=
{
const
profileData
=
{
...
...
apps/pac-web/src/components/plan-detail/persona-feature-hover.tsx
View file @
69eede94
...
@@ -110,4 +110,14 @@ const ALGORITHMS: Record<string, AlgoMeta> = {
...
@@ -110,4 +110,14 @@ const ALGORITHMS: Record<string, AlgoMeta> = {
],
],
note
:
'不打扰时召回引擎硬拦截,不生成 / 已生成的不派单。电话缺失算"待补充电话",不算不打扰'
,
note
:
'不打扰时召回引擎硬拦截,不生成 / 已生成的不派单。电话缺失算"待补充电话",不算不打扰'
,
},
},
[
PersonaFeatureKey
.
ENTITLEMENT_STATUS
]:
{
title
:
'权益身份'
,
subtitle
:
'商保 / 医保结算史(事实投影,史+最近日期)'
,
rules
:
[
{
label
:
'商保客户'
,
body
:
'历史用商业保险结算过(channel=保险 或 有保司名),展示保司 + 最近一次日期'
},
{
label
:
'医保结算'
,
body
:
'用过社保医保结算(普惠,非高端 VIP 信号)'
},
{
label
:
'不打标签'
,
body
:
'无任何保险结算记录'
},
],
note
:
'商保强时效(雇主团险换工作即失效,DW 无保单有效期)→ 只陈述"曾经+最近日期",不断言"当前在保";新鲜度由人/打分器按最近日期判断'
,
},
};
};
packages/types/src/enums/index.ts
View file @
69eede94
...
@@ -380,6 +380,7 @@ export const PersonaFeatureKey = {
...
@@ -380,6 +380,7 @@ export const PersonaFeatureKey = {
DO_NOT_CONTACT_STATUS
:
'do_not_contact_status'
,
// 不打扰状态(合规硬约束)
DO_NOT_CONTACT_STATUS
:
'do_not_contact_status'
,
// 不打扰状态(合规硬约束)
// v1 候选(规则路径,业务方反馈后逐步上)
// v1 候选(规则路径,业务方反馈后逐步上)
ENTITLEMENT_STATUS
:
'entitlement_status'
,
// 权益身份(商保直付 / 医保 / 储值 / 私行;事实投影型,史+最近日期)
INCOMPLETE_TREATMENT
:
'incomplete_treatment'
,
// 未完成治疗(治疗链有缺口)
INCOMPLETE_TREATMENT
:
'incomplete_treatment'
,
// 未完成治疗(治疗链有缺口)
RECOMMENDED_ROLE
:
'recommended_role'
,
// 推荐执行角色(staff/leader/...)
RECOMMENDED_ROLE
:
'recommended_role'
,
// 推荐执行角色(staff/leader/...)
REFERRAL_RELATION
:
'referral_relation'
,
// 转介绍关系(老带新链)
REFERRAL_RELATION
:
'referral_relation'
,
// 转介绍关系(老带新链)
...
...
packages/types/src/labels.ts
View file @
69eede94
...
@@ -42,6 +42,7 @@ export const PERSONA_FEATURE_META: Record<string, { label: string; tone: Tone }>
...
@@ -42,6 +42,7 @@ export const PERSONA_FEATURE_META: Record<string, { label: string; tone: Tone }>
[
PersonaFeatureKey
.
TREATMENT_CHAIN_STATUS
]:
{
label
:
'治疗链状态'
,
tone
:
'amber'
},
[
PersonaFeatureKey
.
TREATMENT_CHAIN_STATUS
]:
{
label
:
'治疗链状态'
,
tone
:
'amber'
},
[
PersonaFeatureKey
.
RECALL_RISK
]:
{
label
:
'流失风险'
,
tone
:
'emerald'
},
[
PersonaFeatureKey
.
RECALL_RISK
]:
{
label
:
'流失风险'
,
tone
:
'emerald'
},
[
PersonaFeatureKey
.
DO_NOT_CONTACT_STATUS
]:
{
label
:
'不打扰状态'
,
tone
:
'slate'
},
[
PersonaFeatureKey
.
DO_NOT_CONTACT_STATUS
]:
{
label
:
'不打扰状态'
,
tone
:
'slate'
},
[
PersonaFeatureKey
.
ENTITLEMENT_STATUS
]:
{
label
:
'权益身份'
,
tone
:
'indigo'
},
[
PersonaFeatureKey
.
INCOMPLETE_TREATMENT
]:
{
label
:
'未完成治疗'
,
tone
:
'amber'
},
[
PersonaFeatureKey
.
INCOMPLETE_TREATMENT
]:
{
label
:
'未完成治疗'
,
tone
:
'amber'
},
[
PersonaFeatureKey
.
RECOMMENDED_ROLE
]:
{
label
:
'推荐角色'
,
tone
:
'teal'
},
[
PersonaFeatureKey
.
RECOMMENDED_ROLE
]:
{
label
:
'推荐角色'
,
tone
:
'teal'
},
[
PersonaFeatureKey
.
REFERRAL_RELATION
]:
{
label
:
'转介绍关系'
,
tone
:
'sky'
},
[
PersonaFeatureKey
.
REFERRAL_RELATION
]:
{
label
:
'转介绍关系'
,
tone
:
'sky'
},
...
...
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