Version: 0.7.2.dev.260322
feat(schedule-plan): ✨ 重构智能排程链路并修复粗排双节对齐问题 - ✨ 新增“对话级排程状态持久化”能力:引入 `agent_schedule_states` 模型/DAO,并接入启动迁移 - ✨ 智能排程图升级:补齐小幅微调(quick refine)分支,完善预算/并发/状态字段流转 - ✨ 预览链路增强:完善排程预览服务读写与桥接逻辑,新增本地预览页 `infra/schedule_preview_viewer.html` - ♻️ 缓存治理统一:将相关缓存处理收口到 DAO + `cache_deleter` 联动清理,移除旧散落逻辑 - 🐛 修复粗排核心 bug:禁止单节降级,强制双节并按 `1-2/3-4/...` 对齐;修复结束日扫描边界问题 - ✅ 新增粗排回归测试:覆盖孤立单节、偶数起点双节、Filler 对齐等关键场景
This commit is contained in:
@@ -18,6 +18,8 @@ const (
|
||||
schedulePlanGraphNodeExit = "schedule_plan_exit"
|
||||
// 图节点:按天拆分并注入上下文标签
|
||||
schedulePlanGraphNodeDailySplit = "schedule_plan_daily_split"
|
||||
// 图节点:小改动快速微调(用于 small scope)
|
||||
schedulePlanGraphNodeQuickRefine = "schedule_plan_quick_refine"
|
||||
// 图节点:并发日内优化
|
||||
schedulePlanGraphNodeDailyRefine = "schedule_plan_daily_refine"
|
||||
// 图节点:合并日内优化结果
|
||||
@@ -120,6 +122,9 @@ func RunSchedulePlanGraph(ctx context.Context, input SchedulePlanGraphRunInput)
|
||||
if err := graph.AddLambdaNode(schedulePlanGraphNodeDailySplit, compose.InvokableLambda(runner.dailySplitNode)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := graph.AddLambdaNode(schedulePlanGraphNodeQuickRefine, compose.InvokableLambda(runner.quickRefineNode)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := graph.AddLambdaNode(schedulePlanGraphNodeDailyRefine, compose.InvokableLambda(runner.dailyRefineNode)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -157,6 +162,7 @@ func RunSchedulePlanGraph(ctx context.Context, input SchedulePlanGraphRunInput)
|
||||
runner.nextAfterRoughBuild,
|
||||
map[string]bool{
|
||||
schedulePlanGraphNodeDailySplit: true,
|
||||
schedulePlanGraphNodeQuickRefine: true,
|
||||
schedulePlanGraphNodeWeeklyRefine: true,
|
||||
schedulePlanGraphNodeExit: true,
|
||||
},
|
||||
@@ -164,7 +170,10 @@ func RunSchedulePlanGraph(ctx context.Context, input SchedulePlanGraphRunInput)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 7. 固定边:dailySplit -> dailyRefine -> merge -> weeklyRefine -> finalCheck -> returnPreview -> END
|
||||
// 7. 固定边:quickRefine -> weeklyRefine;dailySplit -> dailyRefine -> merge -> weeklyRefine -> finalCheck -> returnPreview -> END
|
||||
if err := graph.AddEdge(schedulePlanGraphNodeQuickRefine, schedulePlanGraphNodeWeeklyRefine); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := graph.AddEdge(schedulePlanGraphNodeDailySplit, schedulePlanGraphNodeDailyRefine); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -24,12 +24,16 @@ import (
|
||||
// 3.1 推荐:task_item_id(例如 "12");
|
||||
// 3.2 兼容:任务名称(例如 "高数复习")。
|
||||
type schedulePlanIntentOutput struct {
|
||||
Intent string `json:"intent"`
|
||||
Constraints []string `json:"constraints"`
|
||||
TaskClassIDs []int `json:"task_class_ids"`
|
||||
TaskClassID int `json:"task_class_id"`
|
||||
Strategy string `json:"strategy"`
|
||||
TaskTags map[string]string `json:"task_tags"`
|
||||
Intent string `json:"intent"`
|
||||
Constraints []string `json:"constraints"`
|
||||
TaskClassIDs []int `json:"task_class_ids"`
|
||||
TaskClassID int `json:"task_class_id"`
|
||||
Strategy string `json:"strategy"`
|
||||
TaskTags map[string]string `json:"task_tags"`
|
||||
Restart bool `json:"restart"`
|
||||
AdjustmentScope string `json:"adjustment_scope"`
|
||||
Reason string `json:"reason"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
}
|
||||
|
||||
// runPlanNode 负责“识别排程意图 + 提取约束 + 收敛任务类 ID”。
|
||||
@@ -50,6 +54,10 @@ func runPlanNode(
|
||||
if st == nil {
|
||||
return nil, errors.New("schedule plan graph: nil state in plan node")
|
||||
}
|
||||
st.RestartRequested = false
|
||||
st.AdjustmentReason = ""
|
||||
st.AdjustmentConfidence = 0
|
||||
st.AdjustmentScope = schedulePlanAdjustmentScopeLarge
|
||||
|
||||
emitStage("schedule_plan.plan.analyzing", "正在分析你的排程需求。")
|
||||
|
||||
@@ -80,11 +88,13 @@ func runPlanNode(
|
||||
// 2.2 探测失败不影响主链路,只是少一个 prompt hint。
|
||||
if st.HasPreviousPreview && len(st.PreviousHybridEntries) > 0 {
|
||||
st.IsAdjustment = true
|
||||
st.AdjustmentScope = schedulePlanAdjustmentScopeMedium
|
||||
}
|
||||
previousPlan := extractPreviousPlanFromHistory(chatHistory)
|
||||
if previousPlan != "" {
|
||||
st.PreviousPlanJSON = previousPlan
|
||||
st.IsAdjustment = true
|
||||
st.AdjustmentScope = schedulePlanAdjustmentScopeMedium
|
||||
}
|
||||
|
||||
// 3. 组装模型提示词。
|
||||
@@ -135,6 +145,25 @@ func runPlanNode(
|
||||
if strings.EqualFold(strings.TrimSpace(parsed.Strategy), "rapid") {
|
||||
st.Strategy = "rapid"
|
||||
}
|
||||
st.RestartRequested = parsed.Restart
|
||||
st.AdjustmentScope = normalizeAdjustmentScope(parsed.AdjustmentScope)
|
||||
st.AdjustmentReason = strings.TrimSpace(parsed.Reason)
|
||||
st.AdjustmentConfidence = clampAdjustmentConfidence(parsed.Confidence)
|
||||
|
||||
// 5.1 分级语义兜底:
|
||||
// 5.1.1 非微调请求不走 small/medium,强制按 large 进入完整排程;
|
||||
// 5.1.2 微调请求默认至少走 medium,避免 scope 缺失时误判;
|
||||
// 5.1.3 restart=true 时强制重排并清空历史快照承接。
|
||||
if !st.IsAdjustment {
|
||||
st.AdjustmentScope = schedulePlanAdjustmentScopeLarge
|
||||
} else if st.AdjustmentScope == "" {
|
||||
st.AdjustmentScope = schedulePlanAdjustmentScopeMedium
|
||||
}
|
||||
if st.RestartRequested {
|
||||
st.IsAdjustment = false
|
||||
st.AdjustmentScope = schedulePlanAdjustmentScopeLarge
|
||||
st.clearPreviousPreviewContext()
|
||||
}
|
||||
|
||||
// 6. 合并任务类 ID(新字段 + 旧字段双兼容)。
|
||||
// 6.1 先拼接已有值与模型输出;
|
||||
@@ -172,7 +201,13 @@ func runPlanNode(
|
||||
|
||||
emitStage(
|
||||
"schedule_plan.plan.done",
|
||||
fmt.Sprintf("已识别排程意图,任务类数量=%d。", len(st.TaskClassIDs)),
|
||||
fmt.Sprintf(
|
||||
"已识别排程意图,任务类数量=%d,微调=%t,力度=%s,重排=%t。",
|
||||
len(st.TaskClassIDs),
|
||||
st.IsAdjustment,
|
||||
st.AdjustmentScope,
|
||||
st.RestartRequested,
|
||||
),
|
||||
)
|
||||
return st, nil
|
||||
}
|
||||
@@ -234,12 +269,16 @@ func runRoughBuildNode(
|
||||
// 2.2 失败兜底:若快照不完整(例如 AllocatedItems 为空),会构造最小占位任务块,保持下游校验可运行;
|
||||
// 2.3 回退策略:若没有可复用快照,再走全量粗排构建路径。
|
||||
canReusePreviousPlan := st.IsAdjustment &&
|
||||
!st.RestartRequested &&
|
||||
len(st.PreviousHybridEntries) > 0 &&
|
||||
sameTaskClassSet(taskClassIDs, st.PreviousTaskClassIDs)
|
||||
if canReusePreviousPlan {
|
||||
emitStage("schedule_plan.rough_build.reuse_previous", "检测到连续对话微调,复用上一版排程作为优化起点。")
|
||||
st.HybridEntries = deepCopyEntries(st.PreviousHybridEntries)
|
||||
st.CandidatePlans = hybridEntriesToWeekSchedules(st.HybridEntries)
|
||||
st.CandidatePlans = deepCopyWeekSchedules(st.PreviousCandidatePlans)
|
||||
if len(st.CandidatePlans) == 0 {
|
||||
st.CandidatePlans = hybridEntriesToWeekSchedules(st.HybridEntries)
|
||||
}
|
||||
st.AllocatedItems = deepCopyTaskClassItems(st.PreviousAllocatedItems)
|
||||
if len(st.AllocatedItems) == 0 {
|
||||
st.AllocatedItems = buildAllocatedItemsFromHybridEntries(st.HybridEntries)
|
||||
@@ -601,6 +640,51 @@ func normalizeTaskClassIDs(ids []int) []int {
|
||||
return out
|
||||
}
|
||||
|
||||
// clearPreviousPreviewContext 清空会话承接快照字段。
|
||||
//
|
||||
// 触发场景:
|
||||
// 1. 用户明确要求 restart(重新排);
|
||||
// 2. 需要强制断开“沿用历史方案”的路径,避免脏状态渗透到新方案。
|
||||
func (st *SchedulePlanState) clearPreviousPreviewContext() {
|
||||
if st == nil {
|
||||
return
|
||||
}
|
||||
st.HasPreviousPreview = false
|
||||
st.PreviousTaskClassIDs = nil
|
||||
st.PreviousHybridEntries = nil
|
||||
st.PreviousAllocatedItems = nil
|
||||
st.PreviousCandidatePlans = nil
|
||||
st.PreviousPlanJSON = ""
|
||||
}
|
||||
|
||||
// clampAdjustmentConfidence 约束置信度字段到 [0,1]。
|
||||
func clampAdjustmentConfidence(v float64) float64 {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
if v > 1 {
|
||||
return 1
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// deepCopyWeekSchedules 深拷贝周视图方案切片,避免跨节点共享引用。
|
||||
func deepCopyWeekSchedules(src []model.UserWeekSchedule) []model.UserWeekSchedule {
|
||||
if len(src) == 0 {
|
||||
return nil
|
||||
}
|
||||
dst := make([]model.UserWeekSchedule, 0, len(src))
|
||||
for _, week := range src {
|
||||
eventsCopy := make([]model.WeeklyEventBrief, len(week.Events))
|
||||
copy(eventsCopy, week.Events)
|
||||
dst = append(dst, model.UserWeekSchedule{
|
||||
Week: week.Week,
|
||||
Events: eventsCopy,
|
||||
})
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// sameTaskClassSet 判断两组 task_class_ids 是否表示同一集合(忽略顺序,忽略重复)。
|
||||
//
|
||||
// 语义:
|
||||
|
||||
@@ -11,7 +11,8 @@ const (
|
||||
// 输出约束:
|
||||
// 1. 必须只输出 JSON,禁止附加解释文本;
|
||||
// 2. task_class_ids 是主语义;
|
||||
// 3. task_class_id 仅作为兼容字段保留,便于老链路平滑过渡。
|
||||
// 3. task_class_id 仅作为兼容字段保留,便于老链路平滑过渡;
|
||||
// 4. 需要额外给出 restart + adjustment_scope,用于图分流。
|
||||
SchedulePlanIntentPrompt = `你是 SmartFlow 的排程意图分析器。
|
||||
请根据用户输入,提取排程意图与约束条件。
|
||||
|
||||
@@ -26,6 +27,14 @@ const (
|
||||
- 兼容键:任务名称(例如 "高数复习")
|
||||
- 值只能是:High-Logic / Memory / Review / General
|
||||
- 如果无法判断,输出空对象 {}
|
||||
7) 判定本轮是否要求“强制重排” restart:
|
||||
- 用户明确表达“重新排/推倒重来/忽略之前方案/全部重来”时,restart=true;
|
||||
- 否则 restart=false。
|
||||
8) 判定微调力度 adjustment_scope(small / medium / large):
|
||||
- small:局部微调,通常只改少量时段,不需要重建全局。
|
||||
- medium:中等调整,需要周级再平衡,但不必全量重粗排。
|
||||
- large:大范围调整,或首次创建排程,或约束变化很大,需要完整重排。
|
||||
9) 输出 reason(简短中文理由,<=30字)与 confidence(0~1)。
|
||||
|
||||
输出要求:
|
||||
- 仅输出 JSON,不要 markdown,不要解释。
|
||||
@@ -36,7 +45,11 @@ const (
|
||||
"task_class_ids": [12, 13],
|
||||
"task_class_id": 12,
|
||||
"strategy": "steady",
|
||||
"task_tags": {"12":"High-Logic","英语阅读":"Memory"}
|
||||
"task_tags": {"12":"High-Logic","英语阅读":"Memory"},
|
||||
"restart": false,
|
||||
"adjustment_scope": "medium",
|
||||
"reason": "本次只调整局部时段",
|
||||
"confidence": 0.86
|
||||
}`
|
||||
|
||||
// SchedulePlanDailyReactPrompt 用于 daily_refine 节点。
|
||||
|
||||
77
backend/agent/scheduleplan/quick_refine.go
Normal file
77
backend/agent/scheduleplan/quick_refine.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package scheduleplan
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// runQuickRefineNode 是 small 微调分支的“轻量预算收缩节点”。
|
||||
//
|
||||
// 职责边界:
|
||||
// 1. 负责在进入 weekly_refine 前收缩预算与并发,避免小改动走重链路;
|
||||
// 2. 负责保留“可回退”的最低预算,避免直接压成 0 导致无动作可执行;
|
||||
// 3. 不负责执行任何 Move/Swap(真正动作仍由 weekly_refine 完成)。
|
||||
func runQuickRefineNode(
|
||||
ctx context.Context,
|
||||
st *SchedulePlanState,
|
||||
emitStage func(stage, detail string),
|
||||
) (*SchedulePlanState, error) {
|
||||
_ = ctx
|
||||
if st == nil {
|
||||
return nil, fmt.Errorf("schedule plan quick refine: nil state")
|
||||
}
|
||||
|
||||
emitStage("schedule_plan.quick_refine.start", "检测到小幅微调,正在切换到快速优化路径。")
|
||||
|
||||
// 1. 预算收缩策略:
|
||||
// 1.1 small 场景目标是“快速响应 + 可解释改动”,不追求大规模重排;
|
||||
// 1.2 因此把总预算压到最多 2 次尝试、有效预算压到最多 1 次成功动作;
|
||||
// 1.3 如果上游已配置更小预算,则尊重更小值,不做反向放大。
|
||||
if st.WeeklyTotalBudget <= 0 {
|
||||
st.WeeklyTotalBudget = schedulePlanDefaultWeeklyTotalBudget
|
||||
}
|
||||
if st.WeeklyAdjustBudget <= 0 {
|
||||
st.WeeklyAdjustBudget = schedulePlanDefaultWeeklyAdjustBudget
|
||||
}
|
||||
st.WeeklyTotalBudget = clampBudgetUpper(st.WeeklyTotalBudget, 2)
|
||||
st.WeeklyAdjustBudget = clampBudgetUpper(st.WeeklyAdjustBudget, 1)
|
||||
|
||||
// 2. 预算一致性兜底:
|
||||
// 2.1 总预算至少为 1(否则 weekly worker 无法执行);
|
||||
// 2.2 有效预算至少为 1(否则所有成功动作都不被允许);
|
||||
// 2.3 有效预算永远不能超过总预算。
|
||||
if st.WeeklyTotalBudget < 1 {
|
||||
st.WeeklyTotalBudget = 1
|
||||
}
|
||||
if st.WeeklyAdjustBudget < 1 {
|
||||
st.WeeklyAdjustBudget = 1
|
||||
}
|
||||
if st.WeeklyAdjustBudget > st.WeeklyTotalBudget {
|
||||
st.WeeklyAdjustBudget = st.WeeklyTotalBudget
|
||||
}
|
||||
|
||||
// 3. 小改动路径把周级并发收敛到 1,优先保证稳定与可观察性。
|
||||
st.WeeklyRefineConcurrency = 1
|
||||
|
||||
emitStage(
|
||||
"schedule_plan.quick_refine.done",
|
||||
fmt.Sprintf(
|
||||
"快速微调预算已生效:总预算=%d,有效预算=%d,并发=%d。",
|
||||
st.WeeklyTotalBudget,
|
||||
st.WeeklyAdjustBudget,
|
||||
st.WeeklyRefineConcurrency,
|
||||
),
|
||||
)
|
||||
return st, nil
|
||||
}
|
||||
|
||||
// clampBudgetUpper 把预算裁剪到“非负且不超过上限”。
|
||||
func clampBudgetUpper(current int, upper int) int {
|
||||
if current < 0 {
|
||||
return 0
|
||||
}
|
||||
if current > upper {
|
||||
return upper
|
||||
}
|
||||
return current
|
||||
}
|
||||
@@ -67,6 +67,10 @@ func (r *schedulePlanRunner) dailySplitNode(ctx context.Context, st *SchedulePla
|
||||
return runDailySplitNode(ctx, st, r.emitStage)
|
||||
}
|
||||
|
||||
func (r *schedulePlanRunner) quickRefineNode(ctx context.Context, st *SchedulePlanState) (*SchedulePlanState, error) {
|
||||
return runQuickRefineNode(ctx, st, r.emitStage)
|
||||
}
|
||||
|
||||
func (r *schedulePlanRunner) dailyRefineNode(ctx context.Context, st *SchedulePlanState) (*SchedulePlanState, error) {
|
||||
return runDailyRefineNode(ctx, st, r.chatModel, r.dailyRefineConcurrency, r.emitStage)
|
||||
}
|
||||
@@ -107,6 +111,16 @@ func (r *schedulePlanRunner) nextAfterRoughBuild(_ context.Context, st *Schedule
|
||||
if st == nil || len(st.HybridEntries) == 0 {
|
||||
return schedulePlanGraphNodeExit, nil
|
||||
}
|
||||
|
||||
// 1. 连续微调且判定为 small:先走快速微调节点,收缩预算后再进 weekly。
|
||||
if st.IsAdjustment && st.AdjustmentScope == schedulePlanAdjustmentScopeSmall {
|
||||
return schedulePlanGraphNodeQuickRefine, nil
|
||||
}
|
||||
// 2. 连续微调且判定为 medium:直接走 weekly,跳过 daily。
|
||||
if st.IsAdjustment && st.AdjustmentScope == schedulePlanAdjustmentScopeMedium {
|
||||
return schedulePlanGraphNodeWeeklyRefine, nil
|
||||
}
|
||||
// 3. large 或非微调:保持原有逻辑,多任务类走 daily,单任务类直达 weekly。
|
||||
if len(st.TaskClassIDs) >= 2 {
|
||||
return schedulePlanGraphNodeDailySplit, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package scheduleplan
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/LoveLosita/smartflow/backend/model"
|
||||
@@ -38,6 +39,16 @@ const (
|
||||
// 1. 周级输入规模通常比单天更大,默认并发度不宜过高,避免触发模型侧限流;
|
||||
// 2. 可在运行时按请求状态覆盖。
|
||||
schedulePlanDefaultWeeklyRefineConcurrency = 2
|
||||
|
||||
// schedulePlanAdjustmentScopeSmall 表示“小改动微调”。
|
||||
// 语义:优先走快速路径,只做轻量周级调整。
|
||||
schedulePlanAdjustmentScopeSmall = "small"
|
||||
// schedulePlanAdjustmentScopeMedium 表示“中等改动微调”。
|
||||
// 语义:跳过日内拆分,直接进入周级配平。
|
||||
schedulePlanAdjustmentScopeMedium = "medium"
|
||||
// schedulePlanAdjustmentScopeLarge 表示“大改动重排”。
|
||||
// 语义:必要时重新走全量路径(日内并发 + 周级配平)。
|
||||
schedulePlanAdjustmentScopeLarge = "large"
|
||||
)
|
||||
|
||||
// DayGroup 是“按天拆分后”的最小优化单元。
|
||||
@@ -168,6 +179,23 @@ type SchedulePlanState struct {
|
||||
PreviousPlanJSON string
|
||||
// IsAdjustment 标记本次是否为微调请求(而非全新排程)。
|
||||
IsAdjustment bool
|
||||
// RestartRequested 标记本轮是否要求“放弃历史快照并重新排程”。
|
||||
//
|
||||
// 语义:
|
||||
// 1. true:强制清空 Previous* 并走全新构建;
|
||||
// 2. false:允许按同会话历史快照做增量微调。
|
||||
RestartRequested bool
|
||||
// AdjustmentScope 表示本轮改动力度分级(small/medium/large)。
|
||||
//
|
||||
// 分流语义:
|
||||
// 1. small:走快速微调节点,再进入周级优化;
|
||||
// 2. medium:跳过 daily,直接周级优化;
|
||||
// 3. large:优先走全量路径(多任务类时会经过 daily 并发)。
|
||||
AdjustmentScope string
|
||||
// AdjustmentReason 是模型给出的力度判定理由,用于日志排障与 review。
|
||||
AdjustmentReason string
|
||||
// AdjustmentConfidence 是模型给出的力度判定置信度(0-1)。
|
||||
AdjustmentConfidence float64
|
||||
// HasPreviousPreview 标记是否命中“同会话上一次排程预览快照”。
|
||||
//
|
||||
// 语义:
|
||||
@@ -192,6 +220,12 @@ type SchedulePlanState struct {
|
||||
// 1. 保持 final_check 的数量核对口径稳定;
|
||||
// 2. return_preview 阶段可继续回填 embedded_time。
|
||||
PreviousAllocatedItems []model.TaskClassItem
|
||||
// PreviousCandidatePlans 是上一版预览保存的周视图结构化结果。
|
||||
//
|
||||
// 用途:
|
||||
// 1. 连续微调时可直接复用,避免重复转换;
|
||||
// 2. 兜底展示层(即使本轮未走全量粗排,仍可给前端稳定结构)。
|
||||
PreviousCandidatePlans []model.UserWeekSchedule
|
||||
|
||||
// ── 最终输出 ──
|
||||
|
||||
@@ -215,6 +249,7 @@ func NewSchedulePlanState(traceID string, userID int, conversationID string) *Sc
|
||||
TaskTagHintsByName: make(map[string]string),
|
||||
DailyRefineConcurrency: schedulePlanDefaultDailyRefineConcurrency,
|
||||
WeeklyRefineConcurrency: schedulePlanDefaultWeeklyRefineConcurrency,
|
||||
AdjustmentScope: schedulePlanAdjustmentScopeLarge,
|
||||
ReactMaxRound: 2,
|
||||
WeeklyAdjustBudget: schedulePlanDefaultWeeklyAdjustBudget,
|
||||
WeeklyTotalBudget: schedulePlanDefaultWeeklyTotalBudget,
|
||||
@@ -234,3 +269,19 @@ func schedulePlanLocation() *time.Location {
|
||||
func schedulePlanNowToMinute() time.Time {
|
||||
return time.Now().In(schedulePlanLocation()).Truncate(time.Minute)
|
||||
}
|
||||
|
||||
// normalizeAdjustmentScope 归一化排程微调力度字段。
|
||||
//
|
||||
// 兜底策略:
|
||||
// 1. 只接受 small/medium/large;
|
||||
// 2. 任何未知值都回退为 large,保证不会误走“过轻”路径。
|
||||
func normalizeAdjustmentScope(raw string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(raw)) {
|
||||
case schedulePlanAdjustmentScopeSmall:
|
||||
return schedulePlanAdjustmentScopeSmall
|
||||
case schedulePlanAdjustmentScopeMedium:
|
||||
return schedulePlanAdjustmentScopeMedium
|
||||
default:
|
||||
return schedulePlanAdjustmentScopeLarge
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user