import { and, desc, eq, sql } from "drizzle-orm"; import { getDatabase } from "@/modules/core/server/db"; import { accounts, chronicConditions, elders, healthAnomalyReviews, healthProfiles, vitalRecords, } from "@/modules/core/server/schema"; import type { ChronicCondition, ChronicConditionInput, HealthAdminData, HealthAnomalyReview, HealthProfile, HealthProfileInput, HealthReviewUpdateInput, VitalRecord, VitalRecordInput, } from "@/modules/health/types"; export type MutationFailure = { success: false; reason: string; status: number; }; type ElderRow = typeof elders.$inferSelect; type AccountNameRow = { id: string; name: string; }; export function isMutationFailure(value: unknown): value is MutationFailure { return ( typeof value === "object" && value !== null && "success" in value && (value as { success?: unknown }).success === false ); } function iso(value: Date): string { return value.toISOString(); } function optionalIso(value: Date | null): string | undefined { return value ? iso(value) : undefined; } function buildNameMap(rows: AccountNameRow[]): Map { return new Map(rows.map((row) => [row.id, row.name])); } function toProfile(row: typeof healthProfiles.$inferSelect, elderName: string): HealthProfile { return { id: row.id, organizationId: row.organizationId, elderId: row.elderId, elderName, allergyNotes: row.allergyNotes, medicalHistory: row.medicalHistory, medicationNotes: row.medicationNotes, careRestrictions: row.careRestrictions, emergencyNotes: row.emergencyNotes, createdAt: iso(row.createdAt), updatedAt: iso(row.updatedAt), }; } function toVital(row: typeof vitalRecords.$inferSelect, elderName: string, accountNameById: Map): VitalRecord { return { id: row.id, organizationId: row.organizationId, elderId: row.elderId, elderName, recordedAt: iso(row.recordedAt), source: row.source, systolicBp: row.systolicBp ?? undefined, diastolicBp: row.diastolicBp ?? undefined, heartRate: row.heartRate ?? undefined, temperatureTenths: row.temperatureTenths ?? undefined, spo2: row.spo2 ?? undefined, bloodGlucoseTenths: row.bloodGlucoseTenths ?? undefined, weightTenths: row.weightTenths ?? undefined, notes: row.notes, createdByAccountId: row.createdByAccountId ?? undefined, createdByName: row.createdByAccountId ? accountNameById.get(row.createdByAccountId) : undefined, createdAt: iso(row.createdAt), updatedAt: iso(row.updatedAt), }; } function toCondition(row: typeof chronicConditions.$inferSelect, elderName: string): ChronicCondition { return { id: row.id, organizationId: row.organizationId, elderId: row.elderId, elderName, name: row.name, status: row.status, diagnosedAt: optionalIso(row.diagnosedAt), treatmentNotes: row.treatmentNotes, followUpNotes: row.followUpNotes, createdAt: iso(row.createdAt), updatedAt: iso(row.updatedAt), }; } function toReview( row: typeof healthAnomalyReviews.$inferSelect, elderName: string, accountNameById: Map, ): HealthAnomalyReview { return { id: row.id, organizationId: row.organizationId, elderId: row.elderId, elderName, vitalRecordId: row.vitalRecordId ?? undefined, severity: row.severity, status: row.status, title: row.title, description: row.description, reviewedByAccountId: row.reviewedByAccountId ?? undefined, reviewedByName: row.reviewedByAccountId ? accountNameById.get(row.reviewedByAccountId) : undefined, reviewedAt: optionalIso(row.reviewedAt), resolutionNotes: row.resolutionNotes, createdAt: iso(row.createdAt), updatedAt: iso(row.updatedAt), }; } function getElderName(row: ElderRow | undefined): string { return row?.name ?? ""; } async function findElder(organizationId: string, elderId: string): Promise { const database = getDatabase(); const rows = await database .select() .from(elders) .where(and(eq(elders.id, elderId), eq(elders.organizationId, organizationId))) .limit(1); return rows[0]; } function detectVitalAnomaly(input: VitalRecordInput): { title: string; description: string; severity: "warning" | "critical" } | null { if ((input.systolicBp !== undefined && input.systolicBp >= 180) || (input.diastolicBp !== undefined && input.diastolicBp >= 110)) { return { title: "血压严重异常", description: "血压达到紧急复核阈值", severity: "critical" }; } if ((input.systolicBp !== undefined && input.systolicBp >= 150) || (input.diastolicBp !== undefined && input.diastolicBp >= 95)) { return { title: "血压异常", description: "血压偏高,需要护理人员复核", severity: "warning" }; } if (input.temperatureTenths !== undefined && input.temperatureTenths >= 380) { return { title: "体温异常", description: "体温偏高,需要复核", severity: "warning" }; } if (input.spo2 !== undefined && input.spo2 < 92) { return { title: "血氧异常", description: "血氧偏低,需要复核", severity: "critical" }; } return null; } export async function listHealthAdminData(organizationId: string): Promise { const database = getDatabase(); const todayStart = new Date(); todayStart.setHours(0, 0, 0, 0); const [elderRows, profileRows, vitalRows, conditionRows, reviewRows, accountRows] = await Promise.all([ database.select().from(elders).where(eq(elders.organizationId, organizationId)).orderBy(desc(elders.createdAt)), database.select().from(healthProfiles).where(eq(healthProfiles.organizationId, organizationId)).orderBy(desc(healthProfiles.updatedAt)), database.select().from(vitalRecords).where(eq(vitalRecords.organizationId, organizationId)).orderBy(desc(vitalRecords.recordedAt)).limit(100), database .select() .from(chronicConditions) .where(eq(chronicConditions.organizationId, organizationId)) .orderBy(desc(chronicConditions.updatedAt)), database .select() .from(healthAnomalyReviews) .where(eq(healthAnomalyReviews.organizationId, organizationId)) .orderBy(desc(healthAnomalyReviews.createdAt)), database.select({ id: accounts.id, name: accounts.name }).from(accounts), ]); const elderById = new Map(elderRows.map((elder) => [elder.id, elder])); const accountNameById = buildNameMap(accountRows); return { elders: elderRows.map((elder) => ({ id: elder.id, name: elder.name, careLevel: elder.careLevel, status: elder.status, })), profiles: profileRows.map((profile) => toProfile(profile, getElderName(elderById.get(profile.elderId)))), vitals: vitalRows.map((vital) => toVital(vital, getElderName(elderById.get(vital.elderId)), accountNameById)), chronicConditions: conditionRows.map((condition) => toCondition(condition, getElderName(elderById.get(condition.elderId)))), reviews: reviewRows.map((review) => toReview(review, getElderName(elderById.get(review.elderId)), accountNameById)), metrics: { eldersWithProfiles: profileRows.length, vitalsToday: vitalRows.filter((vital) => vital.recordedAt >= todayStart).length, activeChronicConditions: conditionRows.filter((condition) => condition.status === "active").length, pendingReviews: reviewRows.filter((review) => review.status === "pending").length, }, }; } export async function upsertHealthProfile(input: HealthProfileInput & { elderId: string; organizationId: string }): Promise { const elder = await findElder(input.organizationId, input.elderId); if (!elder) { return { success: false, reason: "老人档案不存在", status: 404 }; } const database = getDatabase(); const rows = await database .insert(healthProfiles) .values({ organizationId: input.organizationId, elderId: input.elderId, allergyNotes: input.allergyNotes, medicalHistory: input.medicalHistory, medicationNotes: input.medicationNotes, careRestrictions: input.careRestrictions, emergencyNotes: input.emergencyNotes, }) .onConflictDoUpdate({ target: [healthProfiles.organizationId, healthProfiles.elderId], set: { allergyNotes: input.allergyNotes, medicalHistory: input.medicalHistory, medicationNotes: input.medicationNotes, careRestrictions: input.careRestrictions, emergencyNotes: input.emergencyNotes, updatedAt: sql`NOW()`, }, }) .returning(); const profile = rows[0]; if (!profile) { return { success: false, reason: "健康档案保存失败", status: 500 }; } return toProfile(profile, elder.name); } export async function createVitalRecord(input: VitalRecordInput & { accountId: string; organizationId: string }): Promise< | { vital: VitalRecord; review?: HealthAnomalyReview; } | MutationFailure > { const elder = await findElder(input.organizationId, input.elderId); if (!elder) { return { success: false, reason: "老人档案不存在", status: 404 }; } const database = getDatabase(); const created = await database.transaction(async (transaction) => { const vitalRows = await transaction .insert(vitalRecords) .values({ organizationId: input.organizationId, elderId: input.elderId, recordedAt: input.recordedAt, source: input.source, systolicBp: input.systolicBp, diastolicBp: input.diastolicBp, heartRate: input.heartRate, temperatureTenths: input.temperatureTenths, spo2: input.spo2, bloodGlucoseTenths: input.bloodGlucoseTenths, weightTenths: input.weightTenths, notes: input.notes, createdByAccountId: input.accountId, }) .returning(); const vital = vitalRows[0]; if (!vital) { return null; } const anomaly = detectVitalAnomaly(input); if (!anomaly) { return { vital }; } const reviewRows = await transaction .insert(healthAnomalyReviews) .values({ organizationId: input.organizationId, elderId: input.elderId, vitalRecordId: vital.id, severity: anomaly.severity, status: "pending", title: anomaly.title, description: anomaly.description, }) .returning(); return { vital, review: reviewRows[0] }; }); if (!created) { return { success: false, reason: "生命体征记录创建失败", status: 500 }; } const accountRows = await database.select({ id: accounts.id, name: accounts.name }).from(accounts).where(eq(accounts.id, input.accountId)); const accountNameById = buildNameMap(accountRows); return { vital: toVital(created.vital, elder.name, accountNameById), review: created.review ? toReview(created.review, elder.name, accountNameById) : undefined, }; } export async function createChronicCondition( input: ChronicConditionInput & { organizationId: string }, ): Promise { const elder = await findElder(input.organizationId, input.elderId); if (!elder) { return { success: false, reason: "老人档案不存在", status: 404 }; } const database = getDatabase(); const rows = await database .insert(chronicConditions) .values({ organizationId: input.organizationId, elderId: input.elderId, name: input.name, status: input.status, diagnosedAt: input.diagnosedAt, treatmentNotes: input.treatmentNotes, followUpNotes: input.followUpNotes, }) .returning(); const condition = rows[0]; if (!condition) { return { success: false, reason: "慢病记录创建失败", status: 500 }; } return toCondition(condition, elder.name); } export async function updateHealthReview(input: HealthReviewUpdateInput & { accountId: string; id: string; organizationId: string }): Promise< HealthAnomalyReview | MutationFailure > { const database = getDatabase(); const rows = await database .update(healthAnomalyReviews) .set({ status: input.status, resolutionNotes: input.resolutionNotes, reviewedByAccountId: input.accountId, reviewedAt: new Date(), updatedAt: new Date(), }) .where(and(eq(healthAnomalyReviews.id, input.id), eq(healthAnomalyReviews.organizationId, input.organizationId))) .returning(); const review = rows[0]; if (!review) { return { success: false, reason: "异常复核记录不存在", status: 404 }; } const [elderRows, accountRows] = await Promise.all([ database.select().from(elders).where(eq(elders.id, review.elderId)).limit(1), database.select({ id: accounts.id, name: accounts.name }).from(accounts).where(eq(accounts.id, input.accountId)), ]); return toReview(review, getElderName(elderRows[0]), buildNameMap(accountRows)); }