Files
teatea-pension/modules/health/types.ts

333 lines
8.5 KiB
TypeScript

export const VITAL_SOURCE_VALUES = ["manual", "device", "import"] as const;
export type VitalSource = (typeof VITAL_SOURCE_VALUES)[number];
export const CHRONIC_CONDITION_STATUS_VALUES = ["active", "controlled", "resolved"] as const;
export type ChronicConditionStatus = (typeof CHRONIC_CONDITION_STATUS_VALUES)[number];
export const HEALTH_REVIEW_STATUS_VALUES = ["pending", "reviewed", "resolved"] as const;
export type HealthReviewStatus = (typeof HEALTH_REVIEW_STATUS_VALUES)[number];
export const HEALTH_REVIEW_SEVERITY_VALUES = ["info", "warning", "critical"] as const;
export type HealthReviewSeverity = (typeof HEALTH_REVIEW_SEVERITY_VALUES)[number];
export const VITAL_SOURCE_LABELS: Record<VitalSource, string> = {
manual: "手工录入",
device: "设备采集",
import: "批量导入",
};
export const CHRONIC_CONDITION_STATUS_LABELS: Record<ChronicConditionStatus, string> = {
active: "持续管理",
controlled: "控制稳定",
resolved: "已结束",
};
export const HEALTH_REVIEW_STATUS_LABELS: Record<HealthReviewStatus, string> = {
pending: "待复核",
reviewed: "已复核",
resolved: "已处理",
};
export const HEALTH_REVIEW_SEVERITY_LABELS: Record<HealthReviewSeverity, string> = {
info: "提示",
warning: "预警",
critical: "紧急",
};
export type HealthElderOption = {
id: string;
name: string;
careLevel: string;
status: string;
};
export type HealthProfile = {
id: string;
organizationId: string;
elderId: string;
elderName: string;
allergyNotes: string;
medicalHistory: string;
medicationNotes: string;
careRestrictions: string;
emergencyNotes: string;
createdAt: string;
updatedAt: string;
};
export type VitalRecord = {
id: string;
organizationId: string;
elderId: string;
elderName: string;
recordedAt: string;
source: VitalSource;
systolicBp?: number;
diastolicBp?: number;
heartRate?: number;
temperatureTenths?: number;
spo2?: number;
bloodGlucoseTenths?: number;
weightTenths?: number;
notes: string;
createdByAccountId?: string;
createdByName?: string;
createdAt: string;
updatedAt: string;
};
export type ChronicCondition = {
id: string;
organizationId: string;
elderId: string;
elderName: string;
name: string;
status: ChronicConditionStatus;
diagnosedAt?: string;
treatmentNotes: string;
followUpNotes: string;
createdAt: string;
updatedAt: string;
};
export type HealthAnomalyReview = {
id: string;
organizationId: string;
elderId: string;
elderName: string;
vitalRecordId?: string;
severity: HealthReviewSeverity;
status: HealthReviewStatus;
title: string;
description: string;
reviewedByAccountId?: string;
reviewedByName?: string;
reviewedAt?: string;
resolutionNotes: string;
createdAt: string;
updatedAt: string;
};
export type HealthAdminMetrics = {
eldersWithProfiles: number;
vitalsToday: number;
activeChronicConditions: number;
pendingReviews: number;
};
export type HealthAdminData = {
elders: HealthElderOption[];
profiles: HealthProfile[];
vitals: VitalRecord[];
chronicConditions: ChronicCondition[];
reviews: HealthAnomalyReview[];
metrics: HealthAdminMetrics;
};
export type HealthProfileInput = {
allergyNotes: string;
medicalHistory: string;
medicationNotes: string;
careRestrictions: string;
emergencyNotes: string;
};
export type VitalRecordInput = {
elderId: string;
recordedAt: Date;
source: VitalSource;
systolicBp?: number;
diastolicBp?: number;
heartRate?: number;
temperatureTenths?: number;
spo2?: number;
bloodGlucoseTenths?: number;
weightTenths?: number;
notes: string;
};
export type ChronicConditionInput = {
elderId: string;
name: string;
status: ChronicConditionStatus;
diagnosedAt?: Date;
treatmentNotes: string;
followUpNotes: string;
};
export type HealthReviewUpdateInput = {
status: HealthReviewStatus;
resolutionNotes: string;
};
type ValidationSuccess<T> = {
success: true;
data: T;
};
type ValidationFailure = {
success: false;
reason: string;
};
export type ValidationResult<T> = ValidationSuccess<T> | ValidationFailure;
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function readString(source: Record<string, unknown>, key: string): string {
const value = source[key];
return typeof value === "string" ? value.trim() : "";
}
function readOptionalInteger(source: Record<string, unknown>, key: string): number | undefined | null {
const value = source[key];
if (value === undefined || value === null || value === "") {
return undefined;
}
const numeric = typeof value === "number" ? value : Number(value);
return Number.isInteger(numeric) ? numeric : null;
}
function readEnum<T extends string>(value: unknown, values: readonly T[]): T | null {
return typeof value === "string" ? values.find((item) => item === value) ?? null : null;
}
function readOptionalDate(source: Record<string, unknown>, key: string): Date | undefined | null {
const value = source[key];
if (value === undefined || value === null || value === "") {
return undefined;
}
if (typeof value !== "string") {
return null;
}
const date = new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
}
export function validateHealthProfileInput(value: unknown): ValidationResult<HealthProfileInput> {
if (!isRecord(value)) {
return { success: false, reason: "请求数据格式无效" };
}
return {
success: true,
data: {
allergyNotes: readString(value, "allergyNotes"),
medicalHistory: readString(value, "medicalHistory"),
medicationNotes: readString(value, "medicationNotes"),
careRestrictions: readString(value, "careRestrictions"),
emergencyNotes: readString(value, "emergencyNotes"),
},
};
}
export function validateVitalRecordInput(value: unknown): ValidationResult<VitalRecordInput> {
if (!isRecord(value)) {
return { success: false, reason: "请求数据格式无效" };
}
const elderId = readString(value, "elderId");
if (!elderId) {
return { success: false, reason: "老人不能为空" };
}
const recordedAt = readOptionalDate(value, "recordedAt");
if (!recordedAt) {
return { success: false, reason: "记录时间无效" };
}
const source = readEnum(value.source, VITAL_SOURCE_VALUES);
if (!source) {
return { success: false, reason: "记录来源无效" };
}
const integerKeys = [
"systolicBp",
"diastolicBp",
"heartRate",
"temperatureTenths",
"spo2",
"bloodGlucoseTenths",
"weightTenths",
] as const;
const parsed: Partial<Record<(typeof integerKeys)[number], number>> = {};
for (const key of integerKeys) {
const numberValue = readOptionalInteger(value, key);
if (numberValue === null) {
return { success: false, reason: "生命体征数值需为整数" };
}
if (numberValue !== undefined) {
parsed[key] = numberValue;
}
}
return {
success: true,
data: {
elderId,
recordedAt,
source,
notes: readString(value, "notes"),
...parsed,
},
};
}
export function validateChronicConditionInput(value: unknown): ValidationResult<ChronicConditionInput> {
if (!isRecord(value)) {
return { success: false, reason: "请求数据格式无效" };
}
const elderId = readString(value, "elderId");
const name = readString(value, "name");
if (!elderId || !name) {
return { success: false, reason: "老人和慢病名称不能为空" };
}
const status = readEnum(value.status, CHRONIC_CONDITION_STATUS_VALUES);
if (!status) {
return { success: false, reason: "慢病状态无效" };
}
const diagnosedAt = readOptionalDate(value, "diagnosedAt");
if (diagnosedAt === null) {
return { success: false, reason: "诊断日期无效" };
}
return {
success: true,
data: {
elderId,
name,
status,
diagnosedAt,
treatmentNotes: readString(value, "treatmentNotes"),
followUpNotes: readString(value, "followUpNotes"),
},
};
}
export function validateHealthReviewUpdateInput(value: unknown): ValidationResult<HealthReviewUpdateInput> {
if (!isRecord(value)) {
return { success: false, reason: "请求数据格式无效" };
}
const status = readEnum(value.status, HEALTH_REVIEW_STATUS_VALUES);
if (!status) {
return { success: false, reason: "复核状态无效" };
}
return {
success: true,
data: {
status,
resolutionNotes: readString(value, "resolutionNotes"),
},
};
}