Commit 0a0ab389 by luoqi

refactor(cleanup): 删 chain-composer/plan-aggregate/admin 死代码(审计 A3/A4/A8/A14/A16)

- chain-composer: 删 filterDiscoveredByPatientLevel(无调用,doc 复位给 markAlternativeClosed)、
  collectS1Facts、_S2_PAYMENT_THRESHOLD_CENTS_DEPRECATED(+void 语句)、ComposeInputEncounter 兼容别名
- plan-aggregate: 删 PatientWithProfile/Fact/loadFactStub 自引用死类型链(declare-only)
- admin.dto: 删 CreateHostRequestDto/CreateHostResponseDto/AiInvocationListItemDto 未用包装类 + 对应 schema import
- skill-registry: 删未用 getSkill
- mock-data(web): fmtDate 去 export(仅内部用)

service + web tsc 通过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 92ecc815
import { createZodDto } from 'nestjs-zod';
import {
CreateHostRequestSchema,
CreateHostResponseSchema,
ListHostsResponseSchema,
HostSummarySchema,
HostDetailSchema,
......@@ -12,14 +10,11 @@ import {
RemovePushSecretResponseSchema,
UpdateHostRequestSchema,
AiInvocationListQuerySchema,
AiInvocationListItemSchema,
AiInvocationDetailSchema,
AiInvocationStatsSchema,
ListAiInvocationsResponseSchema,
} from '@pac/types';
export class CreateHostRequestDto extends createZodDto(CreateHostRequestSchema) {}
export class CreateHostResponseDto extends createZodDto(CreateHostResponseSchema) {}
export class ListHostsResponseDto extends createZodDto(ListHostsResponseSchema) {}
export class HostSummaryDto extends createZodDto(HostSummarySchema) {}
export class HostDetailDto extends createZodDto(HostDetailSchema) {}
......@@ -32,7 +27,6 @@ export class RemovePushSecretResponseDto extends createZodDto(RemovePushSecretRe
// AI Invocations
export class AiInvocationListQueryDto extends createZodDto(AiInvocationListQuerySchema) {}
export class AiInvocationListItemDto extends createZodDto(AiInvocationListItemSchema) {}
export class AiInvocationDetailDto extends createZodDto(AiInvocationDetailSchema) {}
export class AiInvocationStatsDto extends createZodDto(AiInvocationStatsSchema) {}
export class ListAiInvocationsResponseDto extends createZodDto(ListAiInvocationsResponseSchema) {}
......@@ -85,11 +85,6 @@ export class DraftPlanScriptSkillRegistry implements OnModuleInit {
return [...this.skills.values()];
}
/** 单个 skill 取(测试用) */
getSkill(name: string): Skill | undefined {
return this.skills.get(name);
}
private resolveSkillsRoot(): string {
return resolveScriptRoot();
}
......
......@@ -248,18 +248,6 @@ export class PlanAggregateService {
// Serializers(纯函数 — 易测、无副作用)
// ─────────────────────────────────────────────
type PatientWithProfile = NonNullable<
Awaited<ReturnType<PlanAggregateService['getPlanDetailByPlanId']>>['plan']
> extends never
? never
: Parameters<typeof serializePatient>[0];
type Fact = Awaited<ReturnType<typeof loadFactStub>>[number];
// 类型 helper — 仅供推导用,不真调用
declare function loadFactStub(): Promise<
Awaited<ReturnType<PlanAggregateService['getPlanDetailByPlanId']>>['facts']
>;
/// 从 patients.preferences JSON 安全解析专属客服(摄入时 mergePatientPreferences 写入)
function extractDedicatedCs(prefs: unknown): { id: string; name: string } | null {
if (!prefs || typeof prefs !== 'object') return null;
......
......@@ -122,37 +122,6 @@ export class ChainComposerService {
* 注:不强求 other.currentStage >= 3(已治) — 仅"同位置后续 K08 诊断"就足够说明方案变更,
* 客服可以告诉患者"医生已经规划替代方案,先看治疗链全景再决定要不要打"。
*/
/**
* patient 级对齐 — 跟 scenario SQL 排除口径完全一致
*
* 规则:同 patient 内,若 category C 下存在任一 ongoing/closed 链(即患者真做过 C 类 actual),
* 则该 cat 所有 discovered 链**直接从 chains 列表删除**(不只是 target=false 灰显)。
*
* 例:张超 K04 endodontic — 11;22 已做根管(closed)→ 12;13、24;25;42 discovered 桶被 filter 掉,
* UI 完全不显示。chain 数 = reason 数完全对齐,客服无歧义。
*
* limitation:host 结算无牙位级粒度,无法精准判定"12;13 还没做"。等 host 升级 settlement_tooth_position
* 后可改成 chain 级精准 — 真没做的牙位仍立链召回。当前是 patient 级粗粒度对齐。
*/
function filterDiscoveredByPatientLevel(chains: ComposedChain[]): ComposedChain[] {
// 收集每个 category 是否"已启动"(任一桶 status='ongoing'/'closed' 或 'entered')
// 包括 entered — 患者主动预约了同 cat 也算"已动",不再算潜在新发现
const catStarted = new Set<string>();
for (const c of chains) {
if (c.status === 'ongoing' || c.status === 'closed' || c.status === 'entered') {
catStarted.add(c.category);
}
}
// filter out:discovered chain 的 DxMap.code.categories 全集中**任一**已启动 → 直接删
// 跟 scenario SQL.excludeCats 完全对齐(K00 categories=['surgical','prosthodontic','implant','orthodontic']
// 任一启动即 K00 不召回 / 不立链)
return chains.filter((c) => {
if (c.status !== 'discovered') return true;
const cats = c.allCategories ?? [c.category]; // fallback 兜底
return !cats.some((cat) => catStarted.has(cat));
});
}
function markAlternativeClosed(chains: ComposedChain[], byType: FactsByType): void {
// 定性替代治疗类别 — 同牙做了这些 = 原诊断已被替代方案处置(占位/修复/拔除)。
// cosmetic(贴面)与 prosthodontic(冠桥)同属定性修复:李梦维 K00 乳牙滞留 1B;4C —
......@@ -763,17 +732,6 @@ function mergeOverlappingBuckets(buckets: ChainBucket[]): ChainBucket[] {
return [...merged.values()];
}
/// S1 = diagnosis/recommendation 中 code 映射到当前 category 的 fact
function collectS1Facts(category: string, byType: FactsByType): ChainComposeInputFact[] {
const out: ChainComposeInputFact[] = [];
for (const f of [...byType.diagnosis, ...byType.recommendation]) {
const code = String((f.content as Record<string, unknown>).code ?? '');
const rule = lookupDxTreatment(code);
if (rule?.categories.includes(category as never)) out.push(f);
}
return out;
}
/// S2 = S1 之后的"进入治疗链" 强信号 — **必须是患者主动行为**(W4 末严格化)
///
/// 历史演进:
......@@ -911,23 +869,6 @@ function toothOverlap(a: string, b: string): boolean {
return false;
}
/// (W3 末废弃)S2 大额 payment 阈值字典 — payment 无 category/tooth 关联,作为 S2 信号会误归桶
/// 例:罗国标 K04 14;15 桶被"种植定金 ¥4260"误判 entered。改成只用 planned treatment 作 S2 信号。
/// 字典保留(防有 host 接入时按 payment 阈值过滤误算入 LTV / 大额识别),实际不消费。
const _S2_PAYMENT_THRESHOLD_CENTS_DEPRECATED: Record<string, number> = {
implant: 500000,
orthodontic: 500000,
prosthodontic: 200000,
endodontic: 100000,
periodontic: 50000,
restorative: 30000,
surgical: 30000,
preventive: 30000,
cosmetic: 100000,
pediatric: 30000,
};
void _S2_PAYMENT_THRESHOLD_CENTS_DEPRECATED;
/// S3 = 同 category 的 actual treatment
function getActualTreatments(category: string, byType: FactsByType): ChainComposeInputFact[] {
return byType.treatment.filter((tx) => {
......@@ -1617,6 +1558,3 @@ export interface ChainNode {
/// 旧字段保留兼容(未使用)
note?: string;
}
/** 兼容旧 ComposeInputEncounter 类型(给老调用方过渡) */
export type ComposeInputEncounter = ChainComposeInputFact;
......@@ -23,7 +23,7 @@ const daysAgo = (n: number) => {
const hoursAgo = (n: number) => new Date(NOW.getTime() - n * 3600_000);
export const fmtRel = (d: Date): string => sharedFmtRel(d, NOW);
export const fmtDate = sharedFmtDate;
const fmtDate = sharedFmtDate;
// ────────────────────────────────────────────────
// Patient
......
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