feat: complete ops dashboard AI board
This commit is contained in:
@@ -979,7 +979,7 @@ const DEFAULT_SYSTEM_INCIDENTS: SeedSystemIncident[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_DEVICE_ASSETS: SeedDeviceAsset[] = [
|
||||
export const DEFAULT_DEVICE_ASSETS: SeedDeviceAsset[] = [
|
||||
{
|
||||
name: "A102 床头呼叫器",
|
||||
code: "DEV-CALL-A102-2",
|
||||
@@ -1018,7 +1018,7 @@ const DEFAULT_DEVICE_ASSETS: SeedDeviceAsset[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_MAINTENANCE_TICKETS: SeedMaintenanceTicket[] = [
|
||||
export const DEFAULT_MAINTENANCE_TICKETS: SeedMaintenanceTicket[] = [
|
||||
{
|
||||
deviceCode: "DEV-CALL-A102-2",
|
||||
title: "床头呼叫器按键失灵",
|
||||
@@ -1048,7 +1048,7 @@ const DEFAULT_MAINTENANCE_TICKETS: SeedMaintenanceTicket[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_NOTICES: SeedNotice[] = [
|
||||
export const DEFAULT_NOTICES: SeedNotice[] = [
|
||||
{
|
||||
title: "本周家属探访安排",
|
||||
content: "周六上午开放护理楼一层和康复楼探访,请各护理组提前确认老人状态和陪同人员。",
|
||||
@@ -1071,7 +1071,7 @@ const DEFAULT_NOTICES: SeedNotice[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_ALERT_RULES: SeedAlertRule[] = [
|
||||
export const DEFAULT_ALERT_RULES: SeedAlertRule[] = [
|
||||
{
|
||||
name: "血氧低值连续告警",
|
||||
ruleType: "health",
|
||||
@@ -1098,7 +1098,7 @@ const DEFAULT_ALERT_RULES: SeedAlertRule[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_ALERT_TRIGGERS: SeedAlertTrigger[] = [
|
||||
export const DEFAULT_ALERT_TRIGGERS: SeedAlertTrigger[] = [
|
||||
{
|
||||
ruleName: "血氧低值连续告警",
|
||||
elderName: "钱松柏",
|
||||
@@ -1130,20 +1130,20 @@ const DEFAULT_ALERT_TRIGGERS: SeedAlertTrigger[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const DEFAULT_FAMILY_CONTACTS: SeedFamilyContact[] = [
|
||||
export const DEFAULT_FAMILY_CONTACTS: SeedFamilyContact[] = [
|
||||
{ elderName: "王桂兰", name: "张敏", relationship: "女儿", phone: "13800000001", status: "active", notes: "主要联系人,每周六探访。" },
|
||||
{ elderName: "钱松柏", name: "钱宁", relationship: "儿子", phone: "13800000011", status: "active", notes: "关注夜间吸氧情况。" },
|
||||
{ elderName: "林玉琴", name: "林晓", relationship: "女儿", phone: "13800000014", status: "active", notes: "短住托养资料已确认。" },
|
||||
{ elderName: "赵国华", name: "赵蕾", relationship: "孙女", phone: "13800000016", status: "active", notes: "希望接收护理反馈。" },
|
||||
];
|
||||
|
||||
const DEFAULT_FAMILY_VISITS: SeedFamilyVisit[] = [
|
||||
export const DEFAULT_FAMILY_VISITS: SeedFamilyVisit[] = [
|
||||
{ elderName: "王桂兰", contactName: "张敏", scheduledHoursOffset: 30, status: "approved", notes: "安排护理楼一层会客区。" },
|
||||
{ elderName: "钱松柏", contactName: "钱宁", scheduledHoursOffset: 8, status: "requested", notes: "需医生确认夜间血氧情况后再回复。" },
|
||||
{ elderName: "林玉琴", contactName: "林晓", scheduledHoursOffset: -20, status: "completed", notes: "已完成探访和短住事项确认。" },
|
||||
];
|
||||
|
||||
const DEFAULT_FAMILY_FEEDBACK: SeedFamilyFeedback[] = [
|
||||
export const DEFAULT_FAMILY_FEEDBACK: SeedFamilyFeedback[] = [
|
||||
{
|
||||
elderName: "赵国华",
|
||||
contactName: "赵蕾",
|
||||
@@ -1510,6 +1510,271 @@ async function seedDefaultAiWorkspaceData(organizationId: string): Promise<void>
|
||||
});
|
||||
}
|
||||
|
||||
function getFamilyContactKey(elderId: string, contactName: string): string {
|
||||
return `${elderId}:${contactName}`;
|
||||
}
|
||||
|
||||
function getFamilyVisitKey(elderId: string, contactId: string, notes: string): string {
|
||||
return `${elderId}:${contactId}:${notes}`;
|
||||
}
|
||||
|
||||
function getFamilyFeedbackKey(elderId: string, contactId: string, content: string): string {
|
||||
return `${elderId}:${contactId}:${content}`;
|
||||
}
|
||||
|
||||
export async function seedDefaultCollaborationWorkspaceData(organizationId: string): Promise<void> {
|
||||
const database = getDatabase();
|
||||
const [
|
||||
elderRows,
|
||||
existingDeviceRows,
|
||||
existingTicketRows,
|
||||
existingNoticeRows,
|
||||
existingAlertRuleRows,
|
||||
existingAlertTriggerRows,
|
||||
existingFamilyContactRows,
|
||||
existingFamilyVisitRows,
|
||||
existingFamilyFeedbackRows,
|
||||
] = await Promise.all([
|
||||
database.select({ id: elders.id, name: elders.name }).from(elders).where(eq(elders.organizationId, organizationId)),
|
||||
database.select().from(deviceAssets).where(eq(deviceAssets.organizationId, organizationId)),
|
||||
database.select().from(maintenanceTickets).where(eq(maintenanceTickets.organizationId, organizationId)),
|
||||
database.select().from(notices).where(eq(notices.organizationId, organizationId)),
|
||||
database.select().from(alertRules).where(eq(alertRules.organizationId, organizationId)),
|
||||
database.select().from(alertTriggers).where(eq(alertTriggers.organizationId, organizationId)),
|
||||
database.select().from(familyContacts).where(eq(familyContacts.organizationId, organizationId)),
|
||||
database.select().from(familyVisitAppointments).where(eq(familyVisitAppointments.organizationId, organizationId)),
|
||||
database.select().from(familyFeedback).where(eq(familyFeedback.organizationId, organizationId)),
|
||||
]);
|
||||
|
||||
const elderIdByName = new Map(elderRows.map((elder) => [elder.name, elder.id]));
|
||||
const existingDeviceCodes = new Set(existingDeviceRows.map((device) => device.code));
|
||||
const existingTicketTitles = new Set(existingTicketRows.map((ticket) => ticket.title));
|
||||
const existingNoticeTitles = new Set(existingNoticeRows.map((notice) => notice.title));
|
||||
const existingAlertRuleNames = new Set(existingAlertRuleRows.map((rule) => rule.name));
|
||||
const existingAlertTriggerTitles = new Set(existingAlertTriggerRows.map((trigger) => trigger.title));
|
||||
const existingFamilyContactKeys = new Set(existingFamilyContactRows.map((contact) => getFamilyContactKey(contact.elderId, contact.name)));
|
||||
const existingFamilyVisitKeys = new Set(
|
||||
existingFamilyVisitRows
|
||||
.map((visit) => (visit.contactId ? getFamilyVisitKey(visit.elderId, visit.contactId, visit.notes) : ""))
|
||||
.filter(Boolean),
|
||||
);
|
||||
const existingFamilyFeedbackKeys = new Set(
|
||||
existingFamilyFeedbackRows
|
||||
.map((feedback) => (feedback.contactId ? getFamilyFeedbackKey(feedback.elderId, feedback.contactId, feedback.content) : ""))
|
||||
.filter(Boolean),
|
||||
);
|
||||
|
||||
const getContactId = (contactRows: typeof existingFamilyContactRows, elderId: string, contactName: string): string | undefined =>
|
||||
contactRows.find((contact) => contact.elderId === elderId && contact.name === contactName)?.id;
|
||||
const getMissingFamilyVisits = (contactRows: typeof existingFamilyContactRows): SeedFamilyVisit[] =>
|
||||
DEFAULT_FAMILY_VISITS.filter((visit) => {
|
||||
const elderId = elderIdByName.get(visit.elderName);
|
||||
const contactId = elderId ? getContactId(contactRows, elderId, visit.contactName) : undefined;
|
||||
return Boolean(elderId && contactId && !existingFamilyVisitKeys.has(getFamilyVisitKey(elderId, contactId, visit.notes)));
|
||||
});
|
||||
const getMissingFamilyFeedback = (contactRows: typeof existingFamilyContactRows): SeedFamilyFeedback[] =>
|
||||
DEFAULT_FAMILY_FEEDBACK.filter((feedback) => {
|
||||
const elderId = elderIdByName.get(feedback.elderName);
|
||||
const contactId = elderId ? getContactId(contactRows, elderId, feedback.contactName) : undefined;
|
||||
return Boolean(elderId && contactId && !existingFamilyFeedbackKeys.has(getFamilyFeedbackKey(elderId, contactId, feedback.content)));
|
||||
});
|
||||
|
||||
const missingDevices = DEFAULT_DEVICE_ASSETS.filter((device) => !existingDeviceCodes.has(device.code));
|
||||
const missingTickets = DEFAULT_MAINTENANCE_TICKETS.filter((ticket) => !existingTicketTitles.has(ticket.title));
|
||||
const missingNotices = DEFAULT_NOTICES.filter((notice) => !existingNoticeTitles.has(notice.title));
|
||||
const missingAlertRules = DEFAULT_ALERT_RULES.filter((rule) => !existingAlertRuleNames.has(rule.name));
|
||||
const missingAlertTriggers = DEFAULT_ALERT_TRIGGERS.filter((trigger) => !existingAlertTriggerTitles.has(trigger.title));
|
||||
const missingFamilyContacts = DEFAULT_FAMILY_CONTACTS.filter((contact) => {
|
||||
const elderId = elderIdByName.get(contact.elderName);
|
||||
return Boolean(elderId && !existingFamilyContactKeys.has(getFamilyContactKey(elderId, contact.name)));
|
||||
});
|
||||
const missingFamilyVisits = getMissingFamilyVisits(existingFamilyContactRows);
|
||||
const missingFamilyFeedback = getMissingFamilyFeedback(existingFamilyContactRows);
|
||||
|
||||
if (
|
||||
missingDevices.length === 0 &&
|
||||
missingTickets.length === 0 &&
|
||||
missingNotices.length === 0 &&
|
||||
missingAlertRules.length === 0 &&
|
||||
missingAlertTriggers.length === 0 &&
|
||||
missingFamilyContacts.length === 0 &&
|
||||
missingFamilyVisits.length === 0 &&
|
||||
missingFamilyFeedback.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
await database.transaction(async (transaction) => {
|
||||
const insertedDeviceRows = missingDevices.length > 0
|
||||
? await transaction
|
||||
.insert(deviceAssets)
|
||||
.values(
|
||||
missingDevices.map((device) => ({
|
||||
organizationId,
|
||||
name: device.name,
|
||||
code: device.code,
|
||||
category: device.category,
|
||||
location: device.location,
|
||||
status: device.status,
|
||||
lastInspectedAt: device.lastInspectedHoursAgo === undefined ? undefined : new Date(now.getTime() - device.lastInspectedHoursAgo * 60 * 60 * 1000),
|
||||
notes: device.notes,
|
||||
})),
|
||||
)
|
||||
.returning()
|
||||
: [];
|
||||
if (insertedDeviceRows.length !== missingDevices.length) {
|
||||
throw new Error("默认设备台账初始化失败");
|
||||
}
|
||||
const deviceIdByCode = new Map([...existingDeviceRows, ...insertedDeviceRows].map((device) => [device.code, device.id]));
|
||||
|
||||
if (missingTickets.length > 0) {
|
||||
const ticketValues = missingTickets.map((ticket) => {
|
||||
const deviceId = deviceIdByCode.get(ticket.deviceCode);
|
||||
if (!deviceId) {
|
||||
throw new Error("默认维修工单初始化失败");
|
||||
}
|
||||
|
||||
return {
|
||||
organizationId,
|
||||
deviceId,
|
||||
title: ticket.title,
|
||||
description: ticket.description,
|
||||
priority: ticket.priority,
|
||||
status: ticket.status,
|
||||
assigneeLabel: ticket.assigneeLabel,
|
||||
resolutionNotes: ticket.resolutionNotes,
|
||||
resolvedAt: ticket.status === "resolved" || ticket.status === "closed" ? now : undefined,
|
||||
};
|
||||
});
|
||||
await transaction.insert(maintenanceTickets).values(ticketValues);
|
||||
}
|
||||
|
||||
if (missingNotices.length > 0) {
|
||||
await transaction.insert(notices).values(
|
||||
missingNotices.map((notice) => ({
|
||||
organizationId,
|
||||
title: notice.title,
|
||||
content: notice.content,
|
||||
audience: notice.audience,
|
||||
status: notice.status,
|
||||
publishedAt: notice.publishedHoursAgo === undefined ? undefined : new Date(now.getTime() - notice.publishedHoursAgo * 60 * 60 * 1000),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
const insertedAlertRuleRows = missingAlertRules.length > 0
|
||||
? await transaction.insert(alertRules).values(missingAlertRules.map((rule) => ({ ...rule, organizationId }))).returning()
|
||||
: [];
|
||||
if (insertedAlertRuleRows.length !== missingAlertRules.length) {
|
||||
throw new Error("默认预警规则初始化失败");
|
||||
}
|
||||
const alertRuleIdByName = new Map([...existingAlertRuleRows, ...insertedAlertRuleRows].map((rule) => [rule.name, rule.id]));
|
||||
|
||||
const alertTriggerValues = missingAlertTriggers
|
||||
.map((trigger) => {
|
||||
const ruleId = alertRuleIdByName.get(trigger.ruleName);
|
||||
const elderId = trigger.elderName ? elderIdByName.get(trigger.elderName) : undefined;
|
||||
if (!ruleId || (trigger.elderName && !elderId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
organizationId,
|
||||
ruleId,
|
||||
elderId,
|
||||
title: trigger.title,
|
||||
description: trigger.description,
|
||||
status: trigger.status,
|
||||
source: trigger.source,
|
||||
handlingNotes: trigger.handlingNotes,
|
||||
handledAt: trigger.status === "open" ? undefined : now,
|
||||
createdAt: new Date(now.getTime() - trigger.createdHoursAgo * 60 * 60 * 1000),
|
||||
updatedAt: now,
|
||||
};
|
||||
})
|
||||
.filter((value): value is Exclude<typeof value, null> => value !== null);
|
||||
if (alertTriggerValues.length > 0) {
|
||||
await transaction.insert(alertTriggers).values(alertTriggerValues);
|
||||
}
|
||||
|
||||
const insertedFamilyContactRows = missingFamilyContacts.length > 0
|
||||
? await transaction
|
||||
.insert(familyContacts)
|
||||
.values(
|
||||
missingFamilyContacts.map((contact) => {
|
||||
const elderId = elderIdByName.get(contact.elderName);
|
||||
if (!elderId) {
|
||||
throw new Error("默认家属联系人初始化失败");
|
||||
}
|
||||
|
||||
return {
|
||||
organizationId,
|
||||
elderId,
|
||||
name: contact.name,
|
||||
relationship: contact.relationship,
|
||||
phone: contact.phone,
|
||||
status: contact.status,
|
||||
notes: contact.notes,
|
||||
};
|
||||
}),
|
||||
)
|
||||
.returning()
|
||||
: [];
|
||||
if (insertedFamilyContactRows.length !== missingFamilyContacts.length) {
|
||||
throw new Error("默认家属联系人初始化失败");
|
||||
}
|
||||
const allFamilyContactRows = [...existingFamilyContactRows, ...insertedFamilyContactRows];
|
||||
|
||||
const familyVisitValues = getMissingFamilyVisits(allFamilyContactRows)
|
||||
.map((visit) => {
|
||||
const elderId = elderIdByName.get(visit.elderName);
|
||||
const contactId = elderId ? getContactId(allFamilyContactRows, elderId, visit.contactName) : undefined;
|
||||
if (!elderId || !contactId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
organizationId,
|
||||
elderId,
|
||||
contactId,
|
||||
scheduledAt: new Date(now.getTime() + visit.scheduledHoursOffset * 60 * 60 * 1000),
|
||||
status: visit.status,
|
||||
notes: visit.notes,
|
||||
handledAt: visit.status === "requested" ? undefined : now,
|
||||
};
|
||||
})
|
||||
.filter((value): value is Exclude<typeof value, null> => value !== null);
|
||||
if (familyVisitValues.length > 0) {
|
||||
await transaction.insert(familyVisitAppointments).values(familyVisitValues);
|
||||
}
|
||||
|
||||
const familyFeedbackValues = getMissingFamilyFeedback(allFamilyContactRows)
|
||||
.map((feedback) => {
|
||||
const elderId = elderIdByName.get(feedback.elderName);
|
||||
const contactId = elderId ? getContactId(allFamilyContactRows, elderId, feedback.contactName) : undefined;
|
||||
if (!elderId || !contactId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
organizationId,
|
||||
elderId,
|
||||
contactId,
|
||||
feedbackType: feedback.feedbackType,
|
||||
content: feedback.content,
|
||||
status: feedback.status,
|
||||
responseNotes: feedback.responseNotes,
|
||||
handledAt: feedback.status === "open" ? undefined : now,
|
||||
};
|
||||
})
|
||||
.filter((value): value is Exclude<typeof value, null> => value !== null);
|
||||
if (familyFeedbackValues.length > 0) {
|
||||
await transaction.insert(familyFeedback).values(familyFeedbackValues);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function hasRows(rows: unknown[]): boolean {
|
||||
return rows.length > 0;
|
||||
}
|
||||
@@ -1524,6 +1789,7 @@ export async function seedDefaultWorkspaceData(organizationId: string): Promise<
|
||||
]);
|
||||
|
||||
if ([existingRooms, existingBeds, existingElders, existingAdmissions].some(hasRows)) {
|
||||
await seedDefaultCollaborationWorkspaceData(organizationId);
|
||||
await seedDefaultAiWorkspaceData(organizationId);
|
||||
return;
|
||||
}
|
||||
@@ -1983,5 +2249,6 @@ export async function seedDefaultWorkspaceData(organizationId: string): Promise<
|
||||
}),
|
||||
);
|
||||
});
|
||||
await seedDefaultCollaborationWorkspaceData(organizationId);
|
||||
await seedDefaultAiWorkspaceData(organizationId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user