Commit ad3de4e4 authored by yuguo's avatar yuguo

fix

parent f1c4920f
......@@ -18,7 +18,10 @@
"Bash(kill:*)",
"Bash(go run:*)",
"Bash(npm run:*)",
"Bash(lsof:*)"
"Bash(lsof:*)",
"Bash(ipconfig:*)",
"Bash(npm:*)",
"Bash(./api.exe:*)"
]
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -6,11 +6,15 @@ import (
"github.com/gin-gonic/gin"
internalagent "internet-hospital/internal/agent"
"internet-hospital/internal/model"
"internet-hospital/internal/service/knowledgesvc"
"internet-hospital/internal/service/workflowsvc"
"internet-hospital/internal/service/admin"
"internet-hospital/internal/service/consult"
"internet-hospital/internal/service/doctor"
"internet-hospital/internal/service/doctorportal"
"internet-hospital/internal/service/payment"
"internet-hospital/internal/service/preconsult"
"internet-hospital/internal/service/chronic"
"internet-hospital/internal/service/health"
......@@ -19,6 +23,7 @@ import (
"internet-hospital/pkg/config"
"internet-hospital/pkg/database"
"internet-hospital/pkg/middleware"
"internet-hospital/pkg/websocket"
)
func main() {
......@@ -69,6 +74,24 @@ func main() {
&model.AIUsageLog{},
&model.PromptTemplate{},
&model.SystemLog{},
// Agent相关
&model.AgentTool{},
&model.AgentToolLog{},
&model.AgentDefinition{},
&model.AgentSession{},
&model.AgentExecutionLog{},
// 工作流相关
&model.WorkflowDefinition{},
&model.WorkflowExecution{},
&model.WorkflowHumanTask{},
// 知识库相关
&model.KnowledgeCollection{},
&model.KnowledgeDocument{},
&model.KnowledgeChunk{},
// 支付相关
&model.PaymentOrder{},
&model.DoctorIncome{},
&model.DoctorWithdrawal{},
); err != nil {
log.Printf("Warning: AutoMigrate failed: %v", err)
} else {
......@@ -111,6 +134,9 @@ func main() {
log.Printf("Warning: Failed to init departments and doctors: %v", err)
}
// 初始化Agent工具
internalagent.InitTools()
// 设置 Gin 模式
gin.SetMode(cfg.Server.Mode)
......@@ -159,6 +185,42 @@ func main() {
adminHandler := admin.NewHandler()
adminHandler.RegisterRoutes(authApi)
// Agent路由(需要认证)
agentHandler := internalagent.NewHandler()
agentHandler.RegisterRoutes(authApi)
// 工作流路由(需要认证)
workflowHandler := workflowsvc.NewHandler()
workflowHandler.RegisterRoutes(authApi)
// 知识库路由(需要认证)
knowledgeHandler := knowledgesvc.NewHandler()
knowledgeHandler.RegisterRoutes(authApi)
// 支付路由(需要认证)
paymentHandler := payment.NewHandler()
paymentHandler.RegisterRoutes(authApi)
// WebSocket路由(问诊实时消息)
wsHandler := websocket.NewHandler(func(consultID, senderID, senderType, content, contentType string) (*websocket.Message, error) {
// 保存消息到数据库
msg, err := consult.NewService().SendMessage(nil, consultID, senderID, senderType, content, contentType)
if err != nil {
return nil, err
}
return &websocket.Message{
Type: "message",
ConsultID: consultID,
SenderID: senderID,
SenderType: senderType,
Content: content,
ContentType: contentType,
MessageID: msg.ID,
Timestamp: msg.CreatedAt,
}, nil
})
wsHandler.RegisterRoutes(api)
// 健康检查
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
......
......@@ -6,6 +6,8 @@ require (
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.1
github.com/lib/pq v1.11.2
github.com/redis/go-redis/v9 v9.7.0
github.com/spf13/viper v1.19.0
golang.org/x/crypto v0.29.0
......@@ -38,7 +40,6 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.11.2 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
......
......@@ -45,6 +45,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
......
package internalagent
import (
"internet-hospital/pkg/agent"
)
// NewPreConsultAgent 预问诊Agent
func NewPreConsultAgent() *agent.ReActAgent {
return agent.NewReActAgent(agent.ReActConfig{
ID: "pre_consult_agent",
Name: "预问诊智能助手",
Description: "通过多轮对话收集患者症状,生成预问诊报告",
SystemPrompt: `你是一位专业的AI预问诊助手。你的职责是:
1. 通过友好的对话收集患者的症状信息
2. 询问症状的持续时间、严重程度、伴随症状等
3. 利用工具查询症状相关知识
4. 推荐合适的就诊科室
5. 生成简洁的预问诊报告
请用中文与患者交流,语气温和专业。不要做出确定性诊断,只提供参考建议。`,
Tools: []string{
"query_symptom_knowledge",
"recommend_department",
},
MaxIterations: 5,
})
}
// NewDiagnosisAgent 诊断辅助Agent
func NewDiagnosisAgent() *agent.ReActAgent {
return agent.NewReActAgent(agent.ReActConfig{
ID: "diagnosis_agent",
Name: "诊断辅助Agent",
Description: "辅助医生进行诊断,提供鉴别诊断建议",
SystemPrompt: `你是一位经验丰富的诊断辅助AI,协助医生进行临床决策。
你可以:
1. 查询患者病历记录(使用query_medical_record)
2. 检索医学知识库获取临床指南和疾病信息(使用search_medical_knowledge)
3. 分析症状和检验结果
4. 提供鉴别诊断建议
5. 推荐进一步检查项目
诊断流程:
- 首先查询患者病历了解病史
- 使用知识库检索相关疾病的诊断标准和鉴别要点
- 综合分析后给出诊断建议
请基于循证医学原则提供建议,所有建议仅供医生参考。`,
Tools: []string{
"query_medical_record",
"query_symptom_knowledge",
"search_medical_knowledge",
},
MaxIterations: 10,
})
}
// NewPrescriptionAgent 处方审核Agent
func NewPrescriptionAgent() *agent.ReActAgent {
return agent.NewReActAgent(agent.ReActConfig{
ID: "prescription_agent",
Name: "处方审核Agent",
Description: "审核处方合理性,检查药物相互作用、禁忌症和剂量",
SystemPrompt: `你是一位专业的临床药师AI,负责处方审核。
你的职责:
1. 查询药品信息(规格、用法、禁忌)
2. 检查药物相互作用(使用check_drug_interaction工具)
3. 检查患者禁忌症(使用check_contraindication工具)
4. 验证剂量是否合理(使用calculate_dosage工具)
5. 综合评估处方安全性,给出审核意见
审核流程:
- 首先使用check_drug_interaction检查所有药品的相互作用
- 然后对每个药品使用check_contraindication检查患者禁忌
- 使用calculate_dosage验证剂量是否在安全范围内
- 最后综合所有检查结果给出审核意见
请严格按照药品说明书和临床指南进行审核,对于存在风险的处方要明确指出。`,
Tools: []string{
"query_drug",
"check_drug_interaction",
"check_contraindication",
"calculate_dosage",
},
MaxIterations: 10,
})
}
// NewFollowUpAgent 随访管理Agent
func NewFollowUpAgent() *agent.ReActAgent {
return agent.NewReActAgent(agent.ReActConfig{
ID: "follow_up_agent",
Name: "随访管理Agent",
Description: "管理患者随访,提醒用药、复诊,收集健康数据",
SystemPrompt: `你是一位专业的随访管理AI助手。你的职责是:
1. 查询患者的处方和用药情况
2. 提醒患者按时用药
3. 收集患者的健康数据(血压、血糖等)
4. 评估病情变化,必要时建议复诊
5. 生成随访报告
请用温和关怀的语气与患者交流,关注患者的用药依从性和健康状况变化。`,
Tools: []string{
"query_medical_record",
"query_drug",
"query_symptom_knowledge",
},
MaxIterations: 8,
})
}
package internalagent
import (
"net/http"
"github.com/gin-gonic/gin"
)
// Handler Agent HTTP处理器
type Handler struct {
svc *AgentService
}
func NewHandler() *Handler {
return &Handler{svc: GetService()}
}
func (h *Handler) RegisterRoutes(r gin.IRouter) {
g := r.Group("/agent")
g.POST("/:agent_id/chat", h.Chat)
g.GET("/sessions", h.ListSessions)
g.DELETE("/session/:session_id", h.DeleteSession)
g.GET("/list", h.ListAgents)
}
func (h *Handler) Chat(c *gin.Context) {
agentID := c.Param("agent_id")
var req struct {
SessionID string `json:"session_id"`
Message string `json:"message" binding:"required"`
Context map[string]interface{} `json:"context"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
userID, _ := c.Get("user_id")
uid, _ := userID.(string)
output, err := h.svc.Chat(c.Request.Context(), agentID, uid, req.SessionID, req.Message, req.Context)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if output == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "agent not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": output})
}
func (h *Handler) ListAgents(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": h.svc.ListAgents()})
}
func (h *Handler) ListSessions(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"data": []interface{}{}})
}
func (h *Handler) DeleteSession(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "ok"})
}
package internalagent
import (
"internet-hospital/pkg/agent"
"internet-hospital/pkg/agent/tools"
"internet-hospital/pkg/database"
"internet-hospital/pkg/rag"
)
// InitTools 注册所有工具到全局注册中心
func InitTools() {
db := database.GetDB()
r := agent.GetRegistry()
// 初始化RAG组件(Embedder配置从AI配置中读取)
embedder := rag.InitEmbedder("", "", "")
retriever := rag.InitRetriever(db, embedder)
// 基础查询工具
r.Register(&tools.DrugQueryTool{DB: db})
r.Register(&tools.MedicalRecordTool{DB: db})
r.Register(&tools.SymptomKnowledgeTool{})
r.Register(&tools.DepartmentRecommendTool{})
// 处方安全检查工具
r.Register(&tools.DrugInteractionTool{DB: db})
r.Register(&tools.ContraindicationTool{DB: db})
r.Register(&tools.DosageCalculatorTool{DB: db})
// 知识库检索工具
r.Register(&tools.KnowledgeSearchTool{Retriever: retriever})
}
package internalagent
import (
"context"
"encoding/json"
"time"
"internet-hospital/internal/model"
"internet-hospital/pkg/agent"
"internet-hospital/pkg/ai"
"internet-hospital/pkg/database"
"github.com/google/uuid"
)
// AgentService Agent服务
type AgentService struct {
agents map[string]*agent.ReActAgent
}
var globalAgentService *AgentService
func GetService() *AgentService {
if globalAgentService == nil {
globalAgentService = &AgentService{
agents: map[string]*agent.ReActAgent{
"pre_consult_agent": NewPreConsultAgent(),
"diagnosis_agent": NewDiagnosisAgent(),
"prescription_agent": NewPrescriptionAgent(),
"follow_up_agent": NewFollowUpAgent(),
},
}
}
return globalAgentService
}
func (s *AgentService) GetAgent(agentID string) (*agent.ReActAgent, bool) {
a, ok := s.agents[agentID]
return a, ok
}
func (s *AgentService) ListAgents() []map[string]string {
result := make([]map[string]string, 0, len(s.agents))
for _, a := range s.agents {
result = append(result, map[string]string{
"id": a.ID(),
"name": a.Name(),
"description": a.Description(),
})
}
return result
}
// Chat 执行Agent对话并持久化会话
func (s *AgentService) Chat(ctx context.Context, agentID, userID, sessionID, message string, contextData map[string]interface{}) (*agent.AgentOutput, error) {
a, ok := s.GetAgent(agentID)
if !ok {
return nil, nil
}
db := database.GetDB()
// 加载或创建会话
var session model.AgentSession
if sessionID == "" {
sessionID = uuid.New().String()
}
db.Where("session_id = ?", sessionID).First(&session)
// 解析历史消息
var history []ai.ChatMessage
if session.History != "" {
json.Unmarshal([]byte(session.History), &history)
}
input := agent.AgentInput{
SessionID: sessionID,
UserID: userID,
Message: message,
Context: contextData,
History: history,
}
start := time.Now()
output, err := a.Run(ctx, input)
durationMs := int(time.Since(start).Milliseconds())
if err != nil {
return nil, err
}
// 更新历史
history = append(history,
ai.ChatMessage{Role: "user", Content: message},
ai.ChatMessage{Role: "assistant", Content: output.Response},
)
historyJSON, _ := json.Marshal(history)
contextJSON, _ := json.Marshal(contextData)
if session.ID == 0 {
session = model.AgentSession{
SessionID: sessionID,
AgentID: agentID,
UserID: userID,
History: string(historyJSON),
Context: string(contextJSON),
Status: "active",
}
db.Create(&session)
} else {
db.Model(&session).Updates(map[string]interface{}{
"history": string(historyJSON),
"updated_at": time.Now(),
})
}
// 记录执行日志
inputJSON, _ := json.Marshal(input)
outputJSON, _ := json.Marshal(output)
toolCallsJSON, _ := json.Marshal(output.ToolCalls)
db.Create(&model.AgentExecutionLog{
SessionID: sessionID,
AgentID: agentID,
UserID: userID,
Input: string(inputJSON),
Output: string(outputJSON),
ToolCalls: string(toolCallsJSON),
Iterations: output.Iterations,
TotalTokens: output.TotalTokens,
DurationMs: durationMs,
FinishReason: output.FinishReason,
Success: true,
})
return output, nil
}
package model
import "time"
// AgentTool 工具定义
type AgentTool struct {
ID uint `gorm:"primaryKey"`
Name string `gorm:"type:varchar(100);uniqueIndex"`
DisplayName string `gorm:"type:varchar(200)"`
Description string `gorm:"type:text"`
Category string `gorm:"type:varchar(50)"`
Parameters string `gorm:"type:jsonb"`
Status string `gorm:"type:varchar(20);default:'active'"`
CreatedAt time.Time
UpdatedAt time.Time
}
// AgentToolLog 工具调用日志
type AgentToolLog struct {
ID uint `gorm:"primaryKey"`
ToolName string `gorm:"type:varchar(100);index"`
AgentID string `gorm:"type:varchar(100);index"`
SessionID string `gorm:"type:varchar(100);index"`
UserID string `gorm:"type:uuid;index"`
InputParams string `gorm:"type:jsonb"`
OutputResult string `gorm:"type:jsonb"`
Success bool
ErrorMessage string `gorm:"type:text"`
DurationMs int
CreatedAt time.Time
}
// AgentDefinition Agent定义
type AgentDefinition struct {
ID uint `gorm:"primaryKey"`
AgentID string `gorm:"type:varchar(100);uniqueIndex"`
Name string `gorm:"type:varchar(200)"`
Description string `gorm:"type:text"`
Category string `gorm:"type:varchar(50)"`
SystemPrompt string `gorm:"type:text"`
Tools string `gorm:"type:jsonb"`
Config string `gorm:"type:jsonb"`
MaxIterations int `gorm:"default:10"`
Status string `gorm:"type:varchar(20);default:'active'"`
CreatedAt time.Time
UpdatedAt time.Time
}
// AgentSession Agent会话
type AgentSession struct {
ID uint `gorm:"primaryKey"`
SessionID string `gorm:"type:varchar(100);uniqueIndex"`
AgentID string `gorm:"type:varchar(100);index"`
UserID string `gorm:"type:uuid;index"`
Context string `gorm:"type:jsonb"`
History string `gorm:"type:jsonb"`
Status string `gorm:"type:varchar(20);default:'active'"`
CreatedAt time.Time
UpdatedAt time.Time
}
// AgentExecutionLog Agent执行日志
type AgentExecutionLog struct {
ID uint `gorm:"primaryKey"`
SessionID string `gorm:"type:varchar(100);index"`
AgentID string `gorm:"type:varchar(100);index"`
UserID string `gorm:"type:uuid;index"`
Input string `gorm:"type:jsonb"`
Output string `gorm:"type:jsonb"`
ToolCalls string `gorm:"type:jsonb"`
Iterations int
TotalTokens int
DurationMs int
FinishReason string `gorm:"type:varchar(50)"`
Success bool
ErrorMessage string `gorm:"type:text"`
CreatedAt time.Time
}
package model
import "time"
// KnowledgeCollection 知识库集合
type KnowledgeCollection struct {
ID uint `gorm:"primaryKey"`
CollectionID string `gorm:"type:varchar(100);uniqueIndex"`
Name string `gorm:"type:varchar(200)"`
Description string `gorm:"type:text"`
Category string `gorm:"type:varchar(50)"`
DocumentCount int `gorm:"default:0"`
Status string `gorm:"type:varchar(20);default:'active'"`
CreatedAt time.Time
UpdatedAt time.Time
}
// KnowledgeDocument 知识文档
type KnowledgeDocument struct {
ID uint `gorm:"primaryKey"`
DocumentID string `gorm:"type:varchar(100);uniqueIndex"`
CollectionID string `gorm:"type:varchar(100);index"`
Title string `gorm:"type:varchar(500)"`
Content string `gorm:"type:text"`
Metadata string `gorm:"type:jsonb"`
FileType string `gorm:"type:varchar(50)"`
ChunkCount int `gorm:"default:0"`
Status string `gorm:"type:varchar(20);default:'ready'"`
CreatedAt time.Time
UpdatedAt time.Time
}
// KnowledgeChunk 知识分块
type KnowledgeChunk struct {
ID uint `gorm:"primaryKey"`
ChunkID string `gorm:"type:varchar(100);uniqueIndex"`
DocumentID string `gorm:"type:varchar(100);index"`
CollectionID string `gorm:"type:varchar(100);index"`
Content string `gorm:"type:text"`
ChunkIndex int
TokenCount int
CreatedAt time.Time
}
package model
import (
"time"
"gorm.io/gorm"
)
// PaymentOrder 支付订单
type PaymentOrder struct {
ID string `gorm:"type:uuid;primaryKey" json:"id"`
OrderNo string `gorm:"type:varchar(64);uniqueIndex;not null" json:"order_no"`
UserID string `gorm:"type:uuid;index;not null" json:"user_id"`
OrderType string `gorm:"type:varchar(32);not null" json:"order_type"` // consult, prescription, pharmacy
RelatedID string `gorm:"type:uuid" json:"related_id"` // 关联的问诊/处方ID
Amount int64 `gorm:"not null" json:"amount"` // 金额(分)
Status string `gorm:"type:varchar(20);default:'pending'" json:"status"` // pending, paid, refunded, cancelled
PaymentMethod string `gorm:"type:varchar(32)" json:"payment_method"` // wechat, alipay, insurance
TransactionID string `gorm:"type:varchar(128)" json:"transaction_id"` // 第三方交易号
PaidAt *time.Time `json:"paid_at"`
RefundedAt *time.Time `json:"refunded_at"`
ExpireAt *time.Time `json:"expire_at"`
Remark string `gorm:"type:text" json:"remark"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
func (PaymentOrder) TableName() string {
return "payment_orders"
}
// DoctorIncome 医生收入记录
type DoctorIncome struct {
ID string `gorm:"type:uuid;primaryKey" json:"id"`
DoctorID string `gorm:"type:uuid;index;not null" json:"doctor_id"`
ConsultID string `gorm:"type:uuid" json:"consult_id"`
PrescriptionID string `gorm:"type:uuid" json:"prescription_id"`
IncomeType string `gorm:"type:varchar(32);not null" json:"income_type"` // text_consult, video_consult, prescription
Amount int64 `gorm:"not null" json:"amount"` // 金额(分)
Status string `gorm:"type:varchar(20);default:'pending'" json:"status"` // pending, settled, withdrawn
PatientName string `gorm:"type:varchar(64)" json:"patient_name"`
SettledAt *time.Time `json:"settled_at"`
CreatedAt time.Time `json:"created_at"`
}
func (DoctorIncome) TableName() string {
return "doctor_incomes"
}
// DoctorWithdrawal 医生提现记录
type DoctorWithdrawal struct {
ID string `gorm:"type:uuid;primaryKey" json:"id"`
DoctorID string `gorm:"type:uuid;index;not null" json:"doctor_id"`
Amount int64 `gorm:"not null" json:"amount"` // 提现金额(分)
BankAccount string `gorm:"type:varchar(128)" json:"bank_account"`
BankName string `gorm:"type:varchar(64)" json:"bank_name"`
Status string `gorm:"type:varchar(20);default:'pending'" json:"status"` // pending, processing, success, failed
Remark string `gorm:"type:text" json:"remark"`
ProcessedAt *time.Time `json:"processed_at"`
CreatedAt time.Time `json:"created_at"`
}
func (DoctorWithdrawal) TableName() string {
return "doctor_withdrawals"
}
package model
import "time"
// WorkflowDefinition 工作流定义
type WorkflowDefinition struct {
ID uint `gorm:"primaryKey"`
WorkflowID string `gorm:"type:varchar(100);uniqueIndex"`
Name string `gorm:"type:varchar(200)"`
Description string `gorm:"type:text"`
Category string `gorm:"type:varchar(50)"`
Version int `gorm:"default:1"`
Definition string `gorm:"type:jsonb"`
Status string `gorm:"type:varchar(20);default:'draft'"`
CreatedBy string `gorm:"type:uuid"`
CreatedAt time.Time
UpdatedAt time.Time
}
// WorkflowExecution 工作流执行实例
type WorkflowExecution struct {
ID uint `gorm:"primaryKey"`
ExecutionID string `gorm:"type:varchar(100);uniqueIndex"`
WorkflowID string `gorm:"type:varchar(100);index"`
Version int
TriggerType string `gorm:"type:varchar(50)"`
TriggerBy string `gorm:"type:uuid"`
Input string `gorm:"type:jsonb"`
Output string `gorm:"type:jsonb"`
Status string `gorm:"type:varchar(20)"` // pending, running, completed, failed
CurrentNode string `gorm:"type:varchar(100)"`
StartedAt *time.Time
CompletedAt *time.Time
ErrorMessage string `gorm:"type:text"`
CreatedAt time.Time
}
// WorkflowHumanTask 人工审核任务
type WorkflowHumanTask struct {
ID uint `gorm:"primaryKey"`
TaskID string `gorm:"type:varchar(100);uniqueIndex"`
ExecutionID string `gorm:"type:varchar(100);index"`
NodeID string `gorm:"type:varchar(100)"`
AssigneeRole string `gorm:"type:varchar(50)"`
Title string `gorm:"type:varchar(200)"`
Description string `gorm:"type:text"`
FormData string `gorm:"type:jsonb"`
Result string `gorm:"type:jsonb"`
Status string `gorm:"type:varchar(20);default:'pending'"`
CreatedAt time.Time
CompletedAt *time.Time
}
......@@ -98,6 +98,11 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
adm.GET("/prescriptions", h.GetPrescriptionList)
adm.GET("/prescriptions/:id", h.GetPrescriptionDetailAdmin)
adm.POST("/prescriptions/:id/review", h.ReviewPrescription)
// 工作流管理
adm.GET("/workflows", h.ListWorkflows)
adm.POST("/workflows", h.CreateWorkflow)
adm.PUT("/workflows/:id/publish", h.PublishWorkflow)
}
}
......
package admin
import (
"net/http"
"github.com/gin-gonic/gin"
"internet-hospital/internal/model"
"internet-hospital/pkg/database"
)
// ListWorkflows 工作流列表
func (h *Handler) ListWorkflows(c *gin.Context) {
var workflows []model.WorkflowDefinition
database.GetDB().Find(&workflows)
c.JSON(http.StatusOK, gin.H{"data": workflows})
}
// CreateWorkflow 创建工作流
func (h *Handler) CreateWorkflow(c *gin.Context) {
var req struct {
WorkflowID string `json:"workflow_id" binding:"required"`
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Category string `json:"category"`
Definition string `json:"definition"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
wf := model.WorkflowDefinition{
WorkflowID: req.WorkflowID,
Name: req.Name,
Description: req.Description,
Category: req.Category,
Definition: req.Definition,
Status: "draft",
Version: 1,
}
database.GetDB().Create(&wf)
c.JSON(http.StatusOK, gin.H{"data": wf})
}
// PublishWorkflow 发布工作流
func (h *Handler) PublishWorkflow(c *gin.Context) {
database.GetDB().Model(&model.WorkflowDefinition{}).
Where("id = ?", c.Param("id")).
Update("status", "active")
c.JSON(http.StatusOK, gin.H{"message": "ok"})
}
package doctorportal
import (
"context"
"time"
"gorm.io/gorm"
"internet-hospital/internal/model"
"internet-hospital/pkg/database"
)
// PatientRecordService 患者病历服务
type PatientRecordService struct {
db *gorm.DB
}
func NewPatientRecordService() *PatientRecordService {
return &PatientRecordService{
db: database.GetDB(),
}
}
// PatientRecordDetail 患者病历详情
type PatientRecordDetail struct {
// 基本信息
PatientID string `json:"patient_id"`
Name string `json:"name"`
Gender string `json:"gender"`
Age int `json:"age"`
Phone string `json:"phone"`
Avatar string `json:"avatar"`
// 健康档案
MedicalHistory string `json:"medical_history"`
AllergyHistory string `json:"allergy_history"`
InsuranceType string `json:"insurance_type"`
// 就诊统计
ConsultCount int64 `json:"consult_count"`
LastConsultAt *time.Time `json:"last_consult_at"`
// 就诊记录
Consultations []ConsultRecord `json:"consultations"`
// 处方记录
Prescriptions []PrescriptionRecord `json:"prescriptions"`
// 检验报告
LabReports []LabReportRecord `json:"lab_reports"`
// 慢病档案
ChronicRecords []ChronicRecordInfo `json:"chronic_records"`
}
type ConsultRecord struct {
ID string `json:"id"`
Type string `json:"type"`
Status string `json:"status"`
ChiefComplaint string `json:"chief_complaint"`
DoctorName string `json:"doctor_name"`
DepartmentName string `json:"department_name"`
CreatedAt time.Time `json:"created_at"`
EndedAt *time.Time `json:"ended_at"`
}
type PrescriptionRecord struct {
ID string `json:"id"`
PrescriptionNo string `json:"prescription_no"`
Diagnosis string `json:"diagnosis"`
Status string `json:"status"`
TotalAmount int64 `json:"total_amount"`
ItemCount int `json:"item_count"`
CreatedAt time.Time `json:"created_at"`
}
type LabReportRecord struct {
ID string `json:"id"`
Title string `json:"title"`
Category string `json:"category"`
ReportDate time.Time `json:"report_date"`
AIInterpret string `json:"ai_interpret"`
}
type ChronicRecordInfo struct {
ID string `json:"id"`
DiseaseName string `json:"disease_name"`
ControlStatus string `json:"control_status"`
DiagnosisDate time.Time `json:"diagnosis_date"`
CurrentMeds string `json:"current_meds"`
}
// GetPatientRecord 获取患者病历详情
func (s *PatientRecordService) GetPatientRecord(ctx context.Context, doctorID, patientID string) (*PatientRecordDetail, error) {
// 验证医生是否有权限查看该患者(曾经有过问诊记录)
var consultCount int64
s.db.Model(&model.Consultation{}).
Where("doctor_id = ? AND patient_id = ?", doctorID, patientID).
Count(&consultCount)
if consultCount == 0 {
return nil, gorm.ErrRecordNotFound
}
// 获取患者基本信息
var user model.User
if err := s.db.Where("id = ?", patientID).First(&user).Error; err != nil {
return nil, err
}
// 获取患者健康档案
var profile model.PatientProfile
s.db.Where("user_id = ?", patientID).First(&profile)
// 计算年龄
age := 0
if profile.BirthDate != nil {
age = time.Now().Year() - profile.BirthDate.Year()
}
// 获取最后就诊时间
var lastConsult model.Consultation
s.db.Where("patient_id = ?", patientID).Order("created_at DESC").First(&lastConsult)
record := &PatientRecordDetail{
PatientID: patientID,
Name: user.RealName,
Gender: profile.Gender,
Age: age,
Phone: maskPhone(user.Phone),
MedicalHistory: profile.MedicalHistory,
AllergyHistory: profile.AllergyHistory,
InsuranceType: profile.InsuranceType,
ConsultCount: consultCount,
}
if !lastConsult.CreatedAt.IsZero() {
record.LastConsultAt = &lastConsult.CreatedAt
}
// 获取就诊记录
record.Consultations = s.getConsultRecords(patientID)
// 获取处方记录
record.Prescriptions = s.getPrescriptionRecords(patientID)
// 获取检验报告
record.LabReports = s.getLabReports(patientID)
// 获取慢病档案
record.ChronicRecords = s.getChronicRecords(patientID)
return record, nil
}
func (s *PatientRecordService) getConsultRecords(patientID string) []ConsultRecord {
var records []ConsultRecord
rows, _ := s.db.Raw(`
SELECT c.id, c.type, c.status, c.chief_complaint, c.created_at, c.ended_at,
COALESCE(u.name, '') as doctor_name,
COALESCE(d.name, '') as department_name
FROM consultations c
LEFT JOIN doctors doc ON c.doctor_id = doc.user_id
LEFT JOIN users u ON doc.user_id = u.id
LEFT JOIN departments d ON doc.department_id = d.id
WHERE c.patient_id = ?
ORDER BY c.created_at DESC
LIMIT 20
`, patientID).Rows()
if rows != nil {
defer rows.Close()
for rows.Next() {
var r ConsultRecord
rows.Scan(&r.ID, &r.Type, &r.Status, &r.ChiefComplaint, &r.CreatedAt, &r.EndedAt, &r.DoctorName, &r.DepartmentName)
records = append(records, r)
}
}
return records
}
func (s *PatientRecordService) getPrescriptionRecords(patientID string) []PrescriptionRecord {
var records []PrescriptionRecord
rows, _ := s.db.Raw(`
SELECT p.id, p.prescription_no, p.diagnosis, p.status, p.total_amount, p.created_at,
(SELECT COUNT(*) FROM prescription_items WHERE prescription_id = p.id) as item_count
FROM prescriptions p
WHERE p.patient_id = ?
ORDER BY p.created_at DESC
LIMIT 20
`, patientID).Rows()
if rows != nil {
defer rows.Close()
for rows.Next() {
var r PrescriptionRecord
rows.Scan(&r.ID, &r.PrescriptionNo, &r.Diagnosis, &r.Status, &r.TotalAmount, &r.CreatedAt, &r.ItemCount)
records = append(records, r)
}
}
return records
}
func (s *PatientRecordService) getLabReports(patientID string) []LabReportRecord {
var reports []model.LabReport
s.db.Where("user_id = ?", patientID).Order("report_date DESC").Limit(10).Find(&reports)
var records []LabReportRecord
for _, r := range reports {
rec := LabReportRecord{
ID: r.ID,
Title: r.Title,
Category: r.Category,
AIInterpret: r.AIInterpret,
}
if r.ReportDate != nil {
rec.ReportDate = *r.ReportDate
}
records = append(records, rec)
}
return records
}
func (s *PatientRecordService) getChronicRecords(patientID string) []ChronicRecordInfo {
var records []model.ChronicRecord
s.db.Where("user_id = ?", patientID).Find(&records)
var infos []ChronicRecordInfo
for _, r := range records {
info := ChronicRecordInfo{
ID: r.ID,
DiseaseName: r.DiseaseName,
ControlStatus: r.ControlStatus,
CurrentMeds: r.CurrentMeds,
}
if r.DiagnosisDate != nil {
info.DiagnosisDate = *r.DiagnosisDate
}
infos = append(infos, info)
}
return infos
}
func maskPhone(phone string) string {
if len(phone) < 7 {
return phone
}
return phone[:3] + "****" + phone[len(phone)-4:]
}
package knowledgesvc
import (
"net/http"
"strings"
"internet-hospital/internal/model"
"internet-hospital/pkg/database"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type Handler struct{}
func NewHandler() *Handler { return &Handler{} }
func (h *Handler) RegisterRoutes(r gin.IRouter) {
g := r.Group("/knowledge")
g.POST("/search", h.Search)
g.GET("/collections", h.ListCollections)
g.POST("/collections", h.CreateCollection)
g.POST("/documents", h.CreateDocument)
g.GET("/documents", h.ListDocuments)
}
func (h *Handler) Search(c *gin.Context) {
var req struct {
Query string `json:"query" binding:"required"`
CollectionID string `json:"collection_id"`
TopK int `json:"top_k"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.TopK <= 0 {
req.TopK = 5
}
db := database.GetDB()
var chunks []model.KnowledgeChunk
query := db.Where("content ILIKE ?", "%"+req.Query+"%")
if req.CollectionID != "" {
query = query.Where("collection_id = ?", req.CollectionID)
}
query.Limit(req.TopK).Find(&chunks)
results := make([]map[string]interface{}, 0, len(chunks))
for _, ch := range chunks {
results = append(results, map[string]interface{}{
"chunk_id": ch.ChunkID,
"content": ch.Content,
"collection_id": ch.CollectionID,
"document_id": ch.DocumentID,
})
}
c.JSON(http.StatusOK, gin.H{"data": results})
}
func (h *Handler) ListCollections(c *gin.Context) {
var cols []model.KnowledgeCollection
database.GetDB().Find(&cols)
c.JSON(http.StatusOK, gin.H{"data": cols})
}
func (h *Handler) CreateCollection(c *gin.Context) {
var req struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Category string `json:"category"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
col := model.KnowledgeCollection{
CollectionID: uuid.New().String(),
Name: req.Name,
Description: req.Description,
Category: req.Category,
}
database.GetDB().Create(&col)
c.JSON(http.StatusOK, gin.H{"data": col})
}
func (h *Handler) CreateDocument(c *gin.Context) {
var req struct {
CollectionID string `json:"collection_id" binding:"required"`
Title string `json:"title" binding:"required"`
Content string `json:"content" binding:"required"`
FileType string `json:"file_type"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
db := database.GetDB()
docID := uuid.New().String()
doc := model.KnowledgeDocument{
DocumentID: docID,
CollectionID: req.CollectionID,
Title: req.Title,
Content: req.Content,
FileType: req.FileType,
}
// 简单分块:按段落切割
chunks := splitIntoChunks(req.Content, 500)
doc.ChunkCount = len(chunks)
db.Create(&doc)
for i, chunk := range chunks {
db.Create(&model.KnowledgeChunk{
ChunkID: uuid.New().String(),
DocumentID: docID,
CollectionID: req.CollectionID,
Content: chunk,
ChunkIndex: i,
TokenCount: len([]rune(chunk)),
})
}
db.Model(&model.KnowledgeCollection{}).
Where("collection_id = ?", req.CollectionID).
UpdateColumn("document_count", db.Raw("document_count + 1"))
c.JSON(http.StatusOK, gin.H{"data": doc})
}
func (h *Handler) ListDocuments(c *gin.Context) {
collectionID := c.Query("collection_id")
var docs []model.KnowledgeDocument
q := database.GetDB()
if collectionID != "" {
q = q.Where("collection_id = ?", collectionID)
}
q.Find(&docs)
c.JSON(http.StatusOK, gin.H{"data": docs})
}
func splitIntoChunks(text string, chunkSize int) []string {
paragraphs := strings.Split(text, "\n\n")
var chunks []string
var current strings.Builder
for _, p := range paragraphs {
if current.Len()+len(p) > chunkSize && current.Len() > 0 {
chunks = append(chunks, strings.TrimSpace(current.String()))
current.Reset()
}
current.WriteString(p)
current.WriteString("\n\n")
}
if current.Len() > 0 {
chunks = append(chunks, strings.TrimSpace(current.String()))
}
if len(chunks) == 0 {
chunks = []string{text}
}
return chunks
}
package payment
import (
"fmt"
"strconv"
"github.com/gin-gonic/gin"
"internet-hospital/pkg/response"
)
type Handler struct {
service *Service
}
func NewHandler() *Handler {
return &Handler{
service: NewService(),
}
}
func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
// 患者端支付
payment := r.Group("/payment")
{
payment.POST("/order/create", h.CreateOrder)
payment.GET("/order/:id", h.GetOrderDetail)
payment.GET("/orders", h.GetOrderList)
payment.POST("/order/:id/pay", h.PayOrder)
payment.GET("/order/:id/status", h.GetPaymentStatus)
payment.POST("/order/:id/cancel", h.CancelOrder)
}
// 医生端收入
income := r.Group("/doctor/income")
{
income.GET("/stats", h.GetIncomeStats)
income.GET("/records", h.GetIncomeRecords)
income.GET("/monthly", h.GetMonthlyBills)
income.POST("/withdraw", h.RequestWithdraw)
income.GET("/withdrawals", h.GetWithdrawalRecords)
}
}
// ========== 患者端支付 ==========
type CreateOrderRequest struct {
OrderType string `json:"order_type" binding:"required"` // consult, prescription
RelatedID string `json:"related_id" binding:"required"`
Amount int64 `json:"amount" binding:"required"`
}
func (h *Handler) CreateOrder(c *gin.Context) {
userID, _ := c.Get("user_id")
var req CreateOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "请求参数错误")
return
}
order, err := h.service.CreateOrder(c.Request.Context(), userID.(string), req.OrderType, req.RelatedID, req.Amount)
if err != nil {
response.Error(c, 500, err.Error())
return
}
response.Success(c, order)
}
func (h *Handler) GetOrderDetail(c *gin.Context) {
orderID := c.Param("id")
order, err := h.service.GetOrderByID(c.Request.Context(), orderID)
if err != nil {
response.Error(c, 404, "订单不存在")
return
}
response.Success(c, order)
}
func (h *Handler) GetOrderList(c *gin.Context) {
userID, _ := c.Get("user_id")
status := c.Query("status")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
result, err := h.service.GetUserOrders(c.Request.Context(), userID.(string), status, page, pageSize)
if err != nil {
response.Error(c, 500, "获取订单列表失败")
return
}
response.Success(c, result)
}
type PayOrderRequest struct {
PaymentMethod string `json:"payment_method" binding:"required"` // wechat, alipay, insurance
}
func (h *Handler) PayOrder(c *gin.Context) {
orderID := c.Param("id")
var req PayOrderRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "请求参数错误")
return
}
result, err := h.service.PayOrder(c.Request.Context(), orderID, req.PaymentMethod)
if err != nil {
response.Error(c, 400, err.Error())
return
}
response.Success(c, result)
}
func (h *Handler) GetPaymentStatus(c *gin.Context) {
orderID := c.Param("id")
status, err := h.service.GetPaymentStatus(c.Request.Context(), orderID)
if err != nil {
response.Error(c, 404, "订单不存在")
return
}
response.Success(c, gin.H{"status": status})
}
func (h *Handler) CancelOrder(c *gin.Context) {
orderID := c.Param("id")
if err := h.service.CancelOrder(c.Request.Context(), orderID); err != nil {
response.Error(c, 400, err.Error())
return
}
response.Success(c, nil)
}
// ========== 医生端收入 ==========
func (h *Handler) GetIncomeStats(c *gin.Context) {
userID, _ := c.Get("user_id")
stats, err := h.service.GetDoctorIncomeStats(c.Request.Context(), userID.(string))
if err != nil {
response.Error(c, 500, "获取收入统计失败")
return
}
response.Success(c, stats)
}
func (h *Handler) GetIncomeRecords(c *gin.Context) {
userID, _ := c.Get("user_id")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
startDate := c.Query("start_date")
endDate := c.Query("end_date")
result, err := h.service.GetDoctorIncomeRecords(c.Request.Context(), userID.(string), startDate, endDate, page, pageSize)
if err != nil {
response.Error(c, 500, "获取收入记录失败")
return
}
response.Success(c, result)
}
func (h *Handler) GetMonthlyBills(c *gin.Context) {
userID, _ := c.Get("user_id")
bills, err := h.service.GetMonthlyBills(c.Request.Context(), userID.(string))
if err != nil {
response.Error(c, 500, "获取月度账单失败")
return
}
response.Success(c, bills)
}
type WithdrawRequest struct {
Amount int64 `json:"amount" binding:"required"`
BankAccount string `json:"bank_account"`
BankName string `json:"bank_name"`
}
func (h *Handler) RequestWithdraw(c *gin.Context) {
userID, _ := c.Get("user_id")
var req WithdrawRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.BadRequest(c, "请求参数错误")
return
}
withdrawal, err := h.service.RequestWithdraw(c.Request.Context(), userID.(string), req.Amount, req.BankAccount, req.BankName)
if err != nil {
response.Error(c, 400, err.Error())
return
}
response.Success(c, withdrawal)
}
func (h *Handler) GetWithdrawalRecords(c *gin.Context) {
userID, _ := c.Get("user_id")
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
result, err := h.service.GetWithdrawalRecords(c.Request.Context(), userID.(string), page, pageSize)
if err != nil {
response.Error(c, 500, "获取提现记录失败")
return
}
response.Success(c, result)
}
// 支付回调(供第三方支付平台调用)
func (h *Handler) PaymentCallback(c *gin.Context) {
provider := c.Param("provider") // wechat, alipay
// TODO: 验证签名,解析回调数据
var callbackData map[string]interface{}
if err := c.ShouldBindJSON(&callbackData); err != nil {
c.String(400, "FAIL")
return
}
orderNo := fmt.Sprintf("%v", callbackData["order_no"])
transactionID := fmt.Sprintf("%v", callbackData["transaction_id"])
if err := h.service.HandlePaymentCallback(c.Request.Context(), provider, orderNo, transactionID); err != nil {
c.String(500, "FAIL")
return
}
c.String(200, "SUCCESS")
}
package payment
import (
"context"
"errors"
"fmt"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
"internet-hospital/internal/model"
"internet-hospital/pkg/database"
)
type Service struct {
db *gorm.DB
}
func NewService() *Service {
return &Service{
db: database.GetDB(),
}
}
// ========== 支付订单 ==========
func (s *Service) CreateOrder(ctx context.Context, userID, orderType, relatedID string, amount int64) (*model.PaymentOrder, error) {
order := &model.PaymentOrder{
ID: uuid.New().String(),
OrderNo: generateOrderNo(),
UserID: userID,
OrderType: orderType,
RelatedID: relatedID,
Amount: amount,
Status: "pending",
ExpireAt: timePtr(time.Now().Add(30 * time.Minute)),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := s.db.Create(order).Error; err != nil {
return nil, err
}
return order, nil
}
func (s *Service) GetOrderByID(ctx context.Context, orderID string) (*model.PaymentOrder, error) {
var order model.PaymentOrder
if err := s.db.Where("id = ?", orderID).First(&order).Error; err != nil {
return nil, err
}
return &order, nil
}
func (s *Service) GetUserOrders(ctx context.Context, userID, status string, page, pageSize int) (map[string]interface{}, error) {
var orders []model.PaymentOrder
var total int64
query := s.db.Model(&model.PaymentOrder{}).Where("user_id = ?", userID)
if status != "" {
query = query.Where("status = ?", status)
}
query.Count(&total)
query.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&orders)
return map[string]interface{}{
"list": orders,
"total": total,
"page": page,
}, nil
}
func (s *Service) PayOrder(ctx context.Context, orderID, paymentMethod string) (map[string]interface{}, error) {
var order model.PaymentOrder
if err := s.db.Where("id = ?", orderID).First(&order).Error; err != nil {
return nil, errors.New("订单不存在")
}
if order.Status != "pending" {
return nil, errors.New("订单状态不允许支付")
}
if order.ExpireAt != nil && order.ExpireAt.Before(time.Now()) {
return nil, errors.New("订单已过期")
}
// 模拟支付成功(实际应调用第三方支付接口)
now := time.Now()
order.Status = "paid"
order.PaymentMethod = paymentMethod
order.PaidAt = &now
order.TransactionID = fmt.Sprintf("TX%s", uuid.New().String()[:16])
order.UpdatedAt = now
if err := s.db.Save(&order).Error; err != nil {
return nil, err
}
// 创建医生收入记录
if order.OrderType == "consult" {
s.createDoctorIncome(ctx, order.RelatedID, order.Amount, order.OrderType)
}
return map[string]interface{}{
"order_id": order.ID,
"status": order.Status,
"transaction_id": order.TransactionID,
"paid_at": order.PaidAt,
}, nil
}
func (s *Service) GetPaymentStatus(ctx context.Context, orderID string) (string, error) {
var order model.PaymentOrder
if err := s.db.Select("status").Where("id = ?", orderID).First(&order).Error; err != nil {
return "", err
}
return order.Status, nil
}
func (s *Service) CancelOrder(ctx context.Context, orderID string) error {
var order model.PaymentOrder
if err := s.db.Where("id = ?", orderID).First(&order).Error; err != nil {
return errors.New("订单不存在")
}
if order.Status != "pending" {
return errors.New("订单状态不允许取消")
}
order.Status = "cancelled"
order.UpdatedAt = time.Now()
return s.db.Save(&order).Error
}
func (s *Service) HandlePaymentCallback(ctx context.Context, provider, orderNo, transactionID string) error {
var order model.PaymentOrder
if err := s.db.Where("order_no = ?", orderNo).First(&order).Error; err != nil {
return err
}
if order.Status == "paid" {
return nil // 已处理
}
now := time.Now()
order.Status = "paid"
order.PaymentMethod = provider
order.TransactionID = transactionID
order.PaidAt = &now
order.UpdatedAt = now
if err := s.db.Save(&order).Error; err != nil {
return err
}
// 创建医生收入记录
if order.OrderType == "consult" {
s.createDoctorIncome(ctx, order.RelatedID, order.Amount, order.OrderType)
}
return nil
}
// ========== 医生收入 ==========
func (s *Service) createDoctorIncome(ctx context.Context, consultID string, amount int64, incomeType string) error {
// 查询问诊获取医生ID和患者信息
var consult model.Consultation
if err := s.db.Where("id = ?", consultID).First(&consult).Error; err != nil {
return err
}
// 查询患者姓名
var patient model.User
s.db.Select("real_name").Where("id = ?", consult.PatientID).First(&patient)
income := &model.DoctorIncome{
ID: uuid.New().String(),
DoctorID: consult.DoctorID,
ConsultID: consultID,
IncomeType: incomeType,
Amount: amount,
Status: "settled",
PatientName: patient.RealName,
SettledAt: timePtr(time.Now()),
CreatedAt: time.Now(),
}
return s.db.Create(income).Error
}
func (s *Service) GetDoctorIncomeStats(ctx context.Context, doctorID string) (map[string]interface{}, error) {
var totalBalance int64
var monthIncome int64
var monthConsults int64
// 可提现余额(已结算未提现)
s.db.Model(&model.DoctorIncome{}).
Where("doctor_id = ? AND status = ?", doctorID, "settled").
Select("COALESCE(SUM(amount), 0)").Scan(&totalBalance)
// 本月收入
startOfMonth := time.Now().Format("2006-01") + "-01"
s.db.Model(&model.DoctorIncome{}).
Where("doctor_id = ? AND created_at >= ?", doctorID, startOfMonth).
Select("COALESCE(SUM(amount), 0)").Scan(&monthIncome)
// 本月问诊量
s.db.Model(&model.DoctorIncome{}).
Where("doctor_id = ? AND created_at >= ?", doctorID, startOfMonth).
Count(&monthConsults)
return map[string]interface{}{
"total_balance": totalBalance,
"month_income": monthIncome,
"month_consults": monthConsults,
}, nil
}
func (s *Service) GetDoctorIncomeRecords(ctx context.Context, doctorID, startDate, endDate string, page, pageSize int) (map[string]interface{}, error) {
var records []model.DoctorIncome
var total int64
query := s.db.Model(&model.DoctorIncome{}).Where("doctor_id = ?", doctorID)
if startDate != "" {
query = query.Where("created_at >= ?", startDate)
}
if endDate != "" {
query = query.Where("created_at <= ?", endDate+" 23:59:59")
}
query.Count(&total)
query.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records)
return map[string]interface{}{
"list": records,
"total": total,
"page": page,
}, nil
}
type MonthlyBill struct {
Month string `json:"month"`
TotalIncome int64 `json:"total_income"`
ConsultCount int64 `json:"consult_count"`
TextIncome int64 `json:"text_income"`
VideoIncome int64 `json:"video_income"`
PrescriptionIncome int64 `json:"prescription_income"`
}
func (s *Service) GetMonthlyBills(ctx context.Context, doctorID string) ([]MonthlyBill, error) {
var bills []MonthlyBill
// 获取最近6个月的账单
rows, err := s.db.Raw(`
SELECT
TO_CHAR(created_at, 'YYYY-MM') as month,
COALESCE(SUM(amount), 0) as total_income,
COUNT(*) as consult_count,
COALESCE(SUM(CASE WHEN income_type = 'text_consult' THEN amount ELSE 0 END), 0) as text_income,
COALESCE(SUM(CASE WHEN income_type = 'video_consult' THEN amount ELSE 0 END), 0) as video_income,
COALESCE(SUM(CASE WHEN income_type = 'prescription' THEN amount ELSE 0 END), 0) as prescription_income
FROM doctor_incomes
WHERE doctor_id = ? AND created_at >= NOW() - INTERVAL '6 months'
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
ORDER BY month DESC
`, doctorID).Rows()
if err != nil {
return nil, err
}
defer rows.Close()
for rows.Next() {
var bill MonthlyBill
rows.Scan(&bill.Month, &bill.TotalIncome, &bill.ConsultCount, &bill.TextIncome, &bill.VideoIncome, &bill.PrescriptionIncome)
bills = append(bills, bill)
}
return bills, nil
}
func (s *Service) RequestWithdraw(ctx context.Context, doctorID string, amount int64, bankAccount, bankName string) (*model.DoctorWithdrawal, error) {
// 检查可提现余额
var balance int64
s.db.Model(&model.DoctorIncome{}).
Where("doctor_id = ? AND status = ?", doctorID, "settled").
Select("COALESCE(SUM(amount), 0)").Scan(&balance)
if amount > balance {
return nil, errors.New("提现金额超过可提现余额")
}
withdrawal := &model.DoctorWithdrawal{
ID: uuid.New().String(),
DoctorID: doctorID,
Amount: amount,
BankAccount: bankAccount,
BankName: bankName,
Status: "pending",
CreatedAt: time.Now(),
}
if err := s.db.Create(withdrawal).Error; err != nil {
return nil, err
}
// 更新收入状态为已提现
s.db.Model(&model.DoctorIncome{}).
Where("doctor_id = ? AND status = ?", doctorID, "settled").
Update("status", "withdrawn")
return withdrawal, nil
}
func (s *Service) GetWithdrawalRecords(ctx context.Context, doctorID string, page, pageSize int) (map[string]interface{}, error) {
var records []model.DoctorWithdrawal
var total int64
query := s.db.Model(&model.DoctorWithdrawal{}).Where("doctor_id = ?", doctorID)
query.Count(&total)
query.Order("created_at DESC").Offset((page - 1) * pageSize).Limit(pageSize).Find(&records)
return map[string]interface{}{
"list": records,
"total": total,
"page": page,
}, nil
}
// ========== 辅助函数 ==========
func generateOrderNo() string {
return fmt.Sprintf("ORD%s%s", time.Now().Format("20060102150405"), uuid.New().String()[:8])
}
func timePtr(t time.Time) *time.Time {
return &t
}
package workflowsvc
import (
"encoding/json"
"net/http"
"internet-hospital/internal/model"
"internet-hospital/pkg/database"
"internet-hospital/pkg/workflow"
"github.com/gin-gonic/gin"
)
type Handler struct{}
func NewHandler() *Handler { return &Handler{} }
func (h *Handler) RegisterRoutes(r gin.IRouter) {
g := r.Group("/workflow")
g.POST("/:workflow_id/execute", h.Execute)
g.GET("/execution/:id", h.GetExecution)
g.GET("/tasks", h.ListTasks)
g.POST("/task/:id/complete", h.CompleteTask)
}
func (h *Handler) Execute(c *gin.Context) {
workflowID := c.Param("workflow_id")
var input map[string]interface{}
c.ShouldBindJSON(&input)
userID, _ := c.Get("user_id")
uid, _ := userID.(string)
execID, err := workflow.GetEngine().Execute(c.Request.Context(), workflowID, input, uid)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"data": gin.H{"execution_id": execID}})
}
func (h *Handler) GetExecution(c *gin.Context) {
var exec model.WorkflowExecution
if err := database.GetDB().Where("execution_id = ?", c.Param("id")).First(&exec).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": exec})
}
func (h *Handler) ListTasks(c *gin.Context) {
var tasks []model.WorkflowHumanTask
database.GetDB().Where("status = 'pending'").Find(&tasks)
c.JSON(http.StatusOK, gin.H{"data": tasks})
}
func (h *Handler) CompleteTask(c *gin.Context) {
var req struct {
Result map[string]interface{} `json:"result"`
}
c.ShouldBindJSON(&req)
resultJSON, _ := json.Marshal(req.Result)
database.GetDB().Model(&model.WorkflowHumanTask{}).
Where("task_id = ?", c.Param("id")).
Updates(map[string]interface{}{"status": "completed", "result": string(resultJSON)})
c.JSON(http.StatusOK, gin.H{"message": "ok"})
}
-- 智能化升级数据库迁移脚本
-- Phase 1-5: Tool调用、Agent框架、工作流编排、知识库RAG、前端升级
-- ============================================
-- 1. 药物相互作用表
-- ============================================
CREATE TABLE IF NOT EXISTS drug_interactions (
id SERIAL PRIMARY KEY,
drug_a VARCHAR(200) NOT NULL,
drug_b VARCHAR(200) NOT NULL,
interaction_type VARCHAR(50), -- 协同增效、拮抗、血药浓度升高、代谢抑制等
severity VARCHAR(20), -- mild, moderate, severe
description TEXT,
suggestion TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_drug_interactions_drug_a ON drug_interactions(drug_a);
CREATE INDEX IF NOT EXISTS idx_drug_interactions_drug_b ON drug_interactions(drug_b);
-- 插入常见药物相互作用数据
INSERT INTO drug_interactions (drug_a, drug_b, interaction_type, severity, description, suggestion) VALUES
('华法林', '阿司匹林', '协同增效', 'severe', '两药合用增加出血风险', '避免同时使用,如必须使用需密切监测凝血功能'),
('华法林', '维生素K', '拮抗', 'moderate', '维生素K可降低华法林抗凝效果', '避免大量摄入富含维生素K的食物'),
('地高辛', '胺碘酮', '血药浓度升高', 'severe', '胺碘酮可使地高辛血药浓度升高50%', '地高辛剂量减半,监测血药浓度'),
('氨氯地平', '辛伐他汀', '代谢抑制', 'moderate', '氨氯地平可增加辛伐他汀血药浓度', '辛伐他汀日剂量不超过20mg'),
('甲硝唑', '酒精', '双硫仑样反应', 'severe', '可引起面部潮红、心悸、恶心等', '用药期间及停药后3天内禁酒'),
('二甲双胍', '碘造影剂', '乳酸酸中毒', 'severe', '可能增加乳酸酸中毒风险', '造影前48小时停用,造影后48小时复查肾功能后恢复')
ON CONFLICT DO NOTHING;
-- ============================================
-- 2. 为药品表添加禁忌信息字段
-- ============================================
ALTER TABLE medicines ADD COLUMN IF NOT EXISTS contraindications TEXT;
ALTER TABLE medicines ADD COLUMN IF NOT EXISTS allergy_info TEXT;
ALTER TABLE medicines ADD COLUMN IF NOT EXISTS pregnancy_category VARCHAR(10);
ALTER TABLE medicines ADD COLUMN IF NOT EXISTS age_restrictions TEXT;
ALTER TABLE medicines ADD COLUMN IF NOT EXISTS standard_dose TEXT;
ALTER TABLE medicines ADD COLUMN IF NOT EXISTS max_single_dose DECIMAL(10,2);
ALTER TABLE medicines ADD COLUMN IF NOT EXISTS max_daily_dose DECIMAL(10,2);
ALTER TABLE medicines ADD COLUMN IF NOT EXISTS dose_unit VARCHAR(20);
ALTER TABLE medicines ADD COLUMN IF NOT EXISTS dose_per_kg DECIMAL(10,4);
ALTER TABLE medicines ADD COLUMN IF NOT EXISTS renal_adjustment TEXT;
-- ============================================
-- 3. 为患者表添加过敏史和病史字段
-- ============================================
ALTER TABLE patients ADD COLUMN IF NOT EXISTS allergies TEXT;
ALTER TABLE patients ADD COLUMN IF NOT EXISTS medical_history TEXT;
-- ============================================
-- 4. 知识库向量支持(需要先安装pgvector扩展)
-- ============================================
-- CREATE EXTENSION IF NOT EXISTS vector;
-- 为知识分块表添加向量字段
ALTER TABLE knowledge_chunks ADD COLUMN IF NOT EXISTS embedding vector(1024);
-- 创建向量索引(IVFFlat索引,适合中等规模数据)
-- CREATE INDEX IF NOT EXISTS idx_chunks_embedding ON knowledge_chunks USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-- ============================================
-- 5. 随访管理表
-- ============================================
CREATE TABLE IF NOT EXISTS follow_up_plans (
id SERIAL PRIMARY KEY,
plan_id VARCHAR(100) UNIQUE NOT NULL,
patient_id UUID NOT NULL,
consultation_id UUID,
prescription_id UUID,
follow_up_type VARCHAR(50), -- medication, checkup, chronic
frequency VARCHAR(50), -- daily, weekly, monthly
next_follow_up_at TIMESTAMP,
status VARCHAR(20) DEFAULT 'active', -- active, completed, cancelled
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_follow_up_plans_patient ON follow_up_plans(patient_id);
CREATE INDEX IF NOT EXISTS idx_follow_up_plans_status ON follow_up_plans(status);
CREATE TABLE IF NOT EXISTS follow_up_records (
id SERIAL PRIMARY KEY,
record_id VARCHAR(100) UNIQUE NOT NULL,
plan_id VARCHAR(100) NOT NULL,
patient_id UUID NOT NULL,
follow_up_type VARCHAR(50),
content JSONB, -- 随访内容(症状、指标等)
ai_analysis TEXT, -- AI分析结果
need_revisit BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_follow_up_records_plan ON follow_up_records(plan_id);
CREATE INDEX IF NOT EXISTS idx_follow_up_records_patient ON follow_up_records(patient_id);
-- ============================================
-- 6. Agent执行日志增强
-- ============================================
ALTER TABLE agent_execution_logs ADD COLUMN IF NOT EXISTS tool_calls_count INT DEFAULT 0;
ALTER TABLE agent_execution_logs ADD COLUMN IF NOT EXISTS context JSONB;
-- ============================================
-- 7. 工作流定义增强
-- ============================================
ALTER TABLE workflow_definitions ADD COLUMN IF NOT EXISTS trigger_config JSONB;
ALTER TABLE workflow_definitions ADD COLUMN IF NOT EXISTS variables_schema JSONB;
-- ============================================
-- 8. 创建工具调用日志表(如果不存在)
-- ============================================
CREATE TABLE IF NOT EXISTS tool_call_logs (
id SERIAL PRIMARY KEY,
call_id VARCHAR(100) UNIQUE NOT NULL,
session_id VARCHAR(100),
agent_id VARCHAR(100),
tool_name VARCHAR(100) NOT NULL,
arguments JSONB,
result JSONB,
success BOOLEAN,
duration_ms INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_tool_call_logs_session ON tool_call_logs(session_id);
CREATE INDEX IF NOT EXISTS idx_tool_call_logs_tool ON tool_call_logs(tool_name);
-- ============================================
-- 完成
-- ============================================
package agent
import (
"context"
"encoding/json"
"fmt"
)
// Executor 工具执行器
type Executor struct {
registry *ToolRegistry
}
func NewExecutor(r *ToolRegistry) *Executor {
return &Executor{registry: r}
}
// Execute 执行工具调用
func (e *Executor) Execute(ctx context.Context, name string, argsJSON string) ToolResult {
tool, ok := e.registry.Get(name)
if !ok {
return ToolResult{Success: false, Error: fmt.Sprintf("tool not found: %s", name)}
}
var params map[string]interface{}
if err := json.Unmarshal([]byte(argsJSON), &params); err != nil {
return ToolResult{Success: false, Error: fmt.Sprintf("invalid params: %v", err)}
}
data, err := tool.Execute(ctx, params)
if err != nil {
return ToolResult{Success: false, Error: err.Error()}
}
return ToolResult{Success: true, Data: data}
}
package agent
import (
"context"
"encoding/json"
"fmt"
"internet-hospital/pkg/ai"
)
// AgentInput Agent输入
type AgentInput struct {
SessionID string `json:"session_id"`
UserID string `json:"user_id"`
Message string `json:"message"`
Context map[string]interface{} `json:"context"`
History []ai.ChatMessage `json:"history"`
MaxIterations int `json:"max_iterations"`
}
// AgentOutput Agent输出
type AgentOutput struct {
Response string `json:"response"`
ToolCalls []ToolCallResult `json:"tool_calls,omitempty"`
Iterations int `json:"iterations"`
FinishReason string `json:"finish_reason"`
TotalTokens int `json:"total_tokens"`
}
// ToolCallResult 工具调用结果记录
type ToolCallResult struct {
ToolName string `json:"tool_name"`
CallID string `json:"call_id"`
Arguments string `json:"arguments"`
Result interface{} `json:"result"`
Success bool `json:"success"`
}
// AgentChunk 流式输出块
type AgentChunk struct {
Type string `json:"type"` // text | tool_call | tool_result | done
Content interface{} `json:"content"`
}
// ReActConfig ReAct Agent配置
type ReActConfig struct {
ID string
Name string
Description string
SystemPrompt string
Tools []string
MaxIterations int
}
// ReActAgent ReAct模式Agent
type ReActAgent struct {
cfg ReActConfig
registry *ToolRegistry
executor *Executor
}
func NewReActAgent(cfg ReActConfig) *ReActAgent {
r := GetRegistry()
return &ReActAgent{
cfg: cfg,
registry: r,
executor: NewExecutor(r),
}
}
func (a *ReActAgent) ID() string { return a.cfg.ID }
func (a *ReActAgent) Name() string { return a.cfg.Name }
func (a *ReActAgent) Description() string { return a.cfg.Description }
// Run 执行Agent(非流式)
func (a *ReActAgent) Run(ctx context.Context, input AgentInput) (*AgentOutput, error) {
client := ai.GetClient()
if client == nil {
return &AgentOutput{
Response: "AI服务未配置,请在管理端配置API Key",
FinishReason: "no_client",
}, nil
}
maxIter := a.cfg.MaxIterations
if input.MaxIterations > 0 {
maxIter = input.MaxIterations
}
if maxIter <= 0 {
maxIter = 10
}
messages := []ai.ChatMessage{
{Role: "system", Content: a.buildSystemPrompt(input.Context)},
}
messages = append(messages, input.History...)
messages = append(messages, ai.ChatMessage{Role: "user", Content: input.Message})
tools := a.getToolSchemas()
var toolCallResults []ToolCallResult
totalTokens := 0
for i := 0; i < maxIter; i++ {
resp, err := client.ChatWithTools(ctx, messages, tools)
if err != nil {
return nil, fmt.Errorf("AI调用失败: %w", err)
}
totalTokens += resp.Usage.TotalTokens
choice := resp.Choices[0]
if choice.FinishReason == "stop" || len(choice.Message.ToolCalls) == 0 {
return &AgentOutput{
Response: choice.Message.Content,
ToolCalls: toolCallResults,
Iterations: i + 1,
FinishReason: "completed",
TotalTokens: totalTokens,
}, nil
}
// 执行工具调用
assistantMsg := ai.ChatMessage{
Role: "assistant",
Content: choice.Message.Content,
ToolCalls: choice.Message.ToolCalls,
}
messages = append(messages, assistantMsg)
for _, tc := range choice.Message.ToolCalls {
result := a.executor.Execute(ctx, tc.Function.Name, tc.Function.Arguments)
resultJSON, _ := json.Marshal(result)
toolCallResults = append(toolCallResults, ToolCallResult{
ToolName: tc.Function.Name,
CallID: tc.ID,
Arguments: tc.Function.Arguments,
Result: result,
Success: result.Success,
})
messages = append(messages, ai.ChatMessage{
Role: "tool",
ToolCallID: tc.ID,
Content: string(resultJSON),
})
}
}
return &AgentOutput{
Response: "已达到最大迭代次数",
ToolCalls: toolCallResults,
Iterations: maxIter,
FinishReason: "max_iterations",
TotalTokens: totalTokens,
}, nil
}
// RunStream 流式执行Agent
func (a *ReActAgent) RunStream(ctx context.Context, input AgentInput, onChunk func(AgentChunk) error) (*AgentOutput, error) {
// 简化:先用非流式,后续可升级为真正的流式
output, err := a.Run(ctx, input)
if err != nil {
return nil, err
}
// 模拟流式输出
for _, tc := range output.ToolCalls {
if err := onChunk(AgentChunk{Type: "tool_call", Content: tc}); err != nil {
return output, err
}
}
if err := onChunk(AgentChunk{Type: "text", Content: output.Response}); err != nil {
return output, err
}
if err := onChunk(AgentChunk{Type: "done", Content: output.FinishReason}); err != nil {
return output, err
}
return output, nil
}
func (a *ReActAgent) buildSystemPrompt(ctx map[string]interface{}) string {
prompt := a.cfg.SystemPrompt
if ctx != nil {
if patientID, ok := ctx["patient_id"].(string); ok {
prompt += fmt.Sprintf("\n\n当前患者ID: %s", patientID)
}
}
return prompt
}
func (a *ReActAgent) getToolSchemas() []interface{} {
schemas := a.registry.GetSchemas(a.cfg.Tools)
result := make([]interface{}, len(schemas))
for i, s := range schemas {
result[i] = s
}
return result
}
package agent
import "sync"
// ToolRegistry 工具注册中心
type ToolRegistry struct {
tools map[string]Tool
mu sync.RWMutex
}
var globalRegistry = &ToolRegistry{tools: make(map[string]Tool)}
func GetRegistry() *ToolRegistry { return globalRegistry }
func (r *ToolRegistry) Register(tool Tool) {
r.mu.Lock()
defer r.mu.Unlock()
r.tools[tool.Name()] = tool
}
func (r *ToolRegistry) Get(name string) (Tool, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
t, ok := r.tools[name]
return t, ok
}
func (r *ToolRegistry) GetSchemas(names []string) []ToolSchema {
r.mu.RLock()
defer r.mu.RUnlock()
var schemas []ToolSchema
for _, name := range names {
if t, ok := r.tools[name]; ok {
schemas = append(schemas, ToSchema(t))
}
}
return schemas
}
func (r *ToolRegistry) All() map[string]Tool {
r.mu.RLock()
defer r.mu.RUnlock()
result := make(map[string]Tool, len(r.tools))
for k, v := range r.tools {
result[k] = v
}
return result
}
package agent
import "context"
// Tool 工具接口
type Tool interface {
Name() string
Description() string
Parameters() []ToolParameter
Execute(ctx context.Context, params map[string]interface{}) (interface{}, error)
}
// ToolParameter 工具参数定义
type ToolParameter struct {
Name string `json:"name"`
Type string `json:"type"` // string, number, boolean, array, object
Description string `json:"description"`
Required bool `json:"required"`
Enum []string `json:"enum,omitempty"`
}
// ToolResult 工具执行结果
type ToolResult struct {
Success bool `json:"success"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
}
// ToolSchema OpenAI Function Calling 格式
type ToolSchema struct {
Type string `json:"type"` // "function"
Function FunctionSchema `json:"function"`
}
type FunctionSchema struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters ParameterSchema `json:"parameters"`
}
type ParameterSchema struct {
Type string `json:"type"` // "object"
Properties map[string]PropertySchema `json:"properties"`
Required []string `json:"required"`
}
type PropertySchema struct {
Type string `json:"type"`
Description string `json:"description"`
Enum []string `json:"enum,omitempty"`
}
// ToSchema 将 Tool 转换为 OpenAI Function Calling 格式
func ToSchema(t Tool) ToolSchema {
props := make(map[string]PropertySchema)
var required []string
for _, p := range t.Parameters() {
props[p.Name] = PropertySchema{
Type: p.Type,
Description: p.Description,
Enum: p.Enum,
}
if p.Required {
required = append(required, p.Name)
}
}
return ToolSchema{
Type: "function",
Function: FunctionSchema{
Name: t.Name(),
Description: t.Description(),
Parameters: ParameterSchema{
Type: "object",
Properties: props,
Required: required,
},
},
}
}
This diff is collapsed.
package tools
import (
"context"
"internet-hospital/pkg/agent"
)
// DepartmentRecommendTool 科室推荐工具
type DepartmentRecommendTool struct{}
func (t *DepartmentRecommendTool) Name() string { return "recommend_department" }
func (t *DepartmentRecommendTool) Description() string { return "根据症状推荐合适的就诊科室" }
func (t *DepartmentRecommendTool) Parameters() []agent.ToolParameter {
return []agent.ToolParameter{
{Name: "symptoms", Type: "string", Description: "患者症状描述", Required: true},
{Name: "severity", Type: "string", Description: "严重程度: mild/moderate/severe", Required: false,
Enum: []string{"mild", "moderate", "severe"}},
}
}
func (t *DepartmentRecommendTool) Execute(ctx context.Context, params map[string]interface{}) (interface{}, error) {
symptoms, _ := params["symptoms"].(string)
severity, _ := params["severity"].(string)
rules := []struct {
keywords []string
dept string
reason string
}{
{[]string{"心", "胸痛", "心悸", "心慌"}, "心内科", "心脏相关症状"},
{[]string{"头痛", "头晕", "麻木", "抽搐"}, "神经内科", "神经系统相关症状"},
{[]string{"咳嗽", "气喘", "呼吸", "肺"}, "呼吸内科", "呼吸系统相关症状"},
{[]string{"腹痛", "腹泻", "恶心", "呕吐", "胃"}, "消化内科", "消化系统相关症状"},
{[]string{"关节", "骨", "腰痛", "颈椎"}, "骨科", "骨骼关节相关症状"},
{[]string{"皮疹", "瘙痒", "皮肤"}, "皮肤科", "皮肤相关症状"},
}
for _, rule := range rules {
for _, kw := range rule.keywords {
if containsStr(symptoms, kw) {
result := map[string]interface{}{
"recommended_department": rule.dept,
"reason": rule.reason,
"urgency": "normal",
}
if severity == "severe" {
result["urgency"] = "urgent"
result["note"] = "症状较重,建议尽快就诊"
}
return result, nil
}
}
}
return map[string]interface{}{
"recommended_department": "全科/内科",
"reason": "症状不明确,建议先到全科就诊",
"urgency": "normal",
}, nil
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment