feat: add AI knowledge analysis MVP

This commit is contained in:
2026-07-05 00:47:52 -07:00
parent 6aa72d3b43
commit e204974b57
32 changed files with 8705 additions and 4 deletions

View File

@@ -45,6 +45,10 @@ export const PERMISSION_DEFINITIONS: PermissionDefinition[] = [
{ id: "elder:create", label: "新增老人档案", category: "老人", description: "创建老人档案。" },
{ id: "elder:update", label: "更新老人档案", category: "老人", description: "更新老人档案和照护信息。" },
{ id: "elder:delete", label: "删除老人档案", category: "老人", description: "删除老人档案。" },
{ id: "ai:read", label: "查看 AI 分析", category: "AI", description: "查看并生成授权数据范围内的 AI 分析。" },
{ id: "ai:manage", label: "管理 AI 能力", category: "AI", description: "管理 AI 能力配置和分析治理。" },
{ id: "knowledge:read", label: "查看知识库", category: "AI", description: "查看并检索平台和机构知识库内容。" },
{ id: "knowledge:manage", label: "管理知识库", category: "AI", description: "维护平台或机构知识库条目。" },
];
type SeedRole = {
@@ -121,6 +125,10 @@ export const SYSTEM_ROLE_DEFINITIONS: SeedRole[] = [
"elder:create",
"elder:update",
"elder:delete",
"ai:read",
"ai:manage",
"knowledge:read",
"knowledge:manage",
],
},
{
@@ -151,6 +159,10 @@ export const SYSTEM_ROLE_DEFINITIONS: SeedRole[] = [
"elder:create",
"elder:update",
"elder:delete",
"ai:read",
"ai:manage",
"knowledge:read",
"knowledge:manage",
],
},
{
@@ -169,6 +181,8 @@ export const SYSTEM_ROLE_DEFINITIONS: SeedRole[] = [
"admission:read",
"elder:read",
"elder:update",
"ai:read",
"knowledge:read",
],
},
{

View File

@@ -1,7 +1,9 @@
import { sql } from "drizzle-orm";
import {
boolean,
index,
integer,
jsonb,
pgEnum,
pgTable,
primaryKey,
@@ -9,6 +11,7 @@ import {
timestamp,
uniqueIndex,
uuid,
vector,
} from "drizzle-orm/pg-core";
export const accountStatusEnum = pgEnum("account_status", ["active", "disabled", "pending"]);
@@ -41,6 +44,11 @@ export const familyContactStatusEnum = pgEnum("family_contact_status", ["active"
export const familyVisitStatusEnum = pgEnum("family_visit_status", ["requested", "approved", "completed", "cancelled"]);
export const familyFeedbackTypeEnum = pgEnum("family_feedback_type", ["service", "care", "meal", "environment", "other"]);
export const familyFeedbackStatusEnum = pgEnum("family_feedback_status", ["open", "in_progress", "resolved", "closed"]);
export const aiKnowledgeScopeEnum = pgEnum("ai_knowledge_scope", ["platform", "organization"]);
export const aiKnowledgeStatusEnum = pgEnum("ai_knowledge_status", ["enabled", "disabled"]);
export const elderAiAnalysisStatusEnum = pgEnum("elder_ai_analysis_status", ["completed", "failed"]);
export const AI_KNOWLEDGE_EMBEDDING_DIMENSIONS = 1536;
export const systemSettings = pgTable("system_settings", {
id: text("id").primaryKey().default("global"),
@@ -521,3 +529,55 @@ export const systemIncidents = pgTable("system_incidents", {
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
});
export const aiKnowledgeEntries = pgTable("ai_knowledge_entries", {
id: uuid("id").primaryKey().defaultRandom(),
scope: aiKnowledgeScopeEnum("scope").notNull(),
organizationId: uuid("organization_id").references(() => organizations.id, { onDelete: "cascade" }),
title: text("title").notNull(),
category: text("category").notNull().default(""),
tags: text("tags").notNull().default(""),
body: text("body").notNull(),
status: aiKnowledgeStatusEnum("status").notNull().default("enabled"),
createdByAccountId: uuid("created_by_account_id").references(() => accounts.id, { onDelete: "set null" }),
updatedByAccountId: uuid("updated_by_account_id").references(() => accounts.id, { onDelete: "set null" }),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
}, (table) => ({
scopeLookup: index("ai_knowledge_entries_scope_lookup").on(table.scope, table.status),
organizationLookup: index("ai_knowledge_entries_organization_lookup").on(table.organizationId, table.status),
}));
export const aiKnowledgeChunks = pgTable("ai_knowledge_chunks", {
id: uuid("id").primaryKey().defaultRandom(),
entryId: uuid("entry_id").notNull().references(() => aiKnowledgeEntries.id, { onDelete: "cascade" }),
organizationId: uuid("organization_id").references(() => organizations.id, { onDelete: "cascade" }),
scope: aiKnowledgeScopeEnum("scope").notNull(),
chunkIndex: integer("chunk_index").notNull(),
content: text("content").notNull(),
embedding: vector("embedding", { dimensions: AI_KNOWLEDGE_EMBEDDING_DIMENSIONS }).notNull(),
sourceTitle: text("source_title").notNull(),
sourceCategory: text("source_category").notNull().default(""),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
}, (table) => ({
entryLookup: index("ai_knowledge_chunks_entry_lookup").on(table.entryId),
scopeLookup: index("ai_knowledge_chunks_scope_lookup").on(table.scope, table.organizationId),
}));
export const elderAiAnalyses = pgTable("elder_ai_analyses", {
id: uuid("id").primaryKey().defaultRandom(),
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }),
elderId: uuid("elder_id").notNull().references(() => elders.id, { onDelete: "cascade" }),
actorAccountId: uuid("actor_account_id").references(() => accounts.id, { onDelete: "set null" }),
status: elderAiAnalysisStatusEnum("status").notNull(),
dataScopes: jsonb("data_scopes").$type<string[]>().notNull().default(sql`'[]'::jsonb`),
resultJson: jsonb("result_json"),
citationsJson: jsonb("citations_json").notNull().default(sql`'[]'::jsonb`),
modelSummaryJson: jsonb("model_summary_json").notNull().default(sql`'{}'::jsonb`),
errorCategory: text("error_category").notNull().default(""),
errorReason: text("error_reason").notNull().default(""),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
}, (table) => ({
elderLookup: index("elder_ai_analyses_elder_lookup").on(table.organizationId, table.elderId, table.createdAt),
statusLookup: index("elder_ai_analyses_status_lookup").on(table.organizationId, table.status),
}));