feat(dc): Complete Phase 1 - Portal workbench page development
Summary: - Implement DC module Portal page with 3 tool cards - Create ToolCard component with decorative background and hover animations - Implement TaskList component with table layout and progress bars - Implement AssetLibrary component with tab switching and file cards - Complete database verification (4 tables confirmed) - Complete backend API verification (6 endpoints ready) - Optimize UI to match prototype design (V2.html) Frontend Components (~715 lines): - components/ToolCard.tsx - Tool cards with animations - components/TaskList.tsx - Recent tasks table view - components/AssetLibrary.tsx - Data asset library with tabs - hooks/useRecentTasks.ts - Task state management - hooks/useAssets.ts - Asset state management - pages/Portal.tsx - Main portal page - types/portal.ts - TypeScript type definitions Backend Verification: - Backend API: 1495 lines code verified - Database: dc_schema with 4 tables verified - API endpoints: 6 endpoints tested (templates API works) Documentation: - Database verification report - Backend API test report - Phase 1 completion summary - UI optimization report - Development task checklist - Development plan for Tool B Status: Phase 1 completed (100%), ready for browser testing Next: Phase 2 - Tool B Step 1 and 2 development
This commit is contained in:
246
backend/src/modules/dc/tool-b/services/TemplateService.ts
Normal file
246
backend/src/modules/dc/tool-b/services/TemplateService.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* DC模块 - 模板服务
|
||||
*
|
||||
* 功能:
|
||||
* - 管理预设提取模板(疾病类型 + 报告类型)
|
||||
* - 提供模板列表查询
|
||||
* - Seed初始数据(3个预设模板)
|
||||
*
|
||||
* 平台能力复用:
|
||||
* - ✅ prisma: 数据库操作
|
||||
* - ✅ logger: 日志记录
|
||||
*/
|
||||
|
||||
import { prisma } from '../../../../config/database.js';
|
||||
import { logger } from '../../../../common/logging/index.js';
|
||||
|
||||
export interface TemplateField {
|
||||
name: string;
|
||||
desc: string;
|
||||
width?: string; // TailwindCSS class
|
||||
}
|
||||
|
||||
export interface Template {
|
||||
id: string;
|
||||
diseaseType: string;
|
||||
reportType: string;
|
||||
displayName: string;
|
||||
fields: TemplateField[];
|
||||
promptTemplate: string;
|
||||
}
|
||||
|
||||
export class TemplateService {
|
||||
/**
|
||||
* 获取所有模板
|
||||
*/
|
||||
async getAllTemplates(): Promise<Template[]> {
|
||||
try {
|
||||
logger.info('[Template] Fetching all templates');
|
||||
|
||||
const templates = await prisma.dCTemplate.findMany({
|
||||
orderBy: [{ diseaseType: 'asc' }, { reportType: 'asc' }]
|
||||
});
|
||||
|
||||
logger.info('[Template] Templates fetched', { count: templates.length });
|
||||
|
||||
return templates.map(t => ({
|
||||
id: t.id,
|
||||
diseaseType: t.diseaseType,
|
||||
reportType: t.reportType,
|
||||
displayName: t.displayName,
|
||||
fields: t.fields as TemplateField[],
|
||||
promptTemplate: t.promptTemplate
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[Template] Failed to fetch templates', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据疾病和报告类型获取模板
|
||||
*/
|
||||
async getTemplate(diseaseType: string, reportType: string): Promise<Template | null> {
|
||||
try {
|
||||
logger.info('[Template] Fetching template', { diseaseType, reportType });
|
||||
|
||||
const template = await prisma.dCTemplate.findUnique({
|
||||
where: {
|
||||
diseaseType_reportType: { diseaseType, reportType }
|
||||
}
|
||||
});
|
||||
|
||||
if (!template) {
|
||||
logger.warn('[Template] Template not found', { diseaseType, reportType });
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: template.id,
|
||||
diseaseType: template.diseaseType,
|
||||
reportType: template.reportType,
|
||||
displayName: template.displayName,
|
||||
fields: template.fields as TemplateField[],
|
||||
promptTemplate: template.promptTemplate
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[Template] Failed to fetch template', { error, diseaseType, reportType });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化预设模板(Seed数据)
|
||||
*
|
||||
* 包括3个预设模板:
|
||||
* 1. 肺癌病理报告
|
||||
* 2. 糖尿病入院记录
|
||||
* 3. 高血压门诊病历
|
||||
*/
|
||||
async seedTemplates(): Promise<void> {
|
||||
try {
|
||||
logger.info('[Template] Seeding templates');
|
||||
|
||||
const templates = [
|
||||
// 1. 肺癌病理报告
|
||||
{
|
||||
diseaseType: 'lung_cancer',
|
||||
reportType: 'pathology',
|
||||
displayName: '肺癌病理报告',
|
||||
fields: [
|
||||
{ name: '病理类型', desc: '如:浸润性腺癌、鳞状细胞癌', width: 'w-40' },
|
||||
{ name: '分化程度', desc: '高/中/低分化', width: 'w-32' },
|
||||
{ name: '肿瘤大小', desc: '最大径,单位cm', width: 'w-32' },
|
||||
{ name: '淋巴结转移', desc: '有/无及具体组别', width: 'w-48' },
|
||||
{ name: '免疫组化', desc: '关键指标', width: 'w-56' }
|
||||
],
|
||||
promptTemplate: `你是一名病理学专家。请从以下肺癌病理报告中提取关键信息。
|
||||
|
||||
提取字段(必须返回以下所有字段):
|
||||
- 病理类型:病理诊断类型(如浸润性腺癌、鳞状细胞癌)
|
||||
- 分化程度:分化等级(高分化、中分化、低分化、未提及)
|
||||
- 肿瘤大小:肿瘤最大径,单位cm
|
||||
- 淋巴结转移:淋巴结转移情况(有/无及具体组别)
|
||||
- 免疫组化:关键免疫组化指标
|
||||
|
||||
**输出格式:严格的JSON格式(不要有任何额外文本):**
|
||||
\`\`\`json
|
||||
{
|
||||
"病理类型": "...",
|
||||
"分化程度": "...",
|
||||
"肿瘤大小": "...",
|
||||
"淋巴结转移": "...",
|
||||
"免疫组化": "..."
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
如果某个信息未在报告中提及,请填写"未提及"。`
|
||||
},
|
||||
|
||||
// 2. 糖尿病入院记录
|
||||
{
|
||||
diseaseType: 'diabetes',
|
||||
reportType: 'admission',
|
||||
displayName: '糖尿病入院记录',
|
||||
fields: [
|
||||
{ name: '主诉', desc: '患者入院的主要症状', width: 'w-48' },
|
||||
{ name: '现病史', desc: '发病过程', width: 'w-64' },
|
||||
{ name: '既往史', desc: '糖尿病病史年限', width: 'w-40' },
|
||||
{ name: '空腹血糖', desc: '单位mmol/L', width: 'w-32' },
|
||||
{ name: '糖化血红蛋白', desc: '单位%', width: 'w-32' }
|
||||
],
|
||||
promptTemplate: `你是一名内分泌科专家。请从以下糖尿病患者入院记录中提取关键信息。
|
||||
|
||||
提取字段(必须返回以下所有字段):
|
||||
- 主诉:患者入院时的主要症状
|
||||
- 现病史:本次发病的过程和表现
|
||||
- 既往史:糖尿病病史年限
|
||||
- 空腹血糖:最近的空腹血糖值(单位mmol/L)
|
||||
- 糖化血红蛋白:最近的HbA1c值(单位%)
|
||||
|
||||
**输出格式:严格的JSON格式:**
|
||||
\`\`\`json
|
||||
{
|
||||
"主诉": "...",
|
||||
"现病史": "...",
|
||||
"既往史": "...",
|
||||
"空腹血糖": "...",
|
||||
"糖化血红蛋白": "..."
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
如果某个信息未在记录中提及,请填写"未提及"。`
|
||||
},
|
||||
|
||||
// 3. 高血压门诊病历
|
||||
{
|
||||
diseaseType: 'hypertension',
|
||||
reportType: 'outpatient',
|
||||
displayName: '高血压门诊病历',
|
||||
fields: [
|
||||
{ name: '血压值', desc: '单位mmHg', width: 'w-32' },
|
||||
{ name: '心率', desc: '单位次/分', width: 'w-24' },
|
||||
{ name: '当前用药', desc: '高血压药物', width: 'w-56' },
|
||||
{ name: '靶器官损害', desc: '心/脑/肾', width: 'w-40' },
|
||||
{ name: '危险分层', desc: '低/中/高/极高危', width: 'w-32' }
|
||||
],
|
||||
promptTemplate: `你是一名心内科专家。请从以下高血压患者门诊病历中提取关键信息。
|
||||
|
||||
提取字段(必须返回以下所有字段):
|
||||
- 血压值:收缩压/舒张压(单位mmHg)
|
||||
- 心率:心率(单位次/分)
|
||||
- 当前用药:患者当前服用的高血压药物
|
||||
- 靶器官损害:心脏、脑、肾脏等靶器官损害情况
|
||||
- 危险分层:心血管风险分层(低危、中危、高危、极高危)
|
||||
|
||||
**输出格式:严格的JSON格式:**
|
||||
\`\`\`json
|
||||
{
|
||||
"血压值": "...",
|
||||
"心率": "...",
|
||||
"当前用药": "...",
|
||||
"靶器官损害": "...",
|
||||
"危险分层": "..."
|
||||
}
|
||||
\`\`\`
|
||||
|
||||
如果某个信息未在病历中提及,请填写"未提及"。`
|
||||
}
|
||||
];
|
||||
|
||||
// 使用upsert避免重复
|
||||
for (const template of templates) {
|
||||
await prisma.dCTemplate.upsert({
|
||||
where: {
|
||||
diseaseType_reportType: {
|
||||
diseaseType: template.diseaseType,
|
||||
reportType: template.reportType
|
||||
}
|
||||
},
|
||||
update: {
|
||||
displayName: template.displayName,
|
||||
fields: template.fields,
|
||||
promptTemplate: template.promptTemplate
|
||||
},
|
||||
create: template
|
||||
});
|
||||
}
|
||||
|
||||
logger.info('[Template] Templates seeded successfully', { count: templates.length });
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[Template] Failed to seed templates', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
export const templateService = new TemplateService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user