Files
smartmate/backend/model/schedule.go
Losita f3f9902e93 Version: 0.7.1.dev.260321
feat(agent):  重构智能排程分流与双通道交付,补齐周级预算并接入连续微调复用

- 🔀 通用路由升级为 action 分流(chat/quick_note_create/task_query/schedule_plan),路由失败直接返回内部错误,不再回落聊天
- 🧭 智能排程链路重构:统一图编排与节点职责,完善日级/周级调优协作与提示词约束
- 📊 周级预算改为“有效周保底 + 负载加权分配”,避免有效周零预算并提升资源利用率
- ⚙️ 日级并发优化细化:按天拆分 DayGroup 并发执行,低收益天(suggested<=2)跳过,单天失败仅回退该天结果并继续全局
- 🧵 周级并发优化细化:按周并发 worker 执行,单周“单步动作”循环(每轮仅 1 个 Move/Swap 或 done),失败周保留原方案不影响其它周
- 🛰️ 新增排程预览双通道:聊天主链路输出终审文本,结构化 candidate_plans 通过 /api/v1/agent/schedule-preview 拉取
- 🗃️ 增补 Redis 预览缓存读写与清理逻辑,新增对应 API、路由、模型与错误码支持
- ♻️ 接入连续对话微调复用:命中同会话历史预览时复用上轮 HybridEntries,避免每轮重跑粗排
- 🛡️ 增加复用保护:仅当本轮与上轮 task_class_ids 集合一致才复用;不一致回退全量粗排
- 🧰 扩展预览缓存字段(task_class_ids/hybrid_entries/allocated_items),支撑微调承接链路
- 🗺️ 更新 README 5.4 Mermaid(总分流图 + 智能排程流转图)并补充决策文档

- ⚠️ 新增“连续微调复用”链路我尚未完成测试,且文档状态目前较为混乱,待连续对话微调功能真正测试完成后再统一更新
2026-03-21 22:08:35 +08:00

174 lines
8.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package model
import "time"
type ScheduleEvent struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
UserID int `gorm:"column:user_id;index:idx_user_events;not null" json:"user_id"`
Name string `gorm:"column:name;type:varchar(255);not null;comment:课程或任务名称" json:"name"`
Location *string `gorm:"column:location;type:varchar(255);default:'';comment:地点 (教学楼/会议室)" json:"location"`
Type string `gorm:"column:type;type:enum('course','task');not null;comment:日程类型" json:"type"`
RelID *int `gorm:"column:rel_id;comment:关联原始数据ID (如教务系统的课程ID)" json:"rel_id"`
CanBeEmbedded bool `gorm:"column:can_be_embedded;not null;default:0;comment:是否允许在此时段嵌入其他任务" json:"can_be_embedded"`
StartTime time.Time `gorm:"column:start_time;type:time;comment:开始时间" json:"start_time"`
EndTime time.Time `gorm:"column:end_time;type:time;comment:结束时间" json:"end_time"`
}
type Schedule struct {
ID int `gorm:"primaryKey;autoIncrement" json:"id"`
EventID int `gorm:"column:event_id;index:idx_event_id;not null;comment:关联元数据ID" json:"event_id"`
UserID int `gorm:"column:user_id;uniqueIndex:idx_user_slot_atomic,priority:1;not null;comment:冗余UID方便直接查询" json:"user_id"`
Week int `gorm:"column:week;uniqueIndex:idx_user_slot_atomic,priority:2;not null;comment:周次 (1-25)" json:"week"`
DayOfWeek int `gorm:"column:day_of_week;uniqueIndex:idx_user_slot_atomic,priority:3;not null;comment:星期 (1-7)" json:"day_of_week"`
Section int `gorm:"column:section;uniqueIndex:idx_user_slot_atomic,priority:4;not null;comment:原子化节次 (1-12)" json:"section"`
EmbeddedTaskID *int `gorm:"column:embedded_task_id;comment:若为水课嵌入记录具体的任务项ID" json:"embedded_task_id"`
Status string `gorm:"column:status;type:enum('normal','interrupted');default:'normal';comment:状态: 正常/因故中断" json:"status"`
// 💡 必须加上这一行,告诉 GORM 如何关联元数据
Event *ScheduleEvent `gorm:"foreignKey:EventID" json:"event"`
EmbeddedTask *TaskClassItem `gorm:"foreignKey:EmbeddedTaskID" json:"embedded_task"`
}
type ScheduleConflictDetail struct {
EventID int `json:"event_id"`
Name string `json:"name"`
Location string `json:"location"`
DayOfWeek int `json:"day_of_week"`
Week int `json:"week"`
Sections []int `json:"sections"`
StartSection int `json:"start_section"`
EndSection int `json:"end_section"`
Type string `json:"type"`
EmbeddedTasks []ScheduleEmbeddedTask `json:"embedded_tasks"`
}
type ScheduleEmbeddedTask struct {
Section int `json:"section"`
TaskID int `json:"task_id"`
}
type UserTodaySchedule struct {
DayOfWeek int `json:"day_of_week"`
Week int `json:"week"`
Events []EventBrief `json:"events"`
}
type EventBrief struct {
ID int `json:"id"` // 这个 ID 是 ScheduleEvent 的 ID不是 Schedule 的 ID
Order int `json:"order"` // order 用于区分它们的显示顺序
Name string `json:"name"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
Location string `json:"location"`
Type string `json:"type"`
Span int `json:"span"` // 跨越的节数,给前端用来渲染宽度/高度
EmbeddedTaskInfo TaskBrief `json:"embedded_task_info,omitempty"`
}
type TaskBrief struct {
ID int `json:"id"` // 这个 ID 是 ScheduleEvent 的 ID不是 Schedule 的 ID
Name string `json:"name"`
/*StartTime string `json:"start_time"`
EndTime string `json:"end_time"`*/
Type string `json:"type"`
}
type UserWeekSchedule struct {
Week int `json:"week"`
Events []WeeklyEventBrief `json:"events"`
}
type WeeklyEventBrief struct {
ID int `json:"id"` // 这个 ID 是 ScheduleEvent 的 ID不是 Schedule 的 ID
Order int `json:"order"` // order 用于区分它们在一天中的显示顺序
DayOfWeek int `json:"day_of_week"`
Name string `json:"name"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
Location string `json:"location"`
Type string `json:"type"`
Span int `json:"span"` // 跨越的节数,给前端用来渲染宽度/高度
Status string `json:"status"`
EmbeddedTaskInfo TaskBrief `json:"embedded_task_info,omitempty"`
}
type UserDeleteScheduleEvent struct {
ID int `json:"id"` // 这个 ID 是 ScheduleEvent 的 ID不是 Schedule 的 ID
DeleteCourse bool `json:"delete_course"`
DeleteEmbeddedTask bool `json:"delete_embedded_task"`
}
// UserSmartPlanningMultiRequest 是“多任务类智能粗排”接口的请求体。
//
// 设计说明:
// 1. TaskClassIDs 至少包含 1 个任务类 ID
// 2. 实际业务建议传入 >=2 个,用于多任务类混排;
// 3. 服务层会做去重与合法值过滤,接口层只做基础绑定校验。
type UserSmartPlanningMultiRequest struct {
TaskClassIDs []int `json:"task_class_ids" binding:"required,min=1,dive,min=1"`
}
type UserRecentCompletedScheduleResponse struct {
Events []RecentCompletedEventBrief `json:"events"`
}
type RecentCompletedEventBrief struct {
ID int `json:"id"` //如果是嵌入的任务事件这个ID是TaskClassItem的ID如果是课程事件这个ID是ScheduleEvent的ID
Name string `json:"name"`
Type string `json:"type"`
CompletedTime string `json:"completed_time"`
}
type OngoingSchedule struct {
ID int `json:"id"` // 这个 ID 是 ScheduleEvent 的 ID不是 Schedule 的 ID
Name string `json:"name"`
Location string `json:"location"`
Type string `json:"type"`
TimeStatus string `json:"time_status"` // "upcoming", "ongoing"
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
}
// HybridScheduleEntry 表示"混合日程"中的一个时间块。
//
// 设计目标:
// 将既有日程(课程/已落库任务)与粗排建议的任务统一到同一结构中,
// 供 ReAct 精排引擎在内存中操作。
//
// Status 语义:
// - "existing"已确定的日程LLM 不可移动;
// - "suggested"粗排建议的任务LLM 可通过 Tool 调整时间。
type HybridScheduleEntry struct {
Week int `json:"week"`
DayOfWeek int `json:"day_of_week"`
SectionFrom int `json:"section_from"`
SectionTo int `json:"section_to"`
Name string `json:"name"`
Type string `json:"type"` // "course" | "task"
Status string `json:"status"` // "existing" | "suggested"
TaskItemID int `json:"task_item_id,omitempty"` // 仅 suggested 的 task 有值
EventID int `json:"event_id,omitempty"` // 仅 existing 有值
// CanBeEmbedded 表示该条 existing 课程块是否允许嵌入任务。
// 仅课程条目有意义task 条目默认 false。
CanBeEmbedded bool `json:"can_be_embedded,omitempty"`
// BlockForSuggested 表示该条目是否应当阻塞 suggested 任务占位。
//
// 语义说明:
// 1. suggested 条目默认 true任务之间不能重叠
// 2. existing 课程若是“可嵌入且当前格子未被嵌入任务占用”,则为 false
// 3. existing 课程若不可嵌入,或该格子已有嵌入任务,则为 true。
//
// 该字段用于工具层冲突判断,避免把“可嵌入课位”误判为硬冲突。
BlockForSuggested bool `json:"block_for_suggested,omitempty"`
// ContextTag 是任务认知类型标签,仅在 suggested 任务中使用。
// 该标签用于日内优化时的“认知负荷分配”,例如:
// 1. High-Logic数学、编程、逻辑推理
// 2. Memory记忆/背诵类;
// 3. Review复习/回顾类;
// 4. General通用任务。
ContextTag string `json:"context_tag,omitempty"`
}
func (ScheduleEvent) TableName() string { return "schedule_events" }
func (Schedule) TableName() string { return "schedules" }