153 lines
5.9 KiB
Go
153 lines
5.9 KiB
Go
package squad
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// StepEvaluator 负责单步执行产物评估与自适应反馈调整
|
|
type StepEvaluator struct{}
|
|
|
|
// NewStepEvaluator 创建单步评估器
|
|
func NewStepEvaluator() *StepEvaluator {
|
|
return &StepEvaluator{}
|
|
}
|
|
|
|
// EvaluateStepOutput 评估单步产物与黑板事实,推导基准假设与动态重排指令
|
|
func (e *StepEvaluator) EvaluateStepOutput(
|
|
wsDir string,
|
|
step StepSpec,
|
|
outputContent string,
|
|
bb *BlackboardState,
|
|
) (*EvaluationResult, error) {
|
|
now := time.Now()
|
|
res := &EvaluationResult{
|
|
StepID: step.ID,
|
|
Action: StepActionContinue,
|
|
Reason: "步骤按计划正常推进",
|
|
NewAssumptions: make([]AssumptionItem, 0),
|
|
DynamicStepsToAdd: make([]StepSpec, 0),
|
|
DynamicStepsToAlter: make([]StepSpec, 0),
|
|
StepsToSkip: make([]string, 0),
|
|
}
|
|
|
|
contentLower := strings.ToLower(outputContent)
|
|
|
|
// 1. 基于黑板缺口与产物推导“基准假设 (Baseline Assumptions)”
|
|
var gapTexts []string
|
|
if bb != nil {
|
|
for _, g := range bb.Gaps {
|
|
gapTexts = append(gapTexts, strings.ToLower(g.Item))
|
|
}
|
|
}
|
|
combinedLower := contentLower + " " + strings.Join(gapTexts, " ")
|
|
|
|
// 规则 1.1: 灭菌方式缺口推断
|
|
if strings.Contains(combinedLower, "未明确灭菌") || strings.Contains(combinedLower, "未提供灭菌") ||
|
|
strings.Contains(combinedLower, "未确定灭菌") || strings.Contains(combinedLower, "缺少灭菌方式") ||
|
|
strings.Contains(combinedLower, "未提供产品灭菌方式") {
|
|
if !hasAssumptionTopic(bb, "sterilization_method") {
|
|
res.NewAssumptions = append(res.NewAssumptions, AssumptionItem{
|
|
ID: fmt.Sprintf("assump_sterilization_%d", now.UnixNano()),
|
|
Topic: "sterilization_method",
|
|
Statement: "基准假设灭菌方式采用环氧乙烷(EO)灭菌,解析期设定为14天",
|
|
Rationale: "客户未明确灭菌方式,依据二类无源无菌高分子介入器械行业通用工程实践设定",
|
|
Source: "inferred",
|
|
})
|
|
}
|
|
}
|
|
|
|
// 规则 1.2: 货架有效期缺口推断
|
|
if strings.Contains(combinedLower, "未明确货架寿命") || strings.Contains(combinedLower, "有效期未定") ||
|
|
strings.Contains(combinedLower, "未提供有效期") {
|
|
if !hasAssumptionTopic(bb, "shelf_life") {
|
|
res.NewAssumptions = append(res.NewAssumptions, AssumptionItem{
|
|
ID: fmt.Sprintf("assump_shelflife_%d", now.UnixNano()),
|
|
Topic: "shelf_life",
|
|
Statement: "基准假设产品货架有效期设定为2年,需执行加速老化试验",
|
|
Rationale: "符合常规无菌医疗器械首次注册基准设计周期",
|
|
Source: "inferred",
|
|
})
|
|
}
|
|
}
|
|
|
|
// 规则 1.3: 注册目标市场缺口推断
|
|
if strings.Contains(combinedLower, "未明确目标市场") || strings.Contains(combinedLower, "市场未定") {
|
|
if !hasAssumptionTopic(bb, "target_market") {
|
|
res.NewAssumptions = append(res.NewAssumptions, AssumptionItem{
|
|
ID: fmt.Sprintf("assump_market_%d", now.UnixNano()),
|
|
Topic: "target_market",
|
|
Statement: "基准假设首发上市目标为中国境内NMPA二类医疗器械注册",
|
|
Rationale: "以国内优先注册为基准,后续拓展海外认证",
|
|
Source: "inferred",
|
|
})
|
|
}
|
|
}
|
|
|
|
// 2. 检查是否触发动态跳步 (Skip Step)
|
|
if strings.Contains(contentLower, "无需开展重复公开资料检索") ||
|
|
strings.Contains(contentLower, "无需重复开展市场竞品检索") ||
|
|
strings.Contains(contentLower, "竞品数据已全面锁定") ||
|
|
strings.Contains(contentLower, "跳过市场调研") {
|
|
if res.Action == StepActionContinue {
|
|
res.Action = StepActionSkipStep
|
|
}
|
|
res.StepsToSkip = append(res.StepsToSkip, "step_02_market", "step_02", "market")
|
|
res.Reason = "前序分析已充分锁定竞品数据,避免重复冗余检索"
|
|
}
|
|
|
|
// 3. 检查是否触发动态加步 (Add Step)
|
|
// 射频/微波 EMC 与热安全风险
|
|
if (strings.Contains(contentLower, "射频") || strings.Contains(contentLower, "微波") || strings.Contains(contentLower, "消融")) &&
|
|
(strings.Contains(contentLower, "电磁兼容") || strings.Contains(contentLower, "emc") || strings.Contains(contentLower, "热灼伤")) {
|
|
res.Action = StepActionAddStep
|
|
res.DynamicStepsToAdd = append(res.DynamicStepsToAdd, StepSpec{
|
|
ID: "step_rf_emc_safety",
|
|
Role: "engineer",
|
|
Title: "高频电磁兼容性(EMC)与热安全防护专项工程论证",
|
|
SubWorkspace: "tasks/dynamic_rf_emc",
|
|
Mode: "execute",
|
|
AllowDynamicAdjustment: false,
|
|
Accept: []string{
|
|
"核算高频消融电磁辐射泄漏安全边际与抗干扰设计",
|
|
"设计温度反馈闭环与防止意外热灼伤切断机制",
|
|
},
|
|
})
|
|
res.Reason = "识别到大功率射频高频风险,动态注入EMC与热防护专项工程论证工序"
|
|
}
|
|
|
|
// 动物源病毒灭活专题
|
|
if (strings.Contains(contentLower, "牛心包") || strings.Contains(contentLower, "动物源")) &&
|
|
(strings.Contains(contentLower, "病毒灭活") || strings.Contains(contentLower, "免疫原性")) {
|
|
res.Action = StepActionAddStep
|
|
res.DynamicStepsToAdd = append(res.DynamicStepsToAdd, StepSpec{
|
|
ID: "step_animal_virus_inactivation",
|
|
Role: "regulatory",
|
|
Title: "动物源性材料病毒灭活与免疫原性合规专题论证",
|
|
SubWorkspace: "tasks/dynamic_virus_inactivation",
|
|
Mode: "execute",
|
|
AllowDynamicAdjustment: false,
|
|
Accept: []string{
|
|
"核查动物源溯源合规证明与供体检疫要求",
|
|
"拟定基于ISO 22442的病毒灭活有效性验证方案",
|
|
},
|
|
})
|
|
res.Reason = "识别到动物源性材料生物风险,动态追加病毒灭活合规论证工序"
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func hasAssumptionTopic(bb *BlackboardState, topic string) bool {
|
|
if bb == nil {
|
|
return false
|
|
}
|
|
for _, a := range bb.Assumptions {
|
|
if a.Topic == topic {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|