Commit 9765bb32 by luoqi

refactor(persona): 结构化特征值 evidence.data → 独立 persona_features.data 列

evidence 语义是"为什么(证据溯源)",塞业务 payload 越界。改用独立 data Json? 列:
- schema: persona_features 加 data Json?(+ migration 20260602054612)
- PersonaFeatureDraft.data 顶层字段(从 evidence.data 移出);evidence 回归只放 factIds
- persona.service 写入 + 序列化透出 data 列
- entitlement extractor 结构化明细写 data 列

本地验证(6857):data 列含 {commercialInsurers/lastCommercialInsuranceAt/...},evidence 只剩 factIds。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 69eede94
-- AlterTable
ALTER TABLE "persona_features" ADD COLUMN "data" JSONB;
......@@ -602,6 +602,13 @@ model PersonaFeature {
/// 可选数值评分,用于排序/筛选
score Float?
/// 结构化 payload(可空) 特征值里"需要被 scorer / UI 结构化消费"的明细。
/// description(人读串)/ score(排序数)分工:这里放机器要 parse 的对象。
/// :权益身份 { commercialInsurers:[...], lastCommercialInsuranceAt, monthsSinceLastCommercial }
/// evidence 区分:evidence = 为什么(证据溯源),data = 是什么(结构化特征值)
/// 形状由各 feature 应用层自管(zod);DB 不强约束(db-review §6 JSON 列纪律)
data Json?
/// 证据引用:{ factIds, agentInvocationIds, ruleIds, promptVersion }
/// 任何 persona 都能反查到"为什么得出这个结论"
/// 引证 fact 即可,fact 自带 transactionIds 可两跳回溯到原始 transaction
......
......@@ -118,16 +118,17 @@ export class EntitlementStatusFeatureExtractor implements FeatureExtractor {
description: parts.join(';'),
// score = 商保最近月数(越小越新);仅医保时用医保月数兜底。旗标型,非梯度,scorer 读 data 更精确。
score: monthsCommercial ?? monthsMedical ?? null,
// 结构化特征值 → persona_features.data 列(给 scorer/UI 结构化消费)
data: {
commercialInsured: hasCommercial,
commercialInsurers: insurers, // 阶段2 re-ingest 后才有保司名
lastCommercialInsuranceAt: lastCommercialAt ? lastCommercialAt.toISOString() : null,
monthsSinceLastCommercial: monthsCommercial,
medicalInsured: hasMedical,
lastMedicalInsuranceAt: lastMedicalAt ? lastMedicalAt.toISOString() : 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,
},
},
};
}
......
......@@ -53,13 +53,13 @@ export interface PersonaFeatureDraft {
key: PersonaFeatureKey;
description: string;
score?: number | null;
/// 结构化特征值(可空)→ 落 persona_features.data 列。description 人读、score 排序、
/// data 给 scorer / UI 结构化消费(如权益身份 insurers / lastInsuranceAt / monthsSinceLast)。
data?: Record<string, unknown> | null;
/// 证据反查 fact + AI 调用;**不存 ruleIds 字符串常量**(版本追溯靠 git + persona.createdAt)
evidence: {
factIds: string[];
agentInvocationIds?: string[];
/// 结构化 payload(零迁移路线):description 是人读串、score 是排序数,
/// 需要被 scorer / UI 结构化消费的明细放这里(如权益身份 insurers / lastInsuranceAt)。
data?: Record<string, unknown>;
};
}
......
......@@ -220,6 +220,10 @@ export class PersonaService {
key: d.key,
description: d.description,
score: d.score ?? null,
data:
d.data == null
? undefined // nullable Json 列省略 → NULL
: (d.data as unknown as Prisma.InputJsonValue),
evidence: d.evidence as unknown as Prisma.InputJsonValue,
})),
},
......@@ -298,6 +302,7 @@ export class PersonaService {
key: string;
description: string;
score: number | null;
data: unknown;
evidence: unknown;
createdAt: Date;
}>;
......@@ -318,6 +323,7 @@ export class PersonaService {
key: f.key,
description: f.description,
score: f.score,
data: (f.data as Record<string, unknown>) ?? null,
evidence: (f.evidence as Record<string, unknown>) ?? {},
createdAt: f.createdAt.toISOString(),
})),
......
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