Summary of fixes: - Fix service discovery address (change .sae domain to internal IP) - Unify timezone configuration (Asia/Shanghai for all services) - Enhance ECS security group configuration (Redis/Weaviate port binding) - Add image pull strategy best practices - Add Python service memory management guidelines - Update Dify API Key deployment strategy (avoid deadlock) - Add SSH tunnel for RDS database access - Add NAT gateway cost optimization explanation Modified files (7 docs): - 00-部署架构总览.md (enhanced with 7 sections) - 03-Dify-ECS部署完全指南.md (security hardening) - 04-Python微服务-SAE容器部署指南.md (timezone + service discovery) - 05-Node.js后端-SAE容器部署指南.md (timezone configuration) - PostgreSQL部署策略-摸底报告.md (timezone best practice) - 07-关键配置补充说明.md (3 new sections) - 08-部署检查清单.md (service address fix) New files: - 文档修正报告-20251214.md (comprehensive fix report) - Review documents from technical team Impact: - Fixed 3 P0/P1 critical issues (100% connection failure risk) - Fixed 3 P2 important issues (stability and maintainability) - Added 2 P3 best practices (developer convenience) Status: All deployment documents reviewed and corrected, ready for production deployment
263 lines
7.4 KiB
TypeScript
263 lines
7.4 KiB
TypeScript
/**
|
||
* 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();
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|