import type { Permission } from "@/modules/core/types"; export const AI_KNOWLEDGE_SCOPES = ["platform", "organization"] as const; export type AiKnowledgeScope = (typeof AI_KNOWLEDGE_SCOPES)[number]; export const AI_KNOWLEDGE_STATUSES = ["enabled", "disabled"] as const; export type AiKnowledgeStatus = (typeof AI_KNOWLEDGE_STATUSES)[number]; export const ELDER_AI_ANALYSIS_STATUSES = ["completed", "failed"] as const; export type ElderAiAnalysisStatus = (typeof ELDER_AI_ANALYSIS_STATUSES)[number]; export const ELDER_AI_DATA_SCOPES = [ "elder", "health", "care", "family", "admission", "facility", "alert", "incident", "knowledge", ] as const; export type ElderAiDataScope = (typeof ELDER_AI_DATA_SCOPES)[number]; export const ELDER_AI_RISK_LEVELS = ["low", "medium", "high", "critical", "unknown"] as const; export type ElderAiRiskLevel = (typeof ELDER_AI_RISK_LEVELS)[number]; export const ELDER_AI_SEVERITIES = ["info", "warning", "critical"] as const; export type ElderAiSeverity = (typeof ELDER_AI_SEVERITIES)[number]; export const ELDER_AI_RECOMMENDATION_PRIORITIES = ["low", "normal", "high", "urgent"] as const; export type ElderAiRecommendationPriority = (typeof ELDER_AI_RECOMMENDATION_PRIORITIES)[number]; export const AI_ERROR_CATEGORIES = [ "missing_config", "provider_error", "timeout", "schema_validation_failed", "retrieval_failed", "unknown", ] as const; export type AiErrorCategory = (typeof AI_ERROR_CATEGORIES)[number]; export type AiCitation = { id: string; sourceType: "resident_context" | "knowledge"; sourceId: string; title: string; excerpt: string; }; export type ElderAiFinding = { category: string; severity: ElderAiSeverity; evidence: string; citationIds: string[]; }; export type ElderAiRecommendation = { title: string; priority: ElderAiRecommendationPriority; rationale: string; suggestedNextStep: string; citationIds: string[]; }; export type ElderAiModelSummary = { provider: "openai-compatible"; chatModel: string; embeddingModel: string; }; export type ElderAiAnalysisOutput = { overallRiskLevel: ElderAiRiskLevel; summary: string; keyFindings: ElderAiFinding[]; recommendations: ElderAiRecommendation[]; dataGaps: string[]; citations: AiCitation[]; confidence: number; modelSummary: ElderAiModelSummary; }; export type ElderAiAnalysisHistoryItem = { id: string; elderId: string; status: ElderAiAnalysisStatus; dataScopes: ElderAiDataScope[]; createdAt: string; restricted: boolean; result?: ElderAiAnalysisOutput; errorCategory?: AiErrorCategory; errorReason?: string; }; export type KnowledgeEntryInput = { scope: AiKnowledgeScope; organizationId?: string; title: string; category: string; tags: string; body: string; status: AiKnowledgeStatus; }; export type KnowledgeEntry = KnowledgeEntryInput & { id: string; createdAt: string; updatedAt: string; }; export type KnowledgeRetrievalResult = { entryId: string; chunkId: string; title: string; category: string; content: string; score: number; }; const DATA_SCOPE_PERMISSION_MAP: Record, Permission> = { health: "health:read", care: "care:read", family: "family:read", admission: "admission:read", facility: "facility:read", alert: "alert:read", incident: "incident:read", }; export function isElderAiDataScope(value: string): value is ElderAiDataScope { return (ELDER_AI_DATA_SCOPES as readonly string[]).includes(value); } export function getPermissionForDataScope(scope: ElderAiDataScope): Permission { if (scope === "elder") { return "elder:read"; } if (scope === "knowledge") { return "knowledge:read"; } return DATA_SCOPE_PERMISSION_MAP[scope]; } export function canViewAnalysisScopes(scopes: readonly ElderAiDataScope[], permissions: readonly Permission[]): boolean { return scopes.every((scope) => permissions.includes(getPermissionForDataScope(scope))); } export function parseDataScopes(value: unknown): ElderAiDataScope[] { if (!Array.isArray(value)) { return []; } return value.filter((item): item is ElderAiDataScope => typeof item === "string" && isElderAiDataScope(item)); } function isRecord(value: unknown): value is Record { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function readStringArray(value: unknown): string[] | null { if (!Array.isArray(value)) { return null; } const items = value.filter((item): item is string => typeof item === "string"); return items.length === value.length ? items : null; } function validateCitation(value: unknown): AiCitation | null { if (!isRecord(value)) { return null; } const sourceType = value.sourceType; if (sourceType !== "resident_context" && sourceType !== "knowledge") { return null; } if ( typeof value.id !== "string" || typeof value.sourceId !== "string" || typeof value.title !== "string" || typeof value.excerpt !== "string" ) { return null; } return { id: value.id, sourceType, sourceId: value.sourceId, title: value.title, excerpt: value.excerpt, }; } function validateFinding(value: unknown): ElderAiFinding | null { if (!isRecord(value)) { return null; } const citationIds = readStringArray(value.citationIds); if ( typeof value.category !== "string" || typeof value.evidence !== "string" || !ELDER_AI_SEVERITIES.includes(value.severity as ElderAiSeverity) || !citationIds ) { return null; } return { category: value.category, severity: value.severity as ElderAiSeverity, evidence: value.evidence, citationIds, }; } function validateRecommendation(value: unknown): ElderAiRecommendation | null { if (!isRecord(value)) { return null; } const citationIds = readStringArray(value.citationIds); if ( typeof value.title !== "string" || typeof value.rationale !== "string" || typeof value.suggestedNextStep !== "string" || !ELDER_AI_RECOMMENDATION_PRIORITIES.includes(value.priority as ElderAiRecommendationPriority) || !citationIds ) { return null; } return { title: value.title, priority: value.priority as ElderAiRecommendationPriority, rationale: value.rationale, suggestedNextStep: value.suggestedNextStep, citationIds, }; } function validateModelSummary(value: unknown): ElderAiModelSummary | null { if (!isRecord(value)) { return null; } if ( value.provider !== "openai-compatible" || typeof value.chatModel !== "string" || typeof value.embeddingModel !== "string" ) { return null; } return { provider: "openai-compatible", chatModel: value.chatModel, embeddingModel: value.embeddingModel, }; } export function validateElderAiAnalysisOutput(value: unknown): ElderAiAnalysisOutput | null { if (!isRecord(value)) { return null; } const keyFindings = Array.isArray(value.keyFindings) ? value.keyFindings.map(validateFinding) : null; const recommendations = Array.isArray(value.recommendations) ? value.recommendations.map(validateRecommendation) : null; const citations = Array.isArray(value.citations) ? value.citations.map(validateCitation) : null; const dataGaps = readStringArray(value.dataGaps); const modelSummary = validateModelSummary(value.modelSummary); if ( !ELDER_AI_RISK_LEVELS.includes(value.overallRiskLevel as ElderAiRiskLevel) || typeof value.summary !== "string" || typeof value.confidence !== "number" || !keyFindings || keyFindings.includes(null) || !recommendations || recommendations.includes(null) || !dataGaps || !citations || citations.includes(null) || !modelSummary ) { return null; } return { overallRiskLevel: value.overallRiskLevel as ElderAiRiskLevel, summary: value.summary, keyFindings: keyFindings.filter((item): item is ElderAiFinding => Boolean(item)), recommendations: recommendations.filter((item): item is ElderAiRecommendation => Boolean(item)), dataGaps, citations: citations.filter((item): item is AiCitation => Boolean(item)), confidence: Math.max(0, Math.min(1, value.confidence)), modelSummary, }; } export function validateKnowledgeEntryInput(value: unknown): { success: true; input: KnowledgeEntryInput } | { success: false; reason: string } { if (!value || typeof value !== "object") { return { success: false, reason: "请求数据格式无效" }; } const record = value as Record; const scope = typeof record.scope === "string" ? record.scope : ""; const title = typeof record.title === "string" ? record.title.trim() : ""; const category = typeof record.category === "string" ? record.category.trim() : ""; const tags = typeof record.tags === "string" ? record.tags.trim() : ""; const body = typeof record.body === "string" ? record.body.trim() : ""; const status = typeof record.status === "string" ? record.status : "enabled"; const organizationId = typeof record.organizationId === "string" && record.organizationId.trim() ? record.organizationId.trim() : undefined; if (!AI_KNOWLEDGE_SCOPES.includes(scope as AiKnowledgeScope)) { return { success: false, reason: "知识库范围无效" }; } if (!AI_KNOWLEDGE_STATUSES.includes(status as AiKnowledgeStatus)) { return { success: false, reason: "知识库状态无效" }; } if (!title) { return { success: false, reason: "知识标题不能为空" }; } if (!body) { return { success: false, reason: "知识内容不能为空" }; } return { success: true, input: { scope: scope as AiKnowledgeScope, organizationId, title, category, tags, body, status: status as AiKnowledgeStatus, }, }; }